CodeVetter · Repo Unpack · Contributor handoff
shadcn-ui/ui
Most work happens in packages/shadcn (the CLI) and apps/v4 (site + registry authoring). Read the entrypoint, the two big command actions, the resolver/fetcher layer, and the updater pipeline before editing. Build the CLI before running e2e. Respect the invariants: CSS writes last, registry context cleared in finally, globalThis.fetch in proxy.ts, generated public/r is not hand-edited, and components.json is user-owned.
- Read-first files: packages/shadcn/src/index.ts (command table), commands/init.ts + commands/add.ts (the two complex actions), utils/add-components.ts (install pipeline), registry/resolver.ts + registry/fetcher.ts + registry/builder.ts (fetch/addressing), registry/schema.ts (all zod contracts), utils/updaters/update-files.ts (write+transform).
- Dev loop: `pnpm install`; build CLI `pnpm --filter=shadcn build` (tsup); run CLI against local site `pnpm shadcn:dev` (REGISTRY_URL=http://localhost:4000/r, SHADCN_TEMPLATE_DIR=../../templates) after `pnpm v4:dev`; unit tests `pnpm shadcn:test` (vitest run in packages/shadcn).
- E2e tests require the built dist and the v4 site: root `pnpm test` = `pnpm --filter=v4 registry:build` + start-server-and-test on http://localhost:4000 + `turbo run test`; packages/tests executes dist/index.js — editing src without rebuilding silently tests stale code.
- Conventions: zod schemas validate every external input (options schemas, registryItemSchema, rawConfigSchema); interactivity via `prompts`; output via logger/highlighter/spinner wrappers; errors are typed RegistryError subclasses with code/suggestion/context; deepmerge for config merges.
- Import alias: packages/shadcn source imports use '@/src/...' resolved by tsconfig paths + vite-tsconfig-paths in vitest — keep new imports on the alias, not relative chains.
- Trap: registry/proxy.ts must keep calling globalThis.fetch (not undici's) so msw interception works in tests; redirects are manual — don't replace with redirect:'follow' or private-registry headers will leak cross-origin.
- Trap: every command action must end with clearRegistryContext() in finally (headers/env live in AsyncLocalStorage); new commands that fetch registry items need the same discipline.
- Trap: write CSS last — updateCss is intentionally after updateFiles so file watchers rebuild once; preserve ordering when touching the updater pipeline. Also preserve the components.json FILE_BACKUP_SUFFIX backup/restore dance in init.
- Do not hand-edit generated artifacts: apps/v4/public/r/**, registry/__index__.tsx, registry/bases/__index__.tsx, styles/<style>/ui/** are build-registry.mts outputs; edit authored sources under registry/bases, registry/styles/*.css, examples/, and registry/new-york-v4 instead.
- Release flow: changesets (`pnpm release` = changeset version); publishing is `cd packages/shadcn && pnpm pub:beta|pub:rc|pub:next|pub:release`; release.yml auto-publishes prereleases for PRs labeled 'release: beta'/'release: rc'; commits use conventional-commit lint (.commitlintrc.json).
- Env vars that matter: REGISTRY_URL (registry base), SHADCN_TEMPLATE_DIR (template lookup), SHADCN_GITHUB_URL (template repo override), GH_TOKEN/GITHUB_TOKEN (github: item auth), HTTPS_PROXY/HTTP_PROXY/ALL_PROXY (fetch proxying), plus .env* files loaded per-command for ${ENV} expansion.
- Schema parity: registry/schema.ts is published as shadcn/schema and imported by apps/v4 (registryItemSchema in build-registry.mts and the /init route) — changing item shape requires updating both sides and the 871 committed public/r artifacts via a rebuild.
- templates/* are outside the pnpm workspace (pnpm-workspace only includes apps/*+packages/*); after editing a template, scripts/sync-templates.sh syncs them — CI templates.yml validates them.