feat(misc): add useProjectJson flag to project generators (#30319)

Add a `useProjectJson` option to project generators to allow users to
opt in/out of generating the Nx configuration in a `project.json` file.

## Current Behavior

## Expected Behavior

## Related Issue(s)

Fixes #
This commit is contained in:
Leosvel Pérez Espinosa 2025-03-11 17:12:03 +01:00 committed by GitHub
parent 432a645d21
commit cbf80c18d1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
143 changed files with 2047 additions and 624 deletions

View File

@ -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"],

View File

@ -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"],

View File

@ -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"],

View File

@ -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"],

View File

@ -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,

View File

@ -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,

View File

@ -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"],

View File

@ -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"],

View File

@ -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"],

View File

@ -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"],

View File

@ -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"

View File

@ -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"],

View File

@ -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"],

View File

@ -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"],

View File

@ -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"],

View File

@ -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"],

View File

@ -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"],

View File

@ -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"],

View File

@ -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"],

View File

@ -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"],

View File

@ -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();
});
});
});

View File

@ -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,
});
}

View File

@ -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,
});
});

View File

@ -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) {

View File

@ -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,
});
});
});

View File

@ -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,
};
}

View File

@ -12,4 +12,5 @@ export interface Schema {
setParserOptionsProject?: boolean;
framework: 'react-native' | 'expo';
addPlugin?: boolean;
useProjectJson?: boolean;
}

View File

@ -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"]

View File

@ -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();
});
});
});

View File

@ -32,6 +32,7 @@ export async function expoApplicationGenerator(
): Promise<GeneratorCallback> {
return await expoApplicationGeneratorInternal(host, {
addPlugin: false,
useProjectJson: true,
...schema,
});
}

View File

@ -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,

View File

@ -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) {

View File

@ -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);
});
});

View File

@ -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,
};
}

View File

@ -20,4 +20,5 @@ export interface Schema {
nxCloudToken?: string;
useTsSolution?: boolean;
formatter?: 'prettier' | 'none';
useProjectJson?: boolean;
}

View File

@ -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"]

View File

@ -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).

View File

@ -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<Schema, 'name'> {
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;

View File

@ -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();
});
});
});

View File

@ -5,7 +5,6 @@ import {
GeneratorCallback,
installPackagesTask,
joinPathFragments,
names,
offsetFromRoot,
ProjectConfiguration,
runTasksInSerial,
@ -46,6 +45,7 @@ export async function expoLibraryGenerator(
): Promise<GeneratorCallback> {
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(

View File

@ -19,4 +19,5 @@ export interface Schema {
setParserOptionsProject?: boolean;
skipPackageJson?: boolean; // default is false
addPlugin?: boolean;
useProjectJson?: boolean;
}

View File

@ -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"]

View File

@ -32,6 +32,7 @@
},
"dependencies": {
"@nx/devkit": "file:../devkit",
"@nx/js": "file:../js",
"@nx/node": "file:../node",
"tslib": "^2.3.0"
},

View File

@ -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();
});
});
});

View File

@ -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,
};
}

View File

@ -17,4 +17,5 @@ export interface Schema {
standaloneConfig?: boolean;
setParserOptionsProject?: boolean;
addPlugin?: boolean;
useProjectJson?: boolean;
}

View File

@ -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"]

View File

@ -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();
});
});
});

View File

@ -18,6 +18,7 @@ export async function applicationGenerator(
): Promise<GeneratorCallback> {
return await applicationGeneratorInternal(tree, {
addPlugin: false,
useProjectJson: true,
...rawOptions,
});
}

View File

@ -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,
};
}

View File

@ -16,6 +16,7 @@ export interface ApplicationGeneratorOptions {
strict?: boolean;
addPlugin?: boolean;
useTsSolution?: boolean;
useProjectJson?: boolean;
}
interface NormalizedOptions extends ApplicationGeneratorOptions {

View File

@ -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,

View File

@ -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,
};
}

View File

@ -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();
});
});
});

View File

@ -27,6 +27,7 @@ export async function libraryGenerator(
): Promise<GeneratorCallback> {
return await libraryGeneratorInternal(tree, {
addPlugin: false,
useProjectJson: true,
...rawOptions,
});
}

View File

@ -33,6 +33,7 @@ export interface LibraryGeneratorOptions {
simpleName?: boolean;
addPlugin?: boolean;
isUsingTsSolutionsConfig?: boolean;
useProjectJson?: boolean;
}
export interface NormalizedOptions extends LibraryGeneratorOptions {

View File

@ -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,

View File

@ -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() {

View File

@ -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,
});
}

View File

@ -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,

View File

@ -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
);
}
}

View File

@ -97,5 +97,6 @@ export async function normalizeOptions(
unitTestRunner: options.unitTestRunner || 'jest',
importPath,
isTsSolutionSetup,
useProjectJson: options.useProjectJson ?? !isTsSolutionSetup,
};
}

View File

@ -22,4 +22,5 @@ export interface Schema {
addPlugin?: boolean;
useTsSolution?: boolean;
formatter?: 'prettier' | 'none';
useProjectJson?: boolean;
}

View File

@ -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"],

View File

@ -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,
};
}

View File

@ -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();
});
});
});

View File

@ -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,
});
}

View File

@ -24,4 +24,5 @@ export interface Schema {
setParserOptionsProject?: boolean;
skipPackageJson?: boolean;
addPlugin?: boolean;
useProjectJson?: boolean;
}

View File

@ -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"],

View File

@ -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();
});
});
});

View File

@ -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<Schema, 'useTsSolution'> {
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,
};
}

View File

@ -25,6 +25,7 @@ export interface Schema {
isNest?: boolean;
addPlugin?: boolean;
useTsSolution?: boolean;
useProjectJson?: boolean;
}
export type NodeJsFrameWorks = 'express' | 'koa' | 'fastify' | 'nest' | 'none';

View File

@ -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"]

View File

@ -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'))

View File

@ -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,
};
}

View File

@ -9,4 +9,5 @@ export interface Schema {
isNest?: boolean;
skipFormat?: boolean;
addPlugin?: boolean;
useProjectJson?: boolean;
}

View File

@ -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"]

View File

@ -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();
});
});
});

View File

@ -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,
};
}

View File

@ -21,4 +21,5 @@ export interface Schema {
setParserOptionsProject?: boolean;
compiler: 'tsc' | 'swc';
addPlugin?: boolean;
useProjectJson?: boolean;
}

View File

@ -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"]

View File

@ -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."

View File

@ -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();
});
});
});

View File

@ -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'),

View File

@ -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,

View File

@ -46,6 +46,7 @@ export async function normalizeOptions(
parsedTags,
style: options.style ?? 'none',
isUsingTsSolutionConfig,
useProjectJson: options.useProjectJson ?? !isUsingTsSolutionConfig,
} as NormalizedSchema;
normalized.unitTestRunner ??= 'vitest';

View File

@ -16,6 +16,7 @@ export interface Schema {
style?: 'css' | 'scss' | 'less' | 'none';
nxCloudToken?: string;
useTsSolution?: boolean;
useProjectJson?: boolean;
}
export interface NormalizedSchema extends Omit<Schema, 'useTsSolution'> {

View File

@ -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"],

View File

@ -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();
});
});
});

View File

@ -37,6 +37,7 @@ export async function reactNativeApplicationGenerator(
): Promise<GeneratorCallback> {
return await reactNativeApplicationGeneratorInternal(host, {
addPlugin: false,
useProjectJson: true,
...schema,
});
}

View File

@ -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,
});
}
}

View File

@ -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,
});
});
});

View File

@ -80,5 +80,6 @@ export async function normalizeOptions(
e2eProjectName,
e2eProjectRoot,
isTsSolutionSetup,
useProjectJson: options.useProjectJson ?? !isTsSolutionSetup,
};
}

View File

@ -21,4 +21,5 @@ export interface Schema {
nxCloudToken?: string;
useTsSolution?: boolean;
formatter?: 'prettier' | 'none';
useProjectJson?: boolean;
}

View File

@ -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"]

View File

@ -55,6 +55,7 @@ export async function normalizeOptions(
parsedTags,
importPath,
isUsingTsSolutionConfig,
useProjectJson: options.useProjectJson ?? !isUsingTsSolutionConfig,
};
return normalized;

View File

@ -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();
});
});
});

View File

@ -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<GeneratorCallback> {
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) {

View File

@ -19,4 +19,5 @@ export interface Schema {
setParserOptionsProject?: boolean;
skipPackageJson?: boolean; //default is false
addPlugin?: boolean;
useProjectJson?: boolean;
}

View File

@ -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"]

Some files were not shown because too many files have changed in this diff Show More