@glass-ui-kit/cli 0.2.4 → 0.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +44 -44
  3. package/dist/index.js +476 -171
  4. package/package.json +60 -59
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Juan Tellez
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,44 +1,44 @@
1
- # @glass-ui-kit/cli
2
-
3
- The official CLI for Glass UI. Add premium glassmorphism components to your React projects in seconds.
4
-
5
- ## init
6
-
7
- Use the `init` command to initialize the project configuration.
8
-
9
- The `init` command:
10
-
11
- 1. Creates a `glass.config.json` file.
12
- 2. Generates the `cn` utility at `src/lib/utils.ts`.
13
- 3. Installs necessary dependencies (`clsx`, `tailwind-merge`).
14
- 4. Injects Glass Physics variables into your global CSS.
15
-
16
- ```bash
17
- npx @glass-ui-kit/cli init
18
-
19
- ```
20
-
21
- ## add
22
-
23
- Use the `add` command to add components to your project.
24
- The `add` command downloads the component source code and places it directly into your project.
25
-
26
- ```bash
27
- npx @glass-ui-kit/cli add [component]
28
-
29
- ```
30
-
31
- ### Example
32
-
33
- ```bash
34
- npx @glass-ui-kit/cli add card
35
-
36
- ```
37
-
38
- ## Documentation
39
-
40
- Visit [https://ui-glass.vercel.app](https://ui-glass.vercel.app) to view the registry and documentation.
41
-
42
- ## License
43
-
44
- Licensed under the [MIT license](https://github.com/jntellez/glass-ui/blob/master/LICENSE).
1
+ # @glass-ui-kit/cli
2
+
3
+ The official CLI for Glass UI. Add premium glassmorphism components to your React projects in seconds.
4
+
5
+ ## init
6
+
7
+ Use the `init` command to initialize the project configuration.
8
+
9
+ The `init` command:
10
+
11
+ 1. Creates a `glass.config.json` file.
12
+ 2. Generates the `cn` utility at `src/lib/utils.ts`.
13
+ 3. Installs necessary dependencies (`clsx`, `tailwind-merge`).
14
+ 4. Injects Glass Physics variables into your global CSS.
15
+
16
+ ```bash
17
+ npx @glass-ui-kit/cli init
18
+
19
+ ```
20
+
21
+ ## add
22
+
23
+ Use the `add` command to add components to your project.
24
+ The `add` command downloads the component source code and places it directly into your project.
25
+
26
+ ```bash
27
+ npx @glass-ui-kit/cli add [component]
28
+
29
+ ```
30
+
31
+ ### Example
32
+
33
+ ```bash
34
+ npx @glass-ui-kit/cli add card
35
+
36
+ ```
37
+
38
+ ## Documentation
39
+
40
+ Visit [https://ui-glass.vercel.app](https://ui-glass.vercel.app) to view the registry and documentation.
41
+
42
+ ## License
43
+
44
+ Licensed under the [MIT license](https://github.com/jntellez/glass-ui/blob/master/LICENSE).
package/dist/index.js CHANGED
@@ -4,45 +4,135 @@
4
4
  import { Command as Command3 } from "commander";
5
5
 
6
6
  // src/commands/init.ts
7
- import chalk from "chalk";
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 path from "path";
14
- async function writeFile(filePath, content) {
15
- const absolutePath = path.resolve(process.cwd(), filePath);
16
- const dir = path.dirname(absolutePath);
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 = path.resolve(process.cwd(), filePath);
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(path.resolve(process.cwd(), filePath));
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
35
125
  import { spawn } from "child_process";
36
- async function getPackageManager() {
37
- if (exists("bun.lockb")) return "bun";
38
- if (exists("pnpm-lock.yaml")) return "pnpm";
39
- if (exists("yarn.lock")) return "yarn";
126
+ async function getPackageManager(projectRoot = process.cwd()) {
127
+ if (exists("bun.lockb", projectRoot)) return "bun";
128
+ if (exists("pnpm-lock.yaml", projectRoot)) return "pnpm";
129
+ if (exists("yarn.lock", projectRoot)) return "yarn";
40
130
  return "npm";
41
131
  }
42
- async function getFramework() {
43
- if (!exists("package.json")) return "unknown";
132
+ async function getFramework(projectRoot = process.cwd()) {
133
+ if (!exists("package.json", projectRoot)) return "unknown";
44
134
  try {
45
- const content = await readFile("package.json");
135
+ const content = await readFile("package.json", projectRoot);
46
136
  const pkg = JSON.parse(content);
47
137
  const deps = { ...pkg.dependencies, ...pkg.devDependencies };
48
138
  if (deps["@remix-run/react"] || deps["@remix-run/dev"] || deps["@react-router/dev"] || deps["@react-router/node"]) {
@@ -56,29 +146,26 @@ async function getFramework() {
56
146
  }
57
147
  return "unknown";
58
148
  }
59
- function getCssPath(framework) {
149
+ function getCssPath(framework, projectRoot = process.cwd()) {
60
150
  const paths = {
151
+ react: ["src/index.css", "src/main.css", "src/style.css", "index.css"],
61
152
  next: ["app/globals.css", "src/app/globals.css", "styles/globals.css"],
62
153
  vite: ["src/index.css", "src/main.css", "src/style.css"],
63
154
  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
- ],
155
+ remix: ["app/app.css", "app/tailwind.css", "app/globals.css", "app/styles/tailwind.css"],
70
156
  unknown: ["src/index.css", "styles.css"]
71
157
  };
72
158
  for (const p of paths[framework] || []) {
73
- if (exists(p)) return p;
159
+ if (exists(p, projectRoot)) return p;
74
160
  }
75
161
  return paths[framework]?.[0] || null;
76
162
  }
77
- async function installDependencies(deps, pm) {
163
+ async function installDependencies(deps, pm, projectRoot = process.cwd()) {
78
164
  const installCmd = pm === "npm" ? "install" : "add";
79
165
  console.log(`Running ${pm} ${installCmd}...`);
80
166
  return new Promise((resolve, reject) => {
81
167
  const child = spawn(pm, [installCmd, ...deps], {
168
+ cwd: projectRoot,
82
169
  stdio: "inherit",
83
170
  shell: true
84
171
  });
@@ -287,113 +374,152 @@ export function cn(...inputs: ClassValue[]) {
287
374
  }
288
375
  `;
289
376
 
290
- // src/commands/init.ts
291
- var init = new Command().name("init").description("Initialize configuration and dependencies").option("-y, --yes", "Skip confirmation prompt", false).action(async (opts) => {
292
- try {
293
- console.log(chalk.bold("\nInitializing Glass UI..."));
294
- const framework = await getFramework();
295
- const pm = await getPackageManager();
296
- const cwd = process.cwd();
297
- const configPath = "glass.config.json";
298
- const hasSrc = exists("src");
299
- let baseDir = hasSrc ? "src" : "";
300
- if (framework === "remix") {
301
- baseDir = "app";
302
- }
303
- let cssPath = getCssPath(framework);
304
- if (!cssPath || framework === "next") {
305
- if (framework === "next") {
306
- cssPath = hasSrc ? "src/app/globals.css" : "app/globals.css";
307
- } else if (framework === "remix") {
308
- cssPath = "app/app.css";
309
- } else {
310
- cssPath = hasSrc ? "src/index.css" : "index.css";
311
- }
312
- }
313
- const utilsRelativePath = baseDir ? `${baseDir}/lib/utils.ts` : "lib/utils.ts";
314
- const utilsPath = path2.join(cwd, utilsRelativePath);
315
- const aliasPrefix = framework === "remix" ? "~" : "@";
316
- if (!exists(configPath)) {
317
- await writeFile(
318
- configPath,
319
- JSON.stringify(
320
- {
321
- framework,
322
- style: "default",
323
- css: cssPath,
324
- aliases: {
325
- components: `${aliasPrefix}/components/ui`,
326
- utils: `${aliasPrefix}/lib/utils`
327
- }
328
- },
329
- null,
330
- 2
331
- )
332
- );
333
- console.log(chalk.green(" Created glass.config.json"));
334
- } else {
335
- console.log(chalk.gray(" glass.config.json already exists."));
336
- }
337
- if (!exists(utilsPath)) {
338
- await writeFile(utilsPath, UTILS_CN);
339
- console.log(chalk.green(` Created ${utilsRelativePath}`));
340
- } else {
341
- console.log(chalk.gray(` ${utilsRelativePath} already exists.`));
377
+ // src/utils/init/run-init.ts
378
+ var defaultRuntime = {
379
+ cwd: () => process.cwd(),
380
+ exists,
381
+ readFile,
382
+ writeFile,
383
+ getFramework,
384
+ getPackageManager,
385
+ getCssPath,
386
+ installDependencies,
387
+ log: console.log
388
+ };
389
+ async function runInitCommand(options = {}, runtime = defaultRuntime) {
390
+ const resolvedOptions = resolveInitOptions(options, runtime.cwd());
391
+ runtime.log(chalk.bold("\nInitializing Glass UI..."));
392
+ const framework = resolvedOptions.frameworkOverride ?? await runtime.getFramework(resolvedOptions.projectRoot);
393
+ const pm = await runtime.getPackageManager(resolvedOptions.projectRoot);
394
+ const configPath = "glass.config.json";
395
+ const hasSrc = runtime.exists("src", resolvedOptions.projectRoot);
396
+ const plan = resolveInitPaths({
397
+ projectRoot: resolvedOptions.projectRoot,
398
+ hasSrc,
399
+ framework,
400
+ detectedCssPath: runtime.getCssPath(framework, resolvedOptions.projectRoot),
401
+ cssOverride: resolvedOptions.cssOverride,
402
+ componentsAliasOverride: resolvedOptions.componentsAliasOverride,
403
+ utilsAliasOverride: resolvedOptions.utilsAliasOverride
404
+ });
405
+ const writes = [
406
+ {
407
+ filePath: configPath,
408
+ content: JSON.stringify(plan.config, null, 2),
409
+ label: "glass.config.json"
410
+ },
411
+ {
412
+ filePath: plan.utilsRelativePath,
413
+ content: UTILS_CN,
414
+ label: plan.utilsRelativePath
415
+ },
416
+ {
417
+ filePath: plan.cssPath,
418
+ content: `@import "tailwindcss";
419
+
420
+ ${GLASS_BASE_STYLES}`,
421
+ label: plan.cssPath
342
422
  }
343
- let cssContent = "";
344
- try {
345
- if (exists(cssPath)) {
346
- cssContent = await readFile(cssPath);
347
- } else {
348
- console.log(chalk.yellow(` Creating new CSS file at ${cssPath}`));
349
- }
350
- } catch (e) {
423
+ ];
424
+ for (const write of writes.slice(0, 2)) {
425
+ const alreadyExists = runtime.exists(write.filePath, resolvedOptions.projectRoot);
426
+ if (alreadyExists && !resolvedOptions.force) {
427
+ runtime.log(chalk.yellow(` Skipped ${write.label} (already exists)`));
428
+ continue;
351
429
  }
352
- if (!cssContent.includes("--glass-bg")) {
353
- const newCssContent = `@import "tailwindcss";
354
-
355
- ${GLASS_BASE_STYLES}`;
356
- await writeFile(cssPath, newCssContent);
357
- console.log(
358
- chalk.green(
359
- ` Overwrote ${cssPath} with Tailwind import and glass tokens`
360
- )
361
- );
430
+ await runtime.writeFile(write.filePath, write.content, resolvedOptions.projectRoot);
431
+ runtime.log(
432
+ alreadyExists ? chalk.green(` Replaced ${write.label}`) : chalk.green(` Created ${write.label}`)
433
+ );
434
+ }
435
+ const cssWrite = writes[2];
436
+ const cssAlreadyExists = runtime.exists(cssWrite.filePath, resolvedOptions.projectRoot);
437
+ if (!cssAlreadyExists) {
438
+ await runtime.writeFile(cssWrite.filePath, cssWrite.content, resolvedOptions.projectRoot);
439
+ runtime.log(chalk.green(` Created ${cssWrite.label}`));
440
+ } else if (resolvedOptions.force) {
441
+ await runtime.writeFile(cssWrite.filePath, cssWrite.content, resolvedOptions.projectRoot);
442
+ runtime.log(chalk.green(` Replaced ${cssWrite.label}`));
443
+ } else {
444
+ const existingCss = await runtime.readFile(cssWrite.filePath, resolvedOptions.projectRoot) ?? "";
445
+ if (existingCss.includes("--glass-bg")) {
446
+ runtime.log(chalk.yellow(` Skipped ${cssWrite.label} (already exists)`));
362
447
  } else {
363
- console.log(chalk.gray(` Tokens already present in ${cssPath}`));
448
+ const updatedCss = `${existingCss.trimEnd()}
449
+
450
+ ${GLASS_BASE_STYLES}
451
+ `;
452
+ await runtime.writeFile(cssWrite.filePath, updatedCss, resolvedOptions.projectRoot);
453
+ runtime.log(chalk.green(` Updated ${cssWrite.label} with Glass tokens`));
364
454
  }
365
- console.log(
366
- chalk.cyan(
367
- ` Installing dependencies (clsx, tailwind-merge, lucide-react)...`
368
- )
369
- );
370
- await installDependencies(["clsx", "tailwind-merge", "lucide-react"], pm);
371
- const runCommand = pm === "bun" ? "bunx" : pm === "pnpm" ? "pnpm dlx" : "npx";
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`)
455
+ }
456
+ if (resolvedOptions.install) {
457
+ runtime.log(chalk.cyan(` Installing dependencies (clsx, tailwind-merge, lucide-react)...`));
458
+ await runtime.installDependencies(
459
+ ["clsx", "tailwind-merge", "lucide-react"],
460
+ pm,
461
+ resolvedOptions.projectRoot
377
462
  );
378
- console.log("");
463
+ } else {
464
+ runtime.log(chalk.gray(" Skipped dependency installation (--no-install)."));
465
+ }
466
+ const runCommand = pm === "bun" ? "bunx" : pm === "pnpm" ? "pnpm dlx" : "npx";
467
+ runtime.log(chalk.bold.green("\nSetup complete."));
468
+ runtime.log(chalk.gray(` Project root: ${resolvedOptions.projectRoot}`));
469
+ runtime.log(chalk.gray(` Framework: ${plan.framework}`));
470
+ runtime.log("Try adding a component:\n");
471
+ runtime.log(chalk.cyan(` ${runCommand} @glass-ui-kit/cli@latest add card`));
472
+ runtime.log("");
473
+ }
474
+
475
+ // src/commands/init.ts
476
+ var InitCommandError = class extends Error {
477
+ };
478
+ async function runInitCommand2(options = {}, runtime = defaultRuntime) {
479
+ try {
480
+ await runInitCommand(options, runtime);
379
481
  } catch (error) {
380
- console.error(chalk.red("\nInitialization failed:"));
381
482
  if (error instanceof Error) {
382
- console.error(chalk.gray(error.message));
383
- } else {
384
- console.error(chalk.gray(String(error)));
483
+ throw new InitCommandError(error.message);
385
484
  }
386
- process.exit(1);
485
+ throw new InitCommandError(String(error));
387
486
  }
388
- });
487
+ }
488
+ function createInitAction(deps = {
489
+ runInitCommand: runInitCommand2,
490
+ error: console.error,
491
+ exit: process.exit
492
+ }) {
493
+ return async (options = {}) => {
494
+ try {
495
+ await deps.runInitCommand({
496
+ cwd: options.cwd,
497
+ css: options.css,
498
+ components: options.components,
499
+ utils: options.utils,
500
+ framework: options.framework,
501
+ force: options.force === true,
502
+ install: options.install
503
+ });
504
+ } catch (error) {
505
+ deps.error(chalk2.red("\nInitialization failed:"));
506
+ if (error instanceof Error) {
507
+ deps.error(chalk2.gray(error.message));
508
+ } else {
509
+ deps.error(chalk2.gray(String(error)));
510
+ }
511
+ deps.exit(1);
512
+ }
513
+ };
514
+ }
515
+ 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
516
 
390
517
  // src/commands/add.ts
391
- import chalk2 from "chalk";
518
+ import chalk4 from "chalk";
392
519
  import { Command as Command2 } from "commander";
393
- import path4 from "path";
394
520
 
395
521
  // src/utils/registry.ts
396
- import path3 from "path";
522
+ import path4 from "path";
397
523
  import os from "os";
398
524
  import fs2 from "fs/promises";
399
525
 
@@ -427,8 +553,8 @@ var registryIndexSchema = z.array(registryItemSchema);
427
553
 
428
554
  // src/utils/registry.ts
429
555
  var DEFAULT_REGISTRY_URL = "https://ui-glass.vercel.app/registry.json";
430
- var CACHE_DIR = path3.join(os.homedir(), ".glass-ui");
431
- var CACHE_FILE = path3.join(CACHE_DIR, "registry.json");
556
+ var CACHE_DIR = path4.join(os.homedir(), ".glass-ui");
557
+ var CACHE_FILE = path4.join(CACHE_DIR, "registry.json");
432
558
  var CACHE_TTL = 1e3 * 60 * 60 * 24;
433
559
  function getRegistryUrl() {
434
560
  return process.env.GLASS_UI_REGISTRY_URL || DEFAULT_REGISTRY_URL;
@@ -469,14 +595,10 @@ async function fetchRegistry() {
469
595
  try {
470
596
  response = await fetch(url);
471
597
  } catch (error) {
472
- throw new Error(
473
- "Network error: Unable to connect to registry. Check your internet connection."
474
- );
598
+ throw new Error("Network error: Unable to connect to registry. Check your internet connection.");
475
599
  }
476
600
  if (!response.ok) {
477
- throw new Error(
478
- `Registry unavailable. URL: ${url} (Status: ${response.status})`
479
- );
601
+ throw new Error(`Registry unavailable. URL: ${url} (Status: ${response.status})`);
480
602
  }
481
603
  let data;
482
604
  try {
@@ -497,6 +619,85 @@ async function fetchRegistry() {
497
619
  function getItem(registry, name) {
498
620
  return registry.find((item) => item.name === name);
499
621
  }
622
+ function getItems(registry, names) {
623
+ const items = [];
624
+ const missing = [];
625
+ for (const name of names) {
626
+ const item = getItem(registry, name);
627
+ if (item) {
628
+ items.push(item);
629
+ continue;
630
+ }
631
+ missing.push(name);
632
+ }
633
+ return { items, missing };
634
+ }
635
+
636
+ // src/utils/add/selection.ts
637
+ function dedupeNames(names) {
638
+ const uniqueNames = [];
639
+ const seen = /* @__PURE__ */ new Set();
640
+ for (const name of names) {
641
+ if (seen.has(name)) {
642
+ continue;
643
+ }
644
+ seen.add(name);
645
+ uniqueNames.push(name);
646
+ }
647
+ return uniqueNames;
648
+ }
649
+ function resolveAddSelection(registry, requestedNames, selectAll) {
650
+ if (selectAll && requestedNames.length > 0) {
651
+ return { ok: false, reason: "invalid-combination" };
652
+ }
653
+ if (selectAll) {
654
+ return { ok: true, items: registry };
655
+ }
656
+ const uniqueRequestedNames = dedupeNames(requestedNames);
657
+ if (uniqueRequestedNames.length === 0) {
658
+ return { ok: false, reason: "empty-selection" };
659
+ }
660
+ const { items, missing } = getItems(registry, uniqueRequestedNames);
661
+ if (missing.length > 0) {
662
+ return { ok: false, reason: "missing-components", names: missing };
663
+ }
664
+ return { ok: true, items };
665
+ }
666
+
667
+ // src/utils/add/dependencies.ts
668
+ function collectDependencies(items) {
669
+ const dependencies = [];
670
+ const seen = /* @__PURE__ */ new Set();
671
+ for (const item of items) {
672
+ for (const dependency of item.dependencies || []) {
673
+ if (seen.has(dependency)) {
674
+ continue;
675
+ }
676
+ seen.add(dependency);
677
+ dependencies.push(dependency);
678
+ }
679
+ }
680
+ return dependencies;
681
+ }
682
+
683
+ // src/utils/add/paths.ts
684
+ function resolveTargetDir(config, hasSrc, explicitPath) {
685
+ if (explicitPath) {
686
+ return explicitPath;
687
+ }
688
+ const targetDirAlias = config.aliases.components || "@/components/ui";
689
+ const relativeAliasPath = targetDirAlias.replace(/^[@~]\//, "");
690
+ if (config.framework === "remix") {
691
+ return `./app/${relativeAliasPath}`;
692
+ }
693
+ if (hasSrc) {
694
+ return `./src/${relativeAliasPath}`;
695
+ }
696
+ return `./${relativeAliasPath}`;
697
+ }
698
+
699
+ // src/utils/add/planner.ts
700
+ import path5 from "path";
500
701
 
501
702
  // src/utils/transformers.ts
502
703
  function transformImports(content, config) {
@@ -519,63 +720,167 @@ function transformImports(content, config) {
519
720
  return transformed;
520
721
  }
521
722
 
522
- // src/commands/add.ts
523
- var add = new Command2().name("add").description("Add a component to your project").argument("<component>", "The component to add").action(async (componentName) => {
524
- try {
525
- if (!exists("glass.config.json")) {
526
- console.error(chalk2.red("Configuration file not found."));
527
- console.log(chalk2.gray("Please run the init command first:"));
528
- console.log(chalk2.cyan(" npx @glass-ui-kit/cli@latest init"));
529
- process.exit(1);
530
- }
531
- const config = JSON.parse(await readFile("glass.config.json"));
532
- const pm = await getPackageManager();
533
- console.log(chalk2.bold(`Fetching component: ${componentName}...`));
534
- const registry = await fetchRegistry();
535
- const item = getItem(registry, componentName);
536
- if (!item) {
537
- console.error(chalk2.red(`Component '${componentName}' not found.`));
538
- process.exit(1);
723
+ // src/utils/add/planner.ts
724
+ function buildWritePlan(items, config, hasSrc, options) {
725
+ const targetDir = resolveTargetDir(config, hasSrc, options.path);
726
+ return items.flatMap(
727
+ (item) => item.files.filter((file) => Boolean(file.content)).map((file) => {
728
+ const fileName = path5.basename(file.path);
729
+ const filePath = path5.join(targetDir, fileName);
730
+ return {
731
+ filePath,
732
+ content: transformImports(file.content || "", config),
733
+ action: !options.overwrite && options.exists(filePath) ? "skip-existing" : "write"
734
+ };
735
+ })
736
+ );
737
+ }
738
+
739
+ // src/utils/add/run-add.ts
740
+ import path6 from "path";
741
+ import chalk3 from "chalk";
742
+ var defaultRuntime2 = {
743
+ cwd: () => process.cwd(),
744
+ exists,
745
+ readFile,
746
+ writeFile,
747
+ fetchRegistry,
748
+ getPackageManager,
749
+ installDependencies,
750
+ log: console.log
751
+ };
752
+ function buildMissingComponentsMessage(names) {
753
+ return `Components not found: ${names.join(", ")}.`;
754
+ }
755
+ function resolveProjectRoot(cwdOption, cwd) {
756
+ return cwdOption ? path6.resolve(cwd, cwdOption) : cwd;
757
+ }
758
+ async function runAddCommand(componentNames = [], options = {}, runtime = defaultRuntime2) {
759
+ const projectRoot = resolveProjectRoot(options.cwd, runtime.cwd?.() ?? process.cwd());
760
+ if (options.depsOnly && options.install === false) {
761
+ throw new Error("invalid-install-combination");
762
+ }
763
+ if (!runtime.exists("glass.config.json", projectRoot)) {
764
+ throw new Error("config-not-found");
765
+ }
766
+ const config = JSON.parse(await runtime.readFile("glass.config.json", projectRoot));
767
+ runtime.log(chalk3.bold("Fetching components..."));
768
+ const registry = await runtime.fetchRegistry();
769
+ const selection = resolveAddSelection(registry, componentNames, Boolean(options.all));
770
+ if (!selection.ok) {
771
+ if (selection.reason === "invalid-combination") {
772
+ throw new Error("invalid-combination");
539
773
  }
540
- const hasSrc = exists("src");
541
- const targetDirAlias = config.aliases.components || "@/components/ui";
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}`;
774
+ if (selection.reason === "missing-components") {
775
+ throw new Error(buildMissingComponentsMessage(selection.names || []));
550
776
  }
551
- for (const file of item.files) {
552
- const fileName = path4.basename(file.path);
553
- const filePath = path4.join(targetDir, fileName);
554
- if (!file.content) {
777
+ throw new Error("empty-selection");
778
+ }
779
+ const shouldWrite = options.depsOnly !== true;
780
+ const shouldInstall = options.install !== false;
781
+ if (shouldWrite) {
782
+ const plannedWrites = buildWritePlan(
783
+ selection.items,
784
+ config,
785
+ runtime.exists("src", projectRoot),
786
+ {
787
+ exists: (filePath) => runtime.exists(filePath, projectRoot),
788
+ overwrite: options.overwrite,
789
+ path: options.path
790
+ }
791
+ );
792
+ for (const file of plannedWrites) {
793
+ if (file.action === "skip-existing") {
794
+ runtime.log(chalk3.yellow(` Skipped ${file.filePath} (already exists)`));
555
795
  continue;
556
796
  }
557
- const transformedContent = transformImports(file.content, config);
558
- await writeFile(filePath, transformedContent);
559
- console.log(chalk2.green(` Created ${filePath}`));
797
+ await runtime.writeFile(file.filePath, file.content, projectRoot);
798
+ runtime.log(chalk3.green(` Created ${file.filePath}`));
560
799
  }
561
- if (item.dependencies?.length) {
562
- console.log(chalk2.cyan(` Installing dependencies...`));
563
- await installDependencies(item.dependencies, pm);
564
- }
565
- console.log(chalk2.bold.green(`
566
- Done.`));
800
+ }
801
+ const dependencies = collectDependencies(selection.items);
802
+ if (shouldInstall && dependencies.length > 0) {
803
+ const pm = await runtime.getPackageManager(projectRoot);
804
+ runtime.log(chalk3.cyan(" Installing dependencies..."));
805
+ await runtime.installDependencies(dependencies, pm, projectRoot);
806
+ }
807
+ runtime.log(chalk3.bold.green("\nDone."));
808
+ }
809
+
810
+ // src/commands/add.ts
811
+ var AddCommandError = class extends Error {
812
+ constructor(code, message) {
813
+ super(message);
814
+ this.code = code;
815
+ }
816
+ code;
817
+ };
818
+ async function runAddCommand2(componentNames = [], options = {}, runtime = defaultRuntime2) {
819
+ try {
820
+ await runAddCommand(componentNames, options, runtime);
567
821
  } catch (error) {
568
- console.error(chalk2.red("\nOperation failed:"));
569
822
  if (error instanceof Error) {
570
- console.error(chalk2.gray(` ${error.message}`));
823
+ if (error.message === "config-not-found") {
824
+ throw new AddCommandError("config-not-found", "Configuration file not found.");
825
+ }
826
+ if (error.message === "invalid-combination") {
827
+ throw new AddCommandError(
828
+ "invalid-combination",
829
+ "Cannot combine --all with specific component names."
830
+ );
831
+ }
832
+ if (error.message === "invalid-install-combination") {
833
+ throw new AddCommandError(
834
+ "invalid-combination",
835
+ "Cannot combine --deps-only with --no-install."
836
+ );
837
+ }
838
+ if (error.message === "empty-selection") {
839
+ throw new AddCommandError(
840
+ "empty-selection",
841
+ "Please specify at least one component or use --all."
842
+ );
843
+ }
844
+ if (error.message.startsWith("Components not found:")) {
845
+ throw new AddCommandError("missing-components", error.message);
846
+ }
571
847
  }
572
- process.exit(1);
848
+ throw error;
573
849
  }
574
- });
850
+ }
851
+ function createAddAction(deps = {
852
+ runAddCommand: runAddCommand2,
853
+ error: console.error,
854
+ log: console.log,
855
+ exit: process.exit
856
+ }) {
857
+ return async (componentNames = [], options = {}) => {
858
+ try {
859
+ await deps.runAddCommand(componentNames, options);
860
+ } catch (error) {
861
+ if (error instanceof AddCommandError) {
862
+ if (error.code === "config-not-found") {
863
+ deps.error(chalk4.red(error.message));
864
+ deps.log(chalk4.gray("Please run the init command first:"));
865
+ deps.log(chalk4.cyan(" npx @glass-ui-kit/cli@latest init"));
866
+ deps.exit(1);
867
+ }
868
+ deps.error(chalk4.red(error.message));
869
+ deps.exit(1);
870
+ }
871
+ deps.error(chalk4.red("\nOperation failed:"));
872
+ if (error instanceof Error) {
873
+ deps.error(chalk4.gray(` ${error.message}`));
874
+ }
875
+ deps.exit(1);
876
+ }
877
+ };
878
+ }
879
+ 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());
575
880
 
576
881
  // src/index.ts
577
882
  var program = new Command3();
578
- program.name("glass-ui").description("The Glass UI CLI - Add glassmorphism components to your app").version("0.0.1");
883
+ program.name("glass-ui").description("The Glass UI CLI - Add glassmorphism components to your app").version("0.2.5");
579
884
  program.addCommand(init);
580
885
  program.addCommand(add);
581
886
  program.parse(process.argv);
package/package.json CHANGED
@@ -1,59 +1,60 @@
1
- {
2
- "name": "@glass-ui-kit/cli",
3
- "version": "0.2.4",
4
- "description": "The official CLI for Glass UI. Add glassmorphism components to your React projects in seconds.",
5
- "keywords": [
6
- "glassmorphism",
7
- "design-system",
8
- "react",
9
- "cli",
10
- "ui",
11
- "tailwind"
12
- ],
13
- "homepage": "https://github.com/jntellez/glass-ui#readme",
14
- "bugs": {
15
- "url": "https://github.com/jntellez/glass-ui/issues"
16
- },
17
- "repository": {
18
- "type": "git",
19
- "url": "git+https://github.com/jntellez/glass-ui.git",
20
- "directory": "packages/cli"
21
- },
22
- "license": "MIT",
23
- "author": "Glass UI Team",
24
- "type": "module",
25
- "bin": {
26
- "glass-ui": "./dist/index.js"
27
- },
28
- "files": [
29
- "dist",
30
- "README.md"
31
- ],
32
- "engines": {
33
- "node": ">=18.0.0"
34
- },
35
- "scripts": {
36
- "sync": "node scripts/sync-styles.mjs",
37
- "build": "pnpm run sync && tsup src/index.ts --format esm --clean",
38
- "dev": "pnpm run sync && bun run ./src/index.ts",
39
- "lint": "eslint src",
40
- "prepublishOnly": "pnpm run build"
41
- },
42
- "dependencies": {
43
- "chalk": "^5.3.0",
44
- "commander": "^11.1.0",
45
- "node-fetch": "^3.3.2",
46
- "zod": "^3.22.4"
47
- },
48
- "devDependencies": {
49
- "@glass-ui-kit/tsconfig": "workspace:*",
50
- "@glass-ui-kit/schema": "workspace:*",
51
- "@types/node": "^20.11.0",
52
- "bun-types": "latest",
53
- "tsup": "^8.0.1",
54
- "typescript": "^5.3.3"
55
- },
56
- "publishConfig": {
57
- "access": "public"
58
- }
59
- }
1
+ {
2
+ "name": "@glass-ui-kit/cli",
3
+ "version": "0.2.5",
4
+ "description": "The official CLI for Glass UI. Add glassmorphism components to your React projects in seconds.",
5
+ "keywords": [
6
+ "glassmorphism",
7
+ "design-system",
8
+ "react",
9
+ "cli",
10
+ "ui",
11
+ "tailwind"
12
+ ],
13
+ "homepage": "https://github.com/jntellez/glass-ui#readme",
14
+ "bugs": {
15
+ "url": "https://github.com/jntellez/glass-ui/issues"
16
+ },
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/jntellez/glass-ui.git",
20
+ "directory": "packages/cli"
21
+ },
22
+ "license": "MIT",
23
+ "author": "Glass UI Team",
24
+ "type": "module",
25
+ "bin": {
26
+ "glass-ui": "./dist/index.js"
27
+ },
28
+ "files": [
29
+ "dist",
30
+ "README.md"
31
+ ],
32
+ "engines": {
33
+ "node": ">=18.0.0"
34
+ },
35
+ "dependencies": {
36
+ "chalk": "^5.3.0",
37
+ "commander": "^11.1.0",
38
+ "node-fetch": "^3.3.2",
39
+ "zod": "^3.22.4"
40
+ },
41
+ "devDependencies": {
42
+ "@types/node": "^20.11.0",
43
+ "bun-types": "latest",
44
+ "vitest": "^3.2.4",
45
+ "tsup": "^8.0.1",
46
+ "typescript": "^5.3.3",
47
+ "@glass-ui-kit/schema": "0.0.1",
48
+ "@glass-ui-kit/tsconfig": "0.0.1"
49
+ },
50
+ "publishConfig": {
51
+ "access": "public"
52
+ },
53
+ "scripts": {
54
+ "sync": "node scripts/sync-styles.mjs",
55
+ "build": "pnpm run sync && tsup src/index.ts --format esm --clean",
56
+ "dev": "pnpm run sync && bun run ./src/index.ts",
57
+ "lint": "eslint src",
58
+ "test": "vitest run"
59
+ }
60
+ }