pittaya 0.0.8 → 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 path4 from "path";
11
- import fs4 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
  }
@@ -126,28 +172,28 @@ async function checkMissingDependencies(dependencies) {
126
172
  }
127
173
 
128
174
  // src/utils/component-checker.ts
129
- import path3 from "path";
130
- import fs3 from "fs/promises";
175
+ import path4 from "path";
176
+ import fs4 from "fs/promises";
131
177
 
132
178
  // src/utils/project-structure.ts
133
- import fs2 from "fs/promises";
134
- import path2 from "path";
179
+ import fs3 from "fs/promises";
180
+ import path3 from "path";
135
181
  async function hasSrcDirectory(cwd = process.cwd()) {
136
182
  try {
137
- const srcPath = path2.join(cwd, "src");
138
- await fs2.access(srcPath);
183
+ const srcPath = path3.join(cwd, "src");
184
+ await fs3.access(srcPath);
139
185
  const commonDirs = ["app", "components", "lib", "pages"];
140
186
  for (const dir of commonDirs) {
141
187
  try {
142
- await fs2.access(path2.join(srcPath, dir));
188
+ await fs3.access(path3.join(srcPath, dir));
143
189
  return true;
144
190
  } catch {
145
191
  continue;
146
192
  }
147
193
  }
148
194
  try {
149
- const tsconfigPath = path2.join(cwd, "tsconfig.json");
150
- const tsconfig = JSON.parse(await fs2.readFile(tsconfigPath, "utf-8"));
195
+ const tsconfigPath = path3.join(cwd, "tsconfig.json");
196
+ const tsconfig = JSON.parse(await fs3.readFile(tsconfigPath, "utf-8"));
151
197
  if (tsconfig.compilerOptions?.paths) {
152
198
  const paths = tsconfig.compilerOptions.paths;
153
199
  if (paths["@/*"]?.includes("./src/*")) {
@@ -184,12 +230,12 @@ async function getDefaultPaths(cwd = process.cwd()) {
184
230
  // src/utils/component-checker.ts
185
231
  async function isComponentInstalled(name, config) {
186
232
  try {
187
- const component = await getRegistryComponent(name);
233
+ const component = await getRegistryComponent(name, config);
188
234
  if (!component) return false;
189
235
  for (const file of component.files) {
190
236
  const targetPath = await resolveTargetPath(file.name, component.type, config);
191
- const filePath = path3.join(process.cwd(), targetPath);
192
- const exists = await fs3.access(filePath).then(() => true).catch(() => false);
237
+ const filePath = path4.join(process.cwd(), targetPath);
238
+ const exists = await fs4.access(filePath).then(() => true).catch(() => false);
193
239
  if (!exists) return false;
194
240
  }
195
241
  return true;
@@ -198,29 +244,135 @@ async function isComponentInstalled(name, config) {
198
244
  }
199
245
  }
200
246
  async function resolveTargetPath(fileName, type, config) {
247
+ const normalized = fileName.replace(/\\/g, "/");
201
248
  if (type === "registry:ui") {
202
249
  const resolvedPath = await resolveAliasPath(config.aliases.ui);
203
- return path3.join(resolvedPath, fileName);
250
+ const relative = normalized.replace(/^ui\//, "");
251
+ return path4.join(resolvedPath, relative);
204
252
  }
205
253
  if (type === "registry:lib") {
206
254
  const resolvedPath = await resolveAliasPath(config.aliases.lib);
207
- return path3.join(resolvedPath, fileName);
255
+ const relative = normalized.replace(/^lib\//, "");
256
+ return path4.join(resolvedPath, relative);
208
257
  }
209
258
  if (type === "registry:hook") {
210
259
  const resolvedPath = await resolveAliasPath(config.aliases.hooks);
211
- return path3.join(resolvedPath, fileName);
260
+ return path4.join(resolvedPath, fileName);
212
261
  }
213
262
  return fileName;
214
263
  }
215
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
+
216
366
  // src/commands/add.ts
217
367
  async function add(components, options) {
218
368
  const cwd = process.cwd();
219
- const componentsJsonPath = path4.join(cwd, "components.json");
220
369
  let config;
370
+ let pittayaConfig;
221
371
  try {
222
- const configContent = await fs4.readFile(componentsJsonPath, "utf-8");
223
- config = JSON.parse(configContent);
372
+ const loaded = await loadProjectConfig(cwd);
373
+ config = loaded.config;
374
+ pittayaConfig = loaded.pittaya;
375
+ applyPittayaProjectConfig(pittayaConfig);
224
376
  } catch (error) {
225
377
  console.log(chalk.red("\n\u274C components.json not found.\n"));
226
378
  console.log(
@@ -229,10 +381,14 @@ async function add(components, options) {
229
381
  );
230
382
  return;
231
383
  }
384
+ const resolvedOptions = {
385
+ ...options,
386
+ addMissingDeps: options.addMissingDeps ?? pittayaConfig?.install?.autoInstallDeps ?? false
387
+ };
232
388
  const spinner = ora("Fetching available components...").start();
233
389
  let registry;
234
390
  try {
235
- registry = await fetchRegistry();
391
+ registry = await fetchRegistry(config);
236
392
  spinner.succeed("Registry loaded!");
237
393
  } catch (error) {
238
394
  spinner.fail("Error loading registry");
@@ -266,7 +422,7 @@ Adding ${components.length} component(s)...
266
422
  `)
267
423
  );
268
424
  for (const componentName of components) {
269
- await addComponent(componentName, config, options);
425
+ await addComponent(componentName, config, resolvedOptions);
270
426
  }
271
427
  console.log(chalk.green("\n\u2705 Components added successfully!\n"));
272
428
  }
@@ -278,7 +434,7 @@ async function addComponent(name, config, options) {
278
434
  }
279
435
  const spinner = ora(`Installing ${chalk.bold(name)}...`).start();
280
436
  try {
281
- const component = await getRegistryComponent(name);
437
+ const component = await getRegistryComponent(name, config);
282
438
  if (!component) {
283
439
  spinner.fail(`Component "${name}" not found in registry.`);
284
440
  return;
@@ -298,12 +454,10 @@ async function addComponent(name, config, options) {
298
454
  if (options.addMissingDeps) {
299
455
  console.log(chalk.dim("Installing dependencies automatically...\n"));
300
456
  } else {
301
- const { install } = await prompts({
302
- type: "confirm",
303
- name: "install",
304
- message: "Do you want to install the dependencies now?",
305
- initial: true
306
- });
457
+ const install = await confirm(
458
+ "Do you want to install the dependencies now?",
459
+ true
460
+ );
307
461
  if (!install) {
308
462
  console.log(chalk.yellow("\n\u26A0\uFE0F Component not installed. Install the dependencies manually and try again.\n"));
309
463
  return;
@@ -336,25 +490,23 @@ async function addComponent(name, config, options) {
336
490
  }
337
491
  for (const file of component.files) {
338
492
  const targetPath = await resolveTargetPath(file.name, component.type, config);
339
- const filePath = path4.join(process.cwd(), targetPath);
340
- const exists = await fs4.access(filePath).then(() => true).catch(() => false);
493
+ const filePath = path6.join(process.cwd(), targetPath);
494
+ const exists = await fs6.access(filePath).then(() => true).catch(() => false);
341
495
  if (exists && !options.overwrite && !options.yes) {
342
496
  spinner.stop();
343
- const { overwrite } = await prompts({
344
- type: "confirm",
345
- name: "overwrite",
346
- message: `${targetPath} already exists. Overwrite?`,
347
- initial: false
348
- });
497
+ const overwrite = await confirm(
498
+ `${targetPath} already exists. Overwrite?`,
499
+ false
500
+ );
349
501
  if (!overwrite) {
350
502
  spinner.warn(`Skipping ${targetPath}`);
351
503
  continue;
352
504
  }
353
505
  spinner.start();
354
506
  }
355
- await fs4.mkdir(path4.dirname(filePath), { recursive: true });
507
+ await fs6.mkdir(path6.dirname(filePath), { recursive: true });
356
508
  const content = transformImports(file.content, config);
357
- await fs4.writeFile(filePath, content, "utf-8");
509
+ await fs6.writeFile(filePath, content, "utf-8");
358
510
  }
359
511
  spinner.succeed(`${chalk.bold(name)} installed successfully!`);
360
512
  } catch (error) {
@@ -367,29 +519,230 @@ async function addComponent(name, config, options) {
367
519
  import chalk2 from "chalk";
368
520
  import ora2 from "ora";
369
521
  import prompts2 from "prompts";
370
- import path5 from "path";
371
- import fs5 from "fs/promises";
522
+ import path8 from "path";
523
+ import fs8 from "fs/promises";
372
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
373
709
  async function init(options) {
374
- 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
+ );
375
718
  const cwd = process.cwd();
376
- const componentsJsonPath = path5.join(cwd, "components.json");
377
- const exists = await fs5.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);
378
723
  if (exists && !options.yes) {
379
- const { overwrite } = await prompts2({
380
- type: "confirm",
381
- name: "overwrite",
382
- message: "components.json already exists. Do you want to overwrite it?",
383
- initial: false
384
- });
724
+ const overwrite = await confirm(
725
+ `${chalk2.underline("components.json")} already exists. Do you want to overwrite it?`,
726
+ false
727
+ );
385
728
  if (!overwrite) {
386
729
  console.log(chalk2.yellow("\n\u274C Operation cancelled.\n"));
387
730
  return;
388
731
  }
389
732
  }
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
+ }
390
743
  const defaultPaths = await getDefaultPaths(cwd);
391
744
  const config = options.yes ? {
392
- style: "new-york",
745
+ style: "pittaya",
393
746
  tailwindCss: defaultPaths.globalsCss,
394
747
  rsc: true,
395
748
  componentsPath: defaultPaths.components,
@@ -400,9 +753,9 @@ async function init(options) {
400
753
  name: "style",
401
754
  message: "Which style would you like to use?",
402
755
  choices: [
756
+ { title: "Pittaya", value: "pittaya" },
403
757
  { title: "New York", value: "new-york" },
404
- { title: "Default", value: "default" },
405
- { title: "Recife", value: "recife" }
758
+ { title: "Default", value: "default" }
406
759
  ]
407
760
  },
408
761
  {
@@ -412,10 +765,14 @@ async function init(options) {
412
765
  initial: defaultPaths.globalsCss
413
766
  },
414
767
  {
415
- type: "confirm",
768
+ type: "select",
416
769
  name: "rsc",
417
770
  message: "Use React Server Components?",
418
- initial: true
771
+ choices: [
772
+ { title: "Yes", value: true },
773
+ { title: "No", value: false }
774
+ ],
775
+ initial: 0
419
776
  },
420
777
  {
421
778
  type: "text",
@@ -436,11 +793,11 @@ async function init(options) {
436
793
  }
437
794
  const componentsJson = {
438
795
  $schema: "https://raw.githubusercontent.com/pittaya-ui/cli/main/registry/schema.json",
439
- style: config.style || "new-york",
796
+ style: config.style || "pittaya",
440
797
  rsc: config.rsc ?? true,
441
798
  tsx: true,
442
799
  tailwind: {
443
- config: "tailwind.config.ts",
800
+ config: "",
444
801
  css: config.tailwindCss || "src/app/globals.css",
445
802
  baseColor: "neutral",
446
803
  cssVariables: true,
@@ -449,33 +806,71 @@ async function init(options) {
449
806
  aliases: {
450
807
  components: config.componentsPath || "@/components",
451
808
  utils: config.utilsPath || "@/lib/utils",
452
- ui: `${config.componentsPath || "@/components"}/pittaya/ui`,
809
+ ui: "@/components/ui",
453
810
  lib: "@/lib",
454
811
  hooks: "@/hooks"
455
812
  },
456
813
  iconLibrary: "lucide"
457
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
+ }
458
835
  const spinner = ora2("Creating components.json...").start();
459
- await fs5.writeFile(
836
+ await fs8.writeFile(
460
837
  componentsJsonPath,
461
838
  JSON.stringify(componentsJson, null, 2)
462
839
  );
463
840
  spinner.succeed("components.json created successfully!");
464
- 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);
465
847
  const depsSpinner = ora2("Installing base dependencies...").start();
466
848
  try {
849
+ const packageManager = await detectPackageManager();
467
850
  await execa2(packageManager, [
468
851
  packageManager === "yarn" ? "add" : "install",
469
852
  "class-variance-authority",
470
853
  "clsx",
471
- "tailwind-merge"
854
+ "tailwind-merge",
855
+ "lucide-react",
856
+ "tw-animate-css"
472
857
  ]);
473
858
  depsSpinner.succeed("Dependencies installed!");
474
859
  } catch (error) {
475
860
  depsSpinner.fail("Error installing dependencies");
476
861
  console.error(error);
477
862
  }
478
- 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
+ `));
479
874
  console.log(chalk2.dim("Next steps:"));
480
875
  console.log(
481
876
  chalk2.dim(
@@ -489,19 +884,6 @@ async function init(options) {
489
884
  )
490
885
  );
491
886
  }
492
- async function detectPackageManager2() {
493
- try {
494
- await fs5.access("pnpm-lock.yaml");
495
- return "pnpm";
496
- } catch {
497
- }
498
- try {
499
- await fs5.access("yarn.lock");
500
- return "yarn";
501
- } catch {
502
- }
503
- return "npm";
504
- }
505
887
 
506
888
  // src/commands/credits.ts
507
889
  import chalk3 from "chalk";
@@ -517,15 +899,15 @@ async function credits() {
517
899
  import chalk4 from "chalk";
518
900
  import ora3 from "ora";
519
901
  import prompts3 from "prompts";
520
- import path6 from "path";
521
- import fs6 from "fs/promises";
902
+ import path9 from "path";
903
+ import fs9 from "fs/promises";
522
904
  async function diff(components, options) {
523
905
  const cwd = process.cwd();
524
- const componentsJsonPath = path6.join(cwd, "components.json");
525
906
  let config;
526
907
  try {
527
- const configContent = await fs6.readFile(componentsJsonPath, "utf-8");
528
- config = JSON.parse(configContent);
908
+ const loaded = await loadProjectConfig(cwd);
909
+ config = loaded.config;
910
+ applyPittayaProjectConfig(loaded.pittaya);
529
911
  } catch (error) {
530
912
  console.log(chalk4.red("\n\u274C components.json not found.\n"));
531
913
  console.log(
@@ -537,7 +919,7 @@ async function diff(components, options) {
537
919
  const spinner = ora3("Fetching registry...").start();
538
920
  let registry;
539
921
  try {
540
- registry = await fetchRegistry();
922
+ registry = await fetchRegistry(config);
541
923
  spinner.succeed("Registry loaded!");
542
924
  } catch (error) {
543
925
  spinner.fail("Error loading registry");
@@ -602,7 +984,7 @@ Checking ${componentsToCheck.length} component(s)...
602
984
  }
603
985
  async function checkComponentDiff(name, config) {
604
986
  try {
605
- const component = await getRegistryComponent(name);
987
+ const component = await getRegistryComponent(name, config);
606
988
  if (!component) {
607
989
  console.log(chalk4.red(` \u274C Component "${name}" not found in registry.`));
608
990
  return null;
@@ -616,9 +998,9 @@ async function checkComponentDiff(name, config) {
616
998
  let hasChanges = false;
617
999
  for (const file of component.files) {
618
1000
  const targetPath = await resolveTargetPath(file.name, component.type, config);
619
- const filePath = path6.join(process.cwd(), targetPath);
1001
+ const filePath = path9.join(process.cwd(), targetPath);
620
1002
  try {
621
- const localContent = await fs6.readFile(filePath, "utf-8");
1003
+ const localContent = await fs9.readFile(filePath, "utf-8");
622
1004
  const registryContent = transformImports(file.content, config);
623
1005
  const contentMatches = localContent.trim() === registryContent.trim();
624
1006
  fileDiffs.push({
@@ -689,15 +1071,15 @@ function displayDiffResults(results) {
689
1071
  import chalk5 from "chalk";
690
1072
  import ora4 from "ora";
691
1073
  import prompts4 from "prompts";
692
- import path7 from "path";
693
- import fs7 from "fs/promises";
1074
+ import path10 from "path";
1075
+ import fs10 from "fs/promises";
694
1076
  async function update(components, options) {
695
1077
  const cwd = process.cwd();
696
- const componentsJsonPath = path7.join(cwd, "components.json");
697
1078
  let config;
698
1079
  try {
699
- const configContent = await fs7.readFile(componentsJsonPath, "utf-8");
700
- config = JSON.parse(configContent);
1080
+ const loaded = await loadProjectConfig(cwd);
1081
+ config = loaded.config;
1082
+ applyPittayaProjectConfig(loaded.pittaya);
701
1083
  } catch (error) {
702
1084
  console.log(chalk5.red("\n\u274C components.json not found.\n"));
703
1085
  console.log(
@@ -709,7 +1091,7 @@ async function update(components, options) {
709
1091
  const spinner = ora4("Fetching registry...").start();
710
1092
  let registry;
711
1093
  try {
712
- registry = await fetchRegistry();
1094
+ registry = await fetchRegistry(config);
713
1095
  spinner.succeed("Registry loaded!");
714
1096
  } catch (error) {
715
1097
  spinner.fail("Error loading registry");
@@ -730,13 +1112,11 @@ async function update(components, options) {
730
1112
  return;
731
1113
  }
732
1114
  if (!options.yes && !options.force) {
733
- const { confirm } = await prompts4({
734
- type: "confirm",
735
- name: "confirm",
736
- message: `Update ${componentsToUpdate.length} component(s)?`,
737
- initial: true
738
- });
739
- if (!confirm) {
1115
+ const shouldUpdate = await confirm(
1116
+ `Update ${componentsToUpdate.length} component(s)?`,
1117
+ true
1118
+ );
1119
+ if (!shouldUpdate) {
740
1120
  console.log(chalk5.yellow("\n\u274C Update cancelled.\n"));
741
1121
  return;
742
1122
  }
@@ -785,9 +1165,9 @@ Checking ${componentsToUpdate.length} component(s) for updates...
785
1165
  async function hasComponentChanges(component, config) {
786
1166
  for (const file of component.files) {
787
1167
  const targetPath = await resolveTargetPath(file.name, component.type, config);
788
- const filePath = path7.join(process.cwd(), targetPath);
1168
+ const filePath = path10.join(process.cwd(), targetPath);
789
1169
  try {
790
- const localContent = await fs7.readFile(filePath, "utf-8");
1170
+ const localContent = await fs10.readFile(filePath, "utf-8");
791
1171
  const registryContent = transformImports(file.content, config);
792
1172
  if (localContent.trim() !== registryContent.trim()) {
793
1173
  return true;
@@ -800,7 +1180,7 @@ async function hasComponentChanges(component, config) {
800
1180
  }
801
1181
  async function updateComponent(name, config, options) {
802
1182
  try {
803
- const component = await getRegistryComponent(name);
1183
+ const component = await getRegistryComponent(name, config);
804
1184
  if (!component) {
805
1185
  console.log(chalk5.red(` \u274C Component "${name}" not found in registry.`));
806
1186
  return { name, updated: false, skipped: true, reason: "not found in registry" };
@@ -816,13 +1196,8 @@ async function updateComponent(name, config, options) {
816
1196
  return { name, updated: false, skipped: true, reason: "already up to date" };
817
1197
  }
818
1198
  if (!options.yes && !options.force) {
819
- const { confirm } = await prompts4({
820
- type: "confirm",
821
- name: "confirm",
822
- message: `Update ${chalk5.bold(name)}?`,
823
- initial: true
824
- });
825
- if (!confirm) {
1199
+ const shouldUpdate = await confirm(`Update ${chalk5.bold(name)}?`, true);
1200
+ if (!shouldUpdate) {
826
1201
  console.log(chalk5.yellow(` \u23ED\uFE0F Skipped ${name}`));
827
1202
  return { name, updated: false, skipped: true, reason: "user cancelled" };
828
1203
  }
@@ -830,10 +1205,10 @@ async function updateComponent(name, config, options) {
830
1205
  const spinner = ora4(`Updating ${chalk5.bold(name)}...`).start();
831
1206
  for (const file of component.files) {
832
1207
  const targetPath = await resolveTargetPath(file.name, component.type, config);
833
- const filePath = path7.join(process.cwd(), targetPath);
834
- await fs7.mkdir(path7.dirname(filePath), { recursive: true });
1208
+ const filePath = path10.join(process.cwd(), targetPath);
1209
+ await fs10.mkdir(path10.dirname(filePath), { recursive: true });
835
1210
  const content = transformImports(file.content, config);
836
- await fs7.writeFile(filePath, content, "utf-8");
1211
+ await fs10.writeFile(filePath, content, "utf-8");
837
1212
  }
838
1213
  spinner.succeed(`${chalk5.bold(name)} updated successfully!`);
839
1214
  return { name, updated: true, skipped: false };
@@ -869,15 +1244,13 @@ function displayUpdateResults(results) {
869
1244
  // src/commands/list.ts
870
1245
  import chalk6 from "chalk";
871
1246
  import ora5 from "ora";
872
- import path8 from "path";
873
- import fs8 from "fs/promises";
874
1247
  async function list(options) {
875
1248
  const cwd = process.cwd();
876
- const componentsJsonPath = path8.join(cwd, "components.json");
877
1249
  let config;
878
1250
  try {
879
- const configContent = await fs8.readFile(componentsJsonPath, "utf-8");
880
- config = JSON.parse(configContent);
1251
+ const loaded = await loadProjectConfig(cwd);
1252
+ config = loaded.config;
1253
+ applyPittayaProjectConfig(loaded.pittaya);
881
1254
  } catch (error) {
882
1255
  console.log(chalk6.red("\n\u274C components.json not found.\n"));
883
1256
  console.log(
@@ -889,7 +1262,7 @@ async function list(options) {
889
1262
  const spinner = ora5("Fetching components...").start();
890
1263
  let registry;
891
1264
  try {
892
- registry = await fetchRegistry();
1265
+ registry = await fetchRegistry(config);
893
1266
  spinner.succeed("Components loaded!");
894
1267
  } catch (error) {
895
1268
  spinner.fail("Error loading registry");
@@ -899,9 +1272,10 @@ async function list(options) {
899
1272
  const allComponents = registry.components || [];
900
1273
  const componentsWithStatus = await Promise.all(
901
1274
  allComponents.map(async (comp) => {
902
- const installed = await isComponentInstalled(comp.slug, config);
1275
+ const slug = comp.slug || comp.name;
1276
+ const installed = await isComponentInstalled(slug, config);
903
1277
  return {
904
- ...comp,
1278
+ ...comp.slug ? comp : { ...comp, slug },
905
1279
  installed
906
1280
  };
907
1281
  })
@@ -972,20 +1346,20 @@ function displayComponents(components, options) {
972
1346
 
973
1347
  // src/commands/debug.ts
974
1348
  import chalk7 from "chalk";
975
- import path9 from "path";
976
- import fs9 from "fs/promises";
1349
+ import path11 from "path";
1350
+ import fs11 from "fs/promises";
977
1351
  async function debug(options) {
978
1352
  const cwd = process.cwd();
979
- const componentsJsonPath = path9.join(cwd, "components.json");
980
1353
  console.log(chalk7.bold("\n\u{1F50D} Pittaya UI Debug Information\n"));
981
1354
  console.log(chalk7.dim(`Working directory: ${cwd}
982
1355
  `));
983
1356
  let config;
984
1357
  try {
985
- const configContent = await fs9.readFile(componentsJsonPath, "utf-8");
986
- config = JSON.parse(configContent);
1358
+ const loaded = await loadProjectConfig(cwd);
1359
+ config = loaded.config;
1360
+ applyPittayaProjectConfig(loaded.pittaya);
987
1361
  console.log(chalk7.green("\u2705 components.json found"));
988
- console.log(chalk7.dim(` Path: ${componentsJsonPath}`));
1362
+ console.log(chalk7.dim(` Path: ${path11.join(cwd, "components.json")}`));
989
1363
  } catch (error) {
990
1364
  console.log(chalk7.red("\u274C components.json not found\n"));
991
1365
  return;
@@ -1006,7 +1380,7 @@ async function debug(options) {
1006
1380
  \u{1F50D} Debugging component: ${options.component}
1007
1381
  `));
1008
1382
  try {
1009
- const component = await getRegistryComponent(options.component);
1383
+ const component = await getRegistryComponent(options.component, config);
1010
1384
  if (!component) {
1011
1385
  console.log(chalk7.red(`\u274C Component not found in registry
1012
1386
  `));
@@ -1018,20 +1392,20 @@ async function debug(options) {
1018
1392
  console.log(chalk7.bold("\n\u{1F4C1} Expected Files:"));
1019
1393
  for (const file of component.files) {
1020
1394
  const targetPath = await resolveTargetPath(file.name, component.type, config);
1021
- const fullPath = path9.join(cwd, targetPath);
1022
- const exists = await fs9.access(fullPath).then(() => true).catch(() => false);
1395
+ const fullPath = path11.join(cwd, targetPath);
1396
+ const exists = await fs11.access(fullPath).then(() => true).catch(() => false);
1023
1397
  const statusIcon = exists ? chalk7.green("\u2705") : chalk7.red("\u274C");
1024
1398
  const statusText = exists ? chalk7.green("EXISTS") : chalk7.red("NOT FOUND");
1025
1399
  console.log(` ${statusIcon} ${file.name}`);
1026
1400
  console.log(chalk7.dim(` Expected: ${fullPath}`));
1027
1401
  console.log(chalk7.dim(` Status: ${statusText}`));
1028
1402
  if (!exists) {
1029
- const dir = path9.dirname(fullPath);
1403
+ const dir = path11.dirname(fullPath);
1030
1404
  try {
1031
- const dirExists = await fs9.access(dir).then(() => true).catch(() => false);
1405
+ const dirExists = await fs11.access(dir).then(() => true).catch(() => false);
1032
1406
  if (dirExists) {
1033
- const filesInDir = await fs9.readdir(dir);
1034
- const baseName = path9.basename(file.name, path9.extname(file.name));
1407
+ const filesInDir = await fs11.readdir(dir);
1408
+ const baseName = path11.basename(file.name, path11.extname(file.name));
1035
1409
  const similarFiles = filesInDir.filter(
1036
1410
  (f) => f.includes(baseName) || f.toLowerCase().includes(baseName.toLowerCase())
1037
1411
  );
@@ -1070,9 +1444,9 @@ async function debug(options) {
1070
1444
 
1071
1445
  // src/index.ts
1072
1446
  import { readFileSync } from "fs";
1073
- import { fileURLToPath } from "url";
1447
+ import { fileURLToPath as fileURLToPath2 } from "url";
1074
1448
  import { dirname, join } from "path";
1075
- var __filename = fileURLToPath(import.meta.url);
1449
+ var __filename = fileURLToPath2(import.meta.url);
1076
1450
  var __dirname = dirname(__filename);
1077
1451
  var packageJson = JSON.parse(
1078
1452
  readFileSync(join(__dirname, "../package.json"), "utf-8")