bundle: JS support via static scan, with conditions-aware resolution#41
Merged
Conversation
…om review rounds
Squashed three earlier commits (initial scan, bundle integration with conditions-aware
resolution and lockfile pairing, scan-CLI removal) plus a fourth fix-pass landing the
five correctness bugs surfaced by the second-round review. History squashed for a single
clean rebase onto main's --mock work.
Highlights:
- `stasis bundle src/entry.js [src/entry2.js ...]` produces a brotli-compressed Bundle
loadable interchangeably with one produced by `stasis run --bundle=add`. Multiple
entries are all marked as entry points; bundle pairs with `--lockfile=path` to also
emit a matching lockfile that attests every byte in the bundle.
- Resolution: scan passes the full Node-22 condition set
`['node', import|require, 'module-sync', 'node-addons']` to
`createRequire().resolve(spec, { conditions: Set })`. Earlier shortened sets caused
packages with `module-sync` exports to silently resolve to a different file under
the bundle than under plain Node -- the worst kind of bug: green tests, wrong
production behavior. tests/fixtures/scan-module-sync pins this regression.
- Parser: oxc-parser as an optional peer dep, loaded synchronously via createRequire.
Stasis keeps its zero-runtime-deps posture; consumers who never call the bundle
command don't need to install it.
- State.getImport throws a properly-coded ERR_MODULE_NOT_FOUND on missing edges
(previously asserted, producing ERR_ASSERTION that re-threw out of the common
`try{import(x)}catch(e){if(e.code!=='ERR_MODULE_NOT_FOUND')throw e}` guard).
- Config.loadConfig now treats explicit constructor options like env vars: an explicit
`--scope=full` is no longer silently overridden by an on-disk
`{"scope":"node_modules"}` -- the conflict surfaces as the existing
"Flags/env can not override stasis.config.json" error.
- buildJsBundle uses `bundle: 'replace'` to skip merging any pre-existing
stasis.code.br from disk -- building twice in the same directory used to leak
the first build's formats/imports entries into the second's output.
- Static bundles store edges under the wildcard '*' condition key; state.getImport
falls back to '*' when the runtime-condition lookup misses, so runtime-built
bundles still match on the specific key (no behavior change there) and static
bundles work regardless of which conditions Node passes at load time.
Tests cover: 22-cell static-bundle x lock matrix on the popular-npm-modules fixture
(mirrors the runtime matrix), all five regression cases (module-sync, stale inheritance,
config conflict, ERR_MODULE_NOT_FOUND, ESM-only / dual-package), two-directional graph
agreement, and the new scan-esm-conditions and scan-module-sync fixtures. 387 of 397
tests pass; the 10 failures are pre-existing on `main` (`lock=* bundle=add|replace mock`
cells assert byte-equality between mock-on and mock-off bundles).
3 tasks
exo-nikita
added a commit
that referenced
this pull request
Jun 8, 2026
PR #41 was rebased on commit 39d7dd5 and squash-merged after ed41d6f (the --full→--dependencies flip) landed. My tests came in using --full and no-flag patterns that no longer match the CLI: --full was renamed to the implicit default, --dependencies is the node_modules opt-in, and the nm-scoped popular-npm-modules fixture needs --dependencies to avoid env-vs-file scope conflict. Fixes the 26 test failures introduced by the merge race; 400 of 410 pass after this. The remaining 10 are the pre-existing lock×bundle×mock byte-equality failures from PR #40. https://claude.ai/code/session_01DqXmS7gzzwuY4AF4wnViob
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
stasis bundle src/entry.js [src/entry2.js ...]produces a brotli-compressed Bundle interchangeable withstasis run --bundle=add, but built without executing any user code. Multiple entries are all marked as entry points; pair with--lockfile=pathto also emit a matching lockfile that attests every byte in the bundle.['node', import|require, 'module-sync', 'node-addons']tocreateRequire().resolve(spec, { conditions: Set }). Earlier shortened sets caused packages withmodule-syncexports to silently resolve to a different file under the bundle than under plain Node.oxc-parseras an optional peer dep, loaded synchronously viacreateRequire. Stasis keeps its zero-runtime-deps posture; consumers who never call the bundle command don't need it.Five correctness fixes from two rounds of review
module-syncconditionsnode)formats/importsbundle: 'replace'skips on-disk merge--scope=fullsilently ignored whenstasis.config.jsondisagreedassert.ok(file)→ERR_ASSERTION, re-threw out of common guardsERR_MODULE_NOT_FOUND-coded error (matches plain Node's ESM resolver)ERR_PACKAGE_PATH_NOT_EXPORTEDEach fix has a regression test; reproducers exist for all five.
Test plan
lock x bundle x mock matrixcellsbundle=add|replace mock) are pre-existing onmain— verified by checking out cleanorigin/mainand running the same suite. They assert byte-equality between mock-on and mock-off bundles in a separate--mockfeature and are unrelated to this branch's work.node --run lint).Notes for follow-up (non-blocking)
doc/don't yet documentstasis bundle src/entry.js— biggest user-facing gap.src/cmd/bundle.jsreferences the pre-fixassertbehavior; should sayERR_MODULE_NOT_FOUND.bin/stasis.js's--scopevalidation duplicatesConfig's; could importVALID_SCOPE.src/scan.js's per-filefindPackageJSONwalk isn't memoized; O(n × depth) on large graphs.https://claude.ai/code/session_01DqXmS7gzzwuY4AF4wnViob
Generated by Claude Code