@dinachi/cli 0.4.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 (35) hide show
  1. package/README.md +27 -5
  2. package/dist/index.js +1037 -567
  3. package/package.json +6 -5
  4. package/templates/autocomplete/autocomplete.tsx +197 -0
  5. package/templates/autocomplete/index.ts +17 -0
  6. package/templates/combobox/combobox.tsx +202 -0
  7. package/templates/combobox/index.ts +17 -0
  8. package/templates/context-menu/context-menu.tsx +104 -39
  9. package/templates/drawer/drawer.tsx +109 -0
  10. package/templates/drawer/index.ts +12 -0
  11. package/templates/fieldset/fieldset.tsx +32 -0
  12. package/templates/fieldset/index.ts +1 -0
  13. package/templates/menu/index.ts +17 -0
  14. package/templates/menu/menu.tsx +282 -0
  15. package/templates/menubar/menubar.tsx +7 -7
  16. package/templates/meter/index.ts +1 -0
  17. package/templates/meter/meter.tsx +64 -0
  18. package/templates/number-field/index.ts +9 -0
  19. package/templates/number-field/number-field.tsx +114 -0
  20. package/templates/popover/index.ts +12 -0
  21. package/templates/popover/popover.tsx +137 -0
  22. package/templates/preview-card/preview-card.tsx +4 -5
  23. package/templates/progress/index.ts +7 -0
  24. package/templates/progress/progress.tsx +64 -0
  25. package/templates/radio/index.ts +1 -0
  26. package/templates/radio/radio.tsx +39 -0
  27. package/templates/scroll-area/index.ts +8 -0
  28. package/templates/scroll-area/scroll-area.tsx +94 -0
  29. package/templates/separator/index.ts +1 -0
  30. package/templates/separator/separator.tsx +25 -0
  31. package/templates/switch/index.ts +1 -0
  32. package/templates/switch/switch.tsx +42 -0
  33. package/templates/toggle-group/index.ts +1 -0
  34. package/templates/toggle-group/toggle-group.tsx +67 -0
  35. package/templates/tooltip/tooltip.tsx +2 -2
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,315 +18,314 @@ 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: {
24
66
  name: "utils",
25
- dependencies: [
26
- "clsx",
27
- "tailwind-merge"
28
- ]
67
+ dependencies: ["clsx", "tailwind-merge"]
29
68
  },
30
69
  variants: {
31
70
  name: "variants",
32
- dependencies: [
33
- "class-variance-authority"
34
- ]
71
+ dependencies: ["class-variance-authority"]
35
72
  }
36
73
  };
37
74
  }
38
75
  function getComponentRegistry() {
39
76
  return {
40
- button: {
41
- name: "button",
42
- description: "A customizable button component with multiple variants",
43
- files: [
44
- { name: "button.tsx" },
45
- { name: "index.ts" }
46
- ],
47
- dependencies: [
48
- "@base-ui/react",
49
- "class-variance-authority"
50
- ],
51
- componentDependencies: ["core"],
52
- utilityDependencies: ["cn", "variants"]
53
- },
54
- input: {
55
- name: "input",
56
- description: "A customizable input field component with variants and sizes",
57
- files: [
58
- { name: "input.tsx" },
59
- { name: "index.ts" }
60
- ],
61
- dependencies: [
62
- "@base-ui/react"
63
- ],
64
- utilityDependencies: ["cn"]
65
- },
66
- field: {
67
- name: "field",
68
- description: "A component for building accessible forms with custom styling and validation.",
69
- files: [
70
- { name: "field.tsx" },
71
- { name: "index.ts" }
72
- ],
73
- dependencies: [
74
- "@base-ui/react"
75
- ],
76
- utilityDependencies: ["cn"]
77
- },
78
- form: {
79
- name: "form",
80
- description: "A native form element with consolidated error handling, built on Base UI foundation.",
81
- files: [
82
- { name: "form.tsx" },
83
- { name: "index.ts" }
84
- ],
85
- dependencies: [
86
- "@base-ui/react"
87
- ],
88
- utilityDependencies: ["cn"]
89
- },
90
- "alert-dialog": {
91
- name: "alert-dialog",
92
- description: "A modal dialog that interrupts the user with important content and expects a response.",
93
- files: [
94
- { name: "alert-dialog.tsx" },
95
- { name: "index.ts" }
96
- ],
97
- dependencies: [
98
- "@base-ui/react",
99
- "lucide-react"
100
- ],
101
- utilityDependencies: ["cn"]
102
- },
103
77
  accordion: {
104
78
  name: "accordion",
105
79
  description: "A vertically stacked set of interactive headings that each reveal a section of content.",
106
- files: [
107
- { name: "accordion.tsx" },
108
- { name: "index.ts" }
109
- ],
110
- dependencies: [
111
- "@base-ui/react",
112
- "lucide-react",
113
- "tailwindcss-animate"
114
- ],
80
+ files: [{ name: "accordion.tsx" }, { name: "index.ts" }],
81
+ dependencies: ["@base-ui/react", "lucide-react", "tailwindcss-animate"],
115
82
  utilityDependencies: ["cn"]
116
83
  },
117
- tabs: {
118
- name: "tabs",
119
- description: "A component for toggling between related panels on the same page.",
120
- files: [
121
- { name: "tabs.tsx" },
122
- { name: "index.ts" }
123
- ],
124
- dependencies: [
125
- "@base-ui/react"
126
- ],
84
+ "alert-dialog": {
85
+ name: "alert-dialog",
86
+ description: "A modal dialog that interrupts the user with important content and expects a response.",
87
+ files: [{ name: "alert-dialog.tsx" }, { name: "index.ts" }],
88
+ dependencies: ["@base-ui/react", "lucide-react"],
127
89
  utilityDependencies: ["cn"]
128
90
  },
129
- slider: {
130
- name: "slider",
131
- description: "An input where the user selects a value from within a given range.",
132
- files: [
133
- { name: "slider.tsx" },
134
- { name: "index.ts" }
135
- ],
136
- dependencies: [
137
- "@base-ui/react"
138
- ],
91
+ autocomplete: {
92
+ name: "autocomplete",
93
+ description: "A text input with dynamic suggestions that helps users find and select values quickly.",
94
+ files: [{ name: "autocomplete.tsx" }, { name: "index.ts" }],
95
+ dependencies: ["@base-ui/react", "lucide-react"],
139
96
  utilityDependencies: ["cn"]
140
97
  },
141
98
  avatar: {
142
99
  name: "avatar",
143
100
  description: "An image element with a fallback for representing a user.",
144
- files: [
145
- { name: "avatar.tsx" },
146
- { name: "index.ts" }
147
- ],
148
- dependencies: [
149
- "@base-ui/react",
150
- "class-variance-authority"
151
- ],
101
+ files: [{ name: "avatar.tsx" }, { name: "index.ts" }],
102
+ dependencies: ["@base-ui/react", "class-variance-authority"],
152
103
  utilityDependencies: ["cn"]
153
104
  },
105
+ button: {
106
+ name: "button",
107
+ description: "A customizable button component with multiple variants.",
108
+ files: [{ name: "button.tsx" }, { name: "index.ts" }],
109
+ dependencies: ["@base-ui/react", "class-variance-authority"],
110
+ utilityDependencies: ["cn", "variants"]
111
+ },
154
112
  checkbox: {
155
113
  name: "checkbox",
156
114
  description: "A control that allows the user to select one or more options from a set.",
157
- files: [
158
- { name: "checkbox.tsx" },
159
- { name: "index.ts" }
160
- ],
161
- dependencies: [
162
- "@base-ui/react",
163
- "lucide-react"
164
- ],
115
+ files: [{ name: "checkbox.tsx" }, { name: "index.ts" }],
116
+ dependencies: ["@base-ui/react", "lucide-react"],
165
117
  utilityDependencies: ["cn"]
166
118
  },
167
119
  "checkbox-group": {
168
120
  name: "checkbox-group",
169
121
  description: "A group of checkboxes that share a common state.",
170
- files: [
171
- { name: "checkbox-group.tsx" },
172
- { name: "index.ts" }
173
- ],
174
- dependencies: [
175
- "@base-ui/react"
176
- ],
122
+ files: [{ name: "checkbox-group.tsx" }, { name: "index.ts" }],
123
+ dependencies: ["@base-ui/react"],
177
124
  componentDependencies: ["checkbox"],
178
125
  utilityDependencies: ["cn"]
179
126
  },
180
- "collapsible": {
127
+ collapsible: {
181
128
  name: "collapsible",
182
129
  description: "A collapsible panel controlled by a button.",
183
- files: [
184
- { name: "collapsible.tsx" },
185
- { name: "index.ts" }
186
- ],
187
- dependencies: [
188
- "@base-ui/react",
189
- "tailwindcss-animate"
190
- ],
130
+ files: [{ name: "collapsible.tsx" }, { name: "index.ts" }],
131
+ dependencies: ["@base-ui/react", "tailwindcss-animate"],
132
+ utilityDependencies: ["cn"]
133
+ },
134
+ combobox: {
135
+ name: "combobox",
136
+ description: "A combobox that combines text input with a selectable popup list of options.",
137
+ files: [{ name: "combobox.tsx" }, { name: "index.ts" }],
138
+ dependencies: ["@base-ui/react", "lucide-react"],
139
+ utilityDependencies: ["cn"]
140
+ },
141
+ "context-menu": {
142
+ name: "context-menu",
143
+ description: "A menu that appears at the pointer on right click or long press.",
144
+ files: [{ name: "context-menu.tsx" }, { name: "index.ts" }],
145
+ dependencies: ["@base-ui/react", "lucide-react"],
191
146
  utilityDependencies: ["cn"]
192
147
  },
193
148
  dialog: {
194
149
  name: "dialog",
195
150
  description: "A popup that opens on top of the entire page, providing a modal interface for user interactions.",
196
- files: [
197
- { name: "dialog.tsx" },
198
- { name: "index.ts" }
199
- ],
200
- dependencies: [
201
- "@base-ui/react"
202
- ],
151
+ files: [{ name: "dialog.tsx" }, { name: "index.ts" }],
152
+ dependencies: ["@base-ui/react"],
203
153
  utilityDependencies: ["cn"]
204
154
  },
205
- toast: {
206
- name: "toast",
207
- description: "Generates toast notifications with support for different types, promises, actions, and global management.",
208
- files: [
209
- { name: "toast.tsx" },
210
- { name: "index.ts" }
211
- ],
212
- dependencies: [
213
- "@base-ui/react",
214
- "class-variance-authority"
215
- ],
155
+ drawer: {
156
+ name: "drawer",
157
+ description: "A panel that slides in from the edge of the screen for focused workflows.",
158
+ files: [{ name: "drawer.tsx" }, { name: "index.ts" }],
159
+ dependencies: ["@base-ui/react"],
216
160
  utilityDependencies: ["cn"]
217
161
  },
218
- select: {
219
- name: "select",
220
- description: "A common form component for choosing a predefined value in a dropdown menu.",
221
- files: [
222
- { name: "select.tsx" },
223
- { name: "index.ts" }
224
- ],
225
- dependencies: [
226
- "@base-ui/react",
227
- "lucide-react"
228
- ],
162
+ field: {
163
+ name: "field",
164
+ description: "A component for building accessible forms with custom styling and validation.",
165
+ files: [{ name: "field.tsx" }, { name: "index.ts" }],
166
+ dependencies: ["@base-ui/react"],
229
167
  utilityDependencies: ["cn"]
230
168
  },
231
- "context-menu": {
232
- name: "context-menu",
233
- description: "A menu that appears at the pointer on right click or long press.",
234
- files: [
235
- { name: "context-menu.tsx" },
236
- { name: "index.ts" }
237
- ],
238
- dependencies: [
239
- "@base-ui/react",
240
- "lucide-react"
241
- ],
169
+ fieldset: {
170
+ name: "fieldset",
171
+ description: "A grouping container for related form controls with an accessible legend.",
172
+ files: [{ name: "fieldset.tsx" }, { name: "index.ts" }],
173
+ dependencies: ["@base-ui/react"],
174
+ utilityDependencies: ["cn"]
175
+ },
176
+ form: {
177
+ name: "form",
178
+ description: "A native form element with consolidated error handling, built on Base UI foundation.",
179
+ files: [{ name: "form.tsx" }, { name: "index.ts" }],
180
+ dependencies: ["@base-ui/react"],
181
+ utilityDependencies: ["cn"]
182
+ },
183
+ input: {
184
+ name: "input",
185
+ description: "A customizable input field component with variants and sizes.",
186
+ files: [{ name: "input.tsx" }, { name: "index.ts" }],
187
+ dependencies: ["@base-ui/react"],
188
+ utilityDependencies: ["cn"]
189
+ },
190
+ menu: {
191
+ name: "menu",
192
+ description: "A popup menu for actions and options triggered by a button.",
193
+ files: [{ name: "menu.tsx" }, { name: "index.ts" }],
194
+ dependencies: ["@base-ui/react", "lucide-react"],
242
195
  utilityDependencies: ["cn"]
243
196
  },
244
197
  menubar: {
245
198
  name: "menubar",
246
199
  description: "A visually persistent menu common in desktop applications that provides access to a consistent set of commands.",
247
- files: [
248
- { name: "menubar.tsx" },
249
- { name: "index.ts" }
250
- ],
251
- dependencies: [
252
- "@base-ui/react",
253
- "lucide-react"
254
- ],
200
+ files: [{ name: "menubar.tsx" }, { name: "index.ts" }],
201
+ dependencies: ["@base-ui/react", "lucide-react"],
202
+ utilityDependencies: ["cn"]
203
+ },
204
+ meter: {
205
+ name: "meter",
206
+ description: "A visual representation of a scalar measurement within a known range.",
207
+ files: [{ name: "meter.tsx" }, { name: "index.ts" }],
208
+ dependencies: ["@base-ui/react"],
255
209
  utilityDependencies: ["cn"]
256
210
  },
257
211
  "navigation-menu": {
258
212
  name: "navigation-menu",
259
213
  description: "A collection of links and menus for website navigation.",
260
- files: [
261
- { name: "navigation-menu.tsx" },
262
- { name: "index.ts" }
263
- ],
264
- dependencies: [
265
- "@base-ui/react",
266
- "lucide-react"
267
- ],
214
+ files: [{ name: "navigation-menu.tsx" }, { name: "index.ts" }],
215
+ dependencies: ["@base-ui/react", "lucide-react"],
216
+ utilityDependencies: ["cn"]
217
+ },
218
+ "number-field": {
219
+ name: "number-field",
220
+ description: "A numeric input with increment, decrement, and optional scrubbing controls.",
221
+ files: [{ name: "number-field.tsx" }, { name: "index.ts" }],
222
+ dependencies: ["@base-ui/react", "lucide-react"],
223
+ utilityDependencies: ["cn"]
224
+ },
225
+ popover: {
226
+ name: "popover",
227
+ description: "An anchored floating panel for contextual information and lightweight interactions.",
228
+ files: [{ name: "popover.tsx" }, { name: "index.ts" }],
229
+ dependencies: ["@base-ui/react"],
268
230
  utilityDependencies: ["cn"]
269
231
  },
270
232
  "preview-card": {
271
233
  name: "preview-card",
272
234
  description: "A popup that appears when a link is hovered, showing a preview for sighted users.",
273
- files: [
274
- { name: "preview-card.tsx" },
275
- { name: "index.ts" }
276
- ],
277
- dependencies: [
278
- "@base-ui/react"
279
- ],
235
+ files: [{ name: "preview-card.tsx" }, { name: "index.ts" }],
236
+ dependencies: ["@base-ui/react"],
237
+ utilityDependencies: ["cn"]
238
+ },
239
+ progress: {
240
+ name: "progress",
241
+ description: "A progress bar that communicates task completion to users and assistive technology.",
242
+ files: [{ name: "progress.tsx" }, { name: "index.ts" }],
243
+ dependencies: ["@base-ui/react"],
244
+ utilityDependencies: ["cn"]
245
+ },
246
+ radio: {
247
+ name: "radio",
248
+ description: "A radio input and group for selecting a single option from a set.",
249
+ files: [{ name: "radio.tsx" }, { name: "index.ts" }],
250
+ dependencies: ["@base-ui/react", "lucide-react"],
251
+ utilityDependencies: ["cn"]
252
+ },
253
+ "scroll-area": {
254
+ name: "scroll-area",
255
+ description: "A custom scroll container with styled scrollbars and optional corner rendering.",
256
+ files: [{ name: "scroll-area.tsx" }, { name: "index.ts" }],
257
+ dependencies: ["@base-ui/react"],
258
+ utilityDependencies: ["cn"]
259
+ },
260
+ select: {
261
+ name: "select",
262
+ description: "A common form component for choosing a predefined value in a dropdown menu.",
263
+ files: [{ name: "select.tsx" }, { name: "index.ts" }],
264
+ dependencies: ["@base-ui/react", "lucide-react"],
265
+ utilityDependencies: ["cn"]
266
+ },
267
+ separator: {
268
+ name: "separator",
269
+ description: "A visual divider used to separate and organize content.",
270
+ files: [{ name: "separator.tsx" }, { name: "index.ts" }],
271
+ dependencies: ["@base-ui/react"],
272
+ utilityDependencies: ["cn"]
273
+ },
274
+ slider: {
275
+ name: "slider",
276
+ description: "An input where the user selects a value from within a given range.",
277
+ files: [{ name: "slider.tsx" }, { name: "index.ts" }],
278
+ dependencies: ["@base-ui/react"],
279
+ utilityDependencies: ["cn"]
280
+ },
281
+ switch: {
282
+ name: "switch",
283
+ description: "A control that allows users to toggle a setting on or off.",
284
+ files: [{ name: "switch.tsx" }, { name: "index.ts" }],
285
+ dependencies: ["@base-ui/react"],
286
+ utilityDependencies: ["cn"]
287
+ },
288
+ tabs: {
289
+ name: "tabs",
290
+ description: "A component for toggling between related panels on the same page.",
291
+ files: [{ name: "tabs.tsx" }, { name: "index.ts" }],
292
+ dependencies: ["@base-ui/react"],
293
+ utilityDependencies: ["cn"]
294
+ },
295
+ toast: {
296
+ name: "toast",
297
+ description: "Generates toast notifications with support for different types, promises, actions, and global management.",
298
+ files: [{ name: "toast.tsx" }, { name: "index.ts" }],
299
+ dependencies: ["@base-ui/react", "class-variance-authority"],
280
300
  utilityDependencies: ["cn"]
281
301
  },
282
302
  toggle: {
283
303
  name: "toggle",
284
304
  description: "A two-state button that can be on or off.",
285
- files: [
286
- { name: "toggle.tsx" },
287
- { name: "index.ts" }
288
- ],
289
- dependencies: [
290
- "@base-ui/react",
291
- "class-variance-authority"
292
- ],
293
- utilityDependencies: ["cn"]
294
- },
295
- "toolbar": {
296
- "name": "toolbar",
297
- "description": "A container for grouping a set of controls, such as buttons, toggle groups, or dropdown menus.",
298
- "files": [
299
- {
300
- "name": "toolbar.tsx"
301
- },
302
- {
303
- "name": "index.ts"
304
- }
305
- ],
306
- "dependencies": [
307
- "@base-ui/react"
308
- ],
309
- "utilityDependencies": [
310
- "cn"
311
- ]
312
- },
313
- "tooltip": {
314
- "name": "tooltip",
315
- "description": "A popup that displays information related to an element when the element receives keyboard focus or the mouse hovers over it.",
316
- "files": [
317
- {
318
- "name": "tooltip.tsx"
319
- },
320
- {
321
- "name": "index.ts"
322
- }
323
- ],
324
- "dependencies": [
325
- "@base-ui/react"
326
- ],
327
- "utilityDependencies": [
328
- "cn"
329
- ]
305
+ files: [{ name: "toggle.tsx" }, { name: "index.ts" }],
306
+ dependencies: ["@base-ui/react", "class-variance-authority"],
307
+ utilityDependencies: ["cn"]
308
+ },
309
+ "toggle-group": {
310
+ name: "toggle-group",
311
+ description: "A grouped set of toggles supporting single or multiple pressed states.",
312
+ files: [{ name: "toggle-group.tsx" }, { name: "index.ts" }],
313
+ dependencies: ["@base-ui/react", "class-variance-authority"],
314
+ utilityDependencies: ["cn"]
315
+ },
316
+ toolbar: {
317
+ name: "toolbar",
318
+ description: "A container for grouping a set of controls, such as buttons, toggle groups, or dropdown menus.",
319
+ files: [{ name: "toolbar.tsx" }, { name: "index.ts" }],
320
+ dependencies: ["@base-ui/react"],
321
+ utilityDependencies: ["cn"]
322
+ },
323
+ tooltip: {
324
+ name: "tooltip",
325
+ description: "A popup that displays information related to an element when the element receives keyboard focus or the mouse hovers over it.",
326
+ files: [{ name: "tooltip.tsx" }, { name: "index.ts" }],
327
+ dependencies: ["@base-ui/react"],
328
+ utilityDependencies: ["cn"]
330
329
  }
331
330
  };
332
331
  }
@@ -337,302 +336,676 @@ async function getConfig() {
337
336
  }
338
337
  try {
339
338
  const configContent = await fs.readFile(configPath, "utf-8");
340
- return JSON.parse(configContent);
341
- } catch (error) {
339
+ const parsed = JSON.parse(configContent);
340
+ return normalizeConfig(parsed);
341
+ } catch {
342
342
  return null;
343
343
  }
344
344
  }
345
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
+
346
487
  // src/commands/add.ts
347
488
  var __filename2 = fileURLToPath2(import.meta.url);
348
- var __dirname2 = path2.dirname(__filename2);
349
- function resolveAliasPath(aliasPath, projectRoot) {
350
- const cleanPath = aliasPath.replace(/^@\//, "");
351
- const possibleBasePaths = [
352
- path2.join(projectRoot, "src"),
353
- // src/ prefix (most common)
354
- path2.join(projectRoot, "app"),
355
- // app/ prefix (Next.js app router)
356
- projectRoot
357
- // Direct in project root
358
- ];
359
- for (const basePath of possibleBasePaths) {
360
- const fullPath = path2.join(basePath, cleanPath);
361
- if (fs2.existsSync(basePath)) {
362
- return fullPath;
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(/\.[^./\\]+$/, "");
495
+ }
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;
363
539
  }
364
540
  }
365
- return path2.join(projectRoot, "src", cleanPath);
541
+ return null;
366
542
  }
367
- function getComponentDependencies(componentName) {
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);
368
583
  const registry = getComponentRegistry();
369
584
  const component = registry[componentName];
370
585
  if (!component) return [];
371
- let dependencies = component.componentDependencies || [];
586
+ const dependencies = [];
372
587
  for (const dep of component.componentDependencies || []) {
373
- dependencies = [...dependencies, ...getComponentDependencies(dep)];
588
+ if (!registry[dep]) {
589
+ continue;
590
+ }
591
+ dependencies.push(dep, ...getComponentDependencies(dep, visited));
374
592
  }
375
593
  return [...new Set(dependencies)];
376
594
  }
377
- async function ensureTailwindConfig(deps) {
378
- const needsAnimatePlugin = deps.includes("tailwindcss-animate");
379
- if (!needsAnimatePlugin) return;
380
- const configPath = path2.join(process.cwd(), "tailwind.config.js");
381
- if (!fs2.existsSync(configPath)) {
382
- const configContent = `/** @type {import('tailwindcss').Config} */
383
- export default {
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;
612
+ }
613
+ }
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 = {
384
660
  content: [
385
661
  "./src/**/*.{js,ts,jsx,tsx}",
662
+ "./app/**/*.{js,ts,jsx,tsx}",
386
663
  "./components/**/*.{js,ts,jsx,tsx}",
387
664
  "./pages/**/*.{js,ts,jsx,tsx}",
388
665
  ],
389
666
  theme: {
390
667
  extend: {},
391
668
  },
392
- plugins: [
393
- 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}",
394
679
  ],
395
- };`;
396
- await fs2.writeFile(configPath, configContent);
397
- return { created: true };
398
- } else {
399
- const configContent = await fs2.readFile(configPath, "utf-8");
400
- if (!configContent.includes("tailwindcss-animate")) {
401
- let updatedContent = configContent;
402
- if (configContent.includes("plugins:")) {
403
- updatedContent = configContent.replace(
404
- /plugins:\s*\[([\s\S]*?)\]/,
405
- (match, pluginsContent) => {
406
- const cleanPlugins = pluginsContent.trim();
407
- const newPlugin = 'require("tailwindcss-animate")';
408
- if (cleanPlugins === "") {
409
- return `plugins: [
410
- ${newPlugin},
411
- ]`;
412
- } else {
413
- return `plugins: [
414
- ${pluginsContent},
415
- ${newPlugin},
416
- ]`;
417
- }
418
- }
419
- );
420
- } else {
421
- updatedContent = configContent.replace(
422
- /}\s*;?\s*$/,
423
- ' plugins: [\n require("tailwindcss-animate"),\n ],\n};'
424
- );
425
- }
426
- await fs2.writeFile(configPath, updatedContent);
427
- return { updated: true };
428
- }
680
+ theme: {
681
+ extend: {},
682
+ },
683
+ plugins: [tailwindcssAnimate],
684
+ }
685
+ `;
686
+ await fs3.ensureDir(path3.dirname(configPath));
687
+ await fs3.writeFile(configPath, configContent);
688
+ return { created: true, path: configPath };
689
+ }
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}`;
429
701
  }
430
- return { exists: true };
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, "\\$&");
431
711
  }
432
- async function handleIndexFile(sourcePath, targetPath, componentName, allFilesAdded, aliasPath) {
433
- let templateContent = await fs2.readFile(sourcePath, "utf-8");
434
- templateContent = templateContent.replace(/^\/\/ @ts-nocheck\s*\n/m, "");
435
- if (!fs2.existsSync(targetPath)) {
436
- await fs2.writeFile(targetPath, templateContent);
437
- allFilesAdded.push({ name: "index.ts", path: path2.join(aliasPath, "index.ts") });
438
- } else {
439
- const existingContent = await fs2.readFile(targetPath, "utf-8");
440
- const exportRegex = /export\s+\*\s+from\s+['"]\.\/([^'"]+)['"]/g;
441
- const templateExports = [];
442
- let match;
443
- while ((match = exportRegex.exec(templateContent)) !== null) {
444
- 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;
445
726
  }
446
- const componentExportExists = existingContent.includes(`export * from './${componentName}'`);
447
- if (!componentExportExists && templateExports.includes(componentName)) {
448
- const newExportLine = `export * from './${componentName}'`;
449
- const updatedContent = existingContent.trim() + "\n" + newExportLine + "\n";
450
- await fs2.writeFile(targetPath, updatedContent);
451
- allFilesAdded.push({ name: "index.ts", path: path2.join(aliasPath, "index.ts") });
727
+ const modulePath = match[1];
728
+ const modulePathPattern = new RegExp(`from\\s+['"]\\./${escapeRegex(modulePath)}['"]`);
729
+ if (modulePathPattern.test(existingContent)) {
730
+ continue;
452
731
  }
732
+ linesToAppend.push(line.endsWith(";") ? line : `${line};`);
453
733
  }
734
+ if (linesToAppend.length === 0) {
735
+ return;
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") });
454
742
  }
455
- 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) => {
456
- const spinner = ora("Adding component...").start();
457
- try {
458
- const config = await getConfig();
459
- if (!config) {
460
- spinner.fail("\u274C No components.json found. Run `dinachi init` first.");
461
- process.exit(1);
462
- }
463
- const registry = getComponentRegistry();
464
- let componentsToInstall = [];
465
- if (options.all) {
466
- const allComponents = Object.keys(registry);
467
- spinner.text = `Installing all ${allComponents.length} components...`;
468
- const allComponentsWithDeps = /* @__PURE__ */ new Set();
469
- for (const name of allComponents) {
470
- allComponentsWithDeps.add(name);
471
- const deps = getComponentDependencies(name);
472
- deps.forEach((dep) => allComponentsWithDeps.add(dep));
473
- }
474
- componentsToInstall = Array.from(allComponentsWithDeps);
475
- } else {
476
- if (!componentName) {
477
- spinner.fail("\u274C Component name is required when not using --all flag.");
478
- console.log("Available components:");
479
- Object.keys(registry).forEach((name) => {
480
- console.log(` ${chalk.cyan(name)}`);
481
- });
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.");
482
750
  process.exit(1);
483
751
  }
484
- const component = registry[componentName];
485
- if (!component) {
486
- spinner.fail(`\u274C Component "${componentName}" not found.`);
487
- console.log("Available components:");
488
- Object.keys(registry).forEach((name) => {
489
- console.log(` ${chalk.cyan(name)}`);
490
- });
491
- 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)];
492
785
  }
493
- componentsToInstall = [componentName, ...getComponentDependencies(componentName)];
494
- }
495
- if (!options.all) {
496
- spinner.text = `Installing ${componentsToInstall.join(", ")}...`;
497
- }
498
- const componentDir = resolveAliasPath(config.aliases.ui, process.cwd());
499
- await fs2.ensureDir(componentDir);
500
- let allFilesAdded = [];
501
- let allDepsInstalled = [];
502
- let allUtilityDeps = [];
503
- for (const name of componentsToInstall) {
504
- const comp = registry[name];
505
- if (!comp) continue;
506
- if (comp.utilityDependencies?.length) {
507
- allUtilityDeps.push(...comp.utilityDependencies);
786
+ if (!options.all) {
787
+ spinner.text = `Installing ${componentsToInstall.join(", ")}...`;
508
788
  }
509
- }
510
- const utilityRegistry = getUtilityRegistry();
511
- const uniqueUtilityDeps = [...new Set(allUtilityDeps)];
512
- const utilsDir = resolveAliasPath(config.aliases.lib, process.cwd());
513
- if (uniqueUtilityDeps.length > 0) {
514
- await fs2.ensureDir(utilsDir);
515
- for (const utilityName of uniqueUtilityDeps) {
516
- const utility = utilityRegistry[utilityName];
517
- if (!utility) continue;
518
- const utilityFilename = `${utility.name}.ts`;
519
- const sourcePath = path2.join(__dirname2, "../templates/utils", utilityFilename);
520
- const targetPath = path2.join(utilsDir, utilityFilename);
521
- if (!fs2.existsSync(targetPath)) {
522
- let content = await fs2.readFile(sourcePath, "utf-8");
523
- content = content.replace(/^\/\/ @ts-nocheck\s*\n/m, "");
524
- await fs2.writeFile(targetPath, content);
525
- allFilesAdded.push({
526
- name: utilityFilename,
527
- path: path2.join(config.aliases.lib, utilityFilename)
528
- });
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
+ }
529
821
  if (utility.dependencies?.length) {
530
822
  allDepsInstalled.push(...utility.dependencies);
531
823
  }
532
824
  }
533
825
  }
534
- }
535
- for (const name of componentsToInstall) {
536
- const comp = registry[name];
537
- if (!comp) continue;
538
- for (const file of comp.files) {
539
- const sourcePath = path2.join(__dirname2, "../templates", name, file.name);
540
- const targetPath = path2.join(componentDir, file.name);
541
- if (file.name === "index.ts") {
542
- await handleIndexFile(sourcePath, targetPath, name, allFilesAdded, config.aliases.ui);
543
- } else {
544
- 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) {
545
837
  spinner.warn(`\u26A0\uFE0F ${file.name} already exists. Use --overwrite to replace it.`);
546
838
  continue;
547
839
  }
548
- let content = await fs2.readFile(sourcePath, "utf-8");
549
- content = content.replace(/^\/\/ @ts-nocheck\s*\n/m, "");
550
- await fs2.writeFile(targetPath, content);
551
- allFilesAdded.push({ name: file.name, path: path2.join(config.aliases.ui, 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);
552
847
  }
553
848
  }
554
- if (comp.dependencies?.length) {
555
- allDepsInstalled.push(...comp.dependencies);
556
- }
557
- }
558
- let tailwindConfigInfo = null;
559
- if (allDepsInstalled.includes("tailwindcss-animate")) {
560
849
  spinner.text = "Updating Tailwind configuration...";
561
- tailwindConfigInfo = await ensureTailwindConfig(allDepsInstalled);
562
- }
563
- const packageJsonPath = path2.join(process.cwd(), "package.json");
564
- const packageJson = JSON.parse(await fs2.readFile(packageJsonPath, "utf-8"));
565
- const allDeps = { ...packageJson.dependencies, ...packageJson.devDependencies };
566
- const missingDeps = [...new Set(allDepsInstalled)].filter((dep) => !allDeps[dep]);
567
- if (allDepsInstalled.length > 0) {
568
- spinner.text = "Installing dependencies...";
569
- 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...";
570
865
  try {
571
- const packageManager = getPackageManager();
572
- const installCmd = `${packageManager} ${packageManager === "npm" ? "install" : "add"} ${missingDeps.join(" ")}`;
573
- execSync(installCmd, { stdio: "inherit" });
574
- } catch (error) {
866
+ const packageManager = detectPackageManager(projectRoot);
867
+ const installCmd = getInstallCommand(packageManager, missingDeps.map(toInstallSpec));
868
+ execSync(installCmd, { stdio: "inherit", cwd: projectRoot });
869
+ } catch {
575
870
  spinner.warn(`\u26A0\uFE0F Failed to install dependencies. Please install manually: ${missingDeps.join(" ")}`);
576
871
  }
577
- } else {
872
+ } else if (!options.skipInstall && uniqueDeps.length > 0) {
578
873
  spinner.text = "All dependencies already installed.";
579
874
  }
580
- }
581
- if (options.all) {
582
- spinner.succeed(`\u2705 Added all ${componentsToInstall.length} components!`);
583
- } else {
584
- spinner.succeed(`\u2705 Added ${componentsToInstall.join(", ")} component(s)!`);
585
- }
586
- console.log();
587
- console.log("Files added:");
588
- allFilesAdded.forEach((file) => {
589
- console.log(` ${chalk.green("+")} ${file.path}`);
590
- });
591
- if (tailwindConfigInfo) {
592
- console.log();
593
- if (tailwindConfigInfo.created) {
594
- console.log(` ${chalk.green("+")} tailwind.config.js (created with tailwindcss-animate plugin)`);
595
- } else if (tailwindConfigInfo.updated) {
596
- console.log(` ${chalk.blue("~")} tailwind.config.js (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(", ")}!`);
597
879
  }
598
- }
599
- if (missingDeps.length > 0) {
600
- console.log();
601
- console.log("Dependencies installed:");
602
- missingDeps.forEach((dep) => {
603
- console.log(` ${chalk.green("\u2713")} ${dep}`);
604
- });
605
- } else if (allDepsInstalled.length > 0) {
606
880
  console.log();
607
- console.log("Dependencies (already installed):");
608
- [...new Set(allDepsInstalled)].forEach((dep) => {
609
- console.log(` ${chalk.blue("~")} ${dep}`);
881
+ console.log("Files added:");
882
+ allFilesAdded.forEach((file) => {
883
+ console.log(` ${chalk.green("+")} ${file.path}`);
610
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);
611
919
  }
612
- } catch (error) {
613
- spinner.fail(`\u274C Failed to add component: ${error instanceof Error ? error.message : error}`);
614
- process.exit(1);
615
920
  }
616
- });
617
- function getPackageManager() {
618
- if (fs2.existsSync("pnpm-lock.yaml")) return "pnpm";
619
- if (fs2.existsSync("yarn.lock")) return "yarn";
620
- return "npm";
621
- }
921
+ );
622
922
 
623
923
  // src/commands/init.ts
624
924
  import { Command as Command2 } from "commander";
625
925
  import { execSync as execSync2 } from "child_process";
626
- import fs3 from "fs-extra";
627
- import path3 from "path";
926
+ import fs4 from "fs-extra";
927
+ import path4 from "path";
628
928
  import prompts from "prompts";
629
929
  import chalk2 from "chalk";
630
930
  import ora2 from "ora";
631
- 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) => {
632
1004
  console.log(chalk2.bold.cyan("\u{1F3A8} Welcome to Dinachi UI!"));
633
1005
  console.log();
634
- const packageJsonPath = path3.join(process.cwd(), "package.json");
635
- if (!fs3.existsSync(packageJsonPath)) {
1006
+ const projectRoot = process.cwd();
1007
+ const packageJsonPath = path4.join(projectRoot, "package.json");
1008
+ if (!fs4.existsSync(packageJsonPath)) {
636
1009
  console.log(chalk2.red("\u274C No package.json found. Please run this command in a valid project."));
637
1010
  process.exit(1);
638
1011
  }
@@ -651,12 +1024,6 @@ var initCommand = new Command2("init").description("Initialize Dinachi UI in you
651
1024
  name: "utilsPath",
652
1025
  message: "Where would you like to install utilities?",
653
1026
  initial: projectConfig.utilsPath
654
- },
655
- {
656
- type: "confirm",
657
- name: "installDeps",
658
- message: "Install required dependencies?",
659
- initial: true
660
1027
  }
661
1028
  ]);
662
1029
  if (!response.componentsPath || !response.utilsPath) {
@@ -665,65 +1032,91 @@ var initCommand = new Command2("init").description("Initialize Dinachi UI in you
665
1032
  }
666
1033
  const spinner = ora2("Setting up Dinachi UI...").start();
667
1034
  try {
668
- await fs3.ensureDir(path3.dirname(response.componentsPath));
669
- await fs3.ensureDir(response.utilsPath);
670
- const utilsContent = `import { type ClassValue, clsx } from "clsx"
671
- import { twMerge } from "tailwind-merge"
672
-
673
- export function cn(...inputs: ClassValue[]) {
674
- return twMerge(clsx(inputs))
675
- }
676
- `;
677
- await fs3.writeFile(path3.join(response.utilsPath, "utils.ts"), utilsContent);
678
- if (response.installDeps) {
679
- spinner.text = "Installing dependencies...";
680
- const deps = [
681
- "class-variance-authority",
682
- "clsx",
683
- "tailwind-merge"
684
- ];
685
- const packageManager = getPackageManager2();
686
- const installCmd = `${packageManager} ${packageManager === "npm" ? "install" : "add"} ${deps.join(" ")}`;
687
- execSync2(installCmd, { stdio: "inherit" });
688
- }
689
- const rscEnabled = projectConfig.framework === "next.js";
690
- const configContent = `{
691
- "style": "default",
692
- "rsc": ${rscEnabled},
693
- "tsx": true,
694
- "tailwind": {
695
- "config": "${projectConfig.tailwindConfig}",
696
- "css": "${projectConfig.cssPath}",
697
- "baseColor": "slate",
698
- "cssVariables": true
699
- },
700
- "aliases": {
701
- "components": "@/components",
702
- "utils": "@/lib/utils",
703
- "ui": "@/components/ui",
704
- "lib": "@/lib",
705
- "hooks": "@/hooks"
706
- },
707
- "iconLibrary": "lucide"
708
- }`;
709
- await fs3.writeFile("components.json", configContent);
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);
1044
+ spinner.text = "Installing dependencies...";
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"
1071
+ },
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);
710
1078
  spinner.succeed("\u2705 Dinachi UI setup complete!");
711
1079
  console.log();
712
1080
  console.log("Next steps:");
713
1081
  console.log(` 1. Add a component: ${chalk2.cyan("npx @dinachi/cli add button")}`);
714
- console.log(` 2. Components will be installed to: ${chalk2.cyan("@/components/ui")}`);
715
- console.log(` 3. Utils available at: ${chalk2.cyan("@/lib/utils")}`);
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
+ }
716
1102
  if (projectConfig.framework === "next.js") {
717
1103
  console.log();
718
1104
  console.log(chalk2.blue("\u{1F4DD} Next.js specific notes:"));
719
- console.log(` - RSC (React Server Components) enabled in config`);
720
- console.log(` - Make sure to add "use client" directive if needed`);
721
- console.log(` - Tailwind config set to: ${chalk2.cyan(projectConfig.tailwindConfig)}`);
1105
+ console.log(" - RSC (React Server Components) enabled in config");
1106
+ console.log(' - Add "use client" for interactive components where needed');
1107
+ console.log(` - Tailwind config: ${chalk2.cyan(projectConfig.tailwindConfig)}`);
722
1108
  } else if (projectConfig.framework === "remix") {
723
1109
  console.log();
724
1110
  console.log(chalk2.blue("\u{1F4DD} Remix specific notes:"));
725
- console.log(` - Components will be installed to: ${chalk2.cyan(projectConfig.componentsPath)}`);
726
- console.log(` - Utils will be installed to: ${chalk2.cyan(projectConfig.utilsPath)}`);
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
+ });
727
1120
  }
728
1121
  console.log();
729
1122
  console.log("\u{1F4A1} Tip: Install globally for shorter commands:");
@@ -734,43 +1127,61 @@ export function cn(...inputs: ClassValue[]) {
734
1127
  process.exit(1);
735
1128
  }
736
1129
  });
737
- function getPackageManager2() {
738
- if (fs3.existsSync("pnpm-lock.yaml")) return "pnpm";
739
- if (fs3.existsSync("yarn.lock")) return "yarn";
740
- return "npm";
741
- }
742
1130
  function detectProjectType() {
743
- const packageJsonPath = path3.join(process.cwd(), "package.json");
744
- if (!fs3.existsSync(packageJsonPath)) {
745
- return {
746
- framework: "react",
747
- componentsPath: "./src/components/ui",
748
- utilsPath: "./src/lib",
749
- tailwindConfig: "tailwind.config.js",
750
- cssPath: "src/index.css",
751
- srcDir: "src"
752
- };
1131
+ const packageJsonPath = path4.join(process.cwd(), "package.json");
1132
+ if (!fs4.existsSync(packageJsonPath)) {
1133
+ return getDefaultConfig("react", false);
753
1134
  }
754
- const packageJson = JSON.parse(fs3.readFileSync(packageJsonPath, "utf-8"));
755
- const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
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);
1141
+ const tailwindConfig = detectTailwindConfig();
1142
+ const cssPath = detectCssPath(hasSrcDir, hasSrcAppDir);
756
1143
  if (deps.next) {
1144
+ if (hasSrcDir && hasSrcAppDir) {
1145
+ return {
1146
+ framework: "next.js",
1147
+ componentsPath: "./src/components/ui",
1148
+ utilsPath: "./src/lib",
1149
+ tailwindConfig,
1150
+ cssPath,
1151
+ srcDir: "src",
1152
+ isTypeScript
1153
+ };
1154
+ }
1155
+ if (hasAppDir && !hasSrcDir) {
1156
+ return {
1157
+ framework: "next.js",
1158
+ componentsPath: "./components/ui",
1159
+ utilsPath: "./lib",
1160
+ tailwindConfig,
1161
+ cssPath,
1162
+ srcDir: ".",
1163
+ isTypeScript
1164
+ };
1165
+ }
757
1166
  return {
758
1167
  framework: "next.js",
759
- componentsPath: "./src/components/ui",
760
- utilsPath: "./src/lib",
761
- tailwindConfig: "tailwind.config.ts",
762
- cssPath: "src/app/globals.css",
763
- srcDir: "src"
1168
+ componentsPath: hasSrcDir ? "./src/components/ui" : "./components/ui",
1169
+ utilsPath: hasSrcDir ? "./src/lib" : "./lib",
1170
+ tailwindConfig,
1171
+ cssPath,
1172
+ srcDir: hasSrcDir ? "src" : ".",
1173
+ isTypeScript
764
1174
  };
765
1175
  }
766
1176
  if (deps.vite || deps["@vitejs/plugin-react"]) {
767
1177
  return {
768
1178
  framework: "vite",
769
- componentsPath: "./src/components/ui",
770
- utilsPath: "./src/lib",
771
- tailwindConfig: "tailwind.config.js",
772
- cssPath: "src/index.css",
773
- srcDir: "src"
1179
+ componentsPath: hasSrcDir ? "./src/components/ui" : "./components/ui",
1180
+ utilsPath: hasSrcDir ? "./src/lib" : "./lib",
1181
+ tailwindConfig,
1182
+ cssPath,
1183
+ srcDir: hasSrcDir ? "src" : ".",
1184
+ isTypeScript
774
1185
  };
775
1186
  }
776
1187
  if (deps["react-scripts"]) {
@@ -778,9 +1189,10 @@ function detectProjectType() {
778
1189
  framework: "create-react-app",
779
1190
  componentsPath: "./src/components/ui",
780
1191
  utilsPath: "./src/lib",
781
- tailwindConfig: "tailwind.config.js",
782
- cssPath: "src/index.css",
783
- srcDir: "src"
1192
+ tailwindConfig,
1193
+ cssPath,
1194
+ srcDir: "src",
1195
+ isTypeScript
784
1196
  };
785
1197
  }
786
1198
  if (deps["@remix-run/react"]) {
@@ -788,23 +1200,81 @@ function detectProjectType() {
788
1200
  framework: "remix",
789
1201
  componentsPath: "./app/components/ui",
790
1202
  utilsPath: "./app/lib",
791
- tailwindConfig: "tailwind.config.ts",
792
- cssPath: "app/tailwind.css",
793
- srcDir: "app"
1203
+ tailwindConfig,
1204
+ cssPath: detectCssPath(false, false, true),
1205
+ srcDir: "app",
1206
+ isTypeScript
794
1207
  };
795
1208
  }
1209
+ return getDefaultConfig("react", hasSrcDir);
1210
+ }
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"));
796
1213
  return {
797
- framework: "react",
798
- componentsPath: "./src/components/ui",
799
- utilsPath: "./src/lib",
800
- tailwindConfig: "tailwind.config.js",
801
- cssPath: "src/index.css",
802
- srcDir: "src"
1214
+ framework,
1215
+ componentsPath: hasSrcDir ? "./src/components/ui" : "./components/ui",
1216
+ utilsPath: hasSrcDir ? "./src/lib" : "./lib",
1217
+ tailwindConfig: detectTailwindConfig(),
1218
+ cssPath: hasSrcDir ? "src/index.css" : "index.css",
1219
+ srcDir: hasSrcDir ? "src" : ".",
1220
+ isTypeScript
803
1221
  };
804
1222
  }
1223
+ function detectTailwindConfig() {
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
+ }
1236
+ }
1237
+ return "tailwind.config.js";
1238
+ }
1239
+ function detectCssPath(hasSrcDir, hasSrcAppDir, isRemix = false) {
1240
+ const cwd = process.cwd();
1241
+ const possiblePaths = [
1242
+ "app/globals.css",
1243
+ "src/app/globals.css",
1244
+ "styles/globals.css",
1245
+ "src/styles/globals.css",
1246
+ "src/index.css",
1247
+ "index.css",
1248
+ "app/tailwind.css",
1249
+ "app/styles/tailwind.css"
1250
+ ];
1251
+ for (const cssPath of possiblePaths) {
1252
+ if (fs4.existsSync(path4.join(cwd, cssPath))) {
1253
+ return cssPath;
1254
+ }
1255
+ }
1256
+ if (isRemix) return "app/tailwind.css";
1257
+ if (hasSrcAppDir) return "src/app/globals.css";
1258
+ if (hasSrcDir) return "src/index.css";
1259
+ return "index.css";
1260
+ }
805
1261
 
806
1262
  // src/index.ts
1263
+ import fs5 from "fs";
1264
+ import path5 from "path";
1265
+ import { fileURLToPath as fileURLToPath3 } from "url";
807
1266
  var program = new Command3();
808
- program.name("dinachi").description("Add Dinachi UI components to your project").version("0.1.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());
809
1279
  program.addCommand(addCommand).addCommand(initCommand);
810
1280
  program.parse();