fix(build): build-path conformance — stable generated-entry materialization, sealed self-contained invariant, ID-keyed inspection, typed Rslib composition - #79
Conversation
… rejection, ID-keyed inspection, typed Rslib composition Conformance fixes from the Rspack/Rslib/Rsbuild audit (build-path cluster): materialize generated wrapper/registry modules as real files under the reserved .agent-bundle-virtual/ directory instead of the experimental VirtualModulesPlugin and its real-file overlay of the module anchor; reject tools-hatch externals that name reserved specifiers and scan emitted bundles for residual reserved imports; key inspection assertions on the documented lib.id instead of array order; compose per-entry configs with mergeRslibConfig and engine-typed invariant hooks (no as-never casts); exact-match ($) alias keys; drop the unused direct @rspack/core dependency; correct bundleless-dts wording; close leaked build results in tests and retire the rsbuildConfig alias.
…r protection, launch-time-only .env layer
Guard function-form externals at build time (recording violations so the generic bundler failure surfaces as the actionable diagnostic), reject consumer aliases that would shadow reserved specifiers ahead of the framework's exact-match keys, treat object externals with value false as opt-outs, keep materialized-module cleanup running even when close() rejects, and realpath the pnpm symlink before resolving @rspack/core in the packed declaration test. Adds regression coverage: remapped function externals, alias shadowing, benign non-reserved externals, and a residual-import scan probe.
…n dev-invocation generation waits Replace the text-pattern residual-import scan with an es-module-lexer parse of each emitted bundle (the emitted format is ESM by contract), so string literals or comments mentioning a reserved specifier can no longer fail a legitimate build; a bundle that does not parse as ESM fails closed. Also give the three default-budget "generation two" waits in the RSC dev-invocation suite the same 15s budget every sibling generation wait in that file already uses.
🦋 Changeset detectedLatest commit: a59bf03 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
commit: |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: abb57676c7
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…erved specifier The self-containment scan parses each emitted bundle as an ES module rather than grepping it, so a reserved specifier that only appears as a string literal is not a violation. Nothing exercised that distinction.
A consumer tools.rsbuild output.cleanDistPath override would delete the build's own materialized generated sources and any sibling entry already emitted into the shared staged root. The final merge fragment now pins cleanDistPath off on both the Rslib executable path and the MCP Apps path (the asyncChunks precedent: reserved knobs are pinned, not honored), and the resolved-config assertions verify the pin. Adds a regression test proving a hatch that asks to clean the root still gets live outputs. Co-authored-by: ScriptedAlchemy <zackary.l.jackson@gmail.com>
… at dedicated virtual paths (#80) * refactor(build): serve generated entries through VirtualModulesPlugin at dedicated virtual paths Architecture reversal of the generated-source transport from PR #79's on-disk materialization, preserving all its conformance invariants: generated wrappers and registry modules keep their deterministic paths under the reserved .agent-bundle-virtual/ namespace but are now guaranteed-nonexistent module ids served from memory. Rslib validates source.entry existence on the real filesystem, so the profile keys each entry on the authored program and the invariant hook redirects the lowered Rspack entry to the virtual wrapper path — no real path is ever shadowed (the old entryAnchor overlay stays dead). A narrow feature check turns an upstream plugin removal into an actionable diagnostic. * test(build): prove invariant-hook ordering and fail-closed inspection; truthful changeset headline Sol review should-fixes: a hostile tools hatch that strips plugins and repoints the entry is overridden by the framework hook (merged last); resolved environments missing the virtual-module plugin or the wrapper entry fail closed before the build; the conformance changeset headline no longer claims fully documented surfaces now that the virtual-module transport deliberately rides one experimental plugin behind a feature check. * refactor(build): simplify virtual-entry composition with shared narrowing Reuse the core isRecord guard for entry-record and descriptor narrowing (dropping two casts and the self-recursive entryImportsOf trick), inline the one-shot plannedVirtualModules helper at its composition site, hoist the per-entry registry-module computation in assertExecutableConfig, and drop the patch changeset that described replacing disk materialization no release ever shipped (the amended minor changeset already documents the virtual transport).
Summary
Implements the build-path cluster of the 2026-08-31 Rspack/Rslib/Rsbuild API conformance audit (four read-only reports; the dev-server cluster is handled separately). The generated-executable build path now runs entirely on documented, stable bundler surfaces, and the self-contained-artifact invariant is sealed against hostile
toolshatches.What changed
buildWithRslibwrites generated wrapper entries and registry modules as real files under the already-reserved.agent-bundle-virtual/directory for the duration of one build, then removes them before artifact listing/publication. This replacesrspack.experiments.VirtualModulesPlugin(experimental tier: "Rspack may change the APIs of experimental features in minor releases") and the undocumented real-file-overlay that served virtual content at the framework module's own path (entryAnchor). One path, no plugin fallback. Emitted bundles were verified byte-identical across output roots; the only content delta vs. the old path is one scope-hoisting identifier now derived from the stable generated-entry name instead of the framework's install-dependent bundle filename.externals(string/RegExp/object) naming reserved specifiers (agent-bundle/mcp-entry,agent-bundle/mcp-apps, generated registry names), and a post-build scan of every emitted bundle fails closed on residual reserved imports (catches function-form externals). Three regression tests cover the benign path, a hostile object hatch, and a hostile function hatch.assertExecutableConfigkeys on the documentedorigin.environmentConfigs[lib.id]and matches Rspack configs by theirname(which Rslib derives fromlib.id; verified empirically) instead of assuming positional correspondence ofbundlerConfigs.mergeRslibConfig(merged byid); invariant hooks are typed against each executing engine's ownRspack.Configurationand return the config; all fouras nevercasts are gone, leaving one documented type seam between the public hatch types (@rsbuild/core2.2.x) and Rslib's nested engine (2.1.x). The dual-engine reality is documented onAgentBundleToolsConfigand indocs/entry-conventions.md, steering hatch authors to the{ rspack }utils argument instead of importing@rspack/core.@rspack/coredependency dropped per Rslib guidance (the packed declaration test now resolves@rspack/corethrough@rsbuild/core); exact-match ($) alias keys; bundleless-dts wording incore/types.ts+package-build.ts; themcp rundocs no longer claim programmatic builds load.env(they load none); two leaked Rsbuildbuild()results closed in tests;createRsbuild({ rsbuildConfig })modernized to{ config }.Audit findings → disposition
experiments.VirtualModulesPluginin the published build pathentryAnchor@rspack/coredependency contradicts Rslib guidance@rsbuild/coreas nevercomposition, version-split type contractmergeRslibConfig, engine-typed hooks, one documented seam, docsagent-bundle/mcp-entryalias$keystools.rspackmutators returnvoidreturn configexternalsbypasses the self-contained invariantbundlerConfigs[index]assumptionbuild()results in testsmcp run.envsetcreateRsbuild({ rsbuildConfig })alias{ config }provenance.tsStats withoutall: false(perf)Audit provenance: four read-only conformance reports produced against
main@12526a8b(Rspack, Rslib, Rsbuild, dev-server), resolved versions@rspack/core2.2.1 /@rslib/core0.23.2 (nested Rsbuild 2.1.13) /@rsbuild/core2.2.1.Gate results
Three full gate runs (initial implementation, post-review fixes, final). Final run on
abb57676:pnpm buildpnpm typecheck(3 projects)pnpm lint(661 files, 86 rules)pnpm test:unitdev-invocation.integrationpnpm test:packed(incl. scaffolder e2e + public-api-packed)audiobook-curator check,mcp-app checkByte-identity probe: the same fixture built into two output roots emits identical bundles before and after the change;
.agent-bundle-virtual/never survives a build.Independent review (pre-merge)
An independent model review of the full diff raised 3 blockers and 3 should-fixes; all were verified and fixed in follow-up commits, and the re-review confirmed no blockers remain:
@rsbuild/corebeforecreateRequire(the old form worked only under rstest's resolution hooks, not plain Node).$-exact, or path prefix) are now rejected, with the framework's own keys exempted.false(explicit opt-out) no longer count as violations; cleanup now survives a rejectingclose(); the residual scan is syntax-aware (es-module-lexerparse of the emitted ESM, shebang-stripped) so strings/comments mentioning a reserved specifier cannot fail a legitimate build.Test plan
.agent-bundle-virtual/never survives a build