mirror of
https://github.com/vercel/next.js.git
synced 2026-09-20 02:25:18 +08:00
9c939729d1
### What? Adds `@next/swc-linux-x64-gnu` to the preview tarball patching script (`scripts/patch-preview-tarball.mjs`): 1. Adds the package to the `PACKAGES_TO_PATCH` list so its tarball URL is fetched. 2. Adds the package as an explicit dependency in the patched `package.json`, in addition to the overrides/resolutions. ### Why? The `@next/swc-linux-x64-gnu` native binary package is an optional dependency of `next` and may not be installed automatically depending on the platform/package manager. When testing preview tarballs (e.g., in CI or on Linux environments), the SWC binary needs to be explicitly present for Next.js to function correctly. Without this change, preview tarball testing on Linux x64 could fail because the native binary isn't resolved from the preview build. Adding it as a direct dependency (not just an override/resolution) ensures the package manager actually installs it, since optional dependencies from overridden tarballs may not be auto-resolved. ### How? - Added `'@next/swc-linux-x64-gnu'` to the `PACKAGES_TO_PATCH` array so the script fetches its tarball URL. - After writing overrides/resolutions, the script now also adds `@next/swc-linux-x64-gnu` as an explicit entry in `pkg.dependencies` pointing to the tarball URL. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
241 lines
5.8 KiB
JavaScript
241 lines
5.8 KiB
JavaScript
import { execSync } from 'node:child_process'
|
|
import fs from 'node:fs/promises'
|
|
import path from 'node:path'
|
|
import { parseArgs } from 'node:util'
|
|
|
|
const BASE_URL = 'https://vercel-packages.vercel.app/next/commits'
|
|
|
|
const PACKAGES_TO_PATCH = [
|
|
'next',
|
|
'@next/mdx',
|
|
'@next/env',
|
|
'@next/bundle-analyzer',
|
|
'@next/swc-linux-x64-gnu',
|
|
]
|
|
|
|
// --- Argument parsing ---
|
|
|
|
function parseAndValidateArgs() {
|
|
const { values } = parseArgs({
|
|
options: {
|
|
project: { type: 'string' },
|
|
commit: { type: 'string' },
|
|
branch: { type: 'string' },
|
|
},
|
|
strict: true,
|
|
})
|
|
|
|
if (!values.project) {
|
|
console.error(
|
|
'Usage: node scripts/patch-preview-tarball.mjs --project <path> [--commit <sha> | --branch <name>]'
|
|
)
|
|
process.exit(1)
|
|
}
|
|
|
|
if (values.commit && values.branch) {
|
|
console.error('Error: --commit and --branch are mutually exclusive.')
|
|
process.exit(1)
|
|
}
|
|
|
|
return {
|
|
project: path.resolve(values.project),
|
|
commit: values.commit,
|
|
branch: values.branch,
|
|
}
|
|
}
|
|
|
|
// --- Resolve commit SHA ---
|
|
|
|
function resolveCommitSha({ commit, branch }) {
|
|
if (commit) {
|
|
if (!/^[0-9a-f]{7,40}$/i.test(commit)) {
|
|
console.error(`Error: Invalid commit SHA: ${commit}`)
|
|
process.exit(1)
|
|
}
|
|
return commit
|
|
}
|
|
|
|
if (branch) {
|
|
const encoded = encodeURIComponent(branch)
|
|
try {
|
|
const sha = execSync(
|
|
`gh api "repos/vercel/next.js/branches/${encoded}" --jq '.commit.sha'`,
|
|
{ encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }
|
|
).trim()
|
|
if (!sha) {
|
|
console.error(
|
|
`Error: Could not resolve branch '${branch}' to a commit SHA.`
|
|
)
|
|
process.exit(1)
|
|
}
|
|
return sha
|
|
} catch (err) {
|
|
console.error(
|
|
`Error: Failed to look up branch '${branch}' via GitHub API.`
|
|
)
|
|
console.error(err.stderr?.toString() || err.message)
|
|
process.exit(1)
|
|
}
|
|
}
|
|
|
|
// Fallback: local HEAD
|
|
try {
|
|
return execSync('git rev-parse HEAD', {
|
|
encoding: 'utf8',
|
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
}).trim()
|
|
} catch (err) {
|
|
console.error('Error: Failed to resolve local HEAD commit.')
|
|
console.error(err.stderr?.toString() || err.message)
|
|
process.exit(1)
|
|
}
|
|
}
|
|
|
|
// --- URL construction ---
|
|
|
|
function buildTarballUrls(commitSha) {
|
|
const urls = new Map()
|
|
for (const pkg of PACKAGES_TO_PATCH) {
|
|
urls.set(pkg, `${BASE_URL}/${commitSha}/${pkg}`)
|
|
}
|
|
return urls
|
|
}
|
|
|
|
// --- Tarball verification ---
|
|
|
|
async function verifyTarballExists(url) {
|
|
try {
|
|
const res = await fetch(url, { method: 'HEAD', redirect: 'follow' })
|
|
return res.ok
|
|
} catch {
|
|
return false
|
|
}
|
|
}
|
|
|
|
// --- Workspace root finding ---
|
|
|
|
async function fileExists(filePath) {
|
|
try {
|
|
await fs.access(filePath)
|
|
return true
|
|
} catch {
|
|
return false
|
|
}
|
|
}
|
|
|
|
async function findWorkspaceRoot(projectPath) {
|
|
for (const ev of ['NPM_CONFIG_WORKSPACE_DIR', 'npm_config_workspace_dir']) {
|
|
if (process.env[ev]) {
|
|
return process.env[ev]
|
|
}
|
|
}
|
|
|
|
try {
|
|
const canonicalPath = await fs.realpath(projectPath)
|
|
let currentDir = canonicalPath
|
|
|
|
while (currentDir !== path.parse(currentDir).root) {
|
|
if (await fileExists(path.join(currentDir, 'pnpm-workspace.yaml'))) {
|
|
return currentDir
|
|
}
|
|
|
|
const packageJsonPath = path.join(currentDir, 'package.json')
|
|
if (await fileExists(packageJsonPath)) {
|
|
const content = await fs.readFile(packageJsonPath, 'utf8')
|
|
const pkg = JSON.parse(content)
|
|
if (pkg.workspaces) {
|
|
return currentDir
|
|
}
|
|
}
|
|
|
|
currentDir = path.dirname(currentDir)
|
|
}
|
|
|
|
return null
|
|
} catch {
|
|
return null
|
|
}
|
|
}
|
|
|
|
// --- Patch package.json ---
|
|
|
|
async function patchPackageJson(projectPath, tarballUrls) {
|
|
const root = await findWorkspaceRoot(projectPath)
|
|
const packageJsonPath = root
|
|
? path.join(root, 'package.json')
|
|
: path.join(projectPath, 'package.json')
|
|
|
|
if (!(await fileExists(packageJsonPath))) {
|
|
console.error(`Error: package.json not found at ${packageJsonPath}`)
|
|
process.exit(1)
|
|
}
|
|
|
|
const content = await fs.readFile(packageJsonPath, 'utf8')
|
|
const pkg = JSON.parse(content)
|
|
|
|
const entries = Array.from(tarballUrls.entries())
|
|
|
|
// npm/pnpm overrides
|
|
pkg.overrides = pkg.overrides || {}
|
|
for (const [name, url] of entries) {
|
|
pkg.overrides[name] = url
|
|
}
|
|
|
|
// yarn resolutions
|
|
pkg.resolutions = pkg.resolutions || {}
|
|
for (const [name, url] of entries) {
|
|
pkg.resolutions[name] = url
|
|
}
|
|
|
|
// Add @next/swc-linux-x64-gnu to dependencies
|
|
pkg.dependencies = pkg.dependencies || {}
|
|
pkg.dependencies['@next/swc-linux-x64-gnu'] = tarballUrls.get(
|
|
'@next/swc-linux-x64-gnu'
|
|
)
|
|
|
|
await fs.writeFile(packageJsonPath, JSON.stringify(pkg, null, 2) + '\n')
|
|
|
|
console.log(`Patched ${packageJsonPath}`)
|
|
console.log('Packages overridden:')
|
|
for (const [name, url] of entries) {
|
|
console.log(` ${name} -> ${url}`)
|
|
}
|
|
|
|
return packageJsonPath
|
|
}
|
|
|
|
// --- Main ---
|
|
|
|
async function main() {
|
|
const { project, commit, branch } = parseAndValidateArgs()
|
|
|
|
const sha = resolveCommitSha({ commit, branch })
|
|
console.log(`Resolved commit SHA: ${sha}`)
|
|
|
|
const tarballUrls = buildTarballUrls(sha)
|
|
|
|
const nextUrl = tarballUrls.get('next')
|
|
console.log(`Verifying preview tarball exists: ${nextUrl}`)
|
|
const exists = await verifyTarballExists(nextUrl)
|
|
if (!exists) {
|
|
console.error(
|
|
`Preview tarball not found for commit ${sha}.\n` +
|
|
`The "Deploy preview tarball" job may not have completed yet, or the commit may not have a build.\n` +
|
|
`Check: https://github.com/vercel/next.js/actions/workflows/build_and_deploy.yml`
|
|
)
|
|
process.exit(1)
|
|
}
|
|
console.log('Preview tarball verified.')
|
|
|
|
await patchPackageJson(project, tarballUrls)
|
|
|
|
console.log(
|
|
'\nDone! Run your package manager install command to apply changes.'
|
|
)
|
|
}
|
|
|
|
main().catch((err) => {
|
|
console.error(err)
|
|
process.exit(1)
|
|
})
|