mirror of
https://github.com/pnpm/pnpm.git
synced 2026-04-10 18:18:56 -04:00
* feat: switch from pkg to Node.js SEA for creating standalone executables Replace @yao-pkg/pkg with Node.js native Single Executable Applications (--build-sea, Node.js 25.5+). The SEA binary embeds only pnpm.cjs (CJS bootstrap), while pnpm.mjs and all assets live in a dist/ directory shipped alongside the binary in platform-specific tarballs. * refactor: move dist/ from platform packages to @pnpm/exe The dist/ directory (pnpm.mjs, worker.js, templates, etc.) is identical across all platforms, so ship it once in @pnpm/exe instead of duplicating it in each platform package. Platform packages now only contain the binary. The self-updater installs @pnpm/exe (not the platform package) so it gets both dist/ and the binary via optionalDependencies. * refactor: externalize @reflink/reflink in esbuild bundle Make @reflink/reflink external in both the main and worker esbuild bundles so the require() calls resolve at runtime from dist/node_modules instead of being inlined. Add @reflink/reflink as a production dependency of both pnpm (bundled into dist/node_modules by bundle-deps.ts) and @pnpm/exe (installed by npm alongside the binary). For GitHub release tarballs, only the target platform's reflink package is kept. For @pnpm/exe npm publishing, all reflink platform packages are stripped from dist/ since npm installs the right one automatically. * chore: update cspell list * test: update system-node-version tests for SEA detection Mock @pnpm/cli-meta's detectIfCurrentPkgIsExecutable instead of setting process.pkg, which is no longer used for SEA detection. * test: improve cli-meta test coverage for SEA migration Add tests for detectIfCurrentPkgIsExecutable() (non-SEA path) and isExecutedByCorepack() which were previously untested. The SEA=true path of detectIfCurrentPkgIsExecutable() cannot be unit tested since node:sea is unavailable in an ESM test environment. * refactor: move GitHub tarball assembly to copy-artifacts.ts build-artifacts.ts (prepublishOnly of @pnpm/exe) now only builds the SEA executables and prepares the exe npm dist/. The per-target dist/ assembly for GitHub release tarballs moves to copy-artifacts.ts, which is the natural owner of that concern. Other changes: - Extract getReflinkKeepPackages/stripReflinkPackages to reflink-utils.ts with tests using node:test - Move --force from top-level pnpm install in release.yml to the pnpm deploy in bundle-deps.ts, where it is actually needed to install all @reflink/reflink-* platform packages into dist/node_modules - Change @pnpm/exe prepublishOnly to run pnpm's full prepublishOnly (compile + bundle-deps) so dist/node_modules is populated before build-artifacts.ts and copy-artifacts.ts read from pnpm/dist * fix: copy dist/ alongside binary when running pnpm setup for SEA When the pnpm CLI is a Node.js SEA binary, it requires a dist/ directory adjacent to the executable at runtime (containing pnpm.mjs and bundled node_modules). The copyCli function in plugin-commands-setup now copies dist/ from alongside the current binary into the tools directory so that the installed pnpm works correctly after `pnpm setup`. * fix: avoid argument list too long when creating Windows zip archives * fix: propagate errors in copy-artifacts script Previously errors in createArtifactTarball were swallowed, causing the script to exit 0 even when artifact creation failed. Now errors are re-thrown with a descriptive message, and the top-level IIFE has a .catch() handler that sets a non-zero exit code. * refactor: remove reflink-utils.ts from @pnpm/exe The stripReflinkPackages call in build-artifacts.ts stripped all platform packages while keeping @reflink/reflink. Instead, just remove the entire @reflink directory from dist/ — @pnpm/exe already declares @reflink/reflink as a runtime dependency, so npm installs it (along with the right platform package via optionalDependencies) automatically. This eliminates reflink-utils.ts, its tests, and the code duplication with copy-artifacts.ts.
113 lines
3.9 KiB
TypeScript
113 lines
3.9 KiB
TypeScript
import fs from 'node:fs'
|
|
import path from 'node:path'
|
|
import { execSync } from 'node:child_process'
|
|
|
|
// Background
|
|
// ----------
|
|
//
|
|
// The published pnpm package contains a bundled node_modules directory at
|
|
// dist/node_modules.
|
|
//
|
|
// .
|
|
// ├── dist
|
|
// │ ├── node_modules
|
|
// │ │ ├── node-gyp
|
|
// │ │ ├── v8-compile-cache
|
|
// │ │ └── ...
|
|
// │ └── pnpm.mjs
|
|
// ├── ...
|
|
// └── package.json
|
|
//
|
|
// This is used to include certain dependencies like node-gyp out of the box
|
|
// when installing pnpm.
|
|
//
|
|
// Note that most pnpm dependencies are baked into the large pnpm.mjs file by
|
|
// esbuild. This script handles other dependencies the pnpm bundle config
|
|
// declares as "external" and resolved at runtime — node-gyp, v8-compile-cache,
|
|
// and @reflink/reflink (all platform variants, installed via --force).
|
|
//
|
|
// Strategy
|
|
// --------
|
|
//
|
|
// To create dist/node_modules, we'll run a pnpm deploy and move the results
|
|
// over into the dist dir.
|
|
//
|
|
// .
|
|
// ├── temp-deploy
|
|
// │ ├── ...
|
|
// │ ├── README.md
|
|
// │ ├── node_modules ──────────────┐
|
|
// │ ├── package.json │
|
|
// │ ├── pnpm-lock.yaml │
|
|
// │ └── pnpm-workspace.yaml │
|
|
// └── package.json │
|
|
// │
|
|
// . │
|
|
// ├── dist │
|
|
// │ ├── node_modules <────────────┘
|
|
// │ └── pnpm.mjs
|
|
// ├── ...
|
|
// └── package.json
|
|
//
|
|
// The pnpm deploy command should reuse workspace settings, patches, and the
|
|
// pnpm-lock.yaml. This is important to ensure settings such as pnpm.overrides
|
|
// are carried over since they might be overrides to fix CVE vulnerabilities.
|
|
|
|
const WORKSPACE_DIR = path.join(import.meta.dirname, '..')
|
|
const DEPLOY_DIR = path.join(import.meta.dirname, 'temp-deploy')
|
|
|
|
const NODE_MODULES_TEMP_DIR = path.join(DEPLOY_DIR, 'node_modules')
|
|
const NODE_MODULES_DEST_DIR = path.join(import.meta.dirname, 'dist/node_modules')
|
|
|
|
/**
|
|
* Remove files like CHANGELOG.md, README.md, etc from node_modules to keep the
|
|
* final distribution smaller.
|
|
*/
|
|
function cleanupNodeModules (dir: string) {
|
|
const nmPrune = path.join(import.meta.dirname, 'node_modules/.bin/nm-prune')
|
|
execSync(`${nmPrune} --force`, { cwd: dir, stdio: 'inherit' })
|
|
|
|
const pnpmStateFiles = [
|
|
// Since we're installing with --node-linker=hoisted, this directory only
|
|
// contains a small .lock.yaml file that's not needed in the final
|
|
// distribution.
|
|
'node_modules/.pnpm',
|
|
'node_modules/.modules.yaml',
|
|
'node_modules/.pnpm-workspace-state-v1.json',
|
|
]
|
|
for (const file of pnpmStateFiles) {
|
|
fs.rmSync(path.join(dir, file), { recursive: true })
|
|
}
|
|
}
|
|
|
|
function createDistNodeModules () {
|
|
// Remove the target directory to ensure the results of this script are as
|
|
// deterministic as possible and don't carry over old state.
|
|
fs.rmSync(DEPLOY_DIR, { recursive: true, force: true })
|
|
|
|
const pnpmDeploy = [
|
|
'pnpm',
|
|
'--config.inject-workspace-packages=true',
|
|
'--config.node-linker=hoisted',
|
|
'--ignore-scripts',
|
|
// --force installs all optional dependencies regardless of platform, so that
|
|
// all @reflink/reflink-* platform packages end up in dist/node_modules.
|
|
'--force',
|
|
'--filter=pnpm',
|
|
'--prod',
|
|
'deploy',
|
|
DEPLOY_DIR
|
|
].join(' ')
|
|
execSync(pnpmDeploy, { cwd: WORKSPACE_DIR, stdio: 'inherit' })
|
|
|
|
cleanupNodeModules(DEPLOY_DIR)
|
|
|
|
fs.rmSync(NODE_MODULES_DEST_DIR, { recursive: true, force: true })
|
|
fs.mkdirSync(path.dirname(NODE_MODULES_DEST_DIR), { recursive: true })
|
|
fs.renameSync(NODE_MODULES_TEMP_DIR, NODE_MODULES_DEST_DIR)
|
|
|
|
fs.rmSync(DEPLOY_DIR, { recursive: true })
|
|
}
|
|
|
|
createDistNodeModules()
|