From bd9852b62202657602362d34c925b709ecd4e99e Mon Sep 17 00:00:00 2001 From: Matthew Schile Date: Thu, 18 Jun 2026 14:39:34 -0600 Subject: [PATCH 1/3] feat: add explicit cypress install step after dependency install Package managers may no longer run a dependency's postinstall script by default (pnpm and Yarn Berry already block them, npm 12 will via RFC #868). Cypress downloads its binary via postinstall, so on a cold cache the binary was not downloaded and `cypress verify` failed. Run a package-manager-aware `cypress install` after dependencies are installed (npx/yarn/pnpm), keeping the binary available regardless of package manager. The step is a no-op when the binary is already present and is skipped when CYPRESS_INSTALL_BINARY=0. Closes #1798 Co-Authored-By: Claude Opus 4.8 --- dist/index.js | 78 ++++++++++++++++++++++++++++++++++++++++++--------- index.js | 76 +++++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 129 insertions(+), 25 deletions(-) diff --git a/dist/index.js b/dist/index.js index c7cbc3422..b6876be3a 100644 --- a/dist/index.js +++ b/dist/index.js @@ -101894,6 +101894,55 @@ const install = () => { } } +/** + * Runs an explicit, package-manager-aware "cypress install" after the + * dependencies have been installed. This downloads the Cypress binary when + * it is missing and is a no-op when it is already present. + * + * Package managers may no longer run a dependency's `postinstall` script by + * default, which means the script that normally downloads the Cypress binary + * no longer runs during a package-manager install. Running the install + * explicitly keeps the binary available on a cold cache regardless of package + * manager. + */ +const installCypressBinary = () => { + debug('installing Cypress binary') + + if (isCypressBinarySkipped()) { + debug('Skipping Cypress install, binary installation is disabled') + return Promise.resolve() + } + + if (useYarn()) { + debug('installing Cypress binary using Yarn') + return io.which('yarn', true).then((yarnPath) => { + return exec.exec( + quote(yarnPath), + ['cypress', 'install'], + cypressCommandOptions + ) + }) + } else if (usePnpm()) { + debug('installing Cypress binary using pnpm') + return io.which('pnpm', true).then((pnpmPath) => { + return exec.exec( + quote(pnpmPath), + ['cypress', 'install'], + cypressCommandOptions + ) + }) + } else { + debug('installing Cypress binary using npx') + return io.which('npx', true).then((npxPath) => { + return exec.exec( + quote(npxPath), + ['cypress', 'install'], + cypressCommandOptions + ) + }) + } +} + const listCypressBinaries = () => { debug( `Cypress versions in the cache folder ${CYPRESS_CACHE_FOLDER}` @@ -102628,19 +102677,22 @@ const installMaybe = () => { return install().then(() => { debug('install has finished') - return listCypressBinaries().then(() => { - if (npmCacheHit && cypressCacheHit) { - debug('no need to verify Cypress binary or save caches') - return Promise.resolve(undefined) - } - - debug('verifying Cypress binary') - const saveNpmPromise = packageManagerCacheEnabled - ? saveCachedNpm() - : Promise.resolve(undefined) - return verifyCypressBinary() - .then(() => saveNpmPromise) - .then(saveCachedCypressBinary) + return installCypressBinary().then(() => { + debug('Cypress binary install has finished') + return listCypressBinaries().then(() => { + if (npmCacheHit && cypressCacheHit) { + debug('no need to verify Cypress binary or save caches') + return Promise.resolve(undefined) + } + + debug('verifying Cypress binary') + const saveNpmPromise = packageManagerCacheEnabled + ? saveCachedNpm() + : Promise.resolve(undefined) + return verifyCypressBinary() + .then(() => saveNpmPromise) + .then(saveCachedCypressBinary) + }) }) }) }) diff --git a/index.js b/index.js index 75b790792..96c9eaaa6 100644 --- a/index.js +++ b/index.js @@ -273,6 +273,55 @@ const install = () => { } } +/** + * Runs an explicit, package-manager-aware "cypress install" after the + * dependencies have been installed. This downloads the Cypress binary when + * it is missing and is a no-op when it is already present. + * + * Package managers may no longer run a dependency's `postinstall` script by + * default, which means the script that normally downloads the Cypress binary + * no longer runs during a package-manager install. Running the install + * explicitly keeps the binary available on a cold cache regardless of package + * manager. + */ +const installCypressBinary = () => { + debug('installing Cypress binary') + + if (isCypressBinarySkipped()) { + debug('Skipping Cypress install, binary installation is disabled') + return Promise.resolve() + } + + if (useYarn()) { + debug('installing Cypress binary using Yarn') + return io.which('yarn', true).then((yarnPath) => { + return exec.exec( + quote(yarnPath), + ['cypress', 'install'], + cypressCommandOptions + ) + }) + } else if (usePnpm()) { + debug('installing Cypress binary using pnpm') + return io.which('pnpm', true).then((pnpmPath) => { + return exec.exec( + quote(pnpmPath), + ['cypress', 'install'], + cypressCommandOptions + ) + }) + } else { + debug('installing Cypress binary using npx') + return io.which('npx', true).then((npxPath) => { + return exec.exec( + quote(npxPath), + ['cypress', 'install'], + cypressCommandOptions + ) + }) + } +} + const listCypressBinaries = () => { debug( `Cypress versions in the cache folder ${CYPRESS_CACHE_FOLDER}` @@ -1007,19 +1056,22 @@ const installMaybe = () => { return install().then(() => { debug('install has finished') - return listCypressBinaries().then(() => { - if (npmCacheHit && cypressCacheHit) { - debug('no need to verify Cypress binary or save caches') - return Promise.resolve(undefined) - } + return installCypressBinary().then(() => { + debug('Cypress binary install has finished') + return listCypressBinaries().then(() => { + if (npmCacheHit && cypressCacheHit) { + debug('no need to verify Cypress binary or save caches') + return Promise.resolve(undefined) + } - debug('verifying Cypress binary') - const saveNpmPromise = packageManagerCacheEnabled - ? saveCachedNpm() - : Promise.resolve(undefined) - return verifyCypressBinary() - .then(() => saveNpmPromise) - .then(saveCachedCypressBinary) + debug('verifying Cypress binary') + const saveNpmPromise = packageManagerCacheEnabled + ? saveCachedNpm() + : Promise.resolve(undefined) + return verifyCypressBinary() + .then(() => saveNpmPromise) + .then(saveCachedCypressBinary) + }) }) }) }) From b05a651aae57bd5cdee143e304df66a30e10b5fe Mon Sep 17 00:00:00 2001 From: Matthew Schile Date: Thu, 18 Jun 2026 22:31:10 -0600 Subject: [PATCH 2/3] fix: use npx for explicit cypress install across all package managers `pnpm cypress install` fails to resolve the cypress binary inside pnpm workspace packages (ERR_PNPM_RECURSIVE_EXEC_FIRST_FAIL). Match the existing `cypress verify` / `cypress cache list` steps, which invoke cypress via npx for every package manager, so the binary resolves consistently in pnpm and Yarn workspaces. Co-Authored-By: Claude Opus 4.8 --- dist/index.js | 45 ++++++++++++++------------------------------- index.js | 45 ++++++++++++++------------------------------- 2 files changed, 28 insertions(+), 62 deletions(-) diff --git a/dist/index.js b/dist/index.js index b6876be3a..bb76bac7d 100644 --- a/dist/index.js +++ b/dist/index.js @@ -101895,15 +101895,19 @@ const install = () => { } /** - * Runs an explicit, package-manager-aware "cypress install" after the - * dependencies have been installed. This downloads the Cypress binary when - * it is missing and is a no-op when it is already present. + * Runs an explicit "cypress install" after the dependencies have been + * installed. This downloads the Cypress binary when it is missing and is a + * no-op when it is already present. * * Package managers may no longer run a dependency's `postinstall` script by * default, which means the script that normally downloads the Cypress binary * no longer runs during a package-manager install. Running the install * explicitly keeps the binary available on a cold cache regardless of package * manager. + * + * Like "cypress verify" and "cypress cache list", this is invoked via npx for + * all package managers so the Cypress binary is resolved consistently, + * including within pnpm and Yarn workspaces. */ const installCypressBinary = () => { debug('installing Cypress binary') @@ -101913,34 +101917,13 @@ const installCypressBinary = () => { return Promise.resolve() } - if (useYarn()) { - debug('installing Cypress binary using Yarn') - return io.which('yarn', true).then((yarnPath) => { - return exec.exec( - quote(yarnPath), - ['cypress', 'install'], - cypressCommandOptions - ) - }) - } else if (usePnpm()) { - debug('installing Cypress binary using pnpm') - return io.which('pnpm', true).then((pnpmPath) => { - return exec.exec( - quote(pnpmPath), - ['cypress', 'install'], - cypressCommandOptions - ) - }) - } else { - debug('installing Cypress binary using npx') - return io.which('npx', true).then((npxPath) => { - return exec.exec( - quote(npxPath), - ['cypress', 'install'], - cypressCommandOptions - ) - }) - } + return io.which('npx', true).then((npxPath) => { + return exec.exec( + quote(npxPath), + ['cypress', 'install'], + cypressCommandOptions + ) + }) } const listCypressBinaries = () => { diff --git a/index.js b/index.js index 96c9eaaa6..111ba342a 100644 --- a/index.js +++ b/index.js @@ -274,15 +274,19 @@ const install = () => { } /** - * Runs an explicit, package-manager-aware "cypress install" after the - * dependencies have been installed. This downloads the Cypress binary when - * it is missing and is a no-op when it is already present. + * Runs an explicit "cypress install" after the dependencies have been + * installed. This downloads the Cypress binary when it is missing and is a + * no-op when it is already present. * * Package managers may no longer run a dependency's `postinstall` script by * default, which means the script that normally downloads the Cypress binary * no longer runs during a package-manager install. Running the install * explicitly keeps the binary available on a cold cache regardless of package * manager. + * + * Like "cypress verify" and "cypress cache list", this is invoked via npx for + * all package managers so the Cypress binary is resolved consistently, + * including within pnpm and Yarn workspaces. */ const installCypressBinary = () => { debug('installing Cypress binary') @@ -292,34 +296,13 @@ const installCypressBinary = () => { return Promise.resolve() } - if (useYarn()) { - debug('installing Cypress binary using Yarn') - return io.which('yarn', true).then((yarnPath) => { - return exec.exec( - quote(yarnPath), - ['cypress', 'install'], - cypressCommandOptions - ) - }) - } else if (usePnpm()) { - debug('installing Cypress binary using pnpm') - return io.which('pnpm', true).then((pnpmPath) => { - return exec.exec( - quote(pnpmPath), - ['cypress', 'install'], - cypressCommandOptions - ) - }) - } else { - debug('installing Cypress binary using npx') - return io.which('npx', true).then((npxPath) => { - return exec.exec( - quote(npxPath), - ['cypress', 'install'], - cypressCommandOptions - ) - }) - } + return io.which('npx', true).then((npxPath) => { + return exec.exec( + quote(npxPath), + ['cypress', 'install'], + cypressCommandOptions + ) + }) } const listCypressBinaries = () => { From 98240a0c221c1e3b1012bb4aed6bb7d4d06d07dc Mon Sep 17 00:00:00 2001 From: Matthew Schile Date: Thu, 18 Jun 2026 22:57:35 -0600 Subject: [PATCH 3/3] docs: trim comment on npx cypress install rationale Co-Authored-By: Claude Opus 4.8 --- dist/index.js | 3 +-- index.js | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/dist/index.js b/dist/index.js index bb76bac7d..46caa3c9c 100644 --- a/dist/index.js +++ b/dist/index.js @@ -101906,8 +101906,7 @@ const install = () => { * manager. * * Like "cypress verify" and "cypress cache list", this is invoked via npx for - * all package managers so the Cypress binary is resolved consistently, - * including within pnpm and Yarn workspaces. + * all package managers so the Cypress binary is resolved consistently. */ const installCypressBinary = () => { debug('installing Cypress binary') diff --git a/index.js b/index.js index 111ba342a..12d622a2d 100644 --- a/index.js +++ b/index.js @@ -285,8 +285,7 @@ const install = () => { * manager. * * Like "cypress verify" and "cypress cache list", this is invoked via npx for - * all package managers so the Cypress binary is resolved consistently, - * including within pnpm and Yarn workspaces. + * all package managers so the Cypress binary is resolved consistently. */ const installCypressBinary = () => { debug('installing Cypress binary')