From 6b19381f7949476b164f64c684c636f4743b98e3 Mon Sep 17 00:00:00 2001 From: Blue F Date: Thu, 2 Dec 2021 09:36:58 -0800 Subject: [PATCH] fix: Add support for system node 17 (#19094) --- packages/server/lib/plugins/index.js | 60 +++++++++----- .../server/test/unit/plugins/index_spec.js | 78 +++++++++---------- 2 files changed, 78 insertions(+), 60 deletions(-) diff --git a/packages/server/lib/plugins/index.js b/packages/server/lib/plugins/index.js index f9b176e2f15f..14897b8f3442 100644 --- a/packages/server/lib/plugins/index.js +++ b/packages/server/lib/plugins/index.js @@ -8,6 +8,7 @@ const inspector = require('inspector') const errors = require('../errors') const util = require('./util') const pkg = require('@packages/root') +const semver = require('semver') let pluginsProcess = null let registeredEvents = {} @@ -37,6 +38,43 @@ const registerHandler = (handler) => { handlers.push(handler) } +const getChildOptions = (config) => { + const childOptions = { + stdio: 'pipe', + env: { + ...process.env, + NODE_OPTIONS: process.env.ORIGINAL_NODE_OPTIONS || '', + }, + } + + if (config.resolvedNodePath) { + debug('launching using custom node version %o', _.pick(config, ['resolvedNodePath', 'resolvedNodeVersion'])) + childOptions.execPath = config.resolvedNodePath + } + + // https://github.com/cypress-io/cypress/issues/18914 + // If we're on node version 17 or higher, we need the + // NODE_ENV --openssl-legacy-provider so that webpack can continue to use + // the md4 hash function. This would cause an error prior to node 17 + // though, so we have to detect node's major version before spawning the + // plugins process. + + // To be removed on update to webpack >= 5.61, which no longer relies on + // node's builtin crypto.hash function. + if (semver.satisfies(config.resolvedNodeVersion, '>=17.0.0')) { + childOptions.env.NODE_OPTIONS += ' --openssl-legacy-provider' + } + + if (inspector.url()) { + childOptions.execArgv = _.chain(process.execArgv.slice(0)) + .remove('--inspect-brk') + .push(`--inspect=${process.debugPort + 1}`) + .value() + } + + return childOptions +} + const init = (config, options) => { debug('plugins.init', config.pluginsFile) @@ -82,28 +120,9 @@ const init = (config, options) => { const pluginsFile = config.pluginsFile || path.join(__dirname, 'child', 'default_plugins_file.js') const childIndexFilename = path.join(__dirname, 'child', 'index.js') const childArguments = ['--file', pluginsFile, '--projectRoot', options.projectRoot] - const childOptions = { - stdio: 'pipe', - env: { - ...process.env, - NODE_OPTIONS: process.env.ORIGINAL_NODE_OPTIONS || '', - }, - } - - if (config.resolvedNodePath) { - debug('launching using custom node version %o', _.pick(config, ['resolvedNodePath', 'resolvedNodeVersion'])) - childOptions.execPath = config.resolvedNodePath - } + const childOptions = getChildOptions(config) debug('forking to run %s', childIndexFilename) - - if (inspector.url()) { - childOptions.execArgv = _.chain(process.execArgv.slice(0)) - .remove('--inspect-brk') - .push(`--inspect=${process.debugPort + 1}`) - .value() - } - pluginsProcess = cp.fork(childIndexFilename, childArguments, childOptions) if (pluginsProcess.stdout && pluginsProcess.stderr) { @@ -241,6 +260,7 @@ const _setPluginsProcess = (_pluginsProcess) => { } module.exports = { + getChildOptions, getPluginPid, execute, has, diff --git a/packages/server/test/unit/plugins/index_spec.js b/packages/server/test/unit/plugins/index_spec.js index e76df148fb9f..183d563b054f 100644 --- a/packages/server/test/unit/plugins/index_spec.js +++ b/packages/server/test/unit/plugins/index_spec.js @@ -1,6 +1,5 @@ require('../../spec_helper') -const _ = require('lodash') const mockedEnv = require('mocked-env') const cp = require('child_process') @@ -47,6 +46,44 @@ describe('lib/plugins/index', () => { sinon.stub(util, 'wrapIpc').returns(ipc) }) + context('#getChildOptions', () => { + it('uses system Node when available', () => { + const config = { + resolvedNodePath: '/my/path/to/system/node', + } + + const childOptions = plugins.getChildOptions(config) + + expect(childOptions.execPath).to.eq(config.resolvedNodePath) + }) + + it('uses bundled Node when cannot find system Node', () => { + const config = {} + + const childOptions = plugins.getChildOptions(config) + + expect(childOptions.execPath).to.eq(undefined) + }) + + // https://github.com/cypress-io/cypress/issues/18914 + it('includes --openssl-legacy-provider in node 17+', () => { + const childOptions = plugins.getChildOptions({ + resolvedNodeVersion: 'v17.1.0', + }) + + expect(childOptions.env.NODE_OPTIONS).to.contain('--openssl-legacy-provider') + }) + + // https://github.com/cypress-io/cypress/issues/18914 + it('does not include --openssl-legacy-provider in node <=16', () => { + const childOptions = plugins.getChildOptions({ + resolvedNodeVersion: 'v16.31.0', + }) + + expect(childOptions.env.NODE_OPTIONS).not.to.contain('--openssl-legacy-provider') + }) + }) + context('#init', () => { it('uses noop plugins file if no pluginsFile', () => { // have to fire "loaded" message, otherwise plugins.init promise never resolves @@ -79,45 +116,6 @@ describe('lib/plugins/index', () => { }) }) - it('uses system Node when available', () => { - ipc.on.withArgs('loaded').yields([]) - const systemNode = '/my/path/to/system/node' - const config = { - pluginsFile: 'cypress-plugin', - nodeVersion: 'system', - resolvedNodeVersion: 'v1.2.3', - resolvedNodePath: systemNode, - } - - return plugins.init(config, getOptions()) - .then(() => { - const options = { - stdio: 'pipe', - execPath: systemNode, - } - - expect(_.omit(cp.fork.lastCall.args[2], 'env')).to.eql(options) - }) - }) - - it('uses bundled Node when cannot find system Node', () => { - ipc.on.withArgs('loaded').yields([]) - const config = { - pluginsFile: 'cypress-plugin', - nodeVersion: 'system', - resolvedNodeVersion: 'v1.2.3', - } - - return plugins.init(config, getOptions()) - .then(() => { - const options = { - stdio: 'pipe', - } - - expect(_.omit(cp.fork.lastCall.args[2], 'env')).to.eql(options) - }) - }) - it('calls any handlers registered with the wrapped ipc', () => { ipc.on.withArgs('loaded').yields([]) const handler = sinon.spy()