Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,9 +188,9 @@ You can configure `commit-and-tag-version` either by:
> Note for users who have migrated to
> `commit-and-tag-version` from `standard-version`: the previous package.json configuration key of `standard-version` will still work.

2. Creating a `.versionrc`, `.versionrc.json` or `.versionrc.js`.
2. Creating a `.versionrc`, `.versionrc.json`, `.versionrc.js`, `.versionrc.cjs` or `.versionrc.mjs`.

- If you are using a `.versionrc.js` your default export must be a configuration object, or a function returning a configuration object.
- If you are using a `.versionrc.js`, `.versionrc.cjs` or `.versionrc.mjs` your default export must be a configuration object, or a function returning a configuration object. Both CommonJS and ES module configs are supported (ES module configs require Node.js >= 22.12).

Any of the command line parameters accepted by `commit-and-tag-version` can instead
be provided via configuration. Please refer to the [conventional-changelog-config-spec](https://github.com/conventional-changelog/conventional-changelog-config-spec/) for details on available configuration options.
Expand Down Expand Up @@ -342,7 +342,7 @@ commit-and-tag-version -c ./path/to/.versionrc.js
All config file formats can be used:

```sh
commit-and-tag-version -c ./path/to/.versionrc[.js|.cjs|.json]
commit-and-tag-version -c ./path/to/.versionrc[.js|.cjs|.mjs|.json]
```

### Signing Commits and Tags
Expand Down
14 changes: 12 additions & 2 deletions lib/configuration.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const require = createRequire(import.meta.url);
const CONFIGURATION_FILES = [
'.versionrc',
'.versionrc.cjs',
'.versionrc.mjs',
'.versionrc.json',
'.versionrc.js',
];
Expand All @@ -23,8 +24,17 @@ export function getConfiguration(configFile) {
return config;
}
const ext = path.extname(configPath);
if (ext === '.js' || ext === '.cjs') {
const jsConfiguration = require(configPath);
if (ext === '.js' || ext === '.cjs' || ext === '.mjs') {
let jsConfiguration = require(configPath);
// ES modules arrive as a namespace wrapper (via Node's `require(esm)`,
// available since 22.12); the configuration is its default export.
if (
jsConfiguration != null &&
(jsConfiguration.__esModule ||
jsConfiguration[Symbol.toStringTag] === 'Module')
) {
jsConfiguration = jsConfiguration.default;
}
if (typeof jsConfiguration === 'function') {
config = jsConfiguration();
} else {
Expand Down
97 changes: 97 additions & 0 deletions test/esm-config.integration-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import shell from 'shelljs';
import fs from 'fs';
import { execFileSync } from 'child_process';
import { fileURLToPath } from 'url';

const CLI = fileURLToPath(new URL('../bin/cli.js', import.meta.url));

/**
* These tests spawn the real CLI in a child process instead of using the usual
* in-process harness. That is deliberate: `getConfiguration` loads ESM config
* files through Node's `require(esm)` (available since 22.12), and under vitest
* that require is intercepted by the module runner, which throws a Babel
* SyntaxError on ESM sources — a failure mode that does not exist in
* production. Only a child process observes the real behavior.
*/

function setupTestDirectory() {
shell.rm('-rf', 'esm-config-temp');
shell.config.silent = true;
shell.mkdir('esm-config-temp');
shell.cd('esm-config-temp');
shell.exec('git init');
shell.exec('git config commit.gpgSign false');
shell.exec('git config core.autocrlf false');
shell.exec('git commit --allow-empty -m"root-commit"');
}

function resetShell() {
shell.cd('../');
shell.rm('-rf', 'esm-config-temp');
}

function runCli() {
return execFileSync(process.execPath, [CLI, '--dry-run'], {
encoding: 'utf-8',
});
}

describe('ESM config files', function () {
beforeEach(function () {
setupTestDirectory();

fs.writeFileSync(
'package.json',
JSON.stringify({ version: '1.0.0' }),
'utf-8',
);
});

afterEach(function () {
resetShell();
});

it('reads an ES module config-object from .versionrc.js', function () {
fs.writeFileSync(
'.versionrc.js',
'export default { tagPrefix: "custom-" }',
'utf-8',
);

const output = runCli();
expect(output).toContain('tagging release custom-1.0.1');
});

it('reads an ES module config from .versionrc.mjs', function () {
fs.writeFileSync(
'.versionrc.mjs',
'export default { tagPrefix: "custom-" }',
'utf-8',
);

const output = runCli();
expect(output).toContain('tagging release custom-1.0.1');
});

it('evaluates an ES module config-function', function () {
fs.writeFileSync(
'.versionrc.mjs',
'export default function () { return { tagPrefix: "custom-" }; }',
'utf-8',
);

const output = runCli();
expect(output).toContain('tagging release custom-1.0.1');
});

it('applies the same configuration when written as CommonJS', function () {
fs.writeFileSync(
'.versionrc.js',
'module.exports = { tagPrefix: "custom-" }',
'utf-8',
);

const output = runCli();
expect(output).toContain('tagging release custom-1.0.1');
});
});