ui8kit 1.1.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +211 -125
- package/dist/index.js +1794 -760
- package/dist/index.js.map +1 -1
- package/package.json +70 -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,28 @@ 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
|
+
}
|
|
446
469
|
|
|
447
470
|
// src/utils/project.ts
|
|
448
|
-
import
|
|
449
|
-
import
|
|
471
|
+
import fs2 from "fs-extra";
|
|
472
|
+
import path2 from "path";
|
|
450
473
|
var MODERN_CONFIG_NAME = "ui8kit.config.json";
|
|
451
474
|
async function isViteProject() {
|
|
452
475
|
const viteConfigFiles = [
|
|
@@ -456,134 +479,71 @@ async function isViteProject() {
|
|
|
456
479
|
"vite.config.mjs"
|
|
457
480
|
];
|
|
458
481
|
for (const file of viteConfigFiles) {
|
|
459
|
-
if (await
|
|
482
|
+
if (await fs2.pathExists(file)) {
|
|
460
483
|
return true;
|
|
461
484
|
}
|
|
462
485
|
}
|
|
463
486
|
return false;
|
|
464
487
|
}
|
|
465
488
|
async function hasReact() {
|
|
466
|
-
const packageJsonPath =
|
|
467
|
-
if (!await
|
|
489
|
+
const packageJsonPath = path2.join(process.cwd(), "package.json");
|
|
490
|
+
if (!await fs2.pathExists(packageJsonPath)) {
|
|
468
491
|
return false;
|
|
469
492
|
}
|
|
470
|
-
const packageJson = await
|
|
493
|
+
const packageJson = await fs2.readJson(packageJsonPath);
|
|
471
494
|
const deps = {
|
|
472
495
|
...packageJson.dependencies,
|
|
473
496
|
...packageJson.devDependencies
|
|
474
497
|
};
|
|
475
498
|
return "react" in deps;
|
|
476
499
|
}
|
|
477
|
-
async function findConfig(
|
|
500
|
+
async function findConfig(registryType) {
|
|
501
|
+
const rootConfig = await getConfig();
|
|
502
|
+
if (rootConfig)
|
|
503
|
+
return rootConfig;
|
|
478
504
|
const srcConfig = await getConfig("./src");
|
|
479
505
|
if (srcConfig)
|
|
480
506
|
return srcConfig;
|
|
481
|
-
if (
|
|
482
|
-
const registryConfig = await getConfig(`./${
|
|
507
|
+
if (registryType) {
|
|
508
|
+
const registryConfig = await getConfig(`./${registryType}`);
|
|
483
509
|
if (registryConfig)
|
|
484
510
|
return registryConfig;
|
|
485
511
|
}
|
|
486
|
-
return
|
|
512
|
+
return null;
|
|
487
513
|
}
|
|
488
514
|
async function getConfig(registryPath) {
|
|
489
|
-
const baseDir = registryPath ?
|
|
490
|
-
const configPath =
|
|
491
|
-
if (!await
|
|
515
|
+
const baseDir = registryPath ? path2.join(process.cwd(), registryPath) : process.cwd();
|
|
516
|
+
const configPath = path2.join(baseDir, MODERN_CONFIG_NAME);
|
|
517
|
+
if (!await fs2.pathExists(configPath)) {
|
|
492
518
|
return null;
|
|
493
519
|
}
|
|
494
520
|
try {
|
|
495
|
-
const config = await
|
|
521
|
+
const config = await fs2.readJson(configPath);
|
|
496
522
|
return configSchema.parse(config);
|
|
497
523
|
} catch (error) {
|
|
498
524
|
console.error("\u274C Invalid ui8kit.config.json:", error.message);
|
|
499
525
|
return null;
|
|
500
526
|
}
|
|
501
527
|
}
|
|
502
|
-
async function saveConfig(config
|
|
503
|
-
const configPath =
|
|
504
|
-
await
|
|
505
|
-
await
|
|
528
|
+
async function saveConfig(config) {
|
|
529
|
+
const configPath = path2.join(process.cwd(), MODERN_CONFIG_NAME);
|
|
530
|
+
await fs2.ensureDir(path2.dirname(configPath));
|
|
531
|
+
await fs2.writeJson(configPath, config, { spaces: 2 });
|
|
506
532
|
}
|
|
507
533
|
async function ensureDir(dirPath) {
|
|
508
|
-
await
|
|
534
|
+
await fs2.ensureDir(dirPath);
|
|
509
535
|
}
|
|
510
536
|
|
|
511
537
|
// 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
|
-
}
|
|
538
|
+
import fs6 from "fs-extra";
|
|
539
|
+
import path6 from "path";
|
|
540
|
+
import chalk6 from "chalk";
|
|
541
|
+
import prompts2 from "prompts";
|
|
527
542
|
|
|
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
|
-
}
|
|
543
|
+
// src/commands/init.ts
|
|
544
|
+
import chalk5 from "chalk";
|
|
545
|
+
import prompts from "prompts";
|
|
546
|
+
import ora3 from "ora";
|
|
587
547
|
|
|
588
548
|
// src/utils/cli-messages.ts
|
|
589
549
|
var CLI_MESSAGES = {
|
|
@@ -599,14 +559,17 @@ var CLI_MESSAGES = {
|
|
|
599
559
|
noCDNFound: (registryType) => `No working ${registryType} CDN found`,
|
|
600
560
|
componentNotFound: (name, registryType) => `Component "${name}" not found in ${registryType} registry`,
|
|
601
561
|
unknownComponentType: (type) => `Unknown component type: ${type}`,
|
|
602
|
-
fileNotFound: (
|
|
562
|
+
fileNotFound: (path13) => `File not found: ${path13}`,
|
|
603
563
|
invalidConfig: "Invalid ui8kit.config.json:",
|
|
604
564
|
failedToInstall: (name, registryType) => `Failed to install ${name} from ${registryType}`,
|
|
605
565
|
failedToFetch: (registryType) => `Failed to fetch components from ${registryType}`,
|
|
606
566
|
failedToFetchComponent: (name, registryType) => `Failed to fetch ${name} from ${registryType}:`,
|
|
607
|
-
failedToAnalyzeDeps: (
|
|
567
|
+
failedToAnalyzeDeps: (path13) => `Warning: Could not analyze dependencies for ${path13}:`,
|
|
608
568
|
dependenciesFailed: "Failed to install dependencies",
|
|
609
|
-
couldNotInstallDeps: (name) => `Warning: Could not install some dependencies for ${name}
|
|
569
|
+
couldNotInstallDeps: (name) => `Warning: Could not install some dependencies for ${name}`,
|
|
570
|
+
noLocalInstall: "No installed local components were found in this project.",
|
|
571
|
+
invalidRegistryForDiff: "Could not load registry data for diff check.",
|
|
572
|
+
cacheClearFailed: "Could not clear cache."
|
|
610
573
|
},
|
|
611
574
|
// Success messages
|
|
612
575
|
success: {
|
|
@@ -619,14 +582,16 @@ var CLI_MESSAGES = {
|
|
|
619
582
|
registryBuilt: "Registry built successfully!",
|
|
620
583
|
schemasGenerated: "Schema files generated successfully!",
|
|
621
584
|
registryGenerated: (registryName) => `${registryName} registry generated successfully!`,
|
|
622
|
-
depsAvailable: "All dependencies already available"
|
|
585
|
+
depsAvailable: "All dependencies already available",
|
|
586
|
+
cacheCleared: "UI8Kit cache cleared successfully."
|
|
623
587
|
},
|
|
624
588
|
// Info messages
|
|
625
589
|
info: {
|
|
590
|
+
tryListComponents: (registryType) => `Run "npx ui8kit@latest add --all --registry ${registryType}" to list available components`,
|
|
626
591
|
initializing: (registryName) => `Initializing UI8Kit in your project (${registryName} registry)...`,
|
|
627
592
|
installing: (registryName) => `Installing from ${registryName} registry...`,
|
|
628
593
|
installingAll: (registryName) => `Installing all available components from ${registryName} registry...`,
|
|
629
|
-
retryEnabled: "
|
|
594
|
+
retryEnabled: "Aggressive retry mode enabled (3 attempts per request)",
|
|
630
595
|
fetchingComponentList: (registryType) => `Fetching component list from ${registryType}...`,
|
|
631
596
|
fetchingRegistry: (registryType) => `Fetching ${registryType} registry index`,
|
|
632
597
|
scanningComponents: (registryName) => `Scanning ${registryName} components...`,
|
|
@@ -641,12 +606,16 @@ var CLI_MESSAGES = {
|
|
|
641
606
|
using: (registryType, baseUrl) => `Using ${registryType} CDN: ${baseUrl}`,
|
|
642
607
|
loading: (name, registryType, folder, type) => `Loading ${name} from /${registryType}/${folder}/ (type: ${type})`,
|
|
643
608
|
fetching: (registryType, url) => `Fetching from ${registryType}: ${url}`,
|
|
609
|
+
listingComponents: "Listing available components",
|
|
644
610
|
fetchingUrl: "Fetching component from:",
|
|
645
|
-
fetchingUrlWithRetry: "Fetching component from URL with retry:"
|
|
611
|
+
fetchingUrlWithRetry: "Fetching component from URL with retry:",
|
|
612
|
+
checkingForUpdates: "Checking for component updates...",
|
|
613
|
+
localDiffSummary: "Local components compared with registry."
|
|
646
614
|
},
|
|
647
615
|
// Prompts
|
|
648
616
|
prompts: {
|
|
649
|
-
|
|
617
|
+
globalCss: "Where is your global CSS file?",
|
|
618
|
+
aliasComponents: "Configure import aliases?",
|
|
650
619
|
overwrite: (registryName) => `UI8Kit is already initialized for ${registryName} registry. Overwrite configuration?`
|
|
651
620
|
},
|
|
652
621
|
// Examples and help text
|
|
@@ -694,235 +663,82 @@ var CLI_MESSAGES = {
|
|
|
694
663
|
}
|
|
695
664
|
};
|
|
696
665
|
|
|
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}`));
|
|
666
|
+
// src/utils/package-manager.ts
|
|
667
|
+
import chalk3 from "chalk";
|
|
668
|
+
import ora2 from "ora";
|
|
669
|
+
import path4 from "path";
|
|
670
|
+
import fs4 from "fs-extra";
|
|
671
|
+
import { execa } from "execa";
|
|
672
|
+
|
|
673
|
+
// src/utils/dependency-checker.ts
|
|
674
|
+
import fs3 from "fs-extra";
|
|
675
|
+
import path3 from "path";
|
|
676
|
+
import chalk2 from "chalk";
|
|
677
|
+
async function checkProjectDependencies(requiredDeps) {
|
|
678
|
+
const packageJsonPath = path3.join(process.cwd(), "package.json");
|
|
679
|
+
if (!await fs3.pathExists(packageJsonPath)) {
|
|
680
|
+
return {
|
|
681
|
+
installed: [],
|
|
682
|
+
missing: requiredDeps,
|
|
683
|
+
workspace: []
|
|
684
|
+
};
|
|
739
685
|
}
|
|
740
|
-
const spinner = ora(CLI_MESSAGES.info.fetchingComponentList(registryType)).start();
|
|
741
686
|
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
|
-
}
|
|
687
|
+
const packageJson = await fs3.readJson(packageJsonPath);
|
|
688
|
+
const allDeps = {
|
|
689
|
+
...packageJson.dependencies,
|
|
690
|
+
...packageJson.devDependencies
|
|
691
|
+
};
|
|
692
|
+
const installed = [];
|
|
693
|
+
const missing = [];
|
|
694
|
+
const workspace = [];
|
|
695
|
+
for (const dep of requiredDeps) {
|
|
696
|
+
const version = allDeps[dep];
|
|
697
|
+
if (!version) {
|
|
698
|
+
missing.push(dep);
|
|
699
|
+
} else if (version.startsWith("workspace:")) {
|
|
700
|
+
workspace.push(dep);
|
|
701
|
+
} else {
|
|
702
|
+
installed.push(dep);
|
|
813
703
|
}
|
|
814
|
-
spinner.succeed(CLI_MESSAGES.status.installing(component.name, registryType));
|
|
815
|
-
results.push({ name: component.name, status: "success" });
|
|
816
|
-
} catch (error) {
|
|
817
|
-
spinner.fail(CLI_MESSAGES.errors.failedToInstall(componentName, registryType));
|
|
818
|
-
console.error(chalk3.red(` Error: ${error.message}`));
|
|
819
|
-
results.push({
|
|
820
|
-
name: componentName,
|
|
821
|
-
status: "error",
|
|
822
|
-
error: error.message
|
|
823
|
-
});
|
|
824
|
-
}
|
|
825
|
-
}
|
|
826
|
-
return results;
|
|
827
|
-
}
|
|
828
|
-
function displayInstallationSummary(registryType, results) {
|
|
829
|
-
const successful = results.filter((r) => r.status === "success");
|
|
830
|
-
const failed = results.filter((r) => r.status === "error");
|
|
831
|
-
console.log(chalk3.blue("\n\u{1F4CA} Installation Summary:"));
|
|
832
|
-
console.log(` Registry: ${registryType}`);
|
|
833
|
-
console.log(` \u2705 Successful: ${successful.length}`);
|
|
834
|
-
console.log(` \u274C Failed: ${failed.length}`);
|
|
835
|
-
if (successful.length > 0) {
|
|
836
|
-
console.log(chalk3.green(`
|
|
837
|
-
\u{1F389} ${CLI_MESSAGES.success.componentsInstalled}`));
|
|
838
|
-
console.log("You can now import and use them in your project.");
|
|
839
|
-
}
|
|
840
|
-
if (failed.length > 0) {
|
|
841
|
-
process.exit(1);
|
|
842
|
-
}
|
|
843
|
-
}
|
|
844
|
-
async function installComponentFiles(component, config, force = false) {
|
|
845
|
-
for (const file of component.files) {
|
|
846
|
-
const fileName = path3.basename(file.path);
|
|
847
|
-
const target = file.target || inferTargetFromType(component.type);
|
|
848
|
-
const installDir = resolveInstallDir(target, config);
|
|
849
|
-
const targetPath = path3.join(process.cwd(), installDir, fileName);
|
|
850
|
-
if (!force && await fs4.pathExists(targetPath)) {
|
|
851
|
-
console.log(` \u26A0\uFE0F ${CLI_MESSAGES.status.skipped(fileName)}`);
|
|
852
|
-
continue;
|
|
853
704
|
}
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
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";
|
|
705
|
+
return { installed, missing, workspace };
|
|
706
|
+
} catch (error) {
|
|
707
|
+
return {
|
|
708
|
+
installed: [],
|
|
709
|
+
missing: requiredDeps,
|
|
710
|
+
workspace: []
|
|
711
|
+
};
|
|
876
712
|
}
|
|
877
713
|
}
|
|
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);
|
|
714
|
+
function showDependencyStatus(deps) {
|
|
715
|
+
if (deps.installed.length > 0) {
|
|
716
|
+
console.log(chalk2.green(` \u2705 Already installed: ${deps.installed.join(", ")}`));
|
|
885
717
|
}
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
const parentRoot = baseComponentsDir.replace(/[/\\]components$/i, "") || "src";
|
|
889
|
-
return path3.join(parentRoot, normalizedTarget).replace(/\\/g, "/");
|
|
718
|
+
if (deps.workspace.length > 0) {
|
|
719
|
+
console.log(chalk2.blue(` \u{1F517} Workspace dependencies: ${deps.workspace.join(", ")}`));
|
|
890
720
|
}
|
|
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;
|
|
721
|
+
if (deps.missing.length > 0) {
|
|
722
|
+
console.log(chalk2.yellow(` \u{1F4E6} Will install: ${deps.missing.join(", ")}`));
|
|
902
723
|
}
|
|
903
724
|
}
|
|
904
|
-
function
|
|
905
|
-
|
|
725
|
+
async function filterMissingDependencies(dependencies) {
|
|
726
|
+
const status = await checkProjectDependencies(dependencies);
|
|
727
|
+
return status.missing;
|
|
906
728
|
}
|
|
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;
|
|
729
|
+
function isWorkspaceError(errorMessage) {
|
|
730
|
+
return errorMessage.includes("EUNSUPPORTEDPROTOCOL") || errorMessage.includes("workspace:") || errorMessage.includes('Unsupported URL Type "workspace:"');
|
|
918
731
|
}
|
|
919
|
-
|
|
920
|
-
|
|
732
|
+
|
|
733
|
+
// src/utils/package-manager.ts
|
|
734
|
+
async function installDependencies(dependencies, options = {}) {
|
|
735
|
+
const useSpinner = options.useSpinner ?? true;
|
|
736
|
+
const spinner = useSpinner ? ora2(options.spinnerText ?? CLI_MESSAGES.status.installing("dependencies", "")).start() : null;
|
|
921
737
|
try {
|
|
922
738
|
const depStatus = await checkProjectDependencies(dependencies);
|
|
923
739
|
const missingDependencies = await filterMissingDependencies(dependencies);
|
|
924
740
|
if (missingDependencies.length === 0) {
|
|
925
|
-
spinner
|
|
741
|
+
spinner?.succeed(CLI_MESSAGES.success.depsAvailable);
|
|
926
742
|
if (depStatus.workspace.length > 0) {
|
|
927
743
|
console.log(chalk3.blue(` \u{1F517} Using workspace dependencies: ${depStatus.workspace.join(", ")}`));
|
|
928
744
|
}
|
|
@@ -935,15 +751,15 @@ async function installDependencies(dependencies) {
|
|
|
935
751
|
cwd: process.cwd(),
|
|
936
752
|
stdio: "pipe"
|
|
937
753
|
});
|
|
938
|
-
spinner
|
|
754
|
+
spinner?.succeed(CLI_MESSAGES.success.depsInstalled);
|
|
939
755
|
} catch (error) {
|
|
940
|
-
spinner
|
|
756
|
+
spinner?.fail(CLI_MESSAGES.errors.dependenciesFailed);
|
|
941
757
|
const errorMessage = error.stderr || error.message;
|
|
942
758
|
if (isWorkspaceError(errorMessage)) {
|
|
943
759
|
console.log(chalk3.yellow(`
|
|
944
760
|
\u{1F4A1} ${CLI_MESSAGES.info.workspaceDepsDetected}`));
|
|
945
761
|
const results = await installDependenciesIndividually(dependencies);
|
|
946
|
-
if (results.some((
|
|
762
|
+
if (results.some((result) => result.success)) {
|
|
947
763
|
console.log(chalk3.green("\u2705 Some dependencies installed successfully"));
|
|
948
764
|
return;
|
|
949
765
|
}
|
|
@@ -964,7 +780,7 @@ async function installDependenciesIndividually(dependencies) {
|
|
|
964
780
|
});
|
|
965
781
|
console.log(chalk3.green(` \u2705 Installed ${dep}`));
|
|
966
782
|
results.push({ dep, success: true });
|
|
967
|
-
} catch
|
|
783
|
+
} catch {
|
|
968
784
|
console.log(chalk3.yellow(` \u26A0\uFE0F Skipped ${dep} (may already be available)`));
|
|
969
785
|
results.push({ dep, success: false });
|
|
970
786
|
}
|
|
@@ -974,14 +790,14 @@ async function installDependenciesIndividually(dependencies) {
|
|
|
974
790
|
async function detectPackageManager() {
|
|
975
791
|
let dir = process.cwd();
|
|
976
792
|
while (true) {
|
|
977
|
-
if (await fs4.pathExists(
|
|
793
|
+
if (await fs4.pathExists(path4.join(dir, "bun.lock")) || await fs4.pathExists(path4.join(dir, "bun.lockb"))) {
|
|
978
794
|
return "bun";
|
|
979
795
|
}
|
|
980
|
-
if (await fs4.pathExists(
|
|
796
|
+
if (await fs4.pathExists(path4.join(dir, "pnpm-lock.yaml")))
|
|
981
797
|
return "pnpm";
|
|
982
|
-
if (await fs4.pathExists(
|
|
798
|
+
if (await fs4.pathExists(path4.join(dir, "yarn.lock")))
|
|
983
799
|
return "yarn";
|
|
984
|
-
const packageJsonPath =
|
|
800
|
+
const packageJsonPath = path4.join(dir, "package.json");
|
|
985
801
|
if (await fs4.pathExists(packageJsonPath)) {
|
|
986
802
|
try {
|
|
987
803
|
const packageJson = await fs4.readJson(packageJsonPath);
|
|
@@ -997,66 +813,107 @@ async function detectPackageManager() {
|
|
|
997
813
|
} catch {
|
|
998
814
|
}
|
|
999
815
|
}
|
|
1000
|
-
const parent =
|
|
816
|
+
const parent = path4.dirname(dir);
|
|
1001
817
|
if (parent === dir)
|
|
1002
818
|
break;
|
|
1003
819
|
dir = parent;
|
|
1004
820
|
}
|
|
1005
821
|
return "npm";
|
|
1006
822
|
}
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
823
|
+
|
|
824
|
+
// src/commands/init.ts
|
|
825
|
+
import path5 from "path";
|
|
826
|
+
import fs5 from "fs-extra";
|
|
827
|
+
import fetch2 from "node-fetch";
|
|
828
|
+
|
|
829
|
+
// src/utils/errors.ts
|
|
830
|
+
import chalk4 from "chalk";
|
|
831
|
+
var Ui8kitError = class extends Error {
|
|
832
|
+
suggestion;
|
|
833
|
+
constructor(message, suggestion) {
|
|
834
|
+
super(message);
|
|
835
|
+
this.name = this.constructor.name;
|
|
836
|
+
this.suggestion = suggestion;
|
|
837
|
+
}
|
|
838
|
+
};
|
|
839
|
+
var ConfigNotFoundError = class extends Ui8kitError {
|
|
840
|
+
constructor(registry) {
|
|
841
|
+
super(
|
|
842
|
+
`ui8kit config not found for registry "${registry}".`,
|
|
843
|
+
`Run: npx ui8kit@latest init --registry ${registry}`
|
|
844
|
+
);
|
|
845
|
+
}
|
|
846
|
+
};
|
|
847
|
+
function handleError(error) {
|
|
848
|
+
if (error instanceof Ui8kitError) {
|
|
849
|
+
if (error.message) {
|
|
850
|
+
console.error(chalk4.red(error.message));
|
|
1030
851
|
}
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
852
|
+
if (error.suggestion) {
|
|
853
|
+
console.log(chalk4.yellow(`\u{1F4A1} ${error.suggestion}`));
|
|
854
|
+
}
|
|
855
|
+
process.exit(1);
|
|
856
|
+
}
|
|
857
|
+
if (isZodError(error)) {
|
|
858
|
+
console.error(chalk4.red("\u274C Configuration validation error:"));
|
|
859
|
+
error.errors.forEach((issue) => {
|
|
860
|
+
const path13 = issue.path.join(".") || "root";
|
|
861
|
+
console.log(chalk4.yellow(` - ${path13}: ${issue.message}`));
|
|
862
|
+
});
|
|
863
|
+
process.exit(1);
|
|
1034
864
|
}
|
|
865
|
+
console.error(chalk4.red("\u274C Unexpected error:"));
|
|
866
|
+
console.error(chalk4.red(error.message ?? String(error)));
|
|
867
|
+
process.exit(1);
|
|
868
|
+
}
|
|
869
|
+
function isZodError(error) {
|
|
870
|
+
return Boolean(error && typeof error === "object" && "issues" in error);
|
|
1035
871
|
}
|
|
1036
872
|
|
|
1037
873
|
// src/commands/init.ts
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
874
|
+
function buildInitConfig(options) {
|
|
875
|
+
const registryName = options.registry || SCHEMA_CONFIG.defaultRegistryType;
|
|
876
|
+
const aliases = SCHEMA_CONFIG.defaultAliases;
|
|
877
|
+
const globalCss = options.globalCss || "src/index.css";
|
|
878
|
+
const aliasComponents = options.aliasComponents?.trim() || "@/components";
|
|
879
|
+
if (options.yes) {
|
|
880
|
+
return {
|
|
881
|
+
$schema: `${SCHEMA_CONFIG.baseUrl}.json`,
|
|
882
|
+
framework: "vite-react",
|
|
883
|
+
typescript: true,
|
|
884
|
+
globalCss,
|
|
885
|
+
aliases,
|
|
886
|
+
registry: SCHEMA_CONFIG.defaultRegistry,
|
|
887
|
+
componentsDir: SCHEMA_CONFIG.defaultDirectories.components,
|
|
888
|
+
libDir: SCHEMA_CONFIG.defaultDirectories.lib
|
|
889
|
+
};
|
|
890
|
+
}
|
|
891
|
+
return {
|
|
892
|
+
$schema: `${SCHEMA_CONFIG.baseUrl}.json`,
|
|
893
|
+
framework: "vite-react",
|
|
894
|
+
typescript: true,
|
|
895
|
+
globalCss,
|
|
896
|
+
aliases: { ...aliases, "@/components": aliasComponents },
|
|
897
|
+
registry: SCHEMA_CONFIG.defaultRegistry,
|
|
898
|
+
componentsDir: SCHEMA_CONFIG.defaultDirectories.components,
|
|
899
|
+
libDir: SCHEMA_CONFIG.defaultDirectories.lib
|
|
900
|
+
};
|
|
901
|
+
}
|
|
1044
902
|
async function initCommand(options) {
|
|
1045
903
|
const registryName = options.registry || SCHEMA_CONFIG.defaultRegistryType;
|
|
1046
|
-
|
|
1047
|
-
console.log(chalk4.blue(`\u{1F680} ${CLI_MESSAGES.info.initializing(registryName)}`));
|
|
904
|
+
logger.info(CLI_MESSAGES.info.initializing(registryName));
|
|
1048
905
|
const viteDetected = await isViteProject();
|
|
1049
906
|
if (!viteDetected) {
|
|
1050
|
-
console.error(
|
|
907
|
+
console.error(chalk5.red(`\u274C ${CLI_MESSAGES.errors.notViteProject}`));
|
|
1051
908
|
console.log("Please run this command in a Vite project directory.");
|
|
1052
909
|
process.exit(1);
|
|
1053
910
|
}
|
|
1054
911
|
if (!await hasReact()) {
|
|
1055
|
-
console.error(
|
|
912
|
+
console.error(chalk5.red(`\u274C ${CLI_MESSAGES.errors.reactNotInstalled}`));
|
|
1056
913
|
console.log("Please install React first: npm install react react-dom");
|
|
1057
914
|
process.exit(1);
|
|
1058
915
|
}
|
|
1059
|
-
const existingConfig = await
|
|
916
|
+
const existingConfig = await findConfig(registryName);
|
|
1060
917
|
if (existingConfig && !options.yes) {
|
|
1061
918
|
const { overwrite } = await prompts({
|
|
1062
919
|
type: "confirm",
|
|
@@ -1065,68 +922,67 @@ async function initCommand(options) {
|
|
|
1065
922
|
initial: false
|
|
1066
923
|
});
|
|
1067
924
|
if (!overwrite) {
|
|
1068
|
-
|
|
925
|
+
logger.warn(CLI_MESSAGES.info.installationCancelled);
|
|
1069
926
|
return;
|
|
1070
927
|
}
|
|
1071
928
|
}
|
|
1072
|
-
const aliases = SCHEMA_CONFIG.defaultAliases;
|
|
1073
929
|
let config;
|
|
1074
930
|
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
|
-
};
|
|
931
|
+
config = buildInitConfig({ yes: true, registry: registryName });
|
|
1084
932
|
} else {
|
|
1085
933
|
const responses = await prompts([
|
|
1086
934
|
{
|
|
1087
|
-
type: "
|
|
1088
|
-
name: "
|
|
1089
|
-
message: CLI_MESSAGES.prompts.
|
|
1090
|
-
initial:
|
|
935
|
+
type: "text",
|
|
936
|
+
name: "globalCss",
|
|
937
|
+
message: CLI_MESSAGES.prompts.globalCss,
|
|
938
|
+
initial: "src/index.css"
|
|
939
|
+
},
|
|
940
|
+
{
|
|
941
|
+
type: "text",
|
|
942
|
+
name: "aliasComponents",
|
|
943
|
+
message: CLI_MESSAGES.prompts.aliasComponents,
|
|
944
|
+
initial: "@/components"
|
|
1091
945
|
}
|
|
1092
946
|
]);
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
};
|
|
947
|
+
const aliasComponents = responses.aliasComponents?.trim() || "@/components";
|
|
948
|
+
const globalCss = responses.globalCss || "src/index.css";
|
|
949
|
+
config = buildInitConfig({
|
|
950
|
+
yes: false,
|
|
951
|
+
registry: registryName,
|
|
952
|
+
globalCss,
|
|
953
|
+
aliasComponents
|
|
954
|
+
});
|
|
1102
955
|
}
|
|
1103
|
-
const spinner =
|
|
956
|
+
const spinner = ora3(CLI_MESSAGES.info.initializing(registryName)).start();
|
|
1104
957
|
try {
|
|
1105
|
-
await saveConfig(config
|
|
958
|
+
await saveConfig(config);
|
|
1106
959
|
await ensureDir(config.libDir);
|
|
1107
960
|
await ensureDir(config.componentsDir);
|
|
1108
|
-
await ensureDir(
|
|
961
|
+
await ensureDir(path5.join(config.componentsDir, "ui"));
|
|
1109
962
|
await ensureDir(SCHEMA_CONFIG.defaultDirectories.blocks);
|
|
1110
963
|
await ensureDir(SCHEMA_CONFIG.defaultDirectories.layouts);
|
|
1111
964
|
await ensureDir(SCHEMA_CONFIG.defaultDirectories.variants);
|
|
1112
965
|
spinner.text = "Installing core utilities and variants...";
|
|
1113
966
|
await installCoreFiles(registryName, config, spinner);
|
|
967
|
+
spinner.text = "Installing core dependencies...";
|
|
968
|
+
await installDependencies(["clsx", "tailwind-merge"], {
|
|
969
|
+
useSpinner: false
|
|
970
|
+
});
|
|
1114
971
|
spinner.succeed(CLI_MESSAGES.success.initialized(registryName));
|
|
1115
|
-
|
|
1116
|
-
\u2705 ${CLI_MESSAGES.success.setupComplete(registryName)}`)
|
|
972
|
+
logger.success(`
|
|
973
|
+
\u2705 ${CLI_MESSAGES.success.setupComplete(registryName)}`);
|
|
1117
974
|
console.log("\nDirectories created:");
|
|
1118
|
-
console.log(` ${
|
|
1119
|
-
console.log(` ${
|
|
1120
|
-
console.log(` ${
|
|
1121
|
-
console.log(` ${
|
|
1122
|
-
console.log(` ${
|
|
1123
|
-
console.log(` ${
|
|
975
|
+
console.log(` ${chalk5.cyan("src/lib/")} - Utils, helpers, functions`);
|
|
976
|
+
console.log(` ${chalk5.cyan("src/variants/")} - CVA variant configurations`);
|
|
977
|
+
console.log(` ${chalk5.cyan("src/components/ui/")} - UI components`);
|
|
978
|
+
console.log(` ${chalk5.cyan("src/components/")} - Complex components`);
|
|
979
|
+
console.log(` ${chalk5.cyan("src/layouts/")} - Page layouts and structures`);
|
|
980
|
+
console.log(` ${chalk5.cyan("src/blocks/")} - Component blocks`);
|
|
1124
981
|
console.log("\nNext steps:");
|
|
1125
|
-
CLI_MESSAGES.examples.init.forEach((example) => console.log(` ${
|
|
982
|
+
CLI_MESSAGES.examples.init.forEach((example) => console.log(` ${chalk5.cyan(example)}`));
|
|
1126
983
|
} catch (error) {
|
|
1127
984
|
spinner.fail(CLI_MESSAGES.errors.buildFailed);
|
|
1128
|
-
|
|
1129
|
-
process.exit(1);
|
|
985
|
+
handleError(error);
|
|
1130
986
|
}
|
|
1131
987
|
}
|
|
1132
988
|
async function installCoreFiles(registryType, config, spinner) {
|
|
@@ -1135,7 +991,7 @@ async function installCoreFiles(registryType, config, spinner) {
|
|
|
1135
991
|
for (const baseUrl of cdnUrls) {
|
|
1136
992
|
try {
|
|
1137
993
|
const indexUrl = `${baseUrl}/index.json`;
|
|
1138
|
-
const response = await
|
|
994
|
+
const response = await fetch2(indexUrl);
|
|
1139
995
|
if (response.ok) {
|
|
1140
996
|
registryIndex = await response.json();
|
|
1141
997
|
break;
|
|
@@ -1149,87 +1005,795 @@ async function installCoreFiles(registryType, config, spinner) {
|
|
|
1149
1005
|
await createUtilsFile(config.libDir, config.typescript);
|
|
1150
1006
|
return;
|
|
1151
1007
|
}
|
|
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);
|
|
1008
|
+
const variantItems = registryIndex.components.filter((c) => c.type === "registry:variants");
|
|
1009
|
+
const libItems = registryIndex.components.filter((c) => c.type === "registry:lib");
|
|
1010
|
+
for (const item of libItems) {
|
|
1011
|
+
spinner.text = `Installing ${item.name}...`;
|
|
1012
|
+
await installComponentFromRegistry(item.name, "registry:lib", cdnUrls, config);
|
|
1013
|
+
}
|
|
1014
|
+
for (const item of variantItems) {
|
|
1015
|
+
spinner.text = `Installing variant: ${item.name}...`;
|
|
1016
|
+
await installComponentFromRegistry(item.name, "registry:variants", cdnUrls, config);
|
|
1017
|
+
}
|
|
1018
|
+
spinner.text = "Syncing variants index...";
|
|
1019
|
+
const variantsIndexStatus = await installVariantsIndex(cdnUrls);
|
|
1020
|
+
if (variantsIndexStatus === "updated") {
|
|
1021
|
+
spinner.text = "Updated variants/index.ts from CDN";
|
|
1022
|
+
} else if (variantsIndexStatus === "created") {
|
|
1023
|
+
spinner.text = "Created variants/index.ts from CDN";
|
|
1024
|
+
} else if (variantsIndexStatus === "unchanged") {
|
|
1025
|
+
spinner.text = "variants/index.ts is up to date";
|
|
1026
|
+
} else {
|
|
1027
|
+
spinner.text = "variants/index.ts not found in registry (skipped)";
|
|
1028
|
+
}
|
|
1029
|
+
spinner.text = `\u2705 Installed ${libItems.length} utilities and ${variantItems.length} variants`;
|
|
1030
|
+
}
|
|
1031
|
+
async function installComponentFromRegistry(name, type, cdnUrls, config) {
|
|
1032
|
+
const folder = type === "registry:lib" ? "lib" : type === "registry:variants" ? "components/variants" : "components/ui";
|
|
1033
|
+
for (const baseUrl of cdnUrls) {
|
|
1034
|
+
try {
|
|
1035
|
+
const url = `${baseUrl}/${folder}/${name}.json`;
|
|
1036
|
+
const response = await fetch2(url);
|
|
1037
|
+
if (response.ok) {
|
|
1038
|
+
const component = await response.json();
|
|
1039
|
+
for (const file of component.files) {
|
|
1040
|
+
const fileName = path5.basename(file.path);
|
|
1041
|
+
let targetDir;
|
|
1042
|
+
if (type === "registry:lib") {
|
|
1043
|
+
targetDir = config.libDir;
|
|
1044
|
+
} else if (type === "registry:variants") {
|
|
1045
|
+
targetDir = SCHEMA_CONFIG.defaultDirectories.variants;
|
|
1046
|
+
} else {
|
|
1047
|
+
targetDir = path5.join(config.componentsDir, "ui");
|
|
1048
|
+
}
|
|
1049
|
+
const targetPath = path5.join(process.cwd(), targetDir, fileName);
|
|
1050
|
+
await fs5.ensureDir(path5.dirname(targetPath));
|
|
1051
|
+
await fs5.writeFile(targetPath, file.content || "", "utf-8");
|
|
1052
|
+
}
|
|
1053
|
+
return;
|
|
1054
|
+
}
|
|
1055
|
+
} catch {
|
|
1056
|
+
continue;
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
async function installVariantsIndex(cdnUrls) {
|
|
1061
|
+
for (const baseUrl of cdnUrls) {
|
|
1062
|
+
try {
|
|
1063
|
+
const url = `${baseUrl}/components/variants/index.json`;
|
|
1064
|
+
const response = await fetch2(url);
|
|
1065
|
+
if (response.ok) {
|
|
1066
|
+
const component = await response.json();
|
|
1067
|
+
for (const file of component.files) {
|
|
1068
|
+
const fileName = path5.basename(file.path);
|
|
1069
|
+
if (!fileName.startsWith("index.")) {
|
|
1070
|
+
continue;
|
|
1071
|
+
}
|
|
1072
|
+
const targetDir = SCHEMA_CONFIG.defaultDirectories.variants;
|
|
1073
|
+
const targetPath = path5.join(process.cwd(), targetDir, fileName);
|
|
1074
|
+
const incomingContent = file.content || "";
|
|
1075
|
+
const exists = await fs5.pathExists(targetPath);
|
|
1076
|
+
if (exists) {
|
|
1077
|
+
const currentContent = await fs5.readFile(targetPath, "utf-8");
|
|
1078
|
+
if (currentContent === incomingContent) {
|
|
1079
|
+
return "unchanged";
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
await fs5.ensureDir(path5.dirname(targetPath));
|
|
1083
|
+
await fs5.writeFile(targetPath, incomingContent, "utf-8");
|
|
1084
|
+
return exists ? "updated" : "created";
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
} catch {
|
|
1088
|
+
continue;
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
return "skipped";
|
|
1092
|
+
}
|
|
1093
|
+
async function createUtilsFile(libDir, typescript) {
|
|
1094
|
+
const utilsContent = `import { type ClassValue, clsx } from "clsx"
|
|
1095
|
+
import { twMerge } from "tailwind-merge"
|
|
1096
|
+
|
|
1097
|
+
export function cn(...inputs: ClassValue[]) {
|
|
1098
|
+
return twMerge(clsx(inputs))
|
|
1099
|
+
}`;
|
|
1100
|
+
const fileName = typescript ? "utils.ts" : "utils.js";
|
|
1101
|
+
const filePath = path5.join(process.cwd(), libDir, fileName);
|
|
1102
|
+
await fs5.writeFile(filePath, utilsContent, "utf-8");
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
// src/utils/registry-validator.ts
|
|
1106
|
+
async function validateComponentInstallation(components, registryType) {
|
|
1107
|
+
const packageJsonPath = path6.join(process.cwd(), "package.json");
|
|
1108
|
+
if (!await fs6.pathExists(packageJsonPath)) {
|
|
1109
|
+
return {
|
|
1110
|
+
isValid: false,
|
|
1111
|
+
message: "No package.json found in the current directory. Run this command from your project root."
|
|
1112
|
+
};
|
|
1113
|
+
}
|
|
1114
|
+
const nodeMajorVersion = Number.parseInt(process.versions.node.split(".")[0] ?? "0", 10);
|
|
1115
|
+
if (Number.isNaN(nodeMajorVersion) || nodeMajorVersion < 18) {
|
|
1116
|
+
return {
|
|
1117
|
+
isValid: false,
|
|
1118
|
+
message: `Node.js 18+ is required. Current version: ${process.versions.node}`
|
|
1119
|
+
};
|
|
1120
|
+
}
|
|
1121
|
+
const existingConfig = await findConfig(registryType);
|
|
1122
|
+
if (!existingConfig) {
|
|
1123
|
+
const { runInit } = await prompts2({
|
|
1124
|
+
type: "confirm",
|
|
1125
|
+
name: "runInit",
|
|
1126
|
+
message: "ui8kit.config.json not found. Run init now?",
|
|
1127
|
+
initial: true
|
|
1128
|
+
});
|
|
1129
|
+
if (runInit) {
|
|
1130
|
+
await initCommand({ registry: registryType });
|
|
1131
|
+
const configAfterInit = await findConfig(registryType);
|
|
1132
|
+
if (configAfterInit) {
|
|
1133
|
+
return { isValid: true };
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
return {
|
|
1137
|
+
isValid: false,
|
|
1138
|
+
message: `ui8kit is not initialized. Run: npx ui8kit@latest init --registry ${registryType}`
|
|
1139
|
+
};
|
|
1140
|
+
}
|
|
1141
|
+
return { isValid: true };
|
|
1142
|
+
}
|
|
1143
|
+
function handleValidationError(result) {
|
|
1144
|
+
console.error(chalk6.red("\u274C Registry Validation Error:"));
|
|
1145
|
+
console.error(chalk6.red(result.message));
|
|
1146
|
+
if (result.missingComponents && result.missingComponents.length > 0) {
|
|
1147
|
+
console.log(chalk6.yellow("\n\u{1F4A1} Suggestion:"));
|
|
1148
|
+
console.log(`Install missing components first: ${chalk6.cyan(`npx ui8kit add ${result.missingComponents.join(" ")}`)}
|
|
1149
|
+
`);
|
|
1150
|
+
}
|
|
1151
|
+
process.exit(1);
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
// src/utils/dependency-resolver.ts
|
|
1155
|
+
async function resolveRegistryTree(componentNames, registryType, getComponent2) {
|
|
1156
|
+
const componentsByName = /* @__PURE__ */ new Map();
|
|
1157
|
+
const state = /* @__PURE__ */ new Map();
|
|
1158
|
+
const visitStack = [];
|
|
1159
|
+
const normalized = componentNames.map((name) => name.toLowerCase());
|
|
1160
|
+
const ensureComponent = async (name) => {
|
|
1161
|
+
const key = name.toLowerCase();
|
|
1162
|
+
if (state.get(key) === "done") {
|
|
1163
|
+
return componentsByName.get(key) ?? null;
|
|
1164
|
+
}
|
|
1165
|
+
if (state.get(key) === "visiting") {
|
|
1166
|
+
const cycle = visitStack.includes(key) ? [...visitStack, key].join(" -> ") : key;
|
|
1167
|
+
logger.warn(`Circular registry dependency detected: ${cycle}`);
|
|
1168
|
+
return componentsByName.get(key) ?? null;
|
|
1169
|
+
}
|
|
1170
|
+
const component = await getComponent2(name, registryType);
|
|
1171
|
+
if (!component) {
|
|
1172
|
+
logger.warn(`Component ${name} not found in ${registryType} registry, skipping`);
|
|
1173
|
+
state.set(key, "done");
|
|
1174
|
+
return null;
|
|
1175
|
+
}
|
|
1176
|
+
componentsByName.set(key, component);
|
|
1177
|
+
state.set(key, "visiting");
|
|
1178
|
+
visitStack.push(key);
|
|
1179
|
+
for (const dependency of component.registryDependencies ?? []) {
|
|
1180
|
+
await ensureComponent(dependency);
|
|
1181
|
+
}
|
|
1182
|
+
visitStack.pop();
|
|
1183
|
+
state.set(key, "done");
|
|
1184
|
+
return component;
|
|
1185
|
+
};
|
|
1186
|
+
for (const name of normalized) {
|
|
1187
|
+
await ensureComponent(name);
|
|
1188
|
+
}
|
|
1189
|
+
const graph = /* @__PURE__ */ new Map();
|
|
1190
|
+
const inDegree = /* @__PURE__ */ new Map();
|
|
1191
|
+
for (const component of componentsByName.values()) {
|
|
1192
|
+
const name = component.name.toLowerCase();
|
|
1193
|
+
if (!graph.has(name)) {
|
|
1194
|
+
graph.set(name, /* @__PURE__ */ new Set());
|
|
1195
|
+
inDegree.set(name, 0);
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
for (const component of componentsByName.values()) {
|
|
1199
|
+
const from = component.name.toLowerCase();
|
|
1200
|
+
const deps = component.registryDependencies ?? [];
|
|
1201
|
+
for (const dep of deps) {
|
|
1202
|
+
const to = dep.toLowerCase();
|
|
1203
|
+
if (!componentsByName.has(to)) {
|
|
1204
|
+
continue;
|
|
1205
|
+
}
|
|
1206
|
+
const targets = graph.get(to);
|
|
1207
|
+
if (targets) {
|
|
1208
|
+
targets.add(from);
|
|
1209
|
+
} else {
|
|
1210
|
+
graph.set(to, /* @__PURE__ */ new Set([from]));
|
|
1211
|
+
}
|
|
1212
|
+
inDegree.set(from, (inDegree.get(from) ?? 0) + 1);
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
const queue = [];
|
|
1216
|
+
inDegree.forEach((value, name) => {
|
|
1217
|
+
if (value === 0) {
|
|
1218
|
+
queue.push(name);
|
|
1219
|
+
}
|
|
1220
|
+
});
|
|
1221
|
+
const orderedKeys = [];
|
|
1222
|
+
while (queue.length > 0) {
|
|
1223
|
+
const current = queue.shift();
|
|
1224
|
+
if (!current)
|
|
1225
|
+
continue;
|
|
1226
|
+
orderedKeys.push(current);
|
|
1227
|
+
const targets = graph.get(current) || /* @__PURE__ */ new Set();
|
|
1228
|
+
for (const next of targets) {
|
|
1229
|
+
const degree = (inDegree.get(next) ?? 1) - 1;
|
|
1230
|
+
inDegree.set(next, degree);
|
|
1231
|
+
if (degree === 0) {
|
|
1232
|
+
queue.push(next);
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
for (const key of componentsByName.keys()) {
|
|
1237
|
+
if (!orderedKeys.includes(key)) {
|
|
1238
|
+
logger.warn(`Unresolved dependency cycle detected for ${key}, appending in current order`);
|
|
1239
|
+
orderedKeys.push(key);
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
return orderedKeys.map((key) => componentsByName.get(key)).filter((component) => Boolean(component));
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
// src/utils/diff-utils.ts
|
|
1246
|
+
import { createTwoFilesPatch } from "diff";
|
|
1247
|
+
function buildUnifiedDiff(oldFilePath, newFilePath, oldContent, newContent) {
|
|
1248
|
+
return createTwoFilesPatch(
|
|
1249
|
+
oldFilePath,
|
|
1250
|
+
newFilePath,
|
|
1251
|
+
oldContent,
|
|
1252
|
+
newContent,
|
|
1253
|
+
"local",
|
|
1254
|
+
"registry",
|
|
1255
|
+
{ context: 3 }
|
|
1256
|
+
);
|
|
1257
|
+
}
|
|
1258
|
+
function hasDiff(oldContent, newContent) {
|
|
1259
|
+
return oldContent !== newContent;
|
|
1260
|
+
}
|
|
1261
|
+
function formatDiffPreview(diff, maxLines = 80) {
|
|
1262
|
+
const lines = diff.split("\n");
|
|
1263
|
+
if (lines.length <= maxLines) {
|
|
1264
|
+
return diff;
|
|
1265
|
+
}
|
|
1266
|
+
return `${lines.slice(0, maxLines).join("\n")}
|
|
1267
|
+
...`;
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
// src/utils/transform.ts
|
|
1271
|
+
import path7 from "path";
|
|
1272
|
+
import ts from "typescript";
|
|
1273
|
+
var IMPORT_NODE_KIND = ts.SyntaxKind.ImportDeclaration;
|
|
1274
|
+
function normalizeAliasKey(alias) {
|
|
1275
|
+
return alias.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
1276
|
+
}
|
|
1277
|
+
function toPosix(value) {
|
|
1278
|
+
return value.replace(/\\/g, "/");
|
|
1279
|
+
}
|
|
1280
|
+
function normalizeAliasTarget(alias) {
|
|
1281
|
+
return toPosix(alias).replace(/\/+$/, "");
|
|
1282
|
+
}
|
|
1283
|
+
function normalizeDefaultAliases() {
|
|
1284
|
+
const map = /* @__PURE__ */ new Map();
|
|
1285
|
+
for (const [alias, target] of Object.entries(SCHEMA_CONFIG.defaultAliases)) {
|
|
1286
|
+
map.set(normalizeAliasKey(alias), normalizeAliasTarget(target));
|
|
1287
|
+
}
|
|
1288
|
+
return map;
|
|
1289
|
+
}
|
|
1290
|
+
function normalizeConfiguredAliases(aliasMap) {
|
|
1291
|
+
const normalized = /* @__PURE__ */ new Map();
|
|
1292
|
+
for (const [alias, target] of Object.entries(aliasMap)) {
|
|
1293
|
+
normalized.set(normalizeAliasKey(alias), normalizeAliasTarget(target));
|
|
1294
|
+
}
|
|
1295
|
+
return normalized;
|
|
1296
|
+
}
|
|
1297
|
+
function pickAliasForImport(importPath, configuredAliases) {
|
|
1298
|
+
const pathValue = toPosix(importPath);
|
|
1299
|
+
if (!pathValue.startsWith("@/")) {
|
|
1300
|
+
return void 0;
|
|
1301
|
+
}
|
|
1302
|
+
const trimmed = pathValue.slice(2);
|
|
1303
|
+
const [root] = trimmed.split("/");
|
|
1304
|
+
const rootAlias = `@/${root}`;
|
|
1305
|
+
const directAlias = Array.from(configuredAliases.keys()).filter((alias) => pathValue === alias || pathValue.startsWith(`${alias}/`)).sort((a, b) => b.length - a.length)[0];
|
|
1306
|
+
if (directAlias) {
|
|
1307
|
+
const aliasValue = configuredAliases.get(directAlias);
|
|
1308
|
+
if (!aliasValue || !aliasValue.startsWith("@/")) {
|
|
1309
|
+
return void 0;
|
|
1310
|
+
}
|
|
1311
|
+
const remainder = pathValue.slice(directAlias.length).replace(/^\/+/, "");
|
|
1312
|
+
if (!remainder) {
|
|
1313
|
+
return aliasValue;
|
|
1314
|
+
}
|
|
1315
|
+
const remainderParts = remainder.split("/");
|
|
1316
|
+
const targetParts = normalizeAliasKey(aliasValue).replace(/^@\//, "").split("/");
|
|
1317
|
+
const aliasTail = targetParts[targetParts.length - 1];
|
|
1318
|
+
const normalizedRemainder = remainderParts[0] === aliasTail ? remainderParts.slice(1).join("/") : remainder;
|
|
1319
|
+
return normalizedRemainder ? `${aliasValue}/${normalizedRemainder}` : aliasValue;
|
|
1320
|
+
}
|
|
1321
|
+
const defaultAliasCandidates = Array.from(normalizeDefaultAliases().keys()).filter((alias) => pathValue === alias || pathValue.startsWith(`${alias}/`)).sort((a, b) => b.length - a.length);
|
|
1322
|
+
for (const defaultAlias of defaultAliasCandidates) {
|
|
1323
|
+
const defaultParts = normalizeAliasKey(defaultAlias).replace(/^@\//, "").split("/");
|
|
1324
|
+
const remainderFromDefault = trimmed.split("/").slice(defaultParts.length);
|
|
1325
|
+
if (remainderFromDefault.length === 0) {
|
|
1326
|
+
continue;
|
|
1327
|
+
}
|
|
1328
|
+
const candidateAliasTail = remainderFromDefault[0];
|
|
1329
|
+
const candidateAlias = `@/${candidateAliasTail}`;
|
|
1330
|
+
if (configuredAliases.has(candidateAlias)) {
|
|
1331
|
+
const remainderPath = remainderFromDefault.slice(1).join("/");
|
|
1332
|
+
return remainderPath ? `${candidateAlias}/${remainderPath}` : candidateAlias;
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
return void 0;
|
|
1336
|
+
}
|
|
1337
|
+
function rewriteModuleSpecifier(specifierText, configuredAliases) {
|
|
1338
|
+
if (!specifierText.startsWith("@/")) {
|
|
1339
|
+
return specifierText;
|
|
1340
|
+
}
|
|
1341
|
+
const aliasesMap = normalizeConfiguredAliases(configuredAliases);
|
|
1342
|
+
const rewrittenRemainder = pickAliasForImport(specifierText, aliasesMap);
|
|
1343
|
+
if (!rewrittenRemainder || rewrittenRemainder === normalizeAliasKey(specifierText)) {
|
|
1344
|
+
return specifierText;
|
|
1345
|
+
}
|
|
1346
|
+
if (rewrittenRemainder) {
|
|
1347
|
+
return rewrittenRemainder;
|
|
1348
|
+
}
|
|
1349
|
+
return specifierText;
|
|
1350
|
+
}
|
|
1351
|
+
function transformImports(content, aliases) {
|
|
1352
|
+
const sourceFile = ts.createSourceFile("component.tsx", content, ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX);
|
|
1353
|
+
const importSpans = [];
|
|
1354
|
+
const configuredAliases = normalizeConfiguredAliases(aliases);
|
|
1355
|
+
function visit(node) {
|
|
1356
|
+
if (node.kind === IMPORT_NODE_KIND && ts.isImportDeclaration(node)) {
|
|
1357
|
+
const moduleSpecifier = node.moduleSpecifier;
|
|
1358
|
+
if (ts.isStringLiteral(moduleSpecifier)) {
|
|
1359
|
+
const value = moduleSpecifier.text;
|
|
1360
|
+
const rewritten = rewriteModuleSpecifier(value, Object.fromEntries(configuredAliases));
|
|
1361
|
+
if (rewritten !== value) {
|
|
1362
|
+
importSpans.push({
|
|
1363
|
+
start: moduleSpecifier.getStart(sourceFile),
|
|
1364
|
+
end: moduleSpecifier.getEnd(),
|
|
1365
|
+
replacement: `"${rewritten}"`
|
|
1366
|
+
});
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
ts.forEachChild(node, visit);
|
|
1371
|
+
}
|
|
1372
|
+
ts.forEachChild(sourceFile, visit);
|
|
1373
|
+
if (importSpans.length === 0) {
|
|
1374
|
+
return content;
|
|
1375
|
+
}
|
|
1376
|
+
importSpans.sort((a, b) => b.start - a.start);
|
|
1377
|
+
let transformed = content;
|
|
1378
|
+
for (const span of importSpans) {
|
|
1379
|
+
transformed = `${transformed.slice(0, span.start)}${span.replacement}${transformed.slice(span.end)}`;
|
|
1380
|
+
}
|
|
1381
|
+
return transformed;
|
|
1382
|
+
}
|
|
1383
|
+
function transformCleanup(content) {
|
|
1384
|
+
return content.replace(/\r\n/g, "\n").replace(/\n{3,}/g, "\n\n").trimEnd() + "\n";
|
|
1385
|
+
}
|
|
1386
|
+
function applyTransforms(content, aliases) {
|
|
1387
|
+
const withImports = transformImports(content, aliases);
|
|
1388
|
+
return transformCleanup(withImports);
|
|
1389
|
+
}
|
|
1390
|
+
function shouldTransformFile(fileName) {
|
|
1391
|
+
return [".ts", ".tsx"].includes(path7.extname(fileName));
|
|
1392
|
+
}
|
|
1393
|
+
|
|
1394
|
+
// src/commands/add.ts
|
|
1395
|
+
var ADD_EXCLUDED_COMPONENT_TYPES = ["registry:variants", "registry:lib"];
|
|
1396
|
+
async function addCommand(components, options) {
|
|
1397
|
+
const registryType = resolveRegistryType(options.registry);
|
|
1398
|
+
const requestOptions = {
|
|
1399
|
+
excludeTypes: ADD_EXCLUDED_COMPONENT_TYPES,
|
|
1400
|
+
maxRetries: options.retry ? 3 : 1,
|
|
1401
|
+
noCache: options.cache === false
|
|
1402
|
+
};
|
|
1403
|
+
try {
|
|
1404
|
+
if (options.all || components.includes("all")) {
|
|
1405
|
+
await addAllComponents(options, registryType, requestOptions);
|
|
1406
|
+
return;
|
|
1407
|
+
}
|
|
1408
|
+
const selectedComponents = components.length > 0 ? components : await pickComponentsFromPrompt(registryType, requestOptions);
|
|
1409
|
+
if (selectedComponents.length === 0) {
|
|
1410
|
+
logger.warn(CLI_MESSAGES.errors.noComponentsSpecified);
|
|
1411
|
+
return;
|
|
1412
|
+
}
|
|
1413
|
+
const validation = await validateComponentInstallation(selectedComponents, registryType);
|
|
1414
|
+
if (!validation.isValid) {
|
|
1415
|
+
handleValidationError(validation);
|
|
1416
|
+
}
|
|
1417
|
+
const config = await findConfig(registryType);
|
|
1418
|
+
if (!config) {
|
|
1419
|
+
throw new ConfigNotFoundError(registryType);
|
|
1420
|
+
}
|
|
1421
|
+
if (options.retry) {
|
|
1422
|
+
logger.info(CLI_MESSAGES.info.retryEnabled);
|
|
1423
|
+
}
|
|
1424
|
+
logger.info(CLI_MESSAGES.info.installing(registryType));
|
|
1425
|
+
const getComponentFn = (name, type) => getComponent(name, type, requestOptions);
|
|
1426
|
+
const results = await installRequestedComponents(
|
|
1427
|
+
selectedComponents,
|
|
1428
|
+
registryType,
|
|
1429
|
+
config,
|
|
1430
|
+
getComponentFn,
|
|
1431
|
+
options
|
|
1432
|
+
);
|
|
1433
|
+
displayInstallationSummary(registryType, results);
|
|
1434
|
+
} catch (error) {
|
|
1435
|
+
handleError(error);
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1438
|
+
async function addAllComponents(options, registryType, requestOptions) {
|
|
1439
|
+
logger.info(CLI_MESSAGES.info.installingAll(registryType));
|
|
1440
|
+
const validation = await validateComponentInstallation([], registryType);
|
|
1441
|
+
if (!validation.isValid) {
|
|
1442
|
+
handleValidationError(validation);
|
|
1443
|
+
}
|
|
1444
|
+
const config = await findConfig(registryType);
|
|
1445
|
+
if (!config) {
|
|
1446
|
+
throw new ConfigNotFoundError(registryType);
|
|
1447
|
+
}
|
|
1448
|
+
const getAllComponentsFn = (type) => getAllComponents(type, requestOptions);
|
|
1449
|
+
if (options.retry) {
|
|
1450
|
+
logger.info(CLI_MESSAGES.info.retryEnabled);
|
|
1451
|
+
}
|
|
1452
|
+
const spinner = ora4(CLI_MESSAGES.info.fetchingComponentList(registryType)).start();
|
|
1453
|
+
try {
|
|
1454
|
+
const allComponents = await getAllComponentsFn(registryType);
|
|
1455
|
+
if (allComponents.length === 0) {
|
|
1456
|
+
spinner.fail(`No components found in ${registryType} registry`);
|
|
1457
|
+
logger.warn(`
|
|
1458
|
+
\u26A0\uFE0F ${registryType} ${CLI_MESSAGES.errors.registryTempUnavailable}`);
|
|
1459
|
+
console.log("Try these alternatives:");
|
|
1460
|
+
CLI_MESSAGES.examples.troubleshooting.forEach((alt) => console.log(` \u2022 ${alt}`));
|
|
1461
|
+
return;
|
|
1462
|
+
}
|
|
1463
|
+
spinner.succeed(CLI_MESSAGES.status.foundComponents(allComponents.length, registryType));
|
|
1464
|
+
if (options.dryRun) {
|
|
1465
|
+
await installRequestedComponents(
|
|
1466
|
+
allComponents.map((c) => c.name),
|
|
1467
|
+
registryType,
|
|
1468
|
+
config,
|
|
1469
|
+
(name, type) => getComponent(name, type, requestOptions),
|
|
1470
|
+
options,
|
|
1471
|
+
allComponents
|
|
1472
|
+
);
|
|
1473
|
+
return;
|
|
1474
|
+
}
|
|
1475
|
+
const results = await installRequestedComponents(
|
|
1476
|
+
allComponents.map((c) => c.name),
|
|
1477
|
+
registryType,
|
|
1478
|
+
config,
|
|
1479
|
+
(name, type) => getComponent(name, type, requestOptions),
|
|
1480
|
+
options,
|
|
1481
|
+
allComponents
|
|
1482
|
+
);
|
|
1483
|
+
await installComponentsIndex(registryType, config);
|
|
1484
|
+
displayInstallationSummary(registryType, results);
|
|
1485
|
+
} catch (error) {
|
|
1486
|
+
spinner.fail(CLI_MESSAGES.errors.failedToFetch(registryType));
|
|
1487
|
+
logger.error(`Error: ${error.message}`);
|
|
1488
|
+
logger.warn(`
|
|
1489
|
+
\u26A0\uFE0F ${registryType} ${CLI_MESSAGES.errors.registryTempUnavailable}`);
|
|
1490
|
+
console.log("Try these alternatives:");
|
|
1491
|
+
CLI_MESSAGES.examples.troubleshooting.forEach((alt) => console.log(` \u2022 ${alt}`));
|
|
1492
|
+
process.exit(1);
|
|
1493
|
+
}
|
|
1494
|
+
}
|
|
1495
|
+
async function processComponents(componentNames, registryType, config, getComponentFn, options, preloadedComponents, totalCount) {
|
|
1496
|
+
const results = [];
|
|
1497
|
+
const componentMap = new Map(preloadedComponents?.map((c) => [c.name.toLowerCase(), c]));
|
|
1498
|
+
const total = totalCount ?? componentNames.length;
|
|
1499
|
+
if (total > 1) {
|
|
1500
|
+
logger.info(`Installing ${total} components...`);
|
|
1501
|
+
}
|
|
1502
|
+
for (let i = 0; i < componentNames.length; i += 1) {
|
|
1503
|
+
const componentName = componentNames[i];
|
|
1504
|
+
const position = `${i + 1}/${total}`;
|
|
1505
|
+
const spinner = ora4(`[${position}] ${CLI_MESSAGES.status.installing(componentName, registryType)}`).start();
|
|
1506
|
+
try {
|
|
1507
|
+
const lookupName = componentName.toLowerCase();
|
|
1508
|
+
let component = componentMap?.get(lookupName) ?? null;
|
|
1509
|
+
if (!component) {
|
|
1510
|
+
component = await getComponentFn(componentName, registryType);
|
|
1511
|
+
}
|
|
1512
|
+
if (!component) {
|
|
1513
|
+
throw new Error(CLI_MESSAGES.errors.componentNotFound(componentName, registryType));
|
|
1514
|
+
}
|
|
1515
|
+
if (options.dryRun) {
|
|
1516
|
+
spinner.succeed(`[${position}] ${CLI_MESSAGES.status.wouldInstall(component.name, registryType)}`);
|
|
1517
|
+
logger.info(` Type: ${component.type}`);
|
|
1518
|
+
if (component.registryDependencies && component.registryDependencies.length > 0) {
|
|
1519
|
+
logger.info(` Registry deps: ${component.registryDependencies.join(" -> ")}`);
|
|
1520
|
+
}
|
|
1521
|
+
logger.info(` Files: ${component.files.length}`);
|
|
1522
|
+
logger.info(` Dependencies: ${component.dependencies.join(", ") || "none"}`);
|
|
1523
|
+
for (const file of component.files) {
|
|
1524
|
+
const fileName = path8.basename(file.path);
|
|
1525
|
+
const target = file.target || inferTargetFromType(component.type);
|
|
1526
|
+
const installDir = resolveInstallDir(target, config);
|
|
1527
|
+
const targetPath = path8.join(process.cwd(), installDir, fileName);
|
|
1528
|
+
const exists = await fs7.pathExists(targetPath);
|
|
1529
|
+
const status = exists ? "overwrite" : "create";
|
|
1530
|
+
logger.info(` ${status}: ${targetPath}`);
|
|
1531
|
+
if (exists) {
|
|
1532
|
+
const currentContent = await fs7.readFile(targetPath, "utf-8");
|
|
1533
|
+
const transformedIncoming = shouldTransformFile(fileName) ? applyTransforms(file.content, config.aliases) : file.content;
|
|
1534
|
+
const changed = hasDiff(currentContent, transformedIncoming);
|
|
1535
|
+
if (changed) {
|
|
1536
|
+
const patch = buildUnifiedDiff(targetPath, `${component.name}/${fileName}`, currentContent, transformedIncoming);
|
|
1537
|
+
console.log(formatDiffPreview(patch, 40));
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1541
|
+
if (component.dependencies.length > 0) {
|
|
1542
|
+
const depStatus = await checkProjectDependencies(component.dependencies);
|
|
1543
|
+
showDependencyStatus(depStatus);
|
|
1544
|
+
}
|
|
1545
|
+
continue;
|
|
1546
|
+
}
|
|
1547
|
+
await installComponentFiles(component, config, options.force);
|
|
1548
|
+
if (component.dependencies.length > 0) {
|
|
1549
|
+
try {
|
|
1550
|
+
await installDependencies(component.dependencies);
|
|
1551
|
+
} catch (error) {
|
|
1552
|
+
logger.warn(CLI_MESSAGES.errors.couldNotInstallDeps(component.name));
|
|
1553
|
+
logger.warn(` Dependencies: ${component.dependencies.join(", ")}`);
|
|
1554
|
+
logger.warn(" Please install them manually if needed");
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
spinner.succeed(`[${position}] ${CLI_MESSAGES.status.installing(component.name, registryType)}`);
|
|
1558
|
+
results.push({ name: component.name, status: "success" });
|
|
1559
|
+
} catch (error) {
|
|
1560
|
+
spinner.fail(`[${position}] ${CLI_MESSAGES.errors.failedToInstall(componentName, registryType)}`);
|
|
1561
|
+
logger.error(` Error: ${error.message}`);
|
|
1562
|
+
results.push({
|
|
1563
|
+
name: componentName,
|
|
1564
|
+
status: "error",
|
|
1565
|
+
error: error.message
|
|
1566
|
+
});
|
|
1567
|
+
}
|
|
1568
|
+
}
|
|
1569
|
+
return results;
|
|
1570
|
+
}
|
|
1571
|
+
async function pickComponentsFromPrompt(registryType, requestOptions) {
|
|
1572
|
+
const allComponents = await getAllComponents(registryType, requestOptions);
|
|
1573
|
+
if (allComponents.length === 0) {
|
|
1574
|
+
logger.warn(`No components found in ${registryType} registry`);
|
|
1575
|
+
return [];
|
|
1576
|
+
}
|
|
1577
|
+
const sorted = allComponents.filter((component) => !ADD_EXCLUDED_COMPONENT_TYPES.includes(component.type)).sort((a, b) => {
|
|
1578
|
+
if (a.type === b.type) {
|
|
1579
|
+
return a.name.localeCompare(b.name);
|
|
1580
|
+
}
|
|
1581
|
+
return a.type.localeCompare(b.type);
|
|
1582
|
+
});
|
|
1583
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
1584
|
+
for (const component of sorted) {
|
|
1585
|
+
const group = grouped.get(component.type) ?? [];
|
|
1586
|
+
group.push(component);
|
|
1587
|
+
if (!grouped.has(component.type)) {
|
|
1588
|
+
grouped.set(component.type, group);
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1591
|
+
const choices = [];
|
|
1592
|
+
for (const [type, components] of grouped) {
|
|
1593
|
+
choices.push({
|
|
1594
|
+
title: `
|
|
1595
|
+
${type}`,
|
|
1596
|
+
value: "__separator__",
|
|
1597
|
+
description: "",
|
|
1598
|
+
disabled: true
|
|
1599
|
+
});
|
|
1600
|
+
for (const component of components) {
|
|
1601
|
+
choices.push({
|
|
1602
|
+
title: component.name,
|
|
1603
|
+
value: component.name,
|
|
1604
|
+
description: component.description || component.type
|
|
1605
|
+
});
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
if (choices.length === 0) {
|
|
1609
|
+
logger.warn(`No selectable components found in ${registryType} registry`);
|
|
1610
|
+
return [];
|
|
1611
|
+
}
|
|
1612
|
+
const { selected } = await prompts3({
|
|
1613
|
+
type: "multiselect",
|
|
1614
|
+
name: "selected",
|
|
1615
|
+
message: "Which components would you like to add?",
|
|
1616
|
+
instructions: false,
|
|
1617
|
+
choices,
|
|
1618
|
+
hint: "Space to select, Enter to confirm"
|
|
1619
|
+
});
|
|
1620
|
+
return selected || [];
|
|
1621
|
+
}
|
|
1622
|
+
async function installRequestedComponents(componentNames, registryType, config, getComponentFn, options, preloadedComponents = []) {
|
|
1623
|
+
const componentMap = /* @__PURE__ */ new Map();
|
|
1624
|
+
for (const component of preloadedComponents) {
|
|
1625
|
+
componentMap.set(component.name.toLowerCase(), component);
|
|
1626
|
+
}
|
|
1627
|
+
const resolverGetComponent = async (name, type) => {
|
|
1628
|
+
const normalized = name.toLowerCase();
|
|
1629
|
+
const cached = componentMap.get(normalized);
|
|
1630
|
+
if (cached) {
|
|
1631
|
+
return cached;
|
|
1632
|
+
}
|
|
1633
|
+
const component = await getComponentFn(name, type);
|
|
1634
|
+
if (!component) {
|
|
1635
|
+
return null;
|
|
1636
|
+
}
|
|
1637
|
+
componentMap.set(component.name.toLowerCase(), component);
|
|
1638
|
+
return component;
|
|
1639
|
+
};
|
|
1640
|
+
const orderedComponents = await resolveRegistryTree(
|
|
1641
|
+
componentNames,
|
|
1642
|
+
registryType,
|
|
1643
|
+
(name, type) => resolverGetComponent(name, type)
|
|
1644
|
+
);
|
|
1645
|
+
if (options.dryRun && orderedComponents.length > 0) {
|
|
1646
|
+
logger.info("\n\u{1F4E6} Resolved registry dependency tree:");
|
|
1647
|
+
orderedComponents.forEach((component, index) => {
|
|
1648
|
+
console.log(` ${index + 1}. ${component.name}`);
|
|
1649
|
+
});
|
|
1650
|
+
}
|
|
1651
|
+
const orderedNames = new Set(orderedComponents.map((component) => component.name.toLowerCase()));
|
|
1652
|
+
const normalizedRequested = Array.from(new Set(componentNames.map((name) => name.toLowerCase())));
|
|
1653
|
+
const missingRequested = normalizedRequested.filter((name) => !orderedNames.has(name));
|
|
1654
|
+
const missingResults = missingRequested.map((name) => ({
|
|
1655
|
+
name,
|
|
1656
|
+
status: "error",
|
|
1657
|
+
error: `Component "${name}" was not found in ${registryType} registry`
|
|
1658
|
+
}));
|
|
1659
|
+
const processingResults = await processComponents(
|
|
1660
|
+
orderedComponents.map((component) => component.name),
|
|
1661
|
+
registryType,
|
|
1662
|
+
config,
|
|
1663
|
+
resolverGetComponent,
|
|
1664
|
+
options,
|
|
1665
|
+
orderedComponents,
|
|
1666
|
+
orderedComponents.length
|
|
1667
|
+
);
|
|
1668
|
+
return [...missingResults, ...processingResults];
|
|
1669
|
+
}
|
|
1670
|
+
function displayInstallationSummary(registryType, results) {
|
|
1671
|
+
const successful = results.filter((r) => r.status === "success");
|
|
1672
|
+
const failed = results.filter((r) => r.status === "error");
|
|
1673
|
+
logger.info("\n\u{1F4CA} Installation Summary:");
|
|
1674
|
+
console.log(` Registry: ${registryType}`);
|
|
1675
|
+
console.log(` \u2705 Successful: ${successful.length}`);
|
|
1676
|
+
console.log(` \u274C Failed: ${failed.length}`);
|
|
1677
|
+
if (successful.length > 0) {
|
|
1678
|
+
logger.success(`
|
|
1679
|
+
\u{1F389} ${CLI_MESSAGES.success.componentsInstalled}`);
|
|
1680
|
+
console.log("You can now import and use them in your project.");
|
|
1681
|
+
}
|
|
1682
|
+
if (failed.length > 0) {
|
|
1683
|
+
process.exit(1);
|
|
1684
|
+
}
|
|
1685
|
+
}
|
|
1686
|
+
async function installComponentFiles(component, config, force = false) {
|
|
1687
|
+
for (const file of component.files) {
|
|
1688
|
+
const fileName = path8.basename(file.path);
|
|
1689
|
+
const target = file.target || inferTargetFromType(component.type);
|
|
1690
|
+
const installDir = resolveInstallDir(target, config);
|
|
1691
|
+
const targetPath = path8.join(process.cwd(), installDir, fileName);
|
|
1692
|
+
if (!force && await fs7.pathExists(targetPath)) {
|
|
1693
|
+
console.log(` \u26A0\uFE0F ${CLI_MESSAGES.status.skipped(fileName)}`);
|
|
1694
|
+
continue;
|
|
1695
|
+
}
|
|
1696
|
+
await fs7.ensureDir(path8.dirname(targetPath));
|
|
1697
|
+
const preparedContent = shouldTransformFile(fileName) ? applyTransforms(file.content, config.aliases) : file.content;
|
|
1698
|
+
await fs7.writeFile(targetPath, preparedContent, "utf-8");
|
|
1699
|
+
}
|
|
1700
|
+
}
|
|
1701
|
+
function inferTargetFromType(componentType) {
|
|
1702
|
+
switch (componentType) {
|
|
1703
|
+
case "registry:ui":
|
|
1704
|
+
return "ui";
|
|
1705
|
+
case "registry:composite":
|
|
1706
|
+
return "components";
|
|
1707
|
+
case "registry:block":
|
|
1708
|
+
return "blocks";
|
|
1709
|
+
case "registry:component":
|
|
1710
|
+
return "components";
|
|
1711
|
+
case "registry:layout":
|
|
1712
|
+
return "layouts";
|
|
1713
|
+
case "registry:lib":
|
|
1714
|
+
return "lib";
|
|
1715
|
+
case "registry:variants":
|
|
1716
|
+
return "variants";
|
|
1717
|
+
default:
|
|
1718
|
+
return "components";
|
|
1719
|
+
}
|
|
1720
|
+
}
|
|
1721
|
+
function resolveInstallDir(target, config) {
|
|
1722
|
+
const normalizedTarget = target.replace(/\\/g, "/").replace(/^\/?src\//i, "");
|
|
1723
|
+
if (normalizedTarget === "lib") {
|
|
1724
|
+
return normalizeDir(config.libDir || SCHEMA_CONFIG.defaultDirectories.lib);
|
|
1725
|
+
}
|
|
1726
|
+
if (normalizedTarget === "variants") {
|
|
1727
|
+
return normalizeDir(SCHEMA_CONFIG.defaultDirectories.variants);
|
|
1157
1728
|
}
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1729
|
+
const baseComponentsDir = normalizeDir(config.componentsDir || SCHEMA_CONFIG.defaultDirectories.components);
|
|
1730
|
+
if (normalizedTarget.includes("/")) {
|
|
1731
|
+
const parentRoot = baseComponentsDir.replace(/[/\\]components$/i, "") || "src";
|
|
1732
|
+
return path8.join(parentRoot, normalizedTarget).replace(/\\/g, "/");
|
|
1733
|
+
}
|
|
1734
|
+
if (normalizedTarget === "ui")
|
|
1735
|
+
return path8.join(baseComponentsDir, "ui").replace(/\\/g, "/");
|
|
1736
|
+
if (normalizedTarget === "components")
|
|
1737
|
+
return baseComponentsDir;
|
|
1738
|
+
switch (normalizedTarget) {
|
|
1739
|
+
case "blocks":
|
|
1740
|
+
return normalizeDir(SCHEMA_CONFIG.defaultDirectories.blocks);
|
|
1741
|
+
case "layouts":
|
|
1742
|
+
return normalizeDir(SCHEMA_CONFIG.defaultDirectories.layouts);
|
|
1743
|
+
default:
|
|
1744
|
+
return baseComponentsDir;
|
|
1161
1745
|
}
|
|
1162
|
-
spinner.text = `Installing variants index...`;
|
|
1163
|
-
await installVariantsIndex(cdnUrls, config);
|
|
1164
|
-
spinner.text = `\u2705 Installed ${libItems.length} utilities and ${variantItems.length} variants`;
|
|
1165
1746
|
}
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
const fileName = path4.basename(file.path);
|
|
1176
|
-
let targetDir;
|
|
1177
|
-
if (type === "registry:lib") {
|
|
1178
|
-
targetDir = config.libDir;
|
|
1179
|
-
} else if (type === "registry:variants") {
|
|
1180
|
-
targetDir = SCHEMA_CONFIG.defaultDirectories.variants;
|
|
1181
|
-
} else {
|
|
1182
|
-
targetDir = path4.join(config.componentsDir, "ui");
|
|
1183
|
-
}
|
|
1184
|
-
const targetPath = path4.join(process.cwd(), targetDir, fileName);
|
|
1185
|
-
await fs5.ensureDir(path4.dirname(targetPath));
|
|
1186
|
-
await fs5.writeFile(targetPath, file.content || "", "utf-8");
|
|
1187
|
-
}
|
|
1188
|
-
return;
|
|
1189
|
-
}
|
|
1190
|
-
} catch {
|
|
1191
|
-
continue;
|
|
1192
|
-
}
|
|
1747
|
+
function normalizeDir(dir) {
|
|
1748
|
+
return dir.replace(/^\.\//, "").replace(/\\/g, "/");
|
|
1749
|
+
}
|
|
1750
|
+
function resolveRegistryType(registryInput) {
|
|
1751
|
+
if (!registryInput) {
|
|
1752
|
+
return SCHEMA_CONFIG.defaultRegistryType;
|
|
1753
|
+
}
|
|
1754
|
+
if (SCHEMA_CONFIG.registryTypes.includes(registryInput)) {
|
|
1755
|
+
return registryInput;
|
|
1193
1756
|
}
|
|
1757
|
+
logger.warn(`\u26A0\uFE0F Unknown registry type: ${registryInput}`);
|
|
1758
|
+
console.log(`Available registries: ${SCHEMA_CONFIG.registryTypes.join(", ")}`);
|
|
1759
|
+
console.log(`Using default: ${SCHEMA_CONFIG.defaultRegistryType}`);
|
|
1760
|
+
return SCHEMA_CONFIG.defaultRegistryType;
|
|
1194
1761
|
}
|
|
1195
|
-
async function
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
const
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
const
|
|
1205
|
-
const
|
|
1206
|
-
|
|
1207
|
-
|
|
1762
|
+
async function installComponentsIndex(registryType, config) {
|
|
1763
|
+
const spinner = ora4("Installing components index...").start();
|
|
1764
|
+
try {
|
|
1765
|
+
const cdnUrls = SCHEMA_CONFIG.cdnBaseUrls;
|
|
1766
|
+
for (const baseUrl of cdnUrls) {
|
|
1767
|
+
try {
|
|
1768
|
+
const url = `${baseUrl}/components/index.json`;
|
|
1769
|
+
const response = await fetch3(url);
|
|
1770
|
+
if (response.ok) {
|
|
1771
|
+
const component = await response.json();
|
|
1772
|
+
for (const file of component.files) {
|
|
1773
|
+
const fileName = path8.basename(file.path);
|
|
1774
|
+
const targetDir = config.componentsDir;
|
|
1775
|
+
const targetPath = path8.join(process.cwd(), targetDir, fileName);
|
|
1776
|
+
await fs7.ensureDir(path8.dirname(targetPath));
|
|
1777
|
+
await fs7.writeFile(targetPath, file.content || "", "utf-8");
|
|
1778
|
+
}
|
|
1779
|
+
spinner.succeed("Installed components index");
|
|
1780
|
+
return;
|
|
1208
1781
|
}
|
|
1209
|
-
|
|
1782
|
+
} catch {
|
|
1783
|
+
continue;
|
|
1210
1784
|
}
|
|
1211
|
-
} catch {
|
|
1212
|
-
continue;
|
|
1213
1785
|
}
|
|
1786
|
+
spinner.info("Components index not found in registry (optional)");
|
|
1787
|
+
} catch (error) {
|
|
1788
|
+
spinner.fail("Could not install components index");
|
|
1214
1789
|
}
|
|
1215
1790
|
}
|
|
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
1791
|
|
|
1228
1792
|
// src/commands/build.ts
|
|
1229
|
-
import
|
|
1230
|
-
import
|
|
1231
|
-
import
|
|
1232
|
-
import
|
|
1793
|
+
import fs8 from "fs-extra";
|
|
1794
|
+
import path9 from "path";
|
|
1795
|
+
import chalk7 from "chalk";
|
|
1796
|
+
import ora5 from "ora";
|
|
1233
1797
|
|
|
1234
1798
|
// src/registry/build-schema.ts
|
|
1235
1799
|
import { z as z2 } from "zod";
|
|
@@ -1323,6 +1887,11 @@ function generateConfigSchema() {
|
|
|
1323
1887
|
"default": true,
|
|
1324
1888
|
"description": SCHEMA_CONFIG.fieldDescriptions.typescript
|
|
1325
1889
|
},
|
|
1890
|
+
"globalCss": {
|
|
1891
|
+
"type": "string",
|
|
1892
|
+
"default": "src/index.css",
|
|
1893
|
+
"description": SCHEMA_CONFIG.fieldDescriptions.globalCss
|
|
1894
|
+
},
|
|
1326
1895
|
"aliases": {
|
|
1327
1896
|
"type": "object",
|
|
1328
1897
|
"additionalProperties": {
|
|
@@ -1450,44 +2019,44 @@ function generateRegistryItemSchema() {
|
|
|
1450
2019
|
// src/commands/build.ts
|
|
1451
2020
|
async function buildCommand(registryPath = "./src/registry.json", options = {}) {
|
|
1452
2021
|
const buildOptions = {
|
|
1453
|
-
cwd:
|
|
1454
|
-
registryFile:
|
|
1455
|
-
outputDir:
|
|
2022
|
+
cwd: path9.resolve(options.cwd || process.cwd()),
|
|
2023
|
+
registryFile: path9.resolve(registryPath),
|
|
2024
|
+
outputDir: path9.resolve(options.output || "./packages/registry/r")
|
|
1456
2025
|
};
|
|
1457
|
-
console.log(
|
|
2026
|
+
console.log(chalk7.blue(CLI_MESSAGES.info.building));
|
|
1458
2027
|
try {
|
|
1459
|
-
const registryContent = await
|
|
2028
|
+
const registryContent = await fs8.readFile(buildOptions.registryFile, "utf-8");
|
|
1460
2029
|
const registryData = JSON.parse(registryContent);
|
|
1461
2030
|
const registry = registrySchema.parse(registryData);
|
|
1462
|
-
await
|
|
2031
|
+
await ensureVariantsIndexItem(registry, buildOptions.cwd);
|
|
2032
|
+
await fs8.ensureDir(buildOptions.outputDir);
|
|
1463
2033
|
await generateSchemaFiles(buildOptions.outputDir);
|
|
1464
|
-
const spinner =
|
|
2034
|
+
const spinner = ora5(CLI_MESSAGES.info.processingComponents).start();
|
|
1465
2035
|
for (const item of registry.items) {
|
|
1466
2036
|
spinner.text = `Building ${item.name}...`;
|
|
1467
2037
|
item.$schema = "https://ui.buildy.tw/schema/registry-item.json";
|
|
1468
2038
|
for (const file of item.files) {
|
|
1469
|
-
const filePath =
|
|
1470
|
-
if (await
|
|
1471
|
-
file.content = await
|
|
2039
|
+
const filePath = path9.resolve(buildOptions.cwd, file.path);
|
|
2040
|
+
if (await fs8.pathExists(filePath)) {
|
|
2041
|
+
file.content = await fs8.readFile(filePath, "utf-8");
|
|
1472
2042
|
} else {
|
|
1473
2043
|
throw new Error(CLI_MESSAGES.errors.fileNotFound(file.path));
|
|
1474
2044
|
}
|
|
1475
2045
|
}
|
|
1476
2046
|
const validatedItem = registryItemSchema.parse(item);
|
|
1477
2047
|
const typeDir = getOutputDir(validatedItem.type);
|
|
1478
|
-
const outputPath =
|
|
1479
|
-
await
|
|
1480
|
-
const outputFile =
|
|
1481
|
-
await
|
|
2048
|
+
const outputPath = path9.join(buildOptions.outputDir, typeDir);
|
|
2049
|
+
await fs8.ensureDir(outputPath);
|
|
2050
|
+
const outputFile = path9.join(outputPath, `${validatedItem.name}.json`);
|
|
2051
|
+
await fs8.writeFile(outputFile, JSON.stringify(validatedItem, null, 2));
|
|
1482
2052
|
}
|
|
1483
2053
|
spinner.succeed(CLI_MESSAGES.status.builtComponents(registry.items.length));
|
|
1484
2054
|
await createIndexFile(registry, buildOptions.outputDir);
|
|
1485
|
-
console.log(
|
|
2055
|
+
console.log(chalk7.green(`\u2705 ${CLI_MESSAGES.success.registryBuilt}`));
|
|
1486
2056
|
console.log(`Output: ${buildOptions.outputDir}`);
|
|
1487
|
-
console.log(
|
|
2057
|
+
console.log(chalk7.green(`\u2705 ${CLI_MESSAGES.success.schemasGenerated}`));
|
|
1488
2058
|
} catch (error) {
|
|
1489
|
-
|
|
1490
|
-
process.exit(1);
|
|
2059
|
+
handleError(error);
|
|
1491
2060
|
}
|
|
1492
2061
|
}
|
|
1493
2062
|
var BUILD_OUTPUT_FOLDERS = {
|
|
@@ -1517,39 +2086,97 @@ async function createIndexFile(registry, outputDir) {
|
|
|
1517
2086
|
lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1518
2087
|
registry: registry?.registry || SCHEMA_CONFIG.defaultRegistryType
|
|
1519
2088
|
};
|
|
1520
|
-
await
|
|
1521
|
-
|
|
2089
|
+
await fs8.writeFile(
|
|
2090
|
+
path9.join(outputDir, "index.json"),
|
|
1522
2091
|
JSON.stringify(index, null, 2)
|
|
1523
2092
|
);
|
|
1524
2093
|
}
|
|
1525
2094
|
async function generateSchemaFiles(outputDir) {
|
|
1526
|
-
const registryBaseDir =
|
|
1527
|
-
const schemaDir =
|
|
1528
|
-
await
|
|
2095
|
+
const registryBaseDir = path9.dirname(outputDir);
|
|
2096
|
+
const schemaDir = path9.join(registryBaseDir, "schema");
|
|
2097
|
+
await fs8.ensureDir(schemaDir);
|
|
1529
2098
|
const configSchemaJson = generateConfigSchema();
|
|
1530
2099
|
const registrySchemaJson = generateRegistrySchema();
|
|
1531
2100
|
const registryItemSchemaJson = generateRegistryItemSchema();
|
|
1532
|
-
await
|
|
1533
|
-
|
|
2101
|
+
await fs8.writeFile(
|
|
2102
|
+
path9.join(registryBaseDir, "schema.json"),
|
|
1534
2103
|
JSON.stringify(configSchemaJson, null, 2)
|
|
1535
2104
|
);
|
|
1536
|
-
await
|
|
1537
|
-
|
|
2105
|
+
await fs8.writeFile(
|
|
2106
|
+
path9.join(schemaDir, "registry.json"),
|
|
1538
2107
|
JSON.stringify(registrySchemaJson, null, 2)
|
|
1539
2108
|
);
|
|
1540
|
-
await
|
|
1541
|
-
|
|
2109
|
+
await fs8.writeFile(
|
|
2110
|
+
path9.join(schemaDir, "registry-item.json"),
|
|
1542
2111
|
JSON.stringify(registryItemSchemaJson, null, 2)
|
|
1543
2112
|
);
|
|
1544
2113
|
}
|
|
2114
|
+
async function ensureVariantsIndexItem(registry, cwd) {
|
|
2115
|
+
const indexSourcePath = path9.join(cwd, "src/variants/index.ts");
|
|
2116
|
+
if (!await fs8.pathExists(indexSourcePath)) {
|
|
2117
|
+
return;
|
|
2118
|
+
}
|
|
2119
|
+
const sourceContent = await fs8.readFile(indexSourcePath, "utf-8");
|
|
2120
|
+
const dependencies = extractFileDependencies(sourceContent);
|
|
2121
|
+
const exportedModules = extractExportedModules(sourceContent);
|
|
2122
|
+
const indexItem = {
|
|
2123
|
+
name: "index",
|
|
2124
|
+
type: "registry:variants",
|
|
2125
|
+
description: exportedModules.length > 0 ? `Variant exports: ${exportedModules.join(", ")}` : "Variants export index",
|
|
2126
|
+
dependencies,
|
|
2127
|
+
devDependencies: [],
|
|
2128
|
+
files: [
|
|
2129
|
+
{
|
|
2130
|
+
path: path9.relative(cwd, indexSourcePath).replace(/\\/g, "/")
|
|
2131
|
+
}
|
|
2132
|
+
]
|
|
2133
|
+
};
|
|
2134
|
+
const items = Array.isArray(registry.items) ? registry.items : [];
|
|
2135
|
+
const existingIndexIdx = items.findIndex(
|
|
2136
|
+
(item) => item && item.name === "index"
|
|
2137
|
+
);
|
|
2138
|
+
if (existingIndexIdx >= 0) {
|
|
2139
|
+
items[existingIndexIdx] = {
|
|
2140
|
+
...items[existingIndexIdx],
|
|
2141
|
+
...indexItem
|
|
2142
|
+
};
|
|
2143
|
+
} else {
|
|
2144
|
+
items.push(indexItem);
|
|
2145
|
+
}
|
|
2146
|
+
}
|
|
2147
|
+
function extractExportedModules(content) {
|
|
2148
|
+
const exports = /* @__PURE__ */ new Set();
|
|
2149
|
+
const starExportRegex = /export\s+\*\s+from\s+['"]\.\/([^'"]+)['"]/g;
|
|
2150
|
+
const namedExportRegex = /export\s+\{[^}]+\}\s+from\s+['"]\.\/([^'"]+)['"]/g;
|
|
2151
|
+
let match;
|
|
2152
|
+
while ((match = starExportRegex.exec(content)) !== null) {
|
|
2153
|
+
exports.add(match[1]);
|
|
2154
|
+
}
|
|
2155
|
+
while ((match = namedExportRegex.exec(content)) !== null) {
|
|
2156
|
+
exports.add(match[1]);
|
|
2157
|
+
}
|
|
2158
|
+
return [...exports];
|
|
2159
|
+
}
|
|
2160
|
+
function extractFileDependencies(content) {
|
|
2161
|
+
const dependencies = /* @__PURE__ */ new Set();
|
|
2162
|
+
const importRegex = /import\s+[^;\n]+?from\s+['"]([^'"]+)['"]/g;
|
|
2163
|
+
let match;
|
|
2164
|
+
while ((match = importRegex.exec(content)) !== null) {
|
|
2165
|
+
const moduleName = match[1];
|
|
2166
|
+
if (isExternalDependency(moduleName)) {
|
|
2167
|
+
dependencies.add(moduleName);
|
|
2168
|
+
}
|
|
2169
|
+
}
|
|
2170
|
+
return [...dependencies];
|
|
2171
|
+
}
|
|
1545
2172
|
|
|
1546
2173
|
// src/commands/scan.ts
|
|
1547
|
-
import
|
|
1548
|
-
import
|
|
1549
|
-
import
|
|
1550
|
-
import
|
|
2174
|
+
import fs9 from "fs-extra";
|
|
2175
|
+
import path10 from "path";
|
|
2176
|
+
import chalk8 from "chalk";
|
|
2177
|
+
import ora6 from "ora";
|
|
1551
2178
|
import { glob } from "glob";
|
|
1552
|
-
import * as
|
|
2179
|
+
import * as ts2 from "typescript";
|
|
1553
2180
|
var DEV_PATTERNS = [
|
|
1554
2181
|
"@types/",
|
|
1555
2182
|
"eslint",
|
|
@@ -1574,28 +2201,28 @@ async function scanCommand(options = {}) {
|
|
|
1574
2201
|
const registryName = options.registry || SCHEMA_CONFIG.defaultRegistryType;
|
|
1575
2202
|
const registryPath = `./${registryName}`;
|
|
1576
2203
|
const scanOptions = {
|
|
1577
|
-
cwd:
|
|
1578
|
-
registry:
|
|
1579
|
-
outputFile:
|
|
1580
|
-
sourceDir:
|
|
2204
|
+
cwd: path10.resolve(options.cwd || process.cwd()),
|
|
2205
|
+
registry: path10.resolve(registryPath),
|
|
2206
|
+
outputFile: path10.resolve(options.output || "./src/registry.json"),
|
|
2207
|
+
sourceDir: path10.resolve(options.source || "./src")
|
|
1581
2208
|
};
|
|
1582
|
-
console.log(
|
|
2209
|
+
console.log(chalk8.blue(`\u{1F50D} ${CLI_MESSAGES.info.scanningComponents(registryName)}`));
|
|
1583
2210
|
try {
|
|
1584
|
-
const spinner =
|
|
1585
|
-
const componentsDir =
|
|
1586
|
-
const uiDir =
|
|
1587
|
-
const blocksDir =
|
|
1588
|
-
const layoutsDir =
|
|
1589
|
-
const libDir =
|
|
1590
|
-
const variantsDir =
|
|
2211
|
+
const spinner = ora6(CLI_MESSAGES.info.scanningDirectories).start();
|
|
2212
|
+
const componentsDir = path10.resolve(scanOptions.cwd, normalizeDir2(SCHEMA_CONFIG.defaultDirectories.components));
|
|
2213
|
+
const uiDir = path10.join(componentsDir, "ui");
|
|
2214
|
+
const blocksDir = path10.resolve(scanOptions.cwd, normalizeDir2(SCHEMA_CONFIG.defaultDirectories.blocks));
|
|
2215
|
+
const layoutsDir = path10.resolve(scanOptions.cwd, normalizeDir2(SCHEMA_CONFIG.defaultDirectories.layouts));
|
|
2216
|
+
const libDir = path10.resolve(scanOptions.cwd, normalizeDir2(SCHEMA_CONFIG.defaultDirectories.lib));
|
|
2217
|
+
const variantsDir = path10.resolve(scanOptions.cwd, normalizeDir2(SCHEMA_CONFIG.defaultDirectories.variants));
|
|
1591
2218
|
const uiComponents = await scanDirectory(uiDir, "registry:ui");
|
|
1592
2219
|
const compositeComponents = await scanDirectoryFlat(componentsDir, "registry:composite", ["index.ts"]);
|
|
1593
2220
|
const variantComponents = await scanDirectory(variantsDir, "registry:variants", ["index.ts"]);
|
|
1594
2221
|
const blockComponents = await scanDirectory(blocksDir, "registry:block");
|
|
1595
2222
|
const layoutComponents = await scanDirectory(layoutsDir, "registry:layout");
|
|
1596
2223
|
const libComponents = await scanDirectory(libDir, "registry:lib");
|
|
1597
|
-
const variantsIndexItem = await scanSingleFile(
|
|
1598
|
-
const componentsIndexItem = await scanSingleFile(
|
|
2224
|
+
const variantsIndexItem = await scanSingleFile(path10.join(variantsDir, "index.ts"), "registry:variants");
|
|
2225
|
+
const componentsIndexItem = await scanSingleFile(path10.join(componentsDir, "index.ts"), "registry:composite");
|
|
1599
2226
|
const allComponentsRaw = [
|
|
1600
2227
|
...uiComponents,
|
|
1601
2228
|
...compositeComponents,
|
|
@@ -1631,16 +2258,16 @@ async function scanCommand(options = {}) {
|
|
|
1631
2258
|
lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1632
2259
|
registry: registryName
|
|
1633
2260
|
};
|
|
1634
|
-
await
|
|
1635
|
-
await
|
|
2261
|
+
await fs9.ensureDir(path10.dirname(scanOptions.outputFile));
|
|
2262
|
+
await fs9.writeFile(scanOptions.outputFile, JSON.stringify(registry, null, 2));
|
|
1636
2263
|
spinner.succeed(CLI_MESSAGES.status.scannedComponents(allComponents.length));
|
|
1637
|
-
console.log(
|
|
2264
|
+
console.log(chalk8.green(`\u2705 ${CLI_MESSAGES.success.registryGenerated(registryName)}`));
|
|
1638
2265
|
console.log(`Output: ${scanOptions.outputFile}`);
|
|
1639
2266
|
const summary = allComponents.reduce((acc, comp) => {
|
|
1640
2267
|
acc[comp.type] = (acc[comp.type] || 0) + 1;
|
|
1641
2268
|
return acc;
|
|
1642
2269
|
}, {});
|
|
1643
|
-
console.log(
|
|
2270
|
+
console.log(chalk8.blue("\n\u{1F4CA} Component Summary:"));
|
|
1644
2271
|
Object.entries(summary).forEach(([type, count]) => {
|
|
1645
2272
|
console.log(` ${type}: ${count}`);
|
|
1646
2273
|
});
|
|
@@ -1650,30 +2277,29 @@ async function scanCommand(options = {}) {
|
|
|
1650
2277
|
comp.dependencies.forEach((dep) => allDeps.add(dep));
|
|
1651
2278
|
comp.devDependencies.forEach((dep) => allDevDeps.add(dep));
|
|
1652
2279
|
});
|
|
1653
|
-
console.log(
|
|
2280
|
+
console.log(chalk8.blue("\n\u{1F4E6} Dependencies Summary:"));
|
|
1654
2281
|
console.log(` Dependencies: ${allDeps.size} unique (${Array.from(allDeps).join(", ") || "none"})`);
|
|
1655
2282
|
console.log(` DevDependencies: ${allDevDeps.size} unique (${Array.from(allDevDeps).join(", ") || "none"})`);
|
|
1656
2283
|
} catch (error) {
|
|
1657
|
-
|
|
1658
|
-
process.exit(1);
|
|
2284
|
+
handleError(error);
|
|
1659
2285
|
}
|
|
1660
2286
|
}
|
|
1661
2287
|
async function scanDirectory(dirPath, type, ignorePatterns = []) {
|
|
1662
|
-
if (!await
|
|
2288
|
+
if (!await fs9.pathExists(dirPath)) {
|
|
1663
2289
|
return [];
|
|
1664
2290
|
}
|
|
1665
2291
|
const components = [];
|
|
1666
|
-
const pattern =
|
|
2292
|
+
const pattern = path10.join(dirPath, "**/*.{ts,tsx,js,jsx}").replace(/\\/g, "/");
|
|
1667
2293
|
const ignore = ignorePatterns.map((p) => p.replace(/\\/g, "/"));
|
|
1668
2294
|
const files = await glob(pattern, { windowsPathsNoEscape: true, ignore });
|
|
1669
2295
|
for (const filePath of files) {
|
|
1670
|
-
const relativePath =
|
|
1671
|
-
const fileName =
|
|
2296
|
+
const relativePath = path10.relative(process.cwd(), filePath).replace(/\\/g, "/");
|
|
2297
|
+
const fileName = path10.basename(filePath, path10.extname(filePath));
|
|
1672
2298
|
if (fileName === "index" || fileName.startsWith("_")) {
|
|
1673
2299
|
continue;
|
|
1674
2300
|
}
|
|
1675
2301
|
try {
|
|
1676
|
-
const content = await
|
|
2302
|
+
const content = await fs9.readFile(filePath, "utf-8");
|
|
1677
2303
|
const description = extractDescription(content);
|
|
1678
2304
|
if (!hasValidExports(content)) {
|
|
1679
2305
|
continue;
|
|
@@ -1696,20 +2322,20 @@ async function scanDirectory(dirPath, type, ignorePatterns = []) {
|
|
|
1696
2322
|
return components;
|
|
1697
2323
|
}
|
|
1698
2324
|
async function scanDirectoryFlat(dirPath, type, ignoreFiles = []) {
|
|
1699
|
-
if (!await
|
|
2325
|
+
if (!await fs9.pathExists(dirPath)) {
|
|
1700
2326
|
return [];
|
|
1701
2327
|
}
|
|
1702
2328
|
const components = [];
|
|
1703
|
-
const pattern =
|
|
2329
|
+
const pattern = path10.join(dirPath, "*.{ts,tsx,js,jsx}").replace(/\\/g, "/");
|
|
1704
2330
|
const files = await glob(pattern, { windowsPathsNoEscape: true });
|
|
1705
2331
|
for (const filePath of files) {
|
|
1706
|
-
const relativePath =
|
|
1707
|
-
const fileName =
|
|
1708
|
-
if (ignoreFiles.includes(fileName +
|
|
2332
|
+
const relativePath = path10.relative(process.cwd(), filePath).replace(/\\/g, "/");
|
|
2333
|
+
const fileName = path10.basename(filePath, path10.extname(filePath));
|
|
2334
|
+
if (ignoreFiles.includes(fileName + path10.extname(filePath)) || fileName.startsWith("_")) {
|
|
1709
2335
|
continue;
|
|
1710
2336
|
}
|
|
1711
2337
|
try {
|
|
1712
|
-
const content = await
|
|
2338
|
+
const content = await fs9.readFile(filePath, "utf-8");
|
|
1713
2339
|
const description = extractDescription(content);
|
|
1714
2340
|
if (!hasValidExports(content)) {
|
|
1715
2341
|
continue;
|
|
@@ -1732,17 +2358,17 @@ async function scanDirectoryFlat(dirPath, type, ignoreFiles = []) {
|
|
|
1732
2358
|
return components;
|
|
1733
2359
|
}
|
|
1734
2360
|
async function scanSingleFile(filePath, type) {
|
|
1735
|
-
if (!await
|
|
2361
|
+
if (!await fs9.pathExists(filePath)) {
|
|
1736
2362
|
return null;
|
|
1737
2363
|
}
|
|
1738
2364
|
try {
|
|
1739
|
-
const content = await
|
|
2365
|
+
const content = await fs9.readFile(filePath, "utf-8");
|
|
1740
2366
|
const description = extractDescription(content);
|
|
1741
2367
|
if (!hasValidExports(content)) {
|
|
1742
2368
|
return null;
|
|
1743
2369
|
}
|
|
1744
|
-
const relativePath =
|
|
1745
|
-
const fileName =
|
|
2370
|
+
const relativePath = path10.relative(process.cwd(), filePath).replace(/\\/g, "/");
|
|
2371
|
+
const fileName = path10.basename(filePath, path10.extname(filePath));
|
|
1746
2372
|
return {
|
|
1747
2373
|
name: fileName,
|
|
1748
2374
|
type,
|
|
@@ -1779,12 +2405,12 @@ async function analyzeComponentDependencies(files, cwd) {
|
|
|
1779
2405
|
let description;
|
|
1780
2406
|
for (const file of files) {
|
|
1781
2407
|
try {
|
|
1782
|
-
const filePath =
|
|
1783
|
-
const content = await
|
|
1784
|
-
const sourceFile =
|
|
2408
|
+
const filePath = path10.resolve(cwd, file.path);
|
|
2409
|
+
const content = await fs9.readFile(filePath, "utf-8");
|
|
2410
|
+
const sourceFile = ts2.createSourceFile(
|
|
1785
2411
|
file.path,
|
|
1786
2412
|
content,
|
|
1787
|
-
|
|
2413
|
+
ts2.ScriptTarget.Latest,
|
|
1788
2414
|
true
|
|
1789
2415
|
);
|
|
1790
2416
|
const analysis = analyzeAST(sourceFile);
|
|
@@ -1809,9 +2435,9 @@ function analyzeAST(sourceFile) {
|
|
|
1809
2435
|
let description;
|
|
1810
2436
|
let hasExports = false;
|
|
1811
2437
|
function visit(node) {
|
|
1812
|
-
if (
|
|
2438
|
+
if (ts2.isImportDeclaration(node)) {
|
|
1813
2439
|
const moduleSpecifier = node.moduleSpecifier;
|
|
1814
|
-
if (
|
|
2440
|
+
if (ts2.isStringLiteral(moduleSpecifier)) {
|
|
1815
2441
|
const moduleName = moduleSpecifier.text;
|
|
1816
2442
|
if (isExternalDependency(moduleName)) {
|
|
1817
2443
|
if (isDevDependency(moduleName)) {
|
|
@@ -1822,9 +2448,9 @@ function analyzeAST(sourceFile) {
|
|
|
1822
2448
|
}
|
|
1823
2449
|
}
|
|
1824
2450
|
}
|
|
1825
|
-
if (
|
|
2451
|
+
if (ts2.isExportDeclaration(node)) {
|
|
1826
2452
|
hasExports = true;
|
|
1827
|
-
} else if (
|
|
2453
|
+
} else if (ts2.isExportAssignment(node)) {
|
|
1828
2454
|
hasExports = true;
|
|
1829
2455
|
} else if (hasExportModifier(node)) {
|
|
1830
2456
|
hasExports = true;
|
|
@@ -1833,7 +2459,7 @@ function analyzeAST(sourceFile) {
|
|
|
1833
2459
|
if (jsDocComment && !description) {
|
|
1834
2460
|
description = jsDocComment;
|
|
1835
2461
|
}
|
|
1836
|
-
|
|
2462
|
+
ts2.forEachChild(node, visit);
|
|
1837
2463
|
}
|
|
1838
2464
|
visit(sourceFile);
|
|
1839
2465
|
return {
|
|
@@ -1849,16 +2475,16 @@ function isDevDependency(moduleName) {
|
|
|
1849
2475
|
function hasExportModifier(node) {
|
|
1850
2476
|
if ("modifiers" in node && node.modifiers) {
|
|
1851
2477
|
return node.modifiers.some(
|
|
1852
|
-
(mod) => mod.kind ===
|
|
2478
|
+
(mod) => mod.kind === ts2.SyntaxKind.ExportKeyword
|
|
1853
2479
|
);
|
|
1854
2480
|
}
|
|
1855
2481
|
return false;
|
|
1856
2482
|
}
|
|
1857
2483
|
function getJSDocComment(node) {
|
|
1858
2484
|
try {
|
|
1859
|
-
const jsDocTags =
|
|
2485
|
+
const jsDocTags = ts2.getJSDocCommentsAndTags(node);
|
|
1860
2486
|
for (const tag of jsDocTags) {
|
|
1861
|
-
if (
|
|
2487
|
+
if (ts2.isJSDoc(tag) && tag.comment) {
|
|
1862
2488
|
if (typeof tag.comment === "string") {
|
|
1863
2489
|
return tag.comment.trim();
|
|
1864
2490
|
} else if (Array.isArray(tag.comment)) {
|
|
@@ -1878,19 +2504,427 @@ function normalizeDir2(dir) {
|
|
|
1878
2504
|
return dir.replace(/^\.\//, "").replace(/\\/g, "/");
|
|
1879
2505
|
}
|
|
1880
2506
|
|
|
2507
|
+
// src/commands/list.ts
|
|
2508
|
+
import chalk9 from "chalk";
|
|
2509
|
+
var LIST_EXCLUDED_COMPONENT_TYPES = ["registry:variants", "registry:lib"];
|
|
2510
|
+
async function listCommand(options = {}) {
|
|
2511
|
+
const registryType = resolveRegistryType2(options.registry);
|
|
2512
|
+
const requestOptions = {
|
|
2513
|
+
excludeTypes: LIST_EXCLUDED_COMPONENT_TYPES,
|
|
2514
|
+
noCache: options.cache === false
|
|
2515
|
+
};
|
|
2516
|
+
try {
|
|
2517
|
+
const components = await getAllComponents(registryType, requestOptions);
|
|
2518
|
+
if (options.json) {
|
|
2519
|
+
console.log(JSON.stringify(components, null, 2));
|
|
2520
|
+
return;
|
|
2521
|
+
}
|
|
2522
|
+
const byType = /* @__PURE__ */ new Map();
|
|
2523
|
+
for (const component of components) {
|
|
2524
|
+
const group = byType.get(component.type) ?? [];
|
|
2525
|
+
group.push(component);
|
|
2526
|
+
byType.set(component.type, group);
|
|
2527
|
+
}
|
|
2528
|
+
const sortedGroups = Array.from(byType.entries()).sort((a, b) => a[0].localeCompare(b[0]));
|
|
2529
|
+
if (sortedGroups.length === 0) {
|
|
2530
|
+
logger.warn(CLI_MESSAGES.errors.registryTempUnavailable);
|
|
2531
|
+
return;
|
|
2532
|
+
}
|
|
2533
|
+
logger.info(CLI_MESSAGES.info.listingComponents);
|
|
2534
|
+
for (const [type, group] of sortedGroups) {
|
|
2535
|
+
const entries = group.sort((a, b) => a.name.localeCompare(b.name));
|
|
2536
|
+
console.log(chalk9.cyan(`${type} (${entries.length} components)`));
|
|
2537
|
+
for (const component of entries) {
|
|
2538
|
+
const description = component.description ? chalk9.dim(component.description) : "";
|
|
2539
|
+
console.log(chalk9.white(` ${component.name.padEnd(14)}`) + description);
|
|
2540
|
+
}
|
|
2541
|
+
console.log("");
|
|
2542
|
+
}
|
|
2543
|
+
} catch (error) {
|
|
2544
|
+
logger.error(error.message);
|
|
2545
|
+
}
|
|
2546
|
+
}
|
|
2547
|
+
function resolveRegistryType2(registryInput) {
|
|
2548
|
+
if (!registryInput) {
|
|
2549
|
+
return SCHEMA_CONFIG.defaultRegistryType;
|
|
2550
|
+
}
|
|
2551
|
+
if (SCHEMA_CONFIG.registryTypes.includes(registryInput)) {
|
|
2552
|
+
return registryInput;
|
|
2553
|
+
}
|
|
2554
|
+
logger.warn(`\u26A0\uFE0F Unknown registry type: ${registryInput}`);
|
|
2555
|
+
logger.warn(`Available registries: ${SCHEMA_CONFIG.registryTypes.join(", ")}`);
|
|
2556
|
+
return SCHEMA_CONFIG.defaultRegistryType;
|
|
2557
|
+
}
|
|
2558
|
+
|
|
2559
|
+
// src/commands/diff.ts
|
|
2560
|
+
import fs10 from "fs-extra";
|
|
2561
|
+
import path11 from "path";
|
|
2562
|
+
import { glob as glob2 } from "glob";
|
|
2563
|
+
import chalk10 from "chalk";
|
|
2564
|
+
async function diffCommand(componentName, options = {}) {
|
|
2565
|
+
try {
|
|
2566
|
+
const registryType = resolveRegistryType3(options.registry);
|
|
2567
|
+
const config = await findConfig(registryType);
|
|
2568
|
+
const defaultConfig = config ?? {
|
|
2569
|
+
framework: SCHEMA_CONFIG.supportedFrameworks[0],
|
|
2570
|
+
typescript: true,
|
|
2571
|
+
globalCss: "src/index.css",
|
|
2572
|
+
aliases: SCHEMA_CONFIG.defaultAliases,
|
|
2573
|
+
registry: SCHEMA_CONFIG.defaultRegistry,
|
|
2574
|
+
componentsDir: SCHEMA_CONFIG.defaultDirectories.components,
|
|
2575
|
+
libDir: SCHEMA_CONFIG.defaultDirectories.lib
|
|
2576
|
+
};
|
|
2577
|
+
const installed = await scanLocalComponents(defaultConfig);
|
|
2578
|
+
if (installed.length === 0) {
|
|
2579
|
+
logger.warn(CLI_MESSAGES.errors.noLocalInstall);
|
|
2580
|
+
return;
|
|
2581
|
+
}
|
|
2582
|
+
const registryComponents = await getAllComponents(registryType, { noCache: options.cache === false });
|
|
2583
|
+
const registryIndex = new Map(registryComponents.map((item) => [item.name.toLowerCase(), item]));
|
|
2584
|
+
const targets = componentName ? installed.filter((item) => item.name.toLowerCase() === componentName.toLowerCase()) : installed;
|
|
2585
|
+
if (componentName && targets.length === 0) {
|
|
2586
|
+
logger.warn(`Component "${componentName}" not found in local project structure`);
|
|
2587
|
+
return;
|
|
2588
|
+
}
|
|
2589
|
+
const results = [];
|
|
2590
|
+
logger.info(CLI_MESSAGES.info.checkingForUpdates);
|
|
2591
|
+
for (const item of targets) {
|
|
2592
|
+
const remoteComponent = registryIndex.get(item.name.toLowerCase());
|
|
2593
|
+
if (!remoteComponent) {
|
|
2594
|
+
results.push({
|
|
2595
|
+
component: item.name,
|
|
2596
|
+
type: "unknown",
|
|
2597
|
+
status: "missing-remote",
|
|
2598
|
+
files: [{ path: item.filePath, changed: false }]
|
|
2599
|
+
});
|
|
2600
|
+
continue;
|
|
2601
|
+
}
|
|
2602
|
+
const fileSummary = await compareComponentFiles(item, remoteComponent, defaultConfig);
|
|
2603
|
+
const hasChanges = fileSummary.some((file) => file.changed);
|
|
2604
|
+
results.push({
|
|
2605
|
+
component: item.name,
|
|
2606
|
+
type: remoteComponent.type,
|
|
2607
|
+
status: hasChanges ? "update" : "up-to-date",
|
|
2608
|
+
files: fileSummary
|
|
2609
|
+
});
|
|
2610
|
+
}
|
|
2611
|
+
if (options.json) {
|
|
2612
|
+
console.log(JSON.stringify(results, null, 2));
|
|
2613
|
+
return;
|
|
2614
|
+
}
|
|
2615
|
+
const updates = results.filter((item) => item.status === "update").length;
|
|
2616
|
+
const upToDate = results.filter((item) => item.status === "up-to-date").length;
|
|
2617
|
+
for (const result of results) {
|
|
2618
|
+
if (result.status === "missing-remote") {
|
|
2619
|
+
logger.warn(`
|
|
2620
|
+
\u26A0\uFE0F ${result.component}: not found in registry`);
|
|
2621
|
+
continue;
|
|
2622
|
+
}
|
|
2623
|
+
const statusTitle = result.status === "update" ? `${chalk10.yellow("UPDATE")}` : chalk10.green("UP-TO-DATE");
|
|
2624
|
+
const title = `${statusTitle} ${result.component} (${result.type})`;
|
|
2625
|
+
logger.info(title);
|
|
2626
|
+
for (const file of result.files) {
|
|
2627
|
+
console.log(` ${chalk10.white(file.path)}`);
|
|
2628
|
+
if (file.changed && file.diff) {
|
|
2629
|
+
const preview = formatDiffPreview(file.diff, 120);
|
|
2630
|
+
console.log(colorDiff(preview));
|
|
2631
|
+
} else {
|
|
2632
|
+
console.log(chalk10.dim(" No changes"));
|
|
2633
|
+
}
|
|
2634
|
+
}
|
|
2635
|
+
}
|
|
2636
|
+
console.log(
|
|
2637
|
+
`
|
|
2638
|
+
${CLI_MESSAGES.info.localDiffSummary} ${chalk10.yellow(updates)} component(s) have updates, ${chalk10.green(upToDate)} up to date`
|
|
2639
|
+
);
|
|
2640
|
+
if (updates > 0) {
|
|
2641
|
+
console.log('Run "ui8kit add <component> --force" to update.');
|
|
2642
|
+
}
|
|
2643
|
+
} catch (error) {
|
|
2644
|
+
handleError(error);
|
|
2645
|
+
}
|
|
2646
|
+
}
|
|
2647
|
+
async function compareComponentFiles(installed, remote, config) {
|
|
2648
|
+
const localContent = await fs10.readFile(installed.filePath, "utf-8");
|
|
2649
|
+
const remoteCandidate = remote.files.find((file) => {
|
|
2650
|
+
const candidateName = path11.basename(file.path);
|
|
2651
|
+
return candidateName.toLowerCase() === path11.basename(installed.filePath).toLowerCase();
|
|
2652
|
+
});
|
|
2653
|
+
if (!remoteCandidate) {
|
|
2654
|
+
return [{ path: installed.filePath, changed: false }];
|
|
2655
|
+
}
|
|
2656
|
+
const remoteContent = applyTransforms(remoteCandidate.content, config.aliases);
|
|
2657
|
+
const changed = hasDiff(localContent, remoteContent);
|
|
2658
|
+
return changed ? [{
|
|
2659
|
+
path: installed.filePath,
|
|
2660
|
+
changed: true,
|
|
2661
|
+
diff: buildUnifiedDiff(installed.filePath, `${remote.name}/${path11.basename(installed.filePath)}`, localContent, remoteContent)
|
|
2662
|
+
}] : [{ path: installed.filePath, changed: false }];
|
|
2663
|
+
}
|
|
2664
|
+
async function scanLocalComponents(config) {
|
|
2665
|
+
const componentsDir = path11.resolve(process.cwd(), config.componentsDir || SCHEMA_CONFIG.defaultDirectories.components);
|
|
2666
|
+
const componentsUiDir = path11.join(componentsDir, "ui");
|
|
2667
|
+
const blocksDir = path11.resolve(process.cwd(), SCHEMA_CONFIG.defaultDirectories.blocks);
|
|
2668
|
+
const layoutsDir = path11.resolve(process.cwd(), SCHEMA_CONFIG.defaultDirectories.layouts);
|
|
2669
|
+
const directories = [componentsUiDir, componentsDir, blocksDir, layoutsDir];
|
|
2670
|
+
const entries = [];
|
|
2671
|
+
const patterns = directories.map((dir) => path11.join(dir, "*.{ts,tsx}").replace(/\\/g, "/"));
|
|
2672
|
+
for (const pattern of patterns) {
|
|
2673
|
+
const baseDir = path11.dirname(pattern);
|
|
2674
|
+
if (!await fs10.pathExists(baseDir)) {
|
|
2675
|
+
continue;
|
|
2676
|
+
}
|
|
2677
|
+
const filePaths = await glob2(pattern, { windowsPathsNoEscape: true });
|
|
2678
|
+
for (const filePath of filePaths) {
|
|
2679
|
+
const fileName = path11.basename(filePath);
|
|
2680
|
+
if (fileName === "index.tsx" || fileName === "index.ts") {
|
|
2681
|
+
continue;
|
|
2682
|
+
}
|
|
2683
|
+
entries.push({
|
|
2684
|
+
name: path11.parse(fileName).name.toLowerCase(),
|
|
2685
|
+
filePath: path11.resolve(process.cwd(), filePath)
|
|
2686
|
+
});
|
|
2687
|
+
}
|
|
2688
|
+
}
|
|
2689
|
+
const uniqueByName = /* @__PURE__ */ new Map();
|
|
2690
|
+
for (const entry of entries) {
|
|
2691
|
+
if (!uniqueByName.has(entry.name)) {
|
|
2692
|
+
uniqueByName.set(entry.name, entry);
|
|
2693
|
+
}
|
|
2694
|
+
}
|
|
2695
|
+
return Array.from(uniqueByName.values());
|
|
2696
|
+
}
|
|
2697
|
+
function colorDiff(value) {
|
|
2698
|
+
return value.split("\n").map((line) => {
|
|
2699
|
+
if (line.startsWith("+")) {
|
|
2700
|
+
return chalk10.green(line);
|
|
2701
|
+
}
|
|
2702
|
+
if (line.startsWith("-")) {
|
|
2703
|
+
return chalk10.red(line);
|
|
2704
|
+
}
|
|
2705
|
+
return line;
|
|
2706
|
+
}).join("\n");
|
|
2707
|
+
}
|
|
2708
|
+
function resolveRegistryType3(registryInput) {
|
|
2709
|
+
if (!registryInput) {
|
|
2710
|
+
return SCHEMA_CONFIG.defaultRegistryType;
|
|
2711
|
+
}
|
|
2712
|
+
if (SCHEMA_CONFIG.registryTypes.includes(registryInput)) {
|
|
2713
|
+
return registryInput;
|
|
2714
|
+
}
|
|
2715
|
+
logger.warn(`\u26A0\uFE0F Unknown registry type: ${registryInput}`);
|
|
2716
|
+
logger.warn(`Available registries: ${SCHEMA_CONFIG.registryTypes.join(", ")}`);
|
|
2717
|
+
return SCHEMA_CONFIG.defaultRegistryType;
|
|
2718
|
+
}
|
|
2719
|
+
|
|
2720
|
+
// src/commands/cache.ts
|
|
2721
|
+
async function cacheClearCommand() {
|
|
2722
|
+
try {
|
|
2723
|
+
await clearCache();
|
|
2724
|
+
logger.success(`${CLI_MESSAGES.success.cacheCleared} (${getCacheDir()})`);
|
|
2725
|
+
} catch (error) {
|
|
2726
|
+
handleError(error);
|
|
2727
|
+
}
|
|
2728
|
+
}
|
|
2729
|
+
|
|
2730
|
+
// src/commands/info.ts
|
|
2731
|
+
import fs11 from "fs-extra";
|
|
2732
|
+
import os2 from "os";
|
|
2733
|
+
import path12 from "path";
|
|
2734
|
+
import fetch4 from "node-fetch";
|
|
2735
|
+
import chalk11 from "chalk";
|
|
2736
|
+
|
|
2737
|
+
// src/utils/cli-version.ts
|
|
2738
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2739
|
+
import { dirname, resolve } from "node:path";
|
|
2740
|
+
import { fileURLToPath } from "node:url";
|
|
2741
|
+
var __dirname = dirname(fileURLToPath(import.meta.url));
|
|
2742
|
+
function findPackageJsonPath() {
|
|
2743
|
+
const roots = [process.argv[1], __dirname];
|
|
2744
|
+
for (const rawRoot of roots) {
|
|
2745
|
+
if (!rawRoot) {
|
|
2746
|
+
continue;
|
|
2747
|
+
}
|
|
2748
|
+
let current = rawRoot.endsWith(".js") ? dirname(rawRoot) : rawRoot;
|
|
2749
|
+
for (let i = 0; i < 8; i += 1) {
|
|
2750
|
+
const candidate = resolve(current, "package.json");
|
|
2751
|
+
if (existsSync(candidate)) {
|
|
2752
|
+
return candidate;
|
|
2753
|
+
}
|
|
2754
|
+
const parent = dirname(current);
|
|
2755
|
+
if (parent === current) {
|
|
2756
|
+
break;
|
|
2757
|
+
}
|
|
2758
|
+
current = parent;
|
|
2759
|
+
}
|
|
2760
|
+
}
|
|
2761
|
+
return null;
|
|
2762
|
+
}
|
|
2763
|
+
function getCliVersion() {
|
|
2764
|
+
const packageJsonPath = findPackageJsonPath();
|
|
2765
|
+
if (!packageJsonPath) {
|
|
2766
|
+
return "0.0.0";
|
|
2767
|
+
}
|
|
2768
|
+
try {
|
|
2769
|
+
const pkg = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
|
|
2770
|
+
return pkg.version ?? "0.0.0";
|
|
2771
|
+
} catch {
|
|
2772
|
+
return "0.0.0";
|
|
2773
|
+
}
|
|
2774
|
+
}
|
|
2775
|
+
|
|
2776
|
+
// src/commands/info.ts
|
|
2777
|
+
async function infoCommand(options = {}) {
|
|
2778
|
+
const version = getCliVersion();
|
|
2779
|
+
const pm = await detectPackageManager();
|
|
2780
|
+
const cwd = process.cwd();
|
|
2781
|
+
const configStatus = await readConfigStatus();
|
|
2782
|
+
const cache = await readCacheStatus();
|
|
2783
|
+
const cdn = await checkPrimaryCdn();
|
|
2784
|
+
if (options.json) {
|
|
2785
|
+
console.log(JSON.stringify({
|
|
2786
|
+
version,
|
|
2787
|
+
node: process.version,
|
|
2788
|
+
os: `${os2.platform()} ${os2.arch()}`,
|
|
2789
|
+
packageManager: pm,
|
|
2790
|
+
cwd,
|
|
2791
|
+
config: configStatus.config,
|
|
2792
|
+
configFound: configStatus.found,
|
|
2793
|
+
cache,
|
|
2794
|
+
cdn,
|
|
2795
|
+
registry: SCHEMA_CONFIG.defaultRegistry
|
|
2796
|
+
}, null, 2));
|
|
2797
|
+
return;
|
|
2798
|
+
}
|
|
2799
|
+
console.log(`ui8kit v${version}`);
|
|
2800
|
+
console.log(`Node ${process.version}`);
|
|
2801
|
+
console.log(`OS ${os2.platform()} ${os2.arch()}`);
|
|
2802
|
+
console.log(`PM ${pm}`);
|
|
2803
|
+
console.log(`CWD ${cwd}`);
|
|
2804
|
+
console.log("");
|
|
2805
|
+
if (configStatus.found) {
|
|
2806
|
+
console.log(chalk11.green(`Config ${configStatus.path} (found)`));
|
|
2807
|
+
const config = configStatus.config;
|
|
2808
|
+
console.log(` framework ${config.framework}`);
|
|
2809
|
+
console.log(` typescript ${config.typescript}`);
|
|
2810
|
+
console.log(` globalCss ${config.globalCss}`);
|
|
2811
|
+
console.log(` componentsDir ${config.componentsDir}`);
|
|
2812
|
+
console.log(` libDir ${config.libDir}`);
|
|
2813
|
+
} else {
|
|
2814
|
+
console.log(chalk11.yellow("Config not found"));
|
|
2815
|
+
}
|
|
2816
|
+
console.log("");
|
|
2817
|
+
console.log(`Registry ${SCHEMA_CONFIG.defaultRegistry}`);
|
|
2818
|
+
console.log(`CDN ${cdn.url} (${cdn.ok ? "ok" : "failed"})`);
|
|
2819
|
+
console.log(`Cache ${cache.path} (${cache.items} items, ${cache.mb} MB)`);
|
|
2820
|
+
}
|
|
2821
|
+
async function readConfigStatus() {
|
|
2822
|
+
const candidatePaths = [
|
|
2823
|
+
path12.join(process.cwd(), "ui8kit.config.json"),
|
|
2824
|
+
path12.join(process.cwd(), "src", "ui8kit.config.json"),
|
|
2825
|
+
path12.join(process.cwd(), SCHEMA_CONFIG.defaultRegistryType, "ui8kit.config.json")
|
|
2826
|
+
];
|
|
2827
|
+
for (const configPath of candidatePaths) {
|
|
2828
|
+
if (await fs11.pathExists(configPath)) {
|
|
2829
|
+
try {
|
|
2830
|
+
const config = await fs11.readJson(configPath);
|
|
2831
|
+
return {
|
|
2832
|
+
found: true,
|
|
2833
|
+
path: `./${path12.relative(process.cwd(), configPath).replace(/\\/g, "/")}`,
|
|
2834
|
+
config: {
|
|
2835
|
+
framework: config.framework ?? "unknown",
|
|
2836
|
+
typescript: config.typescript ?? false,
|
|
2837
|
+
globalCss: config.globalCss ?? "src/index.css",
|
|
2838
|
+
componentsDir: config.componentsDir ?? SCHEMA_CONFIG.defaultDirectories.components,
|
|
2839
|
+
libDir: config.libDir ?? SCHEMA_CONFIG.defaultDirectories.lib
|
|
2840
|
+
}
|
|
2841
|
+
};
|
|
2842
|
+
} catch {
|
|
2843
|
+
continue;
|
|
2844
|
+
}
|
|
2845
|
+
}
|
|
2846
|
+
}
|
|
2847
|
+
return {
|
|
2848
|
+
found: false,
|
|
2849
|
+
path: null,
|
|
2850
|
+
config: null
|
|
2851
|
+
};
|
|
2852
|
+
}
|
|
2853
|
+
async function readCacheStatus() {
|
|
2854
|
+
const cachePath = getCacheDir();
|
|
2855
|
+
let items = 0;
|
|
2856
|
+
let bytes = 0;
|
|
2857
|
+
if (await fs11.pathExists(cachePath)) {
|
|
2858
|
+
const result = await countCacheFiles(cachePath);
|
|
2859
|
+
items = result.count;
|
|
2860
|
+
bytes = result.bytes;
|
|
2861
|
+
}
|
|
2862
|
+
return {
|
|
2863
|
+
path: cachePath.replace(/\\/g, "/"),
|
|
2864
|
+
items,
|
|
2865
|
+
mb: `${(bytes / (1024 * 1024)).toFixed(1)}`
|
|
2866
|
+
};
|
|
2867
|
+
}
|
|
2868
|
+
async function countCacheFiles(dirPath) {
|
|
2869
|
+
let count = 0;
|
|
2870
|
+
let size = 0;
|
|
2871
|
+
const entries = await fs11.readdir(dirPath, { withFileTypes: true });
|
|
2872
|
+
for (const entry of entries) {
|
|
2873
|
+
const fullPath = path12.join(dirPath, entry.name);
|
|
2874
|
+
if (entry.isDirectory()) {
|
|
2875
|
+
const nested = await countCacheFiles(fullPath);
|
|
2876
|
+
count += nested.count;
|
|
2877
|
+
size += nested.bytes;
|
|
2878
|
+
continue;
|
|
2879
|
+
}
|
|
2880
|
+
count += 1;
|
|
2881
|
+
const stat = await fs11.stat(fullPath);
|
|
2882
|
+
size += stat.size;
|
|
2883
|
+
}
|
|
2884
|
+
return { count, bytes: size };
|
|
2885
|
+
}
|
|
2886
|
+
async function checkPrimaryCdn() {
|
|
2887
|
+
const url = SCHEMA_CONFIG.cdnBaseUrls[0];
|
|
2888
|
+
try {
|
|
2889
|
+
const response = await fetch4(`${url}/index.json`, { method: "HEAD" });
|
|
2890
|
+
if (response.status >= 200 && response.status < 400) {
|
|
2891
|
+
return { url, ok: true };
|
|
2892
|
+
}
|
|
2893
|
+
} catch {
|
|
2894
|
+
}
|
|
2895
|
+
return { url, ok: false };
|
|
2896
|
+
}
|
|
2897
|
+
|
|
1881
2898
|
// src/index.ts
|
|
2899
|
+
import { resolve as resolve2 } from "node:path";
|
|
1882
2900
|
var program = new Command();
|
|
1883
|
-
program.name("ui8kit").description("A CLI for adding UI components to your Vite React projects (UI8Kit registry)").version(
|
|
2901
|
+
program.option("-c, --cwd <dir>", "Working directory", process.cwd()).option("-v, --verbose", "Enable verbose output").option("--no-cache", "Bypass registry cache").name("ui8kit").description("A CLI for adding UI components to your Vite React projects (UI8Kit registry)").version(getCliVersion());
|
|
2902
|
+
program.command("list").description("List available components in registry").option("-r, --registry <type>", "Registry type: ui", "ui").option("--json", "Output raw JSON").action((options) => listCommand(options));
|
|
2903
|
+
program.command("diff").description("Show local vs registry differences").argument("[component]", "Component name").option("-r, --registry <type>", "Registry type: ui", "ui").option("--json", "Output diff in machine-readable JSON").action((component, options) => diffCommand(component, options));
|
|
2904
|
+
program.command("cache").description("Manage local cache").command("clear").description("Clear registry cache").action(cacheClearCommand);
|
|
2905
|
+
program.command("info").description("Show environment and config diagnostics").option("--json", "Output diagnostics as JSON").action(infoCommand);
|
|
1884
2906
|
program.command("init").description("Initialize UI8Kit structure in your project").option("-y, --yes", "Skip prompts and use defaults").option("-r, --registry <type>", "Registry type: ui", "ui").action(initCommand);
|
|
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").
|
|
2907
|
+
program.command("add").description("Add components to your project from the registry").argument("[components...]", "Components to add").option("-a, --all", "Install all available components").option("-f, --force", "Overwrite existing files").option("-r, --registry <type>", "Registry type: ui", "ui").option("--dry-run", "Show what would be installed without installing").option("--retry", "Aggressive retry mode (3 attempts per CDN request)").action(addCommand);
|
|
2908
|
+
program.command("scan").description("Scan and generate registry from existing components").option("-r, --registry <type|path>", "Registry type (ui) or custom path", "ui").option("-o, --output <file>", "Output registry file").option("-s, --source <dir>", "Source directory to scan").action(async (options) => {
|
|
1887
2909
|
await scanCommand(options);
|
|
1888
2910
|
});
|
|
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").
|
|
2911
|
+
program.command("build").description("Build components registry").argument("[registry]", "Path to registry.json file", "./src/registry.json").option("-o, --output <path>", "Output directory", "./packages/registry/r").action(buildCommand);
|
|
1890
2912
|
program.on("command:*", () => {
|
|
1891
|
-
console.error(
|
|
2913
|
+
console.error(chalk12.red(`Invalid command: ${program.args.join(" ")}`));
|
|
1892
2914
|
console.log("See --help for a list of available commands.");
|
|
1893
2915
|
process.exit(1);
|
|
1894
2916
|
});
|
|
2917
|
+
program.hook("preAction", (_, actionCommand) => {
|
|
2918
|
+
const actionOptions = actionCommand?.opts?.();
|
|
2919
|
+
const globalOptions = program.opts();
|
|
2920
|
+
const verbose = globalOptions.verbose || actionOptions?.verbose;
|
|
2921
|
+
const cwd = actionOptions?.cwd || globalOptions.cwd;
|
|
2922
|
+
if (verbose) {
|
|
2923
|
+
logger.setVerbose(true);
|
|
2924
|
+
}
|
|
2925
|
+
if (cwd && resolve2(process.cwd()) !== resolve2(cwd)) {
|
|
2926
|
+
process.chdir(cwd);
|
|
2927
|
+
}
|
|
2928
|
+
});
|
|
1895
2929
|
program.parse();
|
|
1896
2930
|
//# sourceMappingURL=index.js.map
|