diff --git a/@commitlint/load/fixtures/config/.commitlintrc b/@commitlint/load/fixtures/config/.commitlintrc new file mode 100644 index 0000000000..c612d4f34f --- /dev/null +++ b/@commitlint/load/fixtures/config/.commitlintrc @@ -0,0 +1,4 @@ +extends: + - './first-extended' +rules: + zero: [0, 'never'] \ No newline at end of file diff --git a/@commitlint/load/fixtures/config/.commitlintrc.cjs b/@commitlint/load/fixtures/config/.commitlintrc.cjs new file mode 100644 index 0000000000..c58cb47afc --- /dev/null +++ b/@commitlint/load/fixtures/config/.commitlintrc.cjs @@ -0,0 +1,6 @@ +module.exports = { + extends: ['./first-extended'], + rules: { + zero: [0, 'never'], + }, +}; \ No newline at end of file diff --git a/@commitlint/load/fixtures/recursive-extends-js/.commitlintrc.js b/@commitlint/load/fixtures/config/.commitlintrc.js similarity index 96% rename from @commitlint/load/fixtures/recursive-extends-js/.commitlintrc.js rename to @commitlint/load/fixtures/config/.commitlintrc.js index f90e771292..c58cb47afc 100644 --- a/@commitlint/load/fixtures/recursive-extends-js/.commitlintrc.js +++ b/@commitlint/load/fixtures/config/.commitlintrc.js @@ -3,4 +3,4 @@ module.exports = { rules: { zero: [0, 'never'], }, -}; +}; \ No newline at end of file diff --git a/@commitlint/load/fixtures/recursive-extends-json/.commitlintrc.json b/@commitlint/load/fixtures/config/.commitlintrc.json similarity index 97% rename from @commitlint/load/fixtures/recursive-extends-json/.commitlintrc.json rename to @commitlint/load/fixtures/config/.commitlintrc.json index 97335f47a5..a95915c9f6 100644 --- a/@commitlint/load/fixtures/recursive-extends-json/.commitlintrc.json +++ b/@commitlint/load/fixtures/config/.commitlintrc.json @@ -3,4 +3,4 @@ "rules": { "zero": [0, "never"] } -} +} \ No newline at end of file diff --git a/@commitlint/load/fixtures/config/.commitlintrc.mjs b/@commitlint/load/fixtures/config/.commitlintrc.mjs new file mode 100644 index 0000000000..bd3061cabb --- /dev/null +++ b/@commitlint/load/fixtures/config/.commitlintrc.mjs @@ -0,0 +1,6 @@ +export default { + extends: ['./first-extended'], + rules: { + zero: [0, 'never'], + }, +}; \ No newline at end of file diff --git a/@commitlint/load/fixtures/config/.commitlintrc.yaml b/@commitlint/load/fixtures/config/.commitlintrc.yaml new file mode 100644 index 0000000000..c612d4f34f --- /dev/null +++ b/@commitlint/load/fixtures/config/.commitlintrc.yaml @@ -0,0 +1,4 @@ +extends: + - './first-extended' +rules: + zero: [0, 'never'] \ No newline at end of file diff --git a/@commitlint/load/fixtures/recursive-extends-yaml/.commitlintrc.yml b/@commitlint/load/fixtures/config/.commitlintrc.yml similarity index 65% rename from @commitlint/load/fixtures/recursive-extends-yaml/.commitlintrc.yml rename to @commitlint/load/fixtures/config/.commitlintrc.yml index c7c751a959..c612d4f34f 100644 --- a/@commitlint/load/fixtures/recursive-extends-yaml/.commitlintrc.yml +++ b/@commitlint/load/fixtures/config/.commitlintrc.yml @@ -1,4 +1,4 @@ extends: - './first-extended' rules: - zero: [0, 'never'] + zero: [0, 'never'] \ No newline at end of file diff --git a/@commitlint/load/fixtures/config/commitlint.config.cjs b/@commitlint/load/fixtures/config/commitlint.config.cjs new file mode 100644 index 0000000000..c58cb47afc --- /dev/null +++ b/@commitlint/load/fixtures/config/commitlint.config.cjs @@ -0,0 +1,6 @@ +module.exports = { + extends: ['./first-extended'], + rules: { + zero: [0, 'never'], + }, +}; \ No newline at end of file diff --git a/@commitlint/load/fixtures/config/commitlint.config.js b/@commitlint/load/fixtures/config/commitlint.config.js new file mode 100644 index 0000000000..c58cb47afc --- /dev/null +++ b/@commitlint/load/fixtures/config/commitlint.config.js @@ -0,0 +1,6 @@ +module.exports = { + extends: ['./first-extended'], + rules: { + zero: [0, 'never'], + }, +}; \ No newline at end of file diff --git a/@commitlint/load/fixtures/config/commitlint.config.mjs b/@commitlint/load/fixtures/config/commitlint.config.mjs new file mode 100644 index 0000000000..bd3061cabb --- /dev/null +++ b/@commitlint/load/fixtures/config/commitlint.config.mjs @@ -0,0 +1,6 @@ +export default { + extends: ['./first-extended'], + rules: { + zero: [0, 'never'], + }, +}; \ No newline at end of file diff --git a/@commitlint/load/fixtures/recursive-extends-package/package.json b/@commitlint/load/fixtures/config/package.json similarity index 98% rename from @commitlint/load/fixtures/recursive-extends-package/package.json rename to @commitlint/load/fixtures/config/package.json index 704cc882f1..72a2dd6652 100644 --- a/@commitlint/load/fixtures/recursive-extends-package/package.json +++ b/@commitlint/load/fixtures/config/package.json @@ -10,4 +10,4 @@ ] } } -} +} \ No newline at end of file diff --git a/@commitlint/load/fixtures/recursive-extends-json/first-extended/index.js b/@commitlint/load/fixtures/recursive-extends-js-template/first-extended/index.js similarity index 100% rename from @commitlint/load/fixtures/recursive-extends-json/first-extended/index.js rename to @commitlint/load/fixtures/recursive-extends-js-template/first-extended/index.js diff --git a/@commitlint/load/fixtures/recursive-extends-json/first-extended/second-extended/index.js b/@commitlint/load/fixtures/recursive-extends-js-template/first-extended/second-extended/index.js similarity index 100% rename from @commitlint/load/fixtures/recursive-extends-json/first-extended/second-extended/index.js rename to @commitlint/load/fixtures/recursive-extends-js-template/first-extended/second-extended/index.js diff --git a/@commitlint/load/fixtures/recursive-extends-js/first-extended/index.js b/@commitlint/load/fixtures/recursive-extends-js/first-extended/index.js deleted file mode 100644 index d26b0ff209..0000000000 --- a/@commitlint/load/fixtures/recursive-extends-js/first-extended/index.js +++ /dev/null @@ -1,6 +0,0 @@ -module.exports = { - extends: ['./second-extended'], - rules: { - one: [1, 'never'], - }, -}; diff --git a/@commitlint/load/fixtures/recursive-extends-js/first-extended/second-extended/index.js b/@commitlint/load/fixtures/recursive-extends-js/first-extended/second-extended/index.js deleted file mode 100644 index 64caae544a..0000000000 --- a/@commitlint/load/fixtures/recursive-extends-js/first-extended/second-extended/index.js +++ /dev/null @@ -1,5 +0,0 @@ -module.exports = { - rules: { - two: [2, 'always'], - }, -}; diff --git a/@commitlint/load/fixtures/recursive-extends-package/first-extended/index.js b/@commitlint/load/fixtures/recursive-extends-package/first-extended/index.js deleted file mode 100644 index d26b0ff209..0000000000 --- a/@commitlint/load/fixtures/recursive-extends-package/first-extended/index.js +++ /dev/null @@ -1,6 +0,0 @@ -module.exports = { - extends: ['./second-extended'], - rules: { - one: [1, 'never'], - }, -}; diff --git a/@commitlint/load/fixtures/recursive-extends-package/first-extended/second-extended/index.js b/@commitlint/load/fixtures/recursive-extends-package/first-extended/second-extended/index.js deleted file mode 100644 index b0902ace44..0000000000 --- a/@commitlint/load/fixtures/recursive-extends-package/first-extended/second-extended/index.js +++ /dev/null @@ -1,5 +0,0 @@ -module.exports = { - rules: { - two: [2, 'never'], - }, -}; diff --git a/@commitlint/load/fixtures/recursive-extends-ts/commitlint.config.ts b/@commitlint/load/fixtures/recursive-extends-ts/commitlint.config.ts index 396cef436b..fff038733a 100644 --- a/@commitlint/load/fixtures/recursive-extends-ts/commitlint.config.ts +++ b/@commitlint/load/fixtures/recursive-extends-ts/commitlint.config.ts @@ -1,7 +1,7 @@ import type {UserConfig} from './types'; const Configuration: UserConfig = { - extends: ['./first-extended'], + extends: ['./first-extended/index.ts'], rules: { zero: [0, 'never', 'zero'] } diff --git a/@commitlint/load/fixtures/recursive-extends-ts/first-extended/index.ts b/@commitlint/load/fixtures/recursive-extends-ts/first-extended/index.ts index 6111626aea..d6f3551f95 100644 --- a/@commitlint/load/fixtures/recursive-extends-ts/first-extended/index.ts +++ b/@commitlint/load/fixtures/recursive-extends-ts/first-extended/index.ts @@ -1,6 +1,6 @@ import type {UserConfig} from '../types'; module.exports = { - extends: ['./second-extended'], + extends: ['./second-extended/index.ts'], rules: { one: [1, 'never', 'one'] } diff --git a/@commitlint/load/fixtures/recursive-extends-yaml/first-extended/index.js b/@commitlint/load/fixtures/recursive-extends-yaml/first-extended/index.js deleted file mode 100644 index d26b0ff209..0000000000 --- a/@commitlint/load/fixtures/recursive-extends-yaml/first-extended/index.js +++ /dev/null @@ -1,6 +0,0 @@ -module.exports = { - extends: ['./second-extended'], - rules: { - one: [1, 'never'], - }, -}; diff --git a/@commitlint/load/fixtures/recursive-extends-yaml/first-extended/second-extended/index.js b/@commitlint/load/fixtures/recursive-extends-yaml/first-extended/second-extended/index.js deleted file mode 100644 index 64caae544a..0000000000 --- a/@commitlint/load/fixtures/recursive-extends-yaml/first-extended/second-extended/index.js +++ /dev/null @@ -1,5 +0,0 @@ -module.exports = { - rules: { - two: [2, 'always'], - }, -}; diff --git a/@commitlint/load/src/load.test.ts b/@commitlint/load/src/load.test.ts index a2ec33d1b0..c2b053689e 100644 --- a/@commitlint/load/src/load.test.ts +++ b/@commitlint/load/src/load.test.ts @@ -7,10 +7,12 @@ jest.mock('@scope/commitlint-plugin-example', () => scopedPlugin, { }); import path from 'path'; +import {readFileSync, writeFileSync} from 'fs'; import resolveFrom from 'resolve-from'; import {fix, git, npm} from '@commitlint/test'; import load from './load'; +import {isDynamicAwaitSupported} from './utils/load-config'; const fixBootstrap = (name: string) => fix.bootstrap(name, __dirname); const gitBootstrap = (name: string) => git.bootstrap(name, __dirname); @@ -186,24 +188,30 @@ test('respects cwd option', async () => { }); }); -test('recursive extends', async () => { - const cwd = await gitBootstrap('fixtures/recursive-extends'); - const actual = await load({}, {cwd}); - - expect(actual).toMatchObject({ - formatter: '@commitlint/format', - extends: ['./first-extended'], - plugins: {}, - rules: { - zero: [0, 'never'], - one: [1, 'always'], - two: [2, 'never'], - }, - }); -}); +const mjsConfigFiles = isDynamicAwaitSupported() + ? ['commitlint.config.mjs', '.commitlintrc.mjs'] + : []; + +test.each( + [ + 'commitlint.config.cjs', + 'commitlint.config.js', + 'package.json', + '.commitlintrc', + '.commitlintrc.cjs', + '.commitlintrc.js', + '.commitlintrc.json', + '.commitlintrc.yml', + '.commitlintrc.yaml', + ...mjsConfigFiles, + ].map((configFile) => [configFile]) +)('recursive extends with %s', async (configFile) => { + const cwd = await gitBootstrap(`fixtures/recursive-extends-js-template`); + const configPath = path.join(__dirname, `../fixtures/config/${configFile}`); + const config = readFileSync(configPath); + + writeFileSync(path.join(cwd, configFile), config); -test('recursive extends with json file', async () => { - const cwd = await gitBootstrap('fixtures/recursive-extends-json'); const actual = await load({}, {cwd}); expect(actual).toMatchObject({ @@ -218,63 +226,13 @@ test('recursive extends with json file', async () => { }); }); -test('recursive extends with yaml file', async () => { - const cwd = await gitBootstrap('fixtures/recursive-extends-yaml'); - const actual = await load({}, {cwd}); - - expect(actual).toMatchObject({ - formatter: '@commitlint/format', - extends: ['./first-extended'], - plugins: {}, - rules: { - zero: [0, 'never'], - one: [1, 'never'], - two: [2, 'always'], - }, - }); -}); - -test('recursive extends with js file', async () => { - const cwd = await gitBootstrap('fixtures/recursive-extends-js'); - const actual = await load({}, {cwd}); - - expect(actual).toMatchObject({ - formatter: '@commitlint/format', - extends: ['./first-extended'], - plugins: {}, - rules: { - zero: [0, 'never'], - one: [1, 'never'], - two: [2, 'always'], - }, - }); -}); - -test('recursive extends with package.json file', async () => { - const cwd = await gitBootstrap('fixtures/recursive-extends-package'); - const actual = await load({}, {cwd}); - - expect(actual).toMatchObject({ - formatter: '@commitlint/format', - extends: ['./first-extended'], - plugins: {}, - rules: { - zero: [0, 'never'], - one: [1, 'never'], - two: [2, 'never'], - }, - }); -}); - -// fails since a jest update: https://github.com/conventional-changelog/commitlint/pull/3362 -// eslint-disable-next-line jest/no-disabled-tests -test.skip('recursive extends with ts file', async () => { +test('recursive extends with ts file', async () => { const cwd = await gitBootstrap('fixtures/recursive-extends-ts'); const actual = await load({}, {cwd}); expect(actual).toMatchObject({ formatter: '@commitlint/format', - extends: ['./first-extended'], + extends: ['./first-extended/index.ts'], plugins: {}, rules: { zero: [0, 'never', 'zero'], diff --git a/@commitlint/load/src/utils/load-config.ts b/@commitlint/load/src/utils/load-config.ts index b8cc16b98b..e08353d8c4 100644 --- a/@commitlint/load/src/utils/load-config.ts +++ b/@commitlint/load/src/utils/load-config.ts @@ -1,4 +1,9 @@ -import {cosmiconfig, type Loader} from 'cosmiconfig'; +import { + cosmiconfig, + defaultLoadersSync, + Options, + type Loader, +} from 'cosmiconfig'; import {TypeScriptLoader} from 'cosmiconfig-typescript-loader'; import path from 'path'; @@ -8,12 +13,12 @@ export interface LoadConfigResult { isEmpty?: boolean; } +const moduleName = 'commitlint'; + export async function loadConfig( cwd: string, configPath?: string ): Promise { - const moduleName = 'commitlint'; - let tsLoaderInstance: Loader | undefined; const tsLoader: Loader = (...args) => { if (!tsLoaderInstance) { @@ -22,6 +27,8 @@ export async function loadConfig( return tsLoaderInstance(...args); }; + const {searchPlaces, loaders} = getDynamicAwaitConfig(); + const explorer = cosmiconfig(moduleName, { searchPlaces: [ // cosmiconfig overrides default searchPlaces if any new search place is added (For e.g. `*.ts` files), @@ -41,10 +48,14 @@ export async function loadConfig( `.${moduleName}rc.cts`, `${moduleName}.config.ts`, `${moduleName}.config.cts`, + + ...(searchPlaces || []), ], loaders: { '.ts': tsLoader, '.cts': tsLoader, + + ...(loaders || {}), }, }); @@ -59,3 +70,31 @@ export async function loadConfig( return null; } + +// See the following issues for more context: +// - Issue: https://github.com/nodejs/node/issues/40058 +// - Resolution: https://github.com/nodejs/node/pull/48510 (Node v20.8.0) +export const isDynamicAwaitSupported = () => { + const [major, minor] = process.version + .replace('v', '') + .split('.') + .map((val) => parseInt(val)); + + return major >= 20 && minor >= 8; +}; + +// If dynamic await is supported (Node >= v20.8.0), support mjs config. +// Otherwise, don't support mjs and use synchronous js/cjs loaders. +export const getDynamicAwaitConfig = (): Partial => + isDynamicAwaitSupported() + ? { + searchPlaces: [`.${moduleName}rc.mjs`, `${moduleName}.config.mjs`], + loaders: {}, + } + : { + searchPlaces: [], + loaders: { + '.cjs': defaultLoadersSync['.cjs'], + '.js': defaultLoadersSync['.js'], + }, + }; diff --git a/README.md b/README.md index fb3a68173f..a70bc84e67 100644 --- a/README.md +++ b/README.md @@ -142,10 +142,12 @@ Check the [husky documentation](https://typicode.github.io/husky/#/?id=manual) o - `.commitlintrc.yml` - `.commitlintrc.js` - `.commitlintrc.cjs` + - `.commitlintrc.mjs` (Node >= v20.8.0) - `.commitlintrc.ts` - `.commitlintrc.cts` - `commitlint.config.js` - `commitlint.config.cjs` + - `commitlint.config.mjs` (Node >= v20.8.0) - `commitlint.config.ts` - `commitlint.config.cts` - `commitlint` field in `package.json` diff --git a/package.json b/package.json index 612c350c6a..a8ca4dfc3d 100644 --- a/package.json +++ b/package.json @@ -19,8 +19,8 @@ "publish": "lerna publish --conventional-commits", "reinstall": "yarn clean && yarn install", "start": "yarn watch", - "test": "cross-env HOME=$PWD jest", - "test-ci": "cross-env HOME=$PWD jest --runInBand", + "test": "cross-env HOME=$PWD NODE_OPTIONS=--experimental-vm-modules jest", + "test-ci": "yarn test --runInBand", "postinstall": "yarn husky install" }, "commitlint": { diff --git a/yarn.lock b/yarn.lock index 5a6f5626c3..5efbb3cedc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3330,13 +3330,13 @@ cosmiconfig@^7.0.0: yaml "^1.10.0" cosmiconfig@^8.0.0: - version "8.1.3" - resolved "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.1.3.tgz#0e614a118fcc2d9e5afc2f87d53cd09931015689" - integrity sha512-/UkO2JKI18b5jVMJUp0lvKFMpa/Gye+ZgZjKD+DGEN9y7NRcf/nK1A0sp67ONmKtnDCNMS44E6jrk0Yc3bDuUw== + version "8.3.6" + resolved "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz#060a2b871d66dba6c8538ea1118ba1ac16f5fae3" + integrity sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA== dependencies: - import-fresh "^3.2.1" + import-fresh "^3.3.0" js-yaml "^4.1.0" - parse-json "^5.0.0" + parse-json "^5.2.0" path-type "^4.0.0" cp-file@^7.0.0: @@ -4886,7 +4886,7 @@ ignore@^5.0.4, ignore@^5.2.0, ignore@^5.2.4: resolved "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324" integrity sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ== -import-fresh@^3.0.0, import-fresh@^3.2.1: +import-fresh@^3.0.0, import-fresh@^3.2.1, import-fresh@^3.3.0: version "3.3.0" resolved "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==