Codemods modify your source code automatically. Following these best practices ensures safe, reliable transformations.
CRITICAL: Always commit your changes before running codemods. Codemods change source code. Uncommitted changes may be lost or corrupted. This is not optional.
Pre-Flight Checklist
Before running any codemod:
Commit all changes
Ensure your working directory is clean with git statusgit status
# Should show: "nothing to commit, working tree clean"
Create a dedicated branch
Make changes on a separate branch for easy rollbackgit checkout -b migration/util-is-to-native
Backup important work
For critical migrations, create a backup branchgit branch backup/before-codemod
Review the codemod documentation
Understand what the codemod will changenpx codemod @nodejs/<recipe> --help
Running Codemods Safely
Start with a Dry Run
Many codemods support dry-run mode to preview changes:
# Preview without modifying files
npx codemod @nodejs/util-is --dry-run
Not all codemods support --dry-run. Check the codemod’s documentation or try running with --help.
Test on a Small Scope First
Before running on your entire codebase:
# Test on a single directory
cd src/utils
npx codemod @nodejs/util-is
# Review the changes
git diff
# If good, proceed with the rest
cd ../..
npx codemod @nodejs/util-is
After running a codemod:
# See what changed
git diff
# See which files were modified
git status
# Review changes file by file
git diff src/file1.js
git diff src/file2.js
Use a visual diff tool like git difftool or your IDE’s built-in diff viewer for easier review.
Validation Strategy
1. Run Your Test Suite
Your existing tests are the first line of defense:
# Run all tests
npm test
# Run specific test suites
npm test -- --grep "utility functions"
What to check:
- All tests pass
- No new warnings or errors
- Test coverage hasn’t decreased
- Performance hasn’t degraded
2. Check Linting and Type Checking
Ensure code quality standards are maintained:
# Run linter
npm run lint
# Run TypeScript type checking
npm run typecheck
# or
tsc --noEmit
Common issues after codemods:
- Unused imports (codemod removed usage but not import)
- Type errors (API signature changes)
- Formatting issues (indentation, spacing)
Many codemods try to preserve formatting, but some manual cleanup may be needed. Run your formatter:npm run format
# or
prettier --write .
3. Manual Code Review
Spot-check transformed code:
Focus areas:
- Edge cases: Look for unusual patterns the codemod might mishandle
- Complex expressions: Nested calls, ternaries, template literals
- Comments: Ensure comments still make sense after transformation
- Formatting: Check indentation and readability
Example review:
// Before codemod
const isValid = util.isArray(data) && data.length > 0;
// After codemod - verify logic is preserved
const isValid = Array.isArray(data) && data.length > 0; // ✅ Correct
// Watch for complex cases
const check = condition ? util.isArray : util.isObject;
// Did the codemod handle this correctly? ⚠️ Needs review
4. Integration Testing
If you have integration or e2e tests:
# Run integration tests
npm run test:integration
# Run e2e tests
npm run test:e2e
Integration tests catch issues that unit tests miss:
- API contract changes
- Behavior differences in edge cases
- Performance regressions
5. Build and Package
Ensure the project still builds:
# Build the project
npm run build
# Check the build output
ls -la dist/
# Try packaging (if applicable)
npm pack
Rollback Strategies
Quick Rollback with Git
If something goes wrong:
# Discard all changes (if not committed)
git checkout .
# Undo the last commit (if you committed)
git reset --hard HEAD~1
# Return to a specific commit
git reset --hard <commit-hash>
# Switch to your backup branch
git checkout backup/before-codemod
git reset --hard permanently deletes uncommitted changes. Use with caution.
Selective Rollback
Rollback specific files:
# Restore a single file
git checkout HEAD -- src/problematic-file.js
# Restore multiple files
git checkout HEAD -- src/file1.js src/file2.js
# Restore entire directory
git checkout HEAD -- src/utils/
Revert Merged Changes
If the codemod was already merged:
# Create a revert commit
git revert <merge-commit-hash>
# Push the revert
git push origin main
Advanced Safety Techniques
Incremental Migration
For large codebases, migrate incrementally:
Phase 1: Core utilities
Run codemod on your utility library firstnpx codemod @nodejs/util-is src/utils/
Phase 2: Individual modules
Migrate one module at a timenpx codemod @nodejs/util-is src/auth/
git add src/auth && git commit -m "migrate: auth module"
Phase 3: Test and iterate
Run tests after each phase, fix issues before proceeding
Phase 4: Remaining code
Complete migration across the codebase
Benefits:
- Easier to isolate issues
- Smaller, reviewable commits
- Less risky than “big bang” migration
- Can pause and resume migration
Automated Safety Checks
Add pre-commit hooks to catch issues:
// package.json
{
"scripts": {
"pre-commit": "npm run lint && npm test"
}
}
Or use tools like husky:
# .husky/pre-commit
npm run lint
npm test
Snapshot Testing
For critical code, create snapshots before migration:
// Save current behavior
const before = {
result1: myFunction(input1),
result2: myFunction(input2),
// ...
};
// After codemod, verify behavior is identical
const after = {
result1: myFunction(input1),
result2: myFunction(input2),
};
assert.deepEqual(before, after);
Testing Codemods Locally
If you’re developing or testing a codemod:
From Source
# Clone the repository
git clone https://github.com/nodejs/userland-migrations.git
cd userland-migrations
# Run codemod from local file
cd /path/to/your-project
npx codemod workflow run -w /path/to/userland-migrations/recipes/util-is/workflow.yaml
Test Fixtures
Create test cases before running on real code:
// test-fixture.js
const util = require('util');
// Test various patterns
const test1 = util.isArray([]);
const test2 = util.isString('hello');
const test3 = util.isFunction(() => {});
// Test edge cases
const test4 = condition ? util.isArray : util.isObject;
const test5 = items.filter(util.isArray);
Run the codemod on the fixture and verify results:
cp test-fixture.js test-fixture-backup.js
npx codemod @nodejs/util-is test-fixture.js
diff test-fixture.js test-fixture-backup.js
Common Issues and Solutions
Issue: Codemod Skipped Files
Symptom: Some files weren’t transformed
Cause: The codemod didn’t find matching patterns
Solution:
# Check if imports exist
grep -r "require('util')" src/
grep -r "from 'util'" src/
# Check file extensions
find src/ -name "*.js" -o -name "*.ts"
Issue: Syntax Errors After Codemod
Symptom: Code doesn’t parse or run
Cause: Codemod generated invalid syntax
Solution:
# Find syntax errors
npm run lint
# or
node --check src/**/*.js
# Review the problematic files
git diff src/broken-file.js
# Fix manually or rollback
git checkout src/broken-file.js
Issue: Tests Failing
Symptom: Previously passing tests now fail
Cause: Behavior change or edge case not handled
Solution:
- Review the failing test
- Check if the transformation was correct
- Update test if the change is intentional
- Fix the code if the transformation was wrong
# Run specific test
npm test -- --grep "the failing test"
# Debug with verbose output
DEBUG=* npm test
Issue: Merge Conflicts
Symptom: Git conflicts when merging migration branch
Cause: Main branch changed during migration
Solution:
# Update your migration branch
git checkout migration/util-is
git merge main
# Resolve conflicts
# Then re-run codemod on conflicted files if needed
npx codemod @nodejs/util-is src/conflicted-file.js
git add .
git commit -m "resolve: merge conflicts after codemod"
Team Coordination
When running codemods on shared codebases:
Communication
- Announce the migration in your team channel
- Freeze PRs temporarily to avoid conflicts
- Schedule migration during low-activity periods
- Document the process for team reference
Pull Request Best Practices
Good PR structure:
Title: migrate(util-is): replace util.is* with native checks
Description:
- Ran @nodejs/util-is codemod
- Reviewed all changes manually
- All tests passing
- No behavior changes expected
Files changed: 47 files
Breaking changes: None
Testing:
- ✅ Unit tests passing
- ✅ Integration tests passing
- ✅ Linter passing
- ✅ TypeScript types valid
Checklist:
- [x] Committed before running codemod
- [x] Reviewed all diffs
- [x] Ran test suite
- [x] Checked for edge cases
- [x] Updated documentation if needed
Commit strategy:
# Option 1: Single commit (small changes)
git add .
git commit -m "migrate(util-is): replace util.is* with native checks"
# Option 2: Separate commits (large changes)
git add src/utils/
git commit -m "migrate(util-is): transform utils module"
git add src/services/
git commit -m "migrate(util-is): transform services module"
git add .
git commit -m "migrate(util-is): cleanup unused imports"
Code Review Tips
For reviewers:
- Trust but verify: Codemods are reliable but not perfect
- Spot-check edge cases: Don’t review every line, focus on complex patterns
- Run tests locally: Don’t rely solely on CI
- Check the diff stats: Unusual patterns might indicate issues
# See change statistics
git diff --stat
# See detailed diff for specific file types
git diff -- '*.js' | less
Production Deployment
Before deploying codemod changes to production:
Staging environment
Deploy to staging firstgit push origin migration/util-is:staging
Smoke tests
Run critical path tests in staging
Monitor metrics
Check performance, error rates, logs
Gradual rollout
Use feature flags or canary deployments if possible
Production deployment
Deploy to production with rollback plan ready
For critical production systems, consider deploying migrations during maintenance windows or low-traffic periods.
Next Steps
How Codemods Work
Understand the technical foundation of AST transformations
When to Use Codemods
Learn when codemods are the right choice
Additional Resources
The Node.js Userland codemods are battle-tested and used across thousands of projects. Most issues you encounter will be edge cases specific to your codebase, not bugs in the codemod itself.