diff --git a/docs/generated/packages/eslint-plugin/documents/dependency-checks.md b/docs/generated/packages/eslint-plugin/documents/dependency-checks.md index c905320bf852e..10733d0326170 100644 --- a/docs/generated/packages/eslint-plugin/documents/dependency-checks.md +++ b/docs/generated/packages/eslint-plugin/documents/dependency-checks.md @@ -8,9 +8,13 @@ We use the version numbers of the installed packages when checking whether the v ## Usage -You can use the `dependency-checks` rule by adding it to your ESLint rules configuration: +Library generators from `@nx` packages will configure this rule automatically when you opt-in for bundler/build setup. This rule is intended for publishable/buildable libraries, so it will only run if a `build` target is detected in the configuration (this name can be modified - see [options](#options)). -```jsonc {% fileName=".eslintrc.json" %} +### Manual setup + +To set it up manually for existing libraries, you need to add the `dependency-checks` rule to your project's ESLint configuration: + +```jsonc {% fileName="/.eslintrc.json" %} { // ... more ESLint config here "overrides": [ @@ -26,9 +30,9 @@ You can use the `dependency-checks` rule by adding it to your ESLint rules confi } ``` -Linting `JSON` files is not enabled by default, so you will also need to add `package.json` to the `lintFilePatterns`: +Additionally, you need to adjust your `lintFilePatterns` to include the project's `package.json` file:: -```jsonc {% fileName="project.json" %} +```jsonc {% fileName="/project.json" %} { // ... project.json config "targets": { @@ -47,6 +51,8 @@ Linting `JSON` files is not enabled by default, so you will also need to add `pa } ``` +### Overriding defaults + Sometimes we intentionally want to add or remove a dependency from our `package.json` despite what the rule suggests. We can use the rule's options to override default behavior: ```jsonc {% fileName=".eslintrc.json" %} @@ -54,7 +60,11 @@ Sometimes we intentionally want to add or remove a dependency from our `package. "@nx/dependency-checks": [ "error", { - // for available options check below + "buildTargets": ["build", "custom-build"], // add non standard build target names + "ignoredDependencies": ["lodash"], // these libs will be omitted from checks + "checkMissingDependencies": true, // toggle to disable + "checkObsoleteDependencies": true, // toggle to disable + "checkVersionMismatches": true // toggle to disable } ] } diff --git a/docs/shared/packages/linter/dependency-checks.md b/docs/shared/packages/linter/dependency-checks.md index c905320bf852e..10733d0326170 100644 --- a/docs/shared/packages/linter/dependency-checks.md +++ b/docs/shared/packages/linter/dependency-checks.md @@ -8,9 +8,13 @@ We use the version numbers of the installed packages when checking whether the v ## Usage -You can use the `dependency-checks` rule by adding it to your ESLint rules configuration: +Library generators from `@nx` packages will configure this rule automatically when you opt-in for bundler/build setup. This rule is intended for publishable/buildable libraries, so it will only run if a `build` target is detected in the configuration (this name can be modified - see [options](#options)). -```jsonc {% fileName=".eslintrc.json" %} +### Manual setup + +To set it up manually for existing libraries, you need to add the `dependency-checks` rule to your project's ESLint configuration: + +```jsonc {% fileName="/.eslintrc.json" %} { // ... more ESLint config here "overrides": [ @@ -26,9 +30,9 @@ You can use the `dependency-checks` rule by adding it to your ESLint rules confi } ``` -Linting `JSON` files is not enabled by default, so you will also need to add `package.json` to the `lintFilePatterns`: +Additionally, you need to adjust your `lintFilePatterns` to include the project's `package.json` file:: -```jsonc {% fileName="project.json" %} +```jsonc {% fileName="/project.json" %} { // ... project.json config "targets": { @@ -47,6 +51,8 @@ Linting `JSON` files is not enabled by default, so you will also need to add `pa } ``` +### Overriding defaults + Sometimes we intentionally want to add or remove a dependency from our `package.json` despite what the rule suggests. We can use the rule's options to override default behavior: ```jsonc {% fileName=".eslintrc.json" %} @@ -54,7 +60,11 @@ Sometimes we intentionally want to add or remove a dependency from our `package. "@nx/dependency-checks": [ "error", { - // for available options check below + "buildTargets": ["build", "custom-build"], // add non standard build target names + "ignoredDependencies": ["lodash"], // these libs will be omitted from checks + "checkMissingDependencies": true, // toggle to disable + "checkObsoleteDependencies": true, // toggle to disable + "checkVersionMismatches": true // toggle to disable } ] } diff --git a/e2e/expo/src/expo.test.ts b/e2e/expo/src/expo.test.ts index 029d5f8a9ff8f..9b2abff7c93cb 100644 --- a/e2e/expo/src/expo.test.ts +++ b/e2e/expo/src/expo.test.ts @@ -14,6 +14,7 @@ import { runCommandUntil, uniq, updateFile, + updateJson, } from '@nx/e2e/utils'; import { join } from 'path'; @@ -24,6 +25,16 @@ describe('expo', () => { beforeAll(() => { proj = newProject(); + // we create empty preset above which skips creation of `production` named input + updateJson('nx.json', (nxJson) => { + nxJson.namedInputs = { + default: ['{projectRoot}/**/*', 'sharedGlobals'], + production: ['default'], + sharedGlobals: [], + }; + nxJson.targetDefaults.build.inputs = ['production', '^production']; + return nxJson; + }); runCLI(`generate @nx/expo:application ${appName} --no-interactive`); runCLI( `generate @nx/expo:library ${libName} --buildable --publishable --importPath=${proj}/${libName}` diff --git a/e2e/linter/src/linter.test.ts b/e2e/linter/src/linter.test.ts index 47e8608119df1..e2f23bf602a4b 100644 --- a/e2e/linter/src/linter.test.ts +++ b/e2e/linter/src/linter.test.ts @@ -478,12 +478,12 @@ describe('Linter', () => { content.replace(/return .*;/, `return names(${mylib}).className;`) ); - // output should now report missing dependencies section + // output should now report missing dependency out = runCLI(`lint ${mylib}`, { silenceError: true }); expect(out).toContain('they are missing'); expect(out).toContain('@nx/devkit'); - // should fix the missing section issue + // should fix the missing dependency issue out = runCLI(`lint ${mylib} --fix`, { silenceError: true }); expect(out).toContain( `Successfully ran target lint for project ${mylib}` diff --git a/e2e/nx-misc/src/workspace.test.ts b/e2e/nx-misc/src/workspace.test.ts index e96d28420e66c..ad815edcac1b3 100644 --- a/e2e/nx-misc/src/workspace.test.ts +++ b/e2e/nx-misc/src/workspace.test.ts @@ -15,7 +15,6 @@ import { getPackageManagerCommand, getSelectedPackageManager, runCommand, - runCreateWorkspace, } from '@nx/e2e/utils'; let proj: string; @@ -176,6 +175,7 @@ describe('Workspace Tests', () => { expect(project.sourceRoot).toBe(`${newPath}/src`); expect(project.targets.lint.options.lintFilePatterns).toEqual([ `libs/shared/${lib1}/data-access/**/*.ts`, + `libs/shared/${lib1}/data-access/package.json`, ]); /** @@ -306,6 +306,7 @@ describe('Workspace Tests', () => { expect(project.targets.lint.options.lintFilePatterns).toEqual([ `libs/shared/${lib1}/data-access/**/*.ts`, + `libs/shared/${lib1}/data-access/package.json`, ]); /** @@ -434,6 +435,7 @@ describe('Workspace Tests', () => { expect(project.sourceRoot).toBe(`${newPath}/src`); expect(project.targets.lint.options.lintFilePatterns).toEqual([ `packages/shared/${lib1}/data-access/**/*.ts`, + `packages/shared/${lib1}/data-access/package.json`, ]); expect(project.tags).toEqual([]); @@ -569,6 +571,7 @@ describe('Workspace Tests', () => { expect(project.sourceRoot).toBe(`${newPath}/src`); expect(project.targets.lint.options.lintFilePatterns).toEqual([ `libs/${lib1}/data-access/**/*.ts`, + `libs/${lib1}/data-access/package.json`, ]); /** @@ -682,6 +685,7 @@ describe('Workspace Tests', () => { expect(project.sourceRoot).toBe(`${newPath}/src`); expect(project.targets.lint.options.lintFilePatterns).toEqual([ `libs/shared/${lib1}/data-access/**/*.ts`, + `libs/shared/${lib1}/data-access/package.json`, ]); /** diff --git a/e2e/react-native/src/react-native.test.ts b/e2e/react-native/src/react-native.test.ts index a7b4bf482f60d..7de73a0fee948 100644 --- a/e2e/react-native/src/react-native.test.ts +++ b/e2e/react-native/src/react-native.test.ts @@ -14,6 +14,7 @@ import { runCommandUntil, uniq, updateFile, + updateJson, } from '@nx/e2e/utils'; import { ChildProcess } from 'child_process'; import { join } from 'path'; @@ -25,6 +26,16 @@ describe('react native', () => { beforeAll(() => { proj = newProject(); + // we create empty preset above which skips creation of `production` named input + updateJson('nx.json', (nxJson) => { + nxJson.namedInputs = { + default: ['{projectRoot}/**/*', 'sharedGlobals'], + production: ['default'], + sharedGlobals: [], + }; + nxJson.targetDefaults.build.inputs = ['production', '^production']; + return nxJson; + }); runCLI( `generate @nx/react-native:application ${appName} --install=false --no-interactive` ); diff --git a/packages/expo/src/generators/library/library.ts b/packages/expo/src/generators/library/library.ts index 6145b2cdb1d7c..8b431b39f5589 100644 --- a/packages/expo/src/generators/library/library.ts +++ b/packages/expo/src/generators/library/library.ts @@ -20,7 +20,11 @@ import { addTsConfigPath, getRelativePathToRootTsConfig } from '@nx/js'; import init from '../init/init'; import { addLinting } from '../../utils/add-linting'; import { addJest } from '../../utils/add-jest'; -import { nxVersion } from '../../utils/versions'; +import { + nxVersion, + reactNativeVersion, + reactVersion, +} from '../../utils/versions'; import { NormalizedSchema, normalizeOptions } from './lib/normalize-options'; import { Schema } from './schema'; @@ -193,6 +197,10 @@ function createFiles(host: Tree, options: NormalizedSchema) { function updateLibPackageNpmScope(host: Tree, options: NormalizedSchema) { return updateJson(host, `${options.projectRoot}/package.json`, (json) => { json.name = options.importPath; + json.peerDependencies = { + react: reactVersion, + 'react-native': reactNativeVersion, + }; return json; }); } diff --git a/packages/jest/src/generators/init/init.ts b/packages/jest/src/generators/init/init.ts index 6dd9e21ce856e..a810c62e00caf 100644 --- a/packages/jest/src/generators/init/init.ts +++ b/packages/jest/src/generators/init/init.ts @@ -126,7 +126,9 @@ function addTestInputs(tree: Tree) { // Remove jest.config.js/ts '!{projectRoot}/jest.config.[jt]s', // Remove test-setup.js/ts - '!{projectRoot}/src/test-setup.[jt]s' + // TODO(meeroslav) this should be standardized + '!{projectRoot}/src/test-setup.[jt]s', + '!{projectRoot}/test-setup.[jt]s' ); // Dedupe and set nxJson.namedInputs.production = Array.from(new Set(productionFileSet)); diff --git a/packages/js/src/generators/library/library.spec.ts b/packages/js/src/generators/library/library.spec.ts index 21c6f00a1d6bc..e0b546f1df59d 100644 --- a/packages/js/src/generators/library/library.spec.ts +++ b/packages/js/src/generators/library/library.spec.ts @@ -436,7 +436,10 @@ describe('lib', () => { executor: '@nx/linter:eslint', outputs: ['{options.outputFile}'], options: { - lintFilePatterns: ['libs/my-lib/**/*.ts'], + lintFilePatterns: [ + 'libs/my-lib/**/*.ts', + 'libs/my-lib/package.json', + ], }, }); }); @@ -480,6 +483,15 @@ describe('lib', () => { ], "rules": {}, }, + { + "files": [ + "*.json", + ], + "parser": "jsonc-eslint-parser", + "rules": { + "@nx/dependency-checks": "error", + }, + }, ], } `); @@ -500,7 +512,10 @@ describe('lib', () => { executor: '@nx/linter:eslint', outputs: ['{options.outputFile}'], options: { - lintFilePatterns: ['libs/my-dir/my-lib/**/*.ts'], + lintFilePatterns: [ + 'libs/my-dir/my-lib/**/*.ts', + 'libs/my-dir/my-lib/package.json', + ], }, }); }); @@ -545,6 +560,15 @@ describe('lib', () => { ], "rules": {}, }, + { + "files": [ + "*.json", + ], + "parser": "jsonc-eslint-parser", + "rules": { + "@nx/dependency-checks": "error", + }, + }, ], } `); @@ -626,7 +650,10 @@ describe('lib', () => { expect( readProjectConfiguration(tree, 'my-dir-my-lib').targets.lint.options .lintFilePatterns - ).toEqual(['libs/my-dir/my-lib/**/*.js']); + ).toEqual([ + 'libs/my-dir/my-lib/**/*.js', + 'libs/my-dir/my-lib/package.json', + ]); expect(readJson(tree, 'libs/my-dir/my-lib/.eslintrc.json')) .toMatchInlineSnapshot(` { @@ -660,6 +687,15 @@ describe('lib', () => { ], "rules": {}, }, + { + "files": [ + "*.json", + ], + "parser": "jsonc-eslint-parser", + "rules": { + "@nx/dependency-checks": "error", + }, + }, ], } `); diff --git a/packages/js/src/generators/library/library.ts b/packages/js/src/generators/library/library.ts index df93737beb3ee..550f12f33f421 100644 --- a/packages/js/src/generators/library/library.ts +++ b/packages/js/src/generators/library/library.ts @@ -35,9 +35,11 @@ import { swcHelpersVersion, tsLibVersion, typesNodeVersion, + swcNodeVersion, + swcCoreVersion, } from '../../utils/versions'; import jsInitGenerator from '../init/init'; -import { PackageJson } from 'nx/src/utils/package-json'; +import { type PackageJson } from 'nx/src/utils/package-json'; import setupVerdaccio from '../setup-verdaccio/generator'; import { tsConfigBaseOptions } from '../../utils/typescript/create-ts-config'; @@ -130,6 +132,10 @@ export async function projectGenerator( ]); } + if (options.bundler !== 'none') { + addBundlerDependencies(tree, options); + } + if (!options.skipFormat) { await formatFiles(tree); } @@ -249,6 +255,23 @@ export async function addLint( }); } +function addBundlerDependencies(tree: Tree, options: NormalizedSchema) { + updateJson(tree, `${options.projectRoot}/package.json`, (json) => { + if (options.bundler === 'tsc') { + json.dependencies = { + ...json.dependencies, + tslib: tsLibVersion, + }; + } else if (options.bundler === 'swc') { + json.dependencies = { + ...json.dependencies, + '@swc/helpers': swcHelpersVersion, + }; + } + return json; + }); +} + function updateTsConfig(tree: Tree, options: NormalizedSchema) { updateJson(tree, join(options.projectRoot, 'tsconfig.json'), (json) => { if (options.strict) { diff --git a/packages/linter/src/generators/init/init.ts b/packages/linter/src/generators/init/init.ts index 582f17b600168..d9023389f943a 100644 --- a/packages/linter/src/generators/init/init.ts +++ b/packages/linter/src/generators/init/init.ts @@ -47,6 +47,9 @@ function addTargetDefaults(tree: Tree) { updateNxJson(tree, nxJson); } +/** + * Initializes ESLint configuration in a workspace and adds necessary dependencies. + */ function initEsLint(tree: Tree, options: LinterInitOptions): GeneratorCallback { if (findEslintFile(tree)) { return () => {}; diff --git a/packages/linter/src/generators/lint-project/__snapshots__/lint-project.spec.ts.snap b/packages/linter/src/generators/lint-project/__snapshots__/lint-project.spec.ts.snap deleted file mode 100644 index 7bc2691d567ce..0000000000000 --- a/packages/linter/src/generators/lint-project/__snapshots__/lint-project.spec.ts.snap +++ /dev/null @@ -1,45 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`@nx/linter:lint-project --linter eslint should extend to .eslintrc.js when an .eslintrc.js already exist 1`] = ` -"{ - "extends": ["../../.eslintrc.js"], - "ignorePatterns": ["!**/*"], - "overrides": [ - { - "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], - "rules": {} - }, - { - "files": ["*.ts", "*.tsx"], - "rules": {} - }, - { - "files": ["*.js", "*.jsx"], - "rules": {} - } - ] -} -" -`; - -exports[`@nx/linter:lint-project --linter eslint should generate a eslint config 1`] = ` -"{ - "extends": ["../../.eslintrc.json"], - "ignorePatterns": ["!**/*"], - "overrides": [ - { - "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], - "rules": {} - }, - { - "files": ["*.ts", "*.tsx"], - "rules": {} - }, - { - "files": ["*.js", "*.jsx"], - "rules": {} - } - ] -} -" -`; diff --git a/packages/linter/src/generators/lint-project/lint-project.spec.ts b/packages/linter/src/generators/lint-project/lint-project.spec.ts index cbc7f0d9ace38..0aa0aded2d58f 100644 --- a/packages/linter/src/generators/lint-project/lint-project.spec.ts +++ b/packages/linter/src/generators/lint-project/lint-project.spec.ts @@ -19,66 +19,160 @@ describe('@nx/linter:lint-project', () => { tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' }); addProjectConfiguration(tree, 'test-lib', { root: 'libs/test-lib', - targets: {}, + projectType: 'library', + targets: { + test: { + command: 'echo test', + }, + }, + }); + addProjectConfiguration(tree, 'buildable-lib', { + root: 'libs/buildable-lib', + projectType: 'library', + targets: { + build: { + command: 'echo build', + }, + }, }); }); - describe('--linter', () => { - describe('eslint', () => { - it('should generate a eslint config', async () => { - await lintProjectGenerator(tree, { - ...defaultOptions, - linter: Linter.EsLint, - eslintFilePatterns: ['**/*.ts'], - project: 'test-lib', - setParserOptionsProject: false, - }); + it('should generate a eslint config and configure the target in project configuration', async () => { + await lintProjectGenerator(tree, { + ...defaultOptions, + linter: Linter.EsLint, + eslintFilePatterns: ['libs/test-lib/**/*.ts'], + project: 'test-lib', + setParserOptionsProject: false, + }); - expect( - tree.read('libs/test-lib/.eslintrc.json', 'utf-8') - ).toMatchSnapshot(); - }); + expect(tree.read('libs/test-lib/.eslintrc.json', 'utf-8')) + .toMatchInlineSnapshot(` + "{ + "extends": ["../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] + } + " + `); + + const projectConfig = readProjectConfiguration(tree, 'test-lib'); + expect(projectConfig.targets.lint).toMatchInlineSnapshot(` + { + "executor": "@nx/linter:eslint", + "options": { + "lintFilePatterns": [ + "libs/test-lib/**/*.ts", + ], + }, + "outputs": [ + "{options.outputFile}", + ], + } + `); + }); - it('should configure the target in project configuration', async () => { - await lintProjectGenerator(tree, { - ...defaultOptions, - linter: Linter.EsLint, - eslintFilePatterns: ['**/*.ts'], - project: 'test-lib', - setParserOptionsProject: false, - }); + it('should generate a eslint config and configure the target for buildable library', async () => { + await lintProjectGenerator(tree, { + ...defaultOptions, + linter: Linter.EsLint, + eslintFilePatterns: ['libs/buildable-lib/**/*.ts'], + project: 'buildable-lib', + setParserOptionsProject: false, + }); - const projectConfig = readProjectConfiguration(tree, 'test-lib'); - expect(projectConfig.targets.lint).toMatchInlineSnapshot(` + expect(tree.read('libs/buildable-lib/.eslintrc.json', 'utf-8')) + .toMatchInlineSnapshot(` + "{ + "extends": ["../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ { - "executor": "@nx/linter:eslint", - "options": { - "lintFilePatterns": [ - "**/*.ts", - ], - }, - "outputs": [ - "{options.outputFile}", - ], + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.json"], + "parser": "jsonc-eslint-parser", + "rules": { + "@nx/dependency-checks": "error" + } } - `); - }); + ] + } + " + `); - it('should extend to .eslintrc.js when an .eslintrc.js already exist', async () => { - tree.write('.eslintrc.js', '{}'); + const projectConfig = readProjectConfiguration(tree, 'buildable-lib'); + expect(projectConfig.targets.lint).toMatchInlineSnapshot(` + { + "executor": "@nx/linter:eslint", + "options": { + "lintFilePatterns": [ + "libs/buildable-lib/**/*.ts", + "libs/buildable-lib/package.json", + ], + }, + "outputs": [ + "{options.outputFile}", + ], + } + `); + }); - await lintProjectGenerator(tree, { - ...defaultOptions, - linter: Linter.EsLint, - eslintFilePatterns: ['**/*.ts'], - project: 'test-lib', - setParserOptionsProject: false, - }); + it('should extend to .eslintrc.js when an .eslintrc.js already exist', async () => { + tree.write('.eslintrc.js', '{}'); - expect( - tree.read('libs/test-lib/.eslintrc.json', 'utf-8') - ).toMatchSnapshot(); - }); + await lintProjectGenerator(tree, { + ...defaultOptions, + linter: Linter.EsLint, + eslintFilePatterns: ['libs/test-lib/**/*.ts'], + project: 'test-lib', + setParserOptionsProject: false, }); + + expect(tree.read('libs/test-lib/.eslintrc.json', 'utf-8')) + .toMatchInlineSnapshot(` + "{ + "extends": ["../../.eslintrc.js"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] + } + " + `); }); }); diff --git a/packages/linter/src/generators/lint-project/lint-project.ts b/packages/linter/src/generators/lint-project/lint-project.ts index 4667991b549a2..a7fc9abdf4cea 100644 --- a/packages/linter/src/generators/lint-project/lint-project.ts +++ b/packages/linter/src/generators/lint-project/lint-project.ts @@ -29,57 +29,6 @@ interface LintProjectOptions { rootProject?: boolean; } -function createEsLintConfiguration( - tree: Tree, - projectConfig: ProjectConfiguration, - setParserOptionsProject: boolean -) { - const eslintConfig = findEslintFile(tree); - writeJson(tree, join(projectConfig.root, `.eslintrc.json`), { - extends: eslintConfig - ? [`${offsetFromRoot(projectConfig.root)}${eslintConfig}`] - : undefined, - // Include project files to be linted since the global one excludes all files. - ignorePatterns: ['!**/*'], - overrides: [ - { - 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: {}, - }, - ], - }); -} - export function mapLintPattern( projectRoot: string, extension: string, @@ -101,11 +50,16 @@ export async function lintProjectGenerator( }); const projectConfig = readProjectConfiguration(tree, options.project); + const lintFilePatterns = options.eslintFilePatterns; + if (isBuildableLibraryProject(projectConfig)) { + lintFilePatterns.push(`${projectConfig.root}/package.json`); + } + projectConfig.targets['lint'] = { executor: '@nx/linter:eslint', outputs: ['{options.outputFile}'], options: { - lintFilePatterns: options.eslintFilePatterns, + lintFilePatterns: lintFilePatterns, }, }; @@ -151,12 +105,81 @@ export async function lintProjectGenerator( return installTask; } +function createEsLintConfiguration( + tree: Tree, + projectConfig: ProjectConfiguration, + setParserOptionsProject: boolean +) { + const eslintConfig = findEslintFile(tree); + writeJson(tree, join(projectConfig.root, `.eslintrc.json`), { + extends: eslintConfig + ? [`${offsetFromRoot(projectConfig.root)}${eslintConfig}`] + : undefined, + // Include project files to be linted since the global one excludes all files. + ignorePatterns: ['!**/*'], + overrides: [ + { + 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: {}, + }, + ...(isBuildableLibraryProject(projectConfig) + ? [ + { + files: ['*.json'], + parser: 'jsonc-eslint-parser', + rules: { + '@nx/dependency-checks': 'error', + }, + }, + ] + : []), + ], + }); +} + +function isBuildableLibraryProject( + projectConfig: ProjectConfiguration +): boolean { + return ( + projectConfig.projectType === 'library' && + projectConfig.targets?.build && + !!projectConfig.targets.build + ); +} + /** * Detect based on the state of lint target configuration of the root project * if we should migrate eslint configs to monorepo style - * - * @param tree - * @returns */ function isMigrationToMonorepoNeeded( projects: Record, diff --git a/packages/plugin/src/generators/executor/executor.ts b/packages/plugin/src/generators/executor/executor.ts index d461931b3ac2a..e3705f412f8f4 100644 --- a/packages/plugin/src/generators/executor/executor.ts +++ b/packages/plugin/src/generators/executor/executor.ts @@ -16,6 +16,7 @@ import type { Schema } from './schema'; import * as path from 'path'; import { PackageJson } from 'nx/src/utils/package-json'; import pluginLintCheckGenerator from '../lint-checks/generator'; +import { nxVersion } from '../../utils/versions'; interface NormalizedSchema extends Schema { fileName: string; @@ -122,6 +123,18 @@ async function updateExecutorJson(host: Tree, options: NormalizedSchema) { options.skipLintChecks ); } + // add dependencies + updateJson( + host, + joinPathFragments(options.projectRoot, 'package.json'), + (json) => { + json.dependencies = { + '@nx/devkit': nxVersion, + ...json.dependencies, + }; + return json; + } + ); return updateJson(host, executorsPath, (json) => { let executors = json.executors ?? json.builders; diff --git a/packages/plugin/src/generators/generator/generator.ts b/packages/plugin/src/generators/generator/generator.ts index 4cbedc65e83eb..c228b2dca7508 100644 --- a/packages/plugin/src/generators/generator/generator.ts +++ b/packages/plugin/src/generators/generator/generator.ts @@ -17,6 +17,7 @@ import * as path from 'path'; import { hasGenerator } from '../../utils/has-generator'; import pluginLintCheckGenerator from '../lint-checks/generator'; import type { Schema } from './schema'; +import { nxVersion } from '../../utils/versions'; type NormalizedSchema = Schema & ReturnType & { @@ -140,6 +141,18 @@ async function updateGeneratorJson(host: Tree, options: NormalizedSchema) { options.skipFormat ); } + // add dependencies + updateJson( + host, + joinPathFragments(options.projectRoot, 'package.json'), + (json) => { + json.dependencies = { + '@nx/devkit': nxVersion, + ...json.dependencies, + }; + return json; + } + ); updateJson(host, generatorsPath, (json) => { let generators = json.generators ?? json.schematics; diff --git a/packages/plugin/src/generators/migration/migration.ts b/packages/plugin/src/generators/migration/migration.ts index 9e25e506e168a..b6fd7e4245a89 100644 --- a/packages/plugin/src/generators/migration/migration.ts +++ b/packages/plugin/src/generators/migration/migration.ts @@ -15,6 +15,7 @@ import type { Schema } from './schema'; import * as path from 'path'; import { addMigrationJsonChecks } from '../lint-checks/generator'; import { PackageJson, readNxMigrateConfig } from 'nx/src/utils/package-json'; +import { nxVersion } from '../../utils/versions'; interface NormalizedSchema extends Schema { projectRoot: string; projectSourceRoot: string; @@ -108,6 +109,12 @@ function updatePackageJson(host: Tree, options: NormalizedSchema) { preexistingValue.migrations = './migrations.json'; } + // add dependencies + json.dependencies = { + '@nx/devkit': nxVersion, + ...json.dependencies, + }; + return json; } ); diff --git a/packages/plugin/src/generators/plugin/plugin.ts b/packages/plugin/src/generators/plugin/plugin.ts index d53ae96c813ab..dfe9dddfb2345 100644 --- a/packages/plugin/src/generators/plugin/plugin.ts +++ b/packages/plugin/src/generators/plugin/plugin.ts @@ -8,6 +8,7 @@ import { readProjectConfiguration, runTasksInSerial, Tree, + updateJson, updateProjectConfiguration, } from '@nx/devkit'; import { libraryGenerator as jsLibraryGenerator } from '@nx/js'; @@ -21,6 +22,13 @@ import { addTsLibDependencies } from '@nx/js/src/utils/typescript/add-tslib-depe import { addSwcRegisterDependencies } from '@nx/js/src/utils/swc/add-swc-dependencies'; import type { Schema } from './schema'; +import { type PackageJson } from 'nx/src/utils/package-json'; +import { + swcCoreVersion, + swcHelpersVersion, + swcNodeVersion, + tsLibVersion, +} from '@nx/js/src/utils/versions'; const nxVersion = require('../../../package.json').version; diff --git a/packages/plugin/src/utils/versions.ts b/packages/plugin/src/utils/versions.ts index 1c1d5f8a7a6f3..22d65103c5213 100644 --- a/packages/plugin/src/utils/versions.ts +++ b/packages/plugin/src/utils/versions.ts @@ -1 +1,3 @@ +export const nxVersion = require('../../package.json').version; + export const jsoncEslintParserVersion = '^2.1.0'; diff --git a/packages/react-native/src/generators/library/library.ts b/packages/react-native/src/generators/library/library.ts index 5b7ede831563a..8f3710e84d54f 100644 --- a/packages/react-native/src/generators/library/library.ts +++ b/packages/react-native/src/generators/library/library.ts @@ -20,7 +20,11 @@ import { addTsConfigPath, getRelativePathToRootTsConfig } from '@nx/js'; import init from '../init/init'; import { addLinting } from '../../utils/add-linting'; import { addJest } from '../../utils/add-jest'; -import { nxVersion } from '../../utils/versions'; +import { + nxVersion, + reactNativeVersion, + reactVersion, +} from '../../utils/versions'; import { NormalizedSchema, normalizeOptions } from './lib/normalize-options'; import { Schema } from './schema'; @@ -192,6 +196,10 @@ function createFiles(host: Tree, options: NormalizedSchema) { function updateLibPackageNpmScope(host: Tree, options: NormalizedSchema) { return updateJson(host, `${options.projectRoot}/package.json`, (json) => { json.name = options.importPath; + json.peerDependencies = { + react: reactVersion, + 'react-native': reactNativeVersion, + }; return json; }); }