From 68f4f09deb27d0373aa390257a305b84c0292fb1 Mon Sep 17 00:00:00 2001 From: Jesse Oberstein <7157500+joberstein@users.noreply.github.com> Date: Wed, 15 Nov 2023 23:22:21 -0500 Subject: [PATCH] Fix/3768: Consider ESM when selecting cosmiconfig loaders (#3776) * feat(load): use cosmiconfig-typescript-loader v5 to remove ts-node dependency for @commitlint/load * fix(load): add support for async loaders for ESM applications * chore(load): simplify loader selection for js/cjs files * docs: remove node version restriction from mjs config --- .../load/fixtures/basic-config/.commitlintrc | 5 + .../fixtures/basic-config/.commitlintrc.cjs | 8 ++ .../fixtures/basic-config/.commitlintrc.js | 8 ++ .../fixtures/basic-config/.commitlintrc.json | 8 ++ .../fixtures/basic-config/.commitlintrc.yaml | 5 + .../fixtures/basic-config/.commitlintrc.yml | 5 + .../basic-config/commitlint.config.cjs | 8 ++ .../basic-config/commitlint.config.js | 8 ++ .../basic-config/esm/.commitlintrc.js | 8 ++ .../basic-config/esm/.commitlintrc.mjs | 8 ++ .../basic-config/esm/commitlint.config.js | 8 ++ .../basic-config/esm/commitlint.config.mjs | 8 ++ .../load/fixtures/basic-template/package.json | 3 + @commitlint/load/fixtures/config/package.json | 13 -- .../{config => extends-config}/.commitlintrc | 0 .../.commitlintrc.cjs | 0 .../.commitlintrc.js | 0 .../.commitlintrc.json | 0 .../.commitlintrc.yaml | 0 .../.commitlintrc.yml | 0 .../commitlint.config.cjs | 0 .../commitlint.config.js | 0 .../esm/.commitlintrc.js} | 0 .../esm/.commitlintrc.mjs} | 0 .../extends-config/esm/commitlint.config.js | 6 + .../extends-config/esm/commitlint.config.mjs | 6 + .../first-extended/index.js | 0 .../first-extended/second-extended/index.js | 0 .../fixtures/extends-js-template/package.json | 3 + @commitlint/load/src/load.test.ts | 117 ++++++++++++++---- @commitlint/load/src/utils/load-config.ts | 46 +++---- README.md | 4 +- 32 files changed, 223 insertions(+), 62 deletions(-) create mode 100644 @commitlint/load/fixtures/basic-config/.commitlintrc create mode 100644 @commitlint/load/fixtures/basic-config/.commitlintrc.cjs create mode 100644 @commitlint/load/fixtures/basic-config/.commitlintrc.js create mode 100644 @commitlint/load/fixtures/basic-config/.commitlintrc.json create mode 100644 @commitlint/load/fixtures/basic-config/.commitlintrc.yaml create mode 100644 @commitlint/load/fixtures/basic-config/.commitlintrc.yml create mode 100644 @commitlint/load/fixtures/basic-config/commitlint.config.cjs create mode 100644 @commitlint/load/fixtures/basic-config/commitlint.config.js create mode 100644 @commitlint/load/fixtures/basic-config/esm/.commitlintrc.js create mode 100644 @commitlint/load/fixtures/basic-config/esm/.commitlintrc.mjs create mode 100644 @commitlint/load/fixtures/basic-config/esm/commitlint.config.js create mode 100644 @commitlint/load/fixtures/basic-config/esm/commitlint.config.mjs create mode 100644 @commitlint/load/fixtures/basic-template/package.json delete mode 100644 @commitlint/load/fixtures/config/package.json rename @commitlint/load/fixtures/{config => extends-config}/.commitlintrc (100%) rename @commitlint/load/fixtures/{config => extends-config}/.commitlintrc.cjs (100%) rename @commitlint/load/fixtures/{config => extends-config}/.commitlintrc.js (100%) rename @commitlint/load/fixtures/{config => extends-config}/.commitlintrc.json (100%) rename @commitlint/load/fixtures/{config => extends-config}/.commitlintrc.yaml (100%) rename @commitlint/load/fixtures/{config => extends-config}/.commitlintrc.yml (100%) rename @commitlint/load/fixtures/{config => extends-config}/commitlint.config.cjs (100%) rename @commitlint/load/fixtures/{config => extends-config}/commitlint.config.js (100%) rename @commitlint/load/fixtures/{config/.commitlintrc.mjs => extends-config/esm/.commitlintrc.js} (100%) rename @commitlint/load/fixtures/{config/commitlint.config.mjs => extends-config/esm/.commitlintrc.mjs} (100%) create mode 100644 @commitlint/load/fixtures/extends-config/esm/commitlint.config.js create mode 100644 @commitlint/load/fixtures/extends-config/esm/commitlint.config.mjs rename @commitlint/load/fixtures/{recursive-extends-js-template => extends-js-template}/first-extended/index.js (100%) rename @commitlint/load/fixtures/{recursive-extends-js-template => extends-js-template}/first-extended/second-extended/index.js (100%) create mode 100644 @commitlint/load/fixtures/extends-js-template/package.json diff --git a/@commitlint/load/fixtures/basic-config/.commitlintrc b/@commitlint/load/fixtures/basic-config/.commitlintrc new file mode 100644 index 0000000000..c0c57253b1 --- /dev/null +++ b/@commitlint/load/fixtures/basic-config/.commitlintrc @@ -0,0 +1,5 @@ +formatter: '@commitlint/format' +rules: + zero: [0, 'never'] + one: [1, 'always'] + two: [2, 'never'] \ No newline at end of file diff --git a/@commitlint/load/fixtures/basic-config/.commitlintrc.cjs b/@commitlint/load/fixtures/basic-config/.commitlintrc.cjs new file mode 100644 index 0000000000..2f37065f43 --- /dev/null +++ b/@commitlint/load/fixtures/basic-config/.commitlintrc.cjs @@ -0,0 +1,8 @@ +module.exports = { + formatter: '@commitlint/format', + rules: { + zero: [0, 'never'], + one: [1, 'always'], + two: [2, 'never'], + }, +}; \ No newline at end of file diff --git a/@commitlint/load/fixtures/basic-config/.commitlintrc.js b/@commitlint/load/fixtures/basic-config/.commitlintrc.js new file mode 100644 index 0000000000..2f37065f43 --- /dev/null +++ b/@commitlint/load/fixtures/basic-config/.commitlintrc.js @@ -0,0 +1,8 @@ +module.exports = { + formatter: '@commitlint/format', + rules: { + zero: [0, 'never'], + one: [1, 'always'], + two: [2, 'never'], + }, +}; \ No newline at end of file diff --git a/@commitlint/load/fixtures/basic-config/.commitlintrc.json b/@commitlint/load/fixtures/basic-config/.commitlintrc.json new file mode 100644 index 0000000000..df7c0e9b36 --- /dev/null +++ b/@commitlint/load/fixtures/basic-config/.commitlintrc.json @@ -0,0 +1,8 @@ +{ + "formatter": "@commitlint/format", + "rules": { + "zero": [0, "never"], + "one": [1, "always"], + "two": [2, "never"] + } +} \ No newline at end of file diff --git a/@commitlint/load/fixtures/basic-config/.commitlintrc.yaml b/@commitlint/load/fixtures/basic-config/.commitlintrc.yaml new file mode 100644 index 0000000000..c0c57253b1 --- /dev/null +++ b/@commitlint/load/fixtures/basic-config/.commitlintrc.yaml @@ -0,0 +1,5 @@ +formatter: '@commitlint/format' +rules: + zero: [0, 'never'] + one: [1, 'always'] + two: [2, 'never'] \ No newline at end of file diff --git a/@commitlint/load/fixtures/basic-config/.commitlintrc.yml b/@commitlint/load/fixtures/basic-config/.commitlintrc.yml new file mode 100644 index 0000000000..c0c57253b1 --- /dev/null +++ b/@commitlint/load/fixtures/basic-config/.commitlintrc.yml @@ -0,0 +1,5 @@ +formatter: '@commitlint/format' +rules: + zero: [0, 'never'] + one: [1, 'always'] + two: [2, 'never'] \ No newline at end of file diff --git a/@commitlint/load/fixtures/basic-config/commitlint.config.cjs b/@commitlint/load/fixtures/basic-config/commitlint.config.cjs new file mode 100644 index 0000000000..2f37065f43 --- /dev/null +++ b/@commitlint/load/fixtures/basic-config/commitlint.config.cjs @@ -0,0 +1,8 @@ +module.exports = { + formatter: '@commitlint/format', + rules: { + zero: [0, 'never'], + one: [1, 'always'], + two: [2, 'never'], + }, +}; \ No newline at end of file diff --git a/@commitlint/load/fixtures/basic-config/commitlint.config.js b/@commitlint/load/fixtures/basic-config/commitlint.config.js new file mode 100644 index 0000000000..2f37065f43 --- /dev/null +++ b/@commitlint/load/fixtures/basic-config/commitlint.config.js @@ -0,0 +1,8 @@ +module.exports = { + formatter: '@commitlint/format', + rules: { + zero: [0, 'never'], + one: [1, 'always'], + two: [2, 'never'], + }, +}; \ No newline at end of file diff --git a/@commitlint/load/fixtures/basic-config/esm/.commitlintrc.js b/@commitlint/load/fixtures/basic-config/esm/.commitlintrc.js new file mode 100644 index 0000000000..fea4afc3ae --- /dev/null +++ b/@commitlint/load/fixtures/basic-config/esm/.commitlintrc.js @@ -0,0 +1,8 @@ +export default { + formatter: '@commitlint/format', + rules: { + zero: [0, 'never'], + one: [1, 'always'], + two: [2, 'never'], + }, +}; \ No newline at end of file diff --git a/@commitlint/load/fixtures/basic-config/esm/.commitlintrc.mjs b/@commitlint/load/fixtures/basic-config/esm/.commitlintrc.mjs new file mode 100644 index 0000000000..fea4afc3ae --- /dev/null +++ b/@commitlint/load/fixtures/basic-config/esm/.commitlintrc.mjs @@ -0,0 +1,8 @@ +export default { + formatter: '@commitlint/format', + rules: { + zero: [0, 'never'], + one: [1, 'always'], + two: [2, 'never'], + }, +}; \ No newline at end of file diff --git a/@commitlint/load/fixtures/basic-config/esm/commitlint.config.js b/@commitlint/load/fixtures/basic-config/esm/commitlint.config.js new file mode 100644 index 0000000000..fea4afc3ae --- /dev/null +++ b/@commitlint/load/fixtures/basic-config/esm/commitlint.config.js @@ -0,0 +1,8 @@ +export default { + formatter: '@commitlint/format', + rules: { + zero: [0, 'never'], + one: [1, 'always'], + two: [2, 'never'], + }, +}; \ No newline at end of file diff --git a/@commitlint/load/fixtures/basic-config/esm/commitlint.config.mjs b/@commitlint/load/fixtures/basic-config/esm/commitlint.config.mjs new file mode 100644 index 0000000000..fea4afc3ae --- /dev/null +++ b/@commitlint/load/fixtures/basic-config/esm/commitlint.config.mjs @@ -0,0 +1,8 @@ +export default { + formatter: '@commitlint/format', + rules: { + zero: [0, 'never'], + one: [1, 'always'], + two: [2, 'never'], + }, +}; \ No newline at end of file diff --git a/@commitlint/load/fixtures/basic-template/package.json b/@commitlint/load/fixtures/basic-template/package.json new file mode 100644 index 0000000000..0cef8e445f --- /dev/null +++ b/@commitlint/load/fixtures/basic-template/package.json @@ -0,0 +1,3 @@ +{ + "name": "load-test-js" +} \ No newline at end of file diff --git a/@commitlint/load/fixtures/config/package.json b/@commitlint/load/fixtures/config/package.json deleted file mode 100644 index 72a2dd6652..0000000000 --- a/@commitlint/load/fixtures/config/package.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "commitlint": { - "extends": [ - "./first-extended" - ], - "rules": { - "zero": [ - 0, - "never" - ] - } - } -} \ No newline at end of file diff --git a/@commitlint/load/fixtures/config/.commitlintrc b/@commitlint/load/fixtures/extends-config/.commitlintrc similarity index 100% rename from @commitlint/load/fixtures/config/.commitlintrc rename to @commitlint/load/fixtures/extends-config/.commitlintrc diff --git a/@commitlint/load/fixtures/config/.commitlintrc.cjs b/@commitlint/load/fixtures/extends-config/.commitlintrc.cjs similarity index 100% rename from @commitlint/load/fixtures/config/.commitlintrc.cjs rename to @commitlint/load/fixtures/extends-config/.commitlintrc.cjs diff --git a/@commitlint/load/fixtures/config/.commitlintrc.js b/@commitlint/load/fixtures/extends-config/.commitlintrc.js similarity index 100% rename from @commitlint/load/fixtures/config/.commitlintrc.js rename to @commitlint/load/fixtures/extends-config/.commitlintrc.js diff --git a/@commitlint/load/fixtures/config/.commitlintrc.json b/@commitlint/load/fixtures/extends-config/.commitlintrc.json similarity index 100% rename from @commitlint/load/fixtures/config/.commitlintrc.json rename to @commitlint/load/fixtures/extends-config/.commitlintrc.json diff --git a/@commitlint/load/fixtures/config/.commitlintrc.yaml b/@commitlint/load/fixtures/extends-config/.commitlintrc.yaml similarity index 100% rename from @commitlint/load/fixtures/config/.commitlintrc.yaml rename to @commitlint/load/fixtures/extends-config/.commitlintrc.yaml diff --git a/@commitlint/load/fixtures/config/.commitlintrc.yml b/@commitlint/load/fixtures/extends-config/.commitlintrc.yml similarity index 100% rename from @commitlint/load/fixtures/config/.commitlintrc.yml rename to @commitlint/load/fixtures/extends-config/.commitlintrc.yml diff --git a/@commitlint/load/fixtures/config/commitlint.config.cjs b/@commitlint/load/fixtures/extends-config/commitlint.config.cjs similarity index 100% rename from @commitlint/load/fixtures/config/commitlint.config.cjs rename to @commitlint/load/fixtures/extends-config/commitlint.config.cjs diff --git a/@commitlint/load/fixtures/config/commitlint.config.js b/@commitlint/load/fixtures/extends-config/commitlint.config.js similarity index 100% rename from @commitlint/load/fixtures/config/commitlint.config.js rename to @commitlint/load/fixtures/extends-config/commitlint.config.js diff --git a/@commitlint/load/fixtures/config/.commitlintrc.mjs b/@commitlint/load/fixtures/extends-config/esm/.commitlintrc.js similarity index 100% rename from @commitlint/load/fixtures/config/.commitlintrc.mjs rename to @commitlint/load/fixtures/extends-config/esm/.commitlintrc.js diff --git a/@commitlint/load/fixtures/config/commitlint.config.mjs b/@commitlint/load/fixtures/extends-config/esm/.commitlintrc.mjs similarity index 100% rename from @commitlint/load/fixtures/config/commitlint.config.mjs rename to @commitlint/load/fixtures/extends-config/esm/.commitlintrc.mjs diff --git a/@commitlint/load/fixtures/extends-config/esm/commitlint.config.js b/@commitlint/load/fixtures/extends-config/esm/commitlint.config.js new file mode 100644 index 0000000000..bd3061cabb --- /dev/null +++ b/@commitlint/load/fixtures/extends-config/esm/commitlint.config.js @@ -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/extends-config/esm/commitlint.config.mjs b/@commitlint/load/fixtures/extends-config/esm/commitlint.config.mjs new file mode 100644 index 0000000000..bd3061cabb --- /dev/null +++ b/@commitlint/load/fixtures/extends-config/esm/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-js-template/first-extended/index.js b/@commitlint/load/fixtures/extends-js-template/first-extended/index.js similarity index 100% rename from @commitlint/load/fixtures/recursive-extends-js-template/first-extended/index.js rename to @commitlint/load/fixtures/extends-js-template/first-extended/index.js diff --git a/@commitlint/load/fixtures/recursive-extends-js-template/first-extended/second-extended/index.js b/@commitlint/load/fixtures/extends-js-template/first-extended/second-extended/index.js similarity index 100% rename from @commitlint/load/fixtures/recursive-extends-js-template/first-extended/second-extended/index.js rename to @commitlint/load/fixtures/extends-js-template/first-extended/second-extended/index.js diff --git a/@commitlint/load/fixtures/extends-js-template/package.json b/@commitlint/load/fixtures/extends-js-template/package.json new file mode 100644 index 0000000000..0cef8e445f --- /dev/null +++ b/@commitlint/load/fixtures/extends-js-template/package.json @@ -0,0 +1,3 @@ +{ + "name": "load-test-js" +} \ No newline at end of file diff --git a/@commitlint/load/src/load.test.ts b/@commitlint/load/src/load.test.ts index c2b053689e..c4690774ef 100644 --- a/@commitlint/load/src/load.test.ts +++ b/@commitlint/load/src/load.test.ts @@ -188,41 +188,108 @@ test('respects cwd option', async () => { }); }); -const mjsConfigFiles = isDynamicAwaitSupported() - ? ['commitlint.config.mjs', '.commitlintrc.mjs'] - : []; +describe.each([['basic'], ['extends']])('%s config', (template) => { + const isExtendsTemplate = template === 'extends'; -test.each( - [ + const configFiles = [ 'commitlint.config.cjs', 'commitlint.config.js', + 'commitlint.config.mjs', 'package.json', '.commitlintrc', '.commitlintrc.cjs', '.commitlintrc.js', '.commitlintrc.json', + '.commitlintrc.mjs', '.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); - - 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 configTestCases = [ + ...configFiles + .filter((filename) => !filename.endsWith('.mjs')) + .map((filename) => ({filename, isEsm: false})), + ...configFiles + .filter((filename) => + ['.mjs', '.js'].some((ext) => filename.endsWith(ext)) + ) + .map((filename) => ({filename, isEsm: true})), + ]; + + const getConfigContents = ({ + filename, + isEsm, + }): string | NodeJS.ArrayBufferView => { + if (filename === 'package.json') { + const configPath = path.join( + __dirname, + `../fixtures/${template}-config/.commitlintrc.json` + ); + const commitlint = JSON.parse( + readFileSync(configPath, {encoding: 'utf-8'}) + ); + return JSON.stringify({commitlint}); + } else { + const filePath = ['..', 'fixtures', `${template}-config`, filename]; + + if (isEsm) { + filePath.splice(3, 0, 'esm'); + } + + const configPath = path.join(__dirname, filePath.join('/')); + return readFileSync(configPath); + } + }; + + const esmBootstrap = (cwd: string) => { + const packageJsonPath = path.join(cwd, 'package.json'); + const packageJSON = JSON.parse( + readFileSync(packageJsonPath, {encoding: 'utf-8'}) + ); + + writeFileSync( + packageJsonPath, + JSON.stringify({ + ...packageJSON, + type: 'module', + }) + ); + }; + + const templateFolder = [template, isExtendsTemplate ? 'js' : '', 'template'] + .filter((elem) => elem) + .join('-'); + + it.each( + configTestCases + // Skip ESM tests for the extends suite until resolve-extends supports ESM + .filter(({isEsm}) => template !== 'extends' || !isEsm) + // Skip ESM tests if dynamic await is not supported; Jest will crash with a seg fault error + .filter(({isEsm}) => isDynamicAwaitSupported() || !isEsm) + )('$filename, ESM: $isEsm', async ({filename, isEsm}) => { + const cwd = await gitBootstrap(`fixtures/${templateFolder}`); + + if (isEsm) { + esmBootstrap(cwd); + } + + writeFileSync( + path.join(cwd, filename), + getConfigContents({filename, isEsm}) + ); + + const actual = await load({}, {cwd}); + + expect(actual).toMatchObject({ + formatter: '@commitlint/format', + extends: isExtendsTemplate ? ['./first-extended'] : [], + plugins: {}, + rules: { + zero: [0, 'never'], + one: [1, 'always'], + two: [2, 'never'], + }, + }); }); }); diff --git a/@commitlint/load/src/utils/load-config.ts b/@commitlint/load/src/utils/load-config.ts index e08353d8c4..9d4a9b8728 100644 --- a/@commitlint/load/src/utils/load-config.ts +++ b/@commitlint/load/src/utils/load-config.ts @@ -1,10 +1,11 @@ import { cosmiconfig, defaultLoadersSync, - Options, type Loader, + defaultLoaders, } from 'cosmiconfig'; import {TypeScriptLoader} from 'cosmiconfig-typescript-loader'; +import {existsSync, readFileSync} from 'fs'; import path from 'path'; export interface LoadConfigResult { @@ -27,7 +28,12 @@ export async function loadConfig( return tsLoaderInstance(...args); }; - const {searchPlaces, loaders} = getDynamicAwaitConfig(); + // If dynamic await is supported (Node >= v20.8.0) or directory uses ESM, support + // async js/cjs loaders (dynamic import). Otherwise, use synchronous js/cjs loaders. + const loaders = + isDynamicAwaitSupported() || isEsmModule(cwd) + ? defaultLoaders + : defaultLoadersSync; const explorer = cosmiconfig(moduleName, { searchPlaces: [ @@ -40,22 +46,22 @@ export async function loadConfig( `.${moduleName}rc.yml`, `.${moduleName}rc.js`, `.${moduleName}rc.cjs`, + `.${moduleName}rc.mjs`, `${moduleName}.config.js`, `${moduleName}.config.cjs`, + `${moduleName}.config.mjs`, // files supported by TypescriptLoader `.${moduleName}rc.ts`, `.${moduleName}rc.cts`, `${moduleName}.config.ts`, `${moduleName}.config.cts`, - - ...(searchPlaces || []), ], loaders: { '.ts': tsLoader, '.cts': tsLoader, - - ...(loaders || {}), + '.cjs': loaders['.cjs'], + '.js': loaders['.js'], }, }); @@ -71,7 +77,7 @@ export async function loadConfig( return null; } -// See the following issues for more context: +// See the following issues for more context, contributing to failing Jest tests: // - Issue: https://github.com/nodejs/node/issues/40058 // - Resolution: https://github.com/nodejs/node/pull/48510 (Node v20.8.0) export const isDynamicAwaitSupported = () => { @@ -83,18 +89,14 @@ export const isDynamicAwaitSupported = () => { 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'], - }, - }; +// Is the given directory set up to use ESM (ECMAScript Modules)? +export const isEsmModule = (cwd: string) => { + const packagePath = path.join(cwd, 'package.json'); + + if (!existsSync(packagePath)) { + return false; + } + + const packageJSON = readFileSync(packagePath, {encoding: 'utf-8'}); + return JSON.parse(packageJSON)?.type === 'module'; +}; diff --git a/README.md b/README.md index a70bc84e67..3417f83813 100644 --- a/README.md +++ b/README.md @@ -142,12 +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.mjs` - `.commitlintrc.ts` - `.commitlintrc.cts` - `commitlint.config.js` - `commitlint.config.cjs` - - `commitlint.config.mjs` (Node >= v20.8.0) + - `commitlint.config.mjs` - `commitlint.config.ts` - `commitlint.config.cts` - `commitlint` field in `package.json`