@nx/cypress
Version:
249 lines (247 loc) • 12 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.findCypressConfigs = findCypressConfigs;
exports.createNewCypressConfig = createNewCypressConfig;
exports.createSupportFileImport = createSupportFileImport;
exports.updateProjectPaths = updateProjectPaths;
exports.updateImports = updateImports;
exports.writeNewConfig = writeNewConfig;
exports.addConfigToTsConfig = addConfigToTsConfig;
exports.updatePluginFile = updatePluginFile;
const devkit_1 = require("@nx/devkit");
const ensure_typescript_1 = require("@nx/js/src/utils/typescript/ensure-typescript");
const ts_solution_setup_1 = require("@nx/js/src/utils/typescript/ts-solution-setup");
const path_1 = require("path");
let tsModule;
let tsquery;
const validFilesEndingsToUpdate = [
'.js',
'.jsx',
'.ts',
'.tsx',
'.mjs',
'.cjs',
];
function findCypressConfigs(tree, projectConfig, target, config) {
const cypressConfigPathJson = projectConfig.targets[target]?.configurations?.[config]?.cypressConfig ||
projectConfig.targets[target]?.options?.cypressConfig;
// make sure it's a json file, since it could have been updated to ts file from previous configuration migration
if (!cypressConfigPathJson || (0, path_1.extname)(cypressConfigPathJson) !== '.json') {
return {
cypressConfigPathJson: null,
cypressConfigPathTs: null,
};
}
const cypressConfigPathTs = (0, devkit_1.joinPathFragments)((0, path_1.dirname)(cypressConfigPathJson),
// create matching ts config for custom cypress config if present
cypressConfigPathJson?.endsWith('cypress.json')
? 'cypress.config.ts' //default
: `${(0, path_1.basename)(cypressConfigPathJson, (0, path_1.extname)(cypressConfigPathJson))}.config.ts`);
return {
cypressConfigPathJson,
cypressConfigPathTs,
};
}
/**
* update the existing cypress.json config to the new cypress.config.ts structure.
* return both the old and new configs
*/
function createNewCypressConfig(tree, projectConfig, cypressConfigPathJson) {
const cypressConfigJson = (0, devkit_1.readJson)(tree, cypressConfigPathJson);
const { modifyObstructiveCode = null, // no longer needed in configs
integrationFolder = 'src/e2e', // provide the new defaults if the value isn't present
supportFile = 'src/support/e2e.ts', ...restOfConfig } = cypressConfigJson;
const sourceRoot = (0, ts_solution_setup_1.getProjectSourceRoot)(projectConfig, tree);
const newIntegrationFolder = tree.exists((0, devkit_1.joinPathFragments)(sourceRoot, 'integration'))
? 'src/e2e'
: integrationFolder;
const cypressConfigTs = {
e2e: {
...restOfConfig,
specPattern: `${newIntegrationFolder}/**/*.cy.{js,jsx,ts,tsx}`,
// if supportFile is defined (can be false if not using it) and in the default location (or in the new default location),
// then use the new default location.
// otherwise we will use the existing folder location/falsey value
supportFile: (supportFile &&
tree.exists((0, devkit_1.joinPathFragments)(sourceRoot, 'support', 'index.ts'))) ||
tree.exists((0, devkit_1.joinPathFragments)(sourceRoot, 'support', 'e2e.ts'))
? 'src/support/e2e.ts'
: supportFile,
// if the default location is used then will update to the new location otherwise keep the custom location
// this is used down the line, but won't be in the final config file since it's a deprecated option
integrationFolder: newIntegrationFolder,
},
};
return { cypressConfigTs, cypressConfigJson };
}
function createSupportFileImport(oldSupportFilePath, newSupportFilePath, projectSourceRoot) {
// need to get the new import path for the support file.
// before it was "<relative path>/support/index.ts" and the new path will be "<relative path>/support/e2e.ts"
// i.e. take ../support => ../support/e2e.ts
// 1. take apps/app-e2e/support/index.ts => support (this cant have a / in it. must grab the leaf for tsquery)
// 2. if the leaf value is index.ts then grab the parent directory (i.e. so we have support/index.ts => support)
// 3. take apps/app-e2e/support/e2e.ts => support/e2e
// apps/app-e2e/support/e2e.ts => support/e2e
const newFileExt = (0, path_1.extname)(newSupportFilePath);
const newImportPathLeaf = (0, path_1.relative)(projectSourceRoot, newSupportFilePath).replace(newFileExt, '');
// apps/app-e2e/support/index.ts => support/index
const oldFileExt = (0, path_1.extname)(oldSupportFilePath);
const oldImportPathLeaf = (0, path_1.relative)(projectSourceRoot, oldSupportFilePath).replace(oldFileExt, '');
// support/index => support
const oldRelativeImportPath = (0, path_1.basename)(oldImportPathLeaf, oldFileExt);
return {
newImportPathLeaf,
// don't import from 'support/index' it's just 'support'
oldImportPathLeaf: oldRelativeImportPath === 'index'
? (0, path_1.dirname)(oldImportPathLeaf)
: oldImportPathLeaf,
};
}
function updateProjectPaths(tree, projectConfig, { cypressConfigTs, cypressConfigJson, }) {
const { integrationFolder, supportFile } = cypressConfigTs['e2e'];
const sourceRoot = (0, ts_solution_setup_1.getProjectSourceRoot)(projectConfig, tree);
const oldIntegrationFolder = (0, devkit_1.joinPathFragments)(projectConfig.root, cypressConfigJson.integrationFolder);
const newIntegrationFolder = (0, devkit_1.joinPathFragments)(projectConfig.root, integrationFolder);
let newSupportFile;
let oldSupportFile;
let oldImportLeafPath;
let newImportLeafPath;
let shouldUpdateSupportFileImports = false;
// supportFile can be falsey or a string path to the file
if (cypressConfigJson.supportFile) {
// we need to check the test files to see if
// support file import is used and then update it if so
shouldUpdateSupportFileImports = true;
oldSupportFile = (0, devkit_1.joinPathFragments)(projectConfig.root, cypressConfigJson.supportFile);
newSupportFile = (0, devkit_1.joinPathFragments)(projectConfig.root, supportFile);
// oldSupportFile might have already been updated
// to the new default location so must be guarded for
if (oldSupportFile !== newSupportFile && tree.exists(oldSupportFile)) {
tree.rename(oldSupportFile, newSupportFile);
}
}
else {
shouldUpdateSupportFileImports = false;
newSupportFile = supportFile;
// rename the default support file even if not in use to keep the system in sync with cypress v10
const defaultSupportFile = (0, devkit_1.joinPathFragments)(sourceRoot, 'support', 'index.ts');
if (tree.exists(defaultSupportFile)) {
const newSupportDefaultPath = (0, devkit_1.joinPathFragments)(sourceRoot, 'support', 'e2e.ts');
if (defaultSupportFile !== newSupportDefaultPath &&
tree.exists(defaultSupportFile)) {
tree.rename(defaultSupportFile, newSupportDefaultPath);
}
}
}
if (shouldUpdateSupportFileImports) {
const newImportPaths = createSupportFileImport(oldSupportFile, newSupportFile, sourceRoot);
oldImportLeafPath = newImportPaths.oldImportPathLeaf;
newImportLeafPath = newImportPaths.newImportPathLeaf;
}
// tree.rename doesn't work on directories must update each file within
// the directory to the new directory
(0, devkit_1.visitNotIgnoredFiles)(tree, sourceRoot, (path) => {
const normalizedPath = (0, devkit_1.normalizePath)(path);
if (!normalizedPath.includes(oldIntegrationFolder)) {
return;
}
const fileName = (0, path_1.basename)(normalizedPath);
let newPath = normalizedPath.replace(oldIntegrationFolder, newIntegrationFolder);
if (fileName.includes('.spec.')) {
newPath = newPath.replace('.spec.', '.cy.');
}
if (newPath !== normalizedPath && tree.exists(normalizedPath)) {
tree.rename(normalizedPath, newPath);
}
// if they weren't using the supportFile then there is no need to update the imports.
if (shouldUpdateSupportFileImports &&
validFilesEndingsToUpdate.some((e) => path.endsWith(e))) {
updateImports(tree, newPath, oldImportLeafPath, newImportLeafPath);
}
});
if (tree.children(oldIntegrationFolder).length === 0) {
tree.delete(oldIntegrationFolder);
}
}
function updateImports(tree, filePath, oldImportPath, newImportPath) {
if (!tsModule) {
tsModule = (0, ensure_typescript_1.ensureTypescript)();
}
if (!tsquery) {
(0, ensure_typescript_1.ensureTypescript)();
tsquery = require('@phenomnomnominal/tsquery').tsquery;
}
const { isCallExpression, isExportDeclaration, isImportDeclaration } = tsModule;
const endOfImportSelector = `StringLiteral[value=/${oldImportPath}$/]`;
const fileContent = tree.read(filePath, 'utf-8');
const newContent = tsquery.replace(fileContent, endOfImportSelector, (node) => {
// if node.parent is an CallExpression require() ||ImportDeclaration
if (node?.parent &&
(isCallExpression(node.parent) ||
isImportDeclaration(node.parent) ||
isExportDeclaration(node.parent))) {
return `'${node.text.replace(oldImportPath, newImportPath)}'`;
}
return node.text;
});
tree.write(filePath, newContent);
}
function writeNewConfig(tree, cypressConfigPathTs, cypressConfigs) {
// remove deprecated configs options
const { pluginsFile = false, integrationFolder = '', ...restOfConfig } = cypressConfigs.cypressConfigTs.e2e;
const pluginImport = pluginsFile
? `import setupNodeEvents from '${pluginsFile}';`
: '';
const convertedConfig = JSON.stringify(restOfConfig, null, 2);
tree.write(cypressConfigPathTs, (0, devkit_1.stripIndents) `
import { defineConfig } from 'cypress'
import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset';
${pluginImport}
const cypressJsonConfig = ${convertedConfig}
export default defineConfig({
e2e: {
...nxE2EPreset(__dirname),
...cypressJsonConfig,
${pluginsFile ? 'setupNodeEvents' : ''}
}
})
`);
}
function addConfigToTsConfig(tree, tsconfigPath, cypressConfigPath) {
if (tree.exists(tsconfigPath)) {
(0, devkit_1.updateJson)(tree, tsconfigPath, (json) => {
json.include = Array.from(new Set([
...(json.include || []),
(0, path_1.relative)((0, path_1.dirname)(tsconfigPath), cypressConfigPath),
]));
return json;
}, { expectComments: true });
}
}
function updatePluginFile(tree, projectConfig, cypressConfigs) {
// if ts file change module.exports = to export default
// if js file don't do anything
// update cypressConfigTs.e2e to remove file extension
const pluginsFile = cypressConfigs.cypressConfigTs?.e2e?.pluginsFile;
if (!pluginsFile) {
return cypressConfigs;
}
const ext = (0, path_1.extname)(pluginsFile);
const updatedCypressConfigs = {
...cypressConfigs,
cypressConfigTs: {
e2e: {
...cypressConfigs.cypressConfigTs.e2e,
pluginsFile: pluginsFile.replace(ext, ''),
},
},
};
const pluginFilePath = (0, devkit_1.joinPathFragments)(projectConfig.root, pluginsFile);
if (ext === '.ts' && tree.exists(pluginFilePath)) {
const pluginFileContent = tree.read(pluginFilePath, 'utf-8');
tree.write(pluginFilePath, pluginFileContent
.replace('module.exports =', 'export default')
.replace(/module\.exports\.(.*?)=/g, 'export const $1='));
}
return updatedCypressConfigs;
}