@glass-ui-kit/cli 0.2.4 → 1.0.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/LICENSE +21 -0
- package/README.md +44 -44
- package/dist/index.js +874 -178
- package/package.json +60 -59
package/dist/index.js
CHANGED
|
@@ -1,48 +1,150 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import { Command as
|
|
4
|
+
import { Command as Command6 } from "commander";
|
|
5
5
|
|
|
6
6
|
// src/commands/init.ts
|
|
7
|
-
import
|
|
7
|
+
import chalk2 from "chalk";
|
|
8
8
|
import { Command } from "commander";
|
|
9
|
+
|
|
10
|
+
// src/utils/init/options.ts
|
|
11
|
+
import path from "path";
|
|
12
|
+
var SUPPORTED_INIT_FRAMEWORKS = ["react", "vite", "next", "remix"];
|
|
13
|
+
function resolveInitOptions(options = {}, currentWorkingDirectory = process.cwd()) {
|
|
14
|
+
validateFrameworkOverride(options.framework);
|
|
15
|
+
return {
|
|
16
|
+
projectRoot: path.resolve(currentWorkingDirectory, options.cwd ?? "."),
|
|
17
|
+
cssOverride: options.css,
|
|
18
|
+
componentsAliasOverride: options.components,
|
|
19
|
+
utilsAliasOverride: options.utils,
|
|
20
|
+
frameworkOverride: options.framework,
|
|
21
|
+
force: options.force === true,
|
|
22
|
+
install: options.install !== false
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
function validateFrameworkOverride(framework) {
|
|
26
|
+
if (framework === void 0) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
if (SUPPORTED_INIT_FRAMEWORKS.includes(framework)) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
throw new Error(
|
|
33
|
+
`Unsupported framework "${framework}". Supported values: ${SUPPORTED_INIT_FRAMEWORKS.join(", ")}.`
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// src/utils/init/paths.ts
|
|
9
38
|
import path2 from "path";
|
|
39
|
+
function resolveInitPaths({
|
|
40
|
+
projectRoot,
|
|
41
|
+
hasSrc,
|
|
42
|
+
framework,
|
|
43
|
+
detectedCssPath,
|
|
44
|
+
cssOverride,
|
|
45
|
+
componentsAliasOverride,
|
|
46
|
+
utilsAliasOverride
|
|
47
|
+
}) {
|
|
48
|
+
let baseDir = hasSrc ? "src" : "";
|
|
49
|
+
if (framework === "remix") {
|
|
50
|
+
baseDir = "app";
|
|
51
|
+
}
|
|
52
|
+
let cssPath = cssOverride ?? detectedCssPath;
|
|
53
|
+
if (!cssPath) {
|
|
54
|
+
if (framework === "next") {
|
|
55
|
+
cssPath = hasSrc ? "src/app/globals.css" : "app/globals.css";
|
|
56
|
+
} else if (framework === "remix") {
|
|
57
|
+
cssPath = "app/app.css";
|
|
58
|
+
} else {
|
|
59
|
+
cssPath = hasSrc ? "src/index.css" : "index.css";
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
const defaultComponentsAlias = `${framework === "remix" ? "~" : "@"}/components/ui`;
|
|
63
|
+
const defaultUtilsAlias = `${framework === "remix" ? "~" : "@"}/lib/utils`;
|
|
64
|
+
const componentsAlias = componentsAliasOverride ?? defaultComponentsAlias;
|
|
65
|
+
const utilsAlias = utilsAliasOverride ?? defaultUtilsAlias;
|
|
66
|
+
const utilsRelativePath = resolveUtilsRelativePath(utilsAlias, baseDir);
|
|
67
|
+
const utilsPath = path2.join(projectRoot, utilsRelativePath);
|
|
68
|
+
const aliasPrefix = framework === "remix" ? "~" : "@";
|
|
69
|
+
return {
|
|
70
|
+
framework,
|
|
71
|
+
cssPath,
|
|
72
|
+
utilsRelativePath,
|
|
73
|
+
utilsPath,
|
|
74
|
+
aliasPrefix,
|
|
75
|
+
config: {
|
|
76
|
+
framework,
|
|
77
|
+
style: "default",
|
|
78
|
+
css: cssPath,
|
|
79
|
+
aliases: {
|
|
80
|
+
components: componentsAlias,
|
|
81
|
+
utils: utilsAlias
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
function resolveUtilsRelativePath(utilsAlias, baseDir) {
|
|
87
|
+
const aliasBody = stripAliasPrefix(utilsAlias);
|
|
88
|
+
const relativePath = baseDir && !aliasBody.startsWith(`${baseDir}/`) ? path2.posix.join(baseDir, aliasBody) : aliasBody;
|
|
89
|
+
return path2.posix.extname(relativePath) ? relativePath : `${relativePath}.ts`;
|
|
90
|
+
}
|
|
91
|
+
function stripAliasPrefix(alias) {
|
|
92
|
+
return alias.replace(/^[@~]\//, "");
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// src/utils/init/run-init.ts
|
|
96
|
+
import chalk from "chalk";
|
|
10
97
|
|
|
11
98
|
// src/utils/filesystem.ts
|
|
12
99
|
import fs from "fs";
|
|
13
|
-
import
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
100
|
+
import path3 from "path";
|
|
101
|
+
function resolveFromBaseDir(filePath, baseDir = process.cwd()) {
|
|
102
|
+
return path3.resolve(baseDir, filePath);
|
|
103
|
+
}
|
|
104
|
+
async function writeFile(filePath, content, baseDir = process.cwd()) {
|
|
105
|
+
const absolutePath = resolveFromBaseDir(filePath, baseDir);
|
|
106
|
+
const dir = path3.dirname(absolutePath);
|
|
17
107
|
if (!fs.existsSync(dir)) {
|
|
18
108
|
await fs.promises.mkdir(dir, { recursive: true });
|
|
19
109
|
}
|
|
20
110
|
await fs.promises.writeFile(absolutePath, content, "utf-8");
|
|
21
111
|
return absolutePath;
|
|
22
112
|
}
|
|
23
|
-
async function readFile(filePath) {
|
|
24
|
-
const absolutePath =
|
|
113
|
+
async function readFile(filePath, baseDir = process.cwd()) {
|
|
114
|
+
const absolutePath = resolveFromBaseDir(filePath, baseDir);
|
|
25
115
|
if (!fs.existsSync(absolutePath)) {
|
|
26
116
|
throw new Error(`File not found: ${filePath}`);
|
|
27
117
|
}
|
|
28
118
|
return await fs.promises.readFile(absolutePath, "utf-8");
|
|
29
119
|
}
|
|
30
|
-
function exists(filePath) {
|
|
31
|
-
return fs.existsSync(
|
|
120
|
+
function exists(filePath, baseDir = process.cwd()) {
|
|
121
|
+
return fs.existsSync(resolveFromBaseDir(filePath, baseDir));
|
|
32
122
|
}
|
|
33
123
|
|
|
34
124
|
// src/utils/get-project-info.ts
|
|
125
|
+
import path4 from "path";
|
|
35
126
|
import { spawn } from "child_process";
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
127
|
+
function resolveProjectRoot(cwdOption, cwd = process.cwd()) {
|
|
128
|
+
return cwdOption ? path4.resolve(cwd, cwdOption) : cwd;
|
|
129
|
+
}
|
|
130
|
+
function getLockfilePath(projectRoot = process.cwd(), existsAtPath = exists) {
|
|
131
|
+
if (existsAtPath("bun.lock", projectRoot)) return "bun.lock";
|
|
132
|
+
if (existsAtPath("bun.lockb", projectRoot)) return "bun.lockb";
|
|
133
|
+
if (existsAtPath("pnpm-lock.yaml", projectRoot)) return "pnpm-lock.yaml";
|
|
134
|
+
if (existsAtPath("yarn.lock", projectRoot)) return "yarn.lock";
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
async function getPackageManager(projectRoot = process.cwd()) {
|
|
138
|
+
if (exists("bun.lock", projectRoot)) return "bun";
|
|
139
|
+
if (exists("bun.lockb", projectRoot)) return "bun";
|
|
140
|
+
if (exists("pnpm-lock.yaml", projectRoot)) return "pnpm";
|
|
141
|
+
if (exists("yarn.lock", projectRoot)) return "yarn";
|
|
40
142
|
return "npm";
|
|
41
143
|
}
|
|
42
|
-
async function getFramework() {
|
|
43
|
-
if (!exists("package.json")) return "unknown";
|
|
144
|
+
async function getFramework(projectRoot = process.cwd()) {
|
|
145
|
+
if (!exists("package.json", projectRoot)) return "unknown";
|
|
44
146
|
try {
|
|
45
|
-
const content = await readFile("package.json");
|
|
147
|
+
const content = await readFile("package.json", projectRoot);
|
|
46
148
|
const pkg = JSON.parse(content);
|
|
47
149
|
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
48
150
|
if (deps["@remix-run/react"] || deps["@remix-run/dev"] || deps["@react-router/dev"] || deps["@react-router/node"]) {
|
|
@@ -56,29 +158,26 @@ async function getFramework() {
|
|
|
56
158
|
}
|
|
57
159
|
return "unknown";
|
|
58
160
|
}
|
|
59
|
-
function getCssPath(framework) {
|
|
161
|
+
function getCssPath(framework, projectRoot = process.cwd()) {
|
|
60
162
|
const paths = {
|
|
163
|
+
react: ["src/index.css", "src/main.css", "src/style.css", "index.css"],
|
|
61
164
|
next: ["app/globals.css", "src/app/globals.css", "styles/globals.css"],
|
|
62
165
|
vite: ["src/index.css", "src/main.css", "src/style.css"],
|
|
63
166
|
astro: ["src/styles/global.css", "src/global.css"],
|
|
64
|
-
remix: [
|
|
65
|
-
"app/app.css",
|
|
66
|
-
"app/tailwind.css",
|
|
67
|
-
"app/globals.css",
|
|
68
|
-
"app/styles/tailwind.css"
|
|
69
|
-
],
|
|
167
|
+
remix: ["app/app.css", "app/tailwind.css", "app/globals.css", "app/styles/tailwind.css"],
|
|
70
168
|
unknown: ["src/index.css", "styles.css"]
|
|
71
169
|
};
|
|
72
170
|
for (const p of paths[framework] || []) {
|
|
73
|
-
if (exists(p)) return p;
|
|
171
|
+
if (exists(p, projectRoot)) return p;
|
|
74
172
|
}
|
|
75
173
|
return paths[framework]?.[0] || null;
|
|
76
174
|
}
|
|
77
|
-
async function installDependencies(deps, pm) {
|
|
175
|
+
async function installDependencies(deps, pm, projectRoot = process.cwd()) {
|
|
78
176
|
const installCmd = pm === "npm" ? "install" : "add";
|
|
79
177
|
console.log(`Running ${pm} ${installCmd}...`);
|
|
80
178
|
return new Promise((resolve, reject) => {
|
|
81
179
|
const child = spawn(pm, [installCmd, ...deps], {
|
|
180
|
+
cwd: projectRoot,
|
|
82
181
|
stdio: "inherit",
|
|
83
182
|
shell: true
|
|
84
183
|
});
|
|
@@ -287,113 +386,152 @@ export function cn(...inputs: ClassValue[]) {
|
|
|
287
386
|
}
|
|
288
387
|
`;
|
|
289
388
|
|
|
290
|
-
// src/
|
|
291
|
-
var
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
console.log(chalk.gray(" glass.config.json already exists."));
|
|
389
|
+
// src/utils/init/run-init.ts
|
|
390
|
+
var defaultRuntime = {
|
|
391
|
+
cwd: () => process.cwd(),
|
|
392
|
+
exists,
|
|
393
|
+
readFile,
|
|
394
|
+
writeFile,
|
|
395
|
+
getFramework,
|
|
396
|
+
getPackageManager,
|
|
397
|
+
getCssPath,
|
|
398
|
+
installDependencies,
|
|
399
|
+
log: console.log
|
|
400
|
+
};
|
|
401
|
+
async function runInitCommand(options = {}, runtime = defaultRuntime) {
|
|
402
|
+
const resolvedOptions = resolveInitOptions(options, runtime.cwd());
|
|
403
|
+
runtime.log(chalk.bold("\nInitializing Glass UI..."));
|
|
404
|
+
const framework = resolvedOptions.frameworkOverride ?? await runtime.getFramework(resolvedOptions.projectRoot);
|
|
405
|
+
const pm = await runtime.getPackageManager(resolvedOptions.projectRoot);
|
|
406
|
+
const configPath = "glass.config.json";
|
|
407
|
+
const hasSrc = runtime.exists("src", resolvedOptions.projectRoot);
|
|
408
|
+
const plan = resolveInitPaths({
|
|
409
|
+
projectRoot: resolvedOptions.projectRoot,
|
|
410
|
+
hasSrc,
|
|
411
|
+
framework,
|
|
412
|
+
detectedCssPath: runtime.getCssPath(framework, resolvedOptions.projectRoot),
|
|
413
|
+
cssOverride: resolvedOptions.cssOverride,
|
|
414
|
+
componentsAliasOverride: resolvedOptions.componentsAliasOverride,
|
|
415
|
+
utilsAliasOverride: resolvedOptions.utilsAliasOverride
|
|
416
|
+
});
|
|
417
|
+
const writes = [
|
|
418
|
+
{
|
|
419
|
+
filePath: configPath,
|
|
420
|
+
content: JSON.stringify(plan.config, null, 2),
|
|
421
|
+
label: "glass.config.json"
|
|
422
|
+
},
|
|
423
|
+
{
|
|
424
|
+
filePath: plan.utilsRelativePath,
|
|
425
|
+
content: UTILS_CN,
|
|
426
|
+
label: plan.utilsRelativePath
|
|
427
|
+
},
|
|
428
|
+
{
|
|
429
|
+
filePath: plan.cssPath,
|
|
430
|
+
content: `@import "tailwindcss";
|
|
431
|
+
|
|
432
|
+
${GLASS_BASE_STYLES}`,
|
|
433
|
+
label: plan.cssPath
|
|
336
434
|
}
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
435
|
+
];
|
|
436
|
+
for (const write of writes.slice(0, 2)) {
|
|
437
|
+
const alreadyExists = runtime.exists(write.filePath, resolvedOptions.projectRoot);
|
|
438
|
+
if (alreadyExists && !resolvedOptions.force) {
|
|
439
|
+
runtime.log(chalk.yellow(` Skipped ${write.label} (already exists)`));
|
|
440
|
+
continue;
|
|
342
441
|
}
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
${
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
)
|
|
361
|
-
);
|
|
442
|
+
await runtime.writeFile(write.filePath, write.content, resolvedOptions.projectRoot);
|
|
443
|
+
runtime.log(
|
|
444
|
+
alreadyExists ? chalk.green(` Replaced ${write.label}`) : chalk.green(` Created ${write.label}`)
|
|
445
|
+
);
|
|
446
|
+
}
|
|
447
|
+
const cssWrite = writes[2];
|
|
448
|
+
const cssAlreadyExists = runtime.exists(cssWrite.filePath, resolvedOptions.projectRoot);
|
|
449
|
+
if (!cssAlreadyExists) {
|
|
450
|
+
await runtime.writeFile(cssWrite.filePath, cssWrite.content, resolvedOptions.projectRoot);
|
|
451
|
+
runtime.log(chalk.green(` Created ${cssWrite.label}`));
|
|
452
|
+
} else if (resolvedOptions.force) {
|
|
453
|
+
await runtime.writeFile(cssWrite.filePath, cssWrite.content, resolvedOptions.projectRoot);
|
|
454
|
+
runtime.log(chalk.green(` Replaced ${cssWrite.label}`));
|
|
455
|
+
} else {
|
|
456
|
+
const existingCss = await runtime.readFile(cssWrite.filePath, resolvedOptions.projectRoot) ?? "";
|
|
457
|
+
if (existingCss.includes("--glass-bg")) {
|
|
458
|
+
runtime.log(chalk.yellow(` Skipped ${cssWrite.label} (already exists)`));
|
|
362
459
|
} else {
|
|
363
|
-
|
|
460
|
+
const updatedCss = `${existingCss.trimEnd()}
|
|
461
|
+
|
|
462
|
+
${GLASS_BASE_STYLES}
|
|
463
|
+
`;
|
|
464
|
+
await runtime.writeFile(cssWrite.filePath, updatedCss, resolvedOptions.projectRoot);
|
|
465
|
+
runtime.log(chalk.green(` Updated ${cssWrite.label} with Glass tokens`));
|
|
364
466
|
}
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
console.log(chalk.bold.green("\nSetup complete."));
|
|
373
|
-
console.log(`Try adding a component:
|
|
374
|
-
`);
|
|
375
|
-
console.log(
|
|
376
|
-
chalk.cyan(` ${runCommand} @glass-ui-kit/cli@latest add card`)
|
|
467
|
+
}
|
|
468
|
+
if (resolvedOptions.install) {
|
|
469
|
+
runtime.log(chalk.cyan(` Installing dependencies (clsx, tailwind-merge, lucide-react)...`));
|
|
470
|
+
await runtime.installDependencies(
|
|
471
|
+
["clsx", "tailwind-merge", "lucide-react"],
|
|
472
|
+
pm,
|
|
473
|
+
resolvedOptions.projectRoot
|
|
377
474
|
);
|
|
378
|
-
|
|
475
|
+
} else {
|
|
476
|
+
runtime.log(chalk.gray(" Skipped dependency installation (--no-install)."));
|
|
477
|
+
}
|
|
478
|
+
const runCommand = pm === "bun" ? "bunx" : pm === "pnpm" ? "pnpm dlx" : "npx";
|
|
479
|
+
runtime.log(chalk.bold.green("\nSetup complete."));
|
|
480
|
+
runtime.log(chalk.gray(` Project root: ${resolvedOptions.projectRoot}`));
|
|
481
|
+
runtime.log(chalk.gray(` Framework: ${plan.framework}`));
|
|
482
|
+
runtime.log("Try adding a component:\n");
|
|
483
|
+
runtime.log(chalk.cyan(` ${runCommand} @glass-ui-kit/cli@latest add card`));
|
|
484
|
+
runtime.log("");
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// src/commands/init.ts
|
|
488
|
+
var InitCommandError = class extends Error {
|
|
489
|
+
};
|
|
490
|
+
async function runInitCommand2(options = {}, runtime = defaultRuntime) {
|
|
491
|
+
try {
|
|
492
|
+
await runInitCommand(options, runtime);
|
|
379
493
|
} catch (error) {
|
|
380
|
-
console.error(chalk.red("\nInitialization failed:"));
|
|
381
494
|
if (error instanceof Error) {
|
|
382
|
-
|
|
383
|
-
} else {
|
|
384
|
-
console.error(chalk.gray(String(error)));
|
|
495
|
+
throw new InitCommandError(error.message);
|
|
385
496
|
}
|
|
386
|
-
|
|
497
|
+
throw new InitCommandError(String(error));
|
|
387
498
|
}
|
|
388
|
-
}
|
|
499
|
+
}
|
|
500
|
+
function createInitAction(deps = {
|
|
501
|
+
runInitCommand: runInitCommand2,
|
|
502
|
+
error: console.error,
|
|
503
|
+
exit: process.exit
|
|
504
|
+
}) {
|
|
505
|
+
return async (options = {}) => {
|
|
506
|
+
try {
|
|
507
|
+
await deps.runInitCommand({
|
|
508
|
+
cwd: options.cwd,
|
|
509
|
+
css: options.css,
|
|
510
|
+
components: options.components,
|
|
511
|
+
utils: options.utils,
|
|
512
|
+
framework: options.framework,
|
|
513
|
+
force: options.force === true,
|
|
514
|
+
install: options.install
|
|
515
|
+
});
|
|
516
|
+
} catch (error) {
|
|
517
|
+
deps.error(chalk2.red("\nInitialization failed:"));
|
|
518
|
+
if (error instanceof Error) {
|
|
519
|
+
deps.error(chalk2.gray(error.message));
|
|
520
|
+
} else {
|
|
521
|
+
deps.error(chalk2.gray(String(error)));
|
|
522
|
+
}
|
|
523
|
+
deps.exit(1);
|
|
524
|
+
}
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
var init = new Command().name("init").description("Initialize configuration and dependencies").option("-y, --yes", "Skip confirmation prompt", false).option("--cwd <path>", "Run the command against another project directory").option("--css <path>", "Use a custom CSS file instead of autodetecting one").option("--components <alias>", "Set the components alias saved in glass.config.json").option("--utils <alias>", "Set the utils alias saved in glass.config.json").option("--framework <name>", "Force the framework instead of autodetecting it").option("--force", "Overwrite generated files if they already exist").option("--no-install", "Skip dependency installation").action(createInitAction());
|
|
389
528
|
|
|
390
529
|
// src/commands/add.ts
|
|
391
|
-
import
|
|
530
|
+
import chalk4 from "chalk";
|
|
392
531
|
import { Command as Command2 } from "commander";
|
|
393
|
-
import path4 from "path";
|
|
394
532
|
|
|
395
533
|
// src/utils/registry.ts
|
|
396
|
-
import
|
|
534
|
+
import path5 from "path";
|
|
397
535
|
import os from "os";
|
|
398
536
|
import fs2 from "fs/promises";
|
|
399
537
|
|
|
@@ -427,8 +565,8 @@ var registryIndexSchema = z.array(registryItemSchema);
|
|
|
427
565
|
|
|
428
566
|
// src/utils/registry.ts
|
|
429
567
|
var DEFAULT_REGISTRY_URL = "https://ui-glass.vercel.app/registry.json";
|
|
430
|
-
var CACHE_DIR =
|
|
431
|
-
var CACHE_FILE =
|
|
568
|
+
var CACHE_DIR = path5.join(os.homedir(), ".glass-ui");
|
|
569
|
+
var CACHE_FILE = path5.join(CACHE_DIR, "registry.json");
|
|
432
570
|
var CACHE_TTL = 1e3 * 60 * 60 * 24;
|
|
433
571
|
function getRegistryUrl() {
|
|
434
572
|
return process.env.GLASS_UI_REGISTRY_URL || DEFAULT_REGISTRY_URL;
|
|
@@ -459,6 +597,12 @@ async function writeCache(data) {
|
|
|
459
597
|
} catch {
|
|
460
598
|
}
|
|
461
599
|
}
|
|
600
|
+
function getErrorMessage(error, fallback) {
|
|
601
|
+
if (error instanceof Error && error.message.length > 0) {
|
|
602
|
+
return error.message;
|
|
603
|
+
}
|
|
604
|
+
return fallback;
|
|
605
|
+
}
|
|
462
606
|
async function fetchRegistry() {
|
|
463
607
|
const cachedRegistry = await readCache();
|
|
464
608
|
if (cachedRegistry) {
|
|
@@ -469,27 +613,22 @@ async function fetchRegistry() {
|
|
|
469
613
|
try {
|
|
470
614
|
response = await fetch(url);
|
|
471
615
|
} catch (error) {
|
|
472
|
-
throw new Error(
|
|
473
|
-
"Network error: Unable to connect to registry. Check your internet connection."
|
|
474
|
-
);
|
|
616
|
+
throw new Error(getErrorMessage(error, "Network error: Unable to connect to registry."));
|
|
475
617
|
}
|
|
476
618
|
if (!response.ok) {
|
|
477
|
-
throw new Error(
|
|
478
|
-
`Registry unavailable. URL: ${url} (Status: ${response.status})`
|
|
479
|
-
);
|
|
619
|
+
throw new Error(`Registry unavailable. URL: ${url} (Status: ${response.status})`);
|
|
480
620
|
}
|
|
481
621
|
let data;
|
|
482
622
|
try {
|
|
483
623
|
data = await response.json();
|
|
484
|
-
} catch {
|
|
485
|
-
throw new Error("Invalid response: Registry returned non-JSON data.");
|
|
624
|
+
} catch (error) {
|
|
625
|
+
throw new Error(getErrorMessage(error, "Invalid response: Registry returned non-JSON data."));
|
|
486
626
|
}
|
|
487
627
|
const parsed = registryIndexSchema.safeParse(data);
|
|
488
628
|
if (!parsed.success) {
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
);
|
|
629
|
+
const issue = parsed.error.issues[0];
|
|
630
|
+
const location = issue?.path.length ? issue.path.join(".") : "registry";
|
|
631
|
+
throw new Error(`${issue?.message ?? "Schema validation failed"}: ${location}`);
|
|
493
632
|
}
|
|
494
633
|
await writeCache(parsed.data);
|
|
495
634
|
return parsed.data;
|
|
@@ -497,6 +636,85 @@ async function fetchRegistry() {
|
|
|
497
636
|
function getItem(registry, name) {
|
|
498
637
|
return registry.find((item) => item.name === name);
|
|
499
638
|
}
|
|
639
|
+
function getItems(registry, names) {
|
|
640
|
+
const items = [];
|
|
641
|
+
const missing = [];
|
|
642
|
+
for (const name of names) {
|
|
643
|
+
const item = getItem(registry, name);
|
|
644
|
+
if (item) {
|
|
645
|
+
items.push(item);
|
|
646
|
+
continue;
|
|
647
|
+
}
|
|
648
|
+
missing.push(name);
|
|
649
|
+
}
|
|
650
|
+
return { items, missing };
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
// src/utils/add/selection.ts
|
|
654
|
+
function dedupeNames(names) {
|
|
655
|
+
const uniqueNames = [];
|
|
656
|
+
const seen = /* @__PURE__ */ new Set();
|
|
657
|
+
for (const name of names) {
|
|
658
|
+
if (seen.has(name)) {
|
|
659
|
+
continue;
|
|
660
|
+
}
|
|
661
|
+
seen.add(name);
|
|
662
|
+
uniqueNames.push(name);
|
|
663
|
+
}
|
|
664
|
+
return uniqueNames;
|
|
665
|
+
}
|
|
666
|
+
function resolveAddSelection(registry, requestedNames, selectAll) {
|
|
667
|
+
if (selectAll && requestedNames.length > 0) {
|
|
668
|
+
return { ok: false, reason: "invalid-combination" };
|
|
669
|
+
}
|
|
670
|
+
if (selectAll) {
|
|
671
|
+
return { ok: true, items: registry };
|
|
672
|
+
}
|
|
673
|
+
const uniqueRequestedNames = dedupeNames(requestedNames);
|
|
674
|
+
if (uniqueRequestedNames.length === 0) {
|
|
675
|
+
return { ok: false, reason: "empty-selection" };
|
|
676
|
+
}
|
|
677
|
+
const { items, missing } = getItems(registry, uniqueRequestedNames);
|
|
678
|
+
if (missing.length > 0) {
|
|
679
|
+
return { ok: false, reason: "missing-components", names: missing };
|
|
680
|
+
}
|
|
681
|
+
return { ok: true, items };
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
// src/utils/add/dependencies.ts
|
|
685
|
+
function collectDependencies(items) {
|
|
686
|
+
const dependencies = [];
|
|
687
|
+
const seen = /* @__PURE__ */ new Set();
|
|
688
|
+
for (const item of items) {
|
|
689
|
+
for (const dependency of item.dependencies || []) {
|
|
690
|
+
if (seen.has(dependency)) {
|
|
691
|
+
continue;
|
|
692
|
+
}
|
|
693
|
+
seen.add(dependency);
|
|
694
|
+
dependencies.push(dependency);
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
return dependencies;
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
// src/utils/add/paths.ts
|
|
701
|
+
function resolveTargetDir(config, hasSrc, explicitPath) {
|
|
702
|
+
if (explicitPath) {
|
|
703
|
+
return explicitPath;
|
|
704
|
+
}
|
|
705
|
+
const targetDirAlias = config.aliases.components || "@/components/ui";
|
|
706
|
+
const relativeAliasPath = targetDirAlias.replace(/^[@~]\//, "");
|
|
707
|
+
if (config.framework === "remix") {
|
|
708
|
+
return `./app/${relativeAliasPath}`;
|
|
709
|
+
}
|
|
710
|
+
if (hasSrc) {
|
|
711
|
+
return `./src/${relativeAliasPath}`;
|
|
712
|
+
}
|
|
713
|
+
return `./${relativeAliasPath}`;
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
// src/utils/add/planner.ts
|
|
717
|
+
import path6 from "path";
|
|
500
718
|
|
|
501
719
|
// src/utils/transformers.ts
|
|
502
720
|
function transformImports(content, config) {
|
|
@@ -519,63 +737,541 @@ function transformImports(content, config) {
|
|
|
519
737
|
return transformed;
|
|
520
738
|
}
|
|
521
739
|
|
|
522
|
-
// src/
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
740
|
+
// src/utils/add/planner.ts
|
|
741
|
+
function buildWritePlan(items, config, hasSrc, options) {
|
|
742
|
+
const targetDir = resolveTargetDir(config, hasSrc, options.path);
|
|
743
|
+
return items.flatMap(
|
|
744
|
+
(item) => item.files.filter((file) => Boolean(file.content)).map((file) => {
|
|
745
|
+
const fileName = path6.basename(file.path);
|
|
746
|
+
const filePath = path6.join(targetDir, fileName);
|
|
747
|
+
return {
|
|
748
|
+
filePath,
|
|
749
|
+
content: transformImports(file.content || "", config),
|
|
750
|
+
action: !options.overwrite && options.exists(filePath) ? "skip-existing" : "write"
|
|
751
|
+
};
|
|
752
|
+
})
|
|
753
|
+
);
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
// src/utils/add/run-add.ts
|
|
757
|
+
import path7 from "path";
|
|
758
|
+
import chalk3 from "chalk";
|
|
759
|
+
var defaultRuntime2 = {
|
|
760
|
+
cwd: () => process.cwd(),
|
|
761
|
+
exists,
|
|
762
|
+
readFile,
|
|
763
|
+
writeFile,
|
|
764
|
+
fetchRegistry,
|
|
765
|
+
getPackageManager,
|
|
766
|
+
installDependencies,
|
|
767
|
+
log: console.log
|
|
768
|
+
};
|
|
769
|
+
function buildMissingComponentsMessage(names) {
|
|
770
|
+
return `Components not found: ${names.join(", ")}.`;
|
|
771
|
+
}
|
|
772
|
+
function resolveProjectRoot2(cwdOption, cwd) {
|
|
773
|
+
return cwdOption ? path7.resolve(cwd, cwdOption) : cwd;
|
|
774
|
+
}
|
|
775
|
+
async function runAddCommand(componentNames = [], options = {}, runtime = defaultRuntime2) {
|
|
776
|
+
const projectRoot = resolveProjectRoot2(options.cwd, runtime.cwd?.() ?? process.cwd());
|
|
777
|
+
if (options.depsOnly && options.install === false) {
|
|
778
|
+
throw new Error("invalid-install-combination");
|
|
779
|
+
}
|
|
780
|
+
if (!runtime.exists("glass.config.json", projectRoot)) {
|
|
781
|
+
throw new Error("config-not-found");
|
|
782
|
+
}
|
|
783
|
+
const config = JSON.parse(await runtime.readFile("glass.config.json", projectRoot));
|
|
784
|
+
runtime.log(chalk3.bold("Fetching components..."));
|
|
785
|
+
const registry = await runtime.fetchRegistry();
|
|
786
|
+
const selection = resolveAddSelection(registry, componentNames, Boolean(options.all));
|
|
787
|
+
if (!selection.ok) {
|
|
788
|
+
if (selection.reason === "invalid-combination") {
|
|
789
|
+
throw new Error("invalid-combination");
|
|
539
790
|
}
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
const relativeAliasPath = targetDirAlias.replace(/^[@~]\//, "");
|
|
543
|
-
let targetDir = "";
|
|
544
|
-
if (config.framework === "remix") {
|
|
545
|
-
targetDir = `./app/${relativeAliasPath}`;
|
|
546
|
-
} else if (hasSrc) {
|
|
547
|
-
targetDir = `./src/${relativeAliasPath}`;
|
|
548
|
-
} else {
|
|
549
|
-
targetDir = `./${relativeAliasPath}`;
|
|
791
|
+
if (selection.reason === "missing-components") {
|
|
792
|
+
throw new Error(buildMissingComponentsMessage(selection.names || []));
|
|
550
793
|
}
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
794
|
+
throw new Error("empty-selection");
|
|
795
|
+
}
|
|
796
|
+
const shouldWrite = options.depsOnly !== true;
|
|
797
|
+
const shouldInstall = options.install !== false;
|
|
798
|
+
if (shouldWrite) {
|
|
799
|
+
const plannedWrites = buildWritePlan(
|
|
800
|
+
selection.items,
|
|
801
|
+
config,
|
|
802
|
+
runtime.exists("src", projectRoot),
|
|
803
|
+
{
|
|
804
|
+
exists: (filePath) => runtime.exists(filePath, projectRoot),
|
|
805
|
+
overwrite: options.overwrite,
|
|
806
|
+
path: options.path
|
|
807
|
+
}
|
|
808
|
+
);
|
|
809
|
+
for (const file of plannedWrites) {
|
|
810
|
+
if (file.action === "skip-existing") {
|
|
811
|
+
runtime.log(chalk3.yellow(` Skipped ${file.filePath} (already exists)`));
|
|
555
812
|
continue;
|
|
556
813
|
}
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
console.log(chalk2.green(` Created ${filePath}`));
|
|
814
|
+
await runtime.writeFile(file.filePath, file.content, projectRoot);
|
|
815
|
+
runtime.log(chalk3.green(` Created ${file.filePath}`));
|
|
560
816
|
}
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
817
|
+
}
|
|
818
|
+
const dependencies = collectDependencies(selection.items);
|
|
819
|
+
if (shouldInstall && dependencies.length > 0) {
|
|
820
|
+
const pm = await runtime.getPackageManager(projectRoot);
|
|
821
|
+
runtime.log(chalk3.cyan(" Installing dependencies..."));
|
|
822
|
+
await runtime.installDependencies(dependencies, pm, projectRoot);
|
|
823
|
+
}
|
|
824
|
+
runtime.log(chalk3.bold.green("\nDone."));
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
// src/commands/add.ts
|
|
828
|
+
var AddCommandError = class extends Error {
|
|
829
|
+
constructor(code, message) {
|
|
830
|
+
super(message);
|
|
831
|
+
this.code = code;
|
|
832
|
+
}
|
|
833
|
+
code;
|
|
834
|
+
};
|
|
835
|
+
async function runAddCommand2(componentNames = [], options = {}, runtime = defaultRuntime2) {
|
|
836
|
+
try {
|
|
837
|
+
await runAddCommand(componentNames, options, runtime);
|
|
567
838
|
} catch (error) {
|
|
568
|
-
console.error(chalk2.red("\nOperation failed:"));
|
|
569
839
|
if (error instanceof Error) {
|
|
570
|
-
|
|
840
|
+
if (error.message === "config-not-found") {
|
|
841
|
+
throw new AddCommandError("config-not-found", "Configuration file not found.");
|
|
842
|
+
}
|
|
843
|
+
if (error.message === "invalid-combination") {
|
|
844
|
+
throw new AddCommandError(
|
|
845
|
+
"invalid-combination",
|
|
846
|
+
"Cannot combine --all with specific component names."
|
|
847
|
+
);
|
|
848
|
+
}
|
|
849
|
+
if (error.message === "invalid-install-combination") {
|
|
850
|
+
throw new AddCommandError(
|
|
851
|
+
"invalid-combination",
|
|
852
|
+
"Cannot combine --deps-only with --no-install."
|
|
853
|
+
);
|
|
854
|
+
}
|
|
855
|
+
if (error.message === "empty-selection") {
|
|
856
|
+
throw new AddCommandError(
|
|
857
|
+
"empty-selection",
|
|
858
|
+
"Please specify at least one component or use --all."
|
|
859
|
+
);
|
|
860
|
+
}
|
|
861
|
+
if (error.message.startsWith("Components not found:")) {
|
|
862
|
+
throw new AddCommandError("missing-components", error.message);
|
|
863
|
+
}
|
|
571
864
|
}
|
|
572
|
-
|
|
865
|
+
throw error;
|
|
573
866
|
}
|
|
867
|
+
}
|
|
868
|
+
function createAddAction(deps = {
|
|
869
|
+
runAddCommand: runAddCommand2,
|
|
870
|
+
error: console.error,
|
|
871
|
+
log: console.log,
|
|
872
|
+
exit: process.exit
|
|
873
|
+
}) {
|
|
874
|
+
return async (componentNames = [], options = {}) => {
|
|
875
|
+
try {
|
|
876
|
+
await deps.runAddCommand(componentNames, options);
|
|
877
|
+
} catch (error) {
|
|
878
|
+
if (error instanceof AddCommandError) {
|
|
879
|
+
if (error.code === "config-not-found") {
|
|
880
|
+
deps.error(chalk4.red(error.message));
|
|
881
|
+
deps.log(chalk4.gray("Please run the init command first:"));
|
|
882
|
+
deps.log(chalk4.cyan(" npx @glass-ui-kit/cli@latest init"));
|
|
883
|
+
deps.exit(1);
|
|
884
|
+
}
|
|
885
|
+
deps.error(chalk4.red(error.message));
|
|
886
|
+
deps.exit(1);
|
|
887
|
+
}
|
|
888
|
+
deps.error(chalk4.red("\nOperation failed:"));
|
|
889
|
+
if (error instanceof Error) {
|
|
890
|
+
deps.error(chalk4.gray(` ${error.message}`));
|
|
891
|
+
}
|
|
892
|
+
deps.exit(1);
|
|
893
|
+
}
|
|
894
|
+
};
|
|
895
|
+
}
|
|
896
|
+
var add = new Command2().name("add").description("Add one or more components to your project").argument("[components...]", "The components to add").option("--all", "Add all available components").option("--overwrite", "Replace existing component files").option("--path <dir>", "Use a custom output directory").option("--no-install", "Skip dependency installation").option("--deps-only", "Install dependencies without writing component files").option("--cwd <path>", "Run the command against another project directory").action(createAddAction());
|
|
897
|
+
|
|
898
|
+
// src/commands/list.ts
|
|
899
|
+
import chalk5 from "chalk";
|
|
900
|
+
import { Command as Command3 } from "commander";
|
|
901
|
+
|
|
902
|
+
// src/utils/list/run-list.ts
|
|
903
|
+
var defaultRuntime3 = {
|
|
904
|
+
fetchRegistry
|
|
905
|
+
};
|
|
906
|
+
async function runListCommand(options = {}, runtime = defaultRuntime3) {
|
|
907
|
+
const registry = await runtime.fetchRegistry();
|
|
908
|
+
if (options.json) {
|
|
909
|
+
return JSON.stringify(registry, null, 2);
|
|
910
|
+
}
|
|
911
|
+
return registry.map((item) => item.name).join("\n");
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
// src/commands/list.ts
|
|
915
|
+
async function runListCommand2(options = {}, runtime = defaultRuntime3) {
|
|
916
|
+
return runListCommand(options, runtime);
|
|
917
|
+
}
|
|
918
|
+
function createListAction(deps = {
|
|
919
|
+
runListCommand: runListCommand2,
|
|
920
|
+
error: console.error,
|
|
921
|
+
log: console.log,
|
|
922
|
+
exit: process.exit
|
|
923
|
+
}) {
|
|
924
|
+
return async (options = {}) => {
|
|
925
|
+
try {
|
|
926
|
+
const output = await deps.runListCommand({ json: options.json === true });
|
|
927
|
+
if (output.length > 0) {
|
|
928
|
+
deps.log(output);
|
|
929
|
+
}
|
|
930
|
+
} catch (error) {
|
|
931
|
+
deps.error(chalk5.red("\nList failed:"));
|
|
932
|
+
if (error instanceof Error) {
|
|
933
|
+
deps.error(chalk5.gray(error.message));
|
|
934
|
+
} else {
|
|
935
|
+
deps.error(chalk5.gray(String(error)));
|
|
936
|
+
}
|
|
937
|
+
deps.exit(1);
|
|
938
|
+
}
|
|
939
|
+
};
|
|
940
|
+
}
|
|
941
|
+
var list = new Command3().name("list").description("List available components from the registry").option("--json", "Output the validated registry payload as JSON", false).action(createListAction());
|
|
942
|
+
|
|
943
|
+
// src/commands/info.ts
|
|
944
|
+
import chalk6 from "chalk";
|
|
945
|
+
import { Command as Command4 } from "commander";
|
|
946
|
+
|
|
947
|
+
// src/utils/info/format-info.ts
|
|
948
|
+
function formatDependencySection(label, values) {
|
|
949
|
+
return `${label}: ${values && values.length > 0 ? values.join(", ") : "None"}`;
|
|
950
|
+
}
|
|
951
|
+
function formatInfo(item) {
|
|
952
|
+
const lines = [
|
|
953
|
+
`Name: ${item.name}`,
|
|
954
|
+
`Type: ${item.type}`,
|
|
955
|
+
"Files:",
|
|
956
|
+
...item.files.map((file) => `- ${file.path} (${file.type})`),
|
|
957
|
+
formatDependencySection("Dependencies", item.dependencies),
|
|
958
|
+
formatDependencySection("Dev Dependencies", item.devDependencies),
|
|
959
|
+
formatDependencySection("Registry Dependencies", item.registryDependencies)
|
|
960
|
+
];
|
|
961
|
+
if (item.meta?.requiresBlur !== void 0) {
|
|
962
|
+
lines.push(`Requires Blur: ${item.meta.requiresBlur}`);
|
|
963
|
+
}
|
|
964
|
+
return lines.join("\n");
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
// src/utils/info/run-info.ts
|
|
968
|
+
var defaultRuntime4 = {
|
|
969
|
+
fetchRegistry,
|
|
970
|
+
getItem
|
|
971
|
+
};
|
|
972
|
+
async function runInfoCommand(componentName, options = {}, runtime = defaultRuntime4) {
|
|
973
|
+
const registry = await runtime.fetchRegistry();
|
|
974
|
+
const item = runtime.getItem(registry, componentName);
|
|
975
|
+
if (!item) {
|
|
976
|
+
throw new Error(`Component not found: "${componentName}"`);
|
|
977
|
+
}
|
|
978
|
+
if (options.json) {
|
|
979
|
+
return JSON.stringify(item, null, 2);
|
|
980
|
+
}
|
|
981
|
+
return formatInfo(item);
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
// src/commands/info.ts
|
|
985
|
+
async function runInfoCommand2(componentName, options = {}, runtime = defaultRuntime4) {
|
|
986
|
+
return runInfoCommand(componentName, options, runtime);
|
|
987
|
+
}
|
|
988
|
+
function createInfoAction(deps = {
|
|
989
|
+
runInfoCommand: runInfoCommand2,
|
|
990
|
+
error: console.error,
|
|
991
|
+
log: console.log,
|
|
992
|
+
exit: process.exit
|
|
993
|
+
}) {
|
|
994
|
+
return async (componentName, options = {}) => {
|
|
995
|
+
try {
|
|
996
|
+
const output = await deps.runInfoCommand(componentName, { json: options.json === true });
|
|
997
|
+
if (output.length > 0) {
|
|
998
|
+
deps.log(output);
|
|
999
|
+
}
|
|
1000
|
+
} catch (error) {
|
|
1001
|
+
deps.error(chalk6.red("\nInfo failed:"));
|
|
1002
|
+
if (error instanceof Error) {
|
|
1003
|
+
deps.error(chalk6.gray(error.message));
|
|
1004
|
+
} else {
|
|
1005
|
+
deps.error(chalk6.gray(String(error)));
|
|
1006
|
+
}
|
|
1007
|
+
deps.exit(1);
|
|
1008
|
+
}
|
|
1009
|
+
};
|
|
1010
|
+
}
|
|
1011
|
+
var info = new Command4().name("info").description("Show details for a single registry component").argument("<component>", "The exact component name to inspect").option("--json", "Output the validated registry item as JSON", false).action(createInfoAction());
|
|
1012
|
+
|
|
1013
|
+
// src/commands/doctor.ts
|
|
1014
|
+
import chalk7 from "chalk";
|
|
1015
|
+
import { Command as Command5 } from "commander";
|
|
1016
|
+
|
|
1017
|
+
// src/utils/doctor/format-doctor.ts
|
|
1018
|
+
function formatCheck(check) {
|
|
1019
|
+
const icon = check.status === "ok" ? "\u2713" : check.status === "warn" ? "!" : "\u2716";
|
|
1020
|
+
const lines = [`${icon} ${check.id} \u2014 ${check.summary}`];
|
|
1021
|
+
if (check.detail) {
|
|
1022
|
+
lines.push(` Next step: ${check.detail}`);
|
|
1023
|
+
}
|
|
1024
|
+
return lines.join("\n");
|
|
1025
|
+
}
|
|
1026
|
+
function formatDoctorReport(report) {
|
|
1027
|
+
const aliasLine = report.config.aliases ? `- Aliases: components=${report.config.aliases.components}, utils=${report.config.aliases.utils}` : "- Aliases: missing";
|
|
1028
|
+
return [
|
|
1029
|
+
"Glass UI Doctor",
|
|
1030
|
+
`Project root: ${report.projectRoot}`,
|
|
1031
|
+
`Init readiness: ${report.readiness.init ? "ready" : "not ready"}`,
|
|
1032
|
+
`Add readiness: ${report.readiness.add ? "ready" : "not ready"}`,
|
|
1033
|
+
"",
|
|
1034
|
+
"Detected state:",
|
|
1035
|
+
`- Framework: ${report.framework.value} (${report.framework.source})`,
|
|
1036
|
+
`- Package manager: ${report.packageManager.value} (${report.packageManager.source})`,
|
|
1037
|
+
`- Config: ${report.config.path} (${report.config.source})`,
|
|
1038
|
+
`- CSS: ${report.css.path ?? "unresolved"} (${report.css.source}, ${report.css.exists ? "exists" : "missing"})`,
|
|
1039
|
+
aliasLine,
|
|
1040
|
+
"",
|
|
1041
|
+
"Checks:",
|
|
1042
|
+
...report.checks.map(formatCheck)
|
|
1043
|
+
].join("\n");
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
// src/utils/doctor/inspect-project.ts
|
|
1047
|
+
import { z as z2 } from "zod";
|
|
1048
|
+
var configSchema = z2.object({
|
|
1049
|
+
framework: z2.enum(["react", "next", "vite", "astro", "remix", "unknown"]),
|
|
1050
|
+
style: z2.string(),
|
|
1051
|
+
css: z2.string().min(1),
|
|
1052
|
+
aliases: z2.object({
|
|
1053
|
+
components: z2.string().min(1),
|
|
1054
|
+
utils: z2.string().min(1)
|
|
1055
|
+
})
|
|
574
1056
|
});
|
|
1057
|
+
function buildChecks(report) {
|
|
1058
|
+
const checks = [];
|
|
1059
|
+
if (report.config.source === "detected") {
|
|
1060
|
+
checks.push({ id: "config", status: "ok", summary: "Found valid glass.config.json." });
|
|
1061
|
+
} else if (report.config.source === "missing") {
|
|
1062
|
+
checks.push({
|
|
1063
|
+
id: "config",
|
|
1064
|
+
status: "error",
|
|
1065
|
+
summary: "glass.config.json was not found.",
|
|
1066
|
+
detail: "Run `glass-ui init` in this project before running `glass-ui add`."
|
|
1067
|
+
});
|
|
1068
|
+
} else {
|
|
1069
|
+
checks.push({
|
|
1070
|
+
id: "config",
|
|
1071
|
+
status: "error",
|
|
1072
|
+
summary: "glass.config.json is invalid.",
|
|
1073
|
+
detail: report.config.error ?? "Fix the config JSON and try doctor again."
|
|
1074
|
+
});
|
|
1075
|
+
}
|
|
1076
|
+
if (report.framework.source === "detected") {
|
|
1077
|
+
checks.push({
|
|
1078
|
+
id: "framework",
|
|
1079
|
+
status: "ok",
|
|
1080
|
+
summary: `Detected framework: ${report.framework.value}.`
|
|
1081
|
+
});
|
|
1082
|
+
} else {
|
|
1083
|
+
checks.push({
|
|
1084
|
+
id: "framework",
|
|
1085
|
+
status: "warn",
|
|
1086
|
+
summary: `Could not confidently detect the framework; using ${report.framework.value}.`,
|
|
1087
|
+
detail: "Check package.json dependencies or pass an explicit framework during `glass-ui init`."
|
|
1088
|
+
});
|
|
1089
|
+
}
|
|
1090
|
+
if (report.packageManager.source === "detected") {
|
|
1091
|
+
checks.push({
|
|
1092
|
+
id: "package-manager",
|
|
1093
|
+
status: "ok",
|
|
1094
|
+
summary: `Detected package manager: ${report.packageManager.value}.`
|
|
1095
|
+
});
|
|
1096
|
+
} else {
|
|
1097
|
+
checks.push({
|
|
1098
|
+
id: "package-manager",
|
|
1099
|
+
status: "warn",
|
|
1100
|
+
summary: `No lockfile found; defaulting to ${report.packageManager.value}.`,
|
|
1101
|
+
detail: "Create or commit the lockfile used by this project to confirm the package manager."
|
|
1102
|
+
});
|
|
1103
|
+
}
|
|
1104
|
+
if (report.css.path && report.css.exists && report.css.source === "detected") {
|
|
1105
|
+
checks.push({
|
|
1106
|
+
id: "css",
|
|
1107
|
+
status: "ok",
|
|
1108
|
+
summary: `CSS file is available at ${report.css.path}.`
|
|
1109
|
+
});
|
|
1110
|
+
} else if (report.css.path && report.css.exists) {
|
|
1111
|
+
checks.push({
|
|
1112
|
+
id: "css",
|
|
1113
|
+
status: "warn",
|
|
1114
|
+
summary: `CSS path was inferred as ${report.css.path}.`,
|
|
1115
|
+
detail: "Save the CSS path in glass.config.json to confirm it explicitly."
|
|
1116
|
+
});
|
|
1117
|
+
} else if (report.css.path) {
|
|
1118
|
+
checks.push({
|
|
1119
|
+
id: "css",
|
|
1120
|
+
status: "warn",
|
|
1121
|
+
summary: `CSS file is missing at ${report.css.path}.`,
|
|
1122
|
+
detail: "Create the CSS file or update glass.config.json to point at the correct path."
|
|
1123
|
+
});
|
|
1124
|
+
} else {
|
|
1125
|
+
checks.push({
|
|
1126
|
+
id: "css",
|
|
1127
|
+
status: "warn",
|
|
1128
|
+
summary: "Could not resolve a CSS file path.",
|
|
1129
|
+
detail: "Set the css field in glass.config.json or rerun `glass-ui init`."
|
|
1130
|
+
});
|
|
1131
|
+
}
|
|
1132
|
+
if (report.config.aliases) {
|
|
1133
|
+
checks.push({
|
|
1134
|
+
id: "aliases",
|
|
1135
|
+
status: "ok",
|
|
1136
|
+
summary: "Detected components and utils aliases."
|
|
1137
|
+
});
|
|
1138
|
+
} else {
|
|
1139
|
+
checks.push({
|
|
1140
|
+
id: "aliases",
|
|
1141
|
+
status: "error",
|
|
1142
|
+
summary: "Components and utils aliases are missing.",
|
|
1143
|
+
detail: "Save both aliases in glass.config.json so generated imports resolve correctly."
|
|
1144
|
+
});
|
|
1145
|
+
}
|
|
1146
|
+
if (report.readiness.add) {
|
|
1147
|
+
checks.push({ id: "add-readiness", status: "ok", summary: "Project is ready for add." });
|
|
1148
|
+
} else {
|
|
1149
|
+
checks.push({
|
|
1150
|
+
id: "add-readiness",
|
|
1151
|
+
status: "error",
|
|
1152
|
+
summary: "Project is not ready for add.",
|
|
1153
|
+
detail: "Create a valid glass.config.json with aliases before adding components."
|
|
1154
|
+
});
|
|
1155
|
+
}
|
|
1156
|
+
return checks;
|
|
1157
|
+
}
|
|
1158
|
+
async function inspectProject(options = {}, runtime) {
|
|
1159
|
+
const projectRoot = resolveProjectRoot(options.cwd, runtime.cwd());
|
|
1160
|
+
if (!runtime.exists(".", projectRoot)) {
|
|
1161
|
+
throw new Error(
|
|
1162
|
+
`Could not inspect directory "${projectRoot}": path does not exist or is not accessible.`
|
|
1163
|
+
);
|
|
1164
|
+
}
|
|
1165
|
+
const hasPackageJson = runtime.exists("package.json", projectRoot);
|
|
1166
|
+
const frameworkValue = await runtime.getFramework(projectRoot);
|
|
1167
|
+
const frameworkSource = hasPackageJson && frameworkValue !== "unknown" ? "detected" : "inferred";
|
|
1168
|
+
const packageManagerValue = await runtime.getPackageManager(projectRoot);
|
|
1169
|
+
const packageManagerSource = getLockfilePath(projectRoot, runtime.exists) ? "detected" : "inferred";
|
|
1170
|
+
let config = {
|
|
1171
|
+
path: "glass.config.json",
|
|
1172
|
+
source: "missing"
|
|
1173
|
+
};
|
|
1174
|
+
if (runtime.exists("glass.config.json", projectRoot)) {
|
|
1175
|
+
try {
|
|
1176
|
+
const parsed = configSchema.parse(
|
|
1177
|
+
JSON.parse(await runtime.readFile("glass.config.json", projectRoot))
|
|
1178
|
+
);
|
|
1179
|
+
config = {
|
|
1180
|
+
path: "glass.config.json",
|
|
1181
|
+
source: "detected",
|
|
1182
|
+
css: parsed.css,
|
|
1183
|
+
aliases: parsed.aliases
|
|
1184
|
+
};
|
|
1185
|
+
} catch (error) {
|
|
1186
|
+
config = {
|
|
1187
|
+
path: "glass.config.json",
|
|
1188
|
+
source: "invalid",
|
|
1189
|
+
error: error instanceof Error ? `Invalid config: ${error.message}` : "Invalid config."
|
|
1190
|
+
};
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
const cssPath = config.css ?? runtime.getCssPath(frameworkValue, projectRoot);
|
|
1194
|
+
const cssSource = config.css ? "detected" : cssPath ? "inferred" : "missing";
|
|
1195
|
+
const cssExists = cssPath ? runtime.exists(cssPath, projectRoot) : false;
|
|
1196
|
+
const readiness = {
|
|
1197
|
+
init: true,
|
|
1198
|
+
add: config.source === "detected" && Boolean(config.aliases?.components) && Boolean(config.aliases?.utils)
|
|
1199
|
+
};
|
|
1200
|
+
const reportWithoutChecks = {
|
|
1201
|
+
projectRoot,
|
|
1202
|
+
framework: { value: frameworkValue, source: frameworkSource },
|
|
1203
|
+
packageManager: { value: packageManagerValue, source: packageManagerSource },
|
|
1204
|
+
config,
|
|
1205
|
+
css: {
|
|
1206
|
+
path: cssPath,
|
|
1207
|
+
exists: cssExists,
|
|
1208
|
+
source: cssSource
|
|
1209
|
+
},
|
|
1210
|
+
readiness
|
|
1211
|
+
};
|
|
1212
|
+
return {
|
|
1213
|
+
...reportWithoutChecks,
|
|
1214
|
+
checks: buildChecks(reportWithoutChecks)
|
|
1215
|
+
};
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
// src/utils/doctor/run-doctor.ts
|
|
1219
|
+
var defaultRuntime5 = {
|
|
1220
|
+
cwd: () => process.cwd(),
|
|
1221
|
+
exists,
|
|
1222
|
+
readFile,
|
|
1223
|
+
writeFile,
|
|
1224
|
+
getFramework,
|
|
1225
|
+
getPackageManager,
|
|
1226
|
+
getCssPath,
|
|
1227
|
+
installDependencies,
|
|
1228
|
+
inspectProject,
|
|
1229
|
+
formatDoctorReport
|
|
1230
|
+
};
|
|
1231
|
+
async function runDoctorCommand(options = {}, runtime = defaultRuntime5) {
|
|
1232
|
+
const resolvedRuntime = { ...defaultRuntime5, ...runtime };
|
|
1233
|
+
const report = await resolvedRuntime.inspectProject(options, resolvedRuntime);
|
|
1234
|
+
if (options.json) {
|
|
1235
|
+
return JSON.stringify(report, null, 2);
|
|
1236
|
+
}
|
|
1237
|
+
return resolvedRuntime.formatDoctorReport(report);
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
// src/commands/doctor.ts
|
|
1241
|
+
async function runDoctorCommand2(options = {}, runtime = defaultRuntime5) {
|
|
1242
|
+
return runDoctorCommand(options, runtime);
|
|
1243
|
+
}
|
|
1244
|
+
function createDoctorAction(deps = {
|
|
1245
|
+
runDoctorCommand: runDoctorCommand2,
|
|
1246
|
+
error: console.error,
|
|
1247
|
+
log: console.log,
|
|
1248
|
+
exit: process.exit
|
|
1249
|
+
}) {
|
|
1250
|
+
return async (options = {}) => {
|
|
1251
|
+
try {
|
|
1252
|
+
const output = await deps.runDoctorCommand({ cwd: options.cwd, json: options.json === true });
|
|
1253
|
+
if (output.length > 0) {
|
|
1254
|
+
deps.log(output);
|
|
1255
|
+
}
|
|
1256
|
+
} catch (error) {
|
|
1257
|
+
deps.error(chalk7.red("\nDoctor failed:"));
|
|
1258
|
+
if (error instanceof Error) {
|
|
1259
|
+
deps.error(chalk7.gray(error.message));
|
|
1260
|
+
} else {
|
|
1261
|
+
deps.error(chalk7.gray(String(error)));
|
|
1262
|
+
}
|
|
1263
|
+
deps.exit(1);
|
|
1264
|
+
}
|
|
1265
|
+
};
|
|
1266
|
+
}
|
|
1267
|
+
var doctor = new Command5().name("doctor").description("Diagnose whether a project is ready to use the CLI").option("--cwd <path>", "Run the command against another project directory").option("--json", "Output the diagnostic report as JSON", false).action(createDoctorAction());
|
|
575
1268
|
|
|
576
1269
|
// src/index.ts
|
|
577
|
-
var program = new
|
|
578
|
-
program.name("glass-ui").description("The Glass UI CLI - Add glassmorphism components to your app").version("0.
|
|
1270
|
+
var program = new Command6();
|
|
1271
|
+
program.name("glass-ui").description("The Glass UI CLI - Add glassmorphism components to your app").version("0.2.5");
|
|
579
1272
|
program.addCommand(init);
|
|
580
1273
|
program.addCommand(add);
|
|
1274
|
+
program.addCommand(list);
|
|
1275
|
+
program.addCommand(info);
|
|
1276
|
+
program.addCommand(doctor);
|
|
581
1277
|
program.parse(process.argv);
|