# 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`: ```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 `/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 `/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.