feat(misc): remove usages of @nx/cypress:cypress-project internally (#19766)

This commit is contained in:
Jack Hsu 2023-10-24 21:04:15 -04:00 committed by GitHub
parent 1389fc0dab
commit b5ed979b90
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 252 additions and 153 deletions

View File

@ -33,6 +33,11 @@
"type": "string", "type": "string",
"description": "A directory where the project is placed relative from the project root", "description": "A directory where the project is placed relative from the project root",
"default": "cypress" "default": "cypress"
},
"jsx": {
"description": "Whether or not this project uses JSX.",
"type": "boolean",
"default": true
} }
}, },
"required": ["project"], "required": ["project"],

View File

@ -81,6 +81,11 @@
"enum": ["vite", "webpack", "none"], "enum": ["vite", "webpack", "none"],
"x-prompt": "Which Cypress bundler do you want to use?", "x-prompt": "Which Cypress bundler do you want to use?",
"default": "webpack" "default": "webpack"
},
"jsx": {
"description": "Whether or not this project uses JSX.",
"type": "boolean",
"default": true
} }
}, },
"required": ["project"], "required": ["project"],

View File

@ -57,12 +57,6 @@
"description": "Whether or not to configure the ESLint `parserOptions.project` option. We do not do this by default for lint performance reasons.", "description": "Whether or not to configure the ESLint `parserOptions.project` option. We do not do this by default for lint performance reasons.",
"default": false "default": false
}, },
"standaloneConfig": {
"description": "Split the project configuration into `<projectRoot>/project.json` rather than including it inside workspace.json.",
"type": "boolean",
"default": true,
"x-deprecated": "Nx only supports standaloneConfig"
},
"skipPackageJson": { "skipPackageJson": {
"type": "boolean", "type": "boolean",
"default": false, "default": false,

View File

@ -33,12 +33,6 @@
"enum": ["eslint", "none"], "enum": ["eslint", "none"],
"default": "eslint" "default": "eslint"
}, },
"standaloneConfig": {
"description": "Split the project configuration into `<projectRoot>/project.json` rather than including it inside `workspace.json`.",
"type": "boolean",
"default": true,
"x-deprecated": "Nx only supports standaloneConfig"
},
"ciTargetName": { "ciTargetName": {
"type": "string", "type": "string",
"description": "The name of the devServerTarget to use for the Cypress CI configuration. Used to control if using <storybook-project>:static-storybook:ci or <storybook-project>:storybook:ci", "description": "The name of the devServerTarget to use for the Cypress CI configuration. Used to control if using <storybook-project>:static-storybook:ci or <storybook-project>:storybook:ci",
@ -49,6 +43,11 @@
"type": "boolean", "type": "boolean",
"default": false, "default": false,
"x-priority": "internal" "x-priority": "internal"
},
"projectNameAndRootFormat": {
"description": "Whether to generate the project name and root directory as provided (`as-provided`) or generate them composing their values and taking the configured layout into account (`derived`).",
"type": "string",
"enum": ["as-provided", "derived"]
} }
}, },
"required": ["name"], "required": ["name"],

View File

@ -369,6 +369,7 @@ exports[`app --project-name-and-root-format=derived should generate correctly wh
"compilerOptions": { "compilerOptions": {
"allowJs": true, "allowJs": true,
"forceConsistentCasingInFileNames": true, "forceConsistentCasingInFileNames": true,
"module": "commonjs",
"noFallthroughCasesInSwitch": true, "noFallthroughCasesInSwitch": true,
"noImplicitOverride": true, "noImplicitOverride": true,
"noImplicitReturns": true, "noImplicitReturns": true,
@ -383,9 +384,12 @@ exports[`app --project-name-and-root-format=derived should generate correctly wh
}, },
"extends": "../../../tsconfig.base.json", "extends": "../../../tsconfig.base.json",
"include": [ "include": [
"src/**/*.ts", "**/*.ts",
"src/**/*.js", "**/*.js",
"cypress.config.ts", "cypress.config.ts",
"**/*.cy.ts",
"**/*.cy.js",
"**/*.d.ts",
], ],
} }
`; `;
@ -609,6 +613,7 @@ exports[`app --project-name-and-root-format=derived should generate correctly wh
"compilerOptions": { "compilerOptions": {
"allowJs": true, "allowJs": true,
"forceConsistentCasingInFileNames": true, "forceConsistentCasingInFileNames": true,
"module": "commonjs",
"noFallthroughCasesInSwitch": true, "noFallthroughCasesInSwitch": true,
"noImplicitOverride": true, "noImplicitOverride": true,
"noImplicitReturns": true, "noImplicitReturns": true,
@ -623,9 +628,12 @@ exports[`app --project-name-and-root-format=derived should generate correctly wh
}, },
"extends": "../../tsconfig.base.json", "extends": "../../tsconfig.base.json",
"include": [ "include": [
"src/**/*.ts", "**/*.ts",
"src/**/*.js", "**/*.js",
"cypress.config.ts", "cypress.config.ts",
"**/*.cy.ts",
"**/*.cy.js",
"**/*.d.ts",
], ],
} }
`; `;
@ -874,6 +882,7 @@ exports[`app --strict should enable strict type checking: e2e tsconfig.json 1`]
"compilerOptions": { "compilerOptions": {
"allowJs": true, "allowJs": true,
"forceConsistentCasingInFileNames": true, "forceConsistentCasingInFileNames": true,
"module": "commonjs",
"noFallthroughCasesInSwitch": true, "noFallthroughCasesInSwitch": true,
"noImplicitOverride": true, "noImplicitOverride": true,
"noImplicitReturns": true, "noImplicitReturns": true,
@ -888,9 +897,12 @@ exports[`app --strict should enable strict type checking: e2e tsconfig.json 1`]
}, },
"extends": "../tsconfig.base.json", "extends": "../tsconfig.base.json",
"include": [ "include": [
"src/**/*.ts", "**/*.ts",
"src/**/*.js", "**/*.js",
"cypress.config.ts", "cypress.config.ts",
"**/*.cy.ts",
"**/*.cy.js",
"**/*.d.ts",
], ],
} }
`; `;
@ -1218,6 +1230,7 @@ exports[`app not nested should generate files: e2e tsconfig.json 1`] = `
"compilerOptions": { "compilerOptions": {
"allowJs": true, "allowJs": true,
"forceConsistentCasingInFileNames": true, "forceConsistentCasingInFileNames": true,
"module": "commonjs",
"noFallthroughCasesInSwitch": true, "noFallthroughCasesInSwitch": true,
"noImplicitOverride": true, "noImplicitOverride": true,
"noImplicitReturns": true, "noImplicitReturns": true,
@ -1232,9 +1245,12 @@ exports[`app not nested should generate files: e2e tsconfig.json 1`] = `
}, },
"extends": "../tsconfig.base.json", "extends": "../tsconfig.base.json",
"include": [ "include": [
"src/**/*.ts", "**/*.ts",
"src/**/*.js", "**/*.js",
"cypress.config.ts", "cypress.config.ts",
"**/*.cy.ts",
"**/*.cy.js",
"**/*.d.ts",
], ],
} }
`; `;

View File

@ -10,23 +10,27 @@ import {
} from '@nx/devkit'; } from '@nx/devkit';
import { nxVersion } from '../../../utils/versions'; import { nxVersion } from '../../../utils/versions';
import type { NormalizedSchema } from './normalized-schema'; import type { NormalizedSchema } from './normalized-schema';
import { cypressProjectGenerator } from '@nx/cypress'; import { configurationGenerator } from '@nx/cypress';
export async function addE2e(tree: Tree, options: NormalizedSchema) { export async function addE2e(tree: Tree, options: NormalizedSchema) {
if (options.e2eTestRunner === 'cypress') { if (options.e2eTestRunner === 'cypress') {
// TODO: This can call `@nx/web:static-config` generator when ready // TODO: This can call `@nx/web:static-config` generator when ready
addFileServerTarget(tree, options, 'serve-static'); addFileServerTarget(tree, options, 'serve-static');
addProjectConfiguration(tree, options.e2eProjectName, {
await cypressProjectGenerator(tree, { projectType: 'application',
name: options.e2eProjectName, root: options.e2eProjectRoot,
directory: options.e2eProjectRoot, sourceRoot: joinPathFragments(options.e2eProjectRoot, 'src'),
// the name and root are already normalized, instruct the generator to use them as is targets: {},
projectNameAndRootFormat: 'as-provided', tags: [],
project: options.name, implicitDependencies: [options.name],
});
await configurationGenerator(tree, {
project: options.e2eProjectName,
directory: 'src',
linter: options.linter, linter: options.linter,
standaloneConfig: options.standaloneConfig,
skipPackageJson: options.skipPackageJson, skipPackageJson: options.skipPackageJson,
skipFormat: true, skipFormat: true,
devServerTarget: `${options.name}:serve:development`,
}); });
} else if (options.e2eTestRunner === 'playwright') { } else if (options.e2eTestRunner === 'playwright') {
const { configurationGenerator: playwrightConfigurationGenerator } = const { configurationGenerator: playwrightConfigurationGenerator } =
@ -35,6 +39,7 @@ export async function addE2e(tree: Tree, options: NormalizedSchema) {
nxVersion nxVersion
); );
addProjectConfiguration(tree, options.e2eProjectName, { addProjectConfiguration(tree, options.e2eProjectName, {
projectType: 'application',
root: options.e2eProjectRoot, root: options.e2eProjectRoot,
sourceRoot: joinPathFragments(options.e2eProjectRoot, 'src'), sourceRoot: joinPathFragments(options.e2eProjectRoot, 'src'),
targets: {}, targets: {},

View File

@ -13,7 +13,7 @@ import { librarySecondaryEntryPointGenerator } from '../library-secondary-entry-
import { generateTestApplication, generateTestLibrary } from '../utils/testing'; import { generateTestApplication, generateTestLibrary } from '../utils/testing';
import { cypressComponentConfiguration } from './cypress-component-configuration'; import { cypressComponentConfiguration } from './cypress-component-configuration';
let projectGraph: ProjectGraph; let projectGraph: ProjectGraph = { nodes: {}, dependencies: {} };
jest.mock('@nx/cypress/src/utils/cypress-version'); jest.mock('@nx/cypress/src/utils/cypress-version');
jest.mock('@nx/devkit', () => ({ jest.mock('@nx/devkit', () => ({
...jest.requireActual<any>('@nx/devkit'), ...jest.requireActual<any>('@nx/devkit'),

View File

@ -337,11 +337,11 @@ export class E2eMigrator extends ProjectMigrator<SupportedTargets> {
private async migrateCypressE2eProject(): Promise<void> { private async migrateCypressE2eProject(): Promise<void> {
const oldCypressConfigFilePath = this.getOldCypressConfigFilePath(); const oldCypressConfigFilePath = this.getOldCypressConfigFilePath();
// TODO(v18): This needs to be removed before v18 since the generator is going away.
await cypressProjectGenerator(this.tree, { await cypressProjectGenerator(this.tree, {
name: this.project.name, name: this.project.name,
project: this.appName, project: this.appName,
linter: this.isProjectUsingEsLint ? Linter.EsLint : Linter.None, linter: this.isProjectUsingEsLint ? Linter.EsLint : Linter.None,
standaloneConfig: true,
skipFormat: true, skipFormat: true,
}); });

View File

@ -1,12 +1,17 @@
import { configurationGenerator } from './src/generators/configuration/configuration'; import { configurationGenerator } from './src/generators/configuration/configuration';
import { componentConfigurationGenerator } from './src/generators/component-configuration/component-configuration'; import { componentConfigurationGenerator } from './src/generators/component-configuration/component-configuration';
import { cypressProjectGenerator as _cypressProjectGenerator } from './src/generators/cypress-project/cypress-project';
export { configurationGenerator, componentConfigurationGenerator }; export { configurationGenerator, componentConfigurationGenerator };
// Maintain backwards compatibility with the old names in case community plugins used them. // Maintain backwards compatibility with the old names in case community plugins used them.
/** @deprecated Use `configurationGenerator` instead. */ // TODO(v18): Remove old name
/** @deprecated Use `configurationGenerator` instead. It will be removed in Nx 18. */
export const cypressComponentConfiguration = componentConfigurationGenerator; export const cypressComponentConfiguration = componentConfigurationGenerator;
export { configurationGenerator as cypressE2EConfigurationGenerator }; export { configurationGenerator as cypressE2EConfigurationGenerator };
export { cypressProjectGenerator } from './src/generators/cypress-project/cypress-project'; // TODO(v18): Remove project generator
/** @deprecated Add a new project and call `configurationGenerator` instead. It will be removed in Nx 18. */
export const cypressProjectGenerator = _cypressProjectGenerator;
export { cypressInitGenerator } from './src/generators/init/init'; export { cypressInitGenerator } from './src/generators/init/init';
export { migrateCypressProject } from './src/generators/migrate-to-cypress-11/migrate-to-cypress-11'; export { migrateCypressProject } from './src/generators/migrate-to-cypress-11/migrate-to-cypress-11';

View File

@ -17,6 +17,7 @@ export interface CypressBaseSetupSchema {
* default is `cypress` * default is `cypress`
* */ * */
directory?: string; directory?: string;
jsx?: boolean;
} }
export function addBaseCypressSetup( export function addBaseCypressSetup(
@ -33,6 +34,7 @@ export function addBaseCypressSetup(
generateFiles(tree, join(__dirname, 'files'), projectConfig.root, { generateFiles(tree, join(__dirname, 'files'), projectConfig.root, {
...opts, ...opts,
jsx: !!opts.jsx,
offsetFromRoot: offsetFromRoot(projectConfig.root), offsetFromRoot: offsetFromRoot(projectConfig.root),
offsetFromProjectRoot: opts.hasTsConfig ? opts.offsetFromProjectRoot : '', offsetFromProjectRoot: opts.hasTsConfig ? opts.offsetFromProjectRoot : '',
tsConfigPath: opts.hasTsConfig tsConfigPath: opts.hasTsConfig

View File

@ -12,9 +12,9 @@
"**/*.js", "**/*.js",
"<%= offsetFromProjectRoot %>cypress.config.ts", "<%= offsetFromProjectRoot %>cypress.config.ts",
"<%= offsetFromProjectRoot %>**/*.cy.ts", "<%= offsetFromProjectRoot %>**/*.cy.ts",
"<%= offsetFromProjectRoot %>**/*.cy.tsx", <% if (jsx) { %> "<%= offsetFromProjectRoot %>**/*.cy.tsx",<% } %>
"<%= offsetFromProjectRoot %>**/*.cy.js", "<%= offsetFromProjectRoot %>**/*.cy.js",
"<%= offsetFromProjectRoot %>**/*.cy.jsx", <% if (jsx) { %>"<%= offsetFromProjectRoot %>**/*.cy.jsx",<% } %>
"<%= offsetFromProjectRoot %>**/*.d.ts" "<%= offsetFromProjectRoot %>**/*.d.ts"
] ]
} }

View File

@ -100,6 +100,7 @@ describe('Cypress Component Configuration', () => {
await componentConfigurationGenerator(tree, { await componentConfigurationGenerator(tree, {
project: 'cool-lib', project: 'cool-lib',
skipFormat: false, skipFormat: false,
jsx: true,
}); });
const projectConfig = readProjectConfiguration(tree, 'cool-lib'); const projectConfig = readProjectConfiguration(tree, 'cool-lib');
expect(tree.exists('libs/cool-lib/cypress.config.ts')).toEqual(true); expect(tree.exists('libs/cool-lib/cypress.config.ts')).toEqual(true);

View File

@ -85,6 +85,7 @@ function addProjectFiles(
addBaseCypressSetup(tree, { addBaseCypressSetup(tree, {
project: opts.project, project: opts.project,
directory: opts.directory, directory: opts.directory,
jsx: opts.jsx,
}); });
generateFiles( generateFiles(

View File

@ -3,4 +3,5 @@ export interface CypressComponentConfigurationSchema {
skipFormat: boolean; skipFormat: boolean;
directory?: string; directory?: string;
bundler?: 'webpack' | 'vite'; bundler?: 'webpack' | 'vite';
jsx?: boolean;
} }

View File

@ -31,6 +31,11 @@
"type": "string", "type": "string",
"description": "A directory where the project is placed relative from the project root", "description": "A directory where the project is placed relative from the project root",
"default": "cypress" "default": "cypress"
},
"jsx": {
"description": "Whether or not this project uses JSX.",
"type": "boolean",
"default": true
} }
}, },
"required": ["project"], "required": ["project"],

View File

@ -52,11 +52,6 @@ describe('Cypress e2e configuration', () => {
expect(readProjectConfiguration(tree, 'my-app').targets.e2e) expect(readProjectConfiguration(tree, 'my-app').targets.e2e)
.toMatchInlineSnapshot(` .toMatchInlineSnapshot(`
{ {
"configurations": {
"production": {
"devServerTarget": "my-app:serve:production",
},
},
"executor": "@nx/cypress:cypress", "executor": "@nx/cypress:cypress",
"options": { "options": {
"cypressConfig": "apps/my-app/cypress.config.ts", "cypressConfig": "apps/my-app/cypress.config.ts",
@ -85,9 +80,7 @@ describe('Cypress e2e configuration', () => {
"**/*.js", "**/*.js",
"cypress.config.ts", "cypress.config.ts",
"**/*.cy.ts", "**/*.cy.ts",
"**/*.cy.tsx",
"**/*.cy.js", "**/*.cy.js",
"**/*.cy.jsx",
"**/*.d.ts", "**/*.d.ts",
], ],
} }
@ -208,9 +201,7 @@ describe('Cypress e2e configuration', () => {
"**/*.js", "**/*.js",
"../../cypress.config.ts", "../../cypress.config.ts",
"../../**/*.cy.ts", "../../**/*.cy.ts",
"../../**/*.cy.tsx",
"../../**/*.cy.js", "../../**/*.cy.js",
"../../**/*.cy.jsx",
"../../**/*.d.ts", "../../**/*.d.ts",
], ],
} }

View File

@ -35,6 +35,7 @@ export interface CypressE2EConfigSchema {
devServerTarget?: string; devServerTarget?: string;
linter?: Linter; linter?: Linter;
port?: number | 'cypress-auto'; port?: number | 'cypress-auto';
jsx?: boolean;
} }
type NormalizedSchema = ReturnType<typeof normalizeOptions>; type NormalizedSchema = ReturnType<typeof normalizeOptions>;
@ -55,10 +56,12 @@ export async function configurationGenerator(
} }
await addFiles(tree, opts); await addFiles(tree, opts);
addTarget(tree, opts); addTarget(tree, opts);
addLinterToCyProject(tree, {
const linterTask = await addLinterToCyProject(tree, {
...opts, ...opts,
cypressDir: opts.directory, cypressDir: opts.directory,
}); });
tasks.push(linterTask);
if (!opts.skipFormat) { if (!opts.skipFormat) {
await formatFiles(tree); await formatFiles(tree);
@ -132,6 +135,7 @@ async function addFiles(tree: Tree, options: NormalizedSchema) {
addBaseCypressSetup(tree, { addBaseCypressSetup(tree, {
project: options.project, project: options.project,
directory: options.directory, directory: options.directory,
jsx: options.jsx,
}); });
const cyFile = joinPathFragments(projectConfig.root, 'cypress.config.ts'); const cyFile = joinPathFragments(projectConfig.root, 'cypress.config.ts');
@ -197,18 +201,25 @@ function addTarget(tree: Tree, opts: NormalizedSchema) {
port: opts.port, port: opts.port,
}; };
projectConfig.targets.e2e.configurations = {
[parsedTarget.configuration || 'production']: {
devServerTarget: `${opts.devServerTarget}${
parsedTarget.configuration ? '' : ':production'
}`,
},
};
const devServerProjectConfig = readProjectConfiguration( const devServerProjectConfig = readProjectConfiguration(
tree, tree,
parsedTarget.project parsedTarget.project
); );
// Add production e2e target if serve target is found
if (
parsedTarget.configuration !== 'production' &&
devServerProjectConfig.targets?.[parsedTarget.target]?.configurations?.[
'production'
]
) {
projectConfig.targets.e2e.configurations ??= {};
projectConfig.targets.e2e.configurations['production'] = {
devServerTarget: `${parsedTarget.project}:${parsedTarget.target}:production`,
};
}
// Add ci/static e2e target if serve target is found
if (devServerProjectConfig.targets?.['serve-static']) { if (devServerProjectConfig.targets?.['serve-static']) {
projectConfig.targets.e2e.configurations ??= {};
projectConfig.targets.e2e.configurations.ci = { projectConfig.targets.e2e.configurations.ci = {
devServerTarget: `${parsedTarget.project}:serve-static`, devServerTarget: `${parsedTarget.project}:serve-static`,
}; };

View File

@ -84,6 +84,11 @@
"enum": ["vite", "webpack", "none"], "enum": ["vite", "webpack", "none"],
"x-prompt": "Which Cypress bundler do you want to use?", "x-prompt": "Which Cypress bundler do you want to use?",
"default": "webpack" "default": "webpack"
},
"jsx": {
"description": "Whether or not this project uses JSX.",
"type": "boolean",
"default": true
} }
}, },
"required": ["project"], "required": ["project"],

View File

@ -11,7 +11,6 @@ export interface Schema {
js?: boolean; js?: boolean;
skipFormat?: boolean; skipFormat?: boolean;
setParserOptionsProject?: boolean; setParserOptionsProject?: boolean;
standaloneConfig?: boolean;
skipPackageJson?: boolean; skipPackageJson?: boolean;
bundler?: 'webpack' | 'vite' | 'none'; bundler?: 'webpack' | 'vite' | 'none';
} }

View File

@ -59,12 +59,6 @@
"description": "Whether or not to configure the ESLint `parserOptions.project` option. We do not do this by default for lint performance reasons.", "description": "Whether or not to configure the ESLint `parserOptions.project` option. We do not do this by default for lint performance reasons.",
"default": false "default": false
}, },
"standaloneConfig": {
"description": "Split the project configuration into `<projectRoot>/project.json` rather than including it inside workspace.json.",
"type": "boolean",
"default": true,
"x-deprecated": "Nx only supports standaloneConfig"
},
"skipPackageJson": { "skipPackageJson": {
"type": "boolean", "type": "boolean",
"default": false, "default": false,

View File

@ -12,18 +12,24 @@ import { NormalizedSchema } from './normalize-options';
export async function addE2e(host: Tree, options: NormalizedSchema) { export async function addE2e(host: Tree, options: NormalizedSchema) {
if (options.e2eTestRunner === 'cypress') { if (options.e2eTestRunner === 'cypress') {
const { cypressProjectGenerator } = ensurePackage< const { configurationGenerator } = ensurePackage<
typeof import('@nx/cypress') typeof import('@nx/cypress')
>('@nx/cypress', nxVersion); >('@nx/cypress', nxVersion);
return cypressProjectGenerator(host, { addProjectConfiguration(host, options.e2eProjectName, {
root: options.e2eProjectRoot,
sourceRoot: joinPathFragments(options.e2eProjectRoot, 'src'),
targets: {},
tags: [],
implicitDependencies: [options.projectName],
});
return configurationGenerator(host, {
...options, ...options,
linter: Linter.EsLint, linter: Linter.EsLint,
name: options.e2eProjectName, project: options.e2eProjectName,
directory: options.e2eProjectRoot, directory: 'src',
// the name and root are already normalized, instruct the generator to use them as is
projectNameAndRootFormat: 'as-provided',
project: options.projectName,
skipFormat: true, skipFormat: true,
devServerTarget: `${options.projectName}:serve`,
jsx: true,
}); });
} else if (options.e2eTestRunner === 'playwright') { } else if (options.e2eTestRunner === 'playwright') {
const { configurationGenerator } = ensurePackage< const { configurationGenerator } = ensurePackage<

View File

@ -29,6 +29,7 @@ export async function cypressComponentConfiguration(
await baseCyCtConfig(tree, { await baseCyCtConfig(tree, {
project: options.project, project: options.project,
skipFormat: true, skipFormat: true,
jsx: true,
}) })
); );

View File

@ -132,14 +132,14 @@ exports[`app generated files content - as-provided should create all new files i
"my-app/.eslintrc.json", "my-app/.eslintrc.json",
"my-app/vite.config.ts", "my-app/vite.config.ts",
"my-app/tsconfig.spec.json", "my-app/tsconfig.spec.json",
"my-app-e2e/cypress.config.ts",
"my-app-e2e/src/e2e/app.cy.ts",
"my-app-e2e/src/fixtures/example.json",
"my-app-e2e/src/support/app.po.ts",
"my-app-e2e/src/support/commands.ts",
"my-app-e2e/src/support/e2e.ts",
"my-app-e2e/tsconfig.json",
"my-app-e2e/project.json", "my-app-e2e/project.json",
"my-app-e2e/src/e2e/app.cy.ts",
"my-app-e2e/src/support/app.po.ts",
"my-app-e2e/src/support/e2e.ts",
"my-app-e2e/src/fixtures/example.json",
"my-app-e2e/src/support/commands.ts",
"my-app-e2e/cypress.config.ts",
"my-app-e2e/tsconfig.json",
"my-app-e2e/.eslintrc.json", "my-app-e2e/.eslintrc.json",
] ]
`; `;

View File

@ -12,17 +12,25 @@ import { NormalizedSchema } from '../schema';
export async function addE2e(host: Tree, options: NormalizedSchema) { export async function addE2e(host: Tree, options: NormalizedSchema) {
if (options.e2eTestRunner === 'cypress') { if (options.e2eTestRunner === 'cypress') {
const { cypressProjectGenerator } = ensurePackage< const { configurationGenerator } = ensurePackage<
typeof import('@nx/cypress') typeof import('@nx/cypress')
>('@nx/cypress', nxVersion); >('@nx/cypress', nxVersion);
return cypressProjectGenerator(host, { addProjectConfiguration(host, options.e2eProjectName, {
projectType: 'application',
root: options.e2eProjectRoot,
sourceRoot: joinPathFragments(options.e2eProjectRoot, 'src'),
targets: {},
tags: [],
implicitDependencies: [options.projectName],
});
return await configurationGenerator(host, {
...options, ...options,
linter: Linter.EsLint, project: options.e2eProjectName,
name: options.e2eProjectName, directory: 'src',
directory: options.e2eProjectRoot, bundler: 'vite',
projectNameAndRootFormat: 'as-provided',
project: options.projectName,
skipFormat: true, skipFormat: true,
devServerTarget: `${options.projectName}:serve`,
jsx: true,
}); });
} else if (options.e2eTestRunner === 'playwright') { } else if (options.e2eTestRunner === 'playwright') {
const { configurationGenerator } = ensurePackage< const { configurationGenerator } = ensurePackage<

View File

@ -143,6 +143,7 @@ describe('app', () => {
{ {
"compilerOptions": { "compilerOptions": {
"allowJs": true, "allowJs": true,
"module": "commonjs",
"outDir": "../dist/out-tsc", "outDir": "../dist/out-tsc",
"sourceMap": false, "sourceMap": false,
"types": [ "types": [
@ -152,9 +153,14 @@ describe('app', () => {
}, },
"extends": "../tsconfig.base.json", "extends": "../tsconfig.base.json",
"include": [ "include": [
"src/**/*.ts", "**/*.ts",
"src/**/*.js", "**/*.js",
"cypress.config.ts", "cypress.config.ts",
"**/*.cy.ts",
"**/*.cy.tsx",
"**/*.cy.js",
"**/*.cy.jsx",
"**/*.d.ts",
], ],
} }
`); `);

View File

@ -15,31 +15,42 @@ export async function addE2e(
options: NormalizedSchema options: NormalizedSchema
): Promise<GeneratorCallback> { ): Promise<GeneratorCallback> {
switch (options.e2eTestRunner) { switch (options.e2eTestRunner) {
case 'cypress': case 'cypress': {
webStaticServeGenerator(tree, { webStaticServeGenerator(tree, {
buildTarget: `${options.projectName}:build`, buildTarget: `${options.projectName}:build`,
targetName: 'serve-static', targetName: 'serve-static',
}); });
const { cypressProjectGenerator } = ensurePackage< const { configurationGenerator } = ensurePackage<
typeof import('@nx/cypress') typeof import('@nx/cypress')
>('@nx/cypress', nxVersion); >('@nx/cypress', nxVersion);
return await cypressProjectGenerator(tree, { addProjectConfiguration(tree, options.e2eProjectName, {
projectType: 'application',
root: options.e2eProjectRoot,
sourceRoot: joinPathFragments(options.e2eProjectRoot, 'src'),
targets: {},
implicitDependencies: [options.projectName],
tags: [],
});
return await configurationGenerator(tree, {
...options, ...options,
name: options.e2eProjectName, project: options.e2eProjectName,
directory: options.e2eProjectRoot, directory: 'src',
// the name and root are already normalized, instruct the generator to use them as is // the name and root are already normalized, instruct the generator to use them as is
projectNameAndRootFormat: 'as-provided',
project: options.projectName,
bundler: options.bundler === 'rspack' ? 'webpack' : options.bundler, bundler: options.bundler === 'rspack' ? 'webpack' : options.bundler,
skipFormat: true, skipFormat: true,
devServerTarget: `${options.projectName}:serve`,
jsx: true,
}); });
case 'playwright': }
case 'playwright': {
const { configurationGenerator } = ensurePackage< const { configurationGenerator } = ensurePackage<
typeof import('@nx/playwright') typeof import('@nx/playwright')
>('@nx/playwright', nxVersion); >('@nx/playwright', nxVersion);
addProjectConfiguration(tree, options.e2eProjectName, { addProjectConfiguration(tree, options.e2eProjectName, {
projectType: 'application',
root: options.e2eProjectRoot, root: options.e2eProjectRoot,
sourceRoot: joinPathFragments(options.e2eProjectRoot, 'src'), sourceRoot: joinPathFragments(options.e2eProjectRoot, 'src'),
targets: {}, targets: {},
@ -58,6 +69,7 @@ export async function addE2e(
}`, }`,
webServerAddress: 'http://localhost:4200', webServerAddress: 'http://localhost:4200',
}); });
}
case 'none': case 'none':
default: default:
return () => {}; return () => {};

View File

@ -25,6 +25,7 @@ export async function cypressComponentConfigGenerator(
const installTask = await baseCyCtConfig(tree, { const installTask = await baseCyCtConfig(tree, {
project: options.project, project: options.project,
skipFormat: true, skipFormat: true,
jsx: true,
}); });
const found = await addCTTargetWithBuildTarget(tree, { const found = await addCTTargetWithBuildTarget(tree, {

View File

@ -1,19 +1,13 @@
import { installedCypressVersion } from '@nx/cypress/src/utils/cypress-version';
import { readJson, readProjectConfiguration, Tree } from '@nx/devkit'; import { readJson, readProjectConfiguration, Tree } from '@nx/devkit';
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing'; import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import { Linter } from '@nx/eslint'; import { Linter } from '@nx/eslint';
import { libraryGenerator } from '@nx/js'; import { libraryGenerator } from '@nx/js';
import { cypressProjectGenerator } from './cypress-project'; import { cypressProjectGenerator } from './cypress-project';
jest.mock('@nx/cypress/src/utils/cypress-version');
describe('@nx/storybook:cypress-project', () => { describe('@nx/storybook:cypress-project', () => {
let tree: Tree; let tree: Tree;
let mockedInstalledCypressVersion: jest.Mock<
ReturnType<typeof installedCypressVersion>
> = installedCypressVersion as never;
beforeEach(async () => { beforeEach(async () => {
mockedInstalledCypressVersion.mockReturnValue(10);
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' }); tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
await libraryGenerator(tree, { await libraryGenerator(tree, {
name: 'test-ui-lib', name: 'test-ui-lib',
@ -36,19 +30,6 @@ describe('@nx/storybook:cypress-project', () => {
expect(cypressConfig).toMatchSnapshot(); expect(cypressConfig).toMatchSnapshot();
}); });
it('should update cypress.json file if present', async () => {
mockedInstalledCypressVersion.mockReturnValue(9);
await cypressProjectGenerator(tree, {
name: 'test-ui-lib',
linter: Linter.EsLint,
});
expect(tree.exists('apps/test-ui-lib-e2e/cypress.json')).toBeTruthy();
const cypressConfig = readJson(tree, 'apps/test-ui-lib-e2e/cypress.json');
expect(cypressConfig.baseUrl).toEqual('http://localhost:4400');
});
it('should update `angular.json` file', async () => { it('should update `angular.json` file', async () => {
await cypressProjectGenerator(tree, { await cypressProjectGenerator(tree, {
name: 'test-ui-lib', name: 'test-ui-lib',

View File

@ -1,15 +1,11 @@
import { getE2eProjectName } from '@nx/cypress/src/utils/project-name';
import { import {
cypressInitGenerator as _cypressInitGenerator, addProjectConfiguration,
cypressProjectGenerator as _cypressProjectGenerator, ensurePackage,
} from '@nx/cypress';
import {
getE2eProjectName,
getUnscopedLibName,
} from '@nx/cypress/src/utils/project-name';
import {
formatFiles, formatFiles,
generateFiles, generateFiles,
GeneratorCallback, GeneratorCallback,
joinPathFragments,
readJson, readJson,
readProjectConfiguration, readProjectConfiguration,
runTasksInSerial, runTasksInSerial,
@ -18,9 +14,11 @@ import {
updateProjectConfiguration, updateProjectConfiguration,
} from '@nx/devkit'; } from '@nx/devkit';
import { Linter } from '@nx/eslint'; import { Linter } from '@nx/eslint';
import { determineProjectNameAndRootOptions } from '@nx/devkit/src/generators/project-name-and-root-utils';
import { join } from 'path'; import { join } from 'path';
import { safeFileDelete } from '../../utils/utilities'; import { safeFileDelete } from '../../utils/utilities';
import { nxVersion } from '../../utils/versions';
export interface CypressConfigureSchema { export interface CypressConfigureSchema {
name: string; name: string;
@ -30,31 +28,61 @@ export interface CypressConfigureSchema {
standaloneConfig?: boolean; standaloneConfig?: boolean;
ciTargetName?: string; ciTargetName?: string;
skipFormat?: boolean; skipFormat?: boolean;
projectNameAndRootFormat?: 'as-provided' | 'derived';
} }
export async function cypressProjectGenerator( export async function cypressProjectGenerator(
tree: Tree, tree: Tree,
schema: CypressConfigureSchema schema: CypressConfigureSchema
) { ) {
return await cypressProjectGeneratorInternal(tree, {
projectNameAndRootFormat: 'derived',
...schema,
});
}
export async function cypressProjectGeneratorInternal(
tree: Tree,
schema: CypressConfigureSchema
) {
const { configurationGenerator, cypressInitGenerator } = ensurePackage<
typeof import('@nx/cypress')
>('@nx/cypress', nxVersion);
const e2eName = schema.name ? `${schema.name}-e2e` : undefined;
const { projectName, projectRoot } = await determineProjectNameAndRootOptions(
tree,
{
name: e2eName,
projectType: 'application',
directory: schema.directory,
projectNameAndRootFormat: schema.projectNameAndRootFormat,
callingGenerator: '@nx/storybook:cypress-project',
}
);
const libConfig = readProjectConfiguration(tree, schema.name); const libConfig = readProjectConfiguration(tree, schema.name);
const libRoot = libConfig.root; const libRoot = libConfig.root;
const cypressProjectName = `${
schema.directory ? getUnscopedLibName(libRoot) : schema.name
}-e2e`;
const tasks: GeneratorCallback[] = []; const tasks: GeneratorCallback[] = [];
if (!projectAlreadyHasCypress(tree)) { if (!projectAlreadyHasCypress(tree)) {
tasks.push(await _cypressInitGenerator(tree, {})); tasks.push(await cypressInitGenerator(tree, {}));
} }
const installTask = await _cypressProjectGenerator(tree, { addProjectConfiguration(tree, projectName, {
name: cypressProjectName, root: projectRoot,
project: schema.name, projectType: 'application',
sourceRoot: joinPathFragments(projectRoot, 'src'),
targets: {},
implicitDependencies: [projectName],
});
const installTask = await configurationGenerator(tree, {
project: projectName,
js: schema.js, js: schema.js,
linter: schema.linter, linter: schema.linter,
directory: schema.directory, directory: projectRoot,
standaloneConfig: schema.standaloneConfig, devServerTarget: `${schema.name}:storybook`,
skipFormat: true, skipFormat: true,
}); });
tasks.push(installTask); tasks.push(installTask);

View File

@ -33,12 +33,6 @@
"enum": ["eslint", "none"], "enum": ["eslint", "none"],
"default": "eslint" "default": "eslint"
}, },
"standaloneConfig": {
"description": "Split the project configuration into `<projectRoot>/project.json` rather than including it inside `workspace.json`.",
"type": "boolean",
"default": true,
"x-deprecated": "Nx only supports standaloneConfig"
},
"ciTargetName": { "ciTargetName": {
"type": "string", "type": "string",
"description": "The name of the devServerTarget to use for the Cypress CI configuration. Used to control if using <storybook-project>:static-storybook:ci or <storybook-project>:storybook:ci", "description": "The name of the devServerTarget to use for the Cypress CI configuration. Used to control if using <storybook-project>:static-storybook:ci or <storybook-project>:storybook:ci",
@ -49,6 +43,11 @@
"type": "boolean", "type": "boolean",
"default": false, "default": false,
"x-priority": "internal" "x-priority": "internal"
},
"projectNameAndRootFormat": {
"description": "Whether to generate the project name and root directory as provided (`as-provided`) or generate them composing their values and taking the configured layout into account (`derived`).",
"type": "string",
"enum": ["as-provided", "derived"]
} }
}, },
"required": ["name"] "required": ["name"]

View File

@ -15,30 +15,39 @@ export async function addE2e(
options: NormalizedSchema options: NormalizedSchema
): Promise<GeneratorCallback> { ): Promise<GeneratorCallback> {
switch (options.e2eTestRunner) { switch (options.e2eTestRunner) {
case 'cypress': case 'cypress': {
webStaticServeGenerator(tree, { webStaticServeGenerator(tree, {
buildTarget: `${options.projectName}:build`, buildTarget: `${options.projectName}:build`,
targetName: 'serve-static', targetName: 'serve-static',
}); });
const { cypressProjectGenerator } = ensurePackage< const { configurationGenerator } = ensurePackage<
typeof import('@nx/cypress') typeof import('@nx/cypress')
>('@nx/cypress', nxVersion); >('@nx/cypress', nxVersion);
addProjectConfiguration(tree, options.e2eProjectName, {
return await cypressProjectGenerator(tree, { projectType: 'application',
root: options.e2eProjectRoot,
sourceRoot: joinPathFragments(options.e2eProjectRoot, 'src'),
targets: {},
tags: [],
implicitDependencies: [options.projectName],
});
return await configurationGenerator(tree, {
...options, ...options,
name: options.e2eProjectName, project: options.e2eProjectName,
directory: options.e2eProjectRoot, directory: 'src',
projectNameAndRootFormat: 'as-provided',
project: options.projectName,
bundler: 'vite', bundler: 'vite',
skipFormat: true, skipFormat: true,
devServerTarget: `${options.projectName}:serve`,
jsx: true,
}); });
case 'playwright': }
case 'playwright': {
const { configurationGenerator } = ensurePackage< const { configurationGenerator } = ensurePackage<
typeof import('@nx/playwright') typeof import('@nx/playwright')
>('@nx/playwright', nxVersion); >('@nx/playwright', nxVersion);
addProjectConfiguration(tree, options.e2eProjectName, { addProjectConfiguration(tree, options.e2eProjectName, {
projectType: 'application',
root: options.e2eProjectRoot, root: options.e2eProjectRoot,
sourceRoot: joinPathFragments(options.e2eProjectRoot, 'src'), sourceRoot: joinPathFragments(options.e2eProjectRoot, 'src'),
targets: {}, targets: {},
@ -57,6 +66,7 @@ export async function addE2e(
}`, }`,
webServerAddress: 'http://localhost:4200', webServerAddress: 'http://localhost:4200',
}); });
}
case 'none': case 'none':
default: default:
return () => {}; return () => {};

View File

@ -86,6 +86,7 @@ describe('app', () => {
{ {
"compilerOptions": { "compilerOptions": {
"allowJs": true, "allowJs": true,
"module": "commonjs",
"outDir": "../dist/out-tsc", "outDir": "../dist/out-tsc",
"sourceMap": false, "sourceMap": false,
"types": [ "types": [
@ -95,9 +96,12 @@ describe('app', () => {
}, },
"extends": "../tsconfig.base.json", "extends": "../tsconfig.base.json",
"include": [ "include": [
"src/**/*.ts", "**/*.ts",
"src/**/*.js", "**/*.js",
"cypress.config.ts", "cypress.config.ts",
"**/*.cy.ts",
"**/*.cy.js",
"**/*.d.ts",
], ],
} }
`); `);

View File

@ -6,7 +6,6 @@ import {
generateFiles, generateFiles,
GeneratorCallback, GeneratorCallback,
getPackageManagerCommand, getPackageManagerCommand,
getWorkspaceLayout,
joinPathFragments, joinPathFragments,
names, names,
offsetFromRoot, offsetFromRoot,
@ -282,16 +281,22 @@ export async function applicationGeneratorInternal(host: Tree, schema: Schema) {
} }
if (options.e2eTestRunner === 'cypress') { if (options.e2eTestRunner === 'cypress') {
const { cypressProjectGenerator } = ensurePackage< const { configurationGenerator } = ensurePackage<
typeof import('@nx/cypress') typeof import('@nx/cypress')
>('@nx/cypress', nxVersion); >('@nx/cypress', nxVersion);
const cypressTask = await cypressProjectGenerator(host, { addProjectConfiguration(host, options.e2eProjectName, {
root: options.e2eProjectRoot,
sourceRoot: joinPathFragments(options.e2eProjectRoot, 'src'),
projectType: 'application',
targets: {},
tags: [],
implicitDependencies: [options.projectName],
});
const cypressTask = await configurationGenerator(host, {
...options, ...options,
name: options.e2eProjectName, project: options.e2eProjectName,
directory: options.e2eProjectRoot, devServerTarget: `${options.projectName}:serve`,
// the name and root are already normalized, instruct the generator to use them as is directory: 'src',
projectNameAndRootFormat: 'as-provided',
project: options.projectName,
skipFormat: true, skipFormat: true,
}); });
tasks.push(cypressTask); tasks.push(cypressTask);
@ -299,7 +304,6 @@ export async function applicationGeneratorInternal(host: Tree, schema: Schema) {
const { configurationGenerator: playwrightConfigGenerator } = ensurePackage< const { configurationGenerator: playwrightConfigGenerator } = ensurePackage<
typeof import('@nx/playwright') typeof import('@nx/playwright')
>('@nx/playwright', nxVersion); >('@nx/playwright', nxVersion);
addProjectConfiguration(host, options.e2eProjectName, { addProjectConfiguration(host, options.e2eProjectName, {
root: options.e2eProjectRoot, root: options.e2eProjectRoot,
sourceRoot: joinPathFragments(options.e2eProjectRoot, 'src'), sourceRoot: joinPathFragments(options.e2eProjectRoot, 'src'),