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