diff --git a/docs/generated/cli/add.md b/docs/generated/cli/add.md index 4183d0f8faaa6..cc8e359234370 100644 --- a/docs/generated/cli/add.md +++ b/docs/generated/cli/add.md @@ -49,6 +49,12 @@ Type: `string` The package name and optional version (e.g. `@nx/react` or `@nx/react@latest`) to install and initialize. If the version is not specified it will install the same version as the `nx` package for Nx core plugins or the latest version for other packages +### updatePackageScripts + +Type: `boolean` + +Update `package.json` scripts with inferred targets. Defaults to `true` when `NX_PCV3=true` and the package is a core Nx plugin + ### verbose Type: `boolean` diff --git a/docs/generated/packages/angular/generators/init.json b/docs/generated/packages/angular/generators/init.json index ee830114ec521..31227db2b162d 100644 --- a/docs/generated/packages/angular/generators/init.json +++ b/docs/generated/packages/angular/generators/init.json @@ -34,7 +34,6 @@ "default": false } }, - "additionalProperties": false, "presets": [] }, "description": "Initializes the `@nrwl/angular` plugin.", diff --git a/docs/generated/packages/cypress/generators/init.json b/docs/generated/packages/cypress/generators/init.json index f628822d0977c..7cf5184a2e68c 100644 --- a/docs/generated/packages/cypress/generators/init.json +++ b/docs/generated/packages/cypress/generators/init.json @@ -26,6 +26,12 @@ "x-priority": "internal", "description": "Keep existing dependencies versions", "default": false + }, + "updatePackageScripts": { + "type": "boolean", + "x-priority": "internal", + "description": "Update `package.json` scripts with inferred targets", + "default": false } }, "presets": [] diff --git a/docs/generated/packages/detox/generators/init.json b/docs/generated/packages/detox/generators/init.json index b1dc5ef02c86a..cddd1643b5d70 100644 --- a/docs/generated/packages/detox/generators/init.json +++ b/docs/generated/packages/detox/generators/init.json @@ -24,6 +24,12 @@ "x-priority": "internal", "description": "Keep existing dependencies versions", "default": false + }, + "updatePackageScripts": { + "type": "boolean", + "x-priority": "internal", + "description": "Update `package.json` scripts with inferred targets", + "default": false } }, "required": [], diff --git a/docs/generated/packages/eslint/generators/init.json b/docs/generated/packages/eslint/generators/init.json index b818aa481b5bc..717ab167223c4 100644 --- a/docs/generated/packages/eslint/generators/init.json +++ b/docs/generated/packages/eslint/generators/init.json @@ -19,6 +19,12 @@ "x-priority": "internal", "description": "Keep existing dependencies versions", "default": false + }, + "updatePackageScripts": { + "type": "boolean", + "x-priority": "internal", + "description": "Update `package.json` scripts with inferred targets", + "default": false } }, "required": [], diff --git a/docs/generated/packages/expo/generators/init.json b/docs/generated/packages/expo/generators/init.json index 52f3bc331438c..cb89fad4b9398 100644 --- a/docs/generated/packages/expo/generators/init.json +++ b/docs/generated/packages/expo/generators/init.json @@ -24,6 +24,12 @@ "x-priority": "internal", "description": "Keep existing dependencies versions", "default": false + }, + "updatePackageScripts": { + "type": "boolean", + "x-priority": "internal", + "description": "Update `package.json` scripts with inferred targets", + "default": false } }, "required": [], diff --git a/docs/generated/packages/jest/generators/init.json b/docs/generated/packages/jest/generators/init.json index 8613a73cc88bc..03fbf1395fbdc 100644 --- a/docs/generated/packages/jest/generators/init.json +++ b/docs/generated/packages/jest/generators/init.json @@ -26,6 +26,12 @@ "x-priority": "internal", "description": "Keep existing dependencies versions", "default": false + }, + "updatePackageScripts": { + "type": "boolean", + "x-priority": "internal", + "description": "Update `package.json` scripts with inferred targets", + "default": false } }, "required": [], diff --git a/docs/generated/packages/nest/generators/init.json b/docs/generated/packages/nest/generators/init.json index 11f9756f8aadd..872d8b1d6bdaf 100644 --- a/docs/generated/packages/nest/generators/init.json +++ b/docs/generated/packages/nest/generators/init.json @@ -27,7 +27,6 @@ "default": false } }, - "additionalProperties": false, "required": [], "presets": [] }, diff --git a/docs/generated/packages/next/generators/init.json b/docs/generated/packages/next/generators/init.json index 1b7a24b6d950a..85f3af4059456 100644 --- a/docs/generated/packages/next/generators/init.json +++ b/docs/generated/packages/next/generators/init.json @@ -25,6 +25,12 @@ "x-priority": "internal", "description": "Keep existing dependencies versions", "default": false + }, + "updatePackageScripts": { + "type": "boolean", + "x-priority": "internal", + "description": "Update `package.json` scripts with inferred targets", + "default": false } }, "required": [], diff --git a/docs/generated/packages/nuxt/generators/init.json b/docs/generated/packages/nuxt/generators/init.json index d5cf4df65a3d2..d7df654c84c09 100644 --- a/docs/generated/packages/nuxt/generators/init.json +++ b/docs/generated/packages/nuxt/generators/init.json @@ -24,6 +24,12 @@ "x-priority": "internal", "description": "Keep existing dependencies versions", "default": false + }, + "updatePackageScripts": { + "type": "boolean", + "x-priority": "internal", + "description": "Update `package.json` scripts with inferred targets", + "default": false } }, "required": [], diff --git a/docs/generated/packages/nx/documents/add.md b/docs/generated/packages/nx/documents/add.md index 4183d0f8faaa6..cc8e359234370 100644 --- a/docs/generated/packages/nx/documents/add.md +++ b/docs/generated/packages/nx/documents/add.md @@ -49,6 +49,12 @@ Type: `string` The package name and optional version (e.g. `@nx/react` or `@nx/react@latest`) to install and initialize. If the version is not specified it will install the same version as the `nx` package for Nx core plugins or the latest version for other packages +### updatePackageScripts + +Type: `boolean` + +Update `package.json` scripts with inferred targets. Defaults to `true` when `NX_PCV3=true` and the package is a core Nx plugin + ### verbose Type: `boolean` diff --git a/docs/generated/packages/playwright/generators/init.json b/docs/generated/packages/playwright/generators/init.json index 9187feb9e5070..ae425e71d6c81 100644 --- a/docs/generated/packages/playwright/generators/init.json +++ b/docs/generated/packages/playwright/generators/init.json @@ -25,6 +25,12 @@ "x-priority": "internal", "description": "Keep existing dependencies versions", "default": false + }, + "updatePackageScripts": { + "type": "boolean", + "x-priority": "internal", + "description": "Update `package.json` scripts with inferred targets", + "default": false } }, "required": [], diff --git a/docs/generated/packages/react-native/generators/init.json b/docs/generated/packages/react-native/generators/init.json index 68e0851efd5e0..3ef8c1c116612 100644 --- a/docs/generated/packages/react-native/generators/init.json +++ b/docs/generated/packages/react-native/generators/init.json @@ -26,6 +26,12 @@ "x-priority": "internal", "description": "Keep existing dependencies versions", "default": false + }, + "updatePackageScripts": { + "type": "boolean", + "x-priority": "internal", + "description": "Update `package.json` scripts with inferred targets", + "default": false } }, "required": [], diff --git a/docs/generated/packages/remix/generators/init.json b/docs/generated/packages/remix/generators/init.json index e857817c50d5f..4c094d7a8fd0a 100644 --- a/docs/generated/packages/remix/generators/init.json +++ b/docs/generated/packages/remix/generators/init.json @@ -24,6 +24,12 @@ "x-priority": "internal", "description": "Keep existing dependencies versions", "default": false + }, + "updatePackageScripts": { + "type": "boolean", + "x-priority": "internal", + "description": "Update `package.json` scripts with inferred targets", + "default": false } }, "required": [], diff --git a/docs/generated/packages/storybook/generators/init.json b/docs/generated/packages/storybook/generators/init.json index 11a7b97d1f08c..9ab4f6b586bf4 100644 --- a/docs/generated/packages/storybook/generators/init.json +++ b/docs/generated/packages/storybook/generators/init.json @@ -23,6 +23,12 @@ "x-priority": "internal", "description": "Keep existing dependencies versions", "default": false + }, + "updatePackageScripts": { + "type": "boolean", + "x-priority": "internal", + "description": "Update `package.json` scripts with inferred targets", + "default": false } }, "presets": [] diff --git a/docs/generated/packages/vite/generators/init.json b/docs/generated/packages/vite/generators/init.json index e6d7542899135..4208b380bf19c 100644 --- a/docs/generated/packages/vite/generators/init.json +++ b/docs/generated/packages/vite/generators/init.json @@ -23,6 +23,12 @@ "x-priority": "internal", "description": "Keep existing dependencies versions", "default": false + }, + "updatePackageScripts": { + "type": "boolean", + "x-priority": "internal", + "description": "Update `package.json` scripts with inferred targets", + "default": false } }, "presets": [] diff --git a/docs/generated/packages/webpack/generators/init.json b/docs/generated/packages/webpack/generators/init.json index 04b34af9e3a38..d26831485ce04 100644 --- a/docs/generated/packages/webpack/generators/init.json +++ b/docs/generated/packages/webpack/generators/init.json @@ -24,6 +24,12 @@ "x-priority": "internal", "description": "Keep existing dependencies versions", "default": false + }, + "updatePackageScripts": { + "type": "boolean", + "x-priority": "internal", + "description": "Update `package.json` scripts with inferred targets", + "default": false } }, "required": [], diff --git a/packages/angular/src/generators/init/schema.json b/packages/angular/src/generators/init/schema.json index 49d8d59207a05..a5417ec85c859 100644 --- a/packages/angular/src/generators/init/schema.json +++ b/packages/angular/src/generators/init/schema.json @@ -30,6 +30,5 @@ "description": "Keep existing dependencies versions", "default": false } - }, - "additionalProperties": false + } } diff --git a/packages/cypress/src/generators/init/init.ts b/packages/cypress/src/generators/init/init.ts index c7ffb43404f4b..5d427c8d3bd4e 100644 --- a/packages/cypress/src/generators/init/init.ts +++ b/packages/cypress/src/generators/init/init.ts @@ -8,6 +8,8 @@ import { Tree, updateNxJson, } from '@nx/devkit'; +import { updatePackageScripts } from '@nx/devkit/src/utils/update-package-scripts'; +import { createNodes } from '../../plugins/plugin'; import { cypressVersion, nxVersion } from '../../utils/versions'; import { Schema } from './schema'; import { CypressPluginOptions } from '../../plugins/plugin'; @@ -108,6 +110,10 @@ export async function cypressInitGenerator(tree: Tree, options: Schema) { installTask = updateDependencies(tree, options); } + if (options.updatePackageScripts) { + await updatePackageScripts(tree, createNodes); + } + if (!options.skipFormat) { await formatFiles(tree); } diff --git a/packages/cypress/src/generators/init/schema.d.ts b/packages/cypress/src/generators/init/schema.d.ts index 9708e88a1511f..aa1a5bdf92231 100644 --- a/packages/cypress/src/generators/init/schema.d.ts +++ b/packages/cypress/src/generators/init/schema.d.ts @@ -2,4 +2,5 @@ export interface Schema { skipFormat?: boolean; skipPackageJson?: boolean; keepExistingVersions?: boolean; + updatePackageScripts?: boolean; } diff --git a/packages/cypress/src/generators/init/schema.json b/packages/cypress/src/generators/init/schema.json index 7099d1c0013e2..d484c738ba60b 100644 --- a/packages/cypress/src/generators/init/schema.json +++ b/packages/cypress/src/generators/init/schema.json @@ -23,6 +23,12 @@ "x-priority": "internal", "description": "Keep existing dependencies versions", "default": false + }, + "updatePackageScripts": { + "type": "boolean", + "x-priority": "internal", + "description": "Update `package.json` scripts with inferred targets", + "default": false } } } diff --git a/packages/detox/src/generators/application/lib/add-linting.spec.ts b/packages/detox/src/generators/application/lib/add-linting.spec.ts index 80653dd5d6279..c049bdd38fe47 100644 --- a/packages/detox/src/generators/application/lib/add-linting.spec.ts +++ b/packages/detox/src/generators/application/lib/add-linting.spec.ts @@ -24,8 +24,8 @@ describe('Add Linting', () => { }); }); - it('should update configuration when eslint is passed', () => { - addLinting(tree, { + it('should update configuration when eslint is passed', async () => { + await addLinting(tree, { e2eName: 'my-app-e2e', e2eProjectName: 'my-app-e2e', e2eProjectRoot: 'apps/my-app-e2e', @@ -45,7 +45,7 @@ describe('Add Linting', () => { }); it('should not add lint target when "none" is passed', async () => { - addLinting(tree, { + await addLinting(tree, { e2eName: 'my-app-e2e', e2eProjectName: 'my-app-e2e', e2eProjectRoot: 'apps/my-app-e2e', diff --git a/packages/detox/src/generators/init/init.ts b/packages/detox/src/generators/init/init.ts index bdc7dc6390509..4aab0bda3f7fd 100644 --- a/packages/detox/src/generators/init/init.ts +++ b/packages/detox/src/generators/init/init.ts @@ -8,7 +8,8 @@ import { Tree, updateNxJson, } from '@nx/devkit'; -import { DetoxPluginOptions } from '../../plugins/plugin'; +import { updatePackageScripts } from '@nx/devkit/src/utils/update-package-scripts'; +import { createNodes, DetoxPluginOptions } from '../../plugins/plugin'; import { detoxVersion, nxVersion } from '../../utils/versions'; import { Schema } from './schema'; @@ -24,6 +25,10 @@ export async function detoxInitGenerator(host: Tree, schema: Schema) { addPlugin(host); } + if (schema.updatePackageScripts) { + await updatePackageScripts(host, createNodes); + } + if (!schema.skipFormat) { await formatFiles(host); } diff --git a/packages/detox/src/generators/init/schema.d.ts b/packages/detox/src/generators/init/schema.d.ts index fb1f7ae29cd20..e8bc39b25489a 100644 --- a/packages/detox/src/generators/init/schema.d.ts +++ b/packages/detox/src/generators/init/schema.d.ts @@ -2,4 +2,5 @@ export interface Schema { skipFormat?: boolean; skipPackageJson?: boolean; //default is false keepExistingVersions?: boolean; //default is false + updatePackageScripts?: boolean; } diff --git a/packages/detox/src/generators/init/schema.json b/packages/detox/src/generators/init/schema.json index 5a6808230f8d6..d9648d5357a13 100644 --- a/packages/detox/src/generators/init/schema.json +++ b/packages/detox/src/generators/init/schema.json @@ -21,6 +21,12 @@ "x-priority": "internal", "description": "Keep existing dependencies versions", "default": false + }, + "updatePackageScripts": { + "type": "boolean", + "x-priority": "internal", + "description": "Update `package.json` scripts with inferred targets", + "default": false } }, "required": [] diff --git a/packages/devkit/package.json b/packages/devkit/package.json index 63fbfafc31c66..b7fecafb8e714 100644 --- a/packages/devkit/package.json +++ b/packages/devkit/package.json @@ -33,7 +33,8 @@ "ignore": "^5.0.4", "tmp": "~0.2.1", "tslib": "^2.3.0", - "semver": "7.5.3" + "semver": "7.5.3", + "yargs-parser": "21.1.1" }, "peerDependencies": { "nx": ">= 16 <= 18" diff --git a/packages/devkit/src/utils/update-package-scripts.spec.ts b/packages/devkit/src/utils/update-package-scripts.spec.ts new file mode 100644 index 0000000000000..42b190c9de29c --- /dev/null +++ b/packages/devkit/src/utils/update-package-scripts.spec.ts @@ -0,0 +1,293 @@ +import { createTreeWithEmptyWorkspace } from 'nx/src/generators/testing-utils/create-tree-with-empty-workspace'; +import type { Tree } from 'nx/src/generators/tree'; +import { readJson, writeJson } from 'nx/src/generators/utils/json'; +import type { PackageJson } from 'nx/src/utils/package-json'; +import { updatePackageScripts } from './update-package-scripts'; + +describe('updatePackageScripts', () => { + let tree: Tree; + + beforeEach(() => { + tree = createTreeWithEmptyWorkspace(); + }); + + test.each` + script + ${'next-remote-watch'} + ${'anext build'} + ${'next builda'} + `('should not replace "$script"', async ({ script }) => { + tree.write('app1/next.config.js', ''); + writeJson(tree, 'app1/package.json', { + name: 'app1', + scripts: { + build: script, + }, + }); + + await updatePackageScripts(tree, [ + '**/next.config.{js,cjs,mjs}', + () => ({ + projects: { + app1: { + targets: { + build: { command: 'next build' }, + }, + }, + }, + }), + ]); + + const { scripts } = readJson(tree, 'app1/package.json'); + expect(scripts.build).toBe(script); + }); + + test.each` + script | expected + ${'next build'} | ${'nx build'} + ${'npx next build'} | ${'npx nx build'} + ${'next build --debug'} | ${'nx build --debug'} + ${'NODE_OPTIONS="--inspect" next build'} | ${'NODE_OPTIONS="--inspect" nx build'} + ${'NODE_OPTIONS="--inspect" npx next build --debug'} | ${'NODE_OPTIONS="--inspect" npx nx build --debug'} + ${'next build && echo "Done"'} | ${'nx build && echo "Done"'} + ${'echo "Building..." && next build'} | ${'echo "Building..." && nx build'} + ${'echo "Building..." && next build && echo "Done"'} | ${'echo "Building..." && nx build && echo "Done"'} + ${'echo "Building..." &&next build&& echo "Done"'} | ${'echo "Building..." &&nx build&& echo "Done"'} + ${'echo "Building..." && NODE_OPTIONS="--inspect" npx next build --debug && echo "Done"'} | ${'echo "Building..." && NODE_OPTIONS="--inspect" npx nx build --debug && echo "Done"'} + `( + 'should replace "$script" with "$expected"', + async ({ script, expected }) => { + tree.write('app1/next.config.js', ''); + writeJson(tree, 'app1/package.json', { + name: 'app1', + scripts: { + build: script, + }, + }); + + await updatePackageScripts(tree, [ + '**/next.config.{js,cjs,mjs}', + () => ({ + projects: { + app1: { + targets: { + build: { command: 'next build' }, + }, + }, + }, + }), + ]); + + const { scripts } = readJson(tree, 'app1/package.json'); + expect(scripts.build).toBe(expected); + } + ); + + test.each` + script | expected + ${'cypress run --e2e --config-file cypress.config.ts'} | ${'nx e2e'} + ${'echo "Starting..." && cypress run --e2e --config-file cypress.config.ts && echo "Done"'} | ${'echo "Starting..." && nx e2e && echo "Done"'} + `( + 'should replace "$script" with "$expected"', + async ({ script, expected }) => { + tree.write('app1/cypress.config.ts', ''); + writeJson(tree, 'app1/package.json', { + name: 'app1', + scripts: { + e2e: script, + }, + }); + + await updatePackageScripts(tree, [ + '**/cypress.config.{js,ts,mjs,mts,cjs,cts}', + () => ({ + projects: { + app1: { + targets: { + e2e: { + command: 'cypress run --config-file cypress.config.ts --e2e', + }, + }, + }, + }, + }), + ]); + + const { scripts } = readJson(tree, 'app1/package.json'); + expect(scripts.e2e).toBe(expected); + } + ); + + it('should handle scripts with name different than the target name', async () => { + tree.write('app1/next.config.js', ''); + writeJson(tree, 'app1/package.json', { + name: 'app1', + scripts: { + 'build:dev': 'next build', + }, + }); + + await updatePackageScripts(tree, [ + '**/next.config.{js,cjs,mjs}', + () => ({ + projects: { + app1: { + targets: { + build: { + command: 'next build', + }, + }, + }, + }, + }), + ]); + + const { scripts } = readJson(tree, 'app1/package.json'); + expect(scripts['build:dev']).toBe('nx build'); + }); + + it('should support replacing multiple scripts', async () => { + tree.write('app1/next.config.js', ''); + writeJson(tree, 'app1/package.json', { + name: 'app1', + scripts: { + dev: 'PORT=4000 next dev --experimental-https', + start: 'next build && PORT=4000 next start --experimental-https', + }, + }); + + await updatePackageScripts(tree, [ + '**/next.config.{js,cjs,mjs}', + () => ({ + projects: { + app1: { + targets: { + build: { command: 'next build' }, + dev: { command: 'next dev' }, + start: { command: 'next start' }, + }, + }, + }, + }), + ]); + + const { scripts } = readJson(tree, 'app1/package.json'); + expect(scripts.dev).toBe('PORT=4000 nx dev --experimental-https'); + expect(scripts.start).toBe( + 'nx build && PORT=4000 nx start --experimental-https' + ); + }); + + it('should support multiple occurrences of the same command within a script', async () => { + tree.write('app1/tsconfig.json', ''); + writeJson(tree, 'app1/package.json', { + name: 'app1', + scripts: { + typecheck: 'tsc -p tsconfig.lib.json && tsc -p tsconfig.spec.json', + }, + }); + + await updatePackageScripts(tree, [ + '**/tsconfig.json', + () => ({ + projects: { + app1: { + targets: { + build: { command: 'tsc' }, + }, + }, + }, + }), + ]); + + const { scripts } = readJson(tree, 'app1/package.json'); + expect(scripts.typecheck).toBe( + 'nx build -p tsconfig.lib.json && nx build -p tsconfig.spec.json' + ); + }); + + it('should support multiple occurrences of the same command within a script with extra commands', async () => { + tree.write('app1/tsconfig.json', ''); + writeJson(tree, 'app1/package.json', { + name: 'app1', + scripts: { + typecheck: + 'echo "Typechecking..." && tsc -p tsconfig.lib.json && tsc -p tsconfig.spec.json && echo "Done"', + }, + }); + + await updatePackageScripts(tree, [ + '**/tsconfig.json', + () => ({ + projects: { + app1: { + targets: { + build: { command: 'tsc' }, + }, + }, + }, + }), + ]); + + const { scripts } = readJson(tree, 'app1/package.json'); + expect(scripts.typecheck).toBe( + 'echo "Typechecking..." && nx build -p tsconfig.lib.json && nx build -p tsconfig.spec.json && echo "Done"' + ); + }); + + it('should exclude all package.json scripts when none are excluded', async () => { + tree.write('next.config.js', ''); + writeJson(tree, 'package.json', { + name: 'app1', + scripts: { + build: 'next build', + }, + }); + + await updatePackageScripts(tree, [ + '**/next.config.{js,cjs,mjs}', + () => ({ + projects: { + app1: { + targets: { + build: { command: 'next build' }, + }, + }, + }, + }), + ]); + + const { nx } = readJson(tree, 'package.json'); + expect(nx).toStrictEqual({ includedScripts: [] }); + }); + + it('should exclude replaced package.json scripts from nx if they are initially included', async () => { + tree.write('next.config.js', ''); + writeJson(tree, 'package.json', { + name: 'app1', + scripts: { + build: 'next build', + foo: 'echo "foo"', + }, + nx: { + includedScripts: ['build', 'foo'], + }, + }); + + await updatePackageScripts(tree, [ + '**/next.config.{js,cjs,mjs}', + () => ({ + projects: { + app1: { + targets: { + build: { command: 'next build' }, + }, + }, + }, + }), + ]); + + const { nx } = readJson(tree, 'package.json'); + expect(nx).toStrictEqual({ includedScripts: ['foo'] }); + }); +}); diff --git a/packages/devkit/src/utils/update-package-scripts.ts b/packages/devkit/src/utils/update-package-scripts.ts new file mode 100644 index 0000000000000..8d57a598cd66f --- /dev/null +++ b/packages/devkit/src/utils/update-package-scripts.ts @@ -0,0 +1,247 @@ +import type { NxJsonConfiguration } from 'nx/src/config/nx-json'; +import type { Tree } from 'nx/src/generators/tree'; +import type { + CreateNodes, + CreateNodesFunction, + CreateNodesResult, +} from 'nx/src/utils/nx-plugin'; +import type { PackageJson } from 'nx/src/utils/package-json'; +import { basename, dirname } from 'path'; +import * as yargs from 'yargs-parser'; +import { requireNx } from '../../nx'; + +const { glob, readJson, readNxJson, workspaceRoot, writeJson } = requireNx(); + +type TargetCommand = { + command: string; + target: string; + configuration?: string; +}; + +export async function updatePackageScripts( + tree: Tree, + createNodesTuple: CreateNodes +): Promise { + const nxJson = readNxJson(tree); + + const [pattern, createNodes] = createNodesTuple; + const files = glob(tree, [pattern]); + + for (const file of files) { + const projectRoot = getProjectRootFromConfigFile(file); + await processProject(tree, projectRoot, file, createNodes, nxJson); + } +} + +async function processProject( + tree: Tree, + projectRoot: string, + projectConfigurationFile: string, + createNodesFunction: CreateNodesFunction, + nxJsonConfiguration: NxJsonConfiguration +) { + const packageJsonPath = `${projectRoot}/package.json`; + if (!tree.exists(packageJsonPath)) { + return; + } + const packageJson = readJson(tree, packageJsonPath); + if (!packageJson.scripts || !Object.keys(packageJson.scripts).length) { + return; + } + + const result = await createNodesFunction( + projectConfigurationFile, + {}, + { nxJsonConfiguration, workspaceRoot } + ); + + const targetCommands = getInferredTargetCommands(result); + if (!targetCommands.length) { + return; + } + + // exclude package.json scripts from nx + packageJson.nx ??= {}; + packageJson.nx.includedScripts ??= []; + + for (const targetCommand of targetCommands) { + const { command, target, configuration } = targetCommand; + const targetCommandRegex = new RegExp( + `(?<=^|&)((?: )*(?:[^&\\r\\n\\s]+ )*)(${command})((?: [^&\\r\\n\\s]+)*(?: )*)(?=$|&)`, + 'g' + ); + for (const scriptName of Object.keys(packageJson.scripts)) { + const script = packageJson.scripts[scriptName]; + // quick check for exact match within the script + if (targetCommandRegex.test(script)) { + packageJson.scripts[scriptName] = script.replace( + targetCommandRegex, + configuration + ? `$1nx ${target} --configuration=${configuration}$3` + : `$1nx ${target}$3` + ); + excludeScriptFromPackageJson(packageJson, scriptName); + } else { + /** + * Parse script and command to handle the following: + * - if command doesn't match script => don't replace + * - if command has more args => don't replace + * - if command has same args, regardless of order => replace removing args + * - if command has less args or with different value => replace leaving args + */ + const parsedCommand = yargs(command, { + configuration: { 'strip-dashed': true }, + }); + + // this assumes there are no positional args in the command, everything is a command or subcommand + const commandCommand = parsedCommand._.join(' '); + const commandRegex = new RegExp( + `(?<=^|&)((?: )*(?:[^&\\r\\n\\s]+ )*)(${commandCommand})((?: [^&\\r\\n\\s]+)*( )*)(?=$|&)`, + 'g' + ); + const matches = script.match(commandRegex); + if (!matches) { + // the command doesn't match the script, don't replace + continue; + } + + for (const match of matches) { + // parse the matched command within the script + const parsedScript = yargs(match, { + configuration: { 'strip-dashed': true }, + }); + + let hasArgsWithDifferentValues = false; + let scriptHasExtraArgs = false; + let commandHasExtraArgs = false; + for (const [key, value] of Object.entries(parsedCommand)) { + if (key === '_') { + continue; + } + + if (parsedScript[key] === undefined) { + commandHasExtraArgs = true; + break; + } + if (parsedScript[key] !== value) { + hasArgsWithDifferentValues = true; + } + } + + if (commandHasExtraArgs) { + // the command has extra args, don't replace + continue; + } + + for (const key of Object.keys(parsedScript)) { + if (key === '_') { + continue; + } + + if (!parsedCommand[key]) { + scriptHasExtraArgs = true; + break; + } + } + + if (!hasArgsWithDifferentValues && !scriptHasExtraArgs) { + // they are the same, replace with the command removing the args + packageJson.scripts[scriptName] = packageJson.scripts[ + scriptName + ].replace( + match, + match.replace( + commandRegex, + configuration + ? `$1nx ${target} --configuration=${configuration}$4` + : `$1nx ${target}$4` + ) + ); + } else { + // there are different args or the script has extra args, replace with the command leaving the args + packageJson.scripts[scriptName] = packageJson.scripts[ + scriptName + ].replace( + match, + match.replace( + commandRegex, + configuration + ? `$1nx ${target} --configuration=${configuration}$3` + : `$1nx ${target}$3` + ) + ); + } + + excludeScriptFromPackageJson(packageJson, scriptName); + } + } + } + } + + writeJson(tree, packageJsonPath, packageJson); +} + +function getInferredTargetCommands(result: CreateNodesResult): TargetCommand[] { + const targetCommands: TargetCommand[] = []; + + for (const project of Object.values(result.projects ?? {})) { + for (const [targetName, target] of Object.entries(project.targets ?? {})) { + if (target.command) { + targetCommands.push({ command: target.command, target: targetName }); + } else if ( + target.executor === 'nx:run-commands' && + target.options?.command + ) { + targetCommands.push({ + command: target.options.command, + target: targetName, + }); + } + + if (!target.configurations) { + continue; + } + + for (const [configurationName, configuration] of Object.entries( + target.configurations + )) { + if (configuration.command) { + targetCommands.push({ + command: configuration.command, + target: targetName, + configuration: configurationName, + }); + } else if ( + target.executor === 'nx:run-commands' && + configuration.options?.command + ) { + targetCommands.push({ + command: configuration.options.command, + target: targetName, + configuration: configurationName, + }); + } + } + } + } + + return targetCommands; +} + +function excludeScriptFromPackageJson( + packageJson: PackageJson, + scriptName: string +) { + packageJson.nx.includedScripts = packageJson.nx.includedScripts.filter( + (s) => s !== scriptName + ); +} + +function getProjectRootFromConfigFile(file: string): string { + let projectRoot = dirname(file); + if (basename(projectRoot) === '.storybook') { + projectRoot = dirname(projectRoot); + } + + return projectRoot; +} diff --git a/packages/eslint/src/generators/init/init.spec.ts b/packages/eslint/src/generators/init/init.spec.ts index a2d15b065a6fd..60b8ef8d68d1f 100644 --- a/packages/eslint/src/generators/init/init.spec.ts +++ b/packages/eslint/src/generators/init/init.spec.ts @@ -16,8 +16,8 @@ describe('@nx/eslint:init', () => { process.env.NX_PCV3 = envV3; }); - it('should add the root eslint config to the lint targetDefaults for lint', () => { - lintInitGenerator(tree, {}); + it('should add the root eslint config to the lint targetDefaults for lint', async () => { + await lintInitGenerator(tree, {}); expect(readJson(tree, 'nx.json').targetDefaults['@nx/eslint:lint']).toEqual( { @@ -32,22 +32,22 @@ describe('@nx/eslint:init', () => { ); }); - it('should not generate the global eslint config if it already exist', () => { + it('should not generate the global eslint config if it already exist', async () => { tree.write('.eslintrc.js', '{}'); - lintInitGenerator(tree, {}); + await lintInitGenerator(tree, {}); expect(tree.exists('.eslintrc.json')).toBe(false); }); - it('should setup lint target defaults', () => { + it('should setup lint target defaults', async () => { updateJson(tree, 'nx.json', (json) => { json.namedInputs ??= {}; json.namedInputs.production = ['default']; return json; }); - lintInitGenerator(tree, {}); + await lintInitGenerator(tree, {}); expect( readJson(tree, 'nx.json').targetDefaults[ @@ -64,7 +64,7 @@ describe('@nx/eslint:init', () => { }); }); - it('should setup @nx/eslint/plugin', () => { + it('should setup @nx/eslint/plugin', async () => { process.env.NX_PCV3 = 'true'; updateJson(tree, 'nx.json', (json) => { json.namedInputs ??= {}; @@ -72,7 +72,7 @@ describe('@nx/eslint:init', () => { return json; }); - lintInitGenerator(tree, {}); + await lintInitGenerator(tree, {}); expect( readJson(tree, 'nx.json').targetDefaults[ @@ -92,14 +92,14 @@ describe('@nx/eslint:init', () => { `); }); - it('should add @nx/eslint/plugin in subsequent step', () => { + it('should add @nx/eslint/plugin in subsequent step', async () => { updateJson(tree, 'nx.json', (json) => { json.namedInputs ??= {}; json.namedInputs.production = ['default']; return json; }); - lintInitGenerator(tree, {}); + await lintInitGenerator(tree, {}); expect( readJson(tree, 'nx.json').plugins ).not.toBeDefined(); diff --git a/packages/eslint/src/generators/init/init.ts b/packages/eslint/src/generators/init/init.ts index 82eeaf7712794..56fbd535256b7 100644 --- a/packages/eslint/src/generators/init/init.ts +++ b/packages/eslint/src/generators/init/init.ts @@ -6,14 +6,16 @@ import { runTasksInSerial, updateNxJson, } from '@nx/devkit'; +import { updatePackageScripts } from '@nx/devkit/src/utils/update-package-scripts'; import { eslintVersion, nxVersion } from '../../utils/versions'; import { findEslintFile } from '../utils/eslint-file'; -import { EslintPluginOptions } from '../../plugins/plugin'; +import { EslintPluginOptions, createNodes } from '../../plugins/plugin'; import { hasEslintPlugin } from '../utils/plugin'; export interface LinterInitOptions { skipPackageJson?: boolean; keepExistingVersions?: boolean; + updatePackageScripts?: boolean; } function updateProductionFileset(tree: Tree) { @@ -67,13 +69,21 @@ function addPlugin(tree: Tree) { updateNxJson(tree, nxJson); } -function initEsLint(tree: Tree, options: LinterInitOptions): GeneratorCallback { +async function initEsLint( + tree: Tree, + options: LinterInitOptions +): Promise { const addPlugins = process.env.NX_PCV3 === 'true'; const hasPlugin = hasEslintPlugin(tree); const rootEslintFile = findEslintFile(tree); if (rootEslintFile && addPlugins && !hasPlugin) { addPlugin(tree); + + if (options.updatePackageScripts) { + await updatePackageScripts(tree, createNodes); + } + return () => {}; } @@ -106,9 +116,16 @@ function initEsLint(tree: Tree, options: LinterInitOptions): GeneratorCallback { ); } + if (options.updatePackageScripts) { + updatePackageScripts(tree, createNodes); + } + return runTasksInSerial(...tasks); } -export function lintInitGenerator(tree: Tree, options: LinterInitOptions) { - return initEsLint(tree, options); +export async function lintInitGenerator( + tree: Tree, + options: LinterInitOptions +) { + return await initEsLint(tree, options); } diff --git a/packages/eslint/src/generators/init/schema.json b/packages/eslint/src/generators/init/schema.json index ca465407119ce..8b56ce3cc1128 100644 --- a/packages/eslint/src/generators/init/schema.json +++ b/packages/eslint/src/generators/init/schema.json @@ -16,6 +16,12 @@ "x-priority": "internal", "description": "Keep existing dependencies versions", "default": false + }, + "updatePackageScripts": { + "type": "boolean", + "x-priority": "internal", + "description": "Update `package.json` scripts with inferred targets", + "default": false } }, "required": [] diff --git a/packages/eslint/src/generators/lint-project/lint-project.ts b/packages/eslint/src/generators/lint-project/lint-project.ts index ef95eb8464bfa..08958d120c94d 100644 --- a/packages/eslint/src/generators/lint-project/lint-project.ts +++ b/packages/eslint/src/generators/lint-project/lint-project.ts @@ -57,7 +57,7 @@ export async function lintProjectGenerator( options: LintProjectOptions ) { const tasks: GeneratorCallback[] = []; - const initTask = lintInitGenerator(tree, { + const initTask = await lintInitGenerator(tree, { skipPackageJson: options.skipPackageJson, }); tasks.push(initTask); diff --git a/packages/expo/src/generators/init/init.ts b/packages/expo/src/generators/init/init.ts index 0f1ee4e6e1296..9152c9e4a4fc5 100644 --- a/packages/expo/src/generators/init/init.ts +++ b/packages/expo/src/generators/init/init.ts @@ -8,7 +8,8 @@ import { Tree, updateNxJson, } from '@nx/devkit'; -import { ExpoPluginOptions } from '../../../plugins/plugin'; +import { updatePackageScripts } from '@nx/devkit/src/utils/update-package-scripts'; +import { createNodes, ExpoPluginOptions } from '../../../plugins/plugin'; import { easCliVersion, expoCliVersion, @@ -35,6 +36,10 @@ export async function expoInitGenerator(host: Tree, schema: Schema) { tasks.push(updateDependencies(host, schema)); } + if (schema.updatePackageScripts) { + await updatePackageScripts(host, createNodes); + } + if (!schema.skipFormat) { await formatFiles(host); } diff --git a/packages/expo/src/generators/init/schema.d.ts b/packages/expo/src/generators/init/schema.d.ts index cfe04d810b1db..6672085d83771 100644 --- a/packages/expo/src/generators/init/schema.d.ts +++ b/packages/expo/src/generators/init/schema.d.ts @@ -2,4 +2,5 @@ export interface Schema { skipFormat?: boolean; skipPackageJson?: boolean; // default is false keepExistingVersions?: boolean; // default is false + updatePackageScripts?: boolean; } diff --git a/packages/expo/src/generators/init/schema.json b/packages/expo/src/generators/init/schema.json index 8e1900377f197..98ad5bd39834e 100644 --- a/packages/expo/src/generators/init/schema.json +++ b/packages/expo/src/generators/init/schema.json @@ -21,6 +21,12 @@ "x-priority": "internal", "description": "Keep existing dependencies versions", "default": false + }, + "updatePackageScripts": { + "type": "boolean", + "x-priority": "internal", + "description": "Update `package.json` scripts with inferred targets", + "default": false } }, "required": [] diff --git a/packages/expo/src/utils/add-linting.spec.ts b/packages/expo/src/utils/add-linting.spec.ts index 7ca1407e85579..68ad82e24ed63 100644 --- a/packages/expo/src/utils/add-linting.spec.ts +++ b/packages/expo/src/utils/add-linting.spec.ts @@ -16,8 +16,8 @@ describe('Add Linting', () => { }); }); - it('should add update configuration when eslint is passed', () => { - addLinting(tree, { + it('should add update configuration when eslint is passed', async () => { + await addLinting(tree, { projectName: 'my-lib', linter: Linter.EsLint, tsConfigPaths: ['my-lib/tsconfig.lib.json'], @@ -30,7 +30,7 @@ describe('Add Linting', () => { }); it('should not add lint target when "none" is passed', async () => { - addLinting(tree, { + await addLinting(tree, { projectName: 'my-lib', linter: Linter.None, tsConfigPaths: ['my-lib/tsconfig.lib.json'], diff --git a/packages/jest/src/generators/init/init.ts b/packages/jest/src/generators/init/init.ts index ac25c9e2e2b45..a50f0393f0efe 100644 --- a/packages/jest/src/generators/init/init.ts +++ b/packages/jest/src/generators/init/init.ts @@ -8,6 +8,8 @@ import { type GeneratorCallback, type Tree, } from '@nx/devkit'; +import { updatePackageScripts } from '@nx/devkit/src/utils/update-package-scripts'; +import { createNodes } from '../../plugins/plugin'; import { jestVersion, nxVersion } from '../../utils/versions'; import type { JestInitSchema } from './schema'; @@ -118,6 +120,10 @@ export async function jestInitGenerator( tasks.push(updateDependencies(tree, options)); } + if (options.updatePackageScripts) { + await updatePackageScripts(tree, createNodes); + } + if (!options.skipFormat) { await formatFiles(tree); } diff --git a/packages/jest/src/generators/init/schema.d.ts b/packages/jest/src/generators/init/schema.d.ts index e9dc5ddb96c4b..297ba4ad4a6ea 100644 --- a/packages/jest/src/generators/init/schema.d.ts +++ b/packages/jest/src/generators/init/schema.d.ts @@ -2,4 +2,5 @@ export interface JestInitSchema { skipFormat?: boolean; skipPackageJson?: boolean; keepExistingVersions?: boolean; + updatePackageScripts?: boolean; } diff --git a/packages/jest/src/generators/init/schema.json b/packages/jest/src/generators/init/schema.json index 33eff58e67a01..483006134da7b 100644 --- a/packages/jest/src/generators/init/schema.json +++ b/packages/jest/src/generators/init/schema.json @@ -23,6 +23,12 @@ "x-priority": "internal", "description": "Keep existing dependencies versions", "default": false + }, + "updatePackageScripts": { + "type": "boolean", + "x-priority": "internal", + "description": "Update `package.json` scripts with inferred targets", + "default": false } }, "required": [] diff --git a/packages/js/src/generators/library/library.ts b/packages/js/src/generators/library/library.ts index 618a89549731c..c9fb3cbc8d289 100644 --- a/packages/js/src/generators/library/library.ts +++ b/packages/js/src/generators/library/library.ts @@ -271,7 +271,7 @@ export async function addLint( ): Promise { const { lintProjectGenerator } = ensurePackage('@nx/eslint', nxVersion); const projectConfiguration = readProjectConfiguration(tree, options.name); - const task = lintProjectGenerator(tree, { + const task = await lintProjectGenerator(tree, { project: options.name, linter: options.linter, skipFormat: true, diff --git a/packages/nest/src/generators/init/schema.json b/packages/nest/src/generators/init/schema.json index 8e089a20edbd2..39763032bf8b2 100644 --- a/packages/nest/src/generators/init/schema.json +++ b/packages/nest/src/generators/init/schema.json @@ -24,6 +24,5 @@ "default": false } }, - "additionalProperties": false, "required": [] } diff --git a/packages/next/src/generators/init/init.ts b/packages/next/src/generators/init/init.ts index e1fc0b03dd45e..d285e7912c3b3 100644 --- a/packages/next/src/generators/init/init.ts +++ b/packages/next/src/generators/init/init.ts @@ -5,7 +5,9 @@ import { type GeneratorCallback, type Tree, } from '@nx/devkit'; +import { updatePackageScripts } from '@nx/devkit/src/utils/update-package-scripts'; import { reactDomVersion, reactVersion } from '@nx/react/src/utils/versions'; +import { createNodes } from '../../plugins/plugin'; import { addGitIgnoreEntry } from '../../utils/add-gitignore-entry'; import { nextVersion, nxVersion } from '../../utils/versions'; import { addPlugin } from './lib/add-plugin'; @@ -47,6 +49,10 @@ export async function nextInitGenerator(host: Tree, schema: InitSchema) { installTask = updateDependencies(host, schema); } + if (schema.updatePackageScripts) { + await updatePackageScripts(host, createNodes); + } + return installTask; } diff --git a/packages/next/src/generators/init/schema.d.ts b/packages/next/src/generators/init/schema.d.ts index 7ff98c42cfce0..f3f0d56acd0b2 100644 --- a/packages/next/src/generators/init/schema.d.ts +++ b/packages/next/src/generators/init/schema.d.ts @@ -2,4 +2,5 @@ export interface InitSchema { skipFormat?: boolean; skipPackageJson?: boolean; keepExistingVersions?: boolean; + updatePackageScripts?: boolean; } diff --git a/packages/next/src/generators/init/schema.json b/packages/next/src/generators/init/schema.json index 2284da0db3b7e..e0feeaf58f835 100644 --- a/packages/next/src/generators/init/schema.json +++ b/packages/next/src/generators/init/schema.json @@ -22,6 +22,12 @@ "x-priority": "internal", "description": "Keep existing dependencies versions", "default": false + }, + "updatePackageScripts": { + "type": "boolean", + "x-priority": "internal", + "description": "Update `package.json` scripts with inferred targets", + "default": false } }, "required": [] diff --git a/packages/next/src/plugins/plugin.ts b/packages/next/src/plugins/plugin.ts index e46f8ba2557ac..b1a6ae76ff754 100644 --- a/packages/next/src/plugins/plugin.ts +++ b/packages/next/src/plugins/plugin.ts @@ -15,7 +15,6 @@ import { existsSync, readdirSync } from 'fs'; import { projectGraphCacheDirectory } from 'nx/src/utils/cache-directory'; import { calculateHashForCreateNodes } from '@nx/devkit/src/utils/calculate-hash-for-create-nodes'; -import { type NextConfig } from 'next'; import { PHASE_PRODUCTION_BUILD } from 'next/constants'; import { getLockFileName } from '@nx/js'; @@ -114,7 +113,7 @@ async function buildNextTargets( async function getBuildTargetConfig( namedInputs: { [inputName: string]: any[] }, projectRoot: string, - nextConfig: NextConfig + nextConfig: any ) { const nextOutputPath = await getOutputs(projectRoot, nextConfig); // Set output path here so that `withNx` can pick it up. diff --git a/packages/nuxt/src/generators/init/init.ts b/packages/nuxt/src/generators/init/init.ts index fd84e5b3ec3cc..30dc670fc87e3 100644 --- a/packages/nuxt/src/generators/init/init.ts +++ b/packages/nuxt/src/generators/init/init.ts @@ -1,5 +1,7 @@ import { GeneratorCallback, Tree } from '@nx/devkit'; +import { updatePackageScripts } from '@nx/devkit/src/utils/update-package-scripts'; +import { createNodes } from '../../plugins/plugin'; import { InitSchema } from './schema'; import { addPlugin, updateDependencies } from './lib/utils'; @@ -11,6 +13,10 @@ export async function nuxtInitGenerator(host: Tree, schema: InitSchema) { installTask = updateDependencies(host, schema); } + if (schema.updatePackageScripts) { + await updatePackageScripts(host, createNodes); + } + return installTask; } diff --git a/packages/nuxt/src/generators/init/schema.d.ts b/packages/nuxt/src/generators/init/schema.d.ts index 7ff98c42cfce0..f3f0d56acd0b2 100644 --- a/packages/nuxt/src/generators/init/schema.d.ts +++ b/packages/nuxt/src/generators/init/schema.d.ts @@ -2,4 +2,5 @@ export interface InitSchema { skipFormat?: boolean; skipPackageJson?: boolean; keepExistingVersions?: boolean; + updatePackageScripts?: boolean; } diff --git a/packages/nuxt/src/generators/init/schema.json b/packages/nuxt/src/generators/init/schema.json index 98f6381fe5964..e0273e041520b 100644 --- a/packages/nuxt/src/generators/init/schema.json +++ b/packages/nuxt/src/generators/init/schema.json @@ -21,6 +21,12 @@ "x-priority": "internal", "description": "Keep existing dependencies versions", "default": false + }, + "updatePackageScripts": { + "type": "boolean", + "x-priority": "internal", + "description": "Update `package.json` scripts with inferred targets", + "default": false } }, "required": [] diff --git a/packages/nx/src/command-line/add/add.ts b/packages/nx/src/command-line/add/add.ts index 0da9f1a2bf4ef..4a45e6c05be3a 100644 --- a/packages/nx/src/command-line/add/add.ts +++ b/packages/nx/src/command-line/add/add.ts @@ -15,8 +15,8 @@ import { nxVersion } from '../../utils/versions'; import { workspaceRoot } from '../../utils/workspace-root'; import type { AddOptions } from './command-object'; -export function addHandler(args: AddOptions): Promise { - if (args.verbose) { +export function addHandler(options: AddOptions): Promise { + if (options.verbose) { process.env.NX_VERBOSE_LOGGING = 'true'; } const isVerbose = process.env.NX_VERBOSE_LOGGING === 'true'; @@ -24,10 +24,10 @@ export function addHandler(args: AddOptions): Promise { return handleErrors(isVerbose, async () => { output.addNewline(); - const [pkgName, version] = parsePackageSpecifier(args.packageSpecifier); + const [pkgName, version] = parsePackageSpecifier(options.packageSpecifier); await installPackage(pkgName, version); - await initializePlugin(pkgName); + await initializePlugin(pkgName, options); output.success({ title: `Package ${pkgName} added successfully.`, @@ -82,7 +82,10 @@ async function installPackage(pkgName: string, version: string): Promise { spinner.succeed(); } -async function initializePlugin(pkgName: string): Promise { +async function initializePlugin( + pkgName: string, + options: AddOptions +): Promise { const capabilities = await getPluginCapabilities(workspaceRoot, pkgName, {}); const generators = capabilities?.generators; if (!generators) { @@ -104,7 +107,18 @@ async function initializePlugin(pkgName: string): Promise { spinner.start(); try { - await runNxAsync(`g ${pkgName}:${initGenerator}`); + let updatePackageScripts: boolean; + if (options.updatePackageScripts !== undefined) { + updatePackageScripts = options.updatePackageScripts; + } else { + updatePackageScripts = + process.env.NX_PCV3 === 'true' && coreNxPlugins.includes(pkgName); + } + await runNxAsync( + `g ${pkgName}:${initGenerator} --keepExistingVersions${ + updatePackageScripts ? ' --updatePackageScripts' : '' + }` + ); } catch (e) { spinner.fail(); output.addNewline(); diff --git a/packages/nx/src/command-line/add/command-object.ts b/packages/nx/src/command-line/add/command-object.ts index a653f207220f9..1069f4cd6a331 100644 --- a/packages/nx/src/command-line/add/command-object.ts +++ b/packages/nx/src/command-line/add/command-object.ts @@ -2,6 +2,7 @@ import { CommandModule } from 'yargs'; export interface AddOptions { packageSpecifier: string; + updatePackageScripts?: boolean; verbose?: boolean; } @@ -18,6 +19,11 @@ export const yargsAddCommand: CommandModule< description: 'The package name and optional version (e.g. `@nx/react` or `@nx/react@latest`) to install and initialize. If the version is not specified it will install the same version as the `nx` package for Nx core plugins or the latest version for other packages', }) + .option('updatePackageScripts', { + type: 'boolean', + description: + 'Update `package.json` scripts with inferred targets. Defaults to `true` when `NX_PCV3=true` and the package is a core Nx plugin', + }) .option('verbose', { type: 'boolean', description: diff --git a/packages/playwright/src/generators/init/init.ts b/packages/playwright/src/generators/init/init.ts index 16b0b25fedc1d..418c9a20c39a9 100644 --- a/packages/playwright/src/generators/init/init.ts +++ b/packages/playwright/src/generators/init/init.ts @@ -7,6 +7,8 @@ import { Tree, updateNxJson, } from '@nx/devkit'; +import { updatePackageScripts } from '@nx/devkit/src/utils/update-package-scripts'; +import { createNodes } from '../../plugins/plugin'; import { nxVersion, playwrightVersion } from '../../utils/versions'; import { InitGeneratorSchema } from './schema'; @@ -32,6 +34,10 @@ export async function initGenerator(tree: Tree, options: InitGeneratorSchema) { addPlugin(tree); } + if (options.updatePackageScripts) { + await updatePackageScripts(tree, createNodes); + } + if (!options.skipFormat) { await formatFiles(tree); } diff --git a/packages/playwright/src/generators/init/schema.d.ts b/packages/playwright/src/generators/init/schema.d.ts index e45b4de80ab7b..94a79a37d8010 100644 --- a/packages/playwright/src/generators/init/schema.d.ts +++ b/packages/playwright/src/generators/init/schema.d.ts @@ -2,4 +2,5 @@ export interface InitGeneratorSchema { skipFormat?: boolean; skipPackageJson?: boolean; keepExistingVersions?: boolean; + updatePackageScripts?: boolean; } diff --git a/packages/playwright/src/generators/init/schema.json b/packages/playwright/src/generators/init/schema.json index 48acddb0d0420..8a8646c0c7d8a 100644 --- a/packages/playwright/src/generators/init/schema.json +++ b/packages/playwright/src/generators/init/schema.json @@ -22,6 +22,12 @@ "x-priority": "internal", "description": "Keep existing dependencies versions", "default": false + }, + "updatePackageScripts": { + "type": "boolean", + "x-priority": "internal", + "description": "Update `package.json` scripts with inferred targets", + "default": false } }, "required": [] diff --git a/packages/react-native/src/generators/init/init.ts b/packages/react-native/src/generators/init/init.ts index 5e8f8afa8fe58..bbb05726eba77 100644 --- a/packages/react-native/src/generators/init/init.ts +++ b/packages/react-native/src/generators/init/init.ts @@ -8,7 +8,8 @@ import { Tree, updateNxJson, } from '@nx/devkit'; -import { ReactNativePluginOptions } from '../../../plugins/plugin'; +import { updatePackageScripts } from '@nx/devkit/src/utils/update-package-scripts'; +import { createNodes, ReactNativePluginOptions } from '../../../plugins/plugin'; import { nxVersion, reactDomVersion, @@ -34,6 +35,10 @@ export async function reactNativeInitGenerator(host: Tree, schema: Schema) { tasks.push(updateDependencies(host, schema)); } + if (schema.updatePackageScripts) { + await updatePackageScripts(host, createNodes); + } + if (!schema.skipFormat) { await formatFiles(host); } diff --git a/packages/react-native/src/generators/init/schema.d.ts b/packages/react-native/src/generators/init/schema.d.ts index fb1f7ae29cd20..e8bc39b25489a 100644 --- a/packages/react-native/src/generators/init/schema.d.ts +++ b/packages/react-native/src/generators/init/schema.d.ts @@ -2,4 +2,5 @@ export interface Schema { skipFormat?: boolean; skipPackageJson?: boolean; //default is false keepExistingVersions?: boolean; //default is false + updatePackageScripts?: boolean; } diff --git a/packages/react-native/src/generators/init/schema.json b/packages/react-native/src/generators/init/schema.json index e219456225049..61d5d360c3b18 100644 --- a/packages/react-native/src/generators/init/schema.json +++ b/packages/react-native/src/generators/init/schema.json @@ -23,6 +23,12 @@ "x-priority": "internal", "description": "Keep existing dependencies versions", "default": false + }, + "updatePackageScripts": { + "type": "boolean", + "x-priority": "internal", + "description": "Update `package.json` scripts with inferred targets", + "default": false } }, "required": [] diff --git a/packages/react-native/src/utils/add-linting.spec.ts b/packages/react-native/src/utils/add-linting.spec.ts index 03fa628105fa1..847230b710359 100644 --- a/packages/react-native/src/utils/add-linting.spec.ts +++ b/packages/react-native/src/utils/add-linting.spec.ts @@ -15,8 +15,8 @@ describe('Add Linting', () => { }); }); - it('should add update configuration when eslint is passed', () => { - addLinting(tree, { + it('should add update configuration when eslint is passed', async () => { + await addLinting(tree, { projectName: 'my-lib', linter: Linter.EsLint, tsConfigPaths: ['libs/my-lib/tsconfig.lib.json'], @@ -29,7 +29,7 @@ describe('Add Linting', () => { }); it('should not add lint target when "none" is passed', async () => { - addLinting(tree, { + await addLinting(tree, { projectName: 'my-lib', linter: Linter.None, tsConfigPaths: ['libs/my-lib/tsconfig.lib.json'], diff --git a/packages/remix/src/generators/init/init.ts b/packages/remix/src/generators/init/init.ts index 6817a15ef344b..4c06cbd359fd7 100644 --- a/packages/remix/src/generators/init/init.ts +++ b/packages/remix/src/generators/init/init.ts @@ -7,8 +7,10 @@ import { addDependenciesToPackageJson, runTasksInSerial, } from '@nx/devkit'; -import { type Schema } from './schema'; +import { updatePackageScripts } from '@nx/devkit/src/utils/update-package-scripts'; +import { createNodes } from '../../plugins/plugin'; import { nxVersion, remixVersion } from '../../utils/versions'; +import { type Schema } from './schema'; function addPlugin(tree) { const nxJson = readNxJson(tree); @@ -60,6 +62,10 @@ export async function remixInitGenerator(tree: Tree, options: Schema) { addPlugin(tree); } + if (options.updatePackageScripts) { + await updatePackageScripts(tree, createNodes); + } + if (!options.skipFormat) { await formatFiles(tree); } diff --git a/packages/remix/src/generators/init/schema.d.ts b/packages/remix/src/generators/init/schema.d.ts index 9708e88a1511f..aa1a5bdf92231 100644 --- a/packages/remix/src/generators/init/schema.d.ts +++ b/packages/remix/src/generators/init/schema.d.ts @@ -2,4 +2,5 @@ export interface Schema { skipFormat?: boolean; skipPackageJson?: boolean; keepExistingVersions?: boolean; + updatePackageScripts?: boolean; } diff --git a/packages/remix/src/generators/init/schema.json b/packages/remix/src/generators/init/schema.json index 0187f610ff7d3..bc463e1171510 100644 --- a/packages/remix/src/generators/init/schema.json +++ b/packages/remix/src/generators/init/schema.json @@ -21,6 +21,12 @@ "x-priority": "internal", "description": "Keep existing dependencies versions", "default": false + }, + "updatePackageScripts": { + "type": "boolean", + "x-priority": "internal", + "description": "Update `package.json` scripts with inferred targets", + "default": false } }, "required": [] diff --git a/packages/storybook/src/generators/init/init.ts b/packages/storybook/src/generators/init/init.ts index d9f0ff736bb5e..7da20d931524c 100644 --- a/packages/storybook/src/generators/init/init.ts +++ b/packages/storybook/src/generators/init/init.ts @@ -9,7 +9,9 @@ import { updateJson, updateNxJson, } from '@nx/devkit'; +import { updatePackageScripts } from '@nx/devkit/src/utils/update-package-scripts'; import { gte } from 'semver'; +import { createNodes } from '../../plugins/plugin'; import { addPlugin, getInstalledStorybookVersion, @@ -100,6 +102,10 @@ export async function initGenerator(tree: Tree, schema: Schema) { tasks.push(checkDependenciesInstalled(tree, schema)); } + if (schema.updatePackageScripts) { + await updatePackageScripts(tree, createNodes); + } + if (!schema.skipFormat) { await formatFiles(tree); } diff --git a/packages/storybook/src/generators/init/schema.d.ts b/packages/storybook/src/generators/init/schema.d.ts index 9708e88a1511f..aa1a5bdf92231 100644 --- a/packages/storybook/src/generators/init/schema.d.ts +++ b/packages/storybook/src/generators/init/schema.d.ts @@ -2,4 +2,5 @@ export interface Schema { skipFormat?: boolean; skipPackageJson?: boolean; keepExistingVersions?: boolean; + updatePackageScripts?: boolean; } diff --git a/packages/storybook/src/generators/init/schema.json b/packages/storybook/src/generators/init/schema.json index f60a205c85994..f283d6b3a17fa 100644 --- a/packages/storybook/src/generators/init/schema.json +++ b/packages/storybook/src/generators/init/schema.json @@ -20,6 +20,12 @@ "x-priority": "internal", "description": "Keep existing dependencies versions", "default": false + }, + "updatePackageScripts": { + "type": "boolean", + "x-priority": "internal", + "description": "Update `package.json` scripts with inferred targets", + "default": false } } } diff --git a/packages/vite/src/generators/init/init.ts b/packages/vite/src/generators/init/init.ts index 56be72ee0111a..6a724706de3b3 100644 --- a/packages/vite/src/generators/init/init.ts +++ b/packages/vite/src/generators/init/init.ts @@ -6,7 +6,9 @@ import { Tree, updateNxJson, } from '@nx/devkit'; +import { updatePackageScripts } from '@nx/devkit/src/utils/update-package-scripts'; +import { createNodes } from '../../plugins/plugin'; import { InitGeneratorSchema } from './schema'; import { addPlugin, @@ -59,6 +61,10 @@ export async function initGenerator(tree: Tree, schema: InitGeneratorSchema) { tasks.push(checkDependenciesInstalled(tree, schema)); } + if (schema.updatePackageScripts) { + await updatePackageScripts(tree, createNodes); + } + if (!schema.skipFormat) { await formatFiles(tree); } diff --git a/packages/vite/src/generators/init/schema.d.ts b/packages/vite/src/generators/init/schema.d.ts index e45b4de80ab7b..94a79a37d8010 100644 --- a/packages/vite/src/generators/init/schema.d.ts +++ b/packages/vite/src/generators/init/schema.d.ts @@ -2,4 +2,5 @@ export interface InitGeneratorSchema { skipFormat?: boolean; skipPackageJson?: boolean; keepExistingVersions?: boolean; + updatePackageScripts?: boolean; } diff --git a/packages/vite/src/generators/init/schema.json b/packages/vite/src/generators/init/schema.json index 957e60f87cc7a..028676411f84b 100644 --- a/packages/vite/src/generators/init/schema.json +++ b/packages/vite/src/generators/init/schema.json @@ -20,6 +20,12 @@ "x-priority": "internal", "description": "Keep existing dependencies versions", "default": false + }, + "updatePackageScripts": { + "type": "boolean", + "x-priority": "internal", + "description": "Update `package.json` scripts with inferred targets", + "default": false } } } diff --git a/packages/webpack/src/generators/init/init.ts b/packages/webpack/src/generators/init/init.ts index 53b9352be5ece..b2838757823dd 100644 --- a/packages/webpack/src/generators/init/init.ts +++ b/packages/webpack/src/generators/init/init.ts @@ -6,7 +6,8 @@ import { Tree, updateNxJson, } from '@nx/devkit'; -import { WebpackPluginOptions } from '../../plugins/plugin'; +import { updatePackageScripts } from '@nx/devkit/src/utils/update-package-scripts'; +import { createNodes, WebpackPluginOptions } from '../../plugins/plugin'; import { nxVersion, webpackCliVersion } from '../../utils/versions'; import { Schema } from './schema'; @@ -36,6 +37,10 @@ export async function webpackInitGenerator(tree: Tree, schema: Schema) { ); } + if (schema.updatePackageScripts) { + await updatePackageScripts(tree, createNodes); + } + if (!schema.skipFormat) { await formatFiles(tree); } diff --git a/packages/webpack/src/generators/init/schema.d.ts b/packages/webpack/src/generators/init/schema.d.ts index 9708e88a1511f..aa1a5bdf92231 100644 --- a/packages/webpack/src/generators/init/schema.d.ts +++ b/packages/webpack/src/generators/init/schema.d.ts @@ -2,4 +2,5 @@ export interface Schema { skipFormat?: boolean; skipPackageJson?: boolean; keepExistingVersions?: boolean; + updatePackageScripts?: boolean; } diff --git a/packages/webpack/src/generators/init/schema.json b/packages/webpack/src/generators/init/schema.json index 582ce28eb1a5d..4ca7c87ef9083 100644 --- a/packages/webpack/src/generators/init/schema.json +++ b/packages/webpack/src/generators/init/schema.json @@ -21,6 +21,12 @@ "x-priority": "internal", "description": "Keep existing dependencies versions", "default": false + }, + "updatePackageScripts": { + "type": "boolean", + "x-priority": "internal", + "description": "Update `package.json` scripts with inferred targets", + "default": false } }, "required": []