Files
pnpm/pnpm11/docs/cli/pack-app.md
Zoltan KochanandClaude Opus 5.5 6f95957063 docs: lint prose with Vale (#17238)
* 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>
2026-10-22 21:48:09 +02:00

5.5 KiB

id, title
id title
pack-app 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 API under the hood.

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 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:

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). 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:

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

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>

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>

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>

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:

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