From 12400fb007815d8ca32d303b653e51cf6b268e2c Mon Sep 17 00:00:00 2001 From: Augustin Mauroy <97875033+AugustinMauroy@users.noreply.github.com> Date: Wed, 21 Jan 2026 22:10:44 +0100 Subject: [PATCH 01/10] init(`cjs-to-esm`) --- package-lock.json | 41 ++++++++++++----- recipes/cjs-to-esm/README.md | 1 + recipes/cjs-to-esm/codemod.yml | 22 ++++++++++ recipes/cjs-to-esm/package.json | 27 ++++++++++++ recipes/cjs-to-esm/src/export-process.ts | 12 +++++ recipes/cjs-to-esm/src/import-process.ts | 13 ++++++ .../cjs-to-esm/src/package-json-process.ts | 13 ++++++ .../exports-property-to-named/expected.js | 2 + .../expected/file-1.js | 2 + .../export/exports-property-to-named/input.js | 3 ++ .../exports-property-to-named/input/file-1.js | 3 ++ .../module-exports-objectliteral/expected.js | 2 + .../expected/file-1.js | 2 + .../module-exports-objectliteral/input.js | 2 + .../input/file-1.js | 2 + .../destructure-require-to-named/expected.js | 3 ++ .../expected/file-1.js | 3 ++ .../destructure-require-to-named/input.js | 3 ++ .../input/file-1.js | 3 ++ .../expected.js | 3 ++ .../expected/file-1.js | 3 ++ .../input.js | 3 ++ .../input/file-1.js | 3 ++ .../static-require-to-default/expected.js | 8 ++++ .../expected/file-1.js | 8 ++++ .../import/static-require-to-default/input.js | 8 ++++ .../static-require-to-default/input/file-1.js | 8 ++++ .../package-type-suggestion/expected.json | 5 +++ .../expected/file-1.json | 5 +++ .../package-type-suggestion/input.json | 4 ++ .../package-type-suggestion/input/file-1.json | 4 ++ recipes/cjs-to-esm/workflow.yaml | 44 +++++++++++++++++++ 32 files changed, 254 insertions(+), 11 deletions(-) create mode 100644 recipes/cjs-to-esm/README.md create mode 100644 recipes/cjs-to-esm/codemod.yml create mode 100644 recipes/cjs-to-esm/package.json create mode 100644 recipes/cjs-to-esm/src/export-process.ts create mode 100644 recipes/cjs-to-esm/src/import-process.ts create mode 100644 recipes/cjs-to-esm/src/package-json-process.ts create mode 100644 recipes/cjs-to-esm/tests/export/exports-property-to-named/expected.js create mode 100644 recipes/cjs-to-esm/tests/export/exports-property-to-named/expected/file-1.js create mode 100644 recipes/cjs-to-esm/tests/export/exports-property-to-named/input.js create mode 100644 recipes/cjs-to-esm/tests/export/exports-property-to-named/input/file-1.js create mode 100644 recipes/cjs-to-esm/tests/export/module-exports-objectliteral/expected.js create mode 100644 recipes/cjs-to-esm/tests/export/module-exports-objectliteral/expected/file-1.js create mode 100644 recipes/cjs-to-esm/tests/export/module-exports-objectliteral/input.js create mode 100644 recipes/cjs-to-esm/tests/export/module-exports-objectliteral/input/file-1.js create mode 100644 recipes/cjs-to-esm/tests/import/destructure-require-to-named/expected.js create mode 100644 recipes/cjs-to-esm/tests/import/destructure-require-to-named/expected/file-1.js create mode 100644 recipes/cjs-to-esm/tests/import/destructure-require-to-named/input.js create mode 100644 recipes/cjs-to-esm/tests/import/destructure-require-to-named/input/file-1.js create mode 100644 recipes/cjs-to-esm/tests/import/extensionless-path-add-js-extension/expected.js create mode 100644 recipes/cjs-to-esm/tests/import/extensionless-path-add-js-extension/expected/file-1.js create mode 100644 recipes/cjs-to-esm/tests/import/extensionless-path-add-js-extension/input.js create mode 100644 recipes/cjs-to-esm/tests/import/extensionless-path-add-js-extension/input/file-1.js create mode 100644 recipes/cjs-to-esm/tests/import/static-require-to-default/expected.js create mode 100644 recipes/cjs-to-esm/tests/import/static-require-to-default/expected/file-1.js create mode 100644 recipes/cjs-to-esm/tests/import/static-require-to-default/input.js create mode 100644 recipes/cjs-to-esm/tests/import/static-require-to-default/input/file-1.js create mode 100644 recipes/cjs-to-esm/tests/package-json/package-type-suggestion/expected.json create mode 100644 recipes/cjs-to-esm/tests/package-json/package-type-suggestion/expected/file-1.json create mode 100644 recipes/cjs-to-esm/tests/package-json/package-type-suggestion/input.json create mode 100644 recipes/cjs-to-esm/tests/package-json/package-type-suggestion/input/file-1.json create mode 100644 recipes/cjs-to-esm/workflow.yaml diff --git a/package-lock.json b/package-lock.json index 40220450..b05a4861 100644 --- a/package-lock.json +++ b/package-lock.json @@ -402,6 +402,7 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", @@ -1479,6 +1480,10 @@ "resolved": "recipes/chalk-to-util-styletext", "link": true }, + "node_modules/@nodejs/cjs-to-esm": { + "resolved": "recipes/esm-migration", + "link": true + }, "node_modules/@nodejs/codemod-utils": { "resolved": "utils", "link": true @@ -1593,6 +1598,7 @@ "resolved": "https://registry.npmjs.org/@octokit/core/-/core-5.2.2.tgz", "integrity": "sha512-/g2d4sW9nUDJOMz3mabVQvOGhVa4e/BN/Um7yca9Bb2XTzPPnfTWHWQg+IsEYO7M3Vx+EXvaM/I2pJWIMun1bg==", "license": "MIT", + "peer": true, "dependencies": { "@octokit/auth-token": "^4.0.0", "@octokit/graphql": "^7.1.0", @@ -1790,6 +1796,7 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.9.tgz", "integrity": "sha512-/rpCXHlCWeqClNBwUhDcusJxXYDjZTyE8v5oTO7WbL8eij2nKhUeU89/6xgjU7N4/Vh3He0BtyhJdQbDyhiXAw==", "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~7.16.0" } @@ -2108,6 +2115,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.19", "caniuse-lite": "^1.0.30001751", @@ -4286,7 +4294,7 @@ }, "recipes/chalk-to-util-styletext": { "name": "@nodejs/chalk-to-util-styletext", - "version": "1.0.0", + "version": "1.0.1", "license": "MIT", "dependencies": { "@nodejs/codemod-utils": "*" @@ -4335,7 +4343,7 @@ }, "recipes/crypto-fips-to-getFips": { "name": "@nodejs/crypto-fips-to-getFips", - "version": "1.0.1", + "version": "1.0.2", "license": "MIT", "dependencies": { "@nodejs/codemod-utils": "*" @@ -4346,7 +4354,7 @@ }, "recipes/crypto-rsa-pss-update": { "name": "@nodejs/crypto-rsa-pss-update", - "version": "1.0.1", + "version": "1.0.2", "license": "MIT", "dependencies": { "@nodejs/codemod-utils": "*" @@ -4357,6 +4365,17 @@ }, "recipes/dirent-path-to-parent-path": { "name": "@nodejs/dirent-path-to-parent-path", + "version": "1.0.1", + "license": "MIT", + "dependencies": { + "@nodejs/codemod-utils": "*" + }, + "devDependencies": { + "@codemod.com/jssg-types": "^1.3.1" + } + }, + "recipes/esm-migration": { + "name": "@nodejs/cjs-to-esm", "version": "1.0.0", "license": "MIT", "dependencies": { @@ -4379,7 +4398,7 @@ }, "recipes/fs-truncate-fd-deprecation": { "name": "@nodejs/fs-truncate-fd-deprecation", - "version": "1.0.1", + "version": "1.0.2", "license": "MIT", "dependencies": { "@nodejs/codemod-utils": "*" @@ -4417,7 +4436,7 @@ }, "recipes/process-assert-to-node-assert": { "name": "@nodejs/process-assert-to-node-assert", - "version": "1.0.1", + "version": "1.0.2", "license": "MIT", "dependencies": { "@nodejs/codemod-utils": "*" @@ -4450,7 +4469,7 @@ }, "recipes/repl-classes-with-new": { "name": "@nodejs/repl-classes-with-new", - "version": "1.0.1", + "version": "1.0.2", "license": "MIT", "dependencies": { "@nodejs/codemod-utils": "*" @@ -4472,7 +4491,7 @@ }, "recipes/slow-buffer-to-buffer-alloc-unsafe-slow": { "name": "@nodejs/slow-buffer-to-buffer-alloc-unsafe-slow", - "version": "1.0.1", + "version": "1.0.2", "license": "MIT", "dependencies": { "@nodejs/codemod-utils": "*" @@ -4504,7 +4523,7 @@ }, "recipes/util-extend-to-object-assign": { "name": "@nodejs/util-extend-to-object-assign", - "version": "1.0.0", + "version": "1.0.1", "license": "MIT", "dependencies": { "@nodejs/codemod-utils": "*" @@ -4515,7 +4534,7 @@ }, "recipes/util-is": { "name": "@nodejs/util-is", - "version": "1.0.1", + "version": "1.0.2", "license": "MIT", "dependencies": { "@nodejs/codemod-utils": "0.0.0" @@ -4537,7 +4556,7 @@ }, "recipes/util-print-to-console-log": { "name": "@nodejs/util-print-to-console-log", - "version": "1.0.1", + "version": "1.0.2", "license": "MIT", "dependencies": { "@nodejs/codemod-utils": "*" @@ -4548,7 +4567,7 @@ }, "recipes/zlib-bytesread-to-byteswritten": { "name": "@nodejs/zlib-bytesread-to-byteswritten", - "version": "1.0.0", + "version": "1.0.1", "license": "MIT", "dependencies": { "@nodejs/codemod-utils": "*" diff --git a/recipes/cjs-to-esm/README.md b/recipes/cjs-to-esm/README.md new file mode 100644 index 00000000..53b6c3f3 --- /dev/null +++ b/recipes/cjs-to-esm/README.md @@ -0,0 +1 @@ +# ESM Migration Codemod diff --git a/recipes/cjs-to-esm/codemod.yml b/recipes/cjs-to-esm/codemod.yml new file mode 100644 index 00000000..94138aac --- /dev/null +++ b/recipes/cjs-to-esm/codemod.yml @@ -0,0 +1,22 @@ +schema_version: "1.0" +name: "@nodejs/cjs-to-esm" +version: "0.1.0" +description: "Codemod to assist CommonJS -> ESM migrations (imports, exports, package.json guidance)" +author: "Augustin Mauroy" +license: "MIT" +workflow: workflow.yaml +category: migration + +targets: + languages: + - javascript + - typescript + +keywords: + - migration + - esm + - commonjs + +registry: + access: public + visibility: public diff --git a/recipes/cjs-to-esm/package.json b/recipes/cjs-to-esm/package.json new file mode 100644 index 00000000..125276f0 --- /dev/null +++ b/recipes/cjs-to-esm/package.json @@ -0,0 +1,27 @@ +{ + "name": "@nodejs/cjs-to-esm", + "version": "1.0.0", + "description": "Codemod to assist CommonJS -> ESM migrations (imports, exports, package.json guidance)", + "type": "module", + "scripts": { + "test": "node --run test:import && node --run test:export && node --run test:package-json", + "test:import": "npx codemod jssg test -l typescript ./src/import-process.ts ./tests/import", + "test:export": "npx codemod jssg test -l typescript ./src/export-process.ts ./tests/export", + "test:package-json": "npx codemod jssg test -l json ./src/package-json-process.ts ./tests/package-json" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/nodejs/userland-migrations.git", + "directory": "recipes/cjs-to-esm", + "bugs": "https://github.com/nodejs/userland-migrations/issues" + }, + "author": "Augustin Mauroy", + "license": "MIT", + "homepage": "https://github.com/nodejs/userland-migrations/blob/main/recipes/cjs-to-esm/README.md", + "devDependencies": { + "@codemod.com/jssg-types": "^1.3.1" + }, + "dependencies": { + "@nodejs/codemod-utils": "*" + } +} diff --git a/recipes/cjs-to-esm/src/export-process.ts b/recipes/cjs-to-esm/src/export-process.ts new file mode 100644 index 00000000..6da4315f --- /dev/null +++ b/recipes/cjs-to-esm/src/export-process.ts @@ -0,0 +1,12 @@ +import type { SgRoot, Edit } from '@codemod.com/jssg-types/main'; +import type JS from '@codemod.com/jssg-types/langs/javascript'; + +export default function transform(root: SgRoot): string | null { + const rootNode = root.root(); + const edits: Edit[] = []; + + // do some stuff + + if (!edits.length) return null; + return rootNode.commitEdits(edits); +} diff --git a/recipes/cjs-to-esm/src/import-process.ts b/recipes/cjs-to-esm/src/import-process.ts new file mode 100644 index 00000000..aa023691 --- /dev/null +++ b/recipes/cjs-to-esm/src/import-process.ts @@ -0,0 +1,13 @@ +import type { SgRoot, Edit } from '@codemod.com/jssg-types/main'; +import type JS from '@codemod.com/jssg-types/langs/javascript'; + +export default function transform(root: SgRoot): string | null { + const rootNode = root.root(); + const edits: Edit[] = []; + + // do some stuff + + if (!edits.length) return null; + + return rootNode.commitEdits(edits); +} diff --git a/recipes/cjs-to-esm/src/package-json-process.ts b/recipes/cjs-to-esm/src/package-json-process.ts new file mode 100644 index 00000000..72b55fba --- /dev/null +++ b/recipes/cjs-to-esm/src/package-json-process.ts @@ -0,0 +1,13 @@ +import type { Edit, SgRoot } from '@codemod.com/jssg-types/main'; +import type Json from '@codemod.com/jssg-types/langs/json'; + +export default function transform(root: SgRoot): string | null { + const rootNode = root.root(); + const edtis: Edit[] = []; + + // do some stuff + + if (!edtis.length) return null; + + return rootNode.commitEdits(edtis); +} diff --git a/recipes/cjs-to-esm/tests/export/exports-property-to-named/expected.js b/recipes/cjs-to-esm/tests/export/exports-property-to-named/expected.js new file mode 100644 index 00000000..a2f8840e --- /dev/null +++ b/recipes/cjs-to-esm/tests/export/exports-property-to-named/expected.js @@ -0,0 +1,2 @@ +export class Foo { } +export const bar = 'bar'; diff --git a/recipes/cjs-to-esm/tests/export/exports-property-to-named/expected/file-1.js b/recipes/cjs-to-esm/tests/export/exports-property-to-named/expected/file-1.js new file mode 100644 index 00000000..a2f8840e --- /dev/null +++ b/recipes/cjs-to-esm/tests/export/exports-property-to-named/expected/file-1.js @@ -0,0 +1,2 @@ +export class Foo { } +export const bar = 'bar'; diff --git a/recipes/cjs-to-esm/tests/export/exports-property-to-named/input.js b/recipes/cjs-to-esm/tests/export/exports-property-to-named/input.js new file mode 100644 index 00000000..f81f6139 --- /dev/null +++ b/recipes/cjs-to-esm/tests/export/exports-property-to-named/input.js @@ -0,0 +1,3 @@ +class Foo { } +exports.Foo = Foo; +exports.bar = 'bar'; diff --git a/recipes/cjs-to-esm/tests/export/exports-property-to-named/input/file-1.js b/recipes/cjs-to-esm/tests/export/exports-property-to-named/input/file-1.js new file mode 100644 index 00000000..f81f6139 --- /dev/null +++ b/recipes/cjs-to-esm/tests/export/exports-property-to-named/input/file-1.js @@ -0,0 +1,3 @@ +class Foo { } +exports.Foo = Foo; +exports.bar = 'bar'; diff --git a/recipes/cjs-to-esm/tests/export/module-exports-objectliteral/expected.js b/recipes/cjs-to-esm/tests/export/module-exports-objectliteral/expected.js new file mode 100644 index 00000000..3e498659 --- /dev/null +++ b/recipes/cjs-to-esm/tests/export/module-exports-objectliteral/expected.js @@ -0,0 +1,2 @@ +class Baz { } +export { Baz }; diff --git a/recipes/cjs-to-esm/tests/export/module-exports-objectliteral/expected/file-1.js b/recipes/cjs-to-esm/tests/export/module-exports-objectliteral/expected/file-1.js new file mode 100644 index 00000000..3e498659 --- /dev/null +++ b/recipes/cjs-to-esm/tests/export/module-exports-objectliteral/expected/file-1.js @@ -0,0 +1,2 @@ +class Baz { } +export { Baz }; diff --git a/recipes/cjs-to-esm/tests/export/module-exports-objectliteral/input.js b/recipes/cjs-to-esm/tests/export/module-exports-objectliteral/input.js new file mode 100644 index 00000000..16e12824 --- /dev/null +++ b/recipes/cjs-to-esm/tests/export/module-exports-objectliteral/input.js @@ -0,0 +1,2 @@ +class Baz { } +module.exports = { Baz }; diff --git a/recipes/cjs-to-esm/tests/export/module-exports-objectliteral/input/file-1.js b/recipes/cjs-to-esm/tests/export/module-exports-objectliteral/input/file-1.js new file mode 100644 index 00000000..16e12824 --- /dev/null +++ b/recipes/cjs-to-esm/tests/export/module-exports-objectliteral/input/file-1.js @@ -0,0 +1,2 @@ +class Baz { } +module.exports = { Baz }; diff --git a/recipes/cjs-to-esm/tests/import/destructure-require-to-named/expected.js b/recipes/cjs-to-esm/tests/import/destructure-require-to-named/expected.js new file mode 100644 index 00000000..7a85a744 --- /dev/null +++ b/recipes/cjs-to-esm/tests/import/destructure-require-to-named/expected.js @@ -0,0 +1,3 @@ +import { join } from 'node:path'; + +export { join }; diff --git a/recipes/cjs-to-esm/tests/import/destructure-require-to-named/expected/file-1.js b/recipes/cjs-to-esm/tests/import/destructure-require-to-named/expected/file-1.js new file mode 100644 index 00000000..7a85a744 --- /dev/null +++ b/recipes/cjs-to-esm/tests/import/destructure-require-to-named/expected/file-1.js @@ -0,0 +1,3 @@ +import { join } from 'node:path'; + +export { join }; diff --git a/recipes/cjs-to-esm/tests/import/destructure-require-to-named/input.js b/recipes/cjs-to-esm/tests/import/destructure-require-to-named/input.js new file mode 100644 index 00000000..f658f127 --- /dev/null +++ b/recipes/cjs-to-esm/tests/import/destructure-require-to-named/input.js @@ -0,0 +1,3 @@ +const { join } = require('path'); + +module.exports = { join }; diff --git a/recipes/cjs-to-esm/tests/import/destructure-require-to-named/input/file-1.js b/recipes/cjs-to-esm/tests/import/destructure-require-to-named/input/file-1.js new file mode 100644 index 00000000..f658f127 --- /dev/null +++ b/recipes/cjs-to-esm/tests/import/destructure-require-to-named/input/file-1.js @@ -0,0 +1,3 @@ +const { join } = require('path'); + +module.exports = { join }; diff --git a/recipes/cjs-to-esm/tests/import/extensionless-path-add-js-extension/expected.js b/recipes/cjs-to-esm/tests/import/extensionless-path-add-js-extension/expected.js new file mode 100644 index 00000000..51ac73f1 --- /dev/null +++ b/recipes/cjs-to-esm/tests/import/extensionless-path-add-js-extension/expected.js @@ -0,0 +1,3 @@ +import lib from './lib.js'; + +export default lib; diff --git a/recipes/cjs-to-esm/tests/import/extensionless-path-add-js-extension/expected/file-1.js b/recipes/cjs-to-esm/tests/import/extensionless-path-add-js-extension/expected/file-1.js new file mode 100644 index 00000000..51ac73f1 --- /dev/null +++ b/recipes/cjs-to-esm/tests/import/extensionless-path-add-js-extension/expected/file-1.js @@ -0,0 +1,3 @@ +import lib from './lib.js'; + +export default lib; diff --git a/recipes/cjs-to-esm/tests/import/extensionless-path-add-js-extension/input.js b/recipes/cjs-to-esm/tests/import/extensionless-path-add-js-extension/input.js new file mode 100644 index 00000000..d21f2490 --- /dev/null +++ b/recipes/cjs-to-esm/tests/import/extensionless-path-add-js-extension/input.js @@ -0,0 +1,3 @@ +const lib = require('./lib'); + +module.exports = lib; diff --git a/recipes/cjs-to-esm/tests/import/extensionless-path-add-js-extension/input/file-1.js b/recipes/cjs-to-esm/tests/import/extensionless-path-add-js-extension/input/file-1.js new file mode 100644 index 00000000..d21f2490 --- /dev/null +++ b/recipes/cjs-to-esm/tests/import/extensionless-path-add-js-extension/input/file-1.js @@ -0,0 +1,3 @@ +const lib = require('./lib'); + +module.exports = lib; diff --git a/recipes/cjs-to-esm/tests/import/static-require-to-default/expected.js b/recipes/cjs-to-esm/tests/import/static-require-to-default/expected.js new file mode 100644 index 00000000..577fa6d1 --- /dev/null +++ b/recipes/cjs-to-esm/tests/import/static-require-to-default/expected.js @@ -0,0 +1,8 @@ +import fs from 'node:fs'; +import pkg from 'pkg'; + +function read(path) { + return fs.readFileSync(path, 'utf8'); +} + +export default { read }; diff --git a/recipes/cjs-to-esm/tests/import/static-require-to-default/expected/file-1.js b/recipes/cjs-to-esm/tests/import/static-require-to-default/expected/file-1.js new file mode 100644 index 00000000..577fa6d1 --- /dev/null +++ b/recipes/cjs-to-esm/tests/import/static-require-to-default/expected/file-1.js @@ -0,0 +1,8 @@ +import fs from 'node:fs'; +import pkg from 'pkg'; + +function read(path) { + return fs.readFileSync(path, 'utf8'); +} + +export default { read }; diff --git a/recipes/cjs-to-esm/tests/import/static-require-to-default/input.js b/recipes/cjs-to-esm/tests/import/static-require-to-default/input.js new file mode 100644 index 00000000..825718a2 --- /dev/null +++ b/recipes/cjs-to-esm/tests/import/static-require-to-default/input.js @@ -0,0 +1,8 @@ +const fs = require('fs'); +const pkg = require('pkg'); + +function read(path) { + return fs.readFileSync(path, 'utf8'); +} + +module.exports = { read }; diff --git a/recipes/cjs-to-esm/tests/import/static-require-to-default/input/file-1.js b/recipes/cjs-to-esm/tests/import/static-require-to-default/input/file-1.js new file mode 100644 index 00000000..825718a2 --- /dev/null +++ b/recipes/cjs-to-esm/tests/import/static-require-to-default/input/file-1.js @@ -0,0 +1,8 @@ +const fs = require('fs'); +const pkg = require('pkg'); + +function read(path) { + return fs.readFileSync(path, 'utf8'); +} + +module.exports = { read }; diff --git a/recipes/cjs-to-esm/tests/package-json/package-type-suggestion/expected.json b/recipes/cjs-to-esm/tests/package-json/package-type-suggestion/expected.json new file mode 100644 index 00000000..6469f777 --- /dev/null +++ b/recipes/cjs-to-esm/tests/package-json/package-type-suggestion/expected.json @@ -0,0 +1,5 @@ +{ + "name": "my-logger", + "version": "1.0.0", + "type": "module" +} diff --git a/recipes/cjs-to-esm/tests/package-json/package-type-suggestion/expected/file-1.json b/recipes/cjs-to-esm/tests/package-json/package-type-suggestion/expected/file-1.json new file mode 100644 index 00000000..6469f777 --- /dev/null +++ b/recipes/cjs-to-esm/tests/package-json/package-type-suggestion/expected/file-1.json @@ -0,0 +1,5 @@ +{ + "name": "my-logger", + "version": "1.0.0", + "type": "module" +} diff --git a/recipes/cjs-to-esm/tests/package-json/package-type-suggestion/input.json b/recipes/cjs-to-esm/tests/package-json/package-type-suggestion/input.json new file mode 100644 index 00000000..11bbd038 --- /dev/null +++ b/recipes/cjs-to-esm/tests/package-json/package-type-suggestion/input.json @@ -0,0 +1,4 @@ +{ + "name": "my-logger", + "version": "1.0.0" +} diff --git a/recipes/cjs-to-esm/tests/package-json/package-type-suggestion/input/file-1.json b/recipes/cjs-to-esm/tests/package-json/package-type-suggestion/input/file-1.json new file mode 100644 index 00000000..11bbd038 --- /dev/null +++ b/recipes/cjs-to-esm/tests/package-json/package-type-suggestion/input/file-1.json @@ -0,0 +1,4 @@ +{ + "name": "my-logger", + "version": "1.0.0" +} diff --git a/recipes/cjs-to-esm/workflow.yaml b/recipes/cjs-to-esm/workflow.yaml new file mode 100644 index 00000000..0447582e --- /dev/null +++ b/recipes/cjs-to-esm/workflow.yaml @@ -0,0 +1,44 @@ +version: "1" + +nodes: + - id: apply-transforms + name: Apply AST transformations + type: automatic + runtime: + type: direct + steps: + - name: Convert requires/imports to ESM imports + js-ast-grep: + js_file: src/import-process.ts + base_path: . + include: + - "**/*.cjs" + - "**/*.js" + - "**/*.mjs" + - "**/*.ts" + - "**/*.tsx" + exclude: + - "**/node_modules/**" + language: typescript + - name: Convert CommonJS exports to ESM exports + js-ast-grep: + js_file: src/exports-process.ts + base_path: . + include: + - "**/*.cjs" + - "**/*.js" + - "**/*.mjs" + - "**/*.ts" + - "**/*.tsx" + exclude: + - "**/node_modules/**" + language: typescript + - name: "Suggest package.json updates (type: module)" + js-ast-grep: + js_file: src/package-json-process.ts + base_path: . + include: + - "package.json" + exclude: + - "**/node_modules/**" + language: json From 09b4c0341bd2db03105e7b60ed2c299eb08a7e4c Mon Sep 17 00:00:00 2001 From: Augustin Mauroy <97875033+AugustinMauroy@users.noreply.github.com> Date: Wed, 21 Jan 2026 22:40:49 +0100 Subject: [PATCH 02/10] Update package-lock.json --- package-lock.json | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index b05a4861..cd659940 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1481,7 +1481,7 @@ "link": true }, "node_modules/@nodejs/cjs-to-esm": { - "resolved": "recipes/esm-migration", + "resolved": "recipes/cjs-to-esm", "link": true }, "node_modules/@nodejs/codemod-utils": { @@ -4303,6 +4303,17 @@ "@codemod.com/jssg-types": "^1.3.1" } }, + "recipes/cjs-to-esm": { + "name": "@nodejs/cjs-to-esm", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "@nodejs/codemod-utils": "*" + }, + "devDependencies": { + "@codemod.com/jssg-types": "^1.3.1" + } + }, "recipes/correct-ts-specifiers": { "name": "@nodejs/correct-ts-specifiers", "version": "1.0.0", @@ -4377,6 +4388,7 @@ "recipes/esm-migration": { "name": "@nodejs/cjs-to-esm", "version": "1.0.0", + "extraneous": true, "license": "MIT", "dependencies": { "@nodejs/codemod-utils": "*" From 34ba34d7eaeb04c1fecf8143509c342533a86a0a Mon Sep 17 00:00:00 2001 From: Augustin Mauroy <97875033+AugustinMauroy@users.noreply.github.com> Date: Wed, 21 Jan 2026 23:48:05 +0100 Subject: [PATCH 03/10] feat(`package-json-process`): introduce --- .../cjs-to-esm/src/package-json-process.ts | 89 +++++++++++++++++-- .../engines-and-exports/expected/file-1.json | 12 +++ .../engines-and-exports/input/file-1.json | 10 +++ .../expected/file-1.json} | 3 +- .../input/file-1.json} | 2 +- .../expected/file-1.json | 1 + 6 files changed, 110 insertions(+), 7 deletions(-) create mode 100644 recipes/cjs-to-esm/tests/package-json/engines-and-exports/expected/file-1.json create mode 100644 recipes/cjs-to-esm/tests/package-json/engines-and-exports/input/file-1.json rename recipes/cjs-to-esm/tests/package-json/{package-type-suggestion/expected.json => engines-suggestion/expected/file-1.json} (63%) rename recipes/cjs-to-esm/tests/package-json/{package-type-suggestion/input.json => engines-suggestion/input/file-1.json} (50%) diff --git a/recipes/cjs-to-esm/src/package-json-process.ts b/recipes/cjs-to-esm/src/package-json-process.ts index 72b55fba..7c81e5ce 100644 --- a/recipes/cjs-to-esm/src/package-json-process.ts +++ b/recipes/cjs-to-esm/src/package-json-process.ts @@ -1,13 +1,92 @@ -import type { Edit, SgRoot } from '@codemod.com/jssg-types/main'; +import type { Edit, SgRoot, SgNode } from '@codemod.com/jssg-types/main'; import type Json from '@codemod.com/jssg-types/langs/json'; +const TARGET_NODE_TEXT = '"node": "^20.19.0 || >=22.12.0"'; + +function rebuildEnginesText(node: SgNode): { + text: string; + changed: boolean; +} { + const existingText = node.text().trim(); + const valueNode = node.child(2); + if (!valueNode?.is('object')) return { text: existingText, changed: false }; + + const innerChildren = valueNode.children(); + const innerPairs: SgNode[] = []; + for (const ic of innerChildren) if (ic.is('pair')) innerPairs.push(ic); + + const innerTexts: string[] = []; + let nodePresent = false; + let nodeUpdated = false; + + for (const ip of innerPairs) { + const keyNode = ip.child(0); + if (!keyNode) continue; + const name = keyNode.text().replace(/^"|"$/g, ''); + const trimmed = ip.text().trim(); + + if (name === 'node') { + nodePresent = true; + if (trimmed !== TARGET_NODE_TEXT) nodeUpdated = true; + innerTexts.push(TARGET_NODE_TEXT); + } else { + innerTexts.push(trimmed); + } + } + + if (!nodePresent || !nodeUpdated) + return { text: existingText, changed: false }; + + const newValue = `{\n ${innerTexts.join(',\n ')}\n }`; + + return { text: `"engines": ${newValue}`, changed: true }; +} + export default function transform(root: SgRoot): string | null { const rootNode = root.root(); - const edtis: Edit[] = []; + const edits: Edit[] = []; + + const mainObject = rootNode.find({ + rule: { kind: 'object', inside: { kind: 'document' } }, + }); + + // if no main object, means not a valid package.json + if (!mainObject) return null; + + const childNodes = mainObject.children(); + const topPairs: SgNode[] = []; + for (const c of childNodes) if (c.is('pair')) topPairs.push(c); + + const existing: Record> = {}; + const orderedKeys: string[] = []; + for (const p of topPairs) { + const keyNode = p.child(0); + if (!keyNode) continue; + const keyText = keyNode.text().replace(/^"|"$/g, ''); + existing[keyText] = p; + orderedKeys.push(keyText); + } + + const pairTexts: string[] = []; + let enginesChanged = false; + + for (const k of orderedKeys) { + if (k === 'engines') { + const { text, changed } = rebuildEnginesText(existing[k]); + pairTexts.push(text); + if (changed) enginesChanged = true; + } else { + pairTexts.push(existing[k].text().trim()); + } + } + + const typeMissing = !('type' in existing); + if (!typeMissing && !enginesChanged) return null; - // do some stuff + if (typeMissing) pairTexts.push('"type": "module"'); - if (!edtis.length) return null; + const newObjText = `{\n ${pairTexts.join(',\n ')}\n}\n`; + edits.push(mainObject.replace(newObjText)); - return rootNode.commitEdits(edtis); + return rootNode.commitEdits(edits); } diff --git a/recipes/cjs-to-esm/tests/package-json/engines-and-exports/expected/file-1.json b/recipes/cjs-to-esm/tests/package-json/engines-and-exports/expected/file-1.json new file mode 100644 index 00000000..510b9323 --- /dev/null +++ b/recipes/cjs-to-esm/tests/package-json/engines-and-exports/expected/file-1.json @@ -0,0 +1,12 @@ +{ + "name": "pkg", + "version": "1.0.0", + "exports": { + ".": "./index.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "type": "module" +} + diff --git a/recipes/cjs-to-esm/tests/package-json/engines-and-exports/input/file-1.json b/recipes/cjs-to-esm/tests/package-json/engines-and-exports/input/file-1.json new file mode 100644 index 00000000..36bbb7b5 --- /dev/null +++ b/recipes/cjs-to-esm/tests/package-json/engines-and-exports/input/file-1.json @@ -0,0 +1,10 @@ +{ + "name": "pkg", + "version": "1.0.0", + "exports": { + ".": "./index.js" + }, + "engines": { + "node": ">=18.0.0" + } +} diff --git a/recipes/cjs-to-esm/tests/package-json/package-type-suggestion/expected.json b/recipes/cjs-to-esm/tests/package-json/engines-suggestion/expected/file-1.json similarity index 63% rename from recipes/cjs-to-esm/tests/package-json/package-type-suggestion/expected.json rename to recipes/cjs-to-esm/tests/package-json/engines-suggestion/expected/file-1.json index 6469f777..6c8d7307 100644 --- a/recipes/cjs-to-esm/tests/package-json/package-type-suggestion/expected.json +++ b/recipes/cjs-to-esm/tests/package-json/engines-suggestion/expected/file-1.json @@ -1,5 +1,6 @@ { - "name": "my-logger", + "name": "engine-only", "version": "1.0.0", "type": "module" } + diff --git a/recipes/cjs-to-esm/tests/package-json/package-type-suggestion/input.json b/recipes/cjs-to-esm/tests/package-json/engines-suggestion/input/file-1.json similarity index 50% rename from recipes/cjs-to-esm/tests/package-json/package-type-suggestion/input.json rename to recipes/cjs-to-esm/tests/package-json/engines-suggestion/input/file-1.json index 11bbd038..5ec020a0 100644 --- a/recipes/cjs-to-esm/tests/package-json/package-type-suggestion/input.json +++ b/recipes/cjs-to-esm/tests/package-json/engines-suggestion/input/file-1.json @@ -1,4 +1,4 @@ { - "name": "my-logger", + "name": "engine-only", "version": "1.0.0" } diff --git a/recipes/cjs-to-esm/tests/package-json/package-type-suggestion/expected/file-1.json b/recipes/cjs-to-esm/tests/package-json/package-type-suggestion/expected/file-1.json index 6469f777..0361968a 100644 --- a/recipes/cjs-to-esm/tests/package-json/package-type-suggestion/expected/file-1.json +++ b/recipes/cjs-to-esm/tests/package-json/package-type-suggestion/expected/file-1.json @@ -3,3 +3,4 @@ "version": "1.0.0", "type": "module" } + From 5d54541c33434378013f39def7277c09e8be6ddb Mon Sep 17 00:00:00 2001 From: Augustin Mauroy <97875033+AugustinMauroy@users.noreply.github.com> Date: Wed, 21 Jan 2026 23:49:52 +0100 Subject: [PATCH 04/10] WIP --- recipes/cjs-to-esm/src/package-json-process.ts | 1 + .../export/exports-property-to-named/expected/file-1.js | 2 -- .../export/exports-property-to-named/input/file-1.js | 3 --- .../module-exports-objectliteral/expected/file-1.js | 2 -- .../export/module-exports-objectliteral/input/file-1.js | 2 -- .../destructure-require-to-named/expected/file-1.js | 3 --- .../import/destructure-require-to-named/input/file-1.js | 3 --- .../expected/file-1.js | 3 --- .../extensionless-path-add-js-extension/input/file-1.js | 3 --- .../import/static-require-to-default/expected/file-1.js | 8 -------- .../import/static-require-to-default/input/file-1.js | 8 -------- 11 files changed, 1 insertion(+), 37 deletions(-) delete mode 100644 recipes/cjs-to-esm/tests/export/exports-property-to-named/expected/file-1.js delete mode 100644 recipes/cjs-to-esm/tests/export/exports-property-to-named/input/file-1.js delete mode 100644 recipes/cjs-to-esm/tests/export/module-exports-objectliteral/expected/file-1.js delete mode 100644 recipes/cjs-to-esm/tests/export/module-exports-objectliteral/input/file-1.js delete mode 100644 recipes/cjs-to-esm/tests/import/destructure-require-to-named/expected/file-1.js delete mode 100644 recipes/cjs-to-esm/tests/import/destructure-require-to-named/input/file-1.js delete mode 100644 recipes/cjs-to-esm/tests/import/extensionless-path-add-js-extension/expected/file-1.js delete mode 100644 recipes/cjs-to-esm/tests/import/extensionless-path-add-js-extension/input/file-1.js delete mode 100644 recipes/cjs-to-esm/tests/import/static-require-to-default/expected/file-1.js delete mode 100644 recipes/cjs-to-esm/tests/import/static-require-to-default/input/file-1.js diff --git a/recipes/cjs-to-esm/src/package-json-process.ts b/recipes/cjs-to-esm/src/package-json-process.ts index 7c81e5ce..a2d34d45 100644 --- a/recipes/cjs-to-esm/src/package-json-process.ts +++ b/recipes/cjs-to-esm/src/package-json-process.ts @@ -3,6 +3,7 @@ import type Json from '@codemod.com/jssg-types/langs/json'; const TARGET_NODE_TEXT = '"node": "^20.19.0 || >=22.12.0"'; +// in future, I (@AugustinMauroy) want to use `@vlt/semver` to compare versions properly function rebuildEnginesText(node: SgNode): { text: string; changed: boolean; diff --git a/recipes/cjs-to-esm/tests/export/exports-property-to-named/expected/file-1.js b/recipes/cjs-to-esm/tests/export/exports-property-to-named/expected/file-1.js deleted file mode 100644 index a2f8840e..00000000 --- a/recipes/cjs-to-esm/tests/export/exports-property-to-named/expected/file-1.js +++ /dev/null @@ -1,2 +0,0 @@ -export class Foo { } -export const bar = 'bar'; diff --git a/recipes/cjs-to-esm/tests/export/exports-property-to-named/input/file-1.js b/recipes/cjs-to-esm/tests/export/exports-property-to-named/input/file-1.js deleted file mode 100644 index f81f6139..00000000 --- a/recipes/cjs-to-esm/tests/export/exports-property-to-named/input/file-1.js +++ /dev/null @@ -1,3 +0,0 @@ -class Foo { } -exports.Foo = Foo; -exports.bar = 'bar'; diff --git a/recipes/cjs-to-esm/tests/export/module-exports-objectliteral/expected/file-1.js b/recipes/cjs-to-esm/tests/export/module-exports-objectliteral/expected/file-1.js deleted file mode 100644 index 3e498659..00000000 --- a/recipes/cjs-to-esm/tests/export/module-exports-objectliteral/expected/file-1.js +++ /dev/null @@ -1,2 +0,0 @@ -class Baz { } -export { Baz }; diff --git a/recipes/cjs-to-esm/tests/export/module-exports-objectliteral/input/file-1.js b/recipes/cjs-to-esm/tests/export/module-exports-objectliteral/input/file-1.js deleted file mode 100644 index 16e12824..00000000 --- a/recipes/cjs-to-esm/tests/export/module-exports-objectliteral/input/file-1.js +++ /dev/null @@ -1,2 +0,0 @@ -class Baz { } -module.exports = { Baz }; diff --git a/recipes/cjs-to-esm/tests/import/destructure-require-to-named/expected/file-1.js b/recipes/cjs-to-esm/tests/import/destructure-require-to-named/expected/file-1.js deleted file mode 100644 index 7a85a744..00000000 --- a/recipes/cjs-to-esm/tests/import/destructure-require-to-named/expected/file-1.js +++ /dev/null @@ -1,3 +0,0 @@ -import { join } from 'node:path'; - -export { join }; diff --git a/recipes/cjs-to-esm/tests/import/destructure-require-to-named/input/file-1.js b/recipes/cjs-to-esm/tests/import/destructure-require-to-named/input/file-1.js deleted file mode 100644 index f658f127..00000000 --- a/recipes/cjs-to-esm/tests/import/destructure-require-to-named/input/file-1.js +++ /dev/null @@ -1,3 +0,0 @@ -const { join } = require('path'); - -module.exports = { join }; diff --git a/recipes/cjs-to-esm/tests/import/extensionless-path-add-js-extension/expected/file-1.js b/recipes/cjs-to-esm/tests/import/extensionless-path-add-js-extension/expected/file-1.js deleted file mode 100644 index 51ac73f1..00000000 --- a/recipes/cjs-to-esm/tests/import/extensionless-path-add-js-extension/expected/file-1.js +++ /dev/null @@ -1,3 +0,0 @@ -import lib from './lib.js'; - -export default lib; diff --git a/recipes/cjs-to-esm/tests/import/extensionless-path-add-js-extension/input/file-1.js b/recipes/cjs-to-esm/tests/import/extensionless-path-add-js-extension/input/file-1.js deleted file mode 100644 index d21f2490..00000000 --- a/recipes/cjs-to-esm/tests/import/extensionless-path-add-js-extension/input/file-1.js +++ /dev/null @@ -1,3 +0,0 @@ -const lib = require('./lib'); - -module.exports = lib; diff --git a/recipes/cjs-to-esm/tests/import/static-require-to-default/expected/file-1.js b/recipes/cjs-to-esm/tests/import/static-require-to-default/expected/file-1.js deleted file mode 100644 index 577fa6d1..00000000 --- a/recipes/cjs-to-esm/tests/import/static-require-to-default/expected/file-1.js +++ /dev/null @@ -1,8 +0,0 @@ -import fs from 'node:fs'; -import pkg from 'pkg'; - -function read(path) { - return fs.readFileSync(path, 'utf8'); -} - -export default { read }; diff --git a/recipes/cjs-to-esm/tests/import/static-require-to-default/input/file-1.js b/recipes/cjs-to-esm/tests/import/static-require-to-default/input/file-1.js deleted file mode 100644 index 825718a2..00000000 --- a/recipes/cjs-to-esm/tests/import/static-require-to-default/input/file-1.js +++ /dev/null @@ -1,8 +0,0 @@ -const fs = require('fs'); -const pkg = require('pkg'); - -function read(path) { - return fs.readFileSync(path, 'utf8'); -} - -module.exports = { read }; From 170bd9ee254463a91fb64fdb7cfe7be3de0caedc Mon Sep 17 00:00:00 2001 From: Augustin Mauroy <97875033+AugustinMauroy@users.noreply.github.com> Date: Wed, 21 Jan 2026 23:53:25 +0100 Subject: [PATCH 05/10] fix --- biome.jsonc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/biome.jsonc b/biome.jsonc index 0f269bb4..006da53a 100644 --- a/biome.jsonc +++ b/biome.jsonc @@ -5,8 +5,7 @@ "**", "!**/*.snap.cjs", "!**/fixtures", - "!**/expected", - "!**/input" + "!**/tests" ] }, "assist": { "actions": { "source": { "organizeImports": "off" } } }, From c61ae5557c2c351445d90e5d776a5d8d7cc2be5d Mon Sep 17 00:00:00 2001 From: Augustin Mauroy <97875033+AugustinMauroy@users.noreply.github.com> Date: Thu, 22 Jan 2026 00:30:14 +0100 Subject: [PATCH 06/10] feat(`context-local-variable-process`): introduce --- recipes/cjs-to-esm/README.md | 14 +++++ recipes/cjs-to-esm/package.json | 5 +- .../src/context-local-variable-process.ts | 55 +++++++++++++++++++ recipes/cjs-to-esm/src/export-process.ts | 3 + recipes/cjs-to-esm/src/import-process.ts | 3 + .../cjs-to-esm/src/package-json-process.ts | 3 + .../context-local-variable/basic/expected.js | 4 ++ .../context-local-variable/basic/input.js | 4 ++ recipes/cjs-to-esm/workflow.yaml | 15 ++++- 9 files changed, 103 insertions(+), 3 deletions(-) create mode 100644 recipes/cjs-to-esm/src/context-local-variable-process.ts create mode 100644 recipes/cjs-to-esm/tests/context-local-variable/basic/expected.js create mode 100644 recipes/cjs-to-esm/tests/context-local-variable/basic/input.js diff --git a/recipes/cjs-to-esm/README.md b/recipes/cjs-to-esm/README.md index 53b6c3f3..3966c30d 100644 --- a/recipes/cjs-to-esm/README.md +++ b/recipes/cjs-to-esm/README.md @@ -1 +1,15 @@ + # ESM Migration Codemod + +## Context-local Variable Migration + +This codemod includes a step to help migrate context-local variables and Node.js built-in globals from CommonJS to ESM. It detects usages of: + +- `__filename` → `import.meta.filename` (Node.js v20.11.0 / v21.2.0) +- `__dirname` → `import.meta.dirname` (Node.js v20.11.0 / v21.2.0) +- `require.main` → `import.meta.main` (Node.js v22.18.0 / v24.2.0) +- `require.resolve` → `import.meta.resolve` (available) + +If these or other context-local patterns are found, the codemod will emit a warning and suggest reviewing the [migration guide](https://github.com/nodejs/package-examples/blob/main/guide/05-cjs-esm-migration/migrating-context-local-variables/README.md). + +**Note:** Some features require specific Node.js versions as indicated above. diff --git a/recipes/cjs-to-esm/package.json b/recipes/cjs-to-esm/package.json index 125276f0..2eb9f07c 100644 --- a/recipes/cjs-to-esm/package.json +++ b/recipes/cjs-to-esm/package.json @@ -4,10 +4,11 @@ "description": "Codemod to assist CommonJS -> ESM migrations (imports, exports, package.json guidance)", "type": "module", "scripts": { - "test": "node --run test:import && node --run test:export && node --run test:package-json", + "test": "node --run test:import && node --run test:export && node --run test:package-json && node --run test:context-local-variable", "test:import": "npx codemod jssg test -l typescript ./src/import-process.ts ./tests/import", "test:export": "npx codemod jssg test -l typescript ./src/export-process.ts ./tests/export", - "test:package-json": "npx codemod jssg test -l json ./src/package-json-process.ts ./tests/package-json" + "test:package-json": "npx codemod jssg test -l json ./src/package-json-process.ts ./tests/package-json", + "test:context-local-variable": "npx codemod jssg test -l typescript ./src/context-local-variable-process.ts ./tests/context-local-variable" }, "repository": { "type": "git", diff --git a/recipes/cjs-to-esm/src/context-local-variable-process.ts b/recipes/cjs-to-esm/src/context-local-variable-process.ts new file mode 100644 index 00000000..a9b3cce4 --- /dev/null +++ b/recipes/cjs-to-esm/src/context-local-variable-process.ts @@ -0,0 +1,55 @@ +import type { SgRoot, Edit } from '@codemod.com/jssg-types/main'; +import type JS from '@codemod.com/jssg-types/langs/javascript'; + +/** + * @see https://github.com/nodejs/package-examples/blob/main/guide/05-cjs-esm-migration/migrating-context-local-variables/README.md + */ +export default function transform(root: SgRoot): string | null { + const rootNode = root.root(); + const edits: Edit[] = []; + + // __filename -> import.meta.filename + const fileNameNods = rootNode.findAll({ rule: { pattern: '__filename' } }); + for (const node of fileNameNods) { + edits.push(node.replace('import.meta.filename')); + } + + // __dirname -> import.meta.dirname + const dirNameNods = rootNode.findAll({ rule: { pattern: '__dirname' } }); + for (const node of dirNameNods) { + edits.push(node.replace('import.meta.dirname')); + } + + // require.main -> import.meta.main + const requireMainNods = rootNode.findAll({ + rule: { pattern: 'require.main' }, + }); + for (const node of requireMainNods) { + edits.push(node.replace('import.meta.main')); + } + + // require.resolve(...) -> import.meta.resolve(...) + const requireResolveNods = rootNode.findAll({ + rule: { + kind: 'call_expression', + has: { + kind: 'member_expression', + pattern: 'require.resolve', + }, + }, + }); + for (const callExpr of requireResolveNods) { + const argsNode = callExpr.field('arguments'); + + if (argsNode) { + const argTexts = argsNode.text(); + edits.push(callExpr.replace(`import.meta.resolve${argTexts}`)); + } else { + edits.push(callExpr.replace('import.meta.resolve()')); + } + } + + if (!edits.length) return null; + + return rootNode.commitEdits(edits); +} diff --git a/recipes/cjs-to-esm/src/export-process.ts b/recipes/cjs-to-esm/src/export-process.ts index 6da4315f..8227462a 100644 --- a/recipes/cjs-to-esm/src/export-process.ts +++ b/recipes/cjs-to-esm/src/export-process.ts @@ -1,6 +1,9 @@ import type { SgRoot, Edit } from '@codemod.com/jssg-types/main'; import type JS from '@codemod.com/jssg-types/langs/javascript'; +/** + * @see https://github.com/nodejs/package-examples/tree/main/guide/05-cjs-esm-migration/migrating-exports + */ export default function transform(root: SgRoot): string | null { const rootNode = root.root(); const edits: Edit[] = []; diff --git a/recipes/cjs-to-esm/src/import-process.ts b/recipes/cjs-to-esm/src/import-process.ts index aa023691..a66bd8a2 100644 --- a/recipes/cjs-to-esm/src/import-process.ts +++ b/recipes/cjs-to-esm/src/import-process.ts @@ -1,6 +1,9 @@ import type { SgRoot, Edit } from '@codemod.com/jssg-types/main'; import type JS from '@codemod.com/jssg-types/langs/javascript'; +/** + * @see https://github.com/nodejs/package-examples/tree/main/guide/05-cjs-esm-migration/migrating-imports + */ export default function transform(root: SgRoot): string | null { const rootNode = root.root(); const edits: Edit[] = []; diff --git a/recipes/cjs-to-esm/src/package-json-process.ts b/recipes/cjs-to-esm/src/package-json-process.ts index a2d34d45..7dc5d00a 100644 --- a/recipes/cjs-to-esm/src/package-json-process.ts +++ b/recipes/cjs-to-esm/src/package-json-process.ts @@ -43,6 +43,9 @@ function rebuildEnginesText(node: SgNode): { return { text: `"engines": ${newValue}`, changed: true }; } +/** + * @see https://github.com/nodejs/package-examples/tree/main/guide/05-cjs-esm-migration/migrating-package-json + */ export default function transform(root: SgRoot): string | null { const rootNode = root.root(); const edits: Edit[] = []; diff --git a/recipes/cjs-to-esm/tests/context-local-variable/basic/expected.js b/recipes/cjs-to-esm/tests/context-local-variable/basic/expected.js new file mode 100644 index 00000000..19cf96d1 --- /dev/null +++ b/recipes/cjs-to-esm/tests/context-local-variable/basic/expected.js @@ -0,0 +1,4 @@ +console.log(import.meta.filename); +console.log(import.meta.dirname); +console.log(import.meta.main); +console.log(import.meta.resolve('./foo')); diff --git a/recipes/cjs-to-esm/tests/context-local-variable/basic/input.js b/recipes/cjs-to-esm/tests/context-local-variable/basic/input.js new file mode 100644 index 00000000..49ca2677 --- /dev/null +++ b/recipes/cjs-to-esm/tests/context-local-variable/basic/input.js @@ -0,0 +1,4 @@ +console.log(__filename); +console.log(__dirname); +console.log(require.main); +console.log(require.resolve('./foo')); diff --git a/recipes/cjs-to-esm/workflow.yaml b/recipes/cjs-to-esm/workflow.yaml index 0447582e..34229b11 100644 --- a/recipes/cjs-to-esm/workflow.yaml +++ b/recipes/cjs-to-esm/workflow.yaml @@ -22,7 +22,20 @@ nodes: language: typescript - name: Convert CommonJS exports to ESM exports js-ast-grep: - js_file: src/exports-process.ts + js_file: src/export-process.ts + base_path: . + include: + - "**/*.cjs" + - "**/*.js" + - "**/*.mjs" + - "**/*.ts" + - "**/*.tsx" + exclude: + - "**/node_modules/**" + language: typescript + - name: Migrate context-local variables and built-ins + js-ast-grep: + js_file: src/context-local-variable-process.ts base_path: . include: - "**/*.cjs" From 855dd869e8497c33c55950ab85f7a1c35484b16c Mon Sep 17 00:00:00 2001 From: Augustin Mauroy <97875033+AugustinMauroy@users.noreply.github.com> Date: Thu, 22 Jan 2026 12:17:30 +0100 Subject: [PATCH 07/10] WIP --- recipes/cjs-to-esm/README.md | 36 ++++++++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/recipes/cjs-to-esm/README.md b/recipes/cjs-to-esm/README.md index 3966c30d..99eee089 100644 --- a/recipes/cjs-to-esm/README.md +++ b/recipes/cjs-to-esm/README.md @@ -1,15 +1,35 @@ # ESM Migration Codemod -## Context-local Variable Migration + -This codemod includes a step to help migrate context-local variables and Node.js built-in globals from CommonJS to ESM. It detects usages of: +## How it's will/should works -- `__filename` → `import.meta.filename` (Node.js v20.11.0 / v21.2.0) -- `__dirname` → `import.meta.dirname` (Node.js v20.11.0 / v21.2.0) -- `require.main` → `import.meta.main` (Node.js v22.18.0 / v24.2.0) -- `require.resolve` → `import.meta.resolve` (available) +1. Change file extension from `.cjs` to `.js` & `.mjs` to `.js`. And IDK how keep track of this change. +2. Change importing files to use ESM syntax. With updating the specifier to reflect file extension changes. +3. Change exporting files to use ESM syntax. +4. Update context-local variables. If possible to track this change in goal of having the lowest nodejs version in the `engines` field of `package.json`. +5. Update `package.json`: + - Add/update `"type": "module"` field. + - Update file extensions in `"main"`, `"module"`, `"exports"`, and other relevant fields. + - _not sure_ Remove `"exports"` field if it only contains CJS-specific entries. + - update engines field to reflect the minimum Node.js version that supports all ESM features used in the codebase. -If these or other context-local patterns are found, the codemod will emit a warning and suggest reviewing the [migration guide](https://github.com/nodejs/package-examples/blob/main/guide/05-cjs-esm-migration/migrating-context-local-variables/README.md). +## Limitations -**Note:** Some features require specific Node.js versions as indicated above. +- Typescript: its will be more complex because we need to update the whole building process. + +## REFS + +- https://nodejs.github.io/package-examples/05-cjs-esm-migration/ +- https://nodejs.github.io/package-examples/05-cjs-esm-migration/migrating-imports/ +- https://nodejs.github.io/package-examples/05-cjs-esm-migration/migrating-exports/ +- https://nodejs.github.io/package-examples/05-cjs-esm-migration/migrating-context-local-variables/ +- https://nodejs.github.io/package-examples/05-cjs-esm-migration/migrating-package-json/ From 07965c35781d941d7f94cbba8175434750e18805 Mon Sep 17 00:00:00 2001 From: Augustin Mauroy <97875033+AugustinMauroy@users.noreply.github.com> Date: Thu, 22 Jan 2026 14:12:28 +0100 Subject: [PATCH 08/10] WIP --- recipes/cjs-to-esm/codemod.yml | 3 ++ recipes/cjs-to-esm/src/extension-change.ts | 46 ++++++++++++++++++++++ recipes/cjs-to-esm/workflow.yaml | 33 ++++++++++++++++ 3 files changed, 82 insertions(+) create mode 100644 recipes/cjs-to-esm/src/extension-change.ts diff --git a/recipes/cjs-to-esm/codemod.yml b/recipes/cjs-to-esm/codemod.yml index 94138aac..cb6e301f 100644 --- a/recipes/cjs-to-esm/codemod.yml +++ b/recipes/cjs-to-esm/codemod.yml @@ -20,3 +20,6 @@ keywords: registry: access: public visibility: public + +capabilities: + - fs diff --git a/recipes/cjs-to-esm/src/extension-change.ts b/recipes/cjs-to-esm/src/extension-change.ts new file mode 100644 index 00000000..86fcd481 --- /dev/null +++ b/recipes/cjs-to-esm/src/extension-change.ts @@ -0,0 +1,46 @@ +import fs from 'node:fs'; +import path from 'node:path'; +import { + getOrSetStepOutput, + setStepOutput, +} from '@codemod.com/jssg-types/workflow'; +import type { SgRoot } from '@codemod.com/jssg-types/main'; +import type JS from '@codemod.com/jssg-types/langs/javascript'; + +const STEP_ID = 'change-extensions'; +const OUTPUT_NAME = 'extension_changes'; + +export default async function transform( + root: SgRoot, +): Promise { + const sourcePath = root.filename(); + if (!sourcePath || sourcePath === 'anonymous') return null; + + if (!sourcePath.endsWith('.cjs') && !sourcePath.endsWith('.mjs')) return null; + + const targetPath = sourcePath.replace(/\.(c|m)js$/i, '.js'); + if (targetPath === sourcePath) return null; + + fs.renameSync(sourcePath, targetPath); + + const from = path.normalize(path.resolve(sourcePath)); + const to = path.normalize(path.resolve(targetPath)); + + const existingRaw = getOrSetStepOutput(STEP_ID, OUTPUT_NAME, '[]'); + let mappings: Array<{ from: string; to: string }> = []; + + try { + const parsed = JSON.parse(existingRaw); + if (Array.isArray(parsed)) mappings = parsed; + } catch (error) { + console.warn( + 'Failed to parse existing extension changes; resetting list', + error, + ); + } + + mappings.push({ from, to }); + setStepOutput(OUTPUT_NAME, JSON.stringify(mappings)); + + return null; +} diff --git a/recipes/cjs-to-esm/workflow.yaml b/recipes/cjs-to-esm/workflow.yaml index 34229b11..19fd95f2 100644 --- a/recipes/cjs-to-esm/workflow.yaml +++ b/recipes/cjs-to-esm/workflow.yaml @@ -1,8 +1,41 @@ version: "1" +state: + schema: + extension_changes: + description: Tracks files renamed from .cjs/.mjs to .js for downstream specifier fixes + type: array + items: + type: object + properties: + from: + type: string + to: + type: string + nodes: + - id: normalize-extensions + name: Normalize file extensions + type: automatic + runtime: + type: direct + steps: + - id: change-extensions + name: Rename .cjs/.mjs files to .js and record mapping + js-ast-grep: + js_file: src/extension-change.ts + base_path: . + include: + - "**/*.cjs" + - "**/*.mjs" + exclude: + - "**/node_modules/**" + language: typescript + - id: apply-transforms name: Apply AST transformations + depends_on: + - normalize-extensions type: automatic runtime: type: direct From a505375c36da9ad3c89819a3dedd9b0e3317bf91 Mon Sep 17 00:00:00 2001 From: Augustin Mauroy <97875033+AugustinMauroy@users.noreply.github.com> Date: Mon, 2 Feb 2026 11:24:59 +0100 Subject: [PATCH 09/10] simplify --- recipes/cjs-to-esm/.gitkeep | 0 recipes/cjs-to-esm/README.md | 31 +------ recipes/cjs-to-esm/codemod.yml | 2 +- recipes/cjs-to-esm/package.json | 2 +- .../src/context-local-variable-process.ts | 41 +-------- recipes/cjs-to-esm/src/extension-change.ts | 36 ++------ .../cjs-to-esm/src/package-json-process.ts | 84 +------------------ .../tests/context-local-variable/.gitkeep | 0 .../context-local-variable/basic/expected.js | 4 - .../context-local-variable/basic/input.js | 4 - recipes/cjs-to-esm/tests/export/.gitkeep | 0 .../exports-property-to-named/expected.js | 2 - .../export/exports-property-to-named/input.js | 3 - .../module-exports-objectliteral/expected.js | 2 - .../module-exports-objectliteral/input.js | 2 - recipes/cjs-to-esm/tests/import/.gitkeep | 0 .../destructure-require-to-named/expected.js | 3 - .../destructure-require-to-named/input.js | 3 - .../expected.js | 3 - .../input.js | 3 - .../static-require-to-default/expected.js | 8 -- .../import/static-require-to-default/input.js | 8 -- .../engines-and-exports/expected/file-1.json | 12 --- .../engines-and-exports/input/file-1.json | 10 --- .../engines-suggestion/expected/file-1.json | 6 -- .../engines-suggestion/input/file-1.json | 4 - .../expected/file-1.json | 6 -- .../package-type-suggestion/input/file-1.json | 4 - 28 files changed, 14 insertions(+), 269 deletions(-) create mode 100644 recipes/cjs-to-esm/.gitkeep create mode 100644 recipes/cjs-to-esm/tests/context-local-variable/.gitkeep delete mode 100644 recipes/cjs-to-esm/tests/context-local-variable/basic/expected.js delete mode 100644 recipes/cjs-to-esm/tests/context-local-variable/basic/input.js create mode 100644 recipes/cjs-to-esm/tests/export/.gitkeep delete mode 100644 recipes/cjs-to-esm/tests/export/exports-property-to-named/expected.js delete mode 100644 recipes/cjs-to-esm/tests/export/exports-property-to-named/input.js delete mode 100644 recipes/cjs-to-esm/tests/export/module-exports-objectliteral/expected.js delete mode 100644 recipes/cjs-to-esm/tests/export/module-exports-objectliteral/input.js create mode 100644 recipes/cjs-to-esm/tests/import/.gitkeep delete mode 100644 recipes/cjs-to-esm/tests/import/destructure-require-to-named/expected.js delete mode 100644 recipes/cjs-to-esm/tests/import/destructure-require-to-named/input.js delete mode 100644 recipes/cjs-to-esm/tests/import/extensionless-path-add-js-extension/expected.js delete mode 100644 recipes/cjs-to-esm/tests/import/extensionless-path-add-js-extension/input.js delete mode 100644 recipes/cjs-to-esm/tests/import/static-require-to-default/expected.js delete mode 100644 recipes/cjs-to-esm/tests/import/static-require-to-default/input.js delete mode 100644 recipes/cjs-to-esm/tests/package-json/engines-and-exports/expected/file-1.json delete mode 100644 recipes/cjs-to-esm/tests/package-json/engines-and-exports/input/file-1.json delete mode 100644 recipes/cjs-to-esm/tests/package-json/engines-suggestion/expected/file-1.json delete mode 100644 recipes/cjs-to-esm/tests/package-json/engines-suggestion/input/file-1.json delete mode 100644 recipes/cjs-to-esm/tests/package-json/package-type-suggestion/expected/file-1.json delete mode 100644 recipes/cjs-to-esm/tests/package-json/package-type-suggestion/input/file-1.json diff --git a/recipes/cjs-to-esm/.gitkeep b/recipes/cjs-to-esm/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/recipes/cjs-to-esm/README.md b/recipes/cjs-to-esm/README.md index 99eee089..516c98dc 100644 --- a/recipes/cjs-to-esm/README.md +++ b/recipes/cjs-to-esm/README.md @@ -2,34 +2,5 @@ # ESM Migration Codemod - -## How it's will/should works - -1. Change file extension from `.cjs` to `.js` & `.mjs` to `.js`. And IDK how keep track of this change. -2. Change importing files to use ESM syntax. With updating the specifier to reflect file extension changes. -3. Change exporting files to use ESM syntax. -4. Update context-local variables. If possible to track this change in goal of having the lowest nodejs version in the `engines` field of `package.json`. -5. Update `package.json`: - - Add/update `"type": "module"` field. - - Update file extensions in `"main"`, `"module"`, `"exports"`, and other relevant fields. - - _not sure_ Remove `"exports"` field if it only contains CJS-specific entries. - - update engines field to reflect the minimum Node.js version that supports all ESM features used in the codebase. - -## Limitations - -- Typescript: its will be more complex because we need to update the whole building process. - -## REFS - -- https://nodejs.github.io/package-examples/05-cjs-esm-migration/ -- https://nodejs.github.io/package-examples/05-cjs-esm-migration/migrating-imports/ -- https://nodejs.github.io/package-examples/05-cjs-esm-migration/migrating-exports/ -- https://nodejs.github.io/package-examples/05-cjs-esm-migration/migrating-context-local-variables/ -- https://nodejs.github.io/package-examples/05-cjs-esm-migration/migrating-package-json/ diff --git a/recipes/cjs-to-esm/codemod.yml b/recipes/cjs-to-esm/codemod.yml index cb6e301f..9fdbb05f 100644 --- a/recipes/cjs-to-esm/codemod.yml +++ b/recipes/cjs-to-esm/codemod.yml @@ -1,6 +1,6 @@ schema_version: "1.0" name: "@nodejs/cjs-to-esm" -version: "0.1.0" +version: "1.0.0" description: "Codemod to assist CommonJS -> ESM migrations (imports, exports, package.json guidance)" author: "Augustin Mauroy" license: "MIT" diff --git a/recipes/cjs-to-esm/package.json b/recipes/cjs-to-esm/package.json index 2eb9f07c..a08b90d8 100644 --- a/recipes/cjs-to-esm/package.json +++ b/recipes/cjs-to-esm/package.json @@ -4,7 +4,7 @@ "description": "Codemod to assist CommonJS -> ESM migrations (imports, exports, package.json guidance)", "type": "module", "scripts": { - "test": "node --run test:import && node --run test:export && node --run test:package-json && node --run test:context-local-variable", + "test": "echo \"The test will be runned when implementation is done.\"", "test:import": "npx codemod jssg test -l typescript ./src/import-process.ts ./tests/import", "test:export": "npx codemod jssg test -l typescript ./src/export-process.ts ./tests/export", "test:package-json": "npx codemod jssg test -l json ./src/package-json-process.ts ./tests/package-json", diff --git a/recipes/cjs-to-esm/src/context-local-variable-process.ts b/recipes/cjs-to-esm/src/context-local-variable-process.ts index a9b3cce4..6b6706ae 100644 --- a/recipes/cjs-to-esm/src/context-local-variable-process.ts +++ b/recipes/cjs-to-esm/src/context-local-variable-process.ts @@ -8,46 +8,7 @@ export default function transform(root: SgRoot): string | null { const rootNode = root.root(); const edits: Edit[] = []; - // __filename -> import.meta.filename - const fileNameNods = rootNode.findAll({ rule: { pattern: '__filename' } }); - for (const node of fileNameNods) { - edits.push(node.replace('import.meta.filename')); - } - - // __dirname -> import.meta.dirname - const dirNameNods = rootNode.findAll({ rule: { pattern: '__dirname' } }); - for (const node of dirNameNods) { - edits.push(node.replace('import.meta.dirname')); - } - - // require.main -> import.meta.main - const requireMainNods = rootNode.findAll({ - rule: { pattern: 'require.main' }, - }); - for (const node of requireMainNods) { - edits.push(node.replace('import.meta.main')); - } - - // require.resolve(...) -> import.meta.resolve(...) - const requireResolveNods = rootNode.findAll({ - rule: { - kind: 'call_expression', - has: { - kind: 'member_expression', - pattern: 'require.resolve', - }, - }, - }); - for (const callExpr of requireResolveNods) { - const argsNode = callExpr.field('arguments'); - - if (argsNode) { - const argTexts = argsNode.text(); - edits.push(callExpr.replace(`import.meta.resolve${argTexts}`)); - } else { - edits.push(callExpr.replace('import.meta.resolve()')); - } - } + // do some stuff if (!edits.length) return null; diff --git a/recipes/cjs-to-esm/src/extension-change.ts b/recipes/cjs-to-esm/src/extension-change.ts index 86fcd481..79c0bd4e 100644 --- a/recipes/cjs-to-esm/src/extension-change.ts +++ b/recipes/cjs-to-esm/src/extension-change.ts @@ -1,46 +1,26 @@ -import fs from 'node:fs'; -import path from 'node:path'; +/* +To interact with step output we need to use this functions. import { getOrSetStepOutput, setStepOutput, } from '@codemod.com/jssg-types/workflow'; +*/ import type { SgRoot } from '@codemod.com/jssg-types/main'; import type JS from '@codemod.com/jssg-types/langs/javascript'; -const STEP_ID = 'change-extensions'; -const OUTPUT_NAME = 'extension_changes'; +//const STEP_ID = 'change-extensions'; +//const OUTPUT_NAME = 'extension_changes'; export default async function transform( root: SgRoot, ): Promise { const sourcePath = root.filename(); - if (!sourcePath || sourcePath === 'anonymous') return null; - if (!sourcePath.endsWith('.cjs') && !sourcePath.endsWith('.mjs')) return null; + // do some stuff - const targetPath = sourcePath.replace(/\.(c|m)js$/i, '.js'); - if (targetPath === sourcePath) return null; - - fs.renameSync(sourcePath, targetPath); - - const from = path.normalize(path.resolve(sourcePath)); - const to = path.normalize(path.resolve(targetPath)); - - const existingRaw = getOrSetStepOutput(STEP_ID, OUTPUT_NAME, '[]'); - let mappings: Array<{ from: string; to: string }> = []; - - try { - const parsed = JSON.parse(existingRaw); - if (Array.isArray(parsed)) mappings = parsed; - } catch (error) { - console.warn( - 'Failed to parse existing extension changes; resetting list', - error, - ); + if (sourcePath.endsWith('.cjs')) { + // ... } - mappings.push({ from, to }); - setStepOutput(OUTPUT_NAME, JSON.stringify(mappings)); - return null; } diff --git a/recipes/cjs-to-esm/src/package-json-process.ts b/recipes/cjs-to-esm/src/package-json-process.ts index 7dc5d00a..d645d9a9 100644 --- a/recipes/cjs-to-esm/src/package-json-process.ts +++ b/recipes/cjs-to-esm/src/package-json-process.ts @@ -1,48 +1,6 @@ import type { Edit, SgRoot, SgNode } from '@codemod.com/jssg-types/main'; import type Json from '@codemod.com/jssg-types/langs/json'; -const TARGET_NODE_TEXT = '"node": "^20.19.0 || >=22.12.0"'; - -// in future, I (@AugustinMauroy) want to use `@vlt/semver` to compare versions properly -function rebuildEnginesText(node: SgNode): { - text: string; - changed: boolean; -} { - const existingText = node.text().trim(); - const valueNode = node.child(2); - if (!valueNode?.is('object')) return { text: existingText, changed: false }; - - const innerChildren = valueNode.children(); - const innerPairs: SgNode[] = []; - for (const ic of innerChildren) if (ic.is('pair')) innerPairs.push(ic); - - const innerTexts: string[] = []; - let nodePresent = false; - let nodeUpdated = false; - - for (const ip of innerPairs) { - const keyNode = ip.child(0); - if (!keyNode) continue; - const name = keyNode.text().replace(/^"|"$/g, ''); - const trimmed = ip.text().trim(); - - if (name === 'node') { - nodePresent = true; - if (trimmed !== TARGET_NODE_TEXT) nodeUpdated = true; - innerTexts.push(TARGET_NODE_TEXT); - } else { - innerTexts.push(trimmed); - } - } - - if (!nodePresent || !nodeUpdated) - return { text: existingText, changed: false }; - - const newValue = `{\n ${innerTexts.join(',\n ')}\n }`; - - return { text: `"engines": ${newValue}`, changed: true }; -} - /** * @see https://github.com/nodejs/package-examples/tree/main/guide/05-cjs-esm-migration/migrating-package-json */ @@ -50,47 +8,9 @@ export default function transform(root: SgRoot): string | null { const rootNode = root.root(); const edits: Edit[] = []; - const mainObject = rootNode.find({ - rule: { kind: 'object', inside: { kind: 'document' } }, - }); - - // if no main object, means not a valid package.json - if (!mainObject) return null; - - const childNodes = mainObject.children(); - const topPairs: SgNode[] = []; - for (const c of childNodes) if (c.is('pair')) topPairs.push(c); - - const existing: Record> = {}; - const orderedKeys: string[] = []; - for (const p of topPairs) { - const keyNode = p.child(0); - if (!keyNode) continue; - const keyText = keyNode.text().replace(/^"|"$/g, ''); - existing[keyText] = p; - orderedKeys.push(keyText); - } - - const pairTexts: string[] = []; - let enginesChanged = false; - - for (const k of orderedKeys) { - if (k === 'engines') { - const { text, changed } = rebuildEnginesText(existing[k]); - pairTexts.push(text); - if (changed) enginesChanged = true; - } else { - pairTexts.push(existing[k].text().trim()); - } - } - - const typeMissing = !('type' in existing); - if (!typeMissing && !enginesChanged) return null; - - if (typeMissing) pairTexts.push('"type": "module"'); + // do some stuff - const newObjText = `{\n ${pairTexts.join(',\n ')}\n}\n`; - edits.push(mainObject.replace(newObjText)); + if (!edits.length) return null; return rootNode.commitEdits(edits); } diff --git a/recipes/cjs-to-esm/tests/context-local-variable/.gitkeep b/recipes/cjs-to-esm/tests/context-local-variable/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/recipes/cjs-to-esm/tests/context-local-variable/basic/expected.js b/recipes/cjs-to-esm/tests/context-local-variable/basic/expected.js deleted file mode 100644 index 19cf96d1..00000000 --- a/recipes/cjs-to-esm/tests/context-local-variable/basic/expected.js +++ /dev/null @@ -1,4 +0,0 @@ -console.log(import.meta.filename); -console.log(import.meta.dirname); -console.log(import.meta.main); -console.log(import.meta.resolve('./foo')); diff --git a/recipes/cjs-to-esm/tests/context-local-variable/basic/input.js b/recipes/cjs-to-esm/tests/context-local-variable/basic/input.js deleted file mode 100644 index 49ca2677..00000000 --- a/recipes/cjs-to-esm/tests/context-local-variable/basic/input.js +++ /dev/null @@ -1,4 +0,0 @@ -console.log(__filename); -console.log(__dirname); -console.log(require.main); -console.log(require.resolve('./foo')); diff --git a/recipes/cjs-to-esm/tests/export/.gitkeep b/recipes/cjs-to-esm/tests/export/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/recipes/cjs-to-esm/tests/export/exports-property-to-named/expected.js b/recipes/cjs-to-esm/tests/export/exports-property-to-named/expected.js deleted file mode 100644 index a2f8840e..00000000 --- a/recipes/cjs-to-esm/tests/export/exports-property-to-named/expected.js +++ /dev/null @@ -1,2 +0,0 @@ -export class Foo { } -export const bar = 'bar'; diff --git a/recipes/cjs-to-esm/tests/export/exports-property-to-named/input.js b/recipes/cjs-to-esm/tests/export/exports-property-to-named/input.js deleted file mode 100644 index f81f6139..00000000 --- a/recipes/cjs-to-esm/tests/export/exports-property-to-named/input.js +++ /dev/null @@ -1,3 +0,0 @@ -class Foo { } -exports.Foo = Foo; -exports.bar = 'bar'; diff --git a/recipes/cjs-to-esm/tests/export/module-exports-objectliteral/expected.js b/recipes/cjs-to-esm/tests/export/module-exports-objectliteral/expected.js deleted file mode 100644 index 3e498659..00000000 --- a/recipes/cjs-to-esm/tests/export/module-exports-objectliteral/expected.js +++ /dev/null @@ -1,2 +0,0 @@ -class Baz { } -export { Baz }; diff --git a/recipes/cjs-to-esm/tests/export/module-exports-objectliteral/input.js b/recipes/cjs-to-esm/tests/export/module-exports-objectliteral/input.js deleted file mode 100644 index 16e12824..00000000 --- a/recipes/cjs-to-esm/tests/export/module-exports-objectliteral/input.js +++ /dev/null @@ -1,2 +0,0 @@ -class Baz { } -module.exports = { Baz }; diff --git a/recipes/cjs-to-esm/tests/import/.gitkeep b/recipes/cjs-to-esm/tests/import/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/recipes/cjs-to-esm/tests/import/destructure-require-to-named/expected.js b/recipes/cjs-to-esm/tests/import/destructure-require-to-named/expected.js deleted file mode 100644 index 7a85a744..00000000 --- a/recipes/cjs-to-esm/tests/import/destructure-require-to-named/expected.js +++ /dev/null @@ -1,3 +0,0 @@ -import { join } from 'node:path'; - -export { join }; diff --git a/recipes/cjs-to-esm/tests/import/destructure-require-to-named/input.js b/recipes/cjs-to-esm/tests/import/destructure-require-to-named/input.js deleted file mode 100644 index f658f127..00000000 --- a/recipes/cjs-to-esm/tests/import/destructure-require-to-named/input.js +++ /dev/null @@ -1,3 +0,0 @@ -const { join } = require('path'); - -module.exports = { join }; diff --git a/recipes/cjs-to-esm/tests/import/extensionless-path-add-js-extension/expected.js b/recipes/cjs-to-esm/tests/import/extensionless-path-add-js-extension/expected.js deleted file mode 100644 index 51ac73f1..00000000 --- a/recipes/cjs-to-esm/tests/import/extensionless-path-add-js-extension/expected.js +++ /dev/null @@ -1,3 +0,0 @@ -import lib from './lib.js'; - -export default lib; diff --git a/recipes/cjs-to-esm/tests/import/extensionless-path-add-js-extension/input.js b/recipes/cjs-to-esm/tests/import/extensionless-path-add-js-extension/input.js deleted file mode 100644 index d21f2490..00000000 --- a/recipes/cjs-to-esm/tests/import/extensionless-path-add-js-extension/input.js +++ /dev/null @@ -1,3 +0,0 @@ -const lib = require('./lib'); - -module.exports = lib; diff --git a/recipes/cjs-to-esm/tests/import/static-require-to-default/expected.js b/recipes/cjs-to-esm/tests/import/static-require-to-default/expected.js deleted file mode 100644 index 577fa6d1..00000000 --- a/recipes/cjs-to-esm/tests/import/static-require-to-default/expected.js +++ /dev/null @@ -1,8 +0,0 @@ -import fs from 'node:fs'; -import pkg from 'pkg'; - -function read(path) { - return fs.readFileSync(path, 'utf8'); -} - -export default { read }; diff --git a/recipes/cjs-to-esm/tests/import/static-require-to-default/input.js b/recipes/cjs-to-esm/tests/import/static-require-to-default/input.js deleted file mode 100644 index 825718a2..00000000 --- a/recipes/cjs-to-esm/tests/import/static-require-to-default/input.js +++ /dev/null @@ -1,8 +0,0 @@ -const fs = require('fs'); -const pkg = require('pkg'); - -function read(path) { - return fs.readFileSync(path, 'utf8'); -} - -module.exports = { read }; diff --git a/recipes/cjs-to-esm/tests/package-json/engines-and-exports/expected/file-1.json b/recipes/cjs-to-esm/tests/package-json/engines-and-exports/expected/file-1.json deleted file mode 100644 index 510b9323..00000000 --- a/recipes/cjs-to-esm/tests/package-json/engines-and-exports/expected/file-1.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "name": "pkg", - "version": "1.0.0", - "exports": { - ".": "./index.js" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "type": "module" -} - diff --git a/recipes/cjs-to-esm/tests/package-json/engines-and-exports/input/file-1.json b/recipes/cjs-to-esm/tests/package-json/engines-and-exports/input/file-1.json deleted file mode 100644 index 36bbb7b5..00000000 --- a/recipes/cjs-to-esm/tests/package-json/engines-and-exports/input/file-1.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "name": "pkg", - "version": "1.0.0", - "exports": { - ".": "./index.js" - }, - "engines": { - "node": ">=18.0.0" - } -} diff --git a/recipes/cjs-to-esm/tests/package-json/engines-suggestion/expected/file-1.json b/recipes/cjs-to-esm/tests/package-json/engines-suggestion/expected/file-1.json deleted file mode 100644 index 6c8d7307..00000000 --- a/recipes/cjs-to-esm/tests/package-json/engines-suggestion/expected/file-1.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "engine-only", - "version": "1.0.0", - "type": "module" -} - diff --git a/recipes/cjs-to-esm/tests/package-json/engines-suggestion/input/file-1.json b/recipes/cjs-to-esm/tests/package-json/engines-suggestion/input/file-1.json deleted file mode 100644 index 5ec020a0..00000000 --- a/recipes/cjs-to-esm/tests/package-json/engines-suggestion/input/file-1.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "engine-only", - "version": "1.0.0" -} diff --git a/recipes/cjs-to-esm/tests/package-json/package-type-suggestion/expected/file-1.json b/recipes/cjs-to-esm/tests/package-json/package-type-suggestion/expected/file-1.json deleted file mode 100644 index 0361968a..00000000 --- a/recipes/cjs-to-esm/tests/package-json/package-type-suggestion/expected/file-1.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "my-logger", - "version": "1.0.0", - "type": "module" -} - diff --git a/recipes/cjs-to-esm/tests/package-json/package-type-suggestion/input/file-1.json b/recipes/cjs-to-esm/tests/package-json/package-type-suggestion/input/file-1.json deleted file mode 100644 index 11bbd038..00000000 --- a/recipes/cjs-to-esm/tests/package-json/package-type-suggestion/input/file-1.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "my-logger", - "version": "1.0.0" -} From 755564aa32b4abbb044c03bcddd43643f2ec6375 Mon Sep 17 00:00:00 2001 From: Augustin Mauroy <97875033+AugustinMauroy@users.noreply.github.com> Date: Mon, 2 Feb 2026 11:56:05 +0100 Subject: [PATCH 10/10] Update package-json-process.ts --- recipes/cjs-to-esm/src/package-json-process.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/recipes/cjs-to-esm/src/package-json-process.ts b/recipes/cjs-to-esm/src/package-json-process.ts index d645d9a9..9d8288d8 100644 --- a/recipes/cjs-to-esm/src/package-json-process.ts +++ b/recipes/cjs-to-esm/src/package-json-process.ts @@ -1,4 +1,4 @@ -import type { Edit, SgRoot, SgNode } from '@codemod.com/jssg-types/main'; +import type { Edit, SgRoot } from '@codemod.com/jssg-types/main'; import type Json from '@codemod.com/jssg-types/langs/json'; /**