diff --git a/e2e/angular/src/misc.test.ts b/e2e/angular/src/misc.test.ts index 4c0853a5a7d0f..aed7a5e0b1f2f 100644 --- a/e2e/angular/src/misc.test.ts +++ b/e2e/angular/src/misc.test.ts @@ -41,7 +41,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}/.eslintrc.json`); + expect(moveOutput).toContain(`CREATE ${newPath}/eslint.config.js`); expect(moveOutput).toContain(`CREATE ${newPath}/public/favicon.ico`); expect(moveOutput).toContain(`CREATE ${newPath}/src/index.html`); expect(moveOutput).toContain(`CREATE ${newPath}/src/main.ts`); diff --git a/e2e/angular/src/projects.test.ts b/e2e/angular/src/projects.test.ts index d25513cb3fdfc..501f9ef922fee 100644 --- a/e2e/angular/src/projects.test.ts +++ b/e2e/angular/src/projects.test.ts @@ -127,8 +127,7 @@ describe('Angular Projects', () => { // check e2e tests if (runE2ETests('playwright')) { - const e2eResults = runCLI(`e2e ${app1}-e2e`); - expect(e2eResults).toContain('Successfully ran target e2e for project'); + expect(() => runCLI(`e2e ${app1}-e2e`)).not.toThrow(); expect(await killPort(4200)).toBeTruthy(); } @@ -160,10 +159,7 @@ describe('Angular Projects', () => { ); if (runE2ETests('playwright')) { - const e2eResults = runCLI(`e2e ${app}-e2e`); - expect(e2eResults).toContain( - `Successfully ran target e2e for project ${app}-e2e` - ); + expect(() => runCLI(`e2e ${app}-e2e`)).not.toThrow(); expect(await killPort(4200)).toBeTruthy(); } }, 1000000); @@ -495,7 +491,7 @@ describe('Angular Projects', () => { updateFile(`${lib}/src/lib/${lib}.module.ts`, moduleContent); // ACT - const buildOutput = runCLI(`build ${lib}`); + const buildOutput = runCLI(`build ${lib}`, { env: { CI: 'false' } }); // ASSERT expect(buildOutput).toContain(`Building entry point '@${proj}/${lib}'`); @@ -516,14 +512,9 @@ describe('Angular Projects', () => { // check files are generated with the layout directory ("apps/") checkFilesExist(`apps/${appName}/src/app/app.module.ts`); // check build works - expect(runCLI(`build ${appName}`)).toContain( - `Successfully ran target build for project ${appName}` - ); + expect(() => runCLI(`build ${appName}`)).not.toThrow(); // check tests pass - const appTestResult = runCLI(`test ${appName}`); - expect(appTestResult).toContain( - `Successfully ran target test for project ${appName}` - ); + expect(() => runCLI(`test ${appName}`)).not.toThrow(); runCLI( `generate @nx/angular:lib ${libName} --standalone --buildable --project-name-and-root-format=derived` @@ -535,14 +526,9 @@ describe('Angular Projects', () => { `libs/${libName}/src/lib/${libName}/${libName}.component.ts` ); // check build works - expect(runCLI(`build ${libName}`)).toContain( - `Successfully ran target build for project ${libName}` - ); + expect(() => runCLI(`build ${libName}`)).not.toThrow(); // check tests pass - const libTestResult = runCLI(`test ${libName}`); - expect(libTestResult).toContain( - `Successfully ran target test for project ${libName}` - ); + expect(() => runCLI(`test ${libName}`)).not.toThrow(); }, 500_000); it('should support generating libraries with a scoped name when --project-name-and-root-format=as-provided', () => { @@ -568,14 +554,9 @@ describe('Angular Projects', () => { }.component.ts` ); // check build works - expect(runCLI(`build ${libName}`)).toContain( - `Successfully ran target build for project ${libName}` - ); + expect(() => runCLI(`build ${libName}`)).not.toThrow(); // check tests pass - const libTestResult = runCLI(`test ${libName}`); - expect(libTestResult).toContain( - `Successfully ran target test for project ${libName}` - ); + expect(() => runCLI(`test ${libName}`)).not.toThrow(); }, 500_000); it('should support generating applications with SSR and converting targets with webpack-based executors to use the application executor', async () => { diff --git a/e2e/esbuild/src/esbuild.test.ts b/e2e/esbuild/src/esbuild.test.ts index 2cf0b0ccb3d4a..1d696898b7ca6 100644 --- a/e2e/esbuild/src/esbuild.test.ts +++ b/e2e/esbuild/src/esbuild.test.ts @@ -162,6 +162,7 @@ describe('EsBuild Plugin', () => { expect( readJson(`dist/libs/${parentLib}/package.json`).dependencies ).toEqual({ + 'jsonc-eslint-parser': expect.any(String), // Don't care about the versions, just that they exist rambda: expect.any(String), lodash: expect.any(String), diff --git a/e2e/eslint/src/linter-legacy.test.ts b/e2e/eslint/src/linter-legacy.test.ts index 178aaf67cda50..b460f012bd35b 100644 --- a/e2e/eslint/src/linter-legacy.test.ts +++ b/e2e/eslint/src/linter-legacy.test.ts @@ -14,14 +14,17 @@ import { } from '@nx/e2e/utils'; describe('Linter (legacy)', () => { - describe('Integrated', () => { + describe('Integrated (eslintrc config)', () => { + let originalEslintUseFlatConfigVal: string | undefined; const myapp = uniq('myapp'); const mylib = uniq('mylib'); - let projScope; - beforeAll(() => { - projScope = newProject({ + // Opt into legacy .eslintrc config format for these tests + originalEslintUseFlatConfigVal = process.env.ESLINT_USE_FLAT_CONFIG; + process.env.ESLINT_USE_FLAT_CONFIG = 'false'; + + newProject({ packages: ['@nx/react', '@nx/js', '@nx/eslint'], }); runCLI(`generate @nx/react:app ${myapp} --tags=validtag`, { @@ -31,7 +34,10 @@ describe('Linter (legacy)', () => { env: { NX_ADD_PLUGINS: 'false' }, }); }); - afterAll(() => cleanupProject()); + afterAll(() => { + process.env.ESLINT_USE_FLAT_CONFIG = originalEslintUseFlatConfigVal; + cleanupProject(); + }); describe('linting errors', () => { let defaultEslintrc; @@ -58,8 +64,7 @@ describe('Linter (legacy)', () => { updateFile('.eslintrc.json', JSON.stringify(eslintrc, null, 2)); // 1. linting should error when rules are not followed - let out = runCLI(`lint ${myapp}`, { silenceError: true }); - expect(out).toContain('Unexpected console statement'); + expect(() => runCLI(`lint ${myapp}`)).toThrow(); // 2. linting should not error when rules are not followed and the force flag is specified expect(() => runCLI(`lint ${myapp} --force`)).not.toThrow(); @@ -72,8 +77,9 @@ describe('Linter (legacy)', () => { updateFile('.eslintrc.json', JSON.stringify(eslintrc, null, 2)); // 3. linting should not error when all rules are followed - out = runCLI(`lint ${myapp}`, { silenceError: true }); - expect(out).toContain('All files pass linting'); + expect(() => + runCLI(`lint ${myapp}`, { silenceError: true }) + ).not.toThrow(); }, 1000000); it('should print the effective configuration for a file specified using --print-config', () => { @@ -86,6 +92,7 @@ describe('Linter (legacy)', () => { }); updateFile('.eslintrc.json', JSON.stringify(eslint, null, 2)); const out = runCLI(`lint ${myapp} --print-config src/index.ts`, { + env: { CI: 'false' }, // We don't want to show the summary table from cloud runner silenceError: true, }); expect(out).toContain('"specific-rule": ['); @@ -93,9 +100,19 @@ describe('Linter (legacy)', () => { }); }); - describe('Flat config', () => { + describe('eslintrc convert to flat config', () => { + let originalEslintUseFlatConfigVal: string | undefined; const packageManager = getSelectedPackageManager() || 'pnpm'; + beforeAll(() => { + // Opt into legacy .eslintrc config format for these tests + originalEslintUseFlatConfigVal = process.env.ESLINT_USE_FLAT_CONFIG; + process.env.ESLINT_USE_FLAT_CONFIG = 'false'; + }); + afterAll(() => { + process.env.ESLINT_USE_FLAT_CONFIG = originalEslintUseFlatConfigVal; + }); + beforeEach(() => { process.env.NX_ADD_PLUGINS = 'false'; }); @@ -162,7 +179,9 @@ describe('Linter (legacy)', () => { const outFlat = runCLI(`affected -t lint`, { silenceError: true, }); - expect(outFlat).toContain('ran target lint'); + expect(outFlat).toContain(`${myapp}:lint`); + expect(outFlat).toContain(`${mylib}:lint`); + expect(outFlat).toContain(`${mylib2}:lint`); }, 1000000); it('should convert standalone to flat config', () => { @@ -199,7 +218,8 @@ describe('Linter (legacy)', () => { const outFlat = runCLI(`affected -t lint`, { silenceError: true, }); - expect(outFlat).toContain('ran target lint'); + expect(outFlat).toContain(`${myapp}:lint`); + expect(outFlat).toContain(`${mylib}:lint`); }, 1000000); }); }); diff --git a/e2e/eslint/src/linter.test.ts b/e2e/eslint/src/linter.test.ts index 0afb94d9a0b1e..18ccfb20d45e1 100644 --- a/e2e/eslint/src/linter.test.ts +++ b/e2e/eslint/src/linter.test.ts @@ -15,6 +15,16 @@ import { import * as ts from 'typescript'; describe('Linter', () => { + let originalEslintUseFlatConfigVal: string | undefined; + beforeAll(() => { + // Opt into legacy .eslintrc config format for these tests + originalEslintUseFlatConfigVal = process.env.ESLINT_USE_FLAT_CONFIG; + process.env.ESLINT_USE_FLAT_CONFIG = 'false'; + }); + afterAll(() => { + process.env.ESLINT_USE_FLAT_CONFIG = originalEslintUseFlatConfigVal; + }); + describe('Integrated', () => { const myapp = uniq('myapp'); const mylib = uniq('mylib'); @@ -54,7 +64,10 @@ describe('Linter', () => { }); updateFile('.eslintrc.json', JSON.stringify(eslintrc, null, 2)); - let out = runCLI(`lint ${myapp}`, { silenceError: true }); + let out = runCLI(`lint ${myapp}`, { + silenceError: true, + env: { CI: 'false' }, + }); expect(out).toContain('Unexpected console statement'); eslintrc.overrides.forEach((override) => { @@ -65,7 +78,10 @@ describe('Linter', () => { updateFile('.eslintrc.json', JSON.stringify(eslintrc, null, 2)); // 3. linting should not error when all rules are followed - out = runCLI(`lint ${myapp}`, { silenceError: true }); + out = runCLI(`lint ${myapp}`, { + silenceError: true, + env: { CI: 'false' }, + }); expect(out).toContain('Successfully ran target lint'); }, 1000000); @@ -80,7 +96,10 @@ describe('Linter', () => { // should generate a default cache file let cachePath = path.join('apps', myapp, '.eslintcache'); expect(() => checkFilesExist(cachePath)).toThrow(); - runCLI(`lint ${myapp} --cache`, { silenceError: true }); + runCLI(`lint ${myapp} --cache`, { + silenceError: true, + env: { CI: 'false' }, + }); expect(() => checkFilesExist(cachePath)).not.toThrow(); expect(readCacheFile(cachePath)).toContain( path.normalize(`${myapp}/src/app/app.spec.tsx`) @@ -91,6 +110,7 @@ describe('Linter', () => { expect(() => checkFilesExist(cachePath)).toThrow(); runCLI(`lint ${myapp} --cache --cache-location="my-cache"`, { silenceError: true, + env: { CI: 'false' }, }); expect(() => checkFilesExist(cachePath)).not.toThrow(); expect(readCacheFile(cachePath)).toContain( @@ -116,6 +136,7 @@ describe('Linter', () => { `lint ${myapp} --output-file="${outputFile}" --format=json`, { silenceError: true, + env: { CI: 'false' }, } ); expect(stdout).not.toContain('Unexpected console statement'); @@ -147,8 +168,7 @@ describe('Linter', () => { runCLI(`generate @nx/eslint:workspace-rule ${newRuleName}`); // Ensure that the unit tests for the new rule are runnable - const unitTestsOutput = runCLI(`test eslint-rules`); - expect(unitTestsOutput).toContain('Successfully ran target test'); + expect(() => runCLI(`test eslint-rules`)).not.toThrow(); // Update the rule for the e2e test so that we can assert that it produces the expected lint failure when used const knownLintErrorMessage = 'e2e test known error message'; @@ -177,6 +197,7 @@ describe('Linter', () => { const lintOutput = runCLI(`lint ${myapp} --verbose`, { silenceError: true, + env: { CI: 'false' }, }); expect(lintOutput).toContain(newRuleNameForUsage); expect(lintOutput).toContain(knownLintErrorMessage); @@ -232,7 +253,10 @@ describe('Linter', () => { ` ); - const out = runCLI(`lint ${myapp}`, { silenceError: true }); + const out = runCLI(`lint ${myapp}`, { + silenceError: true, + env: { CI: 'false' }, + }); expect(out).toContain( 'Projects cannot be imported by a relative or absolute path, and must begin with a npm scope' ); @@ -379,6 +403,7 @@ describe('Linter', () => { it('should fix noSelfCircularDependencies', () => { const stdout = runCLI(`lint ${libC}`, { silenceError: true, + env: { CI: 'false' }, }); expect(stdout).toContain( 'Projects should use relative imports to import from other files within the same project' @@ -387,6 +412,7 @@ describe('Linter', () => { // fix them const fixedStout = runCLI(`lint ${libC} --fix`, { silenceError: true, + env: { CI: 'false' }, }); expect(fixedStout).toContain( `Successfully ran target lint for project ${libC}` @@ -407,6 +433,7 @@ describe('Linter', () => { it('should fix noRelativeOrAbsoluteImportsAcrossLibraries', () => { const stdout = runCLI(`lint ${libB}`, { silenceError: true, + env: { CI: 'false' }, }); expect(stdout).toContain( 'Projects cannot be imported by a relative or absolute path, and must begin with a npm scope' @@ -415,6 +442,7 @@ describe('Linter', () => { // fix them const fixedStout = runCLI(`lint ${libB} --fix`, { silenceError: true, + env: { CI: 'false' }, }); expect(fixedStout).toContain( `Successfully ran target lint for project ${libB}` @@ -468,7 +496,10 @@ describe('Linter', () => { const nxVersion = rootPackageJson.devDependencies.nx; const tslibVersion = rootPackageJson.dependencies['tslib']; - let out = runCLI(`lint ${mylib}`, { silenceError: true }); + let out = runCLI(`lint ${mylib}`, { + silenceError: true, + env: { CI: 'false' }, + }); expect(out).toContain('Successfully ran target lint'); // make an explict dependency to nx @@ -485,7 +516,10 @@ describe('Linter', () => { }); // output should now report missing dependency and obsolete dependency - out = runCLI(`lint ${mylib}`, { silenceError: true }); + out = runCLI(`lint ${mylib}`, { + silenceError: true, + env: { CI: 'false' }, + }); expect(out).toContain('they are missing'); expect(out).toContain('@nx/devkit'); expect(out).toContain( @@ -493,7 +527,10 @@ describe('Linter', () => { ); // should fix the missing and obsolete dependency issues - out = runCLI(`lint ${mylib} --fix`, { silenceError: true }); + out = runCLI(`lint ${mylib} --fix`, { + silenceError: true, + env: { CI: 'false' }, + }); expect(out).toContain( `Successfully ran target lint for project ${mylib}` ); @@ -518,13 +555,19 @@ describe('Linter', () => { json.dependencies['@nx/devkit'] = '100.0.0'; return json; }); - out = runCLI(`lint ${mylib}`, { silenceError: true }); + out = runCLI(`lint ${mylib}`, { + silenceError: true, + env: { CI: 'false' }, + }); expect(out).toContain( 'version specifier does not contain the installed version of "@nx/devkit"' ); // should fix the version mismatch issue - out = runCLI(`lint ${mylib} --fix`, { silenceError: true }); + out = runCLI(`lint ${mylib} --fix`, { + silenceError: true, + env: { CI: 'false' }, + }); expect(out).toContain( `Successfully ran target lint for project ${mylib}` ); @@ -532,8 +575,15 @@ describe('Linter', () => { }); describe('flat config', () => { + let envVar: string | undefined; beforeAll(() => { runCLI(`generate @nx/eslint:convert-to-flat-config`); + envVar = process.env.ESLINT_USE_FLAT_CONFIG; + // Now that we have converted the existing configs to flat config we need to clear the explicitly set env var to allow it to infer things from the root config file type + delete process.env.ESLINT_USE_FLAT_CONFIG; + }); + afterAll(() => { + process.env.ESLINT_USE_FLAT_CONFIG = envVar; }); it('should generate new projects using flat config', () => { @@ -557,14 +607,8 @@ describe('Linter', () => { ); // validate that the new projects are linted successfully - let output = runCLI(`lint ${reactLib}`); - expect(output).toContain( - `Successfully ran target lint for project ${reactLib}` - ); - output = runCLI(`lint ${jsLib}`); - expect(output).toContain( - `Successfully ran target lint for project ${jsLib}` - ); + expect(() => runCLI(`lint ${reactLib}`)).not.toThrow(); + expect(() => runCLI(`lint ${jsLib}`)).not.toThrow(); }); }); }); @@ -578,12 +622,12 @@ describe('Linter', () => { afterEach(() => cleanupProject()); function verifySuccessfulStandaloneSetup(myapp: string) { - expect(runCLI(`lint ${myapp}`, { silenceError: true })).toContain( - 'Successfully ran target lint' - ); - expect(runCLI(`lint e2e`, { silenceError: true })).toContain( - 'Successfully ran target lint' - ); + expect( + runCLI(`lint ${myapp}`, { silenceError: true, env: { CI: 'false' } }) + ).toContain('Successfully ran target lint'); + expect( + runCLI(`lint e2e`, { silenceError: true, env: { CI: 'false' } }) + ).toContain('Successfully ran target lint'); expect(() => checkFilesExist(`.eslintrc.base.json`)).toThrow(); const rootEslint = readJson('.eslintrc.json'); @@ -595,15 +639,15 @@ describe('Linter', () => { } function verifySuccessfulMigratedSetup(myapp: string, mylib: string) { - expect(runCLI(`lint ${myapp}`, { silenceError: true })).toContain( - 'Successfully ran target lint' - ); - expect(runCLI(`lint e2e`, { silenceError: true })).toContain( - 'Successfully ran target lint' - ); - expect(runCLI(`lint ${mylib}`, { silenceError: true })).toContain( - 'Successfully ran target lint' - ); + expect( + runCLI(`lint ${myapp}`, { silenceError: true, env: { CI: 'false' } }) + ).toContain('Successfully ran target lint'); + expect( + runCLI(`lint e2e`, { silenceError: true, env: { CI: 'false' } }) + ).toContain('Successfully ran target lint'); + expect( + runCLI(`lint ${mylib}`, { silenceError: true, env: { CI: 'false' } }) + ).toContain('Successfully ran target lint'); expect(() => checkFilesExist(`.eslintrc.base.json`)).not.toThrow(); const rootEslint = readJson('.eslintrc.base.json'); diff --git a/e2e/expo/src/expo-legacy.test.ts b/e2e/expo/src/expo-legacy.test.ts index 01ccc09b94756..a72a5274742a1 100644 --- a/e2e/expo/src/expo-legacy.test.ts +++ b/e2e/expo/src/expo-legacy.test.ts @@ -1,7 +1,6 @@ import { checkFilesExist, cleanupProject, - expectTestsPass, getPackageManagerCommand, killPorts, newProject, @@ -64,8 +63,8 @@ describe('@nx/expo (legacy)', () => { return updated; }); - expectTestsPass(await runCLIAsync(`test ${appName}`)); - expectTestsPass(await runCLIAsync(`test ${libName}`)); + expect(() => runCLI(`test ${appName}`)).not.toThrow(); + expect(() => runCLI(`test ${libName}`)).not.toThrow(); const appLintResults = await runCLIAsync(`lint ${appName}`); expect(appLintResults.combinedOutput).toContain( diff --git a/e2e/jest/src/jest-root.test.ts b/e2e/jest/src/jest-root.test.ts index 86c274bcd8933..151c9eba6673b 100644 --- a/e2e/jest/src/jest-root.test.ts +++ b/e2e/jest/src/jest-root.test.ts @@ -16,10 +16,7 @@ describe('Jest root projects', () => { }); it('should test root level app projects', async () => { - const rootProjectTestResults = await runCLIAsync(`test ${myapp}`); - expect(rootProjectTestResults.combinedOutput).toContain( - 'Test Suites: 1 passed, 1 total' - ); + expect(() => runCLI(`test ${myapp}`)).not.toThrow(); }, 300_000); it('should add lib project and tests should still work', async () => { @@ -27,17 +24,8 @@ describe('Jest root projects', () => { `generate @nx/angular:lib ${mylib} --projectNameAndRootFormat as-provided --no-interactive` ); - const libProjectTestResults = await runCLIAsync(`test ${mylib}`); - - expect(libProjectTestResults.combinedOutput).toContain( - 'Test Suites: 1 passed, 1 total' - ); - - const rootProjectTestResults = await runCLIAsync(`test ${myapp}`); - - expect(rootProjectTestResults.combinedOutput).toContain( - 'Test Suites: 1 passed, 1 total' - ); + expect(() => runCLI(`test ${mylib}`)).not.toThrow(); + expect(() => runCLI(`test ${myapp}`)).not.toThrow(); }, 300_000); }); @@ -53,11 +41,7 @@ describe('Jest root projects', () => { }); it('should test root level app projects', async () => { - const rootProjectTestResults = await runCLIAsync(`test ${myapp}`); - - expect(rootProjectTestResults.combinedOutput).toContain( - 'Test Suites: 1 passed, 1 total' - ); + expect(() => runCLI(`test ${myapp}`)).not.toThrow(); }, 300_000); it('should add lib project and tests should still work', async () => { @@ -65,17 +49,8 @@ describe('Jest root projects', () => { `generate @nx/react:lib ${mylib} --unitTestRunner=jest --projectNameAndRootFormat as-provided` ); - const libProjectTestResults = await runCLIAsync(`test ${mylib}`); - - expect(libProjectTestResults.combinedOutput).toContain( - 'Test Suites: 1 passed, 1 total' - ); - - const rootProjectTestResults = await runCLIAsync(`test ${myapp}`); - - expect(rootProjectTestResults.combinedOutput).toContain( - 'Test Suites: 1 passed, 1 total' - ); + expect(() => runCLI(`test ${mylib}`)).not.toThrow(); + expect(() => runCLI(`test ${myapp}`)).not.toThrow(); }, 300_000); }); }); diff --git a/e2e/js/src/js-packaging.test.ts b/e2e/js/src/js-packaging.test.ts index a910b336fc414..de938c67d5bb7 100644 --- a/e2e/js/src/js-packaging.test.ts +++ b/e2e/js/src/js-packaging.test.ts @@ -10,6 +10,7 @@ import { getPackageManagerCommand, readJson, updateFile, + renameFile, } from '@nx/e2e/utils'; import { join } from 'path'; @@ -169,6 +170,16 @@ 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) => { diff --git a/e2e/nuxt/src/nuxt.test.ts b/e2e/nuxt/src/nuxt.test.ts index 8af5eb4ec47cc..ad44b51c6b240 100644 --- a/e2e/nuxt/src/nuxt.test.ts +++ b/e2e/nuxt/src/nuxt.test.ts @@ -30,22 +30,17 @@ describe('Nuxt Plugin', () => { }); it('should build application', async () => { - const result = runCLI(`build ${app}`); - expect(result).toContain( - `Successfully ran target build for project ${app}` - ); + expect(() => runCLI(`build ${app}`)).not.toThrow(); checkFilesExist(`${app}/.nuxt/nuxt.d.ts`); checkFilesExist(`${app}/.output/nitro.json`); }); it('should test application', async () => { - const result = runCLI(`test ${app}`); - expect(result).toContain(`Successfully ran target test for project ${app}`); + expect(() => runCLI(`test ${app}`)).not.toThrow(); }, 150_000); it('should lint application', async () => { - const result = runCLI(`lint ${app}`); - expect(result).toContain(`Successfully ran target lint for project ${app}`); + expect(() => runCLI(`lint ${app}`)).not.toThrow(); }); it('should build storybook for app', () => { diff --git a/e2e/nx-init/src/nx-init-react.test.ts b/e2e/nx-init/src/nx-init-react.test.ts index 947427a72cbd1..017a74094bdaa 100644 --- a/e2e/nx-init/src/nx-init-react.test.ts +++ b/e2e/nx-init/src/nx-init-react.test.ts @@ -120,28 +120,6 @@ describe('nx init (for React - legacy)', () => { process.env.SELECTED_PM = originalPM; }); - it('should convert to a standalone workspace with craco (webpack)', () => { - const appName = 'my-app'; - createReactApp(appName); - - const craToNxOutput = runCommand( - `${ - pmc.runUninstalledPackage - } nx@${getPublishedVersion()} init --no-interactive --vite=false` - ); - - expect(craToNxOutput).toContain('🎉 Done!'); - - runCLI(`build ${appName}`, { - env: { - // since craco 7.1.0 the NODE_ENV is used, since the tests set it - // to "test" is causes an issue with React Refresh Babel - NODE_ENV: undefined, - }, - }); - checkFilesExist(`dist/${appName}/index.html`); - }); - it('should convert to an standalone workspace with Vite', () => { const appName = 'my-app'; createReactApp(appName); diff --git a/e2e/nx/src/__snapshots__/extras.test.ts.snap b/e2e/nx/src/__snapshots__/extras.test.ts.snap index dfd9c9b84d9d5..fbc8c2974e87d 100644 --- a/e2e/nx/src/__snapshots__/extras.test.ts.snap +++ b/e2e/nx/src/__snapshots__/extras.test.ts.snap @@ -25,8 +25,8 @@ exports[`Extra Nx Misc Tests task graph inputs should correctly expand dependent "nx.json", ], "lib-base-123": [ - "libs/lib-base-123/.eslintrc.json", "libs/lib-base-123/README.md", + "libs/lib-base-123/eslint.config.js", "libs/lib-base-123/jest.config.ts", "libs/lib-base-123/package.json", "libs/lib-base-123/project.json", @@ -38,8 +38,8 @@ exports[`Extra Nx Misc Tests task graph inputs should correctly expand dependent "libs/lib-base-123/tsconfig.spec.json", ], "lib-dependent-123": [ - "libs/lib-dependent-123/.eslintrc.json", "libs/lib-dependent-123/README.md", + "libs/lib-dependent-123/eslint.config.js", "libs/lib-dependent-123/jest.config.ts", "libs/lib-dependent-123/package.json", "libs/lib-dependent-123/project.json", diff --git a/e2e/nx/src/import.test.ts b/e2e/nx/src/import.test.ts index 54ad7ea3874e3..11327fccf87d0 100644 --- a/e2e/nx/src/import.test.ts +++ b/e2e/nx/src/import.test.ts @@ -60,9 +60,14 @@ describe('Nx Import', () => { execSync(`git commit -am "initial commit"`, { cwd: tempViteProjectPath, }); - execSync(`git checkout -b main`, { - cwd: tempViteProjectPath, - }); + + try { + execSync(`git checkout -b main`, { + cwd: tempViteProjectPath, + }); + } catch { + // This fails if git is already configured to have `main` branch, but that's OK + } const remote = tempViteProjectPath; const ref = 'main'; diff --git a/e2e/react-native/src/react-native-legacy.test.ts b/e2e/react-native/src/react-native-legacy.test.ts index ad9aedafe3850..7819c28bb169a 100644 --- a/e2e/react-native/src/react-native-legacy.test.ts +++ b/e2e/react-native/src/react-native-legacy.test.ts @@ -1,7 +1,6 @@ import { checkFilesExist, cleanupProject, - expectTestsPass, getPackageManagerCommand, isOSX, killProcessAndPorts, @@ -52,8 +51,7 @@ describe('@nx/react-native (legacy)', () => { }); it('should build for web', async () => { - const results = runCLI(`build ${appName}`); - expect(results).toContain('Successfully ran target build'); + expect(() => runCLI(`build ${appName}`)).not.toThrow(); }); it('should test and lint', async () => { @@ -67,37 +65,28 @@ describe('@nx/react-native (legacy)', () => { return updated; }); - expectTestsPass(await runCLIAsync(`test ${appName}`)); - expectTestsPass(await runCLIAsync(`test ${libName}`)); - - const appLintResults = await runCLIAsync(`lint ${appName}`); - expect(appLintResults.combinedOutput).toContain( - 'Successfully ran target lint' - ); - - const libLintResults = await runCLIAsync(`lint ${libName}`); - expect(libLintResults.combinedOutput).toContain( - 'Successfully ran target lint' - ); + expect(() => runCLI(`test ${appName}`)).not.toThrow(); + expect(() => runCLI(`test ${libName}`)).not.toThrow(); + expect(() => runCLI(`lint ${appName}`)).not.toThrow(); + expect(() => runCLI(`lint ${libName}`)).not.toThrow(); }); it('should run e2e for cypress', async () => { if (runE2ETests()) { - let results = runCLI(`e2e ${appName}-e2e`); - expect(results).toContain('Successfully ran target e2e'); + expect(() => runCLI(`e2e ${appName}-e2e`)).not.toThrow(); - results = runCLI(`e2e ${appName}-e2e --configuration=ci`); - expect(results).toContain('Successfully ran target e2e'); + expect(() => + runCLI(`e2e ${appName}-e2e --configuration=ci`) + ).not.toThrow(); } }); it('should bundle-ios', async () => { - const iosBundleResult = await runCLIAsync( - `bundle-ios ${appName} --sourcemapOutput=../../dist/apps/${appName}/ios/main.map` - ); - expect(iosBundleResult.combinedOutput).toContain( - 'Done writing bundle output' - ); + expect(() => + runCLI( + `bundle-ios ${appName} --sourcemapOutput=../../dist/apps/${appName}/ios/main.map` + ) + ).not.toThrow(); expect(() => { checkFilesExist(`dist/apps/${appName}/ios/main.jsbundle`); checkFilesExist(`dist/apps/${appName}/ios/main.map`); @@ -105,12 +94,12 @@ describe('@nx/react-native (legacy)', () => { }); it('should bundle-android', async () => { - const androidBundleResult = await runCLIAsync( - `bundle-android ${appName} --sourcemapOutput=../../dist/apps/${appName}/android/main.map` - ); - expect(androidBundleResult.combinedOutput).toContain( - 'Done writing bundle output' - ); + expect(() => + runCLI( + `bundle-android ${appName} --sourcemapOutput=../../dist/apps/${appName}/android/main.map` + ) + ).not.toThrow(); + expect(() => { checkFilesExist(`dist/apps/${appName}/android/main.jsbundle`); checkFilesExist(`dist/apps/${appName}/android/main.map`); @@ -283,10 +272,7 @@ describe('@nx/react-native (legacy)', () => { // using the project name as the directory when no directory is provided checkFilesExist(`${appName}/src/app/App.tsx`); // check tests pass - const appTestResult = runCLI(`test ${appName}`); - expect(appTestResult).toContain( - `Successfully ran target test for project ${appName}` - ); + expect(() => runCLI(`test ${appName}`)).not.toThrow(); // assert scoped project names are not supported when --project-name-and-root-format=derived expect(() => @@ -303,10 +289,7 @@ describe('@nx/react-native (legacy)', () => { // using the project name as the directory when no directory is provided checkFilesExist(`${libName}/src/index.ts`); // check tests pass - const libTestResult = runCLI(`test ${libName}`); - expect(libTestResult).toContain( - `Successfully ran target test for project ${libName}` - ); + expect(() => runCLI(`test ${libName}`)).not.toThrow(); }); it('should run build with vite bundler and e2e with playwright', async () => { @@ -314,11 +297,9 @@ describe('@nx/react-native (legacy)', () => { runCLI( `generate @nx/react-native:application ${appName2} --bundler=vite --e2eTestRunner=playwright --install=false --no-interactive` ); - const buildResults = runCLI(`build ${appName2}`); - expect(buildResults).toContain('Successfully ran target build'); + expect(() => runCLI(`build ${appName2}`)).not.toThrow(); if (runE2ETests()) { - const e2eResults = runCLI(`e2e ${appName2}-e2e`); - expect(e2eResults).toContain('Successfully ran target e2e'); + expect(() => runCLI(`e2e ${appName2}-e2e`)).not.toThrow(); } runCLI( diff --git a/e2e/react-native/src/react-native.test.ts b/e2e/react-native/src/react-native.test.ts index dc3a1f94dc452..cc05186ea437a 100644 --- a/e2e/react-native/src/react-native.test.ts +++ b/e2e/react-native/src/react-native.test.ts @@ -25,14 +25,12 @@ describe('@nx/react-native', () => { afterAll(() => cleanupProject()); it('should bundle the app', async () => { - const result = runCLI( - `bundle ${appName} --platform=ios --bundle-output=dist.js --entry-file=src/main.tsx` - ); + expect(() => + runCLI( + `bundle ${appName} --platform=ios --bundle-output=dist.js --entry-file=src/main.tsx` + ) + ).not.toThrow(); fileExists(` ${appName}/dist.js`); - - expect(result).toContain( - `Successfully ran target bundle for project ${appName}` - ); }, 200_000); it('should start the app', async () => { @@ -87,11 +85,11 @@ describe('@nx/react-native', () => { it('should run e2e for cypress', async () => { if (runE2ETests()) { - let results = runCLI(`e2e ${appName}-e2e`); - expect(results).toContain('Successfully ran target e2e'); + expect(() => runCLI(`e2e ${appName}-e2e`)).not.toThrow(); - results = runCLI(`e2e ${appName}-e2e --configuration=ci`); - expect(results).toContain('Successfully ran target e2e'); + expect(() => + runCLI(`e2e ${appName}-e2e --configuration=ci`) + ).not.toThrow(); // port and process cleanup try { @@ -119,11 +117,9 @@ describe('@nx/react-native', () => { runCLI( `generate @nx/react-native:application ${appName2} --bundler=vite --e2eTestRunner=playwright --install=false --no-interactive` ); - const buildResults = runCLI(`build ${appName2}`); - expect(buildResults).toContain('Successfully ran target build'); + expect(() => runCLI(`build ${appName2}`)).not.toThrow(); if (runE2ETests()) { - const e2eResults = runCLI(`e2e ${appName2}-e2e`); - expect(e2eResults).toContain('Successfully ran target e2e'); + expect(() => runCLI(`e2e ${appName2}-e2e`)).not.toThrow(); // port and process cleanup try { if (process && process.pid) { diff --git a/e2e/utils/command-utils.ts b/e2e/utils/command-utils.ts index 0aeb47d0e7bee..1d2cb61822742 100644 --- a/e2e/utils/command-utils.ts +++ b/e2e/utils/command-utils.ts @@ -232,13 +232,25 @@ export function runCommandAsync( }, (err, stdout, stderr) => { if (!opts.silenceError && err) { + logError(`Original command: ${command}`, `${stdout}\n\n${stderr}`); reject(err); } - resolve({ + + const outputs = { stdout: stripConsoleColors(stdout), stderr: stripConsoleColors(stderr), combinedOutput: stripConsoleColors(`${stdout}${stderr}`), - }); + }; + + if (opts.verbose ?? isVerboseE2ERun()) { + output.log({ + title: `Original command: ${command}`, + bodyLines: [outputs.combinedOutput], + color: 'green', + }); + } + + resolve(outputs); } ); }); @@ -302,10 +314,11 @@ export function runCLIAsync( } ): Promise<{ stdout: string; stderr: string; combinedOutput: string }> { const pm = getPackageManagerCommand(); - return runCommandAsync( - `${opts.silent ? pm.runNxSilent : pm.runNx} ${command}`, - opts - ); + const commandToRun = `${opts.silent ? pm.runNxSilent : pm.runNx} ${command} ${ + opts.verbose ?? isVerboseE2ERun() ? ' --verbose' : '' + }${opts.redirectStderr ? ' 2>&1' : ''}`; + + return runCommandAsync(commandToRun, opts); } export function runNgAdd( diff --git a/e2e/vite/src/vite.test.ts b/e2e/vite/src/vite.test.ts index 4b94d6e694b7a..51563c65de918 100644 --- a/e2e/vite/src/vite.test.ts +++ b/e2e/vite/src/vite.test.ts @@ -45,24 +45,20 @@ describe('@nx/vite/plugin', () => { describe('build and test React app', () => { it('should build application', () => { - const result = runCLI(`build ${myApp}`); - expect(result).toContain('Successfully ran target build'); + expect(() => runCLI(`build ${myApp}`)).not.toThrow(); }, 200_000); it('should test application', () => { - const result = runCLI(`test ${myApp} --watch=false`); - expect(result).toContain('Successfully ran target test'); + expect(() => runCLI(`test ${myApp} --watch=false`)).not.toThrow(); }, 200_000); }); describe('build and test Vue app', () => { it('should build application', () => { - const result = runCLI(`build ${myVueApp}`); - expect(result).toContain('Successfully ran target build'); + expect(() => runCLI(`build ${myVueApp}`)).not.toThrow(); }, 200_000); it('should test application', () => { - const result = runCLI(`test ${myVueApp} --watch=false`); - expect(result).toContain('Successfully ran target test'); + expect(() => runCLI(`test ${myVueApp} --watch=false`)).not.toThrow(); }, 200_000); }); @@ -129,13 +125,7 @@ describe('@nx/vite/plugin', () => { });` ); - const result = runCLI(`build ${myApp}`); - expect(result).toContain( - `Running target build for project ${myApp} and 1 task it depends on` - ); - expect(result).toContain( - `Successfully ran target build for project ${myApp} and 1 task it depends on` - ); + expect(() => runCLI(`build ${myApp}`)).not.toThrow(); }); }); diff --git a/nx-dev/nx-dev-e2e/src/helpers.ts b/nx-dev/nx-dev-e2e/src/helpers.ts index 44584f2f74c69..eabf8794e74a9 100644 --- a/nx-dev/nx-dev-e2e/src/helpers.ts +++ b/nx-dev/nx-dev-e2e/src/helpers.ts @@ -1,4 +1,4 @@ -import { Page, test, expect } from '@playwright/test'; +import { expect, test } from '@playwright/test'; /** * Assert a text is present on the visited page. * @param page @@ -11,11 +11,12 @@ export function assertTextOnPage( title: string, selector: string = 'h1' ): void { - test.describe(path, () => + // eslint-disable-next-line playwright/valid-title + test.describe(path, () => { test(`should display "${title}"`, async ({ page }) => { await page.goto(path); const locator = page.locator(selector); await expect(locator).toContainText(title); - }) - ); + }); + }); } diff --git a/package.json b/package.json index a2105a7786e12..7bbbd97a60160 100644 --- a/package.json +++ b/package.json @@ -30,9 +30,9 @@ "@angular-devkit/build-angular": "~18.2.0", "@angular-devkit/core": "~18.2.0", "@angular-devkit/schematics": "~18.2.0", - "@angular-eslint/eslint-plugin": "^18.0.1", - "@angular-eslint/eslint-plugin-template": "^18.0.1", - "@angular-eslint/template-parser": "^18.0.1", + "@angular-eslint/eslint-plugin": "^18.3.0", + "@angular-eslint/eslint-plugin-template": "^18.3.0", + "@angular-eslint/template-parser": "^18.3.0", "@angular/cli": "~18.2.0", "@angular/common": "~18.2.0", "@angular/compiler": "~18.2.0", @@ -45,6 +45,7 @@ "@babel/preset-react": "^7.22.5", "@babel/preset-typescript": "^7.22.5", "@babel/runtime": "^7.22.6", + "@eslint/compat": "^1.1.1", "@eslint/eslintrc": "^2.1.1", "@eslint/js": "^8.48.0", "@floating-ui/react": "0.26.6", @@ -116,6 +117,7 @@ "@types/detect-port": "^1.3.2", "@types/ejs": "3.1.2", "@types/eslint": "~8.56.10", + "@types/eslint__js": "^8.42.3", "@types/express": "4.17.14", "@types/flat": "^5.0.1", "@types/fs-extra": "^11.0.0", @@ -134,16 +136,16 @@ "@types/tmp": "^0.2.0", "@types/yargs": "17.0.10", "@types/yarnpkg__lockfile": "^1.1.5", - "@typescript-eslint/eslint-plugin": "7.16.0", - "@typescript-eslint/parser": "7.16.0", - "@typescript-eslint/type-utils": "^7.16.0", - "@typescript-eslint/utils": "7.16.0", + "@typescript-eslint/rule-tester": "^8.0.0", + "@typescript-eslint/type-utils": "^8.0.0", + "@typescript-eslint/utils": "^8.0.0", "@xstate/immer": "0.3.1", "@xstate/inspect": "0.7.0", "@xstate/react": "3.0.1", "@zkochan/js-yaml": "0.0.7", "ai": "^2.2.10", "ajv": "^8.12.0", + "angular-eslint": "^18.3.0", "autoprefixer": "10.4.13", "babel-jest": "29.7.0", "babel-loader": "^9.1.2", @@ -175,10 +177,10 @@ "eslint-config-next": "14.2.3", "eslint-config-prettier": "9.1.0", "eslint-plugin-cypress": "2.14.0", - "eslint-plugin-import": "2.27.5", + "eslint-plugin-import": "2.30.0", "eslint-plugin-jsx-a11y": "6.7.1", - "eslint-plugin-playwright": "^0.15.3", - "eslint-plugin-react": "7.32.2", + "eslint-plugin-playwright": "^1.6.2", + "eslint-plugin-react": "7.35.0", "eslint-plugin-react-hooks": "4.6.0", "eslint-plugin-storybook": "^0.8.0", "express": "^4.19.2", @@ -190,6 +192,7 @@ "fork-ts-checker-webpack-plugin": "7.2.13", "fs-extra": "^11.1.0", "github-slugger": "^2.0.0", + "globals": "^15.9.0", "gpt3-tokenizer": "^1.1.5", "handlebars": "4.7.7", "html-webpack-plugin": "5.5.0", @@ -287,6 +290,7 @@ "typedoc": "0.25.12", "typedoc-plugin-markdown": "3.17.1", "typescript": "~5.5.2", + "typescript-eslint": "^8.0.0", "unist-builder": "^4.0.0", "unzipper": "^0.10.11", "url-loader": "^4.1.1", diff --git a/packages/angular/package.json b/packages/angular/package.json index ccfbe8f8262a2..cae38228132a5 100644 --- a/packages/angular/package.json +++ b/packages/angular/package.json @@ -48,7 +48,7 @@ }, "dependencies": { "@phenomnomnominal/tsquery": "~5.0.1", - "@typescript-eslint/type-utils": "^7.16.0", + "@typescript-eslint/type-utils": "^8.0.0", "chalk": "^4.1.0", "find-cache-dir": "^3.3.2", "magic-string": "~0.30.2", diff --git a/packages/angular/src/generators/add-linting/add-linting.ts b/packages/angular/src/generators/add-linting/add-linting.ts index 2448d6f81805b..b66d9dacd5b6e 100755 --- a/packages/angular/src/generators/add-linting/add-linting.ts +++ b/packages/angular/src/generators/add-linting/add-linting.ts @@ -7,11 +7,14 @@ import { } from '@nx/devkit'; import { camelize, dasherize } from '@nx/devkit/src/utils/string-utils'; import { Linter, lintProjectGenerator } from '@nx/eslint'; +import type * as eslint from 'eslint'; import { javaScriptOverride, typeScriptOverride, } from '@nx/eslint/src/generators/init/global-eslint-config'; import { + addOverrideToLintConfig, + addPredefinedConfigToFlatLintConfig, findEslintFile, isEslintConfigSupported, replaceOverridesInLintConfig, @@ -19,6 +22,7 @@ import { import { addAngularEsLintDependencies } from './lib/add-angular-eslint-dependencies'; import { isBuildableLibraryProject } from './lib/buildable-project'; import type { AddLintingGeneratorSchema } from './schema'; +import { useFlatConfig } from '@nx/eslint/src/utils/flat-config'; export async function addLintingGenerator( tree: Tree, @@ -49,21 +53,19 @@ export async function addLintingGenerator( .read(joinPathFragments(options.projectRoot, eslintFile), 'utf8') .includes(`${options.projectRoot}/tsconfig.*?.json`); - replaceOverridesInLintConfig(tree, options.projectRoot, [ - ...(rootProject ? [typeScriptOverride, javaScriptOverride] : []), - { + if (useFlatConfig(tree)) { + addPredefinedConfigToFlatLintConfig( + tree, + options.projectRoot, + 'flat/angular' + ); + addPredefinedConfigToFlatLintConfig( + tree, + options.projectRoot, + 'flat/angular-template' + ); + addOverrideToLintConfig(tree, options.projectRoot, { files: ['*.ts'], - ...(hasParserOptions - ? { - parserOptions: { - project: [`${options.projectRoot}/tsconfig.*?.json`], - }, - } - : {}), - extends: [ - 'plugin:@nx/angular', - 'plugin:@angular-eslint/template/process-inline-templates', - ], rules: { '@angular-eslint/directive-selector': [ 'error', @@ -82,28 +84,92 @@ export async function addLintingGenerator( }, ], }, - }, - { + }); + addOverrideToLintConfig(tree, options.projectRoot, { files: ['*.html'], - extends: ['plugin:@nx/angular-template'], - /** - * Having an empty rules object present makes it more obvious to the user where they would - * extend things from if they needed to - */ rules: {}, - }, - ...(isBuildableLibraryProject(tree, options.projectName) - ? [ - { - files: ['*.json'], - parser: 'jsonc-eslint-parser', - rules: { - '@nx/dependency-checks': 'error', + }); + + if (isBuildableLibraryProject(tree, options.projectName)) { + addOverrideToLintConfig(tree, '', { + files: ['*.json'], + parser: 'jsonc-eslint-parser', + rules: { + '@nx/dependency-checks': [ + 'error', + { + // With flat configs, we don't want to include imports in the eslint js/cjs/mjs files to be checked + ignoredFiles: ['{projectRoot}/eslint.config.{js,cjs,mjs}'], + }, + ], + }, + }); + } + } else { + replaceOverridesInLintConfig(tree, options.projectRoot, [ + ...(rootProject ? [typeScriptOverride, javaScriptOverride] : []), + { + files: ['*.ts'], + ...(hasParserOptions + ? { + parserOptions: { + project: [`${options.projectRoot}/tsconfig.*?.json`], + }, + } + : {}), + extends: [ + 'plugin:@nx/angular', + 'plugin:@angular-eslint/template/process-inline-templates', + ], + rules: { + '@angular-eslint/directive-selector': [ + 'error', + { + type: 'attribute', + prefix: camelize(options.prefix), + style: 'camelCase', + }, + ], + '@angular-eslint/component-selector': [ + 'error', + { + type: 'element', + prefix: dasherize(options.prefix), + style: 'kebab-case', + }, + ], + }, + }, + { + files: ['*.html'], + extends: ['plugin:@nx/angular-template'], + /** + * Having an empty rules object present makes it more obvious to the user where they would + * extend things from if they needed to + */ + rules: {}, + }, + ...(isBuildableLibraryProject(tree, options.projectName) + ? [ + { + files: ['*.json'], + parser: 'jsonc-eslint-parser', + rules: { + '@nx/dependency-checks': [ + 'error', + { + // With flat configs, we don't want to include imports in the eslint js/cjs/mjs files to be checked + ignoredFiles: [ + '{projectRoot}/eslint.config.{js,cjs,mjs}', + ], + }, + ], + }, } as any, - }, - ] - : []), - ]); + ] + : []), + ]); + } } if (!options.skipPackageJson) { diff --git a/packages/angular/src/generators/add-linting/lib/add-angular-eslint-dependencies.ts b/packages/angular/src/generators/add-linting/lib/add-angular-eslint-dependencies.ts index 3e54b269817d3..b16d790ace8ff 100644 --- a/packages/angular/src/generators/add-linting/lib/add-angular-eslint-dependencies.ts +++ b/packages/angular/src/generators/add-linting/lib/add-angular-eslint-dependencies.ts @@ -5,6 +5,7 @@ import { } from '@nx/devkit'; import { versions } from '../../utils/version-utils'; import { isBuildableLibraryProject } from './buildable-project'; +import { useFlatConfig } from '@nx/eslint/src/utils/flat-config'; export function addAngularEsLintDependencies( tree: Tree, @@ -12,11 +13,15 @@ export function addAngularEsLintDependencies( ): GeneratorCallback { const compatVersions = versions(tree); const angularEslintVersionToInstall = compatVersions.angularEslintVersion; - const devDependencies = { - '@angular-eslint/eslint-plugin': angularEslintVersionToInstall, - '@angular-eslint/eslint-plugin-template': angularEslintVersionToInstall, - '@angular-eslint/template-parser': angularEslintVersionToInstall, - }; + const devDependencies = useFlatConfig(tree) + ? { + 'angular-eslint': angularEslintVersionToInstall, + } + : { + '@angular-eslint/eslint-plugin': angularEslintVersionToInstall, + '@angular-eslint/eslint-plugin-template': angularEslintVersionToInstall, + '@angular-eslint/template-parser': angularEslintVersionToInstall, + }; if ('typescriptEslintVersion' in compatVersions) { devDependencies['@typescript-eslint/utils'] = diff --git a/packages/angular/src/generators/library/library.spec.ts b/packages/angular/src/generators/library/library.spec.ts index 577f85aacedfa..6c8a011c6efbe 100644 --- a/packages/angular/src/generators/library/library.spec.ts +++ b/packages/angular/src/generators/library/library.spec.ts @@ -701,7 +701,14 @@ describe('lib', () => { ], "parser": "jsonc-eslint-parser", "rules": { - "@nx/dependency-checks": "error" + "@nx/dependency-checks": [ + "error", + { + "ignoredFiles": [ + "{projectRoot}/eslint.config.{js,cjs,mjs}" + ] + } + ] } } ] @@ -1193,7 +1200,52 @@ describe('lib', () => { describe('--linter', () => { describe('eslint', () => { - it('should add valid eslint JSON configuration which extends from Nx presets', async () => { + it('should add valid eslint JSON configuration which extends from Nx presets (flat config)', async () => { + tree.write('eslint.config.js', ''); + + await runLibraryGeneratorWithOpts({ linter: Linter.EsLint }); + + const eslintConfig = tree.read('my-lib/eslint.config.js', 'utf-8'); + expect(eslintConfig).toMatchInlineSnapshot(` + "const nx = require("@nx/eslint-plugin"); + const baseConfig = require("../eslint.config.js"); + + module.exports = [ + ...baseConfig, + ...nx.configs["flat/angular"], + ...nx.configs["flat/angular-template"], + { + files: ["**/*.ts"], + rules: { + "@angular-eslint/directive-selector": [ + "error", + { + type: "attribute", + prefix: "lib", + style: "camelCase" + } + ], + "@angular-eslint/component-selector": [ + "error", + { + type: "element", + prefix: "lib", + style: "kebab-case" + } + ] + } + }, + { + files: ["**/*.html"], + // Override or add rules here + rules: {} + } + ]; + " + `); + }); + + it('should add valid eslint JSON configuration which extends from Nx presets (eslintrc)', async () => { // ACT await runLibraryGeneratorWithOpts({ linter: Linter.EsLint }); @@ -1311,7 +1363,14 @@ describe('lib', () => { ], "parser": "jsonc-eslint-parser", "rules": { - "@nx/dependency-checks": "error", + "@nx/dependency-checks": [ + "error", + { + "ignoredFiles": [ + "{projectRoot}/eslint.config.{js,cjs,mjs}", + ], + }, + ], }, }, ], diff --git a/packages/angular/src/utils/versions.ts b/packages/angular/src/utils/versions.ts index 0f3ec771aa90f..648a79d22eb2b 100644 --- a/packages/angular/src/utils/versions.ts +++ b/packages/angular/src/utils/versions.ts @@ -17,7 +17,7 @@ export const browserSyncVersion = '^3.0.0'; export const moduleFederationNodeVersion = '~2.5.0'; export const moduleFederationEnhancedVersion = '~0.6.0'; -export const angularEslintVersion = '^18.0.1'; +export const angularEslintVersion = '^18.3.0'; export const typescriptEslintVersion = '^7.16.0'; export const tailwindVersion = '^3.0.2'; export const postcssVersion = '^8.4.5'; diff --git a/packages/create-nx-workspace/src/utils/child-process-utils.ts b/packages/create-nx-workspace/src/utils/child-process-utils.ts index 88ec58a2702b3..bd09075c934c3 100644 --- a/packages/create-nx-workspace/src/utils/child-process-utils.ts +++ b/packages/create-nx-workspace/src/utils/child-process-utils.ts @@ -11,7 +11,13 @@ export function spawnAndWait(command: string, args: string[], cwd: string) { const childProcess = spawn(command, args, { cwd, stdio: 'inherit', - env: { ...process.env, NX_DAEMON: 'false' }, + env: { + ...process.env, + NX_DAEMON: 'false', + // This is the same environment variable that ESLint uses to determine if it should use a flat config. + // Default to true for all new workspaces. + ESLINT_USE_FLAT_CONFIG: process.env.ESLINT_USE_FLAT_CONFIG ?? 'true', + }, shell: true, windowsHide: true, }); diff --git a/packages/cypress/src/utils/add-linter.ts b/packages/cypress/src/utils/add-linter.ts index 72d16eaf38827..bf9448ba68da4 100644 --- a/packages/cypress/src/utils/add-linter.ts +++ b/packages/cypress/src/utils/add-linter.ts @@ -13,6 +13,7 @@ import { addExtendsToLintConfig, addOverrideToLintConfig, addPluginsToLintConfig, + addPredefinedConfigToFlatLintConfig, findEslintFile, isEslintConfigSupported, replaceOverridesInLintConfig, @@ -21,6 +22,7 @@ import { javaScriptOverride, typeScriptOverride, } from '@nx/eslint/src/generators/init/global-eslint-config'; +import { useFlatConfig } from '@nx/eslint/src/utils/flat-config'; export interface CyLinterOptions { project: string; @@ -90,16 +92,33 @@ export async function addLinterToCyProject( isEslintConfigSupported(tree) ) { const overrides = []; - if (options.rootProject) { - addPluginsToLintConfig(tree, projectConfig.root, '@nx'); - overrides.push(typeScriptOverride); - overrides.push(javaScriptOverride); + if (useFlatConfig(tree)) { + addPredefinedConfigToFlatLintConfig( + tree, + projectConfig.root, + 'recommended', + 'cypress', + 'eslint-plugin-cypress/flat', + false, + false + ); + addOverrideToLintConfig(tree, projectConfig.root, { + files: ['*.ts', '*.js'], + rules: {}, + }); + } else { + if (options.rootProject) { + addPluginsToLintConfig(tree, projectConfig.root, '@nx'); + overrides.push(typeScriptOverride); + overrides.push(javaScriptOverride); + } + const addExtendsTask = addExtendsToLintConfig( + tree, + projectConfig.root, + 'plugin:cypress/recommended' + ); + tasks.push(addExtendsTask); } - addExtendsToLintConfig( - tree, - projectConfig.root, - 'plugin:cypress/recommended' - ); const cyVersion = installedCypressVersion(); /** * We need this override because we enabled allowJS in the tsconfig to allow for JS based Cypress tests. @@ -116,7 +135,10 @@ export async function addLinterToCyProject( if (options.overwriteExisting) { overrides.unshift({ - files: ['*.ts', '*.tsx', '*.js', '*.jsx'], + files: useFlatConfig(tree) + ? // For flat configs we don't need to specify the files + undefined + : ['*.ts', '*.tsx', '*.js', '*.jsx'], parserOptions: !options.setParserOptionsProject ? undefined : { @@ -130,10 +152,13 @@ export async function addLinterToCyProject( replaceOverridesInLintConfig(tree, projectConfig.root, overrides); } else { overrides.unshift({ - files: [ - '*.cy.{ts,js,tsx,jsx}', - `${options.cypressDir}/**/*.{ts,js,tsx,jsx}`, - ], + files: useFlatConfig(tree) + ? // For flat configs we don't need to specify the files + undefined + : [ + '*.cy.{ts,js,tsx,jsx}', + `${options.cypressDir}/**/*.{ts,js,tsx,jsx}`, + ], parserOptions: !options.setParserOptionsProject ? undefined : { diff --git a/packages/cypress/src/utils/versions.ts b/packages/cypress/src/utils/versions.ts index 20975d26bbc84..2bb6cb1343db5 100644 --- a/packages/cypress/src/utils/versions.ts +++ b/packages/cypress/src/utils/versions.ts @@ -1,5 +1,5 @@ export const nxVersion = require('../../package.json').version; -export const eslintPluginCypressVersion = '^2.13.4'; +export const eslintPluginCypressVersion = '^3.5.0'; export const typesNodeVersion = '18.16.9'; export const cypressViteDevServerVersion = '^2.2.1'; export const cypressVersion = '^13.13.0'; diff --git a/packages/detox/src/generators/application/lib/add-linting.ts b/packages/detox/src/generators/application/lib/add-linting.ts index 58f16e9326191..d578ced9ca571 100644 --- a/packages/detox/src/generators/application/lib/add-linting.ts +++ b/packages/detox/src/generators/application/lib/add-linting.ts @@ -1,6 +1,7 @@ import { Linter, lintProjectGenerator } from '@nx/eslint'; import { addDependenciesToPackageJson, + GeneratorCallback, joinPathFragments, runTasksInSerial, Tree, @@ -9,14 +10,18 @@ import { extraEslintDependencies } from '@nx/react'; import { NormalizedSchema } from './normalize-options'; import { addExtendsToLintConfig, + addOverrideToLintConfig, + addPredefinedConfigToFlatLintConfig, isEslintConfigSupported, } from '@nx/eslint/src/generators/utils/eslint-file'; +import { useFlatConfig } from '@nx/eslint/src/utils/flat-config'; export async function addLinting(host: Tree, options: NormalizedSchema) { if (options.linter === Linter.None) { return () => {}; } + const tasks: GeneratorCallback[] = []; const lintTask = await lintProjectGenerator(host, { linter: options.linter, project: options.e2eProjectName, @@ -26,9 +31,28 @@ export async function addLinting(host: Tree, options: NormalizedSchema) { skipFormat: true, addPlugin: options.addPlugin, }); + tasks.push(lintTask); if (isEslintConfigSupported(host)) { - addExtendsToLintConfig(host, options.e2eProjectRoot, 'plugin:@nx/react'); + if (useFlatConfig(host)) { + addPredefinedConfigToFlatLintConfig( + host, + options.e2eProjectRoot, + 'flat/react' + ); + // Add an empty rules object to users know how to add/override rules + addOverrideToLintConfig(host, options.e2eProjectRoot, { + files: ['*.ts', '*.tsx', '*.js', '*.jsx'], + rules: {}, + }); + } else { + const addExtendsTask = addExtendsToLintConfig( + host, + options.e2eProjectRoot, + { name: 'plugin:@nx/react', needCompatFixup: true } + ); + tasks.push(addExtendsTask); + } } const installTask = addDependenciesToPackageJson( @@ -36,6 +60,7 @@ export async function addLinting(host: Tree, options: NormalizedSchema) { extraEslintDependencies.dependencies, extraEslintDependencies.devDependencies ); + tasks.push(installTask); - return runTasksInSerial(lintTask, installTask); + return runTasksInSerial(...tasks); } diff --git a/packages/eslint-plugin/.eslintrc.json b/packages/eslint-plugin/.eslintrc.json index 86666f71669f0..46d851e2defc2 100644 --- a/packages/eslint-plugin/.eslintrc.json +++ b/packages/eslint-plugin/.eslintrc.json @@ -37,7 +37,14 @@ // Installed to workspace by plugins "@typescript-eslint/parser", "eslint-config-prettier", - "@angular-eslint/eslint-plugin" + "@angular-eslint/eslint-plugin", + "angular-eslint", + "typescript-eslint", + "@eslint/js", + "eslint-plugin-import", + "eslint-plugin-jsx-a11y", + "eslint-plugin-react", + "eslint-plugin-react-hooks" ] } ] diff --git a/packages/eslint-plugin/angular.ts b/packages/eslint-plugin/angular.ts new file mode 100644 index 0000000000000..0af4648e2d929 --- /dev/null +++ b/packages/eslint-plugin/angular.ts @@ -0,0 +1,16 @@ +import angular from './src/flat-configs/angular'; +import angularTemplate from './src/flat-configs/angular-template'; + +const plugin = { + configs: { + angular, + 'angular-template': angularTemplate, + }, + rules: {}, +}; + +// ESM +export default plugin; + +// CommonJS +module.exports = plugin; diff --git a/packages/eslint-plugin/nx.ts b/packages/eslint-plugin/nx.ts new file mode 100644 index 0000000000000..4c4fac6ae6d97 --- /dev/null +++ b/packages/eslint-plugin/nx.ts @@ -0,0 +1,27 @@ +import { workspaceRules } from './src/resolve-workspace-rules'; +import dependencyChecks, { + RULE_NAME as dependencyChecksRuleName, +} from './src/rules/dependency-checks'; +import enforceModuleBoundaries, { + RULE_NAME as enforceModuleBoundariesRuleName, +} from './src/rules/enforce-module-boundaries'; +import nxPluginChecksRule, { + RULE_NAME as nxPluginChecksRuleName, +} from './src/rules/nx-plugin-checks'; + +const plugin = { + configs: {}, + rules: { + [enforceModuleBoundariesRuleName]: enforceModuleBoundaries, + [nxPluginChecksRuleName]: nxPluginChecksRule, + [dependencyChecksRuleName]: dependencyChecks, + // Resolve any custom rules that might exist in the current workspace + ...workspaceRules, + }, +}; + +// ESM +export default plugin; + +// CommonJS +module.exports = plugin; diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index 4f88bd154af70..a1d8c2840ce17 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -25,7 +25,7 @@ }, "homepage": "https://nx.dev", "peerDependencies": { - "@typescript-eslint/parser": "^6.13.2 || ^7.0.0", + "@typescript-eslint/parser": "^6.13.2 || ^7.0.0 || ^8.0.0", "eslint-config-prettier": "^9.0.0" }, "peerDependenciesMeta": { @@ -34,12 +34,14 @@ } }, "dependencies": { + "@eslint/compat": "^1.1.1", "@nx/devkit": "file:../devkit", "@nx/js": "file:../js", - "@typescript-eslint/type-utils": "^7.16.0", - "@typescript-eslint/utils": "^7.16.0", + "@typescript-eslint/type-utils": "^8.0.0", + "@typescript-eslint/utils": "^8.0.0", "chalk": "^4.1.0", "confusing-browser-globals": "^1.0.9", + "globals": "^15.9.0", "jsonc-eslint-parser": "^2.1.0", "semver": "^7.5.3", "tslib": "^2.3.0" diff --git a/packages/eslint-plugin/react.ts b/packages/eslint-plugin/react.ts new file mode 100644 index 0000000000000..808042b48dae4 --- /dev/null +++ b/packages/eslint-plugin/react.ts @@ -0,0 +1,20 @@ +import reactBase from './src/flat-configs/react-base'; +import reactJsx from './src/flat-configs/react-jsx'; +import reactTmp from './src/flat-configs/react-tmp'; +import reactTypescript from './src/flat-configs/react-typescript'; + +const plugin = { + configs: { + react: reactTmp, + 'react-base': reactBase, + 'react-typescript': reactTypescript, + 'react-jsx': reactJsx, + }, + rules: {}, +}; + +// ESM +export default plugin; + +// CommonJS +module.exports = plugin; diff --git a/packages/eslint-plugin/src/configs/javascript.ts b/packages/eslint-plugin/src/configs/javascript.ts index 6becc9d1f7e14..3e27e3cbd15da 100644 --- a/packages/eslint-plugin/src/configs/javascript.ts +++ b/packages/eslint-plugin/src/configs/javascript.ts @@ -66,5 +66,12 @@ export default { '@typescript-eslint/no-unused-vars': 'warn', '@typescript-eslint/no-empty-interface': 'error', '@typescript-eslint/no-explicit-any': 'warn', + /** + * During the migration to use ESLint v9 and typescript-eslint v8 for new workspaces, + * this rule would have created a lot of noise, so we are disabling it by default for now. + * + * TODO(v20): we should make this part of what we re-evaluate in v20 + */ + '@typescript-eslint/no-require-imports': 'off', }, }; diff --git a/packages/eslint-plugin/src/configs/typescript.ts b/packages/eslint-plugin/src/configs/typescript.ts index 58de5a8468f45..62beee6919471 100644 --- a/packages/eslint-plugin/src/configs/typescript.ts +++ b/packages/eslint-plugin/src/configs/typescript.ts @@ -49,5 +49,12 @@ export default { '@typescript-eslint/no-unused-vars': 'warn', '@typescript-eslint/no-empty-interface': 'error', '@typescript-eslint/no-explicit-any': 'warn', + /** + * During the migration to use ESLint v9 and typescript-eslint v8 for new workspaces, + * this rule would have created a lot of noise, so we are disabling it by default for now. + * + * TODO(v20): we should make this part of what we re-evaluate in v20 + */ + '@typescript-eslint/no-require-imports': 'off', }, }; diff --git a/packages/eslint-plugin/src/flat-configs/angular-template.ts b/packages/eslint-plugin/src/flat-configs/angular-template.ts new file mode 100644 index 0000000000000..e187eb488ec68 --- /dev/null +++ b/packages/eslint-plugin/src/flat-configs/angular-template.ts @@ -0,0 +1,25 @@ +import angular from 'angular-eslint'; +import tseslint from 'typescript-eslint'; + +/** + * This configuration is intended to be applied to ALL .html files in Angular + * projects within an Nx workspace, as well as extracted inline templates from + * .component.ts files (or similar). + * + * It should therefore NOT contain any rules or plugins which are related to + * Angular source code. + * + * NOTE: The processor to extract the inline templates is applied in users' + * configs by the relevant schematic. + * + * This configuration is intended to be combined with other configs from this + * package. + */ +export default tseslint.config({ + files: ['**/*.html'], + extends: [ + ...angular.configs.templateRecommended, + ...angular.configs.templateAccessibility, + ], + rules: {}, +}); diff --git a/packages/eslint-plugin/src/flat-configs/angular.ts b/packages/eslint-plugin/src/flat-configs/angular.ts new file mode 100644 index 0000000000000..12880957aa35f --- /dev/null +++ b/packages/eslint-plugin/src/flat-configs/angular.ts @@ -0,0 +1,26 @@ +import angularEslint from 'angular-eslint'; +import globals from 'globals'; +import tseslint from 'typescript-eslint'; + +/** + * This configuration is intended to be applied to ALL .ts files in Angular + * projects within an Nx workspace. + * + * It should therefore NOT contain any rules or plugins which are related to + * Angular Templates, or more cross-cutting concerns which are not specific + * to Angular. + * + * This configuration is intended to be combined with other configs from this + * package. + */ +export default tseslint.config(...angularEslint.configs.tsRecommended, { + languageOptions: { + globals: { + ...globals.browser, + ...globals.es2015, + ...globals.node, + }, + }, + processor: angularEslint.processInlineTemplates, + plugins: { '@angular-eslint': angularEslint.tsPlugin }, +}); diff --git a/packages/eslint-plugin/src/flat-configs/base.ts b/packages/eslint-plugin/src/flat-configs/base.ts new file mode 100644 index 0000000000000..b5e353224979e --- /dev/null +++ b/packages/eslint-plugin/src/flat-configs/base.ts @@ -0,0 +1,10 @@ +export default [ + { + plugins: { + get ['@nx']() { + return require('../index'); + }, + }, + ignores: ['.nx'], + }, +]; diff --git a/packages/eslint-plugin/src/flat-configs/javascript.ts b/packages/eslint-plugin/src/flat-configs/javascript.ts new file mode 100644 index 0000000000000..95f9d69d1743e --- /dev/null +++ b/packages/eslint-plugin/src/flat-configs/javascript.ts @@ -0,0 +1,82 @@ +import eslint from '@eslint/js'; +import globals from 'globals'; +import tseslint from 'typescript-eslint'; +import { packageExists } from '../utils/config-utils'; + +const isPrettierAvailable = + packageExists('prettier') && packageExists('eslint-config-prettier'); + +/** + * This configuration is intended to be applied to ALL .js and .jsx files + * within an Nx workspace. + * + * It should therefore NOT contain any rules or plugins which are specific + * to one ecosystem, such as React, Angular, Node etc. + * + * We use @typescript-eslint/parser rather than the built in JS parser + * because that is what Nx ESLint configs have always done and we don't + * want to change too much all at once. + * + * TODO: Evaluate switching to the built-in JS parser (espree) in Nx v11, + * it should yield a performance improvement but could introduce subtle + * breaking changes - we should also look to replace all the @typescript-eslint + * related plugins and rules below. + */ +export default tseslint.config( + eslint.configs.recommended, + ...tseslint.configs.recommended, + ...(isPrettierAvailable ? [require('eslint-config-prettier')] : []), + { + languageOptions: { + parser: tseslint.parser, + ecmaVersion: 2020, + sourceType: 'module', + globals: { + ...globals.browser, + ...globals.node, + }, + }, + plugins: { '@typescript-eslint': tseslint.plugin }, + }, + { + files: ['**/*.js', '**/*.jsx'], + rules: { + '@typescript-eslint/explicit-member-accessibility': 'off', + '@typescript-eslint/explicit-module-boundary-types': 'off', + '@typescript-eslint/explicit-function-return-type': 'off', + '@typescript-eslint/no-parameter-properties': 'off', + /** + * Until ESM usage in Node matures, using require in e.g. JS config files + * is by far the more common thing to do, so disabling this to avoid users + * having to frequently use "eslint-disable-next-line" in their configs. + */ + '@typescript-eslint/no-var-requires': 'off', + + /** + * From https://typescript-eslint.io/blog/announcing-typescript-eslint-v6/#updated-configuration-rules + * + * The following rules were added to preserve the linting rules that were + * previously defined v5 of `@typescript-eslint`. v6 of `@typescript-eslint` + * changed how configurations are defined. + * + * TODO(v20): re-evalute these deviations from @typescript-eslint/recommended in v20 of Nx + */ + '@typescript-eslint/no-non-null-assertion': 'warn', + '@typescript-eslint/adjacent-overload-signatures': 'error', + '@typescript-eslint/prefer-namespace-keyword': 'error', + 'no-empty-function': 'off', + '@typescript-eslint/no-empty-function': 'error', + '@typescript-eslint/no-inferrable-types': 'error', + '@typescript-eslint/no-unused-vars': 'warn', + '@typescript-eslint/no-empty-interface': 'error', + '@typescript-eslint/no-explicit-any': 'warn', + /** + * During the migration to use ESLint v9 and typescript-eslint v8 for new workspaces, + * this rule would have created a lot of noise, so we are disabling it by default for now. + * + * TODO(v20): we should make this part of what we re-evaluate in v20 + */ + '@typescript-eslint/no-require-imports': 'off', + }, + } +); diff --git a/packages/eslint-plugin/src/flat-configs/react-base.ts b/packages/eslint-plugin/src/flat-configs/react-base.ts new file mode 100644 index 0000000000000..a0a15ccd46e7b --- /dev/null +++ b/packages/eslint-plugin/src/flat-configs/react-base.ts @@ -0,0 +1,148 @@ +import { fixupPluginRules } from '@eslint/compat'; +import * as importPlugin from 'eslint-plugin-import'; +import globals from 'globals'; +import tseslint from 'typescript-eslint'; + +/** + * This configuration is intended to be applied to ALL files within a React + * project in an Nx workspace. + * + * It should therefore NOT contain any rules or plugins which are specific + * to one particular variant, e.g. TypeScript vs JavaScript, .js vs .jsx etc + * + * This configuration is intended to be combined with other configs from this + * package. + */ + +/** + * Rule set originally adapted from: + * https://github.com/facebook/create-react-app/blob/567f36c9235f1e1fd4a76dc6d1ae00be754ca047/packages/eslint-config-react-app/index.js + */ +export default tseslint.config({ + plugins: { import: fixupPluginRules(importPlugin) }, + languageOptions: { + globals: { + ...globals.browser, + ...globals.commonjs, + ...globals.es2015, + ...globals.jest, + ...globals.node, + }, + }, + rules: { + /** + * Standard ESLint rule configurations + * https://eslint.org/docs/rules + */ + 'array-callback-return': 'warn', + 'dot-location': ['warn', 'property'], + eqeqeq: ['warn', 'smart'], + 'new-parens': 'warn', + 'no-caller': 'warn', + 'no-cond-assign': ['warn', 'except-parens'], + 'no-const-assign': 'warn', + 'no-control-regex': 'warn', + 'no-delete-var': 'warn', + 'no-dupe-args': 'warn', + 'no-dupe-keys': 'warn', + 'no-duplicate-case': 'warn', + 'no-empty-character-class': 'warn', + 'no-empty-pattern': 'warn', + 'no-eval': 'warn', + 'no-ex-assign': 'warn', + 'no-extend-native': 'warn', + 'no-extra-bind': 'warn', + 'no-extra-label': 'warn', + 'no-fallthrough': 'warn', + 'no-func-assign': 'warn', + 'no-implied-eval': 'warn', + 'no-invalid-regexp': 'warn', + 'no-iterator': 'warn', + 'no-label-var': 'warn', + 'no-labels': ['warn', { allowLoop: true, allowSwitch: false }], + 'no-lone-blocks': 'warn', + 'no-loop-func': 'warn', + 'no-mixed-operators': [ + 'warn', + { + groups: [ + ['&', '|', '^', '~', '<<', '>>', '>>>'], + ['==', '!=', '===', '!==', '>', '>=', '<', '<='], + ['&&', '||'], + ['in', 'instanceof'], + ], + allowSamePrecedence: false, + }, + ], + 'no-multi-str': 'warn', + 'no-native-reassign': 'warn', + 'no-negated-in-lhs': 'warn', + 'no-new-func': 'warn', + 'no-new-object': 'warn', + 'no-new-symbol': 'warn', + 'no-new-wrappers': 'warn', + 'no-obj-calls': 'warn', + 'no-octal': 'warn', + 'no-octal-escape': 'warn', + 'no-redeclare': 'warn', + 'no-regex-spaces': 'warn', + 'no-restricted-syntax': ['warn', 'WithStatement'], + 'no-script-url': 'warn', + 'no-self-assign': 'warn', + 'no-self-compare': 'warn', + 'no-sequences': 'warn', + 'no-shadow-restricted-names': 'warn', + 'no-sparse-arrays': 'warn', + 'no-template-curly-in-string': 'warn', + 'no-this-before-super': 'warn', + 'no-throw-literal': 'warn', + 'no-restricted-globals': ['error', ...require('confusing-browser-globals')], + 'no-unexpected-multiline': 'warn', + 'no-unreachable': 'warn', + 'no-unused-expressions': 'off', + 'no-unused-labels': 'warn', + 'no-useless-computed-key': 'warn', + 'no-useless-concat': 'warn', + 'no-useless-escape': 'warn', + 'no-useless-rename': [ + 'warn', + { + ignoreDestructuring: false, + ignoreImport: false, + ignoreExport: false, + }, + ], + 'no-with': 'warn', + 'no-whitespace-before-property': 'warn', + 'require-yield': 'warn', + 'rest-spread-spacing': ['warn', 'never'], + strict: ['warn', 'never'], + 'unicode-bom': ['warn', 'never'], + 'use-isnan': 'warn', + 'valid-typeof': 'warn', + 'no-restricted-properties': [ + 'error', + { + object: 'require', + property: 'ensure', + message: + 'Please use import() instead. More info: https://facebook.github.io/create-react-app/docs/code-splitting', + }, + { + object: 'System', + property: 'import', + message: + 'Please use import() instead. More info: https://facebook.github.io/create-react-app/docs/code-splitting', + }, + ], + 'getter-return': 'warn', + + /** + * Import rule configurations + * https://github.com/benmosher/eslint-plugin-import + */ + 'import/first': 'error', + 'import/no-amd': 'error', + 'import/no-webpack-loader-syntax': 'error', + }, +}); diff --git a/packages/eslint-plugin/src/flat-configs/react-jsx.ts b/packages/eslint-plugin/src/flat-configs/react-jsx.ts new file mode 100644 index 0000000000000..262f6826e9c09 --- /dev/null +++ b/packages/eslint-plugin/src/flat-configs/react-jsx.ts @@ -0,0 +1,78 @@ +import jsxA11yPlugin from 'eslint-plugin-jsx-a11y'; +import reactPlugin from 'eslint-plugin-react'; +import reactHooksPlugin from 'eslint-plugin-react-hooks'; +import tseslint from 'typescript-eslint'; + +/** + * This configuration is intended to be applied to ONLY files which contain JSX/TSX + * code. + * + * It should therefore NOT contain any rules or plugins which are generic + * to all file types within variants of React projects. + * + * This configuration is intended to be combined with other configs from this + * package. + */ +export default tseslint.config( + { + plugins: { + 'react-hooks': reactHooksPlugin, + }, + rules: reactHooksPlugin.configs.recommended.rules, + }, + { + settings: { react: { version: 'detect' } }, + plugins: { + 'jsx-a11y': jsxA11yPlugin, + react: reactPlugin, + }, + rules: { + /** + * React-specific rule configurations + * https://github.com/yannickcr/eslint-plugin-react + */ + 'react/forbid-foreign-prop-types': ['warn', { allowInPropTypes: true }], + 'react/jsx-no-comment-textnodes': 'warn', + 'react/jsx-no-duplicate-props': 'warn', + 'react/jsx-no-target-blank': 'warn', + 'react/jsx-no-undef': 'error', + 'react/jsx-pascal-case': ['warn', { allowAllCaps: true, ignore: [] }], + 'react/jsx-uses-vars': 'warn', + 'react/no-danger-with-children': 'warn', + 'react/no-direct-mutation-state': 'warn', + 'react/no-is-mounted': 'warn', + 'react/no-typos': 'error', + 'react/jsx-uses-react': 'off', + 'react/react-in-jsx-scope': 'off', + 'react/require-render-return': 'error', + 'react/style-prop-object': 'warn', + 'react/jsx-no-useless-fragment': 'warn', + + /** + * JSX Accessibility rule configurations + * https://github.com/evcohen/eslint-plugin-jsx-a11y + */ + 'jsx-a11y/accessible-emoji': 'warn', + 'jsx-a11y/alt-text': 'warn', + 'jsx-a11y/anchor-has-content': 'warn', + 'jsx-a11y/anchor-is-valid': [ + 'warn', + { aspects: ['noHref', 'invalidHref'] }, + ], + 'jsx-a11y/aria-activedescendant-has-tabindex': 'warn', + 'jsx-a11y/aria-props': 'warn', + 'jsx-a11y/aria-proptypes': 'warn', + 'jsx-a11y/aria-role': 'warn', + 'jsx-a11y/aria-unsupported-elements': 'warn', + 'jsx-a11y/heading-has-content': 'warn', + 'jsx-a11y/iframe-has-title': 'warn', + 'jsx-a11y/img-redundant-alt': 'warn', + 'jsx-a11y/no-access-key': 'warn', + 'jsx-a11y/no-distracting-elements': 'warn', + 'jsx-a11y/no-redundant-roles': 'warn', + 'jsx-a11y/role-has-required-aria-props': 'warn', + 'jsx-a11y/role-supports-aria-props': 'warn', + 'jsx-a11y/scope': 'warn', + }, + } +); diff --git a/packages/eslint-plugin/src/flat-configs/react-tmp.ts b/packages/eslint-plugin/src/flat-configs/react-tmp.ts new file mode 100644 index 0000000000000..a435f003dc7d1 --- /dev/null +++ b/packages/eslint-plugin/src/flat-configs/react-tmp.ts @@ -0,0 +1,14 @@ +import tseslint from 'typescript-eslint'; +import reactBase from './react-base'; +import reactTypescript from './react-typescript'; +import reactJsx from './react-jsx'; + +/** + * THIS IS A TEMPORARY CONFIG WHICH MATCHES THE CURRENT BEHAVIOR + * of including all the rules for all file types within the ESLint + * config for React projects. + * + * It will be refactored in a follow up PR to correctly apply rules + * to the right file types via overrides. + */ +export default tseslint.config(...reactBase, ...reactTypescript, ...reactJsx); diff --git a/packages/eslint-plugin/src/flat-configs/react-typescript.ts b/packages/eslint-plugin/src/flat-configs/react-typescript.ts new file mode 100644 index 0000000000000..64e208309dbbd --- /dev/null +++ b/packages/eslint-plugin/src/flat-configs/react-typescript.ts @@ -0,0 +1,55 @@ +import tseslint from 'typescript-eslint'; + +/** + * This configuration is intended to be applied to ONLY .ts and .tsx files within a + * React project in an Nx workspace. + * + * It should therefore NOT contain any rules or plugins which are generic + * to all variants of React projects, e.g. TypeScript vs JavaScript, .js vs .jsx etc + * + * This configuration is intended to be combined with other configs from this + * package. + */ +export default tseslint.config({ + rules: { + // TypeScript"s `noFallthroughCasesInSwitch` option is more robust (#6906) + 'default-case': 'off', + // "tsc" already handles this (https://github.com/typescript-eslint/typescript-eslint/issues/291) + 'no-dupe-class-members': 'off', + // "tsc" already handles this (https://github.com/typescript-eslint/typescript-eslint/issues/477) + 'no-undef': 'off', + + // Add TypeScript specific rules (and turn off ESLint equivalents) + 'no-array-constructor': 'off', + '@typescript-eslint/no-array-constructor': 'warn', + '@typescript-eslint/no-namespace': 'error', + 'no-use-before-define': 'off', + '@typescript-eslint/no-use-before-define': [ + 'warn', + { + functions: false, + classes: false, + variables: false, + typedefs: false, + }, + ], + 'no-unused-vars': 'off', + '@typescript-eslint/no-unused-vars': [ + 'warn', + { + args: 'none', + ignoreRestSiblings: true, + }, + ], + 'no-useless-constructor': 'off', + '@typescript-eslint/no-useless-constructor': 'warn', + '@typescript-eslint/no-unused-expressions': [ + 'error', + { + allowShortCircuit: true, + allowTernary: true, + allowTaggedTemplates: true, + }, + ], + }, +}); diff --git a/packages/eslint-plugin/src/flat-configs/typescript.ts b/packages/eslint-plugin/src/flat-configs/typescript.ts new file mode 100644 index 0000000000000..6b04ba8b4cad5 --- /dev/null +++ b/packages/eslint-plugin/src/flat-configs/typescript.ts @@ -0,0 +1,66 @@ +import eslint from '@eslint/js'; +import { workspaceRoot } from '@nx/devkit'; +import tseslint from 'typescript-eslint'; +import { packageExists } from '../utils/config-utils'; + +const isPrettierAvailable = + packageExists('prettier') && packageExists('eslint-config-prettier'); + +/** + * This configuration is intended to be applied to ALL .ts and .tsx files + * within an Nx workspace. + * + * It should therefore NOT contain any rules or plugins which are specific + * to one ecosystem, such as React, Angular, Node etc. + */ +export default tseslint.config( + eslint.configs.recommended, + ...tseslint.configs.recommended, + ...(isPrettierAvailable ? [require('eslint-config-prettier')] : []), + { + plugins: { '@typescript-eslint': tseslint.plugin }, + languageOptions: { + parser: tseslint.parser, + ecmaVersion: 2020, + sourceType: 'module', + parserOptions: { + tsconfigRootDir: workspaceRoot, + }, + }, + }, + { + files: ['**/*.ts', '**/*.tsx'], + rules: { + '@typescript-eslint/explicit-member-accessibility': 'off', + '@typescript-eslint/explicit-module-boundary-types': 'off', + '@typescript-eslint/explicit-function-return-type': 'off', + '@typescript-eslint/no-parameter-properties': 'off', + + /** + * From https://typescript-eslint.io/blog/announcing-typescript-eslint-v6/#updated-configuration-rules + * + * The following rules were added to preserve the linting rules that were + * previously defined v5 of `@typescript-eslint`. v6 of `@typescript-eslint` + * changed how configurations are defined. + * + * TODO(v20): re-evalute these deviations from @typescript-eslint/recommended in v20 of Nx + */ + '@typescript-eslint/no-non-null-assertion': 'warn', + '@typescript-eslint/adjacent-overload-signatures': 'error', + '@typescript-eslint/prefer-namespace-keyword': 'error', + 'no-empty-function': 'off', + '@typescript-eslint/no-empty-function': 'error', + '@typescript-eslint/no-inferrable-types': 'error', + '@typescript-eslint/no-unused-vars': 'warn', + '@typescript-eslint/no-empty-interface': 'error', + '@typescript-eslint/no-explicit-any': 'warn', + /** + * During the migration to use ESLint v9 and typescript-eslint v8 for new workspaces, + * this rule would have created a lot of noise, so we are disabling it by default for now. + * + * TODO(v20): we should make this part of what we re-evaluate in v20 + */ + '@typescript-eslint/no-require-imports': 'off', + }, + } +); diff --git a/packages/eslint-plugin/src/index.ts b/packages/eslint-plugin/src/index.ts index 4037dfeada5dc..27d69cda7bdb6 100644 --- a/packages/eslint-plugin/src/index.ts +++ b/packages/eslint-plugin/src/index.ts @@ -7,6 +7,8 @@ import reactTypescript from './configs/react-typescript'; import angularCode from './configs/angular'; import angularTemplate from './configs/angular-template'; +import flatBase from './flat-configs/base'; + import enforceModuleBoundaries, { RULE_NAME as enforceModuleBoundariesRuleName, } from './rules/enforce-module-boundaries'; @@ -24,6 +26,7 @@ import { workspaceRules } from './resolve-workspace-rules'; module.exports = { configs: { + // eslintrc configs typescript, javascript, react: reactTmp, @@ -32,6 +35,34 @@ module.exports = { 'react-jsx': reactJsx, angular: angularCode, 'angular-template': angularTemplate, + + // flat configs + // Note: Using getters here to avoid importing packages `angular-eslint` statically, which can lead to errors if not installed. + 'flat/base': flatBase, + get ['flat/typescript']() { + return require('./flat-configs/typescript').default; + }, + get ['flat/javascript']() { + return require('./flat-configs/javascript').default; + }, + get ['flat/react']() { + return require('./flat-configs/react-tmp').default; + }, + get ['flat/react-base']() { + return require('./flat-configs/react-base').default; + }, + get ['flat/react-typescript']() { + return require('./flat-configs/react-typescript').default; + }, + get ['flat/react-jsx']() { + return require('./flat-configs/react-jsx').default; + }, + get ['flat/angular']() { + return require('./flat-configs/angular').default; + }, + get ['flat/angular-template']() { + return require('./flat-configs/angular-template').default; + }, }, rules: { [enforceModuleBoundariesRuleName]: enforceModuleBoundaries, diff --git a/packages/eslint-plugin/src/rules/dependency-checks.ts b/packages/eslint-plugin/src/rules/dependency-checks.ts index 5978e2a1f856b..7808a5c946487 100644 --- a/packages/eslint-plugin/src/rules/dependency-checks.ts +++ b/packages/eslint-plugin/src/rules/dependency-checks.ts @@ -47,7 +47,6 @@ export default ESLintUtils.RuleCreator( type: 'suggestion', docs: { description: `Checks dependencies in project's package.json for version mismatches`, - recommended: 'recommended', }, fixable: 'code', schema: [ diff --git a/packages/eslint-plugin/src/rules/enforce-module-boundaries.ts b/packages/eslint-plugin/src/rules/enforce-module-boundaries.ts index b1323b1538aae..d292ba9499e4b 100644 --- a/packages/eslint-plugin/src/rules/enforce-module-boundaries.ts +++ b/packages/eslint-plugin/src/rules/enforce-module-boundaries.ts @@ -91,7 +91,6 @@ export default ESLintUtils.RuleCreator( type: 'suggestion', docs: { description: `Ensure that module boundaries are respected within the monorepo`, - recommended: 'recommended', }, fixable: 'code', schema: [ diff --git a/packages/eslint-plugin/src/rules/nx-plugin-checks.ts b/packages/eslint-plugin/src/rules/nx-plugin-checks.ts index 7bbf3c7017650..c6c7f9c3d5077 100644 --- a/packages/eslint-plugin/src/rules/nx-plugin-checks.ts +++ b/packages/eslint-plugin/src/rules/nx-plugin-checks.ts @@ -57,7 +57,6 @@ export default ESLintUtils.RuleCreator(() => ``)({ meta: { docs: { description: 'Checks common nx-plugin configuration files for validity', - recommended: 'recommended', }, schema: [ { diff --git a/packages/eslint-plugin/typescript.ts b/packages/eslint-plugin/typescript.ts new file mode 100644 index 0000000000000..c4f3146904218 --- /dev/null +++ b/packages/eslint-plugin/typescript.ts @@ -0,0 +1,16 @@ +import javascript from './src/flat-configs/javascript'; +import typescript from './src/flat-configs/typescript'; + +const plugin = { + configs: { + javascript, + typescript, + }, + rules: {}, +}; + +// ESM +export default plugin; + +// CommonJS +module.exports = plugin; diff --git a/packages/eslint/src/executors/lint/utility/eslint-utils.spec.ts b/packages/eslint/src/executors/lint/utility/eslint-utils.spec.ts index 3dde87789fcdb..a4cb7ed8f18aa 100644 --- a/packages/eslint/src/executors/lint/utility/eslint-utils.spec.ts +++ b/packages/eslint/src/executors/lint/utility/eslint-utils.spec.ts @@ -1,8 +1,8 @@ -jest.mock('eslint', () => ({ - ESLint: jest.fn(), +jest.mock('eslint/use-at-your-own-risk', () => ({ + LegacyESLint: jest.fn(), })); -import { ESLint } from 'eslint'; +const { LegacyESLint } = require('eslint/use-at-your-own-risk'); import { resolveAndInstantiateESLint } from './eslint-utils'; describe('eslint-utils', () => { @@ -18,7 +18,7 @@ describe('eslint-utils', () => { cacheStrategy: 'content', }).catch(() => {}); - expect(ESLint).toHaveBeenCalledWith({ + expect(LegacyESLint).toHaveBeenCalledWith({ overrideConfigFile: './.eslintrc.json', fix: true, cache: true, @@ -40,7 +40,7 @@ describe('eslint-utils', () => { cacheStrategy: 'content', }).catch(() => {}); - expect(ESLint).toHaveBeenCalledWith({ + expect(LegacyESLint).toHaveBeenCalledWith({ overrideConfigFile: undefined, fix: true, cache: true, @@ -63,7 +63,7 @@ describe('eslint-utils', () => { noEslintrc: true, }).catch(() => {}); - expect(ESLint).toHaveBeenCalledWith({ + expect(LegacyESLint).toHaveBeenCalledWith({ overrideConfigFile: undefined, fix: true, cache: true, @@ -89,7 +89,7 @@ describe('eslint-utils', () => { rulesdir: extraRuleDirectories, } as any).catch(() => {}); - expect(ESLint).toHaveBeenCalledWith({ + expect(LegacyESLint).toHaveBeenCalledWith({ overrideConfigFile: undefined, fix: true, cache: true, @@ -114,7 +114,7 @@ describe('eslint-utils', () => { resolvePluginsRelativeTo: './some-path', } as any).catch(() => {}); - expect(ESLint).toHaveBeenCalledWith({ + expect(LegacyESLint).toHaveBeenCalledWith({ overrideConfigFile: undefined, fix: true, cache: true, @@ -135,7 +135,7 @@ describe('eslint-utils', () => { reportUnusedDisableDirectives: 'error', } as any).catch(() => {}); - expect(ESLint).toHaveBeenCalledWith({ + expect(LegacyESLint).toHaveBeenCalledWith({ cache: false, cacheLocation: undefined, cacheStrategy: undefined, @@ -153,7 +153,7 @@ describe('eslint-utils', () => { it('should create a ESLint instance with no "reportUnusedDisableDirectives" if it is undefined', async () => { await resolveAndInstantiateESLint(undefined, {} as any); - expect(ESLint).toHaveBeenCalledWith( + expect(LegacyESLint).toHaveBeenCalledWith( expect.objectContaining({ reportUnusedDisableDirectives: undefined, }) diff --git a/packages/eslint/src/executors/lint/utility/eslint-utils.ts b/packages/eslint/src/executors/lint/utility/eslint-utils.ts index e9e85baf6b34d..5a78a15f0150c 100644 --- a/packages/eslint/src/executors/lint/utility/eslint-utils.ts +++ b/packages/eslint/src/executors/lint/utility/eslint-utils.ts @@ -14,7 +14,9 @@ export async function resolveAndInstantiateESLint( 'When using the new Flat Config with ESLint, all configs must be named eslint.config.js or eslint.config.cjs and .eslintrc files may not be used. See https://eslint.org/docs/latest/use/configure/configuration-files' ); } - const ESLint = await resolveESLintClass(useFlatConfig); + const ESLint = await resolveESLintClass({ + useFlatConfigOverrideVal: useFlatConfig, + }); const eslintOptions: ESLint.Options = { overrideConfigFile: eslintConfigPath, diff --git a/packages/eslint/src/generators/convert-to-flat-config/__snapshots__/generator.spec.ts.snap b/packages/eslint/src/generators/convert-to-flat-config/__snapshots__/generator.spec.ts.snap index 491e51e051057..43b7476d1018c 100644 --- a/packages/eslint/src/generators/convert-to-flat-config/__snapshots__/generator.spec.ts.snap +++ b/packages/eslint/src/generators/convert-to-flat-config/__snapshots__/generator.spec.ts.snap @@ -2,9 +2,9 @@ exports[`convert-to-flat-config generator should add env configuration 1`] = ` "const { FlatCompat } = require('@eslint/eslintrc'); +const js = require('@eslint/js'); const nxEslintPlugin = require('@nx/eslint-plugin'); const globals = require('globals'); -const js = require('@eslint/js'); const compat = new FlatCompat({ baseDirectory: __dirname, @@ -52,9 +52,9 @@ module.exports = [ exports[`convert-to-flat-config generator should add global and env configuration 1`] = ` "const { FlatCompat } = require('@eslint/eslintrc'); +const js = require('@eslint/js'); const nxEslintPlugin = require('@nx/eslint-plugin'); const globals = require('globals'); -const js = require('@eslint/js'); const compat = new FlatCompat({ baseDirectory: __dirname, @@ -106,8 +106,8 @@ module.exports = [ exports[`convert-to-flat-config generator should add global configuration 1`] = ` "const { FlatCompat } = require('@eslint/eslintrc'); -const nxEslintPlugin = require('@nx/eslint-plugin'); const js = require('@eslint/js'); +const nxEslintPlugin = require('@nx/eslint-plugin'); const compat = new FlatCompat({ baseDirectory: __dirname, @@ -155,8 +155,8 @@ module.exports = [ exports[`convert-to-flat-config generator should add global eslintignores 1`] = ` "const { FlatCompat } = require('@eslint/eslintrc'); -const nxEslintPlugin = require('@nx/eslint-plugin'); const js = require('@eslint/js'); +const nxEslintPlugin = require('@nx/eslint-plugin'); const compat = new FlatCompat({ baseDirectory: __dirname, @@ -204,9 +204,9 @@ module.exports = [ exports[`convert-to-flat-config generator should add parser 1`] = ` "const { FlatCompat } = require('@eslint/eslintrc'); +const js = require('@eslint/js'); const nxEslintPlugin = require('@nx/eslint-plugin'); const typescriptEslintParser = require('@typescript-eslint/parser'); -const js = require('@eslint/js'); const compat = new FlatCompat({ baseDirectory: __dirname, @@ -254,11 +254,11 @@ module.exports = [ exports[`convert-to-flat-config generator should add plugins 1`] = ` "const { FlatCompat } = require('@eslint/eslintrc'); +const js = require('@eslint/js'); const eslintPluginImport = require('eslint-plugin-import'); const eslintPluginSingleName = require('eslint-plugin-single-name'); const scopeEslintPluginWithName = require('@scope/eslint-plugin-with-name'); const justScopeEslintPlugin = require('@just-scope/eslint-plugin'); -const js = require('@eslint/js'); const compat = new FlatCompat({ baseDirectory: __dirname, @@ -312,8 +312,8 @@ module.exports = [ exports[`convert-to-flat-config generator should add settings 1`] = ` "const { FlatCompat } = require('@eslint/eslintrc'); -const nxEslintPlugin = require('@nx/eslint-plugin'); const js = require('@eslint/js'); +const nxEslintPlugin = require('@nx/eslint-plugin'); const compat = new FlatCompat({ baseDirectory: __dirname, @@ -361,8 +361,8 @@ module.exports = [ exports[`convert-to-flat-config generator should convert json successfully 1`] = ` "const { FlatCompat } = require('@eslint/eslintrc'); -const nxEslintPlugin = require('@nx/eslint-plugin'); const js = require('@eslint/js'); +const nxEslintPlugin = require('@nx/eslint-plugin'); const compat = new FlatCompat({ baseDirectory: __dirname, @@ -414,14 +414,17 @@ module.exports = [ ...baseConfig, { files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'], + // Override or add rules here rules: {}, }, { files: ['**/*.ts', '**/*.tsx'], + // Override or add rules here rules: {}, }, { files: ['**/*.js', '**/*.jsx'], + // Override or add rules here rules: {}, }, ]; @@ -430,8 +433,8 @@ module.exports = [ exports[`convert-to-flat-config generator should convert yaml successfully 1`] = ` "const { FlatCompat } = require('@eslint/eslintrc'); -const nxEslintPlugin = require('@nx/eslint-plugin'); const js = require('@eslint/js'); +const nxEslintPlugin = require('@nx/eslint-plugin'); const compat = new FlatCompat({ baseDirectory: __dirname, @@ -483,14 +486,17 @@ module.exports = [ ...baseConfig, { files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'], + // Override or add rules here rules: {}, }, { files: ['**/*.ts', '**/*.tsx'], + // Override or add rules here rules: {}, }, { files: ['**/*.js', '**/*.jsx'], + // Override or add rules here rules: {}, }, ]; @@ -499,8 +505,8 @@ module.exports = [ exports[`convert-to-flat-config generator should convert yml successfully 1`] = ` "const { FlatCompat } = require('@eslint/eslintrc'); -const nxEslintPlugin = require('@nx/eslint-plugin'); const js = require('@eslint/js'); +const nxEslintPlugin = require('@nx/eslint-plugin'); const compat = new FlatCompat({ baseDirectory: __dirname, @@ -552,14 +558,17 @@ module.exports = [ ...baseConfig, { files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'], + // Override or add rules here rules: {}, }, { files: ['**/*.ts', '**/*.tsx'], + // Override or add rules here rules: {}, }, { files: ['**/*.js', '**/*.jsx'], + // Override or add rules here rules: {}, }, ]; @@ -573,14 +582,17 @@ module.exports = [ ...baseConfig, { files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'], + // Override or add rules here rules: {}, }, { files: ['**/*.ts', '**/*.tsx'], + // Override or add rules here rules: {}, }, { files: ['**/*.js', '**/*.jsx'], + // Override or add rules here rules: {}, }, { ignores: ['ignore/me'] }, diff --git a/packages/eslint/src/generators/convert-to-flat-config/converters/json-converter.spec.ts b/packages/eslint/src/generators/convert-to-flat-config/converters/json-converter.spec.ts index a6ba8c6fa8b58..26e2935cfdaac 100644 --- a/packages/eslint/src/generators/convert-to-flat-config/converters/json-converter.spec.ts +++ b/packages/eslint/src/generators/convert-to-flat-config/converters/json-converter.spec.ts @@ -67,12 +67,12 @@ describe('convertEslintJsonToFlatConfig', () => { expect(content).toMatchInlineSnapshot(` "const { FlatCompat } = require("@eslint/eslintrc"); - const nxEslintPlugin = require("@nx/eslint-plugin"); const js = require("@eslint/js"); + const nxEslintPlugin = require("@nx/eslint-plugin"); const compat = new FlatCompat({ - baseDirectory: __dirname, - recommendedConfig: js.configs.recommended, + baseDirectory: __dirname, + recommendedConfig: js.configs.recommended, }); module.exports = [ @@ -182,13 +182,13 @@ describe('convertEslintJsonToFlatConfig', () => { expect(content).toMatchInlineSnapshot(` "const { FlatCompat } = require("@eslint/eslintrc"); + const js = require("@eslint/js"); const baseConfig = require("../../eslint.config.js"); const globals = require("globals"); - const js = require("@eslint/js"); const compat = new FlatCompat({ - baseDirectory: __dirname, - recommendedConfig: js.configs.recommended, + baseDirectory: __dirname, + recommendedConfig: js.configs.recommended, }); module.exports = [ @@ -213,6 +213,7 @@ describe('convertEslintJsonToFlatConfig', () => { "**/*.ts", "**/*.tsx" ], + // Override or add rules here rules: {} }, { @@ -220,16 +221,14 @@ describe('convertEslintJsonToFlatConfig', () => { "**/*.js", "**/*.jsx" ], + // Override or add rules here rules: {} }, - ...compat.config({ parser: "jsonc-eslint-parser" }).map(config => ({ - ...config, + { files: ["**/*.json"], - rules: { - ...config.rules, - "@nx/dependency-checks": "error" - } - })), + rules: { "@nx/dependency-checks": "error" }, + languageOptions: { parser: require("jsonc-eslint-parser") } + }, { ignores: [".next/**/*"] }, { ignores: ["something/else"] } ]; diff --git a/packages/eslint/src/generators/convert-to-flat-config/converters/json-converter.ts b/packages/eslint/src/generators/convert-to-flat-config/converters/json-converter.ts index fa45bedbc78bf..b46c1e92853c4 100644 --- a/packages/eslint/src/generators/convert-to-flat-config/converters/json-converter.ts +++ b/packages/eslint/src/generators/convert-to-flat-config/converters/json-converter.ts @@ -2,6 +2,7 @@ import { Tree, names } from '@nx/devkit'; import { ESLint } from 'eslint'; import * as ts from 'typescript'; import { + addFlatCompatToFlatConfig, createNodeList, generateAst, generateFlatOverride, @@ -149,6 +150,21 @@ export function convertEslintJsonToFlatConfig( isFlatCompatNeeded = true; } exportElements.push(generateFlatOverride(override)); + + // eslint-plugin-import cannot be used with ESLint v9 yet + // TODO(jack): Once v9 support is released, remove this block. + // See: https://github.com/import-js/eslint-plugin-import/pull/2996 + if (override.extends === 'plugin:@nx/react') { + exportElements.push( + generateFlatOverride({ + rules: { + 'import/first': 'off', + 'import/no-amd': 'off', + 'import/no-webpack-loader-syntax': 'off', + }, + }) + ); + } }); } @@ -181,14 +197,14 @@ export function convertEslintJsonToFlatConfig( } // create the node list and print it to new file - const nodeList = createNodeList( - importsMap, - exportElements, - isFlatCompatNeeded - ); + const nodeList = createNodeList(importsMap, exportElements); + let content = stringifyNodeList(nodeList); + if (isFlatCompatNeeded) { + content = addFlatCompatToFlatConfig(content); + } return { - content: stringifyNodeList(nodeList), + content, addESLintRC: isFlatCompatNeeded, addESLintJS: isESLintJSNeeded, }; diff --git a/packages/eslint/src/generators/convert-to-flat-config/generator.spec.ts b/packages/eslint/src/generators/convert-to-flat-config/generator.spec.ts index f11734dc7fa48..988dfd588b04d 100644 --- a/packages/eslint/src/generators/convert-to-flat-config/generator.spec.ts +++ b/packages/eslint/src/generators/convert-to-flat-config/generator.spec.ts @@ -147,8 +147,8 @@ describe('convert-to-flat-config generator', () => { expect(tree.read('eslint.config.js', 'utf-8')).toMatchInlineSnapshot(` "const { FlatCompat } = require('@eslint/eslintrc'); - const nxEslintPlugin = require('@nx/eslint-plugin'); const js = require('@eslint/js'); + const nxEslintPlugin = require('@nx/eslint-plugin'); const compat = new FlatCompat({ baseDirectory: __dirname, @@ -201,14 +201,17 @@ describe('convert-to-flat-config generator', () => { ...baseConfig, { files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'], + // Override or add rules here rules: {}, }, { files: ['**/*.ts', '**/*.tsx'], + // Override or add rules here rules: {}, }, { files: ['**/*.js', '**/*.jsx'], + // Override or add rules here rules: {}, }, ]; @@ -392,8 +395,8 @@ describe('convert-to-flat-config generator', () => { expect(tree.read('eslint.config.js', 'utf-8')).toMatchInlineSnapshot(` "const { FlatCompat } = require('@eslint/eslintrc'); - const nxEslintPlugin = require('@nx/eslint-plugin'); const js = require('@eslint/js'); + const nxEslintPlugin = require('@nx/eslint-plugin'); const compat = new FlatCompat({ baseDirectory: __dirname, @@ -554,6 +557,7 @@ describe('convert-to-flat-config generator', () => { ...baseConfig, { files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'], + // Override or add rules here rules: {}, languageOptions: { parserOptions: { project: ['apps/dx-assets-ui/tsconfig.*?.json'] }, @@ -561,10 +565,12 @@ describe('convert-to-flat-config generator', () => { }, { files: ['**/*.ts', '**/*.tsx'], + // Override or add rules here rules: {}, }, { files: ['**/*.js', '**/*.jsx'], + // Override or add rules here rules: {}, }, { ignores: ['__fixtures__/**/*'] }, diff --git a/packages/eslint/src/generators/convert-to-flat-config/generator.ts b/packages/eslint/src/generators/convert-to-flat-config/generator.ts index 5537762372fef..795b97b6c91fa 100644 --- a/packages/eslint/src/generators/convert-to-flat-config/generator.ts +++ b/packages/eslint/src/generators/convert-to-flat-config/generator.ts @@ -15,12 +15,16 @@ import { import { ConvertToFlatConfigGeneratorSchema } from './schema'; import { findEslintFile } from '../utils/eslint-file'; import { join } from 'path'; -import { eslintrcVersion, eslintVersion } from '../../utils/versions'; +import { + eslint9__eslintVersion, + eslint9__typescriptESLintVersion, + eslintConfigPrettierVersion, + eslintrcVersion, + eslintVersion, +} from '../../utils/versions'; import { ESLint } from 'eslint'; import { convertEslintJsonToFlatConfig } from './converters/json-converter'; -let shouldInstallDeps = false; - export async function convertToFlatConfigGenerator( tree: Tree, options: ConvertToFlatConfigGeneratorSchema @@ -65,9 +69,7 @@ export async function convertToFlatConfigGenerator( await formatFiles(tree); } - if (shouldInstallDeps) { - return () => installPackagesTask(tree); - } + return () => installPackagesTask(tree); } export default convertToFlatConfigGenerator; @@ -221,25 +223,21 @@ function processConvertedConfig( // save new tree.write(join(root, target), content); + // These dependencies are required for flat configs that are generated by subsequent app/lib generators. + const devDependencies: Record = { + eslint: eslint9__eslintVersion, + 'eslint-config-prettier': eslintConfigPrettierVersion, + 'typescript-eslint': eslint9__typescriptESLintVersion, + }; + // add missing packages if (addESLintRC) { - shouldInstallDeps = true; - addDependenciesToPackageJson( - tree, - {}, - { - '@eslint/eslintrc': eslintrcVersion, - } - ); + devDependencies['@eslint/eslintrc'] = eslintrcVersion; } + if (addESLintJS) { - shouldInstallDeps = true; - addDependenciesToPackageJson( - tree, - {}, - { - '@eslint/js': eslintVersion, - } - ); + devDependencies['@eslint/js'] = eslintVersion; } + + addDependenciesToPackageJson(tree, {}, devDependencies); } diff --git a/packages/eslint/src/generators/init/global-eslint-config.ts b/packages/eslint/src/generators/init/global-eslint-config.ts index 83656e2bf845c..55577059ddcc1 100644 --- a/packages/eslint/src/generators/init/global-eslint-config.ts +++ b/packages/eslint/src/generators/init/global-eslint-config.ts @@ -2,10 +2,9 @@ import { Linter } from 'eslint'; import { addBlockToFlatConfigExport, addImportToFlatConfig, - addPluginsToExportsBlock, createNodeList, - generateAst, generateFlatOverride, + generateFlatPredefinedConfig, stringifyNodeList, } from '../utils/flat-config/ast-utils'; @@ -93,40 +92,56 @@ export const getGlobalEsLintConfiguration = ( }; export const getGlobalFlatEslintConfiguration = ( - unitTestRunner?: string, rootProject?: boolean ): string => { - const nodeList = createNodeList(new Map(), [], true); + const nodeList = createNodeList(new Map(), []); let content = stringifyNodeList(nodeList); - content = addImportToFlatConfig(content, 'nxPlugin', '@nx/eslint-plugin'); - content = addPluginsToExportsBlock(content, [ - { name: '@nx', varName: 'nxPlugin', imp: '@nx/eslint-plugin' }, - ]); - if (!rootProject) { - content = addBlockToFlatConfigExport( - content, - generateFlatOverride(moduleBoundariesOverride) - ); - } + content = addImportToFlatConfig(content, 'nx', '@nx/eslint-plugin'); + content = addBlockToFlatConfigExport( content, - generateFlatOverride(typeScriptOverride) + generateFlatPredefinedConfig('flat/base'), + { insertAtTheEnd: false } ); content = addBlockToFlatConfigExport( content, - generateFlatOverride(javaScriptOverride) + generateFlatPredefinedConfig('flat/typescript') ); - if (unitTestRunner === 'jest') { + content = addBlockToFlatConfigExport( + content, + generateFlatPredefinedConfig('flat/javascript') + ); + + if (!rootProject) { content = addBlockToFlatConfigExport( content, - generateFlatOverride(jestOverride) + generateFlatOverride({ + files: ['*.ts', '*.tsx', '*.js', '*.jsx'], + rules: { + '@nx/enforce-module-boundaries': [ + 'error', + { + enforceBuildableLibDependency: true, + allow: [ + // This allows a root project to be present without causing lint errors + // since all projects will depend on this base file. + '^.*/eslint(\\.base)?\\.config\\.[cm]?js$', + ], + depConstraints: [ + { sourceTag: '*', onlyDependOnLibsWithTags: ['*'] }, + ], + }, + ], + } as Linter.RulesRecord, + }) ); } - // add ignore for .nx folder + content = addBlockToFlatConfigExport( content, - generateAst({ - ignores: ['.nx'], + generateFlatOverride({ + files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'], + rules: {}, }) ); diff --git a/packages/eslint/src/generators/init/init-migration.ts b/packages/eslint/src/generators/init/init-migration.ts index 2469606f8b8e7..ea199ff126b7c 100644 --- a/packages/eslint/src/generators/init/init-migration.ts +++ b/packages/eslint/src/generators/init/init-migration.ts @@ -23,6 +23,7 @@ import { generateSpreadElement, removeCompatExtends, removePlugin, + removePredefinedConfigs, } from '../utils/flat-config/ast-utils'; import { hasEslintPlugin } from '../utils/plugin'; import { ESLINT_CONFIG_FILENAMES } from '../../utils/config-file'; @@ -59,7 +60,7 @@ export function migrateConfigToMonorepoStyle( tree.exists('eslint.config.js') ? 'eslint.base.config.js' : 'eslint.config.js', - getGlobalFlatEslintConfiguration(unitTestRunner) + getGlobalFlatEslintConfiguration() ); } else { const eslintFile = findEslintFile(tree, '.'); @@ -152,6 +153,11 @@ function migrateEslintFile(projectEslintPath: string, tree: Tree) { 'plugin:@nrwl/typescript', 'plugin:@nrwl/javascript', ]); + config = removePredefinedConfigs(config, '@nx/eslint-plugin', 'nx', [ + 'flat/base', + 'flat/typescript', + 'flat/javascript', + ]); tree.write(projectEslintPath, config); } else { updateJson(tree, projectEslintPath, (json) => { diff --git a/packages/eslint/src/generators/lint-project/lint-project.spec.ts b/packages/eslint/src/generators/lint-project/lint-project.spec.ts index ba308961eb869..d68e952e227e0 100644 --- a/packages/eslint/src/generators/lint-project/lint-project.spec.ts +++ b/packages/eslint/src/generators/lint-project/lint-project.spec.ts @@ -42,7 +42,53 @@ describe('@nx/eslint:lint-project', () => { }); }); - it('should generate a eslint config and configure the target in project configuration', async () => { + it('should generate a flat eslint base config', async () => { + const originalEslintUseFlatConfigVal = process.env.ESLINT_USE_FLAT_CONFIG; + process.env.ESLINT_USE_FLAT_CONFIG = 'true'; + await lintProjectGenerator(tree, { + ...defaultOptions, + linter: Linter.EsLint, + project: 'test-lib', + setParserOptionsProject: false, + }); + + expect(tree.read('eslint.config.js', 'utf-8')).toMatchInlineSnapshot(` + "const nx = require('@nx/eslint-plugin'); + + module.exports = [ + ...nx.configs['flat/base'], + ...nx.configs['flat/typescript'], + ...nx.configs['flat/javascript'], + { + files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'], + rules: { + '@nx/enforce-module-boundaries': [ + 'error', + { + enforceBuildableLibDependency: true, + allow: ['^.*/eslint(\\\\.base)?\\\\.config\\\\.[cm]?js$'], + depConstraints: [ + { + sourceTag: '*', + onlyDependOnLibsWithTags: ['*'], + }, + ], + }, + ], + }, + }, + { + files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'], + // Override or add rules here + rules: {}, + }, + ]; + " + `); + process.env.ESLINT_USE_FLAT_CONFIG = originalEslintUseFlatConfigVal; + }); + + it('should generate a eslint config (legacy)', async () => { await lintProjectGenerator(tree, { ...defaultOptions, linter: Linter.EsLint, @@ -121,7 +167,12 @@ describe('@nx/eslint:lint-project', () => { "files": ["*.json"], "parser": "jsonc-eslint-parser", "rules": { - "@nx/dependency-checks": "error" + "@nx/dependency-checks": [ + "error", + { + "ignoredFiles": ["{projectRoot}/eslint.config.{js,cjs,mjs}"] + } + ] } } ] diff --git a/packages/eslint/src/generators/lint-project/lint-project.ts b/packages/eslint/src/generators/lint-project/lint-project.ts index de9e57c3906c8..c7718390145e7 100644 --- a/packages/eslint/src/generators/lint-project/lint-project.ts +++ b/packages/eslint/src/generators/lint-project/lint-project.ts @@ -197,60 +197,69 @@ function createEsLintConfiguration( const pathToRootConfig = extendedRootConfig ? `${offsetFromRoot(projectConfig.root)}${extendedRootConfig}` : undefined; - const addDependencyChecks = isBuildableLibraryProject(projectConfig); + const addDependencyChecks = + options.addPackageJsonDependencyChecks || + isBuildableLibraryProject(projectConfig); - const overrides: Linter.ConfigOverride[] = [ - { - files: ['*.ts', '*.tsx', '*.js', '*.jsx'], - /** - * NOTE: We no longer set parserOptions.project by default when creating new projects. - * - * We have observed that users rarely add rules requiring type-checking to their Nx workspaces, and therefore - * do not actually need the capabilites which parserOptions.project provides. When specifying parserOptions.project, - * typescript-eslint needs to create full TypeScript Programs for you. When omitting it, it can perform a simple - * parse (and AST tranformation) of the source files it encounters during a lint run, which is much faster and much - * less memory intensive. - * - * In the rare case that users attempt to add rules requiring type-checking to their setup later on (and haven't set - * parserOptions.project), the executor will attempt to look for the particular error typescript-eslint gives you - * and provide feedback to the user. - */ - parserOptions: !setParserOptionsProject - ? undefined - : { - project: [`${projectConfig.root}/tsconfig.*?.json`], - }, - /** - * Having an empty rules object present makes it more obvious to the user where they would - * extend things from if they needed to - */ - rules: {}, - }, - { - files: ['*.ts', '*.tsx'], - rules: {}, - }, - { - files: ['*.js', '*.jsx'], - rules: {}, - }, - ]; + const overrides: Linter.ConfigOverride[] = useFlatConfig( + tree + ) + ? // For flat configs, we don't need to generate different overrides for each file. Users should add their own overrides as needed. + [] + : [ + { + files: ['*.ts', '*.tsx', '*.js', '*.jsx'], + /** + * NOTE: We no longer set parserOptions.project by default when creating new projects. + * + * We have observed that users rarely add rules requiring type-checking to their Nx workspaces, and therefore + * do not actually need the capabilites which parserOptions.project provides. When specifying parserOptions.project, + * typescript-eslint needs to create full TypeScript Programs for you. When omitting it, it can perform a simple + * parse (and AST tranformation) of the source files it encounters during a lint run, which is much faster and much + * less memory intensive. + * + * In the rare case that users attempt to add rules requiring type-checking to their setup later on (and haven't set + * parserOptions.project), the executor will attempt to look for the particular error typescript-eslint gives you + * and provide feedback to the user. + */ + parserOptions: !setParserOptionsProject + ? undefined + : { + project: [`${projectConfig.root}/tsconfig.*?.json`], + }, + /** + * Having an empty rules object present makes it more obvious to the user where they would + * extend things from if they needed to + */ + rules: {}, + }, + { + files: ['*.ts', '*.tsx'], + rules: {}, + }, + { + files: ['*.js', '*.jsx'], + rules: {}, + }, + ]; - if ( - options.addPackageJsonDependencyChecks || - isBuildableLibraryProject(projectConfig) - ) { + if (addDependencyChecks) { overrides.push({ files: ['*.json'], parser: 'jsonc-eslint-parser', rules: { - '@nx/dependency-checks': 'error', + '@nx/dependency-checks': [ + 'error', + { + // With flat configs, we don't want to include imports in the eslint js/cjs/mjs files to be checked + ignoredFiles: ['{projectRoot}/eslint.config.{js,cjs,mjs}'], + }, + ], }, }); } if (useFlatConfig(tree)) { - const isCompatNeeded = addDependencyChecks; const nodes = []; const importMap = new Map(); if (extendedRootConfig) { @@ -260,7 +269,7 @@ function createEsLintConfiguration( overrides.forEach((override) => { nodes.push(generateFlatOverride(override)); }); - const nodeList = createNodeList(importMap, nodes, isCompatNeeded); + const nodeList = createNodeList(importMap, nodes); const content = stringifyNodeList(nodeList); tree.write(join(projectConfig.root, 'eslint.config.js'), content); } else { diff --git a/packages/eslint/src/generators/lint-project/setup-root-eslint.ts b/packages/eslint/src/generators/lint-project/setup-root-eslint.ts index 5c776522e3ef8..6a486e7f71e0b 100644 --- a/packages/eslint/src/generators/lint-project/setup-root-eslint.ts +++ b/packages/eslint/src/generators/lint-project/setup-root-eslint.ts @@ -4,12 +4,18 @@ import { type GeneratorCallback, type Tree, } from '@nx/devkit'; +import { useFlatConfig } from '../../utils/flat-config'; import { + eslint9__eslintVersion, + eslint9__typescriptESLintVersion, eslintConfigPrettierVersion, nxVersion, typescriptESLintVersion, } from '../../utils/versions'; -import { getGlobalEsLintConfiguration } from '../init/global-eslint-config'; +import { + getGlobalEsLintConfiguration, + getGlobalFlatEslintConfiguration, +} from '../init/global-eslint-config'; import { findEslintFile } from '../utils/eslint-file'; export type SetupRootEsLintOptions = { @@ -26,7 +32,13 @@ export function setupRootEsLint( if (rootEslintFile) { return () => {}; } + if (!useFlatConfig(tree)) { + return setUpLegacyRootEslintRc(tree, options); + } + return setUpRootFlatConfig(tree, options); +} +function setUpLegacyRootEslintRc(tree: Tree, options: SetupRootEsLintOptions) { writeJson( tree, '.eslintrc.json', @@ -56,3 +68,24 @@ export function setupRootEsLint( ) : () => {}; } + +function setUpRootFlatConfig(tree: Tree, options: SetupRootEsLintOptions) { + tree.write( + 'eslint.config.js', + getGlobalFlatEslintConfiguration(options.rootProject) + ); + + return !options.skipPackageJson + ? addDependenciesToPackageJson( + tree, + {}, + { + '@eslint/js': eslint9__eslintVersion, + '@nx/eslint-plugin': nxVersion, + eslint: eslint9__eslintVersion, + 'eslint-config-prettier': eslintConfigPrettierVersion, + 'typescript-eslint': eslint9__typescriptESLintVersion, + } + ) + : () => {}; +} diff --git a/packages/eslint/src/generators/utils/eslint-file.spec.ts b/packages/eslint/src/generators/utils/eslint-file.spec.ts index 54f48749c2854..5d58681dc4800 100644 --- a/packages/eslint/src/generators/utils/eslint-file.spec.ts +++ b/packages/eslint/src/generators/utils/eslint-file.spec.ts @@ -1,3 +1,10 @@ +import { readJson, type Tree } from '@nx/devkit'; +import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing'; +import * as devkitInternals from 'nx/src/devkit-internals'; +import { + ESLINT_CONFIG_FILENAMES, + baseEsLintConfigFile, +} from '../../utils/config-file'; import { addExtendsToLintConfig, findEslintFile, @@ -5,13 +12,6 @@ import { replaceOverridesInLintConfig, } from './eslint-file'; -import { Tree, readJson } from '@nx/devkit'; -import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing'; -import { - ESLINT_CONFIG_FILENAMES, - baseEsLintConfigFile, -} from '../../utils/config-file'; - describe('@nx/eslint:lint-file', () => { let tree: Tree; @@ -120,6 +120,236 @@ describe('@nx/eslint:lint-file', () => { '../../.eslintrc', ]); }); + + it('should add extends to flat config', () => { + tree.write('eslint.config.js', 'module.exports = {};'); + tree.write( + 'apps/demo/eslint.config.js', + `const baseConfig = require("../../eslint.config.js"); + +module.exports = [ + ...baseConfig, + { + files: [ + "**/*.ts", + "**/*.tsx", + "**/*.js", + "**/*.jsx" + ], + rules: {} + }, +];` + ); + + addExtendsToLintConfig(tree, 'apps/demo', 'plugin:playwright/recommend'); + + expect(tree.read('apps/demo/eslint.config.js', 'utf-8')) + .toMatchInlineSnapshot(` + "const { FlatCompat } = require("@eslint/eslintrc"); + const js = require("@eslint/js"); + const baseConfig = require("../../eslint.config.js"); + + const compat = new FlatCompat({ + baseDirectory: __dirname, + recommendedConfig: js.configs.recommended, + }); + + module.exports = [ + ...compat.extends("plugin:playwright/recommend"), + ...baseConfig, + { + files: [ + "**/*.ts", + "**/*.tsx", + "**/*.js", + "**/*.jsx" + ], + rules: {} + }, + ];" + `); + }); + + it('should add wrapped plugin for compat in extends when using eslint v9', () => { + // mock eslint version + jest.spyOn(devkitInternals, 'readModulePackageJson').mockReturnValue({ + packageJson: { name: 'eslint', version: '9.0.0' }, + path: '', + }); + tree.write('eslint.config.js', 'module.exports = {};'); + tree.write( + 'apps/demo/eslint.config.js', + `const baseConfig = require("../../eslint.config.js"); + +module.exports = [ + ...baseConfig, + { + files: [ + "**/*.ts", + "**/*.tsx", + "**/*.js", + "**/*.jsx" + ], + rules: {} + }, +];` + ); + + addExtendsToLintConfig(tree, 'apps/demo', { + name: 'plugin:playwright/recommend', + needCompatFixup: true, + }); + + expect(tree.read('apps/demo/eslint.config.js', '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 compat = new FlatCompat({ + baseDirectory: __dirname, + recommendedConfig: js.configs.recommended, + }); + + module.exports = [ + ...fixupConfigRules(compat.extends("plugin:playwright/recommend")), + ...baseConfig, + { + files: [ + "**/*.ts", + "**/*.tsx", + "**/*.js", + "**/*.jsx" + ], + rules: {} + }, + ];" + `); + }); + + it('should handle mixed multiple incompatible and compatible plugins and add them to extends in the specified order when using eslint v9', () => { + // mock eslint version + jest.spyOn(devkitInternals, 'readModulePackageJson').mockReturnValue({ + packageJson: { name: 'eslint', version: '9.0.0' }, + path: '', + }); + tree.write('eslint.config.js', 'module.exports = {};'); + tree.write( + 'apps/demo/eslint.config.js', + `const baseConfig = require("../../eslint.config.js"); + +module.exports = [ + ...baseConfig, + { + files: [ + "**/*.ts", + "**/*.tsx", + "**/*.js", + "**/*.jsx" + ], + rules: {} + }, +];` + ); + + addExtendsToLintConfig(tree, 'apps/demo', [ + 'plugin:some-plugin1', + 'plugin:some-plugin2', + { name: 'incompatible-plugin1', needCompatFixup: true }, + { name: 'incompatible-plugin2', needCompatFixup: true }, + 'plugin:some-plugin3', + { name: 'incompatible-plugin3', needCompatFixup: true }, + ]); + + expect(tree.read('apps/demo/eslint.config.js', '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 compat = new FlatCompat({ + baseDirectory: __dirname, + recommendedConfig: js.configs.recommended, + }); + + module.exports = [ + ...compat.extends("plugin:some-plugin1", "plugin:some-plugin2"), + ...fixupConfigRules(compat.extends("incompatible-plugin1")), + ...fixupConfigRules(compat.extends("incompatible-plugin2")), + ...compat.extends("plugin:some-plugin3"), + ...fixupConfigRules(compat.extends("incompatible-plugin3")), + ...baseConfig, + { + files: [ + "**/*.ts", + "**/*.tsx", + "**/*.js", + "**/*.jsx" + ], + rules: {} + }, + ];" + `); + }); + + it('should not add wrapped plugin for compat in extends when not using eslint v9', () => { + // mock eslint version + jest.spyOn(devkitInternals, 'readModulePackageJson').mockReturnValue({ + packageJson: { name: 'eslint', version: '8.0.0' }, + path: '', + }); + tree.write('eslint.config.js', 'module.exports = {};'); + tree.write( + 'apps/demo/eslint.config.js', + `const baseConfig = require("../../eslint.config.js"); + +module.exports = [ + ...baseConfig, + { + files: [ + "**/*.ts", + "**/*.tsx", + "**/*.js", + "**/*.jsx" + ], + rules: {} + }, +];` + ); + + addExtendsToLintConfig(tree, 'apps/demo', { + name: 'plugin:playwright/recommend', + needCompatFixup: true, + }); + + expect(tree.read('apps/demo/eslint.config.js', 'utf-8')) + .toMatchInlineSnapshot(` + "const { FlatCompat } = require("@eslint/eslintrc"); + const js = require("@eslint/js"); + const baseConfig = require("../../eslint.config.js"); + + const compat = new FlatCompat({ + baseDirectory: __dirname, + recommendedConfig: js.configs.recommended, + }); + + module.exports = [ + ...compat.extends("plugin:playwright/recommend"), + ...baseConfig, + { + files: [ + "**/*.ts", + "**/*.tsx", + "**/*.js", + "**/*.jsx" + ], + rules: {} + }, + ];" + `); + }); }); describe('replaceOverridesInLintConfig', () => { @@ -197,10 +427,9 @@ module.exports = [ const baseConfig = require("../../eslint.config.js"); const compat = new FlatCompat({ - baseDirectory: __dirname, - recommendedConfig: js.configs.recommended, - }); - + baseDirectory: __dirname, + recommendedConfig: js.configs.recommended, + }); module.exports = [ ...baseConfig, diff --git a/packages/eslint/src/generators/utils/eslint-file.ts b/packages/eslint/src/generators/utils/eslint-file.ts index 13226afd8b8b2..2db5e49f3f469 100644 --- a/packages/eslint/src/generators/utils/eslint-file.ts +++ b/packages/eslint/src/generators/utils/eslint-file.ts @@ -1,35 +1,43 @@ import { + addDependenciesToPackageJson, + type GeneratorCallback, joinPathFragments, names, offsetFromRoot, readJson, + type Tree, updateJson, } from '@nx/devkit'; -import type { Tree } from '@nx/devkit'; import type { Linter } from 'eslint'; +import { gte } from 'semver'; import { - flatConfigEslintFilename, + baseEsLintConfigFile, + baseEsLintFlatConfigFile, + ESLINT_CONFIG_FILENAMES, +} from '../../utils/config-file'; +import { + getRootESLintFlatConfigFilename, useFlatConfig, } from '../../utils/flat-config'; +import { getInstalledEslintVersion } from '../../utils/version-utils'; +import { eslint9__eslintVersion, eslintCompat } from '../../utils/versions'; import { addBlockToFlatConfigExport, - addCompatToFlatConfig, + addFlatCompatToFlatConfig, addImportToFlatConfig, addPluginsToExportsBlock, generateAst, generateFlatOverride, + generateFlatPredefinedConfig, generatePluginExtendsElement, + generatePluginExtendsElementWithCompatFixup, hasOverride, + overrideNeedsCompat, removeOverridesFromLintConfig, replaceOverride, } from './flat-config/ast-utils'; -import ts = require('typescript'); import { mapFilePath } from './flat-config/path-utils'; -import { - baseEsLintConfigFile, - baseEsLintFlatConfigFile, - ESLINT_CONFIG_FILENAMES, -} from '../../utils/config-file'; +import ts = require('typescript'); export function findEslintFile( tree: Tree, @@ -167,7 +175,7 @@ function offsetFilePath( export function addOverrideToLintConfig( tree: Tree, root: string, - override: Linter.ConfigOverride, + override: Partial>, options: { insertAtTheEnd?: boolean; checkBaseConfig?: boolean } = { insertAtTheEnd: true, } @@ -177,13 +185,13 @@ export function addOverrideToLintConfig( if (useFlatConfig(tree)) { const fileName = joinPathFragments( root, - isBase ? baseEsLintFlatConfigFile : flatConfigEslintFilename(tree) + isBase ? baseEsLintFlatConfigFile : getRootESLintFlatConfigFilename(tree) ); const flatOverride = generateFlatOverride(override); let content = tree.read(fileName, 'utf8'); - // we will be using compat here so we need to make sure it's added + // Check if the provided override using legacy eslintrc properties or plugins, if so we need to add compat if (overrideNeedsCompat(override)) { - content = addCompatToFlatConfig(content); + content = addFlatCompatToFlatConfig(content); } tree.write( fileName, @@ -206,14 +214,6 @@ export function addOverrideToLintConfig( } } -function overrideNeedsCompat( - override: Linter.ConfigOverride -) { - return ( - override.env || override.extends || override.plugins || override.parser - ); -} - export function updateOverrideInLintConfig( tree: Tree, root: string, @@ -223,7 +223,10 @@ export function updateOverrideInLintConfig( ) => Linter.ConfigOverride ) { if (useFlatConfig(tree)) { - const fileName = joinPathFragments(root, flatConfigEslintFilename(tree)); + const fileName = joinPathFragments( + root, + getRootESLintFlatConfigFilename(tree) + ); let content = tree.read(fileName, 'utf8'); content = replaceOverride(content, root, lookup, update); tree.write(fileName, content); @@ -265,7 +268,7 @@ export function lintConfigHasOverride( if (useFlatConfig(tree)) { const fileName = joinPathFragments( root, - isBase ? baseEsLintFlatConfigFile : flatConfigEslintFilename(tree) + isBase ? baseEsLintFlatConfigFile : getRootESLintFlatConfigFilename(tree) ); const content = tree.read(fileName, 'utf8'); return hasOverride(content, lookup); @@ -285,11 +288,14 @@ export function replaceOverridesInLintConfig( overrides: Linter.ConfigOverride[] ) { if (useFlatConfig(tree)) { - const fileName = joinPathFragments(root, flatConfigEslintFilename(tree)); + const fileName = joinPathFragments( + root, + getRootESLintFlatConfigFilename(tree) + ); let content = tree.read(fileName, 'utf8'); - // we will be using compat here so we need to make sure it's added + // Check if any of the provided overrides using legacy eslintrc properties or plugins, if so we need to add compat if (overrides.some(overrideNeedsCompat)) { - content = addCompatToFlatConfig(content); + content = addFlatCompatToFlatConfig(content); } content = removeOverridesFromLintConfig(content); overrides.forEach((override) => { @@ -310,21 +316,92 @@ export function replaceOverridesInLintConfig( export function addExtendsToLintConfig( tree: Tree, root: string, - plugin: string | string[] -) { - const plugins = Array.isArray(plugin) ? plugin : [plugin]; + plugin: + | string + | { name: string; needCompatFixup: boolean } + | Array, + insertAtTheEnd = false +): GeneratorCallback { if (useFlatConfig(tree)) { - const fileName = joinPathFragments(root, flatConfigEslintFilename(tree)); - const pluginExtends = generatePluginExtendsElement(plugins); - let content = tree.read(fileName, 'utf8'); - content = addCompatToFlatConfig(content); - tree.write( - fileName, - addBlockToFlatConfigExport(content, pluginExtends, { - insertAtTheEnd: false, - }) + const pluginExtends: ts.SpreadElement[] = []; + const fileName = joinPathFragments( + root, + getRootESLintFlatConfigFilename(tree) ); + let shouldImportEslintCompat = false; + // assume eslint version is 9 if not found, as it's what we'd be generating by default + const eslintVersion = + getInstalledEslintVersion(tree) ?? eslint9__eslintVersion; + if (gte(eslintVersion, '9.0.0')) { + // eslint v9 requires the incompatible plugins to be wrapped with a helper from @eslint/compat + const plugins = (Array.isArray(plugin) ? plugin : [plugin]).map((p) => + typeof p === 'string' ? { name: p, needCompatFixup: false } : p + ); + let compatiblePluginsBatch: string[] = []; + plugins.forEach(({ name, needCompatFixup }) => { + if (needCompatFixup) { + if (compatiblePluginsBatch.length > 0) { + // flush the current batch of compatible plugins and reset it + pluginExtends.push( + generatePluginExtendsElement(compatiblePluginsBatch) + ); + compatiblePluginsBatch = []; + } + // generate the extends for the incompatible plugin + pluginExtends.push(generatePluginExtendsElementWithCompatFixup(name)); + shouldImportEslintCompat = true; + } else { + // add the compatible plugin to the current batch + compatiblePluginsBatch.push(name); + } + }); + + if (compatiblePluginsBatch.length > 0) { + // flush the batch of compatible plugins + pluginExtends.push( + generatePluginExtendsElement(compatiblePluginsBatch) + ); + } + } else { + const plugins = (Array.isArray(plugin) ? plugin : [plugin]).map((p) => + typeof p === 'string' ? p : p.name + ); + pluginExtends.push(generatePluginExtendsElement(plugins)); + } + + let content = tree.read(fileName, 'utf8'); + if (shouldImportEslintCompat) { + content = addImportToFlatConfig( + content, + ['fixupConfigRules'], + '@eslint/compat' + ); + } + content = addFlatCompatToFlatConfig(content); + // reverse the order to ensure they are added in the correct order at the + // start of the `extends` array + for (const pluginExtend of pluginExtends.reverse()) { + content = addBlockToFlatConfigExport(content, pluginExtend, { + insertAtTheEnd, + }); + } + tree.write(fileName, content); + + if (shouldImportEslintCompat) { + return addDependenciesToPackageJson( + tree, + {}, + { '@eslint/compat': eslintCompat }, + undefined, + true + ); + } + + return () => {}; } else { + const plugins = (Array.isArray(plugin) ? plugin : [plugin]).map((p) => + typeof p === 'string' ? p : p.name + ); const fileName = joinPathFragments(root, '.eslintrc.json'); updateJson(tree, fileName, (json) => { json.extends ??= []; @@ -334,9 +411,39 @@ export function addExtendsToLintConfig( ]; return json; }); + + return () => {}; } } +export function addPredefinedConfigToFlatLintConfig( + tree: Tree, + root: string, + predefinedConfigName: string, + moduleName = 'nx', + moduleImportPath = '@nx/eslint-plugin', + spread = true, + insertAtTheEnd = true +): void { + if (!useFlatConfig(tree)) + throw new Error('Predefined configs can only be used with flat configs'); + + const fileName = joinPathFragments( + root, + getRootESLintFlatConfigFilename(tree) + ); + + let content = tree.read(fileName, 'utf8'); + content = addImportToFlatConfig(content, moduleName, moduleImportPath); + content = addBlockToFlatConfigExport( + content, + generateFlatPredefinedConfig(predefinedConfigName, moduleName, spread), + { insertAtTheEnd } + ); + + tree.write(fileName, content); +} + export function addPluginsToLintConfig( tree: Tree, root: string, @@ -344,7 +451,10 @@ export function addPluginsToLintConfig( ) { const plugins = Array.isArray(plugin) ? plugin : [plugin]; if (useFlatConfig(tree)) { - const fileName = joinPathFragments(root, flatConfigEslintFilename(tree)); + const fileName = joinPathFragments( + root, + getRootESLintFlatConfigFilename(tree) + ); let content = tree.read(fileName, 'utf8'); const mappedPlugins: { name: string; varName: string; imp: string }[] = []; plugins.forEach((name) => { @@ -372,7 +482,10 @@ export function addIgnoresToLintConfig( ignorePatterns: string[] ) { if (useFlatConfig(tree)) { - const fileName = joinPathFragments(root, flatConfigEslintFilename(tree)); + const fileName = joinPathFragments( + root, + getRootESLintFlatConfigFilename(tree) + ); const block = generateAst({ ignores: ignorePatterns.map((path) => mapFilePath(path)), }); diff --git a/packages/eslint/src/generators/utils/flat-config/ast-utils.spec.ts b/packages/eslint/src/generators/utils/flat-config/ast-utils.spec.ts index dc695a4b48fbf..d620a52373eba 100644 --- a/packages/eslint/src/generators/utils/flat-config/ast-utils.spec.ts +++ b/packages/eslint/src/generators/utils/flat-config/ast-utils.spec.ts @@ -1,16 +1,153 @@ import ts = require('typescript'); import { addBlockToFlatConfigExport, - generateAst, + addFlatCompatToFlatConfig, addImportToFlatConfig, - addCompatToFlatConfig, + generateAst, + generateFlatOverride, + generatePluginExtendsElementWithCompatFixup, + removeCompatExtends, + removeImportFromFlatConfig, removeOverridesFromLintConfig, - replaceOverride, removePlugin, - removeCompatExtends, + removePredefinedConfigs, + replaceOverride, } from './ast-utils'; +import { stripIndents } from '@nx/devkit'; describe('ast-utils', () => { + const printer = ts.createPrinter(); + + function printTsNode(node: ts.Node) { + return printer.printNode( + ts.EmitHint.Unspecified, + node, + ts.createSourceFile('test.ts', '', ts.ScriptTarget.Latest) + ); + } + + describe('generateFlatOverride', () => { + it('should create appropriate ASTs for a flat config entries based on the provided legacy eslintrc JSON override data', () => { + // It's easier to review the stringified result of the AST than the AST itself + const getOutput = (input: any) => { + const ast = generateFlatOverride(input); + return printTsNode(ast); + }; + + expect(getOutput({})).toMatchInlineSnapshot(`"{}"`); + + // It should apply rules directly + expect( + getOutput({ + rules: { + a: 'error', + b: 'off', + c: [ + 'error', + { + some: { + rich: ['config', 'options'], + }, + }, + ], + }, + }) + ).toMatchInlineSnapshot(` + "{ + rules: { + a: "error", + b: "off", + c: [ + "error", + { some: { rich: [ + "config", + "options" + ] } } + ] + } + }" + `); + + // It should normalize and apply files as an array + expect( + getOutput({ + files: '*.ts', // old single * syntax should be replaced by **/* + }) + ).toMatchInlineSnapshot(`"{ files: ["**/*.ts"] }"`); + + expect( + getOutput({ + // It should not only nest the parser in languageOptions, but also wrap it in a require call because parsers are passed by reference in flat config + parser: 'jsonc-eslint-parser', + }) + ).toMatchInlineSnapshot(` + "{ + languageOptions: { parser: require("jsonc-eslint-parser") } + }" + `); + + expect( + getOutput({ + // It should nest parserOptions in languageOptions + parserOptions: { + foo: 'bar', + }, + }) + ).toMatchInlineSnapshot(` + "{ + languageOptions: { parserOptions: { foo: "bar" } } + }" + `); + + // It should add the compat tooling for extends, and spread the rules object to allow for easier editing by users + expect(getOutput({ extends: ['plugin:@nx/typescript'] })) + .toMatchInlineSnapshot(` + "...compat.config({ extends: ["plugin:@nx/typescript"] }).map(config => ({ + ...config, + rules: { + ...config.rules + } + }))" + `); + + // It should add the compat tooling for plugins, and spread the rules object to allow for easier editing by users + expect(getOutput({ plugins: ['@nx/eslint-plugin'] })) + .toMatchInlineSnapshot(` + "...compat.config({ plugins: ["@nx/eslint-plugin"] }).map(config => ({ + ...config, + rules: { + ...config.rules + } + }))" + `); + + // It should add the compat tooling for env, and spread the rules object to allow for easier editing by users + expect(getOutput({ env: { jest: true } })).toMatchInlineSnapshot(` + "...compat.config({ env: { jest: true } }).map(config => ({ + ...config, + rules: { + ...config.rules + } + }))" + `); + + // Files for the compat tooling should be added appropriately + expect(getOutput({ env: { jest: true }, files: ['*.ts', '*.tsx'] })) + .toMatchInlineSnapshot(` + "...compat.config({ env: { jest: true } }).map(config => ({ + ...config, + files: [ + "**/*.ts", + "**/*.tsx" + ], + rules: { + ...config.rules + } + }))" + `); + }); + }); + describe('addBlockToFlatConfigExport', () => { it('should inject block to the end of the file', () => { const content = `const baseConfig = require("../../eslint.config.js"); @@ -207,6 +344,32 @@ describe('ast-utils', () => { }); }); + describe('removeImportFromFlatConfig', () => { + it('should remove existing import from config if the var name matches', () => { + const content = stripIndents` + const nx = require("@nx/eslint-plugin"); + const thisShouldRemain = require("@nx/eslint-plugin"); + const playwright = require('eslint-plugin-playwright'); + module.exports = [ + playwright.configs['flat/recommended'], + ]; + `; + const result = removeImportFromFlatConfig( + content, + 'nx', + '@nx/eslint-plugin' + ); + expect(result).toMatchInlineSnapshot(` + " + const thisShouldRemain = require("@nx/eslint-plugin"); + const playwright = require('eslint-plugin-playwright'); + module.exports = [ + playwright.configs['flat/recommended'], + ];" + `); + }); + }); + describe('addCompatToFlatConfig', () => { it('should add compat to config', () => { const content = `const baseConfig = require("../../eslint.config.js"); @@ -221,17 +384,16 @@ describe('ast-utils', () => { }, { ignores: ["my-lib/.cache/**/*"] }, ];`; - const result = addCompatToFlatConfig(content); + const result = addFlatCompatToFlatConfig(content); expect(result).toMatchInlineSnapshot(` "const { FlatCompat } = require("@eslint/eslintrc"); const js = require("@eslint/js"); const baseConfig = require("../../eslint.config.js"); const compat = new FlatCompat({ - baseDirectory: __dirname, - recommendedConfig: js.configs.recommended, - }); - + baseDirectory: __dirname, + recommendedConfig: js.configs.recommended, + }); module.exports = [ ...baseConfig, { @@ -260,17 +422,16 @@ describe('ast-utils', () => { }, { ignores: ["my-lib/.cache/**/*"] }, ];`; - const result = addCompatToFlatConfig(content); + const result = addFlatCompatToFlatConfig(content); expect(result).toMatchInlineSnapshot(` "const { FlatCompat } = require("@eslint/eslintrc"); const baseConfig = require("../../eslint.config.js"); const js = require("@eslint/js"); const compat = new FlatCompat({ - baseDirectory: __dirname, - recommendedConfig: js.configs.recommended, - }); - + baseDirectory: __dirname, + recommendedConfig: js.configs.recommended, + }); module.exports = [ ...baseConfig, { @@ -306,7 +467,7 @@ describe('ast-utils', () => { }, { ignores: ["my-lib/.cache/**/*"] }, ];`; - const result = addCompatToFlatConfig(content); + const result = addFlatCompatToFlatConfig(content); expect(result).toEqual(content); }); }); @@ -833,4 +994,74 @@ describe('ast-utils', () => { `); }); }); + + describe('removePredefinedConfigs', () => { + it('should remove config objects and import', () => { + const content = stripIndents` + const nx = require("@nx/eslint-plugin"); + const playwright = require('eslint-plugin-playwright'); + module.exports = [ + ...nx.config['flat/base'], + ...nx.config['flat/typescript'], + ...nx.config['flat/javascript'], + playwright.configs['flat/recommended'], + ]; + `; + + const result = removePredefinedConfigs( + content, + '@nx/eslint-plugin', + 'nx', + ['flat/base', 'flat/typescript', 'flat/javascript'] + ); + + expect(result).toMatchInlineSnapshot(` + " + const playwright = require('eslint-plugin-playwright'); + module.exports = [ + playwright.configs['flat/recommended'], + ];" + `); + }); + + it('should keep configs that are not in the list', () => { + const content = stripIndents` + const nx = require("@nx/eslint-plugin"); + const playwright = require('eslint-plugin-playwright'); + module.exports = [ + ...nx.config['flat/base'], + ...nx.config['flat/typescript'], + ...nx.config['flat/javascript'], + ...nx.config['flat/react'], + playwright.configs['flat/recommended'], + ]; + `; + + const result = removePredefinedConfigs( + content, + '@nx/eslint-plugin', + 'nx', + ['flat/base', 'flat/typescript', 'flat/javascript'] + ); + + expect(result).toMatchInlineSnapshot(` + "const nx = require("@nx/eslint-plugin"); + const playwright = require('eslint-plugin-playwright'); + module.exports = [ + ...nx.config['flat/react'], + playwright.configs['flat/recommended'], + ];" + `); + }); + }); + + describe('generatePluginExtendsElementWithCompatFixup', () => { + it('should return spread element with fixupConfigRules call wrapping the extended plugin', () => { + const result = generatePluginExtendsElementWithCompatFixup('my-plugin'); + + expect(printTsNode(result)).toMatchInlineSnapshot( + `"...fixupConfigRules(compat.extends("my-plugin"))"` + ); + }); + }); }); diff --git a/packages/eslint/src/generators/utils/flat-config/ast-utils.ts b/packages/eslint/src/generators/utils/flat-config/ast-utils.ts index 5918387782419..9fbe0fd441e56 100644 --- a/packages/eslint/src/generators/utils/flat-config/ast-utils.ts +++ b/packages/eslint/src/generators/utils/flat-config/ast-utils.ts @@ -1,8 +1,8 @@ import { - ChangeType, - StringChange, applyChangesToString, + ChangeType, parseJson, + StringChange, } from '@nx/devkit'; import { Linter } from 'eslint'; import * as ts from 'typescript'; @@ -101,12 +101,7 @@ export function hasOverride( // strip any spread elements objSource = fullNodeText.replace(SPREAD_ELEMENTS_REGEXP, ''); } - const data = parseJson( - objSource - // ensure property names have double quotes so that JSON.parse works - .replace(/'/g, '"') - .replace(/\s([a-zA-Z0-9_]+)\s*:/g, ' "$1": ') - ); + const data = parseTextToJson(objSource); if (lookup(data)) { return true; } @@ -121,6 +116,8 @@ function parseTextToJson(text: string): any { // ensure property names have double quotes so that JSON.parse works .replace(/'/g, '"') .replace(/\s([a-zA-Z0-9_]+)\s*:/g, ' "$1": ') + // stringify any require calls to avoid JSON parsing errors, turn them into just the string value being required + .replace(/require\(['"]([^'"]+)['"]\)/g, '"$1"') ); } @@ -132,8 +129,8 @@ export function replaceOverride( root: string, lookup: (override: Linter.ConfigOverride) => boolean, update?: ( - override: Linter.ConfigOverride - ) => Linter.ConfigOverride + override: Partial> + ) => Partial> ): string { const source = ts.createSourceFile( '', @@ -172,13 +169,18 @@ export function replaceOverride( start, length: end - start, }); - const updatedData = update(data); + let updatedData = update(data); if (updatedData) { - mapFilePaths(updatedData); + updatedData = mapFilePaths(updatedData); changes.push({ type: ChangeType.Insert, index: start, - text: JSON.stringify(updatedData, null, 2).slice(2, -2), // remove curly braces and start/end line breaks since we are injecting just properties + text: JSON.stringify(updatedData, null, 2) + // restore any parser require calls that were stripped during JSON parsing + .replace(/"parser": "([^"]+)"/g, (_, parser) => { + return `"parser": require('${parser}')`; + }) + .slice(2, -2), // remove curly braces and start/end line breaks since we are injecting just properties }); } } @@ -313,6 +315,50 @@ export function addImportToFlatConfig( ]); } +/** + * Remove an import from flat config + */ +export function removeImportFromFlatConfig( + content: string, + variable: string, + imp: string +): string { + const source = ts.createSourceFile( + '', + content, + ts.ScriptTarget.Latest, + true, + ts.ScriptKind.JS + ); + + const changes: StringChange[] = []; + + ts.forEachChild(source, (node) => { + // we can only combine object binding patterns + if ( + ts.isVariableStatement(node) && + ts.isVariableDeclaration(node.declarationList.declarations[0]) && + ts.isIdentifier(node.declarationList.declarations[0].name) && + node.declarationList.declarations[0].name.getText() === variable && + ts.isCallExpression(node.declarationList.declarations[0].initializer) && + node.declarationList.declarations[0].initializer.expression.getText() === + 'require' && + ts.isStringLiteral( + node.declarationList.declarations[0].initializer.arguments[0] + ) && + node.declarationList.declarations[0].initializer.arguments[0].text === imp + ) { + changes.push({ + type: ChangeType.Delete, + start: node.pos, + length: node.end - node.pos, + }); + } + }); + + return applyChangesToString(content, changes); +} + /** * Injects new ts.expression to the end of the module.exports array. */ @@ -342,6 +388,12 @@ export function addBlockToFlatConfigExport( return node.expression.right.elements; } }); + + // The config is not in the format that we generate with, skip update. + // This could happen during `init-migration` when extracting config from the base, but + // base config was not generated by Nx. + if (!exportsArray) return content; + const insert = printer.printNode(ts.EmitHint.Expression, config, source); if (options.insertAtTheEnd) { const index = @@ -520,7 +572,7 @@ export function removeCompatExtends( ts.ScriptKind.JS ); const changes: StringChange[] = []; - findAllBlocks(source).forEach((node) => { + findAllBlocks(source)?.forEach((node) => { if ( ts.isSpreadElement(node) && ts.isCallExpression(node.expression) && @@ -554,7 +606,10 @@ export function removeCompatExtends( text: '\n' + body.replace( - new RegExp('[ \t]s*...' + paramName + '[ \t]*,?\\s*', 'g'), + new RegExp( + '[ \t]s*...' + paramName + '(\\.rules)?[ \t]*,?\\s*', + 'g' + ), '' ), }); @@ -565,6 +620,52 @@ export function removeCompatExtends( return applyChangesToString(content, changes); } +export function removePredefinedConfigs( + content: string, + moduleImport: string, + moduleVariable: string, + configs: string[] +): string { + const source = ts.createSourceFile( + '', + content, + ts.ScriptTarget.Latest, + true, + ts.ScriptKind.JS + ); + const changes: StringChange[] = []; + let removeImport = true; + findAllBlocks(source)?.forEach((node) => { + if ( + ts.isSpreadElement(node) && + ts.isElementAccessExpression(node.expression) && + ts.isPropertyAccessExpression(node.expression.expression) && + ts.isIdentifier(node.expression.expression.expression) && + node.expression.expression.expression.getText() === moduleVariable && + ts.isStringLiteral(node.expression.argumentExpression) + ) { + const config = node.expression.argumentExpression.getText(); + // Check the text without quotes + if (configs.includes(config.substring(1, config.length - 1))) { + changes.push({ + type: ChangeType.Delete, + start: node.pos, + length: node.end - node.pos + 1, // trailing comma + }); + } else { + // If there is still a config used, do not remove import + removeImport = false; + } + } + }); + + let updated = applyChangesToString(content, changes); + if (removeImport) { + updated = removeImportFromFlatConfig(updated, moduleVariable, moduleImport); + } + return updated; +} + /** * Add plugins block to the top of the export blocks */ @@ -596,7 +697,7 @@ export function addPluginsToExportsBlock( /** * Adds compat if missing to flat config */ -export function addCompatToFlatConfig(content: string) { +export function addFlatCompatToFlatConfig(content: string) { let result = content; result = addImportToFlatConfig(result, 'js', '@eslint/js'); if (result.includes('const compat = new FlatCompat')) { @@ -608,42 +709,27 @@ export function addCompatToFlatConfig(content: string) { { type: ChangeType.Insert, index: index - 1, - text: `${DEFAULT_FLAT_CONFIG}\n`, + text: ` +const compat = new FlatCompat({ + baseDirectory: __dirname, + recommendedConfig: js.configs.recommended, +}); +`, }, ]); } -const DEFAULT_FLAT_CONFIG = ` -const compat = new FlatCompat({ - baseDirectory: __dirname, - recommendedConfig: js.configs.recommended, - }); - `; - /** * Generate node list representing the imports and the exports blocks * Optionally add flat compat initialization */ export function createNodeList( importsMap: Map, - exportElements: ts.Expression[], - isFlatCompatNeeded: boolean + exportElements: ts.Expression[] ): ts.NodeArray< ts.VariableStatement | ts.Identifier | ts.ExpressionStatement | ts.SourceFile > { const importsList = []; - if (isFlatCompatNeeded) { - importsMap.set('@eslint/js', 'js'); - - importsList.push( - generateRequire( - ts.factory.createObjectBindingPattern([ - ts.factory.createBindingElement(undefined, undefined, 'FlatCompat'), - ]), - '@eslint/eslintrc' - ) - ); - } // generateRequire(varName, imp, ts.factory); Array.from(importsMap.entries()).forEach(([imp, varName]) => { @@ -655,7 +741,7 @@ export function createNodeList( ...importsList, ts.createSourceFile( '', - isFlatCompatNeeded ? DEFAULT_FLAT_CONFIG : '', + '', ts.ScriptTarget.Latest, false, ts.ScriptKind.JS @@ -694,6 +780,27 @@ export function generatePluginExtendsElement( ); } +export function generatePluginExtendsElementWithCompatFixup( + plugin: string +): ts.SpreadElement { + return ts.factory.createSpreadElement( + ts.factory.createCallExpression( + ts.factory.createIdentifier('fixupConfigRules'), + undefined, + [ + ts.factory.createCallExpression( + ts.factory.createPropertyAccessExpression( + ts.factory.createIdentifier('compat'), + ts.factory.createIdentifier('extends') + ), + undefined, + [ts.factory.createStringLiteral(plugin)] + ), + ] + ) + ); +} + /** * Stringifies TS nodes to file content string */ @@ -754,25 +861,132 @@ export function generateRequire( } /** - * Generates AST object or spread element based on JSON override object + * FROM: https://github.com/eslint/rewrite/blob/e2a7ec809db20e638abbad250d105ddbde88a8d5/packages/migrate-config/src/migrate-config.js#L222 + * + * Converts a glob pattern to a format that can be used in a flat config. + * @param {string} pattern The glob pattern to convert. + * @returns {string} The converted glob pattern. + */ +function convertGlobPattern(pattern: string): string { + const isNegated = pattern.startsWith('!'); + const patternToTest = isNegated ? pattern.slice(1) : pattern; + // if the pattern is already in the correct format, return it + if (patternToTest === '**' || patternToTest.includes('/')) { + return pattern; + } + return `${isNegated ? '!' : ''}**/${patternToTest}`; +} + +// FROM: https://github.com/eslint/rewrite/blob/e2a7ec809db20e638abbad250d105ddbde88a8d5/packages/migrate-config/src/migrate-config.js#L38 +const keysToCopy = ['settings', 'rules', 'processor']; + +export function overrideNeedsCompat( + override: Partial> +) { + return override.env || override.extends || override.plugins; +} + +/** + * Generates an AST object or spread element representing a modern flat config entry, + * based on a given legacy eslintrc JSON override object */ export function generateFlatOverride( - override: Linter.ConfigOverride + _override: Partial> ): ts.ObjectLiteralExpression | ts.SpreadElement { - mapFilePaths(override); - if ( - !override.env && - !override.extends && - !override.plugins && - !override.parser - ) { - if (override.parserOptions) { - const { parserOptions, ...rest } = override; - return generateAst({ ...rest, languageOptions: { parserOptions } }); + const override = mapFilePaths(_override); + + // We do not need the compat tooling for this override + if (!overrideNeedsCompat(override)) { + // Ensure files is an array + let files = override.files; + if (typeof files === 'string') { + files = [files]; + } + + const flatConfigOverride: Linter.FlatConfig = { + files, + }; + + if (override.rules) { + flatConfigOverride.rules = override.rules; + } + + // Copy over everything that stays the same + keysToCopy.forEach((key) => { + if (override[key]) { + flatConfigOverride[key] = override[key]; + } + }); + + if (override.parser || override.parserOptions) { + const languageOptions = {}; + if (override.parser) { + languageOptions['parser'] = override.parser; + } + if (override.parserOptions) { + languageOptions['parserOptions'] = override.parserOptions; + } + if (Object.keys(languageOptions).length) { + flatConfigOverride.languageOptions = languageOptions; + } } - return generateAst(override); + + if (override['languageOptions']) { + flatConfigOverride.languageOptions = override['languageOptions']; + } + + if (override.excludedFiles) { + flatConfigOverride.ignores = ( + Array.isArray(override.excludedFiles) + ? override.excludedFiles + : [override.excludedFiles] + ).map((p) => convertGlobPattern(p)); + } + + return generateAst(flatConfigOverride, { + keyToMatch: /^(parser|rules)$/, + replacer: (propertyAssignment, propertyName) => { + if (propertyName === 'rules') { + // Add comment that user can override rules if there are no overrides. + if ( + ts.isObjectLiteralExpression(propertyAssignment.initializer) && + propertyAssignment.initializer.properties.length === 0 + ) { + return ts.addSyntheticLeadingComment( + ts.factory.createPropertyAssignment( + propertyAssignment.name, + ts.factory.createObjectLiteralExpression([]) + ), + + ts.SyntaxKind.SingleLineCommentTrivia, + ' Override or add rules here' + ); + } + return propertyAssignment; + } else { + // Change parser to require statement. + return ts.factory.createPropertyAssignment( + 'parser', + ts.factory.createCallExpression( + ts.factory.createIdentifier('require'), + undefined, + [ + ts.factory.createStringLiteral( + override['languageOptions']?.['parserOptions']?.parser ?? + override['languageOptions']?.parser ?? + override.parser + ), + ] + ) + ); + } + }, + }); } - const { files, excludedFiles, rules, parserOptions, ...rest } = override; + + // At this point we are applying the flat config compat tooling to the override + const { excludedFiles, parser, parserOptions, rules, files, ...rest } = + override; const objectLiteralElements: ts.ObjectLiteralElementLike[] = [ ts.factory.createSpreadAssignment(ts.factory.createIdentifier('config')), @@ -844,9 +1058,28 @@ export function generateFlatOverride( ); } +export function generateFlatPredefinedConfig( + predefinedConfigName: string, + moduleName = 'nx', + spread = true +): ts.ObjectLiteralExpression | ts.SpreadElement | ts.ElementAccessExpression { + const node = ts.factory.createElementAccessExpression( + ts.factory.createPropertyAccessExpression( + ts.factory.createIdentifier(moduleName), + ts.factory.createIdentifier('configs') + ), + ts.factory.createStringLiteral(predefinedConfigName) + ); + + return spread ? ts.factory.createSpreadElement(node) : node; +} + export function mapFilePaths( - override: Linter.ConfigOverride + _override: Partial> ) { + const override: Partial> = { + ..._override, + }; if (override.files) { override.files = Array.isArray(override.files) ? override.files @@ -861,6 +1094,7 @@ export function mapFilePaths( mapFilePath(file) ); } + return override; } function addTSObjectProperty( @@ -876,10 +1110,21 @@ function addTSObjectProperty( /** * Generates an AST from a JSON-type input */ -export function generateAst(input: unknown): T { +export function generateAst( + input: unknown, + propertyAssignmentReplacer?: { + keyToMatch: RegExp | string; + replacer: ( + propertyAssignment: ts.PropertyAssignment, + propertyName: string + ) => ts.PropertyAssignment; + } +): T { if (Array.isArray(input)) { return ts.factory.createArrayLiteralExpression( - input.map((item) => generateAst(item)), + input.map((item) => + generateAst(item, propertyAssignmentReplacer) + ), input.length > 1 // multiline only if more than one item ) as T; } @@ -888,14 +1133,10 @@ export function generateAst(input: unknown): T { } if (typeof input === 'object') { return ts.factory.createObjectLiteralExpression( - Object.entries(input) - .filter(([_, value]) => value !== undefined) - .map(([key, value]) => - ts.factory.createPropertyAssignment( - isValidKey(key) ? key : ts.factory.createStringLiteral(key), - generateAst(value) - ) - ), + generatePropertyAssignmentsFromObjectEntries( + input, + propertyAssignmentReplacer + ), Object.keys(input).length > 1 // multiline only if more than one property ) as T; } @@ -912,6 +1153,35 @@ export function generateAst(input: unknown): T { throw new Error(`Unknown type: ${typeof input} `); } +function generatePropertyAssignmentsFromObjectEntries( + input: object, + propertyAssignmentReplacer?: { + keyToMatch: RegExp | string; + replacer: ( + propertyAssignment: ts.PropertyAssignment, + propertyName: string + ) => ts.PropertyAssignment; + } +): ts.PropertyAssignment[] { + return Object.entries(input) + .filter(([_, value]) => value !== undefined) + .map(([key, value]) => { + const original = ts.factory.createPropertyAssignment( + isValidKey(key) ? key : ts.factory.createStringLiteral(key), + generateAst(value, propertyAssignmentReplacer) + ); + if ( + propertyAssignmentReplacer && + (typeof propertyAssignmentReplacer.keyToMatch === 'string' + ? key === propertyAssignmentReplacer.keyToMatch + : propertyAssignmentReplacer.keyToMatch.test(key)) + ) { + return propertyAssignmentReplacer.replacer(original, key); + } + return original; + }); +} + function isValidKey(key: string): boolean { return /^[a-zA-Z0-9_]+$/.test(key); } diff --git a/packages/eslint/src/generators/workspace-rules-project/__snapshots__/workspace-rules-project.spec.ts.snap b/packages/eslint/src/generators/workspace-rules-project/__snapshots__/workspace-rules-project.spec.ts.snap index dbc51f509da62..fd165777b0e22 100644 --- a/packages/eslint/src/generators/workspace-rules-project/__snapshots__/workspace-rules-project.spec.ts.snap +++ b/packages/eslint/src/generators/workspace-rules-project/__snapshots__/workspace-rules-project.spec.ts.snap @@ -78,8 +78,7 @@ exports[`@nx/eslint:workspace-rules-project should generate the required files 4 `; exports[`@nx/eslint:workspace-rules-project should generate the required files 5`] = ` -"/* eslint-disable */ -export default { +"export default { displayName: 'eslint-rules', preset: '../../jest.preset.js', transform: { diff --git a/packages/eslint/src/plugins/plugin.ts b/packages/eslint/src/plugins/plugin.ts index 6c61cab70d3fb..1ece1059d07bd 100644 --- a/packages/eslint/src/plugins/plugin.ts +++ b/packages/eslint/src/plugins/plugin.ts @@ -96,7 +96,9 @@ const internalCreateNodes = async ( ).sort((a, b) => (a !== b && isSubDir(a, b) ? -1 : 1)); const excludePatterns = dedupedProjectRoots.map((root) => `${root}/**/*`); - const ESLint = await resolveESLintClass(isFlatConfig(configFilePath)); + const ESLint = await resolveESLintClass({ + useFlatConfigOverrideVal: isFlatConfig(configFilePath), + }); const eslintVersion = ESLint.version; const projects: CreateNodesResult['projects'] = {}; @@ -180,7 +182,9 @@ const internalCreateNodesV2 = async ( ): Promise => { const configDir = dirname(configFilePath); - const ESLint = await resolveESLintClass(isFlatConfig(configFilePath)); + const ESLint = await resolveESLintClass({ + useFlatConfigOverrideVal: isFlatConfig(configFilePath), + }); const eslintVersion = ESLint.version; const projects: CreateNodesResult['projects'] = {}; diff --git a/packages/eslint/src/utils/flat-config.ts b/packages/eslint/src/utils/flat-config.ts index 0e54ec132ef7d..92f3ae48e4276 100644 --- a/packages/eslint/src/utils/flat-config.ts +++ b/packages/eslint/src/utils/flat-config.ts @@ -1,4 +1,5 @@ import { Tree } from '@nx/devkit'; +import { gte } from 'semver'; // todo: add support for eslint.config.mjs, export const eslintFlatConfigFilenames = [ @@ -6,19 +7,42 @@ export const eslintFlatConfigFilenames = [ 'eslint.config.cjs', ]; -export function flatConfigEslintFilename(tree: Tree): string { +export function getRootESLintFlatConfigFilename(tree: Tree): string { for (const file of eslintFlatConfigFilenames) { if (tree.exists(file)) { return file; } } - throw new Error('Could not find flat config file'); + throw new Error('Could not find root flat config file'); } -export function useFlatConfig(tree: Tree): boolean { +export function useFlatConfig(tree?: Tree): boolean { + // Prioritize taking ESLint's own environment variable into account when determining if we should use flat config + // If it is not defined, then default to true. + if (process.env.ESLINT_USE_FLAT_CONFIG === 'true') { + return true; + } else if (process.env.ESLINT_USE_FLAT_CONFIG === 'false') { + return false; + } + + // If we find an existing flat config file in the root of the provided tree, we should use flat config + if (tree) { + const hasRootFlatConfig = eslintFlatConfigFilenames.some((filename) => + tree.exists(filename) + ); + if (hasRootFlatConfig) { + return true; + } + } + + // Otherwise fallback to checking the installed eslint version try { - return !!flatConfigEslintFilename(tree); + const { ESLint } = require('eslint'); + // Default to any v8 version to compare against in this case as it implies a much older version of ESLint was found (and gte() requires a valid version) + const eslintVersion = ESLint.version || '8.0.0'; + return gte(eslintVersion, '9.0.0'); } catch { - return false; + // Default to assuming flat config in case ESLint is not yet installed + return true; } } diff --git a/packages/eslint/src/utils/resolve-eslint-class.ts b/packages/eslint/src/utils/resolve-eslint-class.ts index cb6c2190c4b31..9307d3892bb73 100644 --- a/packages/eslint/src/utils/resolve-eslint-class.ts +++ b/packages/eslint/src/utils/resolve-eslint-class.ts @@ -1,22 +1,26 @@ import type { ESLint } from 'eslint'; +import { useFlatConfig } from '../utils/flat-config'; -export async function resolveESLintClass( - useFlatConfig = false -): Promise { +export async function resolveESLintClass(opts?: { + useFlatConfigOverrideVal: boolean; +}): Promise { try { - // In eslint 8.57.0 (the final v8 version), a dedicated API was added for resolving the correct ESLint class. - const eslint = await import('eslint'); - if (typeof (eslint as any).loadESLint === 'function') { - return await (eslint as any).loadESLint({ useFlatConfig }); - } - // If that API is not available (an older version of v8), we need to use the old way of resolving the ESLint class. - if (!useFlatConfig) { - return eslint.ESLint; - } - // eslint-disable-next-line @typescript-eslint/no-var-requires - const { FlatESLint } = require('eslint/use-at-your-own-risk'); - return FlatESLint; + // Explicitly use the FlatESLint and LegacyESLint classes here because the ESLint class points at a different one based on ESLint v8 vs ESLint v9 + // But the decision on which one to use is not just based on the major version of ESLint. + // @ts-expect-error The may be wrong based on our installed eslint version + const { LegacyESLint, FlatESLint } = await import( + 'eslint/use-at-your-own-risk' + ); + + const shouldESLintUseFlatConfig = + typeof opts?.useFlatConfigOverrideVal === 'boolean' + ? opts.useFlatConfigOverrideVal + : useFlatConfig(); + + return shouldESLintUseFlatConfig ? FlatESLint : LegacyESLint; } catch { - throw new Error('Unable to find ESLint. Ensure ESLint is installed.'); + throw new Error( + 'Unable to find `eslint`. Ensure a valid `eslint` version is installed.' + ); } } diff --git a/packages/eslint/src/utils/version-utils.ts b/packages/eslint/src/utils/version-utils.ts new file mode 100644 index 0000000000000..8d7592e1c4427 --- /dev/null +++ b/packages/eslint/src/utils/version-utils.ts @@ -0,0 +1,32 @@ +import { readJson, readJsonFile, type Tree } from '@nx/devkit'; +import { checkAndCleanWithSemver } from '@nx/devkit/src/utils/semver'; +import { readModulePackageJson } from 'nx/src/devkit-internals'; + +export function getInstalledEslintVersion(tree?: Tree): string | null { + try { + const eslintPackageJson = readModulePackageJson('eslint').packageJson; + return eslintPackageJson.version; + } catch {} + + // eslint is not installed on disk, it could be in the package.json + // but waiting to be installed + const rootPackageJson = tree + ? readJson(tree, 'package.json') + : readJsonFile('package.json'); + const eslintVersionInRootPackageJson = + rootPackageJson.devDependencies?.['eslint'] ?? + rootPackageJson.dependencies?.['eslint']; + + if (!eslintVersionInRootPackageJson) { + // eslint is not installed + return null; + } + + try { + // try to parse and return the version + return checkAndCleanWithSemver('eslint', eslintVersionInRootPackageJson); + } catch {} + + // we could not resolve the version + return null; +} diff --git a/packages/eslint/src/utils/versions.ts b/packages/eslint/src/utils/versions.ts index fe5def1e85ac4..9fcaf70177092 100644 --- a/packages/eslint/src/utils/versions.ts +++ b/packages/eslint/src/utils/versions.ts @@ -4,3 +4,8 @@ export const eslintVersion = '~8.57.0'; export const eslintrcVersion = '^2.1.1'; export const eslintConfigPrettierVersion = '^9.0.0'; export const typescriptESLintVersion = '^7.16.0'; + +// Updated linting stack for ESLint v9, typescript-eslint v8 +export const eslint9__typescriptESLintVersion = '^8.0.0'; +export const eslint9__eslintVersion = '^9.8.0'; +export const eslintCompat = '^1.1.1'; diff --git a/packages/expo/src/executors/install/install.impl.ts b/packages/expo/src/executors/install/install.impl.ts index c180711b7705e..a8f31a7ee22ea 100644 --- a/packages/expo/src/executors/install/install.impl.ts +++ b/packages/expo/src/executors/install/install.impl.ts @@ -35,7 +35,15 @@ export async function installAndUpdatePackageJson( context: ExecutorContext, options: ExpoInstallOptions ) { - await installAsync(context.root, options); + const { installAsync } = require('@expo/cli/build/src/install/installAsync'); + + const packages = + typeof options.packages === 'string' + ? options.packages.split(',') + : options.packages ?? []; + + // Use force in case there are any unmet peer dependencies. + await installAsync(packages, createInstallOptions(options), ['--force']); const projectRoot = context.projectsConfigurations.projects[context.projectName].root; @@ -48,10 +56,6 @@ export async function installAndUpdatePackageJson( const workspacePackageJson = readJsonFile(workspacePackageJsonPath); const projectPackageJson = readJsonFile(projectPackageJsonPath); - const packages = - typeof options.packages === 'string' - ? options.packages.split(',') - : options.packages; displayNewlyAddedDepsMessage( context.projectName, await syncDeps( @@ -65,51 +69,19 @@ export async function installAndUpdatePackageJson( ); } -export function installAsync( - workspaceRoot: string, - options: ExpoInstallOptions -): Promise { - return new Promise((resolve, reject) => { - childProcess = fork( - require.resolve('@expo/cli/build/bin/cli'), - ['install', ...createInstallOptions(options)], - { cwd: workspaceRoot, env: process.env } - ); - - // Ensure the child process is killed when the parent exits - process.on('exit', () => childProcess.kill()); - process.on('SIGTERM', () => childProcess.kill()); - - childProcess.on('error', (err) => { - reject(err); - }); - childProcess.on('exit', (code) => { - if (code === 0) { - resolve(code); - } else { - reject(code); - } - }); - }); -} - // options from https://github.com/expo/expo/blob/main/packages/%40expo/cli/src/install/index.ts function createInstallOptions(options: ExpoInstallOptions) { return Object.keys(options).reduce((acc, k) => { const v = options[k]; - if (k === 'packages') { - const packages = typeof v === 'string' ? v.split(',') : v; - acc.push(...packages); - } else { - if (typeof v === 'boolean') { - if (v === true) { - // when true, does not need to pass the value true, just need to pass the flag in kebob case - acc.push(`--${names(k).fileName}`); - } - } else { - acc.push(`--${names(k).fileName}`, v); + if (typeof v === 'boolean') { + if (v === true) { + // when true, does not need to pass the value true, just need to pass the flag in kebob case + acc.push(`--${names(k).fileName}`); } + } else { + acc.push(`--${names(k).fileName}`, v); } + return acc; }, []); } diff --git a/packages/expo/src/executors/prebuild/prebuild.impl.ts b/packages/expo/src/executors/prebuild/prebuild.impl.ts index 27421fa94b528..6497460f857b3 100644 --- a/packages/expo/src/executors/prebuild/prebuild.impl.ts +++ b/packages/expo/src/executors/prebuild/prebuild.impl.ts @@ -3,7 +3,6 @@ import { ChildProcess, fork } from 'child_process'; import { join } from 'path'; import { podInstall } from '../../utils/pod-install-task'; -import { installAsync } from '../install/install.impl'; import { ExpoPrebuildOptions } from './schema'; export interface ExpoPrebuildOutput { @@ -23,7 +22,10 @@ export default async function* prebuildExecutor( await prebuildAsync(context.root, projectRoot, options); if (options.install) { - await installAsync(workspaceRoot, {}); + const { + installAsync, + } = require('@expo/cli/build/src/install/installAsync'); + await installAsync([], {}); if (options.platform === 'ios') { podInstall(join(context.root, projectRoot, 'ios')); } diff --git a/packages/expo/src/executors/run/run.impl.ts b/packages/expo/src/executors/run/run.impl.ts index 6ac8350714d97..242fd73278f3e 100644 --- a/packages/expo/src/executors/run/run.impl.ts +++ b/packages/expo/src/executors/run/run.impl.ts @@ -7,7 +7,6 @@ import { existsSync } from 'fs-extra'; import { ExpoRunOptions } from './schema'; import { prebuildAsync } from '../prebuild/prebuild.impl'; import { podInstall } from '../../utils/pod-install-task'; -import { installAsync } from '../install/install.impl'; export interface ExpoRunOutput { success: boolean; @@ -34,7 +33,10 @@ export default async function* runExecutor( } if (options.install) { - await installAsync(context.root, {}); + const { + installAsync, + } = require('@expo/cli/build/src/install/installAsync'); + await installAsync([], {}); if (options.platform === 'ios') { podInstall(join(context.root, projectRoot, 'ios')); } diff --git a/packages/expo/src/utils/add-linting.ts b/packages/expo/src/utils/add-linting.ts index e6dc4591899cd..4a79dcf4a578a 100644 --- a/packages/expo/src/utils/add-linting.ts +++ b/packages/expo/src/utils/add-linting.ts @@ -9,8 +9,11 @@ import { extraEslintDependencies } from '@nx/react/src/utils/lint'; import { addExtendsToLintConfig, addIgnoresToLintConfig, + addOverrideToLintConfig, + addPredefinedConfigToFlatLintConfig, isEslintConfigSupported, } from '@nx/eslint/src/generators/utils/eslint-file'; +import { useFlatConfig } from '@nx/eslint/src/utils/flat-config'; interface NormalizedSchema { linter?: Linter | LinterType; @@ -40,7 +43,24 @@ export async function addLinting(host: Tree, options: NormalizedSchema) { tasks.push(lintTask); if (isEslintConfigSupported(host)) { - addExtendsToLintConfig(host, options.projectRoot, 'plugin:@nx/react'); + if (useFlatConfig(host)) { + addPredefinedConfigToFlatLintConfig( + host, + options.projectRoot, + 'flat/react' + ); + // Add an empty rules object to users know how to add/override rules + addOverrideToLintConfig(host, options.projectRoot, { + files: ['*.ts', '*.tsx', '*.js', '*.jsx'], + rules: {}, + }); + } else { + const addExtendsTask = addExtendsToLintConfig(host, options.projectRoot, { + name: 'plugin:@nx/react', + needCompatFixup: true, + }); + tasks.push(addExtendsTask); + } addIgnoresToLintConfig(host, options.projectRoot, [ '.expo', 'web-build', diff --git a/packages/jest/src/generators/configuration/__snapshots__/configuration.spec.ts.snap b/packages/jest/src/generators/configuration/__snapshots__/configuration.spec.ts.snap index d41f58442b460..4074629e8b7b6 100644 --- a/packages/jest/src/generators/configuration/__snapshots__/configuration.spec.ts.snap +++ b/packages/jest/src/generators/configuration/__snapshots__/configuration.spec.ts.snap @@ -1,8 +1,7 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`jestProject --babelJest should generate proper jest.transform when --compiler=swc and supportTsx is true 1`] = ` -"/* eslint-disable */ -export default { +"export default { displayName: 'lib1', preset: '../../jest.preset.js', transform: { @@ -23,8 +22,7 @@ export default { `; exports[`jestProject --babelJest should generate proper jest.transform when babelJest and supportTsx is true 1`] = ` -"/* eslint-disable */ -export default { +"export default { displayName: 'lib1', preset: '../../jest.preset.js', transform: { @@ -37,8 +35,7 @@ export default { `; exports[`jestProject --babelJest should generate proper jest.transform when babelJest is true 1`] = ` -"/* eslint-disable */ -export default { +"export default { displayName: 'lib1', preset: '../../jest.preset.js', transform: { @@ -51,8 +48,7 @@ export default { `; exports[`jestProject --setup-file should have setupFilesAfterEnv and globals.ts-jest in the jest.config when generated for angular 1`] = ` -"/* eslint-disable */ -export default { +"export default { displayName: 'lib1', preset: '../../jest.preset.js', setupFilesAfterEnv: ['/src/test-setup.ts'], @@ -77,8 +73,7 @@ export default { `; exports[`jestProject should create a jest.config.ts 1`] = ` -"/* eslint-disable */ -export default { +"export default { displayName: 'lib1', preset: '../../jest.preset.js', coverageDirectory: '../../coverage/libs/lib1', @@ -87,8 +82,7 @@ export default { `; exports[`jestProject should generate files 2`] = ` -"/* eslint-disable */ -export default { +"export default { displayName: 'lib1', preset: '../../jest.preset.js', setupFilesAfterEnv: ['/src/test-setup.ts'], diff --git a/packages/jest/src/generators/configuration/configuration.spec.ts b/packages/jest/src/generators/configuration/configuration.spec.ts index 36da18e1ae821..333a4d3c7a587 100644 --- a/packages/jest/src/generators/configuration/configuration.spec.ts +++ b/packages/jest/src/generators/configuration/configuration.spec.ts @@ -357,8 +357,7 @@ describe('jestProject', () => { project: 'my-project', }); expect(tree.read('jest.config.ts', 'utf-8')).toMatchInlineSnapshot(` - "/* eslint-disable */ - export default { + "export default { displayName: 'my-project', preset: './jest.preset.js', coverageDirectory: './coverage/my-project', @@ -389,8 +388,7 @@ describe('jestProject', () => { js: true, }); expect(tree.read('jest.config.js', 'utf-8')).toMatchInlineSnapshot(` - "/* eslint-disable */ - module.exports = { + "module.exports = { displayName: 'my-project', preset: './jest.preset.js', coverageDirectory: './coverage/my-project', @@ -424,8 +422,7 @@ describe('jestProject', () => { // ASSERT expect(tree.read('libs/lib1/jest.config.ts', 'utf-8')) .toMatchInlineSnapshot(` - "/* eslint-disable */ - export default { + "export default { displayName: 'lib1', preset: '../../jest.preset.cjs', coverageDirectory: '../../coverage/libs/lib1', @@ -451,8 +448,7 @@ describe('jestProject', () => { expect(tree.exists('jest.preset.cjs')).toBeTruthy(); expect(tree.read('libs/lib1/jest.config.ts', 'utf-8')) .toMatchInlineSnapshot(` - "/* eslint-disable */ - export default { + "export default { displayName: 'lib1', preset: '../../jest.preset.cjs', coverageDirectory: '../../coverage/libs/lib1', diff --git a/packages/jest/src/generators/configuration/files-angular/jest.config.ts__tmpl__ b/packages/jest/src/generators/configuration/files-angular/jest.config.ts__tmpl__ index f3945726987bf..d9e185b331d0e 100644 --- a/packages/jest/src/generators/configuration/files-angular/jest.config.ts__tmpl__ +++ b/packages/jest/src/generators/configuration/files-angular/jest.config.ts__tmpl__ @@ -1,4 +1,3 @@ -/* eslint-disable */ <% if(js){ %>module.exports =<% } else{ %>export default<% } %> { displayName: '<%= project %>', preset: '<%= offsetFromRoot %>jest.preset.<%= presetExt %>', diff --git a/packages/jest/src/generators/configuration/files/jest.config.ts__tmpl__ b/packages/jest/src/generators/configuration/files/jest.config.ts__tmpl__ index af8daaf4cd3f2..1117faf17343c 100644 --- a/packages/jest/src/generators/configuration/files/jest.config.ts__tmpl__ +++ b/packages/jest/src/generators/configuration/files/jest.config.ts__tmpl__ @@ -1,4 +1,3 @@ -/* eslint-disable */ <% if(js){ %>module.exports =<% } else{ %>export default<% } %> { displayName: '<%= project %>', preset: '<%= offsetFromRoot %>jest.preset.<%= presetExt %>',<% if(setupFile !== 'none') { %> diff --git a/packages/js/src/generators/library/library.spec.ts b/packages/js/src/generators/library/library.spec.ts index dc440992a71e8..f99c9b2f8dd8f 100644 --- a/packages/js/src/generators/library/library.spec.ts +++ b/packages/js/src/generators/library/library.spec.ts @@ -537,7 +537,14 @@ describe('lib', () => { ], "parser": "jsonc-eslint-parser", "rules": { - "@nx/dependency-checks": "error", + "@nx/dependency-checks": [ + "error", + { + "ignoredFiles": [ + "{projectRoot}/eslint.config.{js,cjs,mjs}", + ], + }, + ], }, }, ], @@ -594,7 +601,14 @@ describe('lib', () => { ], "parser": "jsonc-eslint-parser", "rules": { - "@nx/dependency-checks": "error", + "@nx/dependency-checks": [ + "error", + { + "ignoredFiles": [ + "{projectRoot}/eslint.config.{js,cjs,mjs}", + ], + }, + ], }, }, ], @@ -719,7 +733,14 @@ describe('lib', () => { ], "parser": "jsonc-eslint-parser", "rules": { - "@nx/dependency-checks": "error", + "@nx/dependency-checks": [ + "error", + { + "ignoredFiles": [ + "{projectRoot}/eslint.config.{js,cjs,mjs}", + ], + }, + ], }, }, ], @@ -745,8 +766,7 @@ describe('lib', () => { expect(tree.exists(`my-lib/jest.config.ts`)).toBeTruthy(); expect(tree.read(`my-lib/jest.config.ts`, 'utf-8')) .toMatchInlineSnapshot(` - "/* eslint-disable */ - export default { + "export default { displayName: 'my-lib', preset: '../jest.preset.js', transform: { @@ -1483,7 +1503,10 @@ describe('lib', () => { '@nx/dependency-checks': [ 'error', { - ignoredFiles: ['{projectRoot}/esbuild.config.{js,ts,mjs,mts}'], + ignoredFiles: [ + '{projectRoot}/eslint.config.{js,cjs,mjs}', + '{projectRoot}/esbuild.config.{js,ts,mjs,mts}', + ], }, ], }, @@ -1508,7 +1531,10 @@ describe('lib', () => { '@nx/dependency-checks': [ 'error', { - ignoredFiles: ['{projectRoot}/rollup.config.{js,ts,mjs,mts}'], + ignoredFiles: [ + '{projectRoot}/eslint.config.{js,cjs,mjs}', + '{projectRoot}/rollup.config.{js,ts,mjs,mts}', + ], }, ], }, diff --git a/packages/js/src/generators/library/library.ts b/packages/js/src/generators/library/library.ts index f7d08fb8b1d32..deae2d32cc07c 100644 --- a/packages/js/src/generators/library/library.ts +++ b/packages/js/src/generators/library/library.ts @@ -347,7 +347,13 @@ export async function addLint( files: ['*.json'], parser: 'jsonc-eslint-parser', rules: { - '@nx/dependency-checks': 'error', + '@nx/dependency-checks': [ + 'error', + { + // With flat configs, we don't want to include imports in the eslint js/cjs/mjs files to be checked + ignoredFiles: ['{projectRoot}/eslint.config.{js,cjs,mjs}'], + }, + ], }, }); } @@ -382,19 +388,22 @@ export async function addLint( ruleOptions = {}; } if (options.bundler === 'vite' || options.unitTestRunner === 'vitest') { - ruleOptions.ignoredFiles = [ - '{projectRoot}/vite.config.{js,ts,mjs,mts}', - ]; + ruleOptions.ignoredFiles ??= []; + ruleOptions.ignoredFiles.push( + '{projectRoot}/vite.config.{js,ts,mjs,mts}' + ); o.rules['@nx/dependency-checks'] = [ruleSeverity, ruleOptions]; } else if (options.bundler === 'rollup') { - ruleOptions.ignoredFiles = [ - '{projectRoot}/rollup.config.{js,ts,mjs,mts}', - ]; + ruleOptions.ignoredFiles ??= []; + ruleOptions.ignoredFiles.push( + '{projectRoot}/rollup.config.{js,ts,mjs,mts}' + ); o.rules['@nx/dependency-checks'] = [ruleSeverity, ruleOptions]; } else if (options.bundler === 'esbuild') { - ruleOptions.ignoredFiles = [ - '{projectRoot}/esbuild.config.{js,ts,mjs,mts}', - ]; + ruleOptions.ignoredFiles ??= []; + ruleOptions.ignoredFiles.push( + '{projectRoot}/esbuild.config.{js,ts,mjs,mts}' + ); o.rules['@nx/dependency-checks'] = [ruleSeverity, ruleOptions]; } return o; diff --git a/packages/nest/src/generators/library/__snapshots__/library.spec.ts.snap b/packages/nest/src/generators/library/__snapshots__/library.spec.ts.snap index c1e313358ffc8..491e8008a3a81 100644 --- a/packages/nest/src/generators/library/__snapshots__/library.spec.ts.snap +++ b/packages/nest/src/generators/library/__snapshots__/library.spec.ts.snap @@ -1,8 +1,7 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`lib --testEnvironment should set target jest testEnvironment to jsdom 1`] = ` -"/* eslint-disable */ -export default { +"export default { displayName: 'my-lib', preset: '../jest.preset.js', transform: { @@ -15,8 +14,7 @@ export default { `; exports[`lib --testEnvironment should set target jest testEnvironment to node by default 1`] = ` -"/* eslint-disable */ -export default { +"export default { displayName: 'my-lib', preset: '../jest.preset.js', testEnvironment: 'node', diff --git a/packages/next/src/generators/application/application.spec.ts b/packages/next/src/generators/application/application.spec.ts index fa54ed0b95d6d..b9027724515bf 100644 --- a/packages/next/src/generators/application/application.spec.ts +++ b/packages/next/src/generators/application/application.spec.ts @@ -601,6 +601,38 @@ describe('app', () => { describe('--linter', () => { describe('default (eslint)', () => { + it('should add flat config as needed', async () => { + tree.write('eslint.config.js', ''); + const name = uniq(); + + await applicationGenerator(tree, { + name, + style: 'css', + projectNameAndRootFormat: 'as-provided', + }); + + expect(tree.read(`${name}/eslint.config.js`, '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 compat = new FlatCompat({ + baseDirectory: __dirname, + recommendedConfig: js.configs.recommended, + }); + + module.exports = [ + ...compat.extends('next', 'next/core-web-vitals'), + ...baseConfig, + ...nx.configs['flat/react-typescript'], + { ignores: ['.next/**/*'] }, + ]; + " + `); + }); + it('should add .eslintrc.json and dependencies', async () => { const name = uniq(); @@ -660,17 +692,6 @@ describe('app', () => { ], "rules": {}, }, - { - "env": { - "jest": true, - }, - "files": [ - "*.spec.ts", - "*.spec.tsx", - "*.spec.js", - "*.spec.jsx", - ], - }, ], } `); diff --git a/packages/next/src/generators/application/lib/add-linting.spec.ts b/packages/next/src/generators/application/lib/add-linting.spec.ts index a955cd54bacbe..7bda2d12d6ad1 100644 --- a/packages/next/src/generators/application/lib/add-linting.spec.ts +++ b/packages/next/src/generators/application/lib/add-linting.spec.ts @@ -90,17 +90,6 @@ describe('updateEslint', () => { ], "rules": {}, }, - { - "env": { - "jest": true, - }, - "files": [ - "*.spec.ts", - "*.spec.tsx", - "*.spec.js", - "*.spec.jsx", - ], - }, ], } `); @@ -115,57 +104,18 @@ describe('updateEslint', () => { .toMatchInlineSnapshot(` "const { FlatCompat } = require("@eslint/eslintrc"); const js = require("@eslint/js"); + const nx = require("@nx/eslint-plugin"); const baseConfig = require("../eslint.config.js"); const compat = new FlatCompat({ - baseDirectory: __dirname, - recommendedConfig: js.configs.recommended, - }); - + baseDirectory: __dirname, + recommendedConfig: js.configs.recommended, + }); module.exports = [ - ...compat.extends("plugin:@nx/react-typescript", "next", "next/core-web-vitals"), + ...compat.extends("next", "next/core-web-vitals"), ...baseConfig, - { - "files": [ - "**/*.ts", - "**/*.tsx", - "**/*.js", - "**/*.jsx" - ], - "rules": { - "@next/next/no-html-link-for-pages": [ - "error", - "my-app/pages" - ] - } - }, - { - files: [ - "**/*.ts", - "**/*.tsx" - ], - rules: {} - }, - { - files: [ - "**/*.js", - "**/*.jsx" - ], - rules: {} - }, - ...compat.config({ env: { jest: true } }).map(config => ({ - ...config, - files: [ - "**/*.spec.ts", - "**/*.spec.tsx", - "**/*.spec.js", - "**/*.spec.jsx" - ], - rules: { - ...config.rules - } - })), + ...nx.configs["flat/react-typescript"], { ignores: [".next/**/*"] } ]; " diff --git a/packages/next/src/generators/application/lib/add-linting.ts b/packages/next/src/generators/application/lib/add-linting.ts index de61e3ffa2ad3..bc131ccb51484 100644 --- a/packages/next/src/generators/application/lib/add-linting.ts +++ b/packages/next/src/generators/application/lib/add-linting.ts @@ -11,11 +11,12 @@ import { NormalizedSchema } from './normalize-options'; import { addExtendsToLintConfig, addIgnoresToLintConfig, - addOverrideToLintConfig, + addPredefinedConfigToFlatLintConfig, isEslintConfigSupported, updateOverrideInLintConfig, } from '@nx/eslint/src/generators/utils/eslint-file'; import { eslintConfigNextVersion } from '../../../utils/versions'; +import { useFlatConfig } from '@nx/eslint/src/utils/flat-config'; export async function addLinting( host: Tree, @@ -39,11 +40,34 @@ export async function addLinting( ); if (options.linter === Linter.EsLint && isEslintConfigSupported(host)) { - addExtendsToLintConfig(host, options.appProjectRoot, [ - 'plugin:@nx/react-typescript', - 'next', - 'next/core-web-vitals', - ]); + if (useFlatConfig(host)) { + addPredefinedConfigToFlatLintConfig( + host, + options.appProjectRoot, + 'flat/react-typescript' + ); + // Since Next.js does not support flat configs yet, we need to use compat fixup. + const addExtendsTask = addExtendsToLintConfig( + host, + options.appProjectRoot, + [ + { name: 'next', needCompatFixup: true }, + { name: 'next/core-web-vitals', needCompatFixup: true }, + ] + ); + tasks.push(addExtendsTask); + } else { + const addExtendsTask = addExtendsToLintConfig( + host, + options.appProjectRoot, + [ + 'plugin:@nx/react-typescript', + { name: 'next', needCompatFixup: true }, + { name: 'next/core-web-vitals', needCompatFixup: true }, + ] + ); + tasks.push(addExtendsTask); + } updateOverrideInLintConfig( host, @@ -65,15 +89,6 @@ export async function addLinting( }, }) ); - // add jest specific config - if (options.unitTestRunner === 'jest') { - addOverrideToLintConfig(host, options.appProjectRoot, { - files: ['*.spec.ts', '*.spec.tsx', '*.spec.js', '*.spec.jsx'], - env: { - jest: true, - }, - }); - } addIgnoresToLintConfig(host, options.appProjectRoot, ['.next/**/*']); } diff --git a/packages/node/src/generators/application/application.spec.ts b/packages/node/src/generators/application/application.spec.ts index 50482703ca2c8..c23c1a336a5f0 100644 --- a/packages/node/src/generators/application/application.spec.ts +++ b/packages/node/src/generators/application/application.spec.ts @@ -433,8 +433,7 @@ describe('app', () => { expect(tree.read(`my-node-app/jest.config.ts`, 'utf-8')) .toMatchInlineSnapshot(` - "/* eslint-disable */ - export default { + "export default { displayName: 'my-node-app', preset: '../jest.preset.js', testEnvironment: 'node', @@ -460,8 +459,7 @@ describe('app', () => { expect(tree.read(`my-node-app/jest.config.ts`, 'utf-8')) .toMatchInlineSnapshot(` - "/* eslint-disable */ - export default { + "export default { displayName: 'my-node-app', preset: '../jest.preset.js', testEnvironment: 'node', diff --git a/packages/node/src/generators/e2e-project/files/cli/jest.config.ts__tmpl__ b/packages/node/src/generators/e2e-project/files/cli/jest.config.ts__tmpl__ index d14e4d6f9eb52..3b0e4fbff3681 100644 --- a/packages/node/src/generators/e2e-project/files/cli/jest.config.ts__tmpl__ +++ b/packages/node/src/generators/e2e-project/files/cli/jest.config.ts__tmpl__ @@ -1,4 +1,3 @@ -/* eslint-disable */ export default { displayName: '<%= e2eProjectName %>', preset: '<%= offsetFromRoot %><%= jestPreset %>', diff --git a/packages/node/src/generators/e2e-project/files/server/common/jest.config.ts__tmpl__ b/packages/node/src/generators/e2e-project/files/server/common/jest.config.ts__tmpl__ index c2e3fc1d6a249..73b4a8b6ca7ad 100644 --- a/packages/node/src/generators/e2e-project/files/server/common/jest.config.ts__tmpl__ +++ b/packages/node/src/generators/e2e-project/files/server/common/jest.config.ts__tmpl__ @@ -1,4 +1,3 @@ -/* eslint-disable */ export default { displayName: '<%= e2eProjectName %>', preset: '<%= offsetFromRoot %><%= jestPreset %>', diff --git a/packages/node/src/generators/library/__snapshots__/library.spec.ts.snap b/packages/node/src/generators/library/__snapshots__/library.spec.ts.snap index 969c956e03d7c..49b724ebc08e1 100644 --- a/packages/node/src/generators/library/__snapshots__/library.spec.ts.snap +++ b/packages/node/src/generators/library/__snapshots__/library.spec.ts.snap @@ -1,8 +1,7 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`lib not nested should update configuration 1`] = ` -"/* eslint-disable */ -export default { +"export default { displayName: 'my-lib', preset: '../jest.preset.js', testEnvironment: 'node', diff --git a/packages/node/src/generators/library/library.spec.ts b/packages/node/src/generators/library/library.spec.ts index 3139ace6d8506..1daa78196af7f 100644 --- a/packages/node/src/generators/library/library.spec.ts +++ b/packages/node/src/generators/library/library.spec.ts @@ -440,8 +440,7 @@ describe('lib', () => { expect(tree.read(`my-lib/jest.config.ts`, 'utf-8')) .toMatchInlineSnapshot(` - "/* eslint-disable */ - export default { + "export default { displayName: 'my-lib', preset: '../jest.preset.js', testEnvironment: 'node', diff --git a/packages/nuxt/.eslintrc.json b/packages/nuxt/.eslintrc.json index bb15d421d50cd..85e1f5edfb9ab 100644 --- a/packages/nuxt/.eslintrc.json +++ b/packages/nuxt/.eslintrc.json @@ -31,6 +31,7 @@ "buildTargets": ["build-base"], "ignoredDependencies": [ "nx", + "eslint", "typescript", "@nx/cypress", "@nx/playwright", diff --git a/packages/nuxt/src/generators/application/__snapshots__/application.spec.ts.snap b/packages/nuxt/src/generators/application/__snapshots__/application.spec.ts.snap index f43a54bfef7ff..798781bfef429 100644 --- a/packages/nuxt/src/generators/application/__snapshots__/application.spec.ts.snap +++ b/packages/nuxt/src/generators/application/__snapshots__/application.spec.ts.snap @@ -18,22 +18,49 @@ 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 1`] = ` +exports[`app generated files content - as-provided - my-app general application should configure eslint correctly (eslintrc) 1`] = ` "{ "extends": ["@nuxt/eslint-config", "../.eslintrc.json"], "ignorePatterns": ["!**/*", ".nuxt/**", ".output/**", "node_modules"], "overrides": [ { "files": ["*.ts", "*.tsx", "*.js", "*.jsx", "*.vue"], - "rules": { - "vue/multi-word-component-names": "off" - } + "rules": {} } ] } " `; +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 compat = new FlatCompat({ + baseDirectory: __dirname, + recommendedConfig: js.configs.recommended, +}); + +module.exports = [ + ...baseConfig, + { + files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx', '**/*.vue'], + // Override or add rules here + rules: {}, + }, + ...compat.extends('@nuxt/eslint-config'), + { + files: ['**/*.vue'], + languageOptions: { + parserOptions: { parser: require('@typescript-eslint/parser') }, + }, + }, + { ignores: ['.nuxt/**', '.output/**', 'node_modules'] }, +]; +" +`; + exports[`app generated files content - as-provided - my-app general application should configure nuxt correctly 1`] = ` "import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin'; import { defineNuxtConfig } from 'nuxt/config'; @@ -358,22 +385,49 @@ 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 1`] = ` +exports[`app generated files content - as-provided - myApp general application should configure eslint correctly (eslintrc) 1`] = ` "{ "extends": ["@nuxt/eslint-config", "../.eslintrc.json"], "ignorePatterns": ["!**/*", ".nuxt/**", ".output/**", "node_modules"], "overrides": [ { "files": ["*.ts", "*.tsx", "*.js", "*.jsx", "*.vue"], - "rules": { - "vue/multi-word-component-names": "off" - } + "rules": {} } ] } " `; +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 compat = new FlatCompat({ + baseDirectory: __dirname, + recommendedConfig: js.configs.recommended, +}); + +module.exports = [ + ...baseConfig, + { + files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx', '**/*.vue'], + // Override or add rules here + rules: {}, + }, + ...compat.extends('@nuxt/eslint-config'), + { + files: ['**/*.vue'], + languageOptions: { + parserOptions: { parser: require('@typescript-eslint/parser') }, + }, + }, + { ignores: ['.nuxt/**', '.output/**', 'node_modules'] }, +]; +" +`; + exports[`app generated files content - as-provided - myApp general application should configure nuxt correctly 1`] = ` "import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin'; import { defineNuxtConfig } from 'nuxt/config'; diff --git a/packages/nuxt/src/generators/application/application.spec.ts b/packages/nuxt/src/generators/application/application.spec.ts index cc7602937ab97..d59e487fe07bb 100644 --- a/packages/nuxt/src/generators/application/application.spec.ts +++ b/packages/nuxt/src/generators/application/application.spec.ts @@ -13,14 +13,15 @@ describe('app', () => { describe('general application', () => { beforeEach(async () => { tree = createTreeWithEmptyWorkspace(); + }); + + it('should not add targets', async () => { await applicationGenerator(tree, { name, projectNameAndRootFormat: 'as-provided', unitTestRunner: 'vitest', }); - }); - it('should not add targets', async () => { const projectConfig = readProjectConfiguration(tree, name); expect(projectConfig.targets.build).toBeUndefined(); expect(projectConfig.targets.serve).toBeUndefined(); @@ -30,27 +31,71 @@ describe('app', () => { }); it('should create all new files in the correct location', async () => { + await applicationGenerator(tree, { + name, + projectNameAndRootFormat: 'as-provided', + unitTestRunner: 'vitest', + }); + const newFiles = tree.listChanges().map((change) => change.path); expect(newFiles).toMatchSnapshot(); }); - it('should add nuxt entries in .gitignore', () => { + it('should add nuxt entries in .gitignore', async () => { + await applicationGenerator(tree, { + name, + projectNameAndRootFormat: 'as-provided', + unitTestRunner: 'vitest', + }); + expect(tree.read('.gitignore', 'utf-8')).toMatchSnapshot(); }); - it('should configure nuxt correctly', () => { + it('should configure nuxt correctly', async () => { + await applicationGenerator(tree, { + name, + projectNameAndRootFormat: 'as-provided', + unitTestRunner: 'vitest', + }); + expect( tree.read(`${name}/nuxt.config.ts`, 'utf-8') ).toMatchSnapshot(); }); - it('should configure eslint correctly', () => { + it('should configure eslint correctly (flat config)', async () => { + tree.write('eslint.config.js', ''); + + await applicationGenerator(tree, { + name, + projectNameAndRootFormat: 'as-provided', + unitTestRunner: 'vitest', + }); + + expect( + tree.read(`${name}/eslint.config.js`, 'utf-8') + ).toMatchSnapshot(); + }); + + it('should configure eslint correctly (eslintrc)', async () => { + await applicationGenerator(tree, { + name, + projectNameAndRootFormat: 'as-provided', + unitTestRunner: 'vitest', + }); + expect( tree.read(`${name}/.eslintrc.json`, 'utf-8') ).toMatchSnapshot(); }); - it('should configure vitest correctly', () => { + it('should configure vitest correctly', async () => { + await applicationGenerator(tree, { + name, + projectNameAndRootFormat: 'as-provided', + unitTestRunner: 'vitest', + }); + expect( tree.read(`${name}/vitest.config.ts`, 'utf-8') ).toMatchSnapshot(); @@ -62,12 +107,24 @@ describe('app', () => { expect(packageJson.devDependencies['vitest']).toEqual('^1.3.1'); }); - it('should configure tsconfig and project.json correctly', () => { + it('should configure tsconfig and project.json correctly', async () => { + await applicationGenerator(tree, { + name, + projectNameAndRootFormat: 'as-provided', + unitTestRunner: 'vitest', + }); + expect(tree.read(`${name}/project.json`, 'utf-8')).toMatchSnapshot(); expect(tree.read(`${name}/tsconfig.json`, 'utf-8')).toMatchSnapshot(); }); - it('should add the nuxt and vitest plugins', () => { + it('should add the nuxt and vitest plugins', async () => { + await applicationGenerator(tree, { + name, + projectNameAndRootFormat: 'as-provided', + unitTestRunner: 'vitest', + }); + const nxJson = readJson(tree, 'nx.json'); expect(nxJson.plugins).toMatchObject([ { diff --git a/packages/nuxt/src/utils/add-linting.ts b/packages/nuxt/src/utils/add-linting.ts index a0acb020ef77e..988ac5fd1e8d1 100644 --- a/packages/nuxt/src/utils/add-linting.ts +++ b/packages/nuxt/src/utils/add-linting.ts @@ -1,15 +1,25 @@ import { Tree } from 'nx/src/generators/tree'; -import { lintProjectGenerator, Linter, LinterType } from '@nx/eslint'; +import type { Linter as EsLintLinter } from 'eslint'; +import { Linter, LinterType, lintProjectGenerator } from '@nx/eslint'; import { joinPathFragments } from 'nx/src/utils/path'; import { - GeneratorCallback, addDependenciesToPackageJson, + GeneratorCallback, runTasksInSerial, - updateJson, } from '@nx/devkit'; -import { editEslintConfigFiles } from '@nx/vue'; +import { + addExtendsToLintConfig, + addIgnoresToLintConfig, + addOverrideToLintConfig, + isEslintConfigSupported, + lintConfigHasOverride, + replaceOverridesInLintConfig, + updateOverrideInLintConfig, +} from '@nx/eslint/src/generators/utils/eslint-file'; import { nuxtEslintConfigVersion } from './versions'; +import { useFlatConfig } from '@nx/eslint/src/utils/flat-config'; +// TODO(colum): Look into the recommended set up using `withNuxt` inside eslint.config.mjs. https://eslint.nuxt.com/packages/config export async function addLinting( host: Tree, options: { @@ -33,30 +43,36 @@ export async function addLinting( }); tasks.push(lintTask); - editEslintConfigFiles(host, options.projectRoot); + if (isEslintConfigSupported(host, options.projectRoot)) { + editEslintConfigFiles(host, options.projectRoot); - updateJson( - host, - joinPathFragments(options.projectRoot, '.eslintrc.json'), - (json) => { - const { - extends: pluginExtends, - ignorePatterns: pluginIgnorePatters, - ...config - } = json; + const addExtendsTask = addExtendsToLintConfig( + host, + options.projectRoot, + ['@nuxt/eslint-config'], + true + ); + tasks.push(addExtendsTask); - return { - extends: ['@nuxt/eslint-config', ...(pluginExtends || [])], - ignorePatterns: [ - ...(pluginIgnorePatters || []), - '.nuxt/**', - '.output/**', - 'node_modules', - ], - ...config, - }; + if (useFlatConfig(host)) { + addOverrideToLintConfig( + host, + options.projectRoot, + { + files: ['**/*.vue'], + languageOptions: { + parserOptions: { parser: '@typescript-eslint/parser' }, + }, + } as unknown // languageOptions is not in eslintrc format but for flat config + ); } - ); + + addIgnoresToLintConfig(host, options.projectRoot, [ + '.nuxt/**', + '.output/**', + 'node_modules', + ]); + } const installTask = addDependenciesToPackageJson( host, @@ -69,3 +85,68 @@ export async function addLinting( } return runTasksInSerial(...tasks); } + +function editEslintConfigFiles(tree: Tree, projectRoot: string) { + const hasVueFiles = ( + o: EsLintLinter.ConfigOverride + ) => + o.files && + (Array.isArray(o.files) + ? o.files.some((f) => f.endsWith('*.vue')) + : o.files.endsWith('*.vue')); + const addVueFiles = ( + o: EsLintLinter.ConfigOverride + ) => { + if (!o.files) { + o.files = ['*.vue']; + } else if (Array.isArray(o.files)) { + o.files.push('*.vue'); + } else { + o.files = [o.files, '*.vue']; + } + }; + if ( + lintConfigHasOverride( + tree, + projectRoot, + (o) => o.parserOptions && !hasVueFiles(o), + true + ) + ) { + updateOverrideInLintConfig( + tree, + projectRoot, + (o) => !!o.parserOptions, + (o) => { + addVueFiles(o); + return o; + } + ); + } else { + replaceOverridesInLintConfig(tree, projectRoot, [ + { + files: ['*.ts', '*.tsx', '*.js', '*.jsx', '*.vue'], + rules: {}, + }, + ]); + } + + if ( + lintConfigHasOverride( + tree, + '', + (o) => o.rules?.['@nx/enforce-module-boundaries'] && !hasVueFiles(o), + true + ) + ) { + updateOverrideInLintConfig( + tree, + '', + (o) => !!o.rules?.['@nx/enforce-module-boundaries'], + (o) => { + addVueFiles(o); + return o; + } + ); + } +} diff --git a/packages/nuxt/src/utils/versions.ts b/packages/nuxt/src/utils/versions.ts index 1142ef0d39a37..6244e7e77df2e 100644 --- a/packages/nuxt/src/utils/versions.ts +++ b/packages/nuxt/src/utils/versions.ts @@ -7,4 +7,4 @@ export const nuxtDevtoolsVersion = '1.0.0'; export const nuxtUiTemplatesVersion = '^1.3.1'; // linting deps -export const nuxtEslintConfigVersion = '~0.3.6'; +export const nuxtEslintConfigVersion = '~0.5.6'; diff --git a/packages/playwright/src/utils/add-linter.ts b/packages/playwright/src/utils/add-linter.ts index 1c8d3e8f98110..ebf60c480089d 100644 --- a/packages/playwright/src/utils/add-linter.ts +++ b/packages/playwright/src/utils/add-linter.ts @@ -13,9 +13,11 @@ import { addExtendsToLintConfig, addOverrideToLintConfig, addPluginsToLintConfig, + addPredefinedConfigToFlatLintConfig, findEslintFile, isEslintConfigSupported, } from '@nx/eslint/src/generators/utils/eslint-file'; +import { useFlatConfig } from '@nx/eslint/src/utils/flat-config'; export interface PlaywrightLinterOptions { project: string; @@ -76,24 +78,42 @@ export async function addLinterToPlaywrightProject( isEslintConfigSupported(tree, projectConfig.root) || isEslintConfigSupported(tree) ) { - addExtendsToLintConfig( - tree, - projectConfig.root, - 'plugin:playwright/recommended' - ); - if (options.rootProject) { - addPluginsToLintConfig(tree, projectConfig.root, '@nx'); - addOverrideToLintConfig(tree, projectConfig.root, javaScriptOverride); + if (useFlatConfig(tree)) { + addPredefinedConfigToFlatLintConfig( + tree, + projectConfig.root, + 'flat/recommended', + 'playwright', + 'eslint-plugin-playwright', + false, + false + ); + addOverrideToLintConfig(tree, projectConfig.root, { + files: ['*.ts', '*.js'], + rules: {}, + }); + } else { + const addExtendsTask = addExtendsToLintConfig( + tree, + projectConfig.root, + 'plugin:playwright/recommended' + ); + tasks.push(addExtendsTask); + + if (options.rootProject) { + addPluginsToLintConfig(tree, projectConfig.root, '@nx'); + addOverrideToLintConfig(tree, projectConfig.root, javaScriptOverride); + } + addOverrideToLintConfig(tree, projectConfig.root, { + files: [`${options.directory}/**/*.{ts,js,tsx,jsx}`], + parserOptions: !options.setParserOptionsProject + ? undefined + : { + project: `${projectConfig.root}/tsconfig.*?.json`, + }, + rules: {}, + }); } - addOverrideToLintConfig(tree, projectConfig.root, { - files: [`${options.directory}/**/*.{ts,js,tsx,jsx}`], - parserOptions: !options.setParserOptionsProject - ? undefined - : { - project: `${projectConfig.root}/tsconfig.*?.json`, - }, - rules: {}, - }); } return runTasksInSerial(...tasks); diff --git a/packages/playwright/src/utils/versions.ts b/packages/playwright/src/utils/versions.ts index 875a771d9a6c3..c1ac5ca6faba3 100644 --- a/packages/playwright/src/utils/versions.ts +++ b/packages/playwright/src/utils/versions.ts @@ -1,3 +1,3 @@ export const nxVersion = require('../../package.json').version; export const playwrightVersion = '^1.36.0'; -export const eslintPluginPlaywrightVersion = '^0.15.3'; +export const eslintPluginPlaywrightVersion = '^1.6.2'; diff --git a/packages/plugin/src/generators/lint-checks/generator.spec.ts b/packages/plugin/src/generators/lint-checks/generator.spec.ts index 21e04270c0886..4df69d48b556f 100644 --- a/packages/plugin/src/generators/lint-checks/generator.spec.ts +++ b/packages/plugin/src/generators/lint-checks/generator.spec.ts @@ -156,7 +156,14 @@ describe('lint-checks generator', () => { ], "parser": "jsonc-eslint-parser", "rules": { - "@nx/dependency-checks": "error", + "@nx/dependency-checks": [ + "error", + { + "ignoredFiles": [ + "{projectRoot}/eslint.config.{js,cjs,mjs}", + ], + }, + ], }, }, { diff --git a/packages/plugin/src/generators/lint-checks/generator.ts b/packages/plugin/src/generators/lint-checks/generator.ts index da1f5e36cdef7..6f81cf3b941e7 100644 --- a/packages/plugin/src/generators/lint-checks/generator.ts +++ b/packages/plugin/src/generators/lint-checks/generator.ts @@ -8,6 +8,7 @@ import { readProjectConfiguration, TargetConfiguration, Tree, + updateJson, updateProjectConfiguration, writeJson, } from '@nx/devkit'; @@ -113,12 +114,23 @@ export function addMigrationJsonChecks( fileSet.add(relativeMigrationsJsonPath); return { ...o, - files: Array.from(fileSet), + files: formatFilesEntries(host, Array.from(fileSet)), }; } ); } +function formatFilesEntries(tree: Tree, files: string[]): string[] { + if (!useFlatConfig(tree)) { + return files; + } + const filesAfter = files.map((f) => { + const after = f.startsWith('./') ? f.replace('./', '**/') : f; + return after; + }); + return filesAfter; +} + function updateProjectTarget( host: Tree, options: PluginLintChecksGeneratorSchema, @@ -199,12 +211,12 @@ function updateProjectEslintConfig( // update it updateOverrideInLintConfig(host, options.root, lookup, (o) => ({ ...o, - files: [ + files: formatFilesEntries(host, [ ...new Set([ ...(Array.isArray(o.files) ? o.files : [o.files]), ...files, ]), - ], + ]), ...parser, rules: { ...o.rules, @@ -214,7 +226,7 @@ function updateProjectEslintConfig( } else { // add it addOverrideToLintConfig(host, options.root, { - files, + files: formatFilesEntries(host, files), ...parser, rules: { '@nx/nx-plugin-checks': 'error', diff --git a/packages/react-native/src/utils/add-linting.ts b/packages/react-native/src/utils/add-linting.ts index dbee0f2831149..0b340d04f0210 100644 --- a/packages/react-native/src/utils/add-linting.ts +++ b/packages/react-native/src/utils/add-linting.ts @@ -9,8 +9,11 @@ import { extraEslintDependencies } from '@nx/react/src/utils/lint'; import { addExtendsToLintConfig, addIgnoresToLintConfig, + addOverrideToLintConfig, + addPredefinedConfigToFlatLintConfig, isEslintConfigSupported, } from '@nx/eslint/src/generators/utils/eslint-file'; +import { useFlatConfig } from '@nx/eslint/src/utils/flat-config'; interface NormalizedSchema { linter?: Linter | LinterType; @@ -40,7 +43,24 @@ export async function addLinting(host: Tree, options: NormalizedSchema) { tasks.push(lintTask); if (isEslintConfigSupported(host)) { - addExtendsToLintConfig(host, options.projectRoot, 'plugin:@nx/react'); + if (useFlatConfig(host)) { + addPredefinedConfigToFlatLintConfig( + host, + options.projectRoot, + 'flat/react' + ); + // Add an empty rules object to users know how to add/override rules + addOverrideToLintConfig(host, options.projectRoot, { + files: ['*.ts', '*.tsx', '*.js', '*.jsx'], + rules: {}, + }); + } else { + const addExtendsTask = addExtendsToLintConfig(host, options.projectRoot, { + name: 'plugin:@nx/react', + needCompatFixup: true, + }); + tasks.push(addExtendsTask); + } addIgnoresToLintConfig(host, options.projectRoot, [ 'public', '.cache', diff --git a/packages/react/src/generators/application/application.ts b/packages/react/src/generators/application/application.ts index 7fadc80a1f2cb..574bf1867b4b0 100644 --- a/packages/react/src/generators/application/application.ts +++ b/packages/react/src/generators/application/application.ts @@ -38,11 +38,14 @@ import { showPossibleWarnings } from './lib/show-possible-warnings'; import { addE2e } from './lib/add-e2e'; import { addExtendsToLintConfig, + addOverrideToLintConfig, + addPredefinedConfigToFlatLintConfig, isEslintConfigSupported, } from '@nx/eslint/src/generators/utils/eslint-file'; import { initGenerator as jsInitGenerator } from '@nx/js'; import { logShowProjectCommand } from '@nx/devkit/src/utils/log-show-project-command'; import { setupTailwindGenerator } from '../setup-tailwind/setup-tailwind'; +import { useFlatConfig } from '@nx/eslint/src/utils/flat-config'; async function addLinting(host: Tree, options: NormalizedSchema) { const tasks: GeneratorCallback[] = []; @@ -62,7 +65,25 @@ async function addLinting(host: Tree, options: NormalizedSchema) { tasks.push(lintTask); if (isEslintConfigSupported(host)) { - addExtendsToLintConfig(host, options.appProjectRoot, 'plugin:@nx/react'); + if (useFlatConfig(host)) { + addPredefinedConfigToFlatLintConfig( + host, + options.appProjectRoot, + 'flat/react' + ); + // Add an empty rules object to users know how to add/override rules + addOverrideToLintConfig(host, options.appProjectRoot, { + files: ['*.ts', '*.tsx', '*.js', '*.jsx'], + rules: {}, + }); + } else { + const addExtendsTask = addExtendsToLintConfig( + host, + options.appProjectRoot, + { name: 'plugin:@nx/react', needCompatFixup: true } + ); + tasks.push(addExtendsTask); + } } if (!options.skipPackageJson) { diff --git a/packages/react/src/generators/library/lib/add-linting.ts b/packages/react/src/generators/library/lib/add-linting.ts index 80876774e7899..7a0c990012899 100644 --- a/packages/react/src/generators/library/lib/add-linting.ts +++ b/packages/react/src/generators/library/lib/add-linting.ts @@ -1,17 +1,25 @@ import { Tree } from 'nx/src/generators/tree'; import { Linter, lintProjectGenerator } from '@nx/eslint'; import { joinPathFragments } from 'nx/src/utils/path'; -import { addDependenciesToPackageJson, runTasksInSerial } from '@nx/devkit'; +import { + addDependenciesToPackageJson, + GeneratorCallback, + runTasksInSerial, +} from '@nx/devkit'; import { NormalizedSchema } from '../schema'; import { extraEslintDependencies } from '../../../utils/lint'; import { addExtendsToLintConfig, + addOverrideToLintConfig, + addPredefinedConfigToFlatLintConfig, isEslintConfigSupported, } from '@nx/eslint/src/generators/utils/eslint-file'; +import { useFlatConfig } from '@nx/eslint/src/utils/flat-config'; export async function addLinting(host: Tree, options: NormalizedSchema) { if (options.linter === Linter.EsLint) { + const tasks: GeneratorCallback[] = []; const lintTask = await lintProjectGenerator(host, { linter: options.linter, project: options.name, @@ -24,9 +32,31 @@ export async function addLinting(host: Tree, options: NormalizedSchema) { setParserOptionsProject: options.setParserOptionsProject, addPlugin: options.addPlugin, }); + tasks.push(lintTask); if (isEslintConfigSupported(host)) { - addExtendsToLintConfig(host, options.projectRoot, 'plugin:@nx/react'); + if (useFlatConfig(host)) { + addPredefinedConfigToFlatLintConfig( + host, + options.projectRoot, + 'flat/react' + ); + // Add an empty rules object to users know how to add/override rules + addOverrideToLintConfig(host, options.projectRoot, { + files: ['*.ts', '*.tsx', '*.js', '*.jsx'], + rules: {}, + }); + } else { + const addExtendsTask = addExtendsToLintConfig( + host, + options.projectRoot, + { + name: 'plugin:@nx/react', + needCompatFixup: true, + } + ); + tasks.push(addExtendsTask); + } } let installTask = () => {}; @@ -36,9 +66,10 @@ export async function addLinting(host: Tree, options: NormalizedSchema) { extraEslintDependencies.dependencies, extraEslintDependencies.devDependencies ); + tasks.push(installTask); } - return runTasksInSerial(lintTask, installTask); + return runTasksInSerial(...tasks); } else { return () => {}; } diff --git a/packages/react/src/utils/versions.ts b/packages/react/src/utils/versions.ts index 5f5865ed3e65e..fc28a3ee4e8d6 100755 --- a/packages/react/src/utils/versions.ts +++ b/packages/react/src/utils/versions.ts @@ -34,11 +34,10 @@ export const testingLibraryReactVersion = '15.0.6'; export const reduxjsToolkitVersion = '1.9.3'; export const reactReduxVersion = '8.0.5'; -export const reactTestRendererVersion = '18.2.0'; -export const eslintPluginImportVersion = '2.27.5'; +export const eslintPluginImportVersion = '2.30.0'; export const eslintPluginJsxA11yVersion = '6.7.1'; -export const eslintPluginReactVersion = '7.32.2'; +export const eslintPluginReactVersion = '7.35.0'; export const eslintPluginReactHooksVersion = '4.6.0'; export const babelPluginStyledComponentsVersion = '1.10.7'; diff --git a/packages/remix/src/generators/application/__snapshots__/application.impl.spec.ts.snap b/packages/remix/src/generators/application/__snapshots__/application.impl.spec.ts.snap index af292f8f31f39..ed7c14a1f210f 100644 --- a/packages/remix/src/generators/application/__snapshots__/application.impl.spec.ts.snap +++ b/packages/remix/src/generators/application/__snapshots__/application.impl.spec.ts.snap @@ -329,8 +329,7 @@ export default { `; exports[`Remix Application Integrated Repo --projectNameAndRootFormat=as-provided --unitTestRunner should generate the correct files for testing using jest 2`] = ` -"/* eslint-disable */ -export default { +"export default { displayName: 'test', preset: '../jest.preset.js', transform: { @@ -846,8 +845,7 @@ export default { `; exports[`Remix Application Integrated Repo --projectNameAndRootFormat=derived --unitTestRunner should generate the correct files for testing using jest 2`] = ` -"/* eslint-disable */ -export default { +"export default { displayName: 'test', preset: '../../jest.preset.js', transform: { @@ -1146,8 +1144,7 @@ export default { `; exports[`Remix Application Standalone Project Repo --unitTestRunner should generate the correct files for testing using jest 2`] = ` -"/* eslint-disable */ -export default { +"export default { setupFilesAfterEnv: ['/test-setup.ts'], displayName: 'test', preset: './jest.preset.cjs', @@ -1403,7 +1400,7 @@ exports[`Remix Application Standalone Project Repo should create the application exports[`Remix Application Standalone Project Repo should create the application correctly 6`] = ` "{ "root": true, - "ignorePatterns": ["!**/*"], + "ignorePatterns": ["!**/*", "build", "public/build"], "plugins": ["@nx"], "overrides": [ { diff --git a/packages/remix/src/generators/application/application.impl.ts b/packages/remix/src/generators/application/application.impl.ts index 73e572a736cba..da5aba5cede10 100644 --- a/packages/remix/src/generators/application/application.impl.ts +++ b/packages/remix/src/generators/application/application.impl.ts @@ -10,13 +10,22 @@ import { readJson, readProjectConfiguration, runTasksInSerial, - stripIndents, toJS, Tree, updateJson, updateProjectConfiguration, + visitNotIgnoredFiles, } from '@nx/devkit'; +import { addBuildTargetDefaults } from '@nx/devkit/src/generators/target-defaults-utils'; +import { logShowProjectCommand } from '@nx/devkit/src/utils/log-show-project-command'; +import { initGenerator as jsInitGenerator } from '@nx/js'; import { extractTsConfigBase } from '@nx/js/src/utils/typescript/create-ts-config'; +import { dirname } from 'node:path'; +import { + createNxCloudOnboardingURLForWelcomeApp, + getNxCloudAppOnBoardingUrl, +} from 'nx/src/nx-cloud/utilities/onboarding'; +import { updateJestTestMatch } from '../../utils/testing-config-utils'; import { eslintVersion, getPackageVersion, @@ -28,18 +37,10 @@ import { typesReactDomVersion, typesReactVersion, } from '../../utils/versions'; -import { normalizeOptions, updateUnitTestConfig, addE2E } from './lib'; -import { NxRemixGeneratorSchema } from './schema'; -import { updateDependencies } from '../utils/update-dependencies'; import initGenerator from '../init/init'; -import { initGenerator as jsInitGenerator } from '@nx/js'; -import { addBuildTargetDefaults } from '@nx/devkit/src/generators/target-defaults-utils'; -import { logShowProjectCommand } from '@nx/devkit/src/utils/log-show-project-command'; -import { updateJestTestMatch } from '../../utils/testing-config-utils'; -import { - getNxCloudAppOnBoardingUrl, - createNxCloudOnboardingURLForWelcomeApp, -} from 'nx/src/nx-cloud/utilities/onboarding'; +import { updateDependencies } from '../utils/update-dependencies'; +import { addE2E, normalizeOptions, updateUnitTestConfig } from './lib'; +import { NxRemixGeneratorSchema } from './schema'; export function remixApplicationGenerator( tree: Tree, @@ -241,6 +242,9 @@ export async function remixApplicationGeneratorInternal( '@nx/eslint', getPackageVersion(tree, 'nx') ); + const { addIgnoresToLintConfig } = await import( + '@nx/eslint/src/generators/utils/eslint-file' + ); const eslintTask = await lintProjectGenerator(tree, { linter: options.linter, project: options.projectName, @@ -254,11 +258,10 @@ export async function remixApplicationGeneratorInternal( }); tasks.push(eslintTask); - tree.write( - joinPathFragments(options.projectRoot, '.eslintignore'), - stripIndents`build - public/build` - ); + addIgnoresToLintConfig(tree, options.projectRoot, [ + 'build', + 'public/build', + ]); } if (options.js) { @@ -319,6 +322,52 @@ 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')); + } + }); + } + } + if (!options.skipFormat) { await formatFiles(tree); } diff --git a/packages/remix/src/generators/library/__snapshots__/library.impl.spec.ts.snap b/packages/remix/src/generators/library/__snapshots__/library.impl.spec.ts.snap index 1d37995033edf..ffa0172a876f6 100644 --- a/packages/remix/src/generators/library/__snapshots__/library.impl.spec.ts.snap +++ b/packages/remix/src/generators/library/__snapshots__/library.impl.spec.ts.snap @@ -1,8 +1,7 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Remix Library Generator -projectNameAndRootFormat=as-provided --unitTestRunner should create the correct config files for testing with jest 1`] = ` -"/* eslint-disable */ -export default { +"export default { setupFilesAfterEnv: ['./src/test-setup.ts'], displayName: 'test', preset: '../jest.preset.js', @@ -80,8 +79,7 @@ exports[`Remix Library Generator -projectNameAndRootFormat=as-provided should ge `; exports[`Remix Library Generator -projectNameAndRootFormat=derived --unitTestRunner should create the correct config files for testing with jest 1`] = ` -"/* eslint-disable */ -export default { +"export default { setupFilesAfterEnv: ['./src/test-setup.ts'], displayName: 'test', preset: '../../jest.preset.js', diff --git a/packages/vite/src/generators/configuration/__snapshots__/configuration.spec.ts.snap b/packages/vite/src/generators/configuration/__snapshots__/configuration.spec.ts.snap index f2f78d6012442..9ae629ee39318 100644 --- a/packages/vite/src/generators/configuration/__snapshots__/configuration.spec.ts.snap +++ b/packages/vite/src/generators/configuration/__snapshots__/configuration.spec.ts.snap @@ -150,8 +150,7 @@ exports[`@nx/vite:configuration js library with --bundler=vite should respect un `; exports[`@nx/vite:configuration js library with --bundler=vite should respect unitTestRunner if passed 5`] = ` -"/* eslint-disable */ -export default { +"export default { displayName: 'my-lib', preset: '../jest.preset.js', transform: { diff --git a/packages/vite/src/generators/configuration/configuration.spec.ts b/packages/vite/src/generators/configuration/configuration.spec.ts index 1aeb955449882..0599c4d997ff2 100644 --- a/packages/vite/src/generators/configuration/configuration.spec.ts +++ b/packages/vite/src/generators/configuration/configuration.spec.ts @@ -290,7 +290,10 @@ describe('@nx/vite:configuration', () => { '@nx/dependency-checks': [ 'error', { - ignoredFiles: ['{projectRoot}/vite.config.{js,ts,mjs,mts}'], + ignoredFiles: [ + '{projectRoot}/eslint.config.{js,cjs,mjs}', + '{projectRoot}/vite.config.{js,ts,mjs,mts}', + ], }, ], }, diff --git a/packages/vue/src/generators/library/__snapshots__/library.spec.ts.snap b/packages/vue/src/generators/library/__snapshots__/library.spec.ts.snap index ec9f374d0546a..a1939cf565e9e 100644 --- a/packages/vue/src/generators/library/__snapshots__/library.spec.ts.snap +++ b/packages/vue/src/generators/library/__snapshots__/library.spec.ts.snap @@ -280,23 +280,18 @@ exports[`lib should ignore test files in tsconfig.lib.json 1`] = ` `; exports[`lib should support eslint flat config 1`] = ` -"const { FlatCompat } = require('@eslint/eslintrc'); -const js = require('@eslint/js'); +"const vue = require('eslint-plugin-vue'); const baseConfig = require('../eslint.config.js'); -const compat = new FlatCompat({ - baseDirectory: __dirname, - recommendedConfig: js.configs.recommended, -}); - module.exports = [ - ...compat.extends( - 'plugin:vue/vue3-essential', - 'eslint:recommended', - '@vue/eslint-config-typescript', - '@vue/eslint-config-prettier/skip-formatting' - ), ...baseConfig, + ...vue.configs['flat/recommended'], + { + files: ['**/*.vue'], + languageOptions: { + parserOptions: { parser: require('@typescript-eslint/parser') }, + }, + }, { files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx', '**/*.vue'], rules: { 'vue/multi-word-component-names': 'off' }, diff --git a/packages/vue/src/utils/add-linting.ts b/packages/vue/src/utils/add-linting.ts index bc95547513892..7b3eb3a9edcc3 100644 --- a/packages/vue/src/utils/add-linting.ts +++ b/packages/vue/src/utils/add-linting.ts @@ -1,16 +1,23 @@ import { Tree } from 'nx/src/generators/tree'; import { Linter, LinterType, lintProjectGenerator } from '@nx/eslint'; import { joinPathFragments } from 'nx/src/utils/path'; -import { addDependenciesToPackageJson, runTasksInSerial } from '@nx/devkit'; +import { + addDependenciesToPackageJson, + GeneratorCallback, + runTasksInSerial, +} from '@nx/devkit'; import { extraEslintDependencies } from './lint'; import { addExtendsToLintConfig, + addOverrideToLintConfig, + addPredefinedConfigToFlatLintConfig, isEslintConfigSupported, lintConfigHasOverride, replaceOverridesInLintConfig, updateOverrideInLintConfig, } from '@nx/eslint/src/generators/utils/eslint-file'; import type { Linter as EsLintLinter } from 'eslint'; +import { useFlatConfig } from '@nx/eslint/src/utils/flat-config'; export async function addLinting( host: Tree, @@ -27,6 +34,7 @@ export async function addLinting( projectType: 'lib' | 'app' ) { if (options.linter === Linter.EsLint) { + const tasks: GeneratorCallback[] = []; const lintTask = await lintProjectGenerator(host, { linter: options.linter, project: options.name, @@ -39,31 +47,41 @@ export async function addLinting( rootProject: options.rootProject, addPlugin: options.addPlugin, }); + tasks.push(lintTask); + + if (useFlatConfig(host)) { + } else { + const addExtendsTask = addExtendsToLintConfig( + host, + options.projectRoot, + [ + 'plugin:vue/vue3-essential', + 'eslint:recommended', + '@vue/eslint-config-typescript', + '@vue/eslint-config-prettier/skip-formatting', + ].filter(Boolean) + ); + tasks.push(addExtendsTask); + } - addExtendsToLintConfig(host, options.projectRoot, [ - 'plugin:vue/vue3-essential', - 'eslint:recommended', - '@vue/eslint-config-typescript', - '@vue/eslint-config-prettier/skip-formatting', - ]); editEslintConfigFiles(host, options.projectRoot); - let installTask = () => {}; if (!options.skipPackageJson) { - installTask = addDependenciesToPackageJson( + const installTask = addDependenciesToPackageJson( host, extraEslintDependencies.dependencies, extraEslintDependencies.devDependencies ); + tasks.push(installTask); } - return runTasksInSerial(lintTask, installTask); + return runTasksInSerial(...tasks); } else { return () => {}; } } -export function editEslintConfigFiles(tree: Tree, projectRoot: string) { +function editEslintConfigFiles(tree: Tree, projectRoot: string) { const hasVueFiles = ( o: EsLintLinter.ConfigOverride ) => @@ -84,30 +102,58 @@ export function editEslintConfigFiles(tree: Tree, projectRoot: string) { }; if (isEslintConfigSupported(tree, projectRoot)) { - if ( - lintConfigHasOverride( + if (useFlatConfig(tree)) { + addPredefinedConfigToFlatLintConfig( tree, projectRoot, - (o) => o.parserOptions && !hasVueFiles(o), - true - ) - ) { - updateOverrideInLintConfig( + 'flat/recommended', + 'vue', + 'eslint-plugin-vue' + ); + // This allows .vue files to be parsed + addOverrideToLintConfig( tree, projectRoot, - (o) => !!o.parserOptions, - (o) => { - addVueFiles(o); - return o; - } + { + files: ['**/*.vue'], + languageOptions: { + parserOptions: { + parser: '@typescript-eslint/parser', + }, + }, + } as unknown // languageOptions is not present on eslintrc override, but it is for flat config ); + // Add an empty rules object to users know how to add/override rules + addOverrideToLintConfig(tree, projectRoot, { + files: ['*.ts', '*.tsx', '*.js', '*.jsx', '*.vue'], + rules: { 'vue/multi-word-component-names': 'off' }, + }); } else { - replaceOverridesInLintConfig(tree, projectRoot, [ - { - files: ['*.ts', '*.tsx', '*.js', '*.jsx', '*.vue'], - rules: { 'vue/multi-word-component-names': 'off' }, - }, - ]); + if ( + lintConfigHasOverride( + tree, + projectRoot, + (o) => o.parserOptions && !hasVueFiles(o), + true + ) + ) { + updateOverrideInLintConfig( + tree, + projectRoot, + (o) => !!o.parserOptions, + (o) => { + addVueFiles(o); + return o; + } + ); + } else { + replaceOverridesInLintConfig(tree, projectRoot, [ + { + files: ['*.ts', '*.tsx', '*.js', '*.jsx', '*.vue'], + rules: { 'vue/multi-word-component-names': 'off' }, + }, + ]); + } } } diff --git a/packages/web/src/generators/application/application.spec.ts b/packages/web/src/generators/application/application.spec.ts index 050c75e42740c..00da5f9ccfd01 100644 --- a/packages/web/src/generators/application/application.spec.ts +++ b/packages/web/src/generators/application/application.spec.ts @@ -621,8 +621,7 @@ describe('app', () => { expect(tree.read(`my-app/jest.config.ts`, 'utf-8')) .toMatchInlineSnapshot(` - "/* eslint-disable */ - export default { + "export default { displayName: 'my-app', preset: '../jest.preset.js', setupFilesAfterEnv: ['/src/test-setup.ts'], @@ -649,8 +648,7 @@ describe('app', () => { expect(tree.read(`my-app/jest.config.ts`, 'utf-8')) .toMatchInlineSnapshot(` - "/* eslint-disable */ - export default { + "export default { displayName: 'my-app', preset: '../jest.preset.js', setupFilesAfterEnv: ['/src/test-setup.ts'], diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cb638601f93ab..acdee6b5883d2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -185,14 +185,14 @@ importers: specifier: ~18.2.0 version: 18.2.1(chokidar@3.6.0) '@angular-eslint/eslint-plugin': - specifier: ^18.0.1 - version: 18.0.1(@typescript-eslint/utils@7.16.0(eslint@8.57.0)(typescript@5.5.3))(eslint@8.57.0)(typescript@5.5.3) + specifier: ^18.3.0 + version: 18.3.0(@typescript-eslint/utils@8.3.0(eslint@8.57.0)(typescript@5.5.3))(eslint@8.57.0)(typescript@5.5.3) '@angular-eslint/eslint-plugin-template': - specifier: ^18.0.1 - version: 18.0.1(@typescript-eslint/utils@7.16.0(eslint@8.57.0)(typescript@5.5.3))(eslint@8.57.0)(typescript@5.5.3) + specifier: ^18.3.0 + version: 18.3.0(@typescript-eslint/utils@8.3.0(eslint@8.57.0)(typescript@5.5.3))(eslint@8.57.0)(typescript@5.5.3) '@angular-eslint/template-parser': - specifier: ^18.0.1 - version: 18.0.1(eslint@8.57.0)(typescript@5.5.3) + specifier: ^18.3.0 + version: 18.3.0(eslint@8.57.0)(typescript@5.5.3) '@angular/cli': specifier: ~18.2.0 version: 18.2.1(chokidar@3.6.0) @@ -229,6 +229,9 @@ importers: '@babel/runtime': specifier: ^7.22.6 version: 7.22.6 + '@eslint/compat': + specifier: ^1.1.1 + version: 1.1.1 '@eslint/eslintrc': specifier: ^2.1.1 version: 2.1.1 @@ -312,7 +315,7 @@ importers: version: 19.7.0-beta.6(@babel/traverse@7.25.4)(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.7)(typescript@5.5.3))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@18.19.8)(@zkochan/js-yaml@0.0.7)(eslint@8.57.0)(nx@19.7.0-beta.6(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.7)(typescript@5.5.3))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(verdaccio@5.31.0(encoding@0.1.13)(typanion@3.14.0)) '@nx/eslint-plugin': specifier: 19.7.0-beta.6 - version: 19.7.0-beta.6(@babel/traverse@7.25.4)(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.7)(typescript@5.5.3))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@18.19.8)(@typescript-eslint/parser@7.16.0(eslint@8.57.0)(typescript@5.5.3))(eslint-config-prettier@9.1.0(eslint@8.57.0))(eslint@8.57.0)(nx@19.7.0-beta.6(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.7)(typescript@5.5.3))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(typescript@5.5.3)(verdaccio@5.31.0(encoding@0.1.13)(typanion@3.14.0)) + version: 19.7.0-beta.6(@babel/traverse@7.25.4)(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.7)(typescript@5.5.3))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@18.19.8)(@typescript-eslint/parser@8.3.0(eslint@8.57.0)(typescript@5.5.3))(eslint-config-prettier@9.1.0(eslint@8.57.0))(eslint@8.57.0)(nx@19.7.0-beta.6(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.7)(typescript@5.5.3))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(typescript@5.5.3)(verdaccio@5.31.0(encoding@0.1.13)(typanion@3.14.0)) '@nx/jest': specifier: 19.7.0-beta.6 version: 19.7.0-beta.6(@babel/traverse@7.25.4)(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.7)(typescript@5.5.3))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@18.19.8)(nx@19.7.0-beta.6(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.7)(typescript@5.5.3))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(ts-node@10.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@18.19.8)(typescript@5.5.3))(typescript@5.5.3)(verdaccio@5.31.0(encoding@0.1.13)(typanion@3.14.0)) @@ -442,6 +445,9 @@ importers: '@types/eslint': specifier: ~8.56.10 version: 8.56.10 + '@types/eslint__js': + specifier: ^8.42.3 + version: 8.42.3 '@types/express': specifier: 4.17.14 version: 4.17.14 @@ -496,18 +502,15 @@ importers: '@types/yarnpkg__lockfile': specifier: ^1.1.5 version: 1.1.5 - '@typescript-eslint/eslint-plugin': - specifier: 7.16.0 - version: 7.16.0(@typescript-eslint/parser@7.16.0(eslint@8.57.0)(typescript@5.5.3))(eslint@8.57.0)(typescript@5.5.3) - '@typescript-eslint/parser': - specifier: 7.16.0 - version: 7.16.0(eslint@8.57.0)(typescript@5.5.3) + '@typescript-eslint/rule-tester': + specifier: ^8.0.0 + version: 8.3.0(eslint@8.57.0)(typescript@5.5.3) '@typescript-eslint/type-utils': - specifier: ^7.16.0 - version: 7.16.0(eslint@8.57.0)(typescript@5.5.3) + specifier: ^8.0.0 + version: 8.3.0(eslint@8.57.0)(typescript@5.5.3) '@typescript-eslint/utils': - specifier: 7.16.0 - version: 7.16.0(eslint@8.57.0)(typescript@5.5.3) + specifier: ^8.0.0 + version: 8.3.0(eslint@8.57.0)(typescript@5.5.3) '@xstate/immer': specifier: 0.3.1 version: 0.3.1(immer@9.0.16)(xstate@4.34.0) @@ -526,6 +529,9 @@ importers: ajv: specifier: ^8.12.0 version: 8.12.0 + angular-eslint: + specifier: ^18.3.0 + version: 18.3.0(@angular-devkit/core@18.2.1(chokidar@3.6.0))(@angular-devkit/schematics@18.2.1(chokidar@3.6.0))(@typescript-eslint/utils@8.3.0(eslint@8.57.0)(typescript@5.5.3))(eslint@8.57.0)(typescript-eslint@8.3.0(eslint@8.57.0)(typescript@5.5.3))(typescript@5.5.3) autoprefixer: specifier: 10.4.13 version: 10.4.13(postcss@8.4.38) @@ -620,17 +626,17 @@ importers: specifier: 2.14.0 version: 2.14.0(eslint@8.57.0) eslint-plugin-import: - specifier: 2.27.5 - version: 2.27.5(@typescript-eslint/parser@7.16.0(eslint@8.57.0)(typescript@5.5.3))(eslint@8.57.0) + specifier: 2.30.0 + version: 2.30.0(@typescript-eslint/parser@8.3.0(eslint@8.57.0)(typescript@5.5.3))(eslint@8.57.0) eslint-plugin-jsx-a11y: specifier: 6.7.1 version: 6.7.1(eslint@8.57.0) eslint-plugin-playwright: - specifier: ^0.15.3 - version: 0.15.3(eslint@8.57.0) + specifier: ^1.6.2 + version: 1.6.2(eslint@8.57.0) eslint-plugin-react: - specifier: 7.32.2 - version: 7.32.2(eslint@8.57.0) + specifier: 7.35.0 + version: 7.35.0(eslint@8.57.0) eslint-plugin-react-hooks: specifier: 4.6.0 version: 4.6.0(eslint@8.57.0) @@ -664,6 +670,9 @@ importers: github-slugger: specifier: ^2.0.0 version: 2.0.0 + globals: + specifier: ^15.9.0 + version: 15.9.0 gpt3-tokenizer: specifier: ^1.1.5 version: 1.1.5 @@ -952,6 +961,9 @@ importers: typescript: specifier: ~5.5.2 version: 5.5.3 + typescript-eslint: + specifier: ^8.0.0 + version: 8.3.0(eslint@8.57.0)(typescript@5.5.3) unist-builder: specifier: ^4.0.0 version: 4.0.0 @@ -1196,33 +1208,45 @@ packages: resolution: {integrity: sha512-2t/q0Jcv7yqhAzEdNgsxoGSCmPgD4qfnVOJ7EJw3LNIA+kX1CmtN4FESUS0i49kN4AyNJFAI5O2pV8iJiliKaw==} engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} - '@angular-eslint/bundled-angular-compiler@18.0.1': - resolution: {integrity: sha512-lr4Ysoo28FBOKcJFQUGTMpbWDcak+gyuYvyggp37ERvazE6EDomPFxzEHNqVT9EI9sZ+GDBOoPR+EdFh0ALGNw==} + '@angular-eslint/builder@18.3.0': + resolution: {integrity: sha512-httEQyqyBw3+0CRtAa7muFxHrauRfkEfk/jmrh5fn2Eiu+I53hAqFPgrwVi1V6AP/kj2zbAiWhd5xM3pMJdoRQ==} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '*' - '@angular-eslint/eslint-plugin-template@18.0.1': - resolution: {integrity: sha512-u/eov/CFBb8l35D8dW78Dx5fBLd8FZFibKN9XQknhzXnDMpISuUOMny5g5/wvYYjqLgqEySXMiHKEAxEup7xtA==} + '@angular-eslint/bundled-angular-compiler@18.3.0': + resolution: {integrity: sha512-v/59FxUKnMzymVce99gV43huxoqXWMb85aKvzlNvLN+ScDu6ZE4YMiTQNpfapVL2lkxhs0uwB3jH17EYd5TcsA==} + + '@angular-eslint/eslint-plugin-template@18.3.0': + resolution: {integrity: sha512-ddR/qwYbUeq9IpyVKrPbfZyRBTy6V8uc5I0JcBKttQ4CZ4joXhqsVgWFsI+JAMi8E66uNj1VC7NuKCOjDINv2Q==} peerDependencies: - '@typescript-eslint/utils': ^7.11.0 || ^8.0.0-alpha.20 + '@typescript-eslint/utils': ^7.11.0 || ^8.0.0 eslint: ^8.57.0 || ^9.0.0 typescript: '*' - '@angular-eslint/eslint-plugin@18.0.1': - resolution: {integrity: sha512-pS3SYLa9DA+ENklGxEUlcw6/xCxgDk9fgjyaheuSjDxL3TIh1pTa4V2TptODdcPh7XCYXiVmy+e/w79mXlGzOw==} + '@angular-eslint/eslint-plugin@18.3.0': + resolution: {integrity: sha512-Vl7gfPMXxvtHTjYdlzR161aj5xrqW6T57wd8ToQ7Gqzm0qHGfY6kE4SQobUa2LCYckTNSlv+zXe48C4ah/dSjw==} peerDependencies: - '@typescript-eslint/utils': ^7.11.0 || ^8.0.0-alpha.20 + '@typescript-eslint/utils': ^7.11.0 || ^8.0.0 eslint: ^8.57.0 || ^9.0.0 typescript: '*' - '@angular-eslint/template-parser@18.0.1': - resolution: {integrity: sha512-22fKzkWo9Ts8aY/WHL1A6seS2tpltgRRXVfnZnnqvQRyRiuPnx1FC0ly7+QPZkThh8vdLwxU+BvtLq9Uiqh9OQ==} + '@angular-eslint/schematics@18.3.0': + resolution: {integrity: sha512-rQ4DEWwf3f5n096GAK6JvXD0SRzRJ52WRaIyKg8MMkk6qvUDfZI8seOkcbjDtZoIe6Ds7DfqSfJgNVte75qvPQ==} + peerDependencies: + '@angular-devkit/core': '>= 18.0.0 < 19.0.0' + '@angular-devkit/schematics': '>= 18.0.0 < 19.0.0' + + '@angular-eslint/template-parser@18.3.0': + resolution: {integrity: sha512-1mUquqcnugI4qsoxcYZKZ6WMi6RPelDcJZg2YqGyuaIuhWmi3ZqJZLErSSpjP60+TbYZu7wM8Kchqa1bwJtEaQ==} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '*' - '@angular-eslint/utils@18.0.1': - resolution: {integrity: sha512-Q9lCySqg+9h2cz08+SoWj48cY1i04tL1k3bsQJmF2TsylAw2mSsNGX2X3h9WkdxY7sUoY0mP7MVW1iU54Gobcg==} + '@angular-eslint/utils@18.3.0': + resolution: {integrity: sha512-sCrkHkpxBJZLuCikdboZoawCfc2UgbJv+T14tu2uQCv+Vwzeadnu04vkeY2vTkA8GeBdBij/G9/N/nvwmwVw3g==} peerDependencies: - '@typescript-eslint/utils': ^7.11.0 || ^8.0.0-alpha.20 + '@typescript-eslint/utils': ^7.11.0 || ^8.0.0 eslint: ^8.57.0 || ^9.0.0 typescript: '*' @@ -4496,6 +4520,10 @@ packages: resolution: {integrity: sha512-pPTNuaAG3QMH+buKyBIGJs3g/S5y0caxw0ygM3YyE6yJFySwiGGSzA+mM3KJ8QQvzeLh3blwgSonkFjgQdxzMw==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + '@eslint/compat@1.1.1': + resolution: {integrity: sha512-lpHyRyplhGPL5mGEh6M9O5nnKk0Gz4bFI+Zu6tKlPpDUN7XshWvH9C/px4UVm87IAANE0W81CEsNGbS1KlzXpA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/eslintrc@2.1.1': resolution: {integrity: sha512-9t7ZA7NGGK8ckelF0PQCfcxIUzs1Md5rrO6U/c+FIQNanea5UZC0wqKXH4vHBccmu4ZJgZ2idtPeW7+Q2npOEA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -6778,6 +6806,9 @@ packages: engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true + '@rtsao/scc@1.1.0': + resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==} + '@rushstack/eslint-patch@1.7.2': resolution: {integrity: sha512-RbhOOTCNoCrbfkRyoXODZp75MlpiHMgbE5MEBZAnnnLyQNgrigEj4p0lzsMDyc1zVsJDLrivB58tgg3emX0eEA==} @@ -7481,6 +7512,9 @@ packages: '@types/eslint@8.56.10': resolution: {integrity: sha512-Shavhk87gCtY2fhXDctcfS3e6FdxWkCx1iUZ9eEUbh7rTqlZT0/IzOkCOVt0fCjcFuZ9FPYfuezTBImfHCDBGQ==} + '@types/eslint__js@8.42.3': + resolution: {integrity: sha512-alfG737uhmPdnvkrLdZLcEKJ/B8s9Y4hrZ+YAdzUeoArBlSUERA2E87ROfOaS4jd/C45fzOoZzidLc1IPwLqOw==} + '@types/estree-jsx@1.0.3': resolution: {integrity: sha512-pvQ+TKeRHeiUGRhvYwRrQ/ISnohKkSJR14fT2yqyZ4e9K5vqc7hrtY2Y1Dw0ZwAzQ6DQsxsaCUuSIIi8v0Cq6w==} @@ -7787,12 +7821,12 @@ packages: '@types/yauzl@2.10.3': resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} - '@typescript-eslint/eslint-plugin@7.16.0': - resolution: {integrity: sha512-py1miT6iQpJcs1BiJjm54AMzeuMPBSPuKPlnT8HlfudbcS5rYeX5jajpLf3mrdRh9dA/Ec2FVUY0ifeVNDIhZw==} - engines: {node: ^18.18.0 || >=20.0.0} + '@typescript-eslint/eslint-plugin@8.3.0': + resolution: {integrity: sha512-FLAIn63G5KH+adZosDYiutqkOkYEx0nvcwNNfJAf+c7Ae/H35qWwTYvPZUKFj5AS+WfHG/WJJfWnDnyNUlp8UA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^7.0.0 - eslint: ^8.56.0 + '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0 + eslint: ^8.57.0 || ^9.0.0 typescript: '*' peerDependenciesMeta: typescript: @@ -7808,16 +7842,22 @@ packages: typescript: optional: true - '@typescript-eslint/parser@7.16.0': - resolution: {integrity: sha512-ar9E+k7CU8rWi2e5ErzQiC93KKEFAXA2Kky0scAlPcxYblLt8+XZuHUZwlyfXILyQa95P6lQg+eZgh/dDs3+Vw==} - engines: {node: ^18.18.0 || >=20.0.0} + '@typescript-eslint/parser@8.3.0': + resolution: {integrity: sha512-h53RhVyLu6AtpUzVCYLPhZGL5jzTD9fZL+SYf/+hYOx2bDkyQXztXSc4tbvKYHzfMXExMLiL9CWqJmVz6+78IQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: ^8.56.0 + eslint: ^8.57.0 || ^9.0.0 typescript: '*' peerDependenciesMeta: typescript: optional: true + '@typescript-eslint/rule-tester@8.3.0': + resolution: {integrity: sha512-ITX1PUjIUZcj0sVpReC41YLNd+BfSEfcWRI4siYAAbjUdTRT5FpT54Uir6ezqS3RGKd5T8D5Yz3I3G80COa56w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + '@typescript-eslint/scope-manager@5.62.0': resolution: {integrity: sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -7830,6 +7870,10 @@ packages: resolution: {integrity: sha512-8gVv3kW6n01Q6TrI1cmTZ9YMFi3ucDT7i7aI5lEikk2ebk1AEjrwX8MDTdaX5D7fPXMBLvnsaa0IFTAu+jcfOw==} engines: {node: ^18.18.0 || >=20.0.0} + '@typescript-eslint/scope-manager@8.3.0': + resolution: {integrity: sha512-mz2X8WcN2nVu5Hodku+IR8GgCOl4C0G/Z1ruaWN4dgec64kDBabuXyPAr+/RgJtumv8EEkqIzf3X2U5DUKB2eg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/type-utils@7.16.0': resolution: {integrity: sha512-j0fuUswUjDHfqV/UdW6mLtOQQseORqfdmoBNDFOqs9rvNVR2e+cmu6zJu/Ku4SDuqiJko6YnhwcL8x45r8Oqxg==} engines: {node: ^18.18.0 || >=20.0.0} @@ -7840,6 +7884,15 @@ packages: typescript: optional: true + '@typescript-eslint/type-utils@8.3.0': + resolution: {integrity: sha512-wrV6qh//nLbfXZQoj32EXKmwHf4b7L+xXLrP3FZ0GOUU72gSvLjeWUl5J5Ue5IwRxIV1TfF73j/eaBapxx99Lg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + '@typescript-eslint/types@5.62.0': resolution: {integrity: sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -7852,6 +7905,10 @@ packages: resolution: {integrity: sha512-fecuH15Y+TzlUutvUl9Cc2XJxqdLr7+93SQIbcZfd4XRGGKoxyljK27b+kxKamjRkU7FYC6RrbSCg0ALcZn/xw==} engines: {node: ^18.18.0 || >=20.0.0} + '@typescript-eslint/types@8.3.0': + resolution: {integrity: sha512-y6sSEeK+facMaAyixM36dQ5NVXTnKWunfD1Ft4xraYqxP0lC0POJmIaL/mw72CUMqjY9qfyVfXafMeaUj0noWw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/typescript-estree@5.62.0': resolution: {integrity: sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -7879,6 +7936,15 @@ packages: typescript: optional: true + '@typescript-eslint/typescript-estree@8.3.0': + resolution: {integrity: sha512-Mq7FTHl0R36EmWlCJWojIC1qn/ZWo2YiWYc1XVtasJ7FIgjo0MVv9rZWXEE7IK2CGrtwe1dVOxWwqXUdNgfRCA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + '@typescript-eslint/utils@5.62.0': resolution: {integrity: sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -7891,6 +7957,12 @@ packages: peerDependencies: eslint: ^8.56.0 + '@typescript-eslint/utils@8.3.0': + resolution: {integrity: sha512-F77WwqxIi/qGkIGOGXNBLV7nykwfjLsdauRB/DOFPdv6LTF3BHHkBpq81/b5iMPSF055oO2BiivDJV4ChvNtXA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + '@typescript-eslint/visitor-keys@5.62.0': resolution: {integrity: sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -7903,6 +7975,10 @@ packages: resolution: {integrity: sha512-rMo01uPy9C7XxG7AFsxa8zLnWXTF8N3PYclekWSrurvhwiw1eW88mrKiAYe6s53AUY57nTRz8dJsuuXdkAhzCg==} engines: {node: ^18.18.0 || >=20.0.0} + '@typescript-eslint/visitor-keys@8.3.0': + resolution: {integrity: sha512-RmZwrTbQ9QveF15m/Cl28n0LXD6ea2CjkhH5rQ55ewz3H24w+AMCJHPVYaZ8/0HoG8Z3cLLFFycRXxeO2tz9FA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@ungap/structured-clone@1.2.0': resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} @@ -8452,6 +8528,13 @@ packages: algoliasearch@4.14.2: resolution: {integrity: sha512-ngbEQonGEmf8dyEh5f+uOIihv4176dgbuOZspiuhmTTBRBuzWu3KCGHre6uHj5YyuC7pNvQGzB6ZNJyZi0z+Sg==} + angular-eslint@18.3.0: + resolution: {integrity: sha512-neBE3BUtxj1EPPNVww3i/e8DKh/gb+fT/WpDEsRZM//8vS+qb0pMC04dn4bqeUriM05Nq/oUESdwkLuyadJE9A==} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '*' + typescript-eslint: ^8.0.0 + ansi-colors@4.1.3: resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} engines: {node: '>=6'} @@ -8550,8 +8633,9 @@ packages: aria-query@5.3.0: resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} - array-buffer-byte-length@1.0.0: - resolution: {integrity: sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==} + array-buffer-byte-length@1.0.1: + resolution: {integrity: sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==} + engines: {node: '>= 0.4'} array-differ@3.0.0: resolution: {integrity: sha512-THtfYS6KtME/yIAhKjZ2ul7XI96lQGHRputJQHO80LAWQnuGP4iCIN8vdMRboGbIEYBwU33q8Tch1os2+X0kMg==} @@ -8571,8 +8655,8 @@ packages: resolution: {integrity: sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==} engines: {node: '>= 0.4'} - array-includes@3.1.7: - resolution: {integrity: sha512-dlcsNBIiWhPkHdOEEKnehA+RNUWDc4UqFtnIXU4uuYDPtA4LDkr7qip2p0VvFAEXNDr0yWZ9PJyIRiGjRLQzwQ==} + array-includes@3.1.8: + resolution: {integrity: sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==} engines: {node: '>= 0.4'} array-keyed-map@2.1.3: @@ -8589,12 +8673,12 @@ packages: resolution: {integrity: sha512-1OvF9IbWwaeiM9VhzYXVQacMibxpXOMYVNIvMtKRyX9SImBXpKcFr8XvFDeEslCyuH/t6KRt7HEO94AlP8Iatw==} engines: {node: '>=12'} - array.prototype.findlastindex@1.2.3: - resolution: {integrity: sha512-LzLoiOMAxvy+Gd3BAq3B7VeIgPdo+Q8hthvKtXybMvRV0jrXfJM/t8mw7nNlpEcVlVUnCnM2KSX4XU5HmpodOA==} + array.prototype.findlast@1.2.5: + resolution: {integrity: sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==} engines: {node: '>= 0.4'} - array.prototype.flat@1.3.1: - resolution: {integrity: sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==} + array.prototype.findlastindex@1.2.5: + resolution: {integrity: sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ==} engines: {node: '>= 0.4'} array.prototype.flat@1.3.2: @@ -8609,11 +8693,12 @@ packages: resolution: {integrity: sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==} engines: {node: '>= 0.4'} - array.prototype.tosorted@1.1.1: - resolution: {integrity: sha512-pZYPXPRl2PqWcsUs6LOMn+1f1532nEoPTYowBtqLwAW+W8vSVhkIGnmOX1t/UQjD6YGI0vcD2B1U7ZFGQH9jnQ==} + array.prototype.tosorted@1.1.4: + resolution: {integrity: sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==} + engines: {node: '>= 0.4'} - arraybuffer.prototype.slice@1.0.2: - resolution: {integrity: sha512-yMBKppFur/fbHu9/6USUe03bZ4knMYiwFBcyiaXB8Go0qNehwX6inYPzK9U0NeQvGxKthcmHcaR8P5MStSRBAw==} + arraybuffer.prototype.slice@1.0.3: + resolution: {integrity: sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==} engines: {node: '>= 0.4'} arrify@1.0.1: @@ -8676,9 +8761,6 @@ packages: async@3.2.5: resolution: {integrity: sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==} - asynciterator.prototype@1.0.0: - resolution: {integrity: sha512-wwHYEIS0Q80f5mosx3L/dfG5t5rjEa9Ft51GTaNt862EnpyGHpgz2RkZvLPp1oF5TnAiTohkEKVEu8pQPJI7Vg==} - asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} @@ -8714,8 +8796,8 @@ packages: peerDependencies: postcss: ^8.1.0 - available-typed-arrays@1.0.5: - resolution: {integrity: sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==} + available-typed-arrays@1.0.7: + resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} engines: {node: '>= 0.4'} aws-sign2@0.7.0: @@ -8734,8 +8816,9 @@ packages: axobject-query@3.2.1: resolution: {integrity: sha512-jsyHu61e6N4Vbz/v18DHwWYKK0bSWLqn47eeDSKPB7m8tqMHF9YJ+mhIk2lVteyZrY8tnSj/jHOv4YiTCuCJgg==} - axobject-query@4.0.0: - resolution: {integrity: sha512-+60uv1hiVFhHZeO+Lz0RYzsVHy5Wr1ayX0mwda9KPDVLNJgZ1T9Ny7VmFbLDzxsH0D87I86vgj3gFrjTJUYznw==} + axobject-query@4.1.0: + resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} + engines: {node: '>= 0.4'} b4a@1.6.4: resolution: {integrity: sha512-fpWrvyVHEKyeEvbKZTVOeZF3VSKKWtJxFIxX/jaVPf+cLbGUSitjb49pHLqPV2BUNNZ0LcoeEGfE/YCpyDYHIw==} @@ -9095,9 +9178,6 @@ packages: resolution: {integrity: sha512-A+Fezp4zxnit6FanDmv9EqXNAi3vt9DWp51/71UEhXukb7QUuvtv9344h91dyAxuTLoSYJFU299qzR3tzwPAhw==} engines: {node: '>=6'} - call-bind@1.0.5: - resolution: {integrity: sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==} - call-bind@1.0.7: resolution: {integrity: sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==} engines: {node: '>= 0.4'} @@ -9984,6 +10064,18 @@ packages: resolution: {integrity: sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==} engines: {node: '>=12'} + data-view-buffer@1.0.1: + resolution: {integrity: sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==} + engines: {node: '>= 0.4'} + + data-view-byte-length@1.0.1: + resolution: {integrity: sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==} + engines: {node: '>= 0.4'} + + data-view-byte-offset@1.0.0: + resolution: {integrity: sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==} + engines: {node: '>= 0.4'} + date-format@4.0.14: resolution: {integrity: sha512-39BOQLs9ZjKh0/patS9nrT8wc3ioX3/eA/zgbKNopnF2wCqJEoxywwwElATYvRsXdnOxA/OQeQoFZ3rFjVajhg==} engines: {node: '>=4.0'} @@ -10115,10 +10207,6 @@ packages: resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==} engines: {node: '>=12'} - define-properties@1.2.0: - resolution: {integrity: sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==} - engines: {node: '>= 0.4'} - define-properties@1.2.1: resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} engines: {node: '>= 0.4'} @@ -10460,12 +10548,8 @@ packages: error-stack-parser@2.1.4: resolution: {integrity: sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==} - es-abstract@1.20.4: - resolution: {integrity: sha512-0UtvRN79eMe2L+UNEF1BwRe364sj/DXhQ/k5FmivgoSdpM90b8Jc0mDzKMGo7QS0BVbOP/bTwBKNnDc9rNzaPA==} - engines: {node: '>= 0.4'} - - es-abstract@1.22.3: - resolution: {integrity: sha512-eiiY8HQeYfYH2Con2berK+To6GrK2RxbPawDkGq4UiCQQfZHb6wX9qQqkbpPqaxQFcl8d9QzZqo0tGE0VcrdwA==} + es-abstract@1.23.3: + resolution: {integrity: sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==} engines: {node: '>= 0.4'} es-define-property@1.0.0: @@ -10476,8 +10560,9 @@ packages: resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} engines: {node: '>= 0.4'} - es-iterator-helpers@1.0.15: - resolution: {integrity: sha512-GhoY8uYqd6iwUl2kgjTm4CZAf6oo5mHK7BPqx3rKgx893YSsy0LGHV6gfqqQvZt/8xM8xeOnfXBCfqclMKkJ5g==} + es-iterator-helpers@1.0.19: + resolution: {integrity: sha512-zoMwbCcH5hwUkKJkT8kDIBZSz9I6mVG//+lDCinLCGov4+r7NIy0ld8o03M0cJxl2spVf6ESYVS6/gpIfq1FFw==} + engines: {node: '>= 0.4'} es-module-lexer@1.3.0: resolution: {integrity: sha512-vZK7T0N2CBmBOixhmjdqx2gWVbFZ4DXZ/NyRMZVlJXPa7CyFS+/a4QQsDGDQy9ZfEzxFuNEsMLeQJnKP2p5/JA==} @@ -10488,12 +10573,16 @@ packages: es-module-lexer@1.5.4: resolution: {integrity: sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==} - es-set-tostringtag@2.0.2: - resolution: {integrity: sha512-BuDyupZt65P9D2D2vA/zqcI3G5xRsklm5N3xCwuiy+/vKy8i0ifdsQP1sLgO4tZDSCaQUSnmC48khknGMV3D2Q==} + es-object-atoms@1.0.0: + resolution: {integrity: sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.0.3: + resolution: {integrity: sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==} engines: {node: '>= 0.4'} - es-shim-unscopables@1.0.0: - resolution: {integrity: sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==} + es-shim-unscopables@1.0.2: + resolution: {integrity: sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==} es-to-primitive@1.2.1: resolution: {integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==} @@ -10625,29 +10714,8 @@ packages: eslint: '*' eslint-plugin-import: '*' - eslint-module-utils@2.7.4: - resolution: {integrity: sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA==} - engines: {node: '>=4'} - peerDependencies: - '@typescript-eslint/parser': '*' - eslint: '*' - eslint-import-resolver-node: '*' - eslint-import-resolver-typescript: '*' - eslint-import-resolver-webpack: '*' - peerDependenciesMeta: - '@typescript-eslint/parser': - optional: true - eslint: - optional: true - eslint-import-resolver-node: - optional: true - eslint-import-resolver-typescript: - optional: true - eslint-import-resolver-webpack: - optional: true - - eslint-module-utils@2.8.0: - resolution: {integrity: sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==} + eslint-module-utils@2.11.0: + resolution: {integrity: sha512-gbBE5Hitek/oG6MUVj6sFuzEjA/ClzNflVrLovHi/JgLdC7fiN5gLAY1WIPW1a0V5I999MnsrvVrCOGmmVqDBQ==} engines: {node: '>=4'} peerDependencies: '@typescript-eslint/parser': '*' @@ -10672,18 +10740,8 @@ packages: peerDependencies: eslint: '>= 3.2.1' - eslint-plugin-import@2.27.5: - resolution: {integrity: sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow==} - engines: {node: '>=4'} - peerDependencies: - '@typescript-eslint/parser': '*' - eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 - peerDependenciesMeta: - '@typescript-eslint/parser': - optional: true - - eslint-plugin-import@2.29.1: - resolution: {integrity: sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==} + eslint-plugin-import@2.30.0: + resolution: {integrity: sha512-/mHNE9jINJfiD2EKkg1BKyPyUk4zdnT54YgbOgfjSakWT5oyX/qQLVNTkehyfpcMxZXMy1zyonZ2v7hZTX43Yw==} engines: {node: '>=4'} peerDependencies: '@typescript-eslint/parser': '*' @@ -10698,10 +10756,11 @@ packages: peerDependencies: eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 - eslint-plugin-playwright@0.15.3: - resolution: {integrity: sha512-LQMW5y0DLK5Fnpya7JR1oAYL2/7Y9wDiYw6VZqlKqcRGSgjbVKNqxraphk7ra1U3Bb5EK444xMgUlQPbMg2M1g==} + eslint-plugin-playwright@1.6.2: + resolution: {integrity: sha512-mraN4Em3b5jLt01q7qWPyLg0Q5v3KAWfJSlEWwldyUXoa7DSPrBR4k6B6LROLqipsG8ndkwWMdjl1Ffdh15tag==} + engines: {node: '>=16.6.0'} peerDependencies: - eslint: '>=7' + eslint: '>=8.40.0' eslint-plugin-jest: '>=25' peerDependenciesMeta: eslint-plugin-jest: @@ -10713,17 +10772,11 @@ packages: peerDependencies: eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 - eslint-plugin-react@7.32.2: - resolution: {integrity: sha512-t2fBMa+XzonrrNkyVirzKlvn5RXzzPwRHtMvLAtVZrt8oxgnTQaYbU6SXTOO1mwQgp1y5+toMSKInnzGr0Knqg==} + eslint-plugin-react@7.35.0: + resolution: {integrity: sha512-v501SSMOWv8gerHkk+IIQBkcGRGrO2nfybfj5pLxuJNFTPxxA3PSryhXTK+9pNbtkggheDdsC0E9Q8CuPk6JKA==} engines: {node: '>=4'} peerDependencies: - eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 - - eslint-plugin-react@7.33.2: - resolution: {integrity: sha512-73QQMKALArI8/7xGLNI/3LylrEYrlKZSb5C9+q3OtOewTnMQi5cT+aE9E41sLCmli3I9PGGmD1yiZydyo4FEPw==} - engines: {node: '>=4'} - peerDependencies: - eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 + eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7 eslint-plugin-storybook@0.8.0: resolution: {integrity: sha512-CZeVO5EzmPY7qghO2t64oaFM+8FTaD4uzOEjHKp516exyTKo+skKAL9GI3QALS2BXhyALJjNtwbmr1XinGE8bA==} @@ -10739,8 +10792,8 @@ packages: resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - eslint-scope@8.0.0: - resolution: {integrity: sha512-zj3Byw6jX4TcFCJmxOzLt6iol5FAr9xQyZZSQjEzW2UiCJXLwXdRIKCYVFftnpZckaC9Ps9xlC7jB8tSeWWOaw==} + eslint-scope@8.0.2: + resolution: {integrity: sha512-6E4xmrTw5wtxnLA5wYL3WDfhZ/1bUBGOXV0zQvVRDOtrR8D0p6W7fs3JweNYhwRYeGvd/1CKX2se0/2s7Q/nJA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} eslint-visitor-keys@3.3.0: @@ -11287,10 +11340,6 @@ packages: function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} - function.prototype.name@1.1.5: - resolution: {integrity: sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==} - engines: {node: '>= 0.4'} - function.prototype.name@1.1.6: resolution: {integrity: sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==} engines: {node: '>= 0.4'} @@ -11324,9 +11373,6 @@ packages: get-func-name@2.0.2: resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==} - get-intrinsic@1.2.2: - resolution: {integrity: sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==} - get-intrinsic@1.2.4: resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==} engines: {node: '>= 0.4'} @@ -11367,8 +11413,8 @@ packages: resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} engines: {node: '>=16'} - get-symbol-description@1.0.0: - resolution: {integrity: sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==} + get-symbol-description@1.0.2: + resolution: {integrity: sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==} engines: {node: '>= 0.4'} get-symbol-from-current-process-h@1.0.2: @@ -11494,10 +11540,14 @@ packages: resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} engines: {node: '>=4'} - globals@13.21.0: - resolution: {integrity: sha512-ybyme3s4yy/t/3s35bewwXKOf7cvzfreG2lH0lZl0JB7I4GxRP2ghxOK/Nb9EkRXdbBXZLfq/p/0W2JUONB/Gg==} + globals@13.24.0: + resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} engines: {node: '>=8'} + globals@15.9.0: + resolution: {integrity: sha512-SmSKyLLKFbSr6rptvP8izbyxJL4ILwqO9Jg23UA0sDlGlu58V59D1//I3vlc0KJphVdUR7vMjHIplYnzBxorQA==} + engines: {node: '>=18'} + globalthis@1.0.3: resolution: {integrity: sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==} engines: {node: '>= 0.4'} @@ -11607,16 +11657,16 @@ packages: has-property-descriptors@1.0.2: resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} - has-proto@1.0.1: - resolution: {integrity: sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==} + has-proto@1.0.3: + resolution: {integrity: sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==} engines: {node: '>= 0.4'} has-symbols@1.0.3: resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} engines: {node: '>= 0.4'} - has-tostringtag@1.0.0: - resolution: {integrity: sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==} + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} engines: {node: '>= 0.4'} has-unicode@2.0.1: @@ -11629,8 +11679,8 @@ packages: hash-sum@2.0.0: resolution: {integrity: sha512-WdZTbAByD+pHfl/g9QSsBIIwy8IT+EsPiKDs0KNX+zSHhdDLFKdZu0BQHljvO+0QI/BasbMSUa8wYNCZTvhslg==} - hasown@2.0.0: - resolution: {integrity: sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==} + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} hast-util-heading-rank@3.0.0: @@ -11890,6 +11940,10 @@ packages: resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==} engines: {node: '>= 4'} + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + image-meta@0.2.0: resolution: {integrity: sha512-ZBGjl0ZMEMeOC3Ns0wUF/5UdUmr3qQhBSCniT0LxOgGGIRHiNFOkMtIHB7EOznRU47V2AxPgiVP+s+0/UCU0Hg==} @@ -11986,8 +12040,8 @@ packages: resolution: {integrity: sha512-SqLLa/Oe5rZUagTR9z+Zd6izyatHglbmbvVofo1KzuVB54YHleWzeHNLoR7FOICGOeQSqeLh1cordb3MzhGcEw==} engines: {node: '>=18'} - internal-slot@1.0.5: - resolution: {integrity: sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==} + internal-slot@1.0.7: + resolution: {integrity: sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==} engines: {node: '>= 0.4'} interpret@1.4.0: @@ -12043,8 +12097,9 @@ packages: resolution: {integrity: sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==} engines: {node: '>= 0.4'} - is-array-buffer@3.0.2: - resolution: {integrity: sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==} + is-array-buffer@3.0.4: + resolution: {integrity: sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==} + engines: {node: '>= 0.4'} is-arrayish@0.2.1: resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} @@ -12083,12 +12138,17 @@ packages: resolution: {integrity: sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==} hasBin: true - is-core-module@2.13.0: - resolution: {integrity: sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==} - is-core-module@2.13.1: resolution: {integrity: sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==} + is-core-module@2.15.1: + resolution: {integrity: sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==} + engines: {node: '>= 0.4'} + + is-data-view@1.0.1: + resolution: {integrity: sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==} + engines: {node: '>= 0.4'} + is-date-object@1.0.5: resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==} engines: {node: '>= 0.4'} @@ -12183,8 +12243,8 @@ packages: is-module@1.0.0: resolution: {integrity: sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==} - is-negative-zero@2.0.2: - resolution: {integrity: sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==} + is-negative-zero@2.0.3: + resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} engines: {node: '>= 0.4'} is-network-error@1.1.0: @@ -12261,8 +12321,9 @@ packages: is-set@2.0.2: resolution: {integrity: sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==} - is-shared-array-buffer@1.0.2: - resolution: {integrity: sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==} + is-shared-array-buffer@1.0.3: + resolution: {integrity: sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==} + engines: {node: '>= 0.4'} is-ssh@1.4.0: resolution: {integrity: sha512-x7+VxdxOdlV3CYpjvRLBv5Lo9OJerlYanjwFrPR9fuGPjCiNiCzFgAWpiLAohSbsnH4ZAys3SBh+hq5rJosxUQ==} @@ -12291,12 +12352,8 @@ packages: resolution: {integrity: sha512-xFuJpne9oFz5qDaodwmmG08e3CawH/2ZV8Qqza1Ko7Sk8POWbkRdwIoAWVhqvq0XeUzANEhKo2n0IXUGBm7A/w==} engines: {node: '>=0.10.0'} - is-typed-array@1.1.12: - resolution: {integrity: sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==} - engines: {node: '>= 0.4'} - - is-typed-array@1.1.9: - resolution: {integrity: sha512-kfrlnTTn8pZkfpJMUgYD7YZ3qzeJgWUn8XfVYBARc4wnmNOmLbmuuaAs3q5fvB0UJOn6yHAKaGTPM7d6ezoD/A==} + is-typed-array@1.1.13: + resolution: {integrity: sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==} engines: {node: '>= 0.4'} is-typedarray@1.0.0: @@ -14096,34 +14153,32 @@ packages: resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} engines: {node: '>= 0.4'} - object.assign@4.1.4: - resolution: {integrity: sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==} + object.assign@4.1.5: + resolution: {integrity: sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==} engines: {node: '>= 0.4'} object.entries@1.1.6: resolution: {integrity: sha512-leTPzo4Zvg3pmbQ3rDK69Rl8GQvIqMWubrkxONG9/ojtFE2rD9fjMKfSI5BxW3osRH1m6VdzmqK8oAY9aT4x5w==} engines: {node: '>= 0.4'} + object.entries@1.1.8: + resolution: {integrity: sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ==} + engines: {node: '>= 0.4'} + object.fromentries@2.0.6: resolution: {integrity: sha512-VciD13dswC4j1Xt5394WR4MzmAQmlgN72phd/riNp9vtD7tp4QQWJ0R4wvclXcafgcYK8veHRed2W6XeGBvcfg==} engines: {node: '>= 0.4'} - object.fromentries@2.0.7: - resolution: {integrity: sha512-UPbPHML6sL8PI/mOqPwsH4G6iyXcCGzLin8KvEPenOZN5lpCNBZZQ+V62vdjB1mQHrmqGQt5/OJzemUA+KJmEA==} + object.fromentries@2.0.8: + resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==} engines: {node: '>= 0.4'} - object.groupby@1.0.1: - resolution: {integrity: sha512-HqaQtqLnp/8Bn4GL16cj+CUYbnpe1bh0TtEaWvybszDG4tgxCJuRpV8VGuvNaI1fAnI4lUJzDG55MXcOH4JZcQ==} - - object.hasown@1.1.2: - resolution: {integrity: sha512-B5UIT3J1W+WuWIU55h0mjlwaqxiE5vYENJXIXZ4VFe05pNYrkKuK0U/6aFcb0pKywYJh7IhfoqUfKVmrJJHZHw==} - - object.values@1.1.6: - resolution: {integrity: sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==} + object.groupby@1.0.3: + resolution: {integrity: sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==} engines: {node: '>= 0.4'} - object.values@1.1.7: - resolution: {integrity: sha512-aU6xnDFYT3x17e/f0IiiwlGPTy2jzMySGfUB4fq6z7CV8l85CWHDk5ErhyhpfDHhrOMwGFhSQkhMGHaIotA6Ng==} + object.values@1.2.0: + resolution: {integrity: sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==} engines: {node: '>= 0.4'} objectorarray@1.0.5: @@ -14588,6 +14643,10 @@ packages: resolution: {integrity: sha512-on2ZJVVDXRADWE6jnQaX0ioEylzgBpQk8r55NE4wjXW1ZxO+BgDlY6DXwj20i0V8eB4SenDQ00WEaxfiIQPcxg==} engines: {node: '>= 0.12.0'} + possible-typed-array-names@1.0.0: + resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==} + engines: {node: '>= 0.4'} + post-robot@8.0.31: resolution: {integrity: sha512-nUhtKgtmcgyuPm4RnIhUB3gsDYJBHOgFry3TvOxhIHpgfwYY/T69d4oB90tw4YUllFZUUwqLEv1Wgyg6eOoJ7A==} @@ -15714,8 +15773,8 @@ packages: regex-parser@2.2.11: resolution: {integrity: sha512-jbD/FT0+9MBU2XAZluI7w2OBs1RBi6p9M83nkoZayQXXU9e8Robt69FcZc7wU4eJD/YFTjn1JdCk3rbMJajz8Q==} - regexp.prototype.flags@1.5.1: - resolution: {integrity: sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg==} + regexp.prototype.flags@1.5.2: + resolution: {integrity: sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==} engines: {node: '>= 0.4'} regexpu-core@5.3.2: @@ -15831,8 +15890,8 @@ packages: resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} hasBin: true - resolve@2.0.0-next.4: - resolution: {integrity: sha512-iMDbmAWtfU+MHpxt/I5iWI7cY6YVEZUQ3MBgPQ++XD1PELuJHIl82xBmObyP2KyQmkNB2dsqF7seoQQiAn5yDQ==} + resolve@2.0.0-next.5: + resolution: {integrity: sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==} hasBin: true responselike@2.0.1: @@ -15964,8 +16023,8 @@ packages: resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==} engines: {node: '>=6'} - safe-array-concat@1.1.0: - resolution: {integrity: sha512-ZdQ0Jeb9Ofti4hbt5lX3T2JcAamT9hfzYU1MNB+z/jaEbB6wfFfPIR/zEORmZqobkCCJhSjodobH6WHNmJ97dg==} + safe-array-concat@1.1.2: + resolution: {integrity: sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==} engines: {node: '>=0.4'} safe-buffer@5.1.2: @@ -15977,8 +16036,9 @@ packages: safe-identifier@0.4.2: resolution: {integrity: sha512-6pNbSMW6OhAi9j+N8V+U715yBQsaWJ7eyEUaOrawX+isg5ZxhUlV1NipNtgaKHmFGiABwt+ZF04Ii+3Xjkg+8w==} - safe-regex-test@1.0.0: - resolution: {integrity: sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==} + safe-regex-test@1.0.3: + resolution: {integrity: sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==} + engines: {node: '>= 0.4'} safe-stable-stringify@2.4.3: resolution: {integrity: sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==} @@ -16173,8 +16233,8 @@ packages: resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} engines: {node: '>= 0.4'} - set-function-name@2.0.1: - resolution: {integrity: sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA==} + set-function-name@2.0.2: + resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} engines: {node: '>= 0.4'} setimmediate-napi@1.0.6: @@ -16235,8 +16295,9 @@ packages: shiki@0.14.7: resolution: {integrity: sha512-dNPAPrxSc87ua2sKJ3H5dQ/6ZaY8RNnaAqK+t0eG7p0Soi2ydiqbGOTaZCqaYvA/uZYfS1LJnemt3Q+mSfcPCg==} - side-channel@1.0.4: - resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==} + side-channel@1.0.6: + resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==} + engines: {node: '>= 0.4'} siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} @@ -16548,24 +16609,23 @@ packages: resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} engines: {node: '>=18'} - string.prototype.matchall@4.0.8: - resolution: {integrity: sha512-6zOCOcJ+RJAQshcTvXPHoxoQGONa3e/Lqx90wUA+wEzX78sg5Bo+1tQo4N0pohS0erG9qtCqJDjNCQBjeWVxyg==} - - string.prototype.trim@1.2.8: - resolution: {integrity: sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ==} + string.prototype.matchall@4.0.11: + resolution: {integrity: sha512-NUdh0aDavY2og7IbBPenWqR9exH+E26Sv8e0/eTe1tltDGZL+GtBkDAnnyBtmekfK6/Dq3MkcGtzXFEd1LQrtg==} engines: {node: '>= 0.4'} - string.prototype.trimend@1.0.5: - resolution: {integrity: sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog==} + string.prototype.repeat@1.0.0: + resolution: {integrity: sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==} - string.prototype.trimend@1.0.7: - resolution: {integrity: sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA==} + string.prototype.trim@1.2.9: + resolution: {integrity: sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==} + engines: {node: '>= 0.4'} - string.prototype.trimstart@1.0.5: - resolution: {integrity: sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg==} + string.prototype.trimend@1.0.8: + resolution: {integrity: sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==} - string.prototype.trimstart@1.0.7: - resolution: {integrity: sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg==} + string.prototype.trimstart@1.0.8: + resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} + engines: {node: '>= 0.4'} string_decoder@1.1.1: resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} @@ -17157,9 +17217,6 @@ packages: resolution: {integrity: sha512-xWFISjviPydmtmgeUAuXp4N1fky+VCtfhOkDUFIv5ea7p4wuTomI4QTrXvFBX2S4jZsmyTSrStQl+E+4w+RzxA==} engines: {node: '>=10.13.0'} - tsconfig-paths@3.14.1: - resolution: {integrity: sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==} - tsconfig-paths@3.15.0: resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} @@ -17269,20 +17326,21 @@ packages: type@2.7.2: resolution: {integrity: sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==} - typed-array-buffer@1.0.0: - resolution: {integrity: sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==} + typed-array-buffer@1.0.2: + resolution: {integrity: sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==} engines: {node: '>= 0.4'} - typed-array-byte-length@1.0.0: - resolution: {integrity: sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==} + typed-array-byte-length@1.0.1: + resolution: {integrity: sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==} engines: {node: '>= 0.4'} - typed-array-byte-offset@1.0.0: - resolution: {integrity: sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==} + typed-array-byte-offset@1.0.2: + resolution: {integrity: sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==} engines: {node: '>= 0.4'} - typed-array-length@1.0.4: - resolution: {integrity: sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==} + typed-array-length@1.0.6: + resolution: {integrity: sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==} + engines: {node: '>= 0.4'} typed-assert@1.0.9: resolution: {integrity: sha512-KNNZtayBCtmnNmbo5mG47p1XsCyrx6iVqomjcZnec/1Y5GGARaxPs6r49RnSPeUP3YjNYiU9sQHAtY4BBvnZwg==} @@ -17305,6 +17363,15 @@ packages: peerDependencies: typescript: 4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x + typescript-eslint@8.3.0: + resolution: {integrity: sha512-EvWjwWLwwKDIJuBjk2I6UkV8KEQcwZ0VM10nR1rIunRDIP67QJTZAHBXTX0HW/oI1H10YESF8yWie8fRQxjvFA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + typescript@5.3.3: resolution: {integrity: sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==} engines: {node: '>=14.17'} @@ -18187,12 +18254,8 @@ packages: which-collection@1.0.1: resolution: {integrity: sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==} - which-typed-array@1.1.13: - resolution: {integrity: sha512-P5Nra0qjSncduVPEAr7xhoF5guty49ArDTwzJ/yNuPIbZppyRxFQsRCWrocxIY+CnMVG+qfbU2FmDKyvSGClow==} - engines: {node: '>= 0.4'} - - which-typed-array@1.1.8: - resolution: {integrity: sha512-Jn4e5PItbcAHyLoRDwvPj1ypu27DJbtdYXUa5zsinrUx77Uvfb0cXwwnGMTn7cjUfhhqgVQnVJCwF+7cgU7tpw==} + which-typed-array@1.1.15: + resolution: {integrity: sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==} engines: {node: '>= 0.4'} which@1.3.1: @@ -18713,37 +18776,56 @@ snapshots: transitivePeerDependencies: - chokidar - '@angular-eslint/bundled-angular-compiler@18.0.1': {} + '@angular-eslint/builder@18.3.0(eslint@8.57.0)(typescript@5.5.3)': + dependencies: + eslint: 8.57.0 + typescript: 5.5.3 + + '@angular-eslint/bundled-angular-compiler@18.3.0': {} - '@angular-eslint/eslint-plugin-template@18.0.1(@typescript-eslint/utils@7.16.0(eslint@8.57.0)(typescript@5.5.3))(eslint@8.57.0)(typescript@5.5.3)': + '@angular-eslint/eslint-plugin-template@18.3.0(@typescript-eslint/utils@8.3.0(eslint@8.57.0)(typescript@5.5.3))(eslint@8.57.0)(typescript@5.5.3)': dependencies: - '@angular-eslint/bundled-angular-compiler': 18.0.1 - '@angular-eslint/utils': 18.0.1(@typescript-eslint/utils@7.16.0(eslint@8.57.0)(typescript@5.5.3))(eslint@8.57.0)(typescript@5.5.3) - '@typescript-eslint/utils': 7.16.0(eslint@8.57.0)(typescript@5.5.3) + '@angular-eslint/bundled-angular-compiler': 18.3.0 + '@angular-eslint/utils': 18.3.0(@typescript-eslint/utils@8.3.0(eslint@8.57.0)(typescript@5.5.3))(eslint@8.57.0)(typescript@5.5.3) + '@typescript-eslint/utils': 8.3.0(eslint@8.57.0)(typescript@5.5.3) aria-query: 5.3.0 - axobject-query: 4.0.0 + axobject-query: 4.1.0 eslint: 8.57.0 typescript: 5.5.3 - '@angular-eslint/eslint-plugin@18.0.1(@typescript-eslint/utils@7.16.0(eslint@8.57.0)(typescript@5.5.3))(eslint@8.57.0)(typescript@5.5.3)': + '@angular-eslint/eslint-plugin@18.3.0(@typescript-eslint/utils@8.3.0(eslint@8.57.0)(typescript@5.5.3))(eslint@8.57.0)(typescript@5.5.3)': dependencies: - '@angular-eslint/bundled-angular-compiler': 18.0.1 - '@angular-eslint/utils': 18.0.1(@typescript-eslint/utils@7.16.0(eslint@8.57.0)(typescript@5.5.3))(eslint@8.57.0)(typescript@5.5.3) - '@typescript-eslint/utils': 7.16.0(eslint@8.57.0)(typescript@5.5.3) + '@angular-eslint/bundled-angular-compiler': 18.3.0 + '@angular-eslint/utils': 18.3.0(@typescript-eslint/utils@8.3.0(eslint@8.57.0)(typescript@5.5.3))(eslint@8.57.0)(typescript@5.5.3) + '@typescript-eslint/utils': 8.3.0(eslint@8.57.0)(typescript@5.5.3) eslint: 8.57.0 typescript: 5.5.3 - '@angular-eslint/template-parser@18.0.1(eslint@8.57.0)(typescript@5.5.3)': + '@angular-eslint/schematics@18.3.0(@angular-devkit/core@18.2.1(chokidar@3.6.0))(@angular-devkit/schematics@18.2.1(chokidar@3.6.0))(@typescript-eslint/utils@8.3.0(eslint@8.57.0)(typescript@5.5.3))(eslint@8.57.0)(typescript@5.5.3)': dependencies: - '@angular-eslint/bundled-angular-compiler': 18.0.1 + '@angular-devkit/core': 18.2.1(chokidar@3.6.0) + '@angular-devkit/schematics': 18.2.1(chokidar@3.6.0) + '@angular-eslint/eslint-plugin': 18.3.0(@typescript-eslint/utils@8.3.0(eslint@8.57.0)(typescript@5.5.3))(eslint@8.57.0)(typescript@5.5.3) + '@angular-eslint/eslint-plugin-template': 18.3.0(@typescript-eslint/utils@8.3.0(eslint@8.57.0)(typescript@5.5.3))(eslint@8.57.0)(typescript@5.5.3) + ignore: 5.3.2 + semver: 7.6.3 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - '@typescript-eslint/utils' + - eslint + - typescript + + '@angular-eslint/template-parser@18.3.0(eslint@8.57.0)(typescript@5.5.3)': + dependencies: + '@angular-eslint/bundled-angular-compiler': 18.3.0 eslint: 8.57.0 - eslint-scope: 8.0.0 + eslint-scope: 8.0.2 typescript: 5.5.3 - '@angular-eslint/utils@18.0.1(@typescript-eslint/utils@7.16.0(eslint@8.57.0)(typescript@5.5.3))(eslint@8.57.0)(typescript@5.5.3)': + '@angular-eslint/utils@18.3.0(@typescript-eslint/utils@8.3.0(eslint@8.57.0)(typescript@5.5.3))(eslint@8.57.0)(typescript@5.5.3)': dependencies: - '@angular-eslint/bundled-angular-compiler': 18.0.1 - '@typescript-eslint/utils': 7.16.0(eslint@8.57.0)(typescript@5.5.3) + '@angular-eslint/bundled-angular-compiler': 18.3.0 + '@typescript-eslint/utils': 8.3.0(eslint@8.57.0)(typescript@5.5.3) eslint: 8.57.0 typescript: 5.5.3 @@ -23449,12 +23531,14 @@ snapshots: '@eslint-community/regexpp@4.6.2': {} + '@eslint/compat@1.1.1': {} + '@eslint/eslintrc@2.1.1': dependencies: ajv: 6.12.6 debug: 4.3.4(supports-color@8.1.1) espree: 9.6.1 - globals: 13.21.0 + globals: 13.24.0 ignore: 5.2.0 import-fresh: 3.3.0 js-yaml: 4.1.0 @@ -23468,7 +23552,7 @@ snapshots: ajv: 6.12.6 debug: 4.3.4(supports-color@8.1.1) espree: 9.6.1 - globals: 13.21.0 + globals: 13.24.0 ignore: 5.3.1 import-fresh: 3.3.0 js-yaml: 4.1.0 @@ -24721,7 +24805,7 @@ snapshots: json-parse-even-better-errors: 3.0.0 normalize-package-data: 5.0.0 proc-log: 3.0.0 - semver: 7.6.2 + semver: 7.6.3 transitivePeerDependencies: - bluebird @@ -24856,9 +24940,9 @@ snapshots: - typescript - verdaccio - '@nrwl/eslint-plugin-nx@19.7.0-beta.6(@babel/traverse@7.25.4)(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.7)(typescript@5.5.3))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@18.19.8)(@typescript-eslint/parser@7.16.0(eslint@8.57.0)(typescript@5.5.3))(eslint-config-prettier@9.1.0(eslint@8.57.0))(eslint@8.57.0)(nx@19.7.0-beta.6(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.7)(typescript@5.5.3))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(typescript@5.5.3)(verdaccio@5.31.0(encoding@0.1.13)(typanion@3.14.0))': + '@nrwl/eslint-plugin-nx@19.7.0-beta.6(@babel/traverse@7.25.4)(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.7)(typescript@5.5.3))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@18.19.8)(@typescript-eslint/parser@8.3.0(eslint@8.57.0)(typescript@5.5.3))(eslint-config-prettier@9.1.0(eslint@8.57.0))(eslint@8.57.0)(nx@19.7.0-beta.6(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.7)(typescript@5.5.3))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(typescript@5.5.3)(verdaccio@5.31.0(encoding@0.1.13)(typanion@3.14.0))': dependencies: - '@nx/eslint-plugin': 19.7.0-beta.6(@babel/traverse@7.25.4)(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.7)(typescript@5.5.3))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@18.19.8)(@typescript-eslint/parser@7.16.0(eslint@8.57.0)(typescript@5.5.3))(eslint-config-prettier@9.1.0(eslint@8.57.0))(eslint@8.57.0)(nx@19.7.0-beta.6(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.7)(typescript@5.5.3))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(typescript@5.5.3)(verdaccio@5.31.0(encoding@0.1.13)(typanion@3.14.0)) + '@nx/eslint-plugin': 19.7.0-beta.6(@babel/traverse@7.25.4)(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.7)(typescript@5.5.3))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@18.19.8)(@typescript-eslint/parser@8.3.0(eslint@8.57.0)(typescript@5.5.3))(eslint-config-prettier@9.1.0(eslint@8.57.0))(eslint@8.57.0)(nx@19.7.0-beta.6(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.7)(typescript@5.5.3))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(typescript@5.5.3)(verdaccio@5.31.0(encoding@0.1.13)(typanion@3.14.0)) transitivePeerDependencies: - '@babel/traverse' - '@swc-node/register' @@ -25421,12 +25505,12 @@ snapshots: - typescript - verdaccio - '@nx/eslint-plugin@19.7.0-beta.6(@babel/traverse@7.25.4)(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.7)(typescript@5.5.3))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@18.19.8)(@typescript-eslint/parser@7.16.0(eslint@8.57.0)(typescript@5.5.3))(eslint-config-prettier@9.1.0(eslint@8.57.0))(eslint@8.57.0)(nx@19.7.0-beta.6(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.7)(typescript@5.5.3))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(typescript@5.5.3)(verdaccio@5.31.0(encoding@0.1.13)(typanion@3.14.0))': + '@nx/eslint-plugin@19.7.0-beta.6(@babel/traverse@7.25.4)(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.7)(typescript@5.5.3))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@18.19.8)(@typescript-eslint/parser@8.3.0(eslint@8.57.0)(typescript@5.5.3))(eslint-config-prettier@9.1.0(eslint@8.57.0))(eslint@8.57.0)(nx@19.7.0-beta.6(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.7)(typescript@5.5.3))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(typescript@5.5.3)(verdaccio@5.31.0(encoding@0.1.13)(typanion@3.14.0))': dependencies: - '@nrwl/eslint-plugin-nx': 19.7.0-beta.6(@babel/traverse@7.25.4)(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.7)(typescript@5.5.3))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@18.19.8)(@typescript-eslint/parser@7.16.0(eslint@8.57.0)(typescript@5.5.3))(eslint-config-prettier@9.1.0(eslint@8.57.0))(eslint@8.57.0)(nx@19.7.0-beta.6(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.7)(typescript@5.5.3))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(typescript@5.5.3)(verdaccio@5.31.0(encoding@0.1.13)(typanion@3.14.0)) + '@nrwl/eslint-plugin-nx': 19.7.0-beta.6(@babel/traverse@7.25.4)(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.7)(typescript@5.5.3))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@18.19.8)(@typescript-eslint/parser@8.3.0(eslint@8.57.0)(typescript@5.5.3))(eslint-config-prettier@9.1.0(eslint@8.57.0))(eslint@8.57.0)(nx@19.7.0-beta.6(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.7)(typescript@5.5.3))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(typescript@5.5.3)(verdaccio@5.31.0(encoding@0.1.13)(typanion@3.14.0)) '@nx/devkit': 19.7.0-beta.6(nx@19.7.0-beta.6(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.7)(typescript@5.5.3))(@swc/core@1.5.7(@swc/helpers@0.5.11))) '@nx/js': 19.7.0-beta.6(@babel/traverse@7.25.4)(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.7)(typescript@5.5.3))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@18.19.8)(nx@19.7.0-beta.6(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.7)(typescript@5.5.3))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(typescript@5.5.3)(verdaccio@5.31.0(encoding@0.1.13)(typanion@3.14.0)) - '@typescript-eslint/parser': 7.16.0(eslint@8.57.0)(typescript@5.5.3) + '@typescript-eslint/parser': 8.3.0(eslint@8.57.0)(typescript@5.5.3) '@typescript-eslint/type-utils': 7.16.0(eslint@8.57.0)(typescript@5.5.3) '@typescript-eslint/utils': 7.16.0(eslint@8.57.0)(typescript@5.5.3) chalk: 4.1.2 @@ -26883,6 +26967,8 @@ snapshots: optionalDependencies: fsevents: 2.3.3 + '@rtsao/scc@1.1.0': {} + '@rushstack/eslint-patch@1.7.2': {} '@schematics/angular@18.2.1(chokidar@3.6.0)': @@ -27095,7 +27181,7 @@ snapshots: magic-string: 0.30.10 path-browserify: 1.0.1 process: 0.11.10 - semver: 7.6.2 + semver: 7.6.3 storybook: 8.2.9(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.7)(utf-8-validate@5.0.10) style-loader: 3.3.1(webpack@5.88.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.19.5)(webpack-cli@5.1.4)) terser-webpack-plugin: 5.3.10(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.19.5)(webpack@5.88.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.19.5)(webpack-cli@5.1.4)) @@ -27218,7 +27304,7 @@ snapshots: react-docgen: 7.0.3 react-dom: 18.3.1(react@18.3.1) resolve: 1.22.8 - semver: 7.6.2 + semver: 7.6.3 storybook: 8.2.9(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.7)(utf-8-validate@5.0.10) tsconfig-paths: 4.2.0 webpack: 5.88.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.19.5)(webpack-cli@5.1.4) @@ -27873,6 +27959,10 @@ snapshots: '@types/estree': 1.0.5 '@types/json-schema': 7.0.12 + '@types/eslint__js@8.42.3': + dependencies: + '@types/eslint': 8.56.10 + '@types/estree-jsx@1.0.3': dependencies: '@types/estree': 1.0.5 @@ -28208,14 +28298,14 @@ snapshots: '@types/node': 18.19.8 optional: true - '@typescript-eslint/eslint-plugin@7.16.0(@typescript-eslint/parser@7.16.0(eslint@8.57.0)(typescript@5.5.3))(eslint@8.57.0)(typescript@5.5.3)': + '@typescript-eslint/eslint-plugin@8.3.0(@typescript-eslint/parser@8.3.0(eslint@8.57.0)(typescript@5.5.3))(eslint@8.57.0)(typescript@5.5.3)': dependencies: '@eslint-community/regexpp': 4.11.0 - '@typescript-eslint/parser': 7.16.0(eslint@8.57.0)(typescript@5.5.3) - '@typescript-eslint/scope-manager': 7.16.0 - '@typescript-eslint/type-utils': 7.16.0(eslint@8.57.0)(typescript@5.5.3) - '@typescript-eslint/utils': 7.16.0(eslint@8.57.0)(typescript@5.5.3) - '@typescript-eslint/visitor-keys': 7.16.0 + '@typescript-eslint/parser': 8.3.0(eslint@8.57.0)(typescript@5.5.3) + '@typescript-eslint/scope-manager': 8.3.0 + '@typescript-eslint/type-utils': 8.3.0(eslint@8.57.0)(typescript@5.5.3) + '@typescript-eslint/utils': 8.3.0(eslint@8.57.0)(typescript@5.5.3) + '@typescript-eslint/visitor-keys': 8.3.0 eslint: 8.57.0 graphemer: 1.4.0 ignore: 5.3.1 @@ -28239,12 +28329,12 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@7.16.0(eslint@8.57.0)(typescript@5.5.3)': + '@typescript-eslint/parser@8.3.0(eslint@8.57.0)(typescript@5.5.3)': dependencies: - '@typescript-eslint/scope-manager': 7.16.0 - '@typescript-eslint/types': 7.16.0 - '@typescript-eslint/typescript-estree': 7.16.0(typescript@5.5.3) - '@typescript-eslint/visitor-keys': 7.16.0 + '@typescript-eslint/scope-manager': 8.3.0 + '@typescript-eslint/types': 8.3.0 + '@typescript-eslint/typescript-estree': 8.3.0(typescript@5.5.3) + '@typescript-eslint/visitor-keys': 8.3.0 debug: 4.3.4(supports-color@8.1.1) eslint: 8.57.0 optionalDependencies: @@ -28252,6 +28342,19 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/rule-tester@8.3.0(eslint@8.57.0)(typescript@5.5.3)': + dependencies: + '@typescript-eslint/typescript-estree': 8.3.0(typescript@5.5.3) + '@typescript-eslint/utils': 8.3.0(eslint@8.57.0)(typescript@5.5.3) + ajv: 6.12.6 + eslint: 8.57.0 + json-stable-stringify-without-jsonify: 1.0.1 + lodash.merge: 4.6.2 + semver: 7.6.3 + transitivePeerDependencies: + - supports-color + - typescript + '@typescript-eslint/scope-manager@5.62.0': dependencies: '@typescript-eslint/types': 5.62.0 @@ -28267,6 +28370,11 @@ snapshots: '@typescript-eslint/types': 7.16.0 '@typescript-eslint/visitor-keys': 7.16.0 + '@typescript-eslint/scope-manager@8.3.0': + dependencies: + '@typescript-eslint/types': 8.3.0 + '@typescript-eslint/visitor-keys': 8.3.0 + '@typescript-eslint/type-utils@7.16.0(eslint@8.57.0)(typescript@5.5.3)': dependencies: '@typescript-eslint/typescript-estree': 7.16.0(typescript@5.5.3) @@ -28279,12 +28387,26 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/type-utils@8.3.0(eslint@8.57.0)(typescript@5.5.3)': + dependencies: + '@typescript-eslint/typescript-estree': 8.3.0(typescript@5.5.3) + '@typescript-eslint/utils': 8.3.0(eslint@8.57.0)(typescript@5.5.3) + debug: 4.3.4(supports-color@8.1.1) + ts-api-utils: 1.3.0(typescript@5.5.3) + optionalDependencies: + typescript: 5.5.3 + transitivePeerDependencies: + - eslint + - supports-color + '@typescript-eslint/types@5.62.0': {} '@typescript-eslint/types@6.18.1': {} '@typescript-eslint/types@7.16.0': {} + '@typescript-eslint/types@8.3.0': {} + '@typescript-eslint/typescript-estree@5.62.0(typescript@5.5.3)': dependencies: '@typescript-eslint/types': 5.62.0 @@ -28322,7 +28444,22 @@ snapshots: globby: 11.1.0 is-glob: 4.0.3 minimatch: 9.0.4 - semver: 7.6.2 + semver: 7.6.3 + ts-api-utils: 1.3.0(typescript@5.5.3) + optionalDependencies: + typescript: 5.5.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/typescript-estree@8.3.0(typescript@5.5.3)': + dependencies: + '@typescript-eslint/types': 8.3.0 + '@typescript-eslint/visitor-keys': 8.3.0 + debug: 4.3.4(supports-color@8.1.1) + fast-glob: 3.3.2 + is-glob: 4.0.3 + minimatch: 9.0.4 + semver: 7.6.3 ts-api-utils: 1.3.0(typescript@5.5.3) optionalDependencies: typescript: 5.5.3 @@ -28339,7 +28476,7 @@ snapshots: '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.5.3) eslint: 8.57.0 eslint-scope: 5.1.1 - semver: 7.6.2 + semver: 7.6.3 transitivePeerDependencies: - supports-color - typescript @@ -28355,6 +28492,17 @@ snapshots: - supports-color - typescript + '@typescript-eslint/utils@8.3.0(eslint@8.57.0)(typescript@5.5.3)': + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) + '@typescript-eslint/scope-manager': 8.3.0 + '@typescript-eslint/types': 8.3.0 + '@typescript-eslint/typescript-estree': 8.3.0(typescript@5.5.3) + eslint: 8.57.0 + transitivePeerDependencies: + - supports-color + - typescript + '@typescript-eslint/visitor-keys@5.62.0': dependencies: '@typescript-eslint/types': 5.62.0 @@ -28370,6 +28518,11 @@ snapshots: '@typescript-eslint/types': 7.16.0 eslint-visitor-keys: 3.4.3 + '@typescript-eslint/visitor-keys@8.3.0': + dependencies: + '@typescript-eslint/types': 8.3.0 + eslint-visitor-keys: 3.4.3 + '@ungap/structured-clone@1.2.0': {} '@unhead/dom@1.8.10': @@ -29177,6 +29330,21 @@ snapshots: '@algolia/requester-node-http': 4.14.2 '@algolia/transporter': 4.14.2 + angular-eslint@18.3.0(@angular-devkit/core@18.2.1(chokidar@3.6.0))(@angular-devkit/schematics@18.2.1(chokidar@3.6.0))(@typescript-eslint/utils@8.3.0(eslint@8.57.0)(typescript@5.5.3))(eslint@8.57.0)(typescript-eslint@8.3.0(eslint@8.57.0)(typescript@5.5.3))(typescript@5.5.3): + dependencies: + '@angular-eslint/builder': 18.3.0(eslint@8.57.0)(typescript@5.5.3) + '@angular-eslint/eslint-plugin': 18.3.0(@typescript-eslint/utils@8.3.0(eslint@8.57.0)(typescript@5.5.3))(eslint@8.57.0)(typescript@5.5.3) + '@angular-eslint/eslint-plugin-template': 18.3.0(@typescript-eslint/utils@8.3.0(eslint@8.57.0)(typescript@5.5.3))(eslint@8.57.0)(typescript@5.5.3) + '@angular-eslint/schematics': 18.3.0(@angular-devkit/core@18.2.1(chokidar@3.6.0))(@angular-devkit/schematics@18.2.1(chokidar@3.6.0))(@typescript-eslint/utils@8.3.0(eslint@8.57.0)(typescript@5.5.3))(eslint@8.57.0)(typescript@5.5.3) + '@angular-eslint/template-parser': 18.3.0(eslint@8.57.0)(typescript@5.5.3) + eslint: 8.57.0 + typescript: 5.5.3 + typescript-eslint: 8.3.0(eslint@8.57.0)(typescript@5.5.3) + transitivePeerDependencies: + - '@angular-devkit/core' + - '@angular-devkit/schematics' + - '@typescript-eslint/utils' + ansi-colors@4.1.3: {} ansi-escapes@4.3.2: @@ -29270,10 +29438,10 @@ snapshots: dependencies: dequal: 2.0.3 - array-buffer-byte-length@1.0.0: + array-buffer-byte-length@1.0.1: dependencies: call-bind: 1.0.7 - is-array-buffer: 3.0.2 + is-array-buffer: 3.0.4 array-differ@3.0.0: {} @@ -29285,17 +29453,18 @@ snapshots: array-includes@3.1.6: dependencies: - call-bind: 1.0.5 - define-properties: 1.2.0 - es-abstract: 1.20.4 - get-intrinsic: 1.2.2 + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + get-intrinsic: 1.2.4 is-string: 1.0.7 - array-includes@3.1.7: + array-includes@3.1.8: dependencies: call-bind: 1.0.7 - define-properties: 1.2.0 - es-abstract: 1.22.3 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-object-atoms: 1.0.0 get-intrinsic: 1.2.4 is-string: 1.0.7 @@ -29307,59 +29476,63 @@ snapshots: array-union@3.0.1: {} - array.prototype.findlastindex@1.2.3: + array.prototype.findlast@1.2.5: dependencies: call-bind: 1.0.7 define-properties: 1.2.1 - es-abstract: 1.22.3 - es-shim-unscopables: 1.0.0 - get-intrinsic: 1.2.4 + es-abstract: 1.23.3 + es-errors: 1.3.0 + es-object-atoms: 1.0.0 + es-shim-unscopables: 1.0.2 - array.prototype.flat@1.3.1: + array.prototype.findlastindex@1.2.5: dependencies: - call-bind: 1.0.5 - define-properties: 1.2.0 - es-abstract: 1.22.3 - es-shim-unscopables: 1.0.0 + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-errors: 1.3.0 + es-object-atoms: 1.0.0 + es-shim-unscopables: 1.0.2 array.prototype.flat@1.3.2: dependencies: call-bind: 1.0.7 define-properties: 1.2.1 - es-abstract: 1.22.3 - es-shim-unscopables: 1.0.0 + es-abstract: 1.23.3 + es-shim-unscopables: 1.0.2 array.prototype.flatmap@1.3.1: dependencies: - call-bind: 1.0.5 - define-properties: 1.2.0 - es-abstract: 1.20.4 - es-shim-unscopables: 1.0.0 + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-shim-unscopables: 1.0.2 array.prototype.flatmap@1.3.2: dependencies: call-bind: 1.0.7 define-properties: 1.2.1 - es-abstract: 1.22.3 - es-shim-unscopables: 1.0.0 + es-abstract: 1.23.3 + es-shim-unscopables: 1.0.2 - array.prototype.tosorted@1.1.1: + array.prototype.tosorted@1.1.4: dependencies: - call-bind: 1.0.5 - define-properties: 1.2.0 - es-abstract: 1.20.4 - es-shim-unscopables: 1.0.0 - get-intrinsic: 1.2.2 + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-errors: 1.3.0 + es-shim-unscopables: 1.0.2 - arraybuffer.prototype.slice@1.0.2: + arraybuffer.prototype.slice@1.0.3: dependencies: - array-buffer-byte-length: 1.0.0 + array-buffer-byte-length: 1.0.1 call-bind: 1.0.7 define-properties: 1.2.1 - es-abstract: 1.22.3 + es-abstract: 1.23.3 + es-errors: 1.3.0 get-intrinsic: 1.2.4 - is-array-buffer: 3.0.2 - is-shared-array-buffer: 1.0.2 + is-array-buffer: 3.0.4 + is-shared-array-buffer: 1.0.3 arrify@1.0.1: {} @@ -29418,10 +29591,6 @@ snapshots: async@3.2.5: {} - asynciterator.prototype@1.0.0: - dependencies: - has-symbols: 1.0.3 - asynckit@0.4.0: {} at-least-node@1.0.0: {} @@ -29460,7 +29629,9 @@ snapshots: postcss: 8.4.41 postcss-value-parser: 4.2.0 - available-typed-arrays@1.0.5: {} + available-typed-arrays@1.0.7: + dependencies: + possible-typed-array-names: 1.0.0 aws-sign2@0.7.0: {} @@ -29480,9 +29651,7 @@ snapshots: dependencies: dequal: 2.0.3 - axobject-query@4.0.0: - dependencies: - dequal: 2.0.3 + axobject-query@4.1.0: {} b4a@1.6.4: {} @@ -30096,12 +30265,6 @@ snapshots: cachedir@2.3.0: {} - call-bind@1.0.5: - dependencies: - function-bind: 1.1.2 - get-intrinsic: 1.2.4 - set-function-length: 1.2.2 - call-bind@1.0.7: dependencies: es-define-property: 1.0.0 @@ -30838,7 +31001,7 @@ snapshots: postcss-modules-scope: 3.2.0(postcss@8.4.38) postcss-modules-values: 4.0.0(postcss@8.4.38) postcss-value-parser: 4.2.0 - semver: 7.6.2 + semver: 7.6.3 optionalDependencies: webpack: 5.88.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.19.5)(webpack-cli@5.1.4) @@ -31146,6 +31309,24 @@ snapshots: whatwg-mimetype: 3.0.0 whatwg-url: 11.0.0 + data-view-buffer@1.0.1: + dependencies: + call-bind: 1.0.7 + es-errors: 1.3.0 + is-data-view: 1.0.1 + + data-view-byte-length@1.0.1: + dependencies: + call-bind: 1.0.7 + es-errors: 1.3.0 + is-data-view: 1.0.1 + + data-view-byte-offset@1.0.0: + dependencies: + call-bind: 1.0.7 + es-errors: 1.3.0 + is-data-view: 1.0.1 + date-format@4.0.14: {} dateformat@3.0.3: {} @@ -31238,11 +31419,6 @@ snapshots: define-lazy-prop@3.0.0: {} - define-properties@1.2.0: - dependencies: - has-property-descriptors: 1.0.2 - object-keys: 1.1.1 - define-properties@1.2.1: dependencies: define-data-property: 1.1.4 @@ -31545,74 +31721,54 @@ snapshots: dependencies: stackframe: 1.3.4 - es-abstract@1.20.4: + es-abstract@1.23.3: dependencies: + array-buffer-byte-length: 1.0.1 + arraybuffer.prototype.slice: 1.0.3 + available-typed-arrays: 1.0.7 call-bind: 1.0.7 - es-to-primitive: 1.2.1 - function-bind: 1.1.2 - function.prototype.name: 1.1.5 - get-intrinsic: 1.2.4 - get-symbol-description: 1.0.0 - has: 1.0.3 - has-property-descriptors: 1.0.2 - has-symbols: 1.0.3 - internal-slot: 1.0.5 - is-callable: 1.2.7 - is-negative-zero: 2.0.2 - is-regex: 1.1.4 - is-shared-array-buffer: 1.0.2 - is-string: 1.0.7 - is-weakref: 1.0.2 - object-inspect: 1.13.1 - object-keys: 1.1.1 - object.assign: 4.1.4 - regexp.prototype.flags: 1.5.1 - safe-regex-test: 1.0.0 - string.prototype.trimend: 1.0.5 - string.prototype.trimstart: 1.0.5 - unbox-primitive: 1.0.2 - - es-abstract@1.22.3: - dependencies: - array-buffer-byte-length: 1.0.0 - arraybuffer.prototype.slice: 1.0.2 - available-typed-arrays: 1.0.5 - call-bind: 1.0.7 - es-set-tostringtag: 2.0.2 + data-view-buffer: 1.0.1 + data-view-byte-length: 1.0.1 + data-view-byte-offset: 1.0.0 + es-define-property: 1.0.0 + es-errors: 1.3.0 + es-object-atoms: 1.0.0 + es-set-tostringtag: 2.0.3 es-to-primitive: 1.2.1 function.prototype.name: 1.1.6 get-intrinsic: 1.2.4 - get-symbol-description: 1.0.0 + get-symbol-description: 1.0.2 globalthis: 1.0.3 gopd: 1.0.1 has-property-descriptors: 1.0.2 - has-proto: 1.0.1 + has-proto: 1.0.3 has-symbols: 1.0.3 - hasown: 2.0.0 - internal-slot: 1.0.5 - is-array-buffer: 3.0.2 + hasown: 2.0.2 + internal-slot: 1.0.7 + is-array-buffer: 3.0.4 is-callable: 1.2.7 - is-negative-zero: 2.0.2 + is-data-view: 1.0.1 + is-negative-zero: 2.0.3 is-regex: 1.1.4 - is-shared-array-buffer: 1.0.2 + is-shared-array-buffer: 1.0.3 is-string: 1.0.7 - is-typed-array: 1.1.12 + is-typed-array: 1.1.13 is-weakref: 1.0.2 object-inspect: 1.13.1 object-keys: 1.1.1 - object.assign: 4.1.4 - regexp.prototype.flags: 1.5.1 - safe-array-concat: 1.1.0 - safe-regex-test: 1.0.0 - string.prototype.trim: 1.2.8 - string.prototype.trimend: 1.0.7 - string.prototype.trimstart: 1.0.7 - typed-array-buffer: 1.0.0 - typed-array-byte-length: 1.0.0 - typed-array-byte-offset: 1.0.0 - typed-array-length: 1.0.4 + object.assign: 4.1.5 + regexp.prototype.flags: 1.5.2 + safe-array-concat: 1.1.2 + safe-regex-test: 1.0.3 + string.prototype.trim: 1.2.9 + string.prototype.trimend: 1.0.8 + string.prototype.trimstart: 1.0.8 + typed-array-buffer: 1.0.2 + typed-array-byte-length: 1.0.1 + typed-array-byte-offset: 1.0.2 + typed-array-length: 1.0.6 unbox-primitive: 1.0.2 - which-typed-array: 1.1.13 + which-typed-array: 1.1.15 es-define-property@1.0.0: dependencies: @@ -31620,22 +31776,22 @@ snapshots: es-errors@1.3.0: {} - es-iterator-helpers@1.0.15: + es-iterator-helpers@1.0.19: dependencies: - asynciterator.prototype: 1.0.0 call-bind: 1.0.7 define-properties: 1.2.1 - es-abstract: 1.22.3 - es-set-tostringtag: 2.0.2 + es-abstract: 1.23.3 + es-errors: 1.3.0 + es-set-tostringtag: 2.0.3 function-bind: 1.1.2 get-intrinsic: 1.2.4 globalthis: 1.0.3 has-property-descriptors: 1.0.2 - has-proto: 1.0.1 + has-proto: 1.0.3 has-symbols: 1.0.3 - internal-slot: 1.0.5 + internal-slot: 1.0.7 iterator.prototype: 1.1.2 - safe-array-concat: 1.1.0 + safe-array-concat: 1.1.2 es-module-lexer@1.3.0: {} @@ -31643,15 +31799,19 @@ snapshots: es-module-lexer@1.5.4: {} - es-set-tostringtag@2.0.2: + es-object-atoms@1.0.0: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.0.3: dependencies: get-intrinsic: 1.2.4 - has-tostringtag: 1.0.0 - hasown: 2.0.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 - es-shim-unscopables@1.0.0: + es-shim-unscopables@1.0.2: dependencies: - has: 1.0.3 + hasown: 2.0.2 es-to-primitive@1.2.1: dependencies: @@ -31934,10 +32094,10 @@ snapshots: '@typescript-eslint/parser': 6.18.1(eslint@8.57.0)(typescript@5.5.3) eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.5.2(eslint-plugin-import@2.29.1)(eslint@8.57.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.18.1(eslint@8.57.0)(typescript@5.5.3))(eslint-import-resolver-typescript@3.5.2)(eslint@8.57.0) + eslint-import-resolver-typescript: 3.5.2(eslint-plugin-import@2.30.0)(eslint@8.57.0) + eslint-plugin-import: 2.30.0(@typescript-eslint/parser@6.18.1(eslint@8.57.0)(typescript@5.5.3))(eslint-import-resolver-typescript@3.5.2)(eslint@8.57.0) eslint-plugin-jsx-a11y: 6.7.1(eslint@8.57.0) - eslint-plugin-react: 7.33.2(eslint@8.57.0) + eslint-plugin-react: 7.35.0(eslint@8.57.0) eslint-plugin-react-hooks: 4.6.0(eslint@8.57.0) optionalDependencies: typescript: 5.5.3 @@ -31952,17 +32112,17 @@ snapshots: eslint-import-resolver-node@0.3.9: dependencies: debug: 3.2.7(supports-color@8.1.1) - is-core-module: 2.13.1 + is-core-module: 2.15.1 resolve: 1.22.8 transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.5.2(eslint-plugin-import@2.29.1)(eslint@8.57.0): + eslint-import-resolver-typescript@3.5.2(eslint-plugin-import@2.30.0)(eslint@8.57.0): dependencies: debug: 4.3.4(supports-color@8.1.1) enhanced-resolve: 5.15.0 eslint: 8.57.0 - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.18.1(eslint@8.57.0)(typescript@5.5.3))(eslint-import-resolver-typescript@3.5.2)(eslint@8.57.0) + eslint-plugin-import: 2.30.0(@typescript-eslint/parser@6.18.1(eslint@8.57.0)(typescript@5.5.3))(eslint-import-resolver-typescript@3.5.2)(eslint@8.57.0) get-tsconfig: 4.3.0 globby: 13.2.2 is-core-module: 2.13.1 @@ -31971,79 +32131,83 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.7.4(@typescript-eslint/parser@7.16.0(eslint@8.57.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint@8.57.0): + eslint-module-utils@2.11.0(@typescript-eslint/parser@6.18.1(eslint@8.57.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.5.2(eslint-plugin-import@2.30.0)(eslint@8.57.0))(eslint@8.57.0): dependencies: debug: 3.2.7(supports-color@8.1.1) optionalDependencies: - '@typescript-eslint/parser': 7.16.0(eslint@8.57.0)(typescript@5.5.3) + '@typescript-eslint/parser': 6.18.1(eslint@8.57.0)(typescript@5.5.3) eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 + eslint-import-resolver-typescript: 3.5.2(eslint-plugin-import@2.30.0)(eslint@8.57.0) transitivePeerDependencies: - supports-color - eslint-module-utils@2.8.0(@typescript-eslint/parser@6.18.1(eslint@8.57.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.5.2(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0): + eslint-module-utils@2.11.0(@typescript-eslint/parser@8.3.0(eslint@8.57.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint@8.57.0): dependencies: debug: 3.2.7(supports-color@8.1.1) optionalDependencies: - '@typescript-eslint/parser': 6.18.1(eslint@8.57.0)(typescript@5.5.3) + '@typescript-eslint/parser': 8.3.0(eslint@8.57.0)(typescript@5.5.3) eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.5.2(eslint-plugin-import@2.29.1)(eslint@8.57.0) transitivePeerDependencies: - supports-color eslint-plugin-cypress@2.14.0(eslint@8.57.0): dependencies: eslint: 8.57.0 - globals: 13.21.0 + globals: 13.24.0 - eslint-plugin-import@2.27.5(@typescript-eslint/parser@7.16.0(eslint@8.57.0)(typescript@5.5.3))(eslint@8.57.0): + eslint-plugin-import@2.30.0(@typescript-eslint/parser@6.18.1(eslint@8.57.0)(typescript@5.5.3))(eslint-import-resolver-typescript@3.5.2)(eslint@8.57.0): dependencies: - array-includes: 3.1.6 - array.prototype.flat: 1.3.1 - array.prototype.flatmap: 1.3.1 + '@rtsao/scc': 1.1.0 + array-includes: 3.1.8 + array.prototype.findlastindex: 1.2.5 + array.prototype.flat: 1.3.2 + array.prototype.flatmap: 1.3.2 debug: 3.2.7(supports-color@8.1.1) doctrine: 2.1.0 eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.7.4(@typescript-eslint/parser@7.16.0(eslint@8.57.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint@8.57.0) - has: 1.0.3 - is-core-module: 2.13.0 + eslint-module-utils: 2.11.0(@typescript-eslint/parser@6.18.1(eslint@8.57.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.5.2(eslint-plugin-import@2.30.0)(eslint@8.57.0))(eslint@8.57.0) + hasown: 2.0.2 + is-core-module: 2.15.1 is-glob: 4.0.3 minimatch: 3.1.2 - object.values: 1.1.6 - resolve: 1.22.8 + object.fromentries: 2.0.8 + object.groupby: 1.0.3 + object.values: 1.2.0 semver: 6.3.1 - tsconfig-paths: 3.14.1 + tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 7.16.0(eslint@8.57.0)(typescript@5.5.3) + '@typescript-eslint/parser': 6.18.1(eslint@8.57.0)(typescript@5.5.3) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack - supports-color - eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.18.1(eslint@8.57.0)(typescript@5.5.3))(eslint-import-resolver-typescript@3.5.2)(eslint@8.57.0): + eslint-plugin-import@2.30.0(@typescript-eslint/parser@8.3.0(eslint@8.57.0)(typescript@5.5.3))(eslint@8.57.0): dependencies: - array-includes: 3.1.7 - array.prototype.findlastindex: 1.2.3 + '@rtsao/scc': 1.1.0 + array-includes: 3.1.8 + array.prototype.findlastindex: 1.2.5 array.prototype.flat: 1.3.2 array.prototype.flatmap: 1.3.2 debug: 3.2.7(supports-color@8.1.1) doctrine: 2.1.0 eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.8.0(@typescript-eslint/parser@6.18.1(eslint@8.57.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.5.2(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0) - hasown: 2.0.0 - is-core-module: 2.13.1 + eslint-module-utils: 2.11.0(@typescript-eslint/parser@8.3.0(eslint@8.57.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint@8.57.0) + hasown: 2.0.2 + is-core-module: 2.15.1 is-glob: 4.0.3 minimatch: 3.1.2 - object.fromentries: 2.0.7 - object.groupby: 1.0.1 - object.values: 1.1.7 + object.fromentries: 2.0.8 + object.groupby: 1.0.3 + object.values: 1.2.0 semver: 6.3.1 tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 6.18.1(eslint@8.57.0)(typescript@5.5.3) + '@typescript-eslint/parser': 8.3.0(eslint@8.57.0)(typescript@5.5.3) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack @@ -32069,52 +32233,36 @@ snapshots: object.fromentries: 2.0.6 semver: 6.3.1 - eslint-plugin-playwright@0.15.3(eslint@8.57.0): + eslint-plugin-playwright@1.6.2(eslint@8.57.0): dependencies: eslint: 8.57.0 + globals: 13.24.0 eslint-plugin-react-hooks@4.6.0(eslint@8.57.0): dependencies: eslint: 8.57.0 - eslint-plugin-react@7.32.2(eslint@8.57.0): - dependencies: - array-includes: 3.1.6 - array.prototype.flatmap: 1.3.1 - array.prototype.tosorted: 1.1.1 - doctrine: 2.1.0 - eslint: 8.57.0 - estraverse: 5.3.0 - jsx-ast-utils: 3.3.3 - minimatch: 3.1.2 - object.entries: 1.1.6 - object.fromentries: 2.0.6 - object.hasown: 1.1.2 - object.values: 1.1.6 - prop-types: 15.8.1 - resolve: 2.0.0-next.4 - semver: 6.3.1 - string.prototype.matchall: 4.0.8 - - eslint-plugin-react@7.33.2(eslint@8.57.0): + eslint-plugin-react@7.35.0(eslint@8.57.0): dependencies: - array-includes: 3.1.7 + array-includes: 3.1.8 + array.prototype.findlast: 1.2.5 array.prototype.flatmap: 1.3.2 - array.prototype.tosorted: 1.1.1 + array.prototype.tosorted: 1.1.4 doctrine: 2.1.0 - es-iterator-helpers: 1.0.15 + es-iterator-helpers: 1.0.19 eslint: 8.57.0 estraverse: 5.3.0 + hasown: 2.0.2 jsx-ast-utils: 3.3.3 minimatch: 3.1.2 - object.entries: 1.1.6 - object.fromentries: 2.0.7 - object.hasown: 1.1.2 - object.values: 1.1.7 + object.entries: 1.1.8 + object.fromentries: 2.0.8 + object.values: 1.2.0 prop-types: 15.8.1 - resolve: 2.0.0-next.4 + resolve: 2.0.0-next.5 semver: 6.3.1 - string.prototype.matchall: 4.0.8 + string.prototype.matchall: 4.0.11 + string.prototype.repeat: 1.0.0 eslint-plugin-storybook@0.8.0(eslint@8.57.0)(typescript@5.5.3): dependencies: @@ -32137,7 +32285,7 @@ snapshots: esrecurse: 4.3.0 estraverse: 5.3.0 - eslint-scope@8.0.0: + eslint-scope@8.0.2: dependencies: esrecurse: 4.3.0 estraverse: 5.3.0 @@ -32171,7 +32319,7 @@ snapshots: file-entry-cache: 6.0.1 find-up: 5.0.0 glob-parent: 6.0.2 - globals: 13.21.0 + globals: 13.24.0 graphemer: 1.4.0 ignore: 5.3.1 imurmurhash: 0.1.4 @@ -32789,7 +32937,7 @@ snapshots: minimatch: 3.1.2 node-abort-controller: 3.1.1 schema-utils: 3.2.0 - semver: 7.6.2 + semver: 7.6.3 tapable: 2.2.1 typescript: 5.3.3 webpack: 5.90.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.19.5)(webpack-cli@5.1.4(webpack-dev-server@5.0.4)(webpack@5.88.0)) @@ -32902,18 +33050,11 @@ snapshots: function-bind@1.1.2: {} - function.prototype.name@1.1.5: - dependencies: - call-bind: 1.0.7 - define-properties: 1.2.1 - es-abstract: 1.22.3 - functions-have-names: 1.2.3 - function.prototype.name@1.1.6: dependencies: call-bind: 1.0.7 define-properties: 1.2.1 - es-abstract: 1.22.3 + es-abstract: 1.23.3 functions-have-names: 1.2.3 functions-have-names@1.2.3: {} @@ -32953,20 +33094,13 @@ snapshots: get-func-name@2.0.2: {} - get-intrinsic@1.2.2: - dependencies: - function-bind: 1.1.2 - has-proto: 1.0.1 - has-symbols: 1.0.3 - hasown: 2.0.0 - get-intrinsic@1.2.4: dependencies: es-errors: 1.3.0 function-bind: 1.1.2 - has-proto: 1.0.1 + has-proto: 1.0.3 has-symbols: 1.0.3 - hasown: 2.0.0 + hasown: 2.0.2 get-package-type@0.1.0: {} @@ -32993,9 +33127,10 @@ snapshots: get-stream@8.0.1: {} - get-symbol-description@1.0.0: + get-symbol-description@1.0.2: dependencies: call-bind: 1.0.7 + es-errors: 1.3.0 get-intrinsic: 1.2.4 get-symbol-from-current-process-h@1.0.2: {} @@ -33163,10 +33298,12 @@ snapshots: globals@11.12.0: {} - globals@13.21.0: + globals@13.24.0: dependencies: type-fest: 0.20.2 + globals@15.9.0: {} + globalthis@1.0.3: dependencies: define-properties: 1.2.1 @@ -33325,11 +33462,11 @@ snapshots: dependencies: es-define-property: 1.0.0 - has-proto@1.0.1: {} + has-proto@1.0.3: {} has-symbols@1.0.3: {} - has-tostringtag@1.0.0: + has-tostringtag@1.0.2: dependencies: has-symbols: 1.0.3 @@ -33341,7 +33478,7 @@ snapshots: hash-sum@2.0.0: {} - hasown@2.0.0: + hasown@2.0.2: dependencies: function-bind: 1.1.2 @@ -33667,6 +33804,8 @@ snapshots: ignore@5.3.1: {} + ignore@5.3.2: {} + image-meta@0.2.0: {} image-size@0.5.5: @@ -33788,11 +33927,11 @@ snapshots: strip-ansi: 6.0.1 wrap-ansi: 6.2.0 - internal-slot@1.0.5: + internal-slot@1.0.7: dependencies: - get-intrinsic: 1.2.4 - has: 1.0.3 - side-channel: 1.0.4 + es-errors: 1.3.0 + hasown: 2.0.2 + side-channel: 1.0.6 interpret@1.4.0: {} @@ -33845,19 +33984,18 @@ snapshots: is-arguments@1.1.1: dependencies: call-bind: 1.0.7 - has-tostringtag: 1.0.0 + has-tostringtag: 1.0.2 - is-array-buffer@3.0.2: + is-array-buffer@3.0.4: dependencies: call-bind: 1.0.7 get-intrinsic: 1.2.4 - is-typed-array: 1.1.12 is-arrayish@0.2.1: {} is-async-function@2.0.0: dependencies: - has-tostringtag: 1.0.0 + has-tostringtag: 1.0.2 is-bigint@1.0.4: dependencies: @@ -33870,7 +34008,7 @@ snapshots: is-boolean-object@1.1.2: dependencies: call-bind: 1.0.7 - has-tostringtag: 1.0.0 + has-tostringtag: 1.0.2 is-buffer@1.1.6: {} @@ -33886,17 +34024,21 @@ snapshots: dependencies: ci-info: 3.5.0 - is-core-module@2.13.0: + is-core-module@2.13.1: + dependencies: + hasown: 2.0.2 + + is-core-module@2.15.1: dependencies: - has: 1.0.3 + hasown: 2.0.2 - is-core-module@2.13.1: + is-data-view@1.0.1: dependencies: - hasown: 2.0.0 + is-typed-array: 1.1.13 is-date-object@1.0.5: dependencies: - has-tostringtag: 1.0.0 + has-tostringtag: 1.0.2 is-decimal@1.0.4: {} @@ -33928,7 +34070,7 @@ snapshots: is-generator-function@1.0.10: dependencies: - has-tostringtag: 1.0.0 + has-tostringtag: 1.0.2 is-glob@4.0.3: dependencies: @@ -33962,13 +34104,13 @@ snapshots: is-module@1.0.0: {} - is-negative-zero@2.0.2: {} + is-negative-zero@2.0.3: {} is-network-error@1.1.0: {} is-number-object@1.0.7: dependencies: - has-tostringtag: 1.0.0 + has-tostringtag: 1.0.2 is-number@7.0.0: {} @@ -34011,11 +34153,11 @@ snapshots: is-regex@1.1.4: dependencies: call-bind: 1.0.7 - has-tostringtag: 1.0.0 + has-tostringtag: 1.0.2 is-set@2.0.2: {} - is-shared-array-buffer@1.0.2: + is-shared-array-buffer@1.0.3: dependencies: call-bind: 1.0.7 @@ -34031,7 +34173,7 @@ snapshots: is-string@1.0.7: dependencies: - has-tostringtag: 1.0.0 + has-tostringtag: 1.0.2 is-symbol@1.0.4: dependencies: @@ -34041,17 +34183,9 @@ snapshots: dependencies: text-extensions: 1.9.0 - is-typed-array@1.1.12: + is-typed-array@1.1.13: dependencies: - which-typed-array: 1.1.13 - - is-typed-array@1.1.9: - dependencies: - available-typed-arrays: 1.0.5 - call-bind: 1.0.7 - es-abstract: 1.22.3 - for-each: 0.3.3 - has-tostringtag: 1.0.0 + which-typed-array: 1.1.15 is-typedarray@1.0.0: {} @@ -34169,7 +34303,7 @@ snapshots: get-intrinsic: 1.2.4 has-symbols: 1.0.3 reflect.getprototypeof: 1.0.4 - set-function-name: 2.0.1 + set-function-name: 2.0.2 its-fine@1.2.5(react@18.3.1): dependencies: @@ -34845,8 +34979,8 @@ snapshots: jsx-ast-utils@3.3.3: dependencies: - array-includes: 3.1.7 - object.assign: 4.1.4 + array-includes: 3.1.8 + object.assign: 4.1.5 jwa@1.4.1: dependencies: @@ -36857,7 +36991,7 @@ snapshots: object-keys@1.1.1: {} - object.assign@4.1.4: + object.assign@4.1.5: dependencies: call-bind: 1.0.7 define-properties: 1.2.1 @@ -36866,45 +37000,40 @@ snapshots: object.entries@1.1.6: dependencies: - call-bind: 1.0.5 - define-properties: 1.2.0 - es-abstract: 1.22.3 - - object.fromentries@2.0.6: - dependencies: - call-bind: 1.0.5 - define-properties: 1.2.0 - es-abstract: 1.22.3 + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 - object.fromentries@2.0.7: + object.entries@1.1.8: dependencies: call-bind: 1.0.7 define-properties: 1.2.1 - es-abstract: 1.22.3 + es-object-atoms: 1.0.0 - object.groupby@1.0.1: + object.fromentries@2.0.6: dependencies: call-bind: 1.0.7 define-properties: 1.2.1 - es-abstract: 1.22.3 - get-intrinsic: 1.2.4 + es-abstract: 1.23.3 - object.hasown@1.1.2: + object.fromentries@2.0.8: dependencies: - define-properties: 1.2.0 - es-abstract: 1.22.3 + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-object-atoms: 1.0.0 - object.values@1.1.6: + object.groupby@1.0.3: dependencies: - call-bind: 1.0.5 - define-properties: 1.2.0 - es-abstract: 1.22.3 + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 - object.values@1.1.7: + object.values@1.2.0: dependencies: call-bind: 1.0.7 define-properties: 1.2.1 - es-abstract: 1.22.3 + es-object-atoms: 1.0.0 objectorarray@1.0.5: {} @@ -37449,6 +37578,8 @@ snapshots: transitivePeerDependencies: - supports-color + possible-typed-array-names@1.0.0: {} + post-robot@8.0.31: dependencies: cross-domain-safe-weakmap: 1.0.29 @@ -38234,11 +38365,11 @@ snapshots: qs@6.10.4: dependencies: - side-channel: 1.0.4 + side-channel: 1.0.6 qs@6.11.0: dependencies: - side-channel: 1.0.4 + side-channel: 1.0.6 querystringify@2.2.0: {} @@ -38582,7 +38713,7 @@ snapshots: dependencies: call-bind: 1.0.7 define-properties: 1.2.1 - es-abstract: 1.22.3 + es-abstract: 1.23.3 get-intrinsic: 1.2.4 globalthis: 1.0.3 which-builtin-type: 1.1.3 @@ -38611,11 +38742,12 @@ snapshots: regex-parser@2.2.11: {} - regexp.prototype.flags@1.5.1: + regexp.prototype.flags@1.5.2: dependencies: call-bind: 1.0.7 define-properties: 1.2.1 - set-function-name: 2.0.1 + es-errors: 1.3.0 + set-function-name: 2.0.2 regexpu-core@5.3.2: dependencies: @@ -38757,7 +38889,7 @@ snapshots: path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 - resolve@2.0.0-next.4: + resolve@2.0.0-next.5: dependencies: is-core-module: 2.13.1 path-parse: 1.0.7 @@ -38960,7 +39092,7 @@ snapshots: dependencies: mri: 1.2.0 - safe-array-concat@1.1.0: + safe-array-concat@1.1.2: dependencies: call-bind: 1.0.7 get-intrinsic: 1.2.4 @@ -38973,10 +39105,10 @@ snapshots: safe-identifier@0.4.2: {} - safe-regex-test@1.0.0: + safe-regex-test@1.0.3: dependencies: call-bind: 1.0.7 - get-intrinsic: 1.2.4 + es-errors: 1.3.0 is-regex: 1.1.4 safe-stable-stringify@2.4.3: {} @@ -39184,9 +39316,10 @@ snapshots: gopd: 1.0.1 has-property-descriptors: 1.0.2 - set-function-name@2.0.1: + set-function-name@2.0.2: dependencies: define-data-property: 1.1.4 + es-errors: 1.3.0 functions-have-names: 1.2.3 has-property-descriptors: 1.0.2 @@ -39244,9 +39377,10 @@ snapshots: vscode-oniguruma: 1.7.0 vscode-textmate: 8.0.0 - side-channel@1.0.4: + side-channel@1.0.6: dependencies: call-bind: 1.0.7 + es-errors: 1.3.0 get-intrinsic: 1.2.4 object-inspect: 1.13.1 @@ -39645,46 +39779,44 @@ snapshots: get-east-asian-width: 1.2.0 strip-ansi: 7.1.0 - string.prototype.matchall@4.0.8: - dependencies: - call-bind: 1.0.5 - define-properties: 1.2.0 - es-abstract: 1.22.3 - get-intrinsic: 1.2.2 - has-symbols: 1.0.3 - internal-slot: 1.0.5 - regexp.prototype.flags: 1.5.1 - side-channel: 1.0.4 - - string.prototype.trim@1.2.8: + string.prototype.matchall@4.0.11: dependencies: call-bind: 1.0.7 define-properties: 1.2.1 - es-abstract: 1.22.3 + es-abstract: 1.23.3 + es-errors: 1.3.0 + es-object-atoms: 1.0.0 + get-intrinsic: 1.2.4 + gopd: 1.0.1 + has-symbols: 1.0.3 + internal-slot: 1.0.7 + regexp.prototype.flags: 1.5.2 + set-function-name: 2.0.2 + side-channel: 1.0.6 - string.prototype.trimend@1.0.5: + string.prototype.repeat@1.0.0: dependencies: - call-bind: 1.0.7 define-properties: 1.2.1 - es-abstract: 1.22.3 + es-abstract: 1.23.3 - string.prototype.trimend@1.0.7: + string.prototype.trim@1.2.9: dependencies: call-bind: 1.0.7 define-properties: 1.2.1 - es-abstract: 1.22.3 + es-abstract: 1.23.3 + es-object-atoms: 1.0.0 - string.prototype.trimstart@1.0.5: + string.prototype.trimend@1.0.8: dependencies: call-bind: 1.0.7 define-properties: 1.2.1 - es-abstract: 1.22.3 + es-object-atoms: 1.0.0 - string.prototype.trimstart@1.0.7: + string.prototype.trimstart@1.0.8: dependencies: call-bind: 1.0.7 define-properties: 1.2.1 - es-abstract: 1.22.3 + es-object-atoms: 1.0.0 string_decoder@1.1.1: dependencies: @@ -40347,13 +40479,6 @@ snapshots: enhanced-resolve: 5.16.1 tsconfig-paths: 4.2.0 - tsconfig-paths@3.14.1: - dependencies: - '@types/json5': 0.0.29 - json5: 1.0.2 - minimist: 1.2.8 - strip-bom: 3.0.0 - tsconfig-paths@3.15.0: dependencies: '@types/json5': 0.0.29 @@ -40449,32 +40574,37 @@ snapshots: type@2.7.2: {} - typed-array-buffer@1.0.0: + typed-array-buffer@1.0.2: dependencies: call-bind: 1.0.7 - get-intrinsic: 1.2.4 - is-typed-array: 1.1.12 + es-errors: 1.3.0 + is-typed-array: 1.1.13 - typed-array-byte-length@1.0.0: + typed-array-byte-length@1.0.1: dependencies: call-bind: 1.0.7 for-each: 0.3.3 - has-proto: 1.0.1 - is-typed-array: 1.1.12 + gopd: 1.0.1 + has-proto: 1.0.3 + is-typed-array: 1.1.13 - typed-array-byte-offset@1.0.0: + typed-array-byte-offset@1.0.2: dependencies: - available-typed-arrays: 1.0.5 + available-typed-arrays: 1.0.7 call-bind: 1.0.7 for-each: 0.3.3 - has-proto: 1.0.1 - is-typed-array: 1.1.12 + gopd: 1.0.1 + has-proto: 1.0.3 + is-typed-array: 1.1.13 - typed-array-length@1.0.4: + typed-array-length@1.0.6: dependencies: call-bind: 1.0.7 for-each: 0.3.3 - is-typed-array: 1.1.12 + gopd: 1.0.1 + has-proto: 1.0.3 + is-typed-array: 1.1.13 + possible-typed-array-names: 1.0.0 typed-assert@1.0.9: {} @@ -40497,6 +40627,17 @@ snapshots: shiki: 0.14.7 typescript: 5.5.3 + typescript-eslint@8.3.0(eslint@8.57.0)(typescript@5.5.3): + dependencies: + '@typescript-eslint/eslint-plugin': 8.3.0(@typescript-eslint/parser@8.3.0(eslint@8.57.0)(typescript@5.5.3))(eslint@8.57.0)(typescript@5.5.3) + '@typescript-eslint/parser': 8.3.0(eslint@8.57.0)(typescript@5.5.3) + '@typescript-eslint/utils': 8.3.0(eslint@8.57.0)(typescript@5.5.3) + optionalDependencies: + typescript: 5.5.3 + transitivePeerDependencies: + - eslint + - supports-color + typescript@5.3.3: {} typescript@5.4.2: {} @@ -40886,8 +41027,8 @@ snapshots: inherits: 2.0.4 is-arguments: 1.1.1 is-generator-function: 1.0.10 - is-typed-array: 1.1.9 - which-typed-array: 1.1.8 + is-typed-array: 1.1.13 + which-typed-array: 1.1.15 utila@0.4.0: {} @@ -41637,7 +41778,7 @@ snapshots: which-builtin-type@1.1.3: dependencies: function.prototype.name: 1.1.6 - has-tostringtag: 1.0.0 + has-tostringtag: 1.0.2 is-async-function: 2.0.0 is-date-object: 1.0.5 is-finalizationregistry: 1.0.2 @@ -41647,7 +41788,7 @@ snapshots: isarray: 2.0.5 which-boxed-primitive: 1.0.2 which-collection: 1.0.1 - which-typed-array: 1.1.13 + which-typed-array: 1.1.15 which-collection@1.0.1: dependencies: @@ -41656,22 +41797,13 @@ snapshots: is-weakmap: 2.0.1 is-weakset: 2.0.2 - which-typed-array@1.1.13: + which-typed-array@1.1.15: dependencies: - available-typed-arrays: 1.0.5 + available-typed-arrays: 1.0.7 call-bind: 1.0.7 for-each: 0.3.3 gopd: 1.0.1 - has-tostringtag: 1.0.0 - - which-typed-array@1.1.8: - dependencies: - available-typed-arrays: 1.0.5 - call-bind: 1.0.7 - es-abstract: 1.22.3 - for-each: 0.3.3 - has-tostringtag: 1.0.0 - is-typed-array: 1.1.12 + has-tostringtag: 1.0.2 which@1.3.1: dependencies: diff --git a/tools/eslint-rules/rules/valid-schema-description.ts b/tools/eslint-rules/rules/valid-schema-description.ts index 174be23b1a9b6..df203a495b2e8 100644 --- a/tools/eslint-rules/rules/valid-schema-description.ts +++ b/tools/eslint-rules/rules/valid-schema-description.ts @@ -1,7 +1,7 @@ import { ESLintUtils } from '@typescript-eslint/utils'; import type { AST } from 'jsonc-eslint-parser'; -// NOTE: The rule will be available in ESLint configs as "@nrwl/nx/workspace/valid-schema-description" +// NOTE: The rule will be available in ESLint configs as "@nx/workspace/valid-schema-description" export const RULE_NAME = 'valid-schema-description'; export const rule = ESLintUtils.RuleCreator(() => __filename)({ @@ -10,7 +10,6 @@ export const rule = ESLintUtils.RuleCreator(() => __filename)({ type: 'problem', docs: { description: `Ensures that nx schemas contain valid descriptions in order to provide consistent --help output for commands`, - recommended: 'recommended', }, fixable: 'code', schema: [],