Skip to content

feat: support ESM configuration files#306

Merged
TimothyJones merged 1 commit into
masterfrom
feat/esm-config-files
Jul 21, 2026
Merged

feat: support ESM configuration files#306
TimothyJones merged 1 commit into
masterfrom
feat/esm-config-files

Conversation

@TimothyJones

Copy link
Copy Markdown
Member

Summary

Adds support for ES module configuration files: .versionrc.mjs, and .versionrc.js written with export default (object or function). CommonJS configs behave exactly as before.

Background

On Node >= 22.12, require() of an ES module succeeds (unflagged require(esm)) and returns a module-namespace wrapper ({ __esModule: true, default: {...} }). getConfiguration never unwrapped it, so an ESM .versionrc.js loaded without any error while the user's configuration was silently ignored — verified with the real CLI, where an ESM config's tagPrefix was not applied while the identical CommonJS config's was.

Changes

  • lib/configuration.js: add .versionrc.mjs to the config-file list, route .mjs through the JS loading branch, and unwrap the module-namespace wrapper (detected via __esModule / Symbol.toStringTag === 'Module') to its default export before the existing function-vs-object handling. getConfiguration remains synchronous — no import(), no API change. The unwrap also covers Babel/TS-transpiled configs, which set __esModule themselves.
  • test/esm-config.integration-test.js (new): four tests — ESM object in .versionrc.js, ESM object in .versionrc.mjs, ESM default-exported function, and a CommonJS control. These spawn bin/cli.js --dry-run in a child process because vitest's module runner intercepts createRequire and throws a Babel SyntaxError on ESM sources, a failure mode that doesn't exist in production; only a child process observes real require(esm) behavior.
  • README.md: document the new formats and the Node >= 22.12 floor for ESM configs.

Notes

  • engines is >=22, but require(esm) was unflagged in 22.12 — on Node 22.0–22.11 an ESM config fails with Node's own ERR_REQUIRE_ESM. Documented in the README rather than adding a catch-and-rethrow; happy to add a friendlier error if preferred.

Test plan

  • npm test — 142 tests passing, plus eslint and prettier checks
  • New child-process integration tests cover ESM object / ESM function / .mjs / CJS control

🤖 Generated with Claude Code

Load .versionrc.mjs (and ESM-syntax .versionrc.js) via Node's
require(esm), unwrapping the module-namespace wrapper to its default
export. getConfiguration stays synchronous. Requires Node >= 22.12,
where require(esm) is unflagged.

Previously an ESM config loaded without error on Node >= 22.12 but was
silently ignored, because the namespace wrapper passed the object check
while the user's settings sat unread under `.default`.

Tests spawn bin/cli.js in a child process: vitest's module runner
intercepts createRequire and rejects ESM sources with a Babel
SyntaxError, so in-process tests cannot observe real require(esm)
behavior.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@TimothyJones
TimothyJones merged commit 9e0e5ab into master Jul 21, 2026
9 checks passed
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