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

This commit is contained in:
Chris Breiding
2020-08-24 17:02:26 -04:00
committed by GitHub
parent 96c8747c9a
commit 383fa22f10
15 changed files with 95 additions and 82 deletions

View File

@@ -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) {
@@ -181,7 +181,7 @@ module.exports = (ipc, pluginsFile, projectRoot) => {
})
if (!tsRegistered) {
registerTsNode(projectRoot)
registerTsNode(projectRoot, pluginsFile)
// ensure typescript is only registered once
tsRegistered = true
@@ -219,3 +219,10 @@ module.exports = (ipc, pluginsFile, projectRoot) => {
execute(ipc, event, ids, args)
})
}
// for testing purposes
runPlugins.__reset = () => {
tsRegistered = false
}
module.exports = runPlugins

View File

@@ -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,
}

View File

@@ -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)
return cfg
}).then((cfg) => {
return this._initPlugins(cfg, options)
.then((modifiedCfg) => {

View File

@@ -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)

View File

@@ -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),
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)

View File

@@ -23,4 +23,11 @@ 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'),
})
})
})

View File

@@ -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`)

View File

@@ -0,0 +1,3 @@
{
"supportFile": false
}

View File

@@ -0,0 +1,3 @@
it('passes', () => {
expect(true).to.be.true
})

View File

@@ -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 () => {}

View File

@@ -0,0 +1,5 @@
{
"compilerOptions": {
"downlevelIteration": true
}
}

View File

@@ -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

View File

@@ -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')

View File

@@ -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,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', () => {