* docs: lint prose with Vale Add Vale with an in-repo style, .vale/styles/pnpm, that turns the writing rules in AGENTS.md and the Google developer documentation style guide into lint rules. The stock Vale packages (Google, Microsoft, RedHat, write-good) were too noisy to block on, for example 1,300 passive-voice hits in the v12 docs alone, so the rules are written for this repo, following the GitLab model. Each one links the guideline it enforces. Levels: - error, fails CI: dashes, plain-word substitutions, condescending words, "please", "note that", hedges, puffery, optional plurals, exclamation marks, ellipses, curly quotes, generic link text, product spellings, sentence-case headings, repeated words, double spaces, implementation history in comments; in changesets, bold or italic emphasis, internal names and release-line names. - warning: future tense, sentences over 30 words, "instead of" and "so that" tails in changesets. - suggestion: passive voice. .vale.ini scopes the rules. Docs get every rule. Contributor Markdown and code comments keep dashes and headings as warnings until their roughly 8,100 em dashes are rewritten. Released changesets stay in .changeset/ and are not rewritten, so lint-prose.mjs lints only the changesets added since PROSE_BASE (the PR base in CI). The prose workflow installs a pinned, checksum-verified Vale release and runs `pnpm lint:prose`. The rest of this change rewrites the existing docs, READMEs, contributor Markdown, and code comments to pass the error-level rules. Only prose and comment text changed. Renamed headings keep their anchors. Spelling stays with cspell, which checks identifiers that Vale does not see. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs: keep prose lint from flattening meaning --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
115 lines
5.5 KiB
Markdown
115 lines
5.5 KiB
Markdown
---
|
|
id: pack-app
|
|
title: pnpm pack-app
|
|
---
|
|
|
|
Added in: v11.0.0
|
|
|
|
:::warning Experimental
|
|
|
|
`pnpm pack-app` is experimental. Its flags, `pnpm.app` configuration schema, and output layout may change in future releases.
|
|
|
|
:::
|
|
|
|
Pack a CommonJS entry file into a standalone executable for one or more target platforms, using the [Node.js Single Executable Applications](https://nodejs.org/api/single-executable-applications.html) API under the hood.
|
|
|
|
```sh
|
|
pnpm pack-app --entry <path> --target <triplet> [--target <triplet> ...]
|
|
```
|
|
|
|
Each target produces an executable under `<output-dir>/<target>/` (default `dist-app/<target>/`). On Windows targets the output is suffixed with `.exe`; macOS outputs are ad-hoc signed automatically (via `codesign` on macOS hosts or `ldid` on Linux hosts) because SEA injection invalidates the existing code signature.
|
|
|
|
## Requirements
|
|
|
|
* The host must run Node.js v25.5+ to perform the SEA injection. If the running Node.js is older (or does not match the embedded runtime version, since SEA blobs are not compatible across minor releases), pnpm downloads a matching builder automatically.
|
|
* Cross-compiling macOS targets from Linux requires [`ldid`](https://github.com/ProcursusTeam/ldid) on `$PATH`. Windows hosts cannot ad-hoc sign macOS outputs; build macOS targets on macOS or Linux.
|
|
|
|
## Supported targets
|
|
|
|
Targets use the format `<os>-<arch>[-<libc>]`:
|
|
|
|
* `linux-x64`, `linux-x64-musl`, `linux-arm64`, `linux-arm64-musl`
|
|
* `darwin-x64`, `darwin-arm64`
|
|
* `win32-x64`, `win32-arm64`
|
|
|
|
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` binaries crash on Intel Macs
|
|
|
|
`darwin-x64` outputs segfault at startup on Intel Macs because of an upstream Node.js bug in the `--build-sea` injection step. LIEF's Mach-O surgery for x64 leaves `LC_DYLD_CHAINED_FIXUPS` chain entries pointing at stale targets after the SEA segment is inserted; dyld then dereferences a raw chain-encoded value as a pointer and the binary crashes in `__cxx_global_var_init` before any user code runs. This is reproducible with the canonical `node --build-sea` + `codesign --sign -` flow with no pnpm involvement.
|
|
|
|
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](https://github.com/nodejs/node/issues/62893): minimal `node --build-sea` repro
|
|
* [nodejs/node#59553](https://github.com/nodejs/node/issues/59553): long-running SEA test failures on macOS x64 with the same root cause
|
|
* [nodejs/node#60250](https://github.com/nodejs/node/pull/60250): Node.js skipping the SEA tests on x64 macOS rather than fixing them
|
|
|
|
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`](https://github.com/yao-pkg/pkg) (which appends to the binary tail rather than mutating Mach-O sections). 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
|
|
|
|
Build for Linux and Windows at once:
|
|
|
|
```sh
|
|
pnpm pack-app --entry dist/index.cjs --target linux-x64 --target win32-x64
|
|
```
|
|
|
|
Embed a specific Node.js version:
|
|
|
|
```sh
|
|
pnpm pack-app --entry dist/index.cjs --target linux-x64-musl --runtime node@25.5.0
|
|
```
|
|
|
|
## Options
|
|
|
|
### --entry <path\>
|
|
|
|
Path to the CJS entry file to embed in the executable. Required unless [`pnpm.app.entry`](#configuration) is set in `package.json`. A bare positional argument is also accepted (e.g. `pnpm pack-app dist/index.cjs`).
|
|
|
|
### --target, -t <triplet\>
|
|
|
|
Target to build for. May be specified multiple times. See [Supported targets](#supported-targets) for the accepted values. Required unless [`pnpm.app.targets`](#configuration) is set. When passed on the CLI, `--target` entirely replaces the configured list so you can narrow the build at invocation time.
|
|
|
|
### --runtime <spec\>
|
|
|
|
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\>
|
|
|
|
Output directory for the built executables. Defaults to `dist-app`.
|
|
|
|
### --output-name <name\>
|
|
|
|
Name for the output executable (without extension). Defaults to the unscoped `name` from `package.json` (e.g. `my-cli` for `@acme/my-cli`).
|
|
|
|
## Configuration
|
|
|
|
Defaults for every flag can be set in `package.json` under `pnpm.app`. CLI flags override the config:
|
|
|
|
```json title="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"
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
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`).
|