breaking: update execa from 1.0.0 to 4.1.0 (#32238)

* breaking: update execa from 1.0.0 to 4.1.0 which breaks the yielded cy.exec() object from yielding property code and instead yields exitCode

* fix launchpad tests on linux

* Update cli/CHANGELOG.md

---------

Co-authored-by: Jennifer Shehane <jennifer@cypress.io>
This commit is contained in:
Bill Glesias
2025-08-18 11:52:24 -04:00
committed by GitHub
parent 4bb6f7b3cc
commit f6e0744e38
20 changed files with 57 additions and 67 deletions
+1 -1
View File
@@ -1,2 +1,2 @@
# Bump this version to force CI to re-create the cache from scratch.
8-7-2025
8-13-2025
+5 -5
View File
@@ -38,7 +38,7 @@ mainBuildFilters: &mainBuildFilters
- /^release\/\d+\.\d+\.\d+$/
# use the following branch as well to ensure that v8 snapshot cache updates are fully tested
- 'update-v8-snapshot-cache-on-develop'
- 'mabel/issue-10425-studio-redesign'
- 'breaking/update_execa'
# usually we don't build Mac app - it takes a long time
# but sometimes we want to really confirm we are doing the right thing
@@ -49,7 +49,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: [ 'feat/support_vite_7', << pipeline.git.branch >> ]
- equal: [ 'breaking/update_execa', << pipeline.git.branch >> ]
- equal: [ 'chore/test_cypress_recipes_15', << pipeline.git.branch >> ]
- equal: [ 'mabel/issue-31677-reporter-redesign', << pipeline.git.branch >> ]
- matches:
@@ -62,7 +62,7 @@ linuxArm64WorkflowFilters: &linux-arm64-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: [ 'feat/support_vite_7', << pipeline.git.branch >> ]
- equal: [ 'breaking/update_execa', << pipeline.git.branch >> ]
- matches:
pattern: /^release\/\d+\.\d+\.\d+$/
value: << pipeline.git.branch >>
@@ -85,7 +85,7 @@ windowsWorkflowFilters: &windows-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: [ 'feat/support_vite_7', << pipeline.git.branch >> ]
- equal: [ 'breaking/update_execa', << pipeline.git.branch >> ]
- matches:
pattern: /^release\/\d+\.\d+\.\d+$/
value: << pipeline.git.branch >>
@@ -159,7 +159,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" != "mabel/issue-10425-studio-redesign" ]]; then
echo 'if ! [[ "$CIRCLE_BRANCH" != "develop" && "$CIRCLE_BRANCH" != "release/"* && "$CIRCLE_BRANCH" != "breaking/update_execa" ]]; 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
+1
View File
@@ -14,6 +14,7 @@ _Released 08/12/2025 (PENDING)_
- The application under test's `pagehide` event in Chromium browsers will no longer trigger Cypress's `window:unload` event. Addressed in [#31853](https://github.com/cypress-io/cypress/pull/31853).
- The `Cypress.SelectorPlayground` API has been renamed to `Cypress.ElementSelector`. This API was renamed to accommodate its use for defining `selectorPriority` in Cypress Studio and our future [`cy.prompt` release](https://on.cypress.io/cy-prompt-early-access?utm_source=docs&utm_medium=app-changelog&utm_content=cy-prompt-release). Additionally, the `getSelector` method and the `onElement` option of `defaults` were removed from this API. Addresses [#31801](https://github.com/cypress-io/cypress/issues/31801). Addressed in [#31889](https://github.com/cypress-io/cypress/pull/31889) and [#32098](https://github.com/cypress-io/cypress/pull/32098).
- The direct download option for installing Cypress is no longer supported. Users should install via a package manager. Addressed in [#32249](https://github.com/cypress-io/cypress/pull/32249).
- Updated `execa` from `1.0.0` to `4.1.0`. This changes the `code` property returned by [`cy.exec()`](https://docs.cypress.io/api/commands/exec) to `exitCode`. Addressed in [#32238](https://github.com/cypress-io/cypress/pull/32238).
- **Component Testing breaking changes:**
- Removed support for Angular 17. The minimum supported version is now `18.0.0`. Addresses [#31303](https://github.com/cypress-io/cypress/issues/31303).
- `@cypress/angular` now requires a minimum of `zone.js` `0.14.0`. Addresses [#31582](https://github.com/cypress-io/cypress/issues/31582).
+1 -1
View File
@@ -154,7 +154,7 @@
"eslint-plugin-react": "7.22.0",
"eslint-plugin-react-hooks": "4.2.0",
"eslint-plugin-vue": "7.18.0",
"execa": "4.0.0",
"execa": "4.1.0",
"fast-xml-parser": "^4.5.1",
"filesize": "10.1.1",
"fs-extra": "9.1.0",
+1 -1
View File
@@ -36,7 +36,7 @@
"dedent": "^0.7.0",
"ejs": "^3.1.10",
"endent": "2.0.1",
"execa": "1.0.0",
"execa": "4.1.0",
"front-matter": "^4.0.2",
"fs-extra": "8.1.0",
"get-tsconfig": "4.10.0",
@@ -2,7 +2,7 @@ const { assertLogLength } = require('../../support/utils')
const { _, Promise } = Cypress
describe('src/cy/commands/exec', () => {
const okResponse = { code: 0 }
const okResponse = { exitCode: 0 }
context('#exec', {
execTimeout: 2500,
@@ -302,7 +302,7 @@ describe('src/cy/commands/exec', () => {
describe('when error code is non-zero', () => {
it('throws error that includes useful information and exit code', (done) => {
Cypress.backend.resolves({ code: 1 })
Cypress.backend.resolves({ exitCode: 1 })
cy.on('fail', (err) => {
expect(err.message).to.contain('`cy.exec(\'ls\')` failed because the command exited with a non-zero code.\n\nPass `{failOnNonZeroExit: false}` to ignore exit code failures.')
@@ -316,7 +316,7 @@ describe('src/cy/commands/exec', () => {
})
it('throws error that includes stderr if it exists and is non-empty', (done) => {
Cypress.backend.resolves({ code: 1, stderr: 'error output', stdout: '' })
Cypress.backend.resolves({ exitCode: 1, stderr: 'error output', stdout: '' })
cy.on('fail', (err) => {
expect(err.message).to.contain('Stderr:\nerror output')
@@ -329,7 +329,7 @@ describe('src/cy/commands/exec', () => {
})
it('throws error that includes stdout if it exists and is non-empty', (done) => {
Cypress.backend.resolves({ code: 1, stderr: '', stdout: 'regular output' })
Cypress.backend.resolves({ exitCode: 1, stderr: '', stdout: 'regular output' })
cy.on('fail', (err) => {
expect(err.message).to.contain('\nStdout:\nregular output')
@@ -342,7 +342,7 @@ describe('src/cy/commands/exec', () => {
})
it('throws error that includes stdout and stderr if they exists and are non-empty', (done) => {
Cypress.backend.resolves({ code: 1, stderr: 'error output', stdout: 'regular output' })
Cypress.backend.resolves({ exitCode: 1, stderr: 'error output', stdout: 'regular output' })
cy.on('fail', (err) => {
expect(err.message).to.contain('\nStdout:\nregular output\nStderr:\nerror output')
@@ -355,7 +355,7 @@ describe('src/cy/commands/exec', () => {
it('truncates the stdout and stderr in the error message', (done) => {
Cypress.backend.resolves({
code: 1,
exitCode: 1,
stderr: `${_.range(200).join()}stderr should be truncated`,
stdout: `${_.range(200).join()}stdout should be truncated`,
})
@@ -381,7 +381,7 @@ describe('src/cy/commands/exec', () => {
expect(Yielded).to.deep.eq({
stdout: 'foo',
stderr: '',
code: 1,
exitCode: 1,
})
done()
@@ -392,7 +392,7 @@ describe('src/cy/commands/exec', () => {
describe('and failOnNonZeroExit is false', () => {
it('does not error', () => {
const response = { code: 1, stderr: 'error output', stdout: 'regular output' }
const response = { exitCode: 1, stderr: 'error output', stdout: 'regular output' }
Cypress.backend.resolves(response)
@@ -93,7 +93,7 @@ context('cy.origin misc', { browser: '!webkit' }, () => {
expect(consoleProps.name).to.equal('exec')
expect(consoleProps.type).to.equal('command')
expect(consoleProps.props['Shell Used']).to.be.undefined
expect(consoleProps.props.Yielded).to.have.property('code').that.equals(0)
expect(consoleProps.props.Yielded).to.have.property('exitCode').that.equals(0)
expect(consoleProps.props.Yielded).to.have.property('stderr').that.equals('')
expect(consoleProps.props.Yielded).to.have.property('stdout').that.equals('foobar')
})
+2 -2
View File
@@ -63,7 +63,7 @@ export default (Commands, Cypress, cy) => {
consoleOutput['Shell Used'] = result.shell
}
if ((result.code === 0) || !options.failOnNonZeroExit) {
if ((result.exitCode === 0) || !options.failOnNonZeroExit) {
return result
}
@@ -79,7 +79,7 @@ export default (Commands, Cypress, cy) => {
return $errUtils.throwErrByPath('exec.non_zero_exit', {
onFail: options._log,
args: { cmd, output, code: result.code },
args: { cmd, output, code: result.exitCode },
})
})
.catch(Promise.TimeoutError, { timedOut: true }, () => {
+1 -1
View File
@@ -14,7 +14,7 @@
"test-unit": "echo 'no unit tests'"
},
"devDependencies": {
"cypress-example-kitchensink": "5.0.0",
"cypress-example-kitchensink": "5.1.0",
"gh-pages": "5.0.0",
"gulp": "4.0.2",
"gulp-clean": "0.4.0",
+1 -1
View File
@@ -74,7 +74,7 @@
"cypress-real-events": "1.14.0",
"dayjs": "^1.9.3",
"dedent": "^0.7.0",
"execa": "4.0.0",
"execa": "4.1.0",
"fake-uuid": "^1.0.0",
"floating-vue": "2.0.0-beta.17",
"fs-extra": "9.1.0",
+1 -1
View File
@@ -17,7 +17,7 @@
"dependencies": {
"bluebird": "3.5.3",
"debug": "^4.3.4",
"execa": "4.0.0",
"execa": "4.1.0",
"fs-extra": "9.1.0",
"lodash": "^4.17.21",
"plist": "3.1.0",
@@ -289,12 +289,14 @@ describe('Launchpad: Open Mode', () => {
// so just adding one here
{
id: 'well-known-editor',
binary: '/usr/bin/well-known',
binary: o.platform === 'win32' ? 'cmd.exe' : '/bin/bash',
name: 'Well known editor',
},
]
ctx.coreData.app.projects = [{ projectRoot: '/some/project', savedState: () => Promise.resolve({}) }]
}, {
platform: Cypress.platform,
})
cy.visitLaunchpad()
@@ -315,7 +317,7 @@ describe('Launchpad: Open Mode', () => {
cy.get('@modal').contains('Choose your editor...').click()
cy.get('@modal').contains('Well known editor').click()
cy.get('@modal').contains('Save changes').click()
cy.wait('@SetPreferred').its('request.body.variables.value').should('include', '/usr/bin/well-known')
cy.wait('@SetPreferred').its('request.body.variables.value').should('include', Cypress.platform === 'win32' ? 'cmd.exe' : '/bin/bash')
})
it('opens using finder', () => {
+3 -3
View File
@@ -2,10 +2,10 @@ const Promise = require('bluebird')
const execa = require('execa')
const shellEnv = require('shell-env')
const _ = require('lodash')
const log = require('./log')
const log = require('debug')('cypress:server:exec')
const utils = require('./util/shell')
const pickMainProps = (val) => _.pick(val, ['stdout', 'stderr', 'code'])
const pickMainProps = (val) => _.pick(val, ['stdout', 'stderr', 'exitCode'])
const trimStdio = (val) => {
const result = { ...val }
@@ -32,7 +32,7 @@ module.exports = {
log('and is running command:', options.cmd)
log('in folder:', projectRoot)
return execa.shell(cmd, { cwd, env, shell })
return execa(cmd, { cwd, env, shell: shell || true })
.then((result) => {
// do we want to return all fields returned by execa?
result.shell = shell
+2 -2
View File
@@ -3,7 +3,7 @@ const Promise = require('bluebird')
const execa = require('execa')
const os = require('os')
const commandExistsModule = require('command-exists')
const log = require('../log')
const log = require('debug')('cypress:server:util:shell')
const isWindows = () => {
return os.platform() === 'win32'
@@ -69,7 +69,7 @@ const sourceShellCommand = function (cmd, shell) {
}
const findBash = () => {
return execa.shell('which bash')
return execa('which bash', { shell: true })
.then((val) => val.stdout)
}
+1 -1
View File
@@ -69,7 +69,7 @@
"electron-context-menu": "3.6.1",
"errorhandler": "1.5.1",
"evil-dns": "0.2.0",
"execa": "1.0.0",
"execa": "4.1.0",
"express": "4.21.0",
"fetch-retry-ts": "^1.3.1",
"find-process": "1.4.7",
+3 -3
View File
@@ -167,12 +167,12 @@ console.log('specfiles:', run)
console.log('test command:')
console.log(cmd)
const child = execa.shell(cmd, { env, stdio: 'inherit' })
const child = execa(cmd, { env, stdio: 'inherit', shell: true })
child.on('exit', (code, signal) => {
child.on('exit', (exitCode, signal) => {
if (signal) {
console.error(`tests exited with signal ${signal}`)
}
process.exit(code === null ? 1 : code)
process.exit(exitCode === null ? 1 : exitCode)
})
+11 -11
View File
@@ -27,12 +27,12 @@ describe('lib/exec', function () {
this.timeout(10000)
describe('basic tests', () => {
it('returns only stdout, stderr, and code', () => {
it('returns only stdout, stderr, and exitCode', () => {
return runCommand('echo foo')
.then((result) => {
const props = Object.keys(result)
expect(props).to.deep.eq(['stdout', 'stderr', 'code'])
expect(props).to.deep.eq(['stdout', 'stderr', 'exitCode'])
})
})
@@ -42,7 +42,7 @@ describe('lib/exec', function () {
const expected = {
stdout: 'foo',
stderr: '',
code: 0,
exitCode: 0,
}
expect(result).to.deep.eq(expected)
@@ -64,7 +64,7 @@ describe('lib/exec', function () {
const expected = {
stdout: '\'foo\'',
stderr: '',
code: 0,
exitCode: 0,
}
expect(result).to.deep.eq(expected)
@@ -77,7 +77,7 @@ describe('lib/exec', function () {
const expected = {
stdout: '\'foo bar\'',
stderr: '',
code: 0,
exitCode: 0,
}
expect(result).to.deep.eq(expected)
@@ -119,7 +119,7 @@ describe('lib/exec', function () {
it('reports the stderr', () => {
return runCommand('>&2 echo \'some error\'')
.then((result) => {
expect(result.code).to.eq(0)
expect(result.exitCode).to.eq(0)
expect(result.stderr).to.equal('\'some error\'')
})
@@ -131,7 +131,7 @@ describe('lib/exec', function () {
.then((result) => {
expect(result.stdout).to.equal('')
expect(result.stderr).to.equal('The system cannot find the file specified.')
expect(result.code).to.equal(1)
expect(result.exitCode).to.equal(1)
// stderr should be trimmed already
expect(result.stderr.trim()).to.equal(result.stderr)
@@ -165,7 +165,7 @@ describe('lib/exec', function () {
const expected = {
stdout: 'foo',
stderr: '',
code: 0,
exitCode: 0,
}
expect(result).to.deep.eq(expected)
@@ -178,7 +178,7 @@ describe('lib/exec', function () {
const expected = {
stdout: 'foo bar',
stderr: '',
code: 0,
exitCode: 0,
}
expect(result).to.deep.eq(expected)
@@ -218,7 +218,7 @@ describe('lib/exec', function () {
it('reports the stderr', () => {
return runCommand('>&2 echo \'some error\'')
.then((result) => {
expect(result.code).to.eq(0)
expect(result.exitCode).to.eq(0)
expect(result.stderr).to.equal('some error')
})
@@ -230,7 +230,7 @@ describe('lib/exec', function () {
.then((result) => {
expect(result.stdout).to.equal('')
expect(result.stderr).to.equal('cat: nooope: No such file or directory')
expect(result.code).to.equal(1)
expect(result.exitCode).to.equal(1)
// stderr should be trimmed already
expect(result.stderr.trim()).to.equal(result.stderr)
@@ -10,7 +10,8 @@ const SUPPRESS_WARNING = `require('${__dirname}/../../../lib/util/suppress_warni
describe('lib/util/suppress_warnings', function () {
it('tls.connect emits warning if NODE_TLS_REJECT_UNAUTHORIZED=0 and not suppressed', function () {
return execa.shell(`node -e "${TLS_CONNECT}"`, {
return execa(`node -e "${TLS_CONNECT}"`, {
shell: true,
env: {
'NODE_TLS_REJECT_UNAUTHORIZED': '0',
},
@@ -22,7 +23,8 @@ describe('lib/util/suppress_warnings', function () {
it('tls.connect does not emit warning if NODE_TLS_REJECT_UNAUTHORIZED=0 and suppressed', function () {
// test 2 sequential tls.connects
return execa.shell(`node -e "${SUPPRESS_WARNING} ${TLS_CONNECT} ${TLS_CONNECT}"`, {
return execa(`node -e "${SUPPRESS_WARNING} ${TLS_CONNECT} ${TLS_CONNECT}"`, {
shell: true,
env: {
'NODE_TLS_REJECT_UNAUTHORIZED': '0',
},
+1 -1
View File
@@ -59,7 +59,7 @@
"dedent": "^0.7.0",
"dockerode": "3.3.1",
"esbuild": "^0.15.3",
"execa": "4",
"execa": "4.1.0",
"express": "4.21.0",
"express-session": "1.16.1",
"express-useragent": "1.0.15",
+5 -20
View File
@@ -13781,10 +13781,10 @@ cypress-each@^1.11.0:
resolved "https://registry.yarnpkg.com/cypress-each/-/cypress-each-1.11.0.tgz#013c9b43a950f157bcf082d4bd0bb424fb370441"
integrity sha512-zeqeQkppPL6BKLIJdfR5IUoZRrxRudApJapnFzWCkkrmefQSqdlBma2fzhmniSJ3TRhxe5xpK3W3/l8aCrHvwQ==
cypress-example-kitchensink@5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/cypress-example-kitchensink/-/cypress-example-kitchensink-5.0.0.tgz#d3757014cd38c94e1cda0d18d953a19dd4c1d7d5"
integrity sha512-/u81WtivUHX9gObcr4Fjj6a0FhJFqIQoN++k2Wqmeo2bVkO7AtOwVFGoy/V0LhY6MsfXXkQ0pklVFhcDLi4yxA==
cypress-example-kitchensink@5.1.0:
version "5.1.0"
resolved "https://registry.yarnpkg.com/cypress-example-kitchensink/-/cypress-example-kitchensink-5.1.0.tgz#fd5dce57b6251d1642cc1a523e4715fc474528f9"
integrity sha512-vuGHIz4fi3CpQ+/TFIXMbaB6qODFDRZGlbhpbKmKn0Iytwj5EMvGhHxCoDXRNXa1DUCp7AMQrl/i16HwcLFKRw==
dependencies:
npm-run-all2 "7.0.2"
serve "14.2.4"
@@ -16279,7 +16279,7 @@ execa@1.0.0, execa@^1.0.0:
signal-exit "^3.0.0"
strip-eof "^1.0.0"
execa@4, execa@4.1.0:
execa@4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/execa/-/execa-4.1.0.tgz#4e5491ad1572f2f17a77d388c6c857135b22847a"
integrity sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==
@@ -16294,21 +16294,6 @@ execa@4, execa@4.1.0:
signal-exit "^3.0.2"
strip-final-newline "^2.0.0"
execa@4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/execa/-/execa-4.0.0.tgz#7f37d6ec17f09e6b8fc53288611695b6d12b9daf"
integrity sha512-JbDUxwV3BoT5ZVXQrSVbAiaXhXUkIwvbhPIwZ0N13kX+5yCzOhUNdocxB/UQRuYOHRYYwAxKYwJYc0T4D12pDA==
dependencies:
cross-spawn "^7.0.0"
get-stream "^5.0.0"
human-signals "^1.1.1"
is-stream "^2.0.0"
merge-stream "^2.0.0"
npm-run-path "^4.0.0"
onetime "^5.1.0"
signal-exit "^3.0.2"
strip-final-newline "^2.0.0"
execa@5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/execa/-/execa-5.0.0.tgz#4029b0007998a841fbd1032e5f4de86a3c1e3376"