Skip to content

sea: mount bundled assets as a virtual file system - #65675

Open
mcollina wants to merge 1 commit into
nodejs:mainfrom
mcollina:vfs-sea-integration
Open

sea: mount bundled assets as a virtual file system#65675
mcollina wants to merge 1 commit into
nodejs:mainfrom
mcollina:vfs-sea-integration

Conversation

@mcollina

@mcollina mcollina commented Aug 31, 2026

Copy link
Copy Markdown
Member

This adds the SEA integration for the virtual file system landed in #63653: setting "useVfs": true in the SEA configuration mounts the bundled assets as a read-only VFS and runs the injected main script from inside the mount.

Since a VFS never shadows the real file system and its mount point is reserved and chosen at runtime, there is no fixed /sea path. Instead, the main script is placed at the root of the mount and executed from there, so:

  • __filename and __dirname point inside the virtual file system;
  • assets are reached through __dirname-relative paths with regular node:fs APIs;
  • relative require() calls and node_modules lookups resolve against the bundled assets, confined to the mount by the module loader integration.

Implementation notes:

  • The main script is not duplicated into the assets at build time; its source already lives in the SEA blob and is injected into the provider at runtime.
  • SEAProvider is read-only and lazy: asset content stays in the executable's SEA blob and is copied into JS memory only when a file is opened, with sizes cached for stat.
  • The implicit SEA mount does not emit the VirtualFileSystem experimental warning; the SEA warning already covers it.
  • ESM entry points ("mainFormat": "module") are supported: the ESM main is loaded from inside the mount through the ESM loader, so import.meta.url/filename/dirname reflect the mount and static imports, dynamic import(), and bare specifier lookups resolve against the bundled assets.
  • "useVfs" is rejected together with "useSnapshot" and "useCodeCache".

This PR was prepared with the help of AI. I've reviewed all changes myself.

@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

Review requested:

  • @nodejs/single-executable

@nodejs-github-bot nodejs-github-bot added lib / src Issues and PRs involving general changes in the lib/ or src/ directories. needs-ci PRs that need a full CI run. labels Aug 31, 2026
@mcollina
mcollina force-pushed the vfs-sea-integration branch 3 times, most recently from 148a0d7 to 4991e64 Compare August 31, 2026 09:14
@mcollina
mcollina requested review from Qard and joyeecheung and removed request for Qard August 31, 2026 10:46
pipobscure added a commit to pipobscure/bundles that referenced this pull request Sep 1, 2026
The tool was one directory of JavaScript that doubled as a library by
accident. This makes the library the point: `src/` is TypeScript compiled
to ESM in `dist/`, every capability has a declared entry point, and the
CLI is a thin wrapper over the same functions an embedder gets.

Four things are now separately importable, which is the shape the rest of
the design needs:

  .           create / sign / verify / inspect / run, from code (api.ts)
  ./record    the -r preload that writes down what a run reads
  ./register  the -r preload that mounts only what is signed
  ./sea       build and boot a self-validating executable

plus ./provider, ./recorder, ./cli, ./manifest, ./archive, ./files,
./skill, ./sigstore and ./oidc for the layers underneath. The package root
deliberately does not re-export the two providers: importing either needs
node:vfs, and creating or verifying an archive does not, so a plain
`import '@pipobscure/bundle'` must not drag `--experimental-vfs` in.
There is a test that holds that line.

The sources are erasable-syntax-only, so `node src/main.ts` runs them
directly under node's type stripping. That is what lets the tests import
the sources rather than the build, and a preload be `-r ./src/register.ts`.
@types/node carries neither the node:zlib ZIP API nor node:vfs's provider
registry, so `src/types/` declares them; the shapes were checked against
the running runtime rather than transcribed from documentation.

sea.js is replaced by src/sea.ts. The old bootstrap was a copy of
manifest.js inlined into a CommonJS file, which had drifted: its signature
marker regex was still the two-field form, so it read every
sigstore-signed container as unsigned. The replacement mounts this package
out of the SEA blob with node:vfs and requires the real library from
there — the userland form of nodejs/node#65675, with the difference that
the mount running the application is the signed archive appended to the
file. Nothing is duplicated, and the verifier a container runs is the one
the test suite tests. `bundle sea` builds one.

Three bugs the type checker and the new tests turned up, all real:

  * --identity and BUNDLE_IDENTITY were only consulted on the sigstore
    path, so an archive signed against an ordinary CA — which carries no
    identity claim at all — satisfied a policy demanding one. A machine
    configured to run only releases from a workflow would have mounted
    anything key-signed. It now reports valid-untrusted.

  * Arguments beginning with `--` never reached a mounted application;
    node claimed them as its own flags. mountArgv() now ends with `--`.

  * Verifying from a Buffer crashed, and sigstore signing was written
    against @sigstore/bundle v2 while v3 is installed. Both fixed, and the
    Buffer path now has a test.

131 tests over the format, both providers, the API, the CLI, the SEA and
the published package's own shape, replacing three JavaScript suites.
@mcollina
mcollina marked this pull request as ready for review September 1, 2026 15:04
@codecov

codecov Bot commented Sep 1, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 99.40476% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 90.06%. Comparing base (4d9cb71) to head (70c5a3f).
⚠️ Report is 30 commits behind head on main.

Files with missing lines Patch % Lines
src/node_sea.cc 93.47% 0 Missing and 3 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #65675      +/-   ##
==========================================
+ Coverage   90.05%   90.06%   +0.01%     
==========================================
  Files         754      756       +2     
  Lines      256308   257020     +712     
  Branches    48464    48579     +115     
==========================================
+ Hits       230817   231494     +677     
- Misses      16593    16634      +41     
+ Partials     8898     8892       -6     
Files with missing lines Coverage Δ
lib/internal/main/embedding.js 91.89% <100.00%> (+2.92%) ⬆️
lib/internal/vfs/providers/sea.js 100.00% <100.00%> (ø)
lib/internal/vfs/sea.js 100.00% <100.00%> (ø)
src/node_sea.h 100.00% <ø> (ø)
src/node_sea.cc 89.77% <93.47%> (+0.27%) ⬆️

... and 48 files with indirect coverage changes

🚀 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.

Comment thread doc/api/single-executable-applications.md Outdated
Support "useVfs": true in the SEA configuration: mount the bundled
assets as a read-only VFS and run the CommonJS or ESM main script from
inside the mount.

Signed-off-by: Matteo Collina <hello@matteocollina.com>
@mcollina
mcollina force-pushed the vfs-sea-integration branch from de3de10 to 70c5a3f Compare September 2, 2026 06:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

lib / src Issues and PRs involving general changes in the lib/ or src/ directories. needs-ci PRs that need a full CI run.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants