diff --git a/docs/generated/packages/cypress/generators/configuration.json b/docs/generated/packages/cypress/generators/configuration.json index 230ad5fc04851..40b13c1e1fa3f 100644 --- a/docs/generated/packages/cypress/generators/configuration.json +++ b/docs/generated/packages/cypress/generators/configuration.json @@ -43,8 +43,8 @@ "linter": { "description": "The tool to use for running lint checks.", "type": "string", - "enum": ["eslint", "none"], - "default": "eslint" + "enum": ["none", "eslint"], + "x-priority": "important" }, "js": { "description": "Generate JavaScript files rather than TypeScript files.", diff --git a/packages/angular/src/generators/cypress-component-configuration/__snapshots__/cypress-component-configuration.spec.ts.snap b/packages/angular/src/generators/cypress-component-configuration/__snapshots__/cypress-component-configuration.spec.ts.snap index 4c0d2464becfc..3d1e8a58be136 100644 --- a/packages/angular/src/generators/cypress-component-configuration/__snapshots__/cypress-component-configuration.spec.ts.snap +++ b/packages/angular/src/generators/cypress-component-configuration/__snapshots__/cypress-component-configuration.spec.ts.snap @@ -22,7 +22,7 @@ import './commands'; // add component testing only related command here, such as mount declare global { -// eslint-disable-next-line @typescript-eslint/no-namespace + // eslint-disable-next-line @typescript-eslint/no-namespace namespace Cypress { // eslint-disable-next-line @typescript-eslint/no-unused-vars interface Chainable { diff --git a/packages/cypress/plugins/cypress-preset.ts b/packages/cypress/plugins/cypress-preset.ts index 086273d666e3b..7ec404862aa13 100644 --- a/packages/cypress/plugins/cypress-preset.ts +++ b/packages/cypress/plugins/cypress-preset.ts @@ -1,14 +1,13 @@ import { workspaceRoot } from '@nx/devkit'; -import { dirname, join, relative } from 'path'; -import { lstatSync } from 'fs'; - -import vitePreprocessor from '../src/plugins/preprocessor-vite'; -import { NX_PLUGIN_OPTIONS } from '../src/utils/constants'; - +import { isUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup'; import { execSync, spawn } from 'child_process'; +import { lstatSync } from 'fs'; import { request as httpRequest } from 'http'; import { request as httpsRequest } from 'https'; +import { dirname, join, relative } from 'path'; import type { InlineConfig } from 'vite'; +import vitePreprocessor from '../src/plugins/preprocessor-vite'; +import { NX_PLUGIN_OPTIONS } from '../src/utils/constants'; // Importing the cypress type here causes the angular and next unit // tests to fail when transpiling, it seems like the cypress types are @@ -54,14 +53,13 @@ export function nxBaseCypressPreset( : dirname(pathToConfig); const projectPath = relative(workspaceRoot, normalizedPath); const offset = relative(normalizedPath, workspaceRoot); - const videosFolder = join(offset, 'dist', 'cypress', projectPath, 'videos'); - const screenshotsFolder = join( - offset, - 'dist', - 'cypress', - projectPath, - 'screenshots' - ); + const isTsSolutionSetup = isUsingTsSolutionSetup(); + const videosFolder = isTsSolutionSetup + ? join('test-output', 'cypress', 'videos') + : join(offset, 'dist', 'cypress', projectPath, 'videos'); + const screenshotsFolder = isTsSolutionSetup + ? join('test-output', 'cypress', 'screenshots') + : join(offset, 'dist', 'cypress', projectPath, 'screenshots'); return { videosFolder, diff --git a/packages/cypress/src/generators/base-setup/base-setup.ts b/packages/cypress/src/generators/base-setup/base-setup.ts index 35357f3d6dd40..d41cc62e3abc7 100644 --- a/packages/cypress/src/generators/base-setup/base-setup.ts +++ b/packages/cypress/src/generators/base-setup/base-setup.ts @@ -4,11 +4,12 @@ import { generateFiles, joinPathFragments, offsetFromRoot, + readJson, readProjectConfiguration, updateJson, - readJson, } from '@nx/devkit'; import { getRelativePathToRootTsConfig } from '@nx/js'; +import { isUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup'; import { join } from 'path'; export interface CypressBaseSetupSchema { @@ -44,6 +45,7 @@ export function addBaseCypressSetup( tsConfigPath: opts.hasTsConfig ? `${opts.offsetFromProjectRoot}tsconfig.json` : getRelativePathToRootTsConfig(tree, projectConfig.root), + linter: isEslintInstalled(tree) ? 'eslint' : 'none', ext: '', }; @@ -54,6 +56,15 @@ export function addBaseCypressSetup( templateVars ); + generateFiles( + tree, + isUsingTsSolutionSetup(tree) + ? join(__dirname, 'files/tsconfig/ts-solution') + : join(__dirname, 'files/tsconfig/non-ts-solution'), + projectConfig.root, + templateVars + ); + if (options.js) { if (isEsmProject(tree, projectConfig.root)) { generateFiles( @@ -144,3 +155,8 @@ function isEsmProject(tree: Tree, projectRoot: string) { } return packageJson.type === 'module'; } + +function isEslintInstalled(tree: Tree): boolean { + const { dependencies, devDependencies } = readJson(tree, 'package.json'); + return !!(dependencies?.eslint || devDependencies?.eslint); +} diff --git a/packages/cypress/src/generators/base-setup/files/common/__directory__/support/commands.ts__ext__ b/packages/cypress/src/generators/base-setup/files/common/__directory__/support/commands.ts__ext__ index 699d750ffd313..a8c9e67f2f59c 100644 --- a/packages/cypress/src/generators/base-setup/files/common/__directory__/support/commands.ts__ext__ +++ b/packages/cypress/src/generators/base-setup/files/common/__directory__/support/commands.ts__ext__ @@ -10,11 +10,13 @@ // https://on.cypress.io/custom-commands // *********************************************** -// eslint-disable-next-line @typescript-eslint/no-namespace -declare namespace Cypress { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - interface Chainable { - login(email: string, password: string): void; +declare global {<% if (linter === 'eslint') { %> + // eslint-disable-next-line @typescript-eslint/no-namespace<% } %> + namespace Cypress {<% if (linter === 'eslint') { %> + // eslint-disable-next-line @typescript-eslint/no-unused-vars<% } %> + interface Chainable { + login(email: string, password: string): void; + } } } diff --git a/packages/cypress/src/generators/base-setup/files/config-js-cjs/__directory__/support/commands.js__ext__ b/packages/cypress/src/generators/base-setup/files/config-js-cjs/__directory__/support/commands.js__ext__ index 699d750ffd313..a8c9e67f2f59c 100644 --- a/packages/cypress/src/generators/base-setup/files/config-js-cjs/__directory__/support/commands.js__ext__ +++ b/packages/cypress/src/generators/base-setup/files/config-js-cjs/__directory__/support/commands.js__ext__ @@ -10,11 +10,13 @@ // https://on.cypress.io/custom-commands // *********************************************** -// eslint-disable-next-line @typescript-eslint/no-namespace -declare namespace Cypress { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - interface Chainable { - login(email: string, password: string): void; +declare global {<% if (linter === 'eslint') { %> + // eslint-disable-next-line @typescript-eslint/no-namespace<% } %> + namespace Cypress {<% if (linter === 'eslint') { %> + // eslint-disable-next-line @typescript-eslint/no-unused-vars<% } %> + interface Chainable { + login(email: string, password: string): void; + } } } diff --git a/packages/cypress/src/generators/base-setup/files/config-js-esm/__directory__/support/commands.js__ext__ b/packages/cypress/src/generators/base-setup/files/config-js-esm/__directory__/support/commands.js__ext__ index 699d750ffd313..a8c9e67f2f59c 100644 --- a/packages/cypress/src/generators/base-setup/files/config-js-esm/__directory__/support/commands.js__ext__ +++ b/packages/cypress/src/generators/base-setup/files/config-js-esm/__directory__/support/commands.js__ext__ @@ -10,11 +10,13 @@ // https://on.cypress.io/custom-commands // *********************************************** -// eslint-disable-next-line @typescript-eslint/no-namespace -declare namespace Cypress { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - interface Chainable { - login(email: string, password: string): void; +declare global {<% if (linter === 'eslint') { %> + // eslint-disable-next-line @typescript-eslint/no-namespace<% } %> + namespace Cypress {<% if (linter === 'eslint') { %> + // eslint-disable-next-line @typescript-eslint/no-unused-vars<% } %> + interface Chainable { + login(email: string, password: string): void; + } } } diff --git a/packages/cypress/src/generators/base-setup/files/config-ts/__directory__/support/commands.ts__ext__ b/packages/cypress/src/generators/base-setup/files/config-ts/__directory__/support/commands.ts__ext__ index 699d750ffd313..a8c9e67f2f59c 100644 --- a/packages/cypress/src/generators/base-setup/files/config-ts/__directory__/support/commands.ts__ext__ +++ b/packages/cypress/src/generators/base-setup/files/config-ts/__directory__/support/commands.ts__ext__ @@ -10,11 +10,13 @@ // https://on.cypress.io/custom-commands // *********************************************** -// eslint-disable-next-line @typescript-eslint/no-namespace -declare namespace Cypress { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - interface Chainable { - login(email: string, password: string): void; +declare global {<% if (linter === 'eslint') { %> + // eslint-disable-next-line @typescript-eslint/no-namespace<% } %> + namespace Cypress {<% if (linter === 'eslint') { %> + // eslint-disable-next-line @typescript-eslint/no-unused-vars<% } %> + interface Chainable { + login(email: string, password: string): void; + } } } diff --git a/packages/cypress/src/generators/base-setup/files/common/__directory__/tsconfig.json__ext__ b/packages/cypress/src/generators/base-setup/files/tsconfig/non-ts-solution/__directory__/tsconfig.json__ext__ similarity index 87% rename from packages/cypress/src/generators/base-setup/files/common/__directory__/tsconfig.json__ext__ rename to packages/cypress/src/generators/base-setup/files/tsconfig/non-ts-solution/__directory__/tsconfig.json__ext__ index ff84d2055f45b..5aa3759b0df57 100644 --- a/packages/cypress/src/generators/base-setup/files/common/__directory__/tsconfig.json__ext__ +++ b/packages/cypress/src/generators/base-setup/files/tsconfig/non-ts-solution/__directory__/tsconfig.json__ext__ @@ -12,7 +12,7 @@ "**/*.js", "<%= offsetFromProjectRoot %>cypress.config.ts", "<%= offsetFromProjectRoot %>**/*.cy.ts", - <%_ if (jsx) { _%> "<%= offsetFromProjectRoot %>**/*.cy.tsx",<%_ } _%> + <%_ if (jsx) { _%>"<%= offsetFromProjectRoot %>**/*.cy.tsx",<%_ } _%> "<%= offsetFromProjectRoot %>**/*.cy.js", <%_ if (jsx) { _%>"<%= offsetFromProjectRoot %>**/*.cy.jsx",<%_ } _%> "<%= offsetFromProjectRoot %>**/*.d.ts" diff --git a/packages/cypress/src/generators/base-setup/files/tsconfig/ts-solution/__directory__/tsconfig.json__ext__ b/packages/cypress/src/generators/base-setup/files/tsconfig/ts-solution/__directory__/tsconfig.json__ext__ new file mode 100644 index 0000000000000..871e69aadc0b9 --- /dev/null +++ b/packages/cypress/src/generators/base-setup/files/tsconfig/ts-solution/__directory__/tsconfig.json__ext__ @@ -0,0 +1,20 @@ +{ + "extends": "<%= tsConfigPath %>", + "compilerOptions": { + "outDir": "dist", + "tsBuildInfoFile": "dist/tsconfig.tsbuildinfo", + "allowJs": true, + "types": ["cypress", "node"], + "sourceMap": false + }, + "include": [ + "**/*.ts", + "**/*.js", + "<%= offsetFromProjectRoot %>cypress.config.ts", + "<%= offsetFromProjectRoot %>**/*.cy.ts", + <%_ if (jsx) { _%>"<%= offsetFromProjectRoot %>**/*.cy.tsx",<%_ } _%> + "<%= offsetFromProjectRoot %>**/*.cy.js", + <%_ if (jsx) { _%>"<%= offsetFromProjectRoot %>**/*.cy.jsx",<%_ } _%> + "<%= offsetFromProjectRoot %>**/*.d.ts" + ] +} diff --git a/packages/cypress/src/generators/component-configuration/component-configuration.ts b/packages/cypress/src/generators/component-configuration/component-configuration.ts index 9407684d86054..0b36d8f929da2 100644 --- a/packages/cypress/src/generators/component-configuration/component-configuration.ts +++ b/packages/cypress/src/generators/component-configuration/component-configuration.ts @@ -13,6 +13,7 @@ import { updateNxJson, runTasksInSerial, GeneratorCallback, + readJson, } from '@nx/devkit'; import { assertNotUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup'; import { installedCypressVersion } from '../../utils/cypress-version'; @@ -139,6 +140,7 @@ function addProjectFiles( ...opts, projectRoot: projectConfig.root, offsetFromRoot: offsetFromRoot(projectConfig.root), + linter: isEslintInstalled(tree) ? 'eslint' : 'none', ext: '', } ); @@ -255,4 +257,9 @@ export function updateTsConfigForComponentTesting( } } +function isEslintInstalled(tree: Tree): boolean { + const { dependencies, devDependencies } = readJson(tree, 'package.json'); + return !!(dependencies?.eslint || devDependencies?.eslint); +} + export default componentConfigurationGenerator; diff --git a/packages/cypress/src/generators/component-configuration/files/__directory__/support/component.ts__ext__ b/packages/cypress/src/generators/component-configuration/files/__directory__/support/component.ts__ext__ index 5a92ae0c384f9..fe952148a2c34 100644 --- a/packages/cypress/src/generators/component-configuration/files/__directory__/support/component.ts__ext__ +++ b/packages/cypress/src/generators/component-configuration/files/__directory__/support/component.ts__ext__ @@ -17,11 +17,10 @@ import './commands'; // add component testing only related command here, such as mount -declare global { -// eslint-disable-next-line @typescript-eslint/no-namespace - namespace Cypress { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - interface Chainable { - } +declare global {<% if (linter === 'eslint') { %> + // eslint-disable-next-line @typescript-eslint/no-namespace<% } %> + namespace Cypress {<% if (linter === 'eslint') { %> + // eslint-disable-next-line @typescript-eslint/no-unused-vars<% } %> + interface Chainable {} } } diff --git a/packages/cypress/src/generators/configuration/configuration.ts b/packages/cypress/src/generators/configuration/configuration.ts index c2620fd8b3cc2..c93aae1368192 100644 --- a/packages/cypress/src/generators/configuration/configuration.ts +++ b/packages/cypress/src/generators/configuration/configuration.ts @@ -5,6 +5,7 @@ import { generateFiles, GeneratorCallback, joinPathFragments, + logger, offsetFromRoot, parseTargetString, ProjectConfiguration, @@ -16,13 +17,21 @@ import { Tree, updateJson, updateProjectConfiguration, + writeJson, } from '@nx/devkit'; +import { resolveImportPath } from '@nx/devkit/src/generators/project-name-and-root-utils'; +import { promptWhenInteractive } from '@nx/devkit/src/generators/prompt'; import { Linter, LinterType } from '@nx/eslint'; import { getRelativePathToRootTsConfig, initGenerator as jsInitGenerator, } from '@nx/js'; -import { assertNotUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup'; +import { + getProjectPackageManagerWorkspaceState, + getProjectPackageManagerWorkspaceStateWarningTask, +} from '@nx/js/src/utils/package-manager-workspaces'; +import { isUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup'; +import { PackageJson } from 'nx/src/utils/package-json'; import { join } from 'path'; import { addLinterToCyProject } from '../../utils/add-linter'; import { addDefaultE2EConfig } from '../../utils/config'; @@ -52,7 +61,7 @@ export interface CypressE2EConfigSchema { addPlugin?: boolean; } -type NormalizedSchema = ReturnType; +type NormalizedSchema = Awaited>; export function configurationGenerator( tree: Tree, @@ -68,10 +77,7 @@ export async function configurationGeneratorInternal( tree: Tree, options: CypressE2EConfigSchema ) { - assertNotUsingTsSolutionSetup(tree, 'cypress', 'configuration'); - - const opts = normalizeOptions(tree, options); - opts.addPlugin ??= process.env.NX_ADD_PLUGINS !== 'false'; + const opts = await normalizeOptions(tree, options); const tasks: GeneratorCallback[] = []; const projectGraph = await createProjectGraphAsync(); @@ -99,6 +105,22 @@ export async function configurationGeneratorInternal( addTarget(tree, opts, projectGraph); } + const { root: projectRoot } = readProjectConfiguration(tree, options.project); + const isTsSolutionSetup = isUsingTsSolutionSetup(tree); + if (isTsSolutionSetup) { + createPackageJson(tree, opts); + ignoreTestOutput(tree); + + if (!options.rootProject) { + // add the project tsconfig to the workspace root tsconfig.json references + updateJson(tree, 'tsconfig.json', (json) => { + json.references ??= []; + json.references.push({ path: './' + projectRoot }); + return json; + }); + } + } + const linterTask = await addLinterToCyProject(tree, { ...opts, cypressDir: opts.directory, @@ -113,6 +135,20 @@ export async function configurationGeneratorInternal( await formatFiles(tree); } + if (isTsSolutionSetup) { + const projectPackageManagerWorkspaceState = + getProjectPackageManagerWorkspaceState(tree, projectRoot); + + if (projectPackageManagerWorkspaceState !== 'included') { + tasks.push( + getProjectPackageManagerWorkspaceStateWarningTask( + projectPackageManagerWorkspaceState, + tree.root + ) + ); + } + } + return runTasksInSerial(...tasks); } @@ -128,7 +164,30 @@ function ensureDependencies(tree: Tree, options: NormalizedSchema) { return addDependenciesToPackageJson(tree, {}, devDependencies); } -function normalizeOptions(tree: Tree, options: CypressE2EConfigSchema) { +async function normalizeOptions(tree: Tree, options: CypressE2EConfigSchema) { + const isTsSolutionSetup = isUsingTsSolutionSetup(tree); + + let linter = options.linter; + if (!linter) { + const choices = isTsSolutionSetup + ? [{ name: 'none' }, { name: 'eslint' }] + : [{ name: 'eslint' }, { name: 'none' }]; + const defaultValue = isTsSolutionSetup ? 'none' : 'eslint'; + + linter = await promptWhenInteractive<{ + linter: 'none' | 'eslint'; + }>( + { + type: 'select', + name: 'linter', + message: `Which linter would you like to use?`, + choices, + initial: 0, + }, + { linter: defaultValue } + ).then(({ linter }) => linter); + } + const projectConfig: ProjectConfiguration | undefined = readProjectConfiguration(tree, options.project); if (projectConfig?.targets?.e2e) { @@ -164,7 +223,7 @@ In this case you need to provide a devServerTarget,':[: ...options, bundler: options.bundler ?? 'webpack', rootProject: options.rootProject ?? projectConfig.root === '.', - linter: options.linter ?? Linter.EsLint, + linter, devServerTarget, }; } @@ -352,4 +411,40 @@ function addTarget( updateProjectConfiguration(tree, opts.project, projectConfig); } +function createPackageJson(tree: Tree, options: NormalizedSchema) { + const projectConfig = readProjectConfiguration(tree, options.project); + const packageJsonPath = joinPathFragments(projectConfig.root, 'package.json'); + + if (tree.exists(packageJsonPath)) { + return; + } + + const importPath = resolveImportPath( + tree, + projectConfig.name, + projectConfig.root + ); + + const packageJson: PackageJson = { + name: importPath, + version: '0.0.1', + private: true, + }; + writeJson(tree, packageJsonPath, packageJson); +} + +function ignoreTestOutput(tree: Tree): void { + if (!tree.exists('.gitignore')) { + logger.warn(`Couldn't find a root .gitignore file to update.`); + } + + let content = tree.read('.gitignore', 'utf-8'); + if (/^test-output$/gm.test(content)) { + return; + } + + content = `${content}\ntest-output\n`; + tree.write('.gitignore', content); +} + export default configurationGenerator; diff --git a/packages/cypress/src/generators/configuration/schema.json b/packages/cypress/src/generators/configuration/schema.json index 8dc9aa4e84e59..cf2bf6405612b 100644 --- a/packages/cypress/src/generators/configuration/schema.json +++ b/packages/cypress/src/generators/configuration/schema.json @@ -46,8 +46,8 @@ "linter": { "description": "The tool to use for running lint checks.", "type": "string", - "enum": ["eslint", "none"], - "default": "eslint" + "enum": ["none", "eslint"], + "x-priority": "important" }, "js": { "description": "Generate JavaScript files rather than TypeScript files.", diff --git a/packages/cypress/src/generators/init/init.ts b/packages/cypress/src/generators/init/init.ts index 89a2d2495a53c..b8deed06a0569 100644 --- a/packages/cypress/src/generators/init/init.ts +++ b/packages/cypress/src/generators/init/init.ts @@ -11,7 +11,6 @@ import { updateNxJson, } from '@nx/devkit'; import { addPlugin as _addPlugin } from '@nx/devkit/src/utils/add-plugin'; -import { assertNotUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup'; import { createNodesV2 } from '../../plugins/plugin'; import { cypressVersion, nxVersion } from '../../utils/versions'; import { Schema } from './schema'; @@ -106,8 +105,6 @@ export async function cypressInitGeneratorInternal( tree: Tree, options: Schema ) { - assertNotUsingTsSolutionSetup(tree, 'cypress', 'init'); - updateProductionFileset(tree); const nxJson = readNxJson(tree);