mirror of
https://github.com/cypress-io/cypress.git
synced 2025-12-31 03:29:48 -06:00
* feat: update to electron 25 and bump node dependencies. need to remove custom docker image * chore: update node versions to 18+ [run ci] * chore: bump @types/node from v16 to v18 and bump the typescript supported CLI version from 3.9 to 4.4 * update mock-fs to 5.2.0 to fix BigInt issue (see https://github.com/tschaub/mock-fs/releases/tag/v5.1.4) [run ci] * chore: update electron integrity check to pass create binary job * chore: fix issues with achrinza/node-ipc not yet having node 18 engine support (only up to 17 by patching from 9.2.2 to 9.2.5 as seen in https://github.com/achrinza/node-ipc/pull/38. vue/cli-service is not maintained and we should migrate these over to vue create at some point in the near future to get rid of these resolutions * allow for TLSv1 tests to work with node 18 as the express server running node 18 with tlsv1 needs to allow legacy ciphers * chore: update snyk workflows to use node 18 * chore: add changelog * update timeout * more cleanup during binary build * bump cache and run ci. include ignore engines for rwa and get rid of 16.16 image references * chore: update FF tests to 115 as serialization now works with errors and click events do not get fired on buttons being typed into * chore: don't error when symlink already exists * chore: fix serialization test for newer versions of firefox * chore: fix CI config * chore: fix chrome system tests to work with chrome 114. updates mostly caused by bugs in screen height in chrome. see https://bugs.chromium.org/p/chromium/issues/detail?id=1416398 * chore: test binary against vite update in RWA * remove wait for RWA as it shouldnt be needed for vite. link example recipes update to node 18 chore: remove commented out code * chore: print message when DISABLE_SNAPSHOT_REQUIRE is set * chore: clean out unneeded dependencies always * chore: remove trailing space * fix: propagate click events for enter and typing on firefox 106 or later * chore: fix changelog failures * fix: correctly simulate click events for buttons on keyup and space type in Firefox versions greater than 91 and simulate click for buttons on enter in Firefox versions greater than or equal to 106 * chore: add documentation to type to clarify firefox synthetic events * chore: update protocol snapshot as order or log messages seems to have changed * update comments * sort commandLogChanged events for protocol * chore: remove PR ids from CRA and CER as the PRs are merged into develop --------- Co-authored-by: Ryan Manuel <ryanm@cypress.io> Co-authored-by: Chris Breiding <chrisbreiding@gmail.com> Co-authored-by: Matt Schile <mschile@cypress.io>
261 lines
11 KiB
JavaScript
261 lines
11 KiB
JavaScript
const fs = require('fs-extra')
|
|
const path = require('path')
|
|
const { consolidateDeps, getSnapshotCacheDir } = require('@tooling/v8-snapshot')
|
|
const del = require('del')
|
|
const esbuild = require('esbuild')
|
|
const tempDir = require('temp-dir')
|
|
const workingDir = path.join(tempDir, 'binary-cleanup-workdir')
|
|
|
|
fs.ensureDirSync(workingDir)
|
|
|
|
async function removeEmptyDirectories (directory) {
|
|
// lstat does not follow symlinks (in contrast to stat)
|
|
const fileStats = await fs.lstat(directory)
|
|
|
|
if (!fileStats.isDirectory()) {
|
|
return
|
|
}
|
|
|
|
let fileNames = await fs.readdir(directory)
|
|
|
|
if (fileNames.length > 0) {
|
|
const recursiveRemovalPromises = fileNames.map(
|
|
(fileName) => removeEmptyDirectories(path.join(directory, fileName)),
|
|
)
|
|
|
|
await Promise.all(recursiveRemovalPromises)
|
|
|
|
// re-evaluate fileNames; after deleting subdirectory
|
|
// we may have parent directory empty now
|
|
fileNames = await fs.readdir(directory)
|
|
}
|
|
|
|
if (fileNames.length === 0) {
|
|
await fs.rmdir(directory)
|
|
}
|
|
}
|
|
|
|
const getDependencyPathsToKeep = async (buildAppDir) => {
|
|
const unixBuildAppDir = buildAppDir.split(path.sep).join(path.posix.sep)
|
|
const startingEntryPoints = [
|
|
'packages/server/lib/plugins/child/require_async_child.js',
|
|
'packages/server/lib/plugins/child/register_ts_node.js',
|
|
'packages/server/node_modules/@cypress/webpack-batteries-included-preprocessor/index.js',
|
|
'packages/server/node_modules/ts-loader/index.js',
|
|
'packages/rewriter/lib/threads/worker.js',
|
|
'npm/webpack-batteries-included-preprocessor/index.js',
|
|
'node_modules/find-up/index.js',
|
|
'node_modules/webpack/lib/webpack.js',
|
|
'node_modules/webpack-dev-server/lib/Server.js',
|
|
'node_modules/html-webpack-plugin-4/index.js',
|
|
'node_modules/html-webpack-plugin-5/index.js',
|
|
'node_modules/mocha-7.0.1/index.js',
|
|
]
|
|
|
|
let entryPoints = new Set([
|
|
...startingEntryPoints.map((entryPoint) => path.join(unixBuildAppDir, entryPoint)),
|
|
// These dependencies are completely dynamic using the pattern `require(`./${name}`)` and will not be pulled in by esbuild but still need to be kept in the binary.
|
|
...['ibmi',
|
|
'sunos',
|
|
'android',
|
|
'darwin',
|
|
'freebsd',
|
|
'linux',
|
|
'openbsd',
|
|
'sunos',
|
|
'win32'].map((platform) => path.join(unixBuildAppDir, `node_modules/default-gateway/${platform}.js`)),
|
|
])
|
|
let esbuildResult
|
|
let newEntryPointsFound = true
|
|
|
|
// The general idea here is to run esbuild on entry points that are used outside of the snapshot. If, during the process,
|
|
// we find places where we do a require.resolve on a module, that should be treated as an additional entry point and we run
|
|
// esbuild again. We do this until we no longer find any new entry points. The resulting metafile inputs are
|
|
// the dependency paths that we need to ensure stay in the snapshot.
|
|
while (newEntryPointsFound) {
|
|
esbuildResult = await esbuild.build({
|
|
entryPoints: [...entryPoints],
|
|
bundle: true,
|
|
outdir: workingDir,
|
|
platform: 'node',
|
|
metafile: true,
|
|
absWorkingDir: unixBuildAppDir,
|
|
external: [
|
|
'./transpile-ts',
|
|
'./start-cypress',
|
|
'fsevents',
|
|
'pnpapi',
|
|
'@swc/core',
|
|
'emitter',
|
|
'ts-loader',
|
|
'uglify-js',
|
|
'esbuild',
|
|
'enhanced-resolve/lib/createInnerCallback',
|
|
'@babel/preset-typescript/package.json',
|
|
'./addon/addon-native',
|
|
],
|
|
})
|
|
|
|
newEntryPointsFound = false
|
|
esbuildResult.warnings.forEach((warning) => {
|
|
const matches = warning.text.match(/"(.*)" should be marked as external for use with "require.resolve"/)
|
|
const warningSubject = matches && matches[1]
|
|
|
|
if (warningSubject) {
|
|
let entryPoint
|
|
|
|
if (warningSubject.startsWith('.')) {
|
|
entryPoint = path.join(unixBuildAppDir, path.dirname(warning.location.file), warningSubject)
|
|
} else {
|
|
entryPoint = require.resolve(warningSubject, { paths: [path.join(unixBuildAppDir, path.dirname(warning.location.file))] })
|
|
}
|
|
|
|
if (path.extname(entryPoint) !== '' && !entryPoints.has(entryPoint)) {
|
|
newEntryPointsFound = true
|
|
entryPoints.add(entryPoint)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
return [...Object.keys(esbuildResult.metafile.inputs), ...entryPoints, 'package.json']
|
|
}
|
|
|
|
const createServerEntryPointBundle = async (buildAppDir) => {
|
|
const unixBuildAppDir = buildAppDir.split(path.sep).join(path.posix.sep)
|
|
const entryPoints = [path.join(unixBuildAppDir, 'packages/server/index.js')]
|
|
// Build the binary entry point ignoring anything that happens in start-cypress since that will be in the v8 snapshot
|
|
const esbuildResult = await esbuild.build({
|
|
entryPoints,
|
|
bundle: true,
|
|
outdir: workingDir,
|
|
platform: 'node',
|
|
metafile: true,
|
|
absWorkingDir: unixBuildAppDir,
|
|
external: [
|
|
'./transpile-ts',
|
|
'./start-cypress',
|
|
],
|
|
})
|
|
|
|
console.log(`copying server entry point bundle from ${path.join(workingDir, 'index.js')} to ${path.join(buildAppDir, 'packages', 'server', 'index.js')}`)
|
|
|
|
await fs.copy(path.join(workingDir, 'index.js'), path.join(buildAppDir, 'packages', 'server', 'index.js'))
|
|
|
|
console.log(`compiling server entry point bundle to ${path.join(buildAppDir, 'packages', 'server', 'index.jsc')}`)
|
|
|
|
// Use bytenode to compile the entry point bundle. This will save time on the v8 compile step and ensure the integrity of the entry point
|
|
const bytenode = await import('bytenode')
|
|
|
|
await bytenode.compileFile({
|
|
filename: path.join(buildAppDir, 'packages', 'server', 'index.js'),
|
|
output: path.join(buildAppDir, 'packages', 'server', 'index.jsc'),
|
|
electron: true,
|
|
})
|
|
|
|
// Convert these inputs to a relative file path. Note that these paths are posix paths.
|
|
return [...Object.keys(esbuildResult.metafile.inputs)].map((input) => `./${input}`)
|
|
}
|
|
|
|
const buildEntryPointAndCleanup = async (buildAppDir) => {
|
|
const [keptDependencies, serverEntryPointBundleDependencies] = await Promise.all([
|
|
// 1. Retrieve all dependencies that still need to be kept in the binary. In theory, we could use the bundles generated here as single files within the binary,
|
|
// but for now, we just track on the dependencies that get pulled in
|
|
getDependencyPathsToKeep(buildAppDir),
|
|
// 2. Create a bundle for the server entry point. This will be used to start the server in the binary. It returns the dependencies that are pulled in by this bundle that potentially can now be removed
|
|
createServerEntryPointBundle(buildAppDir),
|
|
])
|
|
|
|
// 3. Gather the dependencies that could potentially be removed from the binary due to being in the snapshot or in the entry point bundle
|
|
const snapshotMetadata = require(path.join(getSnapshotCacheDir(), 'snapshot-meta.json'))
|
|
const potentiallyRemovedDependencies = [
|
|
...snapshotMetadata.healthy,
|
|
...snapshotMetadata.deferred,
|
|
...snapshotMetadata.norewrite,
|
|
...serverEntryPointBundleDependencies,
|
|
]
|
|
|
|
console.log(`potentially removing ${potentiallyRemovedDependencies.length} dependencies`)
|
|
|
|
// 4. Remove all dependencies that are in the snapshot but not in the list of kept dependencies from the binary
|
|
await Promise.all(potentiallyRemovedDependencies.map(async (dependency) => {
|
|
const typeScriptlessDependency = dependency.replace(/\.ts$/, '.js')
|
|
|
|
// marionette-client and babel/runtime require all of their dependencies in a very non-standard dynamic way. We will keep anything in marionette-client and babel/runtime
|
|
if (!keptDependencies.includes(typeScriptlessDependency.slice(2)) && !typeScriptlessDependency.includes('marionette-client') && !typeScriptlessDependency.includes('@babel/runtime')) {
|
|
await fs.remove(path.join(buildAppDir, typeScriptlessDependency))
|
|
}
|
|
}))
|
|
|
|
// 5. Consolidate dependencies that are safe to consolidate (`lodash` and `bluebird`)
|
|
await consolidateDeps({ projectBaseDir: buildAppDir })
|
|
}
|
|
|
|
const cleanupUnneededDependencies = async (buildAppDir) => {
|
|
console.log(`removing unnecessary files from the binary to further lean out the distribution.`)
|
|
// 6. Remove various unnecessary files from the binary to further clean things up. Likely, there is additional work that can be done here
|
|
// this is it's own function as we always want to clean out unneeded dependencies
|
|
await del([
|
|
// Remove test files
|
|
path.join(buildAppDir, '**', 'test'),
|
|
path.join(buildAppDir, '**', 'tests'),
|
|
// What we need of prettier is entirely encapsulated within the v8 snapshot, but has a few leftover large files
|
|
path.join(buildAppDir, '**', 'prettier', 'esm'),
|
|
path.join(buildAppDir, '**', 'prettier', 'standalone.js'),
|
|
path.join(buildAppDir, '**', 'prettier', 'bin-prettier.js'),
|
|
// ESM files are mostly not needed currently
|
|
path.join(buildAppDir, '**', '@babel', '**', 'esm'),
|
|
path.join(buildAppDir, '**', 'ramda', 'es'),
|
|
path.join(buildAppDir, '**', 'jimp', 'es'),
|
|
path.join(buildAppDir, '**', '@jimp', '**', 'es'),
|
|
path.join(buildAppDir, '**', 'nexus', 'dist-esm'),
|
|
path.join(buildAppDir, '**', '@graphql-tools', '**', '*.mjs'),
|
|
path.join(buildAppDir, '**', 'graphql', '**', '*.mjs'),
|
|
path.join(buildAppDir, '**', '@openTelemetry', '**', 'esm'),
|
|
path.join(buildAppDir, '**', '@openTelemetry', '**', 'esnext'),
|
|
// We currently do not use any map files
|
|
path.join(buildAppDir, '**', '*js.map'),
|
|
// License files need to be kept
|
|
path.join(buildAppDir, '**', '!(LICENSE|license|License).md'),
|
|
// These are type related files that are not used within the binary
|
|
path.join(buildAppDir, '**', '*.d.ts'),
|
|
path.join(buildAppDir, '**', 'ajv', 'lib', '**', '*.ts'),
|
|
path.join(buildAppDir, '**', '*.flow'),
|
|
// Example files are not needed
|
|
path.join(buildAppDir, '**', 'jimp', 'browser', 'examples'),
|
|
// Documentation files are not needed
|
|
path.join(buildAppDir, '**', 'JSV', 'jsdoc-toolkit'),
|
|
path.join(buildAppDir, '**', 'JSV', 'docs'),
|
|
path.join(buildAppDir, '**', 'fluent-ffmpeg', 'doc'),
|
|
// Files used as part of prebuilding are not necessary
|
|
path.join(buildAppDir, '**', 'node_gyp_bins'),
|
|
path.join(buildAppDir, '**', 'better-sqlite3', 'bin'),
|
|
path.join(buildAppDir, '**', 'registry-js', 'prebuilds'),
|
|
path.join(buildAppDir, '**', '*.cc'),
|
|
path.join(buildAppDir, '**', '*.o'),
|
|
path.join(buildAppDir, '**', '*.c'),
|
|
path.join(buildAppDir, '**', '*.h'),
|
|
// Remove distributions that are not needed in the binary
|
|
path.join(buildAppDir, '**', 'ramda', 'dist'),
|
|
path.join(buildAppDir, '**', 'jimp', 'browser'),
|
|
path.join(buildAppDir, '**', '@jimp', '**', 'src'),
|
|
path.join(buildAppDir, '**', 'nexus', 'src'),
|
|
path.join(buildAppDir, '**', 'source-map', 'dist'),
|
|
path.join(buildAppDir, '**', 'source-map-js', 'dist'),
|
|
path.join(buildAppDir, '**', 'pako', 'dist'),
|
|
path.join(buildAppDir, '**', 'node-forge', 'dist'),
|
|
path.join(buildAppDir, '**', 'pngjs', 'browser.js'),
|
|
path.join(buildAppDir, '**', 'plist', 'dist'),
|
|
// Remove yarn locks
|
|
path.join(buildAppDir, '**', 'yarn.lock'),
|
|
], { force: true })
|
|
|
|
// 7. Remove any empty directories as a result of the rest of the cleanup
|
|
await removeEmptyDirectories(buildAppDir)
|
|
}
|
|
|
|
module.exports = {
|
|
buildEntryPointAndCleanup,
|
|
cleanupUnneededDependencies,
|
|
}
|