doo-boilerplate 0.1.16 → 0.2.2

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 (36) hide show
  1. package/dist/index.js +43 -486
  2. package/package.json +1 -1
  3. package/templates/template-vite/_env.example +8 -0
  4. package/templates/template-vite/package.json +5 -2
  5. package/templates/template-vite/src/components/data-table/data-table-column-header.tsx +62 -0
  6. package/templates/template-vite/src/components/data-table/data-table-faceted-filter.tsx +129 -0
  7. package/templates/template-vite/src/components/data-table/data-table-pagination.tsx +80 -0
  8. package/templates/template-vite/src/components/data-table/data-table-toolbar.tsx +66 -0
  9. package/templates/template-vite/src/components/data-table/data-table-view-options.tsx +46 -0
  10. package/templates/template-vite/src/components/data-table/data-table.tsx +63 -0
  11. package/templates/template-vite/src/components/layout/sidebar.tsx +2 -1
  12. package/templates/template-vite/src/components/ui/alert-dialog.tsx +106 -0
  13. package/templates/template-vite/src/components/ui/command.tsx +118 -0
  14. package/templates/template-vite/src/components/ui/popover.tsx +28 -0
  15. package/templates/template-vite/src/components/ui/table.tsx +77 -0
  16. package/templates/template-vite/src/features/dashboard/components/overview-chart.tsx +61 -0
  17. package/templates/template-vite/src/features/dashboard/components/recent-activity.tsx +37 -0
  18. package/templates/template-vite/src/features/dashboard/components/stats-cards.tsx +37 -0
  19. package/templates/template-vite/src/features/users/components/user-delete-confirmation-dialog.tsx +48 -0
  20. package/templates/template-vite/src/features/users/components/user-form-dialog.tsx +143 -0
  21. package/templates/template-vite/src/features/users/components/users-table-columns.tsx +154 -0
  22. package/templates/template-vite/src/features/users/components/users-table.tsx +143 -0
  23. package/templates/template-vite/src/features/users/data/users-mock-data.ts +55 -0
  24. package/templates/template-vite/src/features/users/schemas/user-form-schema.ts +14 -0
  25. package/templates/template-vite/src/features/users/types/user.ts +12 -0
  26. package/templates/template-vite/src/lib/sentry.ts +28 -0
  27. package/templates/template-vite/src/main.tsx +3 -0
  28. package/templates/template-vite/src/routes/_authenticated/dashboard.tsx +12 -6
  29. package/templates/template-vite/src/routes/_authenticated/users.tsx +16 -0
  30. package/templates/template-vite/vite.config.ts +8 -0
  31. package/templates/template-vite/optional/charts/deps.json +0 -7
  32. package/templates/template-vite/optional/dark-mode/deps.json +0 -5
  33. package/templates/template-vite/optional/dnd/deps.json +0 -8
  34. package/templates/template-vite/optional/editor/deps.json +0 -10
  35. package/templates/template-vite/optional/i18n/deps.json +0 -7
  36. package/templates/template-vite/optional/sentry/deps.json +0 -6
package/dist/index.js CHANGED
@@ -2,14 +2,13 @@
2
2
 
3
3
  // src/cli.ts
4
4
  import * as p3 from "@clack/prompts";
5
- import pc3 from "picocolors";
6
- import path5 from "path";
7
- import fse4 from "fs-extra";
5
+ import pc2 from "picocolors";
6
+ import path4 from "path";
7
+ import fse3 from "fs-extra";
8
8
  import { Command } from "commander";
9
9
 
10
10
  // src/prompts.ts
11
11
  import * as p from "@clack/prompts";
12
- import pc from "picocolors";
13
12
 
14
13
  // src/utils/validate.ts
15
14
  import validatePkgName from "validate-npm-package-name";
@@ -27,16 +26,7 @@ function validateProjectName(name) {
27
26
  // src/prompts.ts
28
27
  async function collectOptions(defaults, isTTY2 = true) {
29
28
  if (!isTTY2) {
30
- const valid = ["editor", "charts", "dnd", "sentry"];
31
- const validPMs2 = ["pnpm", "bun", "yarn"];
32
- const projectName2 = defaults.projectName ?? "my-portal";
33
- const framework2 = defaults.framework === "nextjs" || defaults.framework === "vite" ? defaults.framework : "vite";
34
- const packageManager2 = validPMs2.includes(defaults.pm) ? defaults.pm : "pnpm";
35
- const features2 = (defaults.features ?? []).filter((f) => valid.includes(f));
36
- const auth2 = defaults.auth === "jwt" || defaults.auth === "oauth" || defaults.auth === "none" ? defaults.auth : "jwt";
37
- const validThemes2 = ["violet", "blue", "emerald", "rose"];
38
- const theme2 = validThemes2.includes(defaults.theme) ? defaults.theme : "violet";
39
- return { projectName: projectName2, framework: framework2, packageManager: packageManager2, features: features2, auth: auth2, theme: theme2 };
29
+ return { projectName: defaults.projectName ?? "my-portal", packageManager: "pnpm" };
40
30
  }
41
31
  let projectName;
42
32
  if (defaults.projectName) {
@@ -59,424 +49,17 @@ async function collectOptions(defaults, isTTY2 = true) {
59
49
  }
60
50
  projectName = answer;
61
51
  }
62
- let framework;
63
- if (defaults.framework === "nextjs" || defaults.framework === "vite") {
64
- framework = defaults.framework;
65
- } else {
66
- const answer = await p.select({
67
- message: "Framework",
68
- options: [
69
- { value: "vite", label: "Vite 8", hint: "SPA \xB7 faster builds \xB7 TanStack Router" },
70
- { value: "nextjs", label: "Next.js 16", hint: "SSR \xB7 App Router \xB7 i18n-ready" }
71
- ]
72
- });
73
- if (p.isCancel(answer)) {
74
- p.cancel("Operation cancelled.");
75
- process.exit(0);
76
- }
77
- framework = answer;
78
- }
79
- const validPMs = ["pnpm", "bun", "yarn"];
80
- let packageManager;
81
- if (validPMs.includes(defaults.pm)) {
82
- packageManager = defaults.pm;
83
- } else {
84
- const answer = await p.select({
85
- message: "Package manager",
86
- options: [
87
- { value: "pnpm", label: "pnpm", hint: "recommended \xB7 efficient disk usage" },
88
- { value: "bun", label: "bun", hint: "fastest \xB7 all-in-one runtime" },
89
- { value: "yarn", label: "yarn", hint: "reliable \xB7 stable" }
90
- ]
91
- });
92
- if (p.isCancel(answer)) {
93
- p.cancel("Operation cancelled.");
94
- process.exit(0);
95
- }
96
- packageManager = answer;
97
- }
98
- let features;
99
- if (defaults.features && defaults.features.length > 0) {
100
- const valid = ["editor", "charts", "dnd", "sentry"];
101
- features = defaults.features.filter((f) => valid.includes(f));
102
- } else {
103
- const answer = await p.multiselect({
104
- message: "Features " + pc.dim("(space to toggle, enter to confirm)"),
105
- options: [
106
- { value: "editor", label: "Rich text editor", hint: "Tiptap" },
107
- { value: "charts", label: "Charts", hint: "ECharts" },
108
- { value: "dnd", label: "Drag & drop", hint: "@dnd-kit" },
109
- { value: "sentry", label: "Error tracking", hint: "Sentry" }
110
- ],
111
- required: false
112
- });
113
- if (p.isCancel(answer)) {
114
- p.cancel("Operation cancelled.");
115
- process.exit(0);
116
- }
117
- features = answer;
118
- }
119
- const validThemes = ["violet", "blue", "emerald", "rose"];
120
- let theme;
121
- if (validThemes.includes(defaults.theme)) {
122
- theme = defaults.theme;
123
- } else {
124
- const answer = await p.select({
125
- message: "Color theme",
126
- options: [
127
- { value: "violet", label: "Violet", hint: "hsl(262 83% 58%) \xB7 modern & distinctive" },
128
- { value: "blue", label: "Blue", hint: "hsl(217 91% 60%) \xB7 trustworthy & familiar" },
129
- { value: "emerald", label: "Emerald", hint: "hsl(158 64% 42%) \xB7 fresh & natural" },
130
- { value: "rose", label: "Rose", hint: "hsl(346 77% 49%) \xB7 bold & energetic" }
131
- ]
132
- });
133
- if (p.isCancel(answer)) {
134
- p.cancel("Operation cancelled.");
135
- process.exit(0);
136
- }
137
- theme = answer;
138
- }
139
- let auth;
140
- if (defaults.auth === "jwt" || defaults.auth === "oauth" || defaults.auth === "none") {
141
- auth = defaults.auth;
142
- } else {
143
- const answer = await p.select({
144
- message: "Auth pattern",
145
- options: [
146
- { value: "jwt", label: "JWT tokens", hint: "access + refresh token flow" },
147
- { value: "oauth", label: "OAuth", hint: "placeholder, configure later" },
148
- { value: "none", label: "None", hint: "skip auth setup" }
149
- ]
150
- });
151
- if (p.isCancel(answer)) {
152
- p.cancel("Operation cancelled.");
153
- process.exit(0);
154
- }
155
- auth = answer;
156
- }
157
- return { projectName, framework, packageManager, features, auth, theme };
52
+ return { projectName, packageManager: "pnpm" };
158
53
  }
159
54
 
160
55
  // src/scaffold.ts
161
- import fse3 from "fs-extra";
162
- import path3 from "path";
56
+ import fse2 from "fs-extra";
57
+ import path2 from "path";
163
58
  import { fileURLToPath } from "url";
164
59
 
165
- // src/themes.ts
166
- function buildGlobalsCss(lightTokens, darkTokens, hasTailwindLayer) {
167
- const baseStyles = hasTailwindLayer ? `
168
- @layer base {
169
- * {
170
- @apply border-border;
171
- }
172
- body {
173
- @apply bg-background text-foreground;
174
- }
175
- }` : `
176
- * {
177
- border-color: var(--color-border);
178
- }
179
-
180
- body {
181
- background-color: var(--color-background);
182
- color: var(--color-foreground);
183
- font-family: var(--font-sans);
184
- }`;
185
- return `@import "tailwindcss";
186
-
187
- @theme {
188
- /* Typography */
189
- --font-sans: 'Inter', system-ui, sans-serif;
190
- --font-mono: 'JetBrains Mono', ui-monospace, monospace;
191
-
192
- /* Radii \u2014 sharp minimal */
193
- --radius-sm: 0.25rem;
194
- --radius-md: 0.375rem;
195
- --radius-lg: 0.5rem;
196
- --radius-xl: 0.75rem;
197
-
198
- ${lightTokens}
199
- }
200
-
201
- .dark {
202
- ${darkTokens}
203
- }
204
- ${baseStyles}
205
-
206
- /* View transition: circular reveal for theme toggle */
207
- ::view-transition-old(root),
208
- ::view-transition-new(root) {
209
- animation: none;
210
- mix-blend-mode: normal;
211
- }
212
- .dark::view-transition-old(root) {
213
- z-index: 1;
214
- }
215
- .dark::view-transition-new(root) {
216
- z-index: 999;
217
- }
218
- ::view-transition-new(root) {
219
- clip-path: circle(0% at var(--x, 50%) var(--y, 50%));
220
- animation: theme-reveal 0.45s ease-in-out;
221
- }
222
- @keyframes theme-reveal {
223
- from { clip-path: circle(0% at var(--x, 50%) var(--y, 50%)); }
224
- to { clip-path: circle(150% at var(--x, 50%) var(--y, 50%)); }
225
- }
226
- `;
227
- }
228
- var VIOLET_LIGHT = ` /* Violet-tinted neutrals \u2014 cohesive with violet primary */
229
- --color-background: hsl(0 0% 100%);
230
- --color-foreground: hsl(262 30% 10%);
231
- --color-card: hsl(0 0% 100%);
232
- --color-card-foreground: hsl(262 30% 10%);
233
- --color-popover: hsl(0 0% 100%);
234
- --color-popover-foreground: hsl(262 30% 10%);
235
- --color-primary: hsl(262 83% 58%);
236
- --color-primary-foreground: hsl(0 0% 100%);
237
- --color-secondary: hsl(262 30% 96%);
238
- --color-secondary-foreground: hsl(262 47% 12%);
239
- --color-muted: hsl(262 30% 96%);
240
- --color-muted-foreground: hsl(262 15% 47%);
241
- --color-accent: hsl(262 30% 96%);
242
- --color-accent-foreground: hsl(262 47% 12%);
243
- --color-destructive: hsl(0 72% 51%);
244
- --color-destructive-foreground: hsl(0 0% 98%);
245
- --color-border: hsl(262 25% 90%);
246
- --color-input: hsl(262 25% 90%);
247
- --color-ring: hsl(262 83% 58%);
248
- --color-sidebar: hsl(262 30% 98%);
249
- --color-sidebar-foreground: hsl(262 20% 26%);
250
- --color-sidebar-primary: hsl(262 83% 58%);
251
- --color-sidebar-primary-foreground: hsl(0 0% 100%);
252
- --color-sidebar-accent: hsl(262 30% 96%);
253
- --color-sidebar-accent-foreground: hsl(262 47% 12%);
254
- --color-sidebar-border: hsl(262 25% 90%);
255
- --color-sidebar-ring: hsl(262 83% 58%);`;
256
- var VIOLET_DARK = ` --color-background: hsl(262 20% 5%);
257
- --color-foreground: hsl(0 0% 98%);
258
- --color-card: hsl(262 20% 5%);
259
- --color-card-foreground: hsl(0 0% 98%);
260
- --color-popover: hsl(262 20% 7%);
261
- --color-popover-foreground: hsl(0 0% 98%);
262
- --color-primary: hsl(262 70% 67%);
263
- --color-primary-foreground: hsl(0 0% 100%);
264
- --color-secondary: hsl(262 20% 15%);
265
- --color-secondary-foreground: hsl(0 0% 98%);
266
- --color-muted: hsl(262 20% 15%);
267
- --color-muted-foreground: hsl(262 12% 65%);
268
- --color-accent: hsl(262 20% 15%);
269
- --color-accent-foreground: hsl(0 0% 98%);
270
- --color-destructive: hsl(0 63% 31%);
271
- --color-destructive-foreground: hsl(0 0% 98%);
272
- --color-border: hsl(262 20% 15%);
273
- --color-input: hsl(262 20% 15%);
274
- --color-ring: hsl(262 70% 67%);
275
- --color-sidebar: hsl(262 25% 8%);
276
- --color-sidebar-foreground: hsl(262 10% 80%);
277
- --color-sidebar-primary: hsl(262 70% 67%);
278
- --color-sidebar-primary-foreground: hsl(0 0% 100%);
279
- --color-sidebar-accent: hsl(262 20% 15%);
280
- --color-sidebar-accent-foreground: hsl(0 0% 98%);
281
- --color-sidebar-border: hsl(262 20% 15%);
282
- --color-sidebar-ring: hsl(262 70% 67%);`;
283
- var BLUE_LIGHT = ` /* Blue-tinted neutrals \u2014 cohesive with blue-500 primary */
284
- --color-background: hsl(0 0% 100%);
285
- --color-foreground: hsl(217 30% 10%);
286
- --color-card: hsl(0 0% 100%);
287
- --color-card-foreground: hsl(217 30% 10%);
288
- --color-popover: hsl(0 0% 100%);
289
- --color-popover-foreground: hsl(217 30% 10%);
290
- --color-primary: hsl(217 91% 60%);
291
- --color-primary-foreground: hsl(0 0% 100%);
292
- --color-secondary: hsl(217 40% 96%);
293
- --color-secondary-foreground: hsl(217 47% 12%);
294
- --color-muted: hsl(217 40% 96%);
295
- --color-muted-foreground: hsl(217 15% 47%);
296
- --color-accent: hsl(217 40% 96%);
297
- --color-accent-foreground: hsl(217 47% 12%);
298
- --color-destructive: hsl(0 72% 51%);
299
- --color-destructive-foreground: hsl(0 0% 98%);
300
- --color-border: hsl(217 30% 90%);
301
- --color-input: hsl(217 30% 90%);
302
- --color-ring: hsl(217 91% 60%);
303
- --color-sidebar: hsl(217 40% 98%);
304
- --color-sidebar-foreground: hsl(217 20% 26%);
305
- --color-sidebar-primary: hsl(217 91% 60%);
306
- --color-sidebar-primary-foreground: hsl(0 0% 100%);
307
- --color-sidebar-accent: hsl(217 40% 96%);
308
- --color-sidebar-accent-foreground: hsl(217 47% 12%);
309
- --color-sidebar-border: hsl(217 30% 90%);
310
- --color-sidebar-ring: hsl(217 91% 60%);`;
311
- var BLUE_DARK = ` --color-background: hsl(217 25% 5%);
312
- --color-foreground: hsl(0 0% 98%);
313
- --color-card: hsl(217 25% 5%);
314
- --color-card-foreground: hsl(0 0% 98%);
315
- --color-popover: hsl(217 25% 7%);
316
- --color-popover-foreground: hsl(0 0% 98%);
317
- --color-primary: hsl(217 91% 65%);
318
- --color-primary-foreground: hsl(0 0% 100%);
319
- --color-secondary: hsl(217 20% 15%);
320
- --color-secondary-foreground: hsl(0 0% 98%);
321
- --color-muted: hsl(217 20% 15%);
322
- --color-muted-foreground: hsl(217 12% 65%);
323
- --color-accent: hsl(217 20% 15%);
324
- --color-accent-foreground: hsl(0 0% 98%);
325
- --color-destructive: hsl(0 63% 31%);
326
- --color-destructive-foreground: hsl(0 0% 98%);
327
- --color-border: hsl(217 20% 15%);
328
- --color-input: hsl(217 20% 15%);
329
- --color-ring: hsl(217 91% 65%);
330
- --color-sidebar: hsl(217 25% 8%);
331
- --color-sidebar-foreground: hsl(217 10% 80%);
332
- --color-sidebar-primary: hsl(217 91% 65%);
333
- --color-sidebar-primary-foreground: hsl(0 0% 100%);
334
- --color-sidebar-accent: hsl(217 20% 15%);
335
- --color-sidebar-accent-foreground: hsl(0 0% 98%);
336
- --color-sidebar-border: hsl(217 20% 15%);
337
- --color-sidebar-ring: hsl(217 91% 65%);`;
338
- var EMERALD_LIGHT = ` /* Emerald-tinted neutrals \u2014 cohesive with emerald-500 primary */
339
- --color-background: hsl(0 0% 100%);
340
- --color-foreground: hsl(158 30% 8%);
341
- --color-card: hsl(0 0% 100%);
342
- --color-card-foreground: hsl(158 30% 8%);
343
- --color-popover: hsl(0 0% 100%);
344
- --color-popover-foreground: hsl(158 30% 8%);
345
- --color-primary: hsl(158 64% 42%);
346
- --color-primary-foreground: hsl(0 0% 100%);
347
- --color-secondary: hsl(158 30% 95%);
348
- --color-secondary-foreground: hsl(158 47% 10%);
349
- --color-muted: hsl(158 30% 95%);
350
- --color-muted-foreground: hsl(158 12% 46%);
351
- --color-accent: hsl(158 30% 95%);
352
- --color-accent-foreground: hsl(158 47% 10%);
353
- --color-destructive: hsl(0 72% 51%);
354
- --color-destructive-foreground: hsl(0 0% 98%);
355
- --color-border: hsl(158 25% 89%);
356
- --color-input: hsl(158 25% 89%);
357
- --color-ring: hsl(158 64% 42%);
358
- --color-sidebar: hsl(158 25% 98%);
359
- --color-sidebar-foreground: hsl(158 18% 25%);
360
- --color-sidebar-primary: hsl(158 64% 42%);
361
- --color-sidebar-primary-foreground: hsl(0 0% 100%);
362
- --color-sidebar-accent: hsl(158 30% 95%);
363
- --color-sidebar-accent-foreground: hsl(158 47% 10%);
364
- --color-sidebar-border: hsl(158 25% 89%);
365
- --color-sidebar-ring: hsl(158 64% 42%);`;
366
- var EMERALD_DARK = ` --color-background: hsl(158 20% 5%);
367
- --color-foreground: hsl(0 0% 98%);
368
- --color-card: hsl(158 20% 5%);
369
- --color-card-foreground: hsl(0 0% 98%);
370
- --color-popover: hsl(158 20% 7%);
371
- --color-popover-foreground: hsl(0 0% 98%);
372
- --color-primary: hsl(158 60% 52%);
373
- --color-primary-foreground: hsl(0 0% 100%);
374
- --color-secondary: hsl(158 18% 14%);
375
- --color-secondary-foreground: hsl(0 0% 98%);
376
- --color-muted: hsl(158 18% 14%);
377
- --color-muted-foreground: hsl(158 10% 64%);
378
- --color-accent: hsl(158 18% 14%);
379
- --color-accent-foreground: hsl(0 0% 98%);
380
- --color-destructive: hsl(0 63% 31%);
381
- --color-destructive-foreground: hsl(0 0% 98%);
382
- --color-border: hsl(158 18% 14%);
383
- --color-input: hsl(158 18% 14%);
384
- --color-ring: hsl(158 60% 52%);
385
- --color-sidebar: hsl(158 22% 8%);
386
- --color-sidebar-foreground: hsl(158 8% 80%);
387
- --color-sidebar-primary: hsl(158 60% 52%);
388
- --color-sidebar-primary-foreground: hsl(0 0% 100%);
389
- --color-sidebar-accent: hsl(158 18% 14%);
390
- --color-sidebar-accent-foreground: hsl(0 0% 98%);
391
- --color-sidebar-border: hsl(158 18% 14%);
392
- --color-sidebar-ring: hsl(158 60% 52%);`;
393
- var ROSE_LIGHT = ` /* Rose-tinted neutrals \u2014 cohesive with rose-600 primary */
394
- --color-background: hsl(0 0% 100%);
395
- --color-foreground: hsl(346 30% 10%);
396
- --color-card: hsl(0 0% 100%);
397
- --color-card-foreground: hsl(346 30% 10%);
398
- --color-popover: hsl(0 0% 100%);
399
- --color-popover-foreground: hsl(346 30% 10%);
400
- --color-primary: hsl(346 77% 49%);
401
- --color-primary-foreground: hsl(0 0% 100%);
402
- --color-secondary: hsl(346 30% 96%);
403
- --color-secondary-foreground: hsl(346 47% 12%);
404
- --color-muted: hsl(346 30% 96%);
405
- --color-muted-foreground: hsl(346 14% 47%);
406
- --color-accent: hsl(346 30% 96%);
407
- --color-accent-foreground: hsl(346 47% 12%);
408
- --color-destructive: hsl(0 72% 51%);
409
- --color-destructive-foreground: hsl(0 0% 98%);
410
- --color-border: hsl(346 25% 90%);
411
- --color-input: hsl(346 25% 90%);
412
- --color-ring: hsl(346 77% 49%);
413
- --color-sidebar: hsl(346 30% 98%);
414
- --color-sidebar-foreground: hsl(346 20% 26%);
415
- --color-sidebar-primary: hsl(346 77% 49%);
416
- --color-sidebar-primary-foreground: hsl(0 0% 100%);
417
- --color-sidebar-accent: hsl(346 30% 96%);
418
- --color-sidebar-accent-foreground: hsl(346 47% 12%);
419
- --color-sidebar-border: hsl(346 25% 90%);
420
- --color-sidebar-ring: hsl(346 77% 49%);`;
421
- var ROSE_DARK = ` --color-background: hsl(346 20% 5%);
422
- --color-foreground: hsl(0 0% 98%);
423
- --color-card: hsl(346 20% 5%);
424
- --color-card-foreground: hsl(0 0% 98%);
425
- --color-popover: hsl(346 20% 7%);
426
- --color-popover-foreground: hsl(0 0% 98%);
427
- --color-primary: hsl(346 75% 60%);
428
- --color-primary-foreground: hsl(0 0% 100%);
429
- --color-secondary: hsl(346 18% 15%);
430
- --color-secondary-foreground: hsl(0 0% 98%);
431
- --color-muted: hsl(346 18% 15%);
432
- --color-muted-foreground: hsl(346 10% 65%);
433
- --color-accent: hsl(346 18% 15%);
434
- --color-accent-foreground: hsl(0 0% 98%);
435
- --color-destructive: hsl(0 63% 31%);
436
- --color-destructive-foreground: hsl(0 0% 98%);
437
- --color-border: hsl(346 18% 15%);
438
- --color-input: hsl(346 18% 15%);
439
- --color-ring: hsl(346 75% 60%);
440
- --color-sidebar: hsl(346 22% 8%);
441
- --color-sidebar-foreground: hsl(346 8% 80%);
442
- --color-sidebar-primary: hsl(346 75% 60%);
443
- --color-sidebar-primary-foreground: hsl(0 0% 100%);
444
- --color-sidebar-accent: hsl(346 18% 15%);
445
- --color-sidebar-accent-foreground: hsl(0 0% 98%);
446
- --color-sidebar-border: hsl(346 18% 15%);
447
- --color-sidebar-ring: hsl(346 75% 60%);`;
448
- function getThemeCss(theme, framework) {
449
- const hasTailwindLayer = framework === "vite";
450
- switch (theme) {
451
- case "violet":
452
- return buildGlobalsCss(VIOLET_LIGHT, VIOLET_DARK, hasTailwindLayer);
453
- case "blue":
454
- return buildGlobalsCss(BLUE_LIGHT, BLUE_DARK, hasTailwindLayer);
455
- case "emerald":
456
- return buildGlobalsCss(EMERALD_LIGHT, EMERALD_DARK, hasTailwindLayer);
457
- case "rose":
458
- return buildGlobalsCss(ROSE_LIGHT, ROSE_DARK, hasTailwindLayer);
459
- }
460
- }
461
-
462
- // src/utils/package-json.ts
60
+ // src/utils/template.ts
463
61
  import fse from "fs-extra";
464
62
  import path from "path";
465
- async function mergeDepsIntoPackageJson(destDir, deps) {
466
- const pkgPath = path.join(destDir, "package.json");
467
- const pkg = await fse.readJson(pkgPath);
468
- if (deps.dependencies) {
469
- pkg.dependencies = { ...pkg.dependencies, ...deps.dependencies };
470
- }
471
- if (deps.devDependencies) {
472
- pkg.devDependencies = { ...pkg.devDependencies, ...deps.devDependencies };
473
- }
474
- await fse.writeJson(pkgPath, pkg, { spaces: 2 });
475
- }
476
-
477
- // src/utils/template.ts
478
- import fse2 from "fs-extra";
479
- import path2 from "path";
480
63
  var TEXT_EXTENSIONS = [
481
64
  ".ts",
482
65
  ".tsx",
@@ -492,23 +75,23 @@ var TEXT_EXTENSIONS = [
492
75
  ".txt"
493
76
  ];
494
77
  async function getAllTextFiles(dir) {
495
- const entries = await fse2.readdir(dir, { withFileTypes: true, recursive: true });
78
+ const entries = await fse.readdir(dir, { withFileTypes: true, recursive: true });
496
79
  return entries.filter((e) => {
497
80
  if (!e.isFile()) return false;
498
81
  if (!TEXT_EXTENSIONS.some((ext) => e.name.endsWith(ext))) return false;
499
82
  const parent = e.parentPath ?? e.path ?? dir;
500
- const fullPath = path2.join(parent, e.name);
501
- return !fullPath.includes("node_modules") && !fullPath.includes(`${path2.sep}optional${path2.sep}`);
83
+ const fullPath = path.join(parent, e.name);
84
+ return !fullPath.includes("node_modules") && !fullPath.includes(`${path.sep}optional${path.sep}`);
502
85
  }).map((e) => {
503
86
  const parent = e.parentPath ?? e.path ?? dir;
504
- return path2.join(parent, e.name);
87
+ return path.join(parent, e.name);
505
88
  });
506
89
  }
507
90
  async function replaceInFiles(dir, replacements) {
508
91
  const files = await getAllTextFiles(dir);
509
92
  await Promise.all(
510
93
  files.map(async (file) => {
511
- let content = await fse2.readFile(file, "utf-8");
94
+ let content = await fse.readFile(file, "utf-8");
512
95
  let changed = false;
513
96
  for (const [from, to] of Object.entries(replacements)) {
514
97
  if (content.includes(from)) {
@@ -517,14 +100,14 @@ async function replaceInFiles(dir, replacements) {
517
100
  }
518
101
  }
519
102
  if (changed) {
520
- await fse2.writeFile(file, content, "utf-8");
103
+ await fse.writeFile(file, content, "utf-8");
521
104
  }
522
105
  })
523
106
  );
524
107
  }
525
108
 
526
109
  // src/scaffold.ts
527
- var __dirname2 = path3.dirname(fileURLToPath(import.meta.url));
110
+ var __dirname2 = path2.dirname(fileURLToPath(import.meta.url));
528
111
  var FILE_RENAMES = [
529
112
  ["_gitignore", ".gitignore"],
530
113
  ["_env.example", ".env.example"],
@@ -532,43 +115,24 @@ var FILE_RENAMES = [
532
115
  ["_prettierignore", ".prettierignore"]
533
116
  ];
534
117
  async function scaffold(options, destDir) {
535
- const templateName = options.framework === "nextjs" ? "template-nextjs" : "template-vite";
536
- const templateSymlink = path3.join(__dirname2, "..", "templates", templateName);
537
- const templateDir = await fse3.realpath(templateSymlink);
538
- await fse3.copy(templateDir, destDir, {
118
+ const templateSymlink = path2.join(__dirname2, "..", "templates", "template-vite");
119
+ const templateDir = await fse2.realpath(templateSymlink);
120
+ await fse2.copy(templateDir, destDir, {
539
121
  overwrite: true,
540
- filter: (src) => !src.includes(`${path3.sep}optional${path3.sep}`) && src !== path3.join(templateDir, "optional")
122
+ filter: (src) => !src.includes(`${path2.sep}optional${path2.sep}`) && src !== path2.join(templateDir, "optional")
541
123
  });
542
124
  for (const [from, to] of FILE_RENAMES) {
543
- const fromPath = path3.join(destDir, from);
544
- const toPath = path3.join(destDir, to);
545
- if (await fse3.pathExists(fromPath)) {
546
- await fse3.move(fromPath, toPath);
125
+ const fromPath = path2.join(destDir, from);
126
+ const toPath = path2.join(destDir, to);
127
+ if (await fse2.pathExists(fromPath)) {
128
+ await fse2.move(fromPath, toPath);
547
129
  }
548
130
  }
549
- for (const feature of options.features) {
550
- await applyFeature(feature, templateDir, destDir);
551
- }
552
- const globalsCssPath = path3.join(destDir, "src", "styles", "globals.css");
553
- await fse3.writeFile(globalsCssPath, getThemeCss(options.theme, options.framework), "utf-8");
554
131
  await replaceInFiles(destDir, {
555
132
  "{{PROJECT_NAME}}": options.projectName,
556
133
  "{{YEAR}}": (/* @__PURE__ */ new Date()).getFullYear().toString()
557
134
  });
558
135
  }
559
- async function applyFeature(feature, templateDir, destDir) {
560
- const featureDir = path3.join(templateDir, "optional", feature);
561
- if (!await fse3.pathExists(featureDir)) return;
562
- const filesDir = path3.join(featureDir, "files");
563
- if (await fse3.pathExists(filesDir)) {
564
- await fse3.copy(filesDir, path3.join(destDir, "src"), { overwrite: true });
565
- }
566
- const depsFile = path3.join(featureDir, "deps.json");
567
- if (await fse3.pathExists(depsFile)) {
568
- const deps = await fse3.readJson(depsFile);
569
- await mergeDepsIntoPackageJson(destDir, deps);
570
- }
571
- }
572
136
 
573
137
  // src/git.ts
574
138
  import { execa } from "execa";
@@ -582,20 +146,20 @@ async function initGit(destDir) {
582
146
 
583
147
  // src/post-setup.ts
584
148
  import * as p2 from "@clack/prompts";
585
- import pc2 from "picocolors";
586
- import path4 from "path";
149
+ import pc from "picocolors";
150
+ import path3 from "path";
587
151
  function printSuccess(options, destDir, isTTY2 = true) {
588
- const relPath = path4.relative(process.cwd(), destDir);
152
+ const relPath = path3.relative(process.cwd(), destDir);
589
153
  const cdTarget = relPath || options.projectName;
590
154
  const pm = options.packageManager;
591
- const runCmd = `${pm} ${pm === "npm" ? "run dev" : "dev"}`;
592
- const msg = `${pc2.green("\u2713")} Project created! Get started:
155
+ const runCmd = `${pm} dev`;
156
+ const msg = `${pc.green("\u2713")} Project created! Get started:
593
157
 
594
- ${pc2.cyan(`cd ${cdTarget}`)}
595
- ${pc2.cyan(`${pm} install`)}
596
- ${pc2.cyan(runCmd)}
158
+ ${pc.cyan(`cd ${cdTarget}`)}
159
+ ${pc.cyan(`${pm} install`)}
160
+ ${pc.cyan(runCmd)}
597
161
 
598
- ${pc2.dim("Copy .env.example \u2192 .env and fill in your API URLs.")}`;
162
+ ${pc.dim("Copy .env.example \u2192 .env and fill in your API URLs.")}`;
599
163
  if (isTTY2) {
600
164
  p2.outro(msg);
601
165
  } else {
@@ -609,35 +173,28 @@ function log(msg) {
609
173
  console.log(msg);
610
174
  }
611
175
  async function run() {
612
- const program = new Command().name("create-pila-app").description("Scaffold a Pila portal frontend project").version("0.1.0").argument("[project-name]", "Name of the project").option("--framework <framework>", "nextjs or vite").option("--pm <pm>", "Package manager: pnpm, bun, or yarn").option("--features <features>", "Comma-separated: editor,charts,dnd,sentry").option("--auth <auth>", "jwt, oauth, or none").option("--theme <theme>", "Color theme: violet, blue, emerald, or rose").option("--no-git", "Skip git initialization").parse(process.argv);
176
+ const program = new Command().name("create-pila-app").description("Scaffold a Pila portal frontend project").version("0.1.0").argument("[project-name]", "Name of the project").option("--no-git", "Skip git initialization").parse(process.argv);
613
177
  log("");
614
178
  if (isTTY) {
615
- p3.intro(pc3.bgCyan(pc3.black(" create-pila-app ")));
179
+ p3.intro(pc2.bgCyan(pc2.black(" create-pila-app ")));
616
180
  } else {
617
- log(pc3.bgCyan(pc3.black(" create-pila-app ")));
181
+ log(pc2.bgCyan(pc2.black(" create-pila-app ")));
618
182
  }
619
183
  const args = program.args;
620
184
  const opts = program.opts();
621
- const options = await collectOptions({
622
- projectName: args[0],
623
- framework: opts.framework,
624
- pm: opts.pm,
625
- features: opts.features?.split(","),
626
- auth: opts.auth,
627
- theme: opts.theme
628
- }, isTTY);
629
- const destDir = path5.resolve(process.cwd(), options.projectName);
630
- if (await fse4.pathExists(destDir)) {
631
- const files = await fse4.readdir(destDir);
185
+ const options = await collectOptions({ projectName: args[0] }, isTTY);
186
+ const destDir = path4.resolve(process.cwd(), options.projectName);
187
+ if (await fse3.pathExists(destDir)) {
188
+ const files = await fse3.readdir(destDir);
632
189
  if (files.length > 0) {
633
190
  const msg = `Directory "${options.projectName}" already exists and is not empty.`;
634
191
  if (isTTY) p3.cancel(msg);
635
- else log(pc3.red("\u2716 " + msg));
192
+ else log(pc2.red("\u2716 " + msg));
636
193
  process.exit(1);
637
194
  }
638
195
  }
639
- await fse4.ensureDir(destDir);
640
- log(pc3.cyan("\u2192 Scaffolding project..."));
196
+ await fse3.ensureDir(destDir);
197
+ log(pc2.cyan("\u2192 Scaffolding project..."));
641
198
  if (isTTY) {
642
199
  const scaffoldSpinner = p3.spinner();
643
200
  scaffoldSpinner.start("Scaffolding project...");
@@ -650,7 +207,7 @@ async function run() {
650
207
  }
651
208
  } else {
652
209
  await scaffold(options, destDir);
653
- log(pc3.green("\u2713 Project scaffolded"));
210
+ log(pc2.green("\u2713 Project scaffolded"));
654
211
  }
655
212
  if (opts.git !== false) {
656
213
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "doo-boilerplate",
3
- "version": "0.1.16",
3
+ "version": "0.2.2",
4
4
  "description": "CLI to scaffold Pila portal frontend projects",
5
5
  "type": "module",
6
6
  "bin": {
@@ -6,3 +6,11 @@ VITE_API_AUTH_URL=http://localhost:8001
6
6
  VITE_APP_URL=http://localhost:3000
7
7
  VITE_APP_NAME={{PROJECT_NAME}}
8
8
  VITE_MODE=development
9
+
10
+ # Sentry Error Tracking (optional)
11
+ VITE_SENTRY_DSN=
12
+
13
+ # Sentry Source Maps Upload (build-time only, optional)
14
+ SENTRY_AUTH_TOKEN=
15
+ SENTRY_ORG=
16
+ SENTRY_PROJECT=