Skip to content
← Community packages

@kentcdodds/codemod-runner

by @kentcdoddsFollow @kentcdodds

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: apply refuses 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; revert restores 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.