mirror of
https://github.com/cypress-io/cypress.git
synced 2025-12-31 03:29:48 -06:00
* chore: [run ci] does further prerequisites for webpack 5: https://webpack.js.org/migrate/5/#make-sure-your-build-has-no-errors-or-warnings https://webpack.js.org/migrate/5/#make-sure-to-use-mode https://webpack.js.org/migrate/5/#update-outdated-options https://webpack.js.org/migrate/5/#test-webpack-5-compatibility app builds and runs locally. Time to test out in CI and see if buffer or process need to be polyfilled by the build * chore: upgrade to webpack 5 and do the bare minimum to get it working * chore: get @packages/extension working * chore: add TODOs to finish after webpack 5 update * chore: update the webpack config for npm/webpack-batteries-included-preprocessor to be webpack 5 compliant * chore: patch whatwg-url 7.1.0. package 'source-map' uses whatwg-url@7.1.0 which has a dependency on punycode node expected API. since punycode is now polyfilled for us implicitly via the punycode npm package, the API signatures are a bit different https://github.com/mathiasbynens/punycode.js/blob/main/punycode.js#L101 vs https://nodejs.org/api/punycode.html#punycodeucs2. The patch uses the punycode npm package expected API and is needed for source maps to work for cy.origin() dependencies for Cypress.require() * chore: convert whatwg patch into dev patch as source-map is not installed when building the binary / installing prod dependencies * chore: only move production level patches into the binary dist directory for yarn install --production * chore: remove --openssl-legacy-provider code for node versions 17 and over as webpack has been updated to v5 * chore: fix the webpack-batteries-included-preprocessor tests by shimming the correct node globals and built ins * chore: provide the define plugin and evalDevtoolPlugin again as we need define in order to build the react-dom library correctly in the bundle to not include the development version * chore: updating v8 snapshot cache * chore: updating v8 snapshot cache * chore: updating v8 snapshot cache * chore: fix the webpack preprocessor not to change promise references under the hood when compiling the first bundle, as it was causing the webpack preprocessor to hang as the reference itself was different * chore: fix issues from readFile that were caused by Webpack 5 using 'path-browserify' * chore: update chrome component testing snapshots to match Webpack 5 changes * chore: fix mismatched snapshots from webpack 5 update * chore: use Cypress.Buffer instead of Buffer for selectFile system test to avoid having to polyfill Buffer from webpack * chore: fix system test webpack path that now includes e2e workspace * chore: patch enhanced-resolve to properly discover the pnp api for the yarn_v3.1.1_pnp_spec.ts system test. see https://github.com/webpack/enhanced-resolve/issues/263 for more details * chore: set stats to 'none' for experimentalSingleTabMode to prevent different webpack compiled terminal formatting in the snapshot between local and CI. * chore: fix node built in tests and configure webpack-batteries-included-preprocessor correctly * chore: fallback to buffer correctly in config, even though there is no impact due to the provide plugin * Update binary-cleanup.js to exclude added build dependencies for webpack 5 added by webpack-terser-plugin under the hood * chore: add stream-browserify to webpack preprocessor batteries included as a dep as its used in the config [run ci] * chore: make sure process and buffer are installed in the CLI for webpack provide * chore: build cross platform binaries [run ci] * chore: fix webpack evalDevToolPlugin instantiation [run ci] * run all binary jobs [run ci] * chore: updating v8 snapshot cache * add find-up to the entry points that need to be kept * chore: updating v8 snapshot cache * chore: updating v8 snapshot cache * chore: fix mocha build warnings * chore: fix STRIPPED_INTEGRITY_TAG import warnings * chore: add changelog event --------- Co-authored-by: cypress-bot[bot] <+cypress-bot[bot]@users.noreply.github.com> Co-authored-by: Ryan Manuel <ryanm@cypress.io>
254 lines
11 KiB
JavaScript
254 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 })
|
|
|
|
// 6. Remove various unnecessary files from the binary to further clean things up. Likely, there is additional work that can be done here
|
|
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, '**', '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,
|
|
}
|