← Public packages
@kentcdodds/codemod-runner
Run codemods over your own saved packages: scan, dry-run with real publish checks and diffs, apply, and revert.
src/candidates.ts
80 lines · 2.3 KB · TypeScriptimport { kody, packageContext } from 'kody:runtime'
import { type CodemodRef } from './contract.ts'
export type CandidatePackage = {
packageId: string
kodyId: string
}
export type StepPaging = {
cursor: string | null
limit: number
}
/**
* Lists the caller's own packages as codemod candidates, sorted by package
* id with binary comparison so paging cursors are stable. The runner itself
* and the codemod package are always excluded.
*/
export async function listCandidates(input: {
codemod: CodemodRef | null
packageIds?: Array<string>
paging: StepPaging
}): Promise<{ candidates: Array<CandidatePackage>; nextCursor: string | null }> {
if (input.packageIds != null) {
const usable = input.packageIds.filter(
(id) => typeof id === 'string' && id.trim().length > 0,
)
if (usable.length === 0) {
throw new Error(
'packageIds was provided but contains no usable ids. Omit it to run over all packages.',
)
}
}
const result = (await kody.packageList({})) as {
packages: Array<{ package_id: string; kody_id: string }>
}
const selfPackageId = (packageContext as { packageId?: string } | null)
?.packageId
const filterSet =
input.packageIds != null ? new Set(input.packageIds) : null
const all = result.packages
.filter((pkg) => pkg.package_id !== selfPackageId)
.filter((pkg) => pkg.kody_id !== input.codemod?.kodyId)
.filter(
(pkg) =>
filterSet === null ||
filterSet.has(pkg.package_id) ||
filterSet.has(pkg.kody_id),
)
.map((pkg) => ({ packageId: pkg.package_id, kodyId: pkg.kody_id }))
.sort((left, right) =>
left.packageId < right.packageId
? -1
: left.packageId > right.packageId
? 1
: 0,
)
const afterCursor =
input.paging.cursor == null
? all
: all.filter((pkg) => pkg.packageId > (input.paging.cursor as string))
const page = afterCursor.slice(0, input.paging.limit)
const nextCursor =
afterCursor.length > page.length && page.length > 0
? page[page.length - 1]!.packageId
: null
return { candidates: page, nextCursor }
}
export function resolveLimit(input: {
requested?: number
fallback: number
max: number
}): number {
const requested = input.requested ?? input.fallback
if (!Number.isInteger(requested) || requested < 1) {
return input.fallback
}
return Math.min(requested, input.max)
}