diff --git a/cli/scripts/start-build.js b/cli/scripts/start-build.js index b9263973e5..981ef4d312 100755 --- a/cli/scripts/start-build.js +++ b/cli/scripts/start-build.js @@ -24,3 +24,4 @@ includeTypes.forEach((folder) => { shell.exec('babel lib -d build/lib') shell.exec('babel index.js -o build/index.js') +shell.cp('index.mjs', 'build/index.mjs') diff --git a/packages/frontend-shared/cypress/e2e/support/e2eProjectDirs.ts b/packages/frontend-shared/cypress/e2e/support/e2eProjectDirs.ts index 7259d2bfa8..95b194261b 100644 --- a/packages/frontend-shared/cypress/e2e/support/e2eProjectDirs.ts +++ b/packages/frontend-shared/cypress/e2e/support/e2eProjectDirs.ts @@ -144,6 +144,7 @@ export const e2eProjectDirs = [ 'todos', 'ts-installed', 'ts-proj', + 'ts-proj-4-5', 'ts-proj-custom-names', 'ts-proj-esmoduleinterop-true', 'ts-proj-tsconfig-in-plugins', diff --git a/packages/server/lib/plugins/child/ts_node.js b/packages/server/lib/plugins/child/ts_node.js index 652e95f7ac..1bf12ca4d5 100644 --- a/packages/server/lib/plugins/child/ts_node.js +++ b/packages/server/lib/plugins/child/ts_node.js @@ -3,18 +3,39 @@ const debugLib = require('debug') const path = require('path') const tsnode = require('ts-node') const resolve = require('../../util/resolve') +const semver = require('semver') const debug = debugLib('cypress:server:ts-node') const getTsNodeOptions = (tsPath, registeredFile) => { + const version = require(tsPath).version || '0.0.0' + + /** + * NOTE: This circumvents a limitation of ts-node + * + * The "preserveValueImports" option was introduced in TypeScript 4.5.0 + * https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-5.html#disabling-import-elision + * + * If we pass an unknown compiler option to ts-node, + * it ignores all options passed. + * If we want `module: "commonjs"` to always be used, + * we need to only set options that are supported in this version of TypeScript. + */ + const compilerOptions = { + module: 'commonjs', + ...(semver.satisfies(version, '>=4.5.0') + // Only adding this option for TS >= 4.5.0 + ? { preserveValueImports: false } + : {} + ), + } + /** * @type {import('ts-node').RegisterOptions} */ const opts = { compiler: process.env.TS_NODE_COMPILER || tsPath, // use the user's installed typescript - compilerOptions: { - module: 'CommonJS', - }, + compilerOptions, // resolves tsconfig.json starting from the plugins directory // instead of the cwd (the project root) dir: path.dirname(registeredFile), diff --git a/packages/server/test/unit/plugins/child/ts_node_spec.js b/packages/server/test/unit/plugins/child/ts_node_spec.js index dc1edf2a0f..e7db8c9990 100644 --- a/packages/server/test/unit/plugins/child/ts_node_spec.js +++ b/packages/server/test/unit/plugins/child/ts_node_spec.js @@ -1,6 +1,7 @@ require('../../../spec_helper') const tsnode = require('ts-node') +const typescriptObject = require('typescript/lib/typescript.js') const resolve = require(`../../../../lib/util/resolve`) @@ -9,19 +10,35 @@ const tsNodeUtil = require('../../../../lib/plugins/child/ts_node') describe('lib/plugins/child/ts_node', () => { beforeEach(() => { sinon.stub(tsnode, 'register') - sinon.stub(resolve, 'typescript').returns('/path/to/typescript.js') + sinon.stub(resolve, 'typescript').returns('typescript/lib/typescript.js') }) describe('typescript registration', () => { it('registers ts-node if typescript is installed', () => { + sinon.stub(typescriptObject, 'version').value('1.1.1') tsNodeUtil.register('proj-root', '/path/to/plugins/file.js') expect(tsnode.register).to.be.calledWith({ transpileOnly: true, - compiler: '/path/to/typescript.js', + compiler: 'typescript/lib/typescript.js', dir: '/path/to/plugins', compilerOptions: { - module: 'CommonJS', + module: 'commonjs', + }, + }) + }) + + it('registers ts-node with preserveValueImports if typescript 4.5.0 is installed', () => { + sinon.stub(typescriptObject, 'version').value('4.5.0') + tsNodeUtil.register('proj-root', '/path/to/plugins/file.js') + + expect(tsnode.register).to.be.calledWith({ + transpileOnly: true, + compiler: 'typescript/lib/typescript.js', + dir: '/path/to/plugins', + compilerOptions: { + module: 'commonjs', + preserveValueImports: false, }, }) }) diff --git a/system-tests/projects/ts-proj-4-5/cypress.config.ts b/system-tests/projects/ts-proj-4-5/cypress.config.ts new file mode 100644 index 0000000000..9824ae3475 --- /dev/null +++ b/system-tests/projects/ts-proj-4-5/cypress.config.ts @@ -0,0 +1,3 @@ +import { defineConfig } from 'cypress' + +export default defineConfig({}) diff --git a/system-tests/projects/ts-proj-4-5/cypress/e2e/app.cy.ts b/system-tests/projects/ts-proj-4-5/cypress/e2e/app.cy.ts new file mode 100644 index 0000000000..ceacd67310 --- /dev/null +++ b/system-tests/projects/ts-proj-4-5/cypress/e2e/app.cy.ts @@ -0,0 +1,6 @@ +import { add } from './math' + +it('is true', () => { + // @ts-ignore + expect(add(1, 2)).to.eq(3) +}) diff --git a/system-tests/projects/ts-proj-4-5/cypress/e2e/math.ts b/system-tests/projects/ts-proj-4-5/cypress/e2e/math.ts new file mode 100644 index 0000000000..e29e78dc31 --- /dev/null +++ b/system-tests/projects/ts-proj-4-5/cypress/e2e/math.ts @@ -0,0 +1,3 @@ +export const add = (a: number, b: number) => { + return a + b +} diff --git a/system-tests/projects/ts-proj-4-5/cypress/support/e2e.ts b/system-tests/projects/ts-proj-4-5/cypress/support/e2e.ts new file mode 100644 index 0000000000..503f34fc15 --- /dev/null +++ b/system-tests/projects/ts-proj-4-5/cypress/support/e2e.ts @@ -0,0 +1,20 @@ +// *********************************************************** +// This example support/e2e.ts is processed and +// loaded automatically before your test files. +// +// This is a great place to put global configuration and +// behavior that modifies Cypress. +// +// You can change the location of this file or turn off +// automatically serving support files with the +// 'supportFile' configuration option. +// +// You can read more here: +// https://on.cypress.io/configuration +// *********************************************************** + +// Import commands.js using ES2015 syntax: +// import './commands' + +// Alternatively you can use CommonJS syntax: +// require('./commands') diff --git a/system-tests/projects/ts-proj-4-5/package.json b/system-tests/projects/ts-proj-4-5/package.json new file mode 100644 index 0000000000..0ac034816d --- /dev/null +++ b/system-tests/projects/ts-proj-4-5/package.json @@ -0,0 +1,8 @@ +{ + "name": "@test-project/ts-proj-4-5", + "version": "0.0.0-test", + "private": true, + "dependencies": { + "typescript": "4.5.2" + } +} diff --git a/system-tests/projects/ts-proj-4-5/tsconfig.json b/system-tests/projects/ts-proj-4-5/tsconfig.json new file mode 100644 index 0000000000..02717bb0fe --- /dev/null +++ b/system-tests/projects/ts-proj-4-5/tsconfig.json @@ -0,0 +1,6 @@ +{ + "compilerOptions": { + "module": "es2015", + "preserveValueImports": true + } +} diff --git a/system-tests/projects/ts-proj-4-5/yarn.lock b/system-tests/projects/ts-proj-4-5/yarn.lock new file mode 100644 index 0000000000..c6ec202b2c --- /dev/null +++ b/system-tests/projects/ts-proj-4-5/yarn.lock @@ -0,0 +1,8 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +typescript@4.5.2: + version "4.5.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.2.tgz#8ac1fba9f52256fdb06fb89e4122fa6a346c2998" + integrity "sha1-isH7qfUiVv2wb7ieQSL6ajRsKZg= sha512-5BlMof9H1yGt0P8/WF+wPNw6GfctgGjXp5hkblpyT+8rkASSmkUKMXrxR0Xg8ThVCi/JnHQiKXeBaEwCeQwMFw==" diff --git a/system-tests/test/typescript_plugins_spec.ts b/system-tests/test/typescript_plugins_spec.ts index a86685771b..00e38de1e7 100644 --- a/system-tests/test/typescript_plugins_spec.ts +++ b/system-tests/test/typescript_plugins_spec.ts @@ -16,6 +16,12 @@ describe('e2e typescript in plugins file', function () { }) }) + it('can use a tsconfig.json with preserveValueImports: true to run config', function () { + return systemTests.exec(this, { + project: 'ts-proj-4-5', + }) + }) + // https://github.com/cypress-io/cypress/issues/7575 it('allows esModuleInterop to be overridden with true via tsconfig.json', function () { return systemTests.exec(this, {