4cd8b23032
Follow-up to the earlier enabling of classes/variables etc. Bug Related issues linked using fixes #number Integration tests added Errors have helpful link attached, see contributing.md Feature Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR. Related issues linked using fixes #number Integration tests added Documentation added Telemetry added. In case of a feature if it's used or not. Errors have helpful link attached, see contributing.md Documentation / Examples Make sure the linting passes by running pnpm lint The examples guidelines are followed from our contributing doc Co-authored-by: Steven <steven@ceriously.com>
189 lines
5.1 KiB
TypeScript
189 lines
5.1 KiB
TypeScript
import { promises } from 'fs'
|
|
import '../server/node-polyfill-fetch'
|
|
import * as Log from '../build/output/log'
|
|
import findUp from 'next/dist/compiled/find-up'
|
|
import { execSync } from 'child_process'
|
|
// @ts-ignore no-json types
|
|
import nextPkgJson from 'next/package.json'
|
|
import type { UnwrapPromise } from './coalesced-function'
|
|
import { isCI } from '../telemetry/ci-info'
|
|
|
|
let registry: string | undefined
|
|
|
|
async function fetchPkgInfo(pkg: string) {
|
|
if (!registry) {
|
|
try {
|
|
const output = execSync('npm config get registry').toString().trim()
|
|
if (output.startsWith('http')) {
|
|
registry = output
|
|
|
|
if (!registry.endsWith('/')) {
|
|
registry += '/'
|
|
}
|
|
}
|
|
} catch (_) {
|
|
registry = `https://registry.npmjs.org/`
|
|
}
|
|
}
|
|
const res = await fetch(`${registry}${pkg}`)
|
|
|
|
if (!res.ok) {
|
|
throw new Error(
|
|
`Failed to fetch registry info for ${pkg}, got status ${res.status}`
|
|
)
|
|
}
|
|
const data = await res.json()
|
|
const versionData = data.versions[nextPkgJson.version]
|
|
|
|
return {
|
|
os: versionData.os,
|
|
cpu: versionData.cpu,
|
|
engines: versionData.engines,
|
|
tarball: versionData.dist.tarball,
|
|
integrity: versionData.dist.integrity,
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Attempts to patch npm package-lock.json when it
|
|
* fails to include optionalDependencies for other platforms
|
|
* this can occur when the package-lock is rebuilt from a current
|
|
* node_modules install instead of pulling fresh package data
|
|
*/
|
|
export async function patchIncorrectLockfile(dir: string) {
|
|
if (process.env.NEXT_IGNORE_INCORRECT_LOCKFILE) {
|
|
return
|
|
}
|
|
const lockfilePath = await findUp('package-lock.json', { cwd: dir })
|
|
|
|
if (!lockfilePath) {
|
|
// if no lockfile present there is no action to take
|
|
return
|
|
}
|
|
const content = await promises.readFile(lockfilePath, 'utf8')
|
|
// maintain current line ending
|
|
const endingNewline = content.endsWith('\r\n')
|
|
? '\r\n'
|
|
: content.endsWith('\n')
|
|
? '\n'
|
|
: ''
|
|
|
|
const lockfileParsed = JSON.parse(content)
|
|
const lockfileVersion = parseInt(lockfileParsed?.lockfileVersion, 10)
|
|
const expectedSwcPkgs = Object.keys(nextPkgJson.optionalDependencies || {})
|
|
|
|
const patchDependency = (
|
|
pkg: string,
|
|
pkgData: UnwrapPromise<ReturnType<typeof fetchPkgInfo>>
|
|
) => {
|
|
lockfileParsed.dependencies[pkg] = {
|
|
version: nextPkgJson.version,
|
|
resolved: pkgData.tarball,
|
|
integrity: pkgData.integrity,
|
|
optional: true,
|
|
}
|
|
}
|
|
|
|
const patchPackage = (
|
|
pkg: string,
|
|
pkgData: UnwrapPromise<ReturnType<typeof fetchPkgInfo>>
|
|
) => {
|
|
lockfileParsed.packages[pkg] = {
|
|
version: nextPkgJson.version,
|
|
resolved: pkgData.tarball,
|
|
integrity: pkgData.integrity,
|
|
cpu: pkgData.cpu,
|
|
optional: true,
|
|
os: pkgData.os,
|
|
engines: pkgData.engines,
|
|
}
|
|
}
|
|
|
|
try {
|
|
const supportedVersions = [1, 2, 3]
|
|
|
|
if (!supportedVersions.includes(lockfileVersion)) {
|
|
// bail on unsupported version
|
|
return
|
|
}
|
|
// v1 only uses dependencies
|
|
// v2 uses dependencies and packages
|
|
// v3 only uses packages
|
|
const shouldPatchDependencies =
|
|
lockfileVersion === 1 || lockfileVersion === 2
|
|
const shouldPatchPackages = lockfileVersion === 2 || lockfileVersion === 3
|
|
|
|
if (
|
|
(shouldPatchDependencies && !lockfileParsed.dependencies) ||
|
|
(shouldPatchPackages && !lockfileParsed.packages)
|
|
) {
|
|
// invalid lockfile so bail
|
|
return
|
|
}
|
|
const missingSwcPkgs = []
|
|
let pkgPrefix: string | undefined
|
|
|
|
if (shouldPatchPackages) {
|
|
pkgPrefix = ''
|
|
for (const pkg of Object.keys(lockfileParsed.packages)) {
|
|
if (pkg.endsWith('node_modules/next')) {
|
|
pkgPrefix = pkg.substring(0, pkg.length - 4)
|
|
}
|
|
}
|
|
|
|
if (!pkgPrefix) {
|
|
// unable to locate the next package so bail
|
|
return
|
|
}
|
|
}
|
|
|
|
for (const pkg of expectedSwcPkgs) {
|
|
if (
|
|
(shouldPatchDependencies && !lockfileParsed.dependencies[pkg]) ||
|
|
(shouldPatchPackages && !lockfileParsed.packages[`${pkgPrefix}${pkg}`])
|
|
) {
|
|
missingSwcPkgs.push(pkg)
|
|
}
|
|
}
|
|
if (missingSwcPkgs.length === 0) {
|
|
return
|
|
}
|
|
Log.warn(
|
|
`Found lockfile missing swc dependencies,`,
|
|
isCI ? 'run next locally to automatically patch' : 'patching...'
|
|
)
|
|
|
|
if (isCI) {
|
|
// no point in updating in CI as the user can't save the patch
|
|
return
|
|
}
|
|
const pkgsData = await Promise.all(
|
|
missingSwcPkgs.map((pkg) => fetchPkgInfo(pkg))
|
|
)
|
|
|
|
for (let i = 0; i < pkgsData.length; i++) {
|
|
const pkg = missingSwcPkgs[i]
|
|
const pkgData = pkgsData[i]
|
|
|
|
if (shouldPatchDependencies) {
|
|
patchDependency(pkg, pkgData)
|
|
}
|
|
if (shouldPatchPackages) {
|
|
patchPackage(`${pkgPrefix}${pkg}`, pkgData)
|
|
}
|
|
}
|
|
|
|
await promises.writeFile(
|
|
lockfilePath,
|
|
JSON.stringify(lockfileParsed, null, 2) + endingNewline
|
|
)
|
|
Log.warn(
|
|
'Lockfile was successfully patched, please run "npm install" to ensure @next/swc dependencies are downloaded'
|
|
)
|
|
} catch (err) {
|
|
Log.error(
|
|
`Failed to patch lockfile, please try uninstalling and reinstalling next in this workspace`
|
|
)
|
|
console.error(err)
|
|
}
|
|
}
|