diff --git a/docs/generated/packages/detox/generators/application.json b/docs/generated/packages/detox/generators/application.json index 05910a9840..c432051d91 100644 --- a/docs/generated/packages/detox/generators/application.json +++ b/docs/generated/packages/detox/generators/application.json @@ -58,6 +58,10 @@ "type": "boolean", "description": "Whether or not to configure the ESLint `parserOptions.project` option. We do not do this by default for lint performance reasons.", "default": false + }, + "useProjectJson": { + "type": "boolean", + "description": "Use a `project.json` configuration file instead of inlining the Nx configuration in the `package.json` file." } }, "required": ["e2eDirectory", "appProject", "framework"], diff --git a/docs/generated/packages/expo/generators/application.json b/docs/generated/packages/expo/generators/application.json index 389e3f0c5e..08b71b7b5a 100644 --- a/docs/generated/packages/expo/generators/application.json +++ b/docs/generated/packages/expo/generators/application.json @@ -86,6 +86,10 @@ "type": "boolean", "description": "Do not add dependencies to `package.json`.", "default": false + }, + "useProjectJson": { + "type": "boolean", + "description": "Use a `project.json` configuration file instead of inlining the Nx configuration in the `package.json` file." } }, "required": ["directory"], diff --git a/docs/generated/packages/expo/generators/library.json b/docs/generated/packages/expo/generators/library.json index ef35d1e653..d78f62fe22 100644 --- a/docs/generated/packages/expo/generators/library.json +++ b/docs/generated/packages/expo/generators/library.json @@ -89,6 +89,10 @@ "type": "boolean", "description": "Do not add dependencies to `package.json`.", "default": false + }, + "useProjectJson": { + "type": "boolean", + "description": "Use a `project.json` configuration file instead of inlining the Nx configuration in the `package.json` file." } }, "required": ["directory"], diff --git a/docs/generated/packages/express/generators/application.json b/docs/generated/packages/express/generators/application.json index 78107a3658..5154b7fa78 100644 --- a/docs/generated/packages/express/generators/application.json +++ b/docs/generated/packages/express/generators/application.json @@ -73,6 +73,10 @@ "type": "boolean", "description": "Whether or not to configure the ESLint `parserOptions.project` option. We do not do this by default for lint performance reasons.", "default": false + }, + "useProjectJson": { + "type": "boolean", + "description": "Use a `project.json` configuration file instead of inlining the Nx configuration in the `package.json` file." } }, "required": ["directory"], diff --git a/docs/generated/packages/nest/generators/application.json b/docs/generated/packages/nest/generators/application.json index 27826e6cfe..c182d1e18d 100644 --- a/docs/generated/packages/nest/generators/application.json +++ b/docs/generated/packages/nest/generators/application.json @@ -78,6 +78,10 @@ "type": "boolean", "description": "Adds strictNullChecks, noImplicitAny, strictBindCallApply, forceConsistentCasingInFileNames and noFallthroughCasesInSwitch to tsconfig.", "default": false + }, + "useProjectJson": { + "type": "boolean", + "description": "Use a `project.json` configuration file instead of inlining the Nx configuration in the `package.json` file." } }, "additionalProperties": false, diff --git a/docs/generated/packages/nest/generators/library.json b/docs/generated/packages/nest/generators/library.json index fd58e65257..3e60336887 100644 --- a/docs/generated/packages/nest/generators/library.json +++ b/docs/generated/packages/nest/generators/library.json @@ -133,6 +133,10 @@ "description": "Don't include the directory in the name of the module of the library.", "type": "boolean", "default": false + }, + "useProjectJson": { + "type": "boolean", + "description": "Use a `project.json` configuration file instead of inlining the Nx configuration in the `package.json` file." } }, "additionalProperties": false, diff --git a/docs/generated/packages/next/generators/application.json b/docs/generated/packages/next/generators/application.json index fbba2beb03..a3da8465b4 100644 --- a/docs/generated/packages/next/generators/application.json +++ b/docs/generated/packages/next/generators/application.json @@ -138,6 +138,10 @@ "default": false, "hidden": true, "x-priority": "internal" + }, + "useProjectJson": { + "type": "boolean", + "description": "Use a `project.json` configuration file instead of inlining the Nx configuration in the `package.json` file." } }, "required": ["directory"], diff --git a/docs/generated/packages/next/generators/library.json b/docs/generated/packages/next/generators/library.json index 5b3cc7a3aa..73ebcac2e6 100644 --- a/docs/generated/packages/next/generators/library.json +++ b/docs/generated/packages/next/generators/library.json @@ -152,6 +152,10 @@ "default": false, "description": "Do not add dependencies to `package.json`.", "x-priority": "internal" + }, + "useProjectJson": { + "type": "boolean", + "description": "Use a `project.json` configuration file instead of inlining the Nx configuration in the `package.json` file." } }, "required": ["directory"], diff --git a/docs/generated/packages/node/generators/application.json b/docs/generated/packages/node/generators/application.json index 8adcfa844b..d77d124d59 100644 --- a/docs/generated/packages/node/generators/application.json +++ b/docs/generated/packages/node/generators/application.json @@ -123,6 +123,10 @@ "docker": { "type": "boolean", "description": "Add a docker build target" + }, + "useProjectJson": { + "type": "boolean", + "description": "Use a `project.json` configuration file instead of inlining the Nx configuration in the `package.json` file." } }, "required": ["directory"], diff --git a/docs/generated/packages/node/generators/library.json b/docs/generated/packages/node/generators/library.json index f6b9ab4d73..7515a4cdc7 100644 --- a/docs/generated/packages/node/generators/library.json +++ b/docs/generated/packages/node/generators/library.json @@ -123,6 +123,10 @@ "type": "boolean", "description": "Whether or not to configure the ESLint `parserOptions.project`. We do not do this by default for lint performance reasons.", "default": false + }, + "useProjectJson": { + "type": "boolean", + "description": "Use a `project.json` configuration file instead of inlining the Nx configuration in the `package.json` file." } }, "required": ["directory"], diff --git a/docs/generated/packages/nuxt/generators/application.json b/docs/generated/packages/nuxt/generators/application.json index 4877abbc54..54798b71f2 100644 --- a/docs/generated/packages/nuxt/generators/application.json +++ b/docs/generated/packages/nuxt/generators/application.json @@ -1,6 +1,6 @@ { "name": "application", - "factory": "./src/generators/application/application", + "factory": "./src/generators/application/application#applicationGeneratorInternal", "schema": { "$schema": "https://json-schema.org/schema", "cli": "nx", @@ -100,6 +100,10 @@ "type": "boolean", "description": "Whether or not to configure the ESLint `parserOptions.project` option. We do not do this by default for lint performance reasons.", "default": false + }, + "useProjectJson": { + "type": "boolean", + "description": "Use a `project.json` configuration file instead of inlining the Nx configuration in the `package.json` file." } }, "required": ["directory"], @@ -108,7 +112,7 @@ }, "aliases": ["app"], "description": "Create a Nuxt application.", - "implementation": "/packages/nuxt/src/generators/application/application.ts", + "implementation": "/packages/nuxt/src/generators/application/application#applicationGeneratorInternal.ts", "hidden": false, "path": "/packages/nuxt/src/generators/application/schema.json", "type": "generator" diff --git a/docs/generated/packages/react-native/generators/application.json b/docs/generated/packages/react-native/generators/application.json index 658f790279..f3bb2c59bf 100644 --- a/docs/generated/packages/react-native/generators/application.json +++ b/docs/generated/packages/react-native/generators/application.json @@ -95,6 +95,10 @@ "x-prompt": "Which bundler do you want to use to build the application?", "default": "vite", "x-priority": "important" + }, + "useProjectJson": { + "type": "boolean", + "description": "Use a `project.json` configuration file instead of inlining the Nx configuration in the `package.json` file." } }, "required": ["directory"], diff --git a/docs/generated/packages/react-native/generators/library.json b/docs/generated/packages/react-native/generators/library.json index 919ddf8b68..860d883ac4 100644 --- a/docs/generated/packages/react-native/generators/library.json +++ b/docs/generated/packages/react-native/generators/library.json @@ -93,6 +93,10 @@ "type": "boolean", "default": false, "x-priority": "internal" + }, + "useProjectJson": { + "type": "boolean", + "description": "Use a `project.json` configuration file instead of inlining the Nx configuration in the `package.json` file." } }, "required": ["directory"], diff --git a/docs/generated/packages/react/generators/application.json b/docs/generated/packages/react/generators/application.json index ca2d6d8128..e4f2ecedfd 100644 --- a/docs/generated/packages/react/generators/application.json +++ b/docs/generated/packages/react/generators/application.json @@ -181,6 +181,10 @@ "description": "Generate a React app with a minimal setup, no separate test files.", "type": "boolean", "default": false + }, + "useProjectJson": { + "type": "boolean", + "description": "Use a `project.json` configuration file instead of inlining the Nx configuration in the `package.json` file." } }, "required": ["directory"], diff --git a/docs/generated/packages/react/generators/library.json b/docs/generated/packages/react/generators/library.json index 6133297c0d..c990b10fc2 100644 --- a/docs/generated/packages/react/generators/library.json +++ b/docs/generated/packages/react/generators/library.json @@ -184,6 +184,10 @@ "description": "Don't include the directory in the name of the module of the library.", "type": "boolean", "default": false + }, + "useProjectJson": { + "type": "boolean", + "description": "Use a `project.json` configuration file instead of inlining the Nx configuration in the `package.json` file." } }, "required": ["directory"], diff --git a/docs/generated/packages/remix/generators/application.json b/docs/generated/packages/remix/generators/application.json index 773e6e4b84..4a567b324a 100644 --- a/docs/generated/packages/remix/generators/application.json +++ b/docs/generated/packages/remix/generators/application.json @@ -57,6 +57,10 @@ "type": "boolean", "x-priority": "internal", "default": false + }, + "useProjectJson": { + "type": "boolean", + "description": "Use a `project.json` configuration file instead of inlining the Nx configuration in the `package.json` file." } }, "required": ["directory"], diff --git a/docs/generated/packages/remix/generators/library.json b/docs/generated/packages/remix/generators/library.json index 0844b41c46..041cd52b6f 100644 --- a/docs/generated/packages/remix/generators/library.json +++ b/docs/generated/packages/remix/generators/library.json @@ -81,6 +81,10 @@ "default": false, "description": "Generate a buildable library that uses rollup to bundle.", "x-deprecated": "Use the `bundler` option for greater control (none, vite, rollup)." + }, + "useProjectJson": { + "type": "boolean", + "description": "Use a `project.json` configuration file instead of inlining the Nx configuration in the `package.json` file." } }, "required": ["directory"], diff --git a/docs/generated/packages/vue/generators/application.json b/docs/generated/packages/vue/generators/application.json index 38c268c383..0bf94d4c88 100644 --- a/docs/generated/packages/vue/generators/application.json +++ b/docs/generated/packages/vue/generators/application.json @@ -133,6 +133,10 @@ "type": "boolean", "default": false, "hidden": true + }, + "useProjectJson": { + "type": "boolean", + "description": "Use a `project.json` configuration file instead of inlining the Nx configuration in the `package.json` file." } }, "required": ["directory"], diff --git a/docs/generated/packages/vue/generators/library.json b/docs/generated/packages/vue/generators/library.json index 418e359c36..d2c045d974 100644 --- a/docs/generated/packages/vue/generators/library.json +++ b/docs/generated/packages/vue/generators/library.json @@ -125,6 +125,10 @@ "description": "Create a Vue library with a minimal setup, no separate test files.", "type": "boolean", "default": false + }, + "useProjectJson": { + "type": "boolean", + "description": "Use a `project.json` configuration file instead of inlining the Nx configuration in the `package.json` file." } }, "required": ["directory"], diff --git a/docs/generated/packages/web/generators/application.json b/docs/generated/packages/web/generators/application.json index a21a2118ed..cb8bc43c37 100644 --- a/docs/generated/packages/web/generators/application.json +++ b/docs/generated/packages/web/generators/application.json @@ -101,6 +101,10 @@ "type": "boolean", "description": "Creates an application with strict mode and strict type checking.", "default": true + }, + "useProjectJson": { + "type": "boolean", + "description": "Use a `project.json` configuration file instead of inlining the Nx configuration in the `package.json` file." } }, "required": ["directory"], diff --git a/packages/detox/src/generators/application/application.spec.ts b/packages/detox/src/generators/application/application.spec.ts index acb80d8f8d..49f5513b14 100644 --- a/packages/detox/src/generators/application/application.spec.ts +++ b/packages/detox/src/generators/application/application.spec.ts @@ -531,6 +531,7 @@ describe('detox application generator', () => { linter: Linter.None, framework: 'react-native', addPlugin: true, + useProjectJson: false, }); expect(tree.read('tsconfig.json', 'utf-8')).toMatchInlineSnapshot(` @@ -592,6 +593,7 @@ describe('detox application generator', () => { framework: 'react-native', addPlugin: true, skipFormat: true, + useProjectJson: false, }); expect(tree.exists('apps/my-app-e2e/test-setup.ts')).toBeTruthy(); @@ -672,6 +674,7 @@ describe('detox application generator', () => { framework: 'react-native', addPlugin: true, skipFormat: true, + useProjectJson: false, }); const packageJson = readJson(tree, 'apps/my-app-e2e/package.json'); @@ -687,5 +690,38 @@ describe('detox application generator', () => { ] `); }); + + it('should generate project.json if useProjectJson is true', async () => { + writeJson(tree, 'apps/my-app/package.json', { name: 'my-app' }); + + await detoxApplicationGenerator(tree, { + e2eDirectory: 'apps/my-app-e2e', + appProject: 'my-app', + e2eName: 'my-app-e2e', + linter: Linter.None, + framework: 'react-native', + addPlugin: true, + skipFormat: true, + useProjectJson: true, + }); + + expect(tree.exists('apps/my-app-e2e/project.json')).toBeTruthy(); + expect(readProjectConfiguration(tree, 'my-app-e2e')) + .toMatchInlineSnapshot(` + { + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "implicitDependencies": [ + "my-app", + ], + "name": "my-app-e2e", + "projectType": "application", + "root": "apps/my-app-e2e", + "sourceRoot": "apps/my-app-e2e/src", + "tags": [], + "targets": {}, + } + `); + expect(readJson(tree, 'apps/my-app-e2e/package.json').nx).toBeUndefined(); + }); }); }); diff --git a/packages/detox/src/generators/application/application.ts b/packages/detox/src/generators/application/application.ts index 018f755b44..ca61ace6c9 100644 --- a/packages/detox/src/generators/application/application.ts +++ b/packages/detox/src/generators/application/application.ts @@ -18,6 +18,7 @@ import { sortPackageJsonFields } from '@nx/js/src/utils/package-json/sort-fields export async function detoxApplicationGenerator(host: Tree, schema: Schema) { return await detoxApplicationGeneratorInternal(host, { addPlugin: false, + useProjectJson: true, ...schema, }); } diff --git a/packages/detox/src/generators/application/lib/add-project.spec.ts b/packages/detox/src/generators/application/lib/add-project.spec.ts index a88a27b215..177c048614 100644 --- a/packages/detox/src/generators/application/lib/add-project.spec.ts +++ b/packages/detox/src/generators/application/lib/add-project.spec.ts @@ -41,6 +41,7 @@ describe('Add Project', () => { appRoot: 'apps/my-app', linter: Linter.EsLint, framework: 'react-native', + useProjectJson: true, }); }); @@ -91,6 +92,7 @@ describe('Add Project', () => { appRoot: 'apps/my-dir/my-app', linter: Linter.EsLint, framework: 'react-native', + useProjectJson: true, }); }); diff --git a/packages/detox/src/generators/application/lib/add-project.ts b/packages/detox/src/generators/application/lib/add-project.ts index 3afccb4507..59adbbc16b 100644 --- a/packages/detox/src/generators/application/lib/add-project.ts +++ b/packages/detox/src/generators/application/lib/add-project.ts @@ -13,7 +13,7 @@ import { reactNativeTestTarget, } from './get-targets'; import { NormalizedSchema } from './normalize-options'; -import { isUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup'; +import type { PackageJson } from 'nx/src/utils/package-json'; export function addProject(host: Tree, options: NormalizedSchema) { const nxJson = readNxJson(host); @@ -23,20 +23,21 @@ export function addProject(host: Tree, options: NormalizedSchema) { : p.plugin === '@nx/detox/plugin' ); - if (isUsingTsSolutionSetup(host)) { - writeJson(host, joinPathFragments(options.e2eProjectRoot, 'package.json'), { - name: options.importPath, - version: '0.0.1', - private: true, - nx: { - name: - options.e2eProjectName !== options.importPath - ? options.e2eProjectName - : undefined, - targets: hasPlugin ? undefined : getTargets(options), - implicitDependencies: [options.appProject], - }, - }); + const packageJson: PackageJson = { + name: options.importPath, + version: '0.0.1', + private: true, + }; + + if (!options.useProjectJson) { + packageJson.nx = { + name: + options.e2eProjectName !== options.importPath + ? options.e2eProjectName + : undefined, + targets: hasPlugin ? undefined : getTargets(options), + implicitDependencies: [options.appProject], + }; } else { addProjectConfiguration(host, options.e2eProjectName, { root: options.e2eProjectRoot, @@ -47,6 +48,14 @@ export function addProject(host: Tree, options: NormalizedSchema) { implicitDependencies: [options.appProject], }); } + + if (!options.useProjectJson || options.isUsingTsSolutionConfig) { + writeJson( + host, + joinPathFragments(options.e2eProjectRoot, 'package.json'), + packageJson + ); + } } function getTargets(options: NormalizedSchema) { diff --git a/packages/detox/src/generators/application/lib/normalize-options.spec.ts b/packages/detox/src/generators/application/lib/normalize-options.spec.ts index 2bcc6fe504..8efa3033dc 100644 --- a/packages/detox/src/generators/application/lib/normalize-options.spec.ts +++ b/packages/detox/src/generators/application/lib/normalize-options.spec.ts @@ -40,6 +40,7 @@ describe('Normalize Options', () => { isUsingTsSolutionConfig: false, linter: Linter.EsLint, js: false, + useProjectJson: true, }); }); @@ -70,6 +71,7 @@ describe('Normalize Options', () => { framework: 'react-native', isUsingTsSolutionConfig: false, js: false, + useProjectJson: true, }); }); @@ -101,6 +103,7 @@ describe('Normalize Options', () => { framework: 'react-native', isUsingTsSolutionConfig: false, js: false, + useProjectJson: true, }); }); }); diff --git a/packages/detox/src/generators/application/lib/normalize-options.ts b/packages/detox/src/generators/application/lib/normalize-options.ts index f80ce4195a..063d5871be 100644 --- a/packages/detox/src/generators/application/lib/normalize-options.ts +++ b/packages/detox/src/generators/application/lib/normalize-options.ts @@ -41,6 +41,8 @@ export async function normalizeOptions( const isUsingTsSolutionConfig = isUsingTsSolutionSetup(host); const e2eProjectName = !isUsingTsSolutionConfig || options.e2eName ? projectName : importPath; + // We default to generate a project.json file if the new setup is not being used + const useProjectJson = options.useProjectJson ?? !isUsingTsSolutionConfig; return { ...options, @@ -54,5 +56,6 @@ export async function normalizeOptions( importPath, isUsingTsSolutionConfig, js: options.js ?? false, + useProjectJson, }; } diff --git a/packages/detox/src/generators/application/schema.d.ts b/packages/detox/src/generators/application/schema.d.ts index 68e5c28020..966a08985d 100644 --- a/packages/detox/src/generators/application/schema.d.ts +++ b/packages/detox/src/generators/application/schema.d.ts @@ -12,4 +12,5 @@ export interface Schema { setParserOptionsProject?: boolean; framework: 'react-native' | 'expo'; addPlugin?: boolean; + useProjectJson?: boolean; } diff --git a/packages/detox/src/generators/application/schema.json b/packages/detox/src/generators/application/schema.json index 9e15e45017..0a167d77b5 100644 --- a/packages/detox/src/generators/application/schema.json +++ b/packages/detox/src/generators/application/schema.json @@ -60,6 +60,10 @@ "type": "boolean", "description": "Whether or not to configure the ESLint `parserOptions.project` option. We do not do this by default for lint performance reasons.", "default": false + }, + "useProjectJson": { + "type": "boolean", + "description": "Use a `project.json` configuration file instead of inlining the Nx configuration in the `package.json` file." } }, "required": ["e2eDirectory", "appProject", "framework"] diff --git a/packages/expo/src/generators/application/application.spec.ts b/packages/expo/src/generators/application/application.spec.ts index 99a715e37b..154288fa9d 100644 --- a/packages/expo/src/generators/application/application.spec.ts +++ b/packages/expo/src/generators/application/application.spec.ts @@ -482,7 +482,7 @@ describe('app', () => { `); }); - it('should respect provided name', async () => { + it('should respect the provided name', async () => { await expoApplicationGenerator(tree, { directory: 'my-app', name: 'my-app', @@ -493,6 +493,7 @@ describe('app', () => { js: false, unitTestRunner: 'jest', addPlugin: true, + useProjectJson: false, }); const packageJson = readJson(tree, 'my-app/package.json'); @@ -508,5 +509,50 @@ describe('app', () => { ] `); }); + + it('should generate project.json if useProjectJson is true', async () => { + await expoApplicationGenerator(tree, { + directory: 'my-app', + linter: Linter.EsLint, + e2eTestRunner: 'cypress', + useProjectJson: true, + unitTestRunner: 'none', + js: false, + addPlugin: true, + skipFormat: true, + }); + + expect(tree.exists('my-app/project.json')).toBeTruthy(); + expect(readProjectConfiguration(tree, '@proj/my-app')) + .toMatchInlineSnapshot(` + { + "$schema": "../node_modules/nx/schemas/project-schema.json", + "name": "@proj/my-app", + "projectType": "application", + "root": "my-app", + "sourceRoot": "my-app/src", + "tags": [], + "targets": {}, + } + `); + expect(readJson(tree, 'my-app/package.json').nx).toBeUndefined(); + expect(tree.exists('my-app-e2e/project.json')).toBeTruthy(); + expect(readProjectConfiguration(tree, '@proj/my-app-e2e')) + .toMatchInlineSnapshot(` + { + "$schema": "../node_modules/nx/schemas/project-schema.json", + "implicitDependencies": [ + "@proj/my-app", + ], + "name": "@proj/my-app-e2e", + "projectType": "application", + "root": "my-app-e2e", + "sourceRoot": "my-app-e2e/src", + "tags": [], + "targets": {}, + } + `); + expect(readJson(tree, 'my-app-e2e/package.json').nx).toBeUndefined(); + }); }); }); diff --git a/packages/expo/src/generators/application/application.ts b/packages/expo/src/generators/application/application.ts index e4b4a82965..6e3249a46f 100644 --- a/packages/expo/src/generators/application/application.ts +++ b/packages/expo/src/generators/application/application.ts @@ -32,6 +32,7 @@ export async function expoApplicationGenerator( ): Promise { return await expoApplicationGeneratorInternal(host, { addPlugin: false, + useProjectJson: true, ...schema, }); } diff --git a/packages/expo/src/generators/application/lib/add-e2e.ts b/packages/expo/src/generators/application/lib/add-e2e.ts index 63e0279e7a..babc6187c6 100644 --- a/packages/expo/src/generators/application/lib/add-e2e.ts +++ b/packages/expo/src/generators/application/lib/add-e2e.ts @@ -7,15 +7,14 @@ import { Tree, writeJson, } from '@nx/devkit'; -import { webStaticServeGenerator } from '@nx/web'; - -import { nxVersion } from '../../../utils/versions'; -import { hasExpoPlugin } from '../../../utils/has-expo-plugin'; -import { NormalizedSchema } from './normalize-options'; +import { getE2EWebServerInfo } from '@nx/devkit/src/generators/e2e-web-server-info-utils'; import { addE2eCiTargetDefaults } from '@nx/devkit/src/generators/target-defaults-utils'; import { findPluginForConfigFile } from '@nx/devkit/src/utils/find-plugin-for-config-file'; -import { getE2EWebServerInfo } from '@nx/devkit/src/generators/e2e-web-server-info-utils'; -import { isUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup'; +import { webStaticServeGenerator } from '@nx/web'; +import type { PackageJson } from 'nx/src/utils/package-json'; +import { hasExpoPlugin } from '../../../utils/has-expo-plugin'; +import { nxVersion } from '../../../utils/versions'; +import { NormalizedSchema } from './normalize-options'; export async function addE2e( tree: Tree, @@ -42,19 +41,16 @@ export async function addE2e( typeof import('@nx/cypress') >('@nx/cypress', nxVersion); - if (isUsingTsSolutionSetup(tree)) { - writeJson( - tree, - joinPathFragments(options.e2eProjectRoot, 'package.json'), - { - name: options.e2eProjectName, - version: '0.0.1', - private: true, - nx: { - implicitDependencies: [options.projectName], - }, - } - ); + const packageJson: PackageJson = { + name: options.e2eProjectName, + version: '0.0.1', + private: true, + }; + + if (!options.useProjectJson) { + packageJson.nx = { + implicitDependencies: [options.projectName], + }; } else { addProjectConfiguration(tree, options.e2eProjectName, { projectType: 'application', @@ -66,6 +62,14 @@ export async function addE2e( }); } + if (!options.useProjectJson || options.isTsSolutionSetup) { + writeJson( + tree, + joinPathFragments(options.e2eProjectRoot, 'package.json'), + packageJson + ); + } + const e2eTask = await configurationGenerator(tree, { ...options, project: options.e2eProjectName, @@ -123,19 +127,16 @@ export async function addE2e( const { configurationGenerator } = ensurePackage< typeof import('@nx/playwright') >('@nx/playwright', nxVersion); - if (isUsingTsSolutionSetup(tree)) { - writeJson( - tree, - joinPathFragments(options.e2eProjectRoot, 'package.json'), - { - name: options.e2eProjectName, - version: '0.0.1', - private: true, - nx: { - implicitDependencies: [options.projectName], - }, - } - ); + const packageJson: PackageJson = { + name: options.e2eProjectName, + version: '0.0.1', + private: true, + }; + + if (!options.useProjectJson) { + packageJson.nx = { + implicitDependencies: [options.projectName], + }; } else { addProjectConfiguration(tree, options.e2eProjectName, { projectType: 'application', @@ -143,9 +144,18 @@ export async function addE2e( sourceRoot: joinPathFragments(options.e2eProjectRoot, 'src'), targets: {}, implicitDependencies: [options.projectName], + tags: [], }); } + if (!options.useProjectJson || options.isTsSolutionSetup) { + writeJson( + tree, + joinPathFragments(options.e2eProjectRoot, 'package.json'), + packageJson + ); + } + const e2eTask = await configurationGenerator(tree, { project: options.e2eProjectName, skipFormat: true, diff --git a/packages/expo/src/generators/application/lib/add-project.ts b/packages/expo/src/generators/application/lib/add-project.ts index fd43014fae..3922cab48e 100644 --- a/packages/expo/src/generators/application/lib/add-project.ts +++ b/packages/expo/src/generators/application/lib/add-project.ts @@ -6,12 +6,10 @@ import { Tree, writeJson, } from '@nx/devkit'; - +import { addBuildTargetDefaults } from '@nx/devkit/src/generators/target-defaults-utils'; +import type { PackageJson } from 'nx/src/utils/package-json'; import { hasExpoPlugin } from '../../../utils/has-expo-plugin'; import { NormalizedSchema } from './normalize-options'; -import { addBuildTargetDefaults } from '@nx/devkit/src/generators/target-defaults-utils'; -import { isUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup'; -import type { PackageJson } from 'nx/src/utils/package-json'; export function addProject(host: Tree, options: NormalizedSchema) { const hasPlugin = hasExpoPlugin(host); @@ -28,13 +26,13 @@ export function addProject(host: Tree, options: NormalizedSchema) { tags: options.parsedTags, }; - if (isUsingTsSolutionSetup(host)) { - const packageJson: PackageJson = { - name: options.importPath, - version: '0.0.1', - private: true, - }; + const packageJson: PackageJson = { + name: options.importPath, + version: '0.0.1', + private: true, + }; + if (!options.useProjectJson) { if (options.importPath !== options.projectName) { packageJson.nx = { name: options.projectName }; } @@ -46,12 +44,6 @@ export function addProject(host: Tree, options: NormalizedSchema) { packageJson.nx ??= {}; packageJson.nx.tags = options.parsedTags; } - - writeJson( - host, - joinPathFragments(options.appProjectRoot, 'package.json'), - packageJson - ); } else { addProjectConfiguration( host, @@ -60,6 +52,14 @@ export function addProject(host: Tree, options: NormalizedSchema) { options.standaloneConfig ); } + + if (!options.useProjectJson || options.isTsSolutionSetup) { + writeJson( + host, + joinPathFragments(options.appProjectRoot, 'package.json'), + packageJson + ); + } } function getTargets(options: NormalizedSchema) { diff --git a/packages/expo/src/generators/application/lib/normalize-options.spec.ts b/packages/expo/src/generators/application/lib/normalize-options.spec.ts index 2a1c81d8b5..4a54588521 100644 --- a/packages/expo/src/generators/application/lib/normalize-options.spec.ts +++ b/packages/expo/src/generators/application/lib/normalize-options.spec.ts @@ -41,6 +41,7 @@ describe('Normalize Options', () => { e2eProjectName: 'my-app-e2e', e2eProjectRoot: 'my-app-e2e', isTsSolutionSetup: false, + useProjectJson: true, } as NormalizedSchema); }); @@ -74,6 +75,7 @@ describe('Normalize Options', () => { e2eProjectName: 'myApp-e2e', e2eProjectRoot: 'myApp-e2e', isTsSolutionSetup: false, + useProjectJson: true, } as NormalizedSchema); }); @@ -109,6 +111,7 @@ describe('Normalize Options', () => { e2eProjectName: 'my-app-e2e', e2eProjectRoot: 'directory-e2e', isTsSolutionSetup: false, + useProjectJson: true, } as NormalizedSchema); }); @@ -142,6 +145,7 @@ describe('Normalize Options', () => { e2eProjectName: 'my-app-e2e', e2eProjectRoot: 'directory/my-app-e2e', isTsSolutionSetup: false, + useProjectJson: true, } as NormalizedSchema); }); @@ -176,6 +180,7 @@ describe('Normalize Options', () => { e2eProjectName: 'my-app-e2e', e2eProjectRoot: 'my-app-e2e', isTsSolutionSetup: false, + useProjectJson: true, } as NormalizedSchema); }); }); diff --git a/packages/expo/src/generators/application/lib/normalize-options.ts b/packages/expo/src/generators/application/lib/normalize-options.ts index 45f5d5724f..02bb0a1170 100644 --- a/packages/expo/src/generators/application/lib/normalize-options.ts +++ b/packages/expo/src/generators/application/lib/normalize-options.ts @@ -51,6 +51,7 @@ export async function normalizeOptions( const isTsSolutionSetup = isUsingTsSolutionSetup(host); const appProjectName = !isTsSolutionSetup || options.name ? projectName : importPath; + const useProjectJson = options.useProjectJson ?? !isTsSolutionSetup; const e2eProjectName = rootProject ? 'e2e' : `${appProjectName}-e2e`; const e2eProjectRoot = rootProject ? 'e2e' : `${appProjectRoot}-e2e`; @@ -71,5 +72,6 @@ export async function normalizeOptions( e2eProjectName, e2eProjectRoot, isTsSolutionSetup, + useProjectJson, }; } diff --git a/packages/expo/src/generators/application/schema.d.ts b/packages/expo/src/generators/application/schema.d.ts index 692602357b..2ddfedbb97 100644 --- a/packages/expo/src/generators/application/schema.d.ts +++ b/packages/expo/src/generators/application/schema.d.ts @@ -20,4 +20,5 @@ export interface Schema { nxCloudToken?: string; useTsSolution?: boolean; formatter?: 'prettier' | 'none'; + useProjectJson?: boolean; } diff --git a/packages/expo/src/generators/application/schema.json b/packages/expo/src/generators/application/schema.json index 1cea5ca793..b6def7c4fb 100644 --- a/packages/expo/src/generators/application/schema.json +++ b/packages/expo/src/generators/application/schema.json @@ -86,6 +86,10 @@ "type": "boolean", "description": "Do not add dependencies to `package.json`.", "default": false + }, + "useProjectJson": { + "type": "boolean", + "description": "Use a `project.json` configuration file instead of inlining the Nx configuration in the `package.json` file." } }, "required": ["directory"] diff --git a/packages/expo/src/generators/library/files/lib/README.md b/packages/expo/src/generators/library/files/lib/README.md index b74453ce2e..c21854b13f 100644 --- a/packages/expo/src/generators/library/files/lib/README.md +++ b/packages/expo/src/generators/library/files/lib/README.md @@ -1,7 +1,7 @@ -# <%= name %> +# <%= projectName %> This library was generated with [Nx](https://nx.dev). ## Running unit tests -Run `nx test <%= name %>` to execute the unit tests via [Jest](https://jestjs.io). +Run `nx test <%= projectName %>` to execute the unit tests via [Jest](https://jestjs.io). diff --git a/packages/expo/src/generators/library/lib/normalize-options.ts b/packages/expo/src/generators/library/lib/normalize-options.ts index 6b164b425a..afe3df8ee7 100644 --- a/packages/expo/src/generators/library/lib/normalize-options.ts +++ b/packages/expo/src/generators/library/lib/normalize-options.ts @@ -6,8 +6,7 @@ import { import { Schema } from '../schema'; import { isUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup'; -export interface NormalizedSchema extends Schema { - name: string; +export interface NormalizedSchema extends Omit { fileName: string; projectName: string; projectRoot: string; @@ -44,17 +43,19 @@ export async function normalizeOptions( : []; const isUsingTsSolutionConfig = isUsingTsSolutionSetup(host); + const useProjectJson = options.useProjectJson ?? !isUsingTsSolutionConfig; + const normalized: NormalizedSchema = { ...options, fileName: projectName, routePath: `/${projectNames.projectSimpleName}`, - name: projectName, projectName: isUsingTsSolutionConfig && !options.name ? importPath : projectName, projectRoot, parsedTags, importPath, isUsingTsSolutionConfig, + useProjectJson, }; return normalized; diff --git a/packages/expo/src/generators/library/library.spec.ts b/packages/expo/src/generators/library/library.spec.ts index 72bb265331..2e721dfc44 100644 --- a/packages/expo/src/generators/library/library.spec.ts +++ b/packages/expo/src/generators/library/library.spec.ts @@ -480,6 +480,7 @@ describe('lib', () => { await expoLibraryGenerator(appTree, { ...defaultSchema, strict: false, + useProjectJson: false, }); expect(readJson(appTree, 'tsconfig.json').references) @@ -622,6 +623,7 @@ describe('lib', () => { ...defaultSchema, buildable: true, strict: false, + useProjectJson: false, }); expect(readJson(appTree, 'my-lib/package.json')).toMatchInlineSnapshot(` @@ -652,6 +654,7 @@ describe('lib', () => { ...defaultSchema, directory: 'my-lib', name: 'my-lib', // import path contains the npm scope, so it would be different + useProjectJson: false, skipFormat: true, }); @@ -665,6 +668,7 @@ describe('lib', () => { ...defaultSchema, directory: 'my-lib', name: '@proj/my-lib', + useProjectJson: false, skipFormat: true, }); @@ -674,10 +678,34 @@ describe('lib', () => { it('should not set "nx.name" in package.json when the user does not provide a name', async () => { await expoLibraryGenerator(appTree, { ...defaultSchema, // defaultSchema has no name + useProjectJson: false, skipFormat: true, }); expect(readJson(appTree, 'my-lib/package.json').nx).toBeUndefined(); }); + + it('should generate project.json if useProjectJson is true', async () => { + await expoLibraryGenerator(appTree, { + ...defaultSchema, + strict: false, + useProjectJson: true, + }); + + expect(appTree.exists('my-lib/project.json')).toBeTruthy(); + expect(readProjectConfiguration(appTree, '@proj/my-lib')) + .toMatchInlineSnapshot(` + { + "$schema": "../node_modules/nx/schemas/project-schema.json", + "name": "@proj/my-lib", + "projectType": "library", + "root": "my-lib", + "sourceRoot": "my-lib/src", + "tags": [], + "targets": {}, + } + `); + expect(readJson(appTree, 'my-lib/package.json').nx).toBeUndefined(); + }); }); }); diff --git a/packages/expo/src/generators/library/library.ts b/packages/expo/src/generators/library/library.ts index 0e6355a192..a940fc3029 100644 --- a/packages/expo/src/generators/library/library.ts +++ b/packages/expo/src/generators/library/library.ts @@ -5,7 +5,6 @@ import { GeneratorCallback, installPackagesTask, joinPathFragments, - names, offsetFromRoot, ProjectConfiguration, runTasksInSerial, @@ -46,6 +45,7 @@ export async function expoLibraryGenerator( ): Promise { return await expoLibraryGeneratorInternal(host, { addPlugin: false, + useProjectJson: true, ...schema, }); } @@ -157,7 +157,7 @@ export async function expoLibraryGeneratorInternal( } tasks.push(() => { - logShowProjectCommand(options.name); + logShowProjectCommand(options.projectName); }); return runTasksInSerial(...tasks); @@ -175,18 +175,24 @@ async function addProject( targets: {}, }; + let packageJson: PackageJson = { + name: options.importPath, + version: '0.0.1', + peerDependencies: { + react: reactVersion, + 'react-native': reactNativeVersion, + }, + }; + if (options.isUsingTsSolutionConfig) { - const packageJson: PackageJson = { - name: options.projectName, - version: '0.0.1', + packageJson = { + ...packageJson, ...determineEntryFields(options), files: options.publishable ? ['dist', '!**/*.tsbuildinfo'] : undefined, - peerDependencies: { - react: reactVersion, - 'react-native': reactNativeVersion, - }, }; + } + if (!options.useProjectJson) { if (options.projectName !== options.importPath) { packageJson.nx = { name: options.projectName }; } @@ -194,14 +200,21 @@ async function addProject( packageJson.nx ??= {}; packageJson.nx.tags = options.parsedTags; } + } else { + addProjectConfiguration(host, options.projectName, project); + } + if ( + !options.useProjectJson || + options.isUsingTsSolutionConfig || + options.publishable || + options.buildable + ) { writeJson( host, joinPathFragments(options.projectRoot, 'package.json'), packageJson ); - } else { - addProjectConfiguration(host, options.name, project); } if (options.publishable || options.buildable) { @@ -263,7 +276,6 @@ function createFiles(host: Tree, options: NormalizedSchema) { options.projectRoot, { ...options, - ...names(options.name), tmpl: '', offsetFromRoot: offsetFromRoot(options.projectRoot), rootTsConfigPath: getRelativePathToRootTsConfig( diff --git a/packages/expo/src/generators/library/schema.d.ts b/packages/expo/src/generators/library/schema.d.ts index 83ccb2a7cb..12869e6180 100644 --- a/packages/expo/src/generators/library/schema.d.ts +++ b/packages/expo/src/generators/library/schema.d.ts @@ -19,4 +19,5 @@ export interface Schema { setParserOptionsProject?: boolean; skipPackageJson?: boolean; // default is false addPlugin?: boolean; + useProjectJson?: boolean; } diff --git a/packages/expo/src/generators/library/schema.json b/packages/expo/src/generators/library/schema.json index ea0d5c2f03..d312959956 100644 --- a/packages/expo/src/generators/library/schema.json +++ b/packages/expo/src/generators/library/schema.json @@ -89,6 +89,10 @@ "type": "boolean", "description": "Do not add dependencies to `package.json`.", "default": false + }, + "useProjectJson": { + "type": "boolean", + "description": "Use a `project.json` configuration file instead of inlining the Nx configuration in the `package.json` file." } }, "required": ["directory"] diff --git a/packages/express/package.json b/packages/express/package.json index ed3e5869f3..ff14110e50 100644 --- a/packages/express/package.json +++ b/packages/express/package.json @@ -32,6 +32,7 @@ }, "dependencies": { "@nx/devkit": "file:../devkit", + "@nx/js": "file:../js", "@nx/node": "file:../node", "tslib": "^2.3.0" }, diff --git a/packages/express/src/generators/application/application.spec.ts b/packages/express/src/generators/application/application.spec.ts index bbb5656273..9be5d0df88 100644 --- a/packages/express/src/generators/application/application.spec.ts +++ b/packages/express/src/generators/application/application.spec.ts @@ -1,4 +1,10 @@ -import { readJson, Tree, updateJson, writeJson } from '@nx/devkit'; +import { + readJson, + readProjectConfiguration, + Tree, + updateJson, + writeJson, +} from '@nx/devkit'; import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing'; import { applicationGenerator } from './application'; import { Schema } from './schema'; @@ -158,6 +164,7 @@ describe('app', () => { it('should add project references when using TS solution', async () => { await applicationGenerator(appTree, { directory: 'myapp', + useProjectJson: false, } as Schema); expect(readJson(appTree, 'tsconfig.json').references) @@ -171,9 +178,11 @@ describe('app', () => { }, ] `); + const packageJson = readJson(appTree, 'myapp/package.json'); + expect(packageJson.name).toBe('@proj/myapp'); + expect(packageJson.nx.name).toBeUndefined(); // Make sure keys are in idiomatic order - expect(Object.keys(readJson(appTree, 'myapp/package.json'))) - .toMatchInlineSnapshot(` + expect(Object.keys(packageJson)).toMatchInlineSnapshot(` [ "name", "version", @@ -313,5 +322,100 @@ describe('app', () => { } `); }); + + it('should respect the provided name', async () => { + await applicationGenerator(appTree, { + directory: 'myapp', + name: 'myapp', + useProjectJson: false, + skipFormat: true, + } as Schema); + + const packageJson = readJson(appTree, 'myapp/package.json'); + expect(packageJson.name).toBe('@proj/myapp'); + expect(packageJson.nx.name).toBe('myapp'); + // Make sure keys are in idiomatic order + expect(Object.keys(packageJson)).toMatchInlineSnapshot(` + [ + "name", + "version", + "private", + "nx", + ] + `); + }); + + it('should generate project.json if useProjectJson is true', async () => { + await applicationGenerator(appTree, { + directory: 'myapp', + useProjectJson: true, + skipFormat: true, + } as Schema); + + expect(appTree.exists('myapp/project.json')).toBeTruthy(); + expect(readProjectConfiguration(appTree, '@proj/myapp')) + .toMatchInlineSnapshot(` + { + "$schema": "../node_modules/nx/schemas/project-schema.json", + "name": "@proj/myapp", + "projectType": "application", + "root": "myapp", + "sourceRoot": "myapp/src", + "tags": [], + "targets": { + "build": { + "configurations": { + "development": {}, + "production": {}, + }, + "defaultConfiguration": "production", + "executor": "@nx/webpack:webpack", + "options": { + "assets": [ + "myapp/src/assets", + ], + "compiler": "tsc", + "main": "myapp/src/main.ts", + "outputPath": "myapp/dist", + "target": "node", + "tsConfig": "myapp/tsconfig.app.json", + "webpackConfig": "myapp/webpack.config.js", + }, + "outputs": [ + "{options.outputPath}", + ], + }, + "lint": { + "executor": "@nx/eslint:lint", + }, + "serve": { + "configurations": { + "development": { + "buildTarget": "@proj/myapp:build:development", + }, + "production": { + "buildTarget": "@proj/myapp:build:production", + }, + }, + "defaultConfiguration": "development", + "dependsOn": [ + "build", + ], + "executor": "@nx/js:node", + "options": { + "buildTarget": "@proj/myapp:build", + "runBuildTargetDependencies": false, + }, + }, + "test": { + "options": { + "passWithNoTests": true, + }, + }, + }, + } + `); + expect(readJson(appTree, 'myapp/package.json').nx).toBeUndefined(); + }); }); }); diff --git a/packages/express/src/generators/application/application.ts b/packages/express/src/generators/application/application.ts index f094cbeba1..cbaa8d38f0 100644 --- a/packages/express/src/generators/application/application.ts +++ b/packages/express/src/generators/application/application.ts @@ -11,6 +11,7 @@ import { determineProjectNameAndRootOptions, ensureRootProjectName, } from '@nx/devkit/src/generators/project-name-and-root-utils'; +import { isUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup'; import { applicationGenerator as nodeApplicationGenerator } from '@nx/node'; import { tslibVersion } from '@nx/node/src/utils/versions'; import { join } from 'path'; @@ -69,6 +70,7 @@ server.on('error', console.error); export async function applicationGenerator(tree: Tree, schema: Schema) { return await applicationGeneratorInternal(tree, { addPlugin: false, + useProjectJson: true, ...schema, }); } @@ -119,10 +121,14 @@ async function normalizeOptions( nxJson.useInferencePlugins !== false; options.addPlugin ??= addPlugin; + const useProjectJson = + options.useProjectJson ?? !isUsingTsSolutionSetup(host); + return { ...options, appProjectName, appProjectRoot, + useProjectJson, }; } diff --git a/packages/express/src/generators/application/schema.d.ts b/packages/express/src/generators/application/schema.d.ts index 7c0353b2bb..996fda0d20 100644 --- a/packages/express/src/generators/application/schema.d.ts +++ b/packages/express/src/generators/application/schema.d.ts @@ -17,4 +17,5 @@ export interface Schema { standaloneConfig?: boolean; setParserOptionsProject?: boolean; addPlugin?: boolean; + useProjectJson?: boolean; } diff --git a/packages/express/src/generators/application/schema.json b/packages/express/src/generators/application/schema.json index 0397e89e75..690182c487 100644 --- a/packages/express/src/generators/application/schema.json +++ b/packages/express/src/generators/application/schema.json @@ -73,6 +73,10 @@ "type": "boolean", "description": "Whether or not to configure the ESLint `parserOptions.project` option. We do not do this by default for lint performance reasons.", "default": false + }, + "useProjectJson": { + "type": "boolean", + "description": "Use a `project.json` configuration file instead of inlining the Nx configuration in the `package.json` file." } }, "required": ["directory"] diff --git a/packages/nest/src/generators/application/application.spec.ts b/packages/nest/src/generators/application/application.spec.ts index 5e83630b2d..6869b56ca7 100644 --- a/packages/nest/src/generators/application/application.spec.ts +++ b/packages/nest/src/generators/application/application.spec.ts @@ -1,4 +1,10 @@ -import { readJson, updateJson, writeJson, type Tree } from '@nx/devkit'; +import { + readJson, + readProjectConfiguration, + updateJson, + writeJson, + type Tree, +} from '@nx/devkit'; import * as devkit from '@nx/devkit'; import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing'; import { applicationGenerator } from './application'; @@ -193,6 +199,7 @@ describe('application generator', () => { directory: 'myapp', unitTestRunner: 'jest', addPlugin: true, + useProjectJson: false, }); expect(readJson(tree, 'tsconfig.json').references).toMatchInlineSnapshot(` @@ -330,5 +337,124 @@ describe('application generator', () => { } `); }); + + it('should respect the provided name', async () => { + await applicationGenerator(tree, { + directory: 'myapp', + name: 'myapp', + unitTestRunner: 'jest', + addPlugin: true, + useProjectJson: false, + skipFormat: true, + }); + + const packageJson = readJson(tree, 'myapp/package.json'); + expect(packageJson.name).toBe('@proj/myapp'); + expect(packageJson.nx.name).toBe('myapp'); + // Make sure keys are in idiomatic order + expect(Object.keys(packageJson)).toMatchInlineSnapshot(` + [ + "name", + "version", + "private", + "nx", + ] + `); + }); + + it('should generate project.json if useProjectJson is true', async () => { + await applicationGenerator(tree, { + directory: 'myapp', + e2eTestRunner: 'jest', + useProjectJson: true, + addPlugin: true, + skipFormat: true, + }); + + expect(tree.exists('myapp/project.json')).toBeTruthy(); + expect(readProjectConfiguration(tree, '@proj/myapp')) + .toMatchInlineSnapshot(` + { + "$schema": "../node_modules/nx/schemas/project-schema.json", + "name": "@proj/myapp", + "projectType": "application", + "root": "myapp", + "sourceRoot": "myapp/src", + "tags": [], + "targets": { + "build": { + "configurations": { + "development": { + "args": [ + "node-env=development", + ], + }, + }, + "executor": "nx:run-commands", + "options": { + "args": [ + "node-env=production", + ], + "command": "webpack-cli build", + }, + }, + "serve": { + "configurations": { + "development": { + "buildTarget": "@proj/myapp:build:development", + }, + "production": { + "buildTarget": "@proj/myapp:build:production", + }, + }, + "defaultConfiguration": "development", + "dependsOn": [ + "build", + ], + "executor": "@nx/js:node", + "options": { + "buildTarget": "@proj/myapp:build", + "runBuildTargetDependencies": false, + }, + }, + "test": { + "options": { + "passWithNoTests": true, + }, + }, + }, + } + `); + expect(readJson(tree, 'myapp/package.json').nx).toBeUndefined(); + expect(tree.exists('myapp-e2e/project.json')).toBeTruthy(); + expect(readProjectConfiguration(tree, '@proj/myapp-e2e')) + .toMatchInlineSnapshot(` + { + "$schema": "../node_modules/nx/schemas/project-schema.json", + "implicitDependencies": [ + "@proj/myapp", + ], + "name": "@proj/myapp-e2e", + "projectType": "application", + "root": "myapp-e2e", + "targets": { + "e2e": { + "dependsOn": [ + "@proj/myapp:build", + ], + "executor": "@nx/jest:jest", + "options": { + "jestConfig": "myapp-e2e/jest.config.ts", + "passWithNoTests": true, + }, + "outputs": [ + "{workspaceRoot}/coverage/{e2eProjectRoot}", + ], + }, + }, + } + `); + expect(readJson(tree, 'myapp-e2e/package.json').nx).toBeUndefined(); + }); }); }); diff --git a/packages/nest/src/generators/application/application.ts b/packages/nest/src/generators/application/application.ts index c7bc9861ca..186d370748 100644 --- a/packages/nest/src/generators/application/application.ts +++ b/packages/nest/src/generators/application/application.ts @@ -18,6 +18,7 @@ export async function applicationGenerator( ): Promise { return await applicationGeneratorInternal(tree, { addPlugin: false, + useProjectJson: true, ...rawOptions, }); } diff --git a/packages/nest/src/generators/application/lib/normalize-options.ts b/packages/nest/src/generators/application/lib/normalize-options.ts index af3f8e72c4..a4bf2b56d7 100644 --- a/packages/nest/src/generators/application/lib/normalize-options.ts +++ b/packages/nest/src/generators/application/lib/normalize-options.ts @@ -4,6 +4,7 @@ import { ensureRootProjectName, } from '@nx/devkit/src/generators/project-name-and-root-utils'; import { Linter } from '@nx/eslint'; +import { isUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup'; import type { Schema as NodeApplicationGeneratorOptions } from '@nx/node/src/generators/application/schema'; import type { ApplicationGeneratorOptions, NormalizedOptions } from '../schema'; @@ -35,6 +36,7 @@ export async function normalizeOptions( linter: options.linter ?? Linter.EsLint, unitTestRunner: options.unitTestRunner ?? 'jest', e2eTestRunner: options.e2eTestRunner ?? 'jest', + useProjectJson: options.useProjectJson ?? !isUsingTsSolutionSetup(tree), }; } @@ -57,5 +59,6 @@ export function toNodeApplicationGeneratorOptions( bundler: 'webpack', // Some features require webpack plugins such as TS transformers isNest: true, addPlugin: options.addPlugin, + useProjectJson: options.useProjectJson, }; } diff --git a/packages/nest/src/generators/application/schema.d.ts b/packages/nest/src/generators/application/schema.d.ts index 5606731204..9cb3c747e8 100644 --- a/packages/nest/src/generators/application/schema.d.ts +++ b/packages/nest/src/generators/application/schema.d.ts @@ -16,6 +16,7 @@ export interface ApplicationGeneratorOptions { strict?: boolean; addPlugin?: boolean; useTsSolution?: boolean; + useProjectJson?: boolean; } interface NormalizedOptions extends ApplicationGeneratorOptions { diff --git a/packages/nest/src/generators/application/schema.json b/packages/nest/src/generators/application/schema.json index 0eb3ee8764..4fbe17ee11 100644 --- a/packages/nest/src/generators/application/schema.json +++ b/packages/nest/src/generators/application/schema.json @@ -78,6 +78,10 @@ "type": "boolean", "description": "Adds strictNullChecks, noImplicitAny, strictBindCallApply, forceConsistentCasingInFileNames and noFallthroughCasesInSwitch to tsconfig.", "default": false + }, + "useProjectJson": { + "type": "boolean", + "description": "Use a `project.json` configuration file instead of inlining the Nx configuration in the `package.json` file." } }, "additionalProperties": false, diff --git a/packages/nest/src/generators/library/lib/normalize-options.ts b/packages/nest/src/generators/library/lib/normalize-options.ts index 5cab842a09..4081d4d3ec 100644 --- a/packages/nest/src/generators/library/lib/normalize-options.ts +++ b/packages/nest/src/generators/library/lib/normalize-options.ts @@ -58,6 +58,7 @@ export async function normalizeOptions( testEnvironment: options.testEnvironment ?? 'node', unitTestRunner: options.unitTestRunner ?? 'jest', isUsingTsSolutionsConfig, + useProjectJson: options.useProjectJson ?? !isUsingTsSolutionsConfig, }; return normalized; @@ -82,6 +83,6 @@ export function toJsLibraryGeneratorOptions( unitTestRunner: options.unitTestRunner, setParserOptionsProject: options.setParserOptionsProject, addPlugin: options.addPlugin, - useProjectJson: !options.isUsingTsSolutionsConfig, + useProjectJson: options.useProjectJson, }; } diff --git a/packages/nest/src/generators/library/library.spec.ts b/packages/nest/src/generators/library/library.spec.ts index 6088d20371..53d5cd6c9d 100644 --- a/packages/nest/src/generators/library/library.spec.ts +++ b/packages/nest/src/generators/library/library.spec.ts @@ -370,6 +370,7 @@ describe('lib', () => { await libraryGenerator(tree, { directory: 'mylib', unitTestRunner: 'jest', + useProjectJson: false, }); expect(readJson(tree, 'tsconfig.json').references).toMatchInlineSnapshot(` @@ -503,6 +504,7 @@ describe('lib', () => { name: 'my-lib', // import path contains the npm scope, so it would be different linter: 'none', unitTestRunner: 'none', + useProjectJson: false, skipFormat: true, }); @@ -517,6 +519,7 @@ describe('lib', () => { name: '@proj/my-lib', linter: 'none', unitTestRunner: 'none', + useProjectJson: false, skipFormat: true, }); @@ -528,10 +531,48 @@ describe('lib', () => { directory: 'mylib', linter: 'none', unitTestRunner: 'none', + useProjectJson: false, skipFormat: true, }); expect(readJson(tree, 'mylib/package.json').nx).toBeUndefined(); }); + + it('should generate project.json if useProjectJson is true', async () => { + await libraryGenerator(tree, { + directory: 'mylib', + unitTestRunner: 'jest', + useProjectJson: true, + skipFormat: true, + }); + + expect(tree.exists('mylib/project.json')).toBeTruthy(); + expect(readProjectConfiguration(tree, '@proj/mylib')) + .toMatchInlineSnapshot(` + { + "$schema": "../node_modules/nx/schemas/project-schema.json", + "name": "@proj/mylib", + "projectType": "library", + "root": "mylib", + "sourceRoot": "mylib/src", + "tags": [], + "targets": { + "lint": { + "executor": "@nx/eslint:lint", + }, + "test": { + "executor": "@nx/jest:jest", + "options": { + "jestConfig": "mylib/jest.config.ts", + }, + "outputs": [ + "{projectRoot}/test-output/jest/coverage", + ], + }, + }, + } + `); + expect(readJson(tree, 'mylib/package.json').nx).toBeUndefined(); + }); }); }); diff --git a/packages/nest/src/generators/library/library.ts b/packages/nest/src/generators/library/library.ts index 4cd0ea483b..4d2be70f91 100644 --- a/packages/nest/src/generators/library/library.ts +++ b/packages/nest/src/generators/library/library.ts @@ -27,6 +27,7 @@ export async function libraryGenerator( ): Promise { return await libraryGeneratorInternal(tree, { addPlugin: false, + useProjectJson: true, ...rawOptions, }); } diff --git a/packages/nest/src/generators/library/schema.d.ts b/packages/nest/src/generators/library/schema.d.ts index 63e6978b0b..dc068d597c 100644 --- a/packages/nest/src/generators/library/schema.d.ts +++ b/packages/nest/src/generators/library/schema.d.ts @@ -33,6 +33,7 @@ export interface LibraryGeneratorOptions { simpleName?: boolean; addPlugin?: boolean; isUsingTsSolutionsConfig?: boolean; + useProjectJson?: boolean; } export interface NormalizedOptions extends LibraryGeneratorOptions { diff --git a/packages/nest/src/generators/library/schema.json b/packages/nest/src/generators/library/schema.json index 4d1852297b..46cd20eb76 100644 --- a/packages/nest/src/generators/library/schema.json +++ b/packages/nest/src/generators/library/schema.json @@ -133,6 +133,10 @@ "description": "Don't include the directory in the name of the module of the library.", "type": "boolean", "default": false + }, + "useProjectJson": { + "type": "boolean", + "description": "Use a `project.json` configuration file instead of inlining the Nx configuration in the `package.json` file." } }, "additionalProperties": false, diff --git a/packages/next/src/generators/application/application.spec.ts b/packages/next/src/generators/application/application.spec.ts index 171059f518..1911ca9485 100644 --- a/packages/next/src/generators/application/application.spec.ts +++ b/packages/next/src/generators/application/application.spec.ts @@ -872,6 +872,259 @@ describe('app', () => { expect(tsConfigApp.exclude).not.toContain('**/*.spec.js'); }); }); + + describe('TS solution setup', () => { + let tree: Tree; + + beforeEach(() => { + tree = createTreeWithEmptyWorkspace(); + updateJson(tree, 'package.json', (json) => { + json.workspaces = ['packages/*', 'apps/*']; + return json; + }); + writeJson(tree, 'tsconfig.base.json', { + compilerOptions: { + composite: true, + declaration: true, + }, + }); + writeJson(tree, 'tsconfig.json', { + extends: './tsconfig.base.json', + files: [], + references: [], + }); + }); + + it('should add project references when using TS solution', async () => { + await applicationGenerator(tree, { + directory: 'myapp', + appDir: true, + unitTestRunner: 'jest', + style: 'css', + e2eTestRunner: 'cypress', + addPlugin: true, + useProjectJson: false, + }); + + expect(readJson(tree, 'tsconfig.json').references).toMatchInlineSnapshot(` + [ + { + "path": "./myapp-e2e", + }, + { + "path": "./myapp", + }, + ] + `); + const packageJson = readJson(tree, 'myapp/package.json'); + expect(packageJson.name).toBe('@proj/myapp'); + expect(packageJson.nx).toBeUndefined(); + // Make sure keys are in idiomatic order + expect(Object.keys(packageJson)).toMatchInlineSnapshot(` + [ + "name", + "version", + "private", + "dependencies", + ] + `); + expect(readJson(tree, 'myapp/tsconfig.json')).toMatchInlineSnapshot(` + { + "compilerOptions": { + "allowJs": true, + "allowSyntheticDefaultImports": true, + "emitDeclarationOnly": false, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "incremental": true, + "isolatedModules": true, + "jsx": "preserve", + "lib": [ + "dom", + "dom.iterable", + "esnext", + ], + "module": "esnext", + "moduleResolution": "bundler", + "noEmit": true, + "outDir": "dist", + "paths": { + "@/*": [ + "./src/*", + ], + }, + "plugins": [ + { + "name": "next", + }, + ], + "resolveJsonModule": true, + "rootDir": "src", + "strict": true, + "tsBuildInfoFile": "dist/tsconfig.tsbuildinfo", + "types": [ + "jest", + "node", + ], + }, + "exclude": [ + "out-tsc", + "dist", + "node_modules", + "jest.config.ts", + "src/**/*.spec.ts", + "src/**/*.test.ts", + ".next", + "eslint.config.js", + "eslint.config.cjs", + "eslint.config.mjs", + ], + "extends": "../tsconfig.base.json", + "include": [ + "src/**/*.ts", + "src/**/*.tsx", + "src/**/*.js", + "src/**/*.jsx", + "../myapp/.next/types/**/*.ts", + "../dist/myapp/.next/types/**/*.ts", + "next-env.d.ts", + ], + } + `); + expect(readJson(tree, 'myapp/tsconfig.spec.json')).toMatchInlineSnapshot(` + { + "compilerOptions": { + "jsx": "preserve", + "module": "esnext", + "moduleResolution": "bundler", + "outDir": "./out-tsc/jest", + "types": [ + "jest", + "node", + ], + }, + "extends": "../tsconfig.base.json", + "include": [ + "jest.config.ts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.test.tsx", + "src/**/*.spec.tsx", + "src/**/*.test.js", + "src/**/*.spec.js", + "src/**/*.test.jsx", + "src/**/*.spec.jsx", + "src/**/*.d.ts", + ], + "references": [ + { + "path": "./tsconfig.json", + }, + ], + } + `); + expect(readJson(tree, 'myapp-e2e/tsconfig.json')).toMatchInlineSnapshot(` + { + "compilerOptions": { + "allowJs": true, + "outDir": "out-tsc/cypress", + "sourceMap": false, + "types": [ + "cypress", + "node", + ], + }, + "exclude": [ + "out-tsc", + "test-output", + ], + "extends": "../tsconfig.base.json", + "include": [ + "**/*.ts", + "**/*.js", + "cypress.config.ts", + "**/*.cy.ts", + "**/*.cy.tsx", + "**/*.cy.js", + "**/*.cy.jsx", + "**/*.d.ts", + ], + } + `); + }); + + it('should respect the provided name', async () => { + await applicationGenerator(tree, { + directory: 'myapp', + name: 'myapp', + appDir: true, + unitTestRunner: 'jest', + style: 'css', + e2eTestRunner: 'cypress', + addPlugin: true, + useProjectJson: false, + }); + + const packageJson = readJson(tree, 'myapp/package.json'); + expect(packageJson.name).toBe('@proj/myapp'); + expect(packageJson.nx.name).toBe('myapp'); + // Make sure keys are in idiomatic order + expect(Object.keys(packageJson)).toMatchInlineSnapshot(` + [ + "name", + "version", + "private", + "nx", + "dependencies", + ] + `); + }); + + it('should generate project.json if useProjectJson is true', async () => { + await applicationGenerator(tree, { + directory: 'myapp', + appDir: true, + unitTestRunner: 'jest', + style: 'css', + e2eTestRunner: 'cypress', + addPlugin: true, + useProjectJson: true, + skipFormat: true, + }); + + expect(tree.exists('myapp/project.json')).toBeTruthy(); + expect(readProjectConfiguration(tree, '@proj/myapp')) + .toMatchInlineSnapshot(` + { + "$schema": "../node_modules/nx/schemas/project-schema.json", + "name": "@proj/myapp", + "projectType": "application", + "root": "myapp", + "sourceRoot": "myapp", + "tags": [], + "targets": {}, + } + `); + expect(readJson(tree, 'myapp/package.json').nx).toBeUndefined(); + expect(tree.exists('myapp-e2e/project.json')).toBeTruthy(); + expect(readProjectConfiguration(tree, '@proj/myapp-e2e')) + .toMatchInlineSnapshot(` + { + "$schema": "../node_modules/nx/schemas/project-schema.json", + "implicitDependencies": [ + "@proj/myapp", + ], + "name": "@proj/myapp-e2e", + "projectType": "application", + "root": "myapp-e2e", + "sourceRoot": "myapp-e2e/src", + "tags": [], + "targets": {}, + } + `); + expect(readJson(tree, 'myapp-e2e/package.json').nx).toBeUndefined(); + }); + }); }); describe('app (legacy)', () => { @@ -912,204 +1165,6 @@ describe('app (legacy)', () => { expect(projectConfiguration.targets.build).toBeDefined(); expect(projectConfiguration.targets.serve).toBeDefined(); }); - - describe('TS solution setup', () => { - beforeEach(() => { - tree = createTreeWithEmptyWorkspace(); - updateJson(tree, 'package.json', (json) => { - json.workspaces = ['packages/*', 'apps/*']; - return json; - }); - writeJson(tree, 'tsconfig.base.json', { - compilerOptions: { - composite: true, - declaration: true, - }, - }); - writeJson(tree, 'tsconfig.json', { - extends: './tsconfig.base.json', - files: [], - references: [], - }); - }); - - it('should add project references when using TS solution', async () => { - await applicationGenerator(tree, { - ...schema, - addPlugin: true, - directory: 'myapp', - }); - - expect(readJson(tree, 'tsconfig.json').references).toMatchInlineSnapshot(` - [ - { - "path": "./myapp-e2e", - }, - { - "path": "./myapp", - }, - ] - `); - const packageJson = readJson(tree, 'myapp/package.json'); - expect(packageJson.name).toBe('@proj/myapp'); - expect(packageJson.nx).toBeUndefined(); - // Make sure keys are in idiomatic order - expect(Object.keys(packageJson)).toMatchInlineSnapshot(` - [ - "name", - "version", - "private", - "dependencies", - ] - `); - expect(readJson(tree, 'myapp/tsconfig.json')).toMatchInlineSnapshot(` - { - "compilerOptions": { - "allowJs": true, - "allowSyntheticDefaultImports": true, - "emitDeclarationOnly": false, - "esModuleInterop": true, - "forceConsistentCasingInFileNames": true, - "incremental": true, - "isolatedModules": true, - "jsx": "preserve", - "lib": [ - "dom", - "dom.iterable", - "esnext", - ], - "module": "esnext", - "moduleResolution": "bundler", - "noEmit": true, - "outDir": "dist", - "paths": { - "@/*": [ - "./src/*", - ], - }, - "plugins": [ - { - "name": "next", - }, - ], - "resolveJsonModule": true, - "rootDir": "src", - "strict": true, - "tsBuildInfoFile": "dist/tsconfig.tsbuildinfo", - "types": [ - "jest", - "node", - ], - }, - "exclude": [ - "out-tsc", - "dist", - "node_modules", - "jest.config.ts", - "src/**/*.spec.ts", - "src/**/*.test.ts", - ".next", - "eslint.config.js", - "eslint.config.cjs", - "eslint.config.mjs", - ], - "extends": "../tsconfig.base.json", - "include": [ - "src/**/*.ts", - "src/**/*.tsx", - "src/**/*.js", - "src/**/*.jsx", - "../myapp/.next/types/**/*.ts", - "../dist/myapp/.next/types/**/*.ts", - "next-env.d.ts", - ], - } - `); - expect(readJson(tree, 'myapp/tsconfig.spec.json')).toMatchInlineSnapshot(` - { - "compilerOptions": { - "jsx": "preserve", - "module": "esnext", - "moduleResolution": "bundler", - "outDir": "./out-tsc/jest", - "types": [ - "jest", - "node", - ], - }, - "extends": "../tsconfig.base.json", - "include": [ - "jest.config.ts", - "src/**/*.test.ts", - "src/**/*.spec.ts", - "src/**/*.test.tsx", - "src/**/*.spec.tsx", - "src/**/*.test.js", - "src/**/*.spec.js", - "src/**/*.test.jsx", - "src/**/*.spec.jsx", - "src/**/*.d.ts", - ], - "references": [ - { - "path": "./tsconfig.json", - }, - ], - } - `); - expect(readJson(tree, 'myapp-e2e/tsconfig.json')).toMatchInlineSnapshot(` - { - "compilerOptions": { - "allowJs": true, - "outDir": "out-tsc/cypress", - "sourceMap": false, - "types": [ - "cypress", - "node", - ], - }, - "exclude": [ - "out-tsc", - "test-output", - ], - "extends": "../tsconfig.base.json", - "include": [ - "**/*.ts", - "**/*.js", - "cypress.config.ts", - "**/*.cy.ts", - "**/*.cy.tsx", - "**/*.cy.js", - "**/*.cy.jsx", - "**/*.d.ts", - ], - } - `); - }); - - it('should respect the provided name', async () => { - await applicationGenerator(tree, { - ...schema, - addPlugin: true, - directory: 'myapp', - name: 'myapp', - }); - - const packageJson = readJson(tree, 'myapp/package.json'); - expect(packageJson.name).toBe('@proj/myapp'); - expect(packageJson.nx.name).toBe('myapp'); - // Make sure keys are in idiomatic order - expect(Object.keys(packageJson)).toMatchInlineSnapshot(` - [ - "name", - "version", - "private", - "nx", - "dependencies", - ] - `); - }); - }); }); function uniq() { diff --git a/packages/next/src/generators/application/application.ts b/packages/next/src/generators/application/application.ts index bdc6d13c3d..28defdb8bd 100644 --- a/packages/next/src/generators/application/application.ts +++ b/packages/next/src/generators/application/application.ts @@ -40,6 +40,7 @@ import { configureForSwc } from '../../utils/add-swc-to-custom-server'; export async function applicationGenerator(host: Tree, schema: Schema) { return await applicationGeneratorInternal(host, { addPlugin: false, + useProjectJson: true, ...schema, }); } diff --git a/packages/next/src/generators/application/lib/add-e2e.ts b/packages/next/src/generators/application/lib/add-e2e.ts index 5a2c2a3ead..27b10a8714 100644 --- a/packages/next/src/generators/application/lib/add-e2e.ts +++ b/packages/next/src/generators/application/lib/add-e2e.ts @@ -6,15 +6,14 @@ import { Tree, writeJson, } from '@nx/devkit'; +import { getE2EWebServerInfo } from '@nx/devkit/src/generators/e2e-web-server-info-utils'; +import { addE2eCiTargetDefaults } from '@nx/devkit/src/generators/target-defaults-utils'; +import { findPluginForConfigFile } from '@nx/devkit/src/utils/find-plugin-for-config-file'; import { Linter } from '@nx/eslint'; - +import { webStaticServeGenerator } from '@nx/web'; +import type { PackageJson } from 'nx/src/utils/package-json'; import { nxVersion } from '../../../utils/versions'; import { NormalizedSchema } from './normalize-options'; -import { webStaticServeGenerator } from '@nx/web'; -import { findPluginForConfigFile } from '@nx/devkit/src/utils/find-plugin-for-config-file'; -import { addE2eCiTargetDefaults } from '@nx/devkit/src/generators/target-defaults-utils'; -import { getE2EWebServerInfo } from '@nx/devkit/src/generators/e2e-web-server-info-utils'; -import { isUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup'; export async function addE2e(host: Tree, options: NormalizedSchema) { const nxJson = readNxJson(host); @@ -45,19 +44,16 @@ export async function addE2e(host: Tree, options: NormalizedSchema) { }); } - if (isUsingTsSolutionSetup(host)) { - writeJson( - host, - joinPathFragments(options.e2eProjectRoot, 'package.json'), - { - name: options.e2eProjectName, - version: '0.0.1', - private: true, - nx: { - implicitDependencies: [options.projectName], - }, - } - ); + const packageJson: PackageJson = { + name: options.e2eProjectName, + version: '0.0.1', + private: true, + }; + + if (!options.useProjectJson) { + packageJson.nx = { + implicitDependencies: [options.projectName], + }; } else { addProjectConfiguration(host, options.e2eProjectName, { root: options.e2eProjectRoot, @@ -69,6 +65,14 @@ export async function addE2e(host: Tree, options: NormalizedSchema) { }); } + if (!options.useProjectJson || options.isTsSolutionSetup) { + writeJson( + host, + joinPathFragments(options.e2eProjectRoot, 'package.json'), + packageJson + ); + } + const e2eTask = await configurationGenerator(host, { ...options, linter: Linter.EsLint, @@ -124,19 +128,17 @@ export async function addE2e(host: Tree, options: NormalizedSchema) { const { configurationGenerator } = ensurePackage< typeof import('@nx/playwright') >('@nx/playwright', nxVersion); - if (isUsingTsSolutionSetup(host)) { - writeJson( - host, - joinPathFragments(options.e2eProjectRoot, 'package.json'), - { - name: options.e2eProjectName, - version: '0.0.1', - private: true, - nx: { - implicitDependencies: [options.projectName], - }, - } - ); + + const packageJson: PackageJson = { + name: options.e2eProjectName, + version: '0.0.1', + private: true, + }; + + if (!options.useProjectJson) { + packageJson.nx = { + implicitDependencies: [options.projectName], + }; } else { addProjectConfiguration(host, options.e2eProjectName, { root: options.e2eProjectRoot, @@ -148,6 +150,14 @@ export async function addE2e(host: Tree, options: NormalizedSchema) { }); } + if (!options.useProjectJson || options.isTsSolutionSetup) { + writeJson( + host, + joinPathFragments(options.e2eProjectRoot, 'package.json'), + packageJson + ); + } + const e2eTask = await configurationGenerator(host, { rootProject: options.rootProject, project: options.e2eProjectName, diff --git a/packages/next/src/generators/application/lib/add-project.ts b/packages/next/src/generators/application/lib/add-project.ts index 47a6b13e89..fb01b8a900 100644 --- a/packages/next/src/generators/application/lib/add-project.ts +++ b/packages/next/src/generators/application/lib/add-project.ts @@ -72,18 +72,18 @@ export function addProject(host: Tree, options: NormalizedSchema) { tags: options.parsedTags, }; - if (isUsingTsSolutionSetup(host)) { - const packageJson: PackageJson = { - name: options.importPath, - version: '0.0.1', - private: true, - dependencies: { - next: nextVersion, - react: reactVersion, - 'react-dom': reactDomVersion, - }, - }; + const packageJson: PackageJson = { + name: options.importPath, + version: '0.0.1', + private: true, + dependencies: { + next: nextVersion, + react: reactVersion, + 'react-dom': reactDomVersion, + }, + }; + if (!options.useProjectJson) { if (options.projectName !== options.importPath) { packageJson.nx = { name: options.projectName }; } @@ -91,15 +91,17 @@ export function addProject(host: Tree, options: NormalizedSchema) { packageJson.nx ??= {}; packageJson.nx.tags = options.parsedTags; } - - writeJson( - host, - joinPathFragments(options.appProjectRoot, 'package.json'), - packageJson - ); } else { addProjectConfiguration(host, options.projectName, { ...project, }); } + + if (!options.useProjectJson || options.isTsSolutionSetup) { + writeJson( + host, + joinPathFragments(options.appProjectRoot, 'package.json'), + packageJson + ); + } } diff --git a/packages/next/src/generators/application/lib/normalize-options.ts b/packages/next/src/generators/application/lib/normalize-options.ts index c89512cb89..489cbf182d 100644 --- a/packages/next/src/generators/application/lib/normalize-options.ts +++ b/packages/next/src/generators/application/lib/normalize-options.ts @@ -97,5 +97,6 @@ export async function normalizeOptions( unitTestRunner: options.unitTestRunner || 'jest', importPath, isTsSolutionSetup, + useProjectJson: options.useProjectJson ?? !isTsSolutionSetup, }; } diff --git a/packages/next/src/generators/application/schema.d.ts b/packages/next/src/generators/application/schema.d.ts index eea51daa2b..c30d222d25 100644 --- a/packages/next/src/generators/application/schema.d.ts +++ b/packages/next/src/generators/application/schema.d.ts @@ -22,4 +22,5 @@ export interface Schema { addPlugin?: boolean; useTsSolution?: boolean; formatter?: 'prettier' | 'none'; + useProjectJson?: boolean; } diff --git a/packages/next/src/generators/application/schema.json b/packages/next/src/generators/application/schema.json index b28be3e05a..ea9dbd7b24 100644 --- a/packages/next/src/generators/application/schema.json +++ b/packages/next/src/generators/application/schema.json @@ -144,6 +144,10 @@ "default": false, "hidden": true, "x-priority": "internal" + }, + "useProjectJson": { + "type": "boolean", + "description": "Use a `project.json` configuration file instead of inlining the Nx configuration in the `package.json` file." } }, "required": ["directory"], diff --git a/packages/next/src/generators/library/lib/normalize-options.ts b/packages/next/src/generators/library/lib/normalize-options.ts index adc2fc1a57..5c775ffbc2 100644 --- a/packages/next/src/generators/library/lib/normalize-options.ts +++ b/packages/next/src/generators/library/lib/normalize-options.ts @@ -32,11 +32,13 @@ export async function normalizeOptions( process.env.NX_ADD_PLUGINS !== 'false' && nxJson.useInferencePlugins !== false; options.addPlugin ??= addPlugin; + const isUsingTsSolutionConfig = isUsingTsSolutionSetup(host); return { ...options, importPath, projectRoot, - isUsingTsSolutionConfig: isUsingTsSolutionSetup(host), + isUsingTsSolutionConfig, + useProjectJson: options.useProjectJson ?? !isUsingTsSolutionConfig, }; } diff --git a/packages/next/src/generators/library/library.spec.ts b/packages/next/src/generators/library/library.spec.ts index 7bda5498e3..d3d992f08e 100644 --- a/packages/next/src/generators/library/library.spec.ts +++ b/packages/next/src/generators/library/library.spec.ts @@ -1,5 +1,11 @@ import { installedCypressVersion } from '@nx/cypress/src/utils/cypress-version'; -import { readJson, Tree, updateJson, writeJson } from '@nx/devkit'; +import { + readJson, + readProjectConfiguration, + Tree, + updateJson, + writeJson, +} from '@nx/devkit'; import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing'; import { Linter } from '@nx/eslint'; import libraryGenerator from './library'; @@ -145,6 +151,7 @@ describe('next library', () => { unitTestRunner: 'jest', style: 'css', component: false, + useProjectJson: false, }); expect(readJson(tree, 'tsconfig.json').references).toMatchInlineSnapshot(` @@ -264,6 +271,7 @@ describe('next library', () => { linter: 'none', unitTestRunner: 'none', style: 'css', + useProjectJson: false, skipFormat: true, }); @@ -279,6 +287,7 @@ describe('next library', () => { linter: 'none', unitTestRunner: 'none', style: 'css', + useProjectJson: false, skipFormat: true, }); @@ -291,10 +300,38 @@ describe('next library', () => { linter: 'none', unitTestRunner: 'none', style: 'css', + useProjectJson: false, skipFormat: true, }); expect(readJson(tree, 'mylib/package.json').nx).toBeUndefined(); }); + + it('should generate project.json if useProjectJson is true', async () => { + await libraryGenerator(tree, { + directory: 'mylib', + linter: Linter.EsLint, + unitTestRunner: 'jest', + style: 'css', + addPlugin: true, + useProjectJson: true, + skipFormat: true, + }); + + expect(tree.exists('mylib/project.json')).toBeTruthy(); + expect(readProjectConfiguration(tree, '@proj/mylib')) + .toMatchInlineSnapshot(` + { + "$schema": "../node_modules/nx/schemas/project-schema.json", + "name": "@proj/mylib", + "projectType": "library", + "root": "mylib", + "sourceRoot": "mylib/src", + "tags": [], + "targets": {}, + } + `); + expect(readJson(tree, 'mylib/package.json').nx).toBeUndefined(); + }); }); }); diff --git a/packages/next/src/generators/library/library.ts b/packages/next/src/generators/library/library.ts index ac50f10d92..3735d27f9d 100644 --- a/packages/next/src/generators/library/library.ts +++ b/packages/next/src/generators/library/library.ts @@ -28,6 +28,7 @@ import { sortPackageJsonFields } from '@nx/js/src/utils/package-json/sort-fields export async function libraryGenerator(host: Tree, rawOptions: Schema) { return await libraryGeneratorInternal(host, { addPlugin: false, + useProjectJson: true, ...rawOptions, }); } diff --git a/packages/next/src/generators/library/schema.d.ts b/packages/next/src/generators/library/schema.d.ts index 8418bdd0ed..56dfc96134 100644 --- a/packages/next/src/generators/library/schema.d.ts +++ b/packages/next/src/generators/library/schema.d.ts @@ -24,4 +24,5 @@ export interface Schema { setParserOptionsProject?: boolean; skipPackageJson?: boolean; addPlugin?: boolean; + useProjectJson?: boolean; } diff --git a/packages/next/src/generators/library/schema.json b/packages/next/src/generators/library/schema.json index 357ba21e60..036be504d5 100644 --- a/packages/next/src/generators/library/schema.json +++ b/packages/next/src/generators/library/schema.json @@ -158,6 +158,10 @@ "default": false, "description": "Do not add dependencies to `package.json`.", "x-priority": "internal" + }, + "useProjectJson": { + "type": "boolean", + "description": "Use a `project.json` configuration file instead of inlining the Nx configuration in the `package.json` file." } }, "required": ["directory"], diff --git a/packages/node/src/generators/application/application.spec.ts b/packages/node/src/generators/application/application.spec.ts index b173a296b6..a3270c70c7 100644 --- a/packages/node/src/generators/application/application.spec.ts +++ b/packages/node/src/generators/application/application.spec.ts @@ -587,6 +587,7 @@ describe('app', () => { bundler: 'webpack', unitTestRunner: 'jest', addPlugin: true, + useProjectJson: false, }); expect(readJson(tree, 'tsconfig.json').references).toMatchInlineSnapshot(` @@ -723,6 +724,7 @@ describe('app', () => { bundler: 'webpack', unitTestRunner: 'jest', addPlugin: true, + useProjectJson: false, }); const packageJson = readJson(tree, 'myapp/package.json'); @@ -743,6 +745,7 @@ describe('app', () => { await applicationGenerator(tree, { directory: 'apps/my-app', swcJest: true, + useProjectJson: false, } as Schema); expect(tree.read('apps/my-app/jest.config.ts', 'utf-8')) @@ -803,6 +806,7 @@ describe('app', () => { directory: 'apps/my-app', bundler: 'webpack', addPlugin: true, + useProjectJson: false, skipFormat: true, }); @@ -837,6 +841,7 @@ describe('app', () => { directory: 'apps/my-app', bundler: 'webpack', addPlugin: false, + useProjectJson: false, skipFormat: true, }); @@ -851,6 +856,7 @@ describe('app', () => { directory: 'apps/my-app', bundler: 'esbuild', addPlugin: false, + useProjectJson: false, skipFormat: true, }); @@ -859,5 +865,85 @@ describe('app', () => { .outputPath ).toBe('apps/my-app/dist'); }); + + it('should generate project.json if useProjectJson is true', async () => { + await applicationGenerator(tree, { + directory: 'myapp', + bundler: 'webpack', + unitTestRunner: 'jest', + addPlugin: true, + useProjectJson: true, + skipFormat: true, + }); + + expect(tree.exists('myapp/project.json')).toBeTruthy(); + expect(readProjectConfiguration(tree, '@proj/myapp')) + .toMatchInlineSnapshot(` + { + "$schema": "../node_modules/nx/schemas/project-schema.json", + "name": "@proj/myapp", + "projectType": "application", + "root": "myapp", + "sourceRoot": "myapp/src", + "tags": [], + "targets": { + "serve": { + "configurations": { + "development": { + "buildTarget": "@proj/myapp:build:development", + }, + "production": { + "buildTarget": "@proj/myapp:build:production", + }, + }, + "defaultConfiguration": "development", + "dependsOn": [ + "build", + ], + "executor": "@nx/js:node", + "options": { + "buildTarget": "@proj/myapp:build", + "runBuildTargetDependencies": false, + }, + }, + "test": { + "options": { + "passWithNoTests": true, + }, + }, + }, + } + `); + expect(readJson(tree, 'myapp/package.json').nx).toBeUndefined(); + expect(tree.exists('myapp-e2e/project.json')).toBeTruthy(); + expect(readProjectConfiguration(tree, '@proj/myapp-e2e')) + .toMatchInlineSnapshot(` + { + "$schema": "../node_modules/nx/schemas/project-schema.json", + "implicitDependencies": [ + "@proj/myapp", + ], + "name": "@proj/myapp-e2e", + "projectType": "application", + "root": "myapp-e2e", + "targets": { + "e2e": { + "dependsOn": [ + "@proj/myapp:build", + ], + "executor": "@nx/jest:jest", + "options": { + "jestConfig": "myapp-e2e/jest.config.ts", + "passWithNoTests": true, + }, + "outputs": [ + "{workspaceRoot}/coverage/{e2eProjectRoot}", + ], + }, + }, + } + `); + expect(readJson(tree, 'myapp-e2e/package.json').nx).toBeUndefined(); + }); }); }); diff --git a/packages/node/src/generators/application/application.ts b/packages/node/src/generators/application/application.ts index 2350a66147..2dafdec2d7 100644 --- a/packages/node/src/generators/application/application.ts +++ b/packages/node/src/generators/application/application.ts @@ -60,6 +60,7 @@ import { updateTsconfigFiles, } from '@nx/js/src/utils/typescript/ts-solution-setup'; import { sortPackageJsonFields } from '@nx/js/src/utils/package-json/sort-fields'; +import type { PackageJson } from 'nx/src/utils/package-json'; export interface NormalizedSchema extends Omit { appProjectRoot: string; @@ -206,17 +207,18 @@ function addProject(tree: Tree, options: NormalizedSchema) { } project.targets.serve = getServeConfig(options); - if (options.isUsingTsSolutionConfig) { - writeJson(tree, joinPathFragments(options.appProjectRoot, 'package.json'), { - name: options.importPath, - version: '0.0.1', - private: true, - nx: { - name: options.name !== options.importPath ? options.name : undefined, - targets: project.targets, - tags: project.tags?.length ? project.tags : undefined, - }, - }); + const packageJson: PackageJson = { + name: options.importPath, + version: '0.0.1', + private: true, + }; + + if (!options.useProjectJson) { + packageJson.nx = { + name: options.name !== options.importPath ? options.name : undefined, + targets: project.targets, + tags: project.tags?.length ? project.tags : undefined, + }; } else { addProjectConfiguration( tree, @@ -225,6 +227,14 @@ function addProject(tree: Tree, options: NormalizedSchema) { options.standaloneConfig ); } + + if (!options.useProjectJson || options.isUsingTsSolutionConfig) { + writeJson( + tree, + joinPathFragments(options.appProjectRoot, 'package.json'), + packageJson + ); + } } function addAppFiles(tree: Tree, options: NormalizedSchema) { @@ -435,6 +445,7 @@ function updateTsConfigOptions(tree: Tree, options: NormalizedSchema) { export async function applicationGenerator(tree: Tree, schema: Schema) { return await applicationGeneratorInternal(tree, { addPlugin: false, + useProjectJson: true, ...schema, }); } @@ -646,6 +657,7 @@ async function normalizeOptions( const appProjectName = !isUsingTsSolutionConfig || options.name ? projectName : importPath; + const useProjectJson = options.useProjectJson ?? !isUsingTsSolutionConfig; return { addPlugin, @@ -669,6 +681,7 @@ async function normalizeOptions( ), isUsingTsSolutionConfig, swcJest, + useProjectJson, }; } diff --git a/packages/node/src/generators/application/schema.d.ts b/packages/node/src/generators/application/schema.d.ts index 914fe3e591..2135b10f85 100644 --- a/packages/node/src/generators/application/schema.d.ts +++ b/packages/node/src/generators/application/schema.d.ts @@ -25,6 +25,7 @@ export interface Schema { isNest?: boolean; addPlugin?: boolean; useTsSolution?: boolean; + useProjectJson?: boolean; } export type NodeJsFrameWorks = 'express' | 'koa' | 'fastify' | 'nest' | 'none'; diff --git a/packages/node/src/generators/application/schema.json b/packages/node/src/generators/application/schema.json index 0717df09cc..42ce0d7586 100644 --- a/packages/node/src/generators/application/schema.json +++ b/packages/node/src/generators/application/schema.json @@ -123,6 +123,10 @@ "docker": { "type": "boolean", "description": "Add a docker build target" + }, + "useProjectJson": { + "type": "boolean", + "description": "Use a `project.json` configuration file instead of inlining the Nx configuration in the `package.json` file." } }, "required": ["directory"] diff --git a/packages/node/src/generators/e2e-project/e2e-project.spec.ts b/packages/node/src/generators/e2e-project/e2e-project.spec.ts index ec2d1eaa12..63ca04ee9a 100644 --- a/packages/node/src/generators/e2e-project/e2e-project.spec.ts +++ b/packages/node/src/generators/e2e-project/e2e-project.spec.ts @@ -173,6 +173,7 @@ describe('e2eProjectGenerator', () => { framework: 'none', e2eTestRunner: 'none', addPlugin: true, + useProjectJson: false, }); await e2eProjectGenerator(tree, { projectType: 'server', @@ -214,6 +215,7 @@ describe('e2eProjectGenerator', () => { framework: 'none', e2eTestRunner: 'none', addPlugin: true, + useProjectJson: false, }); await e2eProjectGenerator(tree, { projectType: 'server', @@ -282,11 +284,13 @@ describe('e2eProjectGenerator', () => { framework: 'none', e2eTestRunner: 'none', addPlugin: true, + useProjectJson: false, }); await e2eProjectGenerator(tree, { projectType: 'cli', project: '@proj/cli', addPlugin: true, + useProjectJson: false, }); expect(tree.read('cli-e2e/jest.config.ts', 'utf-8')) diff --git a/packages/node/src/generators/e2e-project/e2e-project.ts b/packages/node/src/generators/e2e-project/e2e-project.ts index 08efc2b0a7..e5a35e8227 100644 --- a/packages/node/src/generators/e2e-project/e2e-project.ts +++ b/packages/node/src/generators/e2e-project/e2e-project.ts @@ -36,10 +36,12 @@ import { } from '@nx/js/src/utils/typescript/ts-solution-setup'; import { relative } from 'node:path/posix'; import { addSwcTestConfig } from '@nx/js/src/utils/swc/add-swc-config'; +import type { PackageJson } from 'nx/src/utils/package-json'; export async function e2eProjectGenerator(host: Tree, options: Schema) { return await e2eProjectGeneratorInternal(host, { addPlugin: false, + useProjectJson: true, ...options, }); } @@ -51,33 +53,33 @@ export async function e2eProjectGeneratorInternal( const tasks: GeneratorCallback[] = []; const options = await normalizeOptions(host, _options); const appProject = readProjectConfiguration(host, options.project); - const isUsingTsSolutionConfig = isUsingTsSolutionSetup(host); // TODO(@ndcunningham): This is broken.. the outputs are wrong.. and this isn't using the jest generator - if (isUsingTsSolutionConfig) { - writeJson(host, joinPathFragments(options.e2eProjectRoot, 'package.json'), { - name: options.importPath, - version: '0.0.1', - private: true, - nx: { - name: - options.e2eProjectName !== options.importPath - ? options.e2eProjectName - : undefined, - implicitDependencies: [options.project], - targets: { - e2e: { - executor: '@nx/jest:jest', - outputs: ['{projectRoot}/test-output/jest/coverage'], - options: { - jestConfig: `${options.e2eProjectRoot}/jest.config.ts`, - passWithNoTests: true, - }, - dependsOn: [`${options.project}:build`], + const packageJson: PackageJson = { + name: options.importPath, + version: '0.0.1', + private: true, + }; + + if (!options.useProjectJson) { + packageJson.nx = { + name: + options.e2eProjectName !== options.importPath + ? options.e2eProjectName + : undefined, + implicitDependencies: [options.project], + targets: { + e2e: { + executor: '@nx/jest:jest', + outputs: ['{projectRoot}/test-output/jest/coverage'], + options: { + jestConfig: `${options.e2eProjectRoot}/jest.config.ts`, + passWithNoTests: true, }, + dependsOn: [`${options.project}:build`], }, }, - }); + }; } else { addProjectConfiguration(host, options.e2eProjectName, { root: options.e2eProjectRoot, @@ -96,6 +98,15 @@ export async function e2eProjectGeneratorInternal( }, }); } + + if (!options.useProjectJson || options.isUsingTsSolutionConfig) { + writeJson( + host, + joinPathFragments(options.e2eProjectRoot, 'package.json'), + packageJson + ); + } + // TODO(@nicholas): Find a better way to get build target // We remove the 'test' target from the e2e project because it is not needed @@ -125,11 +136,11 @@ export async function e2eProjectGeneratorInternal( } const jestPreset = findRootJestPreset(host) ?? 'jest.preset.js'; - const tsConfigFile = isUsingTsSolutionConfig + const tsConfigFile = options.isUsingTsSolutionConfig ? 'tsconfig.json' : 'tsconfig.spec.json'; const rootOffset = offsetFromRoot(options.e2eProjectRoot); - const coverageDirectory = isUsingTsSolutionConfig + const coverageDirectory = options.isUsingTsSolutionConfig ? 'test-output/jest/coverage' : joinPathFragments(rootOffset, 'coverage', options.e2eProjectName); const projectSimpleName = options.project.split('/').pop(); @@ -145,7 +156,6 @@ export async function e2eProjectGeneratorInternal( offsetFromRoot: rootOffset, jestPreset, coverageDirectory, - isUsingTsSolutionConfig, tmpl: '', } ); @@ -178,13 +188,12 @@ export async function e2eProjectGeneratorInternal( offsetFromRoot: rootOffset, jestPreset, coverageDirectory, - isUsingTsSolutionConfig, tmpl: '', } ); } - if (isUsingTsSolutionConfig) { + if (options.isUsingTsSolutionConfig) { addSwcTestConfig(host, options.e2eProjectRoot, 'es6'); generateFiles( host, @@ -245,7 +254,7 @@ export async function e2eProjectGeneratorInternal( } } - if (isUsingTsSolutionConfig) { + if (options.isUsingTsSolutionConfig) { updateJson(host, 'tsconfig.json', (json) => { json.references ??= []; const e2eRef = `./${options.e2eProjectRoot}`; @@ -258,7 +267,7 @@ export async function e2eProjectGeneratorInternal( // If we are using the new TS solution // We need to update the workspace file (package.json or pnpm-workspaces.yaml) to include the new project - if (isUsingTsSolutionConfig) { + if (options.isUsingTsSolutionConfig) { await addProjectToTsSolutionWorkspace(host, options.e2eProjectRoot); } @@ -281,6 +290,7 @@ async function normalizeOptions( e2eProjectRoot: string; e2eProjectName: string; importPath: string; + isUsingTsSolutionConfig: boolean; } > { let directory = options.rootProject ? 'e2e' : options.directory; @@ -303,6 +313,8 @@ async function normalizeOptions( process.env.NX_ADD_PLUGINS !== 'false' && nxJson.useInferencePlugins !== false; + const isUsingTsSolutionConfig = isUsingTsSolutionSetup(tree); + return { addPlugin, ...options, @@ -311,6 +323,8 @@ async function normalizeOptions( importPath, port: options.port ?? 3000, rootProject: !!options.rootProject, + isUsingTsSolutionConfig, + useProjectJson: options.useProjectJson ?? !isUsingTsSolutionConfig, }; } diff --git a/packages/node/src/generators/e2e-project/schema.d.ts b/packages/node/src/generators/e2e-project/schema.d.ts index de6eeaee1f..9cf6f08ce6 100644 --- a/packages/node/src/generators/e2e-project/schema.d.ts +++ b/packages/node/src/generators/e2e-project/schema.d.ts @@ -9,4 +9,5 @@ export interface Schema { isNest?: boolean; skipFormat?: boolean; addPlugin?: boolean; + useProjectJson?: boolean; } diff --git a/packages/node/src/generators/e2e-project/schema.json b/packages/node/src/generators/e2e-project/schema.json index 7add1fb212..37fd15a1e3 100644 --- a/packages/node/src/generators/e2e-project/schema.json +++ b/packages/node/src/generators/e2e-project/schema.json @@ -59,6 +59,10 @@ "default": false, "hidden": true, "x-priority": "internal" + }, + "useProjectJson": { + "type": "boolean", + "description": "Use a `project.json` configuration file instead of inlining the Nx configuration in the `package.json` file." } }, "required": ["project"] diff --git a/packages/node/src/generators/library/library.spec.ts b/packages/node/src/generators/library/library.spec.ts index 8e9f4997f7..723997a59a 100644 --- a/packages/node/src/generators/library/library.spec.ts +++ b/packages/node/src/generators/library/library.spec.ts @@ -545,6 +545,7 @@ describe('lib', () => { directory: 'mylib', unitTestRunner: 'jest', addPlugin: true, + useProjectJson: false, } as Schema); expect(readJson(tree, 'tsconfig.json').references).toMatchInlineSnapshot(` @@ -660,6 +661,7 @@ describe('lib', () => { compiler: 'swc', unitTestRunner: 'jest', addPlugin: true, + useProjectJson: false, } as Schema); expect(readJson(tree, 'mylib/package.json')).toMatchInlineSnapshot(` @@ -709,6 +711,7 @@ describe('lib', () => { linter: 'none', unitTestRunner: 'none', addPlugin: true, + useProjectJson: false, skipFormat: true, } as Schema); @@ -724,6 +727,7 @@ describe('lib', () => { linter: 'none', unitTestRunner: 'none', addPlugin: true, + useProjectJson: false, skipFormat: true, } as Schema); @@ -736,10 +740,36 @@ describe('lib', () => { linter: 'none', unitTestRunner: 'none', addPlugin: true, + useProjectJson: false, skipFormat: true, } as Schema); expect(readJson(tree, 'mylib/package.json').nx).toBeUndefined(); }); + + it('should generate project.json if useProjectJson is true', async () => { + await libraryGenerator(tree, { + directory: 'mylib', + unitTestRunner: 'jest', + addPlugin: true, + useProjectJson: true, + skipFormat: true, + } as Schema); + + expect(tree.exists('mylib/project.json')).toBeTruthy(); + expect(readProjectConfiguration(tree, '@proj/mylib')) + .toMatchInlineSnapshot(` + { + "$schema": "../node_modules/nx/schemas/project-schema.json", + "name": "@proj/mylib", + "projectType": "library", + "root": "mylib", + "sourceRoot": "mylib/src", + "tags": [], + "targets": {}, + } + `); + expect(readJson(tree, 'mylib/package.json').nx).toBeUndefined(); + }); }); }); diff --git a/packages/node/src/generators/library/library.ts b/packages/node/src/generators/library/library.ts index 89d0863aa2..61994c3643 100644 --- a/packages/node/src/generators/library/library.ts +++ b/packages/node/src/generators/library/library.ts @@ -47,6 +47,7 @@ export interface NormalizedSchema extends Schema { export async function libraryGenerator(tree: Tree, schema: Schema) { return await libraryGeneratorInternal(tree, { addPlugin: false, + useProjectJson: true, ...schema, }); } @@ -70,6 +71,7 @@ export async function libraryGeneratorInternal(tree: Tree, schema: Schema) { // Create `package.json` first because @nx/js:lib generator will update it. if ( + !options.useProjectJson || options.isUsingTsSolutionConfig || options.publishable || options.buildable @@ -78,7 +80,6 @@ export async function libraryGeneratorInternal(tree: Tree, schema: Schema) { name: options.importPath, version: '0.0.1', private: true, - files: options.publishable ? ['dist', '!**/*.tsbuildinfo'] : undefined, }); } @@ -91,7 +92,7 @@ export async function libraryGeneratorInternal(tree: Tree, schema: Schema) { testEnvironment: 'node', skipFormat: true, setParserOptionsProject: schema.setParserOptionsProject, - useProjectJson: !options.isUsingTsSolutionConfig, + useProjectJson: options.useProjectJson, }) ); @@ -173,6 +174,7 @@ async function normalizeOptions( parsedTags, importPath, isUsingTsSolutionConfig, + useProjectJson: options.useProjectJson ?? !isUsingTsSolutionConfig, }; } diff --git a/packages/node/src/generators/library/schema.d.ts b/packages/node/src/generators/library/schema.d.ts index ffe32d161a..cd3007659c 100644 --- a/packages/node/src/generators/library/schema.d.ts +++ b/packages/node/src/generators/library/schema.d.ts @@ -21,4 +21,5 @@ export interface Schema { setParserOptionsProject?: boolean; compiler: 'tsc' | 'swc'; addPlugin?: boolean; + useProjectJson?: boolean; } diff --git a/packages/node/src/generators/library/schema.json b/packages/node/src/generators/library/schema.json index 0649384de0..a99d28801d 100644 --- a/packages/node/src/generators/library/schema.json +++ b/packages/node/src/generators/library/schema.json @@ -123,6 +123,10 @@ "type": "boolean", "description": "Whether or not to configure the ESLint `parserOptions.project`. We do not do this by default for lint performance reasons.", "default": false + }, + "useProjectJson": { + "type": "boolean", + "description": "Use a `project.json` configuration file instead of inlining the Nx configuration in the `package.json` file." } }, "required": ["directory"] diff --git a/packages/nuxt/generators.json b/packages/nuxt/generators.json index 75915054f3..3966a72038 100644 --- a/packages/nuxt/generators.json +++ b/packages/nuxt/generators.json @@ -11,7 +11,7 @@ "hidden": true }, "application": { - "factory": "./src/generators/application/application", + "factory": "./src/generators/application/application#applicationGeneratorInternal", "schema": "./src/generators/application/schema.json", "aliases": ["app"], "description": "Create a Nuxt application." diff --git a/packages/nuxt/src/generators/application/application.spec.ts b/packages/nuxt/src/generators/application/application.spec.ts index c3dd0d556f..47bfd57a6c 100644 --- a/packages/nuxt/src/generators/application/application.spec.ts +++ b/packages/nuxt/src/generators/application/application.spec.ts @@ -240,6 +240,7 @@ describe('app', () => { e2eTestRunner: 'playwright', unitTestRunner: 'vitest', linter: 'eslint', + useProjectJson: false, }); expect(tree.read('myapp/vite.config.ts', 'utf-8')).toMatchInlineSnapshot( @@ -395,6 +396,7 @@ describe('app', () => { e2eTestRunner: 'playwright', unitTestRunner: 'vitest', linter: 'eslint', + useProjectJson: false, }); const packageJson = readJson(tree, 'myapp/package.json'); @@ -410,5 +412,46 @@ describe('app', () => { ] `); }); + + it('should generate project.json if useProjectJson is true', async () => { + await applicationGenerator(tree, { + directory: 'myapp', + e2eTestRunner: 'playwright', + unitTestRunner: 'vitest', + linter: 'eslint', + useProjectJson: true, + skipFormat: true, + }); + + expect(tree.exists('myapp/project.json')).toBeTruthy(); + expect(readProjectConfiguration(tree, '@proj/myapp')) + .toMatchInlineSnapshot(` + { + "$schema": "../node_modules/nx/schemas/project-schema.json", + "name": "@proj/myapp", + "projectType": "application", + "root": "myapp", + "sourceRoot": "myapp/src", + "targets": {}, + } + `); + expect(readJson(tree, 'myapp/package.json').nx).toBeUndefined(); + expect(tree.exists('myapp-e2e/project.json')).toBeTruthy(); + expect(readProjectConfiguration(tree, '@proj/myapp-e2e')) + .toMatchInlineSnapshot(` + { + "$schema": "../node_modules/nx/schemas/project-schema.json", + "implicitDependencies": [ + "@proj/myapp", + ], + "name": "@proj/myapp-e2e", + "projectType": "application", + "root": "myapp-e2e", + "sourceRoot": "myapp-e2e/src", + "targets": {}, + } + `); + expect(readJson(tree, 'myapp-e2e/package.json').nx).toBeUndefined(); + }); }); }); diff --git a/packages/nuxt/src/generators/application/application.ts b/packages/nuxt/src/generators/application/application.ts index 5867a3c5c7..365ebdf013 100644 --- a/packages/nuxt/src/generators/application/application.ts +++ b/packages/nuxt/src/generators/application/application.ts @@ -40,6 +40,13 @@ import { sortPackageJsonFields } from '@nx/js/src/utils/package-json/sort-fields import type { PackageJson } from 'nx/src/utils/package-json'; export async function applicationGenerator(tree: Tree, schema: Schema) { + return await applicationGeneratorInternal(tree, { + useProjectJson: true, + ...schema, + }); +} + +export async function applicationGeneratorInternal(tree: Tree, schema: Schema) { const tasks: GeneratorCallback[] = []; const jsInitTask = await jsInitGenerator(tree, { @@ -66,13 +73,13 @@ export async function applicationGenerator(tree: Tree, schema: Schema) { tasks.push(ensureDependencies(tree, options)); - if (options.isUsingTsSolutionConfig) { - const packageJson: PackageJson = { - name: options.importPath, - version: '0.0.1', - private: true, - }; + const packageJson: PackageJson = { + name: options.importPath, + version: '0.0.1', + private: true, + }; + if (!options.useProjectJson) { if (options.projectName !== options.importPath) { packageJson.nx = { name: options.projectName }; } @@ -80,12 +87,6 @@ export async function applicationGenerator(tree: Tree, schema: Schema) { packageJson.nx ??= {}; packageJson.nx.tags = options.parsedTags; } - - writeJson( - tree, - joinPathFragments(options.appProjectRoot, 'package.json'), - packageJson - ); } else { addProjectConfiguration(tree, options.projectName, { root: options.appProjectRoot, @@ -96,6 +97,14 @@ export async function applicationGenerator(tree: Tree, schema: Schema) { }); } + if (!options.useProjectJson || options.isUsingTsSolutionConfig) { + writeJson( + tree, + joinPathFragments(options.appProjectRoot, 'package.json'), + packageJson + ); + } + generateFiles( tree, joinPathFragments(__dirname, './files/base'), diff --git a/packages/nuxt/src/generators/application/lib/add-e2e.ts b/packages/nuxt/src/generators/application/lib/add-e2e.ts index cd89618fc5..d06211e64d 100644 --- a/packages/nuxt/src/generators/application/lib/add-e2e.ts +++ b/packages/nuxt/src/generators/application/lib/add-e2e.ts @@ -11,6 +11,7 @@ import { nxVersion } from '../../../utils/versions'; import { NormalizedSchema } from '../schema'; import { findPluginForConfigFile } from '@nx/devkit/src/utils/find-plugin-for-config-file'; import { addE2eCiTargetDefaults } from '@nx/devkit/src/generators/target-defaults-utils'; +import type { PackageJson } from 'nx/src/utils/package-json'; export async function addE2e(host: Tree, options: NormalizedSchema) { const e2eWebServerInfo = await getNuxtE2EWebServerInfo( @@ -25,19 +26,17 @@ export async function addE2e(host: Tree, options: NormalizedSchema) { const { configurationGenerator } = ensurePackage< typeof import('@nx/cypress') >('@nx/cypress', nxVersion); - if (options.isUsingTsSolutionConfig) { - writeJson( - host, - joinPathFragments(options.e2eProjectRoot, 'package.json'), - { - name: options.e2eProjectName, - version: '0.0.1', - private: true, - nx: { - implicitDependencies: [options.projectName], - }, - } - ); + + const packageJson: PackageJson = { + name: options.e2eProjectName, + version: '0.0.1', + private: true, + }; + + if (!options.useProjectJson) { + packageJson.nx = { + implicitDependencies: [options.projectName], + }; } else { addProjectConfiguration(host, options.e2eProjectName, { projectType: 'application', @@ -48,6 +47,15 @@ export async function addE2e(host: Tree, options: NormalizedSchema) { implicitDependencies: [options.projectName], }); } + + if (!options.useProjectJson || options.isUsingTsSolutionConfig) { + writeJson( + host, + joinPathFragments(options.e2eProjectRoot, 'package.json'), + packageJson + ); + } + const e2eTask = await configurationGenerator(host, { ...options, project: options.e2eProjectName, @@ -95,19 +103,17 @@ export async function addE2e(host: Tree, options: NormalizedSchema) { const { configurationGenerator } = ensurePackage< typeof import('@nx/playwright') >('@nx/playwright', nxVersion); - if (options.isUsingTsSolutionConfig) { - writeJson( - host, - joinPathFragments(options.e2eProjectRoot, 'package.json'), - { - name: options.e2eProjectName, - version: '0.0.1', - private: true, - nx: { - implicitDependencies: [options.projectName], - }, - } - ); + + const packageJson: PackageJson = { + name: options.e2eProjectName, + version: '0.0.1', + private: true, + }; + + if (!options.useProjectJson) { + packageJson.nx = { + implicitDependencies: [options.projectName], + }; } else { addProjectConfiguration(host, options.e2eProjectName, { projectType: 'application', @@ -117,6 +123,15 @@ export async function addE2e(host: Tree, options: NormalizedSchema) { implicitDependencies: [options.projectName], }); } + + if (!options.useProjectJson || options.isUsingTsSolutionConfig) { + writeJson( + host, + joinPathFragments(options.e2eProjectRoot, 'package.json'), + packageJson + ); + } + const e2eTask = await configurationGenerator(host, { project: options.e2eProjectName, skipFormat: true, diff --git a/packages/nuxt/src/generators/application/lib/normalize-options.ts b/packages/nuxt/src/generators/application/lib/normalize-options.ts index 194b1c4b9e..c1925fb712 100644 --- a/packages/nuxt/src/generators/application/lib/normalize-options.ts +++ b/packages/nuxt/src/generators/application/lib/normalize-options.ts @@ -46,6 +46,7 @@ export async function normalizeOptions( parsedTags, style: options.style ?? 'none', isUsingTsSolutionConfig, + useProjectJson: options.useProjectJson ?? !isUsingTsSolutionConfig, } as NormalizedSchema; normalized.unitTestRunner ??= 'vitest'; diff --git a/packages/nuxt/src/generators/application/schema.d.ts b/packages/nuxt/src/generators/application/schema.d.ts index e392b4951d..edd61de16e 100644 --- a/packages/nuxt/src/generators/application/schema.d.ts +++ b/packages/nuxt/src/generators/application/schema.d.ts @@ -16,6 +16,7 @@ export interface Schema { style?: 'css' | 'scss' | 'less' | 'none'; nxCloudToken?: string; useTsSolution?: boolean; + useProjectJson?: boolean; } export interface NormalizedSchema extends Omit { diff --git a/packages/nuxt/src/generators/application/schema.json b/packages/nuxt/src/generators/application/schema.json index 75f4fd461a..513c71b0a0 100644 --- a/packages/nuxt/src/generators/application/schema.json +++ b/packages/nuxt/src/generators/application/schema.json @@ -106,6 +106,10 @@ "type": "boolean", "description": "Whether or not to configure the ESLint `parserOptions.project` option. We do not do this by default for lint performance reasons.", "default": false + }, + "useProjectJson": { + "type": "boolean", + "description": "Use a `project.json` configuration file instead of inlining the Nx configuration in the `package.json` file." } }, "required": ["directory"], diff --git a/packages/react-native/src/generators/application/application.spec.ts b/packages/react-native/src/generators/application/application.spec.ts index fa31d4c359..ace4f98422 100644 --- a/packages/react-native/src/generators/application/application.spec.ts +++ b/packages/react-native/src/generators/application/application.spec.ts @@ -286,6 +286,7 @@ describe('app', () => { unitTestRunner: 'jest', bundler: 'vite', addPlugin: true, + useProjectJson: false, }); expect(readJson(tree, 'tsconfig.json').references).toMatchInlineSnapshot(` @@ -422,6 +423,7 @@ describe('app', () => { unitTestRunner: 'jest', bundler: 'vite', addPlugin: true, + useProjectJson: false, }); const packageJson = readJson(tree, 'my-app/package.json'); @@ -437,5 +439,51 @@ describe('app', () => { ] `); }); + + it('should generate project.json if useProjectJson is true', async () => { + await reactNativeApplicationGenerator(tree, { + directory: 'my-app', + linter: Linter.EsLint, + e2eTestRunner: 'cypress', + install: false, + unitTestRunner: 'jest', + bundler: 'vite', + addPlugin: true, + useProjectJson: true, + skipFormat: true, + }); + + expect(tree.exists('my-app/project.json')).toBeTruthy(); + expect(readProjectConfiguration(tree, '@proj/my-app')) + .toMatchInlineSnapshot(` + { + "$schema": "../node_modules/nx/schemas/project-schema.json", + "name": "@proj/my-app", + "projectType": "application", + "root": "my-app", + "sourceRoot": "my-app/src", + "tags": [], + "targets": {}, + } + `); + expect(readJson(tree, 'my-app/package.json').nx).toBeUndefined(); + expect(tree.exists('my-app-e2e/project.json')).toBeTruthy(); + expect(readProjectConfiguration(tree, '@proj/my-app-e2e')) + .toMatchInlineSnapshot(` + { + "$schema": "../node_modules/nx/schemas/project-schema.json", + "implicitDependencies": [ + "@proj/my-app", + ], + "name": "@proj/my-app-e2e", + "projectType": "application", + "root": "my-app-e2e", + "sourceRoot": "my-app-e2e/src", + "tags": [], + "targets": {}, + } + `); + expect(readJson(tree, 'my-app-e2e/package.json').nx).toBeUndefined(); + }); }); }); diff --git a/packages/react-native/src/generators/application/application.ts b/packages/react-native/src/generators/application/application.ts index 353ab052c5..202149bd62 100644 --- a/packages/react-native/src/generators/application/application.ts +++ b/packages/react-native/src/generators/application/application.ts @@ -37,6 +37,7 @@ export async function reactNativeApplicationGenerator( ): Promise { return await reactNativeApplicationGeneratorInternal(host, { addPlugin: false, + useProjectJson: true, ...schema, }); } diff --git a/packages/react-native/src/generators/application/lib/add-project.ts b/packages/react-native/src/generators/application/lib/add-project.ts index f55a0ef501..9aa0b12b11 100644 --- a/packages/react-native/src/generators/application/lib/add-project.ts +++ b/packages/react-native/src/generators/application/lib/add-project.ts @@ -8,7 +8,6 @@ import { writeJson, } from '@nx/devkit'; import { NormalizedSchema } from './normalize-options'; -import { isUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup'; import type { PackageJson } from 'nx/src/utils/package-json'; export function addProject(host: Tree, options: NormalizedSchema) { @@ -27,13 +26,13 @@ export function addProject(host: Tree, options: NormalizedSchema) { tags: options.parsedTags, }; - if (isUsingTsSolutionSetup(host)) { - const packageJson: PackageJson = { - name: options.importPath, - version: '0.0.1', - private: true, - }; + const packageJson: PackageJson = { + name: options.importPath, + version: '0.0.1', + private: true, + }; + if (!options.useProjectJson) { if (options.projectName !== options.importPath) { packageJson.nx = { name: options.projectName }; } @@ -45,16 +44,18 @@ export function addProject(host: Tree, options: NormalizedSchema) { packageJson.nx ??= {}; packageJson.nx.tags = options.parsedTags; } + } else { + addProjectConfiguration(host, options.projectName, { + ...project, + }); + } + if (!options.useProjectJson || options.isTsSolutionSetup) { writeJson( host, joinPathFragments(options.appProjectRoot, 'package.json'), packageJson ); - } else { - addProjectConfiguration(host, options.projectName, { - ...project, - }); } } diff --git a/packages/react-native/src/generators/application/lib/normalize-options.spec.ts b/packages/react-native/src/generators/application/lib/normalize-options.spec.ts index b876d115aa..9eb74780f8 100644 --- a/packages/react-native/src/generators/application/lib/normalize-options.spec.ts +++ b/packages/react-native/src/generators/application/lib/normalize-options.spec.ts @@ -45,6 +45,7 @@ describe('Normalize Options', () => { e2eProjectName: 'my-app-e2e', e2eProjectRoot: 'my-app-e2e', isTsSolutionSetup: false, + useProjectJson: true, }); }); @@ -82,6 +83,7 @@ describe('Normalize Options', () => { e2eProjectName: 'myApp-e2e', e2eProjectRoot: 'myApp-e2e', isTsSolutionSetup: false, + useProjectJson: true, }); }); @@ -120,6 +122,7 @@ describe('Normalize Options', () => { e2eProjectName: 'my-app-e2e', e2eProjectRoot: 'directory/my-app-e2e', isTsSolutionSetup: false, + useProjectJson: true, }); }); @@ -157,6 +160,7 @@ describe('Normalize Options', () => { e2eProjectName: 'my-app-e2e', e2eProjectRoot: 'directory/my-app-e2e', isTsSolutionSetup: false, + useProjectJson: true, }); }); @@ -195,6 +199,7 @@ describe('Normalize Options', () => { e2eProjectName: 'my-app-e2e', e2eProjectRoot: 'my-app-e2e', isTsSolutionSetup: false, + useProjectJson: true, }); }); }); diff --git a/packages/react-native/src/generators/application/lib/normalize-options.ts b/packages/react-native/src/generators/application/lib/normalize-options.ts index 3925901a56..ed4ee9a944 100644 --- a/packages/react-native/src/generators/application/lib/normalize-options.ts +++ b/packages/react-native/src/generators/application/lib/normalize-options.ts @@ -80,5 +80,6 @@ export async function normalizeOptions( e2eProjectName, e2eProjectRoot, isTsSolutionSetup, + useProjectJson: options.useProjectJson ?? !isTsSolutionSetup, }; } diff --git a/packages/react-native/src/generators/application/schema.d.ts b/packages/react-native/src/generators/application/schema.d.ts index c7fba6dec1..4da0c70568 100644 --- a/packages/react-native/src/generators/application/schema.d.ts +++ b/packages/react-native/src/generators/application/schema.d.ts @@ -21,4 +21,5 @@ export interface Schema { nxCloudToken?: string; useTsSolution?: boolean; formatter?: 'prettier' | 'none'; + useProjectJson?: boolean; } diff --git a/packages/react-native/src/generators/application/schema.json b/packages/react-native/src/generators/application/schema.json index e6f1384e2d..9e58ec44ea 100644 --- a/packages/react-native/src/generators/application/schema.json +++ b/packages/react-native/src/generators/application/schema.json @@ -95,6 +95,10 @@ "x-prompt": "Which bundler do you want to use to build the application?", "default": "vite", "x-priority": "important" + }, + "useProjectJson": { + "type": "boolean", + "description": "Use a `project.json` configuration file instead of inlining the Nx configuration in the `package.json` file." } }, "required": ["directory"] diff --git a/packages/react-native/src/generators/library/lib/normalize-options.ts b/packages/react-native/src/generators/library/lib/normalize-options.ts index 4aa4093821..9ea146aa48 100644 --- a/packages/react-native/src/generators/library/lib/normalize-options.ts +++ b/packages/react-native/src/generators/library/lib/normalize-options.ts @@ -55,6 +55,7 @@ export async function normalizeOptions( parsedTags, importPath, isUsingTsSolutionConfig, + useProjectJson: options.useProjectJson ?? !isUsingTsSolutionConfig, }; return normalized; diff --git a/packages/react-native/src/generators/library/library.spec.ts b/packages/react-native/src/generators/library/library.spec.ts index f487d66ff3..45a0ce2e3a 100644 --- a/packages/react-native/src/generators/library/library.spec.ts +++ b/packages/react-native/src/generators/library/library.spec.ts @@ -471,6 +471,7 @@ describe('lib', () => { it('should add project references when using TS solution', async () => { await libraryGenerator(appTree, { ...defaultSchema, + useProjectJson: false, }); expect(readJson(appTree, 'tsconfig.json').references) @@ -481,8 +482,11 @@ describe('lib', () => { }, ] `); + const packageJson = readJson(appTree, 'my-lib/package.json'); + expect(packageJson.name).toBe('@proj/my-lib'); + expect(packageJson.nx).toBeUndefined(); // Make sure keys are in idiomatic order - expect(readJson(appTree, 'my-lib/package.json')).toMatchInlineSnapshot(` + expect(packageJson).toMatchInlineSnapshot(` { "exports": { ".": { @@ -600,6 +604,7 @@ describe('lib', () => { ...defaultSchema, directory: 'my-lib', name: 'my-lib', // import path contains the npm scope, so it would be different + useProjectJson: false, skipFormat: true, }); @@ -613,6 +618,7 @@ describe('lib', () => { ...defaultSchema, directory: 'my-lib', name: '@proj/my-lib', + useProjectJson: false, skipFormat: true, }); @@ -623,10 +629,34 @@ describe('lib', () => { await libraryGenerator(appTree, { ...defaultSchema, directory: 'my-lib', + useProjectJson: false, skipFormat: true, }); expect(readJson(appTree, 'my-lib/package.json').nx).toBeUndefined(); }); + + it('should generate project.json if useProjectJson is true', async () => { + await libraryGenerator(appTree, { + ...defaultSchema, + useProjectJson: true, + skipFormat: true, + }); + + expect(appTree.exists('my-lib/project.json')).toBeTruthy(); + expect(readProjectConfiguration(appTree, '@proj/my-lib')) + .toMatchInlineSnapshot(` + { + "$schema": "../node_modules/nx/schemas/project-schema.json", + "name": "@proj/my-lib", + "projectType": "library", + "root": "my-lib", + "sourceRoot": "my-lib/src", + "tags": [], + "targets": {}, + } + `); + expect(readJson(appTree, 'my-lib/package.json').nx).toBeUndefined(); + }); }); }); diff --git a/packages/react-native/src/generators/library/library.ts b/packages/react-native/src/generators/library/library.ts index c06d4df2b3..66ebe3b266 100644 --- a/packages/react-native/src/generators/library/library.ts +++ b/packages/react-native/src/generators/library/library.ts @@ -33,11 +33,6 @@ import { updateTsconfigFiles, } from '@nx/js/src/utils/typescript/ts-solution-setup'; import { sortPackageJsonFields } from '@nx/js/src/utils/package-json/sort-fields'; -import { - addReleaseConfigForNonTsSolution, - addReleaseConfigForTsSolution, - releaseTasks, -} from '@nx/js/src/generators/library/utils/add-release-config'; import { PackageJson } from 'nx/src/utils/package-json'; import { addRollupBuildTarget } from '@nx/react/src/generators/library/lib/add-rollup-build-target'; import { getRelativeCwd } from '@nx/devkit/src/generators/artifact-name-and-directory-utils'; @@ -50,6 +45,7 @@ export async function reactNativeLibraryGenerator( ): Promise { return await reactNativeLibraryGeneratorInternal(host, { addPlugin: false, + useProjectJson: true, ...schema, }); } @@ -180,10 +176,14 @@ async function addProject( targets: {}, }; - if (options.isUsingTsSolutionConfig) { - const packageJson: PackageJson = { - name: options.importPath, - version: '0.0.1', + let packageJson: PackageJson = { + name: options.importPath, + version: '0.0.1', + }; + + if (!options.useProjectJson) { + packageJson = { + ...packageJson, ...determineEntryFields(options), files: options.publishable ? ['dist', '!**/*.tsbuildinfo'] : undefined, peerDependencies: { @@ -191,7 +191,6 @@ async function addProject( 'react-native': reactNativeVersion, }, }; - if (options.name !== options.importPath) { packageJson.nx = { name: options.name }; } @@ -199,14 +198,21 @@ async function addProject( packageJson.nx ??= {}; packageJson.nx.tags = options.parsedTags; } + } else { + addProjectConfiguration(host, options.name, project); + } + if ( + !options.useProjectJson || + options.isUsingTsSolutionConfig || + options.publishable || + options.buildable + ) { writeJson( host, joinPathFragments(options.projectRoot, 'package.json'), packageJson ); - } else { - addProjectConfiguration(host, options.name, project); } if (options.publishable || options.buildable) { diff --git a/packages/react-native/src/generators/library/schema.d.ts b/packages/react-native/src/generators/library/schema.d.ts index 63a11185cf..ae0d627fe4 100644 --- a/packages/react-native/src/generators/library/schema.d.ts +++ b/packages/react-native/src/generators/library/schema.d.ts @@ -19,4 +19,5 @@ export interface Schema { setParserOptionsProject?: boolean; skipPackageJson?: boolean; //default is false addPlugin?: boolean; + useProjectJson?: boolean; } diff --git a/packages/react-native/src/generators/library/schema.json b/packages/react-native/src/generators/library/schema.json index 41760ef45c..65333a7e18 100644 --- a/packages/react-native/src/generators/library/schema.json +++ b/packages/react-native/src/generators/library/schema.json @@ -93,6 +93,10 @@ "type": "boolean", "default": false, "x-priority": "internal" + }, + "useProjectJson": { + "type": "boolean", + "description": "Use a `project.json` configuration file instead of inlining the Nx configuration in the `package.json` file." } }, "required": ["directory"] diff --git a/packages/react/src/generators/application/application.spec.ts b/packages/react/src/generators/application/application.spec.ts index b1930114d5..57bbb4c338 100644 --- a/packages/react/src/generators/application/application.spec.ts +++ b/packages/react/src/generators/application/application.spec.ts @@ -1317,6 +1317,7 @@ describe('app', () => { bundler: 'vite', unitTestRunner: 'vitest', e2eTestRunner: 'playwright', + useProjectJson: false, }); expect(readJson(appTree, 'tsconfig.json').references) @@ -1485,6 +1486,7 @@ describe('app', () => { bundler: 'vite', unitTestRunner: 'vitest', e2eTestRunner: 'playwright', + useProjectJson: false, }); const packageJson = readJson(appTree, 'myapp/package.json'); @@ -1510,6 +1512,7 @@ describe('app', () => { bundler: 'vite', unitTestRunner: 'none', e2eTestRunner: 'none', + useProjectJson: false, }); await applicationGenerator(appTree, { directory: 'libs/nested1', @@ -1556,6 +1559,7 @@ describe('app', () => { bundler: 'vite', unitTestRunner: 'none', e2eTestRunner: 'none', + useProjectJson: false, }); await applicationGenerator(appTree, { directory: 'apps/nested1', @@ -1565,6 +1569,7 @@ describe('app', () => { bundler: 'vite', unitTestRunner: 'none', e2eTestRunner: 'none', + useProjectJson: false, }); await applicationGenerator(appTree, { directory: 'apps/nested2', @@ -1574,6 +1579,7 @@ describe('app', () => { bundler: 'vite', unitTestRunner: 'none', e2eTestRunner: 'none', + useProjectJson: false, }); await applicationGenerator(appTree, { directory: 'packages/shared/util', @@ -1583,6 +1589,7 @@ describe('app', () => { bundler: 'vite', unitTestRunner: 'none', e2eTestRunner: 'none', + useProjectJson: false, }); const pnpmContent = appTree.read('pnpm-workspace.yaml', 'utf-8'); @@ -1603,6 +1610,7 @@ describe('app', () => { style: 'none', e2eTestRunner: 'none', addPlugin: true, + useProjectJson: false, skipFormat: true, }); @@ -1655,6 +1663,7 @@ describe('app', () => { style: 'none', e2eTestRunner: 'none', addPlugin: false, + useProjectJson: false, skipFormat: true, }); @@ -1672,6 +1681,7 @@ describe('app', () => { style: 'none', e2eTestRunner: 'none', addPlugin: false, + useProjectJson: false, skipFormat: true, }); @@ -1680,6 +1690,51 @@ describe('app', () => { .outputPath ).toBe('apps/my-app/dist'); }); + + it('should generate project.json if useProjectJson is true', async () => { + await applicationGenerator(appTree, { + directory: 'myapp', + addPlugin: true, + linter: Linter.EsLint, + style: 'none', + bundler: 'vite', + unitTestRunner: 'vitest', + e2eTestRunner: 'playwright', + useProjectJson: true, + }); + + expect(appTree.exists('myapp/project.json')).toBeTruthy(); + expect(readProjectConfiguration(appTree, '@proj/myapp')) + .toMatchInlineSnapshot(` + { + "$schema": "../node_modules/nx/schemas/project-schema.json", + "name": "@proj/myapp", + "projectType": "application", + "root": "myapp", + "sourceRoot": "myapp/src", + "tags": [], + "targets": {}, + } + `); + expect(readJson(appTree, 'myapp/package.json').nx).toBeUndefined(); + expect(appTree.exists('myapp-e2e/project.json')).toBeTruthy(); + expect(readProjectConfiguration(appTree, '@proj/myapp-e2e')) + .toMatchInlineSnapshot(` + { + "$schema": "../node_modules/nx/schemas/project-schema.json", + "implicitDependencies": [ + "@proj/myapp", + ], + "name": "@proj/myapp-e2e", + "projectType": "application", + "root": "myapp-e2e", + "sourceRoot": "myapp-e2e/src", + "tags": [], + "targets": {}, + } + `); + expect(readJson(appTree, 'myapp-e2e/package.json').nx).toBeUndefined(); + }); }); describe('--bundler=rsbuild', () => { diff --git a/packages/react/src/generators/application/application.ts b/packages/react/src/generators/application/application.ts index d96f599b29..ab08f5aea8 100644 --- a/packages/react/src/generators/application/application.ts +++ b/packages/react/src/generators/application/application.ts @@ -50,6 +50,7 @@ export async function applicationGenerator( ): Promise { return await applicationGeneratorInternal(tree, { addPlugin: false, + useProjectJson: true, ...schema, }); } diff --git a/packages/react/src/generators/application/lib/add-e2e.ts b/packages/react/src/generators/application/lib/add-e2e.ts index b75182e6c2..fc5687ef5c 100644 --- a/packages/react/src/generators/application/lib/add-e2e.ts +++ b/packages/react/src/generators/application/lib/add-e2e.ts @@ -19,6 +19,7 @@ import { NormalizedSchema } from '../schema'; import { findPluginForConfigFile } from '@nx/devkit/src/utils/find-plugin-for-config-file'; import { addE2eCiTargetDefaults } from '@nx/devkit/src/generators/target-defaults-utils'; import { E2EWebServerDetails } from '@nx/devkit/src/generators/e2e-web-server-info-utils'; +import type { PackageJson } from 'nx/src/utils/package-json'; export async function addE2e( tree: Tree, @@ -102,19 +103,16 @@ export async function addE2e( typeof import('@nx/cypress') >('@nx/cypress', nxVersion); - if (options.isUsingTsSolutionConfig) { - writeJson( - tree, - joinPathFragments(options.e2eProjectRoot, 'package.json'), - { - name: options.e2eProjectName, - version: '0.0.1', - private: true, - nx: { - implicitDependencies: [options.projectName], - }, - } - ); + const packageJson: PackageJson = { + name: options.e2eProjectName, + version: '0.0.1', + private: true, + }; + + if (!options.useProjectJson) { + packageJson.nx = { + implicitDependencies: [options.projectName], + }; } else { addProjectConfiguration(tree, options.e2eProjectName, { projectType: 'application', @@ -126,6 +124,14 @@ export async function addE2e( }); } + if (!options.useProjectJson || options.isUsingTsSolutionConfig) { + writeJson( + tree, + joinPathFragments(options.e2eProjectRoot, 'package.json'), + packageJson + ); + } + const e2eTask = await configurationGenerator(tree, { ...options, project: options.e2eProjectName, @@ -198,19 +204,17 @@ export async function addE2e( const { configurationGenerator } = ensurePackage< typeof import('@nx/playwright') >('@nx/playwright', nxVersion); - if (options.isUsingTsSolutionConfig) { - writeJson( - tree, - joinPathFragments(options.e2eProjectRoot, 'package.json'), - { - name: options.e2eProjectName, - version: '0.0.1', - private: true, - nx: { - implicitDependencies: [options.projectName], - }, - } - ); + + const packageJson: PackageJson = { + name: options.e2eProjectName, + version: '0.0.1', + private: true, + }; + + if (!options.useProjectJson) { + packageJson.nx = { + implicitDependencies: [options.projectName], + }; } else { addProjectConfiguration(tree, options.e2eProjectName, { projectType: 'application', @@ -218,9 +222,18 @@ export async function addE2e( sourceRoot: joinPathFragments(options.e2eProjectRoot, 'src'), targets: {}, implicitDependencies: [options.projectName], + tags: [], }); } + if (!options.useProjectJson || options.isUsingTsSolutionConfig) { + writeJson( + tree, + joinPathFragments(options.e2eProjectRoot, 'package.json'), + packageJson + ); + } + const e2eTask = await configurationGenerator(tree, { project: options.e2eProjectName, skipFormat: true, diff --git a/packages/react/src/generators/application/lib/add-project.ts b/packages/react/src/generators/application/lib/add-project.ts index 0b9f951489..21babd9ca0 100644 --- a/packages/react/src/generators/application/lib/add-project.ts +++ b/packages/react/src/generators/application/lib/add-project.ts @@ -5,7 +5,6 @@ import { ProjectConfiguration, TargetConfiguration, Tree, - updateProjectConfiguration, writeJson, } from '@nx/devkit'; import { hasWebpackPlugin } from '../../../utils/has-webpack-plugin'; @@ -39,13 +38,13 @@ export function addProject(host: Tree, options: NormalizedSchema) { }; } - if (options.isUsingTsSolutionConfig) { - const packageJson: PackageJson = { - name: options.importPath, - version: '0.0.1', - private: true, - }; + const packageJson: PackageJson = { + name: options.importPath, + version: '0.0.1', + private: true, + }; + if (!options.useProjectJson) { if (options.projectName !== options.importPath) { packageJson.nx = { name: options.projectName }; } @@ -57,29 +56,19 @@ export function addProject(host: Tree, options: NormalizedSchema) { packageJson.nx ??= {}; packageJson.nx.tags = options.parsedTags; } + } else { + addProjectConfiguration(host, options.projectName, { + ...project, + }); + } + if (!options.useProjectJson || options.isUsingTsSolutionConfig) { writeJson( host, joinPathFragments(options.appProjectRoot, 'package.json'), packageJson ); } - - if (!options.isUsingTsSolutionConfig || options.alwaysGenerateProjectJson) { - addProjectConfiguration(host, options.projectName, { - ...project, - }); - } else if ( - options.parsedTags?.length || - Object.keys(project.targets).length - ) { - const updatedProject: ProjectConfiguration = { - root: options.appProjectRoot, - targets: project.targets, - tags: options.parsedTags?.length ? options.parsedTags : undefined, - }; - updateProjectConfiguration(host, options.projectName, updatedProject); - } } function createRspackBuildTarget( diff --git a/packages/react/src/generators/application/lib/normalize-options.ts b/packages/react/src/generators/application/lib/normalize-options.ts index aebb0c78a6..66fc4cf6f3 100644 --- a/packages/react/src/generators/application/lib/normalize-options.ts +++ b/packages/react/src/generators/application/lib/normalize-options.ts @@ -66,6 +66,7 @@ export async function normalizeOptions( hasStyles: options.style !== 'none', names: names(projectNames.projectSimpleName), isUsingTsSolutionConfig, + useProjectJson: options.useProjectJson ?? !isUsingTsSolutionConfig, } as NormalizedSchema; normalized.routing = normalized.routing ?? false; diff --git a/packages/react/src/generators/application/schema.d.ts b/packages/react/src/generators/application/schema.d.ts index e46cc0d88c..e654416e68 100644 --- a/packages/react/src/generators/application/schema.d.ts +++ b/packages/react/src/generators/application/schema.d.ts @@ -31,7 +31,7 @@ export interface Schema { nxCloudToken?: string; useTsSolution?: boolean; formatter?: 'prettier' | 'none'; - alwaysGenerateProjectJson?: boolean; // this is needed for MF currently + useProjectJson?: boolean; } export interface NormalizedSchema extends T { diff --git a/packages/react/src/generators/application/schema.json b/packages/react/src/generators/application/schema.json index d384322b53..af1f7b4aa8 100644 --- a/packages/react/src/generators/application/schema.json +++ b/packages/react/src/generators/application/schema.json @@ -187,6 +187,10 @@ "description": "Generate a React app with a minimal setup, no separate test files.", "type": "boolean", "default": false + }, + "useProjectJson": { + "type": "boolean", + "description": "Use a `project.json` configuration file instead of inlining the Nx configuration in the `package.json` file." } }, "required": ["directory"], diff --git a/packages/react/src/generators/host/host.ts b/packages/react/src/generators/host/host.ts index ec7a3177c3..80d25d4815 100644 --- a/packages/react/src/generators/host/host.ts +++ b/packages/react/src/generators/host/host.ts @@ -38,7 +38,7 @@ export async function hostGenerator( const options: NormalizedSchema = { ...(await normalizeOptions(host, { ...schema, - alwaysGenerateProjectJson: true, + useProjectJson: true, })), js: schema.js ?? false, typescriptConfiguration: schema.js @@ -71,7 +71,7 @@ export async function hostGenerator( // The target use-case is loading remotes as child routes, thus always enable routing. routing: true, skipFormat: true, - alwaysGenerateProjectJson: true, + useProjectJson: true, }); tasks.push(initTask); diff --git a/packages/react/src/generators/library/lib/create-files.ts b/packages/react/src/generators/library/lib/create-files.ts index e873b26d7e..ff3f4067a7 100644 --- a/packages/react/src/generators/library/lib/create-files.ts +++ b/packages/react/src/generators/library/lib/create-files.ts @@ -70,7 +70,8 @@ export function createFiles(host: Tree, options: NormalizedSchema) { if ( (options.publishable || options.buildable) && - !options.isUsingTsSolutionConfig + !options.isUsingTsSolutionConfig && + options.useProjectJson ) { if (options.bundler === 'vite') { writeJson(host, `${options.projectRoot}/package.json`, { diff --git a/packages/react/src/generators/library/lib/normalize-options.ts b/packages/react/src/generators/library/lib/normalize-options.ts index 3adc0ce4ae..344c813dd0 100644 --- a/packages/react/src/generators/library/lib/normalize-options.ts +++ b/packages/react/src/generators/library/lib/normalize-options.ts @@ -78,6 +78,7 @@ export async function normalizeOptions( projectRoot, parsedTags, importPath, + useProjectJson: options.useProjectJson ?? !isUsingTsSolutionConfig, } as NormalizedSchema; // Libraries with a bundler or is publishable must also be buildable. diff --git a/packages/react/src/generators/library/library.spec.ts b/packages/react/src/generators/library/library.spec.ts index e4dacf188e..fc8febb2fb 100644 --- a/packages/react/src/generators/library/library.spec.ts +++ b/packages/react/src/generators/library/library.spec.ts @@ -954,6 +954,7 @@ module.exports = withNx( bundler: 'vite', unitTestRunner: 'vitest', directory: 'libs/mylib', + useProjectJson: false, }); expect(tree.read('libs/mylib/vite.config.ts', 'utf-8')) @@ -1145,6 +1146,7 @@ module.exports = withNx( bundler: 'none', unitTestRunner: 'none', directory: 'libs/mylib', + useProjectJson: false, }); await libraryGenerator(tree, { @@ -1153,6 +1155,7 @@ module.exports = withNx( unitTestRunner: 'none', directory: 'libs/myjslib', js: true, + useProjectJson: false, }); expect(readJson(tree, 'libs/mylib/package.json')).toMatchInlineSnapshot(` @@ -1192,6 +1195,7 @@ module.exports = withNx( bundler: 'rollup', unitTestRunner: 'none', directory: 'libs/mylib', + useProjectJson: false, }); expect(tree.read('libs/mylib/rollup.config.cjs', 'utf-8')) @@ -1234,6 +1238,7 @@ module.exports = withNx( directory: 'libs/mylib', publishable: true, importPath: '@acme/mylib', + useProjectJson: false, }); expect(readJson(tree, 'libs/mylib/package.json')).toMatchInlineSnapshot(` @@ -1269,6 +1274,7 @@ module.exports = withNx( unitTestRunner: 'none', directory: 'mylib', name: 'mylib', + useProjectJson: false, }); const pnpmContent = tree.read('pnpm-workspace.yaml', 'utf-8'); const pnpmWorkspaceFile = load(pnpmContent); @@ -1281,6 +1287,7 @@ module.exports = withNx( ...defaultSchema, directory: 'libs/my-lib', name: 'my-lib', // import path contains the npm scope, so it would be different + useProjectJson: false, skipFormat: true, }); @@ -1294,6 +1301,7 @@ module.exports = withNx( ...defaultSchema, directory: 'libs/my-lib', name: '@proj/my-lib', + useProjectJson: false, skipFormat: true, }); @@ -1304,10 +1312,37 @@ module.exports = withNx( await libraryGenerator(tree, { ...defaultSchema, directory: 'libs/my-lib', + useProjectJson: false, skipFormat: true, }); expect(readJson(tree, 'libs/my-lib/package.json').nx).toBeUndefined(); }); + + it('should generate project.json if useProjectJson is true', async () => { + await libraryGenerator(tree, { + ...defaultSchema, + bundler: 'vite', + unitTestRunner: 'vitest', + directory: 'libs/mylib', + useProjectJson: true, + skipFormat: true, + }); + + expect(tree.exists('libs/mylib/project.json')).toBeTruthy(); + expect(readProjectConfiguration(tree, '@proj/mylib')) + .toMatchInlineSnapshot(` + { + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "name": "@proj/mylib", + "projectType": "library", + "root": "libs/mylib", + "sourceRoot": "libs/mylib/src", + "tags": [], + "targets": {}, + } + `); + expect(readJson(tree, 'libs/mylib/package.json').nx).toBeUndefined(); + }); }); }); diff --git a/packages/react/src/generators/library/library.ts b/packages/react/src/generators/library/library.ts index 6fa241c50f..cbb2cfd720 100644 --- a/packages/react/src/generators/library/library.ts +++ b/packages/react/src/generators/library/library.ts @@ -46,6 +46,7 @@ import type { PackageJson } from 'nx/src/utils/package-json'; export async function libraryGenerator(host: Tree, schema: Schema) { return await libraryGeneratorInternal(host, { addPlugin: false, + useProjectJson: true, ...schema, }); } @@ -80,14 +81,14 @@ export async function libraryGeneratorInternal(host: Tree, schema: Schema) { }); tasks.push(initTask); - if (options.isUsingTsSolutionConfig) { - const packageJson: PackageJson = { - name: options.importPath, - version: '0.0.1', - ...determineEntryFields(options), - files: options.publishable ? ['dist', '!**/*.tsbuildinfo'] : undefined, - }; + const packageJson: PackageJson = { + name: options.importPath, + version: '0.0.1', + ...determineEntryFields(options), + files: options.publishable ? ['dist', '!**/*.tsbuildinfo'] : undefined, + }; + if (!options.useProjectJson) { if (options.name !== options.importPath) { packageJson.nx = { name: options.name }; } @@ -95,8 +96,6 @@ export async function libraryGeneratorInternal(host: Tree, schema: Schema) { packageJson.nx ??= {}; packageJson.nx.tags = options.parsedTags; } - - writeJson(host, `${options.projectRoot}/package.json`, packageJson); } else { addProjectConfiguration(host, options.name, { root: options.projectRoot, @@ -107,6 +106,10 @@ export async function libraryGeneratorInternal(host: Tree, schema: Schema) { }); } + if (!options.useProjectJson || options.isUsingTsSolutionConfig) { + writeJson(host, `${options.projectRoot}/package.json`, packageJson); + } + createFiles(host, options); const lintTask = await addLinting(host, options); diff --git a/packages/react/src/generators/library/schema.d.ts b/packages/react/src/generators/library/schema.d.ts index 78db3d81b8..35cdf51356 100644 --- a/packages/react/src/generators/library/schema.d.ts +++ b/packages/react/src/generators/library/schema.d.ts @@ -27,6 +27,7 @@ export interface Schema { minimal?: boolean; simpleName?: boolean; addPlugin?: boolean; + useProjectJson?: boolean; } export interface NormalizedSchema extends Schema { diff --git a/packages/react/src/generators/library/schema.json b/packages/react/src/generators/library/schema.json index 2eefca8d03..19942b8dbc 100644 --- a/packages/react/src/generators/library/schema.json +++ b/packages/react/src/generators/library/schema.json @@ -190,6 +190,10 @@ "description": "Don't include the directory in the name of the module of the library.", "type": "boolean", "default": false + }, + "useProjectJson": { + "type": "boolean", + "description": "Use a `project.json` configuration file instead of inlining the Nx configuration in the `package.json` file." } }, "required": ["directory"] diff --git a/packages/react/src/generators/remote/remote.ts b/packages/react/src/generators/remote/remote.ts index 96d8e14bbe..aa2fa6fdb4 100644 --- a/packages/react/src/generators/remote/remote.ts +++ b/packages/react/src/generators/remote/remote.ts @@ -98,7 +98,7 @@ export async function remoteGenerator(host: Tree, schema: Schema) { const options: NormalizedSchema = { ...(await normalizeOptions(host, { ...schema, - alwaysGenerateProjectJson: true, + useProjectJson: true, })), // when js is set to true, we want to use the js configuration js: schema.js ?? false, @@ -137,7 +137,7 @@ export async function remoteGenerator(host: Tree, schema: Schema) { ...options, name: options.projectName, skipFormat: true, - alwaysGenerateProjectJson: true, + useProjectJson: true, }); tasks.push(initAppTask); diff --git a/packages/remix/src/generators/application/application.impl.spec.ts b/packages/remix/src/generators/application/application.impl.spec.ts index 2c454f35a5..5f98da8155 100644 --- a/packages/remix/src/generators/application/application.impl.spec.ts +++ b/packages/remix/src/generators/application/application.impl.spec.ts @@ -4,6 +4,7 @@ import { joinPathFragments, readJson, readNxJson, + readProjectConfiguration, type Tree, updateJson, writeJson, @@ -460,6 +461,7 @@ describe('Remix Application', () => { unitTestRunner: 'jest', addPlugin: true, tags: 'foo', + useProjectJson: false, }); const packageJson = readJson(tree, 'myapp/package.json'); @@ -663,6 +665,7 @@ describe('Remix Application', () => { unitTestRunner: 'jest', addPlugin: true, tags: 'foo', + useProjectJson: false, }); const packageJson = readJson(tree, 'myapp/package.json'); @@ -690,6 +693,7 @@ describe('Remix Application', () => { e2eTestRunner: 'playwright', unitTestRunner: 'jest', addPlugin: true, + useProjectJson: false, }); expect(readJson(tree, 'apps/myapp/package.json')).toMatchInlineSnapshot(` @@ -725,6 +729,7 @@ describe('Remix Application', () => { e2eTestRunner: 'playwright', unitTestRunner: 'jest', addPlugin: true, + useProjectJson: false, skipFormat: true, }); @@ -738,6 +743,7 @@ describe('Remix Application', () => { directory: 'myapp', unitTestRunner: 'jest', addPlugin: true, + useProjectJson: false, skipFormat: true, }); @@ -797,6 +803,48 @@ describe('Remix Application', () => { " `); }); + + it('should generate project.json if useProjectJson is true', async () => { + await applicationGenerator(tree, { + directory: 'myapp', + e2eTestRunner: 'playwright', + addPlugin: true, + useProjectJson: true, + skipFormat: true, + }); + + expect(tree.exists('myapp/project.json')).toBeTruthy(); + expect(readProjectConfiguration(tree, '@proj/myapp')) + .toMatchInlineSnapshot(` + { + "$schema": "../node_modules/nx/schemas/project-schema.json", + "name": "@proj/myapp", + "projectType": "application", + "root": "myapp", + "sourceRoot": "myapp", + "tags": [], + "targets": {}, + } + `); + expect(readJson(tree, 'myapp/package.json').nx).toBeUndefined(); + expect(tree.exists('myapp-e2e/project.json')).toBeTruthy(); + expect(readProjectConfiguration(tree, '@proj/myapp-e2e')) + .toMatchInlineSnapshot(` + { + "$schema": "../node_modules/nx/schemas/project-schema.json", + "implicitDependencies": [ + "@proj/myapp", + ], + "name": "@proj/myapp-e2e", + "projectType": "application", + "root": "myapp-e2e", + "sourceRoot": "myapp-e2e/src", + "tags": [], + "targets": {}, + } + `); + expect(readJson(tree, 'myapp-e2e/package.json').nx).toBeUndefined(); + }); }); }); diff --git a/packages/remix/src/generators/application/application.impl.ts b/packages/remix/src/generators/application/application.impl.ts index 25804153da..d1dd2ad680 100644 --- a/packages/remix/src/generators/application/application.impl.ts +++ b/packages/remix/src/generators/application/application.impl.ts @@ -54,6 +54,7 @@ export function remixApplicationGenerator( ) { return remixApplicationGeneratorInternal(tree, { addPlugin: true, + useProjectJson: true, ...options, }); } @@ -88,7 +89,7 @@ export async function remixApplicationGeneratorInternal( await addProjectToTsSolutionWorkspace(tree, options.projectRoot); } - if (!options.isUsingTsSolutionConfig) { + if (options.useProjectJson) { addProjectConfiguration(tree, options.projectName, { root: options.projectRoot, sourceRoot: `${options.projectRoot}`, @@ -161,7 +162,9 @@ export async function remixApplicationGeneratorInternal( options.projectRoot, vars ); + } + if (!options.useProjectJson) { updateJson( tree, joinPathFragments(options.projectRoot, 'package.json'), diff --git a/packages/remix/src/generators/application/lib/add-e2e.ts b/packages/remix/src/generators/application/lib/add-e2e.ts index 83d3a6fad2..791a93985b 100644 --- a/packages/remix/src/generators/application/lib/add-e2e.ts +++ b/packages/remix/src/generators/application/lib/add-e2e.ts @@ -12,6 +12,7 @@ import { findPluginForConfigFile } from '@nx/devkit/src/utils/find-plugin-for-co import { addE2eCiTargetDefaults } from '@nx/devkit/src/generators/target-defaults-utils'; import { getE2EWebServerInfo } from '@nx/devkit/src/generators/e2e-web-server-info-utils'; import { isUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup'; +import type { PackageJson } from 'nx/src/utils/package-json'; export async function addE2E(tree: Tree, options: NormalizedSchema) { const hasRemixPlugin = readNxJson(tree).plugins?.find((p) => @@ -32,19 +33,16 @@ export async function addE2E(tree: Tree, options: NormalizedSchema) { typeof import('@nx/cypress') >('@nx/cypress', getPackageVersion(tree, 'nx')); - if (isUsingTsSolutionSetup(tree)) { - writeJson( - tree, - joinPathFragments(options.e2eProjectRoot, 'package.json'), - { - name: options.e2eProjectName, - version: '0.0.1', - private: true, - nx: { - implicitDependencies: [options.projectName], - }, - } - ); + const packageJson: PackageJson = { + name: options.e2eProjectName, + version: '0.0.1', + private: true, + }; + + if (!options.useProjectJson) { + packageJson.nx = { + implicitDependencies: [options.projectName], + }; } else { addProjectConfiguration(tree, options.e2eProjectName, { projectType: 'application', @@ -56,6 +54,14 @@ export async function addE2E(tree: Tree, options: NormalizedSchema) { }); } + if (!options.useProjectJson || options.isUsingTsSolutionConfig) { + writeJson( + tree, + joinPathFragments(options.e2eProjectRoot, 'package.json'), + packageJson + ); + } + const e2eTask = await configurationGenerator(tree, { project: options.e2eProjectName, directory: 'src', @@ -110,19 +116,16 @@ export async function addE2E(tree: Tree, options: NormalizedSchema) { typeof import('@nx/playwright') >('@nx/playwright', getPackageVersion(tree, 'nx')); - if (isUsingTsSolutionSetup(tree)) { - writeJson( - tree, - joinPathFragments(options.e2eProjectRoot, 'package.json'), - { - name: options.e2eProjectName, - version: '0.0.1', - private: true, - nx: { - implicitDependencies: [options.projectName], - }, - } - ); + const packageJson: PackageJson = { + name: options.e2eProjectName, + version: '0.0.1', + private: true, + }; + + if (!options.useProjectJson) { + packageJson.nx = { + implicitDependencies: [options.projectName], + }; } else { addProjectConfiguration(tree, options.e2eProjectName, { projectType: 'application', @@ -134,6 +137,14 @@ export async function addE2E(tree: Tree, options: NormalizedSchema) { }); } + if (!options.useProjectJson || options.isUsingTsSolutionConfig) { + writeJson( + tree, + joinPathFragments(options.e2eProjectRoot, 'package.json'), + packageJson + ); + } + const e2eTask = await configurationGenerator(tree, { project: options.e2eProjectName, skipFormat: true, diff --git a/packages/remix/src/generators/application/lib/normalize-options.ts b/packages/remix/src/generators/application/lib/normalize-options.ts index 83ee4a22b8..15afaac332 100644 --- a/packages/remix/src/generators/application/lib/normalize-options.ts +++ b/packages/remix/src/generators/application/lib/normalize-options.ts @@ -58,5 +58,6 @@ export async function normalizeOptions( parsedTags, useTsSolution: isUsingTsSolutionConfig, isUsingTsSolutionConfig, + useProjectJson: options.useProjectJson ?? !isUsingTsSolutionConfig, }; } diff --git a/packages/remix/src/generators/application/schema.d.ts b/packages/remix/src/generators/application/schema.d.ts index 580a3fc855..c9ab8bf760 100644 --- a/packages/remix/src/generators/application/schema.d.ts +++ b/packages/remix/src/generators/application/schema.d.ts @@ -14,4 +14,5 @@ export interface NxRemixGeneratorSchema { nxCloudToken?: string; useTsSolution?: boolean; formatter?: 'prettier' | 'none'; + useProjectJson?: boolean; } diff --git a/packages/remix/src/generators/application/schema.json b/packages/remix/src/generators/application/schema.json index 5b4ee76eee..10971fe465 100644 --- a/packages/remix/src/generators/application/schema.json +++ b/packages/remix/src/generators/application/schema.json @@ -57,6 +57,10 @@ "type": "boolean", "x-priority": "internal", "default": false + }, + "useProjectJson": { + "type": "boolean", + "description": "Use a `project.json` configuration file instead of inlining the Nx configuration in the `package.json` file." } }, "required": ["directory"] diff --git a/packages/remix/src/generators/library/lib/normalize-options.ts b/packages/remix/src/generators/library/lib/normalize-options.ts index ecd3db542f..f71a7daf8e 100644 --- a/packages/remix/src/generators/library/lib/normalize-options.ts +++ b/packages/remix/src/generators/library/lib/normalize-options.ts @@ -39,5 +39,6 @@ export async function normalizeOptions( projectRoot, importPath, isUsingTsSolutionConfig, + useProjectJson: options.useProjectJson ?? !isUsingTsSolutionConfig, }; } diff --git a/packages/remix/src/generators/library/library.impl.spec.ts b/packages/remix/src/generators/library/library.impl.spec.ts index 6f8c50cb27..1eddde00c1 100644 --- a/packages/remix/src/generators/library/library.impl.spec.ts +++ b/packages/remix/src/generators/library/library.impl.spec.ts @@ -163,6 +163,7 @@ describe('Remix Library Generator', () => { directory: 'packages/foo', style: 'css', addPlugin: true, + useProjectJson: false, }); // Make sure keys are in idiomatic order @@ -200,6 +201,7 @@ describe('Remix Library Generator', () => { directory: 'test', style: 'css', addPlugin: true, + useProjectJson: false, }); expect(tree.exists(`test/src/server.ts`)).toBeTruthy(); @@ -218,6 +220,7 @@ describe('Remix Library Generator', () => { name: 'my-lib', // import path contains the npm scope, so it would be different style: 'css', addPlugin: true, + useProjectJson: false, skipFormat: true, }); @@ -232,6 +235,7 @@ describe('Remix Library Generator', () => { name: '@proj/my-lib', style: 'css', addPlugin: true, + useProjectJson: false, skipFormat: true, }); @@ -243,10 +247,36 @@ describe('Remix Library Generator', () => { directory: 'packages/my-lib', style: 'css', addPlugin: true, + useProjectJson: false, skipFormat: true, }); expect(readJson(tree, 'packages/my-lib/package.json').nx).toBeUndefined(); }); + + it('should generate project.json if useProjectJson is true', async () => { + await libraryGenerator(tree, { + directory: 'packages/my-lib', + style: 'css', + addPlugin: true, + useProjectJson: true, + skipFormat: true, + }); + + expect(tree.exists('packages/my-lib/project.json')).toBeTruthy(); + expect(readProjectConfiguration(tree, '@proj/my-lib')) + .toMatchInlineSnapshot(` + { + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "name": "@proj/my-lib", + "projectType": "library", + "root": "packages/my-lib", + "sourceRoot": "packages/my-lib/src", + "tags": [], + "targets": {}, + } + `); + expect(readJson(tree, 'packages/my-lib/package.json').nx).toBeUndefined(); + }); }); }); diff --git a/packages/remix/src/generators/library/library.impl.ts b/packages/remix/src/generators/library/library.impl.ts index 41812c6dd0..24254d4c6c 100644 --- a/packages/remix/src/generators/library/library.impl.ts +++ b/packages/remix/src/generators/library/library.impl.ts @@ -19,7 +19,11 @@ export async function remixLibraryGenerator( tree: Tree, schema: NxRemixGeneratorSchema ) { - return remixLibraryGeneratorInternal(tree, { addPlugin: false, ...schema }); + return remixLibraryGeneratorInternal(tree, { + addPlugin: false, + useProjectJson: true, + ...schema, + }); } export async function remixLibraryGeneratorInternal( @@ -53,6 +57,7 @@ export async function remixLibraryGeneratorInternal( buildable: options.buildable, bundler: options.bundler, addPlugin: options.addPlugin, + useProjectJson: options.useProjectJson, }); tasks.push(libGenTask); diff --git a/packages/remix/src/generators/library/schema.d.ts b/packages/remix/src/generators/library/schema.d.ts index a0c546e8fd..000688ba12 100644 --- a/packages/remix/src/generators/library/schema.d.ts +++ b/packages/remix/src/generators/library/schema.d.ts @@ -14,4 +14,5 @@ export interface NxRemixGeneratorSchema { js?: boolean; skipFormat?: boolean; addPlugin?: boolean; + useProjectJson?: boolean; } diff --git a/packages/remix/src/generators/library/schema.json b/packages/remix/src/generators/library/schema.json index d5c71fcad4..a6ac61051b 100644 --- a/packages/remix/src/generators/library/schema.json +++ b/packages/remix/src/generators/library/schema.json @@ -81,6 +81,10 @@ "default": false, "description": "Generate a buildable library that uses rollup to bundle.", "x-deprecated": "Use the `bundler` option for greater control (none, vite, rollup)." + }, + "useProjectJson": { + "type": "boolean", + "description": "Use a `project.json` configuration file instead of inlining the Nx configuration in the `package.json` file." } }, "required": ["directory"] diff --git a/packages/vue/src/generators/application/application.spec.ts b/packages/vue/src/generators/application/application.spec.ts index cd97e2fa21..82e92084fb 100644 --- a/packages/vue/src/generators/application/application.spec.ts +++ b/packages/vue/src/generators/application/application.spec.ts @@ -189,6 +189,7 @@ describe('application generator', () => { style: 'none', linter: 'eslint', addPlugin: true, + useProjectJson: false, }); expect(tree.read('test/vite.config.ts', 'utf-8')).toMatchInlineSnapshot(` @@ -363,6 +364,7 @@ describe('application generator', () => { ...options, name: 'myapp', addPlugin: true, + useProjectJson: false, skipFormat: true, }); @@ -379,6 +381,50 @@ describe('application generator', () => { ] `); }); + + it('should generate project.json if useProjectJson is true', async () => { + await applicationGenerator(tree, { + ...options, + directory: 'myapp', + e2eTestRunner: 'cypress', + style: 'none', + linter: 'eslint', + addPlugin: true, + useProjectJson: true, + skipFormat: true, + }); + + expect(tree.exists('myapp/project.json')).toBeTruthy(); + expect(readProjectConfiguration(tree, '@proj/myapp')) + .toMatchInlineSnapshot(` + { + "$schema": "../node_modules/nx/schemas/project-schema.json", + "name": "@proj/myapp", + "projectType": "application", + "root": "myapp", + "sourceRoot": "myapp/src", + "targets": {}, + } + `); + expect(readJson(tree, 'myapp/package.json').nx).toBeUndefined(); + expect(tree.exists('myapp-e2e/project.json')).toBeTruthy(); + expect(readProjectConfiguration(tree, '@proj/myapp-e2e')) + .toMatchInlineSnapshot(` + { + "$schema": "../node_modules/nx/schemas/project-schema.json", + "implicitDependencies": [ + "@proj/myapp", + ], + "name": "@proj/myapp-e2e", + "projectType": "application", + "root": "myapp-e2e", + "sourceRoot": "myapp-e2e/src", + "tags": [], + "targets": {}, + } + `); + expect(readJson(tree, 'myapp-e2e/package.json').nx).toBeUndefined(); + }); }); }); diff --git a/packages/vue/src/generators/application/application.ts b/packages/vue/src/generators/application/application.ts index 11d36bffdd..af135f440d 100644 --- a/packages/vue/src/generators/application/application.ts +++ b/packages/vue/src/generators/application/application.ts @@ -30,7 +30,11 @@ import { sortPackageJsonFields } from '@nx/js/src/utils/package-json/sort-fields import type { PackageJson } from 'nx/src/utils/package-json'; export function applicationGenerator(tree: Tree, options: Schema) { - return applicationGeneratorInternal(tree, { addPlugin: false, ...options }); + return applicationGeneratorInternal(tree, { + addPlugin: false, + useProjectJson: true, + ...options, + }); } export async function applicationGeneratorInternal( @@ -65,13 +69,13 @@ export async function applicationGeneratorInternal( process.env.NX_ADD_PLUGINS !== 'false' && nxJson.useInferencePlugins !== false; - if (options.isUsingTsSolutionConfig) { - const packageJson: PackageJson = { - name: options.importPath, - version: '0.0.1', - private: true, - }; + const packageJson: PackageJson = { + name: options.importPath, + version: '0.0.1', + private: true, + }; + if (!options.useProjectJson) { if (options.projectName !== options.importPath) { packageJson.nx = { name: options.projectName }; } @@ -79,12 +83,6 @@ export async function applicationGeneratorInternal( packageJson.nx ??= {}; packageJson.nx.tags = options.parsedTags; } - - writeJson( - tree, - joinPathFragments(options.appProjectRoot, 'package.json'), - packageJson - ); } else { addProjectConfiguration(tree, options.projectName, { root: options.appProjectRoot, @@ -95,6 +93,14 @@ export async function applicationGeneratorInternal( }); } + if (!options.useProjectJson || options.isUsingTsSolutionConfig) { + writeJson( + tree, + joinPathFragments(options.appProjectRoot, 'package.json'), + packageJson + ); + } + tasks.push( await vueInitGenerator(tree, { ...options, diff --git a/packages/vue/src/generators/application/lib/add-e2e.ts b/packages/vue/src/generators/application/lib/add-e2e.ts index 32ff6bfe2f..ea7994c26e 100644 --- a/packages/vue/src/generators/application/lib/add-e2e.ts +++ b/packages/vue/src/generators/application/lib/add-e2e.ts @@ -14,6 +14,7 @@ import { NormalizedSchema } from '../schema'; import { findPluginForConfigFile } from '@nx/devkit/src/utils/find-plugin-for-config-file'; import { addE2eCiTargetDefaults } from '@nx/devkit/src/generators/target-defaults-utils'; import { E2EWebServerDetails } from '@nx/devkit/src/generators/e2e-web-server-info-utils'; +import type { PackageJson } from 'nx/src/utils/package-json'; export async function addE2e( tree: Tree, @@ -73,19 +74,17 @@ export async function addE2e( const { configurationGenerator } = ensurePackage< typeof import('@nx/cypress') >('@nx/cypress', nxVersion); - if (options.isUsingTsSolutionConfig) { - writeJson( - tree, - joinPathFragments(options.e2eProjectRoot, 'package.json'), - { - name: options.e2eProjectName, - version: '0.0.1', - private: true, - nx: { - implicitDependencies: [options.projectName], - }, - } - ); + + const packageJson: PackageJson = { + name: options.e2eProjectName, + version: '0.0.1', + private: true, + }; + + if (!options.useProjectJson) { + packageJson.nx = { + implicitDependencies: [options.projectName], + }; } else { addProjectConfiguration(tree, options.e2eProjectName, { projectType: 'application', @@ -96,6 +95,15 @@ export async function addE2e( implicitDependencies: [options.projectName], }); } + + if (!options.useProjectJson || options.isUsingTsSolutionConfig) { + writeJson( + tree, + joinPathFragments(options.e2eProjectRoot, 'package.json'), + packageJson + ); + } + const e2eTask = await configurationGenerator(tree, { ...options, project: options.e2eProjectName, @@ -156,19 +164,17 @@ export async function addE2e( const { configurationGenerator } = ensurePackage< typeof import('@nx/playwright') >('@nx/playwright', nxVersion); - if (options.isUsingTsSolutionConfig) { - writeJson( - tree, - joinPathFragments(options.e2eProjectRoot, 'package.json'), - { - name: options.e2eProjectName, - version: '0.0.1', - private: true, - nx: { - implicitDependencies: [options.projectName], - }, - } - ); + + const packageJson: PackageJson = { + name: options.e2eProjectName, + version: '0.0.1', + private: true, + }; + + if (!options.useProjectJson) { + packageJson.nx = { + implicitDependencies: [options.projectName], + }; } else { addProjectConfiguration(tree, options.e2eProjectName, { projectType: 'application', @@ -178,6 +184,15 @@ export async function addE2e( implicitDependencies: [options.projectName], }); } + + if (!options.useProjectJson || options.isUsingTsSolutionConfig) { + writeJson( + tree, + joinPathFragments(options.e2eProjectRoot, 'package.json'), + packageJson + ); + } + const e2eTask = await configurationGenerator(tree, { ...options, project: options.e2eProjectName, diff --git a/packages/vue/src/generators/application/lib/normalize-options.ts b/packages/vue/src/generators/application/lib/normalize-options.ts index bb6092803a..da83bbfab4 100644 --- a/packages/vue/src/generators/application/lib/normalize-options.ts +++ b/packages/vue/src/generators/application/lib/normalize-options.ts @@ -43,6 +43,7 @@ export async function normalizeOptions( e2eProjectRoot, parsedTags, isUsingTsSolutionConfig, + useProjectJson: options.useProjectJson ?? !isUsingTsSolutionConfig, } as NormalizedSchema; normalized.style = options.style ?? 'css'; diff --git a/packages/vue/src/generators/application/schema.d.ts b/packages/vue/src/generators/application/schema.d.ts index 20dd74143f..61d92403be 100644 --- a/packages/vue/src/generators/application/schema.d.ts +++ b/packages/vue/src/generators/application/schema.d.ts @@ -21,6 +21,7 @@ export interface Schema { addPlugin?: boolean; nxCloudToken?: string; useTsSolution?: boolean; + useProjectJson?: boolean; } export interface NormalizedSchema extends Omit { diff --git a/packages/vue/src/generators/application/schema.json b/packages/vue/src/generators/application/schema.json index adb6b34147..e8fdb44f84 100644 --- a/packages/vue/src/generators/application/schema.json +++ b/packages/vue/src/generators/application/schema.json @@ -139,6 +139,10 @@ "type": "boolean", "default": false, "hidden": true + }, + "useProjectJson": { + "type": "boolean", + "description": "Use a `project.json` configuration file instead of inlining the Nx configuration in the `package.json` file." } }, "required": ["directory"], diff --git a/packages/vue/src/generators/library/lib/create-library-files.ts b/packages/vue/src/generators/library/lib/create-library-files.ts index 8f1aa406dd..97574d1b73 100644 --- a/packages/vue/src/generators/library/lib/create-library-files.ts +++ b/packages/vue/src/generators/library/lib/create-library-files.ts @@ -33,6 +33,7 @@ export function createLibraryFiles(host: Tree, options: NormalizedSchema) { if ( !options.isUsingTsSolutionConfig && + options.useProjectJson && (options.publishable || options.bundler !== 'none') ) { writeJson(host, joinPathFragments(options.projectRoot, 'package.json'), { diff --git a/packages/vue/src/generators/library/lib/normalize-options.ts b/packages/vue/src/generators/library/lib/normalize-options.ts index 75bccf0534..243c0f34d3 100644 --- a/packages/vue/src/generators/library/lib/normalize-options.ts +++ b/packages/vue/src/generators/library/lib/normalize-options.ts @@ -70,6 +70,7 @@ export async function normalizeOptions( parsedTags, importPath, isUsingTsSolutionConfig, + useProjectJson: options.useProjectJson ?? !isUsingTsSolutionConfig, } as NormalizedSchema; // Libraries with a bundler or is publishable must also be buildable. diff --git a/packages/vue/src/generators/library/library.spec.ts b/packages/vue/src/generators/library/library.spec.ts index 9d15dbd394..7b5b8b53d4 100644 --- a/packages/vue/src/generators/library/library.spec.ts +++ b/packages/vue/src/generators/library/library.spec.ts @@ -535,6 +535,7 @@ module.exports = [ ...defaultSchema, setParserOptionsProject: true, linter: 'eslint', + useProjectJson: false, }); expect(tree.read('my-lib/vite.config.ts', 'utf-8')) @@ -695,6 +696,7 @@ module.exports = [ directory: 'my-lib', name: 'my-lib', // import path contains the npm scope, so it would be different addPlugin: true, + useProjectJson: false, skipFormat: true, }); @@ -709,6 +711,7 @@ module.exports = [ directory: 'my-lib', name: '@proj/my-lib', addPlugin: true, + useProjectJson: false, skipFormat: true, }); @@ -720,10 +723,36 @@ module.exports = [ ...defaultSchema, // defaultSchema has no name directory: 'my-lib', addPlugin: true, + useProjectJson: false, skipFormat: true, }); expect(readJson(tree, 'my-lib/package.json').nx).toBeUndefined(); }); + + it('should generate project.json if useProjectJson is true', async () => { + await libraryGenerator(tree, { + ...defaultSchema, + linter: 'eslint', + addPlugin: true, + useProjectJson: true, + skipFormat: true, + }); + + expect(tree.exists('my-lib/project.json')).toBeTruthy(); + expect(readProjectConfiguration(tree, '@proj/my-lib')) + .toMatchInlineSnapshot(` + { + "$schema": "../node_modules/nx/schemas/project-schema.json", + "name": "@proj/my-lib", + "projectType": "library", + "root": "my-lib", + "sourceRoot": "my-lib/src", + "tags": [], + "targets": {}, + } + `); + expect(readJson(tree, 'my-lib/package.json').nx).toBeUndefined(); + }); }); }); diff --git a/packages/vue/src/generators/library/library.ts b/packages/vue/src/generators/library/library.ts index 3121893aa3..e2d100a6dd 100644 --- a/packages/vue/src/generators/library/library.ts +++ b/packages/vue/src/generators/library/library.ts @@ -38,7 +38,11 @@ import { import type { PackageJson } from 'nx/src/utils/package-json'; export function libraryGenerator(tree: Tree, schema: Schema) { - return libraryGeneratorInternal(tree, { addPlugin: false, ...schema }); + return libraryGeneratorInternal(tree, { + addPlugin: false, + useProjectJson: true, + ...schema, + }); } export async function libraryGeneratorInternal(tree: Tree, schema: Schema) { @@ -59,14 +63,17 @@ export async function libraryGeneratorInternal(tree: Tree, schema: Schema) { await addProjectToTsSolutionWorkspace(tree, options.projectRoot); } - if (options.isUsingTsSolutionConfig) { - const packageJson: PackageJson = { - name: options.importPath, - version: '0.0.1', + let packageJson: PackageJson = { + name: options.importPath, + version: '0.0.1', + }; + + if (!options.useProjectJson) { + packageJson = { + ...packageJson, ...determineEntryFields(options), files: options.publishable ? ['dist', '!**/*.tsbuildinfo'] : undefined, }; - if (options.projectName !== options.importPath) { packageJson.nx = { name: options.projectName }; } @@ -74,12 +81,6 @@ export async function libraryGeneratorInternal(tree: Tree, schema: Schema) { packageJson.nx ??= {}; packageJson.nx.tags = options.parsedTags; } - - writeJson( - tree, - joinPathFragments(options.projectRoot, 'package.json'), - packageJson - ); } else { addProjectConfiguration(tree, options.projectName, { root: options.projectRoot, @@ -90,6 +91,14 @@ export async function libraryGeneratorInternal(tree: Tree, schema: Schema) { }); } + if (!options.useProjectJson || options.isUsingTsSolutionConfig) { + writeJson( + tree, + joinPathFragments(options.projectRoot, 'package.json'), + packageJson + ); + } + tasks.push( await vueInitGenerator(tree, { ...options, diff --git a/packages/vue/src/generators/library/schema.d.ts b/packages/vue/src/generators/library/schema.d.ts index 7f9ac55279..30f6609f82 100644 --- a/packages/vue/src/generators/library/schema.d.ts +++ b/packages/vue/src/generators/library/schema.d.ts @@ -22,6 +22,7 @@ export interface Schema { minimal?: boolean; e2eTestRunner?: 'cypress' | 'none'; addPlugin?: boolean; + useProjectJson?: boolean; } export interface NormalizedSchema extends Schema { diff --git a/packages/vue/src/generators/library/schema.json b/packages/vue/src/generators/library/schema.json index dda7e5bbb5..36c3aacec2 100644 --- a/packages/vue/src/generators/library/schema.json +++ b/packages/vue/src/generators/library/schema.json @@ -125,6 +125,10 @@ "description": "Create a Vue library with a minimal setup, no separate test files.", "type": "boolean", "default": false + }, + "useProjectJson": { + "type": "boolean", + "description": "Use a `project.json` configuration file instead of inlining the Nx configuration in the `package.json` file." } }, "required": ["directory"] diff --git a/packages/web/src/generators/application/application.spec.ts b/packages/web/src/generators/application/application.spec.ts index af35c3865a..6f858afa9e 100644 --- a/packages/web/src/generators/application/application.spec.ts +++ b/packages/web/src/generators/application/application.spec.ts @@ -768,6 +768,7 @@ describe('app', () => { bundler: 'vite', unitTestRunner: 'vitest', e2eTestRunner: 'playwright', + useProjectJson: false, }); expect(readJson(tree, 'tsconfig.json').references).toMatchInlineSnapshot(` @@ -911,6 +912,7 @@ describe('app', () => { directory: 'apps/my-app', bundler: 'webpack', addPlugin: true, + useProjectJson: false, skipFormat: true, }); @@ -956,6 +958,7 @@ describe('app', () => { bundler: 'vite', unitTestRunner: 'vitest', e2eTestRunner: 'playwright', + useProjectJson: false, }); const packageJson = readJson(tree, 'apps/myapp/package.json'); @@ -971,5 +974,46 @@ describe('app', () => { ] `); }); + + it('should generate project.json if useProjectJson is true', async () => { + await applicationGenerator(tree, { + directory: 'apps/myapp', + addPlugin: true, + useProjectJson: true, + skipFormat: true, + }); + + expect(tree.exists('apps/myapp/project.json')).toBeTruthy(); + expect(readProjectConfiguration(tree, '@proj/myapp')) + .toMatchInlineSnapshot(` + { + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "name": "@proj/myapp", + "projectType": "application", + "root": "apps/myapp", + "sourceRoot": "apps/myapp/src", + "tags": [], + "targets": {}, + } + `); + expect(readJson(tree, 'apps/myapp/package.json').nx).toBeUndefined(); + expect(tree.exists('apps/myapp-e2e/project.json')).toBeTruthy(); + expect(readProjectConfiguration(tree, '@proj/myapp-e2e')) + .toMatchInlineSnapshot(` + { + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "implicitDependencies": [ + "@proj/myapp", + ], + "name": "@proj/myapp-e2e", + "projectType": "application", + "root": "apps/myapp-e2e", + "sourceRoot": "apps/myapp-e2e/src", + "tags": [], + "targets": {}, + } + `); + expect(readJson(tree, 'apps/myapp-e2e/package.json').nx).toBeUndefined(); + }); }); }); diff --git a/packages/web/src/generators/application/application.ts b/packages/web/src/generators/application/application.ts index c472011dfc..a9f302fb45 100644 --- a/packages/web/src/generators/application/application.ts +++ b/packages/web/src/generators/application/application.ts @@ -246,13 +246,13 @@ async function setupBundler(tree: Tree, options: NormalizedSchema) { } async function addProject(tree: Tree, options: NormalizedSchema) { - if (options.isUsingTsSolutionConfig) { - const packageJson: PackageJson = { - name: options.importPath, - version: '0.0.1', - private: true, - }; + const packageJson: PackageJson = { + name: options.importPath, + version: '0.0.1', + private: true, + }; + if (!options.useProjectJson) { if (options.projectName !== options.importPath) { packageJson.nx = { name: options.projectName }; } @@ -260,12 +260,6 @@ async function addProject(tree: Tree, options: NormalizedSchema) { packageJson.nx ??= {}; packageJson.nx.tags = options.parsedTags; } - - writeJson( - tree, - joinPathFragments(options.appProjectRoot, 'package.json'), - packageJson - ); } else { addProjectConfiguration(tree, options.projectName, { projectType: 'application', @@ -275,6 +269,14 @@ async function addProject(tree: Tree, options: NormalizedSchema) { targets: {}, }); } + + if (!options.useProjectJson || options.isUsingTsSolutionConfig) { + writeJson( + tree, + joinPathFragments(options.appProjectRoot, 'package.json'), + packageJson + ); + } } function setDefaults(tree: Tree, options: NormalizedSchema) { @@ -475,19 +477,17 @@ export async function applicationGeneratorInternal(host: Tree, schema: Schema) { const { configurationGenerator } = ensurePackage< typeof import('@nx/cypress') >('@nx/cypress', nxVersion); - if (options.isUsingTsSolutionConfig) { - writeJson( - host, - joinPathFragments(options.e2eProjectRoot, 'package.json'), - { - name: options.e2eProjectName, - version: '0.0.1', - private: true, - nx: { - implicitDependencies: [options.projectName], - }, - } - ); + + const packageJson: PackageJson = { + name: options.e2eProjectName, + version: '0.0.1', + private: true, + }; + + if (!options.useProjectJson) { + packageJson.nx = { + implicitDependencies: [options.projectName], + }; } else { addProjectConfiguration(host, options.e2eProjectName, { root: options.e2eProjectRoot, @@ -498,6 +498,15 @@ export async function applicationGeneratorInternal(host: Tree, schema: Schema) { implicitDependencies: [options.projectName], }); } + + if (!options.useProjectJson || options.isUsingTsSolutionConfig) { + writeJson( + host, + joinPathFragments(options.e2eProjectRoot, 'package.json'), + packageJson + ); + } + const cypressTask = await configurationGenerator(host, { ...options, project: options.e2eProjectName, @@ -551,19 +560,17 @@ export async function applicationGeneratorInternal(host: Tree, schema: Schema) { const { configurationGenerator: playwrightConfigGenerator } = ensurePackage< typeof import('@nx/playwright') >('@nx/playwright', nxVersion); - if (options.isUsingTsSolutionConfig) { - writeJson( - host, - joinPathFragments(options.e2eProjectRoot, 'package.json'), - { - name: options.e2eProjectName, - version: '0.0.1', - private: true, - nx: { - implicitDependencies: [options.projectName], - }, - } - ); + + const packageJson: PackageJson = { + name: options.e2eProjectName, + version: '0.0.1', + private: true, + }; + + if (!options.useProjectJson) { + packageJson.nx = { + implicitDependencies: [options.projectName], + }; } else { addProjectConfiguration(host, options.e2eProjectName, { root: options.e2eProjectRoot, @@ -574,6 +581,15 @@ export async function applicationGeneratorInternal(host: Tree, schema: Schema) { implicitDependencies: [options.projectName], }); } + + if (!options.useProjectJson || options.isUsingTsSolutionConfig) { + writeJson( + host, + joinPathFragments(options.e2eProjectRoot, 'package.json'), + packageJson + ); + } + const playwrightTask = await playwrightConfigGenerator(host, { project: options.e2eProjectName, skipFormat: true, @@ -741,6 +757,7 @@ async function normalizeOptions( parsedTags, names: names(projectName), isUsingTsSolutionConfig, + useProjectJson: options.useProjectJson ?? !isUsingTsSolutionConfig, }; } diff --git a/packages/web/src/generators/application/schema.d.ts b/packages/web/src/generators/application/schema.d.ts index 2cedac1120..6cdc113d93 100644 --- a/packages/web/src/generators/application/schema.d.ts +++ b/packages/web/src/generators/application/schema.d.ts @@ -16,4 +16,5 @@ export interface Schema { setParserOptionsProject?: boolean; strict?: boolean; addPlugin?: boolean; + useProjectJson?: boolean; } diff --git a/packages/web/src/generators/application/schema.json b/packages/web/src/generators/application/schema.json index 327edc0633..61d28c6cc7 100644 --- a/packages/web/src/generators/application/schema.json +++ b/packages/web/src/generators/application/schema.json @@ -104,6 +104,10 @@ "type": "boolean", "description": "Creates an application with strict mode and strict type checking.", "default": true + }, + "useProjectJson": { + "type": "boolean", + "description": "Use a `project.json` configuration file instead of inlining the Nx configuration in the `package.json` file." } }, "required": ["directory"], diff --git a/packages/workspace/src/generators/preset/preset.ts b/packages/workspace/src/generators/preset/preset.ts index 53c34f5804..fb434d1369 100644 --- a/packages/workspace/src/generators/preset/preset.ts +++ b/packages/workspace/src/generators/preset/preset.ts @@ -80,6 +80,7 @@ async function createPreset(tree: Tree, options: Schema) { nxCloudToken: options.nxCloudToken, useTsSolution: options.workspaces, formatter: options.formatter, + useProjectJson: !options.workspaces, }); } else if (options.preset === Preset.ReactStandalone) { const { applicationGenerator: reactApplicationGenerator } = require('@nx' + @@ -115,6 +116,7 @@ async function createPreset(tree: Tree, options: Schema) { nxCloudToken: options.nxCloudToken, useTsSolution: options.workspaces, formatter: options.formatter, + useProjectJson: !options.workspaces, }); } else if (options.preset === Preset.RemixStandalone) { const { applicationGenerator: remixApplicationGenerator } = require('@nx' + @@ -146,6 +148,7 @@ async function createPreset(tree: Tree, options: Schema) { nxCloudToken: options.nxCloudToken, useTsSolution: options.workspaces, formatter: options.formatter, + useProjectJson: !options.workspaces, }); } else if (options.preset === Preset.VueStandalone) { const { applicationGenerator: vueApplicationGenerator } = require('@nx' + @@ -177,6 +180,7 @@ async function createPreset(tree: Tree, options: Schema) { nxCloudToken: options.nxCloudToken, useTsSolution: options.workspaces, formatter: options.formatter, + useProjectJson: !options.workspaces, }); } else if (options.preset === Preset.NuxtStandalone) { const { applicationGenerator: nuxtApplicationGenerator } = require('@nx' + @@ -209,6 +213,7 @@ async function createPreset(tree: Tree, options: Schema) { addPlugin, useTsSolution: options.workspaces, formatter: options.formatter, + useProjectJson: !options.workspaces, }); } else if (options.preset === Preset.NextJsStandalone) { const { applicationGenerator: nextApplicationGenerator } = require('@nx' + @@ -253,6 +258,7 @@ async function createPreset(tree: Tree, options: Schema) { addPlugin, useTsSolution: options.workspaces, formatter: options.formatter, + useProjectJson: !options.workspaces, }); } else if (options.preset === Preset.Express) { const { @@ -267,6 +273,7 @@ async function createPreset(tree: Tree, options: Schema) { addPlugin, useTsSolution: options.workspaces, formatter: options.formatter, + useProjectJson: !options.workspaces, }); } else if (options.preset === Preset.ReactNative) { const { reactNativeApplicationGenerator } = require('@nx' + @@ -282,6 +289,7 @@ async function createPreset(tree: Tree, options: Schema) { bundler: options.bundler ?? 'webpack', useTsSolution: options.workspaces, formatter: options.formatter, + useProjectJson: !options.workspaces, }); } else if (options.preset === Preset.Expo) { const { expoApplicationGenerator } = require('@nx' + '/expo'); @@ -295,6 +303,7 @@ async function createPreset(tree: Tree, options: Schema) { nxCloudToken: options.nxCloudToken, useTsSolution: options.workspaces, formatter: options.formatter, + useProjectJson: !options.workspaces, }); } else if (options.preset === Preset.TS) { const { initGenerator } = require('@nx' + '/js'); @@ -350,6 +359,7 @@ async function createPreset(tree: Tree, options: Schema) { addPlugin, useTsSolution: options.workspaces, formatter: options.formatter, + useProjectJson: !options.workspaces, }); } else { throw new Error(`Invalid preset ${options.preset}`);