mirror of
https://github.com/vercel/next.js.git
synced 2026-09-20 02:25:18 +08:00
69acc33f03
A `pnpm install` installs native bindings for Next.js by default. This makes sense for local development where half of the team doesn't work on native code and just needs the most recent bindings. However, CI almost exclusively wants to test with bindings that are built from the current commit. The rest doesn't need native bindings at all. The only exception is `test_examples` which is likely not functional. Though we still support it by allowing to opt-into automatically installing native bindings in CI. ## test plan - [x] fetch test timings still skips: https://github.com/vercel/next.js/actions/runs/28087707205/job/83157705446?pr=95114#step:7:228
135 lines
4.5 KiB
JavaScript
135 lines
4.5 KiB
JavaScript
import os from 'os'
|
|
import path from 'path'
|
|
import execa from 'execa'
|
|
import fs from 'fs'
|
|
import fsp from 'fs/promises'
|
|
import { outdent } from 'outdent'
|
|
;(async function () {
|
|
// Automatically installing native bindings is opt-in in CI and opt-out in local development.
|
|
// Most CI jobs use native bindings built from the commit they run on anyway.
|
|
const rawSkip = process.env.NEXT_SKIP_NATIVE_POSTINSTALL
|
|
const skipNativePostinstall =
|
|
rawSkip == null || rawSkip === ''
|
|
? process.env.CI === 'true'
|
|
: rawSkip !== '0' && rawSkip !== 'false'
|
|
|
|
if (skipNativePostinstall) {
|
|
console.log(
|
|
`Skipping next-swc postinstall (NEXT_SKIP_NATIVE_POSTINSTALL=${String(JSON.stringify(rawSkip))}, CI=${String(JSON.stringify(process.env.CI))})`
|
|
)
|
|
return
|
|
}
|
|
|
|
const preferOffline = process.env.NEXT_TEST_PREFER_OFFLINE === '1'
|
|
|
|
let cwd = process.cwd()
|
|
const { version: nextVersion } = JSON.parse(
|
|
fs.readFileSync(path.join(cwd, 'packages', 'next', 'package.json'))
|
|
)
|
|
const { packageManager } = JSON.parse(
|
|
fs.readFileSync(path.join(cwd, 'package.json'))
|
|
)
|
|
|
|
try {
|
|
// if installed swc package version matches monorepo version
|
|
// we can skip re-installing
|
|
for (const pkg of fs.readdirSync(path.join(cwd, 'node_modules', '@next'))) {
|
|
if (
|
|
pkg.startsWith('swc-') &&
|
|
JSON.parse(
|
|
fs.readFileSync(
|
|
path.join(cwd, 'node_modules', '@next', pkg, 'package.json')
|
|
)
|
|
).version === nextVersion
|
|
) {
|
|
console.log(`@next/${pkg}@${nextVersion} already installed, skipping`)
|
|
return
|
|
}
|
|
}
|
|
} catch {}
|
|
|
|
try {
|
|
let tmpdir = path.join(os.tmpdir(), `next-swc-${Date.now()}`)
|
|
fs.mkdirSync(tmpdir, { recursive: true })
|
|
let pkgJson = {
|
|
name: 'dummy-package',
|
|
version: '1.0.0',
|
|
optionalDependencies: {
|
|
'@next/swc-darwin-arm64': nextVersion,
|
|
'@next/swc-darwin-x64': nextVersion,
|
|
'@next/swc-linux-arm64-gnu': nextVersion,
|
|
'@next/swc-linux-arm64-musl': nextVersion,
|
|
'@next/swc-linux-x64-gnu': nextVersion,
|
|
'@next/swc-linux-x64-musl': nextVersion,
|
|
'@next/swc-win32-arm64-msvc': nextVersion,
|
|
'@next/swc-win32-x64-msvc': nextVersion,
|
|
},
|
|
packageManager,
|
|
}
|
|
fs.writeFileSync(path.join(tmpdir, 'package.json'), JSON.stringify(pkgJson))
|
|
fs.writeFileSync(
|
|
path.join(tmpdir, 'pnpm-workspace.yaml'),
|
|
'' +
|
|
outdent`
|
|
nodeLinker: hoisted
|
|
` +
|
|
'\n' +
|
|
// Propagate security related settings from file://./../../pnpm-workspace.yaml
|
|
outdent`
|
|
blockExoticSubdeps: true
|
|
minimumReleaseAge: 2880 # 48 hrs
|
|
minimumReleaseAgeExclude:
|
|
- '@next/*'
|
|
- '@turbo/*'
|
|
- '@vercel/*'
|
|
- '@workflow/*'
|
|
- babel-plugin-react-compiler
|
|
- next
|
|
- react
|
|
- react-dom
|
|
- react-is
|
|
- react-server-dom-*
|
|
- scheduler
|
|
- turbo
|
|
`
|
|
)
|
|
|
|
const args = ['install', '--lockfile=false', '--ignore-scripts']
|
|
if (preferOffline) {
|
|
args.push('--prefer-offline')
|
|
}
|
|
await execa('pnpm', args, { cwd: tmpdir })
|
|
|
|
/** @type {string[]} */
|
|
let pkgs
|
|
try {
|
|
pkgs = fs.readdirSync(path.join(tmpdir, 'node_modules/@next'), {})
|
|
} catch (error) {
|
|
throw new Error(
|
|
'No binary candidate found.\n' +
|
|
`This environment is not supported by Next.js or publish of ${nextVersion} is incomplete.\n` +
|
|
'If binaries are built from source, set `NEXT_SKIP_NATIVE_POSTINSTALL=1`.',
|
|
{ cause: error }
|
|
)
|
|
}
|
|
fs.mkdirSync(path.join(cwd, 'node_modules/@next'), { recursive: true })
|
|
|
|
await Promise.all(
|
|
pkgs.map(async (pkg) => {
|
|
const from = path.join(tmpdir, 'node_modules/@next', pkg)
|
|
const to = path.join(cwd, 'node_modules/@next', pkg)
|
|
// The directory from pnpm store is a symlink, which can not be overwritten,
|
|
// so we remove the existing directory before copying
|
|
await fsp.rm(to, { recursive: true, force: true })
|
|
// Renaming is flaky on Windows, and the tmpdir is going to be deleted anyway,
|
|
// so we use copy the directory instead
|
|
return fsp.cp(from, to, { force: true, recursive: true })
|
|
})
|
|
)
|
|
fs.rmSync(tmpdir, { recursive: true, force: true })
|
|
console.log('Installed the following binary packages:', pkgs)
|
|
} catch (e) {
|
|
throw new Error('Failed to install @next/swc binary packages', { cause: e })
|
|
}
|
|
})()
|