跳到主内容
版本:11.x

pnpm pack-app

添加于:v11.0.0

实验性

pnpm pack-app 是实验性的。 它的标志,pnpm.app 配置方案和输出布局可能会在未来版本中改变。

使用底层的 Node.js 单可执行应用程序 API,将 CommonJS 入口文件打包成一个或多个目标平台的独立可执行文件。

pnpm pack-app --entry <path> --target <triplet> [--target <triplet> ...]

每个目标都会在 <output-dir>/<target>/ 下生成一个可执行文件(默认为 dist-app/<target>/)。 在 Windows 目标上,输出文件会以 .exe 为后缀;macOS 输出文件会自动进行临时签名(在 macOS 主机上通过 codesign,在 Linux 主机上通过 ldid),因为 SEA 注入会使现有的代码签名失效。

需求

  • 主机必须运行 Node.js v25.5+ 才能执行 SEA 注入。 如果运行的 Node.js 版本较旧(或与嵌入式运行时版本不匹配——SEA blob 在次要版本之间不兼容),pnpm 会自动下载匹配的构建器。
  • 从 Linux 交叉编译 macOS 目标需要 ldid$PATH 上。 Windows 主机无法临时签署 macOS 输出;应在 macOS 或 Linux 上构建 macOS 目标。

支持的目标

目标使用以下格式:<os>-<arch>[-<libc>]

  • linux-x64, linux-x64-musl, linux-arm64, linux-arm64-musl
  • darwin-x64, darwin-arm64
  • win32-x64, win32-arm64

-musl 后缀仅对 linux 目标有效。 <os> 段与 process.platform 值匹配,因此该标志与 pnpm 的 --os 标志以及 pnpm-workspace.yaml 中的 supportedArchitectures.os 一致。

已知限制

darwin-x64 二进制文件在 Intel Mac 上崩溃

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(或反之亦然)没有任何区别。 重新签名会在已损坏的字节上生成有效的签名。

跟踪:

如果你需要发布一个在 Intel Mac 上运行的 CLI,请使用非 SEA 工具,例如 [@yao-pkg/pkg](https://github.com/yao-pkg/pkg)构建 darwin-x64 工件(该工具会将内容附加到二进制文件的尾部,而不是修改 Mach-O 部分)。 请注意,Rosetta 并非 逃生舱——它只能将 x64 转换为 arm64(适用于运行 Intel 二进制文件的 Apple Silicon Mac),反之则不行,因此 Intel Mac 无法运行 darwin-arm64 构建。

示例

同时构建 Linux 和 Windows 版本:

pnpm pack-app --entry dist/index.cjs --target linux-x64 --target win32-x64

嵌入特定版本的 Node.js:

pnpm pack-app --entry dist/index.cjs --target linux-x64-musl --runtime node@25.5.0

配置项

--entry <path>

要嵌入到可执行文件中的 CJS 入口文件的路径。 除非在 package.json 中设置了 pnpm.app.entry,否则需要此设置。 也可以接受一个纯位置参数(例如 pnpm pack-app dist/index.cjs)。

--target, -t <triplet>

要构建的目标。 可以多次指定。 有关可接受的值,请参阅支持的目标。 除非设置了 pnpm.app.targets,否则是必需的。 在 CLI 中传递 --target 参数时,它会完全替换已配置的列表,以便你可以在调用时缩小构建范围。

--runtime <spec>

要嵌入到可执行文件输出中的运行时,按照 <name>@<version> 规范(例如 node@25node@25.5.0)。 目前仅支持 node<name>@ 前缀为未来的运行时(bundeno)留出了空间。 版本必须 >= v25.5(支持 --build-sea 的最低版本)。 默认为正在运行的 Node.js 版本。

--output-dir, -o <dir>

构建的可执行文件的输出目录。 默认为 dist-app

--output-name <name>

输出可执行文件的名称(不带扩展名)。 默认使用 package.json 中的非作用域 name(例如,my-cli 代表 @acme/my-cli)。

配置

每个标志的默认值都可以在 package.jsonpnpm.app 中设置。 CLI 标志会覆盖配置:

package.json
{
"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)。