@dinachi/cli 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -5
- package/dist/index.js +1086 -349
- package/package.json +17 -9
- package/templates/accordion/accordion.tsx +8 -3
- package/templates/alert-dialog/alert-dialog.tsx +24 -25
- package/templates/alert-dialog/index.ts +1 -1
- package/templates/autocomplete/autocomplete.tsx +0 -1
- package/templates/avatar/avatar.tsx +1 -3
- package/templates/badge/badge.tsx +167 -0
- package/templates/badge/index.ts +2 -0
- package/templates/button/button.tsx +6 -6
- package/templates/button/index.ts +2 -2
- package/templates/card/card.tsx +78 -0
- package/templates/card/index.ts +1 -0
- package/templates/checkbox/checkbox.tsx +2 -3
- package/templates/checkbox-group/checkbox-group.tsx +1 -3
- package/templates/collapsible/collapsible.tsx +1 -2
- package/templates/combobox/combobox.tsx +0 -1
- package/templates/context-menu/context-menu.tsx +0 -1
- package/templates/dialog/dialog.tsx +1 -1
- package/templates/drawer/drawer.tsx +0 -1
- package/templates/field/field.tsx +69 -85
- package/templates/fieldset/fieldset.tsx +0 -1
- package/templates/form/form.tsx +36 -28
- package/templates/input/index.ts +1 -2
- package/templates/input/input.tsx +0 -1
- package/templates/menu/menu.tsx +0 -1
- package/templates/menubar/menubar.tsx +21 -22
- package/templates/meter/meter.tsx +0 -1
- package/templates/navigation-menu/index.ts +1 -13
- package/templates/navigation-menu/navigation-menu.tsx +1 -3
- package/templates/number-field/number-field.tsx +0 -1
- package/templates/popover/popover.tsx +0 -1
- package/templates/preview-card/preview-card.tsx +0 -1
- package/templates/progress/progress.tsx +0 -1
- package/templates/radio/radio.tsx +0 -1
- package/templates/scroll-area/scroll-area.tsx +0 -1
- package/templates/select/select.tsx +1 -4
- package/templates/separator/separator.tsx +0 -1
- package/templates/slider/index.ts +10 -0
- package/templates/slider/slider.tsx +1 -3
- package/templates/switch/switch.tsx +0 -1
- package/templates/tabs/index.ts +8 -0
- package/templates/tabs/tabs.tsx +8 -3
- package/templates/textarea/index.ts +2 -0
- package/templates/textarea/textarea.tsx +23 -0
- package/templates/toast/toast.tsx +3 -2
- package/templates/toggle/toggle.tsx +0 -1
- package/templates/toggle-group/toggle-group.tsx +0 -1
- package/templates/toolbar/toolbar.tsx +0 -1
- package/templates/tooltip/tooltip.tsx +0 -1
- package/templates/tsconfig.json +20 -0
- package/templates/utils/utils.ts +0 -1
- package/templates/utils/variants.ts +0 -1
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
|
|
10
|
-
import
|
|
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: {
|
|
@@ -60,14 +102,27 @@ function getComponentRegistry() {
|
|
|
60
102
|
dependencies: ["@base-ui/react", "class-variance-authority"],
|
|
61
103
|
utilityDependencies: ["cn"]
|
|
62
104
|
},
|
|
105
|
+
badge: {
|
|
106
|
+
name: "badge",
|
|
107
|
+
description: "A small status indicator for highlighting information.",
|
|
108
|
+
files: [{ name: "badge.tsx" }, { name: "index.ts" }],
|
|
109
|
+
dependencies: ["@base-ui/react", "class-variance-authority"],
|
|
110
|
+
utilityDependencies: ["cn"]
|
|
111
|
+
},
|
|
63
112
|
button: {
|
|
64
113
|
name: "button",
|
|
65
114
|
description: "A customizable button component with multiple variants.",
|
|
66
115
|
files: [{ name: "button.tsx" }, { name: "index.ts" }],
|
|
67
116
|
dependencies: ["@base-ui/react", "class-variance-authority"],
|
|
68
|
-
componentDependencies: ["core"],
|
|
69
117
|
utilityDependencies: ["cn", "variants"]
|
|
70
118
|
},
|
|
119
|
+
card: {
|
|
120
|
+
name: "card",
|
|
121
|
+
description: "A container for grouping related content with header, body, and footer sections.",
|
|
122
|
+
files: [{ name: "card.tsx" }, { name: "index.ts" }],
|
|
123
|
+
dependencies: [],
|
|
124
|
+
utilityDependencies: ["cn"]
|
|
125
|
+
},
|
|
71
126
|
checkbox: {
|
|
72
127
|
name: "checkbox",
|
|
73
128
|
description: "A control that allows the user to select one or more options from a set.",
|
|
@@ -251,6 +306,13 @@ function getComponentRegistry() {
|
|
|
251
306
|
dependencies: ["@base-ui/react"],
|
|
252
307
|
utilityDependencies: ["cn"]
|
|
253
308
|
},
|
|
309
|
+
textarea: {
|
|
310
|
+
name: "textarea",
|
|
311
|
+
description: "A multi-line text input for longer form content.",
|
|
312
|
+
files: [{ name: "textarea.tsx" }, { name: "index.ts" }],
|
|
313
|
+
dependencies: [],
|
|
314
|
+
utilityDependencies: ["cn"]
|
|
315
|
+
},
|
|
254
316
|
toast: {
|
|
255
317
|
name: "toast",
|
|
256
318
|
description: "Generates toast notifications with support for different types, promises, actions, and global management.",
|
|
@@ -295,46 +357,327 @@ async function getConfig() {
|
|
|
295
357
|
}
|
|
296
358
|
try {
|
|
297
359
|
const configContent = await fs.readFile(configPath, "utf-8");
|
|
298
|
-
|
|
299
|
-
|
|
360
|
+
const parsed = JSON.parse(configContent);
|
|
361
|
+
return normalizeConfig(parsed);
|
|
362
|
+
} catch {
|
|
300
363
|
return null;
|
|
301
364
|
}
|
|
302
365
|
}
|
|
303
366
|
|
|
367
|
+
// src/utils/package-manager.ts
|
|
368
|
+
import fs2 from "fs-extra";
|
|
369
|
+
import path2 from "path";
|
|
370
|
+
var LOCK_FILE_MAP = [
|
|
371
|
+
{ file: "bun.lockb", manager: "bun" },
|
|
372
|
+
{ file: "bun.lock", manager: "bun" },
|
|
373
|
+
{ file: "pnpm-lock.yaml", manager: "pnpm" },
|
|
374
|
+
{ file: "yarn.lock", manager: "yarn" },
|
|
375
|
+
{ file: "package-lock.json", manager: "npm" }
|
|
376
|
+
];
|
|
377
|
+
function walkUpDirectories(startDir) {
|
|
378
|
+
const dirs = [];
|
|
379
|
+
let current = path2.resolve(startDir);
|
|
380
|
+
while (true) {
|
|
381
|
+
dirs.push(current);
|
|
382
|
+
const parent = path2.dirname(current);
|
|
383
|
+
if (parent === current) {
|
|
384
|
+
break;
|
|
385
|
+
}
|
|
386
|
+
current = parent;
|
|
387
|
+
}
|
|
388
|
+
return dirs;
|
|
389
|
+
}
|
|
390
|
+
function detectManagerFromPackageJson(startDir) {
|
|
391
|
+
for (const dir of walkUpDirectories(startDir)) {
|
|
392
|
+
const packageJsonPath = path2.join(dir, "package.json");
|
|
393
|
+
if (!fs2.existsSync(packageJsonPath)) {
|
|
394
|
+
continue;
|
|
395
|
+
}
|
|
396
|
+
try {
|
|
397
|
+
const packageJson = JSON.parse(fs2.readFileSync(packageJsonPath, "utf-8"));
|
|
398
|
+
const value = packageJson.packageManager ?? "";
|
|
399
|
+
if (value.startsWith("pnpm@")) return "pnpm";
|
|
400
|
+
if (value.startsWith("yarn@")) return "yarn";
|
|
401
|
+
if (value.startsWith("bun@")) return "bun";
|
|
402
|
+
if (value.startsWith("npm@")) return "npm";
|
|
403
|
+
} catch {
|
|
404
|
+
continue;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
return null;
|
|
408
|
+
}
|
|
409
|
+
function detectPackageManager(startDir = process.cwd()) {
|
|
410
|
+
const packageJsonManager = detectManagerFromPackageJson(startDir);
|
|
411
|
+
if (packageJsonManager) {
|
|
412
|
+
return packageJsonManager;
|
|
413
|
+
}
|
|
414
|
+
for (const dir of walkUpDirectories(startDir)) {
|
|
415
|
+
for (const entry of LOCK_FILE_MAP) {
|
|
416
|
+
if (fs2.existsSync(path2.join(dir, entry.file))) {
|
|
417
|
+
return entry.manager;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
return "npm";
|
|
422
|
+
}
|
|
423
|
+
function getInstallCommand(pm, deps) {
|
|
424
|
+
const depsStr = deps.join(" ");
|
|
425
|
+
switch (pm) {
|
|
426
|
+
case "bun":
|
|
427
|
+
return `bun add ${depsStr}`;
|
|
428
|
+
case "pnpm":
|
|
429
|
+
return `pnpm add ${depsStr}`;
|
|
430
|
+
case "yarn":
|
|
431
|
+
return `yarn add ${depsStr}`;
|
|
432
|
+
default:
|
|
433
|
+
return `npm install ${depsStr}`;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// src/utils/json.ts
|
|
438
|
+
function parseJsonWithComments(content) {
|
|
439
|
+
let result = "";
|
|
440
|
+
let inString = false;
|
|
441
|
+
let inSingleLineComment = false;
|
|
442
|
+
let inMultiLineComment = false;
|
|
443
|
+
let isEscaped = false;
|
|
444
|
+
for (let i = 0; i < content.length; i += 1) {
|
|
445
|
+
const char = content[i];
|
|
446
|
+
const next = content[i + 1];
|
|
447
|
+
if (inSingleLineComment) {
|
|
448
|
+
if (char === "\n") {
|
|
449
|
+
inSingleLineComment = false;
|
|
450
|
+
result += char;
|
|
451
|
+
}
|
|
452
|
+
continue;
|
|
453
|
+
}
|
|
454
|
+
if (inMultiLineComment) {
|
|
455
|
+
if (char === "*" && next === "/") {
|
|
456
|
+
inMultiLineComment = false;
|
|
457
|
+
i += 1;
|
|
458
|
+
} else if (char === "\n") {
|
|
459
|
+
result += char;
|
|
460
|
+
}
|
|
461
|
+
continue;
|
|
462
|
+
}
|
|
463
|
+
if (inString) {
|
|
464
|
+
result += char;
|
|
465
|
+
if (!isEscaped && char === '"') {
|
|
466
|
+
inString = false;
|
|
467
|
+
}
|
|
468
|
+
isEscaped = !isEscaped && char === "\\";
|
|
469
|
+
continue;
|
|
470
|
+
}
|
|
471
|
+
if (char === '"') {
|
|
472
|
+
inString = true;
|
|
473
|
+
isEscaped = false;
|
|
474
|
+
result += char;
|
|
475
|
+
continue;
|
|
476
|
+
}
|
|
477
|
+
if (char === "/" && next === "/") {
|
|
478
|
+
inSingleLineComment = true;
|
|
479
|
+
i += 1;
|
|
480
|
+
continue;
|
|
481
|
+
}
|
|
482
|
+
if (char === "/" && next === "*") {
|
|
483
|
+
inMultiLineComment = true;
|
|
484
|
+
i += 1;
|
|
485
|
+
continue;
|
|
486
|
+
}
|
|
487
|
+
result += char;
|
|
488
|
+
}
|
|
489
|
+
const withoutLineComments = result;
|
|
490
|
+
const withoutTrailingCommas = withoutLineComments.replace(/,\s*([}\]])/g, "$1");
|
|
491
|
+
return JSON.parse(withoutTrailingCommas);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// src/utils/dependencies.ts
|
|
495
|
+
var DEPENDENCY_VERSION_MAP = {
|
|
496
|
+
"@base-ui/react": "^1.2.0",
|
|
497
|
+
"lucide-react": "^0.552.0",
|
|
498
|
+
"class-variance-authority": "^0.7.1",
|
|
499
|
+
"tailwindcss-animate": "^1.0.7",
|
|
500
|
+
"clsx": "^2.1.1",
|
|
501
|
+
"tailwind-merge": "^3.3.1"
|
|
502
|
+
};
|
|
503
|
+
function toInstallSpec(dep) {
|
|
504
|
+
const version = DEPENDENCY_VERSION_MAP[dep];
|
|
505
|
+
return version ? `${dep}@${version}` : dep;
|
|
506
|
+
}
|
|
507
|
+
|
|
304
508
|
// src/commands/add.ts
|
|
305
509
|
var __filename2 = fileURLToPath2(import.meta.url);
|
|
306
|
-
var __dirname2 =
|
|
307
|
-
function
|
|
308
|
-
|
|
309
|
-
|
|
510
|
+
var __dirname2 = path3.dirname(__filename2);
|
|
511
|
+
function stripTemplateDirective(content) {
|
|
512
|
+
return content.replace(/^\/\/ @ts-nocheck\s*\n/m, "");
|
|
513
|
+
}
|
|
514
|
+
function stripExtension(filePath) {
|
|
515
|
+
return filePath.replace(/\.[^./\\]+$/, "");
|
|
516
|
+
}
|
|
517
|
+
function toImportPath(fromFilePath, toFilePath) {
|
|
518
|
+
const relativePath = path3.relative(path3.dirname(fromFilePath), stripExtension(toFilePath)).replace(/\\/g, "/");
|
|
519
|
+
return relativePath.startsWith(".") ? relativePath : `./${relativePath}`;
|
|
520
|
+
}
|
|
521
|
+
function matchPathPattern(pattern, input) {
|
|
522
|
+
if (!pattern.includes("*")) {
|
|
523
|
+
return pattern === input ? "" : null;
|
|
524
|
+
}
|
|
525
|
+
const [prefix, suffix] = pattern.split("*");
|
|
526
|
+
if (!input.startsWith(prefix) || !input.endsWith(suffix)) {
|
|
527
|
+
return null;
|
|
528
|
+
}
|
|
529
|
+
return input.slice(prefix.length, input.length - suffix.length);
|
|
530
|
+
}
|
|
531
|
+
function readCompilerPathConfig(projectRoot) {
|
|
532
|
+
const configFiles = ["tsconfig.json", "jsconfig.json"];
|
|
533
|
+
for (const configFile of configFiles) {
|
|
534
|
+
const configPath = path3.join(projectRoot, configFile);
|
|
535
|
+
if (!fs3.existsSync(configPath)) {
|
|
536
|
+
continue;
|
|
537
|
+
}
|
|
538
|
+
try {
|
|
539
|
+
const content = fs3.readFileSync(configPath, "utf-8");
|
|
540
|
+
const parsed = parseJsonWithComments(content);
|
|
541
|
+
const compilerOptions = parsed.compilerOptions ?? {};
|
|
542
|
+
const baseUrl = path3.resolve(
|
|
543
|
+
projectRoot,
|
|
544
|
+
typeof compilerOptions.baseUrl === "string" ? compilerOptions.baseUrl : "."
|
|
545
|
+
);
|
|
546
|
+
const rawPaths = compilerOptions.paths ?? {};
|
|
547
|
+
const paths = {};
|
|
548
|
+
for (const [key, value] of Object.entries(rawPaths)) {
|
|
549
|
+
if (!Array.isArray(value)) {
|
|
550
|
+
continue;
|
|
551
|
+
}
|
|
552
|
+
const targets = value.filter((entry) => typeof entry === "string");
|
|
553
|
+
if (targets.length > 0) {
|
|
554
|
+
paths[key] = targets;
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
return { baseUrl, paths };
|
|
558
|
+
} catch {
|
|
559
|
+
continue;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
return null;
|
|
563
|
+
}
|
|
564
|
+
function resolveConfiguredPath(aliasOrPath, projectRoot, compilerConfig) {
|
|
565
|
+
const normalized = aliasOrPath.replace(/\\/g, "/").trim();
|
|
566
|
+
if (path3.isAbsolute(normalized)) {
|
|
567
|
+
return normalized;
|
|
568
|
+
}
|
|
569
|
+
if (normalized.startsWith("./") || normalized.startsWith("../")) {
|
|
570
|
+
return path3.resolve(projectRoot, normalized);
|
|
571
|
+
}
|
|
572
|
+
if (normalized.startsWith("/")) {
|
|
573
|
+
return path3.resolve(projectRoot, `.${normalized}`);
|
|
574
|
+
}
|
|
575
|
+
if (compilerConfig) {
|
|
576
|
+
for (const [pattern, targets] of Object.entries(compilerConfig.paths)) {
|
|
577
|
+
const wildcard = matchPathPattern(pattern, normalized);
|
|
578
|
+
if (wildcard === null) {
|
|
579
|
+
continue;
|
|
580
|
+
}
|
|
581
|
+
const target = targets[0];
|
|
582
|
+
if (!target) {
|
|
583
|
+
continue;
|
|
584
|
+
}
|
|
585
|
+
const mappedTarget = target.includes("*") ? target.replace("*", wildcard) : target;
|
|
586
|
+
return path3.resolve(compilerConfig.baseUrl, mappedTarget);
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
if (normalized.startsWith("@/") || normalized.startsWith("~/")) {
|
|
590
|
+
return path3.resolve(projectRoot, normalized.slice(2));
|
|
591
|
+
}
|
|
592
|
+
return path3.resolve(projectRoot, normalized);
|
|
310
593
|
}
|
|
311
|
-
function
|
|
594
|
+
function rewriteTemplateImports(content, targetFilePath, utilsFilePath, libDirPath) {
|
|
595
|
+
const utilsImportPath = toImportPath(targetFilePath, utilsFilePath);
|
|
596
|
+
const variantsImportPath = toImportPath(targetFilePath, path3.join(libDirPath, "variants.ts"));
|
|
597
|
+
return content.replace(/(['"])@\/lib\/utils\1/g, `$1${utilsImportPath}$1`).replace(/(['"])@\/lib\/variants\1/g, `$1${variantsImportPath}$1`);
|
|
598
|
+
}
|
|
599
|
+
function getComponentDependencies(componentName, visited = /* @__PURE__ */ new Set()) {
|
|
600
|
+
if (visited.has(componentName)) {
|
|
601
|
+
return [];
|
|
602
|
+
}
|
|
603
|
+
visited.add(componentName);
|
|
312
604
|
const registry = getComponentRegistry();
|
|
313
605
|
const component = registry[componentName];
|
|
314
606
|
if (!component) return [];
|
|
315
|
-
|
|
607
|
+
const dependencies = [];
|
|
316
608
|
for (const dep of component.componentDependencies || []) {
|
|
317
|
-
|
|
609
|
+
if (!registry[dep]) {
|
|
610
|
+
continue;
|
|
611
|
+
}
|
|
612
|
+
dependencies.push(dep, ...getComponentDependencies(dep, visited));
|
|
318
613
|
}
|
|
319
614
|
return [...new Set(dependencies)];
|
|
320
615
|
}
|
|
321
|
-
|
|
322
|
-
const
|
|
323
|
-
if (
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
616
|
+
function detectTailwindConfigPath(projectRoot, configuredName) {
|
|
617
|
+
const preferred = resolveConfiguredPath(configuredName, projectRoot, null);
|
|
618
|
+
if (fs3.existsSync(preferred)) {
|
|
619
|
+
return preferred;
|
|
620
|
+
}
|
|
621
|
+
const alternatives = [
|
|
622
|
+
"tailwind.config.ts",
|
|
623
|
+
"tailwind.config.js",
|
|
624
|
+
"tailwind.config.mjs",
|
|
625
|
+
"tailwind.config.cjs",
|
|
626
|
+
"tailwind.config.cts",
|
|
627
|
+
"tailwind.config.mts"
|
|
628
|
+
];
|
|
629
|
+
for (const candidate of alternatives) {
|
|
630
|
+
const candidatePath = path3.join(projectRoot, candidate);
|
|
631
|
+
if (fs3.existsSync(candidatePath)) {
|
|
632
|
+
return candidatePath;
|
|
333
633
|
}
|
|
334
634
|
}
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
635
|
+
return preferred;
|
|
636
|
+
}
|
|
637
|
+
function insertTailwindPlugin(content, pluginExpression) {
|
|
638
|
+
if (/plugins\s*:\s*\[/.test(content)) {
|
|
639
|
+
return content.replace(/plugins\s*:\s*\[([\s\S]*?)\]/m, (_match, pluginsContent) => {
|
|
640
|
+
const trimmedPlugins = pluginsContent.trim();
|
|
641
|
+
if (trimmedPlugins.length === 0) {
|
|
642
|
+
return `plugins: [
|
|
643
|
+
${pluginExpression},
|
|
644
|
+
]`;
|
|
645
|
+
}
|
|
646
|
+
const normalizedPlugins = pluginsContent.trimEnd();
|
|
647
|
+
const withTrailingComma = normalizedPlugins.endsWith(",") ? normalizedPlugins : `${normalizedPlugins},`;
|
|
648
|
+
return `plugins: [
|
|
649
|
+
${withTrailingComma}
|
|
650
|
+
${pluginExpression},
|
|
651
|
+
]`;
|
|
652
|
+
});
|
|
653
|
+
}
|
|
654
|
+
const trimmed = content.trimEnd();
|
|
655
|
+
const hasSemicolon = trimmed.endsWith(";");
|
|
656
|
+
const withoutSemicolon = hasSemicolon ? trimmed.slice(0, -1) : trimmed;
|
|
657
|
+
const closingBraceIndex = withoutSemicolon.lastIndexOf("}");
|
|
658
|
+
if (closingBraceIndex === -1) {
|
|
659
|
+
return null;
|
|
660
|
+
}
|
|
661
|
+
const beforeClosingBrace = withoutSemicolon.slice(0, closingBraceIndex).trimEnd();
|
|
662
|
+
const needsComma = beforeClosingBrace.length > 0 && !beforeClosingBrace.endsWith(",") && !beforeClosingBrace.endsWith("{");
|
|
663
|
+
const next = `${withoutSemicolon.slice(0, closingBraceIndex)}${needsComma ? "," : ""}
|
|
664
|
+
plugins: [
|
|
665
|
+
${pluginExpression},
|
|
666
|
+
],
|
|
667
|
+
}${hasSemicolon ? ";" : ""}
|
|
668
|
+
`;
|
|
669
|
+
return next;
|
|
670
|
+
}
|
|
671
|
+
async function ensureTailwindConfig(deps, projectRoot, configuredFileName) {
|
|
672
|
+
if (!deps.includes("tailwindcss-animate")) {
|
|
673
|
+
return null;
|
|
674
|
+
}
|
|
675
|
+
const configPath = detectTailwindConfigPath(projectRoot, configuredFileName || "tailwind.config.js");
|
|
676
|
+
if (!fs3.existsSync(configPath)) {
|
|
677
|
+
const ext2 = path3.extname(configPath);
|
|
678
|
+
const isCjs2 = ext2 === ".cjs";
|
|
679
|
+
const configContent = isCjs2 ? `/** @type {import('tailwindcss').Config} */
|
|
680
|
+
module.exports = {
|
|
338
681
|
content: [
|
|
339
682
|
"./src/**/*.{js,ts,jsx,tsx}",
|
|
340
683
|
"./app/**/*.{js,ts,jsx,tsx}",
|
|
@@ -344,265 +687,615 @@ export default {
|
|
|
344
687
|
theme: {
|
|
345
688
|
extend: {},
|
|
346
689
|
},
|
|
347
|
-
plugins: [
|
|
348
|
-
|
|
690
|
+
plugins: [require("tailwindcss-animate")],
|
|
691
|
+
}
|
|
692
|
+
` : `import tailwindcssAnimate from "tailwindcss-animate"
|
|
693
|
+
|
|
694
|
+
export default {
|
|
695
|
+
content: [
|
|
696
|
+
"./src/**/*.{js,ts,jsx,tsx}",
|
|
697
|
+
"./app/**/*.{js,ts,jsx,tsx}",
|
|
698
|
+
"./components/**/*.{js,ts,jsx,tsx}",
|
|
699
|
+
"./pages/**/*.{js,ts,jsx,tsx}",
|
|
349
700
|
],
|
|
350
|
-
|
|
351
|
-
|
|
701
|
+
theme: {
|
|
702
|
+
extend: {},
|
|
703
|
+
},
|
|
704
|
+
plugins: [tailwindcssAnimate],
|
|
705
|
+
}
|
|
706
|
+
`;
|
|
707
|
+
await fs3.ensureDir(path3.dirname(configPath));
|
|
708
|
+
await fs3.writeFile(configPath, configContent);
|
|
352
709
|
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
710
|
}
|
|
385
|
-
|
|
711
|
+
const currentContent = await fs3.readFile(configPath, "utf-8");
|
|
712
|
+
if (currentContent.includes("tailwindcss-animate")) {
|
|
713
|
+
return { exists: true, path: configPath };
|
|
714
|
+
}
|
|
715
|
+
const ext = path3.extname(configPath);
|
|
716
|
+
const isCjs = ext === ".cjs" || /module\.exports\s*=/.test(currentContent);
|
|
717
|
+
const pluginExpression = isCjs ? 'require("tailwindcss-animate")' : "tailwindcssAnimate";
|
|
718
|
+
let updatedContent = currentContent;
|
|
719
|
+
if (!isCjs && !/from\s+['"]tailwindcss-animate['"]/.test(updatedContent)) {
|
|
720
|
+
updatedContent = `import tailwindcssAnimate from "tailwindcss-animate"
|
|
721
|
+
${updatedContent}`;
|
|
722
|
+
}
|
|
723
|
+
const merged = insertTailwindPlugin(updatedContent, pluginExpression);
|
|
724
|
+
if (!merged) {
|
|
725
|
+
return { skipped: true, path: configPath };
|
|
726
|
+
}
|
|
727
|
+
await fs3.writeFile(configPath, merged);
|
|
728
|
+
return { updated: true, path: configPath };
|
|
386
729
|
}
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
730
|
+
function escapeRegex(value) {
|
|
731
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
732
|
+
}
|
|
733
|
+
async function handleIndexFile(sourcePath, targetPath, allFilesAdded, targetDir) {
|
|
734
|
+
const templateContent = stripTemplateDirective(await fs3.readFile(sourcePath, "utf-8"));
|
|
735
|
+
if (!fs3.existsSync(targetPath)) {
|
|
736
|
+
await fs3.writeFile(targetPath, templateContent);
|
|
737
|
+
allFilesAdded.push({ name: "index.ts", path: path3.join(targetDir, "index.ts") });
|
|
738
|
+
return;
|
|
739
|
+
}
|
|
740
|
+
const existingContent = await fs3.readFile(targetPath, "utf-8");
|
|
741
|
+
const exportLines = templateContent.split("\n").map((line) => line.trim()).filter((line) => /^export\s+/.test(line) && /from\s+['"]\.\/[^'"]+['"]/.test(line));
|
|
742
|
+
const linesToAppend = [];
|
|
743
|
+
for (const line of exportLines) {
|
|
744
|
+
const match = line.match(/from\s+['"]\.\/([^'"]+)['"]/);
|
|
745
|
+
if (!match) {
|
|
746
|
+
continue;
|
|
400
747
|
}
|
|
401
|
-
const
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
await fs2.writeFile(targetPath, updatedContent);
|
|
406
|
-
allFilesAdded.push({ name: "index.ts", path: path2.join(targetDir, "index.ts") });
|
|
748
|
+
const modulePath = match[1];
|
|
749
|
+
const modulePathPattern = new RegExp(`from\\s+['"]\\./${escapeRegex(modulePath)}['"]`);
|
|
750
|
+
if (modulePathPattern.test(existingContent)) {
|
|
751
|
+
continue;
|
|
407
752
|
}
|
|
753
|
+
linesToAppend.push(line.endsWith(";") ? line : `${line};`);
|
|
408
754
|
}
|
|
755
|
+
if (linesToAppend.length === 0) {
|
|
756
|
+
return;
|
|
757
|
+
}
|
|
758
|
+
const updatedContent = `${existingContent.trimEnd()}
|
|
759
|
+
${linesToAppend.join("\n")}
|
|
760
|
+
`;
|
|
761
|
+
await fs3.writeFile(targetPath, updatedContent);
|
|
762
|
+
allFilesAdded.push({ name: "index.ts", path: path3.join(targetDir, "index.ts") });
|
|
409
763
|
}
|
|
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").
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
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
|
-
});
|
|
764
|
+
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(
|
|
765
|
+
async (componentName, options) => {
|
|
766
|
+
const spinner = ora("Adding component...").start();
|
|
767
|
+
try {
|
|
768
|
+
const config = await getConfig();
|
|
769
|
+
if (!config) {
|
|
770
|
+
spinner.fail("\u274C No components.json found. Run `dinachi init` first.");
|
|
437
771
|
process.exit(1);
|
|
438
772
|
}
|
|
439
|
-
const
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
}
|
|
446
|
-
|
|
773
|
+
const projectRoot = process.cwd();
|
|
774
|
+
const compilerPathConfig = readCompilerPathConfig(projectRoot);
|
|
775
|
+
const registry = getComponentRegistry();
|
|
776
|
+
let componentsToInstall = [];
|
|
777
|
+
if (options.all) {
|
|
778
|
+
const allComponents = Object.keys(registry);
|
|
779
|
+
spinner.text = `Installing all ${allComponents.length} components...`;
|
|
780
|
+
const allComponentsWithDeps = /* @__PURE__ */ new Set();
|
|
781
|
+
for (const name of allComponents) {
|
|
782
|
+
allComponentsWithDeps.add(name);
|
|
783
|
+
const deps = getComponentDependencies(name);
|
|
784
|
+
deps.forEach((dep) => allComponentsWithDeps.add(dep));
|
|
785
|
+
}
|
|
786
|
+
componentsToInstall = Array.from(allComponentsWithDeps);
|
|
787
|
+
} else {
|
|
788
|
+
if (!componentName) {
|
|
789
|
+
spinner.fail("\u274C Component name is required when not using --all flag.");
|
|
790
|
+
console.log("Available components:");
|
|
791
|
+
Object.keys(registry).forEach((name) => {
|
|
792
|
+
console.log(` ${chalk.cyan(name)}`);
|
|
793
|
+
});
|
|
794
|
+
process.exit(1);
|
|
795
|
+
}
|
|
796
|
+
const component = registry[componentName];
|
|
797
|
+
if (!component) {
|
|
798
|
+
spinner.fail(`\u274C Component "${componentName}" not found.`);
|
|
799
|
+
console.log("Available components:");
|
|
800
|
+
Object.keys(registry).forEach((name) => {
|
|
801
|
+
console.log(` ${chalk.cyan(name)}`);
|
|
802
|
+
});
|
|
803
|
+
process.exit(1);
|
|
804
|
+
}
|
|
805
|
+
componentsToInstall = [componentName, ...getComponentDependencies(componentName)];
|
|
447
806
|
}
|
|
448
|
-
|
|
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);
|
|
807
|
+
if (!options.all) {
|
|
808
|
+
spinner.text = `Installing ${componentsToInstall.join(", ")}...`;
|
|
463
809
|
}
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
const
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
810
|
+
const componentDir = resolveConfiguredPath(config.aliases.ui, projectRoot, compilerPathConfig);
|
|
811
|
+
const libDir = resolveConfiguredPath(config.aliases.lib, projectRoot, compilerPathConfig);
|
|
812
|
+
const utilsFilePath = resolveConfiguredPath(config.aliases.utils, projectRoot, compilerPathConfig);
|
|
813
|
+
await fs3.ensureDir(componentDir);
|
|
814
|
+
await fs3.ensureDir(libDir);
|
|
815
|
+
const allFilesAdded = [];
|
|
816
|
+
const allDepsInstalled = [];
|
|
817
|
+
const allUtilityDeps = [];
|
|
818
|
+
for (const name of componentsToInstall) {
|
|
819
|
+
const comp = registry[name];
|
|
820
|
+
if (!comp) continue;
|
|
821
|
+
if (comp.utilityDependencies?.length) {
|
|
822
|
+
allUtilityDeps.push(...comp.utilityDependencies);
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
const utilityRegistry = getUtilityRegistry();
|
|
826
|
+
const uniqueUtilityDeps = [...new Set(allUtilityDeps)];
|
|
827
|
+
if (uniqueUtilityDeps.length > 0) {
|
|
828
|
+
for (const utilityName of uniqueUtilityDeps) {
|
|
829
|
+
const utility = utilityRegistry[utilityName];
|
|
830
|
+
if (!utility) continue;
|
|
831
|
+
const utilityFilename = `${utility.name}.ts`;
|
|
832
|
+
const sourcePath = path3.join(__dirname2, "../templates/utils", utilityFilename);
|
|
833
|
+
const targetPath = path3.join(libDir, utilityFilename);
|
|
834
|
+
if (!fs3.existsSync(targetPath)) {
|
|
835
|
+
const content = stripTemplateDirective(await fs3.readFile(sourcePath, "utf-8"));
|
|
836
|
+
await fs3.writeFile(targetPath, content);
|
|
837
|
+
allFilesAdded.push({
|
|
838
|
+
name: utilityFilename,
|
|
839
|
+
path: targetPath
|
|
840
|
+
});
|
|
841
|
+
}
|
|
484
842
|
if (utility.dependencies?.length) {
|
|
485
843
|
allDepsInstalled.push(...utility.dependencies);
|
|
486
844
|
}
|
|
487
845
|
}
|
|
488
846
|
}
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
if (
|
|
847
|
+
for (const name of componentsToInstall) {
|
|
848
|
+
const comp = registry[name];
|
|
849
|
+
if (!comp) continue;
|
|
850
|
+
for (const file of comp.files) {
|
|
851
|
+
const sourcePath = path3.join(__dirname2, "../templates", name, file.name);
|
|
852
|
+
const targetPath = path3.join(componentDir, file.name);
|
|
853
|
+
if (file.name === "index.ts") {
|
|
854
|
+
await handleIndexFile(sourcePath, targetPath, allFilesAdded, componentDir);
|
|
855
|
+
continue;
|
|
856
|
+
}
|
|
857
|
+
if (fs3.existsSync(targetPath) && !options.overwrite) {
|
|
500
858
|
spinner.warn(`\u26A0\uFE0F ${file.name} already exists. Use --overwrite to replace it.`);
|
|
501
859
|
continue;
|
|
502
860
|
}
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
await
|
|
506
|
-
allFilesAdded.push({ name: file.name, path:
|
|
861
|
+
const templateContent = stripTemplateDirective(await fs3.readFile(sourcePath, "utf-8"));
|
|
862
|
+
const rewrittenContent = rewriteTemplateImports(templateContent, targetPath, utilsFilePath, libDir);
|
|
863
|
+
await fs3.writeFile(targetPath, rewrittenContent);
|
|
864
|
+
allFilesAdded.push({ name: file.name, path: targetPath });
|
|
865
|
+
}
|
|
866
|
+
if (comp.dependencies?.length) {
|
|
867
|
+
allDepsInstalled.push(...comp.dependencies);
|
|
507
868
|
}
|
|
508
869
|
}
|
|
509
|
-
if (comp.dependencies?.length) {
|
|
510
|
-
allDepsInstalled.push(...comp.dependencies);
|
|
511
|
-
}
|
|
512
|
-
}
|
|
513
|
-
let tailwindConfigInfo = null;
|
|
514
|
-
if (allDepsInstalled.includes("tailwindcss-animate")) {
|
|
515
870
|
spinner.text = "Updating Tailwind configuration...";
|
|
516
|
-
const
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
871
|
+
const tailwindConfigInfo = await ensureTailwindConfig(
|
|
872
|
+
allDepsInstalled,
|
|
873
|
+
projectRoot,
|
|
874
|
+
config.tailwind?.config || "tailwind.config.js"
|
|
875
|
+
);
|
|
876
|
+
const packageJsonPath = path3.join(projectRoot, "package.json");
|
|
877
|
+
if (!fs3.existsSync(packageJsonPath)) {
|
|
878
|
+
throw new Error("No package.json found in the current directory.");
|
|
879
|
+
}
|
|
880
|
+
const packageJson = JSON.parse(await fs3.readFile(packageJsonPath, "utf-8"));
|
|
881
|
+
const declaredDeps = { ...packageJson.dependencies ?? {}, ...packageJson.devDependencies ?? {} };
|
|
882
|
+
const uniqueDeps = [...new Set(allDepsInstalled)];
|
|
883
|
+
const missingDeps = uniqueDeps.filter((dep) => !declaredDeps[dep]);
|
|
884
|
+
if (!options.skipInstall && missingDeps.length > 0) {
|
|
885
|
+
spinner.text = "Installing dependencies...";
|
|
526
886
|
try {
|
|
527
|
-
const packageManager =
|
|
528
|
-
const installCmd = getInstallCommand(packageManager, missingDeps);
|
|
529
|
-
execSync(installCmd, { stdio: "inherit" });
|
|
530
|
-
} catch
|
|
887
|
+
const packageManager = detectPackageManager(projectRoot);
|
|
888
|
+
const installCmd = getInstallCommand(packageManager, missingDeps.map(toInstallSpec));
|
|
889
|
+
execSync(installCmd, { stdio: "inherit", cwd: projectRoot });
|
|
890
|
+
} catch {
|
|
531
891
|
spinner.warn(`\u26A0\uFE0F Failed to install dependencies. Please install manually: ${missingDeps.join(" ")}`);
|
|
532
892
|
}
|
|
533
|
-
} else {
|
|
893
|
+
} else if (!options.skipInstall && uniqueDeps.length > 0) {
|
|
534
894
|
spinner.text = "All dependencies already installed.";
|
|
535
895
|
}
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
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)`);
|
|
896
|
+
if (options.all) {
|
|
897
|
+
spinner.succeed(`\u2705 Added all ${componentsToInstall.length} components!`);
|
|
898
|
+
} else {
|
|
899
|
+
spinner.succeed(`\u2705 Added ${componentsToInstall.join(", ")}!`);
|
|
553
900
|
}
|
|
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
901
|
console.log();
|
|
563
|
-
console.log("
|
|
564
|
-
|
|
565
|
-
console.log(` ${chalk.
|
|
902
|
+
console.log("Files added:");
|
|
903
|
+
allFilesAdded.forEach((file) => {
|
|
904
|
+
console.log(` ${chalk.green("+")} ${file.path}`);
|
|
566
905
|
});
|
|
906
|
+
if (tailwindConfigInfo) {
|
|
907
|
+
console.log();
|
|
908
|
+
if (tailwindConfigInfo.created) {
|
|
909
|
+
console.log(` ${chalk.green("+")} ${tailwindConfigInfo.path} (created with tailwindcss-animate plugin)`);
|
|
910
|
+
} else if (tailwindConfigInfo.updated) {
|
|
911
|
+
console.log(` ${chalk.blue("~")} ${tailwindConfigInfo.path} (updated with tailwindcss-animate plugin)`);
|
|
912
|
+
} else if (tailwindConfigInfo.skipped) {
|
|
913
|
+
console.log(
|
|
914
|
+
` ${chalk.yellow("!")} ${tailwindConfigInfo.path} (could not safely update automatically; add tailwindcss-animate manually)`
|
|
915
|
+
);
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
if (options.skipInstall && missingDeps.length > 0) {
|
|
919
|
+
console.log();
|
|
920
|
+
console.log("Dependencies to install manually:");
|
|
921
|
+
missingDeps.forEach((dep) => {
|
|
922
|
+
console.log(` ${chalk.yellow("\u2022")} ${toInstallSpec(dep)}`);
|
|
923
|
+
});
|
|
924
|
+
} else if (missingDeps.length > 0) {
|
|
925
|
+
console.log();
|
|
926
|
+
console.log("Dependencies installed:");
|
|
927
|
+
missingDeps.forEach((dep) => {
|
|
928
|
+
console.log(` ${chalk.green("\u2713")} ${toInstallSpec(dep)}`);
|
|
929
|
+
});
|
|
930
|
+
} else if (uniqueDeps.length > 0) {
|
|
931
|
+
console.log();
|
|
932
|
+
console.log("Dependencies (already installed):");
|
|
933
|
+
uniqueDeps.forEach((dep) => {
|
|
934
|
+
console.log(` ${chalk.blue("~")} ${dep}`);
|
|
935
|
+
});
|
|
936
|
+
}
|
|
937
|
+
} catch (error) {
|
|
938
|
+
spinner.fail(`\u274C Failed to add component: ${error instanceof Error ? error.message : error}`);
|
|
939
|
+
process.exit(1);
|
|
567
940
|
}
|
|
568
|
-
} catch (error) {
|
|
569
|
-
spinner.fail(`\u274C Failed to add component: ${error instanceof Error ? error.message : error}`);
|
|
570
|
-
process.exit(1);
|
|
571
941
|
}
|
|
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
|
-
}
|
|
942
|
+
);
|
|
592
943
|
|
|
593
944
|
// src/commands/init.ts
|
|
594
945
|
import { Command as Command2 } from "commander";
|
|
595
946
|
import { execSync as execSync2 } from "child_process";
|
|
596
|
-
import
|
|
597
|
-
import
|
|
947
|
+
import fs4 from "fs-extra";
|
|
948
|
+
import path4 from "path";
|
|
598
949
|
import prompts from "prompts";
|
|
599
950
|
import chalk2 from "chalk";
|
|
600
951
|
import ora2 from "ora";
|
|
601
|
-
|
|
952
|
+
function normalizeProjectPath(inputPath, projectRoot) {
|
|
953
|
+
const absolutePath = path4.isAbsolute(inputPath) ? path4.normalize(inputPath) : path4.resolve(projectRoot, inputPath);
|
|
954
|
+
const relativePath = path4.relative(projectRoot, absolutePath).replace(/\\/g, "/");
|
|
955
|
+
const withoutPrefix = relativePath.replace(/^\.\//, "").replace(/\/$/, "");
|
|
956
|
+
return withoutPrefix.length > 0 ? withoutPrefix : ".";
|
|
957
|
+
}
|
|
958
|
+
function toConfigPath(relativePath) {
|
|
959
|
+
return relativePath === "." ? "." : `./${relativePath.replace(/\\/g, "/")}`;
|
|
960
|
+
}
|
|
961
|
+
function createUtilsFileContent() {
|
|
962
|
+
return `import { type ClassValue, clsx } from "clsx"
|
|
963
|
+
import { twMerge } from "tailwind-merge"
|
|
964
|
+
|
|
965
|
+
export function cn(...inputs: ClassValue[]) {
|
|
966
|
+
return twMerge(clsx(inputs))
|
|
967
|
+
}
|
|
968
|
+
`;
|
|
969
|
+
}
|
|
970
|
+
function detectTailwindMajorVersion(projectRoot) {
|
|
971
|
+
const packageJsonPath = path4.join(projectRoot, "package.json");
|
|
972
|
+
if (!fs4.existsSync(packageJsonPath)) return 4;
|
|
973
|
+
try {
|
|
974
|
+
const raw = fs4.readFileSync(packageJsonPath, "utf-8");
|
|
975
|
+
const packageJson = JSON.parse(raw);
|
|
976
|
+
const deps = { ...packageJson.dependencies ?? {}, ...packageJson.devDependencies ?? {} };
|
|
977
|
+
const twVersion = deps.tailwindcss;
|
|
978
|
+
if (!twVersion) return 4;
|
|
979
|
+
const match = twVersion.match(/(\d+)/);
|
|
980
|
+
return match ? parseInt(match[1], 10) : 4;
|
|
981
|
+
} catch {
|
|
982
|
+
return 4;
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
function getThemeCSS(tailwindMajor, mode) {
|
|
986
|
+
const lightVars = `:root {
|
|
987
|
+
--background: oklch(0.986 0.0034 145.5499);
|
|
988
|
+
--foreground: oklch(0.1459 0.0497 142.4953);
|
|
989
|
+
--card: oklch(0.9781 0.0017 145.5621);
|
|
990
|
+
--card-foreground: oklch(0.1459 0.0497 142.4953);
|
|
991
|
+
--popover: oklch(1 0 0);
|
|
992
|
+
--popover-foreground: oklch(0.1324 0.0033 145.3864);
|
|
993
|
+
--primary: oklch(0.1324 0.0033 145.3864);
|
|
994
|
+
--primary-foreground: oklch(0.9729 0.0101 145.4971);
|
|
995
|
+
--secondary: oklch(0.9248 0.0051 145.5339);
|
|
996
|
+
--secondary-foreground: oklch(0.1324 0.0033 145.3864);
|
|
997
|
+
--muted: oklch(0.9631 0.0017 145.5619);
|
|
998
|
+
--muted-foreground: oklch(0.1849 0.0629 142.4953);
|
|
999
|
+
--accent: oklch(0.9248 0.0051 145.5339);
|
|
1000
|
+
--accent-foreground: oklch(0.1459 0.0497 142.4953);
|
|
1001
|
+
--destructive: oklch(0.5248 0.1368 20.8317);
|
|
1002
|
+
--destructive-foreground: oklch(1 0 0);
|
|
1003
|
+
--border: oklch(0.9239 0.0017 145.5613);
|
|
1004
|
+
--input: oklch(0.8481 0.0105 145.4823);
|
|
1005
|
+
--ring: oklch(0.1459 0.0497 142.4953);
|
|
1006
|
+
--radius: 0.625rem;
|
|
1007
|
+
}`;
|
|
1008
|
+
const darkVars = `.dark {
|
|
1009
|
+
--background: oklch(0.1149 0 0);
|
|
1010
|
+
--foreground: oklch(0.7999 0.0218 134.1191);
|
|
1011
|
+
--card: oklch(0.133 0.0021 196.9098);
|
|
1012
|
+
--card-foreground: oklch(0.7996 0.023 132.5769);
|
|
1013
|
+
--popover: oklch(0.1663 0.0138 135.2766);
|
|
1014
|
+
--popover-foreground: oklch(0.9742 0.0101 131.3574);
|
|
1015
|
+
--primary: oklch(0.9729 0.0101 145.4971);
|
|
1016
|
+
--primary-foreground: oklch(0.1324 0.0033 145.3864);
|
|
1017
|
+
--secondary: oklch(0.1844 0.0062 122.0354);
|
|
1018
|
+
--secondary-foreground: oklch(0.8009 0.0399 133.2927);
|
|
1019
|
+
--muted: oklch(0.1579 0.0017 196.9874);
|
|
1020
|
+
--muted-foreground: oklch(0.7897 0.0171 133.8518);
|
|
1021
|
+
--accent: oklch(0.1391 0.0113 136.9894);
|
|
1022
|
+
--accent-foreground: oklch(0.9742 0.0101 131.3574);
|
|
1023
|
+
--destructive: oklch(0.2258 0.0524 12.6119);
|
|
1024
|
+
--destructive-foreground: oklch(1 0 0);
|
|
1025
|
+
--border: oklch(0.1811 0.0128 129.2819);
|
|
1026
|
+
--input: oklch(0.2213 0.0193 135.2915);
|
|
1027
|
+
--ring: oklch(0.9248 0.0051 145.5339);
|
|
1028
|
+
}`;
|
|
1029
|
+
const parts = [];
|
|
1030
|
+
if (tailwindMajor >= 4) {
|
|
1031
|
+
if (mode === "full") {
|
|
1032
|
+
parts.push('@import "tailwindcss";');
|
|
1033
|
+
}
|
|
1034
|
+
parts.push(lightVars, darkVars);
|
|
1035
|
+
parts.push(`@theme inline {
|
|
1036
|
+
--color-background: var(--background);
|
|
1037
|
+
--color-foreground: var(--foreground);
|
|
1038
|
+
--color-card: var(--card);
|
|
1039
|
+
--color-card-foreground: var(--card-foreground);
|
|
1040
|
+
--color-popover: var(--popover);
|
|
1041
|
+
--color-popover-foreground: var(--popover-foreground);
|
|
1042
|
+
--color-primary: var(--primary);
|
|
1043
|
+
--color-primary-foreground: var(--primary-foreground);
|
|
1044
|
+
--color-secondary: var(--secondary);
|
|
1045
|
+
--color-secondary-foreground: var(--secondary-foreground);
|
|
1046
|
+
--color-muted: var(--muted);
|
|
1047
|
+
--color-muted-foreground: var(--muted-foreground);
|
|
1048
|
+
--color-accent: var(--accent);
|
|
1049
|
+
--color-accent-foreground: var(--accent-foreground);
|
|
1050
|
+
--color-destructive: var(--destructive);
|
|
1051
|
+
--color-destructive-foreground: var(--destructive-foreground);
|
|
1052
|
+
--color-border: var(--border);
|
|
1053
|
+
--color-input: var(--input);
|
|
1054
|
+
--color-ring: var(--ring);
|
|
1055
|
+
--radius-sm: calc(var(--radius) - 4px);
|
|
1056
|
+
--radius-md: calc(var(--radius) - 2px);
|
|
1057
|
+
--radius-lg: var(--radius);
|
|
1058
|
+
--radius-xl: calc(var(--radius) + 4px);
|
|
1059
|
+
}`);
|
|
1060
|
+
parts.push(`@layer base {
|
|
1061
|
+
* {
|
|
1062
|
+
@apply border-border outline-ring/50;
|
|
1063
|
+
}
|
|
1064
|
+
body {
|
|
1065
|
+
@apply bg-background text-foreground;
|
|
1066
|
+
}
|
|
1067
|
+
}`);
|
|
1068
|
+
} else {
|
|
1069
|
+
if (mode === "full") {
|
|
1070
|
+
parts.push("@tailwind base;\n@tailwind components;\n@tailwind utilities;");
|
|
1071
|
+
}
|
|
1072
|
+
parts.push(lightVars, darkVars);
|
|
1073
|
+
parts.push(`@layer base {
|
|
1074
|
+
* {
|
|
1075
|
+
@apply border-border;
|
|
1076
|
+
}
|
|
1077
|
+
body {
|
|
1078
|
+
@apply bg-background text-foreground;
|
|
1079
|
+
}
|
|
1080
|
+
}`);
|
|
1081
|
+
}
|
|
1082
|
+
return parts.join("\n\n") + "\n";
|
|
1083
|
+
}
|
|
1084
|
+
async function injectThemeCSS(cssFilePath, tailwindMajor) {
|
|
1085
|
+
await fs4.ensureDir(path4.dirname(cssFilePath));
|
|
1086
|
+
if (fs4.existsSync(cssFilePath)) {
|
|
1087
|
+
const existing = await fs4.readFile(cssFilePath, "utf-8");
|
|
1088
|
+
if (existing.includes("--primary:")) {
|
|
1089
|
+
return { path: cssFilePath, skipped: true };
|
|
1090
|
+
}
|
|
1091
|
+
const theme2 = getThemeCSS(tailwindMajor, "append");
|
|
1092
|
+
await fs4.writeFile(cssFilePath, existing.trimEnd() + "\n\n" + theme2);
|
|
1093
|
+
return { path: cssFilePath, updated: true };
|
|
1094
|
+
}
|
|
1095
|
+
const theme = getThemeCSS(tailwindMajor, "full");
|
|
1096
|
+
await fs4.writeFile(cssFilePath, theme);
|
|
1097
|
+
return { path: cssFilePath, created: true };
|
|
1098
|
+
}
|
|
1099
|
+
function getTW3ColorExtend() {
|
|
1100
|
+
return `colors: {
|
|
1101
|
+
border: "var(--border)",
|
|
1102
|
+
input: "var(--input)",
|
|
1103
|
+
ring: "var(--ring)",
|
|
1104
|
+
background: "var(--background)",
|
|
1105
|
+
foreground: "var(--foreground)",
|
|
1106
|
+
primary: {
|
|
1107
|
+
DEFAULT: "var(--primary)",
|
|
1108
|
+
foreground: "var(--primary-foreground)",
|
|
1109
|
+
},
|
|
1110
|
+
secondary: {
|
|
1111
|
+
DEFAULT: "var(--secondary)",
|
|
1112
|
+
foreground: "var(--secondary-foreground)",
|
|
1113
|
+
},
|
|
1114
|
+
destructive: {
|
|
1115
|
+
DEFAULT: "var(--destructive)",
|
|
1116
|
+
foreground: "var(--destructive-foreground)",
|
|
1117
|
+
},
|
|
1118
|
+
muted: {
|
|
1119
|
+
DEFAULT: "var(--muted)",
|
|
1120
|
+
foreground: "var(--muted-foreground)",
|
|
1121
|
+
},
|
|
1122
|
+
accent: {
|
|
1123
|
+
DEFAULT: "var(--accent)",
|
|
1124
|
+
foreground: "var(--accent-foreground)",
|
|
1125
|
+
},
|
|
1126
|
+
popover: {
|
|
1127
|
+
DEFAULT: "var(--popover)",
|
|
1128
|
+
foreground: "var(--popover-foreground)",
|
|
1129
|
+
},
|
|
1130
|
+
card: {
|
|
1131
|
+
DEFAULT: "var(--card)",
|
|
1132
|
+
foreground: "var(--card-foreground)",
|
|
1133
|
+
},
|
|
1134
|
+
},
|
|
1135
|
+
borderRadius: {
|
|
1136
|
+
lg: "var(--radius)",
|
|
1137
|
+
md: "calc(var(--radius) - 2px)",
|
|
1138
|
+
sm: "calc(var(--radius) - 4px)",
|
|
1139
|
+
},`;
|
|
1140
|
+
}
|
|
1141
|
+
function createTW3Config(isCjs) {
|
|
1142
|
+
const colorExtend = getTW3ColorExtend();
|
|
1143
|
+
if (isCjs) {
|
|
1144
|
+
return `/** @type {import('tailwindcss').Config} */
|
|
1145
|
+
module.exports = {
|
|
1146
|
+
content: [
|
|
1147
|
+
"./src/**/*.{js,ts,jsx,tsx}",
|
|
1148
|
+
"./app/**/*.{js,ts,jsx,tsx}",
|
|
1149
|
+
"./components/**/*.{js,ts,jsx,tsx}",
|
|
1150
|
+
],
|
|
1151
|
+
theme: {
|
|
1152
|
+
extend: {
|
|
1153
|
+
${colorExtend}
|
|
1154
|
+
},
|
|
1155
|
+
},
|
|
1156
|
+
plugins: [],
|
|
1157
|
+
}
|
|
1158
|
+
`;
|
|
1159
|
+
}
|
|
1160
|
+
return `/** @type {import('tailwindcss').Config} */
|
|
1161
|
+
export default {
|
|
1162
|
+
content: [
|
|
1163
|
+
"./src/**/*.{js,ts,jsx,tsx}",
|
|
1164
|
+
"./app/**/*.{js,ts,jsx,tsx}",
|
|
1165
|
+
"./components/**/*.{js,ts,jsx,tsx}",
|
|
1166
|
+
],
|
|
1167
|
+
theme: {
|
|
1168
|
+
extend: {
|
|
1169
|
+
${colorExtend}
|
|
1170
|
+
},
|
|
1171
|
+
},
|
|
1172
|
+
plugins: [],
|
|
1173
|
+
}
|
|
1174
|
+
`;
|
|
1175
|
+
}
|
|
1176
|
+
async function ensureTW3ColorConfig(projectRoot, configFileName) {
|
|
1177
|
+
const candidates = [
|
|
1178
|
+
configFileName,
|
|
1179
|
+
"tailwind.config.ts",
|
|
1180
|
+
"tailwind.config.js",
|
|
1181
|
+
"tailwind.config.mjs",
|
|
1182
|
+
"tailwind.config.cjs"
|
|
1183
|
+
];
|
|
1184
|
+
let configPath = null;
|
|
1185
|
+
for (const candidate of candidates) {
|
|
1186
|
+
const candidatePath = path4.join(projectRoot, candidate);
|
|
1187
|
+
if (fs4.existsSync(candidatePath)) {
|
|
1188
|
+
configPath = candidatePath;
|
|
1189
|
+
break;
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
if (!configPath) {
|
|
1193
|
+
configPath = path4.join(projectRoot, configFileName);
|
|
1194
|
+
const ext = path4.extname(configPath);
|
|
1195
|
+
const isCjs = ext === ".cjs";
|
|
1196
|
+
await fs4.writeFile(configPath, createTW3Config(isCjs));
|
|
1197
|
+
return { path: configPath, created: true };
|
|
1198
|
+
}
|
|
1199
|
+
const content = await fs4.readFile(configPath, "utf-8");
|
|
1200
|
+
if (content.includes('"var(--primary)"') || content.includes("'var(--primary)'")) {
|
|
1201
|
+
return { path: configPath, skipped: true };
|
|
1202
|
+
}
|
|
1203
|
+
const colorExtend = getTW3ColorExtend();
|
|
1204
|
+
const extendMatch = content.match(/extend\s*:\s*\{/);
|
|
1205
|
+
if (extendMatch && extendMatch.index !== void 0) {
|
|
1206
|
+
const insertPos = extendMatch.index + extendMatch[0].length;
|
|
1207
|
+
const updated = content.slice(0, insertPos) + "\n " + colorExtend + content.slice(insertPos);
|
|
1208
|
+
await fs4.writeFile(configPath, updated);
|
|
1209
|
+
return { path: configPath, updated: true };
|
|
1210
|
+
}
|
|
1211
|
+
const themeMatch = content.match(/theme\s*:\s*\{/);
|
|
1212
|
+
if (themeMatch && themeMatch.index !== void 0) {
|
|
1213
|
+
const insertPos = themeMatch.index + themeMatch[0].length;
|
|
1214
|
+
const extendBlock = `
|
|
1215
|
+
extend: {
|
|
1216
|
+
${colorExtend}
|
|
1217
|
+
},`;
|
|
1218
|
+
const updated = content.slice(0, insertPos) + extendBlock + content.slice(insertPos);
|
|
1219
|
+
await fs4.writeFile(configPath, updated);
|
|
1220
|
+
return { path: configPath, updated: true };
|
|
1221
|
+
}
|
|
1222
|
+
const closingBrace = content.lastIndexOf("}");
|
|
1223
|
+
if (closingBrace !== -1) {
|
|
1224
|
+
const before = content.slice(0, closingBrace).trimEnd();
|
|
1225
|
+
const needsComma = !before.endsWith(",") && !before.endsWith("{");
|
|
1226
|
+
const themeBlock = `${needsComma ? "," : ""}
|
|
1227
|
+
theme: {
|
|
1228
|
+
extend: {
|
|
1229
|
+
${colorExtend}
|
|
1230
|
+
},
|
|
1231
|
+
},
|
|
1232
|
+
`;
|
|
1233
|
+
const updated = before + themeBlock + content.slice(closingBrace);
|
|
1234
|
+
await fs4.writeFile(configPath, updated);
|
|
1235
|
+
return { path: configPath, updated: true };
|
|
1236
|
+
}
|
|
1237
|
+
return { path: configPath, skipped: true };
|
|
1238
|
+
}
|
|
1239
|
+
function readJsonConfig(filePath) {
|
|
1240
|
+
try {
|
|
1241
|
+
const content = fs4.readFileSync(filePath, "utf-8");
|
|
1242
|
+
return parseJsonWithComments(content);
|
|
1243
|
+
} catch {
|
|
1244
|
+
return null;
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
async function ensureAtAlias(projectRoot, srcDir, isTypeScript) {
|
|
1248
|
+
const tsConfigPath = path4.join(projectRoot, "tsconfig.json");
|
|
1249
|
+
const jsConfigPath = path4.join(projectRoot, "jsconfig.json");
|
|
1250
|
+
const configPath = fs4.existsSync(tsConfigPath) ? tsConfigPath : fs4.existsSync(jsConfigPath) ? jsConfigPath : path4.join(projectRoot, isTypeScript ? "tsconfig.json" : "jsconfig.json");
|
|
1251
|
+
const existedBefore = fs4.existsSync(configPath);
|
|
1252
|
+
const parsedConfig = readJsonConfig(configPath);
|
|
1253
|
+
if (existedBefore && !parsedConfig) {
|
|
1254
|
+
return { path: configPath, skipped: true };
|
|
1255
|
+
}
|
|
1256
|
+
const parsed = parsedConfig ?? {};
|
|
1257
|
+
const compilerOptions = parsed.compilerOptions ?? {};
|
|
1258
|
+
const rawPaths = compilerOptions.paths ?? {};
|
|
1259
|
+
const paths = {};
|
|
1260
|
+
for (const [key, value] of Object.entries(rawPaths)) {
|
|
1261
|
+
if (!Array.isArray(value)) {
|
|
1262
|
+
continue;
|
|
1263
|
+
}
|
|
1264
|
+
const targets = value.filter((entry) => typeof entry === "string");
|
|
1265
|
+
if (targets.length > 0) {
|
|
1266
|
+
paths[key] = targets;
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
const aliasTarget = srcDir === "." ? "*" : `${srcDir}/*`;
|
|
1270
|
+
const alreadyConfigured = compilerOptions.baseUrl === "." && Array.isArray(paths["@/*"]) && paths["@/*"][0] === aliasTarget;
|
|
1271
|
+
if (alreadyConfigured) {
|
|
1272
|
+
return { path: configPath };
|
|
1273
|
+
}
|
|
1274
|
+
const nextConfig = {
|
|
1275
|
+
...parsed,
|
|
1276
|
+
compilerOptions: {
|
|
1277
|
+
...compilerOptions,
|
|
1278
|
+
baseUrl: ".",
|
|
1279
|
+
paths: {
|
|
1280
|
+
...paths,
|
|
1281
|
+
"@/*": [aliasTarget]
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
};
|
|
1285
|
+
try {
|
|
1286
|
+
await fs4.writeFile(configPath, `${JSON.stringify(nextConfig, null, 2)}
|
|
1287
|
+
`);
|
|
1288
|
+
return { path: configPath, [existedBefore ? "updated" : "created"]: true };
|
|
1289
|
+
} catch {
|
|
1290
|
+
return { path: configPath, skipped: true };
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
var initCommand = new Command2("init").description("Initialize Dinachi UI in your project").option("--skip-install", "Skip package installation").action(async (options) => {
|
|
602
1294
|
console.log(chalk2.bold.cyan("\u{1F3A8} Welcome to Dinachi UI!"));
|
|
603
1295
|
console.log();
|
|
604
|
-
const
|
|
605
|
-
|
|
1296
|
+
const projectRoot = process.cwd();
|
|
1297
|
+
const packageJsonPath = path4.join(projectRoot, "package.json");
|
|
1298
|
+
if (!fs4.existsSync(packageJsonPath)) {
|
|
606
1299
|
console.log(chalk2.red("\u274C No package.json found. Please run this command in a valid project."));
|
|
607
1300
|
process.exit(1);
|
|
608
1301
|
}
|
|
@@ -629,69 +1322,116 @@ var initCommand = new Command2("init").description("Initialize Dinachi UI in you
|
|
|
629
1322
|
}
|
|
630
1323
|
const spinner = ora2("Setting up Dinachi UI...").start();
|
|
631
1324
|
try {
|
|
632
|
-
const normalizedComponentsPath =
|
|
633
|
-
const normalizedUtilsPath =
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
const
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
1325
|
+
const normalizedComponentsPath = normalizeProjectPath(response.componentsPath, projectRoot);
|
|
1326
|
+
const normalizedUtilsPath = normalizeProjectPath(response.utilsPath, projectRoot);
|
|
1327
|
+
const componentsDirPath = path4.resolve(projectRoot, normalizedComponentsPath);
|
|
1328
|
+
const utilsDirPath = path4.resolve(projectRoot, normalizedUtilsPath);
|
|
1329
|
+
const utilsFilePath = path4.join(utilsDirPath, "utils.ts");
|
|
1330
|
+
await fs4.ensureDir(componentsDirPath);
|
|
1331
|
+
await fs4.ensureDir(utilsDirPath);
|
|
1332
|
+
const utilsContent = createUtilsFileContent();
|
|
1333
|
+
await fs4.writeFile(utilsFilePath, utilsContent);
|
|
1334
|
+
spinner.text = "Setting up color theme...";
|
|
1335
|
+
const tailwindMajor = detectTailwindMajorVersion(projectRoot);
|
|
1336
|
+
const cssFilePath = path4.resolve(projectRoot, projectConfig.cssPath);
|
|
1337
|
+
const themeCSSResult = await injectThemeCSS(cssFilePath, tailwindMajor);
|
|
1338
|
+
let twColorConfigResult = null;
|
|
1339
|
+
if (tailwindMajor < 4) {
|
|
1340
|
+
spinner.text = "Configuring Tailwind color mappings...";
|
|
1341
|
+
twColorConfigResult = await ensureTW3ColorConfig(projectRoot, projectConfig.tailwindConfig);
|
|
1342
|
+
}
|
|
644
1343
|
spinner.text = "Installing dependencies...";
|
|
645
|
-
const deps = [
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
const
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
ui: uiAlias,
|
|
672
|
-
lib: libAlias,
|
|
673
|
-
hooks: `${pathToAlias(projectConfig.srcDir)}/hooks`
|
|
1344
|
+
const deps = ["class-variance-authority", "clsx", "tailwind-merge"];
|
|
1345
|
+
if (!options.skipInstall) {
|
|
1346
|
+
const packageManager = detectPackageManager(projectRoot);
|
|
1347
|
+
const installCmd = getInstallCommand(packageManager, deps.map(toInstallSpec));
|
|
1348
|
+
execSync2(installCmd, { stdio: "inherit", cwd: projectRoot });
|
|
1349
|
+
}
|
|
1350
|
+
const hooksPath = projectConfig.srcDir === "." ? "hooks" : path4.join(projectConfig.srcDir, "hooks").replace(/\\/g, "/");
|
|
1351
|
+
const configContent = JSON.stringify(
|
|
1352
|
+
{
|
|
1353
|
+
style: "default",
|
|
1354
|
+
rsc: projectConfig.framework === "next.js",
|
|
1355
|
+
tsx: true,
|
|
1356
|
+
tailwind: {
|
|
1357
|
+
config: projectConfig.tailwindConfig,
|
|
1358
|
+
css: projectConfig.cssPath,
|
|
1359
|
+
baseColor: "slate",
|
|
1360
|
+
cssVariables: true
|
|
1361
|
+
},
|
|
1362
|
+
aliases: {
|
|
1363
|
+
components: toConfigPath(path4.dirname(normalizedComponentsPath)),
|
|
1364
|
+
utils: toConfigPath(path4.join(normalizedUtilsPath, "utils")),
|
|
1365
|
+
ui: toConfigPath(normalizedComponentsPath),
|
|
1366
|
+
lib: toConfigPath(normalizedUtilsPath),
|
|
1367
|
+
hooks: toConfigPath(hooksPath)
|
|
1368
|
+
},
|
|
1369
|
+
iconLibrary: "lucide"
|
|
674
1370
|
},
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
1371
|
+
null,
|
|
1372
|
+
2
|
|
1373
|
+
);
|
|
1374
|
+
await fs4.writeFile(path4.join(projectRoot, "components.json"), `${configContent}
|
|
1375
|
+
`);
|
|
1376
|
+
const aliasConfigUpdate = await ensureAtAlias(projectRoot, projectConfig.srcDir, projectConfig.isTypeScript);
|
|
678
1377
|
spinner.succeed("\u2705 Dinachi UI setup complete!");
|
|
679
1378
|
console.log();
|
|
680
1379
|
console.log("Next steps:");
|
|
681
1380
|
console.log(` 1. Add a component: ${chalk2.cyan("npx @dinachi/cli add button")}`);
|
|
682
|
-
console.log(` 2. Components will be installed to: ${chalk2.cyan(
|
|
683
|
-
console.log(` 3. Utils available at: ${chalk2.cyan(
|
|
1381
|
+
console.log(` 2. Components will be installed to: ${chalk2.cyan(componentsDirPath)}`);
|
|
1382
|
+
console.log(` 3. Utils available at: ${chalk2.cyan(utilsFilePath)}`);
|
|
1383
|
+
console.log();
|
|
1384
|
+
if (aliasConfigUpdate.created) {
|
|
1385
|
+
console.log(` ${chalk2.green("+")} Added @/* path alias in ${aliasConfigUpdate.path}`);
|
|
1386
|
+
} else if (aliasConfigUpdate.updated) {
|
|
1387
|
+
console.log(` ${chalk2.blue("~")} Updated @/* path alias in ${aliasConfigUpdate.path}`);
|
|
1388
|
+
} else if (aliasConfigUpdate.skipped) {
|
|
1389
|
+
console.log(
|
|
1390
|
+
` ${chalk2.yellow("!")} Could not update ${aliasConfigUpdate.path}. Configure @/* manually if you use alias imports.`
|
|
1391
|
+
);
|
|
1392
|
+
}
|
|
1393
|
+
if (themeCSSResult.created) {
|
|
1394
|
+
console.log(` ${chalk2.green("+")} Created ${projectConfig.cssPath} with color theme (light + dark)`);
|
|
1395
|
+
} else if (themeCSSResult.updated) {
|
|
1396
|
+
console.log(` ${chalk2.blue("~")} Updated ${projectConfig.cssPath} with color theme (light + dark)`);
|
|
1397
|
+
} else if (themeCSSResult.skipped) {
|
|
1398
|
+
console.log(` ${chalk2.gray("-")} Color theme already configured in ${projectConfig.cssPath}`);
|
|
1399
|
+
}
|
|
1400
|
+
if (twColorConfigResult) {
|
|
1401
|
+
if (twColorConfigResult.created) {
|
|
1402
|
+
console.log(` ${chalk2.green("+")} Created ${projectConfig.tailwindConfig} with color mappings`);
|
|
1403
|
+
} else if (twColorConfigResult.updated) {
|
|
1404
|
+
console.log(` ${chalk2.blue("~")} Updated ${projectConfig.tailwindConfig} with color mappings`);
|
|
1405
|
+
} else if (twColorConfigResult.skipped) {
|
|
1406
|
+
console.log(` ${chalk2.gray("-")} Color mappings already in ${projectConfig.tailwindConfig}`);
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
if (!projectConfig.isTypeScript) {
|
|
1410
|
+
console.log();
|
|
1411
|
+
console.log(
|
|
1412
|
+
chalk2.yellow(
|
|
1413
|
+
"\u26A0\uFE0F Dinachi components are TypeScript-first. Your project can still consume TSX files, but type-check tooling is recommended."
|
|
1414
|
+
)
|
|
1415
|
+
);
|
|
1416
|
+
}
|
|
684
1417
|
if (projectConfig.framework === "next.js") {
|
|
685
1418
|
console.log();
|
|
686
1419
|
console.log(chalk2.blue("\u{1F4DD} Next.js specific notes:"));
|
|
687
|
-
console.log(
|
|
688
|
-
console.log(
|
|
1420
|
+
console.log(" - RSC (React Server Components) enabled in config");
|
|
1421
|
+
console.log(' - Add "use client" for interactive components where needed');
|
|
689
1422
|
console.log(` - Tailwind config: ${chalk2.cyan(projectConfig.tailwindConfig)}`);
|
|
690
1423
|
} else if (projectConfig.framework === "remix") {
|
|
691
1424
|
console.log();
|
|
692
1425
|
console.log(chalk2.blue("\u{1F4DD} Remix specific notes:"));
|
|
693
|
-
console.log(` - Components
|
|
694
|
-
console.log(` -
|
|
1426
|
+
console.log(` - Components directory: ${chalk2.cyan(componentsDirPath)}`);
|
|
1427
|
+
console.log(` - Utilities directory: ${chalk2.cyan(utilsDirPath)}`);
|
|
1428
|
+
}
|
|
1429
|
+
if (options.skipInstall) {
|
|
1430
|
+
console.log();
|
|
1431
|
+
console.log("Dependencies to install manually:");
|
|
1432
|
+
deps.forEach((dep) => {
|
|
1433
|
+
console.log(` ${chalk2.yellow("\u2022")} ${toInstallSpec(dep)}`);
|
|
1434
|
+
});
|
|
695
1435
|
}
|
|
696
1436
|
console.log();
|
|
697
1437
|
console.log("\u{1F4A1} Tip: Install globally for shorter commands:");
|
|
@@ -702,42 +1442,17 @@ export function cn(...inputs: ClassValue[]) {
|
|
|
702
1442
|
process.exit(1);
|
|
703
1443
|
}
|
|
704
1444
|
});
|
|
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
1445
|
function detectProjectType() {
|
|
732
|
-
const packageJsonPath =
|
|
733
|
-
if (!
|
|
1446
|
+
const packageJsonPath = path4.join(process.cwd(), "package.json");
|
|
1447
|
+
if (!fs4.existsSync(packageJsonPath)) {
|
|
734
1448
|
return getDefaultConfig("react", false);
|
|
735
1449
|
}
|
|
736
|
-
const packageJson = JSON.parse(
|
|
737
|
-
const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
|
|
738
|
-
const hasSrcDir =
|
|
739
|
-
const hasAppDir =
|
|
740
|
-
const hasSrcAppDir =
|
|
1450
|
+
const packageJson = JSON.parse(fs4.readFileSync(packageJsonPath, "utf-8"));
|
|
1451
|
+
const deps = { ...packageJson.dependencies ?? {}, ...packageJson.devDependencies ?? {} };
|
|
1452
|
+
const hasSrcDir = fs4.existsSync(path4.join(process.cwd(), "src"));
|
|
1453
|
+
const hasAppDir = fs4.existsSync(path4.join(process.cwd(), "app"));
|
|
1454
|
+
const hasSrcAppDir = fs4.existsSync(path4.join(process.cwd(), "src", "app"));
|
|
1455
|
+
const isTypeScript = fs4.existsSync(path4.join(process.cwd(), "tsconfig.json")) || fs4.existsSync(path4.join(process.cwd(), "tsconfig.base.json")) || Boolean(deps.typescript);
|
|
741
1456
|
const tailwindConfig = detectTailwindConfig();
|
|
742
1457
|
const cssPath = detectCssPath(hasSrcDir, hasSrcAppDir);
|
|
743
1458
|
if (deps.next) {
|
|
@@ -748,7 +1463,8 @@ function detectProjectType() {
|
|
|
748
1463
|
utilsPath: "./src/lib",
|
|
749
1464
|
tailwindConfig,
|
|
750
1465
|
cssPath,
|
|
751
|
-
srcDir: "src"
|
|
1466
|
+
srcDir: "src",
|
|
1467
|
+
isTypeScript
|
|
752
1468
|
};
|
|
753
1469
|
}
|
|
754
1470
|
if (hasAppDir && !hasSrcDir) {
|
|
@@ -758,7 +1474,8 @@ function detectProjectType() {
|
|
|
758
1474
|
utilsPath: "./lib",
|
|
759
1475
|
tailwindConfig,
|
|
760
1476
|
cssPath,
|
|
761
|
-
srcDir: "."
|
|
1477
|
+
srcDir: ".",
|
|
1478
|
+
isTypeScript
|
|
762
1479
|
};
|
|
763
1480
|
}
|
|
764
1481
|
return {
|
|
@@ -767,7 +1484,8 @@ function detectProjectType() {
|
|
|
767
1484
|
utilsPath: hasSrcDir ? "./src/lib" : "./lib",
|
|
768
1485
|
tailwindConfig,
|
|
769
1486
|
cssPath,
|
|
770
|
-
srcDir: hasSrcDir ? "src" : "."
|
|
1487
|
+
srcDir: hasSrcDir ? "src" : ".",
|
|
1488
|
+
isTypeScript
|
|
771
1489
|
};
|
|
772
1490
|
}
|
|
773
1491
|
if (deps.vite || deps["@vitejs/plugin-react"]) {
|
|
@@ -777,7 +1495,8 @@ function detectProjectType() {
|
|
|
777
1495
|
utilsPath: hasSrcDir ? "./src/lib" : "./lib",
|
|
778
1496
|
tailwindConfig,
|
|
779
1497
|
cssPath,
|
|
780
|
-
srcDir: hasSrcDir ? "src" : "."
|
|
1498
|
+
srcDir: hasSrcDir ? "src" : ".",
|
|
1499
|
+
isTypeScript
|
|
781
1500
|
};
|
|
782
1501
|
}
|
|
783
1502
|
if (deps["react-scripts"]) {
|
|
@@ -787,7 +1506,8 @@ function detectProjectType() {
|
|
|
787
1506
|
utilsPath: "./src/lib",
|
|
788
1507
|
tailwindConfig,
|
|
789
1508
|
cssPath,
|
|
790
|
-
srcDir: "src"
|
|
1509
|
+
srcDir: "src",
|
|
1510
|
+
isTypeScript
|
|
791
1511
|
};
|
|
792
1512
|
}
|
|
793
1513
|
if (deps["@remix-run/react"]) {
|
|
@@ -797,62 +1517,79 @@ function detectProjectType() {
|
|
|
797
1517
|
utilsPath: "./app/lib",
|
|
798
1518
|
tailwindConfig,
|
|
799
1519
|
cssPath: detectCssPath(false, false, true),
|
|
800
|
-
srcDir: "app"
|
|
1520
|
+
srcDir: "app",
|
|
1521
|
+
isTypeScript
|
|
801
1522
|
};
|
|
802
1523
|
}
|
|
803
1524
|
return getDefaultConfig("react", hasSrcDir);
|
|
804
1525
|
}
|
|
805
1526
|
function getDefaultConfig(framework, hasSrcDir) {
|
|
1527
|
+
const isTypeScript = fs4.existsSync(path4.join(process.cwd(), "tsconfig.json")) || fs4.existsSync(path4.join(process.cwd(), "tsconfig.base.json"));
|
|
806
1528
|
return {
|
|
807
1529
|
framework,
|
|
808
1530
|
componentsPath: hasSrcDir ? "./src/components/ui" : "./components/ui",
|
|
809
1531
|
utilsPath: hasSrcDir ? "./src/lib" : "./lib",
|
|
810
1532
|
tailwindConfig: detectTailwindConfig(),
|
|
811
1533
|
cssPath: hasSrcDir ? "src/index.css" : "index.css",
|
|
812
|
-
srcDir: hasSrcDir ? "src" : "."
|
|
1534
|
+
srcDir: hasSrcDir ? "src" : ".",
|
|
1535
|
+
isTypeScript
|
|
813
1536
|
};
|
|
814
1537
|
}
|
|
815
1538
|
function detectTailwindConfig() {
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
1539
|
+
const configCandidates = [
|
|
1540
|
+
"tailwind.config.ts",
|
|
1541
|
+
"tailwind.config.js",
|
|
1542
|
+
"tailwind.config.mjs",
|
|
1543
|
+
"tailwind.config.cjs",
|
|
1544
|
+
"tailwind.config.cts",
|
|
1545
|
+
"tailwind.config.mts"
|
|
1546
|
+
];
|
|
1547
|
+
for (const config of configCandidates) {
|
|
1548
|
+
if (fs4.existsSync(path4.join(process.cwd(), config))) {
|
|
1549
|
+
return config;
|
|
1550
|
+
}
|
|
824
1551
|
}
|
|
825
1552
|
return "tailwind.config.js";
|
|
826
1553
|
}
|
|
827
1554
|
function detectCssPath(hasSrcDir, hasSrcAppDir, isRemix = false) {
|
|
828
1555
|
const cwd = process.cwd();
|
|
829
1556
|
const possiblePaths = [
|
|
830
|
-
// Next.js App Router
|
|
831
1557
|
"app/globals.css",
|
|
832
1558
|
"src/app/globals.css",
|
|
833
|
-
// Next.js Pages / General
|
|
834
1559
|
"styles/globals.css",
|
|
835
1560
|
"src/styles/globals.css",
|
|
836
|
-
// Vite / CRA
|
|
837
1561
|
"src/index.css",
|
|
838
1562
|
"index.css",
|
|
839
|
-
// Remix
|
|
840
1563
|
"app/tailwind.css",
|
|
841
1564
|
"app/styles/tailwind.css"
|
|
842
1565
|
];
|
|
843
1566
|
for (const cssPath of possiblePaths) {
|
|
844
|
-
if (
|
|
1567
|
+
if (fs4.existsSync(path4.join(cwd, cssPath))) {
|
|
845
1568
|
return cssPath;
|
|
846
1569
|
}
|
|
847
1570
|
}
|
|
848
1571
|
if (isRemix) return "app/tailwind.css";
|
|
849
1572
|
if (hasSrcAppDir) return "src/app/globals.css";
|
|
850
1573
|
if (hasSrcDir) return "src/index.css";
|
|
851
|
-
return "
|
|
1574
|
+
return "index.css";
|
|
852
1575
|
}
|
|
853
1576
|
|
|
854
1577
|
// src/index.ts
|
|
1578
|
+
import fs5 from "fs";
|
|
1579
|
+
import path5 from "path";
|
|
1580
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
855
1581
|
var program = new Command3();
|
|
856
|
-
|
|
1582
|
+
var __filename3 = fileURLToPath3(import.meta.url);
|
|
1583
|
+
var __dirname3 = path5.dirname(__filename3);
|
|
1584
|
+
function getCliVersion() {
|
|
1585
|
+
try {
|
|
1586
|
+
const packageJsonPath = path5.resolve(__dirname3, "../package.json");
|
|
1587
|
+
const packageJson = JSON.parse(fs5.readFileSync(packageJsonPath, "utf-8"));
|
|
1588
|
+
return packageJson.version ?? "0.0.0";
|
|
1589
|
+
} catch {
|
|
1590
|
+
return "0.0.0";
|
|
1591
|
+
}
|
|
1592
|
+
}
|
|
1593
|
+
program.name("dinachi").description("Add Dinachi UI components to your project").version(getCliVersion());
|
|
857
1594
|
program.addCommand(addCommand).addCommand(initCommand);
|
|
858
1595
|
program.parse();
|