components-differ 1.0.17 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,206 +1,31 @@
1
- # components-differ
1
+ # ShadCN Project Differ
2
2
 
3
- `components-differ` is a companion CLI for shadcn/ui projects. It compares the current state of a codebase to its initial shadcn scaffold, then emits an installable registry entry that captures only the changes you made. You can pipe that output straight into the official shadcn CLI to bootstrap new projects, patch existing installs, or maintain a fully fledged custom registry.
3
+ This CLI tool figures out the difference between the initial commit of a ShadCN project and the current state of the project and creates a new ShadCN JSON output file with the changes. This ShadCN JSON file can then be used with the ShadCN CLI to generate a new project or add to an existing project.
4
4
 
5
- ## Features
5
+ # Steps
6
6
 
7
- - Generates shadcn-compatible registry items from real projects
8
- - Detects component dependencies based on the latest shadcn/ui catalog
9
- - Understands both `src/` and app-root layouts
10
- - Skips built-in primitives so the diff contains only custom code
11
- - Creates or updates `registry.json` in-place (schema: [`registry.json`](https://ui.shadcn.com/docs/registry/registry-json))
12
- - Supports environment-based configuration for registry metadata
13
- - Ships with TypeScript types, tests, and build scripts for local development
7
+ 1. Create a new NextJS app
8
+ 2. Add ShadCN to the app
9
+ 3. Create a new initial commit; `rm -fr .git && git init && git add . && git commit -m "Initial commit"` or `npx components-differ --init`
10
+ 4. Make your updates to the project
11
+ 5. Run the CLI tool; `npx components-differ`
14
12
 
15
- ## Prerequisites
13
+ The reason we are recreating the initial commit is so that the resulting JSON output is only the changes to the project after ShadCN was added, and not the entire project history.
16
14
 
17
- - Node.js 18+
18
- - `pnpm` (recommended) or `npm`
19
- - A project that has already been initialised with the shadcn CLI
20
-
21
- ## Installation
22
-
23
- Install globally or run via `npx`:
24
-
25
- ```bash
26
- pnpm add -g components-differ
27
- # or
28
- npx components-differ --help
29
- ```
30
-
31
- During local development, clone the repo and install dependencies:
32
-
33
- ```bash
34
- git clone https://github.com/componentshost/components-differ.git
35
- cd components-differ
36
- pnpm install
37
- ```
38
-
39
- ## Quick Start
40
-
41
- 1. Create a fresh Next.js app.
42
- 2. Initialise shadcn/ui (`npx shadcn@latest init`).
43
- 3. Commit the baseline scaffold:
44
-
45
- ```bash
46
- rm -fr .git && git init
47
- git add .
48
- git commit -m "Initial shadcn scaffold"
49
- ```
50
-
51
- _Tip: use `npx components-differ --init` to automate the reset + commit._
52
-
53
- 4. Build out your custom components, hooks, utilities, and pages.
54
- 5. Run the differ:
55
-
56
- ```bash
57
- npx components-differ > registry-item.json
58
- ```
59
-
60
- 6. Publish or host the JSON output, then use it with the shadcn CLI:
61
-
62
- ```bash
63
- npx shadcn@latest add https://example.com/registry-item.json
64
- ```
65
-
66
- ## CLI Usage
67
-
68
- ```bash
69
- npx components-differ [options]
70
- ```
71
-
72
- | Option | Description |
73
- | --- | --- |
74
- | `-n, --name <name>` | Override the generated item name (defaults to the current directory name). |
75
- | `--init` | Reset the git repository and recreate the initial commit (useful for fresh scaffolds). |
76
- | `--registry [path]` | Create or update a `registry.json` file. If no path is provided, `./registry.json` is used. |
77
- | `--initshadcn [type]` | Configure shadcn/ui for your project. Detects project type automatically; pass `next` or `vite` to force a specific scaffold. |
78
-
79
- When `--registry` is omitted, the CLI prints a single registry item (JSON) to stdout. The item includes inferred dependencies, registry dependencies, file payloads, and metadata compatible with the shadcn registry tooling.
80
-
81
- ### Bootstrapping shadcn/ui
82
-
83
- Use `--initshadcn` to apply the official shadcn setup steps for supported frameworks:
84
-
85
- ```bash
86
- # Auto-detect (looks at package.json)
87
- npx components-differ --initshadcn
88
-
89
- # Explicit Vite support (adds Tailwind deps, config files, and runs the shadcn CLI)
90
- npx components-differ --initshadcn vite
91
-
92
- # Explicit Next.js support (runs the shadcn CLI in your project)
93
- npx components-differ --initshadcn next
94
- ```
95
-
96
- For Vite projects the command will:
97
-
98
- - Add `tailwindcss`, `@tailwindcss/vite`, and `@types/node`
99
- - Locate the first CSS file imported by the Vite entry file (`src/main.{ts,tsx,js,jsx}`) and replace its contents with the Tailwind entrypoint
100
- - Inject the required `baseUrl`/`paths` aliases into `tsconfig.json` and `tsconfig.app.json`
101
- - Overwrite `vite.config.ts` with the recommended configuration (includes Tailwind and path aliases)
102
- - Run the interactive `shadcn@latest init` command
103
-
104
- Next.js projects skip the file edits (Next provides the required Tailwind setup) and go straight to the `shadcn` CLI.
105
-
106
- ## Working with `registry.json`
107
-
108
- Passing `--registry` switches the CLI into registry mode:
109
-
110
- ```bash
111
- npx components-differ --registry # writes ./registry.json
112
- npx components-differ --registry configs/registry.json
113
- ```
114
-
115
- - Existing files are parsed and updated; if the item already exists (matching `name`), it is replaced.
116
- - New files are created with the official schema URL and project metadata.
117
- - Parent directories are created automatically.
118
-
119
- ### Registry metadata
120
-
121
- By default the generated file uses:
122
-
123
- - `name`: `shadcn`
124
- - `homepage`: `https://ui.shadcn.com`
125
-
126
- Override these values by defining environment variables at runtime:
127
-
128
- ```bash
129
- REGISTRY_NAME=acme \
130
- REGISTRY_HOMEPAGE=https://acme.dev \
131
- npx components-differ --registry
132
- ```
133
-
134
- If the variables are absent, the CLI also reads a `.env` file in the project root:
135
-
136
- ```
137
- REGISTRY_NAME=acme
138
- REGISTRY_HOMEPAGE=https://acme.dev
139
- ```
140
-
141
- ## Building registry artifacts
142
-
143
- Add a script to your `package.json` so you can build the registry payload with the official shadcn toolchain:
144
-
145
- ```json
146
- {
147
- "scripts": {
148
- "registry:build": "shadcn build"
149
- }
150
- }
151
- ```
152
-
153
- Then run:
15
+ You can then take the resulting JSON ouput and host it on a URL, then use that with the ShadCN CLI to generate a new project or add to an existing project.
154
16
 
155
17
  ```bash
156
- pnpm registry:build
18
+ npx shadcn@latest init http://your-json-output-url
157
19
  ```
158
20
 
159
- Combine this with the differ to capture changes, sync `registry.json`, and ship ready-to-install artifacts in a single workflow.
160
-
161
- ## Recommended Workflow
21
+ You can use the `--src-dir` flag if you want to use the `src` directory in your project.
162
22
 
163
- 1. Run `pnpm build && pnpm test` to ensure your project is green.
164
- 2. Execute `npx components-differ --registry` to keep `registry.json` current.
165
- 3. Publish the updated registry (via HTTP or a git-backed registry).
166
- 4. Consumers install the latest slice using `npx shadcn@latest add <url>`.
167
-
168
- ## Development Scripts
169
-
170
- | Command | Description |
171
- | --- | --- |
172
- | `pnpm build` | Compile TypeScript sources to `dist/`. |
173
- | `pnpm test` | Run the Vitest suite once. |
174
- | `pnpm test:watch` | Run tests in watch mode. |
175
- | `pnpm bump` | Increment the package version (patch) and update `package.json`. |
176
- | `pnpm registry:build` | Run the shadcn CLI build pipeline (requires `shadcn` CLI). |
177
-
178
- During development you can execute the CLI locally:
23
+ Or you can add the JSON output to an existing project:
179
24
 
180
25
  ```bash
181
- pnpm build
182
- node dist/index.js --registry
26
+ npx shadcn@latest add http://your-json-output-url
183
27
  ```
184
28
 
185
- Or link it globally:
186
-
187
- ```bash
188
- pnpm link --global
189
- components-differ --help
190
- ```
191
-
192
- ## Releasing
193
-
194
- 1. Bump the version: `pnpm bump` (or adjust `package.json` manually).
195
- 2. Rebuild and test: `pnpm build && pnpm test`.
196
- 3. Publish: `npm publish`.
197
-
198
- Ensure you are authenticated with an npm account that has publish rights (`npm whoami`).
199
-
200
- ## Contributing
201
-
202
- Contributions are welcome! Please open an issue or pull request with a clear description of the change. Run the full test suite before submitting (`pnpm test`), and include updates to the documentation where relevant.
203
-
204
- ## License
29
+ # Why is this useful?
205
30
 
206
- MIT © Izet Molla
31
+ This allows library maintainers or SaaS services to create a one step installer for their library or service into an existing project, or to bootstrap a new project with the library or service.
package/dist/index.mjs ADDED
@@ -0,0 +1,114 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
+ import { program } from "commander";
6
+
7
+ import { scanForAlteredFiles, scanForFiles, hasSrcDir } from "./src/git.mjs";
8
+ import { readComponentsManifest } from "./src/components.mjs";
9
+ import { createDiff } from "./src/create-diff.mjs";
10
+ import { execSync } from "node:child_process";
11
+
12
+
13
+ program.option("-n, --name <name>").option('--init');
14
+ program.parse();
15
+
16
+ const options = program.opts();
17
+
18
+ const runCommand = (command) => {
19
+ try {
20
+ execSync(command, { stdio: "inherit" });
21
+ } catch (error) {
22
+ console.error(`Failed to execute command: ${command}`);
23
+ process.exit(1);
24
+ }
25
+ };
26
+
27
+ const ensureGitignore = () => {
28
+ const gitignorePath = path.join(process.cwd(), ".gitignore");
29
+ if (!fs.existsSync(gitignorePath)) {
30
+ console.log(".gitignore file is missing. Creating one...");
31
+ const content = `
32
+ /node_modules
33
+ /.pnp
34
+ .pnp.*
35
+ .yarn/*
36
+ !.yarn/patches
37
+ !.yarn/plugins
38
+ !.yarn/releases
39
+ !.yarn/versions
40
+
41
+ # testing
42
+ /coverage
43
+
44
+ # next.js
45
+ /.next/
46
+ /out/
47
+
48
+ # production
49
+ /build
50
+
51
+ # misc
52
+ .DS_Store
53
+ *.pem
54
+
55
+ # debug
56
+ npm-debug.log*
57
+ yarn-debug.log*
58
+ yarn-error.log*
59
+
60
+ # env files (can opt-in for committing if needed)
61
+ .env*
62
+
63
+ # vercel
64
+ .vercel
65
+
66
+ # typescript
67
+ *.tsbuildinfo
68
+ next-env.d.ts
69
+ `;
70
+ fs.writeFileSync(gitignorePath, content, "utf8");
71
+ console.log(".gitignore file created with default rules.");
72
+ } else {
73
+ console.log(".gitignore file already exists.");
74
+ }
75
+ };
76
+
77
+ const main = () => {
78
+ if (options.init) {
79
+ console.log("Initializing git repository for new component");
80
+ // Cross-platform logic
81
+ if (process.platform === "win32") {
82
+ runCommand("rmdir /s /q .git && git init && git add . && git commit -m \"Initial commit\"");
83
+ } else {
84
+ runCommand("rm -fr .git && git init && git add . && git commit -m \"Initial commit\"");
85
+ }
86
+ ensureGitignore()
87
+ return;
88
+ }
89
+
90
+ const name = options.name || path.basename(process.cwd());
91
+
92
+ const { alteredFiles, specificFiles } = scanForAlteredFiles([
93
+ "./package.json",
94
+ ]);
95
+ const currentFiles = scanForFiles(process.cwd());
96
+
97
+ const currentPackageJson = fs.readFileSync("./package.json", "utf-8");
98
+
99
+ const config = readComponentsManifest(process.cwd());
100
+ config.isSrcDir = hasSrcDir(process.cwd());
101
+
102
+ const output = createDiff({
103
+ name,
104
+ config,
105
+ alteredFiles,
106
+ currentFiles,
107
+ specificFiles,
108
+ currentPackageJson,
109
+ });
110
+
111
+ console.log(JSON.stringify(output, null, 2));
112
+ };
113
+
114
+ main();
@@ -0,0 +1,98 @@
1
+ import path from "node:path";
2
+ import fs from "node:fs";
3
+
4
+ const WHITELISTED_COMPONENTS = [
5
+ "accordion",
6
+ "alert",
7
+ "alert-dialog",
8
+ "aspect-ratio",
9
+ "avatar",
10
+ "badge",
11
+ "breadcrumb",
12
+ "button",
13
+ "calendar",
14
+ "card",
15
+ "carousel",
16
+ "chart",
17
+ "checkbox",
18
+ "collapsible",
19
+ "command",
20
+ "context-menu",
21
+ "table",
22
+ "dialog",
23
+ "drawer",
24
+ "dropdown-menu",
25
+ "form",
26
+ "hover-card",
27
+ "input",
28
+ "input-otp",
29
+ "label",
30
+ "menubar",
31
+ "navigation-menu",
32
+ "pagination",
33
+ "popover",
34
+ "progress",
35
+ "radio-group",
36
+ "resizable",
37
+ "scroll-area",
38
+ "select",
39
+ "separator",
40
+ "sheet",
41
+ "skeleton",
42
+ "slider",
43
+ "sonner",
44
+ "switch",
45
+ "tabs",
46
+ "textarea",
47
+ "toast",
48
+ "toggle",
49
+ "toggle-group",
50
+ "tooltip",
51
+ ];
52
+
53
+ export function findComponentFiles(config, originalFiles) {
54
+ const registryDependencies = [];
55
+ const compDir = config.ui.replace("@/", config.isSrcDir ? "src/" : "");
56
+ for (const { path: filePath } of originalFiles) {
57
+ if (filePath.startsWith(compDir)) {
58
+ const fileExtension = path.extname(filePath);
59
+ const fileName = path.basename(filePath, fileExtension);
60
+ if (
61
+ (fileExtension === ".tsx" || fileExtension === ".jsx") &&
62
+ WHITELISTED_COMPONENTS.includes(fileName)
63
+ ) {
64
+ registryDependencies.push(path.basename(filePath, fileExtension));
65
+ }
66
+ }
67
+ }
68
+ return registryDependencies;
69
+ }
70
+
71
+ export function readComponentsManifest(dir) {
72
+ const manifestPath = path.join(dir, "./components.json");
73
+ if (fs.existsSync(manifestPath)) {
74
+ const config = JSON.parse(fs.readFileSync(manifestPath, "utf-8")).aliases;
75
+ return config;
76
+ } else {
77
+ console.error("Components manifest not found");
78
+ process.exit(1);
79
+ }
80
+ }
81
+
82
+ export function getAliasedPaths(config) {
83
+ return [
84
+ config.components.replace("@/", ""),
85
+ config.utils.replace("@/", ""),
86
+ config.ui.replace("@/", ""),
87
+ config.lib.replace("@/", ""),
88
+ config.hooks.replace("@/", ""),
89
+ ];
90
+ }
91
+
92
+ export function isBuiltinComponent(config, filePath) {
93
+ if (filePath.startsWith(config.ui.replace("@/", ""))) {
94
+ const component = path.basename(filePath, path.extname(filePath));
95
+ return WHITELISTED_COMPONENTS.includes(component);
96
+ }
97
+ return false;
98
+ }
@@ -0,0 +1,96 @@
1
+ import {
2
+ findComponentFiles,
3
+ getAliasedPaths,
4
+ isBuiltinComponent,
5
+ } from "./components.mjs";
6
+ import { parseFilePath } from "./parse-file-path.mjs";
7
+
8
+ function addFile(output, config, inSrcDir, relativeFilePath, content) {
9
+ if (!isBuiltinComponent(config, relativeFilePath)) {
10
+ output.files.push(
11
+ parseFilePath(inSrcDir, config, `./${relativeFilePath}`, content)
12
+ );
13
+ }
14
+ }
15
+
16
+ function addDependencies(
17
+ output,
18
+ initialPackageContents,
19
+ currentPackageContents
20
+ ) {
21
+ const initialPackageJson = JSON.parse(initialPackageContents);
22
+ const currentPackageJson = JSON.parse(currentPackageContents);
23
+
24
+ const initialDependencies = initialPackageJson.dependencies ?? {};
25
+ const currentDependencies = currentPackageJson.dependencies ?? {};
26
+ const initialDevDependencies = initialPackageJson.devDependencies ?? {};
27
+ const currentDevDependencies = currentPackageJson.devDependencies ?? {};
28
+
29
+ output.dependencies = Object.keys(currentDependencies).filter(
30
+ (dep) => !Object.prototype.hasOwnProperty.call(initialDependencies, dep)
31
+ );
32
+ output.devDependencies = Object.keys(currentDevDependencies).filter(
33
+ (dep) => !Object.prototype.hasOwnProperty.call(initialDevDependencies, dep)
34
+ );
35
+ }
36
+
37
+ function scanWithSrcDir(output, config, alteredFiles) {
38
+ for (const { path, content } of alteredFiles) {
39
+ if (path.startsWith("src/")) {
40
+ addFile(output, config, true, path.replace("src/", ""), content);
41
+ } else {
42
+ addFile(output, config, false, path, content);
43
+ }
44
+ }
45
+ }
46
+
47
+ function isInAppDir(path) {
48
+ return path.startsWith("app/");
49
+ }
50
+
51
+ function scanWithoutSrcDir(output, config, alteredFiles) {
52
+ const aliasedPaths = getAliasedPaths(config);
53
+
54
+ for (const { path, content } of alteredFiles) {
55
+ addFile(
56
+ output,
57
+ config,
58
+ aliasedPaths.includes(path) || isInAppDir(path),
59
+ path,
60
+ content
61
+ );
62
+ }
63
+ }
64
+
65
+ export function createDiff({
66
+ name,
67
+ config,
68
+ alteredFiles,
69
+ specificFiles,
70
+ currentFiles,
71
+ currentPackageJson,
72
+ }) {
73
+ const output = {
74
+ name,
75
+ type: "registry:block",
76
+ dependencies: [],
77
+ devDependencies: [],
78
+ registryDependencies: [],
79
+ files: [],
80
+ tailwind: {},
81
+ cssVars: {},
82
+ meta: {},
83
+ };
84
+
85
+ if (config.isSrcDir) {
86
+ scanWithSrcDir(output, config, alteredFiles);
87
+ } else {
88
+ scanWithoutSrcDir(output, config, alteredFiles);
89
+ }
90
+
91
+ output.registryDependencies = findComponentFiles(config, currentFiles);
92
+
93
+ addDependencies(output, specificFiles["./package.json"], currentPackageJson);
94
+
95
+ return output;
96
+ }
@@ -0,0 +1,141 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { execSync } from "node:child_process";
4
+ import ignore from "ignore";
5
+ import { start } from "node:repl";
6
+
7
+ const INITIAL_DIR = "_initial";
8
+
9
+ const EXCLUDE_DIRS = [
10
+ "node_modules",
11
+ "dist",
12
+ "fonts",
13
+ "build",
14
+ "public",
15
+ "static",
16
+ ".next",
17
+ ".git",
18
+ INITIAL_DIR,
19
+ ];
20
+
21
+ const EXCLUDE_FILES = [
22
+ ".DS_Store",
23
+ "next-env.d.ts",
24
+ "package-lock.json",
25
+ "yarn.lock",
26
+ "pnpm-lock.yaml",
27
+ "bun.lockb",
28
+ "package.json",
29
+ "tailwind.config.ts",
30
+ "tailwind.config.js",
31
+ "components.json",
32
+ "favicon.ico",
33
+ ];
34
+
35
+ function cloneInitialCommit() {
36
+ deleteInitialDir();
37
+
38
+ try {
39
+ // Get the initial commit hash
40
+ const initialCommit = execSync("git rev-list --max-parents=0 HEAD")
41
+ .toString()
42
+ .trim();
43
+
44
+ // Clone the initial commit quietly
45
+ execSync(`git worktree add -f ${INITIAL_DIR} ${initialCommit}`, {
46
+ stdio: "ignore",
47
+ });
48
+ } catch (error) {
49
+ console.error("Error cloning initial commit:", error.message);
50
+ process.exit(1);
51
+ }
52
+ }
53
+
54
+ function deleteInitialDir() {
55
+ if (fs.existsSync(INITIAL_DIR)) {
56
+ fs.rmSync(INITIAL_DIR, { recursive: true });
57
+
58
+ try {
59
+ execSync("git worktree prune", { stdio: "ignore" });
60
+ } catch (error) {
61
+ console.error("Error pruning git worktree:", error.message);
62
+ }
63
+ }
64
+ }
65
+
66
+ function checkIfFileIsChanged(relativeFilePath) {
67
+ const initialFilePath = path.join(INITIAL_DIR, relativeFilePath);
68
+ const fullPath = path.join(process.cwd(), relativeFilePath);
69
+ if (!fs.existsSync(initialFilePath)) {
70
+ return true; // New file
71
+ }
72
+ const currentContent = fs.readFileSync(fullPath, "utf-8");
73
+ const initialContent = fs.readFileSync(initialFilePath, "utf-8");
74
+ return currentContent !== initialContent;
75
+ }
76
+
77
+ export function scanForFiles(startDir, checkFile = false) {
78
+ const foundFiles = [];
79
+
80
+ let ignorer = () => false;
81
+ if (fs.existsSync(path.join(startDir, ".gitignore"))) {
82
+ const gitIgnore = ignore().add(
83
+ fs.readFileSync(path.join(startDir, ".gitignore")).toString()
84
+ );
85
+ ignorer = (relativeFilePath) => {
86
+ return gitIgnore.ignores(relativeFilePath);
87
+ };
88
+ }
89
+
90
+ function scanDirectory(dir, relativePath = "") {
91
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
92
+
93
+ for (const entry of entries) {
94
+ const fullPath = path.join(dir, entry.name);
95
+ const relativeFilePath = path.join(relativePath, entry.name);
96
+
97
+ if (entry.isDirectory()) {
98
+ if (!EXCLUDE_DIRS.includes(entry.name)) {
99
+ scanDirectory(path.join(dir, entry.name), relativeFilePath);
100
+ }
101
+ } else if (
102
+ !checkFile ||
103
+ (checkFile && checkIfFileIsChanged(relativeFilePath))
104
+ ) {
105
+ if (!EXCLUDE_FILES.includes(entry.name) && !ignorer(relativeFilePath)) {
106
+ foundFiles.push({
107
+ path: relativeFilePath,
108
+ content: fs.readFileSync(fullPath, "utf-8"),
109
+ });
110
+ }
111
+ }
112
+ }
113
+ }
114
+
115
+ scanDirectory(startDir);
116
+
117
+ return foundFiles.sort((a, b) => a.path.localeCompare(b.path));
118
+ }
119
+
120
+ export function scanForAlteredFiles(specificFilesToReturn = []) {
121
+ cloneInitialCommit();
122
+
123
+ const alteredFiles = scanForFiles(process.cwd(), true);
124
+
125
+ const specificFiles = specificFilesToReturn.reduce((out, file) => {
126
+ const fullPath = path.join(process.cwd(), INITIAL_DIR, file);
127
+ out[file] = fs.readFileSync(fullPath, "utf-8");
128
+ return out;
129
+ }, {});
130
+
131
+ deleteInitialDir();
132
+
133
+ return {
134
+ alteredFiles,
135
+ specificFiles,
136
+ };
137
+ }
138
+
139
+ export function hasSrcDir(dir) {
140
+ return fs.existsSync(path.join(dir, "src"));
141
+ }
@@ -0,0 +1,32 @@
1
+ function fixAlias(alias) {
2
+ return alias.replace("@", ".");
3
+ }
4
+
5
+ export function parseFilePath(wasInSrcDir, config, filePath, content) {
6
+ const out = {
7
+ path: filePath,
8
+ content,
9
+ type: "registry:example",
10
+ target: wasInSrcDir ? filePath : `~/${filePath.replace("./", "")}`,
11
+ };
12
+
13
+ if (filePath.startsWith(fixAlias(config.ui))) {
14
+ out.type = "registry:ui";
15
+ out.target = undefined;
16
+ } else if (filePath.startsWith(fixAlias(config.components))) {
17
+ out.type = "registry:block";
18
+ out.target = undefined;
19
+ } else if (filePath.startsWith(fixAlias(config.hooks))) {
20
+ out.type = "registry:hook";
21
+ out.target = undefined;
22
+ } else if (filePath.startsWith(fixAlias(config.lib))) {
23
+ out.type = "registry:lib";
24
+ out.target = undefined;
25
+ }
26
+
27
+ if (out.type === "registry:example") {
28
+ out.path = filePath;
29
+ }
30
+
31
+ return out;
32
+ }