Some checks failed
Test examples / Test Examples (20) (push) Has been cancelled
Test examples / Test Examples (22) (push) Has been cancelled
Lock Threads / action (push) Has been cancelled
Trigger Release / start (push) Has been cancelled
Stale issue handler / stale (push) Has been cancelled
Update Font Data / create-pull-request (push) Has been cancelled
build-and-deploy / deploy-target (push) Has been cancelled
build-and-deploy / build (push) Has been cancelled
build-and-deploy / stable - aarch64-unknown-linux-musl - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-unknown-linux-musl - node@16 (push) Has been cancelled
build-and-deploy / stable - aarch64-unknown-linux-gnu - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-unknown-linux-gnu - node@16 (push) Has been cancelled
build-and-deploy / stable - aarch64-pc-windows-msvc - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-pc-windows-msvc - node@16 (push) Has been cancelled
build-and-deploy / stable - aarch64-apple-darwin - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-apple-darwin - node@16 (push) Has been cancelled
build-and-deploy / build-wasm (nodejs) (push) Has been cancelled
build-and-deploy / build-wasm (web) (push) Has been cancelled
build-and-deploy / Deploy preview tarball (push) Has been cancelled
build-and-deploy / Potentially publish release (push) Has been cancelled
build-and-deploy / publish-turbopack-npm-packages (push) Has been cancelled
build-and-deploy / Deploy examples (push) Has been cancelled
build-and-deploy / thank you, build (push) Has been cancelled
build-and-deploy / Upload Turbopack Bytesize metrics to Datadog (push) Has been cancelled
Rspack Next.js development integration tests / Rspack integration tests (push) Has been cancelled
Rspack Next.js production integration tests / Rspack integration tests (push) Has been cancelled
Turbopack Next.js development integration tests / Next.js integration tests (push) Has been cancelled
Turbopack Next.js production integration tests / Next.js integration tests (push) Has been cancelled
Update Rspack test manifest / Update and upload Rspack development test manifest (push) Has been cancelled
Update Rspack test manifest / Update and upload Rspack production test manifest (push) Has been cancelled
Upload bundler test manifests to areweturboyet.com / Upload test results (push) Has been cancelled
Update React / create-pull-request (push) Has been cancelled
test-e2e-project-reset-cron / reset-test-project (push) Has been cancelled
Notify about the top 15 issues/PRs/feature requests (most reacted) in the last 90 days / run (push) Has been cancelled
211 lines
5.8 KiB
TypeScript
211 lines
5.8 KiB
TypeScript
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>
|
|
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 root = await findWorkspaceRoot(targetProjectPath)
|
|
const packageJsonPath = root
|
|
? path.join(root, 'package.json')
|
|
: path.join(targetProjectPath, 'package.json')
|
|
|
|
const packageJsonValue = await readJsonValue(packageJsonPath)
|
|
await patchWorkspacePackageJsonMap(paths, packageJsonValue)
|
|
await writeJsonValue(packageJsonPath, packageJsonValue)
|
|
|
|
return packageJsonPath
|
|
} catch (error) {
|
|
throw new Error('Error patching package.json', { cause: error })
|
|
}
|
|
}
|
|
|
|
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<PackageJson> {
|
|
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)
|
|
|
|
// 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 packageJsonMap
|
|
}
|
|
|
|
/**
|
|
* 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 })
|
|
}
|
|
}
|
|
|
|
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 })
|
|
}
|
|
}
|