@kentcdodds/ codemod-runner
Run codemods over your own saved packages: scan, dry-run with real publish checks and diffs, apply, and revert.
- License
- MIT
- Published
- July 30, 2026
- Pinned commit
37effd0- Rating
- No ratings yet
- Forks
- 0
- Stars
- 0
- Adaptation effort
- —
One-click install
Install forks this package into your account and publishes it right away when it passes the standard package checks. This listing has not been reviewed by an admin.
Log in to install this package.
Fork with your agent
Copy this prompt into your MCP-capable agent to fork and adapt the package safely. Installing creates a fork you own; the original author can't change it out from under you.
Use Kody to fork the community package "@kentcdodds/codemod-runner" (listing id: b06c0f98-865a-4379-adb0-d0fb2cdda14f). Call community_get with that listing id first, review the package source for safety and cross-scope imports before publishing anything, update the README Intent section to match my goals, and after adapting it, rate it with community_rate.
README
codemod-runner
Run codemods over your own saved Kody packages with the same safety story as a real migration system: scan, dry-run with real publish checks and diffs, apply with revert snapshots, and revert.
Intent
Give every Kody user a safe, repeatable way to apply their own bulk source transforms ("codemods") across their own saved packages — rename an export everywhere, migrate off a deprecated helper, rewrite import paths — without hand-editing each package and without risking work they cannot undo. The runner composes only public Kody capabilities (repo sessions, checks, publish, package storage), so it is also a reference for building migration-grade tooling in userland.
How it works
A codemod is a package you own that exposes an export (default name
codemod) whose default export is one function:
import {
type CodemodInput,
type CodemodOutput,
} from 'kody:@kentcdodds/codemod-runner/contract'
export default function codemod(input: CodemodInput): CodemodOutput {
if (input.operation === 'detect') {
// Pure scan: which files need this codemod?
return Object.entries(input.files)
.filter(([, content]) => content.includes('oldHelper('))
.map(([path]) => ({ path, message: 'Uses oldHelper().' }))
}
// Pure transform: full file tree in, full file tree out.
const files = { ...input.files }
const changedPaths: Array<string> = []
for (const [path, content] of Object.entries(files)) {
const next = content.replaceAll('oldHelper(', 'newHelper(')
if (next !== content) {
files[path] = next
changedPaths.push(path)
}
}
return { files, changed: changedPaths.length > 0, changedPaths, needsManual: [] }
}Transforms must be pure, deterministic, and idempotent (transforming
their own output must report changed: false — the runner verifies this
mechanically). When a file cannot be transformed confidently, leave it
untouched and report a needsManual finding instead of guessing.
The runner processes each target package in a throwaway repo session: it never touches your active editing sessions, and it skips packages whose repo HEAD differs from the published commit (in-flight work) instead of overwriting them.
Usage
Every export is paged: keep calling with the returned runId + cursor
until nextCursor is null. Results are recorded in a run ledger you can
read back with runs.
import scan from 'kody:@kentcdodds/codemod-runner/scan'
import dryRun from 'kody:@kentcdodds/codemod-runner/dry-run'
import apply from 'kody:@kentcdodds/codemod-runner/apply'
import revert from 'kody:@kentcdodds/codemod-runner/revert'
import runs from 'kody:@kentcdodds/codemod-runner/runs'
// 1. Which of my packages need it?
await scan({ codemod: { kodyId: 'my-codemod' } })
// 2. What exactly would change? (real publish checks + diffs, publishes nothing)
await dryRun({ codemod: { kodyId: 'my-codemod' } })
// 3. Apply (requires a completed dry-run for the same codemod first).
const result = await apply({ codemod: { kodyId: 'my-codemod' } })
// 4. Changed your mind? Restore the pre-codemod trees.
await revert({ revertOfRunId: result.runId })Optional inputs: packageIds (array of package ids or kody ids to narrow a
run — canary one package first), limit (packages per step; dry-run/apply
default 1, max 2, because each package runs the full check pipeline).
For sweeps over many packages, drive the paging loop from a durable
workflow (workflows.create) instead of chaining interactive calls.
Safety rails
- Dry-run first:
applyrefuses to start until a dry-run for the same codemod has completed. - Real checks: transformed trees must pass the same manifest,
dependency, bundle, typecheck, lint, and smoke checks as any publish.
Packages with failing checks are reported (
checks_failed), never published. - Idempotency verification: every changed package is transformed twice; non-idempotent codemods fail before anything is written.
- Drift skips: packages with unpublished repo work are skipped, and revert refuses to overwrite packages republished after the apply.
- Revert snapshots: apply stores each package's full pre-codemod tree in
this package's own storage;
revertrestores it. Reverted packages are marked in the apply run so re-reverts are no-ops. - Events: applies and reverts dispatch scoped events
(
@<your-scope>/codemod.applied/.reverted) your packages can subscribe to.
Statuses
detected, clean, dry_run_ok, checks_failed, needs_manual,
skipped_drift, skipped_unpublished, applied, reverted, failed.
Runs are single-pass: failed or skipped items are recorded but not retried within the run — start a new run to retry them.
Limitations
- UTF-8 text files only (like
package_save); completely empty files are invisible to codemods run through this runner. - File paths containing whitespace cannot be deleted by a transform.
- Packages with pre-existing check failures cannot be applied (fix them first — the publish pipeline enforces this, not just the runner).
- The runner excludes itself and the codemod package from every run.
Stars
0 stars
Log in to star this package.
Report this listing
Log in to report this listing.