feat(testing): add support for the ts solution config setup to the cypress plugin (#28637)
<!-- Please make sure you have read the submission guidelines before posting an PR --> <!-- https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr --> <!-- Please make sure that your commit message follows our format --> <!-- Example: `fix(nx): must begin with lowercase` --> <!-- If this is a particularly complex change or feature addition, you can request a dedicated Nx release for this pull request branch. Mention someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they will confirm if the PR warrants its own release for testing purposes, and generate it for you if appropriate. --> ## Current Behavior <!-- This is the behavior we have today --> ## Expected Behavior <!-- This is the behavior we should expect with the changes in this PR --> ## Related Issue(s) <!-- Please link the issue being fixed so it gets closed when this is merged. --> Fixes #
This commit is contained in:
parent
c2e31127d9
commit
6af298d0a9
@ -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.",
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -10,13 +10,15 @@
|
||||
// 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
|
||||
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<Subject> {
|
||||
login(email: string, password: string): void;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -- This is a parent command --
|
||||
Cypress.Commands.add('login', (email, password) => {
|
||||
|
||||
@ -10,13 +10,15 @@
|
||||
// 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
|
||||
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<Subject> {
|
||||
login(email: string, password: string): void;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -- This is a parent command --
|
||||
Cypress.Commands.add('login', (email, password) => {
|
||||
|
||||
@ -10,13 +10,15 @@
|
||||
// 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
|
||||
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<Subject> {
|
||||
login(email: string, password: string): void;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -- This is a parent command --
|
||||
Cypress.Commands.add('login', (email, password) => {
|
||||
|
||||
@ -10,13 +10,15 @@
|
||||
// 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
|
||||
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<Subject> {
|
||||
login(email: string, password: string): void;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -- This is a parent command --
|
||||
Cypress.Commands.add('login', (email, password) => {
|
||||
|
||||
@ -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"
|
||||
]
|
||||
}
|
||||
@ -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;
|
||||
|
||||
@ -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<Subject> {
|
||||
}
|
||||
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<Subject> {}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<typeof normalizeOptions>;
|
||||
type NormalizedSchema = Awaited<ReturnType<typeof normalizeOptions>>;
|
||||
|
||||
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,'<projectName>:<targetName>[:
|
||||
...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;
|
||||
|
||||
@ -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.",
|
||||
|
||||
@ -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);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user