doo-boilerplate 0.1.16 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +43 -461
- package/package.json +1 -1
- package/templates/template-vite/_env.example +8 -0
- package/templates/template-vite/package.json +5 -2
- package/templates/template-vite/src/components/data-table/data-table-column-header.tsx +62 -0
- package/templates/template-vite/src/components/data-table/data-table-faceted-filter.tsx +129 -0
- package/templates/template-vite/src/components/data-table/data-table-pagination.tsx +80 -0
- package/templates/template-vite/src/components/data-table/data-table-toolbar.tsx +66 -0
- package/templates/template-vite/src/components/data-table/data-table-view-options.tsx +46 -0
- package/templates/template-vite/src/components/data-table/data-table.tsx +63 -0
- package/templates/template-vite/src/components/layout/sidebar.tsx +2 -1
- package/templates/template-vite/src/components/ui/alert-dialog.tsx +106 -0
- package/templates/template-vite/src/components/ui/command.tsx +118 -0
- package/templates/template-vite/src/components/ui/popover.tsx +28 -0
- package/templates/template-vite/src/components/ui/table.tsx +77 -0
- package/templates/template-vite/src/features/dashboard/components/overview-chart.tsx +61 -0
- package/templates/template-vite/src/features/dashboard/components/recent-activity.tsx +37 -0
- package/templates/template-vite/src/features/dashboard/components/stats-cards.tsx +37 -0
- package/templates/template-vite/src/features/users/components/user-delete-confirmation-dialog.tsx +48 -0
- package/templates/template-vite/src/features/users/components/user-form-dialog.tsx +143 -0
- package/templates/template-vite/src/features/users/components/users-table-columns.tsx +154 -0
- package/templates/template-vite/src/features/users/components/users-table.tsx +143 -0
- package/templates/template-vite/src/features/users/data/users-mock-data.ts +55 -0
- package/templates/template-vite/src/features/users/schemas/user-form-schema.ts +14 -0
- package/templates/template-vite/src/features/users/types/user.ts +12 -0
- package/templates/template-vite/src/lib/sentry.ts +28 -0
- package/templates/template-vite/src/main.tsx +3 -0
- package/templates/template-vite/src/routes/_authenticated/dashboard.tsx +12 -6
- package/templates/template-vite/src/routes/_authenticated/users.tsx +16 -0
- package/templates/template-vite/vite.config.ts +8 -0
- package/templates/template-vite/optional/charts/deps.json +0 -7
- package/templates/template-vite/optional/dark-mode/deps.json +0 -5
- package/templates/template-vite/optional/dnd/deps.json +0 -8
- package/templates/template-vite/optional/editor/deps.json +0 -10
- package/templates/template-vite/optional/i18n/deps.json +0 -7
- 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
|
|
6
|
-
import
|
|
7
|
-
import
|
|
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,10 @@ 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
29
|
const validPMs2 = ["pnpm", "bun", "yarn"];
|
|
32
30
|
const projectName2 = defaults.projectName ?? "my-portal";
|
|
33
|
-
const framework2 = defaults.framework === "nextjs" || defaults.framework === "vite" ? defaults.framework : "vite";
|
|
34
31
|
const packageManager2 = validPMs2.includes(defaults.pm) ? defaults.pm : "pnpm";
|
|
35
|
-
|
|
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 };
|
|
32
|
+
return { projectName: projectName2, packageManager: packageManager2 };
|
|
40
33
|
}
|
|
41
34
|
let projectName;
|
|
42
35
|
if (defaults.projectName) {
|
|
@@ -59,23 +52,6 @@ async function collectOptions(defaults, isTTY2 = true) {
|
|
|
59
52
|
}
|
|
60
53
|
projectName = answer;
|
|
61
54
|
}
|
|
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
55
|
const validPMs = ["pnpm", "bun", "yarn"];
|
|
80
56
|
let packageManager;
|
|
81
57
|
if (validPMs.includes(defaults.pm)) {
|
|
@@ -95,388 +71,17 @@ async function collectOptions(defaults, isTTY2 = true) {
|
|
|
95
71
|
}
|
|
96
72
|
packageManager = answer;
|
|
97
73
|
}
|
|
98
|
-
|
|
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 };
|
|
74
|
+
return { projectName, packageManager };
|
|
158
75
|
}
|
|
159
76
|
|
|
160
77
|
// src/scaffold.ts
|
|
161
|
-
import
|
|
162
|
-
import
|
|
78
|
+
import fse2 from "fs-extra";
|
|
79
|
+
import path2 from "path";
|
|
163
80
|
import { fileURLToPath } from "url";
|
|
164
81
|
|
|
165
|
-
// src/
|
|
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
|
|
82
|
+
// src/utils/template.ts
|
|
463
83
|
import fse from "fs-extra";
|
|
464
84
|
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
85
|
var TEXT_EXTENSIONS = [
|
|
481
86
|
".ts",
|
|
482
87
|
".tsx",
|
|
@@ -492,23 +97,23 @@ var TEXT_EXTENSIONS = [
|
|
|
492
97
|
".txt"
|
|
493
98
|
];
|
|
494
99
|
async function getAllTextFiles(dir) {
|
|
495
|
-
const entries = await
|
|
100
|
+
const entries = await fse.readdir(dir, { withFileTypes: true, recursive: true });
|
|
496
101
|
return entries.filter((e) => {
|
|
497
102
|
if (!e.isFile()) return false;
|
|
498
103
|
if (!TEXT_EXTENSIONS.some((ext) => e.name.endsWith(ext))) return false;
|
|
499
104
|
const parent = e.parentPath ?? e.path ?? dir;
|
|
500
|
-
const fullPath =
|
|
501
|
-
return !fullPath.includes("node_modules") && !fullPath.includes(`${
|
|
105
|
+
const fullPath = path.join(parent, e.name);
|
|
106
|
+
return !fullPath.includes("node_modules") && !fullPath.includes(`${path.sep}optional${path.sep}`);
|
|
502
107
|
}).map((e) => {
|
|
503
108
|
const parent = e.parentPath ?? e.path ?? dir;
|
|
504
|
-
return
|
|
109
|
+
return path.join(parent, e.name);
|
|
505
110
|
});
|
|
506
111
|
}
|
|
507
112
|
async function replaceInFiles(dir, replacements) {
|
|
508
113
|
const files = await getAllTextFiles(dir);
|
|
509
114
|
await Promise.all(
|
|
510
115
|
files.map(async (file) => {
|
|
511
|
-
let content = await
|
|
116
|
+
let content = await fse.readFile(file, "utf-8");
|
|
512
117
|
let changed = false;
|
|
513
118
|
for (const [from, to] of Object.entries(replacements)) {
|
|
514
119
|
if (content.includes(from)) {
|
|
@@ -517,14 +122,14 @@ async function replaceInFiles(dir, replacements) {
|
|
|
517
122
|
}
|
|
518
123
|
}
|
|
519
124
|
if (changed) {
|
|
520
|
-
await
|
|
125
|
+
await fse.writeFile(file, content, "utf-8");
|
|
521
126
|
}
|
|
522
127
|
})
|
|
523
128
|
);
|
|
524
129
|
}
|
|
525
130
|
|
|
526
131
|
// src/scaffold.ts
|
|
527
|
-
var __dirname2 =
|
|
132
|
+
var __dirname2 = path2.dirname(fileURLToPath(import.meta.url));
|
|
528
133
|
var FILE_RENAMES = [
|
|
529
134
|
["_gitignore", ".gitignore"],
|
|
530
135
|
["_env.example", ".env.example"],
|
|
@@ -532,43 +137,24 @@ var FILE_RENAMES = [
|
|
|
532
137
|
["_prettierignore", ".prettierignore"]
|
|
533
138
|
];
|
|
534
139
|
async function scaffold(options, destDir) {
|
|
535
|
-
const
|
|
536
|
-
const
|
|
537
|
-
|
|
538
|
-
await fse3.copy(templateDir, destDir, {
|
|
140
|
+
const templateSymlink = path2.join(__dirname2, "..", "templates", "template-vite");
|
|
141
|
+
const templateDir = await fse2.realpath(templateSymlink);
|
|
142
|
+
await fse2.copy(templateDir, destDir, {
|
|
539
143
|
overwrite: true,
|
|
540
|
-
filter: (src) => !src.includes(`${
|
|
144
|
+
filter: (src) => !src.includes(`${path2.sep}optional${path2.sep}`) && src !== path2.join(templateDir, "optional")
|
|
541
145
|
});
|
|
542
146
|
for (const [from, to] of FILE_RENAMES) {
|
|
543
|
-
const fromPath =
|
|
544
|
-
const toPath =
|
|
545
|
-
if (await
|
|
546
|
-
await
|
|
147
|
+
const fromPath = path2.join(destDir, from);
|
|
148
|
+
const toPath = path2.join(destDir, to);
|
|
149
|
+
if (await fse2.pathExists(fromPath)) {
|
|
150
|
+
await fse2.move(fromPath, toPath);
|
|
547
151
|
}
|
|
548
152
|
}
|
|
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
153
|
await replaceInFiles(destDir, {
|
|
555
154
|
"{{PROJECT_NAME}}": options.projectName,
|
|
556
155
|
"{{YEAR}}": (/* @__PURE__ */ new Date()).getFullYear().toString()
|
|
557
156
|
});
|
|
558
157
|
}
|
|
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
158
|
|
|
573
159
|
// src/git.ts
|
|
574
160
|
import { execa } from "execa";
|
|
@@ -582,20 +168,20 @@ async function initGit(destDir) {
|
|
|
582
168
|
|
|
583
169
|
// src/post-setup.ts
|
|
584
170
|
import * as p2 from "@clack/prompts";
|
|
585
|
-
import
|
|
586
|
-
import
|
|
171
|
+
import pc from "picocolors";
|
|
172
|
+
import path3 from "path";
|
|
587
173
|
function printSuccess(options, destDir, isTTY2 = true) {
|
|
588
|
-
const relPath =
|
|
174
|
+
const relPath = path3.relative(process.cwd(), destDir);
|
|
589
175
|
const cdTarget = relPath || options.projectName;
|
|
590
176
|
const pm = options.packageManager;
|
|
591
|
-
const runCmd = `${pm}
|
|
592
|
-
const msg = `${
|
|
177
|
+
const runCmd = `${pm} dev`;
|
|
178
|
+
const msg = `${pc.green("\u2713")} Project created! Get started:
|
|
593
179
|
|
|
594
|
-
${
|
|
595
|
-
${
|
|
596
|
-
${
|
|
180
|
+
${pc.cyan(`cd ${cdTarget}`)}
|
|
181
|
+
${pc.cyan(`${pm} install`)}
|
|
182
|
+
${pc.cyan(runCmd)}
|
|
597
183
|
|
|
598
|
-
${
|
|
184
|
+
${pc.dim("Copy .env.example \u2192 .env and fill in your API URLs.")}`;
|
|
599
185
|
if (isTTY2) {
|
|
600
186
|
p2.outro(msg);
|
|
601
187
|
} else {
|
|
@@ -609,35 +195,31 @@ function log(msg) {
|
|
|
609
195
|
console.log(msg);
|
|
610
196
|
}
|
|
611
197
|
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("--
|
|
198
|
+
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("--pm <pm>", "Package manager: pnpm, bun, or yarn").option("--no-git", "Skip git initialization").parse(process.argv);
|
|
613
199
|
log("");
|
|
614
200
|
if (isTTY) {
|
|
615
|
-
p3.intro(
|
|
201
|
+
p3.intro(pc2.bgCyan(pc2.black(" create-pila-app ")));
|
|
616
202
|
} else {
|
|
617
|
-
log(
|
|
203
|
+
log(pc2.bgCyan(pc2.black(" create-pila-app ")));
|
|
618
204
|
}
|
|
619
205
|
const args = program.args;
|
|
620
206
|
const opts = program.opts();
|
|
621
207
|
const options = await collectOptions({
|
|
622
208
|
projectName: args[0],
|
|
623
|
-
|
|
624
|
-
pm: opts.pm,
|
|
625
|
-
features: opts.features?.split(","),
|
|
626
|
-
auth: opts.auth,
|
|
627
|
-
theme: opts.theme
|
|
209
|
+
pm: opts.pm
|
|
628
210
|
}, isTTY);
|
|
629
|
-
const destDir =
|
|
630
|
-
if (await
|
|
631
|
-
const files = await
|
|
211
|
+
const destDir = path4.resolve(process.cwd(), options.projectName);
|
|
212
|
+
if (await fse3.pathExists(destDir)) {
|
|
213
|
+
const files = await fse3.readdir(destDir);
|
|
632
214
|
if (files.length > 0) {
|
|
633
215
|
const msg = `Directory "${options.projectName}" already exists and is not empty.`;
|
|
634
216
|
if (isTTY) p3.cancel(msg);
|
|
635
|
-
else log(
|
|
217
|
+
else log(pc2.red("\u2716 " + msg));
|
|
636
218
|
process.exit(1);
|
|
637
219
|
}
|
|
638
220
|
}
|
|
639
|
-
await
|
|
640
|
-
log(
|
|
221
|
+
await fse3.ensureDir(destDir);
|
|
222
|
+
log(pc2.cyan("\u2192 Scaffolding project..."));
|
|
641
223
|
if (isTTY) {
|
|
642
224
|
const scaffoldSpinner = p3.spinner();
|
|
643
225
|
scaffoldSpinner.start("Scaffolding project...");
|
|
@@ -650,7 +232,7 @@ async function run() {
|
|
|
650
232
|
}
|
|
651
233
|
} else {
|
|
652
234
|
await scaffold(options, destDir);
|
|
653
|
-
log(
|
|
235
|
+
log(pc2.green("\u2713 Project scaffolded"));
|
|
654
236
|
}
|
|
655
237
|
if (opts.git !== false) {
|
|
656
238
|
try {
|
package/package.json
CHANGED
|
@@ -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=
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
"docker:scan": "bash scripts/trivy-scan.sh"
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
+
"@sentry/react": "^9.0.0",
|
|
22
23
|
"react": "^19.2.0",
|
|
23
24
|
"react-dom": "^19.2.0",
|
|
24
25
|
"i18next": "^25.2.1",
|
|
@@ -56,7 +57,8 @@
|
|
|
56
57
|
"@radix-ui/react-switch": "^1.1.3",
|
|
57
58
|
"@radix-ui/react-tabs": "^1.1.3",
|
|
58
59
|
"@radix-ui/react-tooltip": "^1.1.8",
|
|
59
|
-
"cmdk": "^1.1.1"
|
|
60
|
+
"cmdk": "^1.1.1",
|
|
61
|
+
"recharts": "^2.15.0"
|
|
60
62
|
},
|
|
61
63
|
"devDependencies": {
|
|
62
64
|
"@types/react": "^19",
|
|
@@ -83,7 +85,8 @@
|
|
|
83
85
|
"husky": "^9.1.7",
|
|
84
86
|
"lint-staged": "^15.2.11",
|
|
85
87
|
"knip": "^5.64.2",
|
|
86
|
-
"swagger-typescript-api": "^13.2.7"
|
|
88
|
+
"swagger-typescript-api": "^13.2.7",
|
|
89
|
+
"@sentry/vite-plugin": "^3.0.0"
|
|
87
90
|
},
|
|
88
91
|
"lint-staged": {
|
|
89
92
|
"*.{js,ts,tsx,css}": ["eslint --fix", "prettier --write"]
|