← 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/revert.ts
158 lines · 4.2 KB · TypeScriptimport {
createRun,
getRun,
getSnapshot,
markSourceItemReverted,
resumeRun,
saveRun,
summarizeItems,
type RunItem,
} from './ledger.ts'
import { revertItem } from './process-package.ts'
import { shouldStopBeforeNextPackage } from './time-budget.ts'
export type RevertInput = {
/** The prior `apply` run id whose applied packages should be restored. */
revertOfRunId?: string
runId?: string
cursor?: string | null
limit?: number
}
/**
* Restores packages applied by a prior `apply` run to their pre-codemod
* trees from the ledger snapshots. Skips packages republished since the
* apply (drift) rather than overwriting newer work. Items that fail or are
* skipped stay `applied`; start another revert of the same apply run to
* retry them. Paged: keep calling with the returned runId + cursor until
* nextCursor is null.
*/
export default async function revert(input: RevertInput = {}) {
if (!input.revertOfRunId) {
throw new Error('Pass revertOfRunId: the apply run id to revert.')
}
if (input.cursor != null && input.runId == null) {
throw new Error('cursor requires runId to continue an existing run.')
}
const sourceRun = await getRun(input.revertOfRunId)
if (!sourceRun) {
throw new Error(`Run "${input.revertOfRunId}" was not found.`)
}
if (sourceRun.mode !== 'apply') {
throw new Error(
`Run "${input.revertOfRunId}" is a ${sourceRun.mode} run; only apply runs can be reverted.`,
)
}
const run = input.runId
? await resumeRun({
runId: input.runId,
mode: 'revert',
codemod: null,
revertOfRunId: input.revertOfRunId,
})
: await createRun({
mode: 'revert',
codemod: sourceRun.codemod,
revertOfRunId: input.revertOfRunId,
})
const limit = Math.min(Math.max(input.limit ?? 1, 1), 2)
const cursor = input.cursor ?? run.cursor
const appliedItems = sourceRun.items
.filter((item) => item.status === 'applied')
.sort((left, right) =>
left.packageId < right.packageId
? -1
: left.packageId > right.packageId
? 1
: 0,
)
.filter((item) => cursor == null || item.packageId > cursor)
const startedAt = Date.now()
const items: Array<RunItem> = []
let processed = 0
let stoppedEarlyAt: string | null = null
let lastPackageMs = 0
for (const sourceItem of appliedItems) {
if (processed >= limit) {
stoppedEarlyAt = items[items.length - 1]?.packageId ?? cursor ?? null
break
}
if (
shouldStopBeforeNextPackage({
elapsedMs: Date.now() - startedAt,
completedCount: items.length,
lastPackageMs,
})
) {
stoppedEarlyAt = items[items.length - 1]!.packageId
break
}
const packageStarted = Date.now()
const snapshot = await getSnapshot({
runId: sourceRun.id,
packageId: sourceItem.packageId,
})
if (!snapshot) {
const failed = {
...sourceItem,
status: 'failed' as const,
afterCommit: null,
diff: null,
error: 'No revert snapshot found for this package in the apply run.',
}
items.push(failed)
run.items = [...run.items, failed]
run.cursor = sourceItem.packageId
run.status = 'running'
await saveRun(run)
lastPackageMs = Date.now() - packageStarted
processed += 1
continue
}
const item = await revertItem({
revertRunId: run.id,
sourceRunId: sourceRun.id,
sourceCodemod: sourceRun.codemod,
item: sourceItem,
snapshot,
})
if (item.status === 'reverted') {
await markSourceItemReverted({
runId: sourceRun.id,
packageId: sourceItem.packageId,
})
}
items.push(item)
run.items = [...run.items, item]
run.cursor = sourceItem.packageId
run.status = 'running'
await saveRun(run)
lastPackageMs = Date.now() - packageStarted
processed += 1
}
const hasMore =
stoppedEarlyAt != null ||
appliedItems.length > processed
const resolvedNextCursor = hasMore
? (stoppedEarlyAt ?? items[items.length - 1]?.packageId ?? cursor)
: null
run.cursor = resolvedNextCursor
run.status = resolvedNextCursor == null ? 'completed' : 'running'
await saveRun(run)
return {
runId: run.id,
mode: 'revert' as const,
revertOfRunId: input.revertOfRunId,
items,
nextCursor: resolvedNextCursor,
summary: summarizeItems(items),
note:
resolvedNextCursor == null
? null
: `Revert is not finished. Call again with runId "${run.id}" and cursor "${resolvedNextCursor}".`,
}
}