npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@lynstack/shadcn-registry

v0.3.0

Published

Builds a shadcn registry from the source of a TypeScript component library: items, files, dependencies and theme, read from the code itself.

Readme

@lynstack/shadcn-registry

Builds a shadcn registry from the source of a TypeScript component library. You say where items live; the build reads everything else from the code, refuses what the shadcn CLI would install wrongly, and reports every problem at once, with its file and line where it has one.

What it reads

  • Files and targets: every file of an item, under its folder or the one file its rule names, except the ignored ones, with the path the shadcn CLI writes it to.

  • npm dependencies: every package the files import, with the range from package.json. catalog: and workspace: ranges become the ranges pnpm publish would write. Peer dependencies are left out, since the app has them. A type-only import may name a development dependency, which the item lists in its devDependencies.

  • Registry dependencies: every other item the files import, named by @namespace/name or by URL, never by a bare name, which the CLI would look up in shadcn's own registry.

  • Details: an item's description and categories come from the file-level TSDoc comment of its entry, the file itself for a one-file item and index.* for a folder: the first doc comment that carries @packageDocumentation (TSDoc) or @module (TypeDoc). The description is its @summary, or else the first paragraph of its summary; each @category adds a category, once. Tag names read in any case. {@link Button | a button} reads as its text, code spans keep their backticks, other inline tags and HTML tags are left out, and a block tag, even one TSDoc does not define, ends the text. Other tags are API documentation and are ignored. The title is the item's name in title case.

    /**
     * Shows a short status next to other content.
     *
     * @category display
     * @packageDocumentation
     */
  • Themes: :root, .dark and @theme inline become cssVars; every other rule and at-rule becomes css, merged as the CLI merges it into the app. @source, @theme inline reference and an @import or @plugin of tailwindcss or a subpath of it, which serve only the library's own Tailwind CSS, are left out. A package @import or @plugin adds its package as a dependency; an @import of a stylesheet on the web, such as a font's, is kept as it is.

Files and stylesheets are read with \n line endings, and items, files, dependencies and item fields are sorted by code unit, never by locale, so every checkout on every machine builds the same output.

What it refuses

The build replays how the CLI rewrites each import, including its lookup of an import statement among the files one install writes (the item's, and those of every item it depends on through its imports or its rule's registryDependencies). It refuses:

Imports

  • an import of a file no item ships, or one that cannot be resolved;
  • an import of a package package.json does not declare, or of a development dependency at run time;
  • a range an app cannot install: a catalog: range no catalog of pnpm-workspace.yaml defines, a workspace: package that is not linked, or a file:, link:, git:, git+…:, github:, http:, https: or portal: range;
  • a relative import that would point elsewhere once installed;
  • an alias import in a registry:file, a JavaScript, .mts or .d.ts file, whose aliases the CLI does not map, or one it would resolve to another module, and a local import that is neither relative nor @/ or #/;
  • an import() whose module name is computed;
  • CommonJS, whose imports the build cannot follow: a .cjs or .cts file, require() of a module or import x = require();
  • a file that is missing, a folder, not UTF-8 text, or cannot be parsed.

Themes

  • a block nested in a rule, a declaration outside a rule or @utility, or a key given two values;
  • in :root and .dark, anything but custom properties; in @theme inline, anything but custom properties and @keyframes;
  • any @theme but @theme inline and @theme inline reference;
  • an @import or @plugin that names a file or a URL instead of a package (an @import of a stylesheet on the web is kept), and a @plugin with options;
  • the Tailwind CSS v3 at-rules @tailwind and @config;
  • a stylesheet that cannot be parsed.

Configuration

  • a field that is missing, of the wrong type, or that the configuration does not know, such as a misspelled outdir;
  • two items with one name, or a name that is not lowercase letters and digits joined by single hyphens;
  • an item source with backslashes, outside the root, without {item} once in its last segment, or naming a file without an extension;
  • a target that is empty, leaves the app, or does not start with @ui, @components, @lib, @hooks or ~/ (only a registry:file or registry:page may name a plain path);
  • a file directly in the folder of a folder rule, which belongs to no item of it;
  • a rule's registryDependencies entry that is a bare name but no item of this registry;
  • a namespace or url of the wrong shape, or a name that makes a namespace of the wrong shape when neither is set, and an installPeers entry that package.json does not list in peerDependencies;
  • a library that has Tailwind CSS older than 4 installed;
  • an item, or registry.json, that the shadcn/schema of the installed CLI does not accept.

Requirements

Node.js 24.11 or later, a library of ES modules, TypeScript 7 for its own tsconfig.json, Tailwind CSS 4 or later, and shadcn 4.21.4 or a later 4.x, whose schema checks every item.

Install

pnpm add -D @lynstack/shadcn-registry shadcn

Configure

Create shadcn-registry.config.ts next to the library's package.json; .mts, .js and .mjs work too. Node.js runs it directly, so it uses only erasable TypeScript syntax.

import { defineConfig } from "@lynstack/shadcn-registry";

export default defineConfig({
  name: "acme",
  homepage: "https://acme.dev",
  outDir: "public/r",
  items: [
    // One item per folder: src/components/ui/button/** is the item "button".
    {
      source: "src/components/ui/{item}",
      type: "registry:ui",
      target: "@ui",
      registryDependencies: ["style"],
    },
    // One item per file: src/hooks/use-media.ts is the item "use-media".
    { source: "src/hooks/{item}.ts", type: "registry:hook", target: "@hooks" },
  ],
  themes: [
    {
      name: "style",
      type: "registry:style",
      extends: "none",
      sources: ["src/theme/tokens.css", "src/theme/theme.css"],
    },
  ],
});
  • items are tried in order, and a file goes to the first rule whose source holds it: list src/components/ui/{item} before src/components/{item}. A file directly in the folder of a folder rule, such as src/components/ui/index.ts, belongs to no item of it: move it into an item's folder, give it a file rule, or add it to ignore.
  • source is a path with forward slashes, not a glob: a folder such as app/[locale] or (shop) matches itself. A file rule may name more than the item, such as src/hooks/use-{item}.ts.
  • target is the folder the CLI writes into. It starts with @ui, @components, @lib, @hooks or ~/, the folders the CLI knows; a registry:file or registry:page may also name a path from the app's root. Each file keeps its path below the part of source before {item}: src/components/ui/button/button.tsx goes to @ui/button/button.tsx.
  • registryDependencies names items every item of the rule depends on, beyond those its imports name: an item of this registry by its name, or another registry's item by its @namespace/name or URL.
  • describe(item) returns an item's title, description, author, docs, categories and meta. It receives the item's name, its files and the details its documentation gives, and each field it returns replaces the one the documentation gives.
  • Items name each other @<name>/<item> by default; namespace changes the prefix, and url, such as "https://acme.dev/r/{name}.json", names them by URL instead, which needs no registries entry in the app.
  • A theme's extends: "none" makes shadcn init with it replace shadcn's own style.
  • installPeers names peer dependencies an app may lack: items list them as dependencies with the peer range. Other peers, such as react, are left out.
  • tsconfig is the TypeScript configuration that resolves imports, tsconfig.json by default. Paths in the configuration start from the folder of the configuration file.
  • ignore lists the files that never ship, as globs from the root. Tests, specs, type tests, stories and snapshots are ignored by default; setting ignore replaces that list.
  • outDir, public/r by default, is where the build writes. As Vite empties its outDir, the build deletes the JSON files in it that it does not write; emptyOutDir: false keeps them. An outDir that is the root or a folder above it draws a warning: emptied, it loses package.json.
  • test.templates names the templates of shadcn init --template that shadcn-registry test starts an app from, one app each: astro, next, react-router, start or vite, which is the default. laravel is not one, as the shadcn CLI needs an app from laravel new. test.dependencies adds packages to each app.

Run

shadcn-registry build   # write registry.json and <item>.json into outDir
shadcn-registry check   # fail when outDir differs from a fresh build (for CI)
shadcn-registry watch   # build again whenever a file under the root changes
shadcn-registry test    # install every item into a new app per template (needs the network and git)

Each command takes --config <file> (-c) to name the configuration file; by default it is the first of shadcn-registry.config.ts, .mts, .js and .mjs in the current folder. watch skips outDir, node_modules and .git, and reads the configuration once: restart it after changing the configuration.

test builds the registry onto a local server, then tests each template of test.templates in turn, or of --template (-t), which replaces them and may be repeated: shadcn-registry test -t vite -t next. A template named twice is tested once. For each, it sets an app up with the shadcn CLI as a user would: init --template creates the app with shadcn's default style (the nova preset on Base UI) and installs it with the package manager that runs the script, registry add names the registry's namespace, init installs the first theme whose extends is none in place of shadcn's style, and add installs every other item, replacing any file init wrote at the same path. Before the items, an app that pnpm installed gains the library's pnpm build and release-age policy, and every app gains the library's peer dependencies it lacks, at the versions the library has installed. Then the app must build and typecheck with its template's own build and typecheck scripts, and the stylesheet its components.json names must hold every token of the themes. Run it from a package.json script.

From code

buildRegistry(config, root), writeOutput and compareOutput do the same as build and check, and testRegistry does what test does with what loadConfig returns, and with { templates } in place of --template. A registry with problems throws a RegistryError that lists them all. testRegistry throws that RegistryError, or an InstallTestError when it runs outside a package.json script, before it writes any app. Past that, it throws an InstallTestError when a command or a check of the app fails, or an Error whose cause is any other failure, and either names the folder it kept the app in.

License

MIT