/
githubmirror
/
next.js
Обзор
Документация
Войти
/
githubmirror
/
next.js
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
canary
scripts/pack-utils/patch-package-json.ts
272 строки
8 KB
Sebastian "Sebbie" Silbermann
[scripts] Write pnpm overrides for pnpm projects in pack/patch helpers (#94747)
22 июн 2026, 18:12
Не верифицирован
22 июн 2026, 18:12
2aba93f
Код
Авторство
О чём код?
import fs from 'node:fs/promises' import path from 'node:path' import { fileURLToPath } from 'node:url' import os from 'node:os' export interface DependencyPaths { nextTarball: string nextMdxTarball: string nextEnvTarball: string nextBundleAnalyzerTarball: string nextSwcTarball: string } interface NextPeerDeps { react: string reactDom: string [key: string]: unknown } export interface PackageJson { dependencies?: Record<string, string> 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 } /** * Main function to patch a project's package.json with Next.js tarball references * @param paths Configuration options for patching * @returns The path to the patched file */ export default async function patchPackageJson( targetProjectPath: string, paths: DependencyPaths ): Promise<string> { try { const packageJsonPath = await findPackageJsonPath(targetProjectPath) const workspaceRoot = path.dirname(packageJsonPath) const packageJsonValue = await readJsonValue(packageJsonPath) 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) { throw new Error('Error patching package.json', { cause: error }) } } export async function findPackageJsonPath( targetProjectPath: string ): Promise<string> { const root = await findWorkspaceRoot(targetProjectPath) return root ? path.join(root, 'package.json') : path.join(targetProjectPath, 'package.json') } async function readJsonValue(filePath: string): Promise<PackageJson> { try { const content = await fs.readFile(filePath, 'utf8') return JSON.parse(content) as PackageJson } catch (error) { throw new Error(`Could not read or parse ${filePath}`, { cause: error }) } } async function writeJsonValue( filePath: string, value: PackageJson ): Promise<void> { try { const content = JSON.stringify(value, null, 2) + os.EOL await fs.writeFile(filePath, content) } catch (error) { throw new Error(`Failed to write ${filePath}`, { cause: error }) } } async function patchWorkspacePackageJsonMap( paths: DependencyPaths, packageJsonMap: PackageJson ): Promise<[string, string][]> { const nextPeerDeps = await getNextPeerDeps() const overrides: [string, string][] = [ ['next', `file:${paths.nextTarball}`], ['@next/mdx', `file:${paths.nextMdxTarball}`], ['@next/env', `file:${paths.nextEnvTarball}`], ['@next/bundle-analyzer', `file:${paths.nextBundleAnalyzerTarball}`], ['@next/swc', `file:${paths.nextSwcTarball}`], ['react', nextPeerDeps.react], ['react-dom', nextPeerDeps.reactDom], ] // npm uses `overrides` packageJsonMap.overrides = packageJsonMap.overrides || {} insertMapEntries(packageJsonMap.overrides, overrides) // yarn uses `resolutions` 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, [ ['@next/swc', `file:${paths.nextSwcTarball}`], ]) // Update direct dependencies to match overrides updateMapEntriesIfExists(packageJsonMap.dependencies, overrides) return overrides } /** * Get Next.js peer dependencies from its package.json */ async function getNextPeerDeps(): Promise<NextPeerDeps> { try { // Navigate to the next package.json relative to the current module const currentFilePath = fileURLToPath(import.meta.url) const scriptDir = path.dirname(currentFilePath) const packageJsonPath = path.resolve( scriptDir, '../../packages/next/package.json' ) const content = await fs.readFile(packageJsonPath, 'utf8') const nextPackageJson = JSON.parse(content) as PackageJson if (!nextPackageJson.peerDependencies) { throw new Error('Next.js package.json is missing peerDependencies') } return { react: nextPackageJson.peerDependencies.react || '', reactDom: nextPackageJson.peerDependencies['react-dom'] || '', } } catch (error) { throw new Error('Failed to get Next.js peer dependencies', { cause: error }) } } 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][] ): void { for (const [key, value] of entries) { map[key] = value } } function updateMapEntriesIfExists( map: Record<string, string>, entries: [string, string][] ): void { for (const [key, value] of entries) { if (map[key] !== undefined) { map[key] = value } } } async function fileExists(filePath: string): Promise<boolean> { try { await fs.access(filePath) return true } catch { return false } } /** * @returns null if not in a workspace */ async function findWorkspaceRoot(projectPath: string): Promise<string | null> { // Check environment variables const envVars = ['NPM_CONFIG_WORKSPACE_DIR', 'npm_config_workspace_dir'] for (const ev of envVars) { if (process.env[ev]) { return process.env[ev] } } try { const canonicalPath = await fs.realpath(projectPath) let currentDir = canonicalPath // Walk up the directory tree while (currentDir !== path.parse(currentDir).root) { // Check for pnpm workspace if (await fileExists(path.join(currentDir, 'pnpm-workspace.yaml'))) { return currentDir } // Check for npm/yarn workspace const packageJsonPath = path.join(currentDir, 'package.json') if (await fileExists(packageJsonPath)) { const packageJson = await readJsonValue(packageJsonPath) if (packageJson.workspaces) { return currentDir } } // Move up to parent directory currentDir = path.dirname(currentDir) } // No workspace found return null } catch (error) { throw new Error('Failed to find workspace root', { cause: error }) } }