From 4dc667498f0e4a9da34e81bcd5279c08dbcc8bfb Mon Sep 17 00:00:00 2001 From: Miroslav Jonas Date: Tue, 12 Dec 2023 10:03:54 +0100 Subject: [PATCH 1/3] fix(linter): move should migrate all eslint configs --- .../src/generators/init/init-migration.ts | 14 +++++---- .../src/generators/utils/eslint-file.ts | 4 +-- .../convert-to-monorepo.spec.ts | 2 -- .../convert-to-monorepo.ts | 8 +++-- .../move/lib/extract-base-configs.ts | 29 +++++++++---------- .../src/generators/move/move.spec.ts | 25 ++++++++++++++-- .../workspace/src/generators/move/move.ts | 8 +++-- 7 files changed, 59 insertions(+), 31 deletions(-) diff --git a/packages/eslint/src/generators/init/init-migration.ts b/packages/eslint/src/generators/init/init-migration.ts index 0a78ae6804d4a..79eaeac2736e6 100644 --- a/packages/eslint/src/generators/init/init-migration.ts +++ b/packages/eslint/src/generators/init/init-migration.ts @@ -39,18 +39,21 @@ export function migrateConfigToMonorepoStyle( } ); tree.write( - 'eslint.base.config.js', + tree.exists('eslint.config.js') + ? 'eslint.base.config.js' + : 'eslint.config.js', getGlobalFlatEslintConfiguration(unitTestRunner) ); } else { + const eslintFile = findEslintFile(tree, '.'); writeJson( tree, - '.eslintrc.base.json', + eslintFile ? '.eslintrc.base.json' : '.eslintrc.json', getGlobalEsLintConfiguration(unitTestRunner) ); } - // update extens in all projects' eslint configs + // update extends in all projects' eslint configs projects.forEach((project) => { const lintTarget = findLintTarget(project); if (lintTarget) { @@ -76,6 +79,7 @@ export function findLintTarget( } function migrateEslintFile(projectEslintPath: string, tree: Tree) { + const baseFile = findEslintFile(tree); if (isEslintConfigSupported(tree)) { if (useFlatConfig(tree)) { let config = tree.read(projectEslintPath, 'utf-8'); @@ -85,7 +89,7 @@ function migrateEslintFile(projectEslintPath: string, tree: Tree) { config = addImportToFlatConfig( config, 'baseConfig', - `${offsetFromRoot(dirname(projectEslintPath))}eslint.base.config.js` + `${offsetFromRoot(dirname(projectEslintPath))}${baseFile}` ); config = addBlockToFlatConfigExport( config, @@ -117,7 +121,7 @@ function migrateEslintFile(projectEslintPath: string, tree: Tree) { json.extends = json.extends || []; const pathToRootConfig = `${offsetFromRoot( dirname(projectEslintPath) - )}.eslintrc.base.json`; + )}${baseFile}`; if (json.extends.indexOf(pathToRootConfig) === -1) { json.extends.push(pathToRootConfig); } diff --git a/packages/eslint/src/generators/utils/eslint-file.ts b/packages/eslint/src/generators/utils/eslint-file.ts index 85e659d190521..c18ea2b8dd900 100644 --- a/packages/eslint/src/generators/utils/eslint-file.ts +++ b/packages/eslint/src/generators/utils/eslint-file.ts @@ -3,10 +3,10 @@ import { names, offsetFromRoot, readJson, - Tree, updateJson, } from '@nx/devkit'; -import { Linter } from 'eslint'; +import type { Tree } from '@nx/devkit'; +import type { Linter } from 'eslint'; import { useFlatConfig } from '../../utils/flat-config'; import { addBlockToFlatConfigExport, diff --git a/packages/workspace/src/generators/convert-to-monorepo/convert-to-monorepo.spec.ts b/packages/workspace/src/generators/convert-to-monorepo/convert-to-monorepo.spec.ts index 9cea3b3d64c27..1fe03f5be260b 100644 --- a/packages/workspace/src/generators/convert-to-monorepo/convert-to-monorepo.spec.ts +++ b/packages/workspace/src/generators/convert-to-monorepo/convert-to-monorepo.spec.ts @@ -74,7 +74,6 @@ describe('monorepo generator', () => { // Extracted base config files expect(tree.exists('tsconfig.base.json')).toBeTruthy(); - expect(tree.exists('.eslintrc.base.json')).toBeTruthy(); }); it('should respect nested libraries', async () => { @@ -140,7 +139,6 @@ describe('monorepo generator', () => { // Extracted base config files expect(tree.exists('tsconfig.base.json')).toBeTruthy(); - expect(tree.exists('.eslintrc.base.json')).toBeTruthy(); expect(tree.exists('jest.config.ts')).toBeTruthy(); }); diff --git a/packages/workspace/src/generators/convert-to-monorepo/convert-to-monorepo.ts b/packages/workspace/src/generators/convert-to-monorepo/convert-to-monorepo.ts index 8f7c1f2568163..b44523fe14592 100644 --- a/packages/workspace/src/generators/convert-to-monorepo/convert-to-monorepo.ts +++ b/packages/workspace/src/generators/convert-to-monorepo/convert-to-monorepo.ts @@ -19,9 +19,13 @@ export async function monorepoGenerator(tree: Tree, options: {}) { // Need to determine libs vs packages directory base on the type of root project. for (const [, project] of projects) { - if (project.root === '.') rootProject = project; - projectsToMove.unshift(project); // move the root project 1st + if (project.root === '.') { + rootProject = project; + } else { + projectsToMove.push(project); + } } + projectsToMove.unshift(rootProject); // move the root project 1st // Currently, Nx only handles apps+libs or packages. You cannot mix and match them. // If the standalone project is an app (React, Angular, etc), then use apps+libs. diff --git a/packages/workspace/src/generators/move/lib/extract-base-configs.ts b/packages/workspace/src/generators/move/lib/extract-base-configs.ts index 37b9c4affab6d..15cf45f5a7a62 100644 --- a/packages/workspace/src/generators/move/lib/extract-base-configs.ts +++ b/packages/workspace/src/generators/move/lib/extract-base-configs.ts @@ -1,4 +1,9 @@ -import { joinPathFragments, ProjectConfiguration, Tree } from '@nx/devkit'; +import { + getProjects, + joinPathFragments, + ProjectConfiguration, + Tree, +} from '@nx/devkit'; export function maybeExtractTsConfigBase(tree: Tree): void { let extractTsConfigBase: any; @@ -22,27 +27,19 @@ export async function maybeExtractJestConfigBase(tree: Tree): Promise { await jestInitGenerator(tree, {}); } -export function maybeExtractEslintConfigIfRootProject( +export function maybeMigrateEslintConfigIfRootProject( tree: Tree, rootProject: ProjectConfiguration ): void { - if (rootProject.root !== '.') return; - if (tree.exists('.eslintrc.base.json')) return; - let migrateConfigToMonorepoStyle: any; - try { - migrateConfigToMonorepoStyle = require('@nx/' + - 'eslint/src/generators/init/init-migration').migrateConfigToMonorepoStyle; - } catch { - // eslint not installed - } - // Only need to handle migrating the root rootProject. - // If other libs/apps exist, then this migration is already done by `@nx/eslint:lint-rootProject` generator. - migrateConfigToMonorepoStyle?.( - [rootProject], + const { migrateConfigToMonorepoStyle } = require('@nx/' + + 'eslint/src/generators/init/init-migration'); + migrateConfigToMonorepoStyle( + getProjects(tree), tree, tree.exists(joinPathFragments(rootProject.root, 'jest.config.ts')) || tree.exists(joinPathFragments(rootProject.root, 'jest.config.js')) ? 'jest' - : 'none' + : 'none', + true ); } diff --git a/packages/workspace/src/generators/move/move.spec.ts b/packages/workspace/src/generators/move/move.spec.ts index 3b825105ab8c1..faef9bfc62856 100644 --- a/packages/workspace/src/generators/move/move.spec.ts +++ b/packages/workspace/src/generators/move/move.spec.ts @@ -1,4 +1,4 @@ -import { readJson, Tree, updateJson } from '@nx/devkit'; +import { readJson, Tree, updateJson, writeJson } from '@nx/devkit'; import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing'; import { moveGenerator } from './move'; // nx-ignore-next-line @@ -173,7 +173,18 @@ describe('move', () => { // Test that root configs are extracted expect(tree.exists('tsconfig.base.json')).toBeTruthy(); expect(tree.exists('jest.config.ts')).toBeTruthy(); - expect(tree.exists('.eslintrc.base.json')).toBeTruthy(); + expect(tree.exists('.eslintrc.base.json')).not.toBeTruthy(); + expect(tree.exists('.eslintrc.json')).toBeTruthy(); + + // Test that eslint migration was done + expect(readJson(tree, 'my-lib/.eslintrc.json').extends) + .toMatchInlineSnapshot(` + [ + "../.eslintrc.json", + ] + `); + expect(readJson(tree, 'my-lib/.eslintrc.json').plugins).not.toBeDefined(); + expect(readJson(tree, '.eslintrc.json').plugins).toEqual(['@nx']); }); it('should support moving standalone repos', async () => { @@ -190,6 +201,8 @@ describe('move', () => { style: 'css', projectNameAndRootFormat: 'as-provided', }); + expect(readJson(tree, '.eslintrc.json').plugins).toEqual(['@nx']); + expect(readJson(tree, 'e2e/.eslintrc.json').plugins).toEqual(['@nx']); // Test that this does not get moved tree.write('other-lib/index.ts', ''); @@ -200,6 +213,14 @@ describe('move', () => { destination: 'apps/react-app', projectNameAndRootFormat: 'as-provided', }); + + // expect both eslint configs to have been changed + expect(tree.exists('.eslintrc.json')).toBeDefined(); + expect( + readJson(tree, 'apps/react-app/.eslintrc.json').plugins + ).toBeUndefined(); + expect(readJson(tree, 'e2e/.eslintrc.json').plugins).toBeUndefined(); + await moveGenerator(tree, { projectName: 'e2e', updateImportPath: false, diff --git a/packages/workspace/src/generators/move/move.ts b/packages/workspace/src/generators/move/move.ts index 15f1037038eec..3965f128c0775 100644 --- a/packages/workspace/src/generators/move/move.ts +++ b/packages/workspace/src/generators/move/move.ts @@ -21,7 +21,7 @@ import { updateProjectRootFiles } from './lib/update-project-root-files'; import { updateReadme } from './lib/update-readme'; import { updateStorybookConfig } from './lib/update-storybook-config'; import { - maybeExtractEslintConfigIfRootProject, + maybeMigrateEslintConfigIfRootProject, maybeExtractJestConfigBase, maybeExtractTsConfigBase, } from './lib/extract-base-configs'; @@ -42,7 +42,6 @@ export async function moveGeneratorInternal(tree: Tree, rawSchema: Schema) { if (projectConfig.root === '.') { maybeExtractTsConfigBase(tree); await maybeExtractJestConfigBase(tree); - maybeExtractEslintConfigIfRootProject(tree, projectConfig); // Reload config since it has been updated after extracting base configs projectConfig = readProjectConfiguration(tree, rawSchema.projectName); } @@ -62,6 +61,11 @@ export async function moveGeneratorInternal(tree: Tree, rawSchema: Schema) { updateDefaultProject(tree, schema); updateImplicitDependencies(tree, schema); + if (projectConfig.root === '.') { + // we want to migrate eslint config once the root project files are moved + maybeMigrateEslintConfigIfRootProject(tree, projectConfig); + } + await runAngularPlugin(tree, schema); if (!schema.skipFormat) { From 8332c27594c151511e7cc258de3067aa8c03c9aa Mon Sep 17 00:00:00 2001 From: Miroslav Jonas Date: Tue, 12 Dec 2023 14:23:23 +0100 Subject: [PATCH 2/3] fix(linter): support moving of migrated eslint configurations --- .../src/generators/init/init-migration.ts | 67 +++++++++++++------ .../move/lib/extract-base-configs.ts | 16 +++-- .../src/generators/move/move.spec.ts | 57 ++++++++++++++++ 3 files changed, 112 insertions(+), 28 deletions(-) diff --git a/packages/eslint/src/generators/init/init-migration.ts b/packages/eslint/src/generators/init/init-migration.ts index 79eaeac2736e6..94468632a7b92 100644 --- a/packages/eslint/src/generators/init/init-migration.ts +++ b/packages/eslint/src/generators/init/init-migration.ts @@ -29,28 +29,40 @@ export function migrateConfigToMonorepoStyle( tree: Tree, unitTestRunner: string ): void { - if (useFlatConfig(tree)) { - // we need this for the compat - addDependenciesToPackageJson( - tree, - {}, - { - '@eslint/js': eslintVersion, - } - ); - tree.write( - tree.exists('eslint.config.js') - ? 'eslint.base.config.js' - : 'eslint.config.js', - getGlobalFlatEslintConfiguration(unitTestRunner) - ); + const rootEslintConfig = findEslintFile(tree); + let skipCleanup = false; + if ( + rootEslintConfig?.match(/\.base\./) && + !projects.some((p) => p.root === '.') + ) { + // if the migration has been run already, we need to rename the base config + // and only update the extends paths + tree.rename(rootEslintConfig, rootEslintConfig.replace('.base.', '.')); + skipCleanup = true; } else { - const eslintFile = findEslintFile(tree, '.'); - writeJson( - tree, - eslintFile ? '.eslintrc.base.json' : '.eslintrc.json', - getGlobalEsLintConfiguration(unitTestRunner) - ); + if (useFlatConfig(tree)) { + // we need this for the compat + addDependenciesToPackageJson( + tree, + {}, + { + '@eslint/js': eslintVersion, + } + ); + tree.write( + tree.exists('eslint.config.js') + ? 'eslint.base.config.js' + : 'eslint.config.js', + getGlobalFlatEslintConfiguration(unitTestRunner) + ); + } else { + const eslintFile = findEslintFile(tree, '.'); + writeJson( + tree, + eslintFile ? '.eslintrc.base.json' : '.eslintrc.json', + getGlobalEsLintConfiguration(unitTestRunner) + ); + } } // update extends in all projects' eslint configs @@ -61,7 +73,18 @@ export function migrateConfigToMonorepoStyle( lintTarget.options?.eslintConfig || findEslintFile(tree, project.root); if (eslintFile) { const projectEslintPath = joinPathFragments(project.root, eslintFile); - migrateEslintFile(projectEslintPath, tree); + if (skipCleanup) { + const content = tree.read(projectEslintPath, 'utf-8'); + tree.write( + projectEslintPath, + content.replace( + rootEslintConfig, + rootEslintConfig.replace('.base.', '.') + ) + ); + } else { + migrateEslintFile(projectEslintPath, tree); + } } } }); diff --git a/packages/workspace/src/generators/move/lib/extract-base-configs.ts b/packages/workspace/src/generators/move/lib/extract-base-configs.ts index 15cf45f5a7a62..852c3c652f748 100644 --- a/packages/workspace/src/generators/move/lib/extract-base-configs.ts +++ b/packages/workspace/src/generators/move/lib/extract-base-configs.ts @@ -31,15 +31,19 @@ export function maybeMigrateEslintConfigIfRootProject( tree: Tree, rootProject: ProjectConfiguration ): void { - const { migrateConfigToMonorepoStyle } = require('@nx/' + - 'eslint/src/generators/init/init-migration'); - migrateConfigToMonorepoStyle( - getProjects(tree), + let migrateConfigToMonorepoStyle: any; + try { + migrateConfigToMonorepoStyle = require('@nx/' + + 'eslint/src/generators/init/init-migration').migrateConfigToMonorepoStyle; + } catch { + // eslint not installed + } + migrateConfigToMonorepoStyle?.( + Array.from(getProjects(tree).values()), tree, tree.exists(joinPathFragments(rootProject.root, 'jest.config.ts')) || tree.exists(joinPathFragments(rootProject.root, 'jest.config.js')) ? 'jest' - : 'none', - true + : 'none' ); } diff --git a/packages/workspace/src/generators/move/move.spec.ts b/packages/workspace/src/generators/move/move.spec.ts index faef9bfc62856..e65032d09233f 100644 --- a/packages/workspace/src/generators/move/move.spec.ts +++ b/packages/workspace/src/generators/move/move.spec.ts @@ -244,6 +244,63 @@ describe('move', () => { `); }); + it('should correctly move standalone repos that have migrated eslint config', async () => { + // Test that these are not moved + tree.write('.gitignore', ''); + tree.write('README.md', ''); + + await applicationGenerator(tree, { + name: 'react-app', + rootProject: true, + unitTestRunner: 'jest', + e2eTestRunner: 'cypress', + linter: 'eslint', + style: 'css', + projectNameAndRootFormat: 'as-provided', + }); + await libraryGenerator(tree, { + name: 'my-lib', + bundler: 'tsc', + buildable: true, + unitTestRunner: 'jest', + linter: 'eslint', + directory: 'my-lib', + projectNameAndRootFormat: 'as-provided', + }); + // assess the correct starting position + expect(tree.exists('.eslintrc.base.json')).toBeTruthy(); + expect(readJson(tree, '.eslintrc.json').plugins).not.toBeDefined(); + expect(readJson(tree, '.eslintrc.json').extends).toEqual([ + 'plugin:@nx/react', + './.eslintrc.base.json', + ]); + expect(readJson(tree, 'e2e/.eslintrc.json').plugins).not.toBeDefined(); + expect(readJson(tree, 'e2e/.eslintrc.json').extends).toEqual([ + 'plugin:cypress/recommended', + '../.eslintrc.base.json', + ]); + + await moveGenerator(tree, { + projectName: 'react-app', + updateImportPath: false, + destination: 'apps/react-app', + projectNameAndRootFormat: 'as-provided', + }); + + // expect both eslint configs to have been changed + expect(tree.exists('.eslintrc.json')).toBeTruthy(); + expect(tree.exists('.eslintrc.base.json')).toBeFalsy(); + + expect(readJson(tree, 'apps/react-app/.eslintrc.json').extends).toEqual([ + 'plugin:@nx/react', + '../../.eslintrc.json', + ]); + expect(readJson(tree, 'e2e/.eslintrc.json').extends).toEqual([ + 'plugin:cypress/recommended', + '../.eslintrc.json', + ]); + }); + it('should move project correctly when --project-name-and-root-format=derived', async () => { await libraryGenerator(tree, { name: 'my-lib', From acc4ea70e815580e0deb9878a3cdbfdf7bd3edf3 Mon Sep 17 00:00:00 2001 From: Miroslav Jonas Date: Tue, 12 Dec 2023 14:47:42 +0100 Subject: [PATCH 3/3] fix(linter): fix broken test --- .../generators/convert-to-monorepo/convert-to-monorepo.spec.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/workspace/src/generators/convert-to-monorepo/convert-to-monorepo.spec.ts b/packages/workspace/src/generators/convert-to-monorepo/convert-to-monorepo.spec.ts index 1fe03f5be260b..358f2455d7b10 100644 --- a/packages/workspace/src/generators/convert-to-monorepo/convert-to-monorepo.spec.ts +++ b/packages/workspace/src/generators/convert-to-monorepo/convert-to-monorepo.spec.ts @@ -167,7 +167,6 @@ describe('monorepo generator', () => { // Extracted base config files expect(tree.exists('tsconfig.base.json')).toBeTruthy(); - expect(tree.exists('.eslintrc.base.json')).toBeTruthy(); expect(tree.exists('jest.config.ts')).toBeTruthy(); }); });