From d19a6b26f8f7f9f95018fb200348b95382e76bdc Mon Sep 17 00:00:00 2001 From: Chris Breiding Date: Fri, 21 Aug 2020 11:13:02 -0400 Subject: [PATCH 1/7] fix default esmoduleinterop test was erroneously passing because it wasn't written correctly --- .../support/fixtures/projects/ts-proj/cypress/plugins/index.ts | 2 +- .../server/test/support/fixtures/projects/ts-proj/tsconfig.json | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 packages/server/test/support/fixtures/projects/ts-proj/tsconfig.json diff --git a/packages/server/test/support/fixtures/projects/ts-proj/cypress/plugins/index.ts b/packages/server/test/support/fixtures/projects/ts-proj/cypress/plugins/index.ts index 7f27d46b4c1a..ae0a98c0cb83 100644 --- a/packages/server/test/support/fixtures/projects/ts-proj/cypress/plugins/index.ts +++ b/packages/server/test/support/fixtures/projects/ts-proj/cypress/plugins/index.ts @@ -1,6 +1,6 @@ /// -import fn from './commonjs-export-function' +import * as fn from './commonjs-export-function' // if esModuleInterop is forced to be true, this will error // with 'fn is // not a function'. instead, we allow the tsconfig.json to determine the value diff --git a/packages/server/test/support/fixtures/projects/ts-proj/tsconfig.json b/packages/server/test/support/fixtures/projects/ts-proj/tsconfig.json new file mode 100644 index 000000000000..0967ef424bce --- /dev/null +++ b/packages/server/test/support/fixtures/projects/ts-proj/tsconfig.json @@ -0,0 +1 @@ +{} From af830e7fae51c7a524a3010ecdee24bc3e451bc2 Mon Sep 17 00:00:00 2001 From: Chris Breiding Date: Fri, 21 Aug 2020 11:29:28 -0400 Subject: [PATCH 2/7] fix: Resolve tsconfig.json for plugins process from the plugins directory --- packages/server/lib/plugins/child/run_plugins.js | 2 +- packages/server/lib/project.js | 2 +- packages/server/lib/util/ts-node.js | 10 +++++++--- packages/server/test/e2e/1_typescript_plugins_spec.ts | 6 ++++++ .../projects/ts-proj-tsconfig-in-plugins/cypress.json | 3 +++ .../cypress/integration/passing_spec.ts | 3 +++ .../cypress/plugins/index.ts | 6 ++++++ .../cypress/plugins/tsconfig.json | 5 +++++ 8 files changed, 32 insertions(+), 5 deletions(-) create mode 100644 packages/server/test/support/fixtures/projects/ts-proj-tsconfig-in-plugins/cypress.json create mode 100644 packages/server/test/support/fixtures/projects/ts-proj-tsconfig-in-plugins/cypress/integration/passing_spec.ts create mode 100644 packages/server/test/support/fixtures/projects/ts-proj-tsconfig-in-plugins/cypress/plugins/index.ts create mode 100644 packages/server/test/support/fixtures/projects/ts-proj-tsconfig-in-plugins/cypress/plugins/tsconfig.json diff --git a/packages/server/lib/plugins/child/run_plugins.js b/packages/server/lib/plugins/child/run_plugins.js index 894ad24a2331..c8733b6cb84b 100644 --- a/packages/server/lib/plugins/child/run_plugins.js +++ b/packages/server/lib/plugins/child/run_plugins.js @@ -181,7 +181,7 @@ module.exports = (ipc, pluginsFile, projectRoot) => { }) if (!tsRegistered) { - registerTsNode(projectRoot) + registerTsNode(projectRoot, pluginsFile) // ensure typescript is only registered once tsRegistered = true diff --git a/packages/server/lib/project.js b/packages/server/lib/project.js index bde157744fbb..d8c57639b61b 100644 --- a/packages/server/lib/project.js +++ b/packages/server/lib/project.js @@ -101,7 +101,7 @@ class Project extends EE { return scaffold.plugins(path.dirname(cfg.pluginsFile), cfg) } }).then((cfg) => { - registerTsNode(this.projectRoot) + registerTsNode(this.projectRoot, cfg.pluginsFile) return cfg }).then((cfg) => { diff --git a/packages/server/lib/util/ts-node.js b/packages/server/lib/util/ts-node.js index fa81234c9b24..a39ff590847b 100644 --- a/packages/server/lib/util/ts-node.js +++ b/packages/server/lib/util/ts-node.js @@ -1,23 +1,27 @@ const debug = require('debug')('cypress:server:ts-node') +const path = require('path') const tsnode = require('ts-node') const resolve = require('resolve') -const getTsNodeOptions = (tsPath) => { +const getTsNodeOptions = (tsPath, pluginsFile) => { return { compiler: tsPath, // use the user's installed typescript compilerOptions: { module: 'CommonJS', }, + // resolves tsconfig.json starting from the plugins directory + // instead of the cwd (the project root) + dir: path.dirname(pluginsFile), transpileOnly: true, // transpile only (no type-check) for speed } } -const registerTsNode = (projectRoot) => { +const registerTsNode = (projectRoot, pluginsFile) => { try { const tsPath = resolve.sync('typescript', { basedir: projectRoot, }) - const tsOptions = getTsNodeOptions(tsPath) + const tsOptions = getTsNodeOptions(tsPath, pluginsFile) debug('typescript path: %s', tsPath) debug('registering project TS with options %o', tsOptions) diff --git a/packages/server/test/e2e/1_typescript_plugins_spec.ts b/packages/server/test/e2e/1_typescript_plugins_spec.ts index 5954530e9367..c087a1902de0 100644 --- a/packages/server/test/e2e/1_typescript_plugins_spec.ts +++ b/packages/server/test/e2e/1_typescript_plugins_spec.ts @@ -23,4 +23,10 @@ describe('e2e typescript in plugins file', function () { project: Fixtures.projectPath('ts-proj-esmoduleinterop-true'), }) }) + + it('loads tsconfig.json from plugins directory', function () { + return e2e.exec(this, { + project: Fixtures.projectPath('ts-proj-tsconfig-in-plugins'), + }) + }) }) diff --git a/packages/server/test/support/fixtures/projects/ts-proj-tsconfig-in-plugins/cypress.json b/packages/server/test/support/fixtures/projects/ts-proj-tsconfig-in-plugins/cypress.json new file mode 100644 index 000000000000..0c2bdde8665b --- /dev/null +++ b/packages/server/test/support/fixtures/projects/ts-proj-tsconfig-in-plugins/cypress.json @@ -0,0 +1,3 @@ +{ + "supportFile": false +} diff --git a/packages/server/test/support/fixtures/projects/ts-proj-tsconfig-in-plugins/cypress/integration/passing_spec.ts b/packages/server/test/support/fixtures/projects/ts-proj-tsconfig-in-plugins/cypress/integration/passing_spec.ts new file mode 100644 index 000000000000..99a13400edf9 --- /dev/null +++ b/packages/server/test/support/fixtures/projects/ts-proj-tsconfig-in-plugins/cypress/integration/passing_spec.ts @@ -0,0 +1,3 @@ +it('passes', () => { + expect(true).to.be.true +}) diff --git a/packages/server/test/support/fixtures/projects/ts-proj-tsconfig-in-plugins/cypress/plugins/index.ts b/packages/server/test/support/fixtures/projects/ts-proj-tsconfig-in-plugins/cypress/plugins/index.ts new file mode 100644 index 000000000000..4b4b1affda01 --- /dev/null +++ b/packages/server/test/support/fixtures/projects/ts-proj-tsconfig-in-plugins/cypress/plugins/index.ts @@ -0,0 +1,6 @@ +// this tests that the tsconfig.json is loaded from the plugins directory. +// if it isn't, the lack of "downlevelIteration" support will cause this to +// fail at runtime with "RangeError: Invalid array length" +[...Array(100).keys()].map((x) => `${x}`) + +export default () => {} diff --git a/packages/server/test/support/fixtures/projects/ts-proj-tsconfig-in-plugins/cypress/plugins/tsconfig.json b/packages/server/test/support/fixtures/projects/ts-proj-tsconfig-in-plugins/cypress/plugins/tsconfig.json new file mode 100644 index 000000000000..6936fe051166 --- /dev/null +++ b/packages/server/test/support/fixtures/projects/ts-proj-tsconfig-in-plugins/cypress/plugins/tsconfig.json @@ -0,0 +1,5 @@ +{ + "compilerOptions": { + "downlevelIteration": true + } +} From c38ecb186c66a2608a54431d910e5a41533f9350 Mon Sep 17 00:00:00 2001 From: Chris Breiding Date: Fri, 21 Aug 2020 12:05:36 -0400 Subject: [PATCH 3/7] update test --- packages/server/test/unit/project_spec.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/server/test/unit/project_spec.js b/packages/server/test/unit/project_spec.js index 1cae5ac3428c..a2d499ff218d 100644 --- a/packages/server/test/unit/project_spec.js +++ b/packages/server/test/unit/project_spec.js @@ -349,13 +349,15 @@ This option will not have an effect in Some-other-name. Tests that rely on web s const { proj, register } = setupProject('default', tsProjPath) return proj.open().then(() => { - expect(register).to.be.calledWith({ + expect(register).to.be.calledWithMatch({ transpileOnly: true, compiler: projTsPath, compilerOptions: { module: 'CommonJS', }, }) + + expect(register.lastCall.args[0].dir).to.include('ts-installed/cypress/plugins') }) }) From 03d08d0734153f0dff5d17914f3444fa2ba936a7 Mon Sep 17 00:00:00 2001 From: Chris Breiding Date: Fri, 21 Aug 2020 13:19:11 -0400 Subject: [PATCH 4/7] add issue comment --- packages/server/test/e2e/1_typescript_plugins_spec.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/server/test/e2e/1_typescript_plugins_spec.ts b/packages/server/test/e2e/1_typescript_plugins_spec.ts index c087a1902de0..6477c95fc0ee 100644 --- a/packages/server/test/e2e/1_typescript_plugins_spec.ts +++ b/packages/server/test/e2e/1_typescript_plugins_spec.ts @@ -23,7 +23,8 @@ describe('e2e typescript in plugins file', function () { project: Fixtures.projectPath('ts-proj-esmoduleinterop-true'), }) }) - + + // https://github.com/cypress-io/cypress/issues/8359 it('loads tsconfig.json from plugins directory', function () { return e2e.exec(this, { project: Fixtures.projectPath('ts-proj-tsconfig-in-plugins'), From 7d6c910d7e39f5f2af02c833d27ff89193b5d3eb Mon Sep 17 00:00:00 2001 From: Chris Breiding Date: Fri, 21 Aug 2020 13:45:00 -0400 Subject: [PATCH 5/7] fix whitespace --- packages/server/test/e2e/1_typescript_plugins_spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/server/test/e2e/1_typescript_plugins_spec.ts b/packages/server/test/e2e/1_typescript_plugins_spec.ts index 6477c95fc0ee..17db9bcd1e69 100644 --- a/packages/server/test/e2e/1_typescript_plugins_spec.ts +++ b/packages/server/test/e2e/1_typescript_plugins_spec.ts @@ -23,7 +23,7 @@ describe('e2e typescript in plugins file', function () { project: Fixtures.projectPath('ts-proj-esmoduleinterop-true'), }) }) - + // https://github.com/cypress-io/cypress/issues/8359 it('loads tsconfig.json from plugins directory', function () { return e2e.exec(this, { From 254f532e07833653a9485fe42a2face7607b14c7 Mon Sep 17 00:00:00 2001 From: Chris Breiding Date: Fri, 21 Aug 2020 14:54:11 -0400 Subject: [PATCH 6/7] refactor ts-node registration - no longer register ts-node it in the parent process, only in the child process - DRYed up resolving typescript path - added test for not registering ts-node more than once --- .../server/lib/plugins/child/run_plugins.js | 9 +++- packages/server/lib/plugins/preprocessor.js | 5 +- packages/server/lib/project.js | 5 -- .../server/lib/{plugins => util}/resolve.js | 14 ++--- packages/server/lib/util/ts-node.js | 9 ++-- .../unit/plugins/child/run_plugins_spec.js | 42 ++++++++++++++- packages/server/test/unit/project_spec.js | 54 ------------------- 7 files changed, 60 insertions(+), 78 deletions(-) rename packages/server/lib/{plugins => util}/resolve.js (67%) diff --git a/packages/server/lib/plugins/child/run_plugins.js b/packages/server/lib/plugins/child/run_plugins.js index c8733b6cb84b..5ce9151df815 100644 --- a/packages/server/lib/plugins/child/run_plugins.js +++ b/packages/server/lib/plugins/child/run_plugins.js @@ -157,7 +157,7 @@ const execute = (ipc, event, ids, args = []) => { let tsRegistered = false -module.exports = (ipc, pluginsFile, projectRoot) => { +const runPlugins = (ipc, pluginsFile, projectRoot) => { debug('pluginsFile:', pluginsFile) debug('project root:', projectRoot) if (!projectRoot) { @@ -219,3 +219,10 @@ module.exports = (ipc, pluginsFile, projectRoot) => { execute(ipc, event, ids, args) }) } + +// for testing purposes +runPlugins.__reset = () => { + tsRegistered = false +} + +module.exports = runPlugins diff --git a/packages/server/lib/plugins/preprocessor.js b/packages/server/lib/plugins/preprocessor.js index 5ea1ba77cca3..8568baeb3be9 100644 --- a/packages/server/lib/plugins/preprocessor.js +++ b/packages/server/lib/plugins/preprocessor.js @@ -7,7 +7,7 @@ const debug = require('debug')('cypress:server:preprocessor') const Promise = require('bluebird') const appData = require('../util/app_data') const plugins = require('../plugins') -const resolve = require('./resolve') +const resolve = require('../util/resolve') const errorMessage = function (err = {}) { return (err.stack || err.annotated || err.message || err.toString()) @@ -46,8 +46,7 @@ const createPreprocessor = function (options) { const setDefaultPreprocessor = function (config) { debug('set default preprocessor') - const tsPath = resolve.typescript(config) - + const tsPath = resolve.typescript(config.projectRoot) const options = { typescript: tsPath, } diff --git a/packages/server/lib/project.js b/packages/server/lib/project.js index d8c57639b61b..1d9e598ea28a 100644 --- a/packages/server/lib/project.js +++ b/packages/server/lib/project.js @@ -29,7 +29,6 @@ const keys = require('./util/keys') const settings = require('./util/settings') const specsUtil = require('./util/specs') const { escapeFilenameInUrl } = require('./util/escape_filename') -const { registerTsNode } = require('./util/ts-node') const localCwd = cwd() @@ -100,10 +99,6 @@ class Project extends EE { return scaffold.plugins(path.dirname(cfg.pluginsFile), cfg) } - }).then((cfg) => { - registerTsNode(this.projectRoot, cfg.pluginsFile) - - return cfg }).then((cfg) => { return this._initPlugins(cfg, options) .then((modifiedCfg) => { diff --git a/packages/server/lib/plugins/resolve.js b/packages/server/lib/util/resolve.js similarity index 67% rename from packages/server/lib/plugins/resolve.js rename to packages/server/lib/util/resolve.js index ddc216644b7a..3e241644606a 100644 --- a/packages/server/lib/plugins/resolve.js +++ b/packages/server/lib/util/resolve.js @@ -1,26 +1,22 @@ const resolve = require('resolve') -const env = require('../util/env') +const env = require('./env') const debug = require('debug')('cypress:server:plugins') module.exports = { /** * Resolves the path to 'typescript' module. * - * @param {Config} cypress config object + * @param {projectRoot} path to the project root * @returns {string|null} path if typescript exists, otherwise null */ - typescript: (config) => { - if (env.get('CYPRESS_INTERNAL_NO_TYPESCRIPT') === '1') { + typescript: (projectRoot) => { + if (env.get('CYPRESS_INTERNAL_NO_TYPESCRIPT') === '1' || !projectRoot) { return null } try { const options = { - basedir: config.projectRoot, - } - - if (!config.projectRoot) { - throw new Error('Config is missing projet root') + basedir: projectRoot, } debug('resolving typescript with options %o', options) diff --git a/packages/server/lib/util/ts-node.js b/packages/server/lib/util/ts-node.js index a39ff590847b..d44d07c4c0df 100644 --- a/packages/server/lib/util/ts-node.js +++ b/packages/server/lib/util/ts-node.js @@ -1,7 +1,7 @@ const debug = require('debug')('cypress:server:ts-node') const path = require('path') const tsnode = require('ts-node') -const resolve = require('resolve') +const resolve = require('./resolve') const getTsNodeOptions = (tsPath, pluginsFile) => { return { @@ -18,9 +18,10 @@ const getTsNodeOptions = (tsPath, pluginsFile) => { const registerTsNode = (projectRoot, pluginsFile) => { try { - const tsPath = resolve.sync('typescript', { - basedir: projectRoot, - }) + const tsPath = resolve.typescript(projectRoot) + + if (!tsPath) return + const tsOptions = getTsNodeOptions(tsPath, pluginsFile) debug('typescript path: %s', tsPath) diff --git a/packages/server/test/unit/plugins/child/run_plugins_spec.js b/packages/server/test/unit/plugins/child/run_plugins_spec.js index 1510c427e523..bb34605afd76 100644 --- a/packages/server/test/unit/plugins/child/run_plugins_spec.js +++ b/packages/server/test/unit/plugins/child/run_plugins_spec.js @@ -2,11 +2,13 @@ require('../../../spec_helper') const _ = require('lodash') const snapshot = require('snap-shot-it') +const tsnode = require('ts-node') const preprocessor = require(`${root}../../lib/plugins/child/preprocessor`) const task = require(`${root}../../lib/plugins/child/task`) const runPlugins = require(`${root}../../lib/plugins/child/run_plugins`) const util = require(`${root}../../lib/plugins/util`) +const resolve = require(`${root}../../lib/util/resolve`) const browserUtils = require(`${root}../../lib/browsers/utils`) const Fixtures = require(`${root}../../test/support/helpers/fixtures`) @@ -31,8 +33,7 @@ describe('lib/plugins/child/run_plugins', () => { afterEach(() => { mockery.deregisterMock('plugins-file') - - return mockery.deregisterSubstitute('plugins-file') + mockery.deregisterSubstitute('plugins-file') }) it('sends error message if pluginsFile is missing', function () { @@ -77,6 +78,43 @@ describe('lib/plugins/child/run_plugins', () => { return snapshot(JSON.stringify(this.ipc.send.lastCall.args[3])) }) + describe('typescript registration', () => { + beforeEach(function () { + runPlugins.__reset() + + this.register = sinon.stub(tsnode, 'register') + sinon.stub(resolve, 'typescript').returns('/path/to/typescript.js') + }) + + it('registers ts-node if typescript is installed', function () { + runPlugins(this.ipc, '/path/to/plugins/file.js', 'proj-root') + + expect(this.register).to.be.calledWith({ + transpileOnly: true, + compiler: '/path/to/typescript.js', + dir: '/path/to/plugins', + compilerOptions: { + module: 'CommonJS', + }, + }) + }) + + it('only registers ts-node once', function () { + runPlugins(this.ipc, '/path/to/plugins/file.js', 'proj-root') + runPlugins(this.ipc, '/path/to/plugins/file.js', 'proj-root') + + expect(this.register).to.be.calledOnce + }) + + it('does not register ts-node if typescript is not installed', function () { + resolve.typescript.returns(null) + + runPlugins(this.ipc, '/path/to/plugins/file.js', 'proj-root') + + expect(this.register).not.to.be.called + }) + }) + describe('on \'load\' message', () => { it('sends error if pluginsFile function rejects the promise', function (done) { const err = new Error('foo') diff --git a/packages/server/test/unit/project_spec.js b/packages/server/test/unit/project_spec.js index a2d499ff218d..f107fcc09ef0 100644 --- a/packages/server/test/unit/project_spec.js +++ b/packages/server/test/unit/project_spec.js @@ -3,7 +3,6 @@ require('../spec_helper') const mockedEnv = require('mocked-env') const path = require('path') const commitInfo = require('@cypress/commit-info') -const tsnode = require('ts-node') const Fixtures = require('../support/helpers/fixtures') const api = require(`${root}lib/api`) const user = require(`${root}lib/user`) @@ -316,59 +315,6 @@ This option will not have an effect in Some-other-name. Tests that rely on web s expect(config).ok }) }) - - describe('out-of-the-box typescript setup', () => { - const tsProjPath = Fixtures.projectPath('ts-installed') - // Root path is used because resolve finds server typescript path when we use a project under `suppert/projects` folder. - const rootPath = path.join(__dirname, '../../../../..') - const projTsPath = path.join(tsProjPath, 'node_modules/typescript/index.js') - - let cfg - - beforeEach(() => { - return config.get(tsProjPath, {}) - .then((c) => { - cfg = c - }) - }) - - const setupProject = (typescript, projectRoot) => { - const proj = new Project(projectRoot) - - sinon.stub(proj, 'watchSettingsAndStartWebsockets').resolves() - sinon.stub(proj, 'checkSupportFile').resolves() - sinon.stub(proj, 'scaffold').resolves() - sinon.stub(proj, 'getConfig').resolves({ ...cfg, typescript }) - - const register = sinon.stub(tsnode, 'register') - - return { proj, register } - } - - it('ts installed', () => { - const { proj, register } = setupProject('default', tsProjPath) - - return proj.open().then(() => { - expect(register).to.be.calledWithMatch({ - transpileOnly: true, - compiler: projTsPath, - compilerOptions: { - module: 'CommonJS', - }, - }) - - expect(register.lastCall.args[0].dir).to.include('ts-installed/cypress/plugins') - }) - }) - - it('ts not installed', () => { - const { proj, register } = setupProject('default', rootPath) - - return proj.open().then(() => { - expect(register).not.called - }) - }) - }) }) context('#close', () => { From 5888bf2f24a62206cf93edce1c07ccf96f4620b3 Mon Sep 17 00:00:00 2001 From: Chris Breiding Date: Fri, 21 Aug 2020 15:24:06 -0400 Subject: [PATCH 7/7] fix test --- packages/server/test/integration/http_requests_spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/server/test/integration/http_requests_spec.js b/packages/server/test/integration/http_requests_spec.js index 63c925783132..4f7b000f1ea4 100644 --- a/packages/server/test/integration/http_requests_spec.js +++ b/packages/server/test/integration/http_requests_spec.js @@ -23,7 +23,7 @@ const Project = require(`${root}lib/project`) const Watchers = require(`${root}lib/watchers`) const pluginsModule = require(`${root}lib/plugins`) const preprocessor = require(`${root}lib/plugins/preprocessor`) -const resolve = require(`${root}lib/plugins/resolve`) +const resolve = require(`${root}lib/util/resolve`) const fs = require(`${root}lib/util/fs`) const glob = require(`${root}lib/util/glob`) const CacheBuster = require(`${root}lib/util/cache_buster`)