Files
cypress/packages/electron/lib/electron.js
Cacie Prins e463fdbc61 fix: Redirect spammy electron stderr to a debug sink (#32188)
* wip: system test to reproduce

* system test for alsa stderr

* split cypress from 3rd party stderr at parent process to electron child

* rm garbage warning regexes

* fix newline behavior when parsing internal stderr

* migrate left over console errors

* clean up system test name

* fix typed import

* extract stderr splitting to separate pkg so runner can use @packages/error

* rm new err log from packherd-quire

* handle backpressure

* docs

* some unit tests & coverage for stderr-filtering

* unit tests

* no longer test regexp specific output in spawn unit tests

* filter enabled debug namespaces rather than just cypress namespacesc

* revise stream splitting et al

* try to fix v8 snapshot build??

* fix console.log assertion

* add missing eslint config

* rm unused spies

* fix regexp for optional leading wsp and ansi on debug entries

* update unit tests because sinon

* lint

* colon..

* build stderr-filtering before checking if binary exists

* adds TagStream transform stream, fixes stderr from child proc config

* add build-prod script for stderr-filtering

* changelog

* properly handle backpressure in prefixed content transform stream

* use standard tsconfig?

* better tsconfig

* Add pkgStderrFiltering to monorepoPaths

* add \"files\" manifest

* pipe all stderr to stderr when CYPRESS_INTERNAL_DEBUG_ELECTRON is enabled

* rm explicit build of stderr-filtering in check-if-binary-exists step

* ensure all dependencies of scripts/ are built before scripts are executed in the check-if-binary-exists command

* fix dev version ref

* swap logic

* add stdin piping

* fix exec name on the run-on-dependencies command to be more useful

* use correct env

* rm obsolete type refs

* simplify stderr-filtering public iface, pipe cy-in-cy stderr through filtering tx

* bust cache

* fix mocks

* fix v8-snapshot

* move stderrfiltering to dev pkg in cli

* skip integrity check in ci, if they are out of date things should fail anyway

* copypasta over a portion of stderr-filtering to cli, since cli cannot import @packages

* Delete issues.md

* rm special filtering for cy in cy

* rm too narrow rules file

---------

Co-authored-by: Jennifer Shehane <shehane.jennifer@gmail.com>
Co-authored-by: Jennifer Shehane <jennifer@cypress.io>
Co-authored-by: Bill Glesias <bglesias@gmail.com>
2025-08-19 17:05:53 -04:00

195 lines
5.0 KiB
JavaScript

const cp = require('child_process')
const os = require('os')
const path = require('path')
const debugElectron = require('debug')('cypress:electron')
const Promise = require('bluebird')
const minimist = require('minimist')
const inspector = require('inspector')
const execa = require('execa')
const paths = require('./paths')
const install = require('./install')
let fs = require('fs-extra')
const debugStderr = require('debug')('cypress:internal-stderr')
fs = Promise.promisifyAll(fs)
const { filter, DEBUG_PREFIX } = require('@packages/stderr-filtering')
/**
* If running as root on Linux, no-sandbox must be passed or Chrome will not start
*/
const isSandboxNeeded = () => {
// eslint-disable-next-line no-restricted-properties
return (os.platform() === 'linux') && (process.geteuid() === 0)
}
module.exports = {
installIfNeeded () {
return install.check()
},
install (...args) {
debugElectron('installing %o', { args })
return install.package.apply(install, args)
},
getElectronVersion () {
return install.getElectronVersion()
},
/**
* Returns the Node version bundled inside Electron.
*/
getElectronNodeVersion () {
debugElectron('getting Electron Node version')
const args = []
if (isSandboxNeeded()) {
args.push('--no-sandbox')
}
// runs locally installed "electron" bin alias
const localScript = path.join(__dirname, 'print-node-version.js')
debugElectron('local script that prints Node version %s', localScript)
args.push(localScript)
const options = {
preferLocal: true, // finds the "node_modules/.bin/electron"
timeout: 10000, // prevents hanging Electron if there is an error for some reason
}
debugElectron('Running Electron with %o %o', args, options)
return execa('electron', args, options)
.then((result) => result.stdout)
},
icons () {
return install.icons()
},
cli (argv = []) {
const opts = minimist(argv)
debugElectron('cli options %j', opts)
const pathToApp = argv[0]
if (opts.install) {
return this.installIfNeeded()
}
if (pathToApp) {
return this.open(pathToApp, argv)
}
throw new Error('No path to your app was provided.')
},
open (appPath, argv, cb) {
debugElectron('opening %s', appPath)
appPath = path.resolve(appPath)
const dest = paths.getPathToResources('app')
debugElectron('appPath %s', appPath)
debugElectron('dest path %s', dest)
// make sure this path exists!
return fs.accessAsync(appPath)
.then(() => {
debugElectron('appPath exists %s', appPath)
// clear out the existing symlink
return fs.removeAsync(dest)
}).then(() => {
const symlinkType = paths.getSymlinkType()
debugElectron('making symlink from %s to %s of type %s', appPath, dest, symlinkType)
return fs.ensureSymlinkAsync(appPath, dest, symlinkType)
}).then(() => {
const execPath = paths.getPathToExec()
if (isSandboxNeeded()) {
argv.unshift('--no-sandbox')
}
// we have an active debugger session
if (inspector.url()) {
const dp = process.debugPort + 1
const inspectFlag = process.execArgv.some((f) => f === '--inspect' || f.startsWith('--inspect=')) ? '--inspect' : '--inspect-brk'
argv.unshift(`${inspectFlag}=${dp}`)
} else {
const opts = minimist(argv)
if (opts.inspectBrk) {
if (process.env.CYPRESS_DOCKER_DEV_INSPECT_OVERRIDE) {
argv.unshift(`--inspect-brk=${process.env.CYPRESS_DOCKER_DEV_INSPECT_OVERRIDE}`)
} else {
argv.unshift('--inspect-brk=5566')
}
}
}
debugElectron('spawning %s with args', execPath, argv)
if (debugElectron.enabled) {
// enable the internal chromium logger
argv.push('--enable-logging')
}
const spawned = cp.spawn(execPath, argv, { stdio: 'pipe' })
.on('error', (err) => {
// If electron is throwing an error event, we need to ensure it's
// printed to console.
// eslint-disable-next-line no-console
console.error(err)
return process.exit(1)
})
.on('close', (code, signal) => {
debugElectron('electron closing %o', { code, signal })
if (signal) {
debugElectron('electron exited with a signal, forcing code = 1 %o', { signal })
code = 1
}
if (cb) {
debugElectron('calling callback with code', code)
return cb(code)
}
debugElectron('process.exit with code', code)
return process.exit(code)
})
if ([1, '1'].includes(process.env.ELECTRON_ENABLE_LOGGING)) {
spawned.stderr.pipe(process.stderr)
} else {
spawned.stderr.pipe(filter(process.stderr, debugStderr, DEBUG_PREFIX))
}
spawned.stdout.pipe(process.stdout)
process.stdin.pipe(spawned.stdin)
return spawned
}).catch((err) => {
// eslint-disable-next-line no-console
console.debug(err.stack)
return process.exit(1)
})
},
}