From 73de0a8d4db7fc807326f640e8843d04b1b426bb Mon Sep 17 00:00:00 2001 From: Ryan Manuel Date: Tue, 10 Dec 2024 19:36:17 -0700 Subject: [PATCH] fix: error loading config file with node 22.12.0 (#30730) --- .circleci/workflows.yml | 4 +- cli/CHANGELOG.md | 1 + .../data-context/src/data/ProjectConfigIpc.ts | 8 ++++ .../test/unit/data/ProjectConfigIpc.spec.ts | 46 ++++++++----------- .../config-with-ts-module/cypress.config.ts | 2 +- .../test-binary/node_versions_spec.ts | 2 + 6 files changed, 33 insertions(+), 30 deletions(-) diff --git a/.circleci/workflows.yml b/.circleci/workflows.yml index 68996b55bf1c..109dd3d2378e 100644 --- a/.circleci/workflows.yml +++ b/.circleci/workflows.yml @@ -42,7 +42,7 @@ macWorkflowFilters: &darwin-workflow-filters - equal: [ develop, << pipeline.git.branch >> ] # use the following branch as well to ensure that v8 snapshot cache updates are fully tested - equal: [ 'update-v8-snapshot-cache-on-develop', << pipeline.git.branch >> ] - - equal: [ 'chore/update_reporter_mobx', << pipeline.git.branch >> ] + - equal: [ 'ryanm/experiment/esm', << pipeline.git.branch >> ] - matches: pattern: /^release\/\d+\.\d+\.\d+$/ value: << pipeline.git.branch >> @@ -152,7 +152,7 @@ commands: name: Set environment variable to determine whether or not to persist artifacts command: | echo "Setting SHOULD_PERSIST_ARTIFACTS variable" - echo 'if ! [[ "$CIRCLE_BRANCH" != "develop" && "$CIRCLE_BRANCH" != "release/"* && "$CIRCLE_BRANCH" != "chore/update_reporter_mobx" ]]; then + echo 'if ! [[ "$CIRCLE_BRANCH" != "develop" && "$CIRCLE_BRANCH" != "release/"* && "$CIRCLE_BRANCH" != "ryanm/experiment/esm" ]]; then export SHOULD_PERSIST_ARTIFACTS=true fi' >> "$BASH_ENV" # You must run `setup_should_persist_artifacts` command and be using bash before running this command diff --git a/cli/CHANGELOG.md b/cli/CHANGELOG.md index 9b233b0d3c42..d3bff4f49f5c 100644 --- a/cli/CHANGELOG.md +++ b/cli/CHANGELOG.md @@ -7,6 +7,7 @@ _Released 12/17/2024 (PENDING)_ - Fixed an issue where targets may hang if `Network.enable` is not implemented for the target. Addresses [#29876](https://github.com/cypress-io/cypress/issues/29876). - Updated Firefox `userChrome.css` to correctly hide the toolbox during headless mode. Addresses [#30721](https://github.com/cypress-io/cypress/issues/30721). +- Fixed an issue loading the `cypress.config.ts` file with Node.js version `22.12.0`. Addresses [#30715](https://github.com/cypress-io/cypress/issues/30715). **Dependency Updates:** diff --git a/packages/data-context/src/data/ProjectConfigIpc.ts b/packages/data-context/src/data/ProjectConfigIpc.ts index d95f7c687c8c..eaa11a34772f 100644 --- a/packages/data-context/src/data/ProjectConfigIpc.ts +++ b/packages/data-context/src/data/ProjectConfigIpc.ts @@ -313,6 +313,14 @@ export class ProjectConfigIpc extends EventEmitter { tsNodeEsmLoader = `${tsNodeEsmLoader} --no-experimental-detect-module` } + // in nodejs 22.12.0, the --experimental-require-module option is now enabled by default. + // We need to disable it with the --no-experimental-require-module flag. + // @see https://github.com/cypress-io/cypress/issues/30715 + if (this.nodeVersion && semver.gte(this.nodeVersion, '22.12.0')) { + debug(`detected node version ${this.nodeVersion}, adding --no-experimental-require-module option to child_process NODE_OPTIONS.`) + tsNodeEsmLoader = `${tsNodeEsmLoader} --no-experimental-require-module` + } + if (childOptions.env.NODE_OPTIONS) { childOptions.env.NODE_OPTIONS += ` ${tsNodeEsmLoader}` } else { diff --git a/packages/data-context/test/unit/data/ProjectConfigIpc.spec.ts b/packages/data-context/test/unit/data/ProjectConfigIpc.spec.ts index 70117aa1b41a..dc9e965b13d6 100644 --- a/packages/data-context/test/unit/data/ProjectConfigIpc.spec.ts +++ b/packages/data-context/test/unit/data/ProjectConfigIpc.spec.ts @@ -1,5 +1,6 @@ import childProcess from 'child_process' import { expect } from 'chai' +import semver from 'semver' import sinon from 'sinon' import { scaffoldMigrationProject as scaffoldProject } from '../helper' import { ProjectConfigIpc } from '../../../src/data/ProjectConfigIpc' @@ -34,8 +35,11 @@ describe('ProjectConfigIpc', () => { }) context('forkChildProcess', () => { - const NODE_VERSIONS = ['18.20.4', '20.17.0'] - const NODE_VERSIONS_22_7_0_AND_UP = ['22.7.0', '22.11.4'] + // some of these node versions may not exist, but we want to verify + // the experimental flags are correctly disabled for future versions + const NODE_VERSIONS = ['18.20.4', '20.17.0', '22.7.0', '22.11.4', '22.12.0', '22.15.0'] + const experimentalDetectModuleIntroduced = '22.7.0' + const experimentalRequireModuleIntroduced = '22.12.0' let projectConfigIpc let forkSpy @@ -52,10 +56,10 @@ describe('ProjectConfigIpc', () => { }) context('typescript', () => { - [...NODE_VERSIONS, ...NODE_VERSIONS_22_7_0_AND_UP].forEach((nodeVersion) => { + [...NODE_VERSIONS].forEach((nodeVersion) => { context(`node v${nodeVersion}`, () => { context('ESM', () => { - it('uses the experimental module loader if ESM is being used with typescript', async () => { + it('passes the correct experimental flags if ESM is being used with typescript', async () => { // @ts-expect-error const projectPath = await scaffoldProject('config-cjs-and-esm/config-with-ts-module') @@ -77,35 +81,23 @@ describe('ProjectConfigIpc', () => { NODE_OPTIONS: sinon.match('--experimental-specifier-resolution=node --loader'), }, })) - }) - - // @see https://github.com/cypress-io/cypress/issues/30084 - // at time of writing, 22.11.4 is a node version that does not exist. We are using this version to test the logic for future proofing. - if (NODE_VERSIONS_22_7_0_AND_UP.includes(nodeVersion)) { - it(`additionally adds --no-experimental-detect-module for node versions 22.7.0 and up if ESM is being used with typescript`, async () => { - // @ts-expect-error - const projectPath = await scaffoldProject('config-cjs-and-esm/config-with-ts-module') - - const MOCK_NODE_PATH = `/Users/foo/.nvm/versions/node/v${nodeVersion}/bin/node` - const MOCK_NODE_VERSION = nodeVersion - - projectConfigIpc = new ProjectConfigIpc( - MOCK_NODE_PATH, - MOCK_NODE_VERSION, - projectPath, - 'cypress.config.js', - false, - (error) => {}, - () => {}, - ) + if (semver.gte(nodeVersion, experimentalDetectModuleIntroduced)) { expect(forkSpy).to.have.been.calledWith(sinon.match.string, sinon.match.array, sinon.match({ env: { NODE_OPTIONS: sinon.match('--no-experimental-detect-module'), }, })) - }) - } + } + + if (semver.gte(nodeVersion, experimentalRequireModuleIntroduced)) { + expect(forkSpy).to.have.been.calledWith(sinon.match.string, sinon.match.array, sinon.match({ + env: { + NODE_OPTIONS: sinon.match('--no-experimental-require-module'), + }, + })) + } + }) }) context('CommonJS', () => { diff --git a/system-tests/projects/config-cjs-and-esm/config-with-ts-module/cypress.config.ts b/system-tests/projects/config-cjs-and-esm/config-with-ts-module/cypress.config.ts index 70c232267fc3..de29bc7da79a 100644 --- a/system-tests/projects/config-cjs-and-esm/config-with-ts-module/cypress.config.ts +++ b/system-tests/projects/config-cjs-and-esm/config-with-ts-module/cypress.config.ts @@ -3,7 +3,7 @@ import { defineConfig } from 'cypress' export default defineConfig({ e2e: { supportFile: false, - setupNodeEvents: async (_, config) => { + setupNodeEvents: async (_, config: Cypress.PluginConfigOptions) => { await import('find-up') return config diff --git a/system-tests/test-binary/node_versions_spec.ts b/system-tests/test-binary/node_versions_spec.ts index 32c0d7574a2b..2c7edff645ef 100644 --- a/system-tests/test-binary/node_versions_spec.ts +++ b/system-tests/test-binary/node_versions_spec.ts @@ -30,6 +30,7 @@ describe('binary node versions', () => { 'cypress/base:20.12.2', 'cypress/base:22.0.0', 'cypress/base:22.7.0', + 'cypress/base:22.12.0', ].forEach(smokeTestDockerImage) }) @@ -39,6 +40,7 @@ describe('type: module', () => { 'cypress/base:20.12.2', 'cypress/base:22.0.0', 'cypress/base:22.7.0', + 'cypress/base:22.12.0', ].forEach((dockerImage) => { systemTests.it(`can run in ${dockerImage}`, { withBinary: true,