pittaya 0.0.6 → 0.0.9

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/dist/index.js CHANGED
@@ -7,34 +7,80 @@ import { Command } from "commander";
7
7
  import chalk from "chalk";
8
8
  import ora from "ora";
9
9
  import prompts from "prompts";
10
- import path2 from "path";
11
- import fs2 from "fs/promises";
10
+ import path6 from "path";
11
+ import fs6 from "fs/promises";
12
12
  import { execa } from "execa";
13
13
 
14
14
  // src/utils/registry.ts
15
15
  import fetch from "node-fetch";
16
- var REGISTRY_BASE_URL = "https://raw.githubusercontent.com/pittaya-ui/cli/main/registry";
17
- async function fetchRegistry() {
16
+ import fs from "fs/promises";
17
+ import { existsSync } from "fs";
18
+ import path from "path";
19
+ import { fileURLToPath } from "url";
20
+ var DEFAULT_REGISTRY_BASE_URL = "https://raw.githubusercontent.com/pittaya-ui/cli/main/registry";
21
+ function shouldPreferLocalRegistry() {
22
+ const raw = process.env.PITTAYA_REGISTRY_PREFER_LOCAL;
23
+ if (raw == null) return false;
24
+ const normalized = raw.toLowerCase();
25
+ return normalized === "true" || normalized === "1" || normalized === "yes";
26
+ }
27
+ function getLocalRegistryBaseUrl() {
18
28
  try {
19
- const response = await fetch(`${REGISTRY_BASE_URL}/index.json`);
29
+ const filePath = fileURLToPath(import.meta.url);
30
+ const dirPath = path.dirname(filePath);
31
+ const candidate = path.resolve(dirPath, "../../../registry");
32
+ if (!existsSync(candidate)) return null;
33
+ if (!existsSync(path.join(candidate, "styles"))) return null;
34
+ return candidate;
35
+ } catch {
36
+ return null;
37
+ }
38
+ }
39
+ function getRegistryBaseUrl() {
40
+ const localRegistry = shouldPreferLocalRegistry() ? getLocalRegistryBaseUrl() : null;
41
+ if (localRegistry) return localRegistry;
42
+ if (process.env.PITTAYA_REGISTRY_URL) {
43
+ return process.env.PITTAYA_REGISTRY_URL;
44
+ }
45
+ return DEFAULT_REGISTRY_BASE_URL;
46
+ }
47
+ function isHttpUrl(value) {
48
+ return value.startsWith("http://") || value.startsWith("https://");
49
+ }
50
+ async function readJsonFromRegistry(relativePath, config) {
51
+ const baseUrl = getRegistryBaseUrl();
52
+ if (isHttpUrl(baseUrl)) {
53
+ const response = await fetch(`${baseUrl}/${relativePath}`);
20
54
  if (!response.ok) {
21
55
  throw new Error(`HTTP ${response.status}: ${response.statusText}`);
22
56
  }
23
57
  return await response.json();
58
+ }
59
+ const absolutePath = path.resolve(baseUrl, relativePath);
60
+ const raw = await fs.readFile(absolutePath, "utf-8");
61
+ return JSON.parse(raw);
62
+ }
63
+ function getStyleFromConfig(config) {
64
+ return config?.style || "new-york";
65
+ }
66
+ async function fetchRegistry(config) {
67
+ try {
68
+ const style = getStyleFromConfig(config);
69
+ const styleIndex = await readJsonFromRegistry(`styles/${style}/index.json`, config);
70
+ return styleIndex;
24
71
  } catch (error) {
25
72
  console.error("Error fetching registry:", error);
26
73
  throw new Error("Unable to load the registry of components");
27
74
  }
28
75
  }
29
- async function getRegistryComponent(name) {
76
+ async function getRegistryComponent(name, config) {
30
77
  try {
31
- const response = await fetch(
32
- `${REGISTRY_BASE_URL}/components/${name}.json`
78
+ const style = getStyleFromConfig(config);
79
+ const styleComponent = await readJsonFromRegistry(
80
+ `styles/${style}/components/${name}.json`,
81
+ config
33
82
  );
34
- if (!response.ok) {
35
- throw new Error(`Component "${name}" not found in registry`);
36
- }
37
- return await response.json();
83
+ return styleComponent;
38
84
  } catch (error) {
39
85
  throw new Error(`Error loading component "${name}": ${error.message}`);
40
86
  }
@@ -72,21 +118,21 @@ function escapeRegex(str) {
72
118
  }
73
119
 
74
120
  // src/utils/package-manager.ts
75
- import fs from "fs/promises";
76
- import path from "path";
121
+ import fs2 from "fs/promises";
122
+ import path2 from "path";
77
123
  async function detectPackageManager() {
78
124
  try {
79
- await fs.access("pnpm-lock.yaml");
125
+ await fs2.access("pnpm-lock.yaml");
80
126
  return "pnpm";
81
127
  } catch {
82
128
  }
83
129
  try {
84
- await fs.access("yarn.lock");
130
+ await fs2.access("yarn.lock");
85
131
  return "yarn";
86
132
  } catch {
87
133
  }
88
134
  try {
89
- await fs.access("bun.lockb");
135
+ await fs2.access("bun.lockb");
90
136
  return "bun";
91
137
  } catch {
92
138
  }
@@ -95,8 +141,8 @@ async function detectPackageManager() {
95
141
  async function isPackageInstalled(packageName) {
96
142
  const cwd = process.cwd();
97
143
  try {
98
- const packageJsonPath = path.join(cwd, "package.json");
99
- const packageJson2 = JSON.parse(await fs.readFile(packageJsonPath, "utf-8"));
144
+ const packageJsonPath = path2.join(cwd, "package.json");
145
+ const packageJson2 = JSON.parse(await fs2.readFile(packageJsonPath, "utf-8"));
100
146
  const allDeps = {
101
147
  ...packageJson2.dependencies,
102
148
  ...packageJson2.devDependencies
@@ -107,8 +153,8 @@ async function isPackageInstalled(packageName) {
107
153
  } catch {
108
154
  }
109
155
  try {
110
- const packagePath = path.join(cwd, "node_modules", packageName);
111
- await fs.access(packagePath);
156
+ const packagePath = path2.join(cwd, "node_modules", packageName);
157
+ await fs2.access(packagePath);
112
158
  return true;
113
159
  } catch {
114
160
  }
@@ -125,14 +171,208 @@ async function checkMissingDependencies(dependencies) {
125
171
  return missing;
126
172
  }
127
173
 
174
+ // src/utils/component-checker.ts
175
+ import path4 from "path";
176
+ import fs4 from "fs/promises";
177
+
178
+ // src/utils/project-structure.ts
179
+ import fs3 from "fs/promises";
180
+ import path3 from "path";
181
+ async function hasSrcDirectory(cwd = process.cwd()) {
182
+ try {
183
+ const srcPath = path3.join(cwd, "src");
184
+ await fs3.access(srcPath);
185
+ const commonDirs = ["app", "components", "lib", "pages"];
186
+ for (const dir of commonDirs) {
187
+ try {
188
+ await fs3.access(path3.join(srcPath, dir));
189
+ return true;
190
+ } catch {
191
+ continue;
192
+ }
193
+ }
194
+ try {
195
+ const tsconfigPath = path3.join(cwd, "tsconfig.json");
196
+ const tsconfig = JSON.parse(await fs3.readFile(tsconfigPath, "utf-8"));
197
+ if (tsconfig.compilerOptions?.paths) {
198
+ const paths = tsconfig.compilerOptions.paths;
199
+ if (paths["@/*"]?.includes("./src/*")) {
200
+ return true;
201
+ }
202
+ }
203
+ } catch {
204
+ console.error("Bro... just in 21st century you not even have a tsconfig.json file in your project. That way you fuck me up :D");
205
+ }
206
+ return false;
207
+ } catch {
208
+ return false;
209
+ }
210
+ }
211
+ async function resolveAliasPath(aliasPath, cwd = process.cwd()) {
212
+ const usesSrc = await hasSrcDirectory(cwd);
213
+ const baseDir = usesSrc ? "src/" : "";
214
+ return aliasPath.replace(/^@\//, baseDir);
215
+ }
216
+ async function getDefaultPaths(cwd = process.cwd()) {
217
+ const usesSrc = await hasSrcDirectory(cwd);
218
+ const baseDir = usesSrc ? "src/" : "";
219
+ return {
220
+ globalsCss: `${baseDir}app/globals.css`,
221
+ components: "@/components",
222
+ utils: "@/lib/utils",
223
+ ui: "@/components/ui",
224
+ lib: "@/lib",
225
+ hooks: "@/hooks",
226
+ baseDir
227
+ };
228
+ }
229
+
230
+ // src/utils/component-checker.ts
231
+ async function isComponentInstalled(name, config) {
232
+ try {
233
+ const component = await getRegistryComponent(name, config);
234
+ if (!component) return false;
235
+ for (const file of component.files) {
236
+ const targetPath = await resolveTargetPath(file.name, component.type, config);
237
+ const filePath = path4.join(process.cwd(), targetPath);
238
+ const exists = await fs4.access(filePath).then(() => true).catch(() => false);
239
+ if (!exists) return false;
240
+ }
241
+ return true;
242
+ } catch {
243
+ return false;
244
+ }
245
+ }
246
+ async function resolveTargetPath(fileName, type, config) {
247
+ const normalized = fileName.replace(/\\/g, "/");
248
+ if (type === "registry:ui") {
249
+ const resolvedPath = await resolveAliasPath(config.aliases.ui);
250
+ const relative = normalized.replace(/^ui\//, "");
251
+ return path4.join(resolvedPath, relative);
252
+ }
253
+ if (type === "registry:lib") {
254
+ const resolvedPath = await resolveAliasPath(config.aliases.lib);
255
+ const relative = normalized.replace(/^lib\//, "");
256
+ return path4.join(resolvedPath, relative);
257
+ }
258
+ if (type === "registry:hook") {
259
+ const resolvedPath = await resolveAliasPath(config.aliases.hooks);
260
+ return path4.join(resolvedPath, fileName);
261
+ }
262
+ return fileName;
263
+ }
264
+
265
+ // src/utils/project-config.ts
266
+ import fs5 from "fs/promises";
267
+ import path5 from "path";
268
+ function applyPittayaProjectConfig(pittaya) {
269
+ if (pittaya.registry?.url) {
270
+ process.env.PITTAYA_REGISTRY_URL = pittaya.registry.url;
271
+ }
272
+ if (typeof pittaya.registry?.preferLocal === "boolean") {
273
+ process.env.PITTAYA_REGISTRY_PREFER_LOCAL = String(pittaya.registry.preferLocal);
274
+ }
275
+ }
276
+ async function readJsonIfExists(absolutePath) {
277
+ try {
278
+ const raw = await fs5.readFile(absolutePath, "utf-8");
279
+ return JSON.parse(raw);
280
+ } catch {
281
+ return null;
282
+ }
283
+ }
284
+ async function loadProjectConfig(cwd = process.cwd()) {
285
+ const componentsJsonPath = path5.join(cwd, "components.json");
286
+ const pittayaJsonPath = path5.join(cwd, "pittaya.json");
287
+ const config = await readJsonIfExists(componentsJsonPath);
288
+ if (!config) {
289
+ throw new Error("components.json not found");
290
+ }
291
+ const pittaya = await readJsonIfExists(pittayaJsonPath) ?? {};
292
+ return { config, pittaya };
293
+ }
294
+
295
+ // src/utils/confirm.ts
296
+ import { createInterface } from "readline";
297
+ function resolveFromString(answer, initial) {
298
+ const value = (answer ?? "").trim().toLowerCase();
299
+ if (!value) return initial;
300
+ if (value === "y" || value === "yes") return true;
301
+ if (value === "n" || value === "no") return false;
302
+ return initial;
303
+ }
304
+ async function confirm(message, initial = false) {
305
+ const suffix = initial ? "(Y/n)" : "(y/N)";
306
+ const prompt = `${message} ${suffix} `;
307
+ if (!process.stdin.isTTY) {
308
+ const rl = createInterface({
309
+ input: process.stdin,
310
+ output: process.stdout
311
+ });
312
+ return await new Promise((resolve) => {
313
+ rl.question(prompt, (answer) => {
314
+ rl.close();
315
+ resolve(resolveFromString(answer, initial));
316
+ });
317
+ });
318
+ }
319
+ return await new Promise((resolve) => {
320
+ const stdin = process.stdin;
321
+ const stdout = process.stdout;
322
+ const prevRawMode = stdin.isRaw;
323
+ if (typeof stdin.setRawMode === "function") {
324
+ stdin.setRawMode(true);
325
+ }
326
+ stdin.resume();
327
+ stdout.write(prompt);
328
+ const cleanup = () => {
329
+ stdin.off("data", onData);
330
+ if (typeof stdin.setRawMode === "function") {
331
+ stdin.setRawMode(Boolean(prevRawMode));
332
+ }
333
+ stdin.pause();
334
+ };
335
+ const finish = (result) => {
336
+ stdout.write(`${result ? "y" : "n"}
337
+ `);
338
+ cleanup();
339
+ resolve(result);
340
+ };
341
+ const onData = (chunk) => {
342
+ const str = chunk.toString("utf8");
343
+ if (str === "") {
344
+ cleanup();
345
+ process.kill(process.pid, "SIGINT");
346
+ return;
347
+ }
348
+ const key = str.toLowerCase();
349
+ if (key === "\r" || key === "\n") {
350
+ finish(initial);
351
+ return;
352
+ }
353
+ if (key === "y") {
354
+ finish(true);
355
+ return;
356
+ }
357
+ if (key === "n") {
358
+ finish(false);
359
+ return;
360
+ }
361
+ };
362
+ stdin.on("data", onData);
363
+ });
364
+ }
365
+
128
366
  // src/commands/add.ts
129
367
  async function add(components, options) {
130
368
  const cwd = process.cwd();
131
- const componentsJsonPath = path2.join(cwd, "components.json");
132
369
  let config;
370
+ let pittayaConfig;
133
371
  try {
134
- const configContent = await fs2.readFile(componentsJsonPath, "utf-8");
135
- config = JSON.parse(configContent);
372
+ const loaded = await loadProjectConfig(cwd);
373
+ config = loaded.config;
374
+ pittayaConfig = loaded.pittaya;
375
+ applyPittayaProjectConfig(pittayaConfig);
136
376
  } catch (error) {
137
377
  console.log(chalk.red("\n\u274C components.json not found.\n"));
138
378
  console.log(
@@ -141,10 +381,14 @@ async function add(components, options) {
141
381
  );
142
382
  return;
143
383
  }
384
+ const resolvedOptions = {
385
+ ...options,
386
+ addMissingDeps: options.addMissingDeps ?? pittayaConfig?.install?.autoInstallDeps ?? false
387
+ };
144
388
  const spinner = ora("Fetching available components...").start();
145
389
  let registry;
146
390
  try {
147
- registry = await fetchRegistry();
391
+ registry = await fetchRegistry(config);
148
392
  spinner.succeed("Registry loaded!");
149
393
  } catch (error) {
150
394
  spinner.fail("Error loading registry");
@@ -178,25 +422,10 @@ Adding ${components.length} component(s)...
178
422
  `)
179
423
  );
180
424
  for (const componentName of components) {
181
- await addComponent(componentName, config, options);
425
+ await addComponent(componentName, config, resolvedOptions);
182
426
  }
183
427
  console.log(chalk.green("\n\u2705 Components added successfully!\n"));
184
428
  }
185
- async function isComponentInstalled(name, config) {
186
- try {
187
- const component = await getRegistryComponent(name);
188
- if (!component) return false;
189
- for (const file of component.files) {
190
- const targetPath = resolveTargetPath(file.name, component.type, config);
191
- const filePath = path2.join(process.cwd(), targetPath);
192
- const exists = await fs2.access(filePath).then(() => true).catch(() => false);
193
- if (!exists) return false;
194
- }
195
- return true;
196
- } catch {
197
- return false;
198
- }
199
- }
200
429
  async function addComponent(name, config, options) {
201
430
  const alreadyInstalled = await isComponentInstalled(name, config);
202
431
  if (alreadyInstalled && !options.overwrite) {
@@ -205,7 +434,7 @@ async function addComponent(name, config, options) {
205
434
  }
206
435
  const spinner = ora(`Installing ${chalk.bold(name)}...`).start();
207
436
  try {
208
- const component = await getRegistryComponent(name);
437
+ const component = await getRegistryComponent(name, config);
209
438
  if (!component) {
210
439
  spinner.fail(`Component "${name}" not found in registry.`);
211
440
  return;
@@ -225,12 +454,10 @@ async function addComponent(name, config, options) {
225
454
  if (options.addMissingDeps) {
226
455
  console.log(chalk.dim("Installing dependencies automatically...\n"));
227
456
  } else {
228
- const { install } = await prompts({
229
- type: "confirm",
230
- name: "install",
231
- message: "Do you want to install the dependencies now?",
232
- initial: true
233
- });
457
+ const install = await confirm(
458
+ "Do you want to install the dependencies now?",
459
+ true
460
+ );
234
461
  if (!install) {
235
462
  console.log(chalk.yellow("\n\u26A0\uFE0F Component not installed. Install the dependencies manually and try again.\n"));
236
463
  return;
@@ -262,26 +489,24 @@ async function addComponent(name, config, options) {
262
489
  spinner.start(`Installing ${chalk.bold(name)}...`);
263
490
  }
264
491
  for (const file of component.files) {
265
- const targetPath = resolveTargetPath(file.name, component.type, config);
266
- const filePath = path2.join(process.cwd(), targetPath);
267
- const exists = await fs2.access(filePath).then(() => true).catch(() => false);
492
+ const targetPath = await resolveTargetPath(file.name, component.type, config);
493
+ const filePath = path6.join(process.cwd(), targetPath);
494
+ const exists = await fs6.access(filePath).then(() => true).catch(() => false);
268
495
  if (exists && !options.overwrite && !options.yes) {
269
496
  spinner.stop();
270
- const { overwrite } = await prompts({
271
- type: "confirm",
272
- name: "overwrite",
273
- message: `${targetPath} already exists. Overwrite?`,
274
- initial: false
275
- });
497
+ const overwrite = await confirm(
498
+ `${targetPath} already exists. Overwrite?`,
499
+ false
500
+ );
276
501
  if (!overwrite) {
277
502
  spinner.warn(`Skipping ${targetPath}`);
278
503
  continue;
279
504
  }
280
505
  spinner.start();
281
506
  }
282
- await fs2.mkdir(path2.dirname(filePath), { recursive: true });
507
+ await fs6.mkdir(path6.dirname(filePath), { recursive: true });
283
508
  const content = transformImports(file.content, config);
284
- await fs2.writeFile(filePath, content, "utf-8");
509
+ await fs6.writeFile(filePath, content, "utf-8");
285
510
  }
286
511
  spinner.succeed(`${chalk.bold(name)} installed successfully!`);
287
512
  } catch (error) {
@@ -289,86 +514,277 @@ async function addComponent(name, config, options) {
289
514
  console.error(error);
290
515
  }
291
516
  }
292
- function resolveTargetPath(fileName, type, config) {
293
- if (type === "registry:ui") {
294
- return path2.join(
295
- config.aliases.ui.replace("@/", "src/"),
296
- fileName
297
- );
298
- }
299
- if (type === "registry:lib") {
300
- return path2.join(
301
- config.aliases.lib.replace("@/", "src/"),
302
- fileName
303
- );
304
- }
305
- if (type === "registry:hook") {
306
- return path2.join(
307
- config.aliases.hooks.replace("@/", "src/"),
308
- fileName
309
- );
310
- }
311
- return fileName;
312
- }
313
517
 
314
518
  // src/commands/init.ts
315
519
  import chalk2 from "chalk";
316
520
  import ora2 from "ora";
317
521
  import prompts2 from "prompts";
318
- import path3 from "path";
319
- import fs3 from "fs/promises";
522
+ import path8 from "path";
523
+ import fs8 from "fs/promises";
320
524
  import { execa as execa2 } from "execa";
525
+
526
+ // src/utils/style-installer.ts
527
+ import fs7 from "fs/promises";
528
+ import path7 from "path";
529
+ import postcss from "postcss";
530
+ async function applyRegistryStyleToProject(styleComponent, config, options) {
531
+ const cssPath = config.tailwind?.css;
532
+ if (!cssPath) {
533
+ return;
534
+ }
535
+ const absoluteCssPath = path7.resolve(process.cwd(), cssPath);
536
+ const raw = await fs7.readFile(absoluteCssPath, "utf-8").catch(() => "");
537
+ const next = await transformTailwindV4Css(raw, styleComponent.cssVars ?? {}, {
538
+ overwriteCssVars: options?.overwriteCssVars ?? true
539
+ });
540
+ await fs7.mkdir(path7.dirname(absoluteCssPath), { recursive: true });
541
+ await fs7.writeFile(absoluteCssPath, next, "utf-8");
542
+ }
543
+ async function transformTailwindV4Css(input, cssVars, options) {
544
+ const result = await postcss([
545
+ ensureImportPlugin({ importPath: "tailwindcss" }),
546
+ ensureImportPlugin({ importPath: "tw-animate-css" }),
547
+ ensureCustomVariantDarkPlugin(),
548
+ upsertThemeAtRulePlugin(cssVars.theme ?? {}, {
549
+ overwrite: options.overwriteCssVars
550
+ }),
551
+ upsertRuleVarsPlugin(":root", cssVars.light ?? {}, {
552
+ overwrite: options.overwriteCssVars
553
+ }),
554
+ upsertRuleVarsPlugin(".dark", cssVars.dark ?? {}, {
555
+ overwrite: options.overwriteCssVars
556
+ }),
557
+ ensureBaseLayerPlugin()
558
+ ]).process(input, { from: void 0 });
559
+ return result.css.replace(/(\n\s*\n)+/g, "\n\n").trimStart();
560
+ }
561
+ function ensureImportPlugin({
562
+ importPath
563
+ }) {
564
+ return {
565
+ postcssPlugin: "pittaya-ensure-import",
566
+ Once(root) {
567
+ const exists = root.nodes.some(
568
+ (n) => n.type === "atrule" && n.name === "import" && n.params.includes(`"${importPath}"`)
569
+ );
570
+ if (exists) return;
571
+ const node = postcss.atRule({
572
+ name: "import",
573
+ params: `"${importPath}"`
574
+ });
575
+ root.prepend(node);
576
+ }
577
+ };
578
+ }
579
+ ensureImportPlugin.postcss = true;
580
+ function ensureCustomVariantDarkPlugin() {
581
+ return {
582
+ postcssPlugin: "pittaya-ensure-custom-variant-dark",
583
+ Once(root) {
584
+ const exists = root.nodes.some(
585
+ (n) => n.type === "atrule" && n.name === "custom-variant" && n.params.startsWith("dark ")
586
+ );
587
+ if (exists) return;
588
+ const node = postcss.atRule({
589
+ name: "custom-variant",
590
+ params: "dark (&:is(.dark *))"
591
+ });
592
+ root.append(node);
593
+ }
594
+ };
595
+ }
596
+ ensureCustomVariantDarkPlugin.postcss = true;
597
+ function upsertThemeAtRulePlugin(vars, options) {
598
+ return {
599
+ postcssPlugin: "pittaya-upsert-theme-atrule",
600
+ Once(root) {
601
+ if (Object.keys(vars).length === 0) return;
602
+ let themeNode = root.nodes.find(
603
+ (n) => n.type === "atrule" && n.name === "theme" && n.params === "inline"
604
+ );
605
+ if (!themeNode) {
606
+ themeNode = postcss.atRule({
607
+ name: "theme",
608
+ params: "inline"
609
+ });
610
+ root.append(themeNode);
611
+ }
612
+ for (const [k, v] of Object.entries(vars)) {
613
+ const prop = `--${k.replace(/^--/, "")}`;
614
+ const existing = themeNode.nodes?.find(
615
+ (n) => n.type === "decl" && n.prop === prop
616
+ );
617
+ if (existing) {
618
+ if (options.overwrite) {
619
+ existing.value = v;
620
+ }
621
+ } else {
622
+ themeNode.append(
623
+ postcss.decl({
624
+ prop,
625
+ value: v
626
+ })
627
+ );
628
+ }
629
+ }
630
+ }
631
+ };
632
+ }
633
+ upsertThemeAtRulePlugin.postcss = true;
634
+ function upsertRuleVarsPlugin(selector, vars, options) {
635
+ return {
636
+ postcssPlugin: `pittaya-upsert-vars-${selector}`,
637
+ Once(root) {
638
+ if (Object.keys(vars).length === 0) return;
639
+ let rule = root.nodes.find(
640
+ (n) => n.type === "rule" && n.selector === selector
641
+ );
642
+ if (!rule) {
643
+ rule = postcss.rule({ selector });
644
+ root.append(rule);
645
+ }
646
+ for (const [k, v] of Object.entries(vars)) {
647
+ const prop = `--${k.replace(/^--/, "")}`;
648
+ const existing = rule.nodes?.find(
649
+ (n) => n.type === "decl" && n.prop === prop
650
+ );
651
+ if (existing) {
652
+ if (options.overwrite) {
653
+ existing.value = v;
654
+ }
655
+ } else {
656
+ rule.append(
657
+ postcss.decl({
658
+ prop,
659
+ value: v
660
+ })
661
+ );
662
+ }
663
+ }
664
+ }
665
+ };
666
+ }
667
+ upsertRuleVarsPlugin.postcss = true;
668
+ function ensureBaseLayerPlugin() {
669
+ return {
670
+ postcssPlugin: "pittaya-ensure-base-layer",
671
+ Once(root) {
672
+ const hasLayerBase = root.nodes.some(
673
+ (n) => n.type === "atrule" && n.name === "layer" && n.params === "base"
674
+ );
675
+ if (hasLayerBase) return;
676
+ const baseLayer = postcss.atRule({
677
+ name: "layer",
678
+ params: "base"
679
+ });
680
+ baseLayer.append(
681
+ postcss.rule({
682
+ selector: "*",
683
+ nodes: [
684
+ postcss.atRule({
685
+ name: "apply",
686
+ params: "border-border outline-ring/50"
687
+ })
688
+ ]
689
+ })
690
+ );
691
+ baseLayer.append(
692
+ postcss.rule({
693
+ selector: "body",
694
+ nodes: [
695
+ postcss.atRule({
696
+ name: "apply",
697
+ params: "bg-background text-foreground"
698
+ })
699
+ ]
700
+ })
701
+ );
702
+ root.append(baseLayer);
703
+ }
704
+ };
705
+ }
706
+ ensureBaseLayerPlugin.postcss = true;
707
+
708
+ // src/commands/init.ts
321
709
  async function init(options) {
322
- console.log(chalk2.bold("\nWelcome to Pittaya UI!\n"));
710
+ const pittayaColorHex = "#f2556d";
711
+ console.log(
712
+ chalk2.bold(
713
+ `
714
+ Welcome to ${chalk2.hex(pittayaColorHex)("Pittaya")} UI!
715
+ `
716
+ )
717
+ );
323
718
  const cwd = process.cwd();
324
- const componentsJsonPath = path3.join(cwd, "components.json");
325
- const exists = await fs3.access(componentsJsonPath).then(() => true).catch(() => false);
719
+ const componentsJsonPath = path8.join(cwd, "components.json");
720
+ const pittayaJsonPath = path8.join(cwd, "pittaya.json");
721
+ const exists = await fs8.access(componentsJsonPath).then(() => true).catch(() => false);
722
+ const pittayaExists = await fs8.access(pittayaJsonPath).then(() => true).catch(() => false);
326
723
  if (exists && !options.yes) {
327
- const { overwrite } = await prompts2({
328
- type: "confirm",
329
- name: "overwrite",
330
- message: "components.json already exists. Do you want to overwrite it?",
331
- initial: false
332
- });
724
+ const overwrite = await confirm(
725
+ `${chalk2.underline("components.json")} already exists. Do you want to overwrite it?`,
726
+ false
727
+ );
333
728
  if (!overwrite) {
334
729
  console.log(chalk2.yellow("\n\u274C Operation cancelled.\n"));
335
730
  return;
336
731
  }
337
732
  }
338
- const config = options.yes ? getDefaultConfig() : await prompts2([
733
+ let shouldWritePittayaJson = true;
734
+ if (pittayaExists && !options.yes) {
735
+ const overwrite = await confirm(
736
+ `${chalk2.underline("pittaya.json")} already exists. Do you want to overwrite it?`,
737
+ false
738
+ );
739
+ if (!overwrite) {
740
+ shouldWritePittayaJson = false;
741
+ }
742
+ }
743
+ const defaultPaths = await getDefaultPaths(cwd);
744
+ const config = options.yes ? {
745
+ style: "pittaya",
746
+ tailwindCss: defaultPaths.globalsCss,
747
+ rsc: true,
748
+ componentsPath: defaultPaths.components,
749
+ utilsPath: defaultPaths.utils
750
+ } : await prompts2([
339
751
  {
340
752
  type: "select",
341
753
  name: "style",
342
754
  message: "Which style would you like to use?",
343
755
  choices: [
756
+ { title: "Pittaya", value: "pittaya" },
344
757
  { title: "New York", value: "new-york" },
345
- { title: "Default", value: "default" },
346
- { title: "Recife", value: "recife" }
758
+ { title: "Default", value: "default" }
347
759
  ]
348
760
  },
349
761
  {
350
762
  type: "text",
351
763
  name: "tailwindCss",
352
764
  message: "Where is your globals.css file?",
353
- initial: "src/app/globals.css"
765
+ initial: defaultPaths.globalsCss
354
766
  },
355
767
  {
356
- type: "confirm",
768
+ type: "select",
357
769
  name: "rsc",
358
770
  message: "Use React Server Components?",
359
- initial: true
771
+ choices: [
772
+ { title: "Yes", value: true },
773
+ { title: "No", value: false }
774
+ ],
775
+ initial: 0
360
776
  },
361
777
  {
362
778
  type: "text",
363
779
  name: "componentsPath",
364
780
  message: "Path for components?",
365
- initial: "@/components"
781
+ initial: defaultPaths.components
366
782
  },
367
783
  {
368
784
  type: "text",
369
785
  name: "utilsPath",
370
786
  message: "Path for utils?",
371
- initial: "@/lib/utils"
787
+ initial: defaultPaths.utils
372
788
  }
373
789
  ]);
374
790
  if (!config.style && !options.yes) {
@@ -377,11 +793,11 @@ async function init(options) {
377
793
  }
378
794
  const componentsJson = {
379
795
  $schema: "https://raw.githubusercontent.com/pittaya-ui/cli/main/registry/schema.json",
380
- style: config.style || "new-york",
796
+ style: config.style || "pittaya",
381
797
  rsc: config.rsc ?? true,
382
798
  tsx: true,
383
799
  tailwind: {
384
- config: "tailwind.config.ts",
800
+ config: "",
385
801
  css: config.tailwindCss || "src/app/globals.css",
386
802
  baseColor: "neutral",
387
803
  cssVariables: true,
@@ -390,33 +806,71 @@ async function init(options) {
390
806
  aliases: {
391
807
  components: config.componentsPath || "@/components",
392
808
  utils: config.utilsPath || "@/lib/utils",
393
- ui: `${config.componentsPath || "@/components"}/pittaya/ui`,
809
+ ui: "@/components/ui",
394
810
  lib: "@/lib",
395
811
  hooks: "@/hooks"
396
812
  },
397
813
  iconLibrary: "lucide"
398
814
  };
815
+ let pittayaJson = {
816
+ registry: {
817
+ url: "https://raw.githubusercontent.com/pittaya-ui/cli/main/registry",
818
+ preferLocal: false
819
+ },
820
+ theme: {
821
+ overwriteCssVars: true
822
+ },
823
+ install: {
824
+ autoInstallDeps: true
825
+ }
826
+ };
827
+ if (!shouldWritePittayaJson) {
828
+ try {
829
+ const raw = await fs8.readFile(pittayaJsonPath, "utf-8");
830
+ pittayaJson = JSON.parse(raw);
831
+ } catch {
832
+ shouldWritePittayaJson = true;
833
+ }
834
+ }
399
835
  const spinner = ora2("Creating components.json...").start();
400
- await fs3.writeFile(
836
+ await fs8.writeFile(
401
837
  componentsJsonPath,
402
838
  JSON.stringify(componentsJson, null, 2)
403
839
  );
404
840
  spinner.succeed("components.json created successfully!");
405
- const packageManager = await detectPackageManager2();
841
+ if (shouldWritePittayaJson) {
842
+ const pittayaSpinner = ora2("Creating pittaya.json...").start();
843
+ await fs8.writeFile(pittayaJsonPath, JSON.stringify(pittayaJson, null, 2));
844
+ pittayaSpinner.succeed("pittaya.json created successfully!");
845
+ }
846
+ applyPittayaProjectConfig(pittayaJson);
406
847
  const depsSpinner = ora2("Installing base dependencies...").start();
407
848
  try {
849
+ const packageManager = await detectPackageManager();
408
850
  await execa2(packageManager, [
409
851
  packageManager === "yarn" ? "add" : "install",
410
852
  "class-variance-authority",
411
853
  "clsx",
412
- "tailwind-merge"
854
+ "tailwind-merge",
855
+ "lucide-react",
856
+ "tw-animate-css"
413
857
  ]);
414
858
  depsSpinner.succeed("Dependencies installed!");
415
859
  } catch (error) {
416
860
  depsSpinner.fail("Error installing dependencies");
417
861
  console.error(error);
418
862
  }
419
- console.log(chalk2.green("\n\u2705 Pittaya UI configured successfully!\n"));
863
+ try {
864
+ const styleItem = await getRegistryComponent("style", componentsJson);
865
+ await applyRegistryStyleToProject(styleItem, componentsJson, {
866
+ overwriteCssVars: pittayaJson.theme?.overwriteCssVars ?? true
867
+ });
868
+ } catch (error) {
869
+ console.error(error);
870
+ }
871
+ console.log(chalk2.green(`
872
+ \u2705 ${chalk2.hex(pittayaColorHex)("Pittaya")} UI configured successfully!
873
+ `));
420
874
  console.log(chalk2.dim("Next steps:"));
421
875
  console.log(
422
876
  chalk2.dim(
@@ -430,28 +884,6 @@ async function init(options) {
430
884
  )
431
885
  );
432
886
  }
433
- function getDefaultConfig() {
434
- return {
435
- style: "new-york",
436
- tailwindCss: "src/app/globals.css",
437
- rsc: true,
438
- componentsPath: "@/components",
439
- utilsPath: "@/lib/utils"
440
- };
441
- }
442
- async function detectPackageManager2() {
443
- try {
444
- await fs3.access("pnpm-lock.yaml");
445
- return "pnpm";
446
- } catch {
447
- }
448
- try {
449
- await fs3.access("yarn.lock");
450
- return "yarn";
451
- } catch {
452
- }
453
- return "npm";
454
- }
455
887
 
456
888
  // src/commands/credits.ts
457
889
  import chalk3 from "chalk";
@@ -467,15 +899,15 @@ async function credits() {
467
899
  import chalk4 from "chalk";
468
900
  import ora3 from "ora";
469
901
  import prompts3 from "prompts";
470
- import path4 from "path";
471
- import fs4 from "fs/promises";
902
+ import path9 from "path";
903
+ import fs9 from "fs/promises";
472
904
  async function diff(components, options) {
473
905
  const cwd = process.cwd();
474
- const componentsJsonPath = path4.join(cwd, "components.json");
475
906
  let config;
476
907
  try {
477
- const configContent = await fs4.readFile(componentsJsonPath, "utf-8");
478
- config = JSON.parse(configContent);
908
+ const loaded = await loadProjectConfig(cwd);
909
+ config = loaded.config;
910
+ applyPittayaProjectConfig(loaded.pittaya);
479
911
  } catch (error) {
480
912
  console.log(chalk4.red("\n\u274C components.json not found.\n"));
481
913
  console.log(
@@ -487,7 +919,7 @@ async function diff(components, options) {
487
919
  const spinner = ora3("Fetching registry...").start();
488
920
  let registry;
489
921
  try {
490
- registry = await fetchRegistry();
922
+ registry = await fetchRegistry(config);
491
923
  spinner.succeed("Registry loaded!");
492
924
  } catch (error) {
493
925
  spinner.fail("Error loading registry");
@@ -498,7 +930,7 @@ async function diff(components, options) {
498
930
  if (options.all) {
499
931
  const allComponents = registry.components.filter((comp) => comp.type === "registry:ui" || comp.type === "registry:lib").map((comp) => comp.name);
500
932
  for (const comp of allComponents) {
501
- const isInstalled = await isComponentInstalled2(comp, config);
933
+ const isInstalled = await isComponentInstalled(comp, config);
502
934
  if (isInstalled) {
503
935
  componentsToCheck.push(comp);
504
936
  }
@@ -511,7 +943,7 @@ async function diff(components, options) {
511
943
  const allComponents = registry.components.filter((comp) => comp.type === "registry:ui" || comp.type === "registry:lib").map((comp) => comp.name);
512
944
  const installedComponents = [];
513
945
  for (const comp of allComponents) {
514
- const isInstalled = await isComponentInstalled2(comp, config);
946
+ const isInstalled = await isComponentInstalled(comp, config);
515
947
  if (isInstalled) {
516
948
  installedComponents.push(comp);
517
949
  }
@@ -550,29 +982,14 @@ Checking ${componentsToCheck.length} component(s)...
550
982
  }
551
983
  displayDiffResults(results);
552
984
  }
553
- async function isComponentInstalled2(name, config) {
554
- try {
555
- const component = await getRegistryComponent(name);
556
- if (!component) return false;
557
- for (const file of component.files) {
558
- const targetPath = resolveTargetPath2(file.name, component.type, config);
559
- const filePath = path4.join(process.cwd(), targetPath);
560
- const exists = await fs4.access(filePath).then(() => true).catch(() => false);
561
- if (!exists) return false;
562
- }
563
- return true;
564
- } catch {
565
- return false;
566
- }
567
- }
568
985
  async function checkComponentDiff(name, config) {
569
986
  try {
570
- const component = await getRegistryComponent(name);
987
+ const component = await getRegistryComponent(name, config);
571
988
  if (!component) {
572
989
  console.log(chalk4.red(` \u274C Component "${name}" not found in registry.`));
573
990
  return null;
574
991
  }
575
- const isInstalled = await isComponentInstalled2(name, config);
992
+ const isInstalled = await isComponentInstalled(name, config);
576
993
  if (!isInstalled) {
577
994
  console.log(chalk4.dim(` \u23ED\uFE0F ${name} is not installed, skipping...`));
578
995
  return null;
@@ -580,10 +997,10 @@ async function checkComponentDiff(name, config) {
580
997
  const fileDiffs = [];
581
998
  let hasChanges = false;
582
999
  for (const file of component.files) {
583
- const targetPath = resolveTargetPath2(file.name, component.type, config);
584
- const filePath = path4.join(process.cwd(), targetPath);
1000
+ const targetPath = await resolveTargetPath(file.name, component.type, config);
1001
+ const filePath = path9.join(process.cwd(), targetPath);
585
1002
  try {
586
- const localContent = await fs4.readFile(filePath, "utf-8");
1003
+ const localContent = await fs9.readFile(filePath, "utf-8");
587
1004
  const registryContent = transformImports(file.content, config);
588
1005
  const contentMatches = localContent.trim() === registryContent.trim();
589
1006
  fileDiffs.push({
@@ -649,41 +1066,20 @@ function displayDiffResults(results) {
649
1066
  }
650
1067
  console.log();
651
1068
  }
652
- function resolveTargetPath2(fileName, type, config) {
653
- if (type === "registry:ui") {
654
- return path4.join(
655
- config.aliases.ui.replace("@/", "src/"),
656
- fileName
657
- );
658
- }
659
- if (type === "registry:lib") {
660
- return path4.join(
661
- config.aliases.lib.replace("@/", "src/"),
662
- fileName
663
- );
664
- }
665
- if (type === "registry:hook") {
666
- return path4.join(
667
- config.aliases.hooks.replace("@/", "src/"),
668
- fileName
669
- );
670
- }
671
- return fileName;
672
- }
673
1069
 
674
1070
  // src/commands/update.ts
675
1071
  import chalk5 from "chalk";
676
1072
  import ora4 from "ora";
677
1073
  import prompts4 from "prompts";
678
- import path5 from "path";
679
- import fs5 from "fs/promises";
1074
+ import path10 from "path";
1075
+ import fs10 from "fs/promises";
680
1076
  async function update(components, options) {
681
1077
  const cwd = process.cwd();
682
- const componentsJsonPath = path5.join(cwd, "components.json");
683
1078
  let config;
684
1079
  try {
685
- const configContent = await fs5.readFile(componentsJsonPath, "utf-8");
686
- config = JSON.parse(configContent);
1080
+ const loaded = await loadProjectConfig(cwd);
1081
+ config = loaded.config;
1082
+ applyPittayaProjectConfig(loaded.pittaya);
687
1083
  } catch (error) {
688
1084
  console.log(chalk5.red("\n\u274C components.json not found.\n"));
689
1085
  console.log(
@@ -695,7 +1091,7 @@ async function update(components, options) {
695
1091
  const spinner = ora4("Fetching registry...").start();
696
1092
  let registry;
697
1093
  try {
698
- registry = await fetchRegistry();
1094
+ registry = await fetchRegistry(config);
699
1095
  spinner.succeed("Registry loaded!");
700
1096
  } catch (error) {
701
1097
  spinner.fail("Error loading registry");
@@ -706,7 +1102,7 @@ async function update(components, options) {
706
1102
  if (options.all) {
707
1103
  const allComponents = registry.components.filter((comp) => comp.type === "registry:ui" || comp.type === "registry:lib").map((comp) => comp.name);
708
1104
  for (const comp of allComponents) {
709
- const isInstalled = await isComponentInstalled3(comp, config);
1105
+ const isInstalled = await isComponentInstalled(comp, config);
710
1106
  if (isInstalled) {
711
1107
  componentsToUpdate.push(comp);
712
1108
  }
@@ -716,13 +1112,11 @@ async function update(components, options) {
716
1112
  return;
717
1113
  }
718
1114
  if (!options.yes && !options.force) {
719
- const { confirm } = await prompts4({
720
- type: "confirm",
721
- name: "confirm",
722
- message: `Update ${componentsToUpdate.length} component(s)?`,
723
- initial: true
724
- });
725
- if (!confirm) {
1115
+ const shouldUpdate = await confirm(
1116
+ `Update ${componentsToUpdate.length} component(s)?`,
1117
+ true
1118
+ );
1119
+ if (!shouldUpdate) {
726
1120
  console.log(chalk5.yellow("\n\u274C Update cancelled.\n"));
727
1121
  return;
728
1122
  }
@@ -731,7 +1125,7 @@ async function update(components, options) {
731
1125
  const allComponents = registry.components.filter((comp) => comp.type === "registry:ui" || comp.type === "registry:lib").map((comp) => comp.name);
732
1126
  const installedComponents = [];
733
1127
  for (const comp of allComponents) {
734
- const isInstalled = await isComponentInstalled3(comp, config);
1128
+ const isInstalled = await isComponentInstalled(comp, config);
735
1129
  if (isInstalled) {
736
1130
  installedComponents.push(comp);
737
1131
  }
@@ -768,27 +1162,12 @@ Checking ${componentsToUpdate.length} component(s) for updates...
768
1162
  }
769
1163
  displayUpdateResults(results);
770
1164
  }
771
- async function isComponentInstalled3(name, config) {
772
- try {
773
- const component = await getRegistryComponent(name);
774
- if (!component) return false;
775
- for (const file of component.files) {
776
- const targetPath = resolveTargetPath3(file.name, component.type, config);
777
- const filePath = path5.join(process.cwd(), targetPath);
778
- const exists = await fs5.access(filePath).then(() => true).catch(() => false);
779
- if (!exists) return false;
780
- }
781
- return true;
782
- } catch {
783
- return false;
784
- }
785
- }
786
1165
  async function hasComponentChanges(component, config) {
787
1166
  for (const file of component.files) {
788
- const targetPath = resolveTargetPath3(file.name, component.type, config);
789
- const filePath = path5.join(process.cwd(), targetPath);
1167
+ const targetPath = await resolveTargetPath(file.name, component.type, config);
1168
+ const filePath = path10.join(process.cwd(), targetPath);
790
1169
  try {
791
- const localContent = await fs5.readFile(filePath, "utf-8");
1170
+ const localContent = await fs10.readFile(filePath, "utf-8");
792
1171
  const registryContent = transformImports(file.content, config);
793
1172
  if (localContent.trim() !== registryContent.trim()) {
794
1173
  return true;
@@ -801,12 +1180,12 @@ async function hasComponentChanges(component, config) {
801
1180
  }
802
1181
  async function updateComponent(name, config, options) {
803
1182
  try {
804
- const component = await getRegistryComponent(name);
1183
+ const component = await getRegistryComponent(name, config);
805
1184
  if (!component) {
806
1185
  console.log(chalk5.red(` \u274C Component "${name}" not found in registry.`));
807
1186
  return { name, updated: false, skipped: true, reason: "not found in registry" };
808
1187
  }
809
- const isInstalled = await isComponentInstalled3(name, config);
1188
+ const isInstalled = await isComponentInstalled(name, config);
810
1189
  if (!isInstalled) {
811
1190
  console.log(chalk5.dim(` \u23ED\uFE0F ${name} is not installed, skipping...`));
812
1191
  return { name, updated: false, skipped: true, reason: "not installed" };
@@ -817,24 +1196,19 @@ async function updateComponent(name, config, options) {
817
1196
  return { name, updated: false, skipped: true, reason: "already up to date" };
818
1197
  }
819
1198
  if (!options.yes && !options.force) {
820
- const { confirm } = await prompts4({
821
- type: "confirm",
822
- name: "confirm",
823
- message: `Update ${chalk5.bold(name)}?`,
824
- initial: true
825
- });
826
- if (!confirm) {
1199
+ const shouldUpdate = await confirm(`Update ${chalk5.bold(name)}?`, true);
1200
+ if (!shouldUpdate) {
827
1201
  console.log(chalk5.yellow(` \u23ED\uFE0F Skipped ${name}`));
828
1202
  return { name, updated: false, skipped: true, reason: "user cancelled" };
829
1203
  }
830
1204
  }
831
1205
  const spinner = ora4(`Updating ${chalk5.bold(name)}...`).start();
832
1206
  for (const file of component.files) {
833
- const targetPath = resolveTargetPath3(file.name, component.type, config);
834
- const filePath = path5.join(process.cwd(), targetPath);
835
- await fs5.mkdir(path5.dirname(filePath), { recursive: true });
1207
+ const targetPath = await resolveTargetPath(file.name, component.type, config);
1208
+ const filePath = path10.join(process.cwd(), targetPath);
1209
+ await fs10.mkdir(path10.dirname(filePath), { recursive: true });
836
1210
  const content = transformImports(file.content, config);
837
- await fs5.writeFile(filePath, content, "utf-8");
1211
+ await fs10.writeFile(filePath, content, "utf-8");
838
1212
  }
839
1213
  spinner.succeed(`${chalk5.bold(name)} updated successfully!`);
840
1214
  return { name, updated: true, skipped: false };
@@ -866,33 +1240,213 @@ function displayUpdateResults(results) {
866
1240
  }
867
1241
  console.log();
868
1242
  }
869
- function resolveTargetPath3(fileName, type, config) {
870
- if (type === "registry:ui") {
871
- return path5.join(
872
- config.aliases.ui.replace("@/", "src/"),
873
- fileName
1243
+
1244
+ // src/commands/list.ts
1245
+ import chalk6 from "chalk";
1246
+ import ora5 from "ora";
1247
+ async function list(options) {
1248
+ const cwd = process.cwd();
1249
+ let config;
1250
+ try {
1251
+ const loaded = await loadProjectConfig(cwd);
1252
+ config = loaded.config;
1253
+ applyPittayaProjectConfig(loaded.pittaya);
1254
+ } catch (error) {
1255
+ console.log(chalk6.red("\n\u274C components.json not found.\n"));
1256
+ console.log(
1257
+ chalk6.dim(`Run ${chalk6.bold("npx pittaya init")} first.
1258
+ `)
874
1259
  );
1260
+ return;
875
1261
  }
876
- if (type === "registry:lib") {
877
- return path5.join(
878
- config.aliases.lib.replace("@/", "src/"),
879
- fileName
880
- );
1262
+ const spinner = ora5("Fetching components...").start();
1263
+ let registry;
1264
+ try {
1265
+ registry = await fetchRegistry(config);
1266
+ spinner.succeed("Components loaded!");
1267
+ } catch (error) {
1268
+ spinner.fail("Error loading registry");
1269
+ console.error(error);
1270
+ return;
881
1271
  }
882
- if (type === "registry:hook") {
883
- return path5.join(
884
- config.aliases.hooks.replace("@/", "src/"),
885
- fileName
886
- );
1272
+ const allComponents = registry.components || [];
1273
+ const componentsWithStatus = await Promise.all(
1274
+ allComponents.map(async (comp) => {
1275
+ const slug = comp.slug || comp.name;
1276
+ const installed = await isComponentInstalled(slug, config);
1277
+ return {
1278
+ ...comp.slug ? comp : { ...comp, slug },
1279
+ installed
1280
+ };
1281
+ })
1282
+ );
1283
+ let filteredComponents = componentsWithStatus;
1284
+ if (options.installed) {
1285
+ filteredComponents = componentsWithStatus.filter((comp) => comp.installed);
1286
+ } else if (options.available) {
1287
+ filteredComponents = componentsWithStatus.filter((comp) => !comp.installed);
1288
+ }
1289
+ if (options.json) {
1290
+ console.log(JSON.stringify(filteredComponents, null, 2));
1291
+ return;
1292
+ }
1293
+ displayComponents(filteredComponents, options);
1294
+ }
1295
+ function displayComponents(components, options) {
1296
+ if (components.length === 0) {
1297
+ if (options.installed) {
1298
+ console.log(chalk6.yellow("\n\u{1F4E6} No components installed yet.\n"));
1299
+ console.log(chalk6.dim(`Run ${chalk6.bold("npx pittaya add")} to install components.
1300
+ `));
1301
+ } else if (options.available) {
1302
+ console.log(chalk6.yellow("\n\u2728 All components are already installed!\n"));
1303
+ } else {
1304
+ console.log(chalk6.yellow("\n\u26A0\uFE0F No components found in registry.\n"));
1305
+ }
1306
+ return;
1307
+ }
1308
+ const categorized = components.reduce((acc, comp) => {
1309
+ const category = comp.category || "Other";
1310
+ if (!acc[category]) {
1311
+ acc[category] = [];
1312
+ }
1313
+ acc[category].push(comp);
1314
+ return acc;
1315
+ }, {});
1316
+ console.log("\n");
1317
+ if (options.installed) {
1318
+ console.log(chalk6.bold.green("\u{1F4E6} Installed Components\n"));
1319
+ } else if (options.available) {
1320
+ console.log(chalk6.bold.blue("\u2728 Available Components\n"));
1321
+ } else {
1322
+ console.log(chalk6.bold("\u{1F4CB} All Components\n"));
1323
+ }
1324
+ Object.entries(categorized).sort().forEach(([category, comps]) => {
1325
+ console.log(chalk6.bold.cyan(`${category}:`));
1326
+ comps.forEach((comp) => {
1327
+ const status = comp.installed ? chalk6.green("\u2713") : chalk6.dim("\u25CB");
1328
+ const name = comp.installed ? chalk6.bold(comp.slug) : chalk6.dim(comp.slug);
1329
+ const description = comp.description ? chalk6.dim(` - ${comp.description}`) : "";
1330
+ const deps = comp.dependencies && comp.dependencies.length > 0 ? chalk6.dim.yellow(` [${comp.dependencies.length} deps]`) : "";
1331
+ const internalDeps = comp.internalDependencies && comp.internalDependencies.length > 0 ? chalk6.dim.blue(` [requires: ${comp.internalDependencies.join(", ")}]`) : "";
1332
+ console.log(` ${status} ${name}${description}${deps}${internalDeps}`);
1333
+ });
1334
+ console.log();
1335
+ });
1336
+ const installedCount = components.filter((c) => c.installed).length;
1337
+ const totalCount = components.length;
1338
+ if (!options.installed && !options.available) {
1339
+ console.log(chalk6.dim(`Total: ${totalCount} components (${installedCount} installed, ${totalCount - installedCount} available)
1340
+ `));
1341
+ } else {
1342
+ console.log(chalk6.dim(`Total: ${totalCount} components
1343
+ `));
1344
+ }
1345
+ }
1346
+
1347
+ // src/commands/debug.ts
1348
+ import chalk7 from "chalk";
1349
+ import path11 from "path";
1350
+ import fs11 from "fs/promises";
1351
+ async function debug(options) {
1352
+ const cwd = process.cwd();
1353
+ console.log(chalk7.bold("\n\u{1F50D} Pittaya UI Debug Information\n"));
1354
+ console.log(chalk7.dim(`Working directory: ${cwd}
1355
+ `));
1356
+ let config;
1357
+ try {
1358
+ const loaded = await loadProjectConfig(cwd);
1359
+ config = loaded.config;
1360
+ applyPittayaProjectConfig(loaded.pittaya);
1361
+ console.log(chalk7.green("\u2705 components.json found"));
1362
+ console.log(chalk7.dim(` Path: ${path11.join(cwd, "components.json")}`));
1363
+ } catch (error) {
1364
+ console.log(chalk7.red("\u274C components.json not found\n"));
1365
+ return;
1366
+ }
1367
+ const usesSrc = await hasSrcDirectory(cwd);
1368
+ console.log(chalk7.green(`\u2705 Project structure: ${usesSrc ? "src/" : "root"}`));
1369
+ console.log(chalk7.bold("\n\u{1F4CB} Configured Aliases:"));
1370
+ console.log(chalk7.dim(` components: ${config.aliases.components}`));
1371
+ console.log(chalk7.dim(` utils: ${config.aliases.utils}`));
1372
+ console.log(chalk7.dim(` ui: ${config.aliases.ui}`));
1373
+ const resolvedUi = await resolveAliasPath(config.aliases.ui, cwd);
1374
+ const resolvedLib = await resolveAliasPath(config.aliases.lib || "@/lib", cwd);
1375
+ console.log(chalk7.bold("\n\u{1F4C2} Resolved Paths:"));
1376
+ console.log(chalk7.dim(` UI components: ${resolvedUi}`));
1377
+ console.log(chalk7.dim(` Libraries: ${resolvedLib}`));
1378
+ if (options.component) {
1379
+ console.log(chalk7.bold(`
1380
+ \u{1F50D} Debugging component: ${options.component}
1381
+ `));
1382
+ try {
1383
+ const component = await getRegistryComponent(options.component, config);
1384
+ if (!component) {
1385
+ console.log(chalk7.red(`\u274C Component not found in registry
1386
+ `));
1387
+ return;
1388
+ }
1389
+ console.log(chalk7.green(`\u2705 Component found in registry`));
1390
+ console.log(chalk7.dim(` Type: ${component.type}`));
1391
+ console.log(chalk7.dim(` Files: ${component.files.length}`));
1392
+ console.log(chalk7.bold("\n\u{1F4C1} Expected Files:"));
1393
+ for (const file of component.files) {
1394
+ const targetPath = await resolveTargetPath(file.name, component.type, config);
1395
+ const fullPath = path11.join(cwd, targetPath);
1396
+ const exists = await fs11.access(fullPath).then(() => true).catch(() => false);
1397
+ const statusIcon = exists ? chalk7.green("\u2705") : chalk7.red("\u274C");
1398
+ const statusText = exists ? chalk7.green("EXISTS") : chalk7.red("NOT FOUND");
1399
+ console.log(` ${statusIcon} ${file.name}`);
1400
+ console.log(chalk7.dim(` Expected: ${fullPath}`));
1401
+ console.log(chalk7.dim(` Status: ${statusText}`));
1402
+ if (!exists) {
1403
+ const dir = path11.dirname(fullPath);
1404
+ try {
1405
+ const dirExists = await fs11.access(dir).then(() => true).catch(() => false);
1406
+ if (dirExists) {
1407
+ const filesInDir = await fs11.readdir(dir);
1408
+ const baseName = path11.basename(file.name, path11.extname(file.name));
1409
+ const similarFiles = filesInDir.filter(
1410
+ (f) => f.includes(baseName) || f.toLowerCase().includes(baseName.toLowerCase())
1411
+ );
1412
+ if (similarFiles.length > 0) {
1413
+ console.log(chalk7.yellow(` \u{1F4A1} Similar files found in directory:`));
1414
+ similarFiles.forEach((f) => {
1415
+ console.log(chalk7.dim(` - ${f}`));
1416
+ });
1417
+ }
1418
+ } else {
1419
+ console.log(chalk7.red(` \u26A0\uFE0F Directory doesn't exist: ${dir}`));
1420
+ }
1421
+ } catch (err) {
1422
+ }
1423
+ }
1424
+ }
1425
+ const isInstalled = await isComponentInstalled(options.component, config);
1426
+ console.log(chalk7.bold(`
1427
+ \u{1F4CA} Installation Status:`));
1428
+ if (isInstalled) {
1429
+ console.log(chalk7.green(` \u2705 Component is detected as INSTALLED`));
1430
+ } else {
1431
+ console.log(chalk7.red(` \u274C Component is detected as NOT INSTALLED`));
1432
+ console.log(chalk7.yellow(` \u{1F4A1} All files must exist for component to be considered installed`));
1433
+ }
1434
+ } catch (error) {
1435
+ console.log(chalk7.red(`
1436
+ \u274C Error debugging component:`));
1437
+ console.error(error);
1438
+ }
1439
+ } else {
1440
+ console.log(chalk7.yellow("\n\u{1F4A1} To debug a specific component, use:"));
1441
+ console.log(chalk7.dim(" npx pittaya debug --component <name>\n"));
887
1442
  }
888
- return fileName;
889
1443
  }
890
1444
 
891
1445
  // src/index.ts
892
1446
  import { readFileSync } from "fs";
893
- import { fileURLToPath } from "url";
1447
+ import { fileURLToPath as fileURLToPath2 } from "url";
894
1448
  import { dirname, join } from "path";
895
- var __filename = fileURLToPath(import.meta.url);
1449
+ var __filename = fileURLToPath2(import.meta.url);
896
1450
  var __dirname = dirname(__filename);
897
1451
  var packageJson = JSON.parse(
898
1452
  readFileSync(join(__dirname, "../package.json"), "utf-8")
@@ -903,6 +1457,8 @@ program.command("init").description("Initialize Pittaya UI in your project").opt
903
1457
  program.command("add").description("Add a component to your project").argument("[components...]", "Component names to add").option("-y, --yes", "Skip confirmations and overwrite existing files").option("-o, --overwrite", "Overwrite existing files").option("-a, --all", "Add all available components").option("--add-missing-deps", "Automatically install missing dependencies").action(add);
904
1458
  program.command("diff").description("Check for component updates").argument("[components...]", "Component names to check (leave empty for interactive mode)").option("-a, --all", "Check all installed components").action(diff);
905
1459
  program.command("update").description("Update components to latest version").argument("[components...]", "Component names to update (leave empty for interactive mode)").option("-a, --all", "Update all installed components").option("-y, --yes", "Skip confirmation prompts").option("-f, --force", "Force update even if no changes detected").action(update);
1460
+ program.command("list").description("List available and installed components").option("--installed", "Show only installed components").option("--available", "Show only available components").option("--json", "Output in JSON format").action(list);
906
1461
  program.command("credits").description("Show Pittaya UI creators").action(credits);
1462
+ program.command("debug").description("Debug component installation issues").option("-c, --component <name>", "Component name to debug").action(debug);
907
1463
  program.parse();
908
1464
  //# sourceMappingURL=index.js.map