Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions docs-site/guide/custom-node.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,17 @@ PKG_NODE_PATH=/path/to/node pkg app.js
## Caveats

- The binary must be a **compatible** Node.js version — `pkg` still injects its bootstrap prelude and payload, and depends on specific symbol offsets. Use a version supported by `pkg-fetch` unless you know what you're doing.
- `PKG_NODE_PATH` affects a **single target**. Multi-target builds still fetch the other platforms from `pkg-fetch` unless you set it per-run.
- SEA mode uses stock Node.js by its own mechanism and does **not** honour `PKG_NODE_PATH` in the same way. For SEA, use `process.execPath` of the Node.js you want directly.
- `PKG_NODE_PATH` affects a **single target**. In standard mode, multi-target builds still fetch the other platforms from `pkg-fetch` unless you set it per-run.

### SEA mode caveats

SEA mode honours `PKG_NODE_PATH`, and also has its own overrides: the `--sea-node-path` CLI flag or the `seaNodePath` pkg-config key (both take precedence over `PKG_NODE_PATH`). To embed the Node.js you're currently running, pass `PKG_NODE_PATH="$(command -v node)"` (or `--sea-node-path "$(command -v node)"`).

Because SEA injects the payload into the supplied binary directly (there is no per-platform fetch fallback), supplying a custom base binary imposes strict limits to avoid silently baking a mismatched binary into the output:

- **Single target only** — the custom binary applies to exactly one platform/arch, so pkg rejects a run whose targets span more than one (including `linux` vs `alpine` vs `linuxstatic`, which a single binary can't be all of at once).
- **No cross-compilation** — pkg validates the binary by **running it** and checking its `process.platform`, `process.arch`, and major version against the requested target. So the custom base binary must be **natively runnable on your build host** (e.g. you can't supply a Linux binary while building on macOS). Standard, non-SEA builds don't have this restriction and still cross-compile via `pkg-fetch`.
- **Flavor matching is yours** — `process.platform` reports `linux` for glibc, musl (`alpine`), and static (`linuxstatic`) alike, so matching the specific flavor of your binary to the target remains your responsibility.

## See also

Expand Down
14 changes: 14 additions & 0 deletions lib/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,18 @@ const FLAG_SPECS: readonly FlagSpec[] = [
default: false,
},
{ cli: 'sea', cfg: 'sea', resolved: 'sea', kind: 'bool', default: false },
{
// SEA mode: use a specific Node binary as the base for the executable
// instead of downloading one from nodejs.org. Lets you embed a custom
// build (e.g. one that runs on older glibc, or a differently-configured
// runtime). Equivalent to the PKG_NODE_PATH env var (which this overrides);
// the binary's major version must match the target's, and it applies to a
// single target. Also settable as the `seaNodePath` pkg-config key.
cli: 'sea-node-path',
cfg: 'seaNodePath',
resolved: 'seaNodePath',
kind: 'string',
},
{
cli: 'compress',
cfg: 'compress',
Expand Down Expand Up @@ -485,6 +497,8 @@ export interface ResolvedFlags {
fallbackToSource: boolean;
public: boolean;
sea: boolean;
/** SEA mode: path to a base Node binary to embed (overrides the download / PKG_NODE_PATH). */
seaNodePath: string | undefined;
publicPackages: string[] | undefined;
noDictionary: string[] | undefined;
bakeOptions: string[] | undefined;
Expand Down
5 changes: 3 additions & 2 deletions lib/help.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,11 @@ export default function help() {
--signature enable macOS binary signing (default; use to override signature:false in config)
--no-signature skip macOS binary signing [default: sign]
--sea (Experimental) compile given file using node's SEA feature. Requires node v20.0.0 or higher and only single file is supported
--sea-node-path SEA mode: path to a base Node binary to embed instead of downloading one (overrides PKG_NODE_PATH; must match the target's platform/arch/major; single target only)

All build-shaping flags above (compress, fallback-to-source, public, public-packages,
options, bytecode, native-build, no-dict, debug, signature, sea) can also be set in
the pkg config file (camelCase keys). CLI flags override config values.
options, bytecode, native-build, no-dict, debug, signature, sea, sea-node-path) can also
be set in the pkg config file (camelCase keys). CLI flags override config values.

${pc.dim('Examples:')}

Expand Down
7 changes: 7 additions & 0 deletions lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,11 @@ export async function exec(
}

if (flags.sea) {
// Base-node override: embed a custom Node binary instead of downloading one.
// From --sea-node-path / the seaNodePath config key, falling back to the
// PKG_NODE_PATH env var (resolved in lib/sea.ts). Lets you ship a SEA built
// on a custom runtime (e.g. one linked against an older glibc).
const seaBase = { nodePath: flags.seaNodePath };
if (inputJson || configJson) {
// Enhanced SEA mode — use walker pipeline.
// seaEnhanced validates the host Node version and minTargetMajor itself.
Expand All @@ -202,6 +207,7 @@ export async function exec(
params: { ...params, seaMode: true },
addition: isConfiguration(input) ? input : undefined,
doCompress: flags.compress,
...seaBase,
});
} else {
// Simple SEA mode — plain .js file without package.json.
Expand All @@ -216,6 +222,7 @@ export async function exec(
await sea(inputFin, {
targets,
signature: flags.signature,
...seaBase,
});
}
return;
Expand Down
Loading