Files
skills/docs/ui/react/react-ui/scripts/check-registry-eject.md
2026-05-29 08:20:56 +01:00

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-empty files[].
  • Per-file existence: every files[].path resolves to a real file under src/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 single registry/r/<name>.json entry. 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-ui imports to consumer-local paths) which today is a passthrough — build-registry.ts ships source verbatim, so a consumer's shadcn add already gets compilable source. When the transform lands as a callable helper (per the comment block in build-registry.ts), this gate's audit() will be extended to invoke it and then run tsc --noEmit against the transformed tree.
  • index.json is excluded from the per-entry walk — it is the manifest of manifests, not an individual component.
  • Component directories whose only files are barrels (index.ts only, no <name>.tsx/<name>.ts) are NOT detected as component directories. The list-detection mirrors build-registry.ts's heuristic.
  • Both .tsx and .ts extensions are valid for the file listed in files[]. Components like <Toast> that ship as JSX-free TypeScript ship as .ts; the registry entry for them lists <name>.ts and the gate accepts it.