pnpm pack-app
新增于:v11.0.0
🌐 Added in: v11.0.0
pnpm pack-app 是实验性的。它的标志、pnpm.app 配置模式和输出布局可能在将来的版本中发生变化。
将一个 CommonJS 入口文件打包成一个独立可执行文件,用于一个或多个目标平台,底层使用 Node.js 单文件可执行应用 API。
🌐 Pack a CommonJS entry file into a standalone executable for one or more target platforms, using the Node.js Single Executable Applications API under the hood.
pnpm pack-app --entry <path> --target <triplet> [--target <triplet> ...]
每个目标都会在 <output-dir>/<target>/ 下生成一个可执行文件(默认 dist-app/<target>/)。在 Windows 目标上,输出文件会加上 .exe 后缀;macOS 输出会自动进行临时签名(通过 macOS 主机上的 codesign 或 Linux 主机上的 ldid),因为 SEA 注入会使现有的代码签名失效。
要求
🌐 Requirements
- 主机必须运行 Node.js v25.5 及以上版本才能执行 SEA 注入。如果正在运行的 Node.js 版本较旧(或与内嵌的运行时版本不匹配——SEA blob 在小版本间不兼容),pnpm 会自动下载一个匹配的构建器。
- 从 Linux 交叉编译 macOS 目标需要在
$PATH上使用ldid。Windows 主机无法对 macOS 输出进行临时签名;请在 macOS 或 Linux 上构建 macOS 目标。
支持的目标
🌐 Supported targets
目标使用格式 <os>-<arch>[-<libc>]:
linux-x64、linux-x64-musl、linux-arm64、linux-arm64-musldarwin-x64,darwin-arm64win32-x64,win32-arm64
-musl 后缀仅对 linux 目标有效。<os> 段匹配 process.platform 值,因此该标志与 pnpm 的 --os 标志以及 pnpm-workspace.yaml 中的 supportedArchitectures.os 一致。
🌐 The -musl suffix is only valid for linux targets. The <os> segment matches process.platform values so the flag is consistent with pnpm's --os flag and with supportedArchitectures.os in pnpm-workspace.yaml.
已知的限制
🌐 Known limitations
darwin-x64 二进制文件在 Intel Mac 上崩溃
🌐 darwin-x64 binaries crash on Intel Macs
darwin-x64 在 Intel Mac 上启动时会输出段错误,这是由于上游 Node.js 在 --build-sea 注入步骤中的一个 bug。LIEF 对 x64 的 Mach-O 操作在插入 SEA 段后会使 LC_DYLD_CHAINED_FIXUPS 链条条目指向过时的目标;然后 dyld 将一个原始链编码的值作为指针解引用,导致二进制在 __cxx_global_var_init 中崩溃,用户代码尚未运行。这可以通过标准的 node --build-sea + codesign --sign - 流程重现,并且不涉及 pnpm。
Node.js 团队选择不修复这个问题,理由是 x64 macOS 正在被逐步淘汰。与签名相关的解决方法无济于事——损坏发生在注入步骤中,在签名之前,因此将 ldid 替换为 codesign(或反之)没有任何区别。重新签名会在已经损坏的字节上生成一个有效的签名。
🌐 The Node.js team has opted not to fix this on the grounds that x64 macOS is being phased out. Signature-related workarounds do not help — the corruption happens in the injection step, before signing, so swapping ldid for codesign (or vice versa) makes no difference. Re-signing produces a valid signature over already-broken bytes.
追踪:
🌐 Tracking:
- nodejs/node#62893 — 最小
node --build-sea重现 - nodejs/node#59553 — 在 macOS x64 上长时间运行的 SEA 测试失败,原因相同
- nodejs/node#60250 — Node.js 在 x64 macOS 上跳过 SEA 测试而不是修复它们
如果你需要在 Intel Mac 上发布运行的 CLI,请使用非 SEA 工具(例如 @yao-pkg/pkg)构建 darwin-x64 工件(该工具会附加到二进制尾部,而不是修改 Mach-O 节)。请注意,Rosetta 并不是一个逃生通道——它只会将 x64 转换为 arm64(用于在 Apple Silicon Mac 上运行 Intel 二进制文件),而不能反向,因此 Intel Mac 无法运行 darwin-arm64 构建。
🌐 If you need to ship a CLI that runs on Intel Macs, build the darwin-x64 artifact with a non-SEA tool such as @yao-pkg/pkg (which appends to the binary tail rather than mutating Mach-O sections). Note that Rosetta is not an escape hatch — it only translates x64 → arm64 (for Apple Silicon Macs running Intel binaries), not the other direction, so Intel Macs cannot run a darwin-arm64 build.
示例
🌐 Examples
同时为 Linux 和 Windows 构建:
🌐 Build for Linux and Windows at once:
pnpm pack-app --entry dist/index.cjs --target linux-x64 --target win32-x64
嵌入特定的 Node.js 版本:
🌐 Embed a specific Node.js version:
pnpm pack-app --entry dist/index.cjs --target linux-x64-musl --runtime node@25.5.0
选项
🌐 Options
--entry <path>
要嵌入可执行文件的 CJS 入口文件的路径。除非在 package.json 中设置了 pnpm.app.entry,否则这是必需的。也接受一个单独的位置参数(例如 pnpm pack-app dist/index.cjs)。
🌐 Path to the CJS entry file to embed in the executable. Required unless pnpm.app.entry is set in package.json. A bare positional argument is also accepted (e.g. pnpm pack-app dist/index.cjs).
--target, -t <triplet>
要构建的目标。可以多次指定。有关接受的值,请参阅 支持的目标。除非设置了 pnpm.app.targets,否则为必填项。在命令行传递时,--target 会完全替换已配置的列表,因此你可以在调用时缩小构建范围。
🌐 Target to build for. May be specified multiple times. See Supported targets for the accepted values. Required unless pnpm.app.targets is set. When passed on the CLI, --target entirely replaces the configured list so you can narrow the build at invocation time.
--runtime <spec>
在输出可执行文件中嵌入的运行时,作为 <name>@<version> 规范(例如 node@25、node@25.5.0)。目前仅支持 node;<name>@ 前缀为将来的运行时(bun、deno)留出空间。版本必须 >= v25.5(最低支持 --build-sea)。默认使用正在运行的 Node.js 版本。
🌐 Runtime to embed in the output executables, as a <name>@<version> spec (e.g. node@25, node@25.5.0). Only node is supported today; the <name>@ prefix leaves room for future runtimes (bun, deno). The version must be >= v25.5 (the minimum that supports --build-sea). Defaults to the running Node.js version.
--output-dir, -o <dir>
构建的可执行文件的输出目录。默认是 dist-app。
🌐 Output directory for the built executables. Defaults to dist-app.
--output-name <name>
输出可执行文件的名称(不含扩展名)。默认使用 package.json 的未加作用域的 name(例如,@acme/my-cli 的 my-cli)。
🌐 Name for the output executable (without extension). Defaults to the unscoped name from package.json (e.g. my-cli for @acme/my-cli).
配置
🌐 Configuration
每个标志的默认值可以在 package.json 下的 pnpm.app 中设置。CLI 标志会覆盖配置:
🌐 Defaults for every flag can be set in package.json under pnpm.app. CLI flags override the config:
{
"name": "my-cli",
"pnpm": {
"app": {
"entry": "dist/index.cjs",
"targets": [
"linux-x64",
"linux-arm64",
"darwin-x64",
"darwin-arm64",
"win32-x64"
],
"runtime": "node@25.5.0",
"outputDir": "dist-app",
"outputName": "my-cli"
}
}
}
有了这个配置,pnpm pack-app 可以在没有参数的情况下运行。CLI 上的 --target 会替换配置的 targets 列表,这对于缩小构建范围很有用(例如 pnpm pack-app --target linux-x64)。
🌐 With this config in place, pnpm pack-app can be run with no arguments. --target on the CLI replaces the configured targets list, which is useful for narrowing a build (e.g. pnpm pack-app --target linux-x64).