Files
2026-05-27 23:29:53 +01:00

4.4 KiB

Fix storage-uri-plugin-bundle so the ./node-fetch subpath emits its .js

Package: @suaveplan/storage-uri-plugin-bundle Status: PROPOSED Type: bug-fix

Why

The shipped storage-uri-plugin-bundle package declares a ./node-fetch subpath export in its package.json:

"./node-fetch": {
  "import": {
    "types": "./dist/node-fetch/node-fetch.d.ts",
    "default": "./dist/node-fetch/node-fetch.js"
  }
}

…and the existing spec (openspec/specs/storage-uri-plugin-bundle/spec.md, requirement "Server-tier ./node-fetch subpath for file:// roots on Node") requires this entry point to exist and behave correctly. The build, however, only emits dist/node-fetch/node-fetch.d.ts — the .js is never written. Cause:

  • vite.config.ts declares lib: { entry: "src/index.ts" } (single entry).
  • src/index.ts does NOT re-export nodeFileFetch (correctly — the package contract is universal-tier at the root barrel; the node-fetch surface is server-tier and lives behind the subpath).
  • Rollup, walking only from src/index.ts, never sees src/node-fetch/node-fetch.ts and tree-shakes it. vite-plugin-dts walks the source tree independently, so the .d.ts survives — masking the defect from type-only consumers.

Node-tier consumers that import @suaveplan/storage-uri-plugin-bundle/node-fetch get MODULE_NOT_FOUND at runtime. Both Stage-3 audio-ir room packages (club, pool) hit this and worked around it by inlining a local nodeFileFetch adapter. The fix is mechanical: add src/node-fetch/node-fetch.ts as a second rollup entry so it's emitted as a top-level chunk.

What changes

  • MODIFIED: vite.config.ts — replace lib: { entry: "src/index.ts" } with a multi-entry build that produces both dist/index.js and dist/node-fetch/node-fetch.js. Use lib: { entry: { index: "src/index.ts", "node-fetch/node-fetch": "src/node-fetch/node-fetch.ts" }, formats: ["es"] }. preserveModules: true is already set, so per-file chunking continues to work.
  • ADDED requirement: a build-time invariant in the package's capability spec. The runtime contract requirement already exists; this one explicitly mandates that the build artifact ships, so the same regression cannot recur silently.

Out of scope

  • Any runtime behavior change. nodeFileFetch's implementation under src/node-fetch/node-fetch.ts is unchanged.
  • Any change to the root barrel (src/index.ts) — it stays universal-tier with no node:* imports.
  • Audio-IR consumer cleanups. Removing the inline shims in audio-ir-room-plugin-club and audio-ir-room-plugin-pool is a separate, follow-up change once this fix lands. Not bundled here per AGENTS §193 (one change per package).

Source mapping

In-repo fix. No upstream reference. The capability spec already documents the desired behavior; this change adds an additional ADDED requirement covering the build artifact.

Requirements with no source line

Requirement Justification
Build-time emission invariant Caught only by Stage-3 consumers; not previously stated as a spec requirement

Verification approach

  • tests/build/node-fetch-emit.test.ts — after bunx turbo build --filter=@suaveplan/storage-uri-plugin-bundle, the test reads <package-root>/dist/node-fetch/node-fetch.js via node:fs.statSync, asserts the file exists, is non-empty, and is a .js file (not a .d.ts).
  • tests/build/node-fetch-runtime-import.test.ts — dynamically import()s <package-root>/dist/node-fetch/node-fetch.js directly and asserts the export nodeFileFetch is a function — proves the emitted file is actually runnable, not just present.
  • Re-runs of all existing node-fetch source-level tests (src/node-fetch/node-fetch.test.ts) — must remain green; this change does not modify the runtime.

Impact

Affected dependents

Run bunx turbo ls --filter=...@suaveplan/storage-uri-plugin-bundle. Today:

  • @suaveplan/audio-ir-room-plugin-club — [x] no action needed (reason: inline shim under integration tests does not depend on the subpath emit; the follow-up cleanup change will adopt the shipped subpath)
  • @suaveplan/audio-ir-room-plugin-pool — [x] no action needed (same reason)
  • @suaveplan/audio-ir-loader — [x] no action needed (consumer doesn't import the subpath)

All three remain green at 100/100/100/100 after this fix. The shipped subpath becomes usable for new consumers immediately.