Skip to content

test: the orphan guard covers every package, and reads its own entry points (#548) - #768

Merged
divshekhar merged 4 commits into
reticlehq:mainfrom
vaibhav8a:test/orphan-guard-covers-every-package
Sep 6, 2026
Merged

divshekhar merged 4 commits into
reticlehq:mainfrom
vaibhav8a:test/orphan-guard-covers-every-package

Conversation

@vaibhav8a

Copy link
Copy Markdown
Contributor

Closes #548.

What I found first

The issue asks to port orphan-modules.test.ts to packages/browser and packages/core. Both of those have since landed, and the two orphan files it names (observers/subresources.ts, dom/blind-spots.ts) are already deleted — so I looked at what the guard does and does not cover now.

Three packages have a ledger (server, browser, core); four do not. The three are identical apart from their allowlists, and all three share one bug:

const ENTRY_POINTS = new Set(['index.ts']);

That is wrong for any package that publishes a subpath export. @reticlehq/react publishes ./store, and both docs/usage.md:969 and docs/packages/react.mdx:75 tell users to write:

import { useReticleStore } from '@reticlehq/react/store';

store.ts has no importer inside packages/react/src — its only importer is its own spec. So porting the guard to react with the hardcoded set would have reported a documented, published hook as dead code on the very first run, and the natural fix would have been to delete it.

What this does

One scan, in scripts/orphan-scan.mjs, so every package asks the same question the same way. Entry points come from each package's own exports map (plus main, module, types, bin), so the guard and the manifest cannot disagree.

Only build outputs are mapped back to source. A manifest also names things that never had any — "./package.json": "./package.json", JSON schema assets — and turning those into .ts candidates would silently excuse a real source file that happened to share the name.

Coverage is now server, browser, core, react, vite-plugin, eslint-plugin. Every existing allowlist and its prose is preserved verbatim; no entry added or removed.

babel-plugin is deliberately left out: it is one module, that module is the entry point, and its tsconfig is CommonJS, so importing an ESM helper would need a dynamic import for no coverage in return. Say the word if you would rather it had one anyway.

Verification

  • All six guards pass: pnpm --filter @reticlehq/<pkg> exec vitest run src/orphan-modules.test.ts — 2 passed each
  • The guard catches a real orphan: dropping an unimported packages/react/src/deliberately-orphaned-probe.ts fails with expected [ 'deliberately-orphaned-probe.ts' ] to deeply equal [], and passes again once removed
  • node scripts/check-boundaries.mjs — OK, 10 packages
  • prettier --check clean; tsc --noEmit clean on every touched package for the files this PR adds (packages/react has a pre-existing sourceOwner error from a stale @reticlehq/browser dist, present on main too)

scripts/orphan-scan.d.mts carries the types so each package's TypeScript test imports it without an implicit any.

vaibhav8a and others added 3 commits September 5, 2026 14:18
…points (reticlehq#548)

Three packages had a hand-written orphan ledger and four had none. The three
were identical apart from their allowlists, and all three carried the same
bug: entry points were the literal set {'index.ts'}.

That is wrong for any package publishing a subpath export. @reticlehq/react
publishes './store', and docs/usage.md and docs/packages/react.mdx both tell
users to import useReticleStore from '@reticlehq/react/store' - so a guard
copied to that package would have reported a documented, published hook as
dead code on its first run.

The scan moves to scripts/orphan-scan.mjs, which derives entry points from
each package's own exports map (plus main/module/types/bin), so the guard and
the manifest cannot disagree. Only build outputs are mapped back to source: a
manifest also points at things that never had any - './package.json', JSON
schema assets - and turning those into .ts candidates would silently excuse a
real source file that happened to share the name.

Guards now cover server, browser, core, react, vite-plugin and eslint-plugin.
babel-plugin is left out: it is one module, that module is the entry point,
and its tsconfig is CommonJS so importing an ESM helper needs a dynamic
import for no coverage in return.

Both directions are still reported from one scan - undeclared orphans and
declarations that have gone stale - because stopping at the first failure
hides half the work.

Signed-off-by: Vaibhav Srivastava <vaibhavsri1712@gmail.com>
@divshekhar
divshekhar merged commit ff1f9c9 into reticlehq:main Sep 6, 2026
33 of 36 checks passed
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.

orphan-modules.test.ts guards one package of the three that need it

2 participants