ui8kit 1.0.5 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +80 -149
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1896 -0
- package/dist/index.js.map +1 -0
- package/package.json +53 -16
- package/css/dist/styles.css +0 -3325
- package/css/src/semantic/alert.css +0 -11
- package/css/src/semantic/article.css +0 -59
- package/css/src/semantic/avatar.css +0 -11
- package/css/src/semantic/badge.css +0 -19
- package/css/src/semantic/breadcrumb.css +0 -23
- package/css/src/semantic/button.css +0 -39
- package/css/src/semantic/card.css +0 -43
- package/css/src/semantic/index.css +0 -19
- package/css/src/semantic/input.css +0 -3
- package/css/src/semantic/label.css +0 -3
- package/css/src/semantic/link.css +0 -39
- package/css/src/semantic/main.css +0 -3
- package/css/src/semantic/markup.css +0 -23
- package/css/src/semantic/nav.css +0 -63
- package/css/src/semantic/pagination.css +0 -11
- package/css/src/semantic/section.css +0 -43
- package/css/src/semantic/sheet.css +0 -35
- package/css/src/semantic/skeleton.css +0 -3
- package/css/src/semantic/table.css +0 -31
- package/css/src/semantic/textarea.css +0 -3
- package/r/components/article.json +0 -17
- package/r/components/aside.json +0 -17
- package/r/components/footer.json +0 -17
- package/r/components/header.json +0 -17
- package/r/components/main.json +0 -17
- package/r/components/markup.json +0 -17
- package/r/components/media.json +0 -17
- package/r/components/nav.json +0 -18
- package/r/components/navbar.json +0 -15
- package/r/components/section.json +0 -17
- package/r/components/sheet.json +0 -18
- package/r/index.json +0 -135
- package/r/lib/utils.json +0 -18
- package/r/ui/alert.json +0 -18
- package/r/ui/avatar.json +0 -17
- package/r/ui/badge.json +0 -19
- package/r/ui/breadcrumb.json +0 -19
- package/r/ui/button.json +0 -19
- package/r/ui/card.json +0 -17
- package/r/ui/input.json +0 -17
- package/r/ui/label.json +0 -17
- package/r/ui/link.json +0 -18
- package/r/ui/skeleton.json +0 -15
- package/r/ui/table.json +0 -17
- package/r/ui/textarea.json +0 -17
package/dist/index.js
ADDED
|
@@ -0,0 +1,1896 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
import chalk7 from "chalk";
|
|
6
|
+
|
|
7
|
+
// src/commands/add.ts
|
|
8
|
+
import chalk3 from "chalk";
|
|
9
|
+
import ora from "ora";
|
|
10
|
+
import path3 from "path";
|
|
11
|
+
import fs4 from "fs-extra";
|
|
12
|
+
import { execa } from "execa";
|
|
13
|
+
import fetch3 from "node-fetch";
|
|
14
|
+
|
|
15
|
+
// src/registry/api.ts
|
|
16
|
+
import fetch from "node-fetch";
|
|
17
|
+
|
|
18
|
+
// src/registry/schema.ts
|
|
19
|
+
import { z } from "zod";
|
|
20
|
+
|
|
21
|
+
// src/utils/schema-config.ts
|
|
22
|
+
var SCHEMA_CONFIG = {
|
|
23
|
+
// Base schema information
|
|
24
|
+
schemaVersion: "http://json-schema.org/draft-07/schema#",
|
|
25
|
+
baseUrl: "https://ui.buildy.tw/schema",
|
|
26
|
+
// Framework configuration
|
|
27
|
+
supportedFrameworks: ["vite-react"],
|
|
28
|
+
// Default aliases for path mapping
|
|
29
|
+
defaultAliases: {
|
|
30
|
+
"@": "./src",
|
|
31
|
+
"@/components": "./src/components",
|
|
32
|
+
"@/ui": "./src/components/ui",
|
|
33
|
+
"@/layouts": "./src/layouts",
|
|
34
|
+
"@/blocks": "./src/blocks",
|
|
35
|
+
"@/lib": "./src/lib",
|
|
36
|
+
"@/variants": "./src/variants"
|
|
37
|
+
},
|
|
38
|
+
// Registry configuration
|
|
39
|
+
defaultRegistry: "@ui8kit",
|
|
40
|
+
registryTypes: ["ui"],
|
|
41
|
+
// Default registry type
|
|
42
|
+
defaultRegistryType: "ui",
|
|
43
|
+
// CDN base URLs (registryName will be substituted)
|
|
44
|
+
cdnBaseUrls: [
|
|
45
|
+
"https://cdn.jsdelivr.net/npm/@ui8kit/registry@latest/r",
|
|
46
|
+
"https://unpkg.com/@ui8kit/registry@latest/r",
|
|
47
|
+
"https://raw.githubusercontent.com/buildy-ui/ui/main/packages/@ui8kit/registry/r"
|
|
48
|
+
],
|
|
49
|
+
// Component categories
|
|
50
|
+
componentCategories: ["ui", "composite", "components", "layouts", "lib", "blocks", "variants"],
|
|
51
|
+
// Component types (should match registryItemTypeSchema)
|
|
52
|
+
componentTypes: [
|
|
53
|
+
"registry:lib",
|
|
54
|
+
"registry:block",
|
|
55
|
+
"registry:component",
|
|
56
|
+
"registry:ui",
|
|
57
|
+
"registry:composite",
|
|
58
|
+
"registry:layout",
|
|
59
|
+
"registry:variants"
|
|
60
|
+
],
|
|
61
|
+
// Default directories structure
|
|
62
|
+
// Source directories (where CLI scans for components)
|
|
63
|
+
defaultDirectories: {
|
|
64
|
+
components: "./src/components",
|
|
65
|
+
lib: "./src/lib",
|
|
66
|
+
layouts: "./src/layouts",
|
|
67
|
+
blocks: "./src/blocks",
|
|
68
|
+
variants: "./src/variants"
|
|
69
|
+
},
|
|
70
|
+
// Schema descriptions and titles
|
|
71
|
+
descriptions: {
|
|
72
|
+
config: {
|
|
73
|
+
title: "UI8Kit Configuration",
|
|
74
|
+
description: "Configuration file for ui8kit CLI (UI8Kit structure)"
|
|
75
|
+
},
|
|
76
|
+
registry: {
|
|
77
|
+
title: "UI8Kit Registry",
|
|
78
|
+
description: "Registry schema for UI8Kit component system"
|
|
79
|
+
},
|
|
80
|
+
registryItem: {
|
|
81
|
+
title: "UI8Kit Registry Item",
|
|
82
|
+
description: "Schema for individual registry items in the UI8Kit component system"
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
// Field descriptions
|
|
86
|
+
fieldDescriptions: {
|
|
87
|
+
schema: "JSON Schema URL",
|
|
88
|
+
framework: "Target framework",
|
|
89
|
+
typescript: "Whether the project uses TypeScript",
|
|
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
|
+
aliases: z.record(z.string(), z.string()).default(SCHEMA_CONFIG.defaultAliases),
|
|
157
|
+
registry: z.string().default(SCHEMA_CONFIG.defaultRegistry),
|
|
158
|
+
componentsDir: z.string().default(SCHEMA_CONFIG.defaultDirectories.components),
|
|
159
|
+
libDir: z.string().default(SCHEMA_CONFIG.defaultDirectories.lib)
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
// src/registry/api.ts
|
|
163
|
+
var registryCache = /* @__PURE__ */ new Map();
|
|
164
|
+
function isUrl(path7) {
|
|
165
|
+
try {
|
|
166
|
+
new URL(path7);
|
|
167
|
+
return true;
|
|
168
|
+
} catch {
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
function getRegistryCache(registryType) {
|
|
173
|
+
if (!registryCache.has(registryType)) {
|
|
174
|
+
registryCache.set(registryType, {
|
|
175
|
+
workingCDN: null,
|
|
176
|
+
registryIndex: null
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
return registryCache.get(registryType);
|
|
180
|
+
}
|
|
181
|
+
async function findWorkingCDN(registryType) {
|
|
182
|
+
const cache = getRegistryCache(registryType);
|
|
183
|
+
if (cache.workingCDN) {
|
|
184
|
+
return cache.workingCDN;
|
|
185
|
+
}
|
|
186
|
+
const cdnUrls = getCdnUrls(registryType);
|
|
187
|
+
for (const baseUrl of cdnUrls) {
|
|
188
|
+
try {
|
|
189
|
+
console.log(`\u{1F50D} Testing ${registryType} CDN: ${baseUrl}`);
|
|
190
|
+
const response = await fetch(`${baseUrl}/index.json`);
|
|
191
|
+
if (response.ok) {
|
|
192
|
+
cache.workingCDN = baseUrl;
|
|
193
|
+
console.log(`\u2705 Using ${registryType} CDN: ${baseUrl}`);
|
|
194
|
+
return baseUrl;
|
|
195
|
+
}
|
|
196
|
+
} catch (error) {
|
|
197
|
+
console.log(`\u274C ${registryType} CDN failed: ${baseUrl}`);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
throw new Error(`No working ${registryType} CDN found`);
|
|
201
|
+
}
|
|
202
|
+
async function fetchFromWorkingCDN(path7, registryType) {
|
|
203
|
+
const baseUrl = await findWorkingCDN(registryType);
|
|
204
|
+
const url = `${baseUrl}/${path7}`;
|
|
205
|
+
console.log(`\u{1F3AF} Fetching from ${registryType}: ${url}`);
|
|
206
|
+
const response = await fetch(url);
|
|
207
|
+
if (!response.ok) {
|
|
208
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
209
|
+
}
|
|
210
|
+
return await response.json();
|
|
211
|
+
}
|
|
212
|
+
async function getRegistryIndex(registryType) {
|
|
213
|
+
const cache = getRegistryCache(registryType);
|
|
214
|
+
if (cache.registryIndex) {
|
|
215
|
+
return cache.registryIndex;
|
|
216
|
+
}
|
|
217
|
+
console.log(`\u{1F310} Fetching ${registryType} registry index`);
|
|
218
|
+
cache.registryIndex = await fetchFromWorkingCDN("index.json", registryType);
|
|
219
|
+
return cache.registryIndex;
|
|
220
|
+
}
|
|
221
|
+
async function getComponentByType(name, registryType) {
|
|
222
|
+
try {
|
|
223
|
+
const index = await getRegistryIndex(registryType);
|
|
224
|
+
const componentInfo = index.components?.find((c) => c.name === name);
|
|
225
|
+
if (!componentInfo) {
|
|
226
|
+
console.log(`\u274C Component ${name} not found in ${registryType} registry`);
|
|
227
|
+
return null;
|
|
228
|
+
}
|
|
229
|
+
const folder = componentInfo.type === "registry:variants" ? "components/variants" : TYPE_TO_FOLDER[componentInfo.type];
|
|
230
|
+
if (!folder) {
|
|
231
|
+
console.log(`\u274C Unknown component type: ${componentInfo.type}`);
|
|
232
|
+
return null;
|
|
233
|
+
}
|
|
234
|
+
console.log(`\u{1F3AF} Loading ${name} from /${folder}/ (type: ${componentInfo.type})`);
|
|
235
|
+
const data = await fetchFromWorkingCDN(`${folder}/${name}.json`, registryType);
|
|
236
|
+
return componentSchema.parse(data);
|
|
237
|
+
} catch (error) {
|
|
238
|
+
console.log(`\u274C Failed to get component by type: ${error.message}`);
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
async function getComponent(name, registryType = SCHEMA_CONFIG.defaultRegistryType) {
|
|
243
|
+
try {
|
|
244
|
+
if (isUrl(name)) {
|
|
245
|
+
return await fetchFromUrl(name);
|
|
246
|
+
}
|
|
247
|
+
return await getComponentByType(name, registryType);
|
|
248
|
+
} catch (error) {
|
|
249
|
+
console.error(`\u274C Failed to fetch ${name} from ${registryType}:`, error.message);
|
|
250
|
+
return null;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
async function fetchFromUrl(url) {
|
|
254
|
+
console.log(`\u{1F310} Fetching component from: ${url}`);
|
|
255
|
+
const response = await fetch(url);
|
|
256
|
+
if (!response.ok) {
|
|
257
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
258
|
+
}
|
|
259
|
+
const data = await response.json();
|
|
260
|
+
return componentSchema.parse(data);
|
|
261
|
+
}
|
|
262
|
+
async function getAllComponents(registryType = SCHEMA_CONFIG.defaultRegistryType) {
|
|
263
|
+
try {
|
|
264
|
+
console.log(`\u{1F310} Fetching all ${registryType} components using optimized approach`);
|
|
265
|
+
const indexData = await getRegistryIndex(registryType);
|
|
266
|
+
const components = [];
|
|
267
|
+
if (indexData.components && Array.isArray(indexData.components)) {
|
|
268
|
+
for (const componentInfo of indexData.components) {
|
|
269
|
+
const component = await getComponent(componentInfo.name, registryType);
|
|
270
|
+
if (component) {
|
|
271
|
+
components.push(component);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
return components;
|
|
276
|
+
} catch (error) {
|
|
277
|
+
console.error(`\u274C Failed to fetch all ${registryType} components:`, error.message);
|
|
278
|
+
return [];
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// src/registry/retry-api.ts
|
|
283
|
+
import fetch2 from "node-fetch";
|
|
284
|
+
var MAX_RETRIES = 3;
|
|
285
|
+
var RETRY_DELAY = 2e3;
|
|
286
|
+
var registryCache2 = /* @__PURE__ */ new Map();
|
|
287
|
+
function isUrl2(path7) {
|
|
288
|
+
try {
|
|
289
|
+
new URL(path7);
|
|
290
|
+
return true;
|
|
291
|
+
} catch {
|
|
292
|
+
return false;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
function getRegistryCache2(registryType) {
|
|
296
|
+
if (!registryCache2.has(registryType)) {
|
|
297
|
+
registryCache2.set(registryType, {
|
|
298
|
+
workingCDN: null,
|
|
299
|
+
registryIndex: null
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
return registryCache2.get(registryType);
|
|
303
|
+
}
|
|
304
|
+
async function checkInternetConnection() {
|
|
305
|
+
try {
|
|
306
|
+
console.log("\u{1F310} Checking internet connection...");
|
|
307
|
+
const controller = new AbortController();
|
|
308
|
+
const timeoutId = setTimeout(() => controller.abort(), 5e3);
|
|
309
|
+
const response = await fetch2("https://www.google.com", {
|
|
310
|
+
method: "HEAD",
|
|
311
|
+
signal: controller.signal
|
|
312
|
+
});
|
|
313
|
+
clearTimeout(timeoutId);
|
|
314
|
+
return response.ok;
|
|
315
|
+
} catch (error) {
|
|
316
|
+
console.log("\u274C No internet connection detected");
|
|
317
|
+
return false;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
async function fetchWithRetry(url, retries = MAX_RETRIES) {
|
|
321
|
+
for (let attempt = 1; attempt <= retries; attempt++) {
|
|
322
|
+
try {
|
|
323
|
+
console.log(`\u{1F504} Attempt ${attempt}/${retries}: ${url}`);
|
|
324
|
+
const controller = new AbortController();
|
|
325
|
+
const timeoutId = setTimeout(() => controller.abort(), 1e4);
|
|
326
|
+
const response = await fetch2(url, {
|
|
327
|
+
signal: controller.signal
|
|
328
|
+
});
|
|
329
|
+
clearTimeout(timeoutId);
|
|
330
|
+
if (response.ok) {
|
|
331
|
+
return await response.json();
|
|
332
|
+
} else if (response.status === 404) {
|
|
333
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
334
|
+
} else {
|
|
335
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
336
|
+
}
|
|
337
|
+
} catch (error) {
|
|
338
|
+
console.log(`\u274C Attempt ${attempt} failed: ${error.message}`);
|
|
339
|
+
if (attempt === retries) {
|
|
340
|
+
throw error;
|
|
341
|
+
}
|
|
342
|
+
console.log(`\u23F3 Waiting ${RETRY_DELAY / 1e3}s before retry...`);
|
|
343
|
+
await new Promise((resolve) => setTimeout(resolve, RETRY_DELAY));
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
async function findWorkingCDNWithRetry(registryType) {
|
|
348
|
+
const cache = getRegistryCache2(registryType);
|
|
349
|
+
if (cache.workingCDN) {
|
|
350
|
+
return cache.workingCDN;
|
|
351
|
+
}
|
|
352
|
+
if (!await checkInternetConnection()) {
|
|
353
|
+
throw new Error("No internet connection available");
|
|
354
|
+
}
|
|
355
|
+
const cdnUrls = getCdnUrls(registryType);
|
|
356
|
+
for (const baseUrl of cdnUrls) {
|
|
357
|
+
try {
|
|
358
|
+
console.log(`\u{1F50D} Testing ${registryType} CDN with retry: ${baseUrl}`);
|
|
359
|
+
await fetchWithRetry(`${baseUrl}/index.json`, 2);
|
|
360
|
+
cache.workingCDN = baseUrl;
|
|
361
|
+
console.log(`\u2705 Using ${registryType} CDN: ${baseUrl}`);
|
|
362
|
+
return baseUrl;
|
|
363
|
+
} catch (error) {
|
|
364
|
+
console.log(`\u274C ${registryType} CDN failed: ${baseUrl}`);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
throw new Error(`No working ${registryType} CDN found`);
|
|
368
|
+
}
|
|
369
|
+
async function fetchFromWorkingCDNWithRetry(path7, registryType) {
|
|
370
|
+
const baseUrl = await findWorkingCDNWithRetry(registryType);
|
|
371
|
+
const url = `${baseUrl}/${path7}`;
|
|
372
|
+
console.log(`\u{1F3AF} Fetching from ${registryType} with retry: ${url}`);
|
|
373
|
+
return await fetchWithRetry(url);
|
|
374
|
+
}
|
|
375
|
+
async function getRegistryIndexWithRetry(registryType) {
|
|
376
|
+
const cache = getRegistryCache2(registryType);
|
|
377
|
+
if (cache.registryIndex) {
|
|
378
|
+
return cache.registryIndex;
|
|
379
|
+
}
|
|
380
|
+
console.log(`\u{1F310} Fetching ${registryType} registry index with retry`);
|
|
381
|
+
cache.registryIndex = await fetchFromWorkingCDNWithRetry("index.json", registryType);
|
|
382
|
+
return cache.registryIndex;
|
|
383
|
+
}
|
|
384
|
+
async function getComponentByTypeWithRetry(name, registryType) {
|
|
385
|
+
try {
|
|
386
|
+
const index = await getRegistryIndexWithRetry(registryType);
|
|
387
|
+
const componentInfo = index.components?.find((c) => c.name === name);
|
|
388
|
+
if (!componentInfo) {
|
|
389
|
+
console.log(`\u274C Component ${name} not found in ${registryType} registry`);
|
|
390
|
+
return null;
|
|
391
|
+
}
|
|
392
|
+
const folder = TYPE_TO_FOLDER[componentInfo.type];
|
|
393
|
+
if (!folder) {
|
|
394
|
+
console.log(`\u274C Unknown component type: ${componentInfo.type}`);
|
|
395
|
+
return null;
|
|
396
|
+
}
|
|
397
|
+
console.log(`\u{1F3AF} Loading ${name} from /${registryType}/${folder}/ (type: ${componentInfo.type})`);
|
|
398
|
+
const data = await fetchFromWorkingCDNWithRetry(`${folder}/${name}.json`, registryType);
|
|
399
|
+
return componentSchema.parse(data);
|
|
400
|
+
} catch (error) {
|
|
401
|
+
console.log(`\u274C Failed to get component by type: ${error.message}`);
|
|
402
|
+
return null;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
async function getComponentWithRetry(name, registryType = SCHEMA_CONFIG.defaultRegistryType) {
|
|
406
|
+
try {
|
|
407
|
+
if (isUrl2(name)) {
|
|
408
|
+
return await fetchFromUrlWithRetry(name);
|
|
409
|
+
}
|
|
410
|
+
if (!await checkInternetConnection()) {
|
|
411
|
+
throw new Error("No internet connection available");
|
|
412
|
+
}
|
|
413
|
+
return await getComponentByTypeWithRetry(name, registryType);
|
|
414
|
+
} catch (error) {
|
|
415
|
+
console.error(`\u274C Failed to fetch ${name} from ${registryType}:`, error.message);
|
|
416
|
+
return null;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
async function fetchFromUrlWithRetry(url) {
|
|
420
|
+
console.log(`\u{1F310} Fetching component from URL with retry: ${url}`);
|
|
421
|
+
const data = await fetchWithRetry(url);
|
|
422
|
+
return componentSchema.parse(data);
|
|
423
|
+
}
|
|
424
|
+
async function getAllComponentsWithRetry(registryType = SCHEMA_CONFIG.defaultRegistryType) {
|
|
425
|
+
try {
|
|
426
|
+
console.log(`\u{1F310} Fetching all ${registryType} components with retry logic`);
|
|
427
|
+
if (!await checkInternetConnection()) {
|
|
428
|
+
throw new Error("No internet connection available");
|
|
429
|
+
}
|
|
430
|
+
const indexData = await getRegistryIndexWithRetry(registryType);
|
|
431
|
+
const components = [];
|
|
432
|
+
if (indexData.components && Array.isArray(indexData.components)) {
|
|
433
|
+
for (const componentInfo of indexData.components) {
|
|
434
|
+
const component = await getComponentWithRetry(componentInfo.name, registryType);
|
|
435
|
+
if (component) {
|
|
436
|
+
components.push(component);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
return components;
|
|
441
|
+
} catch (error) {
|
|
442
|
+
console.error(`\u274C Failed to fetch all ${registryType} components:`, error.message);
|
|
443
|
+
return [];
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// src/utils/project.ts
|
|
448
|
+
import fs from "fs-extra";
|
|
449
|
+
import path from "path";
|
|
450
|
+
var MODERN_CONFIG_NAME = "ui8kit.config.json";
|
|
451
|
+
async function isViteProject() {
|
|
452
|
+
const viteConfigFiles = [
|
|
453
|
+
"vite.config.ts",
|
|
454
|
+
"vite.config.js",
|
|
455
|
+
"vite.config.mts",
|
|
456
|
+
"vite.config.mjs"
|
|
457
|
+
];
|
|
458
|
+
for (const file of viteConfigFiles) {
|
|
459
|
+
if (await fs.pathExists(file)) {
|
|
460
|
+
return true;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
return false;
|
|
464
|
+
}
|
|
465
|
+
async function hasReact() {
|
|
466
|
+
const packageJsonPath = path.join(process.cwd(), "package.json");
|
|
467
|
+
if (!await fs.pathExists(packageJsonPath)) {
|
|
468
|
+
return false;
|
|
469
|
+
}
|
|
470
|
+
const packageJson = await fs.readJson(packageJsonPath);
|
|
471
|
+
const deps = {
|
|
472
|
+
...packageJson.dependencies,
|
|
473
|
+
...packageJson.devDependencies
|
|
474
|
+
};
|
|
475
|
+
return "react" in deps;
|
|
476
|
+
}
|
|
477
|
+
async function findConfig(_registryType) {
|
|
478
|
+
const srcConfig = await getConfig("./src");
|
|
479
|
+
if (srcConfig)
|
|
480
|
+
return srcConfig;
|
|
481
|
+
if (_registryType) {
|
|
482
|
+
const registryConfig = await getConfig(`./${_registryType}`);
|
|
483
|
+
if (registryConfig)
|
|
484
|
+
return registryConfig;
|
|
485
|
+
}
|
|
486
|
+
return await getConfig();
|
|
487
|
+
}
|
|
488
|
+
async function getConfig(registryPath) {
|
|
489
|
+
const baseDir = registryPath ? path.join(process.cwd(), registryPath) : process.cwd();
|
|
490
|
+
const configPath = path.join(baseDir, MODERN_CONFIG_NAME);
|
|
491
|
+
if (!await fs.pathExists(configPath)) {
|
|
492
|
+
return null;
|
|
493
|
+
}
|
|
494
|
+
try {
|
|
495
|
+
const config = await fs.readJson(configPath);
|
|
496
|
+
return configSchema.parse(config);
|
|
497
|
+
} catch (error) {
|
|
498
|
+
console.error("\u274C Invalid ui8kit.config.json:", error.message);
|
|
499
|
+
return null;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
async function saveConfig(config, registryPath) {
|
|
503
|
+
const configPath = registryPath ? path.join(process.cwd(), registryPath, MODERN_CONFIG_NAME) : path.join(process.cwd(), MODERN_CONFIG_NAME);
|
|
504
|
+
await fs.ensureDir(path.dirname(configPath));
|
|
505
|
+
await fs.writeJson(configPath, config, { spaces: 2 });
|
|
506
|
+
}
|
|
507
|
+
async function ensureDir(dirPath) {
|
|
508
|
+
await fs.ensureDir(dirPath);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// src/utils/registry-validator.ts
|
|
512
|
+
import fs2 from "fs-extra";
|
|
513
|
+
import chalk from "chalk";
|
|
514
|
+
async function validateComponentInstallation(components, registryType) {
|
|
515
|
+
return { isValid: true };
|
|
516
|
+
}
|
|
517
|
+
function handleValidationError(result) {
|
|
518
|
+
console.error(chalk.red("\u274C Registry Validation Error:"));
|
|
519
|
+
console.error(chalk.red(result.message));
|
|
520
|
+
if (result.missingComponents && result.missingComponents.length > 0) {
|
|
521
|
+
console.log(chalk.yellow("\n\u{1F4A1} Suggestion:"));
|
|
522
|
+
console.log(`Install missing components first: ${chalk.cyan(`npx ui8kit add ${result.missingComponents.join(" ")}`)}
|
|
523
|
+
`);
|
|
524
|
+
}
|
|
525
|
+
process.exit(1);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// src/utils/dependency-checker.ts
|
|
529
|
+
import fs3 from "fs-extra";
|
|
530
|
+
import path2 from "path";
|
|
531
|
+
import chalk2 from "chalk";
|
|
532
|
+
async function checkProjectDependencies(requiredDeps) {
|
|
533
|
+
const packageJsonPath = path2.join(process.cwd(), "package.json");
|
|
534
|
+
if (!await fs3.pathExists(packageJsonPath)) {
|
|
535
|
+
return {
|
|
536
|
+
installed: [],
|
|
537
|
+
missing: requiredDeps,
|
|
538
|
+
workspace: []
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
try {
|
|
542
|
+
const packageJson = await fs3.readJson(packageJsonPath);
|
|
543
|
+
const allDeps = {
|
|
544
|
+
...packageJson.dependencies,
|
|
545
|
+
...packageJson.devDependencies
|
|
546
|
+
};
|
|
547
|
+
const installed = [];
|
|
548
|
+
const missing = [];
|
|
549
|
+
const workspace = [];
|
|
550
|
+
for (const dep of requiredDeps) {
|
|
551
|
+
const version = allDeps[dep];
|
|
552
|
+
if (!version) {
|
|
553
|
+
missing.push(dep);
|
|
554
|
+
} else if (version.startsWith("workspace:")) {
|
|
555
|
+
workspace.push(dep);
|
|
556
|
+
} else {
|
|
557
|
+
installed.push(dep);
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
return { installed, missing, workspace };
|
|
561
|
+
} catch (error) {
|
|
562
|
+
return {
|
|
563
|
+
installed: [],
|
|
564
|
+
missing: requiredDeps,
|
|
565
|
+
workspace: []
|
|
566
|
+
};
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
function showDependencyStatus(deps) {
|
|
570
|
+
if (deps.installed.length > 0) {
|
|
571
|
+
console.log(chalk2.green(` \u2705 Already installed: ${deps.installed.join(", ")}`));
|
|
572
|
+
}
|
|
573
|
+
if (deps.workspace.length > 0) {
|
|
574
|
+
console.log(chalk2.blue(` \u{1F517} Workspace dependencies: ${deps.workspace.join(", ")}`));
|
|
575
|
+
}
|
|
576
|
+
if (deps.missing.length > 0) {
|
|
577
|
+
console.log(chalk2.yellow(` \u{1F4E6} Will install: ${deps.missing.join(", ")}`));
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
async function filterMissingDependencies(dependencies) {
|
|
581
|
+
const status = await checkProjectDependencies(dependencies);
|
|
582
|
+
return status.missing;
|
|
583
|
+
}
|
|
584
|
+
function isWorkspaceError(errorMessage) {
|
|
585
|
+
return errorMessage.includes("EUNSUPPORTEDPROTOCOL") || errorMessage.includes("workspace:") || errorMessage.includes('Unsupported URL Type "workspace:"');
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
// src/utils/cli-messages.ts
|
|
589
|
+
var CLI_MESSAGES = {
|
|
590
|
+
// Error messages
|
|
591
|
+
errors: {
|
|
592
|
+
noComponentsSpecified: "Please specify at least one component to add.",
|
|
593
|
+
notInitialized: "ui8kit is not initialized in this project.",
|
|
594
|
+
notViteProject: "This doesn't appear to be a Vite project.",
|
|
595
|
+
reactNotInstalled: "React is not installed in this project.",
|
|
596
|
+
buildFailed: "Build failed:",
|
|
597
|
+
scanFailed: "Scan failed:",
|
|
598
|
+
registryTempUnavailable: "registry temporarily unavailable",
|
|
599
|
+
noCDNFound: (registryType) => `No working ${registryType} CDN found`,
|
|
600
|
+
componentNotFound: (name, registryType) => `Component "${name}" not found in ${registryType} registry`,
|
|
601
|
+
unknownComponentType: (type) => `Unknown component type: ${type}`,
|
|
602
|
+
fileNotFound: (path7) => `File not found: ${path7}`,
|
|
603
|
+
invalidConfig: "Invalid ui8kit.config.json:",
|
|
604
|
+
failedToInstall: (name, registryType) => `Failed to install ${name} from ${registryType}`,
|
|
605
|
+
failedToFetch: (registryType) => `Failed to fetch components from ${registryType}`,
|
|
606
|
+
failedToFetchComponent: (name, registryType) => `Failed to fetch ${name} from ${registryType}:`,
|
|
607
|
+
failedToAnalyzeDeps: (path7) => `Warning: Could not analyze dependencies for ${path7}:`,
|
|
608
|
+
dependenciesFailed: "Failed to install dependencies",
|
|
609
|
+
couldNotInstallDeps: (name) => `Warning: Could not install some dependencies for ${name}`
|
|
610
|
+
},
|
|
611
|
+
// Success messages
|
|
612
|
+
success: {
|
|
613
|
+
initialized: (registryName) => `UI8Kit ${registryName} structure initialized successfully!`,
|
|
614
|
+
setupComplete: (registryName) => `UI8Kit ${registryName} Setup complete!`,
|
|
615
|
+
installed: (name, registryType) => `Installed ${name} from ${registryType}`,
|
|
616
|
+
allInstalled: (registryType) => `All ${registryType} components installed successfully!`,
|
|
617
|
+
componentsInstalled: "Components installed successfully!",
|
|
618
|
+
depsInstalled: "Dependencies installed",
|
|
619
|
+
registryBuilt: "Registry built successfully!",
|
|
620
|
+
schemasGenerated: "Schema files generated successfully!",
|
|
621
|
+
registryGenerated: (registryName) => `${registryName} registry generated successfully!`,
|
|
622
|
+
depsAvailable: "All dependencies already available"
|
|
623
|
+
},
|
|
624
|
+
// Info messages
|
|
625
|
+
info: {
|
|
626
|
+
initializing: (registryName) => `Initializing UI8Kit in your project (${registryName} registry)...`,
|
|
627
|
+
installing: (registryName) => `Installing from ${registryName} registry...`,
|
|
628
|
+
installingAll: (registryName) => `Installing all available components from ${registryName} registry...`,
|
|
629
|
+
retryEnabled: "Retry mode enabled - using enhanced connection logic",
|
|
630
|
+
fetchingComponentList: (registryType) => `Fetching component list from ${registryType}...`,
|
|
631
|
+
fetchingRegistry: (registryType) => `Fetching ${registryType} registry index`,
|
|
632
|
+
scanningComponents: (registryName) => `Scanning ${registryName} components...`,
|
|
633
|
+
building: "Building registry...",
|
|
634
|
+
processingComponents: "Processing components...",
|
|
635
|
+
scanningDirectories: "Scanning directories...",
|
|
636
|
+
analyzingDeps: "Found {count} components, analyzing dependencies...",
|
|
637
|
+
installationCancelled: "Initialization cancelled.",
|
|
638
|
+
workspaceDepsDetected: "Workspace dependency detected. Installing individually...",
|
|
639
|
+
checkingConnection: "Checking internet connection...",
|
|
640
|
+
testing: (registryType, baseUrl) => `Testing ${registryType} CDN: ${baseUrl}`,
|
|
641
|
+
using: (registryType, baseUrl) => `Using ${registryType} CDN: ${baseUrl}`,
|
|
642
|
+
loading: (name, registryType, folder, type) => `Loading ${name} from /${registryType}/${folder}/ (type: ${type})`,
|
|
643
|
+
fetching: (registryType, url) => `Fetching from ${registryType}: ${url}`,
|
|
644
|
+
fetchingUrl: "Fetching component from:",
|
|
645
|
+
fetchingUrlWithRetry: "Fetching component from URL with retry:"
|
|
646
|
+
},
|
|
647
|
+
// Prompts
|
|
648
|
+
prompts: {
|
|
649
|
+
typescript: "Are you using TypeScript?",
|
|
650
|
+
overwrite: (registryName) => `UI8Kit is already initialized for ${registryName} registry. Overwrite configuration?`
|
|
651
|
+
},
|
|
652
|
+
// Examples and help text
|
|
653
|
+
examples: {
|
|
654
|
+
add: [
|
|
655
|
+
"Example: npx ui8kit@latest add button card",
|
|
656
|
+
"Example: npx ui8kit@latest add button --registry ui",
|
|
657
|
+
"Example: npx ui8kit@latest add all # Install all components",
|
|
658
|
+
"Example: npx ui8kit@latest add --all --registry ui # Install all ui components",
|
|
659
|
+
"Example: npx ui8kit@latest add --retry # Enable retry for unreliable connections",
|
|
660
|
+
'Example: npx ui8kit@latest add "https://example.com/component.json"'
|
|
661
|
+
],
|
|
662
|
+
init: [
|
|
663
|
+
`npx ui8kit@latest add button --registry ui - Add a button component`,
|
|
664
|
+
`npx ui8kit@latest add card input --registry ui - Add multiple components`,
|
|
665
|
+
`npx ui8kit@latest add --all --registry ui - Add all components`,
|
|
666
|
+
`npx ui8kit@latest add "https://example.com/component.json" - Add from external URL`
|
|
667
|
+
],
|
|
668
|
+
troubleshooting: [
|
|
669
|
+
"Check your internet connection",
|
|
670
|
+
"Use --retry flag: npx ui8kit@latest add --all --retry",
|
|
671
|
+
"Use VPN if available",
|
|
672
|
+
"Install from URL: npx ui8kit@latest add 'https://...'",
|
|
673
|
+
"Check https://ui.buildy.tw for manual download"
|
|
674
|
+
]
|
|
675
|
+
},
|
|
676
|
+
// Directory descriptions
|
|
677
|
+
directories: {
|
|
678
|
+
lib: "Utils, helpers, functions",
|
|
679
|
+
variants: "CVA variant configurations",
|
|
680
|
+
"components/ui": "UI components",
|
|
681
|
+
components: "Complex components",
|
|
682
|
+
layouts: "Page layouts and structures",
|
|
683
|
+
blocks: "Component blocks"
|
|
684
|
+
},
|
|
685
|
+
// Spinners and status
|
|
686
|
+
status: {
|
|
687
|
+
installing: (name, registryType) => `Installing ${name} from ${registryType}...`,
|
|
688
|
+
wouldInstall: (name, registryType) => `Would install: ${name} from ${registryType}`,
|
|
689
|
+
foundComponents: (count, registryType) => `Found ${count} components in ${registryType}`,
|
|
690
|
+
wouldInstallFrom: (registryType) => `Would install from ${registryType}:`,
|
|
691
|
+
builtComponents: (count) => `Built ${count} components`,
|
|
692
|
+
scannedComponents: (count) => `Scanned ${count} components`,
|
|
693
|
+
skipped: (fileName) => `Skipped ${fileName} (already exists, use --force to overwrite)`
|
|
694
|
+
}
|
|
695
|
+
};
|
|
696
|
+
|
|
697
|
+
// src/commands/add.ts
|
|
698
|
+
async function addCommand(components, options) {
|
|
699
|
+
const registryType = resolveRegistryType(options.registry);
|
|
700
|
+
if (options.all || components.includes("all")) {
|
|
701
|
+
return await addAllComponents(options, registryType);
|
|
702
|
+
}
|
|
703
|
+
if (components.length === 0) {
|
|
704
|
+
console.error(chalk3.red(`\u274C ${CLI_MESSAGES.errors.noComponentsSpecified}`));
|
|
705
|
+
CLI_MESSAGES.examples.add.forEach((example) => console.log(example));
|
|
706
|
+
process.exit(1);
|
|
707
|
+
}
|
|
708
|
+
const validation = await validateComponentInstallation(components, registryType);
|
|
709
|
+
if (!validation.isValid) {
|
|
710
|
+
handleValidationError(validation);
|
|
711
|
+
}
|
|
712
|
+
const config = await findConfig(registryType);
|
|
713
|
+
if (!config) {
|
|
714
|
+
console.error(chalk3.red(`\u274C ${CLI_MESSAGES.errors.notInitialized}`));
|
|
715
|
+
console.log(`Run 'npx ui8kit@latest init' first.`);
|
|
716
|
+
console.log(`For ${registryType} registry, run: npx ui8kit@latest init --registry ${registryType}`);
|
|
717
|
+
process.exit(1);
|
|
718
|
+
}
|
|
719
|
+
const getComponentFn = options.retry ? getComponentWithRetry : getComponent;
|
|
720
|
+
if (options.retry) {
|
|
721
|
+
console.log(chalk3.blue(`\u{1F504} ${CLI_MESSAGES.info.retryEnabled}`));
|
|
722
|
+
}
|
|
723
|
+
console.log(chalk3.blue(`\u{1F4E6} ${CLI_MESSAGES.info.installing(registryType)}`));
|
|
724
|
+
const results = await processComponents(components, registryType, config, getComponentFn, options);
|
|
725
|
+
displayInstallationSummary(registryType, results);
|
|
726
|
+
}
|
|
727
|
+
async function addAllComponents(options, registryType) {
|
|
728
|
+
console.log(chalk3.blue(`\u{1F680} ${CLI_MESSAGES.info.installingAll(registryType)}`));
|
|
729
|
+
const config = await findConfig(registryType);
|
|
730
|
+
if (!config) {
|
|
731
|
+
console.error(chalk3.red(`\u274C ${CLI_MESSAGES.errors.notInitialized}`));
|
|
732
|
+
console.log(`Run 'npx ui8kit@latest init' first.`);
|
|
733
|
+
console.log(`For ${registryType} registry, run: npx ui8kit@latest init --registry ${registryType}`);
|
|
734
|
+
process.exit(1);
|
|
735
|
+
}
|
|
736
|
+
const getAllComponentsFn = options.retry ? getAllComponentsWithRetry : getAllComponents;
|
|
737
|
+
if (options.retry) {
|
|
738
|
+
console.log(chalk3.blue(`\u{1F504} ${CLI_MESSAGES.info.retryEnabled}`));
|
|
739
|
+
}
|
|
740
|
+
const spinner = ora(CLI_MESSAGES.info.fetchingComponentList(registryType)).start();
|
|
741
|
+
try {
|
|
742
|
+
const allComponents = await getAllComponentsFn(registryType);
|
|
743
|
+
if (allComponents.length === 0) {
|
|
744
|
+
spinner.fail(`No components found in ${registryType} registry`);
|
|
745
|
+
console.log(chalk3.yellow(`
|
|
746
|
+
\u26A0\uFE0F ${registryType} ${CLI_MESSAGES.errors.registryTempUnavailable}`));
|
|
747
|
+
console.log("Try these alternatives:");
|
|
748
|
+
CLI_MESSAGES.examples.troubleshooting.forEach((alt) => console.log(` \u2022 ${alt}`));
|
|
749
|
+
return;
|
|
750
|
+
}
|
|
751
|
+
spinner.succeed(CLI_MESSAGES.status.foundComponents(allComponents.length, registryType));
|
|
752
|
+
if (options.dryRun) {
|
|
753
|
+
console.log(chalk3.blue(`
|
|
754
|
+
\u{1F4CB} ${CLI_MESSAGES.status.wouldInstallFrom(registryType)}`));
|
|
755
|
+
allComponents.forEach((comp) => {
|
|
756
|
+
console.log(` - ${comp.name} (${comp.type})`);
|
|
757
|
+
});
|
|
758
|
+
return;
|
|
759
|
+
}
|
|
760
|
+
const results = await processComponents(
|
|
761
|
+
allComponents.map((c) => c.name),
|
|
762
|
+
registryType,
|
|
763
|
+
config,
|
|
764
|
+
options.retry ? getComponentWithRetry : getComponent,
|
|
765
|
+
options,
|
|
766
|
+
allComponents
|
|
767
|
+
);
|
|
768
|
+
await installComponentsIndex(registryType, config);
|
|
769
|
+
displayInstallationSummary(registryType, results);
|
|
770
|
+
} catch (error) {
|
|
771
|
+
spinner.fail(CLI_MESSAGES.errors.failedToFetch(registryType));
|
|
772
|
+
console.error(chalk3.red("\u274C Error:"), error.message);
|
|
773
|
+
console.log(chalk3.yellow(`
|
|
774
|
+
\u26A0\uFE0F ${registryType} ${CLI_MESSAGES.errors.registryTempUnavailable}`));
|
|
775
|
+
console.log("Try these alternatives:");
|
|
776
|
+
CLI_MESSAGES.examples.troubleshooting.forEach((alt) => console.log(` \u2022 ${alt}`));
|
|
777
|
+
process.exit(1);
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
async function processComponents(componentNames, registryType, config, getComponentFn, options, preloadedComponents) {
|
|
781
|
+
const results = [];
|
|
782
|
+
const componentMap = new Map(preloadedComponents?.map((c) => [c.name, c]));
|
|
783
|
+
for (const componentName of componentNames) {
|
|
784
|
+
const spinner = ora(CLI_MESSAGES.status.installing(componentName, registryType)).start();
|
|
785
|
+
try {
|
|
786
|
+
let component = componentMap?.get(componentName);
|
|
787
|
+
if (!component) {
|
|
788
|
+
component = await getComponentFn(componentName, registryType);
|
|
789
|
+
}
|
|
790
|
+
if (!component) {
|
|
791
|
+
throw new Error(CLI_MESSAGES.errors.componentNotFound(componentName, registryType));
|
|
792
|
+
}
|
|
793
|
+
if (options.dryRun) {
|
|
794
|
+
spinner.succeed(CLI_MESSAGES.status.wouldInstall(component.name, registryType));
|
|
795
|
+
console.log(` Type: ${component.type}`);
|
|
796
|
+
console.log(` Files: ${component.files.length}`);
|
|
797
|
+
console.log(` Dependencies: ${component.dependencies.join(", ") || "none"}`);
|
|
798
|
+
if (component.dependencies.length > 0) {
|
|
799
|
+
const depStatus = await checkProjectDependencies(component.dependencies);
|
|
800
|
+
showDependencyStatus(depStatus);
|
|
801
|
+
}
|
|
802
|
+
continue;
|
|
803
|
+
}
|
|
804
|
+
await installComponentFiles(component, config, options.force);
|
|
805
|
+
if (component.dependencies.length > 0) {
|
|
806
|
+
try {
|
|
807
|
+
await installDependencies(component.dependencies);
|
|
808
|
+
} catch (error) {
|
|
809
|
+
console.log(chalk3.yellow(` \u26A0\uFE0F ${CLI_MESSAGES.errors.couldNotInstallDeps(component.name)}`));
|
|
810
|
+
console.log(chalk3.yellow(` Dependencies: ${component.dependencies.join(", ")}`));
|
|
811
|
+
console.log(chalk3.yellow(` Please install them manually if needed`));
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
spinner.succeed(CLI_MESSAGES.status.installing(component.name, registryType));
|
|
815
|
+
results.push({ name: component.name, status: "success" });
|
|
816
|
+
} catch (error) {
|
|
817
|
+
spinner.fail(CLI_MESSAGES.errors.failedToInstall(componentName, registryType));
|
|
818
|
+
console.error(chalk3.red(` Error: ${error.message}`));
|
|
819
|
+
results.push({
|
|
820
|
+
name: componentName,
|
|
821
|
+
status: "error",
|
|
822
|
+
error: error.message
|
|
823
|
+
});
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
return results;
|
|
827
|
+
}
|
|
828
|
+
function displayInstallationSummary(registryType, results) {
|
|
829
|
+
const successful = results.filter((r) => r.status === "success");
|
|
830
|
+
const failed = results.filter((r) => r.status === "error");
|
|
831
|
+
console.log(chalk3.blue("\n\u{1F4CA} Installation Summary:"));
|
|
832
|
+
console.log(` Registry: ${registryType}`);
|
|
833
|
+
console.log(` \u2705 Successful: ${successful.length}`);
|
|
834
|
+
console.log(` \u274C Failed: ${failed.length}`);
|
|
835
|
+
if (successful.length > 0) {
|
|
836
|
+
console.log(chalk3.green(`
|
|
837
|
+
\u{1F389} ${CLI_MESSAGES.success.componentsInstalled}`));
|
|
838
|
+
console.log("You can now import and use them in your project.");
|
|
839
|
+
}
|
|
840
|
+
if (failed.length > 0) {
|
|
841
|
+
process.exit(1);
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
async function installComponentFiles(component, config, force = false) {
|
|
845
|
+
for (const file of component.files) {
|
|
846
|
+
const fileName = path3.basename(file.path);
|
|
847
|
+
const target = file.target || inferTargetFromType(component.type);
|
|
848
|
+
const installDir = resolveInstallDir(target, config);
|
|
849
|
+
const targetPath = path3.join(process.cwd(), installDir, fileName);
|
|
850
|
+
if (!force && await fs4.pathExists(targetPath)) {
|
|
851
|
+
console.log(` \u26A0\uFE0F ${CLI_MESSAGES.status.skipped(fileName)}`);
|
|
852
|
+
continue;
|
|
853
|
+
}
|
|
854
|
+
await fs4.ensureDir(path3.dirname(targetPath));
|
|
855
|
+
await fs4.writeFile(targetPath, file.content, "utf-8");
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
function inferTargetFromType(componentType) {
|
|
859
|
+
switch (componentType) {
|
|
860
|
+
case "registry:ui":
|
|
861
|
+
return "ui";
|
|
862
|
+
case "registry:composite":
|
|
863
|
+
return "components";
|
|
864
|
+
case "registry:block":
|
|
865
|
+
return "blocks";
|
|
866
|
+
case "registry:component":
|
|
867
|
+
return "components";
|
|
868
|
+
case "registry:layout":
|
|
869
|
+
return "layouts";
|
|
870
|
+
case "registry:lib":
|
|
871
|
+
return "lib";
|
|
872
|
+
case "registry:variants":
|
|
873
|
+
return "variants";
|
|
874
|
+
default:
|
|
875
|
+
return "components";
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
function resolveInstallDir(target, config) {
|
|
879
|
+
const normalizedTarget = target.replace(/\\/g, "/").replace(/^\/?src\//i, "");
|
|
880
|
+
if (normalizedTarget === "lib") {
|
|
881
|
+
return normalizeDir(config.libDir || SCHEMA_CONFIG.defaultDirectories.lib);
|
|
882
|
+
}
|
|
883
|
+
if (normalizedTarget === "variants") {
|
|
884
|
+
return normalizeDir(SCHEMA_CONFIG.defaultDirectories.variants);
|
|
885
|
+
}
|
|
886
|
+
const baseComponentsDir = normalizeDir(config.componentsDir || SCHEMA_CONFIG.defaultDirectories.components);
|
|
887
|
+
if (normalizedTarget.includes("/")) {
|
|
888
|
+
const parentRoot = baseComponentsDir.replace(/[/\\]components$/i, "") || "src";
|
|
889
|
+
return path3.join(parentRoot, normalizedTarget).replace(/\\/g, "/");
|
|
890
|
+
}
|
|
891
|
+
if (normalizedTarget === "ui")
|
|
892
|
+
return path3.join(baseComponentsDir, "ui").replace(/\\/g, "/");
|
|
893
|
+
if (normalizedTarget === "components")
|
|
894
|
+
return baseComponentsDir;
|
|
895
|
+
switch (normalizedTarget) {
|
|
896
|
+
case "blocks":
|
|
897
|
+
return normalizeDir(SCHEMA_CONFIG.defaultDirectories.blocks);
|
|
898
|
+
case "layouts":
|
|
899
|
+
return normalizeDir(SCHEMA_CONFIG.defaultDirectories.layouts);
|
|
900
|
+
default:
|
|
901
|
+
return baseComponentsDir;
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
function normalizeDir(dir) {
|
|
905
|
+
return dir.replace(/^\.\//, "").replace(/\\/g, "/");
|
|
906
|
+
}
|
|
907
|
+
function resolveRegistryType(registryInput) {
|
|
908
|
+
if (!registryInput) {
|
|
909
|
+
return SCHEMA_CONFIG.defaultRegistryType;
|
|
910
|
+
}
|
|
911
|
+
if (SCHEMA_CONFIG.registryTypes.includes(registryInput)) {
|
|
912
|
+
return registryInput;
|
|
913
|
+
}
|
|
914
|
+
console.warn(chalk3.yellow(`\u26A0\uFE0F Unknown registry type: ${registryInput}`));
|
|
915
|
+
console.log(`Available registries: ${SCHEMA_CONFIG.registryTypes.join(", ")}`);
|
|
916
|
+
console.log(`Using default: ${SCHEMA_CONFIG.defaultRegistryType}`);
|
|
917
|
+
return SCHEMA_CONFIG.defaultRegistryType;
|
|
918
|
+
}
|
|
919
|
+
async function installDependencies(dependencies) {
|
|
920
|
+
const spinner = ora(CLI_MESSAGES.status.installing("dependencies", "")).start();
|
|
921
|
+
try {
|
|
922
|
+
const depStatus = await checkProjectDependencies(dependencies);
|
|
923
|
+
const missingDependencies = await filterMissingDependencies(dependencies);
|
|
924
|
+
if (missingDependencies.length === 0) {
|
|
925
|
+
spinner.succeed(CLI_MESSAGES.success.depsAvailable);
|
|
926
|
+
if (depStatus.workspace.length > 0) {
|
|
927
|
+
console.log(chalk3.blue(` \u{1F517} Using workspace dependencies: ${depStatus.workspace.join(", ")}`));
|
|
928
|
+
}
|
|
929
|
+
return;
|
|
930
|
+
}
|
|
931
|
+
showDependencyStatus(depStatus);
|
|
932
|
+
const packageManager = await detectPackageManager();
|
|
933
|
+
const installCommand = packageManager === "npm" ? ["install", ...missingDependencies] : ["add", ...missingDependencies];
|
|
934
|
+
await execa(packageManager, installCommand, {
|
|
935
|
+
cwd: process.cwd(),
|
|
936
|
+
stdio: "pipe"
|
|
937
|
+
});
|
|
938
|
+
spinner.succeed(CLI_MESSAGES.success.depsInstalled);
|
|
939
|
+
} catch (error) {
|
|
940
|
+
spinner.fail(CLI_MESSAGES.errors.dependenciesFailed);
|
|
941
|
+
const errorMessage = error.stderr || error.message;
|
|
942
|
+
if (isWorkspaceError(errorMessage)) {
|
|
943
|
+
console.log(chalk3.yellow(`
|
|
944
|
+
\u{1F4A1} ${CLI_MESSAGES.info.workspaceDepsDetected}`));
|
|
945
|
+
const results = await installDependenciesIndividually(dependencies);
|
|
946
|
+
if (results.some((r) => r.success)) {
|
|
947
|
+
console.log(chalk3.green("\u2705 Some dependencies installed successfully"));
|
|
948
|
+
return;
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
throw new Error(`${CLI_MESSAGES.errors.dependenciesFailed}: ${errorMessage}`);
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
async function installDependenciesIndividually(dependencies) {
|
|
955
|
+
const packageManager = await detectPackageManager();
|
|
956
|
+
const results = [];
|
|
957
|
+
const missingDeps = await filterMissingDependencies(dependencies);
|
|
958
|
+
for (const dep of missingDeps) {
|
|
959
|
+
try {
|
|
960
|
+
const installCommand = packageManager === "npm" ? ["install", dep] : ["add", dep];
|
|
961
|
+
await execa(packageManager, installCommand, {
|
|
962
|
+
cwd: process.cwd(),
|
|
963
|
+
stdio: "pipe"
|
|
964
|
+
});
|
|
965
|
+
console.log(chalk3.green(` \u2705 Installed ${dep}`));
|
|
966
|
+
results.push({ dep, success: true });
|
|
967
|
+
} catch (error) {
|
|
968
|
+
console.log(chalk3.yellow(` \u26A0\uFE0F Skipped ${dep} (may already be available)`));
|
|
969
|
+
results.push({ dep, success: false });
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
return results;
|
|
973
|
+
}
|
|
974
|
+
async function detectPackageManager() {
|
|
975
|
+
let dir = process.cwd();
|
|
976
|
+
while (true) {
|
|
977
|
+
if (await fs4.pathExists(path3.join(dir, "bun.lock")) || await fs4.pathExists(path3.join(dir, "bun.lockb"))) {
|
|
978
|
+
return "bun";
|
|
979
|
+
}
|
|
980
|
+
if (await fs4.pathExists(path3.join(dir, "pnpm-lock.yaml")))
|
|
981
|
+
return "pnpm";
|
|
982
|
+
if (await fs4.pathExists(path3.join(dir, "yarn.lock")))
|
|
983
|
+
return "yarn";
|
|
984
|
+
const packageJsonPath = path3.join(dir, "package.json");
|
|
985
|
+
if (await fs4.pathExists(packageJsonPath)) {
|
|
986
|
+
try {
|
|
987
|
+
const packageJson = await fs4.readJson(packageJsonPath);
|
|
988
|
+
const packageManager = String(packageJson.packageManager ?? "");
|
|
989
|
+
if (packageManager.startsWith("bun@"))
|
|
990
|
+
return "bun";
|
|
991
|
+
if (packageManager.startsWith("pnpm@"))
|
|
992
|
+
return "pnpm";
|
|
993
|
+
if (packageManager.startsWith("yarn@"))
|
|
994
|
+
return "yarn";
|
|
995
|
+
if (packageManager.startsWith("npm@"))
|
|
996
|
+
return "npm";
|
|
997
|
+
} catch {
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
const parent = path3.dirname(dir);
|
|
1001
|
+
if (parent === dir)
|
|
1002
|
+
break;
|
|
1003
|
+
dir = parent;
|
|
1004
|
+
}
|
|
1005
|
+
return "npm";
|
|
1006
|
+
}
|
|
1007
|
+
async function installComponentsIndex(registryType, config) {
|
|
1008
|
+
const spinner = ora("Installing components index...").start();
|
|
1009
|
+
try {
|
|
1010
|
+
const cdnUrls = SCHEMA_CONFIG.cdnBaseUrls;
|
|
1011
|
+
for (const baseUrl of cdnUrls) {
|
|
1012
|
+
try {
|
|
1013
|
+
const url = `${baseUrl}/components/index.json`;
|
|
1014
|
+
const response = await fetch3(url);
|
|
1015
|
+
if (response.ok) {
|
|
1016
|
+
const component = await response.json();
|
|
1017
|
+
for (const file of component.files) {
|
|
1018
|
+
const fileName = path3.basename(file.path);
|
|
1019
|
+
const targetDir = config.componentsDir;
|
|
1020
|
+
const targetPath = path3.join(process.cwd(), targetDir, fileName);
|
|
1021
|
+
await fs4.ensureDir(path3.dirname(targetPath));
|
|
1022
|
+
await fs4.writeFile(targetPath, file.content || "", "utf-8");
|
|
1023
|
+
}
|
|
1024
|
+
spinner.succeed("Installed components index");
|
|
1025
|
+
return;
|
|
1026
|
+
}
|
|
1027
|
+
} catch {
|
|
1028
|
+
continue;
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
spinner.info("Components index not found in registry (optional)");
|
|
1032
|
+
} catch (error) {
|
|
1033
|
+
spinner.fail("Could not install components index");
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
// src/commands/init.ts
|
|
1038
|
+
import chalk4 from "chalk";
|
|
1039
|
+
import prompts from "prompts";
|
|
1040
|
+
import ora2 from "ora";
|
|
1041
|
+
import path4 from "path";
|
|
1042
|
+
import fs5 from "fs-extra";
|
|
1043
|
+
import fetch4 from "node-fetch";
|
|
1044
|
+
async function initCommand(options) {
|
|
1045
|
+
const registryName = options.registry || SCHEMA_CONFIG.defaultRegistryType;
|
|
1046
|
+
const registryPath = `./${registryName}`;
|
|
1047
|
+
console.log(chalk4.blue(`\u{1F680} ${CLI_MESSAGES.info.initializing(registryName)}`));
|
|
1048
|
+
const viteDetected = await isViteProject();
|
|
1049
|
+
if (!viteDetected) {
|
|
1050
|
+
console.error(chalk4.red(`\u274C ${CLI_MESSAGES.errors.notViteProject}`));
|
|
1051
|
+
console.log("Please run this command in a Vite project directory.");
|
|
1052
|
+
process.exit(1);
|
|
1053
|
+
}
|
|
1054
|
+
if (!await hasReact()) {
|
|
1055
|
+
console.error(chalk4.red(`\u274C ${CLI_MESSAGES.errors.reactNotInstalled}`));
|
|
1056
|
+
console.log("Please install React first: npm install react react-dom");
|
|
1057
|
+
process.exit(1);
|
|
1058
|
+
}
|
|
1059
|
+
const existingConfig = await getConfig("./src");
|
|
1060
|
+
if (existingConfig && !options.yes) {
|
|
1061
|
+
const { overwrite } = await prompts({
|
|
1062
|
+
type: "confirm",
|
|
1063
|
+
name: "overwrite",
|
|
1064
|
+
message: CLI_MESSAGES.prompts.overwrite(registryName),
|
|
1065
|
+
initial: false
|
|
1066
|
+
});
|
|
1067
|
+
if (!overwrite) {
|
|
1068
|
+
console.log(chalk4.yellow(`\u26A0\uFE0F ${CLI_MESSAGES.info.installationCancelled}`));
|
|
1069
|
+
return;
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
const aliases = SCHEMA_CONFIG.defaultAliases;
|
|
1073
|
+
let config;
|
|
1074
|
+
if (options.yes) {
|
|
1075
|
+
config = {
|
|
1076
|
+
$schema: `${SCHEMA_CONFIG.baseUrl}.json`,
|
|
1077
|
+
framework: "vite-react",
|
|
1078
|
+
typescript: true,
|
|
1079
|
+
aliases,
|
|
1080
|
+
registry: SCHEMA_CONFIG.defaultRegistry,
|
|
1081
|
+
componentsDir: SCHEMA_CONFIG.defaultDirectories.components,
|
|
1082
|
+
libDir: SCHEMA_CONFIG.defaultDirectories.lib
|
|
1083
|
+
};
|
|
1084
|
+
} else {
|
|
1085
|
+
const responses = await prompts([
|
|
1086
|
+
{
|
|
1087
|
+
type: "confirm",
|
|
1088
|
+
name: "typescript",
|
|
1089
|
+
message: CLI_MESSAGES.prompts.typescript,
|
|
1090
|
+
initial: true
|
|
1091
|
+
}
|
|
1092
|
+
]);
|
|
1093
|
+
config = {
|
|
1094
|
+
$schema: `${SCHEMA_CONFIG.baseUrl}.json`,
|
|
1095
|
+
framework: "vite-react",
|
|
1096
|
+
typescript: responses.typescript,
|
|
1097
|
+
aliases,
|
|
1098
|
+
registry: SCHEMA_CONFIG.defaultRegistry,
|
|
1099
|
+
componentsDir: SCHEMA_CONFIG.defaultDirectories.components,
|
|
1100
|
+
libDir: SCHEMA_CONFIG.defaultDirectories.lib
|
|
1101
|
+
};
|
|
1102
|
+
}
|
|
1103
|
+
const spinner = ora2(CLI_MESSAGES.info.initializing(registryName)).start();
|
|
1104
|
+
try {
|
|
1105
|
+
await saveConfig(config, "./src");
|
|
1106
|
+
await ensureDir(config.libDir);
|
|
1107
|
+
await ensureDir(config.componentsDir);
|
|
1108
|
+
await ensureDir(path4.join(config.componentsDir, "ui"));
|
|
1109
|
+
await ensureDir(SCHEMA_CONFIG.defaultDirectories.blocks);
|
|
1110
|
+
await ensureDir(SCHEMA_CONFIG.defaultDirectories.layouts);
|
|
1111
|
+
await ensureDir(SCHEMA_CONFIG.defaultDirectories.variants);
|
|
1112
|
+
spinner.text = "Installing core utilities and variants...";
|
|
1113
|
+
await installCoreFiles(registryName, config, spinner);
|
|
1114
|
+
spinner.succeed(CLI_MESSAGES.success.initialized(registryName));
|
|
1115
|
+
console.log(chalk4.green(`
|
|
1116
|
+
\u2705 ${CLI_MESSAGES.success.setupComplete(registryName)}`));
|
|
1117
|
+
console.log("\nDirectories created:");
|
|
1118
|
+
console.log(` ${chalk4.cyan("src/lib/")} - Utils, helpers, functions`);
|
|
1119
|
+
console.log(` ${chalk4.cyan("src/variants/")} - CVA variant configurations`);
|
|
1120
|
+
console.log(` ${chalk4.cyan("src/components/ui/")} - UI components`);
|
|
1121
|
+
console.log(` ${chalk4.cyan("src/components/")} - Complex components`);
|
|
1122
|
+
console.log(` ${chalk4.cyan("src/layouts/")} - Page layouts and structures`);
|
|
1123
|
+
console.log(` ${chalk4.cyan("src/blocks/")} - Component blocks`);
|
|
1124
|
+
console.log("\nNext steps:");
|
|
1125
|
+
CLI_MESSAGES.examples.init.forEach((example) => console.log(` ${chalk4.cyan(example)}`));
|
|
1126
|
+
} catch (error) {
|
|
1127
|
+
spinner.fail(CLI_MESSAGES.errors.buildFailed);
|
|
1128
|
+
console.error(chalk4.red("\u274C Error:"), error.message);
|
|
1129
|
+
process.exit(1);
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
async function installCoreFiles(registryType, config, spinner) {
|
|
1133
|
+
const cdnUrls = getCdnUrls(registryType);
|
|
1134
|
+
let registryIndex = null;
|
|
1135
|
+
for (const baseUrl of cdnUrls) {
|
|
1136
|
+
try {
|
|
1137
|
+
const indexUrl = `${baseUrl}/index.json`;
|
|
1138
|
+
const response = await fetch4(indexUrl);
|
|
1139
|
+
if (response.ok) {
|
|
1140
|
+
registryIndex = await response.json();
|
|
1141
|
+
break;
|
|
1142
|
+
}
|
|
1143
|
+
} catch {
|
|
1144
|
+
continue;
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
if (!registryIndex) {
|
|
1148
|
+
spinner.text = "\u26A0\uFE0F Could not fetch registry index, creating local utils...";
|
|
1149
|
+
await createUtilsFile(config.libDir, config.typescript);
|
|
1150
|
+
return;
|
|
1151
|
+
}
|
|
1152
|
+
const variantItems = registryIndex.components.filter((c) => c.type === "registry:variants");
|
|
1153
|
+
const libItems = registryIndex.components.filter((c) => c.type === "registry:lib");
|
|
1154
|
+
for (const item of libItems) {
|
|
1155
|
+
spinner.text = `Installing ${item.name}...`;
|
|
1156
|
+
await installComponentFromRegistry(item.name, "registry:lib", cdnUrls, config);
|
|
1157
|
+
}
|
|
1158
|
+
for (const item of variantItems) {
|
|
1159
|
+
spinner.text = `Installing variant: ${item.name}...`;
|
|
1160
|
+
await installComponentFromRegistry(item.name, "registry:variants", cdnUrls, config);
|
|
1161
|
+
}
|
|
1162
|
+
spinner.text = `Installing variants index...`;
|
|
1163
|
+
await installVariantsIndex(cdnUrls, config);
|
|
1164
|
+
spinner.text = `\u2705 Installed ${libItems.length} utilities and ${variantItems.length} variants`;
|
|
1165
|
+
}
|
|
1166
|
+
async function installComponentFromRegistry(name, type, cdnUrls, config) {
|
|
1167
|
+
const folder = type === "registry:lib" ? "lib" : type === "registry:variants" ? "components/variants" : "components/ui";
|
|
1168
|
+
for (const baseUrl of cdnUrls) {
|
|
1169
|
+
try {
|
|
1170
|
+
const url = `${baseUrl}/${folder}/${name}.json`;
|
|
1171
|
+
const response = await fetch4(url);
|
|
1172
|
+
if (response.ok) {
|
|
1173
|
+
const component = await response.json();
|
|
1174
|
+
for (const file of component.files) {
|
|
1175
|
+
const fileName = path4.basename(file.path);
|
|
1176
|
+
let targetDir;
|
|
1177
|
+
if (type === "registry:lib") {
|
|
1178
|
+
targetDir = config.libDir;
|
|
1179
|
+
} else if (type === "registry:variants") {
|
|
1180
|
+
targetDir = SCHEMA_CONFIG.defaultDirectories.variants;
|
|
1181
|
+
} else {
|
|
1182
|
+
targetDir = path4.join(config.componentsDir, "ui");
|
|
1183
|
+
}
|
|
1184
|
+
const targetPath = path4.join(process.cwd(), targetDir, fileName);
|
|
1185
|
+
await fs5.ensureDir(path4.dirname(targetPath));
|
|
1186
|
+
await fs5.writeFile(targetPath, file.content || "", "utf-8");
|
|
1187
|
+
}
|
|
1188
|
+
return;
|
|
1189
|
+
}
|
|
1190
|
+
} catch {
|
|
1191
|
+
continue;
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
async function installVariantsIndex(cdnUrls, config) {
|
|
1196
|
+
for (const baseUrl of cdnUrls) {
|
|
1197
|
+
try {
|
|
1198
|
+
const url = `${baseUrl}/components/variants/index.json`;
|
|
1199
|
+
const response = await fetch4(url);
|
|
1200
|
+
if (response.ok) {
|
|
1201
|
+
const component = await response.json();
|
|
1202
|
+
for (const file of component.files) {
|
|
1203
|
+
const fileName = path4.basename(file.path);
|
|
1204
|
+
const targetDir = SCHEMA_CONFIG.defaultDirectories.variants;
|
|
1205
|
+
const targetPath = path4.join(process.cwd(), targetDir, fileName);
|
|
1206
|
+
await fs5.ensureDir(path4.dirname(targetPath));
|
|
1207
|
+
await fs5.writeFile(targetPath, file.content || "", "utf-8");
|
|
1208
|
+
}
|
|
1209
|
+
return;
|
|
1210
|
+
}
|
|
1211
|
+
} catch {
|
|
1212
|
+
continue;
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
async function createUtilsFile(libDir, typescript) {
|
|
1217
|
+
const utilsContent = `import { type ClassValue, clsx } from "clsx"
|
|
1218
|
+
import { twMerge } from "tailwind-merge"
|
|
1219
|
+
|
|
1220
|
+
export function cn(...inputs: ClassValue[]) {
|
|
1221
|
+
return twMerge(clsx(inputs))
|
|
1222
|
+
}`;
|
|
1223
|
+
const fileName = typescript ? "utils.ts" : "utils.js";
|
|
1224
|
+
const filePath = path4.join(process.cwd(), libDir, fileName);
|
|
1225
|
+
await fs5.writeFile(filePath, utilsContent, "utf-8");
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
// src/commands/build.ts
|
|
1229
|
+
import fs6 from "fs-extra";
|
|
1230
|
+
import path5 from "path";
|
|
1231
|
+
import chalk5 from "chalk";
|
|
1232
|
+
import ora3 from "ora";
|
|
1233
|
+
|
|
1234
|
+
// src/registry/build-schema.ts
|
|
1235
|
+
import { z as z2 } from "zod";
|
|
1236
|
+
var registryItemTypeSchema = z2.enum([
|
|
1237
|
+
"registry:lib",
|
|
1238
|
+
"registry:block",
|
|
1239
|
+
"registry:component",
|
|
1240
|
+
"registry:ui",
|
|
1241
|
+
"registry:composite",
|
|
1242
|
+
"registry:layout",
|
|
1243
|
+
"registry:variants"
|
|
1244
|
+
]);
|
|
1245
|
+
var registryItemFileSchema = z2.object({
|
|
1246
|
+
path: z2.string(),
|
|
1247
|
+
content: z2.string().optional(),
|
|
1248
|
+
// Populated during build
|
|
1249
|
+
target: z2.string().optional()
|
|
1250
|
+
});
|
|
1251
|
+
var registryItemTailwindSchema = z2.object({
|
|
1252
|
+
config: z2.object({
|
|
1253
|
+
content: z2.array(z2.string()).optional(),
|
|
1254
|
+
theme: z2.record(z2.string(), z2.any()).optional(),
|
|
1255
|
+
plugins: z2.array(z2.string()).optional()
|
|
1256
|
+
}).optional()
|
|
1257
|
+
});
|
|
1258
|
+
var registryItemCssVarsSchema = z2.object({
|
|
1259
|
+
theme: z2.record(z2.string(), z2.string()).optional(),
|
|
1260
|
+
light: z2.record(z2.string(), z2.string()).optional(),
|
|
1261
|
+
dark: z2.record(z2.string(), z2.string()).optional()
|
|
1262
|
+
});
|
|
1263
|
+
var registryItemSchema = z2.object({
|
|
1264
|
+
$schema: z2.string().optional(),
|
|
1265
|
+
name: z2.string(),
|
|
1266
|
+
type: registryItemTypeSchema,
|
|
1267
|
+
title: z2.string().optional(),
|
|
1268
|
+
description: z2.string().optional(),
|
|
1269
|
+
dependencies: z2.array(z2.string()).default([]),
|
|
1270
|
+
devDependencies: z2.array(z2.string()).default([]),
|
|
1271
|
+
registryDependencies: z2.array(z2.string()).optional(),
|
|
1272
|
+
files: z2.array(registryItemFileSchema),
|
|
1273
|
+
tailwind: registryItemTailwindSchema.optional(),
|
|
1274
|
+
cssVars: registryItemCssVarsSchema.optional(),
|
|
1275
|
+
meta: z2.record(z2.string(), z2.any()).optional()
|
|
1276
|
+
});
|
|
1277
|
+
var registrySchema = z2.object({
|
|
1278
|
+
$schema: z2.string().optional(),
|
|
1279
|
+
items: z2.array(registryItemSchema)
|
|
1280
|
+
});
|
|
1281
|
+
|
|
1282
|
+
// src/utils/schema-generator.ts
|
|
1283
|
+
import { zodToJsonSchema } from "zod-to-json-schema";
|
|
1284
|
+
import { z as z3 } from "zod";
|
|
1285
|
+
var fullRegistrySchema = z3.object({
|
|
1286
|
+
$schema: z3.string().optional(),
|
|
1287
|
+
name: z3.string().optional(),
|
|
1288
|
+
homepage: z3.string().optional(),
|
|
1289
|
+
registry: z3.enum(SCHEMA_CONFIG.registryTypes).optional(),
|
|
1290
|
+
version: z3.string().optional(),
|
|
1291
|
+
lastUpdated: z3.string().optional(),
|
|
1292
|
+
categories: z3.array(z3.enum(SCHEMA_CONFIG.componentCategories)).optional(),
|
|
1293
|
+
components: z3.array(z3.object({
|
|
1294
|
+
name: z3.string(),
|
|
1295
|
+
type: registryItemTypeSchema,
|
|
1296
|
+
description: z3.string().optional()
|
|
1297
|
+
})).optional(),
|
|
1298
|
+
items: z3.array(registryItemSchema)
|
|
1299
|
+
});
|
|
1300
|
+
function generateConfigSchema() {
|
|
1301
|
+
const baseSchema = zodToJsonSchema(configSchema, {
|
|
1302
|
+
name: "UI8KitConfiguration",
|
|
1303
|
+
$refStrategy: "none"
|
|
1304
|
+
});
|
|
1305
|
+
const actualSchema = baseSchema.definitions?.UI8KitConfiguration || baseSchema;
|
|
1306
|
+
return {
|
|
1307
|
+
"$schema": SCHEMA_CONFIG.schemaVersion,
|
|
1308
|
+
"title": SCHEMA_CONFIG.descriptions.config.title,
|
|
1309
|
+
"description": SCHEMA_CONFIG.descriptions.config.description,
|
|
1310
|
+
"type": "object",
|
|
1311
|
+
"properties": {
|
|
1312
|
+
"$schema": {
|
|
1313
|
+
"type": "string",
|
|
1314
|
+
"description": SCHEMA_CONFIG.fieldDescriptions.schema
|
|
1315
|
+
},
|
|
1316
|
+
"framework": {
|
|
1317
|
+
"type": "string",
|
|
1318
|
+
"enum": SCHEMA_CONFIG.supportedFrameworks,
|
|
1319
|
+
"description": SCHEMA_CONFIG.fieldDescriptions.framework
|
|
1320
|
+
},
|
|
1321
|
+
"typescript": {
|
|
1322
|
+
"type": "boolean",
|
|
1323
|
+
"default": true,
|
|
1324
|
+
"description": SCHEMA_CONFIG.fieldDescriptions.typescript
|
|
1325
|
+
},
|
|
1326
|
+
"aliases": {
|
|
1327
|
+
"type": "object",
|
|
1328
|
+
"additionalProperties": {
|
|
1329
|
+
"type": "string"
|
|
1330
|
+
},
|
|
1331
|
+
"default": SCHEMA_CONFIG.defaultAliases,
|
|
1332
|
+
"description": SCHEMA_CONFIG.fieldDescriptions.aliases
|
|
1333
|
+
},
|
|
1334
|
+
"registry": {
|
|
1335
|
+
"type": "string",
|
|
1336
|
+
"default": SCHEMA_CONFIG.defaultRegistry,
|
|
1337
|
+
"description": SCHEMA_CONFIG.fieldDescriptions.registry
|
|
1338
|
+
},
|
|
1339
|
+
"componentsDir": {
|
|
1340
|
+
"type": "string",
|
|
1341
|
+
"default": SCHEMA_CONFIG.defaultDirectories.components,
|
|
1342
|
+
"description": SCHEMA_CONFIG.fieldDescriptions.componentsDir
|
|
1343
|
+
},
|
|
1344
|
+
"libDir": {
|
|
1345
|
+
"type": "string",
|
|
1346
|
+
"default": SCHEMA_CONFIG.defaultDirectories.lib,
|
|
1347
|
+
"description": SCHEMA_CONFIG.fieldDescriptions.libDir
|
|
1348
|
+
}
|
|
1349
|
+
},
|
|
1350
|
+
"required": ["framework"],
|
|
1351
|
+
"additionalProperties": false
|
|
1352
|
+
};
|
|
1353
|
+
}
|
|
1354
|
+
function generateRegistrySchema() {
|
|
1355
|
+
const baseSchema = zodToJsonSchema(fullRegistrySchema, {
|
|
1356
|
+
name: "UI8KitRegistry",
|
|
1357
|
+
$refStrategy: "none"
|
|
1358
|
+
});
|
|
1359
|
+
const actualSchema = baseSchema.definitions?.UI8KitRegistry || baseSchema;
|
|
1360
|
+
return {
|
|
1361
|
+
"$schema": SCHEMA_CONFIG.schemaVersion,
|
|
1362
|
+
"title": SCHEMA_CONFIG.descriptions.registry.title,
|
|
1363
|
+
"description": SCHEMA_CONFIG.descriptions.registry.description,
|
|
1364
|
+
"type": "object",
|
|
1365
|
+
"properties": {
|
|
1366
|
+
"$schema": {
|
|
1367
|
+
"type": "string",
|
|
1368
|
+
"description": SCHEMA_CONFIG.fieldDescriptions.schema
|
|
1369
|
+
},
|
|
1370
|
+
"name": {
|
|
1371
|
+
"type": "string",
|
|
1372
|
+
"description": SCHEMA_CONFIG.fieldDescriptions.registryName
|
|
1373
|
+
},
|
|
1374
|
+
"homepage": {
|
|
1375
|
+
"type": "string",
|
|
1376
|
+
"description": SCHEMA_CONFIG.fieldDescriptions.registryHomepage
|
|
1377
|
+
},
|
|
1378
|
+
"registry": {
|
|
1379
|
+
"type": "string",
|
|
1380
|
+
"enum": SCHEMA_CONFIG.registryTypes,
|
|
1381
|
+
"description": SCHEMA_CONFIG.fieldDescriptions.registryType
|
|
1382
|
+
},
|
|
1383
|
+
"version": {
|
|
1384
|
+
"type": "string",
|
|
1385
|
+
"description": SCHEMA_CONFIG.fieldDescriptions.registryVersion
|
|
1386
|
+
},
|
|
1387
|
+
"lastUpdated": {
|
|
1388
|
+
"type": "string",
|
|
1389
|
+
"format": "date-time",
|
|
1390
|
+
"description": SCHEMA_CONFIG.fieldDescriptions.lastUpdated
|
|
1391
|
+
},
|
|
1392
|
+
"categories": {
|
|
1393
|
+
"type": "array",
|
|
1394
|
+
"items": {
|
|
1395
|
+
"type": "string",
|
|
1396
|
+
"enum": SCHEMA_CONFIG.componentCategories
|
|
1397
|
+
},
|
|
1398
|
+
"description": SCHEMA_CONFIG.fieldDescriptions.categories
|
|
1399
|
+
},
|
|
1400
|
+
"components": {
|
|
1401
|
+
"type": "array",
|
|
1402
|
+
"items": {
|
|
1403
|
+
"type": "object",
|
|
1404
|
+
"properties": {
|
|
1405
|
+
"name": { "type": "string" },
|
|
1406
|
+
"type": {
|
|
1407
|
+
"type": "string",
|
|
1408
|
+
"enum": actualSchema.properties?.components?.items?.properties?.type?.enum || [
|
|
1409
|
+
"registry:lib",
|
|
1410
|
+
"registry:block",
|
|
1411
|
+
"registry:component",
|
|
1412
|
+
"registry:ui",
|
|
1413
|
+
"registry:template"
|
|
1414
|
+
]
|
|
1415
|
+
},
|
|
1416
|
+
"description": { "type": "string" }
|
|
1417
|
+
},
|
|
1418
|
+
"required": ["name", "type"],
|
|
1419
|
+
"additionalProperties": false
|
|
1420
|
+
},
|
|
1421
|
+
"description": SCHEMA_CONFIG.fieldDescriptions.components
|
|
1422
|
+
},
|
|
1423
|
+
"items": {
|
|
1424
|
+
"type": "array",
|
|
1425
|
+
"items": { "$ref": getSchemaRef("registry-item") },
|
|
1426
|
+
"description": SCHEMA_CONFIG.fieldDescriptions.items
|
|
1427
|
+
}
|
|
1428
|
+
},
|
|
1429
|
+
"required": ["items"],
|
|
1430
|
+
"additionalProperties": false
|
|
1431
|
+
};
|
|
1432
|
+
}
|
|
1433
|
+
function generateRegistryItemSchema() {
|
|
1434
|
+
const baseSchema = zodToJsonSchema(registryItemSchema, {
|
|
1435
|
+
name: "UI8KitRegistryItem",
|
|
1436
|
+
$refStrategy: "none"
|
|
1437
|
+
});
|
|
1438
|
+
const actualSchema = baseSchema.definitions?.UI8KitRegistryItem || baseSchema;
|
|
1439
|
+
return {
|
|
1440
|
+
"$schema": SCHEMA_CONFIG.schemaVersion,
|
|
1441
|
+
"title": SCHEMA_CONFIG.descriptions.registryItem.title,
|
|
1442
|
+
"description": SCHEMA_CONFIG.descriptions.registryItem.description,
|
|
1443
|
+
"type": "object",
|
|
1444
|
+
"properties": actualSchema.properties,
|
|
1445
|
+
"required": actualSchema.required || ["name", "type", "files"],
|
|
1446
|
+
"additionalProperties": false
|
|
1447
|
+
};
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1450
|
+
// src/commands/build.ts
|
|
1451
|
+
async function buildCommand(registryPath = "./src/registry.json", options = {}) {
|
|
1452
|
+
const buildOptions = {
|
|
1453
|
+
cwd: path5.resolve(options.cwd || process.cwd()),
|
|
1454
|
+
registryFile: path5.resolve(registryPath),
|
|
1455
|
+
outputDir: path5.resolve(options.output || "./packages/registry/r")
|
|
1456
|
+
};
|
|
1457
|
+
console.log(chalk5.blue(CLI_MESSAGES.info.building));
|
|
1458
|
+
try {
|
|
1459
|
+
const registryContent = await fs6.readFile(buildOptions.registryFile, "utf-8");
|
|
1460
|
+
const registryData = JSON.parse(registryContent);
|
|
1461
|
+
const registry = registrySchema.parse(registryData);
|
|
1462
|
+
await fs6.ensureDir(buildOptions.outputDir);
|
|
1463
|
+
await generateSchemaFiles(buildOptions.outputDir);
|
|
1464
|
+
const spinner = ora3(CLI_MESSAGES.info.processingComponents).start();
|
|
1465
|
+
for (const item of registry.items) {
|
|
1466
|
+
spinner.text = `Building ${item.name}...`;
|
|
1467
|
+
item.$schema = "https://ui.buildy.tw/schema/registry-item.json";
|
|
1468
|
+
for (const file of item.files) {
|
|
1469
|
+
const filePath = path5.resolve(buildOptions.cwd, file.path);
|
|
1470
|
+
if (await fs6.pathExists(filePath)) {
|
|
1471
|
+
file.content = await fs6.readFile(filePath, "utf-8");
|
|
1472
|
+
} else {
|
|
1473
|
+
throw new Error(CLI_MESSAGES.errors.fileNotFound(file.path));
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1476
|
+
const validatedItem = registryItemSchema.parse(item);
|
|
1477
|
+
const typeDir = getOutputDir(validatedItem.type);
|
|
1478
|
+
const outputPath = path5.join(buildOptions.outputDir, typeDir);
|
|
1479
|
+
await fs6.ensureDir(outputPath);
|
|
1480
|
+
const outputFile = path5.join(outputPath, `${validatedItem.name}.json`);
|
|
1481
|
+
await fs6.writeFile(outputFile, JSON.stringify(validatedItem, null, 2));
|
|
1482
|
+
}
|
|
1483
|
+
spinner.succeed(CLI_MESSAGES.status.builtComponents(registry.items.length));
|
|
1484
|
+
await createIndexFile(registry, buildOptions.outputDir);
|
|
1485
|
+
console.log(chalk5.green(`\u2705 ${CLI_MESSAGES.success.registryBuilt}`));
|
|
1486
|
+
console.log(`Output: ${buildOptions.outputDir}`);
|
|
1487
|
+
console.log(chalk5.green(`\u2705 ${CLI_MESSAGES.success.schemasGenerated}`));
|
|
1488
|
+
} catch (error) {
|
|
1489
|
+
console.error(chalk5.red(`\u274C ${CLI_MESSAGES.errors.buildFailed}`), error.message);
|
|
1490
|
+
process.exit(1);
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1493
|
+
var BUILD_OUTPUT_FOLDERS = {
|
|
1494
|
+
"registry:ui": "components/ui",
|
|
1495
|
+
"registry:composite": "components",
|
|
1496
|
+
"registry:block": "blocks",
|
|
1497
|
+
"registry:component": "components",
|
|
1498
|
+
"registry:lib": "lib",
|
|
1499
|
+
"registry:layout": "layouts",
|
|
1500
|
+
"registry:variants": "components/variants"
|
|
1501
|
+
};
|
|
1502
|
+
function getOutputDir(type) {
|
|
1503
|
+
const folder = BUILD_OUTPUT_FOLDERS[type];
|
|
1504
|
+
return folder || "misc";
|
|
1505
|
+
}
|
|
1506
|
+
async function createIndexFile(registry, outputDir) {
|
|
1507
|
+
const index = {
|
|
1508
|
+
$schema: "https://ui.buildy.tw/schema/registry.json",
|
|
1509
|
+
components: registry.items.map((item) => ({
|
|
1510
|
+
name: item.name,
|
|
1511
|
+
type: item.type,
|
|
1512
|
+
title: item.title,
|
|
1513
|
+
description: item.description
|
|
1514
|
+
})),
|
|
1515
|
+
categories: SCHEMA_CONFIG.componentCategories,
|
|
1516
|
+
version: "1.0.0",
|
|
1517
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1518
|
+
registry: registry?.registry || SCHEMA_CONFIG.defaultRegistryType
|
|
1519
|
+
};
|
|
1520
|
+
await fs6.writeFile(
|
|
1521
|
+
path5.join(outputDir, "index.json"),
|
|
1522
|
+
JSON.stringify(index, null, 2)
|
|
1523
|
+
);
|
|
1524
|
+
}
|
|
1525
|
+
async function generateSchemaFiles(outputDir) {
|
|
1526
|
+
const registryBaseDir = path5.dirname(outputDir);
|
|
1527
|
+
const schemaDir = path5.join(registryBaseDir, "schema");
|
|
1528
|
+
await fs6.ensureDir(schemaDir);
|
|
1529
|
+
const configSchemaJson = generateConfigSchema();
|
|
1530
|
+
const registrySchemaJson = generateRegistrySchema();
|
|
1531
|
+
const registryItemSchemaJson = generateRegistryItemSchema();
|
|
1532
|
+
await fs6.writeFile(
|
|
1533
|
+
path5.join(registryBaseDir, "schema.json"),
|
|
1534
|
+
JSON.stringify(configSchemaJson, null, 2)
|
|
1535
|
+
);
|
|
1536
|
+
await fs6.writeFile(
|
|
1537
|
+
path5.join(schemaDir, "registry.json"),
|
|
1538
|
+
JSON.stringify(registrySchemaJson, null, 2)
|
|
1539
|
+
);
|
|
1540
|
+
await fs6.writeFile(
|
|
1541
|
+
path5.join(schemaDir, "registry-item.json"),
|
|
1542
|
+
JSON.stringify(registryItemSchemaJson, null, 2)
|
|
1543
|
+
);
|
|
1544
|
+
}
|
|
1545
|
+
|
|
1546
|
+
// src/commands/scan.ts
|
|
1547
|
+
import fs7 from "fs-extra";
|
|
1548
|
+
import path6 from "path";
|
|
1549
|
+
import chalk6 from "chalk";
|
|
1550
|
+
import ora4 from "ora";
|
|
1551
|
+
import { glob } from "glob";
|
|
1552
|
+
import * as ts from "typescript";
|
|
1553
|
+
var DEV_PATTERNS = [
|
|
1554
|
+
"@types/",
|
|
1555
|
+
"eslint",
|
|
1556
|
+
"prettier",
|
|
1557
|
+
"typescript",
|
|
1558
|
+
"jest",
|
|
1559
|
+
"vitest",
|
|
1560
|
+
"testing-library",
|
|
1561
|
+
"@testing-library/",
|
|
1562
|
+
"storybook",
|
|
1563
|
+
"@storybook/",
|
|
1564
|
+
"webpack",
|
|
1565
|
+
"vite",
|
|
1566
|
+
"rollup",
|
|
1567
|
+
"babel",
|
|
1568
|
+
"@babel/",
|
|
1569
|
+
"postcss",
|
|
1570
|
+
"tailwindcss",
|
|
1571
|
+
"autoprefixer"
|
|
1572
|
+
];
|
|
1573
|
+
async function scanCommand(options = {}) {
|
|
1574
|
+
const registryName = options.registry || SCHEMA_CONFIG.defaultRegistryType;
|
|
1575
|
+
const registryPath = `./${registryName}`;
|
|
1576
|
+
const scanOptions = {
|
|
1577
|
+
cwd: path6.resolve(options.cwd || process.cwd()),
|
|
1578
|
+
registry: path6.resolve(registryPath),
|
|
1579
|
+
outputFile: path6.resolve(options.output || "./src/registry.json"),
|
|
1580
|
+
sourceDir: path6.resolve(options.source || "./src")
|
|
1581
|
+
};
|
|
1582
|
+
console.log(chalk6.blue(`\u{1F50D} ${CLI_MESSAGES.info.scanningComponents(registryName)}`));
|
|
1583
|
+
try {
|
|
1584
|
+
const spinner = ora4(CLI_MESSAGES.info.scanningDirectories).start();
|
|
1585
|
+
const componentsDir = path6.resolve(scanOptions.cwd, normalizeDir2(SCHEMA_CONFIG.defaultDirectories.components));
|
|
1586
|
+
const uiDir = path6.join(componentsDir, "ui");
|
|
1587
|
+
const blocksDir = path6.resolve(scanOptions.cwd, normalizeDir2(SCHEMA_CONFIG.defaultDirectories.blocks));
|
|
1588
|
+
const layoutsDir = path6.resolve(scanOptions.cwd, normalizeDir2(SCHEMA_CONFIG.defaultDirectories.layouts));
|
|
1589
|
+
const libDir = path6.resolve(scanOptions.cwd, normalizeDir2(SCHEMA_CONFIG.defaultDirectories.lib));
|
|
1590
|
+
const variantsDir = path6.resolve(scanOptions.cwd, normalizeDir2(SCHEMA_CONFIG.defaultDirectories.variants));
|
|
1591
|
+
const uiComponents = await scanDirectory(uiDir, "registry:ui");
|
|
1592
|
+
const compositeComponents = await scanDirectoryFlat(componentsDir, "registry:composite", ["index.ts"]);
|
|
1593
|
+
const variantComponents = await scanDirectory(variantsDir, "registry:variants", ["index.ts"]);
|
|
1594
|
+
const blockComponents = await scanDirectory(blocksDir, "registry:block");
|
|
1595
|
+
const layoutComponents = await scanDirectory(layoutsDir, "registry:layout");
|
|
1596
|
+
const libComponents = await scanDirectory(libDir, "registry:lib");
|
|
1597
|
+
const variantsIndexItem = await scanSingleFile(path6.join(variantsDir, "index.ts"), "registry:variants");
|
|
1598
|
+
const componentsIndexItem = await scanSingleFile(path6.join(componentsDir, "index.ts"), "registry:composite");
|
|
1599
|
+
const allComponentsRaw = [
|
|
1600
|
+
...uiComponents,
|
|
1601
|
+
...compositeComponents,
|
|
1602
|
+
...variantComponents,
|
|
1603
|
+
...variantsIndexItem ? [variantsIndexItem] : [],
|
|
1604
|
+
...componentsIndexItem ? [componentsIndexItem] : [],
|
|
1605
|
+
...blockComponents,
|
|
1606
|
+
...layoutComponents,
|
|
1607
|
+
...libComponents
|
|
1608
|
+
];
|
|
1609
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1610
|
+
const allComponents = [];
|
|
1611
|
+
for (const comp of allComponentsRaw) {
|
|
1612
|
+
const key = `${comp.type}:${comp.name}`;
|
|
1613
|
+
if (seen.has(key))
|
|
1614
|
+
continue;
|
|
1615
|
+
seen.add(key);
|
|
1616
|
+
allComponents.push(comp);
|
|
1617
|
+
}
|
|
1618
|
+
spinner.text = CLI_MESSAGES.info.analyzingDeps.replace("{count}", allComponents.length.toString());
|
|
1619
|
+
for (const component of allComponents) {
|
|
1620
|
+
const analysis = await analyzeComponentDependencies(component.files, scanOptions.cwd);
|
|
1621
|
+
component.dependencies = analysis.dependencies;
|
|
1622
|
+
component.devDependencies = analysis.devDependencies;
|
|
1623
|
+
if (analysis.description && !component.description) {
|
|
1624
|
+
component.description = analysis.description;
|
|
1625
|
+
}
|
|
1626
|
+
}
|
|
1627
|
+
const registry = {
|
|
1628
|
+
$schema: "https://ui.buildy.tw/schema/registry.json",
|
|
1629
|
+
items: allComponents,
|
|
1630
|
+
version: "1.0.0",
|
|
1631
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1632
|
+
registry: registryName
|
|
1633
|
+
};
|
|
1634
|
+
await fs7.ensureDir(path6.dirname(scanOptions.outputFile));
|
|
1635
|
+
await fs7.writeFile(scanOptions.outputFile, JSON.stringify(registry, null, 2));
|
|
1636
|
+
spinner.succeed(CLI_MESSAGES.status.scannedComponents(allComponents.length));
|
|
1637
|
+
console.log(chalk6.green(`\u2705 ${CLI_MESSAGES.success.registryGenerated(registryName)}`));
|
|
1638
|
+
console.log(`Output: ${scanOptions.outputFile}`);
|
|
1639
|
+
const summary = allComponents.reduce((acc, comp) => {
|
|
1640
|
+
acc[comp.type] = (acc[comp.type] || 0) + 1;
|
|
1641
|
+
return acc;
|
|
1642
|
+
}, {});
|
|
1643
|
+
console.log(chalk6.blue("\n\u{1F4CA} Component Summary:"));
|
|
1644
|
+
Object.entries(summary).forEach(([type, count]) => {
|
|
1645
|
+
console.log(` ${type}: ${count}`);
|
|
1646
|
+
});
|
|
1647
|
+
const allDeps = /* @__PURE__ */ new Set();
|
|
1648
|
+
const allDevDeps = /* @__PURE__ */ new Set();
|
|
1649
|
+
allComponents.forEach((comp) => {
|
|
1650
|
+
comp.dependencies.forEach((dep) => allDeps.add(dep));
|
|
1651
|
+
comp.devDependencies.forEach((dep) => allDevDeps.add(dep));
|
|
1652
|
+
});
|
|
1653
|
+
console.log(chalk6.blue("\n\u{1F4E6} Dependencies Summary:"));
|
|
1654
|
+
console.log(` Dependencies: ${allDeps.size} unique (${Array.from(allDeps).join(", ") || "none"})`);
|
|
1655
|
+
console.log(` DevDependencies: ${allDevDeps.size} unique (${Array.from(allDevDeps).join(", ") || "none"})`);
|
|
1656
|
+
} catch (error) {
|
|
1657
|
+
console.error(chalk6.red(`\u274C ${CLI_MESSAGES.errors.scanFailed}`), error.message);
|
|
1658
|
+
process.exit(1);
|
|
1659
|
+
}
|
|
1660
|
+
}
|
|
1661
|
+
async function scanDirectory(dirPath, type, ignorePatterns = []) {
|
|
1662
|
+
if (!await fs7.pathExists(dirPath)) {
|
|
1663
|
+
return [];
|
|
1664
|
+
}
|
|
1665
|
+
const components = [];
|
|
1666
|
+
const pattern = path6.join(dirPath, "**/*.{ts,tsx,js,jsx}").replace(/\\/g, "/");
|
|
1667
|
+
const ignore = ignorePatterns.map((p) => p.replace(/\\/g, "/"));
|
|
1668
|
+
const files = await glob(pattern, { windowsPathsNoEscape: true, ignore });
|
|
1669
|
+
for (const filePath of files) {
|
|
1670
|
+
const relativePath = path6.relative(process.cwd(), filePath).replace(/\\/g, "/");
|
|
1671
|
+
const fileName = path6.basename(filePath, path6.extname(filePath));
|
|
1672
|
+
if (fileName === "index" || fileName.startsWith("_")) {
|
|
1673
|
+
continue;
|
|
1674
|
+
}
|
|
1675
|
+
try {
|
|
1676
|
+
const content = await fs7.readFile(filePath, "utf-8");
|
|
1677
|
+
const description = extractDescription(content);
|
|
1678
|
+
if (!hasValidExports(content)) {
|
|
1679
|
+
continue;
|
|
1680
|
+
}
|
|
1681
|
+
components.push({
|
|
1682
|
+
name: fileName,
|
|
1683
|
+
type,
|
|
1684
|
+
description,
|
|
1685
|
+
dependencies: [],
|
|
1686
|
+
devDependencies: [],
|
|
1687
|
+
files: [{
|
|
1688
|
+
path: relativePath,
|
|
1689
|
+
target: getTargetFromType(type)
|
|
1690
|
+
}]
|
|
1691
|
+
});
|
|
1692
|
+
} catch (error) {
|
|
1693
|
+
console.warn(`Warning: Could not process ${filePath}:`, error.message);
|
|
1694
|
+
}
|
|
1695
|
+
}
|
|
1696
|
+
return components;
|
|
1697
|
+
}
|
|
1698
|
+
async function scanDirectoryFlat(dirPath, type, ignoreFiles = []) {
|
|
1699
|
+
if (!await fs7.pathExists(dirPath)) {
|
|
1700
|
+
return [];
|
|
1701
|
+
}
|
|
1702
|
+
const components = [];
|
|
1703
|
+
const pattern = path6.join(dirPath, "*.{ts,tsx,js,jsx}").replace(/\\/g, "/");
|
|
1704
|
+
const files = await glob(pattern, { windowsPathsNoEscape: true });
|
|
1705
|
+
for (const filePath of files) {
|
|
1706
|
+
const relativePath = path6.relative(process.cwd(), filePath).replace(/\\/g, "/");
|
|
1707
|
+
const fileName = path6.basename(filePath, path6.extname(filePath));
|
|
1708
|
+
if (ignoreFiles.includes(fileName + path6.extname(filePath)) || fileName.startsWith("_")) {
|
|
1709
|
+
continue;
|
|
1710
|
+
}
|
|
1711
|
+
try {
|
|
1712
|
+
const content = await fs7.readFile(filePath, "utf-8");
|
|
1713
|
+
const description = extractDescription(content);
|
|
1714
|
+
if (!hasValidExports(content)) {
|
|
1715
|
+
continue;
|
|
1716
|
+
}
|
|
1717
|
+
components.push({
|
|
1718
|
+
name: fileName,
|
|
1719
|
+
type,
|
|
1720
|
+
description,
|
|
1721
|
+
dependencies: [],
|
|
1722
|
+
devDependencies: [],
|
|
1723
|
+
files: [{
|
|
1724
|
+
path: relativePath,
|
|
1725
|
+
target: getTargetFromType(type)
|
|
1726
|
+
}]
|
|
1727
|
+
});
|
|
1728
|
+
} catch (error) {
|
|
1729
|
+
console.warn(`Warning: Could not process ${filePath}:`, error.message);
|
|
1730
|
+
}
|
|
1731
|
+
}
|
|
1732
|
+
return components;
|
|
1733
|
+
}
|
|
1734
|
+
async function scanSingleFile(filePath, type) {
|
|
1735
|
+
if (!await fs7.pathExists(filePath)) {
|
|
1736
|
+
return null;
|
|
1737
|
+
}
|
|
1738
|
+
try {
|
|
1739
|
+
const content = await fs7.readFile(filePath, "utf-8");
|
|
1740
|
+
const description = extractDescription(content);
|
|
1741
|
+
if (!hasValidExports(content)) {
|
|
1742
|
+
return null;
|
|
1743
|
+
}
|
|
1744
|
+
const relativePath = path6.relative(process.cwd(), filePath).replace(/\\/g, "/");
|
|
1745
|
+
const fileName = path6.basename(filePath, path6.extname(filePath));
|
|
1746
|
+
return {
|
|
1747
|
+
name: fileName,
|
|
1748
|
+
type,
|
|
1749
|
+
description,
|
|
1750
|
+
dependencies: [],
|
|
1751
|
+
devDependencies: [],
|
|
1752
|
+
files: [{
|
|
1753
|
+
path: relativePath,
|
|
1754
|
+
target: getTargetFromType(type)
|
|
1755
|
+
}]
|
|
1756
|
+
};
|
|
1757
|
+
} catch (error) {
|
|
1758
|
+
console.warn(`Warning: Could not process ${filePath}:`, error.message);
|
|
1759
|
+
return null;
|
|
1760
|
+
}
|
|
1761
|
+
}
|
|
1762
|
+
function extractDescription(content) {
|
|
1763
|
+
const jsdocMatch = content.match(/\/\*\*\s*\n\s*\*\s*(.+?)\s*\n\s*\*\//s);
|
|
1764
|
+
if (jsdocMatch) {
|
|
1765
|
+
return jsdocMatch[1].trim();
|
|
1766
|
+
}
|
|
1767
|
+
const commentMatch = content.match(/^\/\/\s*(.+)$/m);
|
|
1768
|
+
if (commentMatch) {
|
|
1769
|
+
return commentMatch[1].trim();
|
|
1770
|
+
}
|
|
1771
|
+
return "";
|
|
1772
|
+
}
|
|
1773
|
+
function hasValidExports(content) {
|
|
1774
|
+
return /export\s+(default\s+)?(function|const|class|interface|type)/m.test(content) || /export\s*\{/.test(content);
|
|
1775
|
+
}
|
|
1776
|
+
async function analyzeComponentDependencies(files, cwd) {
|
|
1777
|
+
const allDependencies = /* @__PURE__ */ new Set();
|
|
1778
|
+
const allDevDependencies = /* @__PURE__ */ new Set();
|
|
1779
|
+
let description;
|
|
1780
|
+
for (const file of files) {
|
|
1781
|
+
try {
|
|
1782
|
+
const filePath = path6.resolve(cwd, file.path);
|
|
1783
|
+
const content = await fs7.readFile(filePath, "utf-8");
|
|
1784
|
+
const sourceFile = ts.createSourceFile(
|
|
1785
|
+
file.path,
|
|
1786
|
+
content,
|
|
1787
|
+
ts.ScriptTarget.Latest,
|
|
1788
|
+
true
|
|
1789
|
+
);
|
|
1790
|
+
const analysis = analyzeAST(sourceFile);
|
|
1791
|
+
analysis.dependencies.forEach((dep) => allDependencies.add(dep));
|
|
1792
|
+
analysis.devDependencies.forEach((dep) => allDevDependencies.add(dep));
|
|
1793
|
+
if (analysis.description && !description) {
|
|
1794
|
+
description = analysis.description;
|
|
1795
|
+
}
|
|
1796
|
+
} catch (error) {
|
|
1797
|
+
console.warn(CLI_MESSAGES.errors.failedToAnalyzeDeps(file.path), error.message);
|
|
1798
|
+
}
|
|
1799
|
+
}
|
|
1800
|
+
return {
|
|
1801
|
+
dependencies: Array.from(allDependencies),
|
|
1802
|
+
devDependencies: Array.from(allDevDependencies),
|
|
1803
|
+
description
|
|
1804
|
+
};
|
|
1805
|
+
}
|
|
1806
|
+
function analyzeAST(sourceFile) {
|
|
1807
|
+
const dependencies = /* @__PURE__ */ new Set();
|
|
1808
|
+
const devDependencies = /* @__PURE__ */ new Set();
|
|
1809
|
+
let description;
|
|
1810
|
+
let hasExports = false;
|
|
1811
|
+
function visit(node) {
|
|
1812
|
+
if (ts.isImportDeclaration(node)) {
|
|
1813
|
+
const moduleSpecifier = node.moduleSpecifier;
|
|
1814
|
+
if (ts.isStringLiteral(moduleSpecifier)) {
|
|
1815
|
+
const moduleName = moduleSpecifier.text;
|
|
1816
|
+
if (isExternalDependency(moduleName)) {
|
|
1817
|
+
if (isDevDependency(moduleName)) {
|
|
1818
|
+
devDependencies.add(moduleName);
|
|
1819
|
+
} else {
|
|
1820
|
+
dependencies.add(moduleName);
|
|
1821
|
+
}
|
|
1822
|
+
}
|
|
1823
|
+
}
|
|
1824
|
+
}
|
|
1825
|
+
if (ts.isExportDeclaration(node)) {
|
|
1826
|
+
hasExports = true;
|
|
1827
|
+
} else if (ts.isExportAssignment(node)) {
|
|
1828
|
+
hasExports = true;
|
|
1829
|
+
} else if (hasExportModifier(node)) {
|
|
1830
|
+
hasExports = true;
|
|
1831
|
+
}
|
|
1832
|
+
const jsDocComment = getJSDocComment(node);
|
|
1833
|
+
if (jsDocComment && !description) {
|
|
1834
|
+
description = jsDocComment;
|
|
1835
|
+
}
|
|
1836
|
+
ts.forEachChild(node, visit);
|
|
1837
|
+
}
|
|
1838
|
+
visit(sourceFile);
|
|
1839
|
+
return {
|
|
1840
|
+
dependencies: Array.from(dependencies),
|
|
1841
|
+
devDependencies: Array.from(devDependencies),
|
|
1842
|
+
description,
|
|
1843
|
+
hasExports
|
|
1844
|
+
};
|
|
1845
|
+
}
|
|
1846
|
+
function isDevDependency(moduleName) {
|
|
1847
|
+
return DEV_PATTERNS.some((pattern) => moduleName.includes(pattern));
|
|
1848
|
+
}
|
|
1849
|
+
function hasExportModifier(node) {
|
|
1850
|
+
if ("modifiers" in node && node.modifiers) {
|
|
1851
|
+
return node.modifiers.some(
|
|
1852
|
+
(mod) => mod.kind === ts.SyntaxKind.ExportKeyword
|
|
1853
|
+
);
|
|
1854
|
+
}
|
|
1855
|
+
return false;
|
|
1856
|
+
}
|
|
1857
|
+
function getJSDocComment(node) {
|
|
1858
|
+
try {
|
|
1859
|
+
const jsDocTags = ts.getJSDocCommentsAndTags(node);
|
|
1860
|
+
for (const tag of jsDocTags) {
|
|
1861
|
+
if (ts.isJSDoc(tag) && tag.comment) {
|
|
1862
|
+
if (typeof tag.comment === "string") {
|
|
1863
|
+
return tag.comment.trim();
|
|
1864
|
+
} else if (Array.isArray(tag.comment)) {
|
|
1865
|
+
return tag.comment.map((part) => part.text).join("").trim();
|
|
1866
|
+
}
|
|
1867
|
+
}
|
|
1868
|
+
}
|
|
1869
|
+
} catch (error) {
|
|
1870
|
+
}
|
|
1871
|
+
return void 0;
|
|
1872
|
+
}
|
|
1873
|
+
function getTargetFromType(type) {
|
|
1874
|
+
const folder = TYPE_TO_FOLDER[type];
|
|
1875
|
+
return folder || "components";
|
|
1876
|
+
}
|
|
1877
|
+
function normalizeDir2(dir) {
|
|
1878
|
+
return dir.replace(/^\.\//, "").replace(/\\/g, "/");
|
|
1879
|
+
}
|
|
1880
|
+
|
|
1881
|
+
// src/index.ts
|
|
1882
|
+
var program = new Command();
|
|
1883
|
+
program.name("ui8kit").description("A CLI for adding UI components to your Vite React projects (UI8Kit registry)").version("0.1.0");
|
|
1884
|
+
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);
|
|
1885
|
+
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", "Enable retry logic for unreliable connections").action(addCommand);
|
|
1886
|
+
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").option("--cwd <dir>", "Working directory").action(async (options) => {
|
|
1887
|
+
await scanCommand(options);
|
|
1888
|
+
});
|
|
1889
|
+
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").option("-c, --cwd <cwd>", "Working directory", process.cwd()).action(buildCommand);
|
|
1890
|
+
program.on("command:*", () => {
|
|
1891
|
+
console.error(chalk7.red(`Invalid command: ${program.args.join(" ")}`));
|
|
1892
|
+
console.log("See --help for a list of available commands.");
|
|
1893
|
+
process.exit(1);
|
|
1894
|
+
});
|
|
1895
|
+
program.parse();
|
|
1896
|
+
//# sourceMappingURL=index.js.map
|