Skip to content

feat!: switch CLI to ESM and bump minimum Node.js to 20.19.0#1296

Merged
sorccu merged 20 commits into
next/v8from
simo/sim-242-switch-cli-to-esm-and-bump-minimum-nodejs-to-20190
May 13, 2026
Merged

feat!: switch CLI to ESM and bump minimum Node.js to 20.19.0#1296
sorccu merged 20 commits into
next/v8from
simo/sim-242-switch-cli-to-esm-and-bump-minimum-nodejs-to-20190

Conversation

@sorccu
Copy link
Copy Markdown
Member

@sorccu sorccu commented May 13, 2026

Summary

BREAKING CHANGE: Minimum Node.js version is now ^20.19.0 || >=22.12.0. Both packages now emit ESM instead of CJS.

ESM migration

  • Set "type": "module" and "module": "nodenext" in both packages
  • Add .js extensions to all relative imports
  • Replace __dirname/__filename with import.meta.url equivalents
  • Replace require() calls with createRequire from node:module
  • Convert dynamic await import('execa') to static imports
  • Fix namespace/default imports for ESM-only packages (json5, ora, log-symbols)
  • Convert CJS config and fixture files to ESM (export default instead of module.exports)
  • Add package.json to CJS e2e fixtures that need to stay CJS
  • Disable oclif enableAutoTranspile in bin scripts (prevents oclif from trying to load .ts source via workspace symlinks)
  • Update bin scripts to use oclif's ESM execute() entry point

Dependency updates (previously blocked on ESM/Node 20)

  • chalk 4→5, uuid 11→14, open 8→11, conf 10→15, p-queue 6→9
  • indent-string 4→5, log-symbols 4→7, nanoid 3→5, execa 5→9 (create-cli)
  • ora 5→9, passwd-user 3→4 (create-cli)
  • glob 10→13, rimraf 5→6, minimatch 9→10, dotenv 16→17
  • config 3→4, lint-staged 15→16, tar 7.5.14→7.5.15

Test fixes

  • Fix execa v9 e2e test compat (encoding option, type rename, __dirname)
  • Make formatCheckTitle tests platform-agnostic (log-symbols v7 uses different symbols on Windows)

Test plan

  • Lint passes
  • Unit tests pass (Ubuntu + Windows)
  • E2E tests pass (Ubuntu + Windows)
  • check-ai-context passes

🤖 Generated with Claude Code

sorccu and others added 20 commits May 13, 2026 19:21
BREAKING CHANGE: Drop support for Node.js 18. The new engine range
is ^20.19.0 || >=22.12.0, which ensures require(esm) support for
CJS consumers of the library.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Prerequisite for ESM migration. Node.js ESM requires explicit file
extensions on relative imports.

Test fixtures with their own package.json are excluded since they
represent user projects with independent module resolution.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
BREAKING CHANGE: Both packages now emit ESM instead of CJS.
CJS consumers on Node 20.19+ can still require() the library
thanks to Node's native require(esm) support.

- Set "type": "module" in both packages
- Switch tsconfig module from node16/commonjs to nodenext
- Remove esModuleInterop and ignoreDeprecations (unnecessary with ESM)
- Update bin scripts to use oclif's ESM execute() entry point
- Fix remaining import paths (from '..' → '../index.js', module augmentation)
- Upgrade conf 10→15 and p-queue 6→9 (ESM-only, now compatible)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace Node.js CJS globals with import.meta.url-based alternatives
since the packages now emit ESM.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ESM modules don't have require() in scope. Use createRequire from
node:module for the remaining cases that need synchronous CJS loading:
ts-node file loader, typescript-estree parser, and playwright version
detection.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Now that both packages emit ESM, update all dependencies that were
blocked on ESM-only versions:

- chalk 4.1.2 → 5.6.2
- conf 10.2.0 → 15.1.0 (done in ESM switch commit)
- indent-string 4.0.0 → 5.0.0
- log-symbols 4.1.0 → 7.0.1
- open 8.4.2 → 11.0.0
- p-queue 6.6.2 → 9.2.0 (done in ESM switch commit)
- nanoid 3.3.12 → 5.1.11
- uuid 11.1.1 → 14.0.0
- execa 5.1.1 → 9.6.1 (create-cli)
- ora 5.4.1 → 9.4.0 (create-cli)
- passwd-user 3.0.0 → 4.0.0 (create-cli)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adapt to export changes in the updated ESM versions:
- log-symbols v7: namespace import → default import
- execa v9: default import → named import
- passwd-user v4: default import → named import

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- config 3.3.12 → 4.4.1
- dotenv 16.6.1 → 17.4.2
- glob 10.5.0 → 13.0.6
- lint-staged 15.5.2 → 16.4.0
- minimatch 9.0.9 → 10.2.5
- rimraf 5.0.10 → 6.1.3

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Now that the package is ESM, dynamic import() for execa is no longer
needed. Use regular static imports instead.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Replace __filename/__dirname with import.meta.url equivalent
- Change encoding option from 'utf-8' to 'utf8' (execa v9 requirement)
- Replace ExecaReturnValue type with Result (execa v9 rename)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
json5 only has a default export. The namespace import
(import * as JSON5) puts stringify on JSON5.default.stringify,
causing "JSON5.stringify is not a function" at runtime.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ora only has a default export. Use type-only import for the Ora
interface instead of namespace import.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The config file used module.exports which doesn't work with
"type": "module" in package.json.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
These .js files used module.exports but inherit "type": "module"
from the package.json, so they need to use export default instead.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This CJS fixture uses require() and module.exports intentionally.
Without its own package.json, it inherits "type": "module" from
the CLI's package.json, causing Node to reject the CJS syntax.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Temporary debug commit to diagnose CI-only bootstrap test failures.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
oclif detects workspace-symlinked packages as "linked" plugins and
attempts to load TypeScript source instead of compiled dist, even in
production mode. This fails in ESM because Node can't import .ts
files without a loader.

Disable enableAutoTranspile via oclif settings to always use the
compiled dist output.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace snapshot assertions with toContain checks to avoid
log-symbols v7 using different Unicode symbols on Windows vs
macOS/Linux. Remove unused snapshots.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The account members command was added on main after the ESM migration
branch. Add .js extensions to its relative imports for ESM compat.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@sorccu sorccu force-pushed the simo/sim-242-switch-cli-to-esm-and-bump-minimum-nodejs-to-20190 branch from c491cda to c309779 Compare May 13, 2026 10:23
@sorccu sorccu merged commit 3113218 into next/v8 May 13, 2026
6 checks passed
@sorccu sorccu deleted the simo/sim-242-switch-cli-to-esm-and-bump-minimum-nodejs-to-20190 branch May 13, 2026 10:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant