feat(eslint): rename eslint.config.js to eslint.config.cjs to resolve them as CommonJS (#29334)

This PR updates our generators to use `eslint.config.cjs` instead of
`eslint.config.js` so that Node resolution will treat it as CommonJS.
This solves an issue where having `"type": "module"` in `package.json`
will result in an error when Node tries to resolve the config file as
ESM.

Also allows us to clean up out Remix generators to not have to rename to
`eslint.config.cjs` to solve the same issue.

<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->

## Current Behavior
<!-- This is the behavior we have today -->

## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #
This commit is contained in:
Jack Hsu 2024-12-18 16:34:10 -05:00 committed by GitHub
parent a675bd2a06
commit b9c0e3db5f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
58 changed files with 774 additions and 336 deletions

View File

@ -69,7 +69,7 @@ Sometimes we intentionally want to add or remove a dependency from our `package.
"checkObsoleteDependencies": true, // toggle to disable
"checkVersionMismatches": true, // toggle to disable
"ignoredDependencies": ["lodash"], // these libs will be omitted from checks
"ignoredFiles": ["webpack.config.js", "eslint.config.js"], // list of files that should be skipped for check
"ignoredFiles": ["webpack.config.js", "eslint.config.cjs"], // list of files that should be skipped for check
"includeTransitiveDependencies": true, // collect dependencies transitively from children
"useLocalPathsForWorkspaceDependencies": true // toggle to disable
}

View File

@ -140,7 +140,7 @@
"default": true
}
},
"examplesFile": "Linter can be configured in multiple ways. The basic way is to provide only `lintFilePatterns`, which is a mandatory property. This tells us where to look for files to lint.\n\n`project.json`:\n\n```json\n\"lint\": {\n \"executor\": \"@nx/eslint:lint\",\n \"options\": {\n \"lintFilePatterns\": [\"apps/frontend/**/*.ts\"]\n }\n}\n```\n\n## Examples\n\n{% tabs %}\n{% tab label=\"Fixing linter issues\" %}\n\nLinter provides an automated way of fixing known issues. To ensure that those changes are properly cached, we need to add an `outputs` property to the `lint` target. Omitting the `outputs` property would produce an invalid cache record. Both of these properties are set by default when scaffolding a new project.\n\n```json\n\"lint\": {\n \"executor\": \"@nx/eslint:lint\",\n \"outputs\": [\"{options.outputFile}\"],\n \"options\": {\n \"lintFilePatterns\": [\"apps/frontend/**/*.ts\"]\n }\n}\n```\n\nWith these settings, we can run the command with a `--fix` flag:\n\n```bash\nnx run frontend:lint --fix\n```\n\nWe can also set this flag via project configuration to always fix files when running lint:\n\n```json\n\"lint\": {\n \"executor\": \"@nx/eslint:lint\",\n \"outputs\": [\"{options.outputFile}\"],\n \"options\": {\n \"lintFilePatterns\": [\"apps/frontend/**/*.ts\"],\n \"fix\": true\n }\n}\n```\n\n{% /tab %}\n{% tab label=\"Custom output format\" %}\n\nESLint executor uses the `stylish` output format by default. You can change this by specifying the `format` property:\n\n```json\n\"lint\": {\n \"executor\": \"@nx/eslint:lint\",\n \"outputs\": [\"{options.outputFile}\"],\n \"options\": {\n \"lintFilePatterns\": [\"apps/frontend/**/*.ts\"],\n \"format\": \"compact\"\n }\n}\n```\n\n{% /tab %}\n{% tab label=\"Silence warnings\" %}\n\nMigrated or legacy projects tend to have an overwhelming amount of lint errors. We might want to change those temporarily to be warnings so they don't block the development. But they would still clutter the report. We can run the command with `--quiet` to hide warning (errors would still break the lint):\n\n```bash\nnx run frontend:lint --quiet\n```\n\nWe can also set this via project configuration as a default option.\n\n```json\n\"lint\": {\n \"executor\": \"@nx/eslint:lint\",\n \"outputs\": [\"{options.outputFile}\"],\n \"options\": {\n \"lintFilePatterns\": [\"apps/frontend/**/*.ts\"],\n \"quiet\": true\n }\n}\n```\n\n{% /tab %}\n{% tab label=\"Flat Config file\" %}\n\n`ESLint` provides several ways of specifying the configuration. The default one is using `.eslintrc.json` but you can override it by setting the `eslintConfig` flag. The new `Flat Config` is now also supported:\n\n```json\n\"lint\": {\n \"executor\": \"@nx/eslint:lint\",\n \"outputs\": [\"{options.outputFile}\"],\n \"options\": {\n \"lintFilePatterns\": [\"apps/frontend/**/*.ts\"],\n \"eslintConfig\": \"eslint.config.js\"\n }\n}\n```\n\n**Note:** In contrast to other configuration formats, the `Flat Config` requires that all configuration files are converted to `eslint.config.js`. Built-in migrations and generators support only `.eslintrc.json` at the moment.\n\n{% /tab %}\n{% /tabs %}\n\n---\n",
"examplesFile": "Linter can be configured in multiple ways. The basic way is to provide only `lintFilePatterns`, which is a mandatory property. This tells us where to look for files to lint.\n\n`project.json`:\n\n```json\n\"lint\": {\n \"executor\": \"@nx/eslint:lint\",\n \"options\": {\n \"lintFilePatterns\": [\"apps/frontend/**/*.ts\"]\n }\n}\n```\n\n## Examples\n\n{% tabs %}\n{% tab label=\"Fixing linter issues\" %}\n\nLinter provides an automated way of fixing known issues. To ensure that those changes are properly cached, we need to add an `outputs` property to the `lint` target. Omitting the `outputs` property would produce an invalid cache record. Both of these properties are set by default when scaffolding a new project.\n\n```json\n\"lint\": {\n \"executor\": \"@nx/eslint:lint\",\n \"outputs\": [\"{options.outputFile}\"],\n \"options\": {\n \"lintFilePatterns\": [\"apps/frontend/**/*.ts\"]\n }\n}\n```\n\nWith these settings, we can run the command with a `--fix` flag:\n\n```bash\nnx run frontend:lint --fix\n```\n\nWe can also set this flag via project configuration to always fix files when running lint:\n\n```json\n\"lint\": {\n \"executor\": \"@nx/eslint:lint\",\n \"outputs\": [\"{options.outputFile}\"],\n \"options\": {\n \"lintFilePatterns\": [\"apps/frontend/**/*.ts\"],\n \"fix\": true\n }\n}\n```\n\n{% /tab %}\n{% tab label=\"Custom output format\" %}\n\nESLint executor uses the `stylish` output format by default. You can change this by specifying the `format` property:\n\n```json\n\"lint\": {\n \"executor\": \"@nx/eslint:lint\",\n \"outputs\": [\"{options.outputFile}\"],\n \"options\": {\n \"lintFilePatterns\": [\"apps/frontend/**/*.ts\"],\n \"format\": \"compact\"\n }\n}\n```\n\n{% /tab %}\n{% tab label=\"Silence warnings\" %}\n\nMigrated or legacy projects tend to have an overwhelming amount of lint errors. We might want to change those temporarily to be warnings so they don't block the development. But they would still clutter the report. We can run the command with `--quiet` to hide warning (errors would still break the lint):\n\n```bash\nnx run frontend:lint --quiet\n```\n\nWe can also set this via project configuration as a default option.\n\n```json\n\"lint\": {\n \"executor\": \"@nx/eslint:lint\",\n \"outputs\": [\"{options.outputFile}\"],\n \"options\": {\n \"lintFilePatterns\": [\"apps/frontend/**/*.ts\"],\n \"quiet\": true\n }\n}\n```\n\n{% /tab %}\n{% tab label=\"Flat Config file\" %}\n\n`ESLint` provides several ways of specifying the configuration. The default one is using `.eslintrc.json` but you can override it by setting the `eslintConfig` flag. The new `Flat Config` is now also supported:\n\n```json\n\"lint\": {\n \"executor\": \"@nx/eslint:lint\",\n \"outputs\": [\"{options.outputFile}\"],\n \"options\": {\n \"lintFilePatterns\": [\"apps/frontend/**/*.ts\"],\n \"eslintConfig\": \"eslint.config.cjs\"\n }\n}\n```\n\n**Note:** In contrast to other configuration formats, the `Flat Config` requires that all configuration files are converted to `eslint.config.cjs`. Built-in migrations and generators support only `.eslintrc.json` at the moment.\n\n{% /tab %}\n{% /tabs %}\n\n---\n",
"presets": []
},
"hasher": "./src/executors/lint/hasher",

View File

@ -69,7 +69,7 @@ Sometimes we intentionally want to add or remove a dependency from our `package.
"checkObsoleteDependencies": true, // toggle to disable
"checkVersionMismatches": true, // toggle to disable
"ignoredDependencies": ["lodash"], // these libs will be omitted from checks
"ignoredFiles": ["webpack.config.js", "eslint.config.js"], // list of files that should be skipped for check
"ignoredFiles": ["webpack.config.js", "eslint.config.cjs"], // list of files that should be skipped for check
"includeTransitiveDependencies": true, // collect dependencies transitively from children
"useLocalPathsForWorkspaceDependencies": true // toggle to disable
}

View File

@ -10,12 +10,12 @@ See below a direct comparison between `JSON`, `JS` and `Flat` config:
{% tabs %}
{% tab label="Flat" %}
```js {% fileName="eslint.config.js" %}
```js {% fileName="eslint.config.cjs" %}
// the older versions were magically interpreting all the imports
// in flat config we do it explicitly
const nxPlugin = require('@nx/eslint-plugin');
const js = require('@eslint/js');
const baseConfig = require('./eslint.base.config.js');
const baseConfig = require('./eslint.base.config.cjs');
const globals = require('globals');
const jsoncParser = require('jsonc-eslint-parser');
const tsParser = require('@typescript-eslint/parser');

View File

@ -39,7 +39,7 @@ describe('Move Angular Project', () => {
expect(moveOutput).toContain(`CREATE ${newPath}/tsconfig.app.json`);
expect(moveOutput).toContain(`CREATE ${newPath}/tsconfig.json`);
expect(moveOutput).toContain(`CREATE ${newPath}/tsconfig.spec.json`);
expect(moveOutput).toContain(`CREATE ${newPath}/eslint.config.js`);
expect(moveOutput).toContain(`CREATE ${newPath}/eslint.config.cjs`);
expect(moveOutput).toContain(`CREATE ${newPath}/public/favicon.ico`);
expect(moveOutput).toContain(`CREATE ${newPath}/src/index.html`);
expect(moveOutput).toContain(`CREATE ${newPath}/src/main.ts`);

View File

@ -164,13 +164,13 @@ describe('Angular Projects', () => {
it('should lint correctly with eslint and handle external HTML files and inline templates', async () => {
// disable the prefer-standalone rule for app1 which is not standalone
let app1EslintConfig = readFile(`${app1}/eslint.config.js`);
let app1EslintConfig = readFile(`${app1}/eslint.config.cjs`);
app1EslintConfig = app1EslintConfig.replace(
`'@angular-eslint/directive-selector': [`,
`'@angular-eslint/prefer-standalone': 'off',
'@angular-eslint/directive-selector': [`
);
updateFile(`${app1}/eslint.config.js`, app1EslintConfig);
updateFile(`${app1}/eslint.config.cjs`, app1EslintConfig);
// check apps and lib pass linting for initial generated code
runCLI(`run-many --target lint --projects=${app1},${lib1} --parallel`);

View File

@ -150,10 +150,10 @@ describe('Linter (legacy)', () => {
env: { NX_ADD_PLUGINS: 'false' },
});
checkFilesExist(
'eslint.config.js',
`apps/${myapp}/eslint.config.js`,
`libs/${mylib}/eslint.config.js`,
`libs/${mylib2}/eslint.config.js`
'eslint.config.cjs',
`apps/${myapp}/eslint.config.cjs`,
`libs/${mylib}/eslint.config.cjs`,
`libs/${mylib2}/eslint.config.cjs`
);
checkFilesDoNotExist(
'.eslintrc.json',
@ -164,12 +164,12 @@ describe('Linter (legacy)', () => {
// move eslint.config one step up
// to test the absence of the flat eslint config in the project root folder
renameFile(`libs/${mylib2}/eslint.config.js`, `libs/eslint.config.js`);
renameFile(`libs/${mylib2}/eslint.config.cjs`, `libs/eslint.config.cjs`);
updateFile(
`libs/eslint.config.js`,
readFile(`libs/eslint.config.js`).replace(
`../../eslint.config.js`,
`../eslint.config.js`
`libs/eslint.config.cjs`,
readFile(`libs/eslint.config.cjs`).replace(
`../../eslint.config.cjs`,
`../eslint.config.cjs`
)
);
@ -202,9 +202,9 @@ describe('Linter (legacy)', () => {
env: { NX_ADD_PLUGINS: 'false' },
});
checkFilesExist(
'eslint.config.js',
`${mylib}/eslint.config.js`,
'eslint.base.config.js'
'eslint.config.cjs',
`${mylib}/eslint.config.cjs`,
'eslint.base.config.cjs'
);
checkFilesDoNotExist(
'.eslintrc.json',

View File

@ -615,8 +615,8 @@ describe('Linter', () => {
runCLI(`generate @nx/js:lib ${jsLib} --linter eslint`);
checkFilesExist(
`${reactLib}/eslint.config.js`,
`${jsLib}/eslint.config.js`
`${reactLib}/eslint.config.cjs`,
`${jsLib}/eslint.config.cjs`
);
checkFilesDoNotExist(
`${reactLib}/.eslintrc.json`,

View File

@ -178,16 +178,6 @@ describe('packaging libs', () => {
`libs/${swcEsmLib}/src/index.ts`,
`export * from './lib/${swcEsmLib}.js';`
);
// We also need to update the eslint config file extensions to be explicitly commonjs
// TODO: re-evaluate this once we support ESM eslint configs
renameFile(
`libs/${tscEsmLib}/eslint.config.js`,
`libs/${tscEsmLib}/eslint.config.cjs`
);
renameFile(
`libs/${swcEsmLib}/eslint.config.js`,
`libs/${swcEsmLib}/eslint.config.cjs`
);
// Add additional entry points for `exports` field
updateJson(join('libs', tscLib, 'project.json'), (json) => {

View File

@ -26,7 +26,7 @@ exports[`Extra Nx Misc Tests task graph inputs should correctly expand dependent
],
"lib-base-123": [
"libs/lib-base-123/README.md",
"libs/lib-base-123/eslint.config.js",
"libs/lib-base-123/eslint.config.cjs",
"libs/lib-base-123/jest.config.ts",
"libs/lib-base-123/package.json",
"libs/lib-base-123/project.json",
@ -39,7 +39,7 @@ exports[`Extra Nx Misc Tests task graph inputs should correctly expand dependent
],
"lib-dependent-123": [
"libs/lib-dependent-123/README.md",
"libs/lib-dependent-123/eslint.config.js",
"libs/lib-dependent-123/eslint.config.cjs",
"libs/lib-dependent-123/jest.config.ts",
"libs/lib-dependent-123/package.json",
"libs/lib-dependent-123/project.json",

View File

@ -1204,14 +1204,14 @@ describe('lib', () => {
describe('--linter', () => {
describe('eslint', () => {
it('should add valid eslint JSON configuration which extends from Nx presets (flat config)', async () => {
tree.write('eslint.config.js', '');
tree.write('eslint.config.cjs', '');
await runLibraryGeneratorWithOpts({ linter: Linter.EsLint });
const eslintConfig = tree.read('my-lib/eslint.config.js', 'utf-8');
const eslintConfig = tree.read('my-lib/eslint.config.cjs', 'utf-8');
expect(eslintConfig).toMatchInlineSnapshot(`
"const nx = require("@nx/eslint-plugin");
const baseConfig = require("../eslint.config.js");
const baseConfig = require("../eslint.config.cjs");
module.exports = [
...baseConfig,

View File

@ -93,7 +93,7 @@ exports[`workspace move to nx layout should create nx.json 1`] = `
"!{projectRoot}/**/*.spec.[jt]s",
"!{projectRoot}/karma.conf.js",
"!{projectRoot}/.eslintrc.json",
"!{projectRoot}/eslint.config.js",
"!{projectRoot}/eslint.config.cjs",
],
"sharedGlobals": [],
},
@ -104,7 +104,7 @@ exports[`workspace move to nx layout should create nx.json 1`] = `
"default",
"{workspaceRoot}/.eslintrc.json",
"{workspaceRoot}/.eslintignore",
"{workspaceRoot}/eslint.config.js",
"{workspaceRoot}/eslint.config.cjs",
],
},
"build": {
@ -129,7 +129,7 @@ exports[`workspace move to nx layout should create nx.json 1`] = `
"inputs": [
"default",
"{workspaceRoot}/.eslintrc.json",
"{workspaceRoot}/eslint.config.js",
"{workspaceRoot}/eslint.config.cjs",
],
},
"test": {

View File

@ -64,7 +64,10 @@ export function createNxJson(
]
: []),
...(targets.lint
? ['!{projectRoot}/.eslintrc.json', '!{projectRoot}/eslint.config.js']
? [
'!{projectRoot}/.eslintrc.json',
'!{projectRoot}/eslint.config.cjs',
]
: []),
].filter(Boolean),
},
@ -85,7 +88,7 @@ export function createNxJson(
inputs: [
'default',
'{workspaceRoot}/.eslintrc.json',
'{workspaceRoot}/eslint.config.js',
'{workspaceRoot}/eslint.config.cjs',
],
cache: true,
}

View File

@ -16,5 +16,5 @@
<%_ if (jsx) { _%>"<%= offsetFromProjectRoot %>**/*.cy.jsx",<%_ } _%>
"<%= offsetFromProjectRoot %>**/*.d.ts"
],
"exclude": ["out-tsc", "test-output"<% if (linter === 'eslint') { %>, "eslint.config.js"<% } %>]
"exclude": ["out-tsc", "test-output"<% if (linter === 'eslint') { %>, "eslint.config.js", "eslint.config.cjs", "eslint.config.mjs"<% } %>]
}

View File

@ -24,7 +24,7 @@ const isPrettierAvailable =
*/
export default tseslint.config(
{
files: ['**/*.js', '**/*.jsx'],
files: ['**/*.js', '**/*.jsx', '**/*.cjs', '**/*.mjs'],
extends: [eslint.configs.recommended, ...tseslint.configs.recommended],
},
{
@ -40,7 +40,7 @@ export default tseslint.config(
plugins: { '@typescript-eslint': tseslint.plugin },
},
{
files: ['**/*.js', '**/*.jsx'],
files: ['**/*.js', '**/*.jsx', '**/*.cjs', '**/*.mjs'],
rules: {
'@typescript-eslint/explicit-member-accessibility': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',

View File

@ -15,7 +15,7 @@ const isPrettierAvailable =
*/
export default tseslint.config(
{
files: ['**/*.ts', '**/*.tsx'],
files: ['**/*.ts', '**/*.tsx', '**/*.cts', '**/*.mts'],
extends: [eslint.configs.recommended, ...tseslint.configs.recommended],
},
{
@ -30,7 +30,7 @@ export default tseslint.config(
},
},
{
files: ['**/*.ts', '**/*.tsx'],
files: ['**/*.ts', '**/*.tsx', , '**/*.cts', '**/*.mts'],
rules: {
'@typescript-eslint/explicit-member-accessibility': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',

View File

@ -96,12 +96,12 @@ We can also set this via project configuration as a default option.
"outputs": ["{options.outputFile}"],
"options": {
"lintFilePatterns": ["apps/frontend/**/*.ts"],
"eslintConfig": "eslint.config.js"
"eslintConfig": "eslint.config.cjs"
}
}
```
**Note:** In contrast to other configuration formats, the `Flat Config` requires that all configuration files are converted to `eslint.config.js`. Built-in migrations and generators support only `.eslintrc.json` at the moment.
**Note:** In contrast to other configuration formats, the `Flat Config` requires that all configuration files are converted to `eslint.config.cjs`. Built-in migrations and generators support only `.eslintrc.json` at the moment.
{% /tab %}
{% /tabs %}

View File

@ -24,6 +24,11 @@
"version": "20.2.0-beta.5",
"description": "Update TypeScript ESLint packages to v8.13.0 if they are already on v8",
"implementation": "./src/migrations/update-20-2-0/update-typescript-eslint-v8-13-0"
},
"add-file-extensions-to-overrides": {
"version": "20.3.0-beta.1",
"description": "Update ESLint flat config to include .cjs, .mjs, .cts, and .mts files in overrides (if needed)",
"implementation": "./src/migrations/update-20-3-0/add-file-extensions-to-overrides"
}
},
"packageJsonUpdates": {

View File

@ -436,7 +436,7 @@ Please see https://nx.dev/recipes/tips-n-tricks/eslint for full guidance on how
it('should intercept the error from `@typescript-eslint` regarding missing parserServices and provide a more detailed user-facing message logging the found flat config', async () => {
setupMocks();
tempFs.createFileSync('apps/proj/eslint.config.js', '');
tempFs.createFileSync('apps/proj/eslint.config.cjs', '');
tempFs.createFileSync('apps/proj/src/some-file.ts', '');
mockLintFiles.mockImplementation(() => {
@ -456,7 +456,7 @@ Occurred while linting ${mockContext.root}/apps/proj/src/some-file.ts`
);
expect(console.error).toHaveBeenCalledWith(
`
Error: You have attempted to use the lint rule "@typescript-eslint/await-thenable" which requires the full TypeScript type-checker to be available, but you do not have "parserOptions.project" configured to point at your project tsconfig.json files in the relevant TypeScript file "overrides" block of your ESLint config "apps/proj/eslint.config.js"
Error: You have attempted to use the lint rule "@typescript-eslint/await-thenable" which requires the full TypeScript type-checker to be available, but you do not have "parserOptions.project" configured to point at your project tsconfig.json files in the relevant TypeScript file "overrides" block of your ESLint config "apps/proj/eslint.config.cjs"
Occurred while linting ${mockContext.root}/apps/proj/src/some-file.ts
Please see https://nx.dev/recipes/tips-n-tricks/eslint for full guidance on how to resolve this issue.
@ -466,7 +466,7 @@ Please see https://nx.dev/recipes/tips-n-tricks/eslint for full guidance on how
it('should intercept the error from `@typescript-eslint` regarding missing parserServices and provide a more detailed user-facing message logging the found flat config at the workspace root', async () => {
setupMocks();
tempFs.createFileSync('eslint.config.js', '');
tempFs.createFileSync('eslint.config.cjs', '');
tempFs.createFileSync('apps/proj/src/some-file.ts', '');
mockLintFiles.mockImplementation(() => {
@ -486,7 +486,7 @@ Occurred while linting ${mockContext.root}/apps/proj/src/some-file.ts`
);
expect(console.error).toHaveBeenCalledWith(
`
Error: You have attempted to use the lint rule "@typescript-eslint/await-thenable" which requires the full TypeScript type-checker to be available, but you do not have "parserOptions.project" configured to point at your project tsconfig.json files in the relevant TypeScript file "overrides" block of your ESLint config "eslint.config.js"
Error: You have attempted to use the lint rule "@typescript-eslint/await-thenable" which requires the full TypeScript type-checker to be available, but you do not have "parserOptions.project" configured to point at your project tsconfig.json files in the relevant TypeScript file "overrides" block of your ESLint config "eslint.config.cjs"
Occurred while linting ${mockContext.root}/apps/proj/src/some-file.ts
Please see https://nx.dev/recipes/tips-n-tricks/eslint for full guidance on how to resolve this issue.
@ -906,15 +906,15 @@ Please see https://nx.dev/recipes/tips-n-tricks/eslint for full guidance on how
expect(result).toEqual({ success: true });
});
it('should pass path to eslint.config.js to resolveAndInstantiateESLint if it is unspecified and we are using flag configuration', async () => {
it('should pass path to eslint.config.cjs to resolveAndInstantiateESLint if it is unspecified and we are using flag configuration', async () => {
setupMocks();
jest.spyOn(fs, 'existsSync').mockReturnValue(true);
await lintExecutor(createValidRunBuilderOptions(), mockContext);
expect(mockResolveAndInstantiateESLint).toHaveBeenCalledWith(
`${mockContext.root}/apps/proj/eslint.config.js`,
`${mockContext.root}/apps/proj/eslint.config.cjs`,
{
lintFilePatterns: [],
eslintConfig: 'apps/proj/eslint.config.js',
eslintConfig: 'apps/proj/eslint.config.cjs',
fix: true,
cache: true,
cacheLocation: 'cacheLocation1/proj',

View File

@ -162,7 +162,7 @@ describe('eslint-utils', () => {
});
describe('ESLint Flat Config', () => {
it('should throw if a non eslint.config.js or eslint.config.cjs file is used with ESLint Flat Config', async () => {
it('should throw if a non eslint.config.cjs or eslint.config.cjs file is used with ESLint Flat Config', async () => {
await expect(
resolveAndInstantiateESLint('./.eslintrc.json', {} as any, true)
).rejects.toThrowErrorMatchingInlineSnapshot(

View File

@ -12,6 +12,9 @@ const compat = new FlatCompat({
});
module.exports = [
{
ignores: ['**/dist'],
},
{ plugins: { '@nx': nxEslintPlugin } },
{ languageOptions: { globals: { ...globals.browser, ...globals.node } } },
{
@ -38,7 +41,7 @@ module.exports = [
})
.map((config) => ({
...config,
files: ['**/*.ts', '**/*.tsx'],
files: ['**/*.ts', '**/*.tsx', '**/*.cts', '**/*.mts'],
rules: {
...config.rules,
},
@ -49,7 +52,7 @@ module.exports = [
})
.map((config) => ({
...config,
files: ['**/*.js', '**/*.jsx'],
files: ['**/*.js', '**/*.jsx', '**/*.cjs', '**/*.mjs'],
rules: {
...config.rules,
},
@ -70,6 +73,9 @@ const compat = new FlatCompat({
});
module.exports = [
{
ignores: ['**/dist'],
},
{ plugins: { '@nx': nxEslintPlugin } },
{
languageOptions: {
@ -100,7 +106,7 @@ module.exports = [
})
.map((config) => ({
...config,
files: ['**/*.ts', '**/*.tsx'],
files: ['**/*.ts', '**/*.tsx', '**/*.cts', '**/*.mts'],
rules: {
...config.rules,
},
@ -111,7 +117,7 @@ module.exports = [
})
.map((config) => ({
...config,
files: ['**/*.js', '**/*.jsx'],
files: ['**/*.js', '**/*.jsx', '**/*.cjs', '**/*.mjs'],
rules: {
...config.rules,
},
@ -131,6 +137,9 @@ const compat = new FlatCompat({
});
module.exports = [
{
ignores: ['**/dist'],
},
{ plugins: { '@nx': nxEslintPlugin } },
{ languageOptions: { globals: { myCustomGlobal: 'readonly' } } },
{
@ -157,7 +166,7 @@ module.exports = [
})
.map((config) => ({
...config,
files: ['**/*.ts', '**/*.tsx'],
files: ['**/*.ts', '**/*.tsx', '**/*.cts', '**/*.mts'],
rules: {
...config.rules,
},
@ -168,7 +177,7 @@ module.exports = [
})
.map((config) => ({
...config,
files: ['**/*.js', '**/*.jsx'],
files: ['**/*.js', '**/*.jsx', '**/*.cjs', '**/*.mjs'],
rules: {
...config.rules,
},
@ -188,6 +197,9 @@ const compat = new FlatCompat({
});
module.exports = [
{
ignores: ['**/dist'],
},
{ plugins: { '@nx': nxEslintPlugin } },
{
files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
@ -213,7 +225,7 @@ module.exports = [
})
.map((config) => ({
...config,
files: ['**/*.ts', '**/*.tsx'],
files: ['**/*.ts', '**/*.tsx', '**/*.cts', '**/*.mts'],
rules: {
...config.rules,
},
@ -224,7 +236,7 @@ module.exports = [
})
.map((config) => ({
...config,
files: ['**/*.js', '**/*.jsx'],
files: ['**/*.js', '**/*.jsx', '**/*.cjs', '**/*.mjs'],
rules: {
...config.rules,
},
@ -248,6 +260,9 @@ const compat = new FlatCompat({
});
module.exports = [
{
ignores: ['**/dist'],
},
{ plugins: { '@nx': nxEslintPlugin } },
{ languageOptions: { parser: typescriptEslintParser } },
{
@ -274,7 +289,7 @@ module.exports = [
})
.map((config) => ({
...config,
files: ['**/*.ts', '**/*.tsx'],
files: ['**/*.ts', '**/*.tsx', '**/*.cts', '**/*.mts'],
rules: {
...config.rules,
},
@ -285,7 +300,7 @@ module.exports = [
})
.map((config) => ({
...config,
files: ['**/*.js', '**/*.jsx'],
files: ['**/*.js', '**/*.jsx', '**/*.cjs', '**/*.mjs'],
rules: {
...config.rules,
},
@ -308,6 +323,9 @@ const compat = new FlatCompat({
});
module.exports = [
{
ignores: ['**/dist'],
},
{
plugins: {
'eslint-plugin-import': eslintPluginImport,
@ -340,7 +358,7 @@ module.exports = [
})
.map((config) => ({
...config,
files: ['**/*.ts', '**/*.tsx'],
files: ['**/*.ts', '**/*.tsx', '**/*.cts', '**/*.mts'],
rules: {
...config.rules,
},
@ -351,7 +369,7 @@ module.exports = [
})
.map((config) => ({
...config,
files: ['**/*.js', '**/*.jsx'],
files: ['**/*.js', '**/*.jsx', '**/*.cjs', '**/*.mjs'],
rules: {
...config.rules,
},
@ -371,6 +389,9 @@ const compat = new FlatCompat({
});
module.exports = [
{
ignores: ['**/dist'],
},
{ plugins: { '@nx': nxEslintPlugin } },
{
settings: {
@ -401,7 +422,7 @@ module.exports = [
})
.map((config) => ({
...config,
files: ['**/*.ts', '**/*.tsx'],
files: ['**/*.ts', '**/*.tsx', '**/*.cts', '**/*.mts'],
rules: {
...config.rules,
},
@ -412,7 +433,7 @@ module.exports = [
})
.map((config) => ({
...config,
files: ['**/*.js', '**/*.jsx'],
files: ['**/*.js', '**/*.jsx', '**/*.cjs', '**/*.mjs'],
rules: {
...config.rules,
},
@ -432,6 +453,9 @@ const compat = new FlatCompat({
});
module.exports = [
{
ignores: ['**/dist'],
},
{ plugins: { '@nx': nxEslintPlugin } },
{
files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
@ -457,7 +481,7 @@ module.exports = [
})
.map((config) => ({
...config,
files: ['**/*.ts', '**/*.tsx'],
files: ['**/*.ts', '**/*.tsx', '**/*.cts', '**/*.mts'],
rules: {
...config.rules,
},
@ -468,7 +492,7 @@ module.exports = [
})
.map((config) => ({
...config,
files: ['**/*.js', '**/*.jsx'],
files: ['**/*.js', '**/*.jsx', '**/*.cjs', '**/*.mjs'],
rules: {
...config.rules,
},
@ -478,9 +502,12 @@ module.exports = [
`;
exports[`convert-to-flat-config generator should convert json successfully 2`] = `
"const baseConfig = require('../../eslint.config.js');
"const baseConfig = require('../../eslint.config.cjs');
module.exports = [
{
ignores: ['**/dist'],
},
...baseConfig,
{
files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
@ -512,6 +539,9 @@ const compat = new FlatCompat({
});
module.exports = [
{
ignores: ['**/dist'],
},
{ plugins: { '@nx': nxEslintPlugin } },
{
files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
@ -537,7 +567,7 @@ module.exports = [
})
.map((config) => ({
...config,
files: ['**/*.ts', '**/*.tsx'],
files: ['**/*.ts', '**/*.tsx', '**/*.cts', '**/*.mts'],
rules: {
...config.rules,
},
@ -548,7 +578,7 @@ module.exports = [
})
.map((config) => ({
...config,
files: ['**/*.js', '**/*.jsx'],
files: ['**/*.js', '**/*.jsx', '**/*.cjs', '**/*.mjs'],
rules: {
...config.rules,
},
@ -558,9 +588,12 @@ module.exports = [
`;
exports[`convert-to-flat-config generator should convert yaml successfully 2`] = `
"const baseConfig = require('../../eslint.config.js');
"const baseConfig = require('../../eslint.config.cjs');
module.exports = [
{
ignores: ['**/dist'],
},
...baseConfig,
{
files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
@ -592,6 +625,9 @@ const compat = new FlatCompat({
});
module.exports = [
{
ignores: ['**/dist'],
},
{ plugins: { '@nx': nxEslintPlugin } },
{
files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
@ -617,7 +653,7 @@ module.exports = [
})
.map((config) => ({
...config,
files: ['**/*.ts', '**/*.tsx'],
files: ['**/*.ts', '**/*.tsx', '**/*.cts', '**/*.mts'],
rules: {
...config.rules,
},
@ -628,7 +664,7 @@ module.exports = [
})
.map((config) => ({
...config,
files: ['**/*.js', '**/*.jsx'],
files: ['**/*.js', '**/*.jsx', '**/*.cjs', '**/*.mjs'],
rules: {
...config.rules,
},
@ -638,9 +674,12 @@ module.exports = [
`;
exports[`convert-to-flat-config generator should convert yml successfully 2`] = `
"const baseConfig = require('../../eslint.config.js');
"const baseConfig = require('../../eslint.config.cjs');
module.exports = [
{
ignores: ['**/dist'],
},
...baseConfig,
{
files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
@ -662,9 +701,12 @@ module.exports = [
`;
exports[`convert-to-flat-config generator should handle custom eslintignores 1`] = `
"const baseConfig = require('../../eslint.config.js');
"const baseConfig = require('../../eslint.config.cjs');
module.exports = [
{
ignores: ['**/dist'],
},
...baseConfig,
{
files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],

View File

@ -40,6 +40,12 @@ describe('convertEslintJsonToFlatConfig', () => {
extends: ['plugin:@nx/typescript'],
rules: {},
},
{
files: ['*.js', '*.jsx'],
extends: ['plugin:@nx/javascript'],
rules: {},
},
{
files: [
'**/*.spec.ts',
@ -76,6 +82,11 @@ describe('convertEslintJsonToFlatConfig', () => {
});
module.exports = [
{
ignores: [
"**/dist"
]
},
{ plugins: { "@nx": nxEslintPlugin } },
{
files: [
@ -110,7 +121,25 @@ describe('convertEslintJsonToFlatConfig', () => {
...config,
files: [
"**/*.ts",
"**/*.tsx"
"**/*.tsx",
"**/*.cts",
"**/*.mts"
],
rules: {
...config.rules
}
})),
...compat.config({
extends: [
"plugin:@nx/javascript"
]
}).map(config => ({
...config,
files: [
"**/*.js",
"**/*.jsx",
"**/*.cjs",
"**/*.mjs"
],
rules: {
...config.rules
@ -205,7 +234,7 @@ describe('convertEslintJsonToFlatConfig', () => {
expect(content).toMatchInlineSnapshot(`
"const { FlatCompat } = require("@eslint/eslintrc");
const js = require("@eslint/js");
const baseConfig = require("../../eslint.config.js");
const baseConfig = require("../../eslint.config.cjs");
const globals = require("globals");
const compat = new FlatCompat({
@ -214,6 +243,11 @@ describe('convertEslintJsonToFlatConfig', () => {
});
module.exports = [
{
ignores: [
"**/dist"
]
},
...baseConfig,
...compat.extends("plugin:@nx/react-typescript", "next", "next/core-web-vitals"),
{ languageOptions: { globals: { ...globals.jest } } },

View File

@ -30,6 +30,13 @@ export function convertEslintJsonToFlatConfig(
let combinedConfig: ts.PropertyAssignment[] = [];
let languageOptions: ts.PropertyAssignment[] = [];
// exclude dist and eslint config from being linted, which matches the default for new workspaces
exportElements.push(
generateAst({
ignores: ['**/dist'],
})
);
if (config.extends) {
const extendsResult = addExtends(importsMap, exportElements, config);
isFlatCompatNeeded = extendsResult.isFlatCompatNeeded;
@ -218,7 +225,7 @@ function addExtends(
configBlocks.push(generateSpreadElement(localName));
const newImport = imp.replace(
/^(.*)\.eslintrc(.base)?\.json$/,
'$1eslint$2.config.js'
'$1eslint$2.config.cjs'
);
importsMap.set(newImport, localName);
} else {

View File

@ -82,19 +82,19 @@ describe('convert-to-flat-config generator', () => {
});
await convertToFlatConfigGenerator(tree, options);
expect(tree.exists('eslint.config.js')).toBeTruthy();
expect(tree.read('eslint.config.js', 'utf-8')).toMatchSnapshot();
expect(tree.exists('libs/test-lib/eslint.config.js')).toBeTruthy();
expect(tree.exists('eslint.config.cjs')).toBeTruthy();
expect(tree.read('eslint.config.cjs', 'utf-8')).toMatchSnapshot();
expect(tree.exists('libs/test-lib/eslint.config.cjs')).toBeTruthy();
expect(
tree.read('libs/test-lib/eslint.config.js', 'utf-8')
tree.read('libs/test-lib/eslint.config.cjs', 'utf-8')
).toMatchSnapshot();
// check nx.json changes
const nxJson = readJson(tree, 'nx.json');
expect(nxJson.targetDefaults.lint.inputs).toContain(
'{workspaceRoot}/eslint.config.js'
'{workspaceRoot}/eslint.config.cjs'
);
expect(nxJson.namedInputs.production).toContain(
'!{projectRoot}/eslint.config.js'
'!{projectRoot}/eslint.config.cjs'
);
});
@ -112,19 +112,19 @@ describe('convert-to-flat-config generator', () => {
await convertToFlatConfigGenerator(tree, options);
expect(tree.exists('eslint.config.js')).toBeTruthy();
expect(tree.read('eslint.config.js', 'utf-8')).toMatchSnapshot();
expect(tree.exists('libs/test-lib/eslint.config.js')).toBeTruthy();
expect(tree.exists('eslint.config.cjs')).toBeTruthy();
expect(tree.read('eslint.config.cjs', 'utf-8')).toMatchSnapshot();
expect(tree.exists('libs/test-lib/eslint.config.cjs')).toBeTruthy();
expect(
tree.read('libs/test-lib/eslint.config.js', 'utf-8')
tree.read('libs/test-lib/eslint.config.cjs', 'utf-8')
).toMatchSnapshot();
// check nx.json changes
const nxJson = readJson(tree, 'nx.json');
expect(nxJson.targetDefaults.lint.inputs).toContain(
'{workspaceRoot}/eslint.config.js'
'{workspaceRoot}/eslint.config.cjs'
);
expect(nxJson.namedInputs.production).toContain(
'!{projectRoot}/eslint.config.js'
'!{projectRoot}/eslint.config.cjs'
);
});
@ -142,19 +142,19 @@ describe('convert-to-flat-config generator', () => {
await convertToFlatConfigGenerator(tree, options);
expect(tree.exists('eslint.config.js')).toBeTruthy();
expect(tree.read('eslint.config.js', 'utf-8')).toMatchSnapshot();
expect(tree.exists('libs/test-lib/eslint.config.js')).toBeTruthy();
expect(tree.exists('eslint.config.cjs')).toBeTruthy();
expect(tree.read('eslint.config.cjs', 'utf-8')).toMatchSnapshot();
expect(tree.exists('libs/test-lib/eslint.config.cjs')).toBeTruthy();
expect(
tree.read('libs/test-lib/eslint.config.js', 'utf-8')
tree.read('libs/test-lib/eslint.config.cjs', 'utf-8')
).toMatchSnapshot();
// check nx.json changes
const nxJson = readJson(tree, 'nx.json');
expect(nxJson.targetDefaults.lint.inputs).toContain(
'{workspaceRoot}/eslint.config.js'
'{workspaceRoot}/eslint.config.cjs'
);
expect(nxJson.namedInputs.production).toContain(
'!{projectRoot}/eslint.config.js'
'!{projectRoot}/eslint.config.cjs'
);
});
@ -171,7 +171,7 @@ describe('convert-to-flat-config generator', () => {
});
await convertToFlatConfigGenerator(tree, options);
expect(tree.read('eslint.config.js', 'utf-8')).toMatchInlineSnapshot(`
expect(tree.read('eslint.config.cjs', 'utf-8')).toMatchInlineSnapshot(`
"const { FlatCompat } = require('@eslint/eslintrc');
const js = require('@eslint/js');
const nxEslintPlugin = require('@nx/eslint-plugin');
@ -182,6 +182,9 @@ describe('convert-to-flat-config generator', () => {
});
module.exports = [
{
ignores: ['**/dist'],
},
...compat.extends('plugin:storybook/recommended'),
{ plugins: { '@nx': nxEslintPlugin } },
{
@ -208,7 +211,7 @@ describe('convert-to-flat-config generator', () => {
})
.map((config) => ({
...config,
files: ['**/*.ts', '**/*.tsx'],
files: ['**/*.ts', '**/*.tsx', '**/*.cts', '**/*.mts'],
rules: {
...config.rules,
},
@ -219,7 +222,7 @@ describe('convert-to-flat-config generator', () => {
})
.map((config) => ({
...config,
files: ['**/*.js', '**/*.jsx'],
files: ['**/*.js', '**/*.jsx', '**/*.cjs', '**/*.mjs'],
rules: {
...config.rules,
},
@ -227,11 +230,14 @@ describe('convert-to-flat-config generator', () => {
];
"
`);
expect(tree.read('libs/test-lib/eslint.config.js', 'utf-8'))
expect(tree.read('libs/test-lib/eslint.config.cjs', 'utf-8'))
.toMatchInlineSnapshot(`
"const baseConfig = require('../../eslint.config.js');
"const baseConfig = require('../../eslint.config.cjs');
module.exports = [
{
ignores: ['**/dist'],
},
...baseConfig,
{
files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
@ -266,7 +272,7 @@ describe('convert-to-flat-config generator', () => {
tree.write('.eslintignore', 'ignore/me');
await convertToFlatConfigGenerator(tree, options);
const config = tree.read('eslint.config.js', 'utf-8');
const config = tree.read('eslint.config.cjs', 'utf-8');
expect(config).toContain('ignore/me');
expect(config).toMatchSnapshot();
expect(tree.exists('.eslintignore')).toBeFalsy();
@ -290,7 +296,7 @@ describe('convert-to-flat-config generator', () => {
await convertToFlatConfigGenerator(tree, options);
expect(
tree.read('libs/test-lib/eslint.config.js', 'utf-8')
tree.read('libs/test-lib/eslint.config.cjs', 'utf-8')
).toMatchSnapshot();
expect(tree.exists('another-folder/.myeslintignore')).toBeFalsy();
expect(tree.exists('libs/test-lib/.eslintignore')).toBeFalsy();
@ -316,7 +322,7 @@ describe('convert-to-flat-config generator', () => {
});
await convertToFlatConfigGenerator(tree, options);
expect(tree.read('eslint.config.js', 'utf-8')).toMatchSnapshot();
expect(tree.read('eslint.config.cjs', 'utf-8')).toMatchSnapshot();
});
it('should add env configuration', async () => {
@ -335,7 +341,7 @@ describe('convert-to-flat-config generator', () => {
});
await convertToFlatConfigGenerator(tree, options);
expect(tree.read('eslint.config.js', 'utf-8')).toMatchSnapshot();
expect(tree.read('eslint.config.cjs', 'utf-8')).toMatchSnapshot();
});
it('should add global configuration', async () => {
@ -353,7 +359,7 @@ describe('convert-to-flat-config generator', () => {
});
await convertToFlatConfigGenerator(tree, options);
expect(tree.read('eslint.config.js', 'utf-8')).toMatchSnapshot();
expect(tree.read('eslint.config.cjs', 'utf-8')).toMatchSnapshot();
});
it('should add global and env configuration', async () => {
@ -374,7 +380,7 @@ describe('convert-to-flat-config generator', () => {
});
await convertToFlatConfigGenerator(tree, options);
expect(tree.read('eslint.config.js', 'utf-8')).toMatchSnapshot();
expect(tree.read('eslint.config.cjs', 'utf-8')).toMatchSnapshot();
});
it('should add plugins', async () => {
@ -395,7 +401,7 @@ describe('convert-to-flat-config generator', () => {
});
await convertToFlatConfigGenerator(tree, options);
expect(tree.read('eslint.config.js', 'utf-8')).toMatchSnapshot();
expect(tree.read('eslint.config.cjs', 'utf-8')).toMatchSnapshot();
});
it('should add parser', async () => {
@ -411,7 +417,7 @@ describe('convert-to-flat-config generator', () => {
});
await convertToFlatConfigGenerator(tree, options);
expect(tree.read('eslint.config.js', 'utf-8')).toMatchSnapshot();
expect(tree.read('eslint.config.cjs', 'utf-8')).toMatchSnapshot();
});
it('should add linter options', async () => {
@ -427,7 +433,7 @@ describe('convert-to-flat-config generator', () => {
});
await convertToFlatConfigGenerator(tree, options);
expect(tree.read('eslint.config.js', 'utf-8')).toMatchInlineSnapshot(`
expect(tree.read('eslint.config.cjs', 'utf-8')).toMatchInlineSnapshot(`
"const { FlatCompat } = require('@eslint/eslintrc');
const js = require('@eslint/js');
const nxEslintPlugin = require('@nx/eslint-plugin');
@ -438,6 +444,9 @@ describe('convert-to-flat-config generator', () => {
});
module.exports = [
{
ignores: ['**/dist'],
},
{ plugins: { '@nx': nxEslintPlugin } },
{
linterOptions: {
@ -468,7 +477,7 @@ describe('convert-to-flat-config generator', () => {
})
.map((config) => ({
...config,
files: ['**/*.ts', '**/*.tsx'],
files: ['**/*.ts', '**/*.tsx', '**/*.cts', '**/*.mts'],
rules: {
...config.rules,
},
@ -479,7 +488,7 @@ describe('convert-to-flat-config generator', () => {
})
.map((config) => ({
...config,
files: ['**/*.js', '**/*.jsx'],
files: ['**/*.js', '**/*.jsx', '**/*.cjs', '**/*.mjs'],
rules: {
...config.rules,
},
@ -510,11 +519,11 @@ describe('convert-to-flat-config generator', () => {
}
);
expect(tree.exists('eslint.config.js')).toBeFalsy();
expect(tree.exists('libs/test-lib/eslint.config.js')).toBeFalsy();
expect(tree.exists('eslint.config.cjs')).toBeFalsy();
expect(tree.exists('libs/test-lib/eslint.config.cjs')).toBeFalsy();
await convertToFlatConfigGenerator(tree, options);
expect(tree.exists('eslint.config.js')).toBeTruthy();
expect(tree.exists('libs/test-lib/eslint.config.js')).toBeTruthy();
expect(tree.exists('eslint.config.cjs')).toBeTruthy();
expect(tree.exists('libs/test-lib/eslint.config.cjs')).toBeTruthy();
});
it('should convert project if target is defined via plugin as object', async () => {
@ -545,11 +554,11 @@ describe('convert-to-flat-config generator', () => {
}
);
expect(tree.exists('eslint.config.js')).toBeFalsy();
expect(tree.exists('libs/test-lib/eslint.config.js')).toBeFalsy();
expect(tree.exists('eslint.config.cjs')).toBeFalsy();
expect(tree.exists('libs/test-lib/eslint.config.cjs')).toBeFalsy();
await convertToFlatConfigGenerator(tree, options);
expect(tree.exists('eslint.config.js')).toBeTruthy();
expect(tree.exists('libs/test-lib/eslint.config.js')).toBeTruthy();
expect(tree.exists('eslint.config.cjs')).toBeTruthy();
expect(tree.exists('libs/test-lib/eslint.config.cjs')).toBeTruthy();
});
it('should handle parser options even if parser is extended', async () => {
@ -589,13 +598,16 @@ describe('convert-to-flat-config generator', () => {
});
await convertToFlatConfigGenerator(tree, options);
expect(tree.exists('apps/dx-assets-ui/eslint.config.js')).toBeTruthy();
expect(tree.exists('eslint.config.js')).toBeTruthy();
expect(tree.read('apps/dx-assets-ui/eslint.config.js', 'utf-8'))
expect(tree.exists('apps/dx-assets-ui/eslint.config.cjs')).toBeTruthy();
expect(tree.exists('eslint.config.cjs')).toBeTruthy();
expect(tree.read('apps/dx-assets-ui/eslint.config.cjs', 'utf-8'))
.toMatchInlineSnapshot(`
"const baseConfig = require('../../eslint.config.js');
"const baseConfig = require('../../eslint.config.cjs');
module.exports = [
{
ignores: ['**/dist'],
},
...baseConfig,
{
files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],

View File

@ -42,10 +42,10 @@ export async function convertToFlatConfigGenerator(
const eslintIgnoreFiles = new Set<string>(['.eslintignore']);
// convert root eslint config to eslint.config.js
// convert root eslint config to eslint.config.cjs
convertRootToFlatConfig(tree, eslintFile);
// convert project eslint files to eslint.config.js
// convert project eslint files to eslint.config.cjs
const projects = getProjects(tree);
for (const [project, projectConfig] of projects) {
convertProjectToFlatConfig(
@ -77,13 +77,13 @@ export default convertToFlatConfigGenerator;
function convertRootToFlatConfig(tree: Tree, eslintFile: string) {
if (/\.base\.(js|json|yml|yaml)$/.test(eslintFile)) {
convertConfigToFlatConfig(tree, '', eslintFile, 'eslint.base.config.js');
convertConfigToFlatConfig(tree, '', eslintFile, 'eslint.base.config.cjs');
}
convertConfigToFlatConfig(
tree,
'',
eslintFile.replace('.base.', '.'),
'eslint.config.js'
'eslint.config.cjs'
);
}
@ -132,7 +132,7 @@ function convertProjectToFlatConfig(
tree,
projectConfig.root,
eslintFile,
'eslint.config.js',
'eslint.config.cjs',
ignorePath
);
eslintIgnoreFiles.add(`${projectConfig.root}/.eslintignore`);
@ -151,17 +151,17 @@ function updateNxJsonConfig(tree: Tree) {
updateJson(tree, 'nx.json', (json: NxJsonConfiguration) => {
if (json.targetDefaults?.lint?.inputs) {
const inputSet = new Set(json.targetDefaults.lint.inputs);
inputSet.add('{workspaceRoot}/eslint.config.js');
inputSet.add('{workspaceRoot}/eslint.config.cjs');
json.targetDefaults.lint.inputs = Array.from(inputSet);
}
if (json.targetDefaults?.['@nx/eslint:lint']?.inputs) {
const inputSet = new Set(json.targetDefaults['@nx/eslint:lint'].inputs);
inputSet.add('{workspaceRoot}/eslint.config.js');
inputSet.add('{workspaceRoot}/eslint.config.cjs');
json.targetDefaults['@nx/eslint:lint'].inputs = Array.from(inputSet);
}
if (json.namedInputs?.production) {
const inputSet = new Set(json.namedInputs.production);
inputSet.add('!{projectRoot}/eslint.config.js');
inputSet.add('!{projectRoot}/eslint.config.cjs');
json.namedInputs.production = Array.from(inputSet);
}
return json;

View File

@ -573,7 +573,7 @@ describe('Eslint - Convert Executors To Plugin', () => {
'default',
'{projectRoot}/.eslintrc.json',
'{projectRoot}/.eslintignore',
'{projectRoot}/eslint.config.js',
'{projectRoot}/eslint.config.cjs',
],
};
updateNxJson(tree, nxJson);
@ -589,7 +589,7 @@ describe('Eslint - Convert Executors To Plugin', () => {
'default',
'{projectRoot}/.eslintrc.json',
'{projectRoot}/.eslintignore',
'{projectRoot}/eslint.config.js',
'{projectRoot}/eslint.config.cjs',
{ externalDependencies: ['eslint'] },
]);
});
@ -604,7 +604,7 @@ describe('Eslint - Convert Executors To Plugin', () => {
'default',
'{projectRoot}/.eslintrc.json',
'{projectRoot}/.eslintignore',
'{projectRoot}/eslint.config.js',
'{projectRoot}/eslint.config.cjs',
{ externalDependencies: ['eslint-plugin-react'] },
],
};
@ -621,7 +621,7 @@ describe('Eslint - Convert Executors To Plugin', () => {
'default',
'{projectRoot}/.eslintrc.json',
'{projectRoot}/.eslintignore',
'{projectRoot}/eslint.config.js',
'{projectRoot}/eslint.config.cjs',
{ externalDependencies: ['eslint-plugin-react', 'eslint'] },
]);
});
@ -636,7 +636,7 @@ describe('Eslint - Convert Executors To Plugin', () => {
'default',
'{projectRoot}/.eslintrc.json',
'{projectRoot}/.eslintignore',
'{projectRoot}/eslint.config.js',
'{projectRoot}/eslint.config.cjs',
{ externalDependencies: ['eslint', 'eslint-plugin-react'] },
],
};
@ -653,7 +653,7 @@ describe('Eslint - Convert Executors To Plugin', () => {
'default',
'{projectRoot}/.eslintrc.json',
'{projectRoot}/.eslintignore',
'{projectRoot}/eslint.config.js',
'{projectRoot}/eslint.config.cjs',
{ externalDependencies: ['eslint', 'eslint-plugin-react'] },
]);
});

View File

@ -65,7 +65,7 @@ function postTargetTransformer(
'default',
'{workspaceRoot}/.eslintrc.json',
'{workspaceRoot}/.eslintignore',
'{workspaceRoot}/eslint.config.js',
'{workspaceRoot}/eslint.config.cjs',
].includes(input)
);
if (inputs.length === 0) {

View File

@ -114,7 +114,9 @@ export const getGlobalFlatEslintConfiguration = (
content = addBlockToFlatConfigExport(
content,
generateFlatOverride({ ignores: ['**/dist'] })
generateFlatOverride({
ignores: ['**/dist'],
})
);
if (!rootProject) {
@ -145,7 +147,14 @@ export const getGlobalFlatEslintConfiguration = (
content = addBlockToFlatConfigExport(
content,
generateFlatOverride({
files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
files: [
'**/*.ts',
'**/*.tsx',
'**/*.js',
'**/*.jsx',
'**/*.cjs',
'**/*.mjs',
],
rules: {},
})
);

View File

@ -57,9 +57,9 @@ export function migrateConfigToMonorepoStyle(
keepExistingVersions
);
tree.write(
tree.exists('eslint.config.js')
? 'eslint.base.config.js'
: 'eslint.config.js',
tree.exists('eslint.config.cjs')
? 'eslint.base.config.cjs'
: 'eslint.config.cjs',
getGlobalFlatEslintConfiguration()
);
} else {
@ -134,7 +134,7 @@ function migrateEslintFile(projectEslintPath: string, tree: Tree) {
let config = tree.read(projectEslintPath, 'utf-8');
// remove @nx plugin
config = removePlugin(config, '@nx', '@nx/eslint-plugin-nx');
// extend eslint.base.config.js
// extend eslint.base.config.cjs
config = addImportToFlatConfig(
config,
'baseConfig',

View File

@ -113,7 +113,7 @@ describe('@nx/eslint:init', () => {
'default',
'{workspaceRoot}/.eslintrc.json',
'{workspaceRoot}/.eslintignore',
'{workspaceRoot}/eslint.config.js',
'{workspaceRoot}/eslint.config.cjs',
],
});
});
@ -137,7 +137,7 @@ describe('@nx/eslint:init', () => {
'default',
'{workspaceRoot}/.eslintrc.json',
'{workspaceRoot}/.eslintignore',
'{workspaceRoot}/eslint.config.js',
'{workspaceRoot}/eslint.config.cjs',
],
});
});

View File

@ -28,7 +28,7 @@ function updateProductionFileset(tree: Tree) {
const productionFileSet = nxJson.namedInputs?.production;
if (productionFileSet) {
productionFileSet.push('!{projectRoot}/.eslintrc.json');
productionFileSet.push('!{projectRoot}/eslint.config.js');
productionFileSet.push('!{projectRoot}/eslint.config.cjs');
// Dedupe and set
nxJson.namedInputs.production = Array.from(new Set(productionFileSet));
}
@ -45,7 +45,7 @@ function addTargetDefaults(tree: Tree) {
'default',
`{workspaceRoot}/.eslintrc.json`,
`{workspaceRoot}/.eslintignore`,
`{workspaceRoot}/eslint.config.js`,
`{workspaceRoot}/eslint.config.cjs`,
];
updateNxJson(tree, nxJson);
}

View File

@ -53,7 +53,7 @@ describe('@nx/eslint:lint-project', () => {
skipFormat: true,
});
expect(tree.read('eslint.config.js', 'utf-8')).toMatchInlineSnapshot(`
expect(tree.read('eslint.config.cjs', 'utf-8')).toMatchInlineSnapshot(`
"const nx = require("@nx/eslint-plugin");
module.exports = [
@ -97,7 +97,9 @@ describe('@nx/eslint:lint-project', () => {
"**/*.ts",
"**/*.tsx",
"**/*.js",
"**/*.jsx"
"**/*.jsx",
"**/*.cjs",
"**/*.mjs"
],
// Override or add rules here
rules: {}

View File

@ -273,8 +273,7 @@ function createEsLintConfiguration(
});
const nodeList = createNodeList(importMap, nodes);
const content = stringifyNodeList(nodeList);
const ext = extendedRootConfig?.endsWith('.cjs') ? '.cjs' : '.js';
tree.write(join(projectConfig.root, `eslint.config${ext}`), content);
tree.write(join(projectConfig.root, `eslint.config.cjs`), content);
} else {
writeJson(tree, join(projectConfig.root, `.eslintrc.json`), {
extends: extendedRootConfig ? [pathToRootConfig] : undefined,

View File

@ -71,7 +71,7 @@ function setUpLegacyRootEslintRc(tree: Tree, options: SetupRootEsLintOptions) {
function setUpRootFlatConfig(tree: Tree, options: SetupRootEsLintOptions) {
tree.write(
'eslint.config.js',
'eslint.config.cjs',
getGlobalFlatEslintConfiguration(options.rootProject)
);

View File

@ -122,10 +122,10 @@ describe('@nx/eslint:lint-file', () => {
});
it('should add extends to flat config', () => {
tree.write('eslint.config.js', 'module.exports = {};');
tree.write('eslint.config.cjs', 'module.exports = {};');
tree.write(
'apps/demo/eslint.config.js',
`const baseConfig = require("../../eslint.config.js");
'apps/demo/eslint.config.cjs',
`const baseConfig = require("../../eslint.config.cjs");
module.exports = [
...baseConfig,
@ -143,11 +143,11 @@ module.exports = [
addExtendsToLintConfig(tree, 'apps/demo', 'plugin:playwright/recommend');
expect(tree.read('apps/demo/eslint.config.js', 'utf-8'))
expect(tree.read('apps/demo/eslint.config.cjs', 'utf-8'))
.toMatchInlineSnapshot(`
"const { FlatCompat } = require("@eslint/eslintrc");
const js = require("@eslint/js");
const baseConfig = require("../../eslint.config.js");
const baseConfig = require("../../eslint.config.cjs");
const compat = new FlatCompat({
baseDirectory: __dirname,
@ -177,10 +177,10 @@ module.exports = [
packageJson: { name: 'eslint', version: '9.0.0' },
path: '',
});
tree.write('eslint.config.js', 'module.exports = {};');
tree.write('eslint.config.cjs', 'module.exports = {};');
tree.write(
'apps/demo/eslint.config.js',
`const baseConfig = require("../../eslint.config.js");
'apps/demo/eslint.config.cjs',
`const baseConfig = require("../../eslint.config.cjs");
module.exports = [
...baseConfig,
@ -201,12 +201,12 @@ module.exports = [
needCompatFixup: true,
});
expect(tree.read('apps/demo/eslint.config.js', 'utf-8'))
expect(tree.read('apps/demo/eslint.config.cjs', 'utf-8'))
.toMatchInlineSnapshot(`
"const { FlatCompat } = require("@eslint/eslintrc");
const js = require("@eslint/js");
const { fixupConfigRules } = require("@eslint/compat");
const baseConfig = require("../../eslint.config.js");
const baseConfig = require("../../eslint.config.cjs");
const compat = new FlatCompat({
baseDirectory: __dirname,
@ -236,10 +236,10 @@ module.exports = [
packageJson: { name: 'eslint', version: '9.0.0' },
path: '',
});
tree.write('eslint.config.js', 'module.exports = {};');
tree.write('eslint.config.cjs', 'module.exports = {};');
tree.write(
'apps/demo/eslint.config.js',
`const baseConfig = require("../../eslint.config.js");
'apps/demo/eslint.config.cjs',
`const baseConfig = require("../../eslint.config.cjs");
module.exports = [
...baseConfig,
@ -264,12 +264,12 @@ module.exports = [
{ name: 'incompatible-plugin3', needCompatFixup: true },
]);
expect(tree.read('apps/demo/eslint.config.js', 'utf-8'))
expect(tree.read('apps/demo/eslint.config.cjs', 'utf-8'))
.toMatchInlineSnapshot(`
"const { FlatCompat } = require("@eslint/eslintrc");
const js = require("@eslint/js");
const { fixupConfigRules } = require("@eslint/compat");
const baseConfig = require("../../eslint.config.js");
const baseConfig = require("../../eslint.config.cjs");
const compat = new FlatCompat({
baseDirectory: __dirname,
@ -307,10 +307,10 @@ module.exports = [
packageJson: { name: 'eslint', version: '8.0.0' },
path: '',
});
tree.write('eslint.config.js', 'module.exports = {};');
tree.write('eslint.config.cjs', 'module.exports = {};');
tree.write(
'apps/demo/eslint.config.js',
`const baseConfig = require("../../eslint.config.js");
'apps/demo/eslint.config.cjs',
`const baseConfig = require("../../eslint.config.cjs");
module.exports = [
...baseConfig,
@ -331,11 +331,11 @@ module.exports = [
needCompatFixup: true,
});
expect(tree.read('apps/demo/eslint.config.js', 'utf-8'))
expect(tree.read('apps/demo/eslint.config.cjs', 'utf-8'))
.toMatchInlineSnapshot(`
"const { FlatCompat } = require("@eslint/eslintrc");
const js = require("@eslint/js");
const baseConfig = require("../../eslint.config.js");
const baseConfig = require("../../eslint.config.cjs");
const compat = new FlatCompat({
baseDirectory: __dirname,
@ -362,10 +362,10 @@ module.exports = [
describe('replaceOverridesInLintConfig', () => {
it('should replace overrides when using flat config', () => {
tree.write('eslint.config.js', 'module.exports = {};');
tree.write('eslint.config.cjs', 'module.exports = {};');
tree.write(
'apps/demo/eslint.config.js',
`const baseConfig = require("../../eslint.config.js");
'apps/demo/eslint.config.cjs',
`const baseConfig = require("../../eslint.config.cjs");
module.exports = [
...baseConfig,
@ -428,11 +428,11 @@ module.exports = [
},
]);
expect(tree.read('apps/demo/eslint.config.js', 'utf-8'))
expect(tree.read('apps/demo/eslint.config.cjs', 'utf-8'))
.toMatchInlineSnapshot(`
"const { FlatCompat } = require("@eslint/eslintrc");
const js = require("@eslint/js");
const baseConfig = require("../../eslint.config.js");
const baseConfig = require("../../eslint.config.cjs");
const compat = new FlatCompat({
baseDirectory: __dirname,

View File

@ -14,9 +14,10 @@ import {
baseEsLintConfigFile,
baseEsLintFlatConfigFile,
ESLINT_CONFIG_FILENAMES,
legacyBaseEsLintFlatConfigFile,
} from '../../utils/config-file';
import {
getRootESLintFlatConfigFilename,
eslintFlatConfigFilenames,
useFlatConfig,
} from '../../utils/flat-config';
import { getInstalledEslintVersion } from '../../utils/version-utils';
@ -50,6 +51,12 @@ export function findEslintFile(
if (projectRoot === undefined && tree.exists(baseEsLintFlatConfigFile)) {
return baseEsLintFlatConfigFile;
}
if (
projectRoot === undefined &&
tree.exists(legacyBaseEsLintFlatConfigFile)
) {
return legacyBaseEsLintFlatConfigFile;
}
projectRoot ??= '';
for (const file of ESLINT_CONFIG_FILENAMES) {
if (tree.exists(joinPathFragments(projectRoot, file))) {
@ -188,10 +195,18 @@ export function addOverrideToLintConfig(
const isBase =
options.checkBaseConfig && findEslintFile(tree, root).includes('.base');
if (useFlatConfig(tree)) {
const fileName = joinPathFragments(
root,
isBase ? baseEsLintFlatConfigFile : getRootESLintFlatConfigFilename(tree)
);
let fileName: string;
if (isBase) {
fileName = joinPathFragments(root, baseEsLintFlatConfigFile);
} else {
for (const f of eslintFlatConfigFilenames) {
if (tree.exists(joinPathFragments(root, f))) {
fileName = joinPathFragments(root, f);
break;
}
}
}
const flatOverride = generateFlatOverride(override);
let content = tree.read(fileName, 'utf8');
// Check if the provided override using legacy eslintrc properties or plugins, if so we need to add compat
@ -235,7 +250,15 @@ export function updateOverrideInLintConfig(
}
if (useFlatConfig(tree)) {
fileName ??= joinPathFragments(root, getRootESLintFlatConfigFilename(tree));
if (!fileName) {
for (const f of eslintFlatConfigFilenames) {
if (tree.exists(joinPathFragments(root, f))) {
fileName = joinPathFragments(root, f);
break;
}
}
}
let content = tree.read(fileName, 'utf8');
content = replaceOverride(content, root, lookup, update);
tree.write(fileName, content);
@ -282,11 +305,18 @@ export function lintConfigHasOverride(
!fileName &&
checkBaseConfig &&
findEslintFile(tree, root).includes('.base');
if (isBase) {
fileName = joinPathFragments(root, baseEsLintFlatConfigFile);
}
if (useFlatConfig(tree)) {
fileName ??= joinPathFragments(
root,
isBase ? baseEsLintFlatConfigFile : getRootESLintFlatConfigFilename(tree)
);
if (!fileName) {
for (const f of eslintFlatConfigFilenames) {
if (tree.exists(joinPathFragments(root, f))) {
fileName = joinPathFragments(root, f);
break;
}
}
}
const content = tree.read(fileName, 'utf8');
return hasOverride(content, lookup);
} else {
@ -305,10 +335,13 @@ export function replaceOverridesInLintConfig(
overrides: Linter.ConfigOverride<Linter.RulesRecord>[]
) {
if (useFlatConfig(tree)) {
const fileName = joinPathFragments(
root,
getRootESLintFlatConfigFilename(tree)
);
let fileName: string;
for (const f of eslintFlatConfigFilenames) {
if (tree.exists(joinPathFragments(root, f))) {
fileName = joinPathFragments(root, f);
break;
}
}
let content = tree.read(fileName, 'utf8');
// Check if any of the provided overrides using legacy eslintrc properties or plugins, if so we need to add compat
if (overrides.some(overrideNeedsCompat)) {
@ -341,10 +374,14 @@ export function addExtendsToLintConfig(
): GeneratorCallback {
if (useFlatConfig(tree)) {
const pluginExtends: ts.SpreadElement[] = [];
const fileName = joinPathFragments(
root,
getRootESLintFlatConfigFilename(tree)
);
let fileName: string;
for (const f of eslintFlatConfigFilenames) {
if (tree.exists(joinPathFragments(root, f))) {
fileName = joinPathFragments(root, f);
break;
}
}
let shouldImportEslintCompat = false;
// assume eslint version is 9 if not found, as it's what we'd be generating by default
const eslintVersion =
@ -445,10 +482,13 @@ export function addPredefinedConfigToFlatLintConfig(
if (!useFlatConfig(tree))
throw new Error('Predefined configs can only be used with flat configs');
const fileName = joinPathFragments(
root,
getRootESLintFlatConfigFilename(tree)
);
let fileName: string;
for (const f of eslintFlatConfigFilenames) {
if (tree.exists(joinPathFragments(root, f))) {
fileName = joinPathFragments(root, f);
break;
}
}
let content = tree.read(fileName, 'utf8');
content = addImportToFlatConfig(content, moduleName, moduleImportPath);
@ -468,10 +508,14 @@ export function addPluginsToLintConfig(
) {
const plugins = Array.isArray(plugin) ? plugin : [plugin];
if (useFlatConfig(tree)) {
const fileName = joinPathFragments(
root,
getRootESLintFlatConfigFilename(tree)
);
let fileName: string;
for (const f of eslintFlatConfigFilenames) {
if (tree.exists(joinPathFragments(root, f))) {
fileName = joinPathFragments(root, f);
break;
}
}
let content = tree.read(fileName, 'utf8');
const mappedPlugins: { name: string; varName: string; imp: string }[] = [];
plugins.forEach((name) => {
@ -499,10 +543,14 @@ export function addIgnoresToLintConfig(
ignorePatterns: string[]
) {
if (useFlatConfig(tree)) {
const fileName = joinPathFragments(
root,
getRootESLintFlatConfigFilename(tree)
);
let fileName: string;
for (const f of eslintFlatConfigFilenames) {
if (tree.exists(joinPathFragments(root, f))) {
fileName = joinPathFragments(root, f);
break;
}
}
const block = generateAst<ts.ObjectLiteralExpression>({
ignores: ignorePatterns.map((path) => mapFilePath(path)),
});

View File

@ -124,6 +124,12 @@ describe('ast-utils', () => {
]
}).map(config => ({
...config,
files: [
"**/*.ts",
"**/*.tsx",
"**/*.cts",
"**/*.mts"
],
rules: {
...config.rules
}
@ -182,7 +188,7 @@ describe('ast-utils', () => {
describe('addBlockToFlatConfigExport', () => {
it('should inject block to the end of the file', () => {
const content = `const baseConfig = require("../../eslint.config.js");
const content = `const baseConfig = require("../../eslint.config.cjs");
module.exports = [
...baseConfig,
{
@ -204,7 +210,7 @@ describe('ast-utils', () => {
})
);
expect(result).toMatchInlineSnapshot(`
"const baseConfig = require("../../eslint.config.js");
"const baseConfig = require("../../eslint.config.cjs");
module.exports = [
...baseConfig,
{
@ -228,7 +234,7 @@ describe('ast-utils', () => {
});
it('should inject spread to the beginning of the file', () => {
const content = `const baseConfig = require("../../eslint.config.js");
const content = `const baseConfig = require("../../eslint.config.cjs");
module.exports = [
...baseConfig,
{
@ -246,7 +252,7 @@ describe('ast-utils', () => {
{ insertAtTheEnd: false }
);
expect(result).toMatchInlineSnapshot(`
"const baseConfig = require("../../eslint.config.js");
"const baseConfig = require("../../eslint.config.cjs");
module.exports = [
...config,
@ -266,7 +272,7 @@ describe('ast-utils', () => {
describe('addImportToFlatConfig', () => {
it('should inject import if not found', () => {
const content = `const baseConfig = require("../../eslint.config.js");
const content = `const baseConfig = require("../../eslint.config.cjs");
module.exports = [
...baseConfig,
{
@ -285,7 +291,7 @@ describe('ast-utils', () => {
);
expect(result).toMatchInlineSnapshot(`
"const varName = require("@myorg/awesome-config");
const baseConfig = require("../../eslint.config.js");
const baseConfig = require("../../eslint.config.cjs");
module.exports = [
...baseConfig,
{
@ -302,7 +308,7 @@ describe('ast-utils', () => {
it('should update import if already found', () => {
const content = `const { varName } = require("@myorg/awesome-config");
const baseConfig = require("../../eslint.config.js");
const baseConfig = require("../../eslint.config.cjs");
module.exports = [
...baseConfig,
{
@ -321,7 +327,7 @@ describe('ast-utils', () => {
);
expect(result).toMatchInlineSnapshot(`
"const { varName, otherName, someName } = require("@myorg/awesome-config");
const baseConfig = require("../../eslint.config.js");
const baseConfig = require("../../eslint.config.cjs");
module.exports = [
...baseConfig,
{
@ -338,7 +344,7 @@ describe('ast-utils', () => {
it('should not inject import if already exists', () => {
const content = `const { varName, otherName } = require("@myorg/awesome-config");
const baseConfig = require("../../eslint.config.js");
const baseConfig = require("../../eslint.config.cjs");
module.exports = [
...baseConfig,
{
@ -360,7 +366,7 @@ describe('ast-utils', () => {
it('should not update import if already exists', () => {
const content = `const varName = require("@myorg/awesome-config");
const baseConfig = require("../../eslint.config.js");
const baseConfig = require("../../eslint.config.cjs");
module.exports = [
...baseConfig,
{
@ -409,7 +415,7 @@ describe('ast-utils', () => {
describe('addCompatToFlatConfig', () => {
it('should add compat to config', () => {
const content = `const baseConfig = require("../../eslint.config.js");
const content = `const baseConfig = require("../../eslint.config.cjs");
module.exports = [
...baseConfig,
{
@ -425,7 +431,7 @@ describe('ast-utils', () => {
expect(result).toMatchInlineSnapshot(`
"const { FlatCompat } = require("@eslint/eslintrc");
const js = require("@eslint/js");
const baseConfig = require("../../eslint.config.js");
const baseConfig = require("../../eslint.config.cjs");
const compat = new FlatCompat({
baseDirectory: __dirname,
@ -446,7 +452,7 @@ describe('ast-utils', () => {
});
it('should add only partially compat to config if parts exist', () => {
const content = `const baseConfig = require("../../eslint.config.js");
const content = `const baseConfig = require("../../eslint.config.cjs");
const js = require("@eslint/js");
module.exports = [
...baseConfig,
@ -462,7 +468,7 @@ describe('ast-utils', () => {
const result = addFlatCompatToFlatConfig(content);
expect(result).toMatchInlineSnapshot(`
"const { FlatCompat } = require("@eslint/eslintrc");
const baseConfig = require("../../eslint.config.js");
const baseConfig = require("../../eslint.config.cjs");
const js = require("@eslint/js");
const compat = new FlatCompat({
@ -485,7 +491,7 @@ describe('ast-utils', () => {
it('should not add compat to config if exist', () => {
const content = `const FlatCompat = require("@eslint/eslintrc");
const baseConfig = require("../../eslint.config.js");
const baseConfig = require("../../eslint.config.cjs");
const js = require("@eslint/js");
const compat = new FlatCompat({
@ -512,7 +518,7 @@ describe('ast-utils', () => {
describe('removeOverridesFromLintConfig', () => {
it('should remove all rules from config', () => {
const content = `const FlatCompat = require("@eslint/eslintrc");
const baseConfig = require("../../eslint.config.js");
const baseConfig = require("../../eslint.config.cjs");
const js = require("@eslint/js");
const compat = new FlatCompat({
@ -552,7 +558,7 @@ describe('ast-utils', () => {
const result = removeOverridesFromLintConfig(content);
expect(result).toMatchInlineSnapshot(`
"const FlatCompat = require("@eslint/eslintrc");
const baseConfig = require("../../eslint.config.js");
const baseConfig = require("../../eslint.config.cjs");
const js = require("@eslint/js");
const compat = new FlatCompat({
@ -568,7 +574,7 @@ describe('ast-utils', () => {
});
it('should remove all rules from starting with first', () => {
const content = `const baseConfig = require("../../eslint.config.js");
const content = `const baseConfig = require("../../eslint.config.cjs");
module.exports = [
{
@ -599,7 +605,7 @@ describe('ast-utils', () => {
];`;
const result = removeOverridesFromLintConfig(content);
expect(result).toMatchInlineSnapshot(`
"const baseConfig = require("../../eslint.config.js");
"const baseConfig = require("../../eslint.config.cjs");
module.exports = [
];"
@ -609,7 +615,7 @@ describe('ast-utils', () => {
describe('replaceOverride', () => {
it('should find and replace rules in override', () => {
const content = `const baseConfig = require("../../eslint.config.js");
const content = `const baseConfig = require("../../eslint.config.cjs");
module.exports = [
{
@ -651,7 +657,7 @@ module.exports = [
})
);
expect(result).toMatchInlineSnapshot(`
"const baseConfig = require("../../eslint.config.js");
"const baseConfig = require("../../eslint.config.cjs");
module.exports = [
{
@ -686,7 +692,7 @@ module.exports = [
});
it('should append rules in override', () => {
const content = `const baseConfig = require("../../eslint.config.js");
const content = `const baseConfig = require("../../eslint.config.cjs");
module.exports = [
{
@ -722,7 +728,7 @@ module.exports = [
})
);
expect(result).toMatchInlineSnapshot(`
"const baseConfig = require("../../eslint.config.js");
"const baseConfig = require("../../eslint.config.cjs");
module.exports = [
{
@ -749,7 +755,7 @@ module.exports = [
});
it('should work for compat overrides', () => {
const content = `const baseConfig = require("../../eslint.config.js");
const content = `const baseConfig = require("../../eslint.config.cjs");
module.exports = [
...compat.config({ extends: ["plugin:@nx/typescript"] }).map(config => ({
@ -777,7 +783,7 @@ module.exports = [
})
);
expect(result).toMatchInlineSnapshot(`
"const baseConfig = require("../../eslint.config.js");
"const baseConfig = require("../../eslint.config.cjs");
module.exports = [
...compat.config({ extends: ["plugin:@nx/typescript"] }).map(config => ({

View File

@ -1002,12 +1002,40 @@ export function generateFlatOverride(
}
// At this point we are applying the flat config compat tooling to the override
const { excludedFiles, parser, parserOptions, rules, files, ...rest } =
let { excludedFiles, parser, parserOptions, rules, files, ...rest } =
override;
const objectLiteralElements: ts.ObjectLiteralElementLike[] = [
ts.factory.createSpreadAssignment(ts.factory.createIdentifier('config')),
];
// If converting the JS rule, then we need to match ESLint default and also include .cjs and .mjs files.
if (
(Array.isArray(rest.extends) &&
rest.extends.includes('plugin:@nx/javascript')) ||
rest.extends === 'plugin:@nx/javascript'
) {
const newFiles = new Set(files);
newFiles.add('**/*.js');
newFiles.add('**/*.jsx');
newFiles.add('**/*.cjs');
newFiles.add('**/*.mjs');
files = Array.from(newFiles);
}
// If converting the TS rule, then we need to match ESLint default and also include .cts and .mts files.
if (
(Array.isArray(rest.extends) &&
rest.extends.includes('plugin:@nx/typescript')) ||
rest.extends === 'plugin:@nx/typescript'
) {
const newFiles = new Set(files);
newFiles.add('**/*.ts');
newFiles.add('**/*.tsx');
newFiles.add('**/*.cts');
newFiles.add('**/*.mts');
files = Array.from(newFiles);
}
addTSObjectProperty(objectLiteralElements, 'files', files);
addTSObjectProperty(objectLiteralElements, 'excludedFiles', excludedFiles);

View File

@ -0,0 +1,187 @@
import { type Tree } from '@nx/devkit';
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import migration from './add-file-extensions-to-overrides';
describe('add-file-extensions-to-overrides', () => {
let tree: Tree;
beforeEach(() => {
tree = createTreeWithEmptyWorkspace();
});
it('should add .cjs, .mjs, .cts, .mts file extensions to overrides converted using convert-to-flat-config', async () => {
tree.write(
'eslint.config.js',
`const { FlatCompat } = require('@eslint/eslintrc');
const js = require('@eslint/js');
const nxEslintPlugin = require('@nx/eslint-plugin');
const compat = new FlatCompat({
baseDirectory: __dirname,
recommendedConfig: js.configs.recommended,
});
module.exports = [
...compat
.config({
extends: ['plugin:@nx/typescript'],
})
.map((config) => ({
...config,
files: ['**/*.ts', '**/*.tsx'],
rules: {
...config.rules,
},
})),
...compat
.config({
extends: ['plugin:@nx/javascript'],
})
.map((config) => ({
...config,
files: ['**/*.js', '**/*.jsx'],
rules: {
...config.rules,
},
})),
];`
);
await migration(tree);
const updated = tree.read('eslint.config.js', 'utf-8');
expect(updated).toMatchInlineSnapshot(`
"const { FlatCompat } = require('@eslint/eslintrc');
const js = require('@eslint/js');
const nxEslintPlugin = require('@nx/eslint-plugin');
const compat = new FlatCompat({
baseDirectory: __dirname,
recommendedConfig: js.configs.recommended,
});
module.exports = [
...compat
.config({
extends: ['plugin:@nx/typescript'],
})
.map((config) => ({
...config,
files: ['**/*.ts', '**/*.tsx', '**/*.cts', '**/*.mts'],
rules: {
...config.rules,
},
})),
...compat
.config({
extends: ['plugin:@nx/javascript'],
})
.map((config) => ({
...config,
files: ['**/*.js', '**/*.jsx', '**/*.cjs', '**/*.mjs'],
rules: {
...config.rules,
},
})),
];"
`);
});
it('should handle duplicates', async () => {
tree.write(
'eslint.config.js',
`const { FlatCompat } = require('@eslint/eslintrc');
const js = require('@eslint/js');
const nxEslintPlugin = require('@nx/eslint-plugin');
const compat = new FlatCompat({
baseDirectory: __dirname,
recommendedConfig: js.configs.recommended,
});
module.exports = [
...compat
.config({
extends: ['plugin:@nx/javascript'],
})
.map((config) => ({
...config,
files: ['**/*.js', '**/*.jsx', '**/*.mjs'],
rules: {
...config.rules,
},
})),
];`
);
await migration(tree);
const updated = tree.read('eslint.config.js', 'utf-8');
expect(updated).toMatchInlineSnapshot(`
"const { FlatCompat } = require('@eslint/eslintrc');
const js = require('@eslint/js');
const nxEslintPlugin = require('@nx/eslint-plugin');
const compat = new FlatCompat({
baseDirectory: __dirname,
recommendedConfig: js.configs.recommended,
});
module.exports = [
...compat
.config({
extends: ['plugin:@nx/javascript'],
})
.map((config) => ({
...config,
files: ['**/*.js', '**/*.jsx', '**/*.mjs', '**/*.cjs'],
rules: {
...config.rules,
},
})),
];"
`);
});
it('should not update if plugin:@nx/javascript and plugin:@nx/typescript are not used', async () => {
const original = `const { FlatCompat } = require('@eslint/eslintrc');
const js = require('@eslint/js');
const nxEslintPlugin = require('@nx/eslint-plugin');
const compat = new FlatCompat({
baseDirectory: __dirname,
recommendedConfig: js.configs.recommended,
});
module.exports = [
...compat
.config({
extends: ['plugin:@acme/foo'],
})
.map((config) => ({
...config,
files: ['**/*.ts', '**/*.tsx'],
rules: {
...config.rules,
},
})),
...compat
.config({
extends: ['plugin:@acme/bar'],
})
.map((config) => ({
...config,
files: ['**/*.js', '**/*.jsx'],
rules: {
...config.rules,
},
})),
];`;
tree.write('eslint.config.js', original);
await migration(tree);
const updated = tree.read('eslint.config.js', 'utf-8');
expect(updated).toEqual(original);
});
});

View File

@ -0,0 +1,97 @@
import { type Tree } from '@nx/devkit';
import * as ts from 'typescript';
import { findNodes, replaceChange } from '@nx/js';
export default async function (tree: Tree): Promise<void> {
let rootConfig: string;
// NOTE: we don't support generating ESM base config currently so they are not handled.
for (const candidate of ['eslint.config.js', 'eslint.config.cjs']) {
if (tree.exists(candidate)) {
rootConfig = candidate;
break;
}
}
if (!rootConfig) return;
updateOverrideFileExtensions(
tree,
rootConfig,
'plugin:@nx/typescript',
[`'**/*.ts'`, `'**/*.tsx'`],
[`'**/*.cts'`, `'**/*.mts'`]
);
updateOverrideFileExtensions(
tree,
rootConfig,
'plugin:@nx/javascript',
[`'**/*.js'`, `'**/*.jsx'`],
[`'**/*.cjs'`, `'**/*.mjs'`]
);
}
function updateOverrideFileExtensions(
tree: Tree,
configFile: string,
plugin: string,
matchingExts: string[],
newExts: string[]
): void {
const content = tree.read(configFile, 'utf-8');
const source = ts.createSourceFile(
'',
content,
ts.ScriptTarget.Latest,
true,
ts.ScriptKind.JS
);
let compatNode: ts.SpreadElement;
const spreadElementNodes = findNodes(
source,
ts.SyntaxKind.SpreadElement
) as ts.SpreadElement[];
for (const a of spreadElementNodes) {
const assignmentNodes = findNodes(
a,
ts.SyntaxKind.PropertyAssignment
) as ts.PropertyAssignment[];
if (assignmentNodes.length === 0) continue;
for (const b of assignmentNodes) {
if (
b.name.getText() === 'extends' &&
b.initializer.getText().includes(plugin)
) {
compatNode = a;
break;
}
}
}
if (compatNode) {
const arrayNodes = findNodes(
compatNode,
ts.SyntaxKind.ArrayLiteralExpression
) as ts.ArrayLiteralExpression[];
for (const a of arrayNodes) {
if (
matchingExts.every((ext) => a.elements.some((e) => e.getText() === ext))
) {
const exts = new Set(a.elements.map((e) => e.getText()));
for (const ext of newExts) {
exts.add(ext);
}
replaceChange(
tree,
source,
configFile,
a.getStart(a.getSourceFile()),
`[${Array.from(exts).join(', ')}]`,
a.getText()
).getText();
}
}
}
}

View File

@ -73,13 +73,13 @@ describe('@nx/eslint/plugin', () => {
// TODO(leo): dynamic import of the flat config fails with jest:
// "TypeError: A dynamic import callback was invoked without --experimental-vm-modules"
// mocking the "eslint.config.js" file import is not working, figure out if there's a way
// mocking the "eslint.config.cjs" file import is not working, figure out if there's a way
it.skip('should not create a node for a root level eslint config when accompanied by a project.json, if no src directory is present', async () => {
createFiles({
'eslint.config.js': `module.exports = {};`,
'eslint.config.cjs': `module.exports = {};`,
'project.json': `{}`,
});
// NOTE: It should set ESLINT_USE_FLAT_CONFIG to true because of the use of eslint.config.js
// NOTE: It should set ESLINT_USE_FLAT_CONFIG to true because of the use of eslint.config.cjs
expect(
await invokeCreateNodesOnMatchingFiles(context, { targetName: 'lint' })
).toMatchInlineSnapshot(`

View File

@ -19,7 +19,9 @@ export const ESLINT_CONFIG_FILENAMES = [
];
export const baseEsLintConfigFile = '.eslintrc.base.json';
export const baseEsLintFlatConfigFile = 'eslint.base.config.js';
export const baseEsLintFlatConfigFile = 'eslint.base.config.cjs';
// Make sure we can handle previous file extension as well for migrations or custom generators.
export const legacyBaseEsLintFlatConfigFile = 'eslint.base.config.js';
export function isFlatConfig(configFilePath: string): boolean {
const configFileName = basename(configFilePath);

View File

@ -3,8 +3,8 @@ import { gte } from 'semver';
// todo: add support for eslint.config.mjs,
export const eslintFlatConfigFilenames = [
'eslint.config.js',
'eslint.config.cjs',
'eslint.config.js',
];
export function getRootESLintFlatConfigFilename(tree: Tree): string {

View File

@ -624,7 +624,7 @@ describe('app', () => {
describe('--linter', () => {
describe('default (eslint)', () => {
it('should add flat config as needed', async () => {
tree.write('eslint.config.js', '');
tree.write('eslint.config.cjs', '');
const name = uniq();
await applicationGenerator(tree, {
@ -632,12 +632,12 @@ describe('app', () => {
style: 'css',
});
expect(tree.read(`${name}/eslint.config.js`, 'utf-8'))
expect(tree.read(`${name}/eslint.config.cjs`, 'utf-8'))
.toMatchInlineSnapshot(`
"const { FlatCompat } = require('@eslint/eslintrc');
const js = require('@eslint/js');
const nx = require('@nx/eslint-plugin');
const baseConfig = require('../eslint.config.js');
const baseConfig = require('../eslint.config.cjs');
const compat = new FlatCompat({
baseDirectory: __dirname,

View File

@ -96,16 +96,16 @@ describe('updateEslint', () => {
});
it('should update the flat config', async () => {
tree.write('eslint.config.js', `module.exports = []`);
tree.write('eslint.config.cjs', `module.exports = []`);
await addLinting(tree, schema);
expect(tree.read(`${schema.appProjectRoot}/eslint.config.js`, 'utf-8'))
expect(tree.read(`${schema.appProjectRoot}/eslint.config.cjs`, 'utf-8'))
.toMatchInlineSnapshot(`
"const { FlatCompat } = require("@eslint/eslintrc");
const js = require("@eslint/js");
const nx = require("@nx/eslint-plugin");
const baseConfig = require("../eslint.config.js");
const baseConfig = require("../eslint.config.cjs");
const compat = new FlatCompat({
baseDirectory: __dirname,

View File

@ -35,7 +35,7 @@ exports[`app generated files content - as-provided - my-app general application
exports[`app generated files content - as-provided - my-app general application should configure eslint correctly (flat config) 1`] = `
"const { FlatCompat } = require('@eslint/eslintrc');
const js = require('@eslint/js');
const baseConfig = require('../eslint.config.js');
const baseConfig = require('../eslint.config.cjs');
const compat = new FlatCompat({
baseDirectory: __dirname,
@ -394,7 +394,7 @@ exports[`app generated files content - as-provided - myApp general application s
exports[`app generated files content - as-provided - myApp general application should configure eslint correctly (flat config) 1`] = `
"const { FlatCompat } = require('@eslint/eslintrc');
const js = require('@eslint/js');
const baseConfig = require('../eslint.config.js');
const baseConfig = require('../eslint.config.cjs');
const compat = new FlatCompat({
baseDirectory: __dirname,

View File

@ -66,7 +66,7 @@ describe('app', () => {
});
it('should configure eslint correctly (flat config)', async () => {
tree.write('eslint.config.js', '');
tree.write('eslint.config.cjs', '');
await applicationGenerator(tree, {
directory: name,
@ -74,7 +74,7 @@ describe('app', () => {
});
expect(
tree.read(`${name}/eslint.config.js`, 'utf-8')
tree.read(`${name}/eslint.config.cjs`, 'utf-8')
).toMatchSnapshot();
});

View File

@ -91,7 +91,7 @@ function createNxJson(
].filter(Boolean)
: []),
...(eslintProjectConfigFile
? ['!{projectRoot}/.eslintrc.json', '!{projectRoot}/eslint.config.js']
? ['!{projectRoot}/.eslintrc.json', '!{projectRoot}/eslint.config.cjs']
: []),
].filter(Boolean),
};
@ -124,8 +124,8 @@ function createNxJson(
if (fileExists(join(repoRoot, '.eslintrc.json'))) {
inputs.push('{workspaceRoot}/.eslintrc.json');
}
if (fileExists(join(repoRoot, 'eslint.config.js'))) {
inputs.push('{workspaceRoot}/eslint.config.js');
if (fileExists(join(repoRoot, 'eslint.config.cjs'))) {
inputs.push('{workspaceRoot}/eslint.config.cjs');
}
nxJson.targetDefaults.lint = {
...nxJson.targetDefaults.lint,
@ -230,7 +230,9 @@ function projectHasEslintConfig(
): boolean {
return (
fileExists(join(project.root, '.eslintrc.json')) ||
fileExists(join(project.root, 'eslint.config.js'))
fileExists(join(project.root, 'eslint.config.js')) ||
fileExists(join(project.root, 'eslint.config.mjs')) ||
fileExists(join(project.root, 'eslint.config.cjs'))
);
}

View File

@ -10923,7 +10923,7 @@ describe('createNxReleaseConfig()', () => {
'group-1': {
projects: 'nx',
versionPlans: {
ignorePatternsForPlanCheck: ['**/eslint.config.js'],
ignorePatternsForPlanCheck: ['**/eslint.config.cjs'],
},
},
'group-2': {
@ -11076,7 +11076,7 @@ describe('createNxReleaseConfig()', () => {
},
"versionPlans": {
"ignorePatternsForPlanCheck": [
"**/eslint.config.js",
"**/eslint.config.cjs",
],
},
},

View File

@ -309,52 +309,6 @@ export default {...nxPreset};
tasks.push(await addE2E(tree, options));
// If the project package.json uses type module, and the project uses flat eslint config, we need to make sure the eslint config uses an explicit .cjs extension
// TODO: This could be re-evaluated once we support ESM in eslint configs
if (
tree.exists(joinPathFragments(options.projectRoot, 'package.json')) &&
tree.exists(joinPathFragments(options.projectRoot, 'eslint.config.js'))
) {
const pkgJson = readJson(
tree,
joinPathFragments(options.projectRoot, 'package.json')
);
if (pkgJson.type === 'module') {
tree.rename(
joinPathFragments(options.projectRoot, 'eslint.config.js'),
joinPathFragments(options.projectRoot, 'eslint.config.cjs')
);
visitNotIgnoredFiles(tree, options.projectRoot, (file) => {
if (file.endsWith('eslint.config.js')) {
// Replace any extends on the eslint config to use the .cjs extension
const content = tree.read(file).toString();
if (content.includes('eslint.config')) {
tree.write(
file,
content
.replace(/eslint\.config'/g, `eslint.config.cjs'`)
.replace(/eslint\.config"/g, `eslint.config.cjs"`)
.replace(/eslint\.config\.js/g, `eslint.config.cjs`)
);
}
// If there is no sibling package.json with type commonjs, we need to rename the .js files to .cjs
const siblingPackageJsonPath = joinPathFragments(
dirname(file),
'package.json'
);
if (tree.exists(siblingPackageJsonPath)) {
const siblingPkgJson = readJson(tree, siblingPackageJsonPath);
if (siblingPkgJson.type === 'module') {
return;
}
}
tree.rename(file, file.replace('.js', '.cjs'));
}
});
}
}
addViteTempFilesToGitIgnore(tree);
if (!options.skipFormat) {
await formatFiles(tree);

View File

@ -279,7 +279,7 @@ exports[`library should ignore test files in tsconfig.lib.json 1`] = `
exports[`library should support eslint flat config 1`] = `
"const vue = require('eslint-plugin-vue');
const baseConfig = require('../eslint.config.js');
const baseConfig = require('../eslint.config.cjs');
module.exports = [
...baseConfig,

View File

@ -151,7 +151,7 @@ describe('library', () => {
it('should support eslint flat config', async () => {
tree.write(
'eslint.config.js',
'eslint.config.cjs',
`const { FlatCompat } = require('@eslint/eslintrc');
const nxEslintPlugin = require('@nx/eslint-plugin');
const js = require('@eslint/js');
@ -208,10 +208,10 @@ module.exports = [
await libraryGenerator(tree, defaultSchema);
const eslintJson = tree.read('my-lib/eslint.config.js', 'utf-8');
const eslintJson = tree.read('my-lib/eslint.config.cjs', 'utf-8');
expect(eslintJson).toMatchSnapshot();
// assert **/*.vue was added to override in base eslint config
const eslintBaseJson = tree.read('eslint.config.js', 'utf-8');
const eslintBaseJson = tree.read('eslint.config.cjs', 'utf-8');
expect(eslintBaseJson).toContain(
`files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx', '**/*.vue'],`
);

View File

@ -140,7 +140,7 @@ exports[`moveProjectConfiguration should rename the project correctly except for
"default",
"{workspaceRoot}/.eslintrc.json",
"{workspaceRoot}/.eslintignore",
"{workspaceRoot}/eslint.config.js",
"{workspaceRoot}/eslint.config.cjs",
],
"options": {},
"outputs": [

View File

@ -199,7 +199,7 @@ describe('moveProjectConfiguration', () => {
'default',
'{workspaceRoot}/.eslintrc.json',
'{workspaceRoot}/.eslintignore',
'{workspaceRoot}/eslint.config.js',
'{workspaceRoot}/eslint.config.cjs',
],
executor: '@nx/eslint:lint',
outputs: ['{options.outputFile}'],

View File

@ -22,6 +22,7 @@ export function moveProjectFiles(
'.babelrc',
'.eslintrc.json',
'eslint.config.js',
'eslint.config.cjs',
/^jest\.config\.((app|lib)\.)?[jt]s$/,
'vite.config.ts',
/^webpack.*\.js$/,

View File

@ -243,7 +243,7 @@ describe('updateEslint (flat config)', () => {
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
tree.delete('.eslintrc.json');
tree.write('eslint.config.js', `module.exports = [];`);
tree.write('eslint.config.cjs', `module.exports = [];`);
});
it('should handle config not existing', async () => {
@ -267,16 +267,16 @@ describe('updateEslint (flat config)', () => {
convertToFlat(tree, 'my-lib');
// This step is usually handled elsewhere
tree.rename(
'my-lib/eslint.config.js',
'shared/my-destination/eslint.config.js'
'my-lib/eslint.config.cjs',
'shared/my-destination/eslint.config.cjs'
);
const projectConfig = readProjectConfiguration(tree, 'my-lib');
updateEslintConfig(tree, schema, projectConfig);
expect(
tree.read('shared/my-destination/eslint.config.js', 'utf-8')
).toEqual(expect.stringContaining(`require('../../eslint.config.js')`));
tree.read('shared/my-destination/eslint.config.cjs', 'utf-8')
).toEqual(expect.stringContaining(`require('../../eslint.config.cjs')`));
});
it('should update config extends path when project is moved from subdirectory', async () => {
@ -287,7 +287,7 @@ describe('updateEslint (flat config)', () => {
});
convertToFlat(tree, 'api/test');
// This step is usually handled elsewhere
tree.rename('api/test/eslint.config.js', 'test/eslint.config.js');
tree.rename('api/test/eslint.config.cjs', 'test/eslint.config.cjs');
const projectConfig = readProjectConfiguration(tree, 'api-test');
@ -302,8 +302,8 @@ describe('updateEslint (flat config)', () => {
updateEslintConfig(tree, newSchema, projectConfig);
expect(tree.read('test/eslint.config.js', 'utf-8')).toEqual(
expect.stringContaining(`require('../eslint.config.js')`)
expect(tree.read('test/eslint.config.cjs', 'utf-8')).toEqual(
expect.stringContaining(`require('../eslint.config.cjs')`)
);
});
@ -316,15 +316,15 @@ describe('updateEslint (flat config)', () => {
convertToFlat(tree, 'my-lib', { hasParser: true });
// This step is usually handled elsewhere
tree.rename(
'my-lib/eslint.config.js',
'shared/my-destination/eslint.config.js'
'my-lib/eslint.config.cjs',
'shared/my-destination/eslint.config.cjs'
);
const projectConfig = readProjectConfiguration(tree, 'my-lib');
updateEslintConfig(tree, schema, projectConfig);
expect(
tree.read('shared/my-destination/eslint.config.js', 'utf-8')
tree.read('shared/my-destination/eslint.config.cjs', 'utf-8')
).toEqual(
expect.stringContaining(
`project: ["shared/my-destination/tsconfig.*?.json"]`
@ -346,15 +346,15 @@ describe('updateEslint (flat config)', () => {
});
// This step is usually handled elsewhere
tree.rename(
'my-lib/eslint.config.js',
'shared/my-destination/eslint.config.js'
'my-lib/eslint.config.cjs',
'shared/my-destination/eslint.config.cjs'
);
const projectConfig = readProjectConfiguration(tree, 'my-lib');
updateEslintConfig(tree, schema, projectConfig);
expect(
tree.read('shared/my-destination/eslint.config.js', 'utf-8')
tree.read('shared/my-destination/eslint.config.cjs', 'utf-8')
).toEqual(
expect.stringContaining(
`project: ["shared/my-destination/tsconfig.*?.json", "shared/my-destination/${storybookProject}"]`
@ -372,15 +372,15 @@ describe('updateEslint (flat config)', () => {
convertToFlat(tree, 'my-lib', { hasParser: true, isString: true });
// This step is usually handled elsewhere
tree.rename(
'my-lib/eslint.config.js',
'shared/my-destination/eslint.config.js'
'my-lib/eslint.config.cjs',
'shared/my-destination/eslint.config.cjs'
);
const projectConfig = readProjectConfiguration(tree, 'my-lib');
updateEslintConfig(tree, schema, projectConfig);
expect(
tree.read('shared/my-destination/eslint.config.js', 'utf-8')
tree.read('shared/my-destination/eslint.config.cjs', 'utf-8')
).toEqual(
expect.stringContaining(
`project: "shared/my-destination/tsconfig.*?.json"`
@ -417,9 +417,9 @@ function convertToFlat(
}
tree.write(
joinPathFragments(path, 'eslint.config.js'),
joinPathFragments(path, 'eslint.config.cjs'),
`const { FlatCompat } = require("@eslint/eslintrc");
const baseConfig = require("${offset}eslint.config.js");
const baseConfig = require("${offset}eslint.config.cjs");
const js = require("@eslint/js");
const compat = new FlatCompat({
baseDirectory: __dirname,

View File

@ -15,8 +15,10 @@ export function updateEslintConfig(
if (
!tree.exists('.eslintrc.json') &&
!tree.exists('eslint.config.js') &&
!tree.exists('eslint.config.cjs') &&
!tree.exists('.eslintrc.base.json') &&
!tree.exists('eslint.base.config.js')
!tree.exists('eslint.base.config.js') &&
!tree.exists('eslint.base.config.cjs')
) {
return;
}

View File

@ -51,7 +51,11 @@ export function updateFilesForRootProjects(
if (!allowedExt.includes(ext)) {
continue;
}
if (file === '.eslintrc.json' || file === 'eslint.config.js') {
if (
file === '.eslintrc.json' ||
file === 'eslint.config.js' ||
file === 'eslint.config.cjs'
) {
continue;
}
@ -104,7 +108,11 @@ export function updateFilesForNonRootProjects(
if (!allowedExt.includes(ext)) {
continue;
}
if (file === '.eslintrc.json' || file === 'eslint.config.js') {
if (
file === '.eslintrc.json' ||
file === 'eslint.config.cjs' ||
file === 'eslint.config.js'
) {
continue;
}