Registry distribution — remote/URL/namespaced installs + patties ui build
shadcn resolves components from a built registry.json that can live at an
Purpose
shadcn resolves components from a built registry.json that can live at an arbitrary URL, supports namespaced third-party registries (@acme/...), and is produced by shadcn build. patties resolves only by name, from the single local patties-ui registry installed in node_modules (Bun.resolveSync("patties-ui/registry", cwd), load-catalog.ts:20). The matrix records the cluster: add <url>/add <local-path> (§1), namespaced and remote registries (§4), and a build step (§1, §4) — plus the divergence that patties-ui is private: true and not on npm (§4). All four were promoted from spec-later to specced on 2026-05-29 ([[rfc-patties-ui-registries]], accepted); they are the lowest-priority specced rows — implement after the diff/view/ path/init/theming/migrate work — but no longer gated on a third-party ecosystem existing first.
Divergence we keep
patties continues to refuse to run npm/bun install — it edits package.json and tells the user to run bun install (add.ts:73). Remote installs do not change that: source is fetched and stamped, deps are recorded, install stays the user's explicit step. This Bun-native, CI-safe stance is non-negotiable (matrix §1 "Divergent-by-design").
Source kinds for patties add
add's positional argument is currently always a catalog name (resolveNames, add.ts:78). This spec classifies each positional into one of:
- name —
button. Resolved from the localpatties-uicatalog (today's path, unchanged default). - namespaced name —
@acme/fancy-button. Resolved from a registry registered under the@acmenamespace (see config below). - local path —
./vendor/widgets/registry.tsor a path to a single component file. Loaded from disk viaBun.file. Useful for monorepo-internal catalogs. - URL —
https://…/registry.jsonor…/component.json. Fetched (see security below).
Classification: a leading @ns/ is namespaced; a ./,../,/ or existing path is local; an http(s):// prefix is URL; otherwise a local-catalog name.
Registry config
Namespaces map to base URLs / local paths under the ui config block ([[framework/25-ui-config-block]]), extended here:
ui: {
registries: {
"@acme": "https://registry.acme.dev/r", // remote
"@internal": "./packages/ui-catalog", // local path
},
}
The built-in patties-ui catalog is always available unprefixed; it needs no registry entry.
Remote fetch security
- HTTPS only; an
http://URL is rejected unless--allow-insecure. - The fetched payload must validate against the
ComponentEntryschema (patties-ui/types); malformed JSON/TS →EXIT.USAGE, nothing written. - Remote source is shown before stamping unless
--yes(reuses the--viewrendering from [[cli/13-add-view]]) — never silently execute fetched code; the stamped files are source the user reviews, not run at install time. - Fetched component source is treated as untrusted text: it is written verbatim to the destination, never
eval/import-ed by the CLI.
patties ui build
shadcn's build compiles registry.json from source. patties' registry is a hand- maintained TS module (registry.ts) by design (matrix §4 "hand-maintained by design for now"). patties ui build is the inverse-direction tool for registry authors (e.g. an @acme publisher): it reads a registry.ts + templates/ dir and emits a static, fetchable registry.json + per-component JSON payloads that the remote-fetch path above consumes.
patties ui build --out dist/r # emit dist/r/registry.json + dist/r/<name>.json
- Validates every entry against
ComponentEntryand that eachfiles[].fromexists undertemplates/. - Inlines each template's source into its component JSON so a consumer needs one fetch per component, no directory walking.
- Pure transform; no network, no install.
The first-party patties-ui stays hand-maintained TS and is not required to be built — patties ui build exists for third-party publishers, not for the core catalog.
Acceptance criteria
patties add buttonis unchanged: resolves from the localpatties-uicatalog.patties add ./vendor/widgets/card.tsxstamps that local file into the resolved components dir.patties add @acme/fancy-buttonwithregistries["@acme"]set fetches/loads from that base, shows source, and stamps on confirmation; deps recorded, no install run.- A non-HTTPS URL is rejected without
--allow-insecure. - A fetched payload that fails
ComponentEntryvalidation writes nothing and exitsEXIT.USAGE. patties ui build --out dist/remits a schema-validregistry.jsonplus one JSON per component with inlined source; fails if a templatefrompath is missing.- No code path runs
npm/bun install; thebun installreminder is printed after any dep change (add.ts:73).