UNPKG

@nx/cypress

Version:

The Nx Plugin for Cypress contains executors and generators allowing your workspace to use the powerful Cypress integration testing capabilities.

249 lines (247 loc) • 12 kB
"use strict"; 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; }