diff --git a/.circleci/workflows.yml b/.circleci/workflows.yml index 8eef743ef4d0..0ed20304c6a8 100644 --- a/.circleci/workflows.yml +++ b/.circleci/workflows.yml @@ -38,7 +38,7 @@ mainBuildFilters: &mainBuildFilters - /^release\/\d+\.\d+\.\d+$/ # use the following branch as well to ensure that v8 snapshot cache updates are fully tested - 'update-v8-snapshot-cache-on-develop' - - 'remove-migration' + - 'feat/wbip_full_resolve_ts_config' # usually we don't build Mac app - it takes a long time # but sometimes we want to really confirm we are doing the right thing @@ -49,7 +49,7 @@ macWorkflowFilters: &darwin-workflow-filters - equal: [ develop, << pipeline.git.branch >> ] # use the following branch as well to ensure that v8 snapshot cache updates are fully tested - equal: [ 'update-v8-snapshot-cache-on-develop', << pipeline.git.branch >> ] - - equal: [ 'remove-migration', << pipeline.git.branch >> ] + - equal: [ 'feat/wbip_full_resolve_ts_config', << pipeline.git.branch >> ] - matches: pattern: /^release\/\d+\.\d+\.\d+$/ value: << pipeline.git.branch >> @@ -60,7 +60,7 @@ linuxArm64WorkflowFilters: &linux-arm64-workflow-filters - equal: [ develop, << pipeline.git.branch >> ] # use the following branch as well to ensure that v8 snapshot cache updates are fully tested - equal: [ 'update-v8-snapshot-cache-on-develop', << pipeline.git.branch >> ] - - equal: [ 'remove-migration', << pipeline.git.branch >> ] + - equal: [ 'feat/wbip_full_resolve_ts_config', << pipeline.git.branch >> ] - matches: pattern: /^release\/\d+\.\d+\.\d+$/ value: << pipeline.git.branch >> @@ -83,7 +83,7 @@ windowsWorkflowFilters: &windows-workflow-filters - equal: [ develop, << pipeline.git.branch >> ] # use the following branch as well to ensure that v8 snapshot cache updates are fully tested - equal: [ 'update-v8-snapshot-cache-on-develop', << pipeline.git.branch >> ] - - equal: [ 'remove-migration', << pipeline.git.branch >> ] + - equal: [ 'feat/wbip_full_resolve_ts_config', << pipeline.git.branch >> ] - matches: pattern: /^release\/\d+\.\d+\.\d+$/ value: << pipeline.git.branch >> @@ -157,7 +157,7 @@ commands: name: Set environment variable to determine whether or not to persist artifacts command: | echo "Setting SHOULD_PERSIST_ARTIFACTS variable" - echo 'if ! [[ "$CIRCLE_BRANCH" != "develop" && "$CIRCLE_BRANCH" != "release/"* && "$CIRCLE_BRANCH" != "remove-migration" ]]; then + echo 'if ! [[ "$CIRCLE_BRANCH" != "develop" && "$CIRCLE_BRANCH" != "release/"* && "$CIRCLE_BRANCH" != "feat/wbip_full_resolve_ts_config" ]]; then export SHOULD_PERSIST_ARTIFACTS=true fi' >> "$BASH_ENV" # You must run `setup_should_persist_artifacts` command and be using bash before running this command diff --git a/npm/vite-dev-server/tsconfig.json b/npm/vite-dev-server/tsconfig.json index bce39021eb1d..5e937d27bdda 100644 --- a/npm/vite-dev-server/tsconfig.json +++ b/npm/vite-dev-server/tsconfig.json @@ -4,6 +4,7 @@ "resolveJsonModule": true, "target": "ES2017" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'. */, "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */, + "moduleResolution": "node", "lib": [ "es2015", "dom" diff --git a/npm/webpack-batteries-included-preprocessor/index.js b/npm/webpack-batteries-included-preprocessor/index.js index 883be4fe566c..23e12d56f075 100644 --- a/npm/webpack-batteries-included-preprocessor/index.js +++ b/npm/webpack-batteries-included-preprocessor/index.js @@ -1,11 +1,19 @@ const path = require('path') const Debug = require('debug') +const getTsConfig = require('get-tsconfig') const webpackPreprocessor = require('@cypress/webpack-preprocessor') const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin const debug = Debug('cypress:webpack-batteries-included-preprocessor') const WBADebugNamespace = 'cypress-verbose:webpack-batteries-included-preprocessor:bundle-analyzer' +class TsConfigNotFoundError extends Error { + constructor () { + super('No tsconfig.json found, but typescript is installed. ts-loader needs a tsconfig.json file to work. Please add one to your project in either the root or the cypress directory.') + this.name = 'TsConfigNotFoundError' + } +} + const hasTsLoader = (rules) => { return rules.some((rule) => { if (!rule.use || !Array.isArray(rule.use)) return false @@ -17,6 +25,17 @@ const hasTsLoader = (rules) => { } const addTypeScriptConfig = (file, options) => { + // returns null if tsconfig cannot be found in the path/parent hierarchy + const configFile = getTsConfig.getTsconfig(file.filePath) + + if (!configFile && typescriptExtensionRegex.test(file.filePath)) { + debug('no user tsconfig.json found. Throwing TsConfigNotFoundError') + // @see https://github.com/cypress-io/cypress/issues/18938 + throw new TsConfigNotFoundError() + } + + debug(`found user tsconfig.json at ${configFile?.path} with compilerOptions: ${JSON.stringify(configFile?.config?.compilerOptions)}`) + // shortcut if we know we've already added typescript support if (options.__typescriptSupportAdded) return options @@ -36,13 +55,6 @@ const addTypeScriptConfig = (file, options) => { const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin') // node will try to load a projects tsconfig.json instead of the node - const getTsConfig = require('get-tsconfig') - - // returns null if tsconfig cannot be found in the path/parent hierarchy - const configFile = getTsConfig.getTsconfig(file.filePath) - - configFile ? debug(`found user tsconfig.json at ${configFile?.path} with compilerOptions: ${JSON.stringify(configFile?.config?.compilerOptions)}`) : debug('no user tsconfig.json found') - webpackOptions.module.rules.push({ test: /\.tsx?$/, exclude: [/node_modules/], @@ -51,6 +63,10 @@ const addTypeScriptConfig = (file, options) => { loader: require.resolve('ts-loader'), options: { compiler: options.typescript, + // pass in the resolved compiler options from the tsconfig file into ts-loader to most accurately transpile the code + ...(configFile ? { + compilerOptions: configFile.config.compilerOptions, + } : {}), logLevel: 'error', silent: true, transpileOnly: true, diff --git a/npm/webpack-batteries-included-preprocessor/test/unit/index.spec.js b/npm/webpack-batteries-included-preprocessor/test/unit/index.spec.js index 38df61e930e8..bf8a246cd538 100644 --- a/npm/webpack-batteries-included-preprocessor/test/unit/index.spec.js +++ b/npm/webpack-batteries-included-preprocessor/test/unit/index.spec.js @@ -82,8 +82,16 @@ describe('webpack-batteries-included-preprocessor', () => { mock.stop('@cypress/webpack-preprocessor') }) - it('always returns loader options even if there is an error discovering the user\'s tsconfig.json', () => { - getTsConfigMock.returns(null) + it('correctly passes the options in the user\'s tsconfig.json options into ts-loader', () => { + getTsConfigMock.returns({ + config: { + compilerOptions: { + module: 'ESNext', + moduleResolution: 'Bundler', + }, + path: '/foo/tsconfig.json', + }, + }) const preprocessorCB = preprocessor({ typescript: true, @@ -104,8 +112,28 @@ describe('webpack-batteries-included-preprocessor', () => { expect(tsLoader.options.silent).to.be.true expect(tsLoader.options.transpileOnly).to.be.true - // compilerOptions are set by `@cypress/webpack-preprocessor` if ts-loader is present - expect(tsLoader.options.compilerOptions).to.be.undefined + // compilerOptions are overridden (sourceMap=true) by `@cypress/webpack-preprocessor` if ts-loader is present + expect(tsLoader.options.compilerOptions).to.deep.equal({ + module: 'ESNext', + moduleResolution: 'Bundler', + }) + }) + + // @see https://github.com/cypress-io/cypress/issues/18938. ts-loader needs a tsconfig.json file to work. + it('throws an error if the user\'s tsconfig.json is not found', () => { + getTsConfigMock.returns(null) + + const preprocessorCB = preprocessor({ + typescript: true, + webpackOptions, + }) + + expect(() => { + return preprocessorCB({ + filePath: 'foo.ts', + outputPath: '.js', + }) + }).to.throw('No tsconfig.json found, but typescript is installed. ts-loader needs a tsconfig.json file to work. Please add one to your project in either the root or the cypress directory.') }) }) }) diff --git a/npm/webpack-dev-server/tsconfig.json b/npm/webpack-dev-server/tsconfig.json index 4ff434cdba66..e54df376408a 100644 --- a/npm/webpack-dev-server/tsconfig.json +++ b/npm/webpack-dev-server/tsconfig.json @@ -4,6 +4,7 @@ "resolveJsonModule": true, "target": "ES2017" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'. */, "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */, + "moduleResolution": "node", "lib": [ "es2015", "dom" diff --git a/system-tests/__snapshots__/no_tsconfig_ts_js_mix.ts.js b/system-tests/__snapshots__/no_tsconfig_ts_js_mix.ts.js new file mode 100644 index 000000000000..4421d751f23a --- /dev/null +++ b/system-tests/__snapshots__/no_tsconfig_ts_js_mix.ts.js @@ -0,0 +1,115 @@ +exports['e2e no tsconfig ts js mix / fails with no tsconfig.json error'] = ` + +==================================================================================================== + + (Run Starting) + + ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ Cypress: 1.2.3 │ + │ Browser: FooBrowser 88 │ + │ Specs: 1 found (spec.cy.ts) │ + │ Searched: cypress/e2e/spec.cy.ts │ + └────────────────────────────────────────────────────────────────────────────────────────────────┘ + + +──────────────────────────────────────────────────────────────────────────────────────────────────── + + Running: spec.cy.ts (1 of 1) + +Oops...we found an error preparing this test file: + + > cypress/e2e/spec.cy.ts + +The error was: + +TsConfigNotFoundError: No tsconfig.json found, but typescript is installed. ts-loader needs a tsconfig.json file to work. Please add one to your project in either the root or the cypress directory. + [stack trace lines] + +This occurred while Cypress was compiling and bundling your test code. This is usually caused by: + +- A missing file or dependency +- A syntax error in the file or one of its dependencies + +Fix the error in your code and re-run your tests. + + (Results) + + ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ Tests: 0 │ + │ Passing: 0 │ + │ Failing: 1 │ + │ Pending: 0 │ + │ Skipped: 0 │ + │ Screenshots: 0 │ + │ Video: false │ + │ Duration: X seconds │ + │ Spec Ran: spec.cy.ts │ + └────────────────────────────────────────────────────────────────────────────────────────────────┘ + + +==================================================================================================== + + (Run Finished) + + + Spec Tests Passing Failing Pending Skipped + ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ ✖ spec.cy.ts XX:XX - - 1 - - │ + └────────────────────────────────────────────────────────────────────────────────────────────────┘ + ✖ 1 of 1 failed (100%) XX:XX - - 1 - - + + +` + +exports['e2e no tsconfig ts js mix / passes'] = ` + +==================================================================================================== + + (Run Starting) + + ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ Cypress: 1.2.3 │ + │ Browser: FooBrowser 88 │ + │ Specs: 1 found (spec.cy.js) │ + │ Searched: cypress/e2e/spec.cy.js │ + └────────────────────────────────────────────────────────────────────────────────────────────────┘ + + +──────────────────────────────────────────────────────────────────────────────────────────────────── + + Running: spec.cy.js (1 of 1) + + + ✓ is true + + 1 passing + + + (Results) + + ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ Tests: 1 │ + │ Passing: 1 │ + │ Failing: 0 │ + │ Pending: 0 │ + │ Skipped: 0 │ + │ Screenshots: 0 │ + │ Video: false │ + │ Duration: X seconds │ + │ Spec Ran: spec.cy.js │ + └────────────────────────────────────────────────────────────────────────────────────────────────┘ + + +==================================================================================================== + + (Run Finished) + + + Spec Tests Passing Failing Pending Skipped + ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ ✔ spec.cy.js XX:XX 1 1 - - - │ + └────────────────────────────────────────────────────────────────────────────────────────────────┘ + ✔ All specs passed! XX:XX 1 1 - - - + + +` diff --git a/system-tests/projects/no-tsconfig-ts-js-mix/cypress.config.js b/system-tests/projects/no-tsconfig-ts-js-mix/cypress.config.js new file mode 100644 index 000000000000..624bfba45bd4 --- /dev/null +++ b/system-tests/projects/no-tsconfig-ts-js-mix/cypress.config.js @@ -0,0 +1,7 @@ +module.exports = { + e2e: { + setupNodeEvents (on, config) { + return config + }, + }, +} diff --git a/system-tests/projects/no-tsconfig-ts-js-mix/cypress/e2e/spec.cy.js b/system-tests/projects/no-tsconfig-ts-js-mix/cypress/e2e/spec.cy.js new file mode 100644 index 000000000000..7258e472bd59 --- /dev/null +++ b/system-tests/projects/no-tsconfig-ts-js-mix/cypress/e2e/spec.cy.js @@ -0,0 +1,3 @@ +it('is true', () => { + expect(true).to.be.true +}) diff --git a/system-tests/projects/no-tsconfig-ts-js-mix/cypress/e2e/spec.cy.ts b/system-tests/projects/no-tsconfig-ts-js-mix/cypress/e2e/spec.cy.ts new file mode 100644 index 000000000000..7258e472bd59 --- /dev/null +++ b/system-tests/projects/no-tsconfig-ts-js-mix/cypress/e2e/spec.cy.ts @@ -0,0 +1,3 @@ +it('is true', () => { + expect(true).to.be.true +}) diff --git a/system-tests/projects/no-tsconfig-ts-js-mix/cypress/support/e2e.js b/system-tests/projects/no-tsconfig-ts-js-mix/cypress/support/e2e.js new file mode 100644 index 000000000000..36db4933d909 --- /dev/null +++ b/system-tests/projects/no-tsconfig-ts-js-mix/cypress/support/e2e.js @@ -0,0 +1 @@ +// intentionally left empty diff --git a/system-tests/projects/no-tsconfig-ts-js-mix/package.json b/system-tests/projects/no-tsconfig-ts-js-mix/package.json new file mode 100644 index 000000000000..0e7aff11889b --- /dev/null +++ b/system-tests/projects/no-tsconfig-ts-js-mix/package.json @@ -0,0 +1,7 @@ +{ + "name": "no-tsconfig-ts-js-mix", + "version": "0.0.0-test", + "devDependencies": { + "typescript": "^5.6.0" + } +} diff --git a/system-tests/projects/no-tsconfig-ts-js-mix/yarn.lock b/system-tests/projects/no-tsconfig-ts-js-mix/yarn.lock new file mode 100644 index 000000000000..4efab5fa0cc2 --- /dev/null +++ b/system-tests/projects/no-tsconfig-ts-js-mix/yarn.lock @@ -0,0 +1,8 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +typescript@^5.6.0: + version "5.8.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.8.3.tgz#92f8a3e5e3cf497356f4178c34cd65a7f5e8440e" + integrity sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ== diff --git a/system-tests/test/no_tsconfig_ts_js_mix.ts b/system-tests/test/no_tsconfig_ts_js_mix.ts new file mode 100644 index 000000000000..103155a1a4e6 --- /dev/null +++ b/system-tests/test/no_tsconfig_ts_js_mix.ts @@ -0,0 +1,25 @@ +import systemTests from '../lib/system-tests' + +describe('e2e no tsconfig ts js mix', () => { + systemTests.setup() + + systemTests.it('passes', { + spec: 'spec.cy.js', + browser: 'chrome', + project: 'no-tsconfig-ts-js-mix', + snapshot: true, + }) + + systemTests.it('fails with no tsconfig.json error', { + spec: 'spec.cy.ts', + browser: 'chrome', + project: 'no-tsconfig-ts-js-mix', + snapshot: true, + expectedExitCode: 1, + async onRun (exec, browserName) { + const { stdout } = await exec() + + expect(stdout).to.include('TsConfigNotFoundError: No tsconfig.json found, but typescript is installed. ts-loader needs a tsconfig.json file to work. Please add one to your project in either the root or the cypress directory.') + }, + }) +})