Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Resolve tsconfig.json for plugins file from the plugins directory #8377

Merged
merged 11 commits into from
Aug 24, 2020
11 changes: 9 additions & 2 deletions packages/server/lib/plugins/child/run_plugins.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -181,7 +181,7 @@ module.exports = (ipc, pluginsFile, projectRoot) => {
})

if (!tsRegistered) {
registerTsNode(projectRoot)
registerTsNode(projectRoot, pluginsFile)

// ensure typescript is only registered once
tsRegistered = true
Expand Down Expand Up @@ -219,3 +219,10 @@ module.exports = (ipc, pluginsFile, projectRoot) => {
execute(ipc, event, ids, args)
})
}

// for testing purposes
runPlugins.__reset = () => {
tsRegistered = false
}

module.exports = runPlugins
5 changes: 2 additions & 3 deletions packages/server/lib/plugins/preprocessor.js
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down Expand Up @@ -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,
}
Expand Down
5 changes: 0 additions & 5 deletions packages/server/lib/project.js
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down Expand Up @@ -100,10 +99,6 @@ class Project extends EE {

return scaffold.plugins(path.dirname(cfg.pluginsFile), cfg)
}
}).then((cfg) => {
registerTsNode(this.projectRoot)

return cfg
}).then((cfg) => {
return this._initPlugins(cfg, options)
.then((modifiedCfg) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -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)
Expand Down
19 changes: 12 additions & 7 deletions packages/server/lib/util/ts-node.js
Original file line number Diff line number Diff line change
@@ -1,23 +1,28 @@
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) => {
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),
kuceb marked this conversation as resolved.
Show resolved Hide resolved
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 tsPath = resolve.typescript(projectRoot)

if (!tsPath) return

const tsOptions = getTsNodeOptions(tsPath, pluginsFile)

debug('typescript path: %s', tsPath)
debug('registering project TS with options %o', tsOptions)
Expand Down
7 changes: 7 additions & 0 deletions packages/server/test/e2e/1_typescript_plugins_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,11 @@ describe('e2e typescript in plugins file', function () {
project: Fixtures.projectPath('ts-proj-esmoduleinterop-true'),
})
})

chrisbreiding marked this conversation as resolved.
Show resolved Hide resolved
// 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'),
})
})
})
2 changes: 1 addition & 1 deletion packages/server/test/integration/http_requests_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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`)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"supportFile": false
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
it('passes', () => {
expect(true).to.be.true
})
Original file line number Diff line number Diff line change
@@ -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 () => {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"compilerOptions": {
"downlevelIteration": true
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/// <reference types="cypress" />

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
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
chrisbreiding marked this conversation as resolved.
Show resolved Hide resolved
42 changes: 40 additions & 2 deletions packages/server/test/unit/plugins/child/run_plugins_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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`)

Expand All @@ -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 () {
Expand Down Expand Up @@ -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')
Expand Down
52 changes: 0 additions & 52 deletions packages/server/test/unit/project_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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`)
Expand Down Expand Up @@ -316,57 +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.calledWith({
transpileOnly: true,
compiler: projTsPath,
compilerOptions: {
module: 'CommonJS',
},
})
})
})

it('ts not installed', () => {
const { proj, register } = setupProject('default', rootPath)

return proj.open().then(() => {
expect(register).not.called
})
})
})
})

context('#close', () => {
Expand Down