mirror of
https://github.com/pnpm/pnpm.git
synced 2026-06-06 23:44:55 -04:00
## Summary Closes #11423. `pnpm-darwin-x64.tar.gz` and `@pnpm/macos-x64` are removed because the binaries they contain segfault at startup on Intel Macs and the underlying bug is upstream and unfixed. ## Why this isn't a fix in code The crash happens in `__cxx_global_var_init` with `EXC_BAD_ACCESS (code=1, address=0x3)` — the unprocessed-chain-entry tag — in dyld's chained-fixup processing. PR #11415's hypothesis was that `ldid`'s page hashes were the cause, but switching to native `codesign` in #11415 didn't fix it: the upstream minimal repro in [nodejs/node#62893](https://github.com/nodejs/node/issues/62893) is `node --build-sea` + `codesign --sign -` + run, with no pnpm and no `ldid`, and it still crashes. The corruption is in LIEF's Mach-O surgery during `--build-sea` for x64 — chained-fixup chain entries get rewritten incorrectly when the SEA segment is inserted, and re-signing produces a valid signature over the broken bytes. The Node.js team is not going to fix this: - [nodejs/node#60250](https://github.com/nodejs/node/pull/60250) (merged) — *"It's unlikely that anyone would invest in fixing them on x64 macOS in the near future, now that x64 macOS is being phased out."* They skipped the SEA tests on x64 macOS rather than chase the bug. - [nodejs/node#59553](https://github.com/nodejs/node/issues/59553) (open) — long-running test failures on macOS x64 with the same root cause (sometimes surfacing as `unsupported thread-local, larger than 4GB`). `@yao-pkg/pkg` works around it by appending the JS payload to the file tail and using a custom-patched Node binary that reads from the tail at startup; this avoids Mach-O surgery entirely. We can't reuse pack-app for that because vanilla Node from nodejs.org doesn't read tail-appended payloads — only pkg-fetch's patched binaries do — so adopting that path would mean re-implementing pkg-fetch for one target. For now we're dropping the broken artifact rather than introducing a second build mechanism. ## Changes - **`pnpm/artifacts/exe/package.json`** — remove `@pnpm/macos-x64` from `optionalDependencies`; remove `darwin-x64` from `pnpm.app.targets`. - **`.meta-updater/src/index.ts`** — remove `@pnpm/macos-x64` from the enforced `optionalDependencies` list (otherwise `meta-updater` would put it back). - **`pnpm/artifacts/exe/scripts/build-artifacts.ts`** — drop `darwin-x64` from `narrowTargets` so dev-local builds match the published matrix; comment explains why. - **`__utils__/scripts/src/copy-artifacts.ts`** — stop creating `pnpm-darwin-x64.tar.gz` so the GitHub release page no longer ships it. - **`pnpm/artifacts/darwin-x64/`** — deleted (was the workspace source for `@pnpm/macos-x64`). - **`pnpm/artifacts/exe/setup.js`** — wraps the `import.meta.resolve('${pkgName}/package.json')` lookup in `try`/`catch`. On Intel Mac specifically, prints a clear message pointing at this issue, the upstream Node.js issue, and the two workarounds (`npm install -g pnpm` to use the system Node.js, or stay on pnpm 10.x). Other unsupported hosts get a generic message in the same shape. Exits non-zero so the install fails loudly instead of silently leaving a broken `pnpm`. - **`pnpm-lock.yaml`** — regenerated. - **`.changeset/drop-darwin-x64-broken-sea.md`** — patch bumps for `@pnpm/exe` and `pnpm` with user-facing explanation and pointers. Docs side already lists this limitation under `pack-app` Known limitations: pnpm/pnpm.io@36d962f6 / pnpm/pnpm.io@91f45632. ## Compat - Intel Mac users on existing `@pnpm/exe` (≤ 11.0.4) keep working with the (broken) old binary they already have. - `pnpm self-update` from an Intel Mac on an older `@pnpm/exe` will hit the new `setup.js` error path with a clear pointer to the workarounds. - New Intel Mac installs via `npm install -g @pnpm/exe` will fail loudly with the same pointer. - Install via `npm install -g pnpm` (the JS-only package, uses system Node) is unaffected and remains the recommended path. - The `install.sh` from `get.pnpm.io` will fail with a 404 on the missing `pnpm-darwin-x64.tar.gz`. That's a separate repo and a follow-up — happy to do that as a second PR.
126 lines
4.5 KiB
TypeScript
126 lines
4.5 KiB
TypeScript
import fs from 'node:fs'
|
|
import path from 'node:path'
|
|
import stream from 'node:stream'
|
|
|
|
import * as execa from 'execa'
|
|
import { makeEmptyDir } from 'make-empty-dir'
|
|
import * as tar from 'tar'
|
|
import { glob } from 'tinyglobby'
|
|
|
|
const repoRoot = path.join(import.meta.dirname, '../../..')
|
|
const dest = path.join(repoRoot, 'dist')
|
|
const artifactsDir = path.join(repoRoot, 'pnpm/artifacts')
|
|
const pnpmDistDir = path.join(repoRoot, 'pnpm/dist')
|
|
|
|
;(async () => {
|
|
await makeEmptyDir(dest)
|
|
if (!fs.existsSync(path.join(artifactsDir, 'linux-x64/pnpm'))) {
|
|
execa.sync('pnpm', ['--filter=@pnpm/exe', 'run', 'build-artifacts'], {
|
|
cwd: repoRoot,
|
|
stdio: 'inherit',
|
|
})
|
|
}
|
|
await createArtifactTarball('linux-x64', 'pnpm')
|
|
await createArtifactTarball('linux-x64-musl', 'pnpm')
|
|
await createArtifactTarball('linux-arm64', 'pnpm')
|
|
await createArtifactTarball('linux-arm64-musl', 'pnpm')
|
|
// darwin-x64 is intentionally absent: Node.js SEA injection produces a
|
|
// binary that segfaults on Intel Mac (pnpm/pnpm#11423, nodejs/node#62893).
|
|
await createArtifactTarball('darwin-arm64', 'pnpm')
|
|
await createArtifactTarball('win32-x64', 'pnpm.exe')
|
|
await createArtifactTarball('win32-arm64', 'pnpm.exe')
|
|
await createSourceMapsArchive()
|
|
})().catch((err) => {
|
|
console.error(err)
|
|
process.exitCode = 1
|
|
})
|
|
|
|
async function createArtifactTarball (target: string, binaryName: string): Promise<void> {
|
|
try {
|
|
const artifactDir = path.join(artifactsDir, target)
|
|
const binaryPath = path.join(artifactDir, binaryName)
|
|
if (!fs.existsSync(binaryPath)) {
|
|
console.log(`Warning: ${binaryPath} not found, skipping ${target}`)
|
|
return
|
|
}
|
|
|
|
// Copy dist/ from the pnpm build output and strip non-target reflink packages.
|
|
// Source maps are removed from this copy — they are archived separately via
|
|
// createSourceMapsArchive(), which reads from the original pnpmDistDir.
|
|
const distDest = path.join(artifactDir, 'dist')
|
|
fs.rmSync(distDest, { recursive: true, force: true })
|
|
fs.cpSync(pnpmDistDir, distDest, { recursive: true, verbatimSymlinks: true })
|
|
stripReflinkPackages(distDest, getReflinkKeepPackages(target))
|
|
for (const mapFile of await glob('**/*.map', { cwd: distDest })) {
|
|
fs.rmSync(path.join(distDest, mapFile))
|
|
}
|
|
|
|
const isWindows = target.startsWith('win32-')
|
|
const archiveName = isWindows ? `pnpm-${target}.zip` : `pnpm-${target}.tar.gz`
|
|
|
|
if (isWindows) {
|
|
// Create zip for Windows
|
|
const zipPath = path.join(dest, archiveName)
|
|
execa.sync('zip', ['-r', zipPath, binaryName, 'dist'], {
|
|
cwd: artifactDir,
|
|
stdio: 'inherit',
|
|
})
|
|
} else {
|
|
// Create tar.gz for Unix
|
|
await stream.promises.pipeline(
|
|
tar.create({ gzip: true, cwd: artifactDir }, [binaryName, 'dist']),
|
|
fs.createWriteStream(path.join(dest, archiveName))
|
|
)
|
|
}
|
|
console.log(`Created ${archiveName}`)
|
|
} catch (err) {
|
|
console.error(`Failed to create artifact for target "${target}":`, err)
|
|
throw err
|
|
}
|
|
}
|
|
|
|
async function createSourceMapsArchive () {
|
|
// The tar.create function can accept a filter callback function, but this
|
|
// approach ends up adding empty directories to the archive. Using tinyglobby
|
|
// instead.
|
|
const mapFiles = await glob('**/*.map', { cwd: pnpmDistDir })
|
|
|
|
await stream.promises.pipeline(
|
|
tar.create({ gzip: true, cwd: pnpmDistDir }, mapFiles),
|
|
fs.createWriteStream(path.join(dest, 'source-maps.tgz'))
|
|
)
|
|
}
|
|
|
|
// Reflink platform package names needed for a build target.
|
|
// Target format: '<platform>-<arch>[-<libc>]' where <platform> is one of
|
|
// 'linux', 'darwin', 'win32', <arch> is 'x64' | 'arm64', and <libc> is only
|
|
// ever 'musl' (linux-only).
|
|
function getReflinkKeepPackages (target: string): string[] {
|
|
if (target.startsWith('darwin-')) {
|
|
return [`@reflink/reflink-${target}`]
|
|
}
|
|
if (target.startsWith('win32-')) {
|
|
return [`@reflink/reflink-${target}-msvc`]
|
|
}
|
|
if (target.startsWith('linux-')) {
|
|
const arch = target.includes('arm64') ? 'arm64' : 'x64'
|
|
return [
|
|
`@reflink/reflink-linux-${arch}-gnu`,
|
|
`@reflink/reflink-linux-${arch}-musl`,
|
|
]
|
|
}
|
|
return []
|
|
}
|
|
|
|
function stripReflinkPackages (distDir: string, keepPackages: string[]): void {
|
|
const reflinkDir = path.join(distDir, 'node_modules', '@reflink')
|
|
if (!fs.existsSync(reflinkDir)) return
|
|
|
|
for (const entry of fs.readdirSync(reflinkDir)) {
|
|
if (entry === 'reflink') continue // keep the main package
|
|
if (!keepPackages.includes(`@reflink/${entry}`)) {
|
|
fs.rmSync(path.join(reflinkDir, entry), { recursive: true })
|
|
}
|
|
}
|
|
}
|