Skip to content

feat(sea): use node --build-sea when the base Node supports it (>= 25.5)#280

Open
cstrahan wants to merge 2 commits into
yao-pkg:mainfrom
cstrahan:cstrahan/sea-build-sea
Open

feat(sea): use node --build-sea when the base Node supports it (>= 25.5)#280
cstrahan wants to merge 2 commits into
yao-pkg:mainfrom
cstrahan:cstrahan/sea-build-sea

Conversation

@cstrahan

Copy link
Copy Markdown

(This builds on top of #279)

Node v25.5.0 added an in-core node --build-sea <config> that generates the SEA blob and injects it into a base binary (config executable -> output) in one step, using Node's bundled, current LIEF. That avoids the external postject, whose 3-year-old vendored LIEF (0.13.0) corrupts the dynamic symbol table of PIE (ET_DYN) binaries (see nodejs/postject#101 and lief-project/LIEF#1097) -- which silently breaks native (raw-V8) addons in the resulting SEA (undefined symbol: _ZN2v8...). The corruption was fixed upstream in LIEF 0.16.7; Node >= 25.5 ships a fixed LIEF.

When the host-runnable generator Node reports >= 25.5, both SEA paths (enhanced and simple) inject via --build-sea instead of generating a prep blob and shelling out to postject. assertSingleTargetMajor guarantees every target shares the generator's major, so the generated blob is compatible across targets -- the multi-target concern that previously ruled --build-sea out does not actually apply (it reads the base from the config executable field, not the running binary). Older generators keep the postject path unchanged.

Combined with --sea-node-path / --sea-use-local-node, this lets a custom Node build (e.g. one linked against an older glibc) produce SEAs with working native addons and no external injector. ARCHITECTURE.md is updated to reflect the new path and to correct the prior claim that --build-sea could not serve pkg.

cstrahan and others added 2 commits June 10, 2026 21:23
SEA mode previously always downloaded a base Node binary from nodejs.org, so
the resulting executable was pinned to an official build. That makes it
impossible to ship a SEA on top of a custom runtime -- e.g. a Node built
against an older glibc (to run on EL7 / older distros), or one configured
differently.

The internal SeaOptions / SeaEnhancedOptions already accepted `nodePath` /
`useLocalNode`; this just exposes them:

  --sea-node-path <path>   embed a specific Node binary as the base
  --sea-use-local-node     embed the Node running pkg (process.execPath)

Both are also settable in the pkg config (seaNodePath / seaUseLocalNode) and
via the programmatic exec() API. The two are mutually exclusive. The embedded
binary's major version must match the target's (the existing version-skew
checks still apply).

This is also a prerequisite for adopting `node --build-sea` (Node >= 25.5):
that flag is only meaningful once the base Node is no longer a fixed download.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…25.5)

Node v25.5.0 added an in-core `node --build-sea <config>` that generates the
SEA blob and injects it into a base binary (config `executable` -> `output`) in
one step, using Node's *bundled, current* LIEF. That avoids the external
`postject`, whose 3-year-old vendored LIEF (0.13.0) corrupts the dynamic symbol
table of PIE (ET_DYN) binaries -- which silently breaks native (raw-V8) addons
in the resulting SEA (`undefined symbol: _ZN2v8...`). The corruption was fixed
upstream in LIEF 0.16.7; Node >= 25.5 ships a fixed LIEF.

When the host-runnable generator Node reports >= 25.5, both SEA paths (enhanced
and simple) inject via `--build-sea` instead of generating a prep blob and
shelling out to postject. `assertSingleTargetMajor` guarantees every target
shares the generator's major, so the generated blob is compatible across
targets -- the multi-target concern that previously ruled `--build-sea` out
does not actually apply (it reads the base from the config `executable` field,
not the running binary). Older generators keep the postject path unchanged.

Combined with --sea-node-path / --sea-use-local-node, this lets a custom Node
build (e.g. one linked against an older glibc) produce SEAs with working native
addons and no external injector. ARCHITECTURE.md is updated to reflect the new
path and to correct the prior claim that `--build-sea` could not serve pkg.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@cstrahan cstrahan changed the title Cstrahan/sea build sea feat(sea): use node --build-sea when the base Node supports it (>= 25.5) Jun 11, 2026
Comment thread lib/sea.ts
Comment on lines -726 to -729
* Uses --experimental-sea-config (not --build-sea): --build-sea produces
* a finished executable and bypasses the prep-blob + postject flow that
* pkg relies on for multi-target support and for injecting custom
* bootstraps into downloaded node binaries.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the reason I didn't do that is this one

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, this might boil down to my own lack of familiarity with the full functionality of pkg, resulting in my misinterpretation of the reasoning outlined here - I'm guessing around either the implications of multi-target (we only need one target, so I don't have experience with that yet), or the "injecting custom bootstraps" aspect.

I'll familiarize myself the Targets docs and the boostrap logic and circle back. If --biuld-sea is fundamentally unfit for this project, I'll see if I can add a little extra info here so others don't feel tempted to go down the same path (or maybe a patch sent upstream to Node's --build-sea could make it fit for purpose?).

@codecov

codecov Bot commented Jun 11, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 71.82320% with 51 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.14%. Comparing base (546bbf0) to head (7e5634f).

Files with missing lines Patch % Lines
lib/sea.ts 68.50% 40 Missing ⚠️
lib/index.ts 57.14% 6 Missing ⚠️
lib/help.ts 0.00% 5 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #280      +/-   ##
==========================================
- Coverage   86.36%   86.14%   -0.22%     
==========================================
  Files          22       22              
  Lines        7297     7442     +145     
  Branches     1047     1051       +4     
==========================================
+ Hits         6302     6411     +109     
- Misses        988     1024      +36     
  Partials        7        7              
Files with missing lines Coverage Δ
lib/config.ts 96.24% <100.00%> (+0.08%) ⬆️
lib/types.ts 100.00% <100.00%> (ø)
lib/help.ts 3.22% <0.00%> (-0.17%) ⬇️
lib/index.ts 82.31% <57.14%> (-1.07%) ⬇️
lib/sea.ts 73.06% <68.50%> (-0.22%) ⬇️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants