Skip to content

fix: derive the bundled manifest, and release 0.0.4 - #50

Merged
maxigimenez merged 1 commit into
mainfrom
fix/bundled-exports-drift
Sep 7, 2026
Merged

maxigimenez merged 1 commit into
mainfrom
fix/bundled-exports-drift

Conversation

@maxigimenez

Copy link
Copy Markdown
Owner

The bug

sentinel0 start from the published 0.0.3 dies on its first import:

Error [ERR_PACKAGE_PATH_NOT_EXPORTED]: Package subpath './github' is not defined
by "exports" in .../node_modules/@sentinel0/common/package.json
imported from .../@sentinel0/orchestrator/dist/orchestrator/src/runtime/services.js

prepare-package.mjs writes each bundled package a minimal manifest — the workspace one carries workspace:* ranges npm cannot resolve — and that manifest included a hand-written literal copy of exports:

exports: {
  '.':          { ... },
  './executor': { ... },
}

@sentinel0/common has since grown ./github, ./prompt-catalog, ./route-catalog and ./route-validation. The literal never followed. Inside the tarball there is no pnpm symlink back to packages/common, so that stale copy is the only exports Node consults.

Nothing caught it because every check looked past it. Tests and local runs resolve through the symlink against the real manifest, and verify-bundled-imports.mjs collapsed @sentinel0/common/github to @sentinel0/common, which is present.

The fix

  • bundledManifest() derives the manifest from the workspace package.json — keeping name, version, type, main, types, exports, bin, dropping dependencies and the dev-only fields. The drift is now unrepresentable: adding a subpath means editing packages/common/package.json and nothing else.
  • prepare-package.mjs also refuses to pack a manifest naming a file the build did not emit — the same failure one step later, as ERR_MODULE_NOT_FOUND.
  • verify-bundled-imports.mjs checks the subpath against the installed exports map, not just that the package directory exists.
  • test/bundled-manifest.test.ts (new, 7 cases) asserts the derivation carries exports verbatim, drops dependencies, names only built files, and sweeps packages/*/src so any @sentinel0/common/<subpath> the code imports must be declared.

Version bumped to 0.0.4 via pnpm version:set, and docs/releasing.md updated alongside.

Verification

Both guards were confirmed to fire against a reconstructed 0.0.3 exports map, not just to pass on the fix:

Check 0.0.3 map 0.0.4
bundled-manifest.test.ts fails, naming @sentinel0/common/github in orchestrator and cloud-api passes
verify-bundled-imports.mjs exit 1, naming the exact runtime/services.js from the report passes

The packed sentinel0-0.0.4.tgz was then npm installed into a clean directory, where the failing command now reaches its config check instead of module resolution:

$ ./node_modules/.bin/sentinel0 start --api-port 9371
No Hermes gateway configured. Run "sentinel0 init" first.

pnpm lint, pnpm build and all 533 tests pass.

🤖 Generated with Claude Code

https://claude.ai/code/session_01EkUWsM92gcbit4p6hEhuyV

`sentinel0 start` from the published 0.0.3 died on its first import with
ERR_PACKAGE_PATH_NOT_EXPORTED for `@sentinel0/common/github`.

`prepare-package.mjs` wrote each bundled package a minimal manifest — the
workspace one carries `workspace:*` ranges npm cannot resolve — and that
manifest included a hand-written literal copy of `exports` listing `.` and
`./executor`. `@sentinel0/common` has since grown `./github`,
`./prompt-catalog`, `./route-catalog` and `./route-validation`, and the copy
never followed. Inside the tarball there is no pnpm symlink back to
`packages/common`, so the stale copy is the only `exports` Node consults.

Nothing caught it because every check looked past it: tests and local runs
resolve through the symlink against the real manifest, and
`verify-bundled-imports.mjs` collapsed `@sentinel0/common/github` to
`@sentinel0/common`, which is present.

- `bundledManifest()` now derives the manifest from the workspace
  `package.json`, keeping name, version, type, main, types, exports and bin
  and dropping `dependencies` and the dev-only fields. The drift is now
  unrepresentable, and adding a subpath means editing one file.
- `prepare-package.mjs` also refuses to pack a manifest naming a file the
  build did not emit — the same failure, one step later.
- `verify-bundled-imports.mjs` checks the subpath against the installed
  `exports` map rather than only the package directory.
- `test/bundled-manifest.test.ts` asserts the derivation carries `exports`
  verbatim, drops `dependencies`, names only built files, and sweeps
  `packages/*/src` so a `@sentinel0/common/<subpath>` the code imports must
  be declared.

Both guards were confirmed to fire against a reconstructed 0.0.3 exports map,
the second naming the exact `runtime/services.js` from the report. The packed
0.0.4 tarball was installed clean and `sentinel0 start` now reaches its config
check.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EkUWsM92gcbit4p6hEhuyV
@maxigimenez
maxigimenez merged commit 0b63273 into main Sep 7, 2026
2 checks passed
@maxigimenez
maxigimenez deleted the fix/bundled-exports-drift branch September 7, 2026 08:02
maxigimenez added a commit that referenced this pull request Sep 7, 2026
The 0.0.4 publish failed at Test. `publish-cli.yml` runs Lint → Test → Build,
where `ci.yml` builds first, and the bundled-manifest test added in #50 read
`packages/common/dist/` — absent at that point, so every `exports` entry
looked unbuilt.

The test no longer depends on a build: it maps each `./dist/<module>.{js,d.ts}`
target back to `src/<module>.ts` and asserts the source exists, flagging any
target shape the mapping does not recognise so it cannot pass by accident.
Guarding it with `skipIf(!existsSync(dist))` — the pattern `common`'s
circular-import guard uses — would have been the worse fix: green, and testing
nothing, on the one path that publishes. `prepare-package.mjs` still asserts
against real `dist/` at pack time, where a build has just run.

`publish-cli.yml` now builds before testing anyway, matching `ci.yml`. That
also un-skips `common/test/built-output.test.ts`, which imports the compiled
package to catch circular imports the source alias hides and had been skipping
silently on every publish.

Verified both ways from a clean tree: with no `dist/` present the suite passes
and still fails on an `exports` entry with no source module; in the new order
`common` reports 4 files passed rather than 3 passed and 1 skipped. The packed
tarball installs clean, clears the bundle gate, and `sentinel0 start` reaches
its config check.


Claude-Session: https://claude.ai/code/session_01EkUWsM92gcbit4p6hEhuyV

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant