3.9 KiB
check-registry-eject
Purpose
Shadcn-eject smoke gate for @suaveplan/react-ui. Verifies the registry/r/*.json tree against the package source so a downstream consumer running shadcn add @suaveplan/<name> always finds the files the registry promised. Catches the realistic drift modes today: a registry entry whose listed file vanished (rename / restructure), a component directory shipped without a registry entry (forgot to regenerate), an orphaned registry entry whose component was deleted (forgot to clean up), and entries that fail JSON or shape validation. Runs as part of bunx turbo lint on every commit.
Features
- Per-entry validation: shape (
name,type,files), JSON parse, non-emptyfiles[]. - Per-file existence: every
files[].pathresolves to a real file undersrc/components/<name>/. - Bidirectional drift detection: registry entries without matching component directories AND component directories without registry entries both fail.
- Sorted, stable diagnostic output for diff-friendly CI logs.
- Pure helper exports (
audit,loadEntry) reusable from tests.
Usage: Basic Example
bun run check:registry-eject
# check-registry-eject: clean — 51 registry entries match 51 component directories.
When a registry entry lists a missing file:
bun run check:registry-eject
# MISSING_SOURCE_FILE registry/r/toast.json: file 'toast.tsx' does not exist at src/components/toast/toast.tsx
# check-registry-eject: 1 diagnostic(s) — see above.
# (exit 1)
Usage: Advanced Example
The exported helpers can be reused — for example, a CI report generator that walks both registries (react-ui + a future react-ui-social) and emits a unified report:
import { audit } from "@suaveplan/react-ui/scripts/check-registry-eject";
const diagnostics = await audit({
registryEntries: await listEntries(packageA),
componentDirs: await listDirs(packageA),
componentsDir: join(packageA, "src/components"),
});
API Reference
CLI
bun scripts/check-registry-eject.ts
Exits 0 on a clean tree; exits 1 on any diagnostic; exits 2 when the registry directory or components directory is missing entirely (configuration error).
Exported helpers
loadEntry(entryPath): Promise<RegistryEntry | { error }>— reads + validates a singleregistry/r/<name>.jsonentry. Returns{ error: string }on any structural failure (missing field, wrong type, malformed JSON).audit({ registryEntries, componentDirs, componentsDir }): Promise<Diagnostic[]>— pure function returning sorted diagnostics. The four diagnostic kinds are:MISSING_REGISTRY_ENTRY,MISSING_SOURCE_FILE,EMPTY_FILES_LIST,MALFORMED_ENTRY,ORPHAN_REGISTRY_ENTRY.
Implementation Notes
- The gate is intentionally narrower than the proposal's full vision — it verifies file presence + entry shape, NOT that the ejected source compiles against a synthesized consumer
tsconfig.json. The synthesized-tsconfig roundtrip depends on the registry transform itself (rewriting@suaveplan/react-uiimports to consumer-local paths) which today is a passthrough —build-registry.tsships source verbatim, so a consumer'sshadcn addalready gets compilable source. When the transform lands as a callable helper (per the comment block inbuild-registry.ts), this gate'saudit()will be extended to invoke it and then runtsc --noEmitagainst the transformed tree. index.jsonis excluded from the per-entry walk — it is the manifest of manifests, not an individual component.- Component directories whose only files are barrels (
index.tsonly, no<name>.tsx/<name>.ts) are NOT detected as component directories. The list-detection mirrorsbuild-registry.ts's heuristic. - Both
.tsxand.tsextensions are valid for the file listed infiles[]. Components like<Toast>that ship as JSX-free TypeScript ship as.ts; the registry entry for them lists<name>.tsand the gate accepts it.