/
githubmirror
/
next.js
Обзор
Документация
Войти
/
githubmirror
/
next.js
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
canary
test/lib/create-next-install.js
388 строк
12 KB
dan
[test] Unflake more tests (#96081)
27 июл 2026, 22:54
Не верифицирован
27 июл 2026, 22:54
e2dce63
Код
Авторство
О чём код?
const os = require('os') const path = require('path') const execa = require('execa') const fs = require('fs-extra') const childProcess = require('child_process') const { randomBytes } = require('crypto') const { linkPackages } = require('../../.github/actions/next-stats-action/src/prepare/repo-setup')() const yaml = require('js-yaml') const { getPnpmSecuritySettings, mergePnpmSecuritySettingsIntoYaml, getYarnSecuritySettings, mergeYarnSecuritySettingsIntoYaml, } = require('./pnpm-security-settings') const PREFER_OFFLINE = process.env.NEXT_TEST_PREFER_OFFLINE === '1' const useRspack = process.env.NEXT_TEST_USE_RSPACK === '1' const ROOT_PACKAGE_MANAGER = require('../../package.json').packageManager async function installDependencies(cwd, tmpDir) { const args = [ 'install', '--strict-peer-dependencies=false', '--no-frozen-lockfile', `--config.cacheDir=${tmpDir}`, ] if (PREFER_OFFLINE) { args.push('--prefer-offline') } await execa('pnpm', args, { cwd, stdio: ['ignore', 'inherit', 'inherit'], }) } /** * Finds `fileName` in the dirs from `installDir` up to `isolationRoot` * (inclusive), or null if absent. * * @param {string} fileName * @param {string} installDir * @param {string} isolationRoot * @returns {Promise<string | null>} */ async function findConfigFile(fileName, installDir, isolationRoot) { let dir = path.resolve(installDir) const stopDir = path.resolve(isolationRoot) while (true) { const file = path.join(dir, fileName) if (await fs.pathExists(file)) { return file } if (dir === stopDir) break const parent = path.dirname(dir) if (parent === dir) break dir = parent } return null } /** * Applies the supply-chain security settings from the repo root * `pnpm-workspace.yaml` to installs in the isolated test dir, by writing (or * merging into) a `pnpm-workspace.yaml` and a `.yarnrc.yml`. npm added * equivalent functionality in 11.10.0; we can configure it here once we * upgrade npm. * * @param {string} installDir * @param {string} isolationRoot * @returns {Promise<void>} */ async function applyInstallSecuritySettings(installDir, isolationRoot) { const workspaceFile = await findConfigFile( 'pnpm-workspace.yaml', installDir, isolationRoot ) if (workspaceFile !== null) { await fs.writeFile( workspaceFile, mergePnpmSecuritySettingsIntoYaml( await fs.readFile(workspaceFile, 'utf8') ) ) } else { await fs.writeFile( path.join(installDir, 'pnpm-workspace.yaml'), yaml.dump(getPnpmSecuritySettings()) ) } const yarnrcFile = await findConfigFile( '.yarnrc.yml', installDir, isolationRoot ) if (yarnrcFile !== null) { await fs.writeFile( yarnrcFile, mergeYarnSecuritySettingsIntoYaml(await fs.readFile(yarnrcFile, 'utf8')) ) } else { await fs.writeFile( path.join(installDir, '.yarnrc.yml'), yaml.dump(getYarnSecuritySettings()) ) } } /** * pnpm only honors overrides at the workspace root, so they go into the * `pnpm-workspace.yaml` that governs the install. * * @param {string} installDir * @param {string} isolationRoot * @param {Record<string, string>} overrides * @returns {Promise<void>} */ async function applyWorkspaceOverrides(installDir, isolationRoot, overrides) { const workspaceFile = await findConfigFile( 'pnpm-workspace.yaml', installDir, isolationRoot ) if (workspaceFile === null) { return } const workspaceConfig = /** @type {Record<string, any>} */ ( yaml.load(await fs.readFile(workspaceFile, 'utf8')) ) ?? {} workspaceConfig.overrides = { ...overrides, ...(workspaceConfig.overrides || {}), } await fs.writeFile(workspaceFile, yaml.dump(workspaceConfig)) } /** * * @param {object} param0 * @param {import('@next/telemetry').Span} param0.parentSpan * @param {object} [param0.dependencies] * @param {object | null} [param0.resolutions] * @param { ((ctx: { dependencies: { [key: string]: string } }) => string) | string | null} [param0.installCommand] * @param {object} [param0.packageJson] * @param {string} [param0.subDir] * @param {(span: import('@next/telemetry').Span, installDir: string) => Promise<void>} [param0.beforeInstall] * @returns {Promise<{installDir: string, pkgPaths: Map<string, string>}>} */ async function createNextInstall({ parentSpan, dependencies = {}, resolutions = null, installCommand = null, packageJson = {}, subDir = '', beforeInstall, }) { const tmpDir = await fs.realpath(process.env.NEXT_TEST_DIR || os.tmpdir()) return await parentSpan .traceChild('createNextInstall') .traceAsyncFn(async (rootSpan) => { const origRepoDir = path.join(__dirname, '../../') const isolationRoot = path.join( tmpDir, `next-install-${randomBytes(32).toString('hex')}` ) const installDir = path.join(isolationRoot, subDir) require('console').log('Creating next instance in:') require('console').log(installDir) const pkgPathsEnv = process.env.NEXT_TEST_PKG_PATHS let pkgPaths if (pkgPathsEnv) { pkgPaths = new Map(JSON.parse(pkgPathsEnv)) require('console').log('using provided pkg paths') } else { await rootSpan.traceChild('turbo-run-pack').traceAsyncFn(() => execa( 'pnpm', [ 'turbo', 'run', 'pack-for-isolated-tests', '--output-logs', 'new-only', // Jest tui can't handle Turborepo tui. But we're cutting off stdin // so Turborepo's tui isn't interactive anyway. '--ui', 'stream', ], { cwd: origRepoDir, stdio: ['ignore', 'inherit', 'inherit'], } ) ) if (process.env.NEXT_TEST_WASM) { const wasmPath = path.join(origRepoDir, 'crates', 'wasm', 'pkg') const hasWasmBinary = fs.existsSync( path.join(wasmPath, 'package.json') ) if (hasWasmBinary) { process.env.NEXT_TEST_WASM_DIR = wasmPath } } else { const nativePath = path.join(origRepoDir, 'packages/next-swc/native') const hasNativeBinary = fs.existsSync(nativePath) ? fs.readdirSync(nativePath).some((item) => item.endsWith('.node')) : false if (hasNativeBinary) { process.env.NEXT_TEST_NATIVE_DIR = nativePath } else { const swcDirectory = fs .readdirSync(path.join(origRepoDir, 'node_modules/@next')) .find((directory) => directory.startsWith('swc-')) process.env.NEXT_TEST_NATIVE_DIR = path.join( origRepoDir, 'node_modules/@next', swcDirectory ) } } require('console').log({ swcNativeDirectory: process.env.NEXT_TEST_NATIVE_DIR, swcWasmDirectory: process.env.NEXT_TEST_WASM_DIR, }) pkgPaths = await rootSpan.traceChild('linkPackages').traceAsyncFn(() => linkPackages({ repoDir: origRepoDir, }) ) } const combinedDependencies = { next: pkgPaths.get('next'), ...Object.keys(dependencies).reduce((prev, pkg) => { const pkgPath = pkgPaths.get(pkg) prev[pkg] = pkgPath || dependencies[pkg] return prev }, {}), } if (useRspack) { combinedDependencies['next-rspack'] = pkgPaths.get('next-rspack') } // Build overrides to resolve transitive workspace deps from local // tarballs. Write all three formats so npm, pnpm, and yarn all work. const workspacePkgOverrides = {} for (const [name, tarballPath] of pkgPaths.entries()) { if (!combinedDependencies[name]) { workspacePkgOverrides[name] = tarballPath } } const scripts = { debug: `NEXT_PRIVATE_SKIP_CANARY_CHECK=1 NEXT_TELEMETRY_DISABLED=1 NEXT_TEST_NATIVE_DIR=${process.env.NEXT_TEST_NATIVE_DIR} node --inspect --trace-deprecation --enable-source-maps node_modules/next/dist/bin/next`, 'debug-brk': `NEXT_PRIVATE_SKIP_CANARY_CHECK=1 NEXT_TELEMETRY_DISABLED=1 NEXT_TEST_NATIVE_DIR=${process.env.NEXT_TEST_NATIVE_DIR} node --inspect-brk --trace-deprecation --enable-source-maps node_modules/next/dist/bin/next`, ...packageJson.scripts, } // Pin the same pnpm version the repo uses so corepack resolves a // consistent pnpm across isolated test dirs. Without this, `pnpm` may // fall back to whatever version is installed at the system level, which // can disagree with the repo's `packageManager` field and cause mismatch // errors (e.g. pnpm-workspace.yaml written for v10 parsed by v9). // // Only fall back to the root `packageManager` for the default pnpm // install path. Tests that provide their own `installCommand` (e.g. // yarn-pnp) need to switch package managers themselves and would be // blocked by corepack if the file already pinned `pnpm@...`. const rootPackageManager = require( path.join(__dirname, '../../package.json') ).packageManager const packageManagerField = packageJson.packageManager || (installCommand ? undefined : rootPackageManager) await fs.ensureDir(installDir) await fs.writeFile( path.join(installDir, 'package.json'), JSON.stringify( { // Pin packageManager so corepack doesn't auto-inject a reference // to the latest version (and rewrite this file mid-test). // Callers can override via packageJson.packageManager. packageManager: ROOT_PACKAGE_MANAGER, ...packageJson, ...(packageManagerField && { packageManager: packageManagerField }), scripts, dependencies: combinedDependencies, private: true, overrides: { ...workspacePkgOverrides, ...(packageJson.overrides || {}), }, resolutions: { ...workspacePkgOverrides, ...(resolutions || {}), }, }, null, 2 ) ) if (beforeInstall !== undefined) { await rootSpan .traceChild('beforeInstall') .traceAsyncFn(async (span) => { await beforeInstall(span, installDir) }) } const installString = installCommand ? typeof installCommand === 'function' ? installCommand({ dependencies: combinedDependencies, resolutions, }) : installCommand : null await applyInstallSecuritySettings(installDir, isolationRoot) await applyWorkspaceOverrides(installDir, isolationRoot, { ...workspacePkgOverrides, ...(resolutions || {}), }) if (installString !== null) { console.log('running install command', installString) rootSpan.traceChild('run custom install').traceFn(() => { childProcess.execSync(installString, { cwd: installDir, stdio: ['ignore', 'inherit', 'inherit'], }) }) } else { await rootSpan .traceChild('run generic install command', combinedDependencies) .traceAsyncFn(() => installDependencies(installDir, tmpDir)) // `@next/env` is a dependency of `next`, so it only resolves to the // local tarball if the overrides were applied. if (!combinedDependencies['@next/env']) { const envDir = await fs.realpath( path.join( await fs.realpath(path.join(installDir, 'node_modules/next')), '../@next/env' ) ) if (!envDir.includes('@next+env@file')) { throw new Error( `@next/env resolved from the npm registry instead of the local tarball (${envDir}), ` + 'the workspace overrides were not applied to the install' ) } } } if (useRspack) { process.env.NEXT_RSPACK = 'true' process.env.RSPACK_CONFIG_VALIDATE = 'loose-silent' } return { installDir, pkgPaths, } }) } module.exports = { createNextInstall, getPkgPaths: linkPackages, }