[scripts] Write pnpm overrides for pnpm projects in pack/patch helpers (#94747)

This commit is contained in:
Sebastian "Sebbie" Silbermann
2026-06-22 17:12:14 +02:00
committed by GitHub
parent 0ef205e72b
commit 2aba93f005
4 changed files with 115 additions and 8 deletions
+2
View File
@@ -157,6 +157,7 @@
"@types/html-validator": "5.0.3",
"@types/http-proxy": "1.17.17",
"@types/jest": "29.5.5",
"@types/js-yaml": "4.0.9",
"@types/node": "20.17.6",
"@types/node-fetch": "2.6.1",
"@types/react": "19.2.10",
@@ -226,6 +227,7 @@
"jest-extended": "4.0.2",
"jest-junit": "16.0.0",
"jest-snapshot": "30.0.0-alpha.6",
"js-yaml": "4.2.0",
"json5": "2.2.3",
"kleur": "^4.1.0",
"ky": "0.19.1",
+6
View File
@@ -216,6 +216,9 @@ importers:
'@types/jest':
specifier: 29.5.5
version: 29.5.5
'@types/js-yaml':
specifier: 4.0.9
version: 4.0.9
'@types/node':
specifier: 20.17.6
version: 20.17.6(patch_hash=9c31d25336aea4076b3cb942c35e59fd160c540947f01ea455a060367153f9fd)
@@ -423,6 +426,9 @@ importers:
jest-snapshot:
specifier: 30.0.0-alpha.6
version: 30.0.0-alpha.6
js-yaml:
specifier: 4.2.0
version: 4.2.0
json5:
specifier: 2.2.3
version: 2.2.3
+60 -3
View File
@@ -22,6 +22,7 @@ export interface PackageJson {
peerDependencies?: Record<string, string>
overrides?: Record<string, string>
resolutions?: Record<string, string>
pnpm?: { overrides?: Record<string, string>; [key: string]: unknown }
workspaces?: string[] | { packages: string[] }
[key: string]: unknown
}
@@ -37,9 +38,26 @@ export default async function patchPackageJson(
): Promise<string> {
try {
const packageJsonPath = await findPackageJsonPath(targetProjectPath)
const workspaceRoot = path.dirname(packageJsonPath)
const packageJsonValue = await readJsonValue(packageJsonPath)
await patchWorkspacePackageJsonMap(paths, packageJsonValue)
const overrides = await patchWorkspacePackageJsonMap(
paths,
packageJsonValue
)
await writeJsonValue(packageJsonPath, packageJsonValue)
// Also mirror overrides into pnpm-workspace.yaml so pnpm v11+ picks them
// up. We don't try to detect the pnpm version — pnpm v10 reads
// `pnpm.overrides` (written above) and pnpm v11+ reads
// `pnpm-workspace.yaml#overrides`. To avoid dropping a pnpm-workspace.yaml
// into non-pnpm projects, we only write the file if it already exists or
// if a `pnpm-lock.yaml` indicates this is a pnpm project. See
// https://pnpm.io/settings and https://github.com/pnpm/pnpm/issues/11536.
if (await shouldWritePnpmWorkspace(workspaceRoot)) {
await mergePnpmWorkspaceOverrides(
workspaceRoot,
Object.fromEntries(overrides)
)
}
return packageJsonPath
} catch (error) {
@@ -80,7 +98,7 @@ async function writeJsonValue(
async function patchWorkspacePackageJsonMap(
paths: DependencyPaths,
packageJsonMap: PackageJson
): Promise<PackageJson> {
): Promise<[string, string][]> {
const nextPeerDeps = await getNextPeerDeps()
const overrides: [string, string][] = [
@@ -101,6 +119,12 @@ async function patchWorkspacePackageJsonMap(
packageJsonMap.resolutions = packageJsonMap.resolutions || {}
insertMapEntries(packageJsonMap.resolutions, overrides)
// pnpm v10 and below read `pnpm.overrides` from package.json.
// pnpm v11+ reads `pnpm-workspace.yaml#overrides` (written separately).
packageJsonMap.pnpm = packageJsonMap.pnpm || {}
packageJsonMap.pnpm.overrides = packageJsonMap.pnpm.overrides || {}
insertMapEntries(packageJsonMap.pnpm.overrides, overrides)
// Add @next/swc to dependencies
packageJsonMap.dependencies = packageJsonMap.dependencies || {}
insertMapEntries(packageJsonMap.dependencies, [
@@ -110,7 +134,7 @@ async function patchWorkspacePackageJsonMap(
// Update direct dependencies to match overrides
updateMapEntriesIfExists(packageJsonMap.dependencies, overrides)
return packageJsonMap
return overrides
}
/**
@@ -142,6 +166,39 @@ async function getNextPeerDeps(): Promise<NextPeerDeps> {
}
}
async function shouldWritePnpmWorkspace(
workspaceRoot: string
): Promise<boolean> {
return (
(await fileExists(path.join(workspaceRoot, 'pnpm-workspace.yaml'))) ||
(await fileExists(path.join(workspaceRoot, 'pnpm-lock.yaml')))
)
}
async function mergePnpmWorkspaceOverrides(
workspaceRoot: string,
overrides: Record<string, string>
): Promise<void> {
const yaml = await import('js-yaml')
const filePath = path.join(workspaceRoot, 'pnpm-workspace.yaml')
let doc: Record<string, unknown> = {}
if (await fileExists(filePath)) {
const parsed = yaml.load(await fs.readFile(filePath, 'utf8'))
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
doc = parsed as Record<string, unknown>
}
}
const existing =
doc.overrides && typeof doc.overrides === 'object'
? (doc.overrides as Record<string, string>)
: {}
doc.overrides = { ...existing, ...overrides }
await fs.writeFile(filePath, yaml.dump(doc))
}
function insertMapEntries(
map: Record<string, string>,
entries: [string, string][]
+47 -5
View File
@@ -2,6 +2,7 @@ import { execSync } from 'node:child_process'
import fs from 'node:fs/promises'
import path from 'node:path'
import { parseArgs } from 'node:util'
import yaml from 'js-yaml'
const BASE_URL = 'https://vercel-packages.vercel.app/next/commits'
@@ -161,9 +162,8 @@ async function findWorkspaceRoot(projectPath) {
async function patchPackageJson(projectPath, tarballUrls) {
const root = await findWorkspaceRoot(projectPath)
const packageJsonPath = root
? path.join(root, 'package.json')
: path.join(projectPath, 'package.json')
const workspaceRoot = root ?? projectPath
const packageJsonPath = path.join(workspaceRoot, 'package.json')
if (!(await fileExists(packageJsonPath))) {
console.error(`Error: package.json not found at ${packageJsonPath}`)
@@ -175,18 +175,27 @@ async function patchPackageJson(projectPath, tarballUrls) {
const entries = Array.from(tarballUrls.entries())
// npm/pnpm overrides
// npm uses top-level `overrides`
pkg.overrides = pkg.overrides || {}
for (const [name, url] of entries) {
pkg.overrides[name] = url
}
// yarn resolutions
// yarn uses top-level `resolutions`
pkg.resolutions = pkg.resolutions || {}
for (const [name, url] of entries) {
pkg.resolutions[name] = url
}
// pnpm v10 and below read `pnpm.overrides` from package.json.
// pnpm v11+ reads `pnpm-workspace.yaml#overrides` (handled below). See
// https://pnpm.io/settings and https://github.com/pnpm/pnpm/issues/11536.
pkg.pnpm = pkg.pnpm || {}
pkg.pnpm.overrides = pkg.pnpm.overrides || {}
for (const [name, url] of entries) {
pkg.pnpm.overrides[name] = url
}
// Add @next/swc-linux-x64-gnu to dependencies
pkg.dependencies = pkg.dependencies || {}
pkg.dependencies['@next/swc-linux-x64-gnu'] = tarballUrls.get(
@@ -195,6 +204,16 @@ async function patchPackageJson(projectPath, tarballUrls) {
await fs.writeFile(packageJsonPath, JSON.stringify(pkg, null, 2) + '\n')
// Only touch pnpm-workspace.yaml if it already exists (merge into it) or if
// a pnpm-lock.yaml indicates this is a pnpm project — avoids dropping a
// pnpm-workspace.yaml into non-pnpm projects.
if (await shouldWritePnpmWorkspace(workspaceRoot)) {
await mergePnpmWorkspaceOverrides(
workspaceRoot,
Object.fromEntries(entries)
)
}
console.log(`Patched ${packageJsonPath}`)
console.log('Packages overridden:')
for (const [name, url] of entries) {
@@ -204,6 +223,29 @@ async function patchPackageJson(projectPath, tarballUrls) {
return packageJsonPath
}
async function shouldWritePnpmWorkspace(workspaceRoot) {
return (
(await fileExists(path.join(workspaceRoot, 'pnpm-workspace.yaml'))) ||
(await fileExists(path.join(workspaceRoot, 'pnpm-lock.yaml')))
)
}
async function mergePnpmWorkspaceOverrides(workspaceRoot, overrides) {
const filePath = path.join(workspaceRoot, 'pnpm-workspace.yaml')
let doc = {}
if (await fileExists(filePath)) {
const parsed = yaml.load(await fs.readFile(filePath, 'utf8'))
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
doc = parsed
}
}
doc.overrides = { ...(doc.overrides ?? {}), ...overrides }
await fs.writeFile(filePath, yaml.dump(doc))
}
// --- Main ---
async function main() {