236 lines
6.5 KiB
TypeScript
236 lines
6.5 KiB
TypeScript
import {
|
|
addDependenciesToPackageJson,
|
|
formatFiles,
|
|
generateFiles,
|
|
GeneratorCallback,
|
|
joinPathFragments,
|
|
offsetFromRoot,
|
|
parseTargetString,
|
|
readProjectConfiguration,
|
|
runTasksInSerial,
|
|
toJS,
|
|
Tree,
|
|
updateJson,
|
|
updateProjectConfiguration,
|
|
} from '@nx/devkit';
|
|
import { getRelativePathToRootTsConfig } from '@nx/js';
|
|
import { Linter } from '@nx/eslint';
|
|
import { join } from 'path';
|
|
import { addLinterToCyProject } from '../../utils/add-linter';
|
|
import { addDefaultE2EConfig } from '../../utils/config';
|
|
import { installedCypressVersion } from '../../utils/cypress-version';
|
|
import { viteVersion } from '../../utils/versions';
|
|
import cypressInitGenerator from '../init/init';
|
|
import { addBaseCypressSetup } from '../base-setup/base-setup';
|
|
|
|
export interface CypressE2EConfigSchema {
|
|
project: string;
|
|
baseUrl?: string;
|
|
directory?: string;
|
|
js?: boolean;
|
|
skipFormat?: boolean;
|
|
setParserOptionsProject?: boolean;
|
|
skipPackageJson?: boolean;
|
|
bundler?: 'webpack' | 'vite' | 'none';
|
|
devServerTarget?: string;
|
|
linter?: Linter;
|
|
port?: number | 'cypress-auto';
|
|
jsx?: boolean;
|
|
}
|
|
|
|
type NormalizedSchema = ReturnType<typeof normalizeOptions>;
|
|
|
|
export async function configurationGenerator(
|
|
tree: Tree,
|
|
options: CypressE2EConfigSchema
|
|
) {
|
|
const opts = normalizeOptions(tree, options);
|
|
|
|
const tasks: GeneratorCallback[] = [];
|
|
|
|
if (!installedCypressVersion()) {
|
|
tasks.push(await cypressInitGenerator(tree, opts));
|
|
}
|
|
if (opts.bundler === 'vite') {
|
|
tasks.push(addDependenciesToPackageJson(tree, {}, { vite: viteVersion }));
|
|
}
|
|
await addFiles(tree, opts);
|
|
addTarget(tree, opts);
|
|
|
|
const linterTask = await addLinterToCyProject(tree, {
|
|
...opts,
|
|
cypressDir: opts.directory,
|
|
});
|
|
tasks.push(linterTask);
|
|
|
|
if (!opts.skipFormat) {
|
|
await formatFiles(tree);
|
|
}
|
|
|
|
return runTasksInSerial(...tasks);
|
|
}
|
|
|
|
function normalizeOptions(tree: Tree, options: CypressE2EConfigSchema) {
|
|
const projectConfig = readProjectConfiguration(tree, options.project);
|
|
if (projectConfig?.targets?.e2e) {
|
|
throw new Error(`Project ${options.project} already has an e2e target.
|
|
Rename or remove the existing e2e target.`);
|
|
}
|
|
|
|
if (
|
|
!options.baseUrl &&
|
|
!options.devServerTarget &&
|
|
!projectConfig.targets.serve
|
|
) {
|
|
throw new Error(`The project ${options.project} does not have a 'serve' target.
|
|
In this case you need to provide a devServerTarget,'<projectName>:<targetName>[:<configName>]', or a baseUrl option`);
|
|
}
|
|
|
|
options.directory ??= 'src';
|
|
|
|
return {
|
|
...options,
|
|
bundler: options.bundler ?? 'webpack',
|
|
rootProject: projectConfig.root === '.',
|
|
linter: options.linter ?? Linter.EsLint,
|
|
devServerTarget:
|
|
options.devServerTarget ??
|
|
(projectConfig.targets.serve ? `${options.project}:serve` : undefined),
|
|
};
|
|
}
|
|
|
|
async function addFiles(tree: Tree, options: NormalizedSchema) {
|
|
const projectConfig = readProjectConfiguration(tree, options.project);
|
|
const cyVersion = installedCypressVersion();
|
|
const filesToUse = cyVersion && cyVersion < 10 ? 'v9' : 'v10';
|
|
|
|
const hasTsConfig = tree.exists(
|
|
joinPathFragments(projectConfig.root, 'tsconfig.json')
|
|
);
|
|
const offsetFromProjectRoot = options.directory
|
|
.split('/')
|
|
.map((_) => '..')
|
|
.join('/');
|
|
|
|
const fileOpts = {
|
|
...options,
|
|
dir: options.directory ?? 'src',
|
|
ext: options.js ? 'js' : 'ts',
|
|
offsetFromRoot: offsetFromRoot(projectConfig.root),
|
|
offsetFromProjectRoot,
|
|
projectRoot: projectConfig.root,
|
|
tsConfigPath: hasTsConfig
|
|
? `${offsetFromProjectRoot}/tsconfig.json`
|
|
: getRelativePathToRootTsConfig(tree, projectConfig.root),
|
|
tmpl: '',
|
|
};
|
|
|
|
generateFiles(
|
|
tree,
|
|
join(__dirname, 'files', filesToUse),
|
|
projectConfig.root,
|
|
fileOpts
|
|
);
|
|
|
|
if (filesToUse === 'v10') {
|
|
addBaseCypressSetup(tree, {
|
|
project: options.project,
|
|
directory: options.directory,
|
|
jsx: options.jsx,
|
|
});
|
|
|
|
const cyFile = joinPathFragments(projectConfig.root, 'cypress.config.ts');
|
|
|
|
const updatedCyConfig = await addDefaultE2EConfig(
|
|
tree.read(cyFile, 'utf-8'),
|
|
{
|
|
directory: options.directory,
|
|
bundler: options.bundler,
|
|
}
|
|
);
|
|
|
|
tree.write(cyFile, updatedCyConfig);
|
|
}
|
|
|
|
if (
|
|
cyVersion &&
|
|
cyVersion < 7 &&
|
|
tree.exists(
|
|
joinPathFragments(projectConfig.root, 'src', 'plugins', 'index.js')
|
|
)
|
|
) {
|
|
updateJson(tree, join(projectConfig.root, 'cypress.json'), (json) => {
|
|
json.pluginsFile = './src/plugins/index';
|
|
return json;
|
|
});
|
|
} else if (cyVersion < 10) {
|
|
const pluginPath = join(projectConfig.root, 'src/plugins/index.js');
|
|
if (tree.exists(pluginPath)) {
|
|
tree.delete(pluginPath);
|
|
}
|
|
}
|
|
|
|
if (options.js) {
|
|
toJS(tree);
|
|
}
|
|
}
|
|
|
|
function addTarget(tree: Tree, opts: NormalizedSchema) {
|
|
const projectConfig = readProjectConfiguration(tree, opts.project);
|
|
const cyVersion = installedCypressVersion();
|
|
projectConfig.targets.e2e = {
|
|
executor: '@nx/cypress:cypress',
|
|
options: {
|
|
cypressConfig: joinPathFragments(
|
|
projectConfig.root,
|
|
cyVersion && cyVersion < 10 ? 'cypress.json' : 'cypress.config.ts'
|
|
),
|
|
testingType: 'e2e',
|
|
},
|
|
};
|
|
if (opts.baseUrl) {
|
|
projectConfig.targets.e2e.options = {
|
|
...projectConfig.targets.e2e.options,
|
|
baseUrl: opts.baseUrl,
|
|
};
|
|
} else if (opts.devServerTarget) {
|
|
const parsedTarget = parseTargetString(opts.devServerTarget);
|
|
|
|
projectConfig.targets.e2e.options = {
|
|
...projectConfig.targets.e2e.options,
|
|
devServerTarget: opts.devServerTarget,
|
|
port: opts.port,
|
|
};
|
|
|
|
const devServerProjectConfig = readProjectConfiguration(
|
|
tree,
|
|
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']) {
|
|
projectConfig.targets.e2e.configurations ??= {};
|
|
projectConfig.targets.e2e.configurations.ci = {
|
|
devServerTarget: `${parsedTarget.project}:serve-static`,
|
|
};
|
|
}
|
|
} else {
|
|
throw new Error('Either baseUrl or devServerTarget must be provided');
|
|
}
|
|
|
|
updateProjectConfiguration(tree, opts.project, projectConfig);
|
|
}
|
|
|
|
export default configurationGenerator;
|