From f74ff4bddcc7b43b646096672f34b73e8ef643ab Mon Sep 17 00:00:00 2001 From: Caleb Ukle Date: Wed, 21 Jun 2023 10:40:25 -0500 Subject: [PATCH] fix(testing): deprecate cypress tsconfig option (#17165) --- .../packages/cypress/executors/cypress.json | 1 + packages/cypress/migrations.json | 6 + .../src/executors/cypress/cypress.impl.ts | 6 - .../cypress/src/executors/cypress/schema.json | 1 + .../update-16-4-0/tsconfig-sourcemaps.spec.ts | 175 ++++++++++++++++++ .../update-16-4-0/tsconfig-sourcemaps.ts | 102 ++++++++++ 6 files changed, 285 insertions(+), 6 deletions(-) create mode 100644 packages/cypress/src/migrations/update-16-4-0/tsconfig-sourcemaps.spec.ts create mode 100644 packages/cypress/src/migrations/update-16-4-0/tsconfig-sourcemaps.ts diff --git a/docs/generated/packages/cypress/executors/cypress.json b/docs/generated/packages/cypress/executors/cypress.json index 6ca1251f6b..7eead1fc5c 100644 --- a/docs/generated/packages/cypress/executors/cypress.json +++ b/docs/generated/packages/cypress/executors/cypress.json @@ -32,6 +32,7 @@ "default": false }, "tsConfig": { + "x-deprecated": "This option no longer has any effect. Cypress supports typescript out of the box. Add any options directly to /tsconfig.json or /cypress/tsconfig.json", "type": "string", "description": "The path of the Cypress tsconfig configuration json file.", "x-completion-type": "file", diff --git a/packages/cypress/migrations.json b/packages/cypress/migrations.json index 1f635977fb..77d19c9166 100644 --- a/packages/cypress/migrations.json +++ b/packages/cypress/migrations.json @@ -53,6 +53,12 @@ "version": "16.2.0-beta.0", "description": "Normalize tsconfig.cy.json files to be located at '/cypress/tsconfig.json'", "implementation": "./src/migrations/update-16-2-0/update-cy-tsconfig" + }, + "update-16-3-0-remove-old-tsconfigs": { + "cli": "nx", + "version": "16.4.0-beta.10", + "description": "Remove tsconfig.e2e.json and add settings to project tsconfig.json. tsConfigs executor option is now deprecated. The project level tsconfig.json file should be used instead.", + "implementation": "./src/migrations/update-16-4-0/tsconfig-sourcemaps" } }, "packageJsonUpdates": { diff --git a/packages/cypress/src/executors/cypress/cypress.impl.ts b/packages/cypress/src/executors/cypress/cypress.impl.ts index 4023551206..b06d265fba 100644 --- a/packages/cypress/src/executors/cypress/cypress.impl.ts +++ b/packages/cypress/src/executors/cypress/cypress.impl.ts @@ -24,7 +24,6 @@ export type Json = { [k: string]: any }; export interface CypressExecutorOptions extends Json { cypressConfig: string; watch?: boolean; - tsConfig?: string; devServerTarget?: string; headed?: boolean; /** @@ -91,11 +90,6 @@ function normalizeOptions( context: ExecutorContext ): NormalizedCypressExecutorOptions { options.env = options.env || {}; - if (options.tsConfig) { - const tsConfigPath = join(context.root, options.tsConfig); - options.env.tsConfig = tsConfigPath; - process.env.TS_NODE_PROJECT = tsConfigPath; - } if (options.testingType === 'component') { const project = context?.projectGraph?.nodes?.[context.projectName]; if (project?.data?.root) { diff --git a/packages/cypress/src/executors/cypress/schema.json b/packages/cypress/src/executors/cypress/schema.json index 925b2890d4..50917c806a 100644 --- a/packages/cypress/src/executors/cypress/schema.json +++ b/packages/cypress/src/executors/cypress/schema.json @@ -32,6 +32,7 @@ "default": false }, "tsConfig": { + "x-deprecated": "This option no longer has any effect. Cypress supports typescript out of the box. Add any options directly to /tsconfig.json or /cypress/tsconfig.json", "type": "string", "description": "The path of the Cypress tsconfig configuration json file.", "x-completion-type": "file", diff --git a/packages/cypress/src/migrations/update-16-4-0/tsconfig-sourcemaps.spec.ts b/packages/cypress/src/migrations/update-16-4-0/tsconfig-sourcemaps.spec.ts new file mode 100644 index 0000000000..307c1ae566 --- /dev/null +++ b/packages/cypress/src/migrations/update-16-4-0/tsconfig-sourcemaps.spec.ts @@ -0,0 +1,175 @@ +import { + Tree, + addProjectConfiguration, + readJson, + readProjectConfiguration, + updateJson, + updateProjectConfiguration, +} from '@nx/devkit'; +import { createTreeWithEmptyWorkspace } from 'nx/src/devkit-testing-exports'; +import { fixLegacyCypressTsconfig } from './tsconfig-sourcemaps'; + +describe('Cypress Migration: tsconfig-sourcemaps', () => { + let tree: Tree; + + beforeEach(() => { + tree = createTreeWithEmptyWorkspace(); + }); + + it('should remove tsconfig.e2e.json and update tsconfig.json', async () => { + addLegacyProject(tree); + + await fixLegacyCypressTsconfig(tree); + expect(readProjectConfiguration(tree, 'legacy-e2e').targets.e2e) + .toMatchInlineSnapshot(` + { + "configurations": { + "production": { + "devServerTarget": "legacy-e2e:serve:production", + }, + }, + "executor": "@nx/cypress:cypress", + "options": { + "cypressConfig": "apps/legacy-e2e/cypress.config.ts", + "devServerTarget": "legacy-e2e:serve", + "testingType": "e2e", + }, + } + `); + expect(readJson(tree, 'apps/legacy-e2e/tsconfig.json')) + .toMatchInlineSnapshot(` + { + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "resolveJsonModule": true, + "sourceMap": false, + "strict": true, + "types": [ + "cypress", + "node", + ], + }, + "exclude": [], + "extends": "../../tsconfig.base.json", + "files": [], + "include": [ + "cypress.ci.1.config.ts", + "cypress.ci.2.config.ts", + "cypress.ci.3.config.ts", + "cypress.ci.4.config.ts", + "src/**/*.ts", + "cypress.config.ts", + ], + "references": [], + } + `); + expect(tree.exists('apps/legacy-e2e/tsconfig.e2e.json')).toBeFalsy(); + expect(tree.exists('apps/legacy-e2e/tsconfig.e2e.prod.json')).toBeFalsy(); + }); + + it('should do nothing if tsconfig option is not used', async () => { + addLegacyProject(tree); + + tree.delete('apps/legacy-e2e/tsconfig.e2e.json'); + tree.delete('apps/legacy-e2e/tsconfig.e2e.prod.json'); + const pc = readProjectConfiguration(tree, 'legacy-e2e'); + + delete pc.targets.e2e.options.tsConfig; + delete pc.targets.e2e.configurations; + + const existingProjectConfig = pc.targets.e2e; + + updateProjectConfiguration(tree, 'legacy-e2e', pc); + + updateJson(tree, 'apps/legacy-e2e/tsconfig.json', (json) => { + json.references = []; + return json; + }); + + const existingTsConfig = readJson(tree, 'apps/legacy-e2e/tsconfig.json'); + + await fixLegacyCypressTsconfig(tree); + + expect(readProjectConfiguration(tree, 'legacy-e2e').targets.e2e).toEqual( + existingProjectConfig + ); + expect(readJson(tree, 'apps/legacy-e2e/tsconfig.json')).toEqual( + existingTsConfig + ); + }); +}); + +function addLegacyProject(tree: Tree) { + addProjectConfiguration(tree, 'legacy-e2e', { + root: 'apps/legacy-e2e', + sourceRoot: 'apps/legacy-e2e/src', + targets: { + e2e: { + executor: '@nx/cypress:cypress', + options: { + cypressConfig: 'apps/legacy-e2e/cypress.config.ts', + tsConfig: 'apps/legacy-e2e/tsconfig.e2e.json', + devServerTarget: 'legacy-e2e:serve', + testingType: 'e2e', + }, + configurations: { + production: { + devServerTarget: 'legacy-e2e:serve:production', + tsConfig: 'apps/legacy-e2e/tsconfig.e2e.prod.json', + }, + }, + }, + }, + }); + + tree.write( + 'apps/legacy-e2e/tsconfig.e2e.json', + JSON.stringify({ + extends: './tsconfig.json', + compilerOptions: { + sourceMap: false, + outDir: '../../dist/out-tsc', + types: ['cypress', 'node'], + }, + include: ['src/**/*.ts', 'cypress.config.ts'], + }) + ); + tree.write( + 'apps/legacy-e2e/tsconfig.e2e.prod.json', + JSON.stringify({ + extends: './tsconfig.e2e.json', + compilerOptions: { + sourceMap: false, + outDir: '../../dist/out-tsc', + types: ['cypress', 'node'], + strict: true, + }, + include: ['src/**/*.ts', 'cypress.config.ts'], + }) + ); + tree.write( + 'apps/legacy-e2e/tsconfig.json', + JSON.stringify({ + extends: '../../tsconfig.base.json', + compilerOptions: { + types: ['cypress', 'node'], + resolveJsonModule: true, + }, + include: [ + 'cypress.ci.1.config.ts', + 'cypress.ci.2.config.ts', + 'cypress.ci.3.config.ts', + 'cypress.ci.4.config.ts', + ], + files: [], + references: [ + { + path: './tsconfig.e2e.json', + }, + { + path: './tsconfig.e2e.prod.json', + }, + ], + }) + ); +} diff --git a/packages/cypress/src/migrations/update-16-4-0/tsconfig-sourcemaps.ts b/packages/cypress/src/migrations/update-16-4-0/tsconfig-sourcemaps.ts new file mode 100644 index 0000000000..a90982d8b7 --- /dev/null +++ b/packages/cypress/src/migrations/update-16-4-0/tsconfig-sourcemaps.ts @@ -0,0 +1,102 @@ +import { forEachExecutorOptions } from '@nx/devkit/src/generators/executor-options-utils'; +import { CypressExecutorOptions } from '../../executors/cypress/cypress.impl'; +import { + updateJson, + Tree, + getProjects, + joinPathFragments, + readJson, + updateProjectConfiguration, + formatFiles, +} from '@nx/devkit'; +import { posix } from 'path'; + +export async function fixLegacyCypressTsconfig(tree: Tree) { + const projects = getProjects(tree); + forEachExecutorOptions( + tree, + '@nx/cypress:cypress', + (options, projectName, targetName, configName) => { + const projectConfig = projects.get(projectName); + + if ( + options.testingType !== 'e2e' && + projectConfig.targets[targetName]?.options?.testingType !== 'e2e' + ) { + return; + } + + const tsconfigToRemove = + options.tsConfig ?? + joinPathFragments(projectConfig.root, 'tsconfig.e2e.json'); + + const projectLevelConfigPath = joinPathFragments( + projectConfig.root, + 'tsconfig.json' + ); + + if ( + !tree.exists(projectLevelConfigPath) || + !tree.exists(tsconfigToRemove) + ) { + return; + } + + if (tsconfigToRemove === projectLevelConfigPath) { + updateJson(tree, projectLevelConfigPath, (json) => { + json.compilerOptions = { + sourceMap: false, + ...json.compilerOptions, + }; + return json; + }); + } else { + const e2eConfig = readJson(tree, tsconfigToRemove); + + updateJson(tree, projectLevelConfigPath, (json) => { + json.compilerOptions = { + sourceMap: false, + ...json.compilerOptions, + ...e2eConfig.compilerOptions, + }; + json.files = Array.from( + new Set([...(json.files ?? []), ...(e2eConfig.files ?? [])]) + ); + json.include = Array.from( + new Set([...(json.include ?? []), ...(e2eConfig.include ?? [])]) + ); + json.exclude = Array.from( + new Set([...(json.exclude ?? []), ...(e2eConfig.exclude ?? [])]) + ); + + // these paths will always be 'unix style' + // and on windows relative will not work on these paths + const tsConfigFromProjRoot = posix.relative( + projectConfig.root, + tsconfigToRemove + ); + + json.references = (json.references ?? []).filter( + ({ path }) => !path.includes(tsConfigFromProjRoot) + ); + return json; + }); + + tree.delete(tsconfigToRemove); + } + + if (configName) { + delete projectConfig.targets[targetName].configurations[configName] + .tsConfig; + } else { + delete projectConfig.targets[targetName].options.tsConfig; + } + + updateProjectConfiguration(tree, projectName, projectConfig); + } + ); + + await formatFiles(tree); +} + +export default fixLegacyCypressTsconfig;