Files
cypress/scripts/binary/index.js
Cacie Prins 6b9f27c95d dependency: electron@36.4.0 (#31912)
* dependency: update Electron to 34

* setup workflows to run against binary branch and on all tests

* changelog entry

* node version did bump minorly

* Update base-internal image to match new node version

* fix typo

* changelog updates

* bumping to newest version just released today - hopefully solves glibc error

* fix cy in cy

* remove extra register_ts_node require

* updated lockfile

* upgrade better-sqlite3

* changelog

* update electron in top level package.json

* ts issue, update to use binary workflow for e35, update ancillary deps

* update gh issue templates

* bump missed image names and engines field

* node 22

* snapgen?

* ts issue, log errors even if err.stderr/stdout is null

* more logging

* defer http-proxy common.js due to regexp issue in v8 13.4.* - 13.8.91

* update images for node 22.15.1, use bullseye instead of buster for bettersqlite

* use bullseye image for glibc2.31 build of bettersqlite

* use electron-36 publish binary branch

* node-abi update, set http-proxy deferred in darwin

* update .node-version

* attempt to patch http-proxy to immediately defer  http-proxy/lib/http-proxy/common.js

* empty commit [run ci]

* better patch?

* changelog

* changelog

* Updates v8 snapshots to fix windows build (#31918)

* use node 22 in the v8 snapshot update workflow

* index on windows-v8-snapshots: a013464197 use node 22 in the v8 snapshot update workflow

* index on windows-v8-snapshots: a013464197 use node 22 in the v8 snapshot update workflow

* index on windows-v8-snapshots: a013464197 use node 22 in the v8 snapshot update workflow

* run workflows on windows/mac

---------

Co-authored-by: cypress-bot[bot] <+cypress-bot[bot]@users.noreply.github.com>

* update protocol system test snapshots (#31925)

* use snapshot to verify the error message on invalid json (#31926)

* chore: account for all node: internal stacks when trying to calculate the code frame. Accounts additionally for node:diagnostics_channel (#31935)

* Fixes electron 36 integrity checks (#31956)

* update the fs.readFileSync integrity check expectation

* maybe this fn is missing from the expected stack?

* more debug, change the stack up a little

* actual fn name is traceSync

* logging

* logging

* remove logging from integrity check

* maybe circle api changed?

* correct params

* inspect stack frames for differences

* have to manually serialize the stack frames

* change expectation

* update expected global keys

* additional allow list

* update key allow list

* increase zipfile size limit on non-windows builds

* revert logging changes

* Update scripts/binary/binary-integrity-check-source.js

* increase timeout to 120s for darwin fsevents/native module test (#31975)

* print out stdout for darwin test

* try and fix test

* update readme re: browsers-internal images, ensure module_api_spec binary test uses correct electron version

* Update .circleci/workflows.yml

Co-authored-by: Bill Glesias <bglesias@gmail.com>

* Update .circleci/workflows.yml

Co-authored-by: Bill Glesias <bglesias@gmail.com>

* trigger 15.0.0 binary pipeline rather than electron-36 specific one

* Update cli/CHANGELOG.md

Co-authored-by: Bill Glesias <bglesias@gmail.com>

* Update cli/CHANGELOG.md

---------

Co-authored-by: Jennifer Shehane <jennifer@cypress.io>
Co-authored-by: Ryan Manuel <ryanm@cypress.io>
Co-authored-by: Jennifer Shehane <shehane.jennifer@gmail.com>
Co-authored-by: cypress-bot[bot] <+cypress-bot[bot]@users.noreply.github.com>
Co-authored-by: Bill Glesias <bglesias@gmail.com>
2025-07-07 13:03:13 -04:00

434 lines
11 KiB
JavaScript

// store the cwd
const cwd = process.cwd()
const path = require('path')
const _ = require('lodash')
const os = require('os')
const simpleGit = require('simple-git')
const chalk = require('chalk')
const Promise = require('bluebird')
const minimist = require('minimist')
const la = require('lazy-ass')
const check = require('check-more-types')
const debug = require('debug')('cypress:binary')
const rp = require('@cypress/request-promise')
const zip = require('./zip')
const ask = require('./ask')
const meta = require('./meta')
const build = require('./build')
const upload = require('./upload')
const questionsRemain = require('./util/questions-remain')
const uploadUtils = require('./util/upload')
const { uploadArtifactToS3 } = require('./upload-build-artifact')
const { moveBinaries } = require('./move-binaries')
const { exec } = require('child_process')
const xvfb = require('../../cli/lib/exec/xvfb')
const smoke = require('./smoke')
const verify = require('../../cli/lib/tasks/verify')
const execa = require('execa')
const log = function (msg) {
const time = new Date()
const timeStamp = time.toLocaleTimeString()
console.log(timeStamp, chalk.yellow(msg), chalk.blue(meta.PLATFORM))
}
const success = (str) => {
return console.log(chalk.bgGreen(` ${chalk.black(str)} `))
}
const fail = (str) => {
return console.log(chalk.bgRed(` ${chalk.black(str)} `))
}
const zippedFilename = () => upload.zipName
// goes through the list of properties and asks relevant question
// resolves with all relevant options set
// if the property already exists, skips the question
const askMissingOptions = function (properties = []) {
const questions = {
platform: ask.whichPlatform,
version: ask.deployNewVersion,
// note: zip file might not be absolute
zip: ask.whichZipFile,
commit: ask.toCommit,
}
const pickedQuestions = _.pick(questions, properties)
return questionsRemain(pickedQuestions)
}
async function testExecutableVersion (buildAppExecutable, version) {
log('#testVersion')
console.log('testing built app executable version')
console.log(`by calling: ${buildAppExecutable} --version`)
const args = ['--version']
if (verify.needsSandbox()) {
args.push('--no-sandbox')
}
const result = await execa(buildAppExecutable, args)
la(result.stdout, 'missing output when getting built version', result)
console.log('built app version', result.stdout)
la(
result.stdout.trim() === version.trim(),
`different version reported: '${result.stdout.trim()}' from input version to build '${version.trim()}'`,
)
console.log('✅ using --version on the Cypress binary works')
}
// hack for @packages/server modifying cwd
process.chdir(cwd)
const commitVersion = function (version) {
const msg = `release ${version} [skip ci]`
return simpleGit.commit(msg, {
'--allow-empty': null,
})
}
const deploy = {
meta,
parseOptions (argv) {
const opts = minimist(argv, {
alias: {
zip: ['zipFile', 'zip-file', 'filename'],
},
})
if (opts['skip-tests']) {
opts.runTests = false
}
if (!opts.platform) opts.platform = os.platform()
debug('parsed command line options')
debug(opts)
return opts
},
release () {
// read off the argv
const options = this.parseOptions(process.argv)
const release = ({ version, commit }) => {
return upload.s3Manifest(version)
.then(() => {
if (commit) {
return commitVersion(version)
}
}).then(() => {
return success('Release Complete')
}).catch((err) => {
fail('Release Failed')
throw err
})
.then(() => {
return this.checkDownloads({ version })
})
}
return askMissingOptions(['version'])(options)
.then(release)
},
checkDownloads ({ version }) {
const systems = [
{ platform: 'linux', arch: 'x64' },
{ platform: 'linux', arch: 'arm64' },
{ platform: 'darwin', arch: 'x64' },
{ platform: 'darwin', arch: 'arm64' },
{ platform: 'win32', arch: 'x64' },
]
const urlExists = (url) => {
return rp.head(url)
.then(() => true)
.catch(() => false)
}
const checkSystem = ({ platform, arch }) => {
const url = `https://download.cypress.io/desktop/${version}?platform=${platform}&arch=${arch}`
const system = `${platform}-${arch}`
process.stdout.write(`Checking for ${chalk.yellow(system)} at ${chalk.cyan(url)} ... `)
return urlExists(url)
.then((exists) => {
const result = exists ? '✅' : '❌'
process.stdout.write(`${result}\n`)
return { exists, platform, arch, url }
})
}
const allEnsured = (results) => {
return !results.filter(({ exists }) => !exists).length
}
return Promise.mapSeries(systems, checkSystem)
.then((results) => {
if (allEnsured(results)) return results
console.log(chalk.red(`\nCould not ensure v${version} of the Cypress binary is available for the following systems:`))
return results
})
.map((result) => {
const { exists, platform, arch, url } = result
if (exists) return result
console.log(`
${chalk.yellow('Platform')}: ${platform}
${chalk.yellow('Arch')}: ${arch}
${chalk.yellow('URL')}: ${url}`)
return result
})
.then((results) => {
if (allEnsured(results)) return
const purgeCommand = `yarn binary-purge --version ${version}`
const ensureCommand = `yarn binary-ensure --version ${version}`
console.log(`\nPurge the cloudflare cache with ${chalk.yellow(purgeCommand)} and check again with ${chalk.yellow(ensureCommand)}\n`)
process.exit(1)
})
},
ensure () {
const options = this.parseOptions(process.argv)
return questionsRemain({ version: ask.getEnsureVersion })(options)
.then(this.checkDownloads)
},
build (options) {
console.log('#build')
if (options == null) {
options = this.parseOptions(process.argv)
}
debug('parsed build options %o', options)
return askMissingOptions(['version', 'platform'])(options)
.then(() => {
console.log('building binary: platform %s version %s', options.platform, options.version)
return build.buildCypressApp(options)
})
},
package (options) {
console.log('#package')
if (options == null) {
options = this.parseOptions(process.argv)
}
debug('parsed build options %o', options)
return askMissingOptions(['version', 'platform'])(options)
.then(() => {
console.log('packaging binary: platform %s version %s', options.platform, options.version)
return build.packageElectronApp(options)
})
},
async smoke (options) {
console.log('#smoke')
if (options == null) {
options = this.parseOptions(process.argv)
}
debug('parsed build options %o', options)
await askMissingOptions(['version'])(options)
// runSmokeTests
let usingXvfb = xvfb.isNeeded()
try {
if (usingXvfb) {
await xvfb.start()
}
log(`#testExecutableVersion ${meta.buildAppExecutable()}`)
await testExecutableVersion(meta.buildAppExecutable(), options.version)
const executablePath = meta.buildAppExecutable()
await smoke.test(executablePath, meta.buildAppDir())
} finally {
if (usingXvfb) {
await xvfb.stop()
}
}
},
zip (options) {
console.log('#zip')
if (!options) {
options = this.parseOptions(process.argv)
}
return askMissingOptions(['platform'])(options)
.then((options) => {
const zipDir = meta.zipDir(options.platform)
console.log('directory to zip %s', zipDir)
options.zip = path.resolve(zippedFilename(options.platform))
return zip.ditto(zipDir, options.zip)
})
},
// upload Cypress binary or NPM Package zip file under unique hash
'upload-build-artifact' (args = process.argv) {
console.log('#uploadBuildArtifact')
return uploadArtifactToS3(args)
},
// uploads a single built Cypress binary ZIP file
// usually a binary is built on CI and is uploaded
upload (options) {
console.log('#upload')
if (!options) {
options = this.parseOptions(process.argv)
}
return askMissingOptions(['version', 'platform', 'zip'])(options)
.then((options) => {
la(check.unemptyString(options.zip),
'missing zipped filename', options)
options.zip = path.resolve(options.zip)
return options
}).then((options) => {
console.log('Need to upload file %s', options.zip)
console.log('for platform %s version %s',
options.platform, options.version)
const uploadPath = upload.getFullUploadPath({
version: options.version,
platform: options.platform,
name: upload.zipName,
})
return upload.toS3({
file: options.zip,
uploadPath,
}).then(() => {
return uploadUtils.purgeDesktopAppFromCache({
version: options.version,
platform: options.platform,
zipName: options.zip,
})
})
})
},
'move-binaries' (args = process.argv) {
console.log('#moveBinaries')
return moveBinaries(args)
},
// purge all URLs from Cloudflare cache from file
'purge-urls' (args = process.argv) {
console.log('#purge-urls')
const options = minimist(args, {
string: 'filePath',
alias: {
filePath: 'f',
},
})
la(check.unemptyString(options.filePath), 'missing file path to url list', options)
return uploadUtils.purgeUrlsFromCloudflareCache(options.filePath)
},
// purge all platforms of a desktop app for specific version
'purge-version' (args = process.argv) {
console.log('#purge-version')
const options = minimist(args, {
string: 'version',
alias: {
version: 'v',
},
})
la(check.unemptyString(options.version), 'missing app version to purge', options)
return uploadUtils.purgeDesktopAppAllPlatforms(options.version, upload.zipName)
},
// goes through the entire pipeline:
// - build
// - zip
// - upload
deploy () {
const options = this.parseOptions(process.argv)
return askMissingOptions(['version', 'platform'])(options)
.then((options) => {
return this.build(options)
.then(() => {
return this.zip(options)
})
// assumes options.zip contains the zipped filename
.then(() => {
return this.upload(options)
})
})
},
async checkIfBinaryExistsOnCdn (args = process.argv) {
console.log('#checkIfBinaryExistsOnCdn')
const url = await uploadArtifactToS3([...args, '--dry-run', 'true'])
console.log(`Checking if ${url} exists...`)
const binaryExists = await rp.head(url)
.then(() => true)
.catch(() => false)
if (binaryExists) {
console.log('A binary was already built for this operating system and commit hash. Skipping binary build process...')
exec('circleci-agent step halt', (_, __, stdout) => {
console.log(stdout)
})
return
}
console.log('Binary does not yet exist. Continuing to build binary...')
return binaryExists
},
}
module.exports = _.bindAll(deploy, _.functions(deploy))