@dinachi/cli 0.5.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +13 -5
  2. package/dist/index.js +771 -349
  3. package/package.json +6 -5
package/dist/index.js CHANGED
@@ -6,8 +6,8 @@ import { Command as Command3 } from "commander";
6
6
  // src/commands/add.ts
7
7
  import { Command } from "commander";
8
8
  import { execSync } from "child_process";
9
- import fs2 from "fs-extra";
10
- import path2 from "path";
9
+ import fs3 from "fs-extra";
10
+ import path3 from "path";
11
11
  import { fileURLToPath as fileURLToPath2 } from "url";
12
12
  import ora from "ora";
13
13
  import chalk from "chalk";
@@ -18,6 +18,48 @@ import path from "path";
18
18
  import { fileURLToPath } from "url";
19
19
  var __filename = fileURLToPath(import.meta.url);
20
20
  var __dirname = path.dirname(__filename);
21
+ function dirnameLike(input) {
22
+ const normalized = input.replace(/\/+$/, "");
23
+ const idx = normalized.lastIndexOf("/");
24
+ if (idx <= 0) {
25
+ return normalized;
26
+ }
27
+ return normalized.slice(0, idx);
28
+ }
29
+ function normalizeConfig(raw) {
30
+ const style = typeof raw.style === "string" ? raw.style : "default";
31
+ const rsc = typeof raw.rsc === "boolean" ? raw.rsc : false;
32
+ const tsx = typeof raw.tsx === "boolean" ? raw.tsx : true;
33
+ const tailwindConfig = typeof raw.tailwind?.config === "string" ? raw.tailwind.config : "tailwind.config.js";
34
+ const tailwindCss = typeof raw.tailwind?.css === "string" ? raw.tailwind.css : "src/index.css";
35
+ const tailwindBaseColor = typeof raw.tailwind?.baseColor === "string" ? raw.tailwind.baseColor : "slate";
36
+ const tailwindCssVariables = typeof raw.tailwind?.cssVariables === "boolean" ? raw.tailwind.cssVariables : true;
37
+ const rawComponentsAlias = typeof raw.aliases?.components === "string" ? raw.aliases.components : "./src/components";
38
+ const hasLegacyUiPathInComponents = rawComponentsAlias.replace(/\/+$/, "").endsWith("/ui");
39
+ const componentsAlias = hasLegacyUiPathInComponents ? dirnameLike(rawComponentsAlias) : rawComponentsAlias;
40
+ const uiAlias = typeof raw.aliases?.ui === "string" ? raw.aliases.ui : hasLegacyUiPathInComponents ? rawComponentsAlias : `${componentsAlias}/ui`;
41
+ const utilsAlias = typeof raw.aliases?.utils === "string" ? raw.aliases.utils : "./src/lib/utils";
42
+ const libAlias = typeof raw.aliases?.lib === "string" ? raw.aliases.lib : dirnameLike(utilsAlias);
43
+ const hooksAlias = typeof raw.aliases?.hooks === "string" ? raw.aliases.hooks : "./src/hooks";
44
+ return {
45
+ style,
46
+ rsc,
47
+ tsx,
48
+ tailwind: {
49
+ config: tailwindConfig,
50
+ css: tailwindCss,
51
+ baseColor: tailwindBaseColor,
52
+ cssVariables: tailwindCssVariables
53
+ },
54
+ aliases: {
55
+ components: componentsAlias,
56
+ utils: utilsAlias,
57
+ ui: uiAlias,
58
+ lib: libAlias,
59
+ hooks: hooksAlias
60
+ }
61
+ };
62
+ }
21
63
  function getUtilityRegistry() {
22
64
  return {
23
65
  cn: {
@@ -65,7 +107,6 @@ function getComponentRegistry() {
65
107
  description: "A customizable button component with multiple variants.",
66
108
  files: [{ name: "button.tsx" }, { name: "index.ts" }],
67
109
  dependencies: ["@base-ui/react", "class-variance-authority"],
68
- componentDependencies: ["core"],
69
110
  utilityDependencies: ["cn", "variants"]
70
111
  },
71
112
  checkbox: {
@@ -295,46 +336,327 @@ async function getConfig() {
295
336
  }
296
337
  try {
297
338
  const configContent = await fs.readFile(configPath, "utf-8");
298
- return JSON.parse(configContent);
299
- } catch (error) {
339
+ const parsed = JSON.parse(configContent);
340
+ return normalizeConfig(parsed);
341
+ } catch {
300
342
  return null;
301
343
  }
302
344
  }
303
345
 
346
+ // src/utils/package-manager.ts
347
+ import fs2 from "fs-extra";
348
+ import path2 from "path";
349
+ var LOCK_FILE_MAP = [
350
+ { file: "bun.lockb", manager: "bun" },
351
+ { file: "bun.lock", manager: "bun" },
352
+ { file: "pnpm-lock.yaml", manager: "pnpm" },
353
+ { file: "yarn.lock", manager: "yarn" },
354
+ { file: "package-lock.json", manager: "npm" }
355
+ ];
356
+ function walkUpDirectories(startDir) {
357
+ const dirs = [];
358
+ let current = path2.resolve(startDir);
359
+ while (true) {
360
+ dirs.push(current);
361
+ const parent = path2.dirname(current);
362
+ if (parent === current) {
363
+ break;
364
+ }
365
+ current = parent;
366
+ }
367
+ return dirs;
368
+ }
369
+ function detectManagerFromPackageJson(startDir) {
370
+ for (const dir of walkUpDirectories(startDir)) {
371
+ const packageJsonPath = path2.join(dir, "package.json");
372
+ if (!fs2.existsSync(packageJsonPath)) {
373
+ continue;
374
+ }
375
+ try {
376
+ const packageJson = JSON.parse(fs2.readFileSync(packageJsonPath, "utf-8"));
377
+ const value = packageJson.packageManager ?? "";
378
+ if (value.startsWith("pnpm@")) return "pnpm";
379
+ if (value.startsWith("yarn@")) return "yarn";
380
+ if (value.startsWith("bun@")) return "bun";
381
+ if (value.startsWith("npm@")) return "npm";
382
+ } catch {
383
+ continue;
384
+ }
385
+ }
386
+ return null;
387
+ }
388
+ function detectPackageManager(startDir = process.cwd()) {
389
+ const packageJsonManager = detectManagerFromPackageJson(startDir);
390
+ if (packageJsonManager) {
391
+ return packageJsonManager;
392
+ }
393
+ for (const dir of walkUpDirectories(startDir)) {
394
+ for (const entry of LOCK_FILE_MAP) {
395
+ if (fs2.existsSync(path2.join(dir, entry.file))) {
396
+ return entry.manager;
397
+ }
398
+ }
399
+ }
400
+ return "npm";
401
+ }
402
+ function getInstallCommand(pm, deps) {
403
+ const depsStr = deps.join(" ");
404
+ switch (pm) {
405
+ case "bun":
406
+ return `bun add ${depsStr}`;
407
+ case "pnpm":
408
+ return `pnpm add ${depsStr}`;
409
+ case "yarn":
410
+ return `yarn add ${depsStr}`;
411
+ default:
412
+ return `npm install ${depsStr}`;
413
+ }
414
+ }
415
+
416
+ // src/utils/json.ts
417
+ function parseJsonWithComments(content) {
418
+ let result = "";
419
+ let inString = false;
420
+ let inSingleLineComment = false;
421
+ let inMultiLineComment = false;
422
+ let isEscaped = false;
423
+ for (let i = 0; i < content.length; i += 1) {
424
+ const char = content[i];
425
+ const next = content[i + 1];
426
+ if (inSingleLineComment) {
427
+ if (char === "\n") {
428
+ inSingleLineComment = false;
429
+ result += char;
430
+ }
431
+ continue;
432
+ }
433
+ if (inMultiLineComment) {
434
+ if (char === "*" && next === "/") {
435
+ inMultiLineComment = false;
436
+ i += 1;
437
+ } else if (char === "\n") {
438
+ result += char;
439
+ }
440
+ continue;
441
+ }
442
+ if (inString) {
443
+ result += char;
444
+ if (!isEscaped && char === '"') {
445
+ inString = false;
446
+ }
447
+ isEscaped = !isEscaped && char === "\\";
448
+ continue;
449
+ }
450
+ if (char === '"') {
451
+ inString = true;
452
+ isEscaped = false;
453
+ result += char;
454
+ continue;
455
+ }
456
+ if (char === "/" && next === "/") {
457
+ inSingleLineComment = true;
458
+ i += 1;
459
+ continue;
460
+ }
461
+ if (char === "/" && next === "*") {
462
+ inMultiLineComment = true;
463
+ i += 1;
464
+ continue;
465
+ }
466
+ result += char;
467
+ }
468
+ const withoutLineComments = result;
469
+ const withoutTrailingCommas = withoutLineComments.replace(/,\s*([}\]])/g, "$1");
470
+ return JSON.parse(withoutTrailingCommas);
471
+ }
472
+
473
+ // src/utils/dependencies.ts
474
+ var DEPENDENCY_VERSION_MAP = {
475
+ "@base-ui/react": "^1.2.0",
476
+ "lucide-react": "^0.552.0",
477
+ "class-variance-authority": "^0.7.1",
478
+ "tailwindcss-animate": "^1.0.7",
479
+ "clsx": "^2.1.1",
480
+ "tailwind-merge": "^3.3.1"
481
+ };
482
+ function toInstallSpec(dep) {
483
+ const version = DEPENDENCY_VERSION_MAP[dep];
484
+ return version ? `${dep}@${version}` : dep;
485
+ }
486
+
304
487
  // src/commands/add.ts
305
488
  var __filename2 = fileURLToPath2(import.meta.url);
306
- var __dirname2 = path2.dirname(__filename2);
307
- function resolveAliasPath(aliasPath, projectRoot) {
308
- const cleanPath = aliasPath.replace(/^@\//, "").replace(/^\.\//, "");
309
- return path2.join(projectRoot, cleanPath);
489
+ var __dirname2 = path3.dirname(__filename2);
490
+ function stripTemplateDirective(content) {
491
+ return content.replace(/^\/\/ @ts-nocheck\s*\n/m, "");
492
+ }
493
+ function stripExtension(filePath) {
494
+ return filePath.replace(/\.[^./\\]+$/, "");
310
495
  }
311
- function getComponentDependencies(componentName) {
496
+ function toImportPath(fromFilePath, toFilePath) {
497
+ const relativePath = path3.relative(path3.dirname(fromFilePath), stripExtension(toFilePath)).replace(/\\/g, "/");
498
+ return relativePath.startsWith(".") ? relativePath : `./${relativePath}`;
499
+ }
500
+ function matchPathPattern(pattern, input) {
501
+ if (!pattern.includes("*")) {
502
+ return pattern === input ? "" : null;
503
+ }
504
+ const [prefix, suffix] = pattern.split("*");
505
+ if (!input.startsWith(prefix) || !input.endsWith(suffix)) {
506
+ return null;
507
+ }
508
+ return input.slice(prefix.length, input.length - suffix.length);
509
+ }
510
+ function readCompilerPathConfig(projectRoot) {
511
+ const configFiles = ["tsconfig.json", "jsconfig.json"];
512
+ for (const configFile of configFiles) {
513
+ const configPath = path3.join(projectRoot, configFile);
514
+ if (!fs3.existsSync(configPath)) {
515
+ continue;
516
+ }
517
+ try {
518
+ const content = fs3.readFileSync(configPath, "utf-8");
519
+ const parsed = parseJsonWithComments(content);
520
+ const compilerOptions = parsed.compilerOptions ?? {};
521
+ const baseUrl = path3.resolve(
522
+ projectRoot,
523
+ typeof compilerOptions.baseUrl === "string" ? compilerOptions.baseUrl : "."
524
+ );
525
+ const rawPaths = compilerOptions.paths ?? {};
526
+ const paths = {};
527
+ for (const [key, value] of Object.entries(rawPaths)) {
528
+ if (!Array.isArray(value)) {
529
+ continue;
530
+ }
531
+ const targets = value.filter((entry) => typeof entry === "string");
532
+ if (targets.length > 0) {
533
+ paths[key] = targets;
534
+ }
535
+ }
536
+ return { baseUrl, paths };
537
+ } catch {
538
+ continue;
539
+ }
540
+ }
541
+ return null;
542
+ }
543
+ function resolveConfiguredPath(aliasOrPath, projectRoot, compilerConfig) {
544
+ const normalized = aliasOrPath.replace(/\\/g, "/").trim();
545
+ if (path3.isAbsolute(normalized)) {
546
+ return normalized;
547
+ }
548
+ if (normalized.startsWith("./") || normalized.startsWith("../")) {
549
+ return path3.resolve(projectRoot, normalized);
550
+ }
551
+ if (normalized.startsWith("/")) {
552
+ return path3.resolve(projectRoot, `.${normalized}`);
553
+ }
554
+ if (compilerConfig) {
555
+ for (const [pattern, targets] of Object.entries(compilerConfig.paths)) {
556
+ const wildcard = matchPathPattern(pattern, normalized);
557
+ if (wildcard === null) {
558
+ continue;
559
+ }
560
+ const target = targets[0];
561
+ if (!target) {
562
+ continue;
563
+ }
564
+ const mappedTarget = target.includes("*") ? target.replace("*", wildcard) : target;
565
+ return path3.resolve(compilerConfig.baseUrl, mappedTarget);
566
+ }
567
+ }
568
+ if (normalized.startsWith("@/") || normalized.startsWith("~/")) {
569
+ return path3.resolve(projectRoot, normalized.slice(2));
570
+ }
571
+ return path3.resolve(projectRoot, normalized);
572
+ }
573
+ function rewriteTemplateImports(content, targetFilePath, utilsFilePath, libDirPath) {
574
+ const utilsImportPath = toImportPath(targetFilePath, utilsFilePath);
575
+ const variantsImportPath = toImportPath(targetFilePath, path3.join(libDirPath, "variants.ts"));
576
+ return content.replace(/(['"])@\/lib\/utils\1/g, `$1${utilsImportPath}$1`).replace(/(['"])@\/lib\/variants\1/g, `$1${variantsImportPath}$1`);
577
+ }
578
+ function getComponentDependencies(componentName, visited = /* @__PURE__ */ new Set()) {
579
+ if (visited.has(componentName)) {
580
+ return [];
581
+ }
582
+ visited.add(componentName);
312
583
  const registry = getComponentRegistry();
313
584
  const component = registry[componentName];
314
585
  if (!component) return [];
315
- let dependencies = component.componentDependencies || [];
586
+ const dependencies = [];
316
587
  for (const dep of component.componentDependencies || []) {
317
- dependencies = [...dependencies, ...getComponentDependencies(dep)];
588
+ if (!registry[dep]) {
589
+ continue;
590
+ }
591
+ dependencies.push(dep, ...getComponentDependencies(dep, visited));
318
592
  }
319
593
  return [...new Set(dependencies)];
320
594
  }
321
- async function ensureTailwindConfig(deps, configFileName) {
322
- const needsAnimatePlugin = deps.includes("tailwindcss-animate");
323
- if (!needsAnimatePlugin) return;
324
- let configPath = path2.join(process.cwd(), configFileName);
325
- if (!fs2.existsSync(configPath)) {
326
- const alternatives = ["tailwind.config.js", "tailwind.config.ts", "tailwind.config.mjs"];
327
- for (const alt of alternatives) {
328
- const altPath = path2.join(process.cwd(), alt);
329
- if (fs2.existsSync(altPath)) {
330
- configPath = altPath;
331
- break;
332
- }
595
+ function detectTailwindConfigPath(projectRoot, configuredName) {
596
+ const preferred = resolveConfiguredPath(configuredName, projectRoot, null);
597
+ if (fs3.existsSync(preferred)) {
598
+ return preferred;
599
+ }
600
+ const alternatives = [
601
+ "tailwind.config.ts",
602
+ "tailwind.config.js",
603
+ "tailwind.config.mjs",
604
+ "tailwind.config.cjs",
605
+ "tailwind.config.cts",
606
+ "tailwind.config.mts"
607
+ ];
608
+ for (const candidate of alternatives) {
609
+ const candidatePath = path3.join(projectRoot, candidate);
610
+ if (fs3.existsSync(candidatePath)) {
611
+ return candidatePath;
333
612
  }
334
613
  }
335
- if (!fs2.existsSync(configPath)) {
336
- const configContent = `/** @type {import('tailwindcss').Config} */
337
- export default {
614
+ return preferred;
615
+ }
616
+ function insertTailwindPlugin(content, pluginExpression) {
617
+ if (/plugins\s*:\s*\[/.test(content)) {
618
+ return content.replace(/plugins\s*:\s*\[([\s\S]*?)\]/m, (_match, pluginsContent) => {
619
+ const trimmedPlugins = pluginsContent.trim();
620
+ if (trimmedPlugins.length === 0) {
621
+ return `plugins: [
622
+ ${pluginExpression},
623
+ ]`;
624
+ }
625
+ const normalizedPlugins = pluginsContent.trimEnd();
626
+ const withTrailingComma = normalizedPlugins.endsWith(",") ? normalizedPlugins : `${normalizedPlugins},`;
627
+ return `plugins: [
628
+ ${withTrailingComma}
629
+ ${pluginExpression},
630
+ ]`;
631
+ });
632
+ }
633
+ const trimmed = content.trimEnd();
634
+ const hasSemicolon = trimmed.endsWith(";");
635
+ const withoutSemicolon = hasSemicolon ? trimmed.slice(0, -1) : trimmed;
636
+ const closingBraceIndex = withoutSemicolon.lastIndexOf("}");
637
+ if (closingBraceIndex === -1) {
638
+ return null;
639
+ }
640
+ const beforeClosingBrace = withoutSemicolon.slice(0, closingBraceIndex).trimEnd();
641
+ const needsComma = beforeClosingBrace.length > 0 && !beforeClosingBrace.endsWith(",") && !beforeClosingBrace.endsWith("{");
642
+ const next = `${withoutSemicolon.slice(0, closingBraceIndex)}${needsComma ? "," : ""}
643
+ plugins: [
644
+ ${pluginExpression},
645
+ ],
646
+ }${hasSemicolon ? ";" : ""}
647
+ `;
648
+ return next;
649
+ }
650
+ async function ensureTailwindConfig(deps, projectRoot, configuredFileName) {
651
+ if (!deps.includes("tailwindcss-animate")) {
652
+ return null;
653
+ }
654
+ const configPath = detectTailwindConfigPath(projectRoot, configuredFileName || "tailwind.config.js");
655
+ if (!fs3.existsSync(configPath)) {
656
+ const ext2 = path3.extname(configPath);
657
+ const isCjs2 = ext2 === ".cjs";
658
+ const configContent = isCjs2 ? `/** @type {import('tailwindcss').Config} */
659
+ module.exports = {
338
660
  content: [
339
661
  "./src/**/*.{js,ts,jsx,tsx}",
340
662
  "./app/**/*.{js,ts,jsx,tsx}",
@@ -344,265 +666,346 @@ export default {
344
666
  theme: {
345
667
  extend: {},
346
668
  },
347
- plugins: [
348
- require("tailwindcss-animate"),
669
+ plugins: [require("tailwindcss-animate")],
670
+ }
671
+ ` : `import tailwindcssAnimate from "tailwindcss-animate"
672
+
673
+ export default {
674
+ content: [
675
+ "./src/**/*.{js,ts,jsx,tsx}",
676
+ "./app/**/*.{js,ts,jsx,tsx}",
677
+ "./components/**/*.{js,ts,jsx,tsx}",
678
+ "./pages/**/*.{js,ts,jsx,tsx}",
349
679
  ],
350
- };`;
351
- await fs2.writeFile(configPath, configContent);
680
+ theme: {
681
+ extend: {},
682
+ },
683
+ plugins: [tailwindcssAnimate],
684
+ }
685
+ `;
686
+ await fs3.ensureDir(path3.dirname(configPath));
687
+ await fs3.writeFile(configPath, configContent);
352
688
  return { created: true, path: configPath };
353
- } else {
354
- const configContent = await fs2.readFile(configPath, "utf-8");
355
- if (!configContent.includes("tailwindcss-animate")) {
356
- let updatedContent = configContent;
357
- if (configContent.includes("plugins:")) {
358
- updatedContent = configContent.replace(
359
- /plugins:\s*\[([\s\S]*?)\]/,
360
- (match, pluginsContent) => {
361
- const cleanPlugins = pluginsContent.trim();
362
- const newPlugin = 'require("tailwindcss-animate")';
363
- if (cleanPlugins === "") {
364
- return `plugins: [
365
- ${newPlugin},
366
- ]`;
367
- } else {
368
- return `plugins: [
369
- ${pluginsContent},
370
- ${newPlugin},
371
- ]`;
372
- }
373
- }
374
- );
375
- } else {
376
- updatedContent = configContent.replace(
377
- /}\s*;?\s*$/,
378
- ' plugins: [\n require("tailwindcss-animate"),\n ],\n};'
379
- );
380
- }
381
- await fs2.writeFile(configPath, updatedContent);
382
- return { updated: true, path: configPath };
383
- }
384
689
  }
385
- return { exists: true, path: configPath };
690
+ const currentContent = await fs3.readFile(configPath, "utf-8");
691
+ if (currentContent.includes("tailwindcss-animate")) {
692
+ return { exists: true, path: configPath };
693
+ }
694
+ const ext = path3.extname(configPath);
695
+ const isCjs = ext === ".cjs" || /module\.exports\s*=/.test(currentContent);
696
+ const pluginExpression = isCjs ? 'require("tailwindcss-animate")' : "tailwindcssAnimate";
697
+ let updatedContent = currentContent;
698
+ if (!isCjs && !/from\s+['"]tailwindcss-animate['"]/.test(updatedContent)) {
699
+ updatedContent = `import tailwindcssAnimate from "tailwindcss-animate"
700
+ ${updatedContent}`;
701
+ }
702
+ const merged = insertTailwindPlugin(updatedContent, pluginExpression);
703
+ if (!merged) {
704
+ return { skipped: true, path: configPath };
705
+ }
706
+ await fs3.writeFile(configPath, merged);
707
+ return { updated: true, path: configPath };
708
+ }
709
+ function escapeRegex(value) {
710
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
386
711
  }
387
- async function handleIndexFile(sourcePath, targetPath, componentName, allFilesAdded, targetDir) {
388
- let templateContent = await fs2.readFile(sourcePath, "utf-8");
389
- templateContent = templateContent.replace(/^\/\/ @ts-nocheck\s*\n/m, "");
390
- if (!fs2.existsSync(targetPath)) {
391
- await fs2.writeFile(targetPath, templateContent);
392
- allFilesAdded.push({ name: "index.ts", path: path2.join(targetDir, "index.ts") });
393
- } else {
394
- const existingContent = await fs2.readFile(targetPath, "utf-8");
395
- const exportRegex = /export\s+\*\s+from\s+['"]\.\/([^'"]+)['"]/g;
396
- const templateExports = [];
397
- let match;
398
- while ((match = exportRegex.exec(templateContent)) !== null) {
399
- templateExports.push(match[1]);
712
+ async function handleIndexFile(sourcePath, targetPath, allFilesAdded, targetDir) {
713
+ const templateContent = stripTemplateDirective(await fs3.readFile(sourcePath, "utf-8"));
714
+ if (!fs3.existsSync(targetPath)) {
715
+ await fs3.writeFile(targetPath, templateContent);
716
+ allFilesAdded.push({ name: "index.ts", path: path3.join(targetDir, "index.ts") });
717
+ return;
718
+ }
719
+ const existingContent = await fs3.readFile(targetPath, "utf-8");
720
+ const exportLines = templateContent.split("\n").map((line) => line.trim()).filter((line) => /^export\s+/.test(line) && /from\s+['"]\.\/[^'"]+['"]/.test(line));
721
+ const linesToAppend = [];
722
+ for (const line of exportLines) {
723
+ const match = line.match(/from\s+['"]\.\/([^'"]+)['"]/);
724
+ if (!match) {
725
+ continue;
400
726
  }
401
- const componentExportExists = existingContent.includes(`export * from './${componentName}'`);
402
- if (!componentExportExists && templateExports.includes(componentName)) {
403
- const newExportLine = `export * from './${componentName}'`;
404
- const updatedContent = existingContent.trim() + "\n" + newExportLine + "\n";
405
- await fs2.writeFile(targetPath, updatedContent);
406
- allFilesAdded.push({ name: "index.ts", path: path2.join(targetDir, "index.ts") });
727
+ const modulePath = match[1];
728
+ const modulePathPattern = new RegExp(`from\\s+['"]\\./${escapeRegex(modulePath)}['"]`);
729
+ if (modulePathPattern.test(existingContent)) {
730
+ continue;
407
731
  }
732
+ linesToAppend.push(line.endsWith(";") ? line : `${line};`);
733
+ }
734
+ if (linesToAppend.length === 0) {
735
+ return;
408
736
  }
737
+ const updatedContent = `${existingContent.trimEnd()}
738
+ ${linesToAppend.join("\n")}
739
+ `;
740
+ await fs3.writeFile(targetPath, updatedContent);
741
+ allFilesAdded.push({ name: "index.ts", path: path3.join(targetDir, "index.ts") });
409
742
  }
410
- var addCommand = new Command("add").description("Add a component to your project").argument("[component]", "Name of the component (optional when using --all)").option("-y, --yes", "Skip confirmation prompts").option("-o, --overwrite", "Overwrite existing files").option("-a, --all", "Install all available components").action(async (componentName, options) => {
411
- const spinner = ora("Adding component...").start();
412
- try {
413
- const config = await getConfig();
414
- if (!config) {
415
- spinner.fail("\u274C No components.json found. Run `dinachi init` first.");
416
- process.exit(1);
417
- }
418
- const registry = getComponentRegistry();
419
- let componentsToInstall = [];
420
- if (options.all) {
421
- const allComponents = Object.keys(registry);
422
- spinner.text = `Installing all ${allComponents.length} components...`;
423
- const allComponentsWithDeps = /* @__PURE__ */ new Set();
424
- for (const name of allComponents) {
425
- allComponentsWithDeps.add(name);
426
- const deps = getComponentDependencies(name);
427
- deps.forEach((dep) => allComponentsWithDeps.add(dep));
428
- }
429
- componentsToInstall = Array.from(allComponentsWithDeps);
430
- } else {
431
- if (!componentName) {
432
- spinner.fail("\u274C Component name is required when not using --all flag.");
433
- console.log("Available components:");
434
- Object.keys(registry).forEach((name) => {
435
- console.log(` ${chalk.cyan(name)}`);
436
- });
743
+ var addCommand = new Command("add").description("Add a component to your project").argument("[component]", "Name of the component (optional when using --all)").option("-y, --yes", "Skip confirmation prompts").option("-o, --overwrite", "Overwrite existing files").option("-a, --all", "Install all available components").option("--skip-install", "Skip package installation").action(
744
+ async (componentName, options) => {
745
+ const spinner = ora("Adding component...").start();
746
+ try {
747
+ const config = await getConfig();
748
+ if (!config) {
749
+ spinner.fail("\u274C No components.json found. Run `dinachi init` first.");
437
750
  process.exit(1);
438
751
  }
439
- const component = registry[componentName];
440
- if (!component) {
441
- spinner.fail(`\u274C Component "${componentName}" not found.`);
442
- console.log("Available components:");
443
- Object.keys(registry).forEach((name) => {
444
- console.log(` ${chalk.cyan(name)}`);
445
- });
446
- process.exit(1);
752
+ const projectRoot = process.cwd();
753
+ const compilerPathConfig = readCompilerPathConfig(projectRoot);
754
+ const registry = getComponentRegistry();
755
+ let componentsToInstall = [];
756
+ if (options.all) {
757
+ const allComponents = Object.keys(registry);
758
+ spinner.text = `Installing all ${allComponents.length} components...`;
759
+ const allComponentsWithDeps = /* @__PURE__ */ new Set();
760
+ for (const name of allComponents) {
761
+ allComponentsWithDeps.add(name);
762
+ const deps = getComponentDependencies(name);
763
+ deps.forEach((dep) => allComponentsWithDeps.add(dep));
764
+ }
765
+ componentsToInstall = Array.from(allComponentsWithDeps);
766
+ } else {
767
+ if (!componentName) {
768
+ spinner.fail("\u274C Component name is required when not using --all flag.");
769
+ console.log("Available components:");
770
+ Object.keys(registry).forEach((name) => {
771
+ console.log(` ${chalk.cyan(name)}`);
772
+ });
773
+ process.exit(1);
774
+ }
775
+ const component = registry[componentName];
776
+ if (!component) {
777
+ spinner.fail(`\u274C Component "${componentName}" not found.`);
778
+ console.log("Available components:");
779
+ Object.keys(registry).forEach((name) => {
780
+ console.log(` ${chalk.cyan(name)}`);
781
+ });
782
+ process.exit(1);
783
+ }
784
+ componentsToInstall = [componentName, ...getComponentDependencies(componentName)];
447
785
  }
448
- componentsToInstall = [componentName, ...getComponentDependencies(componentName)];
449
- }
450
- if (!options.all) {
451
- spinner.text = `Installing ${componentsToInstall.join(", ")}...`;
452
- }
453
- const componentDir = resolveAliasPath(config.aliases.ui, process.cwd());
454
- await fs2.ensureDir(componentDir);
455
- let allFilesAdded = [];
456
- let allDepsInstalled = [];
457
- let allUtilityDeps = [];
458
- for (const name of componentsToInstall) {
459
- const comp = registry[name];
460
- if (!comp) continue;
461
- if (comp.utilityDependencies?.length) {
462
- allUtilityDeps.push(...comp.utilityDependencies);
786
+ if (!options.all) {
787
+ spinner.text = `Installing ${componentsToInstall.join(", ")}...`;
463
788
  }
464
- }
465
- const utilityRegistry = getUtilityRegistry();
466
- const uniqueUtilityDeps = [...new Set(allUtilityDeps)];
467
- const utilsDir = resolveAliasPath(config.aliases.lib, process.cwd());
468
- if (uniqueUtilityDeps.length > 0) {
469
- await fs2.ensureDir(utilsDir);
470
- for (const utilityName of uniqueUtilityDeps) {
471
- const utility = utilityRegistry[utilityName];
472
- if (!utility) continue;
473
- const utilityFilename = `${utility.name}.ts`;
474
- const sourcePath = path2.join(__dirname2, "../templates/utils", utilityFilename);
475
- const targetPath = path2.join(utilsDir, utilityFilename);
476
- if (!fs2.existsSync(targetPath)) {
477
- let content = await fs2.readFile(sourcePath, "utf-8");
478
- content = content.replace(/^\/\/ @ts-nocheck\s*\n/m, "");
479
- await fs2.writeFile(targetPath, content);
480
- allFilesAdded.push({
481
- name: utilityFilename,
482
- path: path2.join(utilsDir, utilityFilename)
483
- });
789
+ const componentDir = resolveConfiguredPath(config.aliases.ui, projectRoot, compilerPathConfig);
790
+ const libDir = resolveConfiguredPath(config.aliases.lib, projectRoot, compilerPathConfig);
791
+ const utilsFilePath = resolveConfiguredPath(config.aliases.utils, projectRoot, compilerPathConfig);
792
+ await fs3.ensureDir(componentDir);
793
+ await fs3.ensureDir(libDir);
794
+ const allFilesAdded = [];
795
+ const allDepsInstalled = [];
796
+ const allUtilityDeps = [];
797
+ for (const name of componentsToInstall) {
798
+ const comp = registry[name];
799
+ if (!comp) continue;
800
+ if (comp.utilityDependencies?.length) {
801
+ allUtilityDeps.push(...comp.utilityDependencies);
802
+ }
803
+ }
804
+ const utilityRegistry = getUtilityRegistry();
805
+ const uniqueUtilityDeps = [...new Set(allUtilityDeps)];
806
+ if (uniqueUtilityDeps.length > 0) {
807
+ for (const utilityName of uniqueUtilityDeps) {
808
+ const utility = utilityRegistry[utilityName];
809
+ if (!utility) continue;
810
+ const utilityFilename = `${utility.name}.ts`;
811
+ const sourcePath = path3.join(__dirname2, "../templates/utils", utilityFilename);
812
+ const targetPath = path3.join(libDir, utilityFilename);
813
+ if (!fs3.existsSync(targetPath)) {
814
+ const content = stripTemplateDirective(await fs3.readFile(sourcePath, "utf-8"));
815
+ await fs3.writeFile(targetPath, content);
816
+ allFilesAdded.push({
817
+ name: utilityFilename,
818
+ path: targetPath
819
+ });
820
+ }
484
821
  if (utility.dependencies?.length) {
485
822
  allDepsInstalled.push(...utility.dependencies);
486
823
  }
487
824
  }
488
825
  }
489
- }
490
- for (const name of componentsToInstall) {
491
- const comp = registry[name];
492
- if (!comp) continue;
493
- for (const file of comp.files) {
494
- const sourcePath = path2.join(__dirname2, "../templates", name, file.name);
495
- const targetPath = path2.join(componentDir, file.name);
496
- if (file.name === "index.ts") {
497
- await handleIndexFile(sourcePath, targetPath, name, allFilesAdded, componentDir);
498
- } else {
499
- if (fs2.existsSync(targetPath) && !options.overwrite) {
826
+ for (const name of componentsToInstall) {
827
+ const comp = registry[name];
828
+ if (!comp) continue;
829
+ for (const file of comp.files) {
830
+ const sourcePath = path3.join(__dirname2, "../templates", name, file.name);
831
+ const targetPath = path3.join(componentDir, file.name);
832
+ if (file.name === "index.ts") {
833
+ await handleIndexFile(sourcePath, targetPath, allFilesAdded, componentDir);
834
+ continue;
835
+ }
836
+ if (fs3.existsSync(targetPath) && !options.overwrite) {
500
837
  spinner.warn(`\u26A0\uFE0F ${file.name} already exists. Use --overwrite to replace it.`);
501
838
  continue;
502
839
  }
503
- let content = await fs2.readFile(sourcePath, "utf-8");
504
- content = content.replace(/^\/\/ @ts-nocheck\s*\n/m, "");
505
- await fs2.writeFile(targetPath, content);
506
- allFilesAdded.push({ name: file.name, path: path2.join(componentDir, file.name) });
840
+ const templateContent = stripTemplateDirective(await fs3.readFile(sourcePath, "utf-8"));
841
+ const rewrittenContent = rewriteTemplateImports(templateContent, targetPath, utilsFilePath, libDir);
842
+ await fs3.writeFile(targetPath, rewrittenContent);
843
+ allFilesAdded.push({ name: file.name, path: targetPath });
844
+ }
845
+ if (comp.dependencies?.length) {
846
+ allDepsInstalled.push(...comp.dependencies);
507
847
  }
508
848
  }
509
- if (comp.dependencies?.length) {
510
- allDepsInstalled.push(...comp.dependencies);
511
- }
512
- }
513
- let tailwindConfigInfo = null;
514
- if (allDepsInstalled.includes("tailwindcss-animate")) {
515
849
  spinner.text = "Updating Tailwind configuration...";
516
- const configFileName = config.tailwind?.config || "tailwind.config.js";
517
- tailwindConfigInfo = await ensureTailwindConfig(allDepsInstalled, configFileName);
518
- }
519
- const packageJsonPath = path2.join(process.cwd(), "package.json");
520
- const packageJson = JSON.parse(await fs2.readFile(packageJsonPath, "utf-8"));
521
- const allDeps = { ...packageJson.dependencies, ...packageJson.devDependencies };
522
- const missingDeps = [...new Set(allDepsInstalled)].filter((dep) => !allDeps[dep]);
523
- if (allDepsInstalled.length > 0) {
524
- spinner.text = "Installing dependencies...";
525
- if (missingDeps.length > 0) {
850
+ const tailwindConfigInfo = await ensureTailwindConfig(
851
+ allDepsInstalled,
852
+ projectRoot,
853
+ config.tailwind?.config || "tailwind.config.js"
854
+ );
855
+ const packageJsonPath = path3.join(projectRoot, "package.json");
856
+ if (!fs3.existsSync(packageJsonPath)) {
857
+ throw new Error("No package.json found in the current directory.");
858
+ }
859
+ const packageJson = JSON.parse(await fs3.readFile(packageJsonPath, "utf-8"));
860
+ const declaredDeps = { ...packageJson.dependencies ?? {}, ...packageJson.devDependencies ?? {} };
861
+ const uniqueDeps = [...new Set(allDepsInstalled)];
862
+ const missingDeps = uniqueDeps.filter((dep) => !declaredDeps[dep]);
863
+ if (!options.skipInstall && missingDeps.length > 0) {
864
+ spinner.text = "Installing dependencies...";
526
865
  try {
527
- const packageManager = getPackageManager();
528
- const installCmd = getInstallCommand(packageManager, missingDeps);
529
- execSync(installCmd, { stdio: "inherit" });
530
- } catch (error) {
866
+ const packageManager = detectPackageManager(projectRoot);
867
+ const installCmd = getInstallCommand(packageManager, missingDeps.map(toInstallSpec));
868
+ execSync(installCmd, { stdio: "inherit", cwd: projectRoot });
869
+ } catch {
531
870
  spinner.warn(`\u26A0\uFE0F Failed to install dependencies. Please install manually: ${missingDeps.join(" ")}`);
532
871
  }
533
- } else {
872
+ } else if (!options.skipInstall && uniqueDeps.length > 0) {
534
873
  spinner.text = "All dependencies already installed.";
535
874
  }
536
- }
537
- if (options.all) {
538
- spinner.succeed(`\u2705 Added all ${componentsToInstall.length} components!`);
539
- } else {
540
- spinner.succeed(`\u2705 Added ${componentsToInstall.join(", ")}!`);
541
- }
542
- console.log();
543
- console.log("Files added:");
544
- allFilesAdded.forEach((file) => {
545
- console.log(` ${chalk.green("+")} ${file.path}`);
546
- });
547
- if (tailwindConfigInfo) {
548
- console.log();
549
- if (tailwindConfigInfo.created) {
550
- console.log(` ${chalk.green("+")} ${tailwindConfigInfo.path} (created with tailwindcss-animate plugin)`);
551
- } else if (tailwindConfigInfo.updated) {
552
- console.log(` ${chalk.blue("~")} ${tailwindConfigInfo.path} (updated with tailwindcss-animate plugin)`);
875
+ if (options.all) {
876
+ spinner.succeed(`\u2705 Added all ${componentsToInstall.length} components!`);
877
+ } else {
878
+ spinner.succeed(`\u2705 Added ${componentsToInstall.join(", ")}!`);
553
879
  }
554
- }
555
- if (missingDeps.length > 0) {
556
- console.log();
557
- console.log("Dependencies installed:");
558
- missingDeps.forEach((dep) => {
559
- console.log(` ${chalk.green("\u2713")} ${dep}`);
560
- });
561
- } else if (allDepsInstalled.length > 0) {
562
880
  console.log();
563
- console.log("Dependencies (already installed):");
564
- [...new Set(allDepsInstalled)].forEach((dep) => {
565
- console.log(` ${chalk.blue("~")} ${dep}`);
881
+ console.log("Files added:");
882
+ allFilesAdded.forEach((file) => {
883
+ console.log(` ${chalk.green("+")} ${file.path}`);
566
884
  });
885
+ if (tailwindConfigInfo) {
886
+ console.log();
887
+ if (tailwindConfigInfo.created) {
888
+ console.log(` ${chalk.green("+")} ${tailwindConfigInfo.path} (created with tailwindcss-animate plugin)`);
889
+ } else if (tailwindConfigInfo.updated) {
890
+ console.log(` ${chalk.blue("~")} ${tailwindConfigInfo.path} (updated with tailwindcss-animate plugin)`);
891
+ } else if (tailwindConfigInfo.skipped) {
892
+ console.log(
893
+ ` ${chalk.yellow("!")} ${tailwindConfigInfo.path} (could not safely update automatically; add tailwindcss-animate manually)`
894
+ );
895
+ }
896
+ }
897
+ if (options.skipInstall && missingDeps.length > 0) {
898
+ console.log();
899
+ console.log("Dependencies to install manually:");
900
+ missingDeps.forEach((dep) => {
901
+ console.log(` ${chalk.yellow("\u2022")} ${toInstallSpec(dep)}`);
902
+ });
903
+ } else if (missingDeps.length > 0) {
904
+ console.log();
905
+ console.log("Dependencies installed:");
906
+ missingDeps.forEach((dep) => {
907
+ console.log(` ${chalk.green("\u2713")} ${toInstallSpec(dep)}`);
908
+ });
909
+ } else if (uniqueDeps.length > 0) {
910
+ console.log();
911
+ console.log("Dependencies (already installed):");
912
+ uniqueDeps.forEach((dep) => {
913
+ console.log(` ${chalk.blue("~")} ${dep}`);
914
+ });
915
+ }
916
+ } catch (error) {
917
+ spinner.fail(`\u274C Failed to add component: ${error instanceof Error ? error.message : error}`);
918
+ process.exit(1);
567
919
  }
568
- } catch (error) {
569
- spinner.fail(`\u274C Failed to add component: ${error instanceof Error ? error.message : error}`);
570
- process.exit(1);
571
920
  }
572
- });
573
- function getPackageManager() {
574
- if (fs2.existsSync("bun.lockb") || fs2.existsSync("bun.lock")) return "bun";
575
- if (fs2.existsSync("pnpm-lock.yaml")) return "pnpm";
576
- if (fs2.existsSync("yarn.lock")) return "yarn";
577
- return "npm";
578
- }
579
- function getInstallCommand(pm, deps) {
580
- const depsStr = deps.join(" ");
581
- switch (pm) {
582
- case "bun":
583
- return `bun add ${depsStr}`;
584
- case "pnpm":
585
- return `pnpm add ${depsStr}`;
586
- case "yarn":
587
- return `yarn add ${depsStr}`;
588
- default:
589
- return `npm install ${depsStr}`;
590
- }
591
- }
921
+ );
592
922
 
593
923
  // src/commands/init.ts
594
924
  import { Command as Command2 } from "commander";
595
925
  import { execSync as execSync2 } from "child_process";
596
- import fs3 from "fs-extra";
597
- import path3 from "path";
926
+ import fs4 from "fs-extra";
927
+ import path4 from "path";
598
928
  import prompts from "prompts";
599
929
  import chalk2 from "chalk";
600
930
  import ora2 from "ora";
601
- var initCommand = new Command2("init").description("Initialize Dinachi UI in your project").action(async () => {
931
+ function normalizeProjectPath(inputPath, projectRoot) {
932
+ const absolutePath = path4.isAbsolute(inputPath) ? path4.normalize(inputPath) : path4.resolve(projectRoot, inputPath);
933
+ const relativePath = path4.relative(projectRoot, absolutePath).replace(/\\/g, "/");
934
+ const withoutPrefix = relativePath.replace(/^\.\//, "").replace(/\/$/, "");
935
+ return withoutPrefix.length > 0 ? withoutPrefix : ".";
936
+ }
937
+ function toConfigPath(relativePath) {
938
+ return relativePath === "." ? "." : `./${relativePath.replace(/\\/g, "/")}`;
939
+ }
940
+ function createUtilsFileContent() {
941
+ return `import { type ClassValue, clsx } from "clsx"
942
+ import { twMerge } from "tailwind-merge"
943
+
944
+ export function cn(...inputs: ClassValue[]) {
945
+ return twMerge(clsx(inputs))
946
+ }
947
+ `;
948
+ }
949
+ function readJsonConfig(filePath) {
950
+ try {
951
+ const content = fs4.readFileSync(filePath, "utf-8");
952
+ return parseJsonWithComments(content);
953
+ } catch {
954
+ return null;
955
+ }
956
+ }
957
+ async function ensureAtAlias(projectRoot, srcDir, isTypeScript) {
958
+ const tsConfigPath = path4.join(projectRoot, "tsconfig.json");
959
+ const jsConfigPath = path4.join(projectRoot, "jsconfig.json");
960
+ const configPath = fs4.existsSync(tsConfigPath) ? tsConfigPath : fs4.existsSync(jsConfigPath) ? jsConfigPath : path4.join(projectRoot, isTypeScript ? "tsconfig.json" : "jsconfig.json");
961
+ const existedBefore = fs4.existsSync(configPath);
962
+ const parsedConfig = readJsonConfig(configPath);
963
+ if (existedBefore && !parsedConfig) {
964
+ return { path: configPath, skipped: true };
965
+ }
966
+ const parsed = parsedConfig ?? {};
967
+ const compilerOptions = parsed.compilerOptions ?? {};
968
+ const rawPaths = compilerOptions.paths ?? {};
969
+ const paths = {};
970
+ for (const [key, value] of Object.entries(rawPaths)) {
971
+ if (!Array.isArray(value)) {
972
+ continue;
973
+ }
974
+ const targets = value.filter((entry) => typeof entry === "string");
975
+ if (targets.length > 0) {
976
+ paths[key] = targets;
977
+ }
978
+ }
979
+ const aliasTarget = srcDir === "." ? "*" : `${srcDir}/*`;
980
+ const alreadyConfigured = compilerOptions.baseUrl === "." && Array.isArray(paths["@/*"]) && paths["@/*"][0] === aliasTarget;
981
+ if (alreadyConfigured) {
982
+ return { path: configPath };
983
+ }
984
+ const nextConfig = {
985
+ ...parsed,
986
+ compilerOptions: {
987
+ ...compilerOptions,
988
+ baseUrl: ".",
989
+ paths: {
990
+ ...paths,
991
+ "@/*": [aliasTarget]
992
+ }
993
+ }
994
+ };
995
+ try {
996
+ await fs4.writeFile(configPath, `${JSON.stringify(nextConfig, null, 2)}
997
+ `);
998
+ return { path: configPath, [existedBefore ? "updated" : "created"]: true };
999
+ } catch {
1000
+ return { path: configPath, skipped: true };
1001
+ }
1002
+ }
1003
+ var initCommand = new Command2("init").description("Initialize Dinachi UI in your project").option("--skip-install", "Skip package installation").action(async (options) => {
602
1004
  console.log(chalk2.bold.cyan("\u{1F3A8} Welcome to Dinachi UI!"));
603
1005
  console.log();
604
- const packageJsonPath = path3.join(process.cwd(), "package.json");
605
- if (!fs3.existsSync(packageJsonPath)) {
1006
+ const projectRoot = process.cwd();
1007
+ const packageJsonPath = path4.join(projectRoot, "package.json");
1008
+ if (!fs4.existsSync(packageJsonPath)) {
606
1009
  console.log(chalk2.red("\u274C No package.json found. Please run this command in a valid project."));
607
1010
  process.exit(1);
608
1011
  }
@@ -629,69 +1032,91 @@ var initCommand = new Command2("init").description("Initialize Dinachi UI in you
629
1032
  }
630
1033
  const spinner = ora2("Setting up Dinachi UI...").start();
631
1034
  try {
632
- const normalizedComponentsPath = normalizePath(response.componentsPath);
633
- const normalizedUtilsPath = normalizePath(response.utilsPath);
634
- await fs3.ensureDir(normalizedComponentsPath);
635
- await fs3.ensureDir(normalizedUtilsPath);
636
- const utilsContent = `import { type ClassValue, clsx } from "clsx"
637
- import { twMerge } from "tailwind-merge"
638
-
639
- export function cn(...inputs: ClassValue[]) {
640
- return twMerge(clsx(inputs))
641
- }
642
- `;
643
- await fs3.writeFile(path3.join(normalizedUtilsPath, "utils.ts"), utilsContent);
1035
+ const normalizedComponentsPath = normalizeProjectPath(response.componentsPath, projectRoot);
1036
+ const normalizedUtilsPath = normalizeProjectPath(response.utilsPath, projectRoot);
1037
+ const componentsDirPath = path4.resolve(projectRoot, normalizedComponentsPath);
1038
+ const utilsDirPath = path4.resolve(projectRoot, normalizedUtilsPath);
1039
+ const utilsFilePath = path4.join(utilsDirPath, "utils.ts");
1040
+ await fs4.ensureDir(componentsDirPath);
1041
+ await fs4.ensureDir(utilsDirPath);
1042
+ const utilsContent = createUtilsFileContent();
1043
+ await fs4.writeFile(utilsFilePath, utilsContent);
644
1044
  spinner.text = "Installing dependencies...";
645
- const deps = [
646
- "class-variance-authority",
647
- "clsx",
648
- "tailwind-merge"
649
- ];
650
- const packageManager = getPackageManager2();
651
- const installCmd = getInstallCommand2(packageManager, deps);
652
- execSync2(installCmd, { stdio: "inherit" });
653
- const componentsAlias = pathToAlias(normalizedComponentsPath);
654
- const libAlias = pathToAlias(normalizedUtilsPath);
655
- const uiAlias = componentsAlias;
656
- const libParentAlias = pathToAlias(path3.dirname(normalizedUtilsPath) === "." ? normalizedUtilsPath : normalizedUtilsPath);
657
- const rscEnabled = projectConfig.framework === "next.js";
658
- const configContent = JSON.stringify({
659
- style: "default",
660
- rsc: rscEnabled,
661
- tsx: true,
662
- tailwind: {
663
- config: projectConfig.tailwindConfig,
664
- css: projectConfig.cssPath,
665
- baseColor: "slate",
666
- cssVariables: true
667
- },
668
- aliases: {
669
- components: pathToAlias(path3.dirname(normalizedComponentsPath)),
670
- utils: `${libAlias}/utils`,
671
- ui: uiAlias,
672
- lib: libAlias,
673
- hooks: `${pathToAlias(projectConfig.srcDir)}/hooks`
1045
+ const deps = ["class-variance-authority", "clsx", "tailwind-merge"];
1046
+ if (!options.skipInstall) {
1047
+ const packageManager = detectPackageManager(projectRoot);
1048
+ const installCmd = getInstallCommand(packageManager, deps.map(toInstallSpec));
1049
+ execSync2(installCmd, { stdio: "inherit", cwd: projectRoot });
1050
+ }
1051
+ const hooksPath = projectConfig.srcDir === "." ? "hooks" : path4.join(projectConfig.srcDir, "hooks").replace(/\\/g, "/");
1052
+ const configContent = JSON.stringify(
1053
+ {
1054
+ style: "default",
1055
+ rsc: projectConfig.framework === "next.js",
1056
+ tsx: true,
1057
+ tailwind: {
1058
+ config: projectConfig.tailwindConfig,
1059
+ css: projectConfig.cssPath,
1060
+ baseColor: "slate",
1061
+ cssVariables: true
1062
+ },
1063
+ aliases: {
1064
+ components: toConfigPath(path4.dirname(normalizedComponentsPath)),
1065
+ utils: toConfigPath(path4.join(normalizedUtilsPath, "utils")),
1066
+ ui: toConfigPath(normalizedComponentsPath),
1067
+ lib: toConfigPath(normalizedUtilsPath),
1068
+ hooks: toConfigPath(hooksPath)
1069
+ },
1070
+ iconLibrary: "lucide"
674
1071
  },
675
- iconLibrary: "lucide"
676
- }, null, 2);
677
- await fs3.writeFile("components.json", configContent);
1072
+ null,
1073
+ 2
1074
+ );
1075
+ await fs4.writeFile(path4.join(projectRoot, "components.json"), `${configContent}
1076
+ `);
1077
+ const aliasConfigUpdate = await ensureAtAlias(projectRoot, projectConfig.srcDir, projectConfig.isTypeScript);
678
1078
  spinner.succeed("\u2705 Dinachi UI setup complete!");
679
1079
  console.log();
680
1080
  console.log("Next steps:");
681
1081
  console.log(` 1. Add a component: ${chalk2.cyan("npx @dinachi/cli add button")}`);
682
- console.log(` 2. Components will be installed to: ${chalk2.cyan(normalizedComponentsPath)}`);
683
- console.log(` 3. Utils available at: ${chalk2.cyan(path3.join(normalizedUtilsPath, "utils.ts"))}`);
1082
+ console.log(` 2. Components will be installed to: ${chalk2.cyan(componentsDirPath)}`);
1083
+ console.log(` 3. Utils available at: ${chalk2.cyan(utilsFilePath)}`);
1084
+ console.log();
1085
+ if (aliasConfigUpdate.created) {
1086
+ console.log(` ${chalk2.green("+")} Added @/* path alias in ${aliasConfigUpdate.path}`);
1087
+ } else if (aliasConfigUpdate.updated) {
1088
+ console.log(` ${chalk2.blue("~")} Updated @/* path alias in ${aliasConfigUpdate.path}`);
1089
+ } else if (aliasConfigUpdate.skipped) {
1090
+ console.log(
1091
+ ` ${chalk2.yellow("!")} Could not update ${aliasConfigUpdate.path}. Configure @/* manually if you use alias imports.`
1092
+ );
1093
+ }
1094
+ if (!projectConfig.isTypeScript) {
1095
+ console.log();
1096
+ console.log(
1097
+ chalk2.yellow(
1098
+ "\u26A0\uFE0F Dinachi components are TypeScript-first. Your project can still consume TSX files, but type-check tooling is recommended."
1099
+ )
1100
+ );
1101
+ }
684
1102
  if (projectConfig.framework === "next.js") {
685
1103
  console.log();
686
1104
  console.log(chalk2.blue("\u{1F4DD} Next.js specific notes:"));
687
- console.log(` - RSC (React Server Components) enabled in config`);
688
- console.log(` - Make sure to add "use client" directive if needed`);
1105
+ console.log(" - RSC (React Server Components) enabled in config");
1106
+ console.log(' - Add "use client" for interactive components where needed');
689
1107
  console.log(` - Tailwind config: ${chalk2.cyan(projectConfig.tailwindConfig)}`);
690
1108
  } else if (projectConfig.framework === "remix") {
691
1109
  console.log();
692
1110
  console.log(chalk2.blue("\u{1F4DD} Remix specific notes:"));
693
- console.log(` - Components will be installed to: ${chalk2.cyan(normalizedComponentsPath)}`);
694
- console.log(` - Utils will be installed to: ${chalk2.cyan(normalizedUtilsPath)}`);
1111
+ console.log(` - Components directory: ${chalk2.cyan(componentsDirPath)}`);
1112
+ console.log(` - Utilities directory: ${chalk2.cyan(utilsDirPath)}`);
1113
+ }
1114
+ if (options.skipInstall) {
1115
+ console.log();
1116
+ console.log("Dependencies to install manually:");
1117
+ deps.forEach((dep) => {
1118
+ console.log(` ${chalk2.yellow("\u2022")} ${toInstallSpec(dep)}`);
1119
+ });
695
1120
  }
696
1121
  console.log();
697
1122
  console.log("\u{1F4A1} Tip: Install globally for shorter commands:");
@@ -702,42 +1127,17 @@ export function cn(...inputs: ClassValue[]) {
702
1127
  process.exit(1);
703
1128
  }
704
1129
  });
705
- function normalizePath(inputPath) {
706
- return inputPath.replace(/^\.\//, "").replace(/\/$/, "");
707
- }
708
- function pathToAlias(filePath) {
709
- const normalized = normalizePath(filePath);
710
- return `@/${normalized}`;
711
- }
712
- function getPackageManager2() {
713
- if (fs3.existsSync("bun.lockb") || fs3.existsSync("bun.lock")) return "bun";
714
- if (fs3.existsSync("pnpm-lock.yaml")) return "pnpm";
715
- if (fs3.existsSync("yarn.lock")) return "yarn";
716
- return "npm";
717
- }
718
- function getInstallCommand2(pm, deps) {
719
- const depsStr = deps.join(" ");
720
- switch (pm) {
721
- case "bun":
722
- return `bun add ${depsStr}`;
723
- case "pnpm":
724
- return `pnpm add ${depsStr}`;
725
- case "yarn":
726
- return `yarn add ${depsStr}`;
727
- default:
728
- return `npm install ${depsStr}`;
729
- }
730
- }
731
1130
  function detectProjectType() {
732
- const packageJsonPath = path3.join(process.cwd(), "package.json");
733
- if (!fs3.existsSync(packageJsonPath)) {
1131
+ const packageJsonPath = path4.join(process.cwd(), "package.json");
1132
+ if (!fs4.existsSync(packageJsonPath)) {
734
1133
  return getDefaultConfig("react", false);
735
1134
  }
736
- const packageJson = JSON.parse(fs3.readFileSync(packageJsonPath, "utf-8"));
737
- const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
738
- const hasSrcDir = fs3.existsSync(path3.join(process.cwd(), "src"));
739
- const hasAppDir = fs3.existsSync(path3.join(process.cwd(), "app"));
740
- const hasSrcAppDir = fs3.existsSync(path3.join(process.cwd(), "src", "app"));
1135
+ const packageJson = JSON.parse(fs4.readFileSync(packageJsonPath, "utf-8"));
1136
+ const deps = { ...packageJson.dependencies ?? {}, ...packageJson.devDependencies ?? {} };
1137
+ const hasSrcDir = fs4.existsSync(path4.join(process.cwd(), "src"));
1138
+ const hasAppDir = fs4.existsSync(path4.join(process.cwd(), "app"));
1139
+ const hasSrcAppDir = fs4.existsSync(path4.join(process.cwd(), "src", "app"));
1140
+ const isTypeScript = fs4.existsSync(path4.join(process.cwd(), "tsconfig.json")) || fs4.existsSync(path4.join(process.cwd(), "tsconfig.base.json")) || Boolean(deps.typescript);
741
1141
  const tailwindConfig = detectTailwindConfig();
742
1142
  const cssPath = detectCssPath(hasSrcDir, hasSrcAppDir);
743
1143
  if (deps.next) {
@@ -748,7 +1148,8 @@ function detectProjectType() {
748
1148
  utilsPath: "./src/lib",
749
1149
  tailwindConfig,
750
1150
  cssPath,
751
- srcDir: "src"
1151
+ srcDir: "src",
1152
+ isTypeScript
752
1153
  };
753
1154
  }
754
1155
  if (hasAppDir && !hasSrcDir) {
@@ -758,7 +1159,8 @@ function detectProjectType() {
758
1159
  utilsPath: "./lib",
759
1160
  tailwindConfig,
760
1161
  cssPath,
761
- srcDir: "."
1162
+ srcDir: ".",
1163
+ isTypeScript
762
1164
  };
763
1165
  }
764
1166
  return {
@@ -767,7 +1169,8 @@ function detectProjectType() {
767
1169
  utilsPath: hasSrcDir ? "./src/lib" : "./lib",
768
1170
  tailwindConfig,
769
1171
  cssPath,
770
- srcDir: hasSrcDir ? "src" : "."
1172
+ srcDir: hasSrcDir ? "src" : ".",
1173
+ isTypeScript
771
1174
  };
772
1175
  }
773
1176
  if (deps.vite || deps["@vitejs/plugin-react"]) {
@@ -777,7 +1180,8 @@ function detectProjectType() {
777
1180
  utilsPath: hasSrcDir ? "./src/lib" : "./lib",
778
1181
  tailwindConfig,
779
1182
  cssPath,
780
- srcDir: hasSrcDir ? "src" : "."
1183
+ srcDir: hasSrcDir ? "src" : ".",
1184
+ isTypeScript
781
1185
  };
782
1186
  }
783
1187
  if (deps["react-scripts"]) {
@@ -787,7 +1191,8 @@ function detectProjectType() {
787
1191
  utilsPath: "./src/lib",
788
1192
  tailwindConfig,
789
1193
  cssPath,
790
- srcDir: "src"
1194
+ srcDir: "src",
1195
+ isTypeScript
791
1196
  };
792
1197
  }
793
1198
  if (deps["@remix-run/react"]) {
@@ -797,62 +1202,79 @@ function detectProjectType() {
797
1202
  utilsPath: "./app/lib",
798
1203
  tailwindConfig,
799
1204
  cssPath: detectCssPath(false, false, true),
800
- srcDir: "app"
1205
+ srcDir: "app",
1206
+ isTypeScript
801
1207
  };
802
1208
  }
803
1209
  return getDefaultConfig("react", hasSrcDir);
804
1210
  }
805
1211
  function getDefaultConfig(framework, hasSrcDir) {
1212
+ const isTypeScript = fs4.existsSync(path4.join(process.cwd(), "tsconfig.json")) || fs4.existsSync(path4.join(process.cwd(), "tsconfig.base.json"));
806
1213
  return {
807
1214
  framework,
808
1215
  componentsPath: hasSrcDir ? "./src/components/ui" : "./components/ui",
809
1216
  utilsPath: hasSrcDir ? "./src/lib" : "./lib",
810
1217
  tailwindConfig: detectTailwindConfig(),
811
1218
  cssPath: hasSrcDir ? "src/index.css" : "index.css",
812
- srcDir: hasSrcDir ? "src" : "."
1219
+ srcDir: hasSrcDir ? "src" : ".",
1220
+ isTypeScript
813
1221
  };
814
1222
  }
815
1223
  function detectTailwindConfig() {
816
- if (fs3.existsSync(path3.join(process.cwd(), "tailwind.config.ts"))) {
817
- return "tailwind.config.ts";
818
- }
819
- if (fs3.existsSync(path3.join(process.cwd(), "tailwind.config.js"))) {
820
- return "tailwind.config.js";
821
- }
822
- if (fs3.existsSync(path3.join(process.cwd(), "tailwind.config.mjs"))) {
823
- return "tailwind.config.mjs";
1224
+ const configCandidates = [
1225
+ "tailwind.config.ts",
1226
+ "tailwind.config.js",
1227
+ "tailwind.config.mjs",
1228
+ "tailwind.config.cjs",
1229
+ "tailwind.config.cts",
1230
+ "tailwind.config.mts"
1231
+ ];
1232
+ for (const config of configCandidates) {
1233
+ if (fs4.existsSync(path4.join(process.cwd(), config))) {
1234
+ return config;
1235
+ }
824
1236
  }
825
1237
  return "tailwind.config.js";
826
1238
  }
827
1239
  function detectCssPath(hasSrcDir, hasSrcAppDir, isRemix = false) {
828
1240
  const cwd = process.cwd();
829
1241
  const possiblePaths = [
830
- // Next.js App Router
831
1242
  "app/globals.css",
832
1243
  "src/app/globals.css",
833
- // Next.js Pages / General
834
1244
  "styles/globals.css",
835
1245
  "src/styles/globals.css",
836
- // Vite / CRA
837
1246
  "src/index.css",
838
1247
  "index.css",
839
- // Remix
840
1248
  "app/tailwind.css",
841
1249
  "app/styles/tailwind.css"
842
1250
  ];
843
1251
  for (const cssPath of possiblePaths) {
844
- if (fs3.existsSync(path3.join(cwd, cssPath))) {
1252
+ if (fs4.existsSync(path4.join(cwd, cssPath))) {
845
1253
  return cssPath;
846
1254
  }
847
1255
  }
848
1256
  if (isRemix) return "app/tailwind.css";
849
1257
  if (hasSrcAppDir) return "src/app/globals.css";
850
1258
  if (hasSrcDir) return "src/index.css";
851
- return "app/globals.css";
1259
+ return "index.css";
852
1260
  }
853
1261
 
854
1262
  // src/index.ts
1263
+ import fs5 from "fs";
1264
+ import path5 from "path";
1265
+ import { fileURLToPath as fileURLToPath3 } from "url";
855
1266
  var program = new Command3();
856
- program.name("dinachi").description("Add Dinachi UI components to your project").version("0.5.0");
1267
+ var __filename3 = fileURLToPath3(import.meta.url);
1268
+ var __dirname3 = path5.dirname(__filename3);
1269
+ function getCliVersion() {
1270
+ try {
1271
+ const packageJsonPath = path5.resolve(__dirname3, "../package.json");
1272
+ const packageJson = JSON.parse(fs5.readFileSync(packageJsonPath, "utf-8"));
1273
+ return packageJson.version ?? "0.0.0";
1274
+ } catch {
1275
+ return "0.0.0";
1276
+ }
1277
+ }
1278
+ program.name("dinachi").description("Add Dinachi UI components to your project").version(getCliVersion());
857
1279
  program.addCommand(addCommand).addCommand(initCommand);
858
1280
  program.parse();