Move docs/ package mirror into document-navigator plugin
Relocate the ~6k-file docs tree from the repo root into plugins/document-navigator/docs/ so the plugin is self-contained and portable. docs/superpowers/ (specs and plans) stays at the repo root. Moved via cp+rm rather than git mv because directory rename() fails with EPERM on this WSL /mnt DrvFs mount; git rename-detection pairs the identical-content files at commit time. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
# `check-i18n-keys`
|
||||
|
||||
## Purpose
|
||||
|
||||
Translation-key drift gate for `@suaveplan/react-ui`. Walks every `*.{ts,tsx}` under the package's source roots, extracts every `react-ui.*` translation-key reference from the documented call shapes, and diffs the discovered set against the canonical `src/locales/en-GB.json`. Reports `MISSING <key>` for keys referenced in code but absent from the bundle (consumers will hit a missing-key warning) and `ORPHAN <key>` for bundle keys nothing in source references (dead translation keys). Catches drift the moment a developer renames a key in code but forgets the bundle, or vice versa.
|
||||
|
||||
## Features
|
||||
|
||||
- Pattern-based extraction over six chassis-recognised call shapes: `t("react-ui.…")`, `tDynamic(...)`, `i18nKey="…"` JSX attribute, `i18nKey: "…"` object literal, `<name>Key={"…"}` prop default, `?? "react-ui.…"` fallback shorthand, `const DEFAULT_*_KEY = "…"` module-scope literal, `labelKey: "…"` plugin shape.
|
||||
- Source location (`<file>:<line>`) attached to every MISSING diagnostic for quick navigation.
|
||||
- `--strict` flag promotes ORPHAN diagnostics to errors. Default exits 0 on ORPHAN-only (warn) so the gate ships green even when the bundle has pre-existing dead keys; CI can opt into strict mode after a cleanup pass.
|
||||
- Wired into `bunx turbo lint` — every commit in CI runs the default mode.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```bash
|
||||
bun run check:i18n-keys
|
||||
# check-i18n-keys: clean — 23 keys in code, 23 keys in bundle.
|
||||
```
|
||||
|
||||
When a key is missing:
|
||||
|
||||
```bash
|
||||
bun run check:i18n-keys
|
||||
# MISSING react-ui.foo.absent at src/components/foo/foo.tsx:42
|
||||
# check-i18n-keys: 1 missing, 0 orphan (orphan = warn)
|
||||
# (exit 1)
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
Strict mode for a periodic bundle-cleanup pass:
|
||||
|
||||
```bash
|
||||
bun run check:i18n-keys:strict
|
||||
# ORPHAN react-ui.popover.close
|
||||
# ORPHAN react-ui.alertDialog.cancel
|
||||
# check-i18n-keys: 0 missing, 2 orphan (strict)
|
||||
# (exit 1)
|
||||
```
|
||||
|
||||
The script's exported helpers can be reused by other tooling (e.g. a CI report aggregator):
|
||||
|
||||
```ts
|
||||
import {
|
||||
diff,
|
||||
extractKeysFromSource,
|
||||
loadBundleKeys,
|
||||
} from "./check-i18n-keys.js";
|
||||
|
||||
const sourceKeys = (await extractKeysFromSource(files, root)).keys;
|
||||
const bundleKeys = await loadBundleKeys(bundlePath);
|
||||
const diagnostics = diff(sourceKeys, bundleKeys);
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### CLI
|
||||
|
||||
`bun scripts/check-i18n-keys.ts [--strict]`
|
||||
|
||||
- Default mode: exits non-zero only when at least one MISSING diagnostic appears. ORPHANs are printed as warnings.
|
||||
- `--strict`: exits non-zero on any diagnostic (MISSING or ORPHAN).
|
||||
|
||||
### Exported helpers
|
||||
|
||||
- `extractKeysFromSource(files, rootForRelative): Promise<{ keys: Map<key, location> }>` — walks the supplied source files, returns a map keyed by translation key, value `<relative-path>:<line>`.
|
||||
- `loadBundleKeys(bundlePath): Promise<Set<string>>` — reads the bundle JSON and returns the subset of keys in the `react-ui.*` namespace.
|
||||
- `diff(sourceKeys, bundleKeys): Diagnostic[]` — pure function returning sorted diagnostics (MISSING before ORPHAN, alphabetic within each kind).
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- Pattern-based instead of AST-based — no `@babel/parser` dep. The supported call shapes are a closed set documented at the top of the script. New idioms (e.g. a future `const KEYS = { close: "react-ui.foo" }` const-record style) need a new pattern; the script falls into the catch-all-of-last-resort `?? "react-ui.…"` only when the value lives in a discriminating context.
|
||||
- The extraction explicitly EXCLUDES `*.test.tsx`, `*.stories.tsx`, `*.stories.test.tsx`, and `*.bench.ts` — test fixtures and stories often reference keys that exist in the bundle for component contracts, not for the test's own runtime use; treating them as references would mask real orphans.
|
||||
- Scan roots: `src/components`, `src/i18n`, `src/theme`, `src/pack`, `src/provider`, `src/hooks`, `src/errors`. Any future surface that ships `react-ui.*` keys must be added here.
|
||||
- The default-vs-strict split is deliberate. Pre-existing orphans in the bundle (`react-ui.empty.defaultDescription`, `react-ui.popover.close`, …) are public-API keys consumers may pass via `*Key` prop overrides; removing them would be a breaking change. Default mode warns; strict mode lets the team enforce zero-orphan after a bundle cleanup change.
|
||||
- The script is a `Bun.import.meta.main` entry point; `process.exit(await main())` is the only top-level side effect. Imports are pure helpers reusable from tests.
|
||||
@@ -0,0 +1,63 @@
|
||||
# `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
|
||||
|
||||
```bash
|
||||
bun run check:registry-eject
|
||||
# check-registry-eject: clean — 51 registry entries match 51 component directories.
|
||||
```
|
||||
|
||||
When a registry entry lists a missing file:
|
||||
|
||||
```bash
|
||||
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:
|
||||
|
||||
```ts
|
||||
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.
|
||||
Reference in New Issue
Block a user