ui8kit 1.0.6 → 1.3.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.
Files changed (78) hide show
  1. package/README.md +211 -194
  2. package/dist/index.d.ts +1 -0
  3. package/dist/index.js +2930 -0
  4. package/dist/index.js.map +1 -0
  5. package/package.json +70 -28
  6. package/css/dist/styles.css +0 -3325
  7. package/css/src/semantic/alert.css +0 -11
  8. package/css/src/semantic/article.css +0 -59
  9. package/css/src/semantic/avatar.css +0 -11
  10. package/css/src/semantic/badge.css +0 -19
  11. package/css/src/semantic/breadcrumb.css +0 -23
  12. package/css/src/semantic/button.css +0 -39
  13. package/css/src/semantic/card.css +0 -43
  14. package/css/src/semantic/index.css +0 -19
  15. package/css/src/semantic/input.css +0 -3
  16. package/css/src/semantic/label.css +0 -3
  17. package/css/src/semantic/link.css +0 -39
  18. package/css/src/semantic/main.css +0 -3
  19. package/css/src/semantic/markup.css +0 -23
  20. package/css/src/semantic/nav.css +0 -63
  21. package/css/src/semantic/pagination.css +0 -11
  22. package/css/src/semantic/section.css +0 -43
  23. package/css/src/semantic/sheet.css +0 -35
  24. package/css/src/semantic/skeleton.css +0 -3
  25. package/css/src/semantic/table.css +0 -31
  26. package/css/src/semantic/textarea.css +0 -3
  27. package/r/schema/registry-item.json +0 -68
  28. package/r/schema/registry.json +0 -93
  29. package/r/schema.json +0 -60
  30. package/r/semantic/components/article.json +0 -17
  31. package/r/semantic/components/aside.json +0 -17
  32. package/r/semantic/components/footer.json +0 -17
  33. package/r/semantic/components/header.json +0 -17
  34. package/r/semantic/components/main.json +0 -17
  35. package/r/semantic/components/markup.json +0 -17
  36. package/r/semantic/components/media.json +0 -17
  37. package/r/semantic/components/nav.json +0 -18
  38. package/r/semantic/components/section.json +0 -17
  39. package/r/semantic/components/sheet.json +0 -18
  40. package/r/semantic/index.json +0 -130
  41. package/r/semantic/lib/utils.json +0 -18
  42. package/r/semantic/ui/alert.json +0 -18
  43. package/r/semantic/ui/avatar.json +0 -17
  44. package/r/semantic/ui/badge.json +0 -18
  45. package/r/semantic/ui/breadcrumb.json +0 -19
  46. package/r/semantic/ui/button.json +0 -18
  47. package/r/semantic/ui/card.json +0 -17
  48. package/r/semantic/ui/input.json +0 -17
  49. package/r/semantic/ui/label.json +0 -17
  50. package/r/semantic/ui/link.json +0 -18
  51. package/r/semantic/ui/skeleton.json +0 -15
  52. package/r/semantic/ui/table.json +0 -17
  53. package/r/semantic/ui/textarea.json +0 -17
  54. package/r/utility/components/article.json +0 -17
  55. package/r/utility/components/aside.json +0 -17
  56. package/r/utility/components/footer.json +0 -17
  57. package/r/utility/components/header.json +0 -17
  58. package/r/utility/components/main.json +0 -17
  59. package/r/utility/components/markup.json +0 -17
  60. package/r/utility/components/media.json +0 -17
  61. package/r/utility/components/nav.json +0 -18
  62. package/r/utility/components/navbar.json +0 -15
  63. package/r/utility/components/section.json +0 -17
  64. package/r/utility/components/sheet.json +0 -18
  65. package/r/utility/index.json +0 -135
  66. package/r/utility/lib/utils.json +0 -18
  67. package/r/utility/ui/alert.json +0 -18
  68. package/r/utility/ui/avatar.json +0 -17
  69. package/r/utility/ui/badge.json +0 -19
  70. package/r/utility/ui/breadcrumb.json +0 -19
  71. package/r/utility/ui/button.json +0 -19
  72. package/r/utility/ui/card.json +0 -17
  73. package/r/utility/ui/input.json +0 -17
  74. package/r/utility/ui/label.json +0 -17
  75. package/r/utility/ui/link.json +0 -18
  76. package/r/utility/ui/skeleton.json +0 -15
  77. package/r/utility/ui/table.json +0 -17
  78. package/r/utility/ui/textarea.json +0 -17
package/dist/index.js ADDED
@@ -0,0 +1,2930 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { Command } from "commander";
5
+ import chalk12 from "chalk";
6
+
7
+ // src/commands/add.ts
8
+ import ora4 from "ora";
9
+ import path8 from "path";
10
+ import fs7 from "fs-extra";
11
+ import fetch3 from "node-fetch";
12
+ import prompts3 from "prompts";
13
+
14
+ // src/registry/api.ts
15
+ import fetch from "node-fetch";
16
+
17
+ // src/registry/schema.ts
18
+ import { z } from "zod";
19
+
20
+ // src/utils/schema-config.ts
21
+ var SCHEMA_CONFIG = {
22
+ // Base schema information
23
+ schemaVersion: "http://json-schema.org/draft-07/schema#",
24
+ baseUrl: "https://ui.buildy.tw/schema",
25
+ // Framework configuration
26
+ supportedFrameworks: ["vite-react"],
27
+ // Default aliases for path mapping
28
+ defaultAliases: {
29
+ "@": "./src",
30
+ "@/components": "./src/components",
31
+ "@/ui": "./src/components/ui",
32
+ "@/layouts": "./src/layouts",
33
+ "@/blocks": "./src/blocks",
34
+ "@/lib": "./src/lib",
35
+ "@/variants": "./src/variants"
36
+ },
37
+ // Registry configuration
38
+ defaultRegistry: "@ui8kit",
39
+ registryTypes: ["ui"],
40
+ // Default registry type
41
+ defaultRegistryType: "ui",
42
+ // CDN base URLs (registryName will be substituted)
43
+ cdnBaseUrls: [
44
+ "https://cdn.jsdelivr.net/npm/@ui8kit/registry@latest/r",
45
+ "https://unpkg.com/@ui8kit/registry@latest/r",
46
+ "https://raw.githubusercontent.com/buildy-ui/ui/main/packages/@ui8kit/registry/r"
47
+ ],
48
+ // Component categories
49
+ componentCategories: ["ui", "composite", "components", "layouts", "lib", "blocks", "variants"],
50
+ // Component types (should match registryItemTypeSchema)
51
+ componentTypes: [
52
+ "registry:lib",
53
+ "registry:block",
54
+ "registry:component",
55
+ "registry:ui",
56
+ "registry:composite",
57
+ "registry:layout",
58
+ "registry:variants"
59
+ ],
60
+ // Default directories structure
61
+ // Source directories (where CLI scans for components)
62
+ defaultDirectories: {
63
+ components: "./src/components",
64
+ lib: "./src/lib",
65
+ layouts: "./src/layouts",
66
+ blocks: "./src/blocks",
67
+ variants: "./src/variants"
68
+ },
69
+ // Schema descriptions and titles
70
+ descriptions: {
71
+ config: {
72
+ title: "UI8Kit Configuration",
73
+ description: "Configuration file for ui8kit CLI (UI8Kit structure)"
74
+ },
75
+ registry: {
76
+ title: "UI8Kit Registry",
77
+ description: "Registry schema for UI8Kit component system"
78
+ },
79
+ registryItem: {
80
+ title: "UI8Kit Registry Item",
81
+ description: "Schema for individual registry items in the UI8Kit component system"
82
+ }
83
+ },
84
+ // Field descriptions
85
+ fieldDescriptions: {
86
+ schema: "JSON Schema URL",
87
+ framework: "Target framework",
88
+ typescript: "Whether the project uses TypeScript",
89
+ globalCss: "Path to global styles entry file",
90
+ aliases: "Path aliases for imports in UI8Kit structure",
91
+ registry: "Default component registry",
92
+ componentsDir: "Directory where utility components will be installed",
93
+ libDir: "Directory for utility libraries",
94
+ registryName: "Registry name",
95
+ registryHomepage: "Registry homepage URL",
96
+ registryType: "Registry type (e.g., ui)",
97
+ registryVersion: "Registry version",
98
+ lastUpdated: "Last update timestamp",
99
+ categories: "Available component categories",
100
+ components: "Component metadata for quick lookup",
101
+ items: "Full component definitions"
102
+ }
103
+ };
104
+ var TYPE_TO_FOLDER = {
105
+ "registry:ui": "components/ui",
106
+ "registry:composite": "components",
107
+ "registry:block": "blocks",
108
+ "registry:component": "components",
109
+ "registry:lib": "lib",
110
+ "registry:layout": "layouts",
111
+ "registry:variants": "variants"
112
+ };
113
+ function getCdnUrls(_registryType) {
114
+ return [...SCHEMA_CONFIG.cdnBaseUrls];
115
+ }
116
+ function isExternalDependency(moduleName) {
117
+ return !moduleName.startsWith(".") && !moduleName.startsWith("@/") && !moduleName.startsWith("~/") && !moduleName.startsWith("@ui8kit/") && !moduleName.includes("\\") && moduleName !== "" && !moduleName.startsWith("file:");
118
+ }
119
+ function getSchemaRef(schemaName) {
120
+ return `${SCHEMA_CONFIG.baseUrl}/${schemaName}.json`;
121
+ }
122
+
123
+ // src/registry/schema.ts
124
+ var componentFileSchema = z.object({
125
+ path: z.string(),
126
+ content: z.string(),
127
+ target: z.string().optional()
128
+ });
129
+ var componentSchema = z.object({
130
+ name: z.string(),
131
+ type: z.enum(SCHEMA_CONFIG.componentTypes),
132
+ title: z.string().optional(),
133
+ description: z.string().optional(),
134
+ dependencies: z.array(z.string()).default([]),
135
+ devDependencies: z.array(z.string()).default([]),
136
+ registryDependencies: z.array(z.string()).optional(),
137
+ files: z.array(componentFileSchema),
138
+ tailwind: z.object({
139
+ config: z.object({
140
+ content: z.array(z.string()).optional(),
141
+ theme: z.record(z.string(), z.any()).optional(),
142
+ plugins: z.array(z.string()).optional()
143
+ }).optional()
144
+ }).optional(),
145
+ cssVars: z.object({
146
+ theme: z.record(z.string(), z.string()).optional(),
147
+ light: z.record(z.string(), z.string()).optional(),
148
+ dark: z.record(z.string(), z.string()).optional()
149
+ }).optional(),
150
+ meta: z.record(z.string(), z.any()).optional()
151
+ });
152
+ var configSchema = z.object({
153
+ $schema: z.string().optional(),
154
+ framework: z.literal(SCHEMA_CONFIG.supportedFrameworks[0]),
155
+ typescript: z.boolean().default(true),
156
+ globalCss: z.string().default("src/index.css"),
157
+ aliases: z.record(z.string(), z.string()).default(SCHEMA_CONFIG.defaultAliases),
158
+ registry: z.string().default(SCHEMA_CONFIG.defaultRegistry),
159
+ componentsDir: z.string().default(SCHEMA_CONFIG.defaultDirectories.components),
160
+ libDir: z.string().default(SCHEMA_CONFIG.defaultDirectories.lib)
161
+ });
162
+
163
+ // src/utils/logger.ts
164
+ import chalk from "chalk";
165
+ import ora from "ora";
166
+ var verboseEnabled = false;
167
+ function output(level, message, ...args) {
168
+ const prefix = (() => {
169
+ switch (level) {
170
+ case "info":
171
+ return chalk.blue("\u2139");
172
+ case "success":
173
+ return chalk.green("\u2705");
174
+ case "warn":
175
+ return chalk.yellow("\u26A0\uFE0F");
176
+ case "error":
177
+ return chalk.red("\u274C");
178
+ case "debug":
179
+ return chalk.gray("\u{1F41E}");
180
+ default:
181
+ return "";
182
+ }
183
+ })();
184
+ if (level === "debug" && !verboseEnabled) {
185
+ return;
186
+ }
187
+ console.log(`${prefix} ${message}`, ...args);
188
+ }
189
+ var logger = {
190
+ setVerbose(enabled) {
191
+ verboseEnabled = enabled;
192
+ },
193
+ info(message, ...args) {
194
+ output("info", message, ...args);
195
+ },
196
+ success(message, ...args) {
197
+ output("success", message, ...args);
198
+ },
199
+ warn(message, ...args) {
200
+ output("warn", message, ...args);
201
+ },
202
+ error(message, ...args) {
203
+ output("error", message, ...args);
204
+ },
205
+ debug(message, ...args) {
206
+ output("debug", message, ...args);
207
+ },
208
+ spinner(text) {
209
+ return ora(text).start();
210
+ }
211
+ };
212
+
213
+ // src/utils/cache.ts
214
+ import fs from "fs-extra";
215
+ import os from "os";
216
+ import path from "path";
217
+ var DEFAULT_TTL_MS = 36e5;
218
+ function normalizeKey(key) {
219
+ return key.trim().replace(/^\/+/, "").replace(/\\/g, "/");
220
+ }
221
+ function cacheFilePath(key) {
222
+ const cacheDir = getCacheDir();
223
+ const normalized = normalizeKey(key);
224
+ const normalizedWithJson = normalized.endsWith(".json") ? normalized : `${normalized}.json`;
225
+ const dataPath = path.join(cacheDir, normalizedWithJson);
226
+ return { dataPath, metaPath: `${dataPath}.meta.json` };
227
+ }
228
+ function getCacheDir() {
229
+ return path.join(os.homedir(), ".ui8kit", "cache");
230
+ }
231
+ function parseTimestamp(timestamp) {
232
+ if (typeof timestamp !== "number") {
233
+ return null;
234
+ }
235
+ return Number.isFinite(timestamp) ? timestamp : null;
236
+ }
237
+ async function getCachedJson(key, options = {}) {
238
+ const ttlMs = options.ttlMs ?? DEFAULT_TTL_MS;
239
+ if (options.noCache) {
240
+ return null;
241
+ }
242
+ const { dataPath, metaPath } = cacheFilePath(key);
243
+ if (!await fs.pathExists(dataPath) || !await fs.pathExists(metaPath)) {
244
+ return null;
245
+ }
246
+ try {
247
+ const meta = await fs.readJson(metaPath);
248
+ const lastFetched = parseTimestamp(meta?.lastFetched);
249
+ const metaTtl = parseTimestamp(meta?.ttl) ?? ttlMs;
250
+ if (!lastFetched) {
251
+ return null;
252
+ }
253
+ const now = Date.now();
254
+ if (now - lastFetched > metaTtl) {
255
+ return null;
256
+ }
257
+ return await fs.readJson(dataPath);
258
+ } catch {
259
+ return null;
260
+ }
261
+ }
262
+ async function setCachedJson(key, data, options = {}) {
263
+ const ttlMs = options.ttlMs ?? DEFAULT_TTL_MS;
264
+ const { dataPath, metaPath } = cacheFilePath(key);
265
+ await fs.ensureDir(path.dirname(dataPath));
266
+ await fs.writeJson(dataPath, data, { spaces: 2 });
267
+ await fs.writeJson(metaPath, {
268
+ lastFetched: Date.now(),
269
+ ttl: ttlMs
270
+ });
271
+ }
272
+ async function clearCache() {
273
+ const cacheDir = getCacheDir();
274
+ if (await fs.pathExists(cacheDir)) {
275
+ await fs.remove(cacheDir);
276
+ }
277
+ }
278
+
279
+ // src/registry/api.ts
280
+ var REGISTRY_INDEX_CACHE_TTL_MS = 36e5;
281
+ var registryCache = /* @__PURE__ */ new Map();
282
+ var DEFAULT_TIMEOUT_MS = 1e4;
283
+ var DEFAULT_MAX_RETRIES = 1;
284
+ var RETRY_DELAY_MS = 1200;
285
+ function isUrl(path13) {
286
+ try {
287
+ new URL(path13);
288
+ return true;
289
+ } catch {
290
+ return false;
291
+ }
292
+ }
293
+ function getRegistryCache(registryType) {
294
+ if (!registryCache.has(registryType)) {
295
+ registryCache.set(registryType, {
296
+ workingCDN: null,
297
+ registryIndex: null
298
+ });
299
+ }
300
+ return registryCache.get(registryType);
301
+ }
302
+ async function fetchJsonWithTimeout(url, timeoutMs) {
303
+ const controller = new AbortController();
304
+ const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
305
+ try {
306
+ const response = await fetch(url, { signal: controller.signal });
307
+ if (!response.ok) {
308
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
309
+ }
310
+ return await response.json();
311
+ } finally {
312
+ clearTimeout(timeoutId);
313
+ }
314
+ }
315
+ async function fetchJsonWithRetry(url, maxRetries, timeoutMs) {
316
+ let attempt = 0;
317
+ let lastError;
318
+ while (attempt < maxRetries) {
319
+ attempt += 1;
320
+ try {
321
+ return await fetchJsonWithTimeout(url, timeoutMs);
322
+ } catch (error) {
323
+ lastError = error;
324
+ if (attempt >= maxRetries) {
325
+ break;
326
+ }
327
+ await new Promise((resolve3) => setTimeout(resolve3, RETRY_DELAY_MS));
328
+ }
329
+ }
330
+ throw lastError instanceof Error ? lastError : new Error("Request failed");
331
+ }
332
+ async function fetchFromRegistryPath(requestPath, registryType, options = {}) {
333
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
334
+ const maxRetries = Math.max(1, options.maxRetries ?? DEFAULT_MAX_RETRIES);
335
+ const cache = getRegistryCache(registryType);
336
+ const cdnUrls = getCdnUrls(registryType);
337
+ const orderedUrls = cache.workingCDN ? [cache.workingCDN, ...cdnUrls.filter((url) => url !== cache.workingCDN)] : [...cdnUrls];
338
+ let lastError;
339
+ for (const baseUrl of orderedUrls) {
340
+ try {
341
+ const data = await fetchJsonWithRetry(`${baseUrl}/${requestPath}`, maxRetries, timeoutMs);
342
+ if (cache.workingCDN !== baseUrl) {
343
+ cache.registryIndex = null;
344
+ }
345
+ cache.workingCDN = baseUrl;
346
+ return data;
347
+ } catch (error) {
348
+ lastError = error;
349
+ if (cache.workingCDN === baseUrl) {
350
+ cache.workingCDN = null;
351
+ }
352
+ }
353
+ }
354
+ throw lastError instanceof Error ? lastError : new Error(`No working ${registryType} CDN found`);
355
+ }
356
+ async function getRegistryIndex(registryType, options = {}) {
357
+ if (cacheStateHasIndex(registryType, options)) {
358
+ return getCachedInMemory(registryType);
359
+ }
360
+ const cached = await getCachedJson(
361
+ `${registryType}/index.json`,
362
+ { noCache: options.noCache, ttlMs: REGISTRY_INDEX_CACHE_TTL_MS }
363
+ );
364
+ if (cached) {
365
+ setCachedInMemory(registryType, cached);
366
+ return cached;
367
+ }
368
+ const cache = getRegistryCache(registryType);
369
+ cache.registryIndex = await fetchFromRegistryPath("index.json", registryType, options);
370
+ await setCachedJson(
371
+ `${registryType}/index.json`,
372
+ cache.registryIndex,
373
+ { ttlMs: REGISTRY_INDEX_CACHE_TTL_MS }
374
+ );
375
+ return cache.registryIndex;
376
+ }
377
+ async function getComponentByType(name, registryType, options = {}) {
378
+ const normalizedName = name.toLowerCase();
379
+ const cachedComponent = await getCachedJson(
380
+ `${registryType}/components/${normalizedName}.json`,
381
+ { noCache: options.noCache, ttlMs: REGISTRY_INDEX_CACHE_TTL_MS }
382
+ );
383
+ if (cachedComponent) {
384
+ try {
385
+ return componentSchema.parse(cachedComponent);
386
+ } catch (error) {
387
+ logger.debug(`Invalid cached component for ${name}, refetching`);
388
+ }
389
+ }
390
+ try {
391
+ const index = await getRegistryIndex(registryType, options);
392
+ const excludeTypes = options.excludeTypes ?? [];
393
+ const componentInfo = index.components?.find(
394
+ (c) => typeof c?.name === "string" && c.name.toLowerCase() === normalizedName && !excludeTypes.includes(c.type)
395
+ );
396
+ if (!componentInfo) {
397
+ logger.debug(`Component ${name} not found in ${registryType} registry`);
398
+ return null;
399
+ }
400
+ const folder = componentInfo.type === "registry:variants" ? "components/variants" : TYPE_TO_FOLDER[componentInfo.type];
401
+ if (!folder) {
402
+ logger.debug(`Unknown component type: ${componentInfo.type}`);
403
+ return null;
404
+ }
405
+ logger.debug(`Loading ${name} from /${folder}/ (type: ${componentInfo.type})`);
406
+ const data = await fetchFromRegistryPath(`${folder}/${name}.json`, registryType, options);
407
+ await setCachedJson(`${registryType}/components/${normalizedName}.json`, data, { ttlMs: REGISTRY_INDEX_CACHE_TTL_MS });
408
+ return componentSchema.parse(data);
409
+ } catch (error) {
410
+ logger.debug(`Failed to get component by type: ${error.message}`);
411
+ return null;
412
+ }
413
+ }
414
+ async function getComponent(name, registryType = SCHEMA_CONFIG.defaultRegistryType, options = {}) {
415
+ try {
416
+ if (isUrl(name)) {
417
+ return await fetchFromUrl(name, options);
418
+ }
419
+ return await getComponentByType(name, registryType, options);
420
+ } catch (error) {
421
+ logger.debug(`Failed to fetch ${name} from ${registryType}: ${error.message}`);
422
+ return null;
423
+ }
424
+ }
425
+ async function fetchFromUrl(url, options = {}) {
426
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
427
+ const maxRetries = Math.max(1, options.maxRetries ?? DEFAULT_MAX_RETRIES);
428
+ logger.debug(`Fetching component from: ${url}`);
429
+ const data = await fetchJsonWithRetry(url, maxRetries, timeoutMs);
430
+ return componentSchema.parse(data);
431
+ }
432
+ async function getAllComponents(registryType = SCHEMA_CONFIG.defaultRegistryType, options = {}) {
433
+ try {
434
+ logger.debug(`Fetching all ${registryType} components using optimized approach`);
435
+ const indexData = await getRegistryIndex(registryType, options);
436
+ const components = [];
437
+ const excludeTypes = options.excludeTypes ?? [];
438
+ if (indexData.components && Array.isArray(indexData.components)) {
439
+ for (const componentInfo of indexData.components) {
440
+ if (excludeTypes.includes(componentInfo.type)) {
441
+ continue;
442
+ }
443
+ const component = await getComponent(componentInfo.name, registryType, options);
444
+ if (component) {
445
+ components.push(component);
446
+ }
447
+ }
448
+ }
449
+ return components;
450
+ } catch (error) {
451
+ logger.debug(`Failed to fetch all ${registryType} components: ${error.message}`);
452
+ return [];
453
+ }
454
+ }
455
+ function cacheStateHasIndex(registryType, options) {
456
+ if (options.noCache) {
457
+ return false;
458
+ }
459
+ const cache = getRegistryCache(registryType);
460
+ return Boolean(cache.registryIndex);
461
+ }
462
+ function getCachedInMemory(registryType) {
463
+ return getRegistryCache(registryType).registryIndex;
464
+ }
465
+ function setCachedInMemory(registryType, index) {
466
+ const cache = getRegistryCache(registryType);
467
+ cache.registryIndex = index;
468
+ }
469
+
470
+ // src/utils/project.ts
471
+ import fs2 from "fs-extra";
472
+ import path2 from "path";
473
+ var MODERN_CONFIG_NAME = "ui8kit.config.json";
474
+ async function isViteProject() {
475
+ const viteConfigFiles = [
476
+ "vite.config.ts",
477
+ "vite.config.js",
478
+ "vite.config.mts",
479
+ "vite.config.mjs"
480
+ ];
481
+ for (const file of viteConfigFiles) {
482
+ if (await fs2.pathExists(file)) {
483
+ return true;
484
+ }
485
+ }
486
+ return false;
487
+ }
488
+ async function hasReact() {
489
+ const packageJsonPath = path2.join(process.cwd(), "package.json");
490
+ if (!await fs2.pathExists(packageJsonPath)) {
491
+ return false;
492
+ }
493
+ const packageJson = await fs2.readJson(packageJsonPath);
494
+ const deps = {
495
+ ...packageJson.dependencies,
496
+ ...packageJson.devDependencies
497
+ };
498
+ return "react" in deps;
499
+ }
500
+ async function findConfig(registryType) {
501
+ const rootConfig = await getConfig();
502
+ if (rootConfig)
503
+ return rootConfig;
504
+ const srcConfig = await getConfig("./src");
505
+ if (srcConfig)
506
+ return srcConfig;
507
+ if (registryType) {
508
+ const registryConfig = await getConfig(`./${registryType}`);
509
+ if (registryConfig)
510
+ return registryConfig;
511
+ }
512
+ return null;
513
+ }
514
+ async function getConfig(registryPath) {
515
+ const baseDir = registryPath ? path2.join(process.cwd(), registryPath) : process.cwd();
516
+ const configPath = path2.join(baseDir, MODERN_CONFIG_NAME);
517
+ if (!await fs2.pathExists(configPath)) {
518
+ return null;
519
+ }
520
+ try {
521
+ const config = await fs2.readJson(configPath);
522
+ return configSchema.parse(config);
523
+ } catch (error) {
524
+ console.error("\u274C Invalid ui8kit.config.json:", error.message);
525
+ return null;
526
+ }
527
+ }
528
+ async function saveConfig(config) {
529
+ const configPath = path2.join(process.cwd(), MODERN_CONFIG_NAME);
530
+ await fs2.ensureDir(path2.dirname(configPath));
531
+ await fs2.writeJson(configPath, config, { spaces: 2 });
532
+ }
533
+ async function ensureDir(dirPath) {
534
+ await fs2.ensureDir(dirPath);
535
+ }
536
+
537
+ // src/utils/registry-validator.ts
538
+ import fs6 from "fs-extra";
539
+ import path6 from "path";
540
+ import chalk6 from "chalk";
541
+ import prompts2 from "prompts";
542
+
543
+ // src/commands/init.ts
544
+ import chalk5 from "chalk";
545
+ import prompts from "prompts";
546
+ import ora3 from "ora";
547
+
548
+ // src/utils/cli-messages.ts
549
+ var CLI_MESSAGES = {
550
+ // Error messages
551
+ errors: {
552
+ noComponentsSpecified: "Please specify at least one component to add.",
553
+ notInitialized: "ui8kit is not initialized in this project.",
554
+ notViteProject: "This doesn't appear to be a Vite project.",
555
+ reactNotInstalled: "React is not installed in this project.",
556
+ buildFailed: "Build failed:",
557
+ scanFailed: "Scan failed:",
558
+ registryTempUnavailable: "registry temporarily unavailable",
559
+ noCDNFound: (registryType) => `No working ${registryType} CDN found`,
560
+ componentNotFound: (name, registryType) => `Component "${name}" not found in ${registryType} registry`,
561
+ unknownComponentType: (type) => `Unknown component type: ${type}`,
562
+ fileNotFound: (path13) => `File not found: ${path13}`,
563
+ invalidConfig: "Invalid ui8kit.config.json:",
564
+ failedToInstall: (name, registryType) => `Failed to install ${name} from ${registryType}`,
565
+ failedToFetch: (registryType) => `Failed to fetch components from ${registryType}`,
566
+ failedToFetchComponent: (name, registryType) => `Failed to fetch ${name} from ${registryType}:`,
567
+ failedToAnalyzeDeps: (path13) => `Warning: Could not analyze dependencies for ${path13}:`,
568
+ dependenciesFailed: "Failed to install dependencies",
569
+ couldNotInstallDeps: (name) => `Warning: Could not install some dependencies for ${name}`,
570
+ noLocalInstall: "No installed local components were found in this project.",
571
+ invalidRegistryForDiff: "Could not load registry data for diff check.",
572
+ cacheClearFailed: "Could not clear cache."
573
+ },
574
+ // Success messages
575
+ success: {
576
+ initialized: (registryName) => `UI8Kit ${registryName} structure initialized successfully!`,
577
+ setupComplete: (registryName) => `UI8Kit ${registryName} Setup complete!`,
578
+ installed: (name, registryType) => `Installed ${name} from ${registryType}`,
579
+ allInstalled: (registryType) => `All ${registryType} components installed successfully!`,
580
+ componentsInstalled: "Components installed successfully!",
581
+ depsInstalled: "Dependencies installed",
582
+ registryBuilt: "Registry built successfully!",
583
+ schemasGenerated: "Schema files generated successfully!",
584
+ registryGenerated: (registryName) => `${registryName} registry generated successfully!`,
585
+ depsAvailable: "All dependencies already available",
586
+ cacheCleared: "UI8Kit cache cleared successfully."
587
+ },
588
+ // Info messages
589
+ info: {
590
+ tryListComponents: (registryType) => `Run "npx ui8kit@latest add --all --registry ${registryType}" to list available components`,
591
+ initializing: (registryName) => `Initializing UI8Kit in your project (${registryName} registry)...`,
592
+ installing: (registryName) => `Installing from ${registryName} registry...`,
593
+ installingAll: (registryName) => `Installing all available components from ${registryName} registry...`,
594
+ retryEnabled: "Aggressive retry mode enabled (3 attempts per request)",
595
+ fetchingComponentList: (registryType) => `Fetching component list from ${registryType}...`,
596
+ fetchingRegistry: (registryType) => `Fetching ${registryType} registry index`,
597
+ scanningComponents: (registryName) => `Scanning ${registryName} components...`,
598
+ building: "Building registry...",
599
+ processingComponents: "Processing components...",
600
+ scanningDirectories: "Scanning directories...",
601
+ analyzingDeps: "Found {count} components, analyzing dependencies...",
602
+ installationCancelled: "Initialization cancelled.",
603
+ workspaceDepsDetected: "Workspace dependency detected. Installing individually...",
604
+ checkingConnection: "Checking internet connection...",
605
+ testing: (registryType, baseUrl) => `Testing ${registryType} CDN: ${baseUrl}`,
606
+ using: (registryType, baseUrl) => `Using ${registryType} CDN: ${baseUrl}`,
607
+ loading: (name, registryType, folder, type) => `Loading ${name} from /${registryType}/${folder}/ (type: ${type})`,
608
+ fetching: (registryType, url) => `Fetching from ${registryType}: ${url}`,
609
+ listingComponents: "Listing available components",
610
+ fetchingUrl: "Fetching component from:",
611
+ fetchingUrlWithRetry: "Fetching component from URL with retry:",
612
+ checkingForUpdates: "Checking for component updates...",
613
+ localDiffSummary: "Local components compared with registry."
614
+ },
615
+ // Prompts
616
+ prompts: {
617
+ globalCss: "Where is your global CSS file?",
618
+ aliasComponents: "Configure import aliases?",
619
+ overwrite: (registryName) => `UI8Kit is already initialized for ${registryName} registry. Overwrite configuration?`
620
+ },
621
+ // Examples and help text
622
+ examples: {
623
+ add: [
624
+ "Example: npx ui8kit@latest add button card",
625
+ "Example: npx ui8kit@latest add button --registry ui",
626
+ "Example: npx ui8kit@latest add all # Install all components",
627
+ "Example: npx ui8kit@latest add --all --registry ui # Install all ui components",
628
+ "Example: npx ui8kit@latest add --retry # Enable retry for unreliable connections",
629
+ 'Example: npx ui8kit@latest add "https://example.com/component.json"'
630
+ ],
631
+ init: [
632
+ `npx ui8kit@latest add button --registry ui - Add a button component`,
633
+ `npx ui8kit@latest add card input --registry ui - Add multiple components`,
634
+ `npx ui8kit@latest add --all --registry ui - Add all components`,
635
+ `npx ui8kit@latest add "https://example.com/component.json" - Add from external URL`
636
+ ],
637
+ troubleshooting: [
638
+ "Check your internet connection",
639
+ "Use --retry flag: npx ui8kit@latest add --all --retry",
640
+ "Use VPN if available",
641
+ "Install from URL: npx ui8kit@latest add 'https://...'",
642
+ "Check https://ui.buildy.tw for manual download"
643
+ ]
644
+ },
645
+ // Directory descriptions
646
+ directories: {
647
+ lib: "Utils, helpers, functions",
648
+ variants: "CVA variant configurations",
649
+ "components/ui": "UI components",
650
+ components: "Complex components",
651
+ layouts: "Page layouts and structures",
652
+ blocks: "Component blocks"
653
+ },
654
+ // Spinners and status
655
+ status: {
656
+ installing: (name, registryType) => `Installing ${name} from ${registryType}...`,
657
+ wouldInstall: (name, registryType) => `Would install: ${name} from ${registryType}`,
658
+ foundComponents: (count, registryType) => `Found ${count} components in ${registryType}`,
659
+ wouldInstallFrom: (registryType) => `Would install from ${registryType}:`,
660
+ builtComponents: (count) => `Built ${count} components`,
661
+ scannedComponents: (count) => `Scanned ${count} components`,
662
+ skipped: (fileName) => `Skipped ${fileName} (already exists, use --force to overwrite)`
663
+ }
664
+ };
665
+
666
+ // src/utils/package-manager.ts
667
+ import chalk3 from "chalk";
668
+ import ora2 from "ora";
669
+ import path4 from "path";
670
+ import fs4 from "fs-extra";
671
+ import { execa } from "execa";
672
+
673
+ // src/utils/dependency-checker.ts
674
+ import fs3 from "fs-extra";
675
+ import path3 from "path";
676
+ import chalk2 from "chalk";
677
+ async function checkProjectDependencies(requiredDeps) {
678
+ const packageJsonPath = path3.join(process.cwd(), "package.json");
679
+ if (!await fs3.pathExists(packageJsonPath)) {
680
+ return {
681
+ installed: [],
682
+ missing: requiredDeps,
683
+ workspace: []
684
+ };
685
+ }
686
+ try {
687
+ const packageJson = await fs3.readJson(packageJsonPath);
688
+ const allDeps = {
689
+ ...packageJson.dependencies,
690
+ ...packageJson.devDependencies
691
+ };
692
+ const installed = [];
693
+ const missing = [];
694
+ const workspace = [];
695
+ for (const dep of requiredDeps) {
696
+ const version = allDeps[dep];
697
+ if (!version) {
698
+ missing.push(dep);
699
+ } else if (version.startsWith("workspace:")) {
700
+ workspace.push(dep);
701
+ } else {
702
+ installed.push(dep);
703
+ }
704
+ }
705
+ return { installed, missing, workspace };
706
+ } catch (error) {
707
+ return {
708
+ installed: [],
709
+ missing: requiredDeps,
710
+ workspace: []
711
+ };
712
+ }
713
+ }
714
+ function showDependencyStatus(deps) {
715
+ if (deps.installed.length > 0) {
716
+ console.log(chalk2.green(` \u2705 Already installed: ${deps.installed.join(", ")}`));
717
+ }
718
+ if (deps.workspace.length > 0) {
719
+ console.log(chalk2.blue(` \u{1F517} Workspace dependencies: ${deps.workspace.join(", ")}`));
720
+ }
721
+ if (deps.missing.length > 0) {
722
+ console.log(chalk2.yellow(` \u{1F4E6} Will install: ${deps.missing.join(", ")}`));
723
+ }
724
+ }
725
+ async function filterMissingDependencies(dependencies) {
726
+ const status = await checkProjectDependencies(dependencies);
727
+ return status.missing;
728
+ }
729
+ function isWorkspaceError(errorMessage) {
730
+ return errorMessage.includes("EUNSUPPORTEDPROTOCOL") || errorMessage.includes("workspace:") || errorMessage.includes('Unsupported URL Type "workspace:"');
731
+ }
732
+
733
+ // src/utils/package-manager.ts
734
+ async function installDependencies(dependencies, options = {}) {
735
+ const useSpinner = options.useSpinner ?? true;
736
+ const spinner = useSpinner ? ora2(options.spinnerText ?? CLI_MESSAGES.status.installing("dependencies", "")).start() : null;
737
+ try {
738
+ const depStatus = await checkProjectDependencies(dependencies);
739
+ const missingDependencies = await filterMissingDependencies(dependencies);
740
+ if (missingDependencies.length === 0) {
741
+ spinner?.succeed(CLI_MESSAGES.success.depsAvailable);
742
+ if (depStatus.workspace.length > 0) {
743
+ console.log(chalk3.blue(` \u{1F517} Using workspace dependencies: ${depStatus.workspace.join(", ")}`));
744
+ }
745
+ return;
746
+ }
747
+ showDependencyStatus(depStatus);
748
+ const packageManager = await detectPackageManager();
749
+ const installCommand = packageManager === "npm" ? ["install", ...missingDependencies] : ["add", ...missingDependencies];
750
+ await execa(packageManager, installCommand, {
751
+ cwd: process.cwd(),
752
+ stdio: "pipe"
753
+ });
754
+ spinner?.succeed(CLI_MESSAGES.success.depsInstalled);
755
+ } catch (error) {
756
+ spinner?.fail(CLI_MESSAGES.errors.dependenciesFailed);
757
+ const errorMessage = error.stderr || error.message;
758
+ if (isWorkspaceError(errorMessage)) {
759
+ console.log(chalk3.yellow(`
760
+ \u{1F4A1} ${CLI_MESSAGES.info.workspaceDepsDetected}`));
761
+ const results = await installDependenciesIndividually(dependencies);
762
+ if (results.some((result) => result.success)) {
763
+ console.log(chalk3.green("\u2705 Some dependencies installed successfully"));
764
+ return;
765
+ }
766
+ }
767
+ throw new Error(`${CLI_MESSAGES.errors.dependenciesFailed}: ${errorMessage}`);
768
+ }
769
+ }
770
+ async function installDependenciesIndividually(dependencies) {
771
+ const packageManager = await detectPackageManager();
772
+ const results = [];
773
+ const missingDeps = await filterMissingDependencies(dependencies);
774
+ for (const dep of missingDeps) {
775
+ try {
776
+ const installCommand = packageManager === "npm" ? ["install", dep] : ["add", dep];
777
+ await execa(packageManager, installCommand, {
778
+ cwd: process.cwd(),
779
+ stdio: "pipe"
780
+ });
781
+ console.log(chalk3.green(` \u2705 Installed ${dep}`));
782
+ results.push({ dep, success: true });
783
+ } catch {
784
+ console.log(chalk3.yellow(` \u26A0\uFE0F Skipped ${dep} (may already be available)`));
785
+ results.push({ dep, success: false });
786
+ }
787
+ }
788
+ return results;
789
+ }
790
+ async function detectPackageManager() {
791
+ let dir = process.cwd();
792
+ while (true) {
793
+ if (await fs4.pathExists(path4.join(dir, "bun.lock")) || await fs4.pathExists(path4.join(dir, "bun.lockb"))) {
794
+ return "bun";
795
+ }
796
+ if (await fs4.pathExists(path4.join(dir, "pnpm-lock.yaml")))
797
+ return "pnpm";
798
+ if (await fs4.pathExists(path4.join(dir, "yarn.lock")))
799
+ return "yarn";
800
+ const packageJsonPath = path4.join(dir, "package.json");
801
+ if (await fs4.pathExists(packageJsonPath)) {
802
+ try {
803
+ const packageJson = await fs4.readJson(packageJsonPath);
804
+ const packageManager = String(packageJson.packageManager ?? "");
805
+ if (packageManager.startsWith("bun@"))
806
+ return "bun";
807
+ if (packageManager.startsWith("pnpm@"))
808
+ return "pnpm";
809
+ if (packageManager.startsWith("yarn@"))
810
+ return "yarn";
811
+ if (packageManager.startsWith("npm@"))
812
+ return "npm";
813
+ } catch {
814
+ }
815
+ }
816
+ const parent = path4.dirname(dir);
817
+ if (parent === dir)
818
+ break;
819
+ dir = parent;
820
+ }
821
+ return "npm";
822
+ }
823
+
824
+ // src/commands/init.ts
825
+ import path5 from "path";
826
+ import fs5 from "fs-extra";
827
+ import fetch2 from "node-fetch";
828
+
829
+ // src/utils/errors.ts
830
+ import chalk4 from "chalk";
831
+ var Ui8kitError = class extends Error {
832
+ suggestion;
833
+ constructor(message, suggestion) {
834
+ super(message);
835
+ this.name = this.constructor.name;
836
+ this.suggestion = suggestion;
837
+ }
838
+ };
839
+ var ConfigNotFoundError = class extends Ui8kitError {
840
+ constructor(registry) {
841
+ super(
842
+ `ui8kit config not found for registry "${registry}".`,
843
+ `Run: npx ui8kit@latest init --registry ${registry}`
844
+ );
845
+ }
846
+ };
847
+ function handleError(error) {
848
+ if (error instanceof Ui8kitError) {
849
+ if (error.message) {
850
+ console.error(chalk4.red(error.message));
851
+ }
852
+ if (error.suggestion) {
853
+ console.log(chalk4.yellow(`\u{1F4A1} ${error.suggestion}`));
854
+ }
855
+ process.exit(1);
856
+ }
857
+ if (isZodError(error)) {
858
+ console.error(chalk4.red("\u274C Configuration validation error:"));
859
+ error.errors.forEach((issue) => {
860
+ const path13 = issue.path.join(".") || "root";
861
+ console.log(chalk4.yellow(` - ${path13}: ${issue.message}`));
862
+ });
863
+ process.exit(1);
864
+ }
865
+ console.error(chalk4.red("\u274C Unexpected error:"));
866
+ console.error(chalk4.red(error.message ?? String(error)));
867
+ process.exit(1);
868
+ }
869
+ function isZodError(error) {
870
+ return Boolean(error && typeof error === "object" && "issues" in error);
871
+ }
872
+
873
+ // src/commands/init.ts
874
+ function buildInitConfig(options) {
875
+ const registryName = options.registry || SCHEMA_CONFIG.defaultRegistryType;
876
+ const aliases = SCHEMA_CONFIG.defaultAliases;
877
+ const globalCss = options.globalCss || "src/index.css";
878
+ const aliasComponents = options.aliasComponents?.trim() || "@/components";
879
+ if (options.yes) {
880
+ return {
881
+ $schema: `${SCHEMA_CONFIG.baseUrl}.json`,
882
+ framework: "vite-react",
883
+ typescript: true,
884
+ globalCss,
885
+ aliases,
886
+ registry: SCHEMA_CONFIG.defaultRegistry,
887
+ componentsDir: SCHEMA_CONFIG.defaultDirectories.components,
888
+ libDir: SCHEMA_CONFIG.defaultDirectories.lib
889
+ };
890
+ }
891
+ return {
892
+ $schema: `${SCHEMA_CONFIG.baseUrl}.json`,
893
+ framework: "vite-react",
894
+ typescript: true,
895
+ globalCss,
896
+ aliases: { ...aliases, "@/components": aliasComponents },
897
+ registry: SCHEMA_CONFIG.defaultRegistry,
898
+ componentsDir: SCHEMA_CONFIG.defaultDirectories.components,
899
+ libDir: SCHEMA_CONFIG.defaultDirectories.lib
900
+ };
901
+ }
902
+ async function initCommand(options) {
903
+ const registryName = options.registry || SCHEMA_CONFIG.defaultRegistryType;
904
+ logger.info(CLI_MESSAGES.info.initializing(registryName));
905
+ const viteDetected = await isViteProject();
906
+ if (!viteDetected) {
907
+ console.error(chalk5.red(`\u274C ${CLI_MESSAGES.errors.notViteProject}`));
908
+ console.log("Please run this command in a Vite project directory.");
909
+ process.exit(1);
910
+ }
911
+ if (!await hasReact()) {
912
+ console.error(chalk5.red(`\u274C ${CLI_MESSAGES.errors.reactNotInstalled}`));
913
+ console.log("Please install React first: npm install react react-dom");
914
+ process.exit(1);
915
+ }
916
+ const existingConfig = await findConfig(registryName);
917
+ if (existingConfig && !options.yes) {
918
+ const { overwrite } = await prompts({
919
+ type: "confirm",
920
+ name: "overwrite",
921
+ message: CLI_MESSAGES.prompts.overwrite(registryName),
922
+ initial: false
923
+ });
924
+ if (!overwrite) {
925
+ logger.warn(CLI_MESSAGES.info.installationCancelled);
926
+ return;
927
+ }
928
+ }
929
+ let config;
930
+ if (options.yes) {
931
+ config = buildInitConfig({ yes: true, registry: registryName });
932
+ } else {
933
+ const responses = await prompts([
934
+ {
935
+ type: "text",
936
+ name: "globalCss",
937
+ message: CLI_MESSAGES.prompts.globalCss,
938
+ initial: "src/index.css"
939
+ },
940
+ {
941
+ type: "text",
942
+ name: "aliasComponents",
943
+ message: CLI_MESSAGES.prompts.aliasComponents,
944
+ initial: "@/components"
945
+ }
946
+ ]);
947
+ const aliasComponents = responses.aliasComponents?.trim() || "@/components";
948
+ const globalCss = responses.globalCss || "src/index.css";
949
+ config = buildInitConfig({
950
+ yes: false,
951
+ registry: registryName,
952
+ globalCss,
953
+ aliasComponents
954
+ });
955
+ }
956
+ const spinner = ora3(CLI_MESSAGES.info.initializing(registryName)).start();
957
+ try {
958
+ await saveConfig(config);
959
+ await ensureDir(config.libDir);
960
+ await ensureDir(config.componentsDir);
961
+ await ensureDir(path5.join(config.componentsDir, "ui"));
962
+ await ensureDir(SCHEMA_CONFIG.defaultDirectories.blocks);
963
+ await ensureDir(SCHEMA_CONFIG.defaultDirectories.layouts);
964
+ await ensureDir(SCHEMA_CONFIG.defaultDirectories.variants);
965
+ spinner.text = "Installing core utilities and variants...";
966
+ await installCoreFiles(registryName, config, spinner);
967
+ spinner.text = "Installing core dependencies...";
968
+ await installDependencies(["clsx", "tailwind-merge"], {
969
+ useSpinner: false
970
+ });
971
+ spinner.succeed(CLI_MESSAGES.success.initialized(registryName));
972
+ logger.success(`
973
+ \u2705 ${CLI_MESSAGES.success.setupComplete(registryName)}`);
974
+ console.log("\nDirectories created:");
975
+ console.log(` ${chalk5.cyan("src/lib/")} - Utils, helpers, functions`);
976
+ console.log(` ${chalk5.cyan("src/variants/")} - CVA variant configurations`);
977
+ console.log(` ${chalk5.cyan("src/components/ui/")} - UI components`);
978
+ console.log(` ${chalk5.cyan("src/components/")} - Complex components`);
979
+ console.log(` ${chalk5.cyan("src/layouts/")} - Page layouts and structures`);
980
+ console.log(` ${chalk5.cyan("src/blocks/")} - Component blocks`);
981
+ console.log("\nNext steps:");
982
+ CLI_MESSAGES.examples.init.forEach((example) => console.log(` ${chalk5.cyan(example)}`));
983
+ } catch (error) {
984
+ spinner.fail(CLI_MESSAGES.errors.buildFailed);
985
+ handleError(error);
986
+ }
987
+ }
988
+ async function installCoreFiles(registryType, config, spinner) {
989
+ const cdnUrls = getCdnUrls(registryType);
990
+ let registryIndex = null;
991
+ for (const baseUrl of cdnUrls) {
992
+ try {
993
+ const indexUrl = `${baseUrl}/index.json`;
994
+ const response = await fetch2(indexUrl);
995
+ if (response.ok) {
996
+ registryIndex = await response.json();
997
+ break;
998
+ }
999
+ } catch {
1000
+ continue;
1001
+ }
1002
+ }
1003
+ if (!registryIndex) {
1004
+ spinner.text = "\u26A0\uFE0F Could not fetch registry index, creating local utils...";
1005
+ await createUtilsFile(config.libDir, config.typescript);
1006
+ return;
1007
+ }
1008
+ const variantItems = registryIndex.components.filter((c) => c.type === "registry:variants");
1009
+ const libItems = registryIndex.components.filter((c) => c.type === "registry:lib");
1010
+ for (const item of libItems) {
1011
+ spinner.text = `Installing ${item.name}...`;
1012
+ await installComponentFromRegistry(item.name, "registry:lib", cdnUrls, config);
1013
+ }
1014
+ for (const item of variantItems) {
1015
+ spinner.text = `Installing variant: ${item.name}...`;
1016
+ await installComponentFromRegistry(item.name, "registry:variants", cdnUrls, config);
1017
+ }
1018
+ spinner.text = "Syncing variants index...";
1019
+ const variantsIndexStatus = await installVariantsIndex(cdnUrls);
1020
+ if (variantsIndexStatus === "updated") {
1021
+ spinner.text = "Updated variants/index.ts from CDN";
1022
+ } else if (variantsIndexStatus === "created") {
1023
+ spinner.text = "Created variants/index.ts from CDN";
1024
+ } else if (variantsIndexStatus === "unchanged") {
1025
+ spinner.text = "variants/index.ts is up to date";
1026
+ } else {
1027
+ spinner.text = "variants/index.ts not found in registry (skipped)";
1028
+ }
1029
+ spinner.text = `\u2705 Installed ${libItems.length} utilities and ${variantItems.length} variants`;
1030
+ }
1031
+ async function installComponentFromRegistry(name, type, cdnUrls, config) {
1032
+ const folder = type === "registry:lib" ? "lib" : type === "registry:variants" ? "components/variants" : "components/ui";
1033
+ for (const baseUrl of cdnUrls) {
1034
+ try {
1035
+ const url = `${baseUrl}/${folder}/${name}.json`;
1036
+ const response = await fetch2(url);
1037
+ if (response.ok) {
1038
+ const component = await response.json();
1039
+ for (const file of component.files) {
1040
+ const fileName = path5.basename(file.path);
1041
+ let targetDir;
1042
+ if (type === "registry:lib") {
1043
+ targetDir = config.libDir;
1044
+ } else if (type === "registry:variants") {
1045
+ targetDir = SCHEMA_CONFIG.defaultDirectories.variants;
1046
+ } else {
1047
+ targetDir = path5.join(config.componentsDir, "ui");
1048
+ }
1049
+ const targetPath = path5.join(process.cwd(), targetDir, fileName);
1050
+ await fs5.ensureDir(path5.dirname(targetPath));
1051
+ await fs5.writeFile(targetPath, file.content || "", "utf-8");
1052
+ }
1053
+ return;
1054
+ }
1055
+ } catch {
1056
+ continue;
1057
+ }
1058
+ }
1059
+ }
1060
+ async function installVariantsIndex(cdnUrls) {
1061
+ for (const baseUrl of cdnUrls) {
1062
+ try {
1063
+ const url = `${baseUrl}/components/variants/index.json`;
1064
+ const response = await fetch2(url);
1065
+ if (response.ok) {
1066
+ const component = await response.json();
1067
+ for (const file of component.files) {
1068
+ const fileName = path5.basename(file.path);
1069
+ if (!fileName.startsWith("index.")) {
1070
+ continue;
1071
+ }
1072
+ const targetDir = SCHEMA_CONFIG.defaultDirectories.variants;
1073
+ const targetPath = path5.join(process.cwd(), targetDir, fileName);
1074
+ const incomingContent = file.content || "";
1075
+ const exists = await fs5.pathExists(targetPath);
1076
+ if (exists) {
1077
+ const currentContent = await fs5.readFile(targetPath, "utf-8");
1078
+ if (currentContent === incomingContent) {
1079
+ return "unchanged";
1080
+ }
1081
+ }
1082
+ await fs5.ensureDir(path5.dirname(targetPath));
1083
+ await fs5.writeFile(targetPath, incomingContent, "utf-8");
1084
+ return exists ? "updated" : "created";
1085
+ }
1086
+ }
1087
+ } catch {
1088
+ continue;
1089
+ }
1090
+ }
1091
+ return "skipped";
1092
+ }
1093
+ async function createUtilsFile(libDir, typescript) {
1094
+ const utilsContent = `import { type ClassValue, clsx } from "clsx"
1095
+ import { twMerge } from "tailwind-merge"
1096
+
1097
+ export function cn(...inputs: ClassValue[]) {
1098
+ return twMerge(clsx(inputs))
1099
+ }`;
1100
+ const fileName = typescript ? "utils.ts" : "utils.js";
1101
+ const filePath = path5.join(process.cwd(), libDir, fileName);
1102
+ await fs5.writeFile(filePath, utilsContent, "utf-8");
1103
+ }
1104
+
1105
+ // src/utils/registry-validator.ts
1106
+ async function validateComponentInstallation(components, registryType) {
1107
+ const packageJsonPath = path6.join(process.cwd(), "package.json");
1108
+ if (!await fs6.pathExists(packageJsonPath)) {
1109
+ return {
1110
+ isValid: false,
1111
+ message: "No package.json found in the current directory. Run this command from your project root."
1112
+ };
1113
+ }
1114
+ const nodeMajorVersion = Number.parseInt(process.versions.node.split(".")[0] ?? "0", 10);
1115
+ if (Number.isNaN(nodeMajorVersion) || nodeMajorVersion < 18) {
1116
+ return {
1117
+ isValid: false,
1118
+ message: `Node.js 18+ is required. Current version: ${process.versions.node}`
1119
+ };
1120
+ }
1121
+ const existingConfig = await findConfig(registryType);
1122
+ if (!existingConfig) {
1123
+ const { runInit } = await prompts2({
1124
+ type: "confirm",
1125
+ name: "runInit",
1126
+ message: "ui8kit.config.json not found. Run init now?",
1127
+ initial: true
1128
+ });
1129
+ if (runInit) {
1130
+ await initCommand({ registry: registryType });
1131
+ const configAfterInit = await findConfig(registryType);
1132
+ if (configAfterInit) {
1133
+ return { isValid: true };
1134
+ }
1135
+ }
1136
+ return {
1137
+ isValid: false,
1138
+ message: `ui8kit is not initialized. Run: npx ui8kit@latest init --registry ${registryType}`
1139
+ };
1140
+ }
1141
+ return { isValid: true };
1142
+ }
1143
+ function handleValidationError(result) {
1144
+ console.error(chalk6.red("\u274C Registry Validation Error:"));
1145
+ console.error(chalk6.red(result.message));
1146
+ if (result.missingComponents && result.missingComponents.length > 0) {
1147
+ console.log(chalk6.yellow("\n\u{1F4A1} Suggestion:"));
1148
+ console.log(`Install missing components first: ${chalk6.cyan(`npx ui8kit add ${result.missingComponents.join(" ")}`)}
1149
+ `);
1150
+ }
1151
+ process.exit(1);
1152
+ }
1153
+
1154
+ // src/utils/dependency-resolver.ts
1155
+ async function resolveRegistryTree(componentNames, registryType, getComponent2) {
1156
+ const componentsByName = /* @__PURE__ */ new Map();
1157
+ const state = /* @__PURE__ */ new Map();
1158
+ const visitStack = [];
1159
+ const normalized = componentNames.map((name) => name.toLowerCase());
1160
+ const ensureComponent = async (name) => {
1161
+ const key = name.toLowerCase();
1162
+ if (state.get(key) === "done") {
1163
+ return componentsByName.get(key) ?? null;
1164
+ }
1165
+ if (state.get(key) === "visiting") {
1166
+ const cycle = visitStack.includes(key) ? [...visitStack, key].join(" -> ") : key;
1167
+ logger.warn(`Circular registry dependency detected: ${cycle}`);
1168
+ return componentsByName.get(key) ?? null;
1169
+ }
1170
+ const component = await getComponent2(name, registryType);
1171
+ if (!component) {
1172
+ logger.warn(`Component ${name} not found in ${registryType} registry, skipping`);
1173
+ state.set(key, "done");
1174
+ return null;
1175
+ }
1176
+ componentsByName.set(key, component);
1177
+ state.set(key, "visiting");
1178
+ visitStack.push(key);
1179
+ for (const dependency of component.registryDependencies ?? []) {
1180
+ await ensureComponent(dependency);
1181
+ }
1182
+ visitStack.pop();
1183
+ state.set(key, "done");
1184
+ return component;
1185
+ };
1186
+ for (const name of normalized) {
1187
+ await ensureComponent(name);
1188
+ }
1189
+ const graph = /* @__PURE__ */ new Map();
1190
+ const inDegree = /* @__PURE__ */ new Map();
1191
+ for (const component of componentsByName.values()) {
1192
+ const name = component.name.toLowerCase();
1193
+ if (!graph.has(name)) {
1194
+ graph.set(name, /* @__PURE__ */ new Set());
1195
+ inDegree.set(name, 0);
1196
+ }
1197
+ }
1198
+ for (const component of componentsByName.values()) {
1199
+ const from = component.name.toLowerCase();
1200
+ const deps = component.registryDependencies ?? [];
1201
+ for (const dep of deps) {
1202
+ const to = dep.toLowerCase();
1203
+ if (!componentsByName.has(to)) {
1204
+ continue;
1205
+ }
1206
+ const targets = graph.get(to);
1207
+ if (targets) {
1208
+ targets.add(from);
1209
+ } else {
1210
+ graph.set(to, /* @__PURE__ */ new Set([from]));
1211
+ }
1212
+ inDegree.set(from, (inDegree.get(from) ?? 0) + 1);
1213
+ }
1214
+ }
1215
+ const queue = [];
1216
+ inDegree.forEach((value, name) => {
1217
+ if (value === 0) {
1218
+ queue.push(name);
1219
+ }
1220
+ });
1221
+ const orderedKeys = [];
1222
+ while (queue.length > 0) {
1223
+ const current = queue.shift();
1224
+ if (!current)
1225
+ continue;
1226
+ orderedKeys.push(current);
1227
+ const targets = graph.get(current) || /* @__PURE__ */ new Set();
1228
+ for (const next of targets) {
1229
+ const degree = (inDegree.get(next) ?? 1) - 1;
1230
+ inDegree.set(next, degree);
1231
+ if (degree === 0) {
1232
+ queue.push(next);
1233
+ }
1234
+ }
1235
+ }
1236
+ for (const key of componentsByName.keys()) {
1237
+ if (!orderedKeys.includes(key)) {
1238
+ logger.warn(`Unresolved dependency cycle detected for ${key}, appending in current order`);
1239
+ orderedKeys.push(key);
1240
+ }
1241
+ }
1242
+ return orderedKeys.map((key) => componentsByName.get(key)).filter((component) => Boolean(component));
1243
+ }
1244
+
1245
+ // src/utils/diff-utils.ts
1246
+ import { createTwoFilesPatch } from "diff";
1247
+ function buildUnifiedDiff(oldFilePath, newFilePath, oldContent, newContent) {
1248
+ return createTwoFilesPatch(
1249
+ oldFilePath,
1250
+ newFilePath,
1251
+ oldContent,
1252
+ newContent,
1253
+ "local",
1254
+ "registry",
1255
+ { context: 3 }
1256
+ );
1257
+ }
1258
+ function hasDiff(oldContent, newContent) {
1259
+ return oldContent !== newContent;
1260
+ }
1261
+ function formatDiffPreview(diff, maxLines = 80) {
1262
+ const lines = diff.split("\n");
1263
+ if (lines.length <= maxLines) {
1264
+ return diff;
1265
+ }
1266
+ return `${lines.slice(0, maxLines).join("\n")}
1267
+ ...`;
1268
+ }
1269
+
1270
+ // src/utils/transform.ts
1271
+ import path7 from "path";
1272
+ import ts from "typescript";
1273
+ var IMPORT_NODE_KIND = ts.SyntaxKind.ImportDeclaration;
1274
+ function normalizeAliasKey(alias) {
1275
+ return alias.replace(/\\/g, "/").replace(/\/+$/, "");
1276
+ }
1277
+ function toPosix(value) {
1278
+ return value.replace(/\\/g, "/");
1279
+ }
1280
+ function normalizeAliasTarget(alias) {
1281
+ return toPosix(alias).replace(/\/+$/, "");
1282
+ }
1283
+ function normalizeDefaultAliases() {
1284
+ const map = /* @__PURE__ */ new Map();
1285
+ for (const [alias, target] of Object.entries(SCHEMA_CONFIG.defaultAliases)) {
1286
+ map.set(normalizeAliasKey(alias), normalizeAliasTarget(target));
1287
+ }
1288
+ return map;
1289
+ }
1290
+ function normalizeConfiguredAliases(aliasMap) {
1291
+ const normalized = /* @__PURE__ */ new Map();
1292
+ for (const [alias, target] of Object.entries(aliasMap)) {
1293
+ normalized.set(normalizeAliasKey(alias), normalizeAliasTarget(target));
1294
+ }
1295
+ return normalized;
1296
+ }
1297
+ function pickAliasForImport(importPath, configuredAliases) {
1298
+ const pathValue = toPosix(importPath);
1299
+ if (!pathValue.startsWith("@/")) {
1300
+ return void 0;
1301
+ }
1302
+ const trimmed = pathValue.slice(2);
1303
+ const [root] = trimmed.split("/");
1304
+ const rootAlias = `@/${root}`;
1305
+ const directAlias = Array.from(configuredAliases.keys()).filter((alias) => pathValue === alias || pathValue.startsWith(`${alias}/`)).sort((a, b) => b.length - a.length)[0];
1306
+ if (directAlias) {
1307
+ const aliasValue = configuredAliases.get(directAlias);
1308
+ if (!aliasValue || !aliasValue.startsWith("@/")) {
1309
+ return void 0;
1310
+ }
1311
+ const remainder = pathValue.slice(directAlias.length).replace(/^\/+/, "");
1312
+ if (!remainder) {
1313
+ return aliasValue;
1314
+ }
1315
+ const remainderParts = remainder.split("/");
1316
+ const targetParts = normalizeAliasKey(aliasValue).replace(/^@\//, "").split("/");
1317
+ const aliasTail = targetParts[targetParts.length - 1];
1318
+ const normalizedRemainder = remainderParts[0] === aliasTail ? remainderParts.slice(1).join("/") : remainder;
1319
+ return normalizedRemainder ? `${aliasValue}/${normalizedRemainder}` : aliasValue;
1320
+ }
1321
+ const defaultAliasCandidates = Array.from(normalizeDefaultAliases().keys()).filter((alias) => pathValue === alias || pathValue.startsWith(`${alias}/`)).sort((a, b) => b.length - a.length);
1322
+ for (const defaultAlias of defaultAliasCandidates) {
1323
+ const defaultParts = normalizeAliasKey(defaultAlias).replace(/^@\//, "").split("/");
1324
+ const remainderFromDefault = trimmed.split("/").slice(defaultParts.length);
1325
+ if (remainderFromDefault.length === 0) {
1326
+ continue;
1327
+ }
1328
+ const candidateAliasTail = remainderFromDefault[0];
1329
+ const candidateAlias = `@/${candidateAliasTail}`;
1330
+ if (configuredAliases.has(candidateAlias)) {
1331
+ const remainderPath = remainderFromDefault.slice(1).join("/");
1332
+ return remainderPath ? `${candidateAlias}/${remainderPath}` : candidateAlias;
1333
+ }
1334
+ }
1335
+ return void 0;
1336
+ }
1337
+ function rewriteModuleSpecifier(specifierText, configuredAliases) {
1338
+ if (!specifierText.startsWith("@/")) {
1339
+ return specifierText;
1340
+ }
1341
+ const aliasesMap = normalizeConfiguredAliases(configuredAliases);
1342
+ const rewrittenRemainder = pickAliasForImport(specifierText, aliasesMap);
1343
+ if (!rewrittenRemainder || rewrittenRemainder === normalizeAliasKey(specifierText)) {
1344
+ return specifierText;
1345
+ }
1346
+ if (rewrittenRemainder) {
1347
+ return rewrittenRemainder;
1348
+ }
1349
+ return specifierText;
1350
+ }
1351
+ function transformImports(content, aliases) {
1352
+ const sourceFile = ts.createSourceFile("component.tsx", content, ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX);
1353
+ const importSpans = [];
1354
+ const configuredAliases = normalizeConfiguredAliases(aliases);
1355
+ function visit(node) {
1356
+ if (node.kind === IMPORT_NODE_KIND && ts.isImportDeclaration(node)) {
1357
+ const moduleSpecifier = node.moduleSpecifier;
1358
+ if (ts.isStringLiteral(moduleSpecifier)) {
1359
+ const value = moduleSpecifier.text;
1360
+ const rewritten = rewriteModuleSpecifier(value, Object.fromEntries(configuredAliases));
1361
+ if (rewritten !== value) {
1362
+ importSpans.push({
1363
+ start: moduleSpecifier.getStart(sourceFile),
1364
+ end: moduleSpecifier.getEnd(),
1365
+ replacement: `"${rewritten}"`
1366
+ });
1367
+ }
1368
+ }
1369
+ }
1370
+ ts.forEachChild(node, visit);
1371
+ }
1372
+ ts.forEachChild(sourceFile, visit);
1373
+ if (importSpans.length === 0) {
1374
+ return content;
1375
+ }
1376
+ importSpans.sort((a, b) => b.start - a.start);
1377
+ let transformed = content;
1378
+ for (const span of importSpans) {
1379
+ transformed = `${transformed.slice(0, span.start)}${span.replacement}${transformed.slice(span.end)}`;
1380
+ }
1381
+ return transformed;
1382
+ }
1383
+ function transformCleanup(content) {
1384
+ return content.replace(/\r\n/g, "\n").replace(/\n{3,}/g, "\n\n").trimEnd() + "\n";
1385
+ }
1386
+ function applyTransforms(content, aliases) {
1387
+ const withImports = transformImports(content, aliases);
1388
+ return transformCleanup(withImports);
1389
+ }
1390
+ function shouldTransformFile(fileName) {
1391
+ return [".ts", ".tsx"].includes(path7.extname(fileName));
1392
+ }
1393
+
1394
+ // src/commands/add.ts
1395
+ var ADD_EXCLUDED_COMPONENT_TYPES = ["registry:variants", "registry:lib"];
1396
+ async function addCommand(components, options) {
1397
+ const registryType = resolveRegistryType(options.registry);
1398
+ const requestOptions = {
1399
+ excludeTypes: ADD_EXCLUDED_COMPONENT_TYPES,
1400
+ maxRetries: options.retry ? 3 : 1,
1401
+ noCache: options.cache === false
1402
+ };
1403
+ try {
1404
+ if (options.all || components.includes("all")) {
1405
+ await addAllComponents(options, registryType, requestOptions);
1406
+ return;
1407
+ }
1408
+ const selectedComponents = components.length > 0 ? components : await pickComponentsFromPrompt(registryType, requestOptions);
1409
+ if (selectedComponents.length === 0) {
1410
+ logger.warn(CLI_MESSAGES.errors.noComponentsSpecified);
1411
+ return;
1412
+ }
1413
+ const validation = await validateComponentInstallation(selectedComponents, registryType);
1414
+ if (!validation.isValid) {
1415
+ handleValidationError(validation);
1416
+ }
1417
+ const config = await findConfig(registryType);
1418
+ if (!config) {
1419
+ throw new ConfigNotFoundError(registryType);
1420
+ }
1421
+ if (options.retry) {
1422
+ logger.info(CLI_MESSAGES.info.retryEnabled);
1423
+ }
1424
+ logger.info(CLI_MESSAGES.info.installing(registryType));
1425
+ const getComponentFn = (name, type) => getComponent(name, type, requestOptions);
1426
+ const results = await installRequestedComponents(
1427
+ selectedComponents,
1428
+ registryType,
1429
+ config,
1430
+ getComponentFn,
1431
+ options
1432
+ );
1433
+ displayInstallationSummary(registryType, results);
1434
+ } catch (error) {
1435
+ handleError(error);
1436
+ }
1437
+ }
1438
+ async function addAllComponents(options, registryType, requestOptions) {
1439
+ logger.info(CLI_MESSAGES.info.installingAll(registryType));
1440
+ const validation = await validateComponentInstallation([], registryType);
1441
+ if (!validation.isValid) {
1442
+ handleValidationError(validation);
1443
+ }
1444
+ const config = await findConfig(registryType);
1445
+ if (!config) {
1446
+ throw new ConfigNotFoundError(registryType);
1447
+ }
1448
+ const getAllComponentsFn = (type) => getAllComponents(type, requestOptions);
1449
+ if (options.retry) {
1450
+ logger.info(CLI_MESSAGES.info.retryEnabled);
1451
+ }
1452
+ const spinner = ora4(CLI_MESSAGES.info.fetchingComponentList(registryType)).start();
1453
+ try {
1454
+ const allComponents = await getAllComponentsFn(registryType);
1455
+ if (allComponents.length === 0) {
1456
+ spinner.fail(`No components found in ${registryType} registry`);
1457
+ logger.warn(`
1458
+ \u26A0\uFE0F ${registryType} ${CLI_MESSAGES.errors.registryTempUnavailable}`);
1459
+ console.log("Try these alternatives:");
1460
+ CLI_MESSAGES.examples.troubleshooting.forEach((alt) => console.log(` \u2022 ${alt}`));
1461
+ return;
1462
+ }
1463
+ spinner.succeed(CLI_MESSAGES.status.foundComponents(allComponents.length, registryType));
1464
+ if (options.dryRun) {
1465
+ await installRequestedComponents(
1466
+ allComponents.map((c) => c.name),
1467
+ registryType,
1468
+ config,
1469
+ (name, type) => getComponent(name, type, requestOptions),
1470
+ options,
1471
+ allComponents
1472
+ );
1473
+ return;
1474
+ }
1475
+ const results = await installRequestedComponents(
1476
+ allComponents.map((c) => c.name),
1477
+ registryType,
1478
+ config,
1479
+ (name, type) => getComponent(name, type, requestOptions),
1480
+ options,
1481
+ allComponents
1482
+ );
1483
+ await installComponentsIndex(registryType, config);
1484
+ displayInstallationSummary(registryType, results);
1485
+ } catch (error) {
1486
+ spinner.fail(CLI_MESSAGES.errors.failedToFetch(registryType));
1487
+ logger.error(`Error: ${error.message}`);
1488
+ logger.warn(`
1489
+ \u26A0\uFE0F ${registryType} ${CLI_MESSAGES.errors.registryTempUnavailable}`);
1490
+ console.log("Try these alternatives:");
1491
+ CLI_MESSAGES.examples.troubleshooting.forEach((alt) => console.log(` \u2022 ${alt}`));
1492
+ process.exit(1);
1493
+ }
1494
+ }
1495
+ async function processComponents(componentNames, registryType, config, getComponentFn, options, preloadedComponents, totalCount) {
1496
+ const results = [];
1497
+ const componentMap = new Map(preloadedComponents?.map((c) => [c.name.toLowerCase(), c]));
1498
+ const total = totalCount ?? componentNames.length;
1499
+ if (total > 1) {
1500
+ logger.info(`Installing ${total} components...`);
1501
+ }
1502
+ for (let i = 0; i < componentNames.length; i += 1) {
1503
+ const componentName = componentNames[i];
1504
+ const position = `${i + 1}/${total}`;
1505
+ const spinner = ora4(`[${position}] ${CLI_MESSAGES.status.installing(componentName, registryType)}`).start();
1506
+ try {
1507
+ const lookupName = componentName.toLowerCase();
1508
+ let component = componentMap?.get(lookupName) ?? null;
1509
+ if (!component) {
1510
+ component = await getComponentFn(componentName, registryType);
1511
+ }
1512
+ if (!component) {
1513
+ throw new Error(CLI_MESSAGES.errors.componentNotFound(componentName, registryType));
1514
+ }
1515
+ if (options.dryRun) {
1516
+ spinner.succeed(`[${position}] ${CLI_MESSAGES.status.wouldInstall(component.name, registryType)}`);
1517
+ logger.info(` Type: ${component.type}`);
1518
+ if (component.registryDependencies && component.registryDependencies.length > 0) {
1519
+ logger.info(` Registry deps: ${component.registryDependencies.join(" -> ")}`);
1520
+ }
1521
+ logger.info(` Files: ${component.files.length}`);
1522
+ logger.info(` Dependencies: ${component.dependencies.join(", ") || "none"}`);
1523
+ for (const file of component.files) {
1524
+ const fileName = path8.basename(file.path);
1525
+ const target = file.target || inferTargetFromType(component.type);
1526
+ const installDir = resolveInstallDir(target, config);
1527
+ const targetPath = path8.join(process.cwd(), installDir, fileName);
1528
+ const exists = await fs7.pathExists(targetPath);
1529
+ const status = exists ? "overwrite" : "create";
1530
+ logger.info(` ${status}: ${targetPath}`);
1531
+ if (exists) {
1532
+ const currentContent = await fs7.readFile(targetPath, "utf-8");
1533
+ const transformedIncoming = shouldTransformFile(fileName) ? applyTransforms(file.content, config.aliases) : file.content;
1534
+ const changed = hasDiff(currentContent, transformedIncoming);
1535
+ if (changed) {
1536
+ const patch = buildUnifiedDiff(targetPath, `${component.name}/${fileName}`, currentContent, transformedIncoming);
1537
+ console.log(formatDiffPreview(patch, 40));
1538
+ }
1539
+ }
1540
+ }
1541
+ if (component.dependencies.length > 0) {
1542
+ const depStatus = await checkProjectDependencies(component.dependencies);
1543
+ showDependencyStatus(depStatus);
1544
+ }
1545
+ continue;
1546
+ }
1547
+ await installComponentFiles(component, config, options.force);
1548
+ if (component.dependencies.length > 0) {
1549
+ try {
1550
+ await installDependencies(component.dependencies);
1551
+ } catch (error) {
1552
+ logger.warn(CLI_MESSAGES.errors.couldNotInstallDeps(component.name));
1553
+ logger.warn(` Dependencies: ${component.dependencies.join(", ")}`);
1554
+ logger.warn(" Please install them manually if needed");
1555
+ }
1556
+ }
1557
+ spinner.succeed(`[${position}] ${CLI_MESSAGES.status.installing(component.name, registryType)}`);
1558
+ results.push({ name: component.name, status: "success" });
1559
+ } catch (error) {
1560
+ spinner.fail(`[${position}] ${CLI_MESSAGES.errors.failedToInstall(componentName, registryType)}`);
1561
+ logger.error(` Error: ${error.message}`);
1562
+ results.push({
1563
+ name: componentName,
1564
+ status: "error",
1565
+ error: error.message
1566
+ });
1567
+ }
1568
+ }
1569
+ return results;
1570
+ }
1571
+ async function pickComponentsFromPrompt(registryType, requestOptions) {
1572
+ const allComponents = await getAllComponents(registryType, requestOptions);
1573
+ if (allComponents.length === 0) {
1574
+ logger.warn(`No components found in ${registryType} registry`);
1575
+ return [];
1576
+ }
1577
+ const sorted = allComponents.filter((component) => !ADD_EXCLUDED_COMPONENT_TYPES.includes(component.type)).sort((a, b) => {
1578
+ if (a.type === b.type) {
1579
+ return a.name.localeCompare(b.name);
1580
+ }
1581
+ return a.type.localeCompare(b.type);
1582
+ });
1583
+ const grouped = /* @__PURE__ */ new Map();
1584
+ for (const component of sorted) {
1585
+ const group = grouped.get(component.type) ?? [];
1586
+ group.push(component);
1587
+ if (!grouped.has(component.type)) {
1588
+ grouped.set(component.type, group);
1589
+ }
1590
+ }
1591
+ const choices = [];
1592
+ for (const [type, components] of grouped) {
1593
+ choices.push({
1594
+ title: `
1595
+ ${type}`,
1596
+ value: "__separator__",
1597
+ description: "",
1598
+ disabled: true
1599
+ });
1600
+ for (const component of components) {
1601
+ choices.push({
1602
+ title: component.name,
1603
+ value: component.name,
1604
+ description: component.description || component.type
1605
+ });
1606
+ }
1607
+ }
1608
+ if (choices.length === 0) {
1609
+ logger.warn(`No selectable components found in ${registryType} registry`);
1610
+ return [];
1611
+ }
1612
+ const { selected } = await prompts3({
1613
+ type: "multiselect",
1614
+ name: "selected",
1615
+ message: "Which components would you like to add?",
1616
+ instructions: false,
1617
+ choices,
1618
+ hint: "Space to select, Enter to confirm"
1619
+ });
1620
+ return selected || [];
1621
+ }
1622
+ async function installRequestedComponents(componentNames, registryType, config, getComponentFn, options, preloadedComponents = []) {
1623
+ const componentMap = /* @__PURE__ */ new Map();
1624
+ for (const component of preloadedComponents) {
1625
+ componentMap.set(component.name.toLowerCase(), component);
1626
+ }
1627
+ const resolverGetComponent = async (name, type) => {
1628
+ const normalized = name.toLowerCase();
1629
+ const cached = componentMap.get(normalized);
1630
+ if (cached) {
1631
+ return cached;
1632
+ }
1633
+ const component = await getComponentFn(name, type);
1634
+ if (!component) {
1635
+ return null;
1636
+ }
1637
+ componentMap.set(component.name.toLowerCase(), component);
1638
+ return component;
1639
+ };
1640
+ const orderedComponents = await resolveRegistryTree(
1641
+ componentNames,
1642
+ registryType,
1643
+ (name, type) => resolverGetComponent(name, type)
1644
+ );
1645
+ if (options.dryRun && orderedComponents.length > 0) {
1646
+ logger.info("\n\u{1F4E6} Resolved registry dependency tree:");
1647
+ orderedComponents.forEach((component, index) => {
1648
+ console.log(` ${index + 1}. ${component.name}`);
1649
+ });
1650
+ }
1651
+ const orderedNames = new Set(orderedComponents.map((component) => component.name.toLowerCase()));
1652
+ const normalizedRequested = Array.from(new Set(componentNames.map((name) => name.toLowerCase())));
1653
+ const missingRequested = normalizedRequested.filter((name) => !orderedNames.has(name));
1654
+ const missingResults = missingRequested.map((name) => ({
1655
+ name,
1656
+ status: "error",
1657
+ error: `Component "${name}" was not found in ${registryType} registry`
1658
+ }));
1659
+ const processingResults = await processComponents(
1660
+ orderedComponents.map((component) => component.name),
1661
+ registryType,
1662
+ config,
1663
+ resolverGetComponent,
1664
+ options,
1665
+ orderedComponents,
1666
+ orderedComponents.length
1667
+ );
1668
+ return [...missingResults, ...processingResults];
1669
+ }
1670
+ function displayInstallationSummary(registryType, results) {
1671
+ const successful = results.filter((r) => r.status === "success");
1672
+ const failed = results.filter((r) => r.status === "error");
1673
+ logger.info("\n\u{1F4CA} Installation Summary:");
1674
+ console.log(` Registry: ${registryType}`);
1675
+ console.log(` \u2705 Successful: ${successful.length}`);
1676
+ console.log(` \u274C Failed: ${failed.length}`);
1677
+ if (successful.length > 0) {
1678
+ logger.success(`
1679
+ \u{1F389} ${CLI_MESSAGES.success.componentsInstalled}`);
1680
+ console.log("You can now import and use them in your project.");
1681
+ }
1682
+ if (failed.length > 0) {
1683
+ process.exit(1);
1684
+ }
1685
+ }
1686
+ async function installComponentFiles(component, config, force = false) {
1687
+ for (const file of component.files) {
1688
+ const fileName = path8.basename(file.path);
1689
+ const target = file.target || inferTargetFromType(component.type);
1690
+ const installDir = resolveInstallDir(target, config);
1691
+ const targetPath = path8.join(process.cwd(), installDir, fileName);
1692
+ if (!force && await fs7.pathExists(targetPath)) {
1693
+ console.log(` \u26A0\uFE0F ${CLI_MESSAGES.status.skipped(fileName)}`);
1694
+ continue;
1695
+ }
1696
+ await fs7.ensureDir(path8.dirname(targetPath));
1697
+ const preparedContent = shouldTransformFile(fileName) ? applyTransforms(file.content, config.aliases) : file.content;
1698
+ await fs7.writeFile(targetPath, preparedContent, "utf-8");
1699
+ }
1700
+ }
1701
+ function inferTargetFromType(componentType) {
1702
+ switch (componentType) {
1703
+ case "registry:ui":
1704
+ return "ui";
1705
+ case "registry:composite":
1706
+ return "components";
1707
+ case "registry:block":
1708
+ return "blocks";
1709
+ case "registry:component":
1710
+ return "components";
1711
+ case "registry:layout":
1712
+ return "layouts";
1713
+ case "registry:lib":
1714
+ return "lib";
1715
+ case "registry:variants":
1716
+ return "variants";
1717
+ default:
1718
+ return "components";
1719
+ }
1720
+ }
1721
+ function resolveInstallDir(target, config) {
1722
+ const normalizedTarget = target.replace(/\\/g, "/").replace(/^\/?src\//i, "");
1723
+ if (normalizedTarget === "lib") {
1724
+ return normalizeDir(config.libDir || SCHEMA_CONFIG.defaultDirectories.lib);
1725
+ }
1726
+ if (normalizedTarget === "variants") {
1727
+ return normalizeDir(SCHEMA_CONFIG.defaultDirectories.variants);
1728
+ }
1729
+ const baseComponentsDir = normalizeDir(config.componentsDir || SCHEMA_CONFIG.defaultDirectories.components);
1730
+ if (normalizedTarget.includes("/")) {
1731
+ const parentRoot = baseComponentsDir.replace(/[/\\]components$/i, "") || "src";
1732
+ return path8.join(parentRoot, normalizedTarget).replace(/\\/g, "/");
1733
+ }
1734
+ if (normalizedTarget === "ui")
1735
+ return path8.join(baseComponentsDir, "ui").replace(/\\/g, "/");
1736
+ if (normalizedTarget === "components")
1737
+ return baseComponentsDir;
1738
+ switch (normalizedTarget) {
1739
+ case "blocks":
1740
+ return normalizeDir(SCHEMA_CONFIG.defaultDirectories.blocks);
1741
+ case "layouts":
1742
+ return normalizeDir(SCHEMA_CONFIG.defaultDirectories.layouts);
1743
+ default:
1744
+ return baseComponentsDir;
1745
+ }
1746
+ }
1747
+ function normalizeDir(dir) {
1748
+ return dir.replace(/^\.\//, "").replace(/\\/g, "/");
1749
+ }
1750
+ function resolveRegistryType(registryInput) {
1751
+ if (!registryInput) {
1752
+ return SCHEMA_CONFIG.defaultRegistryType;
1753
+ }
1754
+ if (SCHEMA_CONFIG.registryTypes.includes(registryInput)) {
1755
+ return registryInput;
1756
+ }
1757
+ logger.warn(`\u26A0\uFE0F Unknown registry type: ${registryInput}`);
1758
+ console.log(`Available registries: ${SCHEMA_CONFIG.registryTypes.join(", ")}`);
1759
+ console.log(`Using default: ${SCHEMA_CONFIG.defaultRegistryType}`);
1760
+ return SCHEMA_CONFIG.defaultRegistryType;
1761
+ }
1762
+ async function installComponentsIndex(registryType, config) {
1763
+ const spinner = ora4("Installing components index...").start();
1764
+ try {
1765
+ const cdnUrls = SCHEMA_CONFIG.cdnBaseUrls;
1766
+ for (const baseUrl of cdnUrls) {
1767
+ try {
1768
+ const url = `${baseUrl}/components/index.json`;
1769
+ const response = await fetch3(url);
1770
+ if (response.ok) {
1771
+ const component = await response.json();
1772
+ for (const file of component.files) {
1773
+ const fileName = path8.basename(file.path);
1774
+ const targetDir = config.componentsDir;
1775
+ const targetPath = path8.join(process.cwd(), targetDir, fileName);
1776
+ await fs7.ensureDir(path8.dirname(targetPath));
1777
+ await fs7.writeFile(targetPath, file.content || "", "utf-8");
1778
+ }
1779
+ spinner.succeed("Installed components index");
1780
+ return;
1781
+ }
1782
+ } catch {
1783
+ continue;
1784
+ }
1785
+ }
1786
+ spinner.info("Components index not found in registry (optional)");
1787
+ } catch (error) {
1788
+ spinner.fail("Could not install components index");
1789
+ }
1790
+ }
1791
+
1792
+ // src/commands/build.ts
1793
+ import fs8 from "fs-extra";
1794
+ import path9 from "path";
1795
+ import chalk7 from "chalk";
1796
+ import ora5 from "ora";
1797
+
1798
+ // src/registry/build-schema.ts
1799
+ import { z as z2 } from "zod";
1800
+ var registryItemTypeSchema = z2.enum([
1801
+ "registry:lib",
1802
+ "registry:block",
1803
+ "registry:component",
1804
+ "registry:ui",
1805
+ "registry:composite",
1806
+ "registry:layout",
1807
+ "registry:variants"
1808
+ ]);
1809
+ var registryItemFileSchema = z2.object({
1810
+ path: z2.string(),
1811
+ content: z2.string().optional(),
1812
+ // Populated during build
1813
+ target: z2.string().optional()
1814
+ });
1815
+ var registryItemTailwindSchema = z2.object({
1816
+ config: z2.object({
1817
+ content: z2.array(z2.string()).optional(),
1818
+ theme: z2.record(z2.string(), z2.any()).optional(),
1819
+ plugins: z2.array(z2.string()).optional()
1820
+ }).optional()
1821
+ });
1822
+ var registryItemCssVarsSchema = z2.object({
1823
+ theme: z2.record(z2.string(), z2.string()).optional(),
1824
+ light: z2.record(z2.string(), z2.string()).optional(),
1825
+ dark: z2.record(z2.string(), z2.string()).optional()
1826
+ });
1827
+ var registryItemSchema = z2.object({
1828
+ $schema: z2.string().optional(),
1829
+ name: z2.string(),
1830
+ type: registryItemTypeSchema,
1831
+ title: z2.string().optional(),
1832
+ description: z2.string().optional(),
1833
+ dependencies: z2.array(z2.string()).default([]),
1834
+ devDependencies: z2.array(z2.string()).default([]),
1835
+ registryDependencies: z2.array(z2.string()).optional(),
1836
+ files: z2.array(registryItemFileSchema),
1837
+ tailwind: registryItemTailwindSchema.optional(),
1838
+ cssVars: registryItemCssVarsSchema.optional(),
1839
+ meta: z2.record(z2.string(), z2.any()).optional()
1840
+ });
1841
+ var registrySchema = z2.object({
1842
+ $schema: z2.string().optional(),
1843
+ items: z2.array(registryItemSchema)
1844
+ });
1845
+
1846
+ // src/utils/schema-generator.ts
1847
+ import { zodToJsonSchema } from "zod-to-json-schema";
1848
+ import { z as z3 } from "zod";
1849
+ var fullRegistrySchema = z3.object({
1850
+ $schema: z3.string().optional(),
1851
+ name: z3.string().optional(),
1852
+ homepage: z3.string().optional(),
1853
+ registry: z3.enum(SCHEMA_CONFIG.registryTypes).optional(),
1854
+ version: z3.string().optional(),
1855
+ lastUpdated: z3.string().optional(),
1856
+ categories: z3.array(z3.enum(SCHEMA_CONFIG.componentCategories)).optional(),
1857
+ components: z3.array(z3.object({
1858
+ name: z3.string(),
1859
+ type: registryItemTypeSchema,
1860
+ description: z3.string().optional()
1861
+ })).optional(),
1862
+ items: z3.array(registryItemSchema)
1863
+ });
1864
+ function generateConfigSchema() {
1865
+ const baseSchema = zodToJsonSchema(configSchema, {
1866
+ name: "UI8KitConfiguration",
1867
+ $refStrategy: "none"
1868
+ });
1869
+ const actualSchema = baseSchema.definitions?.UI8KitConfiguration || baseSchema;
1870
+ return {
1871
+ "$schema": SCHEMA_CONFIG.schemaVersion,
1872
+ "title": SCHEMA_CONFIG.descriptions.config.title,
1873
+ "description": SCHEMA_CONFIG.descriptions.config.description,
1874
+ "type": "object",
1875
+ "properties": {
1876
+ "$schema": {
1877
+ "type": "string",
1878
+ "description": SCHEMA_CONFIG.fieldDescriptions.schema
1879
+ },
1880
+ "framework": {
1881
+ "type": "string",
1882
+ "enum": SCHEMA_CONFIG.supportedFrameworks,
1883
+ "description": SCHEMA_CONFIG.fieldDescriptions.framework
1884
+ },
1885
+ "typescript": {
1886
+ "type": "boolean",
1887
+ "default": true,
1888
+ "description": SCHEMA_CONFIG.fieldDescriptions.typescript
1889
+ },
1890
+ "globalCss": {
1891
+ "type": "string",
1892
+ "default": "src/index.css",
1893
+ "description": SCHEMA_CONFIG.fieldDescriptions.globalCss
1894
+ },
1895
+ "aliases": {
1896
+ "type": "object",
1897
+ "additionalProperties": {
1898
+ "type": "string"
1899
+ },
1900
+ "default": SCHEMA_CONFIG.defaultAliases,
1901
+ "description": SCHEMA_CONFIG.fieldDescriptions.aliases
1902
+ },
1903
+ "registry": {
1904
+ "type": "string",
1905
+ "default": SCHEMA_CONFIG.defaultRegistry,
1906
+ "description": SCHEMA_CONFIG.fieldDescriptions.registry
1907
+ },
1908
+ "componentsDir": {
1909
+ "type": "string",
1910
+ "default": SCHEMA_CONFIG.defaultDirectories.components,
1911
+ "description": SCHEMA_CONFIG.fieldDescriptions.componentsDir
1912
+ },
1913
+ "libDir": {
1914
+ "type": "string",
1915
+ "default": SCHEMA_CONFIG.defaultDirectories.lib,
1916
+ "description": SCHEMA_CONFIG.fieldDescriptions.libDir
1917
+ }
1918
+ },
1919
+ "required": ["framework"],
1920
+ "additionalProperties": false
1921
+ };
1922
+ }
1923
+ function generateRegistrySchema() {
1924
+ const baseSchema = zodToJsonSchema(fullRegistrySchema, {
1925
+ name: "UI8KitRegistry",
1926
+ $refStrategy: "none"
1927
+ });
1928
+ const actualSchema = baseSchema.definitions?.UI8KitRegistry || baseSchema;
1929
+ return {
1930
+ "$schema": SCHEMA_CONFIG.schemaVersion,
1931
+ "title": SCHEMA_CONFIG.descriptions.registry.title,
1932
+ "description": SCHEMA_CONFIG.descriptions.registry.description,
1933
+ "type": "object",
1934
+ "properties": {
1935
+ "$schema": {
1936
+ "type": "string",
1937
+ "description": SCHEMA_CONFIG.fieldDescriptions.schema
1938
+ },
1939
+ "name": {
1940
+ "type": "string",
1941
+ "description": SCHEMA_CONFIG.fieldDescriptions.registryName
1942
+ },
1943
+ "homepage": {
1944
+ "type": "string",
1945
+ "description": SCHEMA_CONFIG.fieldDescriptions.registryHomepage
1946
+ },
1947
+ "registry": {
1948
+ "type": "string",
1949
+ "enum": SCHEMA_CONFIG.registryTypes,
1950
+ "description": SCHEMA_CONFIG.fieldDescriptions.registryType
1951
+ },
1952
+ "version": {
1953
+ "type": "string",
1954
+ "description": SCHEMA_CONFIG.fieldDescriptions.registryVersion
1955
+ },
1956
+ "lastUpdated": {
1957
+ "type": "string",
1958
+ "format": "date-time",
1959
+ "description": SCHEMA_CONFIG.fieldDescriptions.lastUpdated
1960
+ },
1961
+ "categories": {
1962
+ "type": "array",
1963
+ "items": {
1964
+ "type": "string",
1965
+ "enum": SCHEMA_CONFIG.componentCategories
1966
+ },
1967
+ "description": SCHEMA_CONFIG.fieldDescriptions.categories
1968
+ },
1969
+ "components": {
1970
+ "type": "array",
1971
+ "items": {
1972
+ "type": "object",
1973
+ "properties": {
1974
+ "name": { "type": "string" },
1975
+ "type": {
1976
+ "type": "string",
1977
+ "enum": actualSchema.properties?.components?.items?.properties?.type?.enum || [
1978
+ "registry:lib",
1979
+ "registry:block",
1980
+ "registry:component",
1981
+ "registry:ui",
1982
+ "registry:template"
1983
+ ]
1984
+ },
1985
+ "description": { "type": "string" }
1986
+ },
1987
+ "required": ["name", "type"],
1988
+ "additionalProperties": false
1989
+ },
1990
+ "description": SCHEMA_CONFIG.fieldDescriptions.components
1991
+ },
1992
+ "items": {
1993
+ "type": "array",
1994
+ "items": { "$ref": getSchemaRef("registry-item") },
1995
+ "description": SCHEMA_CONFIG.fieldDescriptions.items
1996
+ }
1997
+ },
1998
+ "required": ["items"],
1999
+ "additionalProperties": false
2000
+ };
2001
+ }
2002
+ function generateRegistryItemSchema() {
2003
+ const baseSchema = zodToJsonSchema(registryItemSchema, {
2004
+ name: "UI8KitRegistryItem",
2005
+ $refStrategy: "none"
2006
+ });
2007
+ const actualSchema = baseSchema.definitions?.UI8KitRegistryItem || baseSchema;
2008
+ return {
2009
+ "$schema": SCHEMA_CONFIG.schemaVersion,
2010
+ "title": SCHEMA_CONFIG.descriptions.registryItem.title,
2011
+ "description": SCHEMA_CONFIG.descriptions.registryItem.description,
2012
+ "type": "object",
2013
+ "properties": actualSchema.properties,
2014
+ "required": actualSchema.required || ["name", "type", "files"],
2015
+ "additionalProperties": false
2016
+ };
2017
+ }
2018
+
2019
+ // src/commands/build.ts
2020
+ async function buildCommand(registryPath = "./src/registry.json", options = {}) {
2021
+ const buildOptions = {
2022
+ cwd: path9.resolve(options.cwd || process.cwd()),
2023
+ registryFile: path9.resolve(registryPath),
2024
+ outputDir: path9.resolve(options.output || "./packages/registry/r")
2025
+ };
2026
+ console.log(chalk7.blue(CLI_MESSAGES.info.building));
2027
+ try {
2028
+ const registryContent = await fs8.readFile(buildOptions.registryFile, "utf-8");
2029
+ const registryData = JSON.parse(registryContent);
2030
+ const registry = registrySchema.parse(registryData);
2031
+ await ensureVariantsIndexItem(registry, buildOptions.cwd);
2032
+ await fs8.ensureDir(buildOptions.outputDir);
2033
+ await generateSchemaFiles(buildOptions.outputDir);
2034
+ const spinner = ora5(CLI_MESSAGES.info.processingComponents).start();
2035
+ for (const item of registry.items) {
2036
+ spinner.text = `Building ${item.name}...`;
2037
+ item.$schema = "https://ui.buildy.tw/schema/registry-item.json";
2038
+ for (const file of item.files) {
2039
+ const filePath = path9.resolve(buildOptions.cwd, file.path);
2040
+ if (await fs8.pathExists(filePath)) {
2041
+ file.content = await fs8.readFile(filePath, "utf-8");
2042
+ } else {
2043
+ throw new Error(CLI_MESSAGES.errors.fileNotFound(file.path));
2044
+ }
2045
+ }
2046
+ const validatedItem = registryItemSchema.parse(item);
2047
+ const typeDir = getOutputDir(validatedItem.type);
2048
+ const outputPath = path9.join(buildOptions.outputDir, typeDir);
2049
+ await fs8.ensureDir(outputPath);
2050
+ const outputFile = path9.join(outputPath, `${validatedItem.name}.json`);
2051
+ await fs8.writeFile(outputFile, JSON.stringify(validatedItem, null, 2));
2052
+ }
2053
+ spinner.succeed(CLI_MESSAGES.status.builtComponents(registry.items.length));
2054
+ await createIndexFile(registry, buildOptions.outputDir);
2055
+ console.log(chalk7.green(`\u2705 ${CLI_MESSAGES.success.registryBuilt}`));
2056
+ console.log(`Output: ${buildOptions.outputDir}`);
2057
+ console.log(chalk7.green(`\u2705 ${CLI_MESSAGES.success.schemasGenerated}`));
2058
+ } catch (error) {
2059
+ handleError(error);
2060
+ }
2061
+ }
2062
+ var BUILD_OUTPUT_FOLDERS = {
2063
+ "registry:ui": "components/ui",
2064
+ "registry:composite": "components",
2065
+ "registry:block": "blocks",
2066
+ "registry:component": "components",
2067
+ "registry:lib": "lib",
2068
+ "registry:layout": "layouts",
2069
+ "registry:variants": "components/variants"
2070
+ };
2071
+ function getOutputDir(type) {
2072
+ const folder = BUILD_OUTPUT_FOLDERS[type];
2073
+ return folder || "misc";
2074
+ }
2075
+ async function createIndexFile(registry, outputDir) {
2076
+ const index = {
2077
+ $schema: "https://ui.buildy.tw/schema/registry.json",
2078
+ components: registry.items.map((item) => ({
2079
+ name: item.name,
2080
+ type: item.type,
2081
+ title: item.title,
2082
+ description: item.description
2083
+ })),
2084
+ categories: SCHEMA_CONFIG.componentCategories,
2085
+ version: "1.0.0",
2086
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
2087
+ registry: registry?.registry || SCHEMA_CONFIG.defaultRegistryType
2088
+ };
2089
+ await fs8.writeFile(
2090
+ path9.join(outputDir, "index.json"),
2091
+ JSON.stringify(index, null, 2)
2092
+ );
2093
+ }
2094
+ async function generateSchemaFiles(outputDir) {
2095
+ const registryBaseDir = path9.dirname(outputDir);
2096
+ const schemaDir = path9.join(registryBaseDir, "schema");
2097
+ await fs8.ensureDir(schemaDir);
2098
+ const configSchemaJson = generateConfigSchema();
2099
+ const registrySchemaJson = generateRegistrySchema();
2100
+ const registryItemSchemaJson = generateRegistryItemSchema();
2101
+ await fs8.writeFile(
2102
+ path9.join(registryBaseDir, "schema.json"),
2103
+ JSON.stringify(configSchemaJson, null, 2)
2104
+ );
2105
+ await fs8.writeFile(
2106
+ path9.join(schemaDir, "registry.json"),
2107
+ JSON.stringify(registrySchemaJson, null, 2)
2108
+ );
2109
+ await fs8.writeFile(
2110
+ path9.join(schemaDir, "registry-item.json"),
2111
+ JSON.stringify(registryItemSchemaJson, null, 2)
2112
+ );
2113
+ }
2114
+ async function ensureVariantsIndexItem(registry, cwd) {
2115
+ const indexSourcePath = path9.join(cwd, "src/variants/index.ts");
2116
+ if (!await fs8.pathExists(indexSourcePath)) {
2117
+ return;
2118
+ }
2119
+ const sourceContent = await fs8.readFile(indexSourcePath, "utf-8");
2120
+ const dependencies = extractFileDependencies(sourceContent);
2121
+ const exportedModules = extractExportedModules(sourceContent);
2122
+ const indexItem = {
2123
+ name: "index",
2124
+ type: "registry:variants",
2125
+ description: exportedModules.length > 0 ? `Variant exports: ${exportedModules.join(", ")}` : "Variants export index",
2126
+ dependencies,
2127
+ devDependencies: [],
2128
+ files: [
2129
+ {
2130
+ path: path9.relative(cwd, indexSourcePath).replace(/\\/g, "/")
2131
+ }
2132
+ ]
2133
+ };
2134
+ const items = Array.isArray(registry.items) ? registry.items : [];
2135
+ const existingIndexIdx = items.findIndex(
2136
+ (item) => item && item.name === "index"
2137
+ );
2138
+ if (existingIndexIdx >= 0) {
2139
+ items[existingIndexIdx] = {
2140
+ ...items[existingIndexIdx],
2141
+ ...indexItem
2142
+ };
2143
+ } else {
2144
+ items.push(indexItem);
2145
+ }
2146
+ }
2147
+ function extractExportedModules(content) {
2148
+ const exports = /* @__PURE__ */ new Set();
2149
+ const starExportRegex = /export\s+\*\s+from\s+['"]\.\/([^'"]+)['"]/g;
2150
+ const namedExportRegex = /export\s+\{[^}]+\}\s+from\s+['"]\.\/([^'"]+)['"]/g;
2151
+ let match;
2152
+ while ((match = starExportRegex.exec(content)) !== null) {
2153
+ exports.add(match[1]);
2154
+ }
2155
+ while ((match = namedExportRegex.exec(content)) !== null) {
2156
+ exports.add(match[1]);
2157
+ }
2158
+ return [...exports];
2159
+ }
2160
+ function extractFileDependencies(content) {
2161
+ const dependencies = /* @__PURE__ */ new Set();
2162
+ const importRegex = /import\s+[^;\n]+?from\s+['"]([^'"]+)['"]/g;
2163
+ let match;
2164
+ while ((match = importRegex.exec(content)) !== null) {
2165
+ const moduleName = match[1];
2166
+ if (isExternalDependency(moduleName)) {
2167
+ dependencies.add(moduleName);
2168
+ }
2169
+ }
2170
+ return [...dependencies];
2171
+ }
2172
+
2173
+ // src/commands/scan.ts
2174
+ import fs9 from "fs-extra";
2175
+ import path10 from "path";
2176
+ import chalk8 from "chalk";
2177
+ import ora6 from "ora";
2178
+ import { glob } from "glob";
2179
+ import * as ts2 from "typescript";
2180
+ var DEV_PATTERNS = [
2181
+ "@types/",
2182
+ "eslint",
2183
+ "prettier",
2184
+ "typescript",
2185
+ "jest",
2186
+ "vitest",
2187
+ "testing-library",
2188
+ "@testing-library/",
2189
+ "storybook",
2190
+ "@storybook/",
2191
+ "webpack",
2192
+ "vite",
2193
+ "rollup",
2194
+ "babel",
2195
+ "@babel/",
2196
+ "postcss",
2197
+ "tailwindcss",
2198
+ "autoprefixer"
2199
+ ];
2200
+ async function scanCommand(options = {}) {
2201
+ const registryName = options.registry || SCHEMA_CONFIG.defaultRegistryType;
2202
+ const registryPath = `./${registryName}`;
2203
+ const scanOptions = {
2204
+ cwd: path10.resolve(options.cwd || process.cwd()),
2205
+ registry: path10.resolve(registryPath),
2206
+ outputFile: path10.resolve(options.output || "./src/registry.json"),
2207
+ sourceDir: path10.resolve(options.source || "./src")
2208
+ };
2209
+ console.log(chalk8.blue(`\u{1F50D} ${CLI_MESSAGES.info.scanningComponents(registryName)}`));
2210
+ try {
2211
+ const spinner = ora6(CLI_MESSAGES.info.scanningDirectories).start();
2212
+ const componentsDir = path10.resolve(scanOptions.cwd, normalizeDir2(SCHEMA_CONFIG.defaultDirectories.components));
2213
+ const uiDir = path10.join(componentsDir, "ui");
2214
+ const blocksDir = path10.resolve(scanOptions.cwd, normalizeDir2(SCHEMA_CONFIG.defaultDirectories.blocks));
2215
+ const layoutsDir = path10.resolve(scanOptions.cwd, normalizeDir2(SCHEMA_CONFIG.defaultDirectories.layouts));
2216
+ const libDir = path10.resolve(scanOptions.cwd, normalizeDir2(SCHEMA_CONFIG.defaultDirectories.lib));
2217
+ const variantsDir = path10.resolve(scanOptions.cwd, normalizeDir2(SCHEMA_CONFIG.defaultDirectories.variants));
2218
+ const uiComponents = await scanDirectory(uiDir, "registry:ui");
2219
+ const compositeComponents = await scanDirectoryFlat(componentsDir, "registry:composite", ["index.ts"]);
2220
+ const variantComponents = await scanDirectory(variantsDir, "registry:variants", ["index.ts"]);
2221
+ const blockComponents = await scanDirectory(blocksDir, "registry:block");
2222
+ const layoutComponents = await scanDirectory(layoutsDir, "registry:layout");
2223
+ const libComponents = await scanDirectory(libDir, "registry:lib");
2224
+ const variantsIndexItem = await scanSingleFile(path10.join(variantsDir, "index.ts"), "registry:variants");
2225
+ const componentsIndexItem = await scanSingleFile(path10.join(componentsDir, "index.ts"), "registry:composite");
2226
+ const allComponentsRaw = [
2227
+ ...uiComponents,
2228
+ ...compositeComponents,
2229
+ ...variantComponents,
2230
+ ...variantsIndexItem ? [variantsIndexItem] : [],
2231
+ ...componentsIndexItem ? [componentsIndexItem] : [],
2232
+ ...blockComponents,
2233
+ ...layoutComponents,
2234
+ ...libComponents
2235
+ ];
2236
+ const seen = /* @__PURE__ */ new Set();
2237
+ const allComponents = [];
2238
+ for (const comp of allComponentsRaw) {
2239
+ const key = `${comp.type}:${comp.name}`;
2240
+ if (seen.has(key))
2241
+ continue;
2242
+ seen.add(key);
2243
+ allComponents.push(comp);
2244
+ }
2245
+ spinner.text = CLI_MESSAGES.info.analyzingDeps.replace("{count}", allComponents.length.toString());
2246
+ for (const component of allComponents) {
2247
+ const analysis = await analyzeComponentDependencies(component.files, scanOptions.cwd);
2248
+ component.dependencies = analysis.dependencies;
2249
+ component.devDependencies = analysis.devDependencies;
2250
+ if (analysis.description && !component.description) {
2251
+ component.description = analysis.description;
2252
+ }
2253
+ }
2254
+ const registry = {
2255
+ $schema: "https://ui.buildy.tw/schema/registry.json",
2256
+ items: allComponents,
2257
+ version: "1.0.0",
2258
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
2259
+ registry: registryName
2260
+ };
2261
+ await fs9.ensureDir(path10.dirname(scanOptions.outputFile));
2262
+ await fs9.writeFile(scanOptions.outputFile, JSON.stringify(registry, null, 2));
2263
+ spinner.succeed(CLI_MESSAGES.status.scannedComponents(allComponents.length));
2264
+ console.log(chalk8.green(`\u2705 ${CLI_MESSAGES.success.registryGenerated(registryName)}`));
2265
+ console.log(`Output: ${scanOptions.outputFile}`);
2266
+ const summary = allComponents.reduce((acc, comp) => {
2267
+ acc[comp.type] = (acc[comp.type] || 0) + 1;
2268
+ return acc;
2269
+ }, {});
2270
+ console.log(chalk8.blue("\n\u{1F4CA} Component Summary:"));
2271
+ Object.entries(summary).forEach(([type, count]) => {
2272
+ console.log(` ${type}: ${count}`);
2273
+ });
2274
+ const allDeps = /* @__PURE__ */ new Set();
2275
+ const allDevDeps = /* @__PURE__ */ new Set();
2276
+ allComponents.forEach((comp) => {
2277
+ comp.dependencies.forEach((dep) => allDeps.add(dep));
2278
+ comp.devDependencies.forEach((dep) => allDevDeps.add(dep));
2279
+ });
2280
+ console.log(chalk8.blue("\n\u{1F4E6} Dependencies Summary:"));
2281
+ console.log(` Dependencies: ${allDeps.size} unique (${Array.from(allDeps).join(", ") || "none"})`);
2282
+ console.log(` DevDependencies: ${allDevDeps.size} unique (${Array.from(allDevDeps).join(", ") || "none"})`);
2283
+ } catch (error) {
2284
+ handleError(error);
2285
+ }
2286
+ }
2287
+ async function scanDirectory(dirPath, type, ignorePatterns = []) {
2288
+ if (!await fs9.pathExists(dirPath)) {
2289
+ return [];
2290
+ }
2291
+ const components = [];
2292
+ const pattern = path10.join(dirPath, "**/*.{ts,tsx,js,jsx}").replace(/\\/g, "/");
2293
+ const ignore = ignorePatterns.map((p) => p.replace(/\\/g, "/"));
2294
+ const files = await glob(pattern, { windowsPathsNoEscape: true, ignore });
2295
+ for (const filePath of files) {
2296
+ const relativePath = path10.relative(process.cwd(), filePath).replace(/\\/g, "/");
2297
+ const fileName = path10.basename(filePath, path10.extname(filePath));
2298
+ if (fileName === "index" || fileName.startsWith("_")) {
2299
+ continue;
2300
+ }
2301
+ try {
2302
+ const content = await fs9.readFile(filePath, "utf-8");
2303
+ const description = extractDescription(content);
2304
+ if (!hasValidExports(content)) {
2305
+ continue;
2306
+ }
2307
+ components.push({
2308
+ name: fileName,
2309
+ type,
2310
+ description,
2311
+ dependencies: [],
2312
+ devDependencies: [],
2313
+ files: [{
2314
+ path: relativePath,
2315
+ target: getTargetFromType(type)
2316
+ }]
2317
+ });
2318
+ } catch (error) {
2319
+ console.warn(`Warning: Could not process ${filePath}:`, error.message);
2320
+ }
2321
+ }
2322
+ return components;
2323
+ }
2324
+ async function scanDirectoryFlat(dirPath, type, ignoreFiles = []) {
2325
+ if (!await fs9.pathExists(dirPath)) {
2326
+ return [];
2327
+ }
2328
+ const components = [];
2329
+ const pattern = path10.join(dirPath, "*.{ts,tsx,js,jsx}").replace(/\\/g, "/");
2330
+ const files = await glob(pattern, { windowsPathsNoEscape: true });
2331
+ for (const filePath of files) {
2332
+ const relativePath = path10.relative(process.cwd(), filePath).replace(/\\/g, "/");
2333
+ const fileName = path10.basename(filePath, path10.extname(filePath));
2334
+ if (ignoreFiles.includes(fileName + path10.extname(filePath)) || fileName.startsWith("_")) {
2335
+ continue;
2336
+ }
2337
+ try {
2338
+ const content = await fs9.readFile(filePath, "utf-8");
2339
+ const description = extractDescription(content);
2340
+ if (!hasValidExports(content)) {
2341
+ continue;
2342
+ }
2343
+ components.push({
2344
+ name: fileName,
2345
+ type,
2346
+ description,
2347
+ dependencies: [],
2348
+ devDependencies: [],
2349
+ files: [{
2350
+ path: relativePath,
2351
+ target: getTargetFromType(type)
2352
+ }]
2353
+ });
2354
+ } catch (error) {
2355
+ console.warn(`Warning: Could not process ${filePath}:`, error.message);
2356
+ }
2357
+ }
2358
+ return components;
2359
+ }
2360
+ async function scanSingleFile(filePath, type) {
2361
+ if (!await fs9.pathExists(filePath)) {
2362
+ return null;
2363
+ }
2364
+ try {
2365
+ const content = await fs9.readFile(filePath, "utf-8");
2366
+ const description = extractDescription(content);
2367
+ if (!hasValidExports(content)) {
2368
+ return null;
2369
+ }
2370
+ const relativePath = path10.relative(process.cwd(), filePath).replace(/\\/g, "/");
2371
+ const fileName = path10.basename(filePath, path10.extname(filePath));
2372
+ return {
2373
+ name: fileName,
2374
+ type,
2375
+ description,
2376
+ dependencies: [],
2377
+ devDependencies: [],
2378
+ files: [{
2379
+ path: relativePath,
2380
+ target: getTargetFromType(type)
2381
+ }]
2382
+ };
2383
+ } catch (error) {
2384
+ console.warn(`Warning: Could not process ${filePath}:`, error.message);
2385
+ return null;
2386
+ }
2387
+ }
2388
+ function extractDescription(content) {
2389
+ const jsdocMatch = content.match(/\/\*\*\s*\n\s*\*\s*(.+?)\s*\n\s*\*\//s);
2390
+ if (jsdocMatch) {
2391
+ return jsdocMatch[1].trim();
2392
+ }
2393
+ const commentMatch = content.match(/^\/\/\s*(.+)$/m);
2394
+ if (commentMatch) {
2395
+ return commentMatch[1].trim();
2396
+ }
2397
+ return "";
2398
+ }
2399
+ function hasValidExports(content) {
2400
+ return /export\s+(default\s+)?(function|const|class|interface|type)/m.test(content) || /export\s*\{/.test(content);
2401
+ }
2402
+ async function analyzeComponentDependencies(files, cwd) {
2403
+ const allDependencies = /* @__PURE__ */ new Set();
2404
+ const allDevDependencies = /* @__PURE__ */ new Set();
2405
+ let description;
2406
+ for (const file of files) {
2407
+ try {
2408
+ const filePath = path10.resolve(cwd, file.path);
2409
+ const content = await fs9.readFile(filePath, "utf-8");
2410
+ const sourceFile = ts2.createSourceFile(
2411
+ file.path,
2412
+ content,
2413
+ ts2.ScriptTarget.Latest,
2414
+ true
2415
+ );
2416
+ const analysis = analyzeAST(sourceFile);
2417
+ analysis.dependencies.forEach((dep) => allDependencies.add(dep));
2418
+ analysis.devDependencies.forEach((dep) => allDevDependencies.add(dep));
2419
+ if (analysis.description && !description) {
2420
+ description = analysis.description;
2421
+ }
2422
+ } catch (error) {
2423
+ console.warn(CLI_MESSAGES.errors.failedToAnalyzeDeps(file.path), error.message);
2424
+ }
2425
+ }
2426
+ return {
2427
+ dependencies: Array.from(allDependencies),
2428
+ devDependencies: Array.from(allDevDependencies),
2429
+ description
2430
+ };
2431
+ }
2432
+ function analyzeAST(sourceFile) {
2433
+ const dependencies = /* @__PURE__ */ new Set();
2434
+ const devDependencies = /* @__PURE__ */ new Set();
2435
+ let description;
2436
+ let hasExports = false;
2437
+ function visit(node) {
2438
+ if (ts2.isImportDeclaration(node)) {
2439
+ const moduleSpecifier = node.moduleSpecifier;
2440
+ if (ts2.isStringLiteral(moduleSpecifier)) {
2441
+ const moduleName = moduleSpecifier.text;
2442
+ if (isExternalDependency(moduleName)) {
2443
+ if (isDevDependency(moduleName)) {
2444
+ devDependencies.add(moduleName);
2445
+ } else {
2446
+ dependencies.add(moduleName);
2447
+ }
2448
+ }
2449
+ }
2450
+ }
2451
+ if (ts2.isExportDeclaration(node)) {
2452
+ hasExports = true;
2453
+ } else if (ts2.isExportAssignment(node)) {
2454
+ hasExports = true;
2455
+ } else if (hasExportModifier(node)) {
2456
+ hasExports = true;
2457
+ }
2458
+ const jsDocComment = getJSDocComment(node);
2459
+ if (jsDocComment && !description) {
2460
+ description = jsDocComment;
2461
+ }
2462
+ ts2.forEachChild(node, visit);
2463
+ }
2464
+ visit(sourceFile);
2465
+ return {
2466
+ dependencies: Array.from(dependencies),
2467
+ devDependencies: Array.from(devDependencies),
2468
+ description,
2469
+ hasExports
2470
+ };
2471
+ }
2472
+ function isDevDependency(moduleName) {
2473
+ return DEV_PATTERNS.some((pattern) => moduleName.includes(pattern));
2474
+ }
2475
+ function hasExportModifier(node) {
2476
+ if ("modifiers" in node && node.modifiers) {
2477
+ return node.modifiers.some(
2478
+ (mod) => mod.kind === ts2.SyntaxKind.ExportKeyword
2479
+ );
2480
+ }
2481
+ return false;
2482
+ }
2483
+ function getJSDocComment(node) {
2484
+ try {
2485
+ const jsDocTags = ts2.getJSDocCommentsAndTags(node);
2486
+ for (const tag of jsDocTags) {
2487
+ if (ts2.isJSDoc(tag) && tag.comment) {
2488
+ if (typeof tag.comment === "string") {
2489
+ return tag.comment.trim();
2490
+ } else if (Array.isArray(tag.comment)) {
2491
+ return tag.comment.map((part) => part.text).join("").trim();
2492
+ }
2493
+ }
2494
+ }
2495
+ } catch (error) {
2496
+ }
2497
+ return void 0;
2498
+ }
2499
+ function getTargetFromType(type) {
2500
+ const folder = TYPE_TO_FOLDER[type];
2501
+ return folder || "components";
2502
+ }
2503
+ function normalizeDir2(dir) {
2504
+ return dir.replace(/^\.\//, "").replace(/\\/g, "/");
2505
+ }
2506
+
2507
+ // src/commands/list.ts
2508
+ import chalk9 from "chalk";
2509
+ var LIST_EXCLUDED_COMPONENT_TYPES = ["registry:variants", "registry:lib"];
2510
+ async function listCommand(options = {}) {
2511
+ const registryType = resolveRegistryType2(options.registry);
2512
+ const requestOptions = {
2513
+ excludeTypes: LIST_EXCLUDED_COMPONENT_TYPES,
2514
+ noCache: options.cache === false
2515
+ };
2516
+ try {
2517
+ const components = await getAllComponents(registryType, requestOptions);
2518
+ if (options.json) {
2519
+ console.log(JSON.stringify(components, null, 2));
2520
+ return;
2521
+ }
2522
+ const byType = /* @__PURE__ */ new Map();
2523
+ for (const component of components) {
2524
+ const group = byType.get(component.type) ?? [];
2525
+ group.push(component);
2526
+ byType.set(component.type, group);
2527
+ }
2528
+ const sortedGroups = Array.from(byType.entries()).sort((a, b) => a[0].localeCompare(b[0]));
2529
+ if (sortedGroups.length === 0) {
2530
+ logger.warn(CLI_MESSAGES.errors.registryTempUnavailable);
2531
+ return;
2532
+ }
2533
+ logger.info(CLI_MESSAGES.info.listingComponents);
2534
+ for (const [type, group] of sortedGroups) {
2535
+ const entries = group.sort((a, b) => a.name.localeCompare(b.name));
2536
+ console.log(chalk9.cyan(`${type} (${entries.length} components)`));
2537
+ for (const component of entries) {
2538
+ const description = component.description ? chalk9.dim(component.description) : "";
2539
+ console.log(chalk9.white(` ${component.name.padEnd(14)}`) + description);
2540
+ }
2541
+ console.log("");
2542
+ }
2543
+ } catch (error) {
2544
+ logger.error(error.message);
2545
+ }
2546
+ }
2547
+ function resolveRegistryType2(registryInput) {
2548
+ if (!registryInput) {
2549
+ return SCHEMA_CONFIG.defaultRegistryType;
2550
+ }
2551
+ if (SCHEMA_CONFIG.registryTypes.includes(registryInput)) {
2552
+ return registryInput;
2553
+ }
2554
+ logger.warn(`\u26A0\uFE0F Unknown registry type: ${registryInput}`);
2555
+ logger.warn(`Available registries: ${SCHEMA_CONFIG.registryTypes.join(", ")}`);
2556
+ return SCHEMA_CONFIG.defaultRegistryType;
2557
+ }
2558
+
2559
+ // src/commands/diff.ts
2560
+ import fs10 from "fs-extra";
2561
+ import path11 from "path";
2562
+ import { glob as glob2 } from "glob";
2563
+ import chalk10 from "chalk";
2564
+ async function diffCommand(componentName, options = {}) {
2565
+ try {
2566
+ const registryType = resolveRegistryType3(options.registry);
2567
+ const config = await findConfig(registryType);
2568
+ const defaultConfig = config ?? {
2569
+ framework: SCHEMA_CONFIG.supportedFrameworks[0],
2570
+ typescript: true,
2571
+ globalCss: "src/index.css",
2572
+ aliases: SCHEMA_CONFIG.defaultAliases,
2573
+ registry: SCHEMA_CONFIG.defaultRegistry,
2574
+ componentsDir: SCHEMA_CONFIG.defaultDirectories.components,
2575
+ libDir: SCHEMA_CONFIG.defaultDirectories.lib
2576
+ };
2577
+ const installed = await scanLocalComponents(defaultConfig);
2578
+ if (installed.length === 0) {
2579
+ logger.warn(CLI_MESSAGES.errors.noLocalInstall);
2580
+ return;
2581
+ }
2582
+ const registryComponents = await getAllComponents(registryType, { noCache: options.cache === false });
2583
+ const registryIndex = new Map(registryComponents.map((item) => [item.name.toLowerCase(), item]));
2584
+ const targets = componentName ? installed.filter((item) => item.name.toLowerCase() === componentName.toLowerCase()) : installed;
2585
+ if (componentName && targets.length === 0) {
2586
+ logger.warn(`Component "${componentName}" not found in local project structure`);
2587
+ return;
2588
+ }
2589
+ const results = [];
2590
+ logger.info(CLI_MESSAGES.info.checkingForUpdates);
2591
+ for (const item of targets) {
2592
+ const remoteComponent = registryIndex.get(item.name.toLowerCase());
2593
+ if (!remoteComponent) {
2594
+ results.push({
2595
+ component: item.name,
2596
+ type: "unknown",
2597
+ status: "missing-remote",
2598
+ files: [{ path: item.filePath, changed: false }]
2599
+ });
2600
+ continue;
2601
+ }
2602
+ const fileSummary = await compareComponentFiles(item, remoteComponent, defaultConfig);
2603
+ const hasChanges = fileSummary.some((file) => file.changed);
2604
+ results.push({
2605
+ component: item.name,
2606
+ type: remoteComponent.type,
2607
+ status: hasChanges ? "update" : "up-to-date",
2608
+ files: fileSummary
2609
+ });
2610
+ }
2611
+ if (options.json) {
2612
+ console.log(JSON.stringify(results, null, 2));
2613
+ return;
2614
+ }
2615
+ const updates = results.filter((item) => item.status === "update").length;
2616
+ const upToDate = results.filter((item) => item.status === "up-to-date").length;
2617
+ for (const result of results) {
2618
+ if (result.status === "missing-remote") {
2619
+ logger.warn(`
2620
+ \u26A0\uFE0F ${result.component}: not found in registry`);
2621
+ continue;
2622
+ }
2623
+ const statusTitle = result.status === "update" ? `${chalk10.yellow("UPDATE")}` : chalk10.green("UP-TO-DATE");
2624
+ const title = `${statusTitle} ${result.component} (${result.type})`;
2625
+ logger.info(title);
2626
+ for (const file of result.files) {
2627
+ console.log(` ${chalk10.white(file.path)}`);
2628
+ if (file.changed && file.diff) {
2629
+ const preview = formatDiffPreview(file.diff, 120);
2630
+ console.log(colorDiff(preview));
2631
+ } else {
2632
+ console.log(chalk10.dim(" No changes"));
2633
+ }
2634
+ }
2635
+ }
2636
+ console.log(
2637
+ `
2638
+ ${CLI_MESSAGES.info.localDiffSummary} ${chalk10.yellow(updates)} component(s) have updates, ${chalk10.green(upToDate)} up to date`
2639
+ );
2640
+ if (updates > 0) {
2641
+ console.log('Run "ui8kit add <component> --force" to update.');
2642
+ }
2643
+ } catch (error) {
2644
+ handleError(error);
2645
+ }
2646
+ }
2647
+ async function compareComponentFiles(installed, remote, config) {
2648
+ const localContent = await fs10.readFile(installed.filePath, "utf-8");
2649
+ const remoteCandidate = remote.files.find((file) => {
2650
+ const candidateName = path11.basename(file.path);
2651
+ return candidateName.toLowerCase() === path11.basename(installed.filePath).toLowerCase();
2652
+ });
2653
+ if (!remoteCandidate) {
2654
+ return [{ path: installed.filePath, changed: false }];
2655
+ }
2656
+ const remoteContent = applyTransforms(remoteCandidate.content, config.aliases);
2657
+ const changed = hasDiff(localContent, remoteContent);
2658
+ return changed ? [{
2659
+ path: installed.filePath,
2660
+ changed: true,
2661
+ diff: buildUnifiedDiff(installed.filePath, `${remote.name}/${path11.basename(installed.filePath)}`, localContent, remoteContent)
2662
+ }] : [{ path: installed.filePath, changed: false }];
2663
+ }
2664
+ async function scanLocalComponents(config) {
2665
+ const componentsDir = path11.resolve(process.cwd(), config.componentsDir || SCHEMA_CONFIG.defaultDirectories.components);
2666
+ const componentsUiDir = path11.join(componentsDir, "ui");
2667
+ const blocksDir = path11.resolve(process.cwd(), SCHEMA_CONFIG.defaultDirectories.blocks);
2668
+ const layoutsDir = path11.resolve(process.cwd(), SCHEMA_CONFIG.defaultDirectories.layouts);
2669
+ const directories = [componentsUiDir, componentsDir, blocksDir, layoutsDir];
2670
+ const entries = [];
2671
+ const patterns = directories.map((dir) => path11.join(dir, "*.{ts,tsx}").replace(/\\/g, "/"));
2672
+ for (const pattern of patterns) {
2673
+ const baseDir = path11.dirname(pattern);
2674
+ if (!await fs10.pathExists(baseDir)) {
2675
+ continue;
2676
+ }
2677
+ const filePaths = await glob2(pattern, { windowsPathsNoEscape: true });
2678
+ for (const filePath of filePaths) {
2679
+ const fileName = path11.basename(filePath);
2680
+ if (fileName === "index.tsx" || fileName === "index.ts") {
2681
+ continue;
2682
+ }
2683
+ entries.push({
2684
+ name: path11.parse(fileName).name.toLowerCase(),
2685
+ filePath: path11.resolve(process.cwd(), filePath)
2686
+ });
2687
+ }
2688
+ }
2689
+ const uniqueByName = /* @__PURE__ */ new Map();
2690
+ for (const entry of entries) {
2691
+ if (!uniqueByName.has(entry.name)) {
2692
+ uniqueByName.set(entry.name, entry);
2693
+ }
2694
+ }
2695
+ return Array.from(uniqueByName.values());
2696
+ }
2697
+ function colorDiff(value) {
2698
+ return value.split("\n").map((line) => {
2699
+ if (line.startsWith("+")) {
2700
+ return chalk10.green(line);
2701
+ }
2702
+ if (line.startsWith("-")) {
2703
+ return chalk10.red(line);
2704
+ }
2705
+ return line;
2706
+ }).join("\n");
2707
+ }
2708
+ function resolveRegistryType3(registryInput) {
2709
+ if (!registryInput) {
2710
+ return SCHEMA_CONFIG.defaultRegistryType;
2711
+ }
2712
+ if (SCHEMA_CONFIG.registryTypes.includes(registryInput)) {
2713
+ return registryInput;
2714
+ }
2715
+ logger.warn(`\u26A0\uFE0F Unknown registry type: ${registryInput}`);
2716
+ logger.warn(`Available registries: ${SCHEMA_CONFIG.registryTypes.join(", ")}`);
2717
+ return SCHEMA_CONFIG.defaultRegistryType;
2718
+ }
2719
+
2720
+ // src/commands/cache.ts
2721
+ async function cacheClearCommand() {
2722
+ try {
2723
+ await clearCache();
2724
+ logger.success(`${CLI_MESSAGES.success.cacheCleared} (${getCacheDir()})`);
2725
+ } catch (error) {
2726
+ handleError(error);
2727
+ }
2728
+ }
2729
+
2730
+ // src/commands/info.ts
2731
+ import fs11 from "fs-extra";
2732
+ import os2 from "os";
2733
+ import path12 from "path";
2734
+ import fetch4 from "node-fetch";
2735
+ import chalk11 from "chalk";
2736
+
2737
+ // src/utils/cli-version.ts
2738
+ import { existsSync, readFileSync } from "node:fs";
2739
+ import { dirname, resolve } from "node:path";
2740
+ import { fileURLToPath } from "node:url";
2741
+ var __dirname = dirname(fileURLToPath(import.meta.url));
2742
+ function findPackageJsonPath() {
2743
+ const roots = [process.argv[1], __dirname];
2744
+ for (const rawRoot of roots) {
2745
+ if (!rawRoot) {
2746
+ continue;
2747
+ }
2748
+ let current = rawRoot.endsWith(".js") ? dirname(rawRoot) : rawRoot;
2749
+ for (let i = 0; i < 8; i += 1) {
2750
+ const candidate = resolve(current, "package.json");
2751
+ if (existsSync(candidate)) {
2752
+ return candidate;
2753
+ }
2754
+ const parent = dirname(current);
2755
+ if (parent === current) {
2756
+ break;
2757
+ }
2758
+ current = parent;
2759
+ }
2760
+ }
2761
+ return null;
2762
+ }
2763
+ function getCliVersion() {
2764
+ const packageJsonPath = findPackageJsonPath();
2765
+ if (!packageJsonPath) {
2766
+ return "0.0.0";
2767
+ }
2768
+ try {
2769
+ const pkg = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
2770
+ return pkg.version ?? "0.0.0";
2771
+ } catch {
2772
+ return "0.0.0";
2773
+ }
2774
+ }
2775
+
2776
+ // src/commands/info.ts
2777
+ async function infoCommand(options = {}) {
2778
+ const version = getCliVersion();
2779
+ const pm = await detectPackageManager();
2780
+ const cwd = process.cwd();
2781
+ const configStatus = await readConfigStatus();
2782
+ const cache = await readCacheStatus();
2783
+ const cdn = await checkPrimaryCdn();
2784
+ if (options.json) {
2785
+ console.log(JSON.stringify({
2786
+ version,
2787
+ node: process.version,
2788
+ os: `${os2.platform()} ${os2.arch()}`,
2789
+ packageManager: pm,
2790
+ cwd,
2791
+ config: configStatus.config,
2792
+ configFound: configStatus.found,
2793
+ cache,
2794
+ cdn,
2795
+ registry: SCHEMA_CONFIG.defaultRegistry
2796
+ }, null, 2));
2797
+ return;
2798
+ }
2799
+ console.log(`ui8kit v${version}`);
2800
+ console.log(`Node ${process.version}`);
2801
+ console.log(`OS ${os2.platform()} ${os2.arch()}`);
2802
+ console.log(`PM ${pm}`);
2803
+ console.log(`CWD ${cwd}`);
2804
+ console.log("");
2805
+ if (configStatus.found) {
2806
+ console.log(chalk11.green(`Config ${configStatus.path} (found)`));
2807
+ const config = configStatus.config;
2808
+ console.log(` framework ${config.framework}`);
2809
+ console.log(` typescript ${config.typescript}`);
2810
+ console.log(` globalCss ${config.globalCss}`);
2811
+ console.log(` componentsDir ${config.componentsDir}`);
2812
+ console.log(` libDir ${config.libDir}`);
2813
+ } else {
2814
+ console.log(chalk11.yellow("Config not found"));
2815
+ }
2816
+ console.log("");
2817
+ console.log(`Registry ${SCHEMA_CONFIG.defaultRegistry}`);
2818
+ console.log(`CDN ${cdn.url} (${cdn.ok ? "ok" : "failed"})`);
2819
+ console.log(`Cache ${cache.path} (${cache.items} items, ${cache.mb} MB)`);
2820
+ }
2821
+ async function readConfigStatus() {
2822
+ const candidatePaths = [
2823
+ path12.join(process.cwd(), "ui8kit.config.json"),
2824
+ path12.join(process.cwd(), "src", "ui8kit.config.json"),
2825
+ path12.join(process.cwd(), SCHEMA_CONFIG.defaultRegistryType, "ui8kit.config.json")
2826
+ ];
2827
+ for (const configPath of candidatePaths) {
2828
+ if (await fs11.pathExists(configPath)) {
2829
+ try {
2830
+ const config = await fs11.readJson(configPath);
2831
+ return {
2832
+ found: true,
2833
+ path: `./${path12.relative(process.cwd(), configPath).replace(/\\/g, "/")}`,
2834
+ config: {
2835
+ framework: config.framework ?? "unknown",
2836
+ typescript: config.typescript ?? false,
2837
+ globalCss: config.globalCss ?? "src/index.css",
2838
+ componentsDir: config.componentsDir ?? SCHEMA_CONFIG.defaultDirectories.components,
2839
+ libDir: config.libDir ?? SCHEMA_CONFIG.defaultDirectories.lib
2840
+ }
2841
+ };
2842
+ } catch {
2843
+ continue;
2844
+ }
2845
+ }
2846
+ }
2847
+ return {
2848
+ found: false,
2849
+ path: null,
2850
+ config: null
2851
+ };
2852
+ }
2853
+ async function readCacheStatus() {
2854
+ const cachePath = getCacheDir();
2855
+ let items = 0;
2856
+ let bytes = 0;
2857
+ if (await fs11.pathExists(cachePath)) {
2858
+ const result = await countCacheFiles(cachePath);
2859
+ items = result.count;
2860
+ bytes = result.bytes;
2861
+ }
2862
+ return {
2863
+ path: cachePath.replace(/\\/g, "/"),
2864
+ items,
2865
+ mb: `${(bytes / (1024 * 1024)).toFixed(1)}`
2866
+ };
2867
+ }
2868
+ async function countCacheFiles(dirPath) {
2869
+ let count = 0;
2870
+ let size = 0;
2871
+ const entries = await fs11.readdir(dirPath, { withFileTypes: true });
2872
+ for (const entry of entries) {
2873
+ const fullPath = path12.join(dirPath, entry.name);
2874
+ if (entry.isDirectory()) {
2875
+ const nested = await countCacheFiles(fullPath);
2876
+ count += nested.count;
2877
+ size += nested.bytes;
2878
+ continue;
2879
+ }
2880
+ count += 1;
2881
+ const stat = await fs11.stat(fullPath);
2882
+ size += stat.size;
2883
+ }
2884
+ return { count, bytes: size };
2885
+ }
2886
+ async function checkPrimaryCdn() {
2887
+ const url = SCHEMA_CONFIG.cdnBaseUrls[0];
2888
+ try {
2889
+ const response = await fetch4(`${url}/index.json`, { method: "HEAD" });
2890
+ if (response.status >= 200 && response.status < 400) {
2891
+ return { url, ok: true };
2892
+ }
2893
+ } catch {
2894
+ }
2895
+ return { url, ok: false };
2896
+ }
2897
+
2898
+ // src/index.ts
2899
+ import { resolve as resolve2 } from "node:path";
2900
+ var program = new Command();
2901
+ program.option("-c, --cwd <dir>", "Working directory", process.cwd()).option("-v, --verbose", "Enable verbose output").option("--no-cache", "Bypass registry cache").name("ui8kit").description("A CLI for adding UI components to your Vite React projects (UI8Kit registry)").version(getCliVersion());
2902
+ program.command("list").description("List available components in registry").option("-r, --registry <type>", "Registry type: ui", "ui").option("--json", "Output raw JSON").action((options) => listCommand(options));
2903
+ program.command("diff").description("Show local vs registry differences").argument("[component]", "Component name").option("-r, --registry <type>", "Registry type: ui", "ui").option("--json", "Output diff in machine-readable JSON").action((component, options) => diffCommand(component, options));
2904
+ program.command("cache").description("Manage local cache").command("clear").description("Clear registry cache").action(cacheClearCommand);
2905
+ program.command("info").description("Show environment and config diagnostics").option("--json", "Output diagnostics as JSON").action(infoCommand);
2906
+ program.command("init").description("Initialize UI8Kit structure in your project").option("-y, --yes", "Skip prompts and use defaults").option("-r, --registry <type>", "Registry type: ui", "ui").action(initCommand);
2907
+ program.command("add").description("Add components to your project from the registry").argument("[components...]", "Components to add").option("-a, --all", "Install all available components").option("-f, --force", "Overwrite existing files").option("-r, --registry <type>", "Registry type: ui", "ui").option("--dry-run", "Show what would be installed without installing").option("--retry", "Aggressive retry mode (3 attempts per CDN request)").action(addCommand);
2908
+ program.command("scan").description("Scan and generate registry from existing components").option("-r, --registry <type|path>", "Registry type (ui) or custom path", "ui").option("-o, --output <file>", "Output registry file").option("-s, --source <dir>", "Source directory to scan").action(async (options) => {
2909
+ await scanCommand(options);
2910
+ });
2911
+ program.command("build").description("Build components registry").argument("[registry]", "Path to registry.json file", "./src/registry.json").option("-o, --output <path>", "Output directory", "./packages/registry/r").action(buildCommand);
2912
+ program.on("command:*", () => {
2913
+ console.error(chalk12.red(`Invalid command: ${program.args.join(" ")}`));
2914
+ console.log("See --help for a list of available commands.");
2915
+ process.exit(1);
2916
+ });
2917
+ program.hook("preAction", (_, actionCommand) => {
2918
+ const actionOptions = actionCommand?.opts?.();
2919
+ const globalOptions = program.opts();
2920
+ const verbose = globalOptions.verbose || actionOptions?.verbose;
2921
+ const cwd = actionOptions?.cwd || globalOptions.cwd;
2922
+ if (verbose) {
2923
+ logger.setVerbose(true);
2924
+ }
2925
+ if (cwd && resolve2(process.cwd()) !== resolve2(cwd)) {
2926
+ process.chdir(cwd);
2927
+ }
2928
+ });
2929
+ program.parse();
2930
+ //# sourceMappingURL=index.js.map