beaver-build 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +237 -0
- package/dist/index.js +1920 -0
- package/package.json +56 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1920 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __esm = (fn, res) => function __init() {
|
|
5
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
6
|
+
};
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
// src/constants/index.ts
|
|
13
|
+
var MENU_OPTIONS_LEVEL_1;
|
|
14
|
+
var init_constants = __esm({
|
|
15
|
+
"src/constants/index.ts"() {
|
|
16
|
+
"use strict";
|
|
17
|
+
MENU_OPTIONS_LEVEL_1 = {
|
|
18
|
+
ReactVite: {
|
|
19
|
+
display: "React + Vite",
|
|
20
|
+
value: "REACT_VITE",
|
|
21
|
+
description: "React 19 + Vite",
|
|
22
|
+
disabled: false
|
|
23
|
+
},
|
|
24
|
+
NextJS: {
|
|
25
|
+
display: "Next.js",
|
|
26
|
+
value: "NEXTJS",
|
|
27
|
+
description: "Next 15",
|
|
28
|
+
disabled: true
|
|
29
|
+
},
|
|
30
|
+
ChromeExtension: {
|
|
31
|
+
display: "Chrome Extension",
|
|
32
|
+
value: "CHROME_EXTENSION",
|
|
33
|
+
description: "React 19 + Vite (Manifest v3)",
|
|
34
|
+
disabled: false
|
|
35
|
+
},
|
|
36
|
+
Nuxt: {
|
|
37
|
+
display: "Nuxt",
|
|
38
|
+
value: "NUXT",
|
|
39
|
+
description: "Upcoming...",
|
|
40
|
+
disabled: true
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// src/options/react-vite/constants/index.ts
|
|
47
|
+
var REACT_MENU_LAYOUT, REACT_MENU_ROUTER, REACT_MENU_STATE_MANAGEMENT, REACT_MENU_QUERY, REACT_MENU_CSS, REACT_MENU_LINTER;
|
|
48
|
+
var init_constants2 = __esm({
|
|
49
|
+
"src/options/react-vite/constants/index.ts"() {
|
|
50
|
+
"use strict";
|
|
51
|
+
REACT_MENU_LAYOUT = {
|
|
52
|
+
FSD: {
|
|
53
|
+
display: "FSD",
|
|
54
|
+
value: "FSD",
|
|
55
|
+
description: "Feature slice design",
|
|
56
|
+
disabled: false
|
|
57
|
+
},
|
|
58
|
+
BPR: {
|
|
59
|
+
display: "BPR",
|
|
60
|
+
value: "BPR",
|
|
61
|
+
description: "Bulletproof React",
|
|
62
|
+
disabled: false
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
REACT_MENU_ROUTER = {
|
|
66
|
+
notUsing: {
|
|
67
|
+
display: "Not Using",
|
|
68
|
+
value: "NOT_USING",
|
|
69
|
+
description: "Not using any router",
|
|
70
|
+
disabled: false
|
|
71
|
+
},
|
|
72
|
+
tanstack: {
|
|
73
|
+
display: "TanStack Router",
|
|
74
|
+
value: "TANSTACK_ROUTER",
|
|
75
|
+
description: "TanStack Router v1.144.0",
|
|
76
|
+
disabled: false
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
REACT_MENU_STATE_MANAGEMENT = {
|
|
80
|
+
notUsing: {
|
|
81
|
+
display: "Not Using",
|
|
82
|
+
value: "NOT_USING",
|
|
83
|
+
description: "Not using any state management",
|
|
84
|
+
disabled: false
|
|
85
|
+
},
|
|
86
|
+
zustand: {
|
|
87
|
+
display: "Zustand",
|
|
88
|
+
value: "ZUSTAND",
|
|
89
|
+
description: "Zustand state management",
|
|
90
|
+
disabled: false
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
REACT_MENU_QUERY = {
|
|
94
|
+
notUsing: {
|
|
95
|
+
display: "Not Using",
|
|
96
|
+
value: "NOT_USING",
|
|
97
|
+
description: "No data fetching library",
|
|
98
|
+
disabled: false
|
|
99
|
+
},
|
|
100
|
+
tanstackQuery: {
|
|
101
|
+
display: "TanStack Query",
|
|
102
|
+
value: "TANSTACK_QUERY",
|
|
103
|
+
description: "Server state management (v5.74.4)",
|
|
104
|
+
disabled: false
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
REACT_MENU_CSS = {
|
|
108
|
+
notUsing: {
|
|
109
|
+
display: "Not Using",
|
|
110
|
+
value: "NOT_USING",
|
|
111
|
+
description: "No CSS framework",
|
|
112
|
+
disabled: false
|
|
113
|
+
},
|
|
114
|
+
tailwind: {
|
|
115
|
+
display: "Tailwind CSS",
|
|
116
|
+
value: "TAILWIND",
|
|
117
|
+
description: "Tailwind CSS v4.1.3 (Vite plugin, zero-config)",
|
|
118
|
+
disabled: false
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
REACT_MENU_LINTER = {
|
|
122
|
+
notUsing: {
|
|
123
|
+
display: "Not Using",
|
|
124
|
+
value: "NOT_USING",
|
|
125
|
+
description: "No linter/formatter",
|
|
126
|
+
disabled: false
|
|
127
|
+
},
|
|
128
|
+
biome: {
|
|
129
|
+
display: "Biome",
|
|
130
|
+
value: "BIOME",
|
|
131
|
+
description: "Fast all-in-one: lint + format (v1.9.4)",
|
|
132
|
+
disabled: false
|
|
133
|
+
},
|
|
134
|
+
eslint: {
|
|
135
|
+
display: "ESLint",
|
|
136
|
+
value: "ESLINT",
|
|
137
|
+
description: "ESLint v9 (flat config) + typescript-eslint",
|
|
138
|
+
disabled: false
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
// src/scaffold/utils.ts
|
|
145
|
+
import fs from "fs/promises";
|
|
146
|
+
import path from "path";
|
|
147
|
+
var dirExists, writeProjectFile;
|
|
148
|
+
var init_utils = __esm({
|
|
149
|
+
"src/scaffold/utils.ts"() {
|
|
150
|
+
"use strict";
|
|
151
|
+
dirExists = async (dirPath) => {
|
|
152
|
+
try {
|
|
153
|
+
const stat = await fs.stat(dirPath);
|
|
154
|
+
return stat.isDirectory();
|
|
155
|
+
} catch {
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
writeProjectFile = async (projectRoot, relativePath, content) => {
|
|
160
|
+
const fullPath = path.join(projectRoot, relativePath);
|
|
161
|
+
await fs.mkdir(path.dirname(fullPath), { recursive: true });
|
|
162
|
+
await fs.writeFile(fullPath, content, "utf-8");
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
// src/scaffold/errors.ts
|
|
168
|
+
var ScaffoldError, isNodeError;
|
|
169
|
+
var init_errors = __esm({
|
|
170
|
+
"src/scaffold/errors.ts"() {
|
|
171
|
+
"use strict";
|
|
172
|
+
ScaffoldError = class extends Error {
|
|
173
|
+
constructor(message, cause) {
|
|
174
|
+
super(message);
|
|
175
|
+
this.cause = cause;
|
|
176
|
+
this.name = "ScaffoldError";
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
isNodeError = (e) => e instanceof Error && "code" in e;
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
// src/scaffold/react-vite/templates/package-json.ts
|
|
184
|
+
var packageJsonTemplate;
|
|
185
|
+
var init_package_json = __esm({
|
|
186
|
+
"src/scaffold/react-vite/templates/package-json.ts"() {
|
|
187
|
+
"use strict";
|
|
188
|
+
packageJsonTemplate = (cart) => {
|
|
189
|
+
const deps = {
|
|
190
|
+
react: "19.1.0",
|
|
191
|
+
"react-dom": "19.1.0"
|
|
192
|
+
};
|
|
193
|
+
if (cart.router === "TANSTACK_ROUTER") {
|
|
194
|
+
deps["@tanstack/react-router"] = "1.144.0";
|
|
195
|
+
}
|
|
196
|
+
if (cart.stateManagement === "ZUSTAND") {
|
|
197
|
+
deps["zustand"] = "5.0.5";
|
|
198
|
+
}
|
|
199
|
+
if (cart.query === "TANSTACK_QUERY") {
|
|
200
|
+
deps["@tanstack/react-query"] = "5.74.4";
|
|
201
|
+
deps["@tanstack/react-query-devtools"] = "5.74.4";
|
|
202
|
+
}
|
|
203
|
+
const devDeps = {
|
|
204
|
+
"@types/react": "19.1.1",
|
|
205
|
+
"@types/react-dom": "19.1.1",
|
|
206
|
+
"@vitejs/plugin-react": "4.4.1",
|
|
207
|
+
typescript: "5.8.3",
|
|
208
|
+
vite: "6.3.1"
|
|
209
|
+
};
|
|
210
|
+
if (cart.router === "TANSTACK_ROUTER") {
|
|
211
|
+
devDeps["@tanstack/router-devtools"] = "1.144.0";
|
|
212
|
+
devDeps["@tanstack/router-vite-plugin"] = "1.144.0";
|
|
213
|
+
}
|
|
214
|
+
if (cart.css === "TAILWIND") {
|
|
215
|
+
devDeps["@tailwindcss/vite"] = "4.1.3";
|
|
216
|
+
devDeps["tailwindcss"] = "4.1.3";
|
|
217
|
+
}
|
|
218
|
+
if (cart.linter === "BIOME") {
|
|
219
|
+
devDeps["@biomejs/biome"] = "1.9.4";
|
|
220
|
+
} else if (cart.linter === "ESLINT") {
|
|
221
|
+
devDeps["@eslint/js"] = "9.22.0";
|
|
222
|
+
devDeps["eslint"] = "9.22.0";
|
|
223
|
+
devDeps["eslint-plugin-react-hooks"] = "5.2.0";
|
|
224
|
+
devDeps["eslint-plugin-react-refresh"] = "0.4.19";
|
|
225
|
+
devDeps["globals"] = "15.15.0";
|
|
226
|
+
devDeps["typescript-eslint"] = "8.26.0";
|
|
227
|
+
}
|
|
228
|
+
const scripts = {
|
|
229
|
+
dev: "vite",
|
|
230
|
+
build: "tsc && vite build",
|
|
231
|
+
preview: "vite preview"
|
|
232
|
+
};
|
|
233
|
+
if (cart.linter === "BIOME") {
|
|
234
|
+
scripts["lint"] = "biome check .";
|
|
235
|
+
scripts["format"] = "biome format --write .";
|
|
236
|
+
} else if (cart.linter === "ESLINT") {
|
|
237
|
+
scripts["lint"] = "eslint .";
|
|
238
|
+
}
|
|
239
|
+
return JSON.stringify(
|
|
240
|
+
{
|
|
241
|
+
name: cart.projectName,
|
|
242
|
+
private: true,
|
|
243
|
+
version: "0.0.0",
|
|
244
|
+
type: "module",
|
|
245
|
+
scripts,
|
|
246
|
+
dependencies: deps,
|
|
247
|
+
devDependencies: devDeps
|
|
248
|
+
},
|
|
249
|
+
null,
|
|
250
|
+
2
|
|
251
|
+
);
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
// src/scaffold/react-vite/templates/vite-config.ts
|
|
257
|
+
var viteConfigTemplate;
|
|
258
|
+
var init_vite_config = __esm({
|
|
259
|
+
"src/scaffold/react-vite/templates/vite-config.ts"() {
|
|
260
|
+
"use strict";
|
|
261
|
+
viteConfigTemplate = (cart) => {
|
|
262
|
+
const hasTanstack = cart.router === "TANSTACK_ROUTER";
|
|
263
|
+
const hasTailwind = cart.css === "TAILWIND";
|
|
264
|
+
const tailwindImport = hasTailwind ? `import tailwindcss from '@tailwindcss/vite';
|
|
265
|
+
` : "";
|
|
266
|
+
const tanstackImport = hasTanstack ? `import { TanStackRouterVite } from '@tanstack/router-vite-plugin';
|
|
267
|
+
` : "";
|
|
268
|
+
const tailwindPlugin = hasTailwind ? `
|
|
269
|
+
tailwindcss(),` : "";
|
|
270
|
+
const tanstackPlugin = hasTanstack ? `
|
|
271
|
+
TanStackRouterVite({ routesDirectory: './src/routes' }),` : "";
|
|
272
|
+
return `import { defineConfig } from 'vite';
|
|
273
|
+
import react from '@vitejs/plugin-react';
|
|
274
|
+
${tailwindImport}${tanstackImport}
|
|
275
|
+
// https://vitejs.dev/config/
|
|
276
|
+
export default defineConfig({
|
|
277
|
+
plugins: [
|
|
278
|
+
react(),${tailwindPlugin}${tanstackPlugin}
|
|
279
|
+
],
|
|
280
|
+
});
|
|
281
|
+
`;
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
// src/scaffold/react-vite/templates/tsconfig.ts
|
|
287
|
+
var tsconfigTemplate, tsconfigNodeTemplate;
|
|
288
|
+
var init_tsconfig = __esm({
|
|
289
|
+
"src/scaffold/react-vite/templates/tsconfig.ts"() {
|
|
290
|
+
"use strict";
|
|
291
|
+
tsconfigTemplate = () => JSON.stringify(
|
|
292
|
+
{
|
|
293
|
+
compilerOptions: {
|
|
294
|
+
target: "ES2020",
|
|
295
|
+
useDefineForClassFields: true,
|
|
296
|
+
lib: ["ES2020", "DOM", "DOM.Iterable"],
|
|
297
|
+
module: "ESNext",
|
|
298
|
+
skipLibCheck: true,
|
|
299
|
+
moduleResolution: "bundler",
|
|
300
|
+
allowImportingTsExtensions: true,
|
|
301
|
+
resolveJsonModule: true,
|
|
302
|
+
isolatedModules: true,
|
|
303
|
+
noEmit: true,
|
|
304
|
+
jsx: "react-jsx",
|
|
305
|
+
strict: true,
|
|
306
|
+
noUnusedLocals: true,
|
|
307
|
+
noUnusedParameters: true,
|
|
308
|
+
noFallthroughCasesInSwitch: true
|
|
309
|
+
},
|
|
310
|
+
include: ["src"],
|
|
311
|
+
references: [{ path: "./tsconfig.node.json" }]
|
|
312
|
+
},
|
|
313
|
+
null,
|
|
314
|
+
2
|
|
315
|
+
);
|
|
316
|
+
tsconfigNodeTemplate = () => JSON.stringify(
|
|
317
|
+
{
|
|
318
|
+
compilerOptions: {
|
|
319
|
+
composite: true,
|
|
320
|
+
skipLibCheck: true,
|
|
321
|
+
module: "ESNext",
|
|
322
|
+
moduleResolution: "bundler",
|
|
323
|
+
allowSyntheticDefaultImports: true,
|
|
324
|
+
strict: true
|
|
325
|
+
},
|
|
326
|
+
include: ["vite.config.ts"]
|
|
327
|
+
},
|
|
328
|
+
null,
|
|
329
|
+
2
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
// src/scaffold/react-vite/templates/index-html.ts
|
|
335
|
+
var indexHtmlTemplate;
|
|
336
|
+
var init_index_html = __esm({
|
|
337
|
+
"src/scaffold/react-vite/templates/index-html.ts"() {
|
|
338
|
+
"use strict";
|
|
339
|
+
indexHtmlTemplate = (projectName) => `<!doctype html>
|
|
340
|
+
<html lang="en">
|
|
341
|
+
<head>
|
|
342
|
+
<meta charset="UTF-8" />
|
|
343
|
+
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
|
344
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
345
|
+
<title>${projectName}</title>
|
|
346
|
+
</head>
|
|
347
|
+
<body>
|
|
348
|
+
<div id="root"></div>
|
|
349
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
350
|
+
</body>
|
|
351
|
+
</html>
|
|
352
|
+
`;
|
|
353
|
+
}
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
// src/scaffold/react-vite/templates/main-tsx.ts
|
|
357
|
+
var mainTsxFsd, mainTsxBpr, mainTsxTemplate;
|
|
358
|
+
var init_main_tsx = __esm({
|
|
359
|
+
"src/scaffold/react-vite/templates/main-tsx.ts"() {
|
|
360
|
+
"use strict";
|
|
361
|
+
mainTsxFsd = (hasTailwind) => `import { StrictMode } from 'react';
|
|
362
|
+
import { createRoot } from 'react-dom/client';
|
|
363
|
+
${hasTailwind ? "import './index.css';\n" : ""}import { App } from './app';
|
|
364
|
+
|
|
365
|
+
createRoot(document.getElementById('root')!).render(
|
|
366
|
+
<StrictMode>
|
|
367
|
+
<App />
|
|
368
|
+
</StrictMode>,
|
|
369
|
+
);
|
|
370
|
+
`;
|
|
371
|
+
mainTsxBpr = (hasTailwind) => `import { StrictMode } from 'react';
|
|
372
|
+
import { createRoot } from 'react-dom/client';
|
|
373
|
+
${hasTailwind ? "import './index.css';\n" : ""}import App from './App';
|
|
374
|
+
|
|
375
|
+
createRoot(document.getElementById('root')!).render(
|
|
376
|
+
<StrictMode>
|
|
377
|
+
<App />
|
|
378
|
+
</StrictMode>,
|
|
379
|
+
);
|
|
380
|
+
`;
|
|
381
|
+
mainTsxTemplate = (cart) => {
|
|
382
|
+
const hasTailwind = cart.css === "TAILWIND";
|
|
383
|
+
return cart.layout === "FSD" ? mainTsxFsd(hasTailwind) : mainTsxBpr(hasTailwind);
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
// src/scaffold/react-vite/templates/app-tsx.ts
|
|
389
|
+
var appTsxFsdTemplate, appTsxBprTemplate;
|
|
390
|
+
var init_app_tsx = __esm({
|
|
391
|
+
"src/scaffold/react-vite/templates/app-tsx.ts"() {
|
|
392
|
+
"use strict";
|
|
393
|
+
appTsxFsdTemplate = (hasRouter, hasQuery) => {
|
|
394
|
+
const queryImports = hasQuery ? `import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
395
|
+
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
|
|
396
|
+
` : "";
|
|
397
|
+
const queryClientDef = hasQuery ? `
|
|
398
|
+
const queryClient = new QueryClient();
|
|
399
|
+
` : "";
|
|
400
|
+
const routerImports = hasRouter ? `import { RouterProvider, createRouter } from '@tanstack/react-router';
|
|
401
|
+
import { routeTree } from '../routes/routeTree.gen';
|
|
402
|
+
|
|
403
|
+
const router = createRouter({ routeTree });
|
|
404
|
+
|
|
405
|
+
declare module '@tanstack/react-router' {
|
|
406
|
+
interface Register {
|
|
407
|
+
router: typeof router;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
` : "";
|
|
411
|
+
const inner = hasRouter ? `<RouterProvider router={router} />` : `<div>
|
|
412
|
+
<h1>Hello from FSD</h1>
|
|
413
|
+
</div>`;
|
|
414
|
+
if (hasQuery) {
|
|
415
|
+
return `${queryImports}${routerImports}${queryClientDef}
|
|
416
|
+
export const App = () => {
|
|
417
|
+
return (
|
|
418
|
+
<QueryClientProvider client={queryClient}>
|
|
419
|
+
${inner}
|
|
420
|
+
<ReactQueryDevtools initialIsOpen={false} />
|
|
421
|
+
</QueryClientProvider>
|
|
422
|
+
);
|
|
423
|
+
};
|
|
424
|
+
`;
|
|
425
|
+
}
|
|
426
|
+
if (hasRouter) {
|
|
427
|
+
return `${routerImports}
|
|
428
|
+
export const App = () => {
|
|
429
|
+
return <RouterProvider router={router} />;
|
|
430
|
+
};
|
|
431
|
+
`;
|
|
432
|
+
}
|
|
433
|
+
return `export const App = () => {
|
|
434
|
+
return (
|
|
435
|
+
<div>
|
|
436
|
+
<h1>Hello from FSD</h1>
|
|
437
|
+
</div>
|
|
438
|
+
);
|
|
439
|
+
};
|
|
440
|
+
`;
|
|
441
|
+
};
|
|
442
|
+
appTsxBprTemplate = (hasRouter, hasQuery) => {
|
|
443
|
+
const providerImport = hasQuery ? `import { AppProvider } from './providers';
|
|
444
|
+
` : "";
|
|
445
|
+
const routerImports = hasRouter ? `import { RouterProvider, createRouter } from '@tanstack/react-router';
|
|
446
|
+
import { routeTree } from './routes/routeTree.gen';
|
|
447
|
+
|
|
448
|
+
const router = createRouter({ routeTree });
|
|
449
|
+
|
|
450
|
+
declare module '@tanstack/react-router' {
|
|
451
|
+
interface Register {
|
|
452
|
+
router: typeof router;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
` : "";
|
|
456
|
+
const inner = hasRouter ? `<RouterProvider router={router} />` : `<div>
|
|
457
|
+
<h1>Hello from Bulletproof React</h1>
|
|
458
|
+
</div>`;
|
|
459
|
+
const body = hasQuery ? `<AppProvider>
|
|
460
|
+
${inner}
|
|
461
|
+
</AppProvider>` : inner;
|
|
462
|
+
return `${providerImport}${routerImports}
|
|
463
|
+
const App = () => {
|
|
464
|
+
return (
|
|
465
|
+
${body}
|
|
466
|
+
);
|
|
467
|
+
};
|
|
468
|
+
|
|
469
|
+
export default App;
|
|
470
|
+
`;
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
// src/scaffold/react-vite/templates/router.ts
|
|
476
|
+
var rootRouteTemplate, indexRouteTemplate;
|
|
477
|
+
var init_router = __esm({
|
|
478
|
+
"src/scaffold/react-vite/templates/router.ts"() {
|
|
479
|
+
"use strict";
|
|
480
|
+
rootRouteTemplate = () => `import { createRootRoute, Outlet } from '@tanstack/react-router';
|
|
481
|
+
import { TanStackRouterDevtools } from '@tanstack/router-devtools';
|
|
482
|
+
|
|
483
|
+
export const Route = createRootRoute({
|
|
484
|
+
component: () => (
|
|
485
|
+
<>
|
|
486
|
+
<Outlet />
|
|
487
|
+
<TanStackRouterDevtools />
|
|
488
|
+
</>
|
|
489
|
+
),
|
|
490
|
+
});
|
|
491
|
+
`;
|
|
492
|
+
indexRouteTemplate = () => `import { createFileRoute } from '@tanstack/react-router';
|
|
493
|
+
|
|
494
|
+
export const Route = createFileRoute('/')({
|
|
495
|
+
component: Index,
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
function Index() {
|
|
499
|
+
return (
|
|
500
|
+
<div>
|
|
501
|
+
<h3>Welcome Home!</h3>
|
|
502
|
+
</div>
|
|
503
|
+
);
|
|
504
|
+
}
|
|
505
|
+
`;
|
|
506
|
+
}
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
// src/scaffold/react-vite/templates/zustand.ts
|
|
510
|
+
var zustandStoreTemplate;
|
|
511
|
+
var init_zustand = __esm({
|
|
512
|
+
"src/scaffold/react-vite/templates/zustand.ts"() {
|
|
513
|
+
"use strict";
|
|
514
|
+
zustandStoreTemplate = () => `import { create } from 'zustand';
|
|
515
|
+
|
|
516
|
+
interface AppState {
|
|
517
|
+
count: number;
|
|
518
|
+
increment: () => void;
|
|
519
|
+
decrement: () => void;
|
|
520
|
+
reset: () => void;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
export const useAppStore = create<AppState>((set) => ({
|
|
524
|
+
count: 0,
|
|
525
|
+
increment: () => set((state) => ({ count: state.count + 1 })),
|
|
526
|
+
decrement: () => set((state) => ({ count: state.count - 1 })),
|
|
527
|
+
reset: () => set({ count: 0 }),
|
|
528
|
+
}));
|
|
529
|
+
`;
|
|
530
|
+
}
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
// src/scaffold/react-vite/templates/linter.ts
|
|
534
|
+
var biomeConfigTemplate, eslintConfigTemplate;
|
|
535
|
+
var init_linter = __esm({
|
|
536
|
+
"src/scaffold/react-vite/templates/linter.ts"() {
|
|
537
|
+
"use strict";
|
|
538
|
+
biomeConfigTemplate = () => JSON.stringify(
|
|
539
|
+
{
|
|
540
|
+
$schema: "https://biomejs.dev/schemas/1.9.4/schema.json",
|
|
541
|
+
organizeImports: { enabled: true },
|
|
542
|
+
linter: {
|
|
543
|
+
enabled: true,
|
|
544
|
+
rules: { recommended: true }
|
|
545
|
+
},
|
|
546
|
+
formatter: {
|
|
547
|
+
enabled: true,
|
|
548
|
+
indentStyle: "space",
|
|
549
|
+
indentWidth: 2
|
|
550
|
+
},
|
|
551
|
+
javascript: {
|
|
552
|
+
formatter: { quoteStyle: "single" }
|
|
553
|
+
}
|
|
554
|
+
},
|
|
555
|
+
null,
|
|
556
|
+
2
|
|
557
|
+
);
|
|
558
|
+
eslintConfigTemplate = () => `import js from '@eslint/js';
|
|
559
|
+
import globals from 'globals';
|
|
560
|
+
import reactHooks from 'eslint-plugin-react-hooks';
|
|
561
|
+
import reactRefresh from 'eslint-plugin-react-refresh';
|
|
562
|
+
import tseslint from 'typescript-eslint';
|
|
563
|
+
|
|
564
|
+
export default tseslint.config(
|
|
565
|
+
{ ignores: ['dist'] },
|
|
566
|
+
{
|
|
567
|
+
extends: [js.configs.recommended, ...tseslint.configs.recommended],
|
|
568
|
+
files: ['**/*.{ts,tsx}'],
|
|
569
|
+
languageOptions: {
|
|
570
|
+
ecmaVersion: 2020,
|
|
571
|
+
globals: globals.browser,
|
|
572
|
+
},
|
|
573
|
+
plugins: {
|
|
574
|
+
'react-hooks': reactHooks,
|
|
575
|
+
'react-refresh': reactRefresh,
|
|
576
|
+
},
|
|
577
|
+
rules: {
|
|
578
|
+
...reactHooks.configs.recommended.rules,
|
|
579
|
+
'react-refresh/only-export-components': ['warn', { allowConstantExport: true }],
|
|
580
|
+
},
|
|
581
|
+
},
|
|
582
|
+
);
|
|
583
|
+
`;
|
|
584
|
+
}
|
|
585
|
+
});
|
|
586
|
+
|
|
587
|
+
// src/scaffold/react-vite/templates/gitignore.ts
|
|
588
|
+
var gitignoreTemplate;
|
|
589
|
+
var init_gitignore = __esm({
|
|
590
|
+
"src/scaffold/react-vite/templates/gitignore.ts"() {
|
|
591
|
+
"use strict";
|
|
592
|
+
gitignoreTemplate = () => `# Logs
|
|
593
|
+
logs
|
|
594
|
+
*.log
|
|
595
|
+
npm-debug.log*
|
|
596
|
+
|
|
597
|
+
# Dependencies
|
|
598
|
+
node_modules/
|
|
599
|
+
dist/
|
|
600
|
+
dist-ssr/
|
|
601
|
+
*.local
|
|
602
|
+
|
|
603
|
+
# Editor directories and files
|
|
604
|
+
.vscode/*
|
|
605
|
+
!.vscode/extensions.json
|
|
606
|
+
.idea
|
|
607
|
+
.DS_Store
|
|
608
|
+
*.suo
|
|
609
|
+
*.ntvs*
|
|
610
|
+
*.njsproj
|
|
611
|
+
*.sln
|
|
612
|
+
*.sw?
|
|
613
|
+
`;
|
|
614
|
+
}
|
|
615
|
+
});
|
|
616
|
+
|
|
617
|
+
// src/scaffold/react-vite/templates/styles.ts
|
|
618
|
+
var stylesCssTemplate;
|
|
619
|
+
var init_styles = __esm({
|
|
620
|
+
"src/scaffold/react-vite/templates/styles.ts"() {
|
|
621
|
+
"use strict";
|
|
622
|
+
stylesCssTemplate = () => `@import "tailwindcss";
|
|
623
|
+
`;
|
|
624
|
+
}
|
|
625
|
+
});
|
|
626
|
+
|
|
627
|
+
// src/scaffold/react-vite/templates/vite-env-d-ts.ts
|
|
628
|
+
var viteEnvDtsTemplate;
|
|
629
|
+
var init_vite_env_d_ts = __esm({
|
|
630
|
+
"src/scaffold/react-vite/templates/vite-env-d-ts.ts"() {
|
|
631
|
+
"use strict";
|
|
632
|
+
viteEnvDtsTemplate = () => `/// <reference types="vite/client" />
|
|
633
|
+
|
|
634
|
+
declare module '*.css' {
|
|
635
|
+
const content: string;
|
|
636
|
+
export default content;
|
|
637
|
+
}
|
|
638
|
+
`;
|
|
639
|
+
}
|
|
640
|
+
});
|
|
641
|
+
|
|
642
|
+
// src/scaffold/react-vite/templates/copilot-instructions.ts
|
|
643
|
+
var FSD_PATHS, BPR_PATHS, frontmatter, generalTemplate, componentsTemplate, hooksTemplate, routerTemplate, zustandTemplate, queryTemplate, tailwindTemplate, linterTemplate, getCopilotInstructionFiles;
|
|
644
|
+
var init_copilot_instructions = __esm({
|
|
645
|
+
"src/scaffold/react-vite/templates/copilot-instructions.ts"() {
|
|
646
|
+
"use strict";
|
|
647
|
+
FSD_PATHS = {
|
|
648
|
+
components: "src/**/ui/**/*.tsx,src/shared/ui/**/*.tsx",
|
|
649
|
+
hooks: "src/**/model/**/*.ts,src/**/lib/**/*.ts",
|
|
650
|
+
stores: "src/shared/lib/store.ts,src/**/model/**/*.ts",
|
|
651
|
+
apis: "src/**/api/**/*.ts",
|
|
652
|
+
routes: "src/routes/**/*.tsx"
|
|
653
|
+
};
|
|
654
|
+
BPR_PATHS = {
|
|
655
|
+
components: "src/components/**/*.tsx,src/features/*/components/**/*.tsx",
|
|
656
|
+
hooks: "src/hooks/**/*.ts,src/features/*/hooks/**/*.ts",
|
|
657
|
+
stores: "src/stores/**/*.ts,src/features/*/stores/**/*.ts",
|
|
658
|
+
apis: "src/features/*/api/**/*.ts,src/lib/**/*.ts",
|
|
659
|
+
routes: "src/routes/**/*.tsx"
|
|
660
|
+
};
|
|
661
|
+
frontmatter = (applyTo) => `---
|
|
662
|
+
applyTo: "${applyTo}"
|
|
663
|
+
---
|
|
664
|
+
|
|
665
|
+
`;
|
|
666
|
+
generalTemplate = (cart) => {
|
|
667
|
+
const isFsd = cart.layout === "FSD";
|
|
668
|
+
const stackLines = [
|
|
669
|
+
"- React 19 + Vite 6 + TypeScript 5",
|
|
670
|
+
`- Architecture: ${isFsd ? "Feature-Sliced Design (FSD)" : "Bulletproof React (BPR)"}`,
|
|
671
|
+
cart.router === "TANSTACK_ROUTER" ? "- Routing: TanStack Router (file-based)" : "- Routing: none",
|
|
672
|
+
cart.stateManagement === "ZUSTAND" ? "- State: Zustand" : "- State: local React state only",
|
|
673
|
+
cart.query === "TANSTACK_QUERY" ? "- Server state: TanStack Query" : "- Server state: none",
|
|
674
|
+
cart.css === "TAILWIND" ? "- CSS: Tailwind CSS v4.1.3 (Vite plugin, `src/index.css`)" : "- CSS: plain CSS",
|
|
675
|
+
cart.linter === "BIOME" ? "- Linter/formatter: Biome (`biome.json`)" : cart.linter === "ESLINT" ? "- Linter: ESLint (`eslint.config.js`)" : "- Linter: none"
|
|
676
|
+
].join("\n");
|
|
677
|
+
const layoutBlock = isFsd ? `## Architecture \u2014 Feature-Sliced Design
|
|
678
|
+
|
|
679
|
+
Layer order (top \u2192 bottom): \`app \u2192 pages \u2192 widgets \u2192 features \u2192 entities \u2192 shared\`.
|
|
680
|
+
A layer may only import from layers **below** it. Never import from \`features\` inside \`shared\`, never import from \`pages\` inside \`widgets\`, and so on.
|
|
681
|
+
|
|
682
|
+
Inside every slice, only these segments are allowed \u2014 do not invent new ones:
|
|
683
|
+
|
|
684
|
+
- \`ui/\` \u2014 React components for the slice
|
|
685
|
+
- \`model/\` \u2014 state, hooks, selectors, slice-local types
|
|
686
|
+
- \`api/\` \u2014 network calls, request/response types
|
|
687
|
+
- \`lib/\` \u2014 pure helpers specific to the slice
|
|
688
|
+
- \`config/\` \u2014 constants, enums
|
|
689
|
+
|
|
690
|
+
Where new code belongs:
|
|
691
|
+
|
|
692
|
+
- Reusable primitive (Button, Input) \u2192 \`src/shared/ui/<Name>/\`
|
|
693
|
+
- Cross-cutting helper \u2192 \`src/shared/lib/\`
|
|
694
|
+
- Domain model (User, Post) \u2192 \`src/entities/<name>/\`
|
|
695
|
+
- User-facing interaction (LoginForm) \u2192 \`src/features/<name>/\`
|
|
696
|
+
- Composition of features \u2192 \`src/widgets/<name>/\`
|
|
697
|
+
- Page-level composition \u2192 \`src/pages/<name>/ui/<Name>Page.tsx\` with \`src/pages/<name>/index.ts\` barrel
|
|
698
|
+
|
|
699
|
+
Pages **compose** widgets/features \u2014 no business logic inside \`pages/*/ui\`.
|
|
700
|
+
` : `## Architecture \u2014 Bulletproof React
|
|
701
|
+
|
|
702
|
+
Global, cross-feature folders directly under \`src/\`:
|
|
703
|
+
|
|
704
|
+
- \`components/\` \u2014 app-wide reusable UI primitives. No feature-specific UI.
|
|
705
|
+
- \`hooks/\` \u2014 hooks used by more than one feature.
|
|
706
|
+
- \`utils/\` \u2014 pure, framework-agnostic helpers.
|
|
707
|
+
- \`lib/\` \u2014 configured third-party clients (axios, dayjs).
|
|
708
|
+
- \`types/\` \u2014 types shared across features.
|
|
709
|
+
- \`config/\` \u2014 env readers, constants, feature flags.
|
|
710
|
+
- \`stores/\` \u2014 global stores.
|
|
711
|
+
- \`providers/\` \u2014 root-level providers. \`providers/index.tsx\` wraps the tree \u2014 compose new providers there.
|
|
712
|
+
|
|
713
|
+
Feature-scoped code lives under \`src/features/<feature>/\` with its own \`components/\`, \`hooks/\`, \`api/\`, \`stores/\`, \`types/\`, \`utils/\`.
|
|
714
|
+
|
|
715
|
+
Rule of thumb: if only one feature uses it, it stays inside that feature. Promote to \`src/\` only when a second feature needs it.
|
|
716
|
+
`;
|
|
717
|
+
return `# Copilot Instructions \u2014 ${cart.projectName}
|
|
718
|
+
|
|
719
|
+
These are the project-wide conventions GitHub Copilot must follow. Match the structure below exactly \u2014 do not invent new top-level folders.
|
|
720
|
+
|
|
721
|
+
Path-specific rules live in \`.github/instructions/*.instructions.md\` \u2014 those files apply automatically to the files their \`applyTo\` glob matches.
|
|
722
|
+
|
|
723
|
+
## Stack
|
|
724
|
+
${stackLines}
|
|
725
|
+
|
|
726
|
+
## Naming conventions
|
|
727
|
+
|
|
728
|
+
- **Components**: \`PascalCase.tsx\`, one component per file, named export (no default).
|
|
729
|
+
- **Hooks**: \`useXxx.ts\`, camelCase, named export, must start with \`use\`.
|
|
730
|
+
- **Stores** (Zustand): camelCase ending in \`Store.ts\` (e.g. \`authStore.ts\`). Export a typed hook, never the raw store.
|
|
731
|
+
- **Types / interfaces**: PascalCase, in \`*.types.ts\` or inline when used in one file only.
|
|
732
|
+
- **Constants**: \`UPPER_SNAKE_CASE\`, grouped in \`constants.ts\` per slice/feature.
|
|
733
|
+
- **Utility functions**: camelCase, one concern per file, named exports.
|
|
734
|
+
|
|
735
|
+
${layoutBlock}
|
|
736
|
+
## When unsure
|
|
737
|
+
|
|
738
|
+
Open the closest existing file of the same kind and match its location, casing, and export style.
|
|
739
|
+
`;
|
|
740
|
+
};
|
|
741
|
+
componentsTemplate = (cart) => {
|
|
742
|
+
const paths = cart.layout === "FSD" ? FSD_PATHS : BPR_PATHS;
|
|
743
|
+
const placement = cart.layout === "FSD" ? `- Reusable primitives (Button, Input, Modal) \u2192 \`src/shared/ui/<Name>/<Name>.tsx\`.
|
|
744
|
+
- Slice-specific components \u2192 \`src/<layer>/<slice>/ui/<Name>.tsx\`.
|
|
745
|
+
- Never put a component directly under \`src/pages/<name>/\` \u2014 use \`src/pages/<name>/ui/\`.` : `- App-wide reusable UI \u2192 \`src/components/<Name>/<Name>.tsx\`.
|
|
746
|
+
- Feature-specific UI \u2192 \`src/features/<feature>/components/<Name>.tsx\`.
|
|
747
|
+
- Do **not** put feature-specific components under \`src/components\`.`;
|
|
748
|
+
return frontmatter(paths.components) + `# React components
|
|
749
|
+
|
|
750
|
+
- File name matches the component name exactly: \`UserCard.tsx\` exports \`UserCard\`.
|
|
751
|
+
- One component per file. Named export only \u2014 never \`export default\`.
|
|
752
|
+
- Functional components with TypeScript props interface named \`<Name>Props\`, defined in the same file unless reused.
|
|
753
|
+
- Keep JSX return focused. Extract sub-trees into smaller components when the return exceeds ~60 lines.
|
|
754
|
+
- Hooks run at the top of the component body, before any conditional return.
|
|
755
|
+
- No business logic in components \u2014 delegate to hooks, stores, or query functions.
|
|
756
|
+
- Co-locate component-scoped styles, stories, and tests next to the component file.
|
|
757
|
+
|
|
758
|
+
## Where to place a new component
|
|
759
|
+
|
|
760
|
+
${placement}
|
|
761
|
+
`;
|
|
762
|
+
};
|
|
763
|
+
hooksTemplate = (cart) => {
|
|
764
|
+
const paths = cart.layout === "FSD" ? FSD_PATHS : BPR_PATHS;
|
|
765
|
+
const placement = cart.layout === "FSD" ? `- Slice-specific hook \u2192 \`src/<layer>/<slice>/model/useXxx.ts\` (stateful) or \`src/<layer>/<slice>/lib/useXxx.ts\` (pure helper).
|
|
766
|
+
- Reusable across slices \u2192 \`src/shared/lib/useXxx.ts\`.` : `- Cross-feature hook \u2192 \`src/hooks/useXxx.ts\`.
|
|
767
|
+
- Feature-specific hook \u2192 \`src/features/<feature>/hooks/useXxx.ts\`.`;
|
|
768
|
+
return frontmatter(paths.hooks) + `# Custom hooks
|
|
769
|
+
|
|
770
|
+
- File name starts with \`use\` and matches the exported hook: \`useDebouncedValue.ts\` exports \`useDebouncedValue\`.
|
|
771
|
+
- One hook per file. Named export only.
|
|
772
|
+
- Must call other hooks \u2014 if a function does not use any hook, it is a utility, not a hook.
|
|
773
|
+
- Return either a tuple \`[value, setter]\` or an object with named fields. Do not mix patterns within one hook.
|
|
774
|
+
- Keep hooks focused on one concern. Compose multiple hooks rather than building a single large one.
|
|
775
|
+
- Do not call hooks conditionally. Do not rename destructured return values unless renaming makes usage clearer.
|
|
776
|
+
|
|
777
|
+
## Where to place a new hook
|
|
778
|
+
|
|
779
|
+
${placement}
|
|
780
|
+
`;
|
|
781
|
+
};
|
|
782
|
+
routerTemplate = (cart) => {
|
|
783
|
+
const paths = cart.layout === "FSD" ? FSD_PATHS : BPR_PATHS;
|
|
784
|
+
return frontmatter(paths.routes) + `# TanStack Router (file-based)
|
|
785
|
+
|
|
786
|
+
- Every file in \`src/routes/\` is a route. Do not create unrelated helper files here.
|
|
787
|
+
- Root layout lives in \`src/routes/__root.tsx\` \u2014 wrap \`<Outlet />\` with app-wide chrome (header, devtools, providers that need router context).
|
|
788
|
+
- Leaf routes use \`createFileRoute('/path')\` and export \`Route\`. File name matches the URL segment.
|
|
789
|
+
- Dynamic segments use \`$param\` file names: \`src/routes/posts/$postId.tsx\`.
|
|
790
|
+
- Co-locate the route's \`loader\`, \`beforeLoad\`, and \`component\` in the same file. Push heavy logic into hooks / query functions imported from the feature folder.
|
|
791
|
+
- **Never edit \`src/routes/routeTree.gen.ts\`** \u2014 it is regenerated on dev/build.
|
|
792
|
+
- Internal navigation uses \`<Link to="..." />\` from \`@tanstack/react-router\`. Do not use \`<a href>\` for in-app links.
|
|
793
|
+
- Type-safe search params: declare them in the route's \`validateSearch\` and read via \`Route.useSearch()\`.
|
|
794
|
+
`;
|
|
795
|
+
};
|
|
796
|
+
zustandTemplate = (cart) => {
|
|
797
|
+
const paths = cart.layout === "FSD" ? FSD_PATHS : BPR_PATHS;
|
|
798
|
+
const initialPath = cart.layout === "FSD" ? "src/shared/lib/store.ts" : "src/stores/appStore.ts";
|
|
799
|
+
const newStorePath = cart.layout === "FSD" ? "- New global store \u2192 `src/shared/lib/<name>Store.ts`.\n- Entity-owned store \u2192 `src/entities/<name>/model/store.ts`." : "- New global store \u2192 `src/stores/<name>Store.ts`.\n- Feature-scoped store \u2192 `src/features/<feature>/stores/<name>Store.ts`.";
|
|
800
|
+
return frontmatter(paths.stores) + `# Zustand stores
|
|
801
|
+
|
|
802
|
+
- Initial store lives at \`${initialPath}\`.
|
|
803
|
+
- One slice per concern \u2014 never pile unrelated state into a single store.
|
|
804
|
+
- Export a **typed hook** (e.g. \`useAuthStore\`). Never export the raw store object or the \`create\` result directly.
|
|
805
|
+
- Use selectors at call sites to read only what you need: \`const user = useAuthStore(s => s.user)\`. This avoids re-rendering on unrelated state changes.
|
|
806
|
+
- Derived values belong in selectors, not inside the store.
|
|
807
|
+
- Async actions go inside the store definition. They read/write state via \`set\` / \`get\`.
|
|
808
|
+
- Shape a store as \`{ ...state, ...actions }\`. Actions are methods, not separate exports.
|
|
809
|
+
- For cross-slice reads, use individual selectors \u2014 do not merge stores.
|
|
810
|
+
|
|
811
|
+
## Where to place a new store
|
|
812
|
+
|
|
813
|
+
${newStorePath}
|
|
814
|
+
`;
|
|
815
|
+
};
|
|
816
|
+
queryTemplate = (cart) => {
|
|
817
|
+
const paths = cart.layout === "FSD" ? FSD_PATHS : BPR_PATHS;
|
|
818
|
+
const keysPath = cart.layout === "FSD" ? `- Query keys, query functions, and hooks live in \`src/<layer>/<slice>/api/\`:
|
|
819
|
+
- \`keys.ts\` \u2014 exported query key factories
|
|
820
|
+
- \`queries.ts\` \u2014 \`useXxxQuery\` / \`useXxxMutation\` hooks wrapping \`useQuery\` / \`useMutation\`` : `- Query keys, query functions, and hooks live in \`src/features/<feature>/api/\`:
|
|
821
|
+
- \`keys.ts\` \u2014 exported query key factories
|
|
822
|
+
- \`queries.ts\` \u2014 \`useXxxQuery\` / \`useXxxMutation\` hooks wrapping \`useQuery\` / \`useMutation\``;
|
|
823
|
+
return frontmatter(paths.apis) + `# TanStack Query
|
|
824
|
+
|
|
825
|
+
- A single \`QueryClient\` is already instantiated in the root provider. Do **not** create additional clients.
|
|
826
|
+
- Do not call \`useQuery\` / \`useMutation\` directly in leaf components unless the component is a page-level container. Wrap them in a hook inside the feature/slice.
|
|
827
|
+
|
|
828
|
+
## Query keys
|
|
829
|
+
|
|
830
|
+
- Use a **query key factory** per feature/slice \u2014 do not scatter string literals.
|
|
831
|
+
- Factory shape: \`export const postKeys = { all: ['posts'] as const, list: (f) => [...postKeys.all, 'list', f] as const, detail: (id) => [...postKeys.all, 'detail', id] as const };\`
|
|
832
|
+
- Invalidate via the factory: \`queryClient.invalidateQueries({ queryKey: postKeys.all })\`.
|
|
833
|
+
|
|
834
|
+
## Query functions
|
|
835
|
+
|
|
836
|
+
- Query functions return parsed, typed data \u2014 no raw \`Response\`. Throw on non-2xx.
|
|
837
|
+
- Mutations invalidate relevant keys in \`onSuccess\`; never manually refetch by calling queries.
|
|
838
|
+
- Prefer \`select\` for view-model transforms so the cached data stays normalized.
|
|
839
|
+
|
|
840
|
+
## Where to place queries
|
|
841
|
+
|
|
842
|
+
${keysPath}
|
|
843
|
+
`;
|
|
844
|
+
};
|
|
845
|
+
tailwindTemplate = (cart) => {
|
|
846
|
+
const paths = cart.layout === "FSD" ? FSD_PATHS : BPR_PATHS;
|
|
847
|
+
return frontmatter(paths.components) + `# Tailwind CSS v4
|
|
848
|
+
|
|
849
|
+
> **This project uses Tailwind CSS v4.** v4 removes \`tailwind.config.js\` entirely.
|
|
850
|
+
> All theme customization is done inside \`src/index.css\` using the \`@theme\` directive.
|
|
851
|
+
> Do NOT create or reference \`tailwind.config.js\`, \`tailwind.config.ts\`, or \`postcss.config.*\`.
|
|
852
|
+
|
|
853
|
+
## Entry point
|
|
854
|
+
|
|
855
|
+
\`src/index.css\` is the single Tailwind entry point \u2014 the only file that contains \`@import "tailwindcss"\`.
|
|
856
|
+
Import it once in \`src/main.tsx\`. Do not add additional Tailwind imports elsewhere.
|
|
857
|
+
|
|
858
|
+
## Using utilities
|
|
859
|
+
|
|
860
|
+
- Use Tailwind utility classes directly in JSX \`className\`. Do not write custom CSS for layout or spacing that a utility already covers.
|
|
861
|
+
- Prefer composing utilities over \`@apply\`. Only use \`@apply\` inside \`@layer components\` or \`@layer base\` in \`src/index.css\`, never in component files.
|
|
862
|
+
- Do not use inline \`style={{}}\` for values that a Tailwind utility can express.
|
|
863
|
+
- Responsive variants go smallest \u2192 largest: \`sm:\`, \`md:\`, \`lg:\`, \`xl:\`, \`2xl:\`.
|
|
864
|
+
- Dark mode: use the \`dark:\` variant. Do not add a separate CSS file or a manual class toggle.
|
|
865
|
+
|
|
866
|
+
## Theme customization \u2014 colors, spacing, and CSS variables
|
|
867
|
+
|
|
868
|
+
All design tokens live in \`src/index.css\` under the \`@theme\` block, **not** in a config file.
|
|
869
|
+
|
|
870
|
+
### Adding custom colors
|
|
871
|
+
|
|
872
|
+
\`\`\`css
|
|
873
|
+
@import "tailwindcss";
|
|
874
|
+
|
|
875
|
+
@theme {
|
|
876
|
+
--color-brand: oklch(0.55 0.22 265);
|
|
877
|
+
--color-brand-light: oklch(0.72 0.14 265);
|
|
878
|
+
--color-brand-dark: oklch(0.38 0.22 265);
|
|
879
|
+
}
|
|
880
|
+
\`\`\`
|
|
881
|
+
|
|
882
|
+
This generates \`bg-brand\`, \`text-brand\`, \`border-brand\`, \`fill-brand\`, etc. automatically.
|
|
883
|
+
|
|
884
|
+
### Namespace \u2192 utility mapping (do not invent names outside these namespaces)
|
|
885
|
+
|
|
886
|
+
| CSS variable | Generated utilities |
|
|
887
|
+
|---|---|
|
|
888
|
+
| \`--color-*\` | \`bg-*\`, \`text-*\`, \`border-*\`, \`fill-*\`, \`stroke-*\` |
|
|
889
|
+
| \`--font-*\` | \`font-*\` (font family) |
|
|
890
|
+
| \`--text-*\` | \`text-*\` (font size) |
|
|
891
|
+
| \`--font-weight-*\` | \`font-*\` (weight) |
|
|
892
|
+
| \`--spacing-*\` | \`p-*\`, \`m-*\`, \`w-*\`, \`h-*\`, \`gap-*\`, etc. |
|
|
893
|
+
| \`--radius-*\` | \`rounded-*\` |
|
|
894
|
+
| \`--shadow-*\` | \`shadow-*\` |
|
|
895
|
+
| \`--breakpoint-*\` | responsive variants (\`sm:\`, \`md:\`, \u2026) |
|
|
896
|
+
| \`--animate-*\` | \`animate-*\` |
|
|
897
|
+
|
|
898
|
+
### Extending vs. replacing defaults
|
|
899
|
+
|
|
900
|
+
**Extend** (keep defaults, add yours):
|
|
901
|
+
\`\`\`css
|
|
902
|
+
@theme {
|
|
903
|
+
--color-tahiti: #3ab7bf; /* new color, defaults kept */
|
|
904
|
+
}
|
|
905
|
+
\`\`\`
|
|
906
|
+
|
|
907
|
+
**Replace an entire namespace** (remove all defaults in that group):
|
|
908
|
+
\`\`\`css
|
|
909
|
+
@theme {
|
|
910
|
+
--color-*: initial; /* wipe default palette */
|
|
911
|
+
--color-white: #fff;
|
|
912
|
+
--color-primary: oklch(0.55 0.22 265);
|
|
913
|
+
}
|
|
914
|
+
\`\`\`
|
|
915
|
+
|
|
916
|
+
**Replace everything** (no defaults at all):
|
|
917
|
+
\`\`\`css
|
|
918
|
+
@theme {
|
|
919
|
+
--*: initial;
|
|
920
|
+
--spacing: 4px;
|
|
921
|
+
--color-primary: oklch(0.55 0.22 265);
|
|
922
|
+
}
|
|
923
|
+
\`\`\`
|
|
924
|
+
|
|
925
|
+
### Variables that reference other variables
|
|
926
|
+
|
|
927
|
+
Use \`@theme inline\` when a token references another CSS variable:
|
|
928
|
+
\`\`\`css
|
|
929
|
+
@theme inline {
|
|
930
|
+
--font-sans: var(--font-inter); /* resolves the value, not the reference */
|
|
931
|
+
}
|
|
932
|
+
\`\`\`
|
|
933
|
+
|
|
934
|
+
### Regular CSS variables vs. theme tokens
|
|
935
|
+
|
|
936
|
+
- \`@theme { --color-brand: \u2026 }\` \u2192 creates utility classes (\`bg-brand\`, etc.)
|
|
937
|
+
- \`:root { --brand-opacity: 0.9; }\` \u2192 plain CSS variable, no utility generated
|
|
938
|
+
|
|
939
|
+
Use \`:root\` for runtime values (e.g., animation targets, JS-readable tokens) that must **not** produce utility classes.
|
|
940
|
+
|
|
941
|
+
### Defining custom animations
|
|
942
|
+
|
|
943
|
+
\`\`\`css
|
|
944
|
+
@theme {
|
|
945
|
+
--animate-slide-in: slide-in 0.3s ease-out;
|
|
946
|
+
|
|
947
|
+
@keyframes slide-in {
|
|
948
|
+
from { transform: translateY(-8px); opacity: 0; }
|
|
949
|
+
to { transform: translateY(0); opacity: 1; }
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
\`\`\`
|
|
953
|
+
`;
|
|
954
|
+
};
|
|
955
|
+
linterTemplate = (cart) => {
|
|
956
|
+
if (cart.linter === "BIOME") {
|
|
957
|
+
return frontmatter("**") + `# Biome
|
|
958
|
+
|
|
959
|
+
- Configuration: \`biome.json\` at the repo root. Respect the rules it enforces.
|
|
960
|
+
- Run \`npm run lint\` and \`npm run format\` before committing.
|
|
961
|
+
- Do not disable rules inline (\`// biome-ignore\`) without a comment explaining why.
|
|
962
|
+
- Import order, formatting, and unused imports are enforced by Biome \u2014 do not manually format differently.
|
|
963
|
+
`;
|
|
964
|
+
}
|
|
965
|
+
if (cart.linter === "ESLINT") {
|
|
966
|
+
return frontmatter("**") + `# ESLint
|
|
967
|
+
|
|
968
|
+
- Configuration: \`eslint.config.js\` (flat config) at the repo root.
|
|
969
|
+
- Run \`npm run lint\` before committing; fix all errors and warnings.
|
|
970
|
+
- Do not add \`// eslint-disable\` or \`/* eslint-disable */\` without a short comment explaining why.
|
|
971
|
+
- React hooks rules are enforced \u2014 never call hooks conditionally or in loops.
|
|
972
|
+
- Unused variables must be prefixed with \`_\` or removed.
|
|
973
|
+
`;
|
|
974
|
+
}
|
|
975
|
+
return "";
|
|
976
|
+
};
|
|
977
|
+
getCopilotInstructionFiles = (cart) => {
|
|
978
|
+
const hasRouter = cart.router === "TANSTACK_ROUTER";
|
|
979
|
+
const hasZustand = cart.stateManagement === "ZUSTAND";
|
|
980
|
+
const hasQuery = cart.query === "TANSTACK_QUERY";
|
|
981
|
+
const hasLinter = cart.linter !== "NOT_USING";
|
|
982
|
+
const files = [
|
|
983
|
+
{ relativePath: ".github/copilot-instructions.md", content: generalTemplate(cart) },
|
|
984
|
+
{ relativePath: ".github/instructions/components.instructions.md", content: componentsTemplate(cart) },
|
|
985
|
+
{ relativePath: ".github/instructions/hooks.instructions.md", content: hooksTemplate(cart) }
|
|
986
|
+
];
|
|
987
|
+
if (hasRouter) {
|
|
988
|
+
files.push({
|
|
989
|
+
relativePath: ".github/instructions/tanstack-router.instructions.md",
|
|
990
|
+
content: routerTemplate(cart)
|
|
991
|
+
});
|
|
992
|
+
}
|
|
993
|
+
if (hasZustand) {
|
|
994
|
+
files.push({
|
|
995
|
+
relativePath: ".github/instructions/zustand.instructions.md",
|
|
996
|
+
content: zustandTemplate(cart)
|
|
997
|
+
});
|
|
998
|
+
}
|
|
999
|
+
if (hasQuery) {
|
|
1000
|
+
files.push({
|
|
1001
|
+
relativePath: ".github/instructions/tanstack-query.instructions.md",
|
|
1002
|
+
content: queryTemplate(cart)
|
|
1003
|
+
});
|
|
1004
|
+
}
|
|
1005
|
+
if (cart.css === "TAILWIND") {
|
|
1006
|
+
files.push({
|
|
1007
|
+
relativePath: ".github/instructions/tailwind.instructions.md",
|
|
1008
|
+
content: tailwindTemplate(cart)
|
|
1009
|
+
});
|
|
1010
|
+
}
|
|
1011
|
+
if (hasLinter) {
|
|
1012
|
+
files.push({
|
|
1013
|
+
relativePath: `.github/instructions/${cart.linter === "BIOME" ? "biome" : "eslint"}.instructions.md`,
|
|
1014
|
+
content: linterTemplate(cart)
|
|
1015
|
+
});
|
|
1016
|
+
}
|
|
1017
|
+
return files;
|
|
1018
|
+
};
|
|
1019
|
+
}
|
|
1020
|
+
});
|
|
1021
|
+
|
|
1022
|
+
// src/scaffold/react-vite/templates/fsd-layout.ts
|
|
1023
|
+
var getFsdFileMap;
|
|
1024
|
+
var init_fsd_layout = __esm({
|
|
1025
|
+
"src/scaffold/react-vite/templates/fsd-layout.ts"() {
|
|
1026
|
+
"use strict";
|
|
1027
|
+
init_package_json();
|
|
1028
|
+
init_vite_config();
|
|
1029
|
+
init_tsconfig();
|
|
1030
|
+
init_index_html();
|
|
1031
|
+
init_main_tsx();
|
|
1032
|
+
init_app_tsx();
|
|
1033
|
+
init_router();
|
|
1034
|
+
init_zustand();
|
|
1035
|
+
init_linter();
|
|
1036
|
+
init_gitignore();
|
|
1037
|
+
init_styles();
|
|
1038
|
+
init_vite_env_d_ts();
|
|
1039
|
+
init_copilot_instructions();
|
|
1040
|
+
getFsdFileMap = (cart) => {
|
|
1041
|
+
const hasRouter = cart.router === "TANSTACK_ROUTER";
|
|
1042
|
+
const hasZustand = cart.stateManagement === "ZUSTAND";
|
|
1043
|
+
const hasQuery = cart.query === "TANSTACK_QUERY";
|
|
1044
|
+
const files = [
|
|
1045
|
+
{ relativePath: "package.json", content: packageJsonTemplate(cart) },
|
|
1046
|
+
{ relativePath: "vite.config.ts", content: viteConfigTemplate(cart) },
|
|
1047
|
+
{ relativePath: "tsconfig.json", content: tsconfigTemplate() },
|
|
1048
|
+
{ relativePath: "tsconfig.node.json", content: tsconfigNodeTemplate() },
|
|
1049
|
+
{ relativePath: "index.html", content: indexHtmlTemplate(cart.projectName) },
|
|
1050
|
+
{ relativePath: ".gitignore", content: gitignoreTemplate() },
|
|
1051
|
+
...getCopilotInstructionFiles(cart),
|
|
1052
|
+
{ relativePath: "src/vite-env.d.ts", content: viteEnvDtsTemplate() },
|
|
1053
|
+
{ relativePath: "src/main.tsx", content: mainTsxTemplate(cart) },
|
|
1054
|
+
{ relativePath: "src/app/index.tsx", content: appTsxFsdTemplate(hasRouter, hasQuery) },
|
|
1055
|
+
{
|
|
1056
|
+
relativePath: "src/pages/home/ui/HomePage.tsx",
|
|
1057
|
+
content: `export const HomePage = () => <div>Home Page</div>;
|
|
1058
|
+
`
|
|
1059
|
+
},
|
|
1060
|
+
{
|
|
1061
|
+
relativePath: "src/pages/home/index.ts",
|
|
1062
|
+
content: `export { HomePage } from './ui/HomePage';
|
|
1063
|
+
`
|
|
1064
|
+
},
|
|
1065
|
+
{ relativePath: "src/widgets/.gitkeep", content: "" },
|
|
1066
|
+
{ relativePath: "src/features/.gitkeep", content: "" },
|
|
1067
|
+
{ relativePath: "src/entities/.gitkeep", content: "" },
|
|
1068
|
+
{ relativePath: "src/shared/ui/.gitkeep", content: "" },
|
|
1069
|
+
{ relativePath: "src/shared/lib/.gitkeep", content: "" },
|
|
1070
|
+
{ relativePath: "src/shared/api/.gitkeep", content: "" },
|
|
1071
|
+
{ relativePath: "src/shared/config/.gitkeep", content: "" }
|
|
1072
|
+
];
|
|
1073
|
+
if (hasRouter) {
|
|
1074
|
+
files.push(
|
|
1075
|
+
{ relativePath: "src/routes/__root.tsx", content: rootRouteTemplate() },
|
|
1076
|
+
{ relativePath: "src/routes/index.tsx", content: indexRouteTemplate() }
|
|
1077
|
+
);
|
|
1078
|
+
}
|
|
1079
|
+
if (hasZustand) {
|
|
1080
|
+
files.push({
|
|
1081
|
+
relativePath: "src/shared/lib/store.ts",
|
|
1082
|
+
content: zustandStoreTemplate()
|
|
1083
|
+
});
|
|
1084
|
+
}
|
|
1085
|
+
if (cart.css === "TAILWIND") {
|
|
1086
|
+
files.push({ relativePath: "src/index.css", content: stylesCssTemplate() });
|
|
1087
|
+
}
|
|
1088
|
+
if (cart.linter === "BIOME") {
|
|
1089
|
+
files.push({ relativePath: "biome.json", content: biomeConfigTemplate() });
|
|
1090
|
+
} else if (cart.linter === "ESLINT") {
|
|
1091
|
+
files.push({ relativePath: "eslint.config.js", content: eslintConfigTemplate() });
|
|
1092
|
+
}
|
|
1093
|
+
return files;
|
|
1094
|
+
};
|
|
1095
|
+
}
|
|
1096
|
+
});
|
|
1097
|
+
|
|
1098
|
+
// src/scaffold/react-vite/templates/query.ts
|
|
1099
|
+
var queryProviderBprTemplate, simpleProviderBprTemplate;
|
|
1100
|
+
var init_query = __esm({
|
|
1101
|
+
"src/scaffold/react-vite/templates/query.ts"() {
|
|
1102
|
+
"use strict";
|
|
1103
|
+
queryProviderBprTemplate = () => `import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
1104
|
+
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
|
|
1105
|
+
|
|
1106
|
+
const queryClient = new QueryClient();
|
|
1107
|
+
|
|
1108
|
+
export const AppProvider = ({ children }: { children: React.ReactNode }) => {
|
|
1109
|
+
return (
|
|
1110
|
+
<QueryClientProvider client={queryClient}>
|
|
1111
|
+
{children}
|
|
1112
|
+
<ReactQueryDevtools initialIsOpen={false} />
|
|
1113
|
+
</QueryClientProvider>
|
|
1114
|
+
);
|
|
1115
|
+
};
|
|
1116
|
+
`;
|
|
1117
|
+
simpleProviderBprTemplate = () => `export const AppProvider = ({ children }: { children: React.ReactNode }) => {
|
|
1118
|
+
return <>{children}</>;
|
|
1119
|
+
};
|
|
1120
|
+
`;
|
|
1121
|
+
}
|
|
1122
|
+
});
|
|
1123
|
+
|
|
1124
|
+
// src/scaffold/react-vite/templates/bpr-layout.ts
|
|
1125
|
+
var getBprFileMap;
|
|
1126
|
+
var init_bpr_layout = __esm({
|
|
1127
|
+
"src/scaffold/react-vite/templates/bpr-layout.ts"() {
|
|
1128
|
+
"use strict";
|
|
1129
|
+
init_package_json();
|
|
1130
|
+
init_vite_config();
|
|
1131
|
+
init_tsconfig();
|
|
1132
|
+
init_index_html();
|
|
1133
|
+
init_main_tsx();
|
|
1134
|
+
init_app_tsx();
|
|
1135
|
+
init_router();
|
|
1136
|
+
init_zustand();
|
|
1137
|
+
init_query();
|
|
1138
|
+
init_linter();
|
|
1139
|
+
init_gitignore();
|
|
1140
|
+
init_styles();
|
|
1141
|
+
init_vite_env_d_ts();
|
|
1142
|
+
init_copilot_instructions();
|
|
1143
|
+
getBprFileMap = (cart) => {
|
|
1144
|
+
const hasRouter = cart.router === "TANSTACK_ROUTER";
|
|
1145
|
+
const hasZustand = cart.stateManagement === "ZUSTAND";
|
|
1146
|
+
const hasQuery = cart.query === "TANSTACK_QUERY";
|
|
1147
|
+
const files = [
|
|
1148
|
+
{ relativePath: "package.json", content: packageJsonTemplate(cart) },
|
|
1149
|
+
{ relativePath: "vite.config.ts", content: viteConfigTemplate(cart) },
|
|
1150
|
+
{ relativePath: "tsconfig.json", content: tsconfigTemplate() },
|
|
1151
|
+
{ relativePath: "tsconfig.node.json", content: tsconfigNodeTemplate() },
|
|
1152
|
+
{ relativePath: "index.html", content: indexHtmlTemplate(cart.projectName) },
|
|
1153
|
+
{ relativePath: ".gitignore", content: gitignoreTemplate() },
|
|
1154
|
+
...getCopilotInstructionFiles(cart),
|
|
1155
|
+
{ relativePath: "src/vite-env.d.ts", content: viteEnvDtsTemplate() },
|
|
1156
|
+
{ relativePath: "src/main.tsx", content: mainTsxTemplate(cart) },
|
|
1157
|
+
{ relativePath: "src/App.tsx", content: appTsxBprTemplate(hasRouter, hasQuery) },
|
|
1158
|
+
{
|
|
1159
|
+
relativePath: "src/providers/index.tsx",
|
|
1160
|
+
content: hasQuery ? queryProviderBprTemplate() : simpleProviderBprTemplate()
|
|
1161
|
+
},
|
|
1162
|
+
{ relativePath: "src/assets/.gitkeep", content: "" },
|
|
1163
|
+
{ relativePath: "src/components/.gitkeep", content: "" },
|
|
1164
|
+
{ relativePath: "src/config/.gitkeep", content: "" },
|
|
1165
|
+
{ relativePath: "src/features/.gitkeep", content: "" },
|
|
1166
|
+
{ relativePath: "src/hooks/.gitkeep", content: "" },
|
|
1167
|
+
{ relativePath: "src/lib/.gitkeep", content: "" },
|
|
1168
|
+
{ relativePath: "src/stores/.gitkeep", content: "" },
|
|
1169
|
+
{ relativePath: "src/types/.gitkeep", content: "" },
|
|
1170
|
+
{ relativePath: "src/utils/.gitkeep", content: "" }
|
|
1171
|
+
];
|
|
1172
|
+
if (hasRouter) {
|
|
1173
|
+
files.push(
|
|
1174
|
+
{ relativePath: "src/routes/__root.tsx", content: rootRouteTemplate() },
|
|
1175
|
+
{ relativePath: "src/routes/index.tsx", content: indexRouteTemplate() }
|
|
1176
|
+
);
|
|
1177
|
+
}
|
|
1178
|
+
if (hasZustand) {
|
|
1179
|
+
files.push({
|
|
1180
|
+
relativePath: "src/stores/appStore.ts",
|
|
1181
|
+
content: zustandStoreTemplate()
|
|
1182
|
+
});
|
|
1183
|
+
}
|
|
1184
|
+
if (cart.css === "TAILWIND") {
|
|
1185
|
+
files.push({ relativePath: "src/index.css", content: stylesCssTemplate() });
|
|
1186
|
+
}
|
|
1187
|
+
if (cart.linter === "BIOME") {
|
|
1188
|
+
files.push({ relativePath: "biome.json", content: biomeConfigTemplate() });
|
|
1189
|
+
} else if (cart.linter === "ESLINT") {
|
|
1190
|
+
files.push({ relativePath: "eslint.config.js", content: eslintConfigTemplate() });
|
|
1191
|
+
}
|
|
1192
|
+
return files;
|
|
1193
|
+
};
|
|
1194
|
+
}
|
|
1195
|
+
});
|
|
1196
|
+
|
|
1197
|
+
// src/scaffold/react-vite/index.ts
|
|
1198
|
+
var react_vite_exports = {};
|
|
1199
|
+
__export(react_vite_exports, {
|
|
1200
|
+
scaffoldReactVite: () => scaffoldReactVite
|
|
1201
|
+
});
|
|
1202
|
+
import path2 from "path";
|
|
1203
|
+
import chalk from "chalk";
|
|
1204
|
+
import { createSpinner } from "nanospinner";
|
|
1205
|
+
var scaffoldReactVite;
|
|
1206
|
+
var init_react_vite = __esm({
|
|
1207
|
+
"src/scaffold/react-vite/index.ts"() {
|
|
1208
|
+
"use strict";
|
|
1209
|
+
init_constants();
|
|
1210
|
+
init_utils();
|
|
1211
|
+
init_errors();
|
|
1212
|
+
init_fsd_layout();
|
|
1213
|
+
init_bpr_layout();
|
|
1214
|
+
scaffoldReactVite = async (cart) => {
|
|
1215
|
+
if (!cart || cart.type !== MENU_OPTIONS_LEVEL_1.ReactVite.value) return;
|
|
1216
|
+
const { projectName } = cart;
|
|
1217
|
+
const projectRoot = path2.resolve(process.cwd(), projectName);
|
|
1218
|
+
if (await dirExists(projectRoot)) {
|
|
1219
|
+
throw new ScaffoldError(
|
|
1220
|
+
`Directory "${projectName}" already exists. Please choose a different project name or remove the existing directory.`
|
|
1221
|
+
);
|
|
1222
|
+
}
|
|
1223
|
+
const spinner = createSpinner(`Scaffolding ${chalk.cyan(projectName)}...`).start();
|
|
1224
|
+
try {
|
|
1225
|
+
const fileMap = cart.layout === "FSD" ? getFsdFileMap(cart) : getBprFileMap(cart);
|
|
1226
|
+
for (const { relativePath, content } of fileMap) {
|
|
1227
|
+
await writeProjectFile(projectRoot, relativePath, content);
|
|
1228
|
+
}
|
|
1229
|
+
spinner.success({
|
|
1230
|
+
text: chalk.green(`Project ${chalk.bold(projectName)} created successfully!`)
|
|
1231
|
+
});
|
|
1232
|
+
console.log("");
|
|
1233
|
+
console.log(chalk.whiteBright("Next steps:"));
|
|
1234
|
+
console.log(chalk.cyan(` cd ${projectName}`));
|
|
1235
|
+
console.log(chalk.cyan(" npm install"));
|
|
1236
|
+
console.log(chalk.cyan(" npm run dev"));
|
|
1237
|
+
if (cart.router === "TANSTACK_ROUTER") {
|
|
1238
|
+
console.log("");
|
|
1239
|
+
console.log(chalk.gray(" Note: TanStack Router will auto-generate routeTree.gen.ts on first dev run."));
|
|
1240
|
+
}
|
|
1241
|
+
console.log("");
|
|
1242
|
+
} catch (err) {
|
|
1243
|
+
spinner.error({ text: chalk.red("Scaffolding failed.") });
|
|
1244
|
+
if (err instanceof ScaffoldError) {
|
|
1245
|
+
console.error(chalk.red(err.message));
|
|
1246
|
+
} else if (isNodeError(err)) {
|
|
1247
|
+
console.error(chalk.red(`File system error (${err.code}): ${err.message}`));
|
|
1248
|
+
} else {
|
|
1249
|
+
console.error(chalk.red("An unexpected error occurred."), err);
|
|
1250
|
+
}
|
|
1251
|
+
try {
|
|
1252
|
+
const { rm } = await import("fs/promises");
|
|
1253
|
+
await rm(projectRoot, { recursive: true, force: true });
|
|
1254
|
+
} catch {
|
|
1255
|
+
}
|
|
1256
|
+
process.exit(1);
|
|
1257
|
+
}
|
|
1258
|
+
};
|
|
1259
|
+
}
|
|
1260
|
+
});
|
|
1261
|
+
|
|
1262
|
+
// src/options/react-vite/index.ts
|
|
1263
|
+
var react_vite_exports2 = {};
|
|
1264
|
+
__export(react_vite_exports2, {
|
|
1265
|
+
flowReactVite: () => flowReactVite,
|
|
1266
|
+
menuCss: () => menuCss,
|
|
1267
|
+
menuLayout: () => menuLayout,
|
|
1268
|
+
menuLinter: () => menuLinter,
|
|
1269
|
+
menuQuery: () => menuQuery,
|
|
1270
|
+
menuRouter: () => menuRouter,
|
|
1271
|
+
menuStateManagement: () => menuStateManagement
|
|
1272
|
+
});
|
|
1273
|
+
import { select, input, Separator } from "@inquirer/prompts";
|
|
1274
|
+
import chalk2 from "chalk";
|
|
1275
|
+
var selectFromMenu, menuProjectName, menuLayout, menuRouter, menuStateManagement, menuQuery, menuCss, menuLinter, flowReactVite;
|
|
1276
|
+
var init_react_vite2 = __esm({
|
|
1277
|
+
"src/options/react-vite/index.ts"() {
|
|
1278
|
+
"use strict";
|
|
1279
|
+
init_constants();
|
|
1280
|
+
init_constants2();
|
|
1281
|
+
selectFromMenu = async (menuOptions, message) => {
|
|
1282
|
+
const keys = Object.keys(menuOptions);
|
|
1283
|
+
const enabledKeys = keys.filter((key) => !menuOptions[key].disabled);
|
|
1284
|
+
const choices = enabledKeys.map((key) => ({
|
|
1285
|
+
name: menuOptions[key].display,
|
|
1286
|
+
value: menuOptions[key].value,
|
|
1287
|
+
description: menuOptions[key].description
|
|
1288
|
+
}));
|
|
1289
|
+
const answer = await select({
|
|
1290
|
+
message: chalk2.whiteBright(message),
|
|
1291
|
+
choices: [...choices, new Separator()]
|
|
1292
|
+
});
|
|
1293
|
+
return answer;
|
|
1294
|
+
};
|
|
1295
|
+
menuProjectName = async (cart) => {
|
|
1296
|
+
if (!cart || cart.type !== MENU_OPTIONS_LEVEL_1.ReactVite.value) return;
|
|
1297
|
+
cart.projectName = await input({
|
|
1298
|
+
message: chalk2.whiteBright("Project name:"),
|
|
1299
|
+
validate: (value) => {
|
|
1300
|
+
if (!value.trim()) return "Project name cannot be empty";
|
|
1301
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(value.trim()))
|
|
1302
|
+
return "Only letters, numbers, hyphens, and underscores allowed";
|
|
1303
|
+
return true;
|
|
1304
|
+
},
|
|
1305
|
+
transformer: (value) => value.trim()
|
|
1306
|
+
});
|
|
1307
|
+
};
|
|
1308
|
+
menuLayout = async (cart) => {
|
|
1309
|
+
if (!cart || cart.type !== MENU_OPTIONS_LEVEL_1.ReactVite.value) return;
|
|
1310
|
+
cart.layout = await selectFromMenu(REACT_MENU_LAYOUT, "Choose a layout");
|
|
1311
|
+
};
|
|
1312
|
+
menuRouter = async (cart) => {
|
|
1313
|
+
if (!cart || cart.type !== MENU_OPTIONS_LEVEL_1.ReactVite.value) return;
|
|
1314
|
+
cart.router = await selectFromMenu(REACT_MENU_ROUTER, "Choose a Router");
|
|
1315
|
+
};
|
|
1316
|
+
menuStateManagement = async (cart) => {
|
|
1317
|
+
if (!cart || cart.type !== MENU_OPTIONS_LEVEL_1.ReactVite.value) return;
|
|
1318
|
+
cart.stateManagement = await selectFromMenu(
|
|
1319
|
+
REACT_MENU_STATE_MANAGEMENT,
|
|
1320
|
+
"Choose a State Management"
|
|
1321
|
+
);
|
|
1322
|
+
};
|
|
1323
|
+
menuQuery = async (cart) => {
|
|
1324
|
+
if (!cart || cart.type !== MENU_OPTIONS_LEVEL_1.ReactVite.value) return;
|
|
1325
|
+
cart.query = await selectFromMenu(
|
|
1326
|
+
REACT_MENU_QUERY,
|
|
1327
|
+
"Choose a Data Fetching library"
|
|
1328
|
+
);
|
|
1329
|
+
};
|
|
1330
|
+
menuCss = async (cart) => {
|
|
1331
|
+
if (!cart || cart.type !== MENU_OPTIONS_LEVEL_1.ReactVite.value) return;
|
|
1332
|
+
cart.css = await selectFromMenu(REACT_MENU_CSS, "Choose a CSS framework");
|
|
1333
|
+
};
|
|
1334
|
+
menuLinter = async (cart) => {
|
|
1335
|
+
if (!cart || cart.type !== MENU_OPTIONS_LEVEL_1.ReactVite.value) return;
|
|
1336
|
+
cart.linter = await selectFromMenu(
|
|
1337
|
+
REACT_MENU_LINTER,
|
|
1338
|
+
"Choose a Linter / Formatter"
|
|
1339
|
+
);
|
|
1340
|
+
};
|
|
1341
|
+
flowReactVite = async (cart) => {
|
|
1342
|
+
if (!cart || cart.type !== MENU_OPTIONS_LEVEL_1.ReactVite.value) return;
|
|
1343
|
+
await menuProjectName(cart);
|
|
1344
|
+
await menuLayout(cart);
|
|
1345
|
+
await menuRouter(cart);
|
|
1346
|
+
await menuStateManagement(cart);
|
|
1347
|
+
await menuQuery(cart);
|
|
1348
|
+
await menuCss(cart);
|
|
1349
|
+
await menuLinter(cart);
|
|
1350
|
+
const { scaffoldReactVite: scaffoldReactVite2 } = await Promise.resolve().then(() => (init_react_vite(), react_vite_exports));
|
|
1351
|
+
await scaffoldReactVite2(cart);
|
|
1352
|
+
};
|
|
1353
|
+
}
|
|
1354
|
+
});
|
|
1355
|
+
|
|
1356
|
+
// src/options/chrome-extension/constants/index.ts
|
|
1357
|
+
var CHROME_MENU_STATE_MANAGEMENT, CHROME_MENU_QUERY, CHROME_MENU_CSS, CHROME_MENU_LINTER;
|
|
1358
|
+
var init_constants3 = __esm({
|
|
1359
|
+
"src/options/chrome-extension/constants/index.ts"() {
|
|
1360
|
+
"use strict";
|
|
1361
|
+
CHROME_MENU_STATE_MANAGEMENT = {
|
|
1362
|
+
notUsing: {
|
|
1363
|
+
display: "Not Using",
|
|
1364
|
+
value: "NOT_USING",
|
|
1365
|
+
description: "Not using any state management",
|
|
1366
|
+
disabled: false
|
|
1367
|
+
},
|
|
1368
|
+
zustand: {
|
|
1369
|
+
display: "Zustand",
|
|
1370
|
+
value: "ZUSTAND",
|
|
1371
|
+
description: "Zustand state management",
|
|
1372
|
+
disabled: false
|
|
1373
|
+
}
|
|
1374
|
+
};
|
|
1375
|
+
CHROME_MENU_QUERY = {
|
|
1376
|
+
notUsing: {
|
|
1377
|
+
display: "Not Using",
|
|
1378
|
+
value: "NOT_USING",
|
|
1379
|
+
description: "No data fetching library",
|
|
1380
|
+
disabled: false
|
|
1381
|
+
},
|
|
1382
|
+
tanstackQuery: {
|
|
1383
|
+
display: "TanStack Query",
|
|
1384
|
+
value: "TANSTACK_QUERY",
|
|
1385
|
+
description: "Server state management (v5.74.4)",
|
|
1386
|
+
disabled: false
|
|
1387
|
+
}
|
|
1388
|
+
};
|
|
1389
|
+
CHROME_MENU_CSS = {
|
|
1390
|
+
notUsing: {
|
|
1391
|
+
display: "Not Using",
|
|
1392
|
+
value: "NOT_USING",
|
|
1393
|
+
description: "No CSS framework",
|
|
1394
|
+
disabled: false
|
|
1395
|
+
},
|
|
1396
|
+
tailwind: {
|
|
1397
|
+
display: "Tailwind CSS",
|
|
1398
|
+
value: "TAILWIND",
|
|
1399
|
+
description: "Tailwind CSS v4.1.3 (Vite plugin, zero-config)",
|
|
1400
|
+
disabled: false
|
|
1401
|
+
}
|
|
1402
|
+
};
|
|
1403
|
+
CHROME_MENU_LINTER = {
|
|
1404
|
+
notUsing: {
|
|
1405
|
+
display: "Not Using",
|
|
1406
|
+
value: "NOT_USING",
|
|
1407
|
+
description: "No linter/formatter",
|
|
1408
|
+
disabled: false
|
|
1409
|
+
},
|
|
1410
|
+
biome: {
|
|
1411
|
+
display: "Biome",
|
|
1412
|
+
value: "BIOME",
|
|
1413
|
+
description: "Fast all-in-one: lint + format (v1.9.4)",
|
|
1414
|
+
disabled: false
|
|
1415
|
+
},
|
|
1416
|
+
eslint: {
|
|
1417
|
+
display: "ESLint",
|
|
1418
|
+
value: "ESLINT",
|
|
1419
|
+
description: "ESLint v9 (flat config) + typescript-eslint",
|
|
1420
|
+
disabled: false
|
|
1421
|
+
}
|
|
1422
|
+
};
|
|
1423
|
+
}
|
|
1424
|
+
});
|
|
1425
|
+
|
|
1426
|
+
// src/scaffold/chrome-extension/templates/package-json.ts
|
|
1427
|
+
var packageJsonTemplate2;
|
|
1428
|
+
var init_package_json2 = __esm({
|
|
1429
|
+
"src/scaffold/chrome-extension/templates/package-json.ts"() {
|
|
1430
|
+
"use strict";
|
|
1431
|
+
packageJsonTemplate2 = (cart) => {
|
|
1432
|
+
const deps = {
|
|
1433
|
+
react: "19.1.0",
|
|
1434
|
+
"react-dom": "19.1.0"
|
|
1435
|
+
};
|
|
1436
|
+
if (cart.stateManagement === "ZUSTAND") {
|
|
1437
|
+
deps["zustand"] = "5.0.5";
|
|
1438
|
+
}
|
|
1439
|
+
if (cart.query === "TANSTACK_QUERY") {
|
|
1440
|
+
deps["@tanstack/react-query"] = "5.74.4";
|
|
1441
|
+
deps["@tanstack/react-query-devtools"] = "5.74.4";
|
|
1442
|
+
}
|
|
1443
|
+
const devDeps = {
|
|
1444
|
+
"@types/react": "19.1.1",
|
|
1445
|
+
"@types/react-dom": "19.1.1",
|
|
1446
|
+
"@vitejs/plugin-react": "4.4.1",
|
|
1447
|
+
typescript: "5.8.3",
|
|
1448
|
+
vite: "6.3.1"
|
|
1449
|
+
};
|
|
1450
|
+
if (cart.css === "TAILWIND") {
|
|
1451
|
+
devDeps["@tailwindcss/vite"] = "4.1.3";
|
|
1452
|
+
devDeps["tailwindcss"] = "4.1.3";
|
|
1453
|
+
}
|
|
1454
|
+
if (cart.linter === "BIOME") {
|
|
1455
|
+
devDeps["@biomejs/biome"] = "1.9.4";
|
|
1456
|
+
} else if (cart.linter === "ESLINT") {
|
|
1457
|
+
devDeps["@eslint/js"] = "9.22.0";
|
|
1458
|
+
devDeps["eslint"] = "9.22.0";
|
|
1459
|
+
devDeps["eslint-plugin-react-hooks"] = "5.2.0";
|
|
1460
|
+
devDeps["eslint-plugin-react-refresh"] = "0.4.19";
|
|
1461
|
+
devDeps["globals"] = "15.15.0";
|
|
1462
|
+
devDeps["typescript-eslint"] = "8.26.0";
|
|
1463
|
+
}
|
|
1464
|
+
const scripts = {
|
|
1465
|
+
dev: "vite",
|
|
1466
|
+
build: "tsc && vite build",
|
|
1467
|
+
"build-extension": "node scripts/build-extension.js",
|
|
1468
|
+
preview: "vite preview"
|
|
1469
|
+
};
|
|
1470
|
+
if (cart.linter === "BIOME") {
|
|
1471
|
+
scripts["lint"] = "biome check .";
|
|
1472
|
+
scripts["format"] = "biome format --write .";
|
|
1473
|
+
} else if (cart.linter === "ESLINT") {
|
|
1474
|
+
scripts["lint"] = "eslint .";
|
|
1475
|
+
}
|
|
1476
|
+
return JSON.stringify(
|
|
1477
|
+
{
|
|
1478
|
+
name: cart.projectName,
|
|
1479
|
+
private: true,
|
|
1480
|
+
version: "0.0.0",
|
|
1481
|
+
type: "module",
|
|
1482
|
+
scripts,
|
|
1483
|
+
dependencies: deps,
|
|
1484
|
+
devDependencies: devDeps
|
|
1485
|
+
},
|
|
1486
|
+
null,
|
|
1487
|
+
2
|
|
1488
|
+
);
|
|
1489
|
+
};
|
|
1490
|
+
}
|
|
1491
|
+
});
|
|
1492
|
+
|
|
1493
|
+
// src/scaffold/chrome-extension/templates/vite-config.ts
|
|
1494
|
+
var viteConfigTemplate2;
|
|
1495
|
+
var init_vite_config2 = __esm({
|
|
1496
|
+
"src/scaffold/chrome-extension/templates/vite-config.ts"() {
|
|
1497
|
+
"use strict";
|
|
1498
|
+
viteConfigTemplate2 = (cart) => {
|
|
1499
|
+
const hasTailwind = cart.css === "TAILWIND";
|
|
1500
|
+
const imports = [
|
|
1501
|
+
`import { defineConfig } from 'vite';`,
|
|
1502
|
+
`import react from '@vitejs/plugin-react';`,
|
|
1503
|
+
hasTailwind ? `import tailwindcss from '@tailwindcss/vite';` : ""
|
|
1504
|
+
].filter(Boolean).join("\n");
|
|
1505
|
+
const plugins = ["react()", hasTailwind ? "tailwindcss()" : ""].filter(Boolean).join(",\n ");
|
|
1506
|
+
return `${imports}
|
|
1507
|
+
|
|
1508
|
+
export default defineConfig({
|
|
1509
|
+
plugins: [
|
|
1510
|
+
${plugins},
|
|
1511
|
+
],
|
|
1512
|
+
build: {
|
|
1513
|
+
outDir: 'dist',
|
|
1514
|
+
rollupOptions: {
|
|
1515
|
+
input: {
|
|
1516
|
+
main: 'index.html',
|
|
1517
|
+
},
|
|
1518
|
+
},
|
|
1519
|
+
},
|
|
1520
|
+
});
|
|
1521
|
+
`;
|
|
1522
|
+
};
|
|
1523
|
+
}
|
|
1524
|
+
});
|
|
1525
|
+
|
|
1526
|
+
// src/scaffold/chrome-extension/templates/manifest-json.ts
|
|
1527
|
+
var manifestJsonTemplate;
|
|
1528
|
+
var init_manifest_json = __esm({
|
|
1529
|
+
"src/scaffold/chrome-extension/templates/manifest-json.ts"() {
|
|
1530
|
+
"use strict";
|
|
1531
|
+
manifestJsonTemplate = (projectName) => JSON.stringify(
|
|
1532
|
+
{
|
|
1533
|
+
manifest_version: 3,
|
|
1534
|
+
name: projectName,
|
|
1535
|
+
description: "",
|
|
1536
|
+
version: "1.0.0",
|
|
1537
|
+
action: {
|
|
1538
|
+
default_popup: "index.html"
|
|
1539
|
+
},
|
|
1540
|
+
permissions: []
|
|
1541
|
+
},
|
|
1542
|
+
null,
|
|
1543
|
+
2
|
|
1544
|
+
);
|
|
1545
|
+
}
|
|
1546
|
+
});
|
|
1547
|
+
|
|
1548
|
+
// src/scaffold/chrome-extension/templates/build-extension-script.ts
|
|
1549
|
+
var buildExtensionScriptTemplate;
|
|
1550
|
+
var init_build_extension_script = __esm({
|
|
1551
|
+
"src/scaffold/chrome-extension/templates/build-extension-script.ts"() {
|
|
1552
|
+
"use strict";
|
|
1553
|
+
buildExtensionScriptTemplate = () => `import { execSync } from 'child_process';
|
|
1554
|
+
import { copyFileSync } from 'fs';
|
|
1555
|
+
import { join, dirname } from 'path';
|
|
1556
|
+
import { fileURLToPath } from 'url';
|
|
1557
|
+
|
|
1558
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
1559
|
+
const root = join(__dirname, '..');
|
|
1560
|
+
|
|
1561
|
+
execSync('npm run build', { stdio: 'inherit' });
|
|
1562
|
+
|
|
1563
|
+
copyFileSync(join(root, 'manifest.json'), join(root, 'dist', 'manifest.json'));
|
|
1564
|
+
|
|
1565
|
+
console.log('Build extension completed');
|
|
1566
|
+
`;
|
|
1567
|
+
}
|
|
1568
|
+
});
|
|
1569
|
+
|
|
1570
|
+
// src/scaffold/chrome-extension/templates/main-tsx.ts
|
|
1571
|
+
var mainTsxTemplate2;
|
|
1572
|
+
var init_main_tsx2 = __esm({
|
|
1573
|
+
"src/scaffold/chrome-extension/templates/main-tsx.ts"() {
|
|
1574
|
+
"use strict";
|
|
1575
|
+
mainTsxTemplate2 = (cart) => {
|
|
1576
|
+
const hasTailwind = cart.css === "TAILWIND";
|
|
1577
|
+
return `import { StrictMode } from 'react';
|
|
1578
|
+
import { createRoot } from 'react-dom/client';
|
|
1579
|
+
${hasTailwind ? "import './index.css';\n" : ""}import App from './App';
|
|
1580
|
+
|
|
1581
|
+
createRoot(document.getElementById('root')!).render(
|
|
1582
|
+
<StrictMode>
|
|
1583
|
+
<App />
|
|
1584
|
+
</StrictMode>,
|
|
1585
|
+
);
|
|
1586
|
+
`;
|
|
1587
|
+
};
|
|
1588
|
+
}
|
|
1589
|
+
});
|
|
1590
|
+
|
|
1591
|
+
// src/scaffold/chrome-extension/templates/app-tsx.ts
|
|
1592
|
+
var appTsxTemplate;
|
|
1593
|
+
var init_app_tsx2 = __esm({
|
|
1594
|
+
"src/scaffold/chrome-extension/templates/app-tsx.ts"() {
|
|
1595
|
+
"use strict";
|
|
1596
|
+
appTsxTemplate = (cart) => {
|
|
1597
|
+
const hasQuery = cart.query === "TANSTACK_QUERY";
|
|
1598
|
+
if (hasQuery) {
|
|
1599
|
+
return `import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
1600
|
+
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
|
|
1601
|
+
|
|
1602
|
+
const queryClient = new QueryClient();
|
|
1603
|
+
|
|
1604
|
+
const App = () => {
|
|
1605
|
+
return (
|
|
1606
|
+
<QueryClientProvider client={queryClient}>
|
|
1607
|
+
<div>
|
|
1608
|
+
<h1>${"${cart.projectName}"}</h1>
|
|
1609
|
+
</div>
|
|
1610
|
+
<ReactQueryDevtools initialIsOpen={false} />
|
|
1611
|
+
</QueryClientProvider>
|
|
1612
|
+
);
|
|
1613
|
+
};
|
|
1614
|
+
|
|
1615
|
+
export default App;
|
|
1616
|
+
`.replace("${cart.projectName}", cart.projectName);
|
|
1617
|
+
}
|
|
1618
|
+
return `const App = () => {
|
|
1619
|
+
return (
|
|
1620
|
+
<div>
|
|
1621
|
+
<h1>${cart.projectName}</h1>
|
|
1622
|
+
</div>
|
|
1623
|
+
);
|
|
1624
|
+
};
|
|
1625
|
+
|
|
1626
|
+
export default App;
|
|
1627
|
+
`;
|
|
1628
|
+
};
|
|
1629
|
+
}
|
|
1630
|
+
});
|
|
1631
|
+
|
|
1632
|
+
// src/scaffold/chrome-extension/templates/layout.ts
|
|
1633
|
+
var getChromeExtensionFileMap;
|
|
1634
|
+
var init_layout = __esm({
|
|
1635
|
+
"src/scaffold/chrome-extension/templates/layout.ts"() {
|
|
1636
|
+
"use strict";
|
|
1637
|
+
init_package_json2();
|
|
1638
|
+
init_vite_config2();
|
|
1639
|
+
init_manifest_json();
|
|
1640
|
+
init_build_extension_script();
|
|
1641
|
+
init_main_tsx2();
|
|
1642
|
+
init_app_tsx2();
|
|
1643
|
+
init_vite_env_d_ts();
|
|
1644
|
+
init_tsconfig();
|
|
1645
|
+
init_index_html();
|
|
1646
|
+
init_gitignore();
|
|
1647
|
+
init_styles();
|
|
1648
|
+
init_zustand();
|
|
1649
|
+
init_linter();
|
|
1650
|
+
getChromeExtensionFileMap = (cart) => {
|
|
1651
|
+
const hasZustand = cart.stateManagement === "ZUSTAND";
|
|
1652
|
+
const files = [
|
|
1653
|
+
{ relativePath: "package.json", content: packageJsonTemplate2(cart) },
|
|
1654
|
+
{ relativePath: "vite.config.ts", content: viteConfigTemplate2(cart) },
|
|
1655
|
+
{ relativePath: "tsconfig.json", content: tsconfigTemplate() },
|
|
1656
|
+
{ relativePath: "tsconfig.node.json", content: tsconfigNodeTemplate() },
|
|
1657
|
+
{ relativePath: "index.html", content: indexHtmlTemplate(cart.projectName) },
|
|
1658
|
+
{ relativePath: "manifest.json", content: manifestJsonTemplate(cart.projectName) },
|
|
1659
|
+
{ relativePath: ".gitignore", content: gitignoreTemplate() },
|
|
1660
|
+
{ relativePath: "scripts/build-extension.js", content: buildExtensionScriptTemplate() },
|
|
1661
|
+
{ relativePath: "src/vite-env.d.ts", content: viteEnvDtsTemplate() },
|
|
1662
|
+
{ relativePath: "src/main.tsx", content: mainTsxTemplate2(cart) },
|
|
1663
|
+
{ relativePath: "src/App.tsx", content: appTsxTemplate(cart) },
|
|
1664
|
+
{ relativePath: "src/components/.gitkeep", content: "" },
|
|
1665
|
+
{ relativePath: "src/hooks/.gitkeep", content: "" },
|
|
1666
|
+
{ relativePath: "src/lib/.gitkeep", content: "" },
|
|
1667
|
+
{ relativePath: "src/types/.gitkeep", content: "" },
|
|
1668
|
+
{ relativePath: "src/utils/.gitkeep", content: "" }
|
|
1669
|
+
];
|
|
1670
|
+
if (hasZustand) {
|
|
1671
|
+
files.push({ relativePath: "src/stores/appStore.ts", content: zustandStoreTemplate() });
|
|
1672
|
+
}
|
|
1673
|
+
if (cart.css === "TAILWIND") {
|
|
1674
|
+
files.push({ relativePath: "src/index.css", content: stylesCssTemplate() });
|
|
1675
|
+
}
|
|
1676
|
+
if (cart.linter === "BIOME") {
|
|
1677
|
+
files.push({ relativePath: "biome.json", content: biomeConfigTemplate() });
|
|
1678
|
+
} else if (cart.linter === "ESLINT") {
|
|
1679
|
+
files.push({ relativePath: "eslint.config.js", content: eslintConfigTemplate() });
|
|
1680
|
+
}
|
|
1681
|
+
return files;
|
|
1682
|
+
};
|
|
1683
|
+
}
|
|
1684
|
+
});
|
|
1685
|
+
|
|
1686
|
+
// src/scaffold/chrome-extension/index.ts
|
|
1687
|
+
var chrome_extension_exports = {};
|
|
1688
|
+
__export(chrome_extension_exports, {
|
|
1689
|
+
scaffoldChromeExtension: () => scaffoldChromeExtension
|
|
1690
|
+
});
|
|
1691
|
+
import path3 from "path";
|
|
1692
|
+
import chalk3 from "chalk";
|
|
1693
|
+
import { createSpinner as createSpinner2 } from "nanospinner";
|
|
1694
|
+
var scaffoldChromeExtension;
|
|
1695
|
+
var init_chrome_extension = __esm({
|
|
1696
|
+
"src/scaffold/chrome-extension/index.ts"() {
|
|
1697
|
+
"use strict";
|
|
1698
|
+
init_constants();
|
|
1699
|
+
init_utils();
|
|
1700
|
+
init_errors();
|
|
1701
|
+
init_layout();
|
|
1702
|
+
scaffoldChromeExtension = async (cart) => {
|
|
1703
|
+
if (!cart || cart.type !== MENU_OPTIONS_LEVEL_1.ChromeExtension.value) return;
|
|
1704
|
+
const { projectName } = cart;
|
|
1705
|
+
const projectRoot = path3.resolve(process.cwd(), projectName);
|
|
1706
|
+
if (await dirExists(projectRoot)) {
|
|
1707
|
+
throw new ScaffoldError(
|
|
1708
|
+
`Directory "${projectName}" already exists. Please choose a different project name or remove the existing directory.`
|
|
1709
|
+
);
|
|
1710
|
+
}
|
|
1711
|
+
const spinner = createSpinner2(`Scaffolding ${chalk3.cyan(projectName)}...`).start();
|
|
1712
|
+
try {
|
|
1713
|
+
const fileMap = getChromeExtensionFileMap(cart);
|
|
1714
|
+
for (const { relativePath, content } of fileMap) {
|
|
1715
|
+
await writeProjectFile(projectRoot, relativePath, content);
|
|
1716
|
+
}
|
|
1717
|
+
spinner.success({
|
|
1718
|
+
text: chalk3.green(`Project ${chalk3.bold(projectName)} created successfully!`)
|
|
1719
|
+
});
|
|
1720
|
+
console.log("");
|
|
1721
|
+
console.log(chalk3.whiteBright("Next steps:"));
|
|
1722
|
+
console.log(chalk3.cyan(` cd ${projectName}`));
|
|
1723
|
+
console.log(chalk3.cyan(" npm install"));
|
|
1724
|
+
console.log(chalk3.cyan(" npm run dev"));
|
|
1725
|
+
console.log("");
|
|
1726
|
+
console.log(chalk3.whiteBright("To build the extension:"));
|
|
1727
|
+
console.log(chalk3.cyan(" npm run build-extension"));
|
|
1728
|
+
console.log(chalk3.gray(" Then load the dist/ folder in Chrome at chrome://extensions"));
|
|
1729
|
+
console.log("");
|
|
1730
|
+
} catch (err) {
|
|
1731
|
+
spinner.error({ text: chalk3.red("Scaffolding failed.") });
|
|
1732
|
+
if (err instanceof ScaffoldError) {
|
|
1733
|
+
console.error(chalk3.red(err.message));
|
|
1734
|
+
} else if (isNodeError(err)) {
|
|
1735
|
+
console.error(chalk3.red(`File system error (${err.code}): ${err.message}`));
|
|
1736
|
+
} else {
|
|
1737
|
+
console.error(chalk3.red("An unexpected error occurred."), err);
|
|
1738
|
+
}
|
|
1739
|
+
try {
|
|
1740
|
+
const { rm } = await import("fs/promises");
|
|
1741
|
+
await rm(projectRoot, { recursive: true, force: true });
|
|
1742
|
+
} catch {
|
|
1743
|
+
}
|
|
1744
|
+
process.exit(1);
|
|
1745
|
+
}
|
|
1746
|
+
};
|
|
1747
|
+
}
|
|
1748
|
+
});
|
|
1749
|
+
|
|
1750
|
+
// src/options/chrome-extension/index.ts
|
|
1751
|
+
var chrome_extension_exports2 = {};
|
|
1752
|
+
__export(chrome_extension_exports2, {
|
|
1753
|
+
flowChromeExtension: () => flowChromeExtension
|
|
1754
|
+
});
|
|
1755
|
+
import { select as select2, input as input2, Separator as Separator2 } from "@inquirer/prompts";
|
|
1756
|
+
import chalk4 from "chalk";
|
|
1757
|
+
var selectFromMenu2, menuProjectName2, menuStateManagement2, menuQuery2, menuCss2, menuLinter2, flowChromeExtension;
|
|
1758
|
+
var init_chrome_extension2 = __esm({
|
|
1759
|
+
"src/options/chrome-extension/index.ts"() {
|
|
1760
|
+
"use strict";
|
|
1761
|
+
init_constants();
|
|
1762
|
+
init_constants3();
|
|
1763
|
+
selectFromMenu2 = async (menuOptions, message) => {
|
|
1764
|
+
const keys = Object.keys(menuOptions);
|
|
1765
|
+
const enabledKeys = keys.filter((key) => !menuOptions[key].disabled);
|
|
1766
|
+
const choices = enabledKeys.map((key) => ({
|
|
1767
|
+
name: menuOptions[key].display,
|
|
1768
|
+
value: menuOptions[key].value,
|
|
1769
|
+
description: menuOptions[key].description
|
|
1770
|
+
}));
|
|
1771
|
+
const answer = await select2({
|
|
1772
|
+
message: chalk4.whiteBright(message),
|
|
1773
|
+
choices: [...choices, new Separator2()]
|
|
1774
|
+
});
|
|
1775
|
+
return answer;
|
|
1776
|
+
};
|
|
1777
|
+
menuProjectName2 = async (cart) => {
|
|
1778
|
+
if (!cart || cart.type !== MENU_OPTIONS_LEVEL_1.ChromeExtension.value) return;
|
|
1779
|
+
cart.projectName = await input2({
|
|
1780
|
+
message: chalk4.whiteBright("Project name:"),
|
|
1781
|
+
validate: (value) => {
|
|
1782
|
+
if (!value.trim()) return "Project name cannot be empty";
|
|
1783
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(value.trim()))
|
|
1784
|
+
return "Only letters, numbers, hyphens, and underscores allowed";
|
|
1785
|
+
return true;
|
|
1786
|
+
},
|
|
1787
|
+
transformer: (value) => value.trim()
|
|
1788
|
+
});
|
|
1789
|
+
};
|
|
1790
|
+
menuStateManagement2 = async (cart) => {
|
|
1791
|
+
if (!cart || cart.type !== MENU_OPTIONS_LEVEL_1.ChromeExtension.value) return;
|
|
1792
|
+
cart.stateManagement = await selectFromMenu2(
|
|
1793
|
+
CHROME_MENU_STATE_MANAGEMENT,
|
|
1794
|
+
"Choose a State Management"
|
|
1795
|
+
);
|
|
1796
|
+
};
|
|
1797
|
+
menuQuery2 = async (cart) => {
|
|
1798
|
+
if (!cart || cart.type !== MENU_OPTIONS_LEVEL_1.ChromeExtension.value) return;
|
|
1799
|
+
cart.query = await selectFromMenu2(CHROME_MENU_QUERY, "Choose a Data Fetching library");
|
|
1800
|
+
};
|
|
1801
|
+
menuCss2 = async (cart) => {
|
|
1802
|
+
if (!cart || cart.type !== MENU_OPTIONS_LEVEL_1.ChromeExtension.value) return;
|
|
1803
|
+
cart.css = await selectFromMenu2(CHROME_MENU_CSS, "Choose a CSS framework");
|
|
1804
|
+
};
|
|
1805
|
+
menuLinter2 = async (cart) => {
|
|
1806
|
+
if (!cart || cart.type !== MENU_OPTIONS_LEVEL_1.ChromeExtension.value) return;
|
|
1807
|
+
cart.linter = await selectFromMenu2(CHROME_MENU_LINTER, "Choose a Linter / Formatter");
|
|
1808
|
+
};
|
|
1809
|
+
flowChromeExtension = async (cart) => {
|
|
1810
|
+
if (!cart || cart.type !== MENU_OPTIONS_LEVEL_1.ChromeExtension.value) return;
|
|
1811
|
+
await menuProjectName2(cart);
|
|
1812
|
+
await menuStateManagement2(cart);
|
|
1813
|
+
await menuQuery2(cart);
|
|
1814
|
+
await menuCss2(cart);
|
|
1815
|
+
await menuLinter2(cart);
|
|
1816
|
+
const { scaffoldChromeExtension: scaffoldChromeExtension2 } = await Promise.resolve().then(() => (init_chrome_extension(), chrome_extension_exports));
|
|
1817
|
+
await scaffoldChromeExtension2(cart);
|
|
1818
|
+
};
|
|
1819
|
+
}
|
|
1820
|
+
});
|
|
1821
|
+
|
|
1822
|
+
// src/options/index.ts
|
|
1823
|
+
init_constants();
|
|
1824
|
+
import { select as select3, Separator as Separator3 } from "@inquirer/prompts";
|
|
1825
|
+
import chalk5 from "chalk";
|
|
1826
|
+
var menu = async () => {
|
|
1827
|
+
const keys = Object.keys(MENU_OPTIONS_LEVEL_1);
|
|
1828
|
+
const enabledKeys = keys.filter((key) => !MENU_OPTIONS_LEVEL_1[key].disabled);
|
|
1829
|
+
const choices = [
|
|
1830
|
+
...enabledKeys.map((key) => ({
|
|
1831
|
+
name: MENU_OPTIONS_LEVEL_1[key].display,
|
|
1832
|
+
value: MENU_OPTIONS_LEVEL_1[key].value,
|
|
1833
|
+
description: MENU_OPTIONS_LEVEL_1[key].description
|
|
1834
|
+
})),
|
|
1835
|
+
new Separator3(),
|
|
1836
|
+
{
|
|
1837
|
+
name: MENU_OPTIONS_LEVEL_1.Nuxt.display,
|
|
1838
|
+
value: MENU_OPTIONS_LEVEL_1.Nuxt.value,
|
|
1839
|
+
disabled: MENU_OPTIONS_LEVEL_1.Nuxt.description
|
|
1840
|
+
}
|
|
1841
|
+
];
|
|
1842
|
+
const answer = await select3({
|
|
1843
|
+
message: chalk5.whiteBright("How can I help you \u{1F468}\u200D\u{1F373}"),
|
|
1844
|
+
choices
|
|
1845
|
+
});
|
|
1846
|
+
const cart = { type: answer };
|
|
1847
|
+
if (answer === MENU_OPTIONS_LEVEL_1.ReactVite.value) {
|
|
1848
|
+
const { flowReactVite: flowReactVite2 } = await Promise.resolve().then(() => (init_react_vite2(), react_vite_exports2));
|
|
1849
|
+
await flowReactVite2(cart);
|
|
1850
|
+
} else if (answer === MENU_OPTIONS_LEVEL_1.ChromeExtension.value) {
|
|
1851
|
+
const { flowChromeExtension: flowChromeExtension2 } = await Promise.resolve().then(() => (init_chrome_extension2(), chrome_extension_exports2));
|
|
1852
|
+
await flowChromeExtension2(cart);
|
|
1853
|
+
}
|
|
1854
|
+
return cart;
|
|
1855
|
+
};
|
|
1856
|
+
|
|
1857
|
+
// src/utils/index.ts
|
|
1858
|
+
var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
1859
|
+
|
|
1860
|
+
// src/utils/animation.ts
|
|
1861
|
+
var typeWriter = async (text, colorFunc, speed = 50) => {
|
|
1862
|
+
for (const char of text) {
|
|
1863
|
+
process.stdout.write(colorFunc(char));
|
|
1864
|
+
await sleep(speed);
|
|
1865
|
+
}
|
|
1866
|
+
process.stdout.write("\n");
|
|
1867
|
+
};
|
|
1868
|
+
|
|
1869
|
+
// src/utils/user.ts
|
|
1870
|
+
import { execSync } from "child_process";
|
|
1871
|
+
var getUserName = () => {
|
|
1872
|
+
const username = execSync("git config --global user.name").toString().trim();
|
|
1873
|
+
return username || process.env.USER || process.env.USERNAME || "Guest";
|
|
1874
|
+
};
|
|
1875
|
+
|
|
1876
|
+
// src/index.ts
|
|
1877
|
+
import chalk6 from "chalk";
|
|
1878
|
+
import { readFileSync } from "fs";
|
|
1879
|
+
import { dirname, join } from "path";
|
|
1880
|
+
import { fileURLToPath } from "url";
|
|
1881
|
+
var __dirname = dirname(fileURLToPath(import.meta.url));
|
|
1882
|
+
var getVersion = () => {
|
|
1883
|
+
try {
|
|
1884
|
+
const pkg = JSON.parse(readFileSync(join(__dirname, "../package.json"), "utf-8"));
|
|
1885
|
+
return pkg.version;
|
|
1886
|
+
} catch {
|
|
1887
|
+
return "unknown";
|
|
1888
|
+
}
|
|
1889
|
+
};
|
|
1890
|
+
var main = async () => {
|
|
1891
|
+
const args = process.argv.slice(2);
|
|
1892
|
+
if (args.includes("--version") || args.includes("-v")) {
|
|
1893
|
+
console.log(`bver-build v${getVersion()}`);
|
|
1894
|
+
process.exit(0);
|
|
1895
|
+
}
|
|
1896
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
1897
|
+
console.log(`
|
|
1898
|
+
bver-build - Interactive CLI for scaffolding modern web projects
|
|
1899
|
+
|
|
1900
|
+
Usage:
|
|
1901
|
+
beaver [options]
|
|
1902
|
+
|
|
1903
|
+
Options:
|
|
1904
|
+
-v, --version Show version
|
|
1905
|
+
-h, --help Show help
|
|
1906
|
+
`);
|
|
1907
|
+
process.exit(0);
|
|
1908
|
+
}
|
|
1909
|
+
await typeWriter(`Hi! ${getUserName()} \u{1F646}`, chalk6.whiteBright, 50);
|
|
1910
|
+
await sleep(500);
|
|
1911
|
+
try {
|
|
1912
|
+
await menu();
|
|
1913
|
+
} catch (err) {
|
|
1914
|
+
if (err instanceof Error) {
|
|
1915
|
+
console.error(chalk6.red(err.message));
|
|
1916
|
+
}
|
|
1917
|
+
process.exit(1);
|
|
1918
|
+
}
|
|
1919
|
+
};
|
|
1920
|
+
main();
|