create-fullstack-scaffold 0.4.22 → 0.4.23

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.
@@ -0,0 +1,4063 @@
1
+ #!/usr/bin/env node
2
+ import { readFileSync, existsSync, readdirSync, statSync } from 'fs';
3
+ import path, { join } from 'path';
4
+ import { fileURLToPath, pathToFileURL } from 'url';
5
+ import { Command } from 'commander';
6
+ import chalk from 'chalk';
7
+ import { select } from '@inquirer/prompts';
8
+ import fs from 'fs-extra';
9
+ import ora from 'ora';
10
+
11
+ var tsImportFn;
12
+ async function getTsImport() {
13
+ if (tsImportFn) return tsImportFn;
14
+ const { tsImport } = await import('tsx/esm/api');
15
+ tsImportFn = tsImport;
16
+ return tsImportFn;
17
+ }
18
+ async function loadManifests(templateDir) {
19
+ const tsImport = await getTsImport();
20
+ const serverDir = join(templateDir, "src", "server");
21
+ const modules = /* @__PURE__ */ new Map();
22
+ const entries = readdirSync(serverDir);
23
+ const moduleDirs = entries.filter(
24
+ (e) => e.startsWith("module-") && statSync(join(serverDir, e)).isDirectory()
25
+ );
26
+ const parentURL = pathToFileURL(join(serverDir, "dummy.ts")).href;
27
+ for (const dir of moduleDirs) {
28
+ const manifestPath = join(serverDir, dir, "module.ts");
29
+ if (!existsSync(manifestPath)) continue;
30
+ try {
31
+ const mod = await tsImport(manifestPath, { parentURL });
32
+ const manifest = mod.default;
33
+ if (!manifest || !manifest.name) {
34
+ console.error(`\u274C Invalid manifest in ${dir}: missing name`);
35
+ continue;
36
+ }
37
+ modules.set(manifest.name, manifest);
38
+ } catch (err) {
39
+ console.error(`\u274C Failed to load manifest from ${dir}:`, err);
40
+ }
41
+ }
42
+ return modules;
43
+ }
44
+ async function loadPresets(templateDir) {
45
+ const configPath = join(templateDir, "modules.config.ts");
46
+ if (!existsSync(configPath)) {
47
+ return [getDefaultPreset()];
48
+ }
49
+ try {
50
+ const tsImport = await getTsImport();
51
+ const parentURL = pathToFileURL(configPath).href;
52
+ const mod = await tsImport(configPath, { parentURL });
53
+ if (mod.TEMPLATE_PRESETS) {
54
+ return mod.TEMPLATE_PRESETS;
55
+ }
56
+ } catch (err) {
57
+ console.error("\u274C Failed to load presets:", err);
58
+ }
59
+ return [getDefaultPreset()];
60
+ }
61
+ function getDefaultPreset() {
62
+ return {
63
+ id: "fullstack-admin",
64
+ name: "Full Admin",
65
+ description: "All modules included",
66
+ modules: [
67
+ "todos",
68
+ "chat",
69
+ "notifications",
70
+ "file",
71
+ "captcha",
72
+ "permission",
73
+ "admin",
74
+ "order",
75
+ "ticket",
76
+ "dispute",
77
+ "content"
78
+ ]
79
+ };
80
+ }
81
+ function resolvePreset(preset, allManifests) {
82
+ const modules = /* @__PURE__ */ new Map();
83
+ const toProcess = [...preset.modules];
84
+ const processed = /* @__PURE__ */ new Set();
85
+ while (toProcess.length > 0) {
86
+ const name = toProcess.shift();
87
+ if (processed.has(name)) continue;
88
+ processed.add(name);
89
+ const manifest = allManifests.get(name);
90
+ if (manifest) {
91
+ modules.set(name, manifest);
92
+ for (const dep of manifest.dependsOn) {
93
+ if (!processed.has(dep)) {
94
+ toProcess.push(dep);
95
+ }
96
+ }
97
+ }
98
+ }
99
+ let hasSSE = false;
100
+ let hasWebSocket = false;
101
+ let hasAdmin = false;
102
+ for (const manifest of modules.values()) {
103
+ if (manifest.hasSSE) hasSSE = true;
104
+ if (manifest.hasWebSocket) hasWebSocket = true;
105
+ if (manifest.adminPages && manifest.adminPages.length > 0) hasAdmin = true;
106
+ if (manifest.routes.admin && manifest.routes.admin.length > 0) hasAdmin = true;
107
+ }
108
+ const isCliOnly = preset.id === "cli-only";
109
+ return {
110
+ preset,
111
+ modules,
112
+ hasAdmin,
113
+ hasClient: !isCliOnly,
114
+ hasCli: true,
115
+ hasSSE,
116
+ hasWebSocket,
117
+ hasPermission: modules.has("permission"),
118
+ hasCaptcha: modules.has("captcha")
119
+ };
120
+ }
121
+ function getDbSchemaFiles(resolved) {
122
+ const files = [];
123
+ for (const [, manifest] of resolved.modules) {
124
+ if (manifest.dbSchemas) {
125
+ files.push(...manifest.dbSchemas.files);
126
+ }
127
+ }
128
+ return files;
129
+ }
130
+ function getClientPages(resolved) {
131
+ const pages = [];
132
+ for (const [, manifest] of resolved.modules) {
133
+ if (manifest.clientPages) {
134
+ pages.push(...manifest.clientPages);
135
+ }
136
+ }
137
+ return pages;
138
+ }
139
+ function getAdminPages(resolved) {
140
+ const pages = [];
141
+ for (const [, manifest] of resolved.modules) {
142
+ if (manifest.adminPages) {
143
+ pages.push(...manifest.adminPages);
144
+ }
145
+ }
146
+ return pages;
147
+ }
148
+
149
+ // src/generators/file-filter.ts
150
+ function getExcludePatterns(resolved, allManifests) {
151
+ const excludes = [];
152
+ for (const [name, manifest] of allManifests) {
153
+ if (resolved.modules.has(name)) continue;
154
+ excludes.push(`src/server/module-${name}`);
155
+ if (manifest.sharedSchemas) {
156
+ excludes.push(`src/shared/modules/${manifest.sharedSchemas.path}`);
157
+ if (manifest.sharedSchemas.additionalPaths) {
158
+ for (const extra of manifest.sharedSchemas.additionalPaths) {
159
+ excludes.push(`src/shared/modules/${extra}`);
160
+ }
161
+ }
162
+ }
163
+ if (manifest.dbSchemas) {
164
+ for (const file of manifest.dbSchemas.files) {
165
+ excludes.push(`src/server/db/schema/${file}.ts`);
166
+ }
167
+ }
168
+ if (manifest.clientPages) {
169
+ for (const page of manifest.clientPages) {
170
+ excludes.push(`src/client/pages/${page.name}.tsx`);
171
+ excludes.push(`src/client/pages/__tests__/${page.name}.test.tsx`);
172
+ }
173
+ }
174
+ if (manifest.clientStores) {
175
+ for (const store of manifest.clientStores) {
176
+ excludes.push(`src/client/stores/${store}.ts`);
177
+ excludes.push(`src/client/stores/__tests__/${store}.test.ts`);
178
+ }
179
+ }
180
+ if (manifest.adminPages) {
181
+ for (const page of manifest.adminPages) {
182
+ excludes.push(`src/admin/pages/${page.name}.tsx`);
183
+ excludes.push(`src/admin/pages/__tests__/${page.name}.test.tsx`);
184
+ }
185
+ }
186
+ if (manifest.providesMiddleware) {
187
+ for (const mw of manifest.providesMiddleware) {
188
+ excludes.push(`src/server/middleware/${mw.name}.ts`);
189
+ excludes.push(`src/server/middleware/__tests__/${mw.name}.test.ts`);
190
+ }
191
+ }
192
+ if (name === "todos") {
193
+ excludes.push("src/server/__tests__/integration/todos-api.test.ts");
194
+ }
195
+ if (manifest.cliModule) {
196
+ excludes.push(`src/cli/modules/${manifest.cliModule.dir}`);
197
+ }
198
+ }
199
+ if (!resolved.modules.has("admin")) {
200
+ excludes.push("src/admin");
201
+ excludes.push("admin.html");
202
+ excludes.push("auth-inject.html");
203
+ }
204
+ if (!resolved.modules.has("tenant")) {
205
+ excludes.push("src/tenant");
206
+ excludes.push("tenant.html");
207
+ excludes.push("src/server/middleware/tenant-isolation.ts");
208
+ excludes.push("src/server/middleware/__tests__/tenant-isolation.test.ts");
209
+ }
210
+ if (!resolved.modules.has("merchant")) {
211
+ excludes.push("src/merchant");
212
+ excludes.push("merchant.html");
213
+ }
214
+ if (!resolved.hasClient) {
215
+ excludes.push("src/client");
216
+ excludes.push("index.html");
217
+ excludes.push("admin.html");
218
+ excludes.push("auth-inject.html");
219
+ excludes.push("src/admin");
220
+ excludes.push("vite.config.ts");
221
+ excludes.push("postcss.config.js");
222
+ excludes.push("tailwind.config.js");
223
+ }
224
+ if (!resolved.hasClient) {
225
+ excludes.push("src/client/components/AuthButton.tsx");
226
+ excludes.push("src/client/components/__tests__/AuthButton.test.tsx");
227
+ } else if (!resolved.modules.has("admin") && !resolved.modules.has("auth")) {
228
+ excludes.push("src/client/components/AuthButton.tsx");
229
+ excludes.push("src/client/components/__tests__/AuthButton.test.tsx");
230
+ excludes.push("src/server/utils/auth.ts");
231
+ }
232
+ excludes.push("src/client/preset-ui-config.ts");
233
+ const standaloneSharedModules = {
234
+ cart: { pages: ["CartPage"] },
235
+ community: { pages: ["TopicsPage", "ProfilePage"], serverModules: ["content"] },
236
+ dashboard: { pages: ["DashboardPage"] }
237
+ };
238
+ for (const [moduleName, config] of Object.entries(standaloneSharedModules)) {
239
+ const hasRelevantPage = config.pages && [...resolved.modules.values()].some(
240
+ (m) => m.clientPages?.some((p) => config.pages.includes(p.name)) ?? false
241
+ );
242
+ const hasRelevantServerModule = config.serverModules?.some((sm) => resolved.modules.has(sm)) ?? false;
243
+ if (!hasRelevantPage && !hasRelevantServerModule) {
244
+ excludes.push(`src/shared/modules/${moduleName}`);
245
+ }
246
+ }
247
+ if (!resolved.hasPermission && !resolved.modules.has("auth")) {
248
+ excludes.push("src/server/utils/permission-utils.ts");
249
+ excludes.push("src/server/utils/__tests__/permission-utils.test.ts");
250
+ excludes.push("src/server/middleware/__tests__/auth-simple.test.ts");
251
+ excludes.push("src/server/middleware/__tests__/auth.test.ts");
252
+ excludes.push("src/server/middleware/__tests__/error-response-format.test.ts");
253
+ excludes.push("src/server/utils/__tests__/auth.test.ts");
254
+ } else if (!resolved.hasPermission && resolved.modules.has("auth")) {
255
+ excludes.push("src/server/utils/permission-utils.ts");
256
+ excludes.push("src/server/utils/__tests__/permission-utils.test.ts");
257
+ excludes.push("src/server/middleware/__tests__/auth-simple.test.ts");
258
+ excludes.push("src/server/middleware/__tests__/auth.test.ts");
259
+ excludes.push("src/server/middleware/__tests__/error-response-format.test.ts");
260
+ excludes.push("src/server/utils/__tests__/auth.test.ts");
261
+ excludes.push("src/server/module-auth/__tests__/auth-service.test.ts");
262
+ } else if (!resolved.modules.has("admin")) {
263
+ excludes.push("src/server/middleware/__tests__/error-response-format.test.ts");
264
+ }
265
+ if (!resolved.modules.has("captcha")) {
266
+ excludes.push("src/server/utils/__tests__/captcha.test.ts");
267
+ excludes.push("src/admin/components/CaptchaModal.tsx");
268
+ excludes.push("src/admin/stores/captchaStore.ts");
269
+ excludes.push("src/admin/stores/__tests__/captchaStore.test.ts");
270
+ excludes.push("src/admin/stores/__tests__/captchaStoreBranches.test.ts");
271
+ }
272
+ return excludes;
273
+ }
274
+ function getGeneratedFiles(resolved) {
275
+ const files = [
276
+ "src/server/route-registry.ts",
277
+ "src/server/db/schema/index.ts",
278
+ "src/shared/modules/index.ts",
279
+ "src/shared/schemas/index.ts",
280
+ "src/server/middleware/index.ts",
281
+ "src/server/app.ts",
282
+ "src/cli/modules/index.ts"
283
+ ];
284
+ if (resolved.hasClient) {
285
+ files.push(
286
+ "src/client/App.tsx",
287
+ "src/client/components/Navigation.tsx",
288
+ "src/client/components/index.ts",
289
+ "src/client/Layout.tsx",
290
+ "src/client/components/__tests__/App.test.tsx",
291
+ "src/client/components/__tests__/Navigation.test.tsx",
292
+ "src/client/main.tsx",
293
+ "src/client/preset-ui-config.ts"
294
+ );
295
+ }
296
+ if (resolved.hasClient && resolved.modules.has("admin")) {
297
+ files.push("src/admin/App.tsx");
298
+ files.push("src/admin/components/index.ts");
299
+ files.push("src/admin/services/apiClient.ts");
300
+ }
301
+ if (resolved.hasClient && !resolved.modules.has("admin")) {
302
+ files.push("vite.config.ts");
303
+ }
304
+ if (resolved.hasClient && resolved.modules.has("admin")) {
305
+ files.push("vite.config.ts");
306
+ }
307
+ if (!resolved.hasPermission) {
308
+ files.push("src/server/middleware/auth.ts");
309
+ files.push("src/server/utils/auth.ts");
310
+ }
311
+ if (!resolved.modules.has("admin") && resolved.hasPermission) {
312
+ files.push("src/server/utils/auth.ts");
313
+ }
314
+ files.push("src/server/db/init.ts");
315
+ return files;
316
+ }
317
+
318
+ // src/generators/route-registry.ts
319
+ function generateRouteRegistry(resolved) {
320
+ const imports = [
321
+ `import { OpenAPIHono } from '@hono/zod-openapi'`,
322
+ `import { rateLimitMiddleware } from './middleware/rate-limit'`
323
+ ];
324
+ const clientRoutes = [];
325
+ const adminRoutes = [];
326
+ const moduleEntries = [...resolved.modules.entries()];
327
+ const usedNames = /* @__PURE__ */ new Set();
328
+ for (const [name, manifest] of moduleEntries) {
329
+ const moduleDir = `module-${name}`;
330
+ if (manifest.routes.client) {
331
+ const clientRouteList = Array.isArray(manifest.routes.client) ? manifest.routes.client : [manifest.routes.client];
332
+ for (const route of clientRouteList) {
333
+ const { importPath, exportName } = route;
334
+ const localName = usedNames.has(exportName) ? `${name}${exportName.charAt(0).toUpperCase()}${exportName.slice(1)}` : exportName;
335
+ usedNames.add(localName);
336
+ const importStmt = localName === exportName ? `import { ${exportName} } from './${moduleDir}/${importPath.replace(/^\.\//, "")}'` : `import { ${exportName} as ${localName} } from './${moduleDir}/${importPath.replace(
337
+ /^\.\//,
338
+ ""
339
+ )}'`;
340
+ imports.push(importStmt);
341
+ clientRoutes.push(` .route('/api', ${localName})`);
342
+ }
343
+ }
344
+ if (manifest.routes.admin) {
345
+ for (const route of manifest.routes.admin) {
346
+ const { importPath, exportName } = route;
347
+ const localName = usedNames.has(exportName) ? `${name}${exportName.charAt(0).toUpperCase()}${exportName.slice(1)}` : exportName;
348
+ usedNames.add(localName);
349
+ const importStmt = localName === exportName ? `import { ${exportName} } from './${moduleDir}/${importPath.replace(/^\.\//, "")}'` : `import { ${exportName} as ${localName} } from './${moduleDir}/${importPath.replace(
350
+ /^\.\//,
351
+ ""
352
+ )}'`;
353
+ imports.push(importStmt);
354
+ adminRoutes.push(` .route('/api', ${localName})`);
355
+ }
356
+ }
357
+ }
358
+ let content = imports.join("\n") + "\n\n";
359
+ content += `const apiRateLimit = rateLimitMiddleware({
360
+ `;
361
+ content += ` windowMs: 60 * 1000,
362
+ `;
363
+ content += ` max: 100,
364
+ `;
365
+ content += `})
366
+
367
+ `;
368
+ if (clientRoutes.length > 0) {
369
+ content += `// client API routes
370
+ `;
371
+ content += `export const clientApiRoutes = new OpenAPIHono()
372
+ `;
373
+ content += ` .use('*', apiRateLimit)
374
+ `;
375
+ content += clientRoutes.join("\n") + "\n\n";
376
+ } else {
377
+ content += `// No client modules selected
378
+ `;
379
+ content += `export const clientApiRoutes = new OpenAPIHono()
380
+
381
+ `;
382
+ }
383
+ if (adminRoutes.length > 0) {
384
+ content += `// admin API routes
385
+ `;
386
+ content += `export const adminApiRoutes = new OpenAPIHono()
387
+ `;
388
+ content += adminRoutes.join("\n") + "\n\n";
389
+ } else {
390
+ content += `// No admin modules selected
391
+ `;
392
+ content += `export const adminApiRoutes = new OpenAPIHono()
393
+
394
+ `;
395
+ }
396
+ content += `export type ClientApiRoutes = typeof clientApiRoutes
397
+ `;
398
+ content += `export type AdminApiRoutes = typeof adminApiRoutes
399
+ `;
400
+ return content;
401
+ }
402
+ function getStandaloneRoutes(resolved) {
403
+ const routes = [];
404
+ const usedNames = /* @__PURE__ */ new Set();
405
+ for (const [name, manifest] of resolved.modules) {
406
+ if (manifest.routes.standalone) {
407
+ const { exportName, mountPath } = manifest.routes.standalone;
408
+ const localName = usedNames.has(exportName) ? `${name}${exportName.charAt(0).toUpperCase()}${exportName.slice(1)}` : exportName;
409
+ usedNames.add(localName);
410
+ routes.push({ localName, mountPath });
411
+ }
412
+ }
413
+ return routes;
414
+ }
415
+
416
+ // src/generators/client-navigation.ts
417
+ var DEFAULT_ICON = "Circle";
418
+ var ICON_MAP = {
419
+ TodoPage: "CheckCircle",
420
+ NotificationPage: "Bell",
421
+ WebSocketPage: "Plug",
422
+ ContentListPage: "FileText",
423
+ PluginsPage: "Package",
424
+ CategoriesPage: "Grid",
425
+ SearchPage: "Search",
426
+ PublishPage: "Upload",
427
+ DeveloperDashboardPage: "Code",
428
+ PluginDetailPage: "Package",
429
+ TopicsPage: "Hash",
430
+ ProfilePage: "User",
431
+ DashboardPage: "LayoutDashboard",
432
+ SettingsPage: "Settings",
433
+ CartPage: "ShoppingCart",
434
+ OrdersPage: "Package"
435
+ };
436
+ function generateClientNavigation(resolved) {
437
+ const pages = getClientPages(resolved).filter((p) => !p.route.includes(":"));
438
+ const hasAuth = resolved.modules.has("auth");
439
+ const navItems = [];
440
+ for (const page of pages) {
441
+ const icon = ICON_MAP[page.name] || DEFAULT_ICON;
442
+ const label = page.name === "TodoPage" ? "Todos" : page.name === "NotificationPage" ? "Notifications" : page.name === "WebSocketPage" ? "WebSocket" : page.name === "PluginsPage" ? "Plugins" : page.name === "CategoriesPage" ? "Categories" : page.name === "SearchPage" ? "Search" : page.name === "PublishPage" ? "Publish" : page.name === "DeveloperDashboardPage" ? "Developer" : page.name === "TopicsPage" ? "Topics" : page.name === "ProfilePage" ? "Profile" : page.name === "DashboardPage" ? "Dashboard" : page.name === "SettingsPage" ? "Settings" : page.name === "CartPage" ? "Cart" : page.name === "OrdersPage" ? "Orders" : page.route.replace(/^\//, "").charAt(0).toUpperCase() + page.route.replace(/^\//, "").slice(1);
443
+ navItems.push(` { label: '${label}', icon: '${icon}', path: '${page.route}' },`);
444
+ }
445
+ const authImport = hasAuth ? `
446
+ import { useAuthStore } from '../stores/authStore'` : "";
447
+ const authSection = hasAuth ? `
448
+ function AuthSection({ style }: { style: string }) {
449
+ const isAuthenticated = useAuthStore((state: any) => state.isAuthenticated)
450
+ const user = useAuthStore((state: any) => state.user)
451
+ const logout = useAuthStore((state: any) => state.logout)
452
+
453
+ if (style === 'none') return null
454
+
455
+ if (isAuthenticated) {
456
+ return (
457
+ <div className="flex items-center gap-2">
458
+ <span className="text-sm text-gray-600">{user?.username}</span>
459
+ <button onClick={logout} className="text-xs text-gray-400 hover:text-red-500">Sign Out</button>
460
+ </div>
461
+ )
462
+ }
463
+
464
+ if (style === 'buttons') {
465
+ return (
466
+ <div className="flex items-center gap-2">
467
+ <Link
468
+ to="/login"
469
+ className="px-3 py-1 text-sm bg-blue-500 text-white rounded hover:bg-blue-600"
470
+ >
471
+ Sign In
472
+ </Link>
473
+ <Link
474
+ to="/register"
475
+ className="px-3 py-1 text-sm border border-gray-300 text-gray-700 rounded hover:bg-gray-50"
476
+ >
477
+ Sign Up
478
+ </Link>
479
+ </div>
480
+ )
481
+ }
482
+
483
+ return (
484
+ <Link to="/login" className="text-sm text-gray-500 hover:text-gray-900">
485
+ Login
486
+ </Link>
487
+ )
488
+ }
489
+ ` : `
490
+ function AuthSection(_style: { style: string }) {
491
+ return null
492
+ }
493
+ `;
494
+ return `import { NavLink${hasAuth ? ", Link" : ""} } from 'react-router-dom'
495
+ import { Rocket, Sparkles } from 'lucide-react'${authImport}
496
+ import type { PresetTheme, NavigationConfig, ClientNavItem } from '../preset-ui-config'
497
+
498
+ interface NavigationProps {
499
+ preset?: string
500
+ items?: ClientNavItem[]
501
+ theme?: PresetTheme
502
+ navigation?: NavigationConfig
503
+ }
504
+ ${authSection}
505
+ export const Navigation: React.FC<NavigationProps> = ({
506
+ items,
507
+ theme,
508
+ navigation,
509
+ }) => {
510
+ const navItems = navigation?.navItems === 'none' ? [] : (items ?? [])
511
+ const primaryColor = theme?.primaryColor ?? '#6366f1'
512
+ const logoText = theme?.logoText ?? 'Biomimic'
513
+ const showLogo = navigation?.showLogo !== false
514
+ const authStyle = navigation?.authStyle ?? 'none'
515
+
516
+ return (
517
+ <nav className="hidden md:block bg-white/80 backdrop-blur-md border-b border-gray-200 sticky top-0 z-50" data-testid="app-nav">
518
+ <div className="max-w-7xl mx-auto px-4 h-16 flex items-center justify-between gap-3">
519
+ {showLogo && (
520
+ <NavLink to="/" className="flex items-center gap-2 group shrink-0" data-testid="app-title">
521
+ <div
522
+ className="w-8 h-8 rounded-lg flex items-center justify-center shadow-sm group-hover:shadow-md transition-shadow"
523
+ style={{ backgroundColor: primaryColor }}
524
+ >
525
+ <Rocket className="w-4 h-4 text-white" />
526
+ </div>
527
+ <span className="text-lg font-semibold text-gray-900 tracking-tight whitespace-nowrap">
528
+ {logoText}
529
+ </span>
530
+ <Sparkles className="w-3.5 h-3.5 shrink-0" style={{ color: primaryColor }} />
531
+ </NavLink>
532
+ )}
533
+
534
+ <div className="flex items-center gap-0.5 overflow-x-auto flex-1 min-w-0 scrollbar-hide">
535
+ {navItems.map(item => (
536
+ <NavLink
537
+ key={item.path}
538
+ to={item.path}
539
+ data-testid={\`nav-\${item.label.toLowerCase().replace(/\\s+/g, '-')}-button\`}
540
+ className={({ isActive }: { isActive: boolean }) =>
541
+ \`px-3 py-1.5 rounded-lg text-sm font-medium transition-all duration-200 shrink-0 whitespace-nowrap \${
542
+ isActive ? 'text-white' : 'text-gray-500 hover:text-gray-900 hover:bg-gray-50'
543
+ }\`
544
+ }
545
+ style={
546
+ (({ isActive }: { isActive: boolean }) =>
547
+ isActive
548
+ ? { backgroundColor: \`\${primaryColor}15\`, color: primaryColor }
549
+ : undefined) as never
550
+ }
551
+ >
552
+ {item.label}
553
+ </NavLink>
554
+ ))}
555
+ </div>
556
+
557
+ <AuthSection style={authStyle} />
558
+ </div>
559
+ </nav>
560
+ )
561
+ }
562
+ `;
563
+ }
564
+
565
+ // src/generators/client-app-test.ts
566
+ function generateClientAppTest(resolved) {
567
+ const pages = getClientPages(resolved);
568
+ const mocks = pages.map(
569
+ (p) => `vi.mock('@client/pages/${p.name}', () => ({
570
+ ${p.name}: () => <div data-testid="${p.name.toLowerCase()}-page">${p.name}</div>,
571
+ }))`
572
+ ).join("\n\n ");
573
+ return `import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
574
+ import { render, screen, cleanup } from '@testing-library/react'
575
+ import '@testing-library/jest-dom'
576
+ import { App } from '@client/App'
577
+
578
+ ${mocks}
579
+
580
+ describe('App Component', () => {
581
+ beforeEach(() => {
582
+ vi.clearAllMocks()
583
+ })
584
+
585
+ afterEach(() => {
586
+ cleanup()
587
+ })
588
+
589
+ describe('Initial Render', () => {
590
+ it('should render navigation', () => {
591
+ render(<App />)
592
+ expect(screen.getByTestId('app-nav')).toBeInTheDocument()
593
+ })
594
+
595
+ it('should render main content area', () => {
596
+ render(<App />)
597
+ expect(screen.getByTestId('app-main')).toBeInTheDocument()
598
+ })
599
+
600
+ it('should render container', () => {
601
+ render(<App />)
602
+ expect(screen.getByTestId('app-container')).toBeInTheDocument()
603
+ })
604
+ })
605
+
606
+ describe('Navigation Links', () => {
607
+ it('should render footer', () => {
608
+ render(<App />)
609
+ expect(screen.getByTestId('app-footer')).toBeInTheDocument()
610
+ })
611
+ })
612
+ })
613
+ `;
614
+ }
615
+
616
+ // src/generators/client-navigation-test.ts
617
+ var LABEL_MAP = {
618
+ TodoPage: "Todos",
619
+ NotificationPage: "Notifications",
620
+ WebSocketPage: "WebSocket",
621
+ PluginsPage: "Plugins",
622
+ CategoriesPage: "Categories",
623
+ SearchPage: "Search",
624
+ PublishPage: "Publish",
625
+ DeveloperDashboardPage: "Developer",
626
+ TopicsPage: "Topics",
627
+ ProfilePage: "Profile",
628
+ DashboardPage: "Dashboard",
629
+ SettingsPage: "Settings",
630
+ CartPage: "Cart",
631
+ OrdersPage: "Orders"
632
+ };
633
+ function generateClientNavigationTest(resolved) {
634
+ const pages = getClientPages(resolved).filter((p) => !p.route.includes(":"));
635
+ const firstPage = pages[0];
636
+ if (!firstPage) {
637
+ return `import { describe, it, expect } from 'vitest'
638
+ import { render, screen } from '@testing-library/react'
639
+ import { BrowserRouter } from 'react-router-dom'
640
+ import { Navigation } from '../Navigation'
641
+
642
+ const renderWithRouter = (component: React.ReactNode) => {
643
+ return render(<BrowserRouter>{component}</BrowserRouter>)
644
+ }
645
+
646
+ describe('Navigation', () => {
647
+ it('should render navigation', () => {
648
+ renderWithRouter(<Navigation />)
649
+ expect(screen.getByTestId('app-nav')).toBeInTheDocument()
650
+ })
651
+ })
652
+ `;
653
+ }
654
+ const firstLabel = LABEL_MAP[firstPage.name] || firstPage.name.replace("Page", "");
655
+ return `import { describe, it, expect } from 'vitest'
656
+ import { render, screen } from '@testing-library/react'
657
+ import { BrowserRouter } from 'react-router-dom'
658
+ import { Navigation } from '../Navigation'
659
+
660
+ const renderWithRouter = (component: React.ReactNode) => {
661
+ return render(<BrowserRouter>{component}</BrowserRouter>)
662
+ }
663
+
664
+ describe('Navigation', () => {
665
+ it('should render navigation', () => {
666
+ renderWithRouter(<Navigation />)
667
+ expect(screen.getByTestId('app-nav')).toBeInTheDocument()
668
+ })
669
+
670
+ it('should render nav items', () => {
671
+ renderWithRouter(<Navigation />)
672
+ expect(screen.getByText('${firstLabel}')).toBeInTheDocument()
673
+ })
674
+ })
675
+ `;
676
+ }
677
+
678
+ // src/generators/admin-app.ts
679
+ function generateAdminApp(resolved) {
680
+ if (!resolved.hasAdmin) return null;
681
+ const pages = getAdminPages(resolved) ?? [];
682
+ if (pages.length === 0) return null;
683
+ const publicPages = pages.filter((p) => p.isPublic);
684
+ const protectedPages = pages.filter((p) => !p.isPublic);
685
+ const imports = [
686
+ `import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom'`,
687
+ `import { ConfigProvider } from 'antd'`,
688
+ `import { Layout } from './layouts/Layout'`
689
+ ];
690
+ for (const page of pages) {
691
+ imports.push(`import { ${page.name} } from './pages/${page.name}'`);
692
+ }
693
+ const captchaImport = resolved.hasCaptcha ? ", CaptchaModal" : "";
694
+ imports.push(`import { ProtectedRoute${captchaImport} } from './components'`);
695
+ const protectedRouteElements = protectedPages.map(
696
+ (p) => ` <Route path="${p.route}" element={<${p.name} />} />`
697
+ );
698
+ const defaultProtectedRoute = protectedPages.length > 0 ? protectedPages[0].route : "/";
699
+ const captchaElement = resolved.hasCaptcha ? `
700
+ <CaptchaModal />` : "";
701
+ const publicRouteLines = publicPages.map(
702
+ (p) => ` <Route path="${p.route}" element={<${p.name} />} />`
703
+ );
704
+ return `${imports.join("\n")}
705
+
706
+ export const App: React.FC<{ basePath?: string }> = ({ basePath = '/admin' }) => {
707
+ return (
708
+ <ConfigProvider
709
+ theme={{
710
+ token: {
711
+ colorPrimary: '#1890ff',
712
+ },
713
+ }}
714
+ >
715
+ <BrowserRouter basename={basePath}>
716
+ <Routes>
717
+ ${publicRouteLines.join("\n")}
718
+ <Route
719
+ path="/*"
720
+ element={
721
+ <ProtectedRoute>
722
+ <Layout>
723
+ <Routes>
724
+ <Route path="/" element={<Navigate to="${defaultProtectedRoute}" replace />} />
725
+ ${protectedRouteElements.join("\n")}
726
+ <Route path="/system/monitor" element={<div className="p-6"><h2 className="text-xl font-semibold">System Monitor</h2><p className="text-gray-500 mt-2">Coming soon...</p></div>} />
727
+ </Routes>
728
+ </Layout>
729
+ </ProtectedRoute>
730
+ }
731
+ />
732
+ </Routes>${captchaElement}
733
+ </BrowserRouter>
734
+ </ConfigProvider>
735
+ )
736
+ }
737
+ `;
738
+ }
739
+ function generateAdminComponentsIndex(resolved) {
740
+ if (!resolved.hasAdmin) return null;
741
+ const lines = [
742
+ `export { UserTable } from './UserTable'`,
743
+ `export { StatsCard } from './StatsCard'`,
744
+ `export { PageHeader } from './PageHeader'`,
745
+ `export { UserFormModal } from './UserFormModal'`,
746
+ `export { ProtectedRoute } from './ProtectedRoute'`
747
+ ];
748
+ if (resolved.hasCaptcha) {
749
+ lines.push(`export { CaptchaModal } from './CaptchaModal'`);
750
+ }
751
+ lines.push(`export { PermissionGuard, PermissionButton, Can, Cannot } from './PermissionGuard'`);
752
+ return lines.join("\n") + "\n";
753
+ }
754
+ function generateAdminApiClient(resolved) {
755
+ if (!resolved.hasAdmin) return null;
756
+ const captchaImport = resolved.hasCaptcha ? `import { useCaptchaStore } from '../stores/captchaStore'
757
+ ` : "";
758
+ const captchaFetchSetup = resolved.hasCaptcha ? ` const showCaptcha = useCaptchaStore.getState().show
759
+
760
+ ` : "";
761
+ const captchaHandler = resolved.hasCaptcha ? ` onShowCaptcha: async config => {
762
+ return showCaptcha({
763
+ type: config.type,
764
+ captchaUrl: config.captchaUrl,
765
+ })
766
+ },` : ` onShowCaptcha: async () => true,`;
767
+ return `/**
768
+ * @framework-baseline ab16e97716a7556e
769
+ * @framework-modify
770
+ * @reason Conditional captcha support based on preset modules
771
+ * @impact Captcha module excluded = no captchaStore import
772
+ */
773
+
774
+ import { hc } from 'hono/client'
775
+ import { WSClientImpl } from '@shared/core/ws-client'
776
+ import { SSEClientImpl } from '@shared/core/sse-client'
777
+ import { createRequestInterceptor } from './requestInterceptor'
778
+ ${captchaImport}import type { AdminApiType } from '@server/index'
779
+
780
+ const baseUrl = import.meta.env.API_BASE_URL || window.location.origin
781
+
782
+ const TOKEN_KEY = 'admin-storage'
783
+
784
+ function clearAuthAndRedirect(): void {
785
+ localStorage.removeItem(TOKEN_KEY)
786
+ if (window.location.pathname !== '/admin/login') {
787
+ window.location.href = '/admin/login'
788
+ }
789
+ }
790
+
791
+ function getAuthToken(): string | null {
792
+ try {
793
+ const stored = localStorage.getItem(TOKEN_KEY)
794
+ if (stored) {
795
+ const parsed = JSON.parse(stored)
796
+ return parsed.state?.token || null
797
+ }
798
+ } catch {
799
+ return null
800
+ }
801
+ return null
802
+ }
803
+
804
+ function createCustomFetch() {
805
+ ${captchaFetchSetup} return createRequestInterceptor({
806
+ onShowLogin: clearAuthAndRedirect,
807
+ ${captchaHandler} })
808
+ }
809
+
810
+ export const apiClient = hc<AdminApiType>(baseUrl, {
811
+ fetch: createCustomFetch() as typeof fetch,
812
+ webSocket: url => new WSClientImpl(url) as unknown as WebSocket,
813
+ sse: url => {
814
+ const token = getAuthToken()
815
+ const headers: Record<string, string> = {}
816
+ if (token) {
817
+ headers['Authorization'] = \`Bearer \${token}\`
818
+ }
819
+ return new SSEClientImpl(url, headers)
820
+ },
821
+ })
822
+
823
+ export { api } from '@shared/core/api-request'
824
+ `;
825
+ }
826
+
827
+ // src/generators/db-schema-barrel.ts
828
+ function generateDbSchemaBarrel(resolved) {
829
+ const files = getDbSchemaFiles(resolved);
830
+ const exports = files.map((f) => `export * from './${f}'`);
831
+ return exports.join("\n") + "\n";
832
+ }
833
+
834
+ // src/generators/db-init.ts
835
+ var INITIAL_PERMISSIONS = `const initialPermissions = [
836
+ {
837
+ id: 'perm_user_view',
838
+ code: 'user:view',
839
+ name: '\u67E5\u770B\u7528\u6237',
840
+ label: '\u67E5\u770B\u7528\u6237',
841
+ category: 'user',
842
+ sortOrder: 1,
843
+ },
844
+ {
845
+ id: 'perm_user_create',
846
+ code: 'user:create',
847
+ name: '\u521B\u5EFA\u7528\u6237',
848
+ label: '\u521B\u5EFA\u7528\u6237',
849
+ category: 'user',
850
+ sortOrder: 2,
851
+ },
852
+ {
853
+ id: 'perm_user_edit',
854
+ code: 'user:edit',
855
+ name: '\u7F16\u8F91\u7528\u6237',
856
+ label: '\u7F16\u8F91\u7528\u6237',
857
+ category: 'user',
858
+ sortOrder: 3,
859
+ },
860
+ {
861
+ id: 'perm_user_delete',
862
+ code: 'user:delete',
863
+ name: '\u5220\u9664\u7528\u6237',
864
+ label: '\u5220\u9664\u7528\u6237',
865
+ category: 'user',
866
+ sortOrder: 4,
867
+ },
868
+ {
869
+ id: 'perm_content_view',
870
+ code: 'content:view',
871
+ name: '\u67E5\u770B\u5185\u5BB9',
872
+ label: '\u67E5\u770B\u5185\u5BB9',
873
+ category: 'content',
874
+ sortOrder: 1,
875
+ },
876
+ {
877
+ id: 'perm_content_create',
878
+ code: 'content:create',
879
+ name: '\u521B\u5EFA\u5185\u5BB9',
880
+ label: '\u521B\u5EFA\u5185\u5BB9',
881
+ category: 'content',
882
+ sortOrder: 2,
883
+ },
884
+ {
885
+ id: 'perm_content_edit',
886
+ code: 'content:edit',
887
+ name: '\u7F16\u8F91\u5185\u5BB9',
888
+ label: '\u7F16\u8F91\u5185\u5BB9',
889
+ category: 'content',
890
+ sortOrder: 3,
891
+ },
892
+ {
893
+ id: 'perm_content_delete',
894
+ code: 'content:delete',
895
+ name: '\u5220\u9664\u5185\u5BB9',
896
+ label: '\u5220\u9664\u5185\u5BB9',
897
+ category: 'content',
898
+ sortOrder: 4,
899
+ },
900
+ {
901
+ id: 'perm_system_settings',
902
+ code: 'system:settings',
903
+ name: '\u7CFB\u7EDF\u8BBE\u7F6E',
904
+ label: '\u7CFB\u7EDF\u8BBE\u7F6E',
905
+ category: 'system',
906
+ sortOrder: 1,
907
+ },
908
+ {
909
+ id: 'perm_system_logs',
910
+ code: 'system:logs',
911
+ name: '\u7CFB\u7EDF\u65E5\u5FD7',
912
+ label: '\u7CFB\u7EDF\u65E5\u5FD7',
913
+ category: 'system',
914
+ sortOrder: 2,
915
+ },
916
+ {
917
+ id: 'perm_system_monitor',
918
+ code: 'system:monitor',
919
+ name: '\u7CFB\u7EDF\u76D1\u63A7',
920
+ label: '\u7CFB\u7EDF\u76D1\u63A7',
921
+ category: 'system',
922
+ sortOrder: 3,
923
+ },
924
+ {
925
+ id: 'perm_data_export',
926
+ code: 'data:export',
927
+ name: '\u6570\u636E\u5BFC\u51FA',
928
+ label: '\u6570\u636E\u5BFC\u51FA',
929
+ category: 'data',
930
+ sortOrder: 1,
931
+ },
932
+ {
933
+ id: 'perm_data_import',
934
+ code: 'data:import',
935
+ name: '\u6570\u636E\u5BFC\u5165',
936
+ label: '\u6570\u636E\u5BFC\u5165',
937
+ category: 'data',
938
+ sortOrder: 2,
939
+ },
940
+ {
941
+ id: 'perm_order_view',
942
+ code: 'order:view',
943
+ name: '\u67E5\u770B\u8BA2\u5355',
944
+ label: '\u67E5\u770B\u8BA2\u5355',
945
+ category: 'order',
946
+ sortOrder: 1,
947
+ },
948
+ {
949
+ id: 'perm_order_create',
950
+ code: 'order:create',
951
+ name: '\u521B\u5EFA\u8BA2\u5355',
952
+ label: '\u521B\u5EFA\u8BA2\u5355',
953
+ category: 'order',
954
+ sortOrder: 2,
955
+ },
956
+ {
957
+ id: 'perm_order_edit',
958
+ code: 'order:edit',
959
+ name: '\u7F16\u8F91\u8BA2\u5355',
960
+ label: '\u7F16\u8F91\u8BA2\u5355',
961
+ category: 'order',
962
+ sortOrder: 3,
963
+ },
964
+ {
965
+ id: 'perm_order_delete',
966
+ code: 'order:delete',
967
+ name: '\u5220\u9664\u8BA2\u5355',
968
+ label: '\u5220\u9664\u8BA2\u5355',
969
+ category: 'order',
970
+ sortOrder: 4,
971
+ },
972
+ {
973
+ id: 'perm_order_process',
974
+ code: 'order:process',
975
+ name: '\u5904\u7406\u8BA2\u5355',
976
+ label: '\u5904\u7406\u8BA2\u5355',
977
+ category: 'order',
978
+ sortOrder: 5,
979
+ },
980
+ {
981
+ id: 'perm_ticket_view',
982
+ code: 'ticket:view',
983
+ name: '\u67E5\u770B\u5DE5\u5355',
984
+ label: '\u67E5\u770B\u5DE5\u5355',
985
+ category: 'ticket',
986
+ sortOrder: 1,
987
+ },
988
+ {
989
+ id: 'perm_ticket_create',
990
+ code: 'ticket:create',
991
+ name: '\u521B\u5EFA\u5DE5\u5355',
992
+ label: '\u521B\u5EFA\u5DE5\u5355',
993
+ category: 'ticket',
994
+ sortOrder: 2,
995
+ },
996
+ {
997
+ id: 'perm_ticket_edit',
998
+ code: 'ticket:edit',
999
+ name: '\u7F16\u8F91\u5DE5\u5355',
1000
+ label: '\u7F16\u8F91\u5DE5\u5355',
1001
+ category: 'ticket',
1002
+ sortOrder: 3,
1003
+ },
1004
+ {
1005
+ id: 'perm_ticket_delete',
1006
+ code: 'ticket:delete',
1007
+ name: '\u5220\u9664\u5DE5\u5355',
1008
+ label: '\u5220\u9664\u5DE5\u5355',
1009
+ category: 'ticket',
1010
+ sortOrder: 4,
1011
+ },
1012
+ {
1013
+ id: 'perm_ticket_reply',
1014
+ code: 'ticket:reply',
1015
+ name: '\u56DE\u590D\u5DE5\u5355',
1016
+ label: '\u56DE\u590D\u5DE5\u5355',
1017
+ category: 'ticket',
1018
+ sortOrder: 5,
1019
+ },
1020
+ {
1021
+ id: 'perm_ticket_close',
1022
+ code: 'ticket:close',
1023
+ name: '\u5173\u95ED\u5DE5\u5355',
1024
+ label: '\u5173\u95ED\u5DE5\u5355',
1025
+ category: 'ticket',
1026
+ sortOrder: 6,
1027
+ },
1028
+ {
1029
+ id: 'perm_dispute_view',
1030
+ code: 'dispute:view',
1031
+ name: '\u67E5\u770B\u4E89\u8BAE',
1032
+ label: '\u67E5\u770B\u4E89\u8BAE',
1033
+ category: 'dispute',
1034
+ sortOrder: 1,
1035
+ },
1036
+ {
1037
+ id: 'perm_dispute_create',
1038
+ code: 'dispute:create',
1039
+ name: '\u521B\u5EFA\u4E89\u8BAE',
1040
+ label: '\u521B\u5EFA\u4E89\u8BAE',
1041
+ category: 'dispute',
1042
+ sortOrder: 2,
1043
+ },
1044
+ {
1045
+ id: 'perm_dispute_edit',
1046
+ code: 'dispute:edit',
1047
+ name: '\u7F16\u8F91\u4E89\u8BAE',
1048
+ label: '\u7F16\u8F91\u4E89\u8BAE',
1049
+ category: 'dispute',
1050
+ sortOrder: 3,
1051
+ },
1052
+ {
1053
+ id: 'perm_dispute_delete',
1054
+ code: 'dispute:delete',
1055
+ name: '\u5220\u9664\u4E89\u8BAE',
1056
+ label: '\u5220\u9664\u4E89\u8BAE',
1057
+ category: 'dispute',
1058
+ sortOrder: 4,
1059
+ },
1060
+ {
1061
+ id: 'perm_dispute_resolve',
1062
+ code: 'dispute:resolve',
1063
+ name: '\u89E3\u51B3\u4E89\u8BAE',
1064
+ label: '\u89E3\u51B3\u4E89\u8BAE',
1065
+ category: 'dispute',
1066
+ sortOrder: 5,
1067
+ },
1068
+ {
1069
+ id: 'perm_role_view',
1070
+ code: 'role:view',
1071
+ name: '\u67E5\u770B\u89D2\u8272',
1072
+ label: '\u67E5\u770B\u89D2\u8272',
1073
+ category: 'role',
1074
+ sortOrder: 1,
1075
+ },
1076
+ {
1077
+ id: 'perm_role_create',
1078
+ code: 'role:create',
1079
+ name: '\u521B\u5EFA\u89D2\u8272',
1080
+ label: '\u521B\u5EFA\u89D2\u8272',
1081
+ category: 'role',
1082
+ sortOrder: 2,
1083
+ },
1084
+ {
1085
+ id: 'perm_role_edit',
1086
+ code: 'role:edit',
1087
+ name: '\u7F16\u8F91\u89D2\u8272',
1088
+ label: '\u7F16\u8F91\u89D2\u8272',
1089
+ category: 'role',
1090
+ sortOrder: 3,
1091
+ },
1092
+ {
1093
+ id: 'perm_role_delete',
1094
+ code: 'role:delete',
1095
+ name: '\u5220\u9664\u89D2\u8272',
1096
+ label: '\u5220\u9664\u89D2\u8272',
1097
+ category: 'role',
1098
+ sortOrder: 4,
1099
+ },
1100
+ ]`;
1101
+ var INITIAL_ROLES = `const initialRoles = [
1102
+ {
1103
+ id: 'role_super_admin',
1104
+ code: 'super_admin',
1105
+ name: '\u8D85\u7EA7\u7BA1\u7406\u5458',
1106
+ label: '\u8D85\u7EA7\u7BA1\u7406\u5458',
1107
+ isSystem: true,
1108
+ sortOrder: 1,
1109
+ },
1110
+ {
1111
+ id: 'role_customer_service',
1112
+ code: 'customer_service',
1113
+ name: '\u5BA2\u670D\u4EBA\u5458',
1114
+ label: '\u5BA2\u670D\u4EBA\u5458',
1115
+ isSystem: true,
1116
+ sortOrder: 2,
1117
+ },
1118
+ {
1119
+ id: 'role_user',
1120
+ code: 'user',
1121
+ name: '\u666E\u901A\u7528\u6237',
1122
+ label: '\u666E\u901A\u7528\u6237',
1123
+ isSystem: true,
1124
+ sortOrder: 3,
1125
+ },
1126
+ ]`;
1127
+ var INITIAL_ROLE_PERMISSIONS = `const initialRolePermissions = [
1128
+ { roleId: 'role_super_admin', permissionId: 'perm_user_view' },
1129
+ { roleId: 'role_super_admin', permissionId: 'perm_user_create' },
1130
+ { roleId: 'role_super_admin', permissionId: 'perm_user_edit' },
1131
+ { roleId: 'role_super_admin', permissionId: 'perm_user_delete' },
1132
+ { roleId: 'role_super_admin', permissionId: 'perm_content_view' },
1133
+ { roleId: 'role_super_admin', permissionId: 'perm_content_create' },
1134
+ { roleId: 'role_super_admin', permissionId: 'perm_content_edit' },
1135
+ { roleId: 'role_super_admin', permissionId: 'perm_content_delete' },
1136
+ { roleId: 'role_super_admin', permissionId: 'perm_system_settings' },
1137
+ { roleId: 'role_super_admin', permissionId: 'perm_system_logs' },
1138
+ { roleId: 'role_super_admin', permissionId: 'perm_system_monitor' },
1139
+ { roleId: 'role_super_admin', permissionId: 'perm_data_export' },
1140
+ { roleId: 'role_super_admin', permissionId: 'perm_data_import' },
1141
+ { roleId: 'role_super_admin', permissionId: 'perm_order_view' },
1142
+ { roleId: 'role_super_admin', permissionId: 'perm_order_create' },
1143
+ { roleId: 'role_super_admin', permissionId: 'perm_order_edit' },
1144
+ { roleId: 'role_super_admin', permissionId: 'perm_order_delete' },
1145
+ { roleId: 'role_super_admin', permissionId: 'perm_order_process' },
1146
+ { roleId: 'role_super_admin', permissionId: 'perm_ticket_view' },
1147
+ { roleId: 'role_super_admin', permissionId: 'perm_ticket_create' },
1148
+ { roleId: 'role_super_admin', permissionId: 'perm_ticket_edit' },
1149
+ { roleId: 'role_super_admin', permissionId: 'perm_ticket_delete' },
1150
+ { roleId: 'role_super_admin', permissionId: 'perm_ticket_reply' },
1151
+ { roleId: 'role_super_admin', permissionId: 'perm_ticket_close' },
1152
+ { roleId: 'role_super_admin', permissionId: 'perm_dispute_view' },
1153
+ { roleId: 'role_super_admin', permissionId: 'perm_dispute_create' },
1154
+ { roleId: 'role_super_admin', permissionId: 'perm_dispute_edit' },
1155
+ { roleId: 'role_super_admin', permissionId: 'perm_dispute_delete' },
1156
+ { roleId: 'role_super_admin', permissionId: 'perm_dispute_resolve' },
1157
+ { roleId: 'role_customer_service', permissionId: 'perm_content_view' },
1158
+ { roleId: 'role_customer_service', permissionId: 'perm_order_view' },
1159
+ { roleId: 'role_customer_service', permissionId: 'perm_order_create' },
1160
+ { roleId: 'role_customer_service', permissionId: 'perm_order_edit' },
1161
+ { roleId: 'role_customer_service', permissionId: 'perm_order_delete' },
1162
+ { roleId: 'role_customer_service', permissionId: 'perm_order_process' },
1163
+ { roleId: 'role_customer_service', permissionId: 'perm_ticket_view' },
1164
+ { roleId: 'role_customer_service', permissionId: 'perm_ticket_create' },
1165
+ { roleId: 'role_customer_service', permissionId: 'perm_ticket_edit' },
1166
+ { roleId: 'role_customer_service', permissionId: 'perm_ticket_delete' },
1167
+ { roleId: 'role_customer_service', permissionId: 'perm_ticket_reply' },
1168
+ { roleId: 'role_customer_service', permissionId: 'perm_ticket_close' },
1169
+ { roleId: 'role_customer_service', permissionId: 'perm_dispute_view' },
1170
+ { roleId: 'role_customer_service', permissionId: 'perm_dispute_create' },
1171
+ { roleId: 'role_customer_service', permissionId: 'perm_dispute_edit' },
1172
+ { roleId: 'role_customer_service', permissionId: 'perm_dispute_delete' },
1173
+ { roleId: 'role_customer_service', permissionId: 'perm_dispute_resolve' },
1174
+ { roleId: 'role_customer_service', permissionId: 'perm_data_export' },
1175
+ { roleId: 'role_customer_service', permissionId: 'perm_system_logs' },
1176
+ { roleId: 'role_user', permissionId: 'perm_content_view' },
1177
+ { roleId: 'role_user', permissionId: 'perm_order_view' },
1178
+ ]`;
1179
+ function generateDbInit(resolved) {
1180
+ const activeSeeds = [];
1181
+ for (const [name, manifest] of resolved.modules) {
1182
+ if (manifest.dbSchemas?.hasSeed && manifest.dbSchemas.seed) {
1183
+ activeSeeds.push({
1184
+ moduleDir: `module-${name}`,
1185
+ serviceFile: manifest.dbSchemas.seed.serviceFile,
1186
+ functionName: manifest.dbSchemas.seed.functionName
1187
+ });
1188
+ }
1189
+ }
1190
+ if (!resolved.hasPermission) {
1191
+ return generateMinimalDbInit(activeSeeds);
1192
+ }
1193
+ return generateFullDbInit(activeSeeds);
1194
+ }
1195
+ function generateMinimalDbInit(activeSeeds) {
1196
+ const seedCalls = activeSeeds.map((s) => ` import('../${s.moduleDir}/services/${s.serviceFile}').then(m => m.${s.functionName}()),`).join("\n");
1197
+ const seedBlock = activeSeeds.length > 0 ? `
1198
+ log.info({}, 'Seeding module data...')
1199
+ await Promise.all([
1200
+ ${seedCalls}
1201
+ ])
1202
+ log.info({}, 'Module data seeding complete!')` : "";
1203
+ return `import { getDb } from './driver'
1204
+ import { logger } from '../utils/logger'
1205
+
1206
+ const log = logger.db()
1207
+
1208
+ export async function initializeDatabase() {
1209
+ await getDb()
1210
+
1211
+ log.info({}, 'Initializing database...')
1212
+
1213
+ log.info({}, 'Database initialization complete!')${seedBlock}
1214
+ }
1215
+ `;
1216
+ }
1217
+ function generateFullDbInit(activeSeeds) {
1218
+ const seedCalls = activeSeeds.map((s) => ` import('../${s.moduleDir}/services/${s.serviceFile}').then(m => m.${s.functionName}()),`).join("\n");
1219
+ const seedBlock = activeSeeds.length > 0 ? `
1220
+ log.info({}, 'Seeding module data...')
1221
+ await Promise.all([
1222
+ ${seedCalls}
1223
+ ])
1224
+ log.info({}, 'Module data seeding complete!')` : "";
1225
+ return `import { getDb } from './driver'
1226
+ import { permissions, roles, rolePermissions } from './schema'
1227
+ import { logger } from '../utils/logger'
1228
+
1229
+ const log = logger.db()
1230
+
1231
+ ${INITIAL_PERMISSIONS}
1232
+
1233
+ ${INITIAL_ROLES}
1234
+
1235
+ ${INITIAL_ROLE_PERMISSIONS}
1236
+
1237
+ export async function initializeDatabase() {
1238
+ const db = await getDb()
1239
+
1240
+ log.info({}, 'Initializing database...')
1241
+
1242
+ const existingPermissions = await db.select().from(permissions)
1243
+ if (existingPermissions.length === 0) {
1244
+ log.info({}, 'Inserting initial permissions...')
1245
+ await db.insert(permissions).values(
1246
+ initialPermissions.map(p => ({
1247
+ ...p,
1248
+ description: null,
1249
+ isActive: true,
1250
+ createdAt: new Date(),
1251
+ updatedAt: new Date(),
1252
+ }))
1253
+ )
1254
+ }
1255
+
1256
+ const existingRoles = await db.select().from(roles)
1257
+ if (existingRoles.length === 0) {
1258
+ log.info({}, 'Inserting initial roles...')
1259
+ await db.insert(roles).values(
1260
+ initialRoles.map(r => ({
1261
+ ...r,
1262
+ description: null,
1263
+ isActive: true,
1264
+ createdAt: new Date(),
1265
+ updatedAt: new Date(),
1266
+ }))
1267
+ )
1268
+ }
1269
+
1270
+ const existingRolePermissions = await db.select().from(rolePermissions)
1271
+ if (existingRolePermissions.length === 0) {
1272
+ log.info({}, 'Inserting initial role permissions...')
1273
+ await db.insert(rolePermissions).values(
1274
+ initialRolePermissions.map(rp => ({
1275
+ ...rp,
1276
+ createdAt: new Date(),
1277
+ }))
1278
+ )
1279
+ }
1280
+
1281
+ log.info({}, 'Database initialization complete!')${seedBlock}
1282
+ }
1283
+ `;
1284
+ }
1285
+
1286
+ // src/generators/server-app.ts
1287
+ function generateServerApp(resolved) {
1288
+ const useRealtime = resolved.hasSSE || resolved.hasWebSocket;
1289
+ const useAuditLog = resolved.hasPermission;
1290
+ const useCaptcha = resolved.hasCaptcha;
1291
+ const standaloneRoutes = getStandaloneRoutes(resolved);
1292
+ const imports = [
1293
+ `import { OpenAPIHono } from '@hono/zod-openapi'`,
1294
+ ``,
1295
+ `import { HTTPException } from 'hono/http-exception'`,
1296
+ `import type { ContentfulStatusCode } from 'hono/utils/http-status'`,
1297
+ `import { ZodError } from 'zod'`,
1298
+ `import type { AppBindings, CreateAppOptions } from './types/bindings'`,
1299
+ `import { AppError } from './utils/app-error'`,
1300
+ `import { autoRegisterRealtime } from './core/realtime-scanner'`,
1301
+ `import { corsMiddleware, loggerMiddleware, errorHandlerMiddleware } from './middleware'`
1302
+ ];
1303
+ if (useRealtime) {
1304
+ imports.push(`import { realtimeEnvMiddleware } from './middleware/realtime-env'`);
1305
+ }
1306
+ if (useAuditLog) {
1307
+ imports.push(`import { auditLogMiddleware } from './middleware/audit-log'`);
1308
+ }
1309
+ if (useCaptcha) {
1310
+ imports.push(`import { captchaMiddleware } from './middleware/captcha'`);
1311
+ }
1312
+ imports.push(
1313
+ `import { createModuleLoggerSync } from './utils/logger'`,
1314
+ `import { adminApiRoutes, clientApiRoutes } from './route-registry'`
1315
+ );
1316
+ const standaloneImportSet = /* @__PURE__ */ new Set();
1317
+ for (const [name, manifest] of resolved.modules) {
1318
+ if (manifest.routes.standalone) {
1319
+ const { importPath, exportName } = manifest.routes.standalone;
1320
+ const moduleDir = `module-${name}`;
1321
+ const relPath = importPath.replace(/^\.\//, "");
1322
+ const stmt = `import { ${exportName} } from './${moduleDir}/${relPath}'`;
1323
+ if (!standaloneImportSet.has(stmt)) {
1324
+ standaloneImportSet.add(stmt);
1325
+ imports.push(stmt);
1326
+ }
1327
+ }
1328
+ }
1329
+ const middlewareChain = [
1330
+ `.use('*', errorHandlerMiddleware())`,
1331
+ `.use('*', loggerMiddleware())`,
1332
+ `.use('*', corsMiddleware())`
1333
+ ];
1334
+ if (useRealtime) {
1335
+ middlewareChain.push(`.use('*', realtimeEnvMiddleware())`);
1336
+ }
1337
+ if (useAuditLog) {
1338
+ middlewareChain.push(`.use('/api/*', auditLogMiddleware())`);
1339
+ }
1340
+ if (useCaptcha) {
1341
+ middlewareChain.push(
1342
+ `.use(
1343
+ '/api/admin/*',
1344
+ captchaMiddleware({
1345
+ maxRequests: 20,
1346
+ windowMs: 60000,
1347
+ })
1348
+ )`
1349
+ );
1350
+ }
1351
+ const routes = [`.route('/', clientApiRoutes)`, `.route('/', adminApiRoutes)`];
1352
+ for (const sr of standaloneRoutes) {
1353
+ routes.push(`.route('${sr.mountPath}', ${sr.localName})`);
1354
+ }
1355
+ const indent = " ";
1356
+ const chain = [...middlewareChain, ...routes].join(`
1357
+ ${indent}`);
1358
+ return `${imports.join("\n")}
1359
+
1360
+ export { type AppBindings, type CreateAppOptions } from './types/bindings'
1361
+
1362
+ export function createApp<T extends AppBindings = AppBindings>(_options: CreateAppOptions = {}) {
1363
+ const app = new OpenAPIHono<{ Bindings: T }>()
1364
+ ${chain}
1365
+ .get('/health', async c => {
1366
+ try {
1367
+ const { getDb } = await import('./db')
1368
+ await getDb()
1369
+ return c.json({ status: 'ok', timestamp: new Date().toISOString(), db: 'connected' })
1370
+ } catch {
1371
+ return c.json({ status: 'ok', timestamp: new Date().toISOString(), db: 'not configured' })
1372
+ }
1373
+ })
1374
+ .post('/api/__test__/cleanup', async c => {
1375
+ try {
1376
+ const { cleanupTestDatabase } = await import('./db/test-setup')
1377
+ await cleanupTestDatabase()
1378
+ return c.json({ success: true as const, message: 'Database cleaned up' })
1379
+ } catch (error) {
1380
+ console.error('Error during database cleanup:', error)
1381
+ return c.json({ success: false as const, message: 'Failed to cleanup database' }, 500)
1382
+ }
1383
+ })
1384
+
1385
+ autoRegisterRealtime(app as unknown as Parameters<typeof autoRegisterRealtime>[0])
1386
+
1387
+ app.onError((err, c) => {
1388
+ const log = createModuleLoggerSync('api')
1389
+ c.res.headers.set('Content-Type', 'application/json')
1390
+
1391
+ if (AppError.isAppError(err)) {
1392
+ return c.json(
1393
+ {
1394
+ success: false as const,
1395
+ error: err.message,
1396
+ status: err.statusCode,
1397
+ details: err.details,
1398
+ },
1399
+ err.statusCode as ContentfulStatusCode
1400
+ )
1401
+ }
1402
+
1403
+ if (err instanceof HTTPException) {
1404
+ return c.json(
1405
+ { success: false as const, error: err.message, status: err.status },
1406
+ err.status as ContentfulStatusCode
1407
+ )
1408
+ }
1409
+
1410
+ if (err instanceof ZodError) {
1411
+ const details = err.issues.map(issue => ({
1412
+ field: issue.path.join('.'),
1413
+ message: issue.message,
1414
+ }))
1415
+ return c.json(
1416
+ { success: false as const, error: 'Validation failed', status: 400, details },
1417
+ 400
1418
+ )
1419
+ }
1420
+
1421
+ log.error({ err, path: c.req.path }, 'Unhandled error')
1422
+ return c.json(
1423
+ { success: false as const, error: err.message || 'Internal server error', status: 500 },
1424
+ 500
1425
+ )
1426
+ })
1427
+
1428
+ return app
1429
+ }
1430
+ export type AdminApiType = typeof adminApiRoutes
1431
+ export type ClientApiType = typeof clientApiRoutes
1432
+ export type AppType = ReturnType<typeof createApp>
1433
+ `;
1434
+ }
1435
+
1436
+ // src/generators/shared-modules-index.ts
1437
+ var MODULE_EXPORTS = {
1438
+ chat: {
1439
+ namedExports: ["ChatProtocolSchema", "type ChatProtocol"]
1440
+ },
1441
+ todos: {
1442
+ namedExports: [
1443
+ "TodoSchema",
1444
+ "TodoStatusSchema",
1445
+ "CreateTodoSchema",
1446
+ "UpdateTodoSchema",
1447
+ "TodoIdSchema",
1448
+ "type Todo",
1449
+ "type TodoStatus",
1450
+ "type CreateTodoInput",
1451
+ "type UpdateTodoInput"
1452
+ ]
1453
+ },
1454
+ files: {
1455
+ namedExports: [
1456
+ "FileDownloadSchema",
1457
+ "PrivateFileQuerySchema",
1458
+ "PublicFileUrlSchema",
1459
+ "PrivateFileUrlSchema",
1460
+ "GenerateUrlRequestSchema",
1461
+ "FileUrlResponseSchema",
1462
+ "EmptySchema"
1463
+ ]
1464
+ },
1465
+ notifications: {
1466
+ namedExports: [
1467
+ "NotificationSchema",
1468
+ "NotificationTypeSchema",
1469
+ "CreateNotificationSchema",
1470
+ "NotificationListQuerySchema",
1471
+ "SSEEventSchema",
1472
+ "AppSSEProtocolSchema",
1473
+ "type AppNotification",
1474
+ "type NotificationType",
1475
+ "type CreateNotificationInput",
1476
+ "type NotificationListQuery",
1477
+ "type SSEEvent",
1478
+ "type AppSSEProtocol"
1479
+ ]
1480
+ },
1481
+ admin: {
1482
+ namedExports: [
1483
+ "SystemStatsSchema",
1484
+ "HealthCheckSchema",
1485
+ "RecentActivityItemSchema",
1486
+ "RecentActivitySchema",
1487
+ "AuthUserSchema",
1488
+ "ClearTodosResultSchema",
1489
+ "type SystemStats",
1490
+ "type HealthCheck",
1491
+ "type RecentActivityItem",
1492
+ "type AuthUserResponse",
1493
+ "type ClearTodosResult"
1494
+ ]
1495
+ },
1496
+ permission: {
1497
+ namedExports: [
1498
+ "RoleEnum",
1499
+ "RoleInfoSchema",
1500
+ "PermissionInfoSchema",
1501
+ "UserPermissionsSchema",
1502
+ "RoleListSchema",
1503
+ "PermissionListSchema",
1504
+ "Role",
1505
+ "Permission",
1506
+ "ROLE_PERMISSIONS",
1507
+ "ROLE_LABELS",
1508
+ "PERMISSION_LABELS",
1509
+ "PERMISSION_CATEGORIES",
1510
+ "getPermissionsByRole",
1511
+ "hasPermission",
1512
+ "hasAnyPermission",
1513
+ "hasAllPermissions",
1514
+ "type PermissionRoleType",
1515
+ "type RoleInfo",
1516
+ "type PermissionInfo",
1517
+ "type UserPermissions"
1518
+ ]
1519
+ },
1520
+ auth: {
1521
+ namedExports: [
1522
+ "DeveloperProfileSchema",
1523
+ "LoginSchema",
1524
+ "RegisterSchema",
1525
+ "TokenResponseSchema",
1526
+ "type DeveloperProfile",
1527
+ "type LoginInput",
1528
+ "type RegisterInput",
1529
+ "type TokenResponse"
1530
+ ]
1531
+ },
1532
+ plugin: {
1533
+ namedExports: [
1534
+ "PluginSchema",
1535
+ "PluginStatusSchema",
1536
+ "CreatePluginSchema",
1537
+ "UpdatePluginSchema",
1538
+ "PluginVersionStatusSchema",
1539
+ "VersionSchema",
1540
+ "CategorySchema",
1541
+ "ReviewSchema",
1542
+ "CreateReviewSchema",
1543
+ "MarketplaceStatsSchema",
1544
+ "PluginListResponseSchema",
1545
+ "AdminPluginSchema",
1546
+ "AdminDashboardStatsSchema",
1547
+ "PluginListQuerySchema",
1548
+ "PluginSlugSchema",
1549
+ "type Plugin",
1550
+ "type PluginStatus",
1551
+ "type CreatePluginInput",
1552
+ "type UpdatePluginInput",
1553
+ "type PluginVersionStatus",
1554
+ "type Version",
1555
+ "type Category",
1556
+ "type Review",
1557
+ "type CreateReviewInput",
1558
+ "type MarketplaceStats",
1559
+ "type PluginListResponse",
1560
+ "type AdminPlugin",
1561
+ "type AdminDashboardStats",
1562
+ "type PluginListQuery"
1563
+ ]
1564
+ },
1565
+ merchant: {
1566
+ namedExports: [
1567
+ "ProductSchema",
1568
+ "CreateProductSchema",
1569
+ "UpdateProductSchema",
1570
+ "ProductListSchema",
1571
+ "type Product",
1572
+ "type CreateProductInput",
1573
+ "type UpdateProductInput"
1574
+ ]
1575
+ },
1576
+ tenant: {
1577
+ namedExports: [
1578
+ "TenantSchema",
1579
+ "TenantStatusSchema",
1580
+ "TenantPlanSchema",
1581
+ "TenantSettingsSchema",
1582
+ "CreateTenantSchema",
1583
+ "UpdateTenantSchema",
1584
+ "TenantIdSchema",
1585
+ "TenantSlugSchema",
1586
+ "TenantListResponseSchema",
1587
+ "TenantQuerySchema",
1588
+ "TenantIdResponseSchema",
1589
+ "type Tenant",
1590
+ "type TenantStatus",
1591
+ "type TenantPlan",
1592
+ "type TenantSettings",
1593
+ "type CreateTenantInput",
1594
+ "type UpdateTenantInput",
1595
+ "type TenantId",
1596
+ "type TenantSlug",
1597
+ "type TenantListResponse",
1598
+ "type TenantQuery",
1599
+ "type TenantIdResponse"
1600
+ ]
1601
+ },
1602
+ order: {
1603
+ namedExports: [
1604
+ "OrderStatusSchema",
1605
+ "OrderSchema",
1606
+ "CreateOrderSchema",
1607
+ "UpdateOrderSchema",
1608
+ "OrderListSchema",
1609
+ "OrderQuerySchema",
1610
+ "OrderDeleteResultSchema",
1611
+ "ProcessOrderSchema",
1612
+ "CancelOrderSchema",
1613
+ "RemoveCartItemResponseSchema",
1614
+ "ECommerceProductSchema",
1615
+ "ECommerceOrderStatusSchema",
1616
+ "ECommerceOrderSchema",
1617
+ "ECommerceOrderListSchema",
1618
+ "type OrderStatus",
1619
+ "type Order",
1620
+ "type CreateOrderInput",
1621
+ "type UpdateOrderInput",
1622
+ "type OrderDeleteResult",
1623
+ "type ProcessOrderInput",
1624
+ "type CancelOrderInput",
1625
+ "type OrderQueryInput",
1626
+ "type RemoveCartItemResponse",
1627
+ "type ECommerceProduct",
1628
+ "type ECommerceOrderStatus",
1629
+ "type ECommerceOrder"
1630
+ ]
1631
+ },
1632
+ ticket: {
1633
+ namedExports: [
1634
+ "TicketStatusSchema",
1635
+ "TicketPrioritySchema",
1636
+ "TicketCategorySchema",
1637
+ "TicketReplySchema",
1638
+ "TicketSchema",
1639
+ "CreateTicketSchema",
1640
+ "UpdateTicketSchema",
1641
+ "ReplyTicketSchema",
1642
+ "TicketListSchema",
1643
+ "TicketDeleteResultSchema",
1644
+ "type TicketStatus",
1645
+ "type TicketPriority",
1646
+ "type TicketCategory",
1647
+ "type TicketReply",
1648
+ "type Ticket",
1649
+ "type CreateTicketInput",
1650
+ "type UpdateTicketInput",
1651
+ "type ReplyTicketInput",
1652
+ "type TicketDeleteResult"
1653
+ ]
1654
+ },
1655
+ dispute: {
1656
+ namedExports: [
1657
+ "DisputeTypeSchema",
1658
+ "DisputeStatusSchema",
1659
+ "DisputeSchema",
1660
+ "CreateDisputeSchema",
1661
+ "UpdateDisputeSchema",
1662
+ "ResolveDisputeSchema",
1663
+ "DisputeListSchema",
1664
+ "DisputeDeleteResultSchema",
1665
+ "type DisputeType",
1666
+ "type DisputeStatus",
1667
+ "type Dispute",
1668
+ "type CreateDisputeInput",
1669
+ "type UpdateDisputeInput",
1670
+ "type ResolveDisputeInput",
1671
+ "type DisputeDeleteResult"
1672
+ ]
1673
+ },
1674
+ content: {
1675
+ namedExports: [
1676
+ "ContentCategorySchema",
1677
+ "ContentStatusSchema",
1678
+ "ContentSchema",
1679
+ "CreateContentSchema",
1680
+ "UpdateContentSchema",
1681
+ "ContentListSchema",
1682
+ "ContentDeleteResultSchema",
1683
+ "type ContentCategory",
1684
+ "type ContentStatus",
1685
+ "type Content",
1686
+ "type CreateContentInput",
1687
+ "type UpdateContentInput",
1688
+ "type ContentDeleteResult"
1689
+ ]
1690
+ },
1691
+ captcha: {
1692
+ namedExports: [
1693
+ "CaptchaResponseSchema",
1694
+ "VerifyCaptchaRequestSchema",
1695
+ "CaptchaVerifyResponseSchema",
1696
+ "type CaptchaResponse",
1697
+ "type VerifyCaptchaRequest",
1698
+ "type CaptchaVerifyResponse"
1699
+ ]
1700
+ },
1701
+ dashboard: {
1702
+ namedExports: [
1703
+ "DashboardStatSchema",
1704
+ "RevenueDataSchema",
1705
+ "ActivityStatusSchema",
1706
+ "ActivitySchema",
1707
+ "DashboardResponseSchema",
1708
+ "type DashboardStat",
1709
+ "type RevenueData",
1710
+ "type ActivityStatus",
1711
+ "type Activity",
1712
+ "type DashboardResponse"
1713
+ ]
1714
+ },
1715
+ cart: {
1716
+ namedExports: [
1717
+ "CartItemSchema",
1718
+ "CartSummarySchema",
1719
+ "CartResponseSchema",
1720
+ "AddCartItemSchema",
1721
+ "CartItemIdSchema",
1722
+ "type CartItem",
1723
+ "type CartSummary",
1724
+ "type CartResponse",
1725
+ "type AddCartItemInput"
1726
+ ]
1727
+ },
1728
+ community: {
1729
+ namedExports: [
1730
+ "TopicStatusSchema",
1731
+ "TopicTagSchema",
1732
+ "TopicAuthorSchema",
1733
+ "TopicSchema",
1734
+ "TopicsResponseSchema",
1735
+ "ProfileStatsSchema",
1736
+ "ActivityTypeSchema",
1737
+ "ProfileActivitySchema",
1738
+ "ProfileResponseSchema",
1739
+ "type TopicStatus",
1740
+ "type TopicTag",
1741
+ "type TopicAuthor",
1742
+ "type Topic",
1743
+ "type ProfileStats",
1744
+ "type ActivityType",
1745
+ "type ProfileActivity",
1746
+ "type ProfileResponse"
1747
+ ]
1748
+ },
1749
+ audit: {
1750
+ namedExports: ["ResourceTypeSchema", "ActionTypeSchema", "AuditLogSchema", "type AuditLogType"]
1751
+ },
1752
+ role: {
1753
+ namedExports: [
1754
+ "RoleSchema",
1755
+ "CreateRoleSchema",
1756
+ "UpdateRoleSchema",
1757
+ "UpdateRolePermissionsSchema",
1758
+ "RoleSuccessSchema",
1759
+ "type RoleDataType",
1760
+ "type CreateRoleType",
1761
+ "type UpdateRoleType"
1762
+ ]
1763
+ }
1764
+ };
1765
+ function generateSharedModulesIndex(resolved) {
1766
+ const lines = [];
1767
+ const moduleOrder2 = [
1768
+ "chat",
1769
+ "todos",
1770
+ "file",
1771
+ "notifications",
1772
+ "admin",
1773
+ "permission",
1774
+ "auth",
1775
+ "plugin",
1776
+ "merchant",
1777
+ "tenant",
1778
+ "order",
1779
+ "ticket",
1780
+ "dispute",
1781
+ "content",
1782
+ "captcha"
1783
+ ];
1784
+ const STANDALONE_SHARED_MODULES2 = {
1785
+ cart: { pages: ["CartPage.tsx"] },
1786
+ community: { pages: ["TopicsPage.tsx", "ProfilePage.tsx"], serverModules: ["content"] },
1787
+ dashboard: { pages: ["DashboardPage.tsx"] }
1788
+ };
1789
+ for (const moduleName of moduleOrder2) {
1790
+ if (!resolved.modules.has(moduleName)) continue;
1791
+ const manifest = resolved.modules.get(moduleName);
1792
+ const exportKey = manifest.sharedSchemas?.path ?? moduleName;
1793
+ const exports = MODULE_EXPORTS[exportKey];
1794
+ if (!exports) continue;
1795
+ lines.push(`export {
1796
+ ${exports.namedExports.join(",\n ")},
1797
+ } from './${exportKey}'`);
1798
+ }
1799
+ const additionalExported = /* @__PURE__ */ new Set();
1800
+ for (const moduleName of moduleOrder2) {
1801
+ if (!resolved.modules.has(moduleName)) continue;
1802
+ const manifest = resolved.modules.get(moduleName);
1803
+ if (manifest.sharedSchemas?.additionalPaths) {
1804
+ for (const extra of manifest.sharedSchemas.additionalPaths) {
1805
+ if (additionalExported.has(extra)) continue;
1806
+ additionalExported.add(extra);
1807
+ const exports = MODULE_EXPORTS[extra];
1808
+ if (!exports) continue;
1809
+ lines.push(`export {
1810
+ ${exports.namedExports.join(",\n ")},
1811
+ } from './${extra}'`);
1812
+ }
1813
+ }
1814
+ }
1815
+ for (const [moduleName, config] of Object.entries(STANDALONE_SHARED_MODULES2)) {
1816
+ const hasRelevantPage = config.pages && [...resolved.modules.values()].some(
1817
+ (m) => m.clientPages?.some((p) => config.pages.includes(p.name + ".tsx")) ?? false
1818
+ );
1819
+ const hasRelevantServerModule = config.serverModules?.some((sm) => resolved.modules.has(sm)) ?? false;
1820
+ if (!hasRelevantPage && !hasRelevantServerModule) continue;
1821
+ const exports = MODULE_EXPORTS[moduleName];
1822
+ if (!exports) continue;
1823
+ lines.push(`export {
1824
+ ${exports.namedExports.join(",\n ")},
1825
+ } from './${moduleName}'`);
1826
+ }
1827
+ return lines.join("\n") + "\n";
1828
+ }
1829
+
1830
+ // src/generators/shared-schemas-index.ts
1831
+ var MODULE_EXPORTS2 = {
1832
+ chat: {
1833
+ namedExports: [
1834
+ "ChatProtocolSchema",
1835
+ "WebSocketStatusSchema",
1836
+ "type ChatProtocol",
1837
+ "type WebSocketStatus"
1838
+ ]
1839
+ },
1840
+ file: {
1841
+ namedExports: [
1842
+ "FileDownloadSchema",
1843
+ "PrivateFileQuerySchema",
1844
+ "PublicFileUrlSchema",
1845
+ "PrivateFileUrlSchema",
1846
+ "GenerateUrlRequestSchema",
1847
+ "FileUrlResponseSchema",
1848
+ "EmptySchema",
1849
+ "UploadResultSchema",
1850
+ "UploadFileBodySchema"
1851
+ ]
1852
+ },
1853
+ todos: {
1854
+ namedExports: [
1855
+ "TodoSchema",
1856
+ "TodoStatusSchema",
1857
+ "CreateTodoSchema",
1858
+ "UpdateTodoSchema",
1859
+ "TodoIdSchema",
1860
+ "TodoIdResponseSchema",
1861
+ "TodoAttachmentSchema",
1862
+ "TodoAttachmentListSchema",
1863
+ "TodoWithAttachmentsSchema",
1864
+ "UploadFileSchema",
1865
+ "AttachmentIdResponseSchema",
1866
+ "type Todo",
1867
+ "type TodoStatus",
1868
+ "type CreateTodoInput",
1869
+ "type UpdateTodoInput",
1870
+ "type TodoIdResponse",
1871
+ "type TodoAttachment",
1872
+ "type TodoWithAttachments"
1873
+ ]
1874
+ },
1875
+ notifications: {
1876
+ namedExports: [
1877
+ "NotificationSchema",
1878
+ "NotificationTypeSchema",
1879
+ "CreateNotificationSchema",
1880
+ "NotificationListQuerySchema",
1881
+ "SSEEventSchema",
1882
+ "AppSSEProtocolSchema",
1883
+ "UnreadCountSchema",
1884
+ "NotificationIdSchema",
1885
+ "UnreadCountEventSchema",
1886
+ "type AppNotification",
1887
+ "type NotificationType",
1888
+ "type CreateNotificationInput",
1889
+ "type NotificationListQuery",
1890
+ "type SSEEvent",
1891
+ "type AppSSEProtocol",
1892
+ "type UnreadCount",
1893
+ "type NotificationId",
1894
+ "type UnreadCountEvent"
1895
+ ]
1896
+ },
1897
+ auth: {
1898
+ namedExports: [
1899
+ "DeveloperProfileSchema",
1900
+ "LoginSchema",
1901
+ "RegisterSchema",
1902
+ "TokenResponseSchema",
1903
+ "ProfileSchema",
1904
+ "type DeveloperProfile",
1905
+ "type LoginInput",
1906
+ "type RegisterInput",
1907
+ "type TokenResponse",
1908
+ "type Profile"
1909
+ ]
1910
+ },
1911
+ plugin: {
1912
+ namedExports: [
1913
+ "PluginSchema",
1914
+ "PluginStatusSchema",
1915
+ "CreatePluginSchema",
1916
+ "UpdatePluginSchema",
1917
+ "PluginVersionStatusSchema",
1918
+ "VersionSchema",
1919
+ "CategorySchema",
1920
+ "ReviewSchema",
1921
+ "CreateReviewSchema",
1922
+ "MarketplaceStatsSchema",
1923
+ "PluginListResponseSchema",
1924
+ "AdminPluginSchema",
1925
+ "AdminDashboardStatsSchema",
1926
+ "PluginListQuerySchema",
1927
+ "PluginSlugSchema",
1928
+ "PluginSearchQuerySchema",
1929
+ "PluginDeleteResponseSchema",
1930
+ "ReviewIdParamsSchema",
1931
+ "ReviewDeleteResponseSchema",
1932
+ "CategorySlugParamsSchema",
1933
+ "CategoryPluginsQuerySchema",
1934
+ "PluginListAdminSchema",
1935
+ "AdminListQuerySchema",
1936
+ "AdminListAllQuerySchema",
1937
+ "RejectPluginBodySchema",
1938
+ "BulkApproveBodySchema",
1939
+ "BulkRejectBodySchema",
1940
+ "BulkResponseSchema",
1941
+ "CreateCategoryBodySchema",
1942
+ "UpdateCategoryBodySchema",
1943
+ "CategoryIdParamsSchema",
1944
+ "CategoryIdResponseSchema",
1945
+ "type Plugin",
1946
+ "type PluginStatus",
1947
+ "type CreatePluginInput",
1948
+ "type UpdatePluginInput",
1949
+ "type PluginVersionStatus",
1950
+ "type Version",
1951
+ "type Category",
1952
+ "type Review",
1953
+ "type CreateReviewInput",
1954
+ "type MarketplaceStats",
1955
+ "type PluginListResponse",
1956
+ "type AdminPlugin",
1957
+ "type AdminDashboardStats",
1958
+ "type PluginListQuery"
1959
+ ]
1960
+ },
1961
+ admin: {
1962
+ namedExports: [
1963
+ "SystemStatsSchema",
1964
+ "HealthCheckSchema",
1965
+ "RecentActivityItemSchema",
1966
+ "RecentActivitySchema",
1967
+ "AuthUserSchema",
1968
+ "LoginRequestSchema",
1969
+ "LoginResponseSchema",
1970
+ "RegisterRequestSchema",
1971
+ "UserSchema",
1972
+ "UserListSchema",
1973
+ "UpdateUserRequestSchema",
1974
+ "CreateUserRequestSchema",
1975
+ "ClearTodosResultSchema",
1976
+ "AdminSuccessSchema",
1977
+ "DownloadTokenSchema",
1978
+ "type SystemStats",
1979
+ "type HealthCheck",
1980
+ "type RecentActivityItem",
1981
+ "type AuthUserResponse",
1982
+ "type CreateUserRequest",
1983
+ "type LoginRequest",
1984
+ "type LoginResponse",
1985
+ "type RegisterRequest",
1986
+ "type User",
1987
+ "type UpdateUserRequest",
1988
+ "type ClearTodosResult"
1989
+ ]
1990
+ },
1991
+ audit: {
1992
+ namedExports: ["ResourceTypeSchema", "ActionTypeSchema", "AuditLogSchema", "type AuditLogType"]
1993
+ },
1994
+ captcha: {
1995
+ namedExports: [
1996
+ "CaptchaResponseSchema",
1997
+ "VerifyCaptchaRequestSchema",
1998
+ "CaptchaVerifyResponseSchema",
1999
+ "type CaptchaResponse",
2000
+ "type VerifyCaptchaRequest",
2001
+ "type CaptchaVerifyResponse"
2002
+ ]
2003
+ },
2004
+ cart: {
2005
+ namedExports: [
2006
+ "CartItemSchema",
2007
+ "CartSummarySchema",
2008
+ "CartResponseSchema",
2009
+ "AddCartItemSchema",
2010
+ "CartItemIdSchema",
2011
+ "type CartItem",
2012
+ "type CartSummary",
2013
+ "type CartResponse",
2014
+ "type AddCartItemInput"
2015
+ ]
2016
+ },
2017
+ community: {
2018
+ namedExports: [
2019
+ "TopicStatusSchema",
2020
+ "TopicTagSchema",
2021
+ "TopicAuthorSchema",
2022
+ "TopicSchema",
2023
+ "TopicsResponseSchema",
2024
+ "ProfileStatsSchema",
2025
+ "ActivityTypeSchema",
2026
+ "ProfileActivitySchema",
2027
+ "ProfileResponseSchema",
2028
+ "type TopicStatus",
2029
+ "type TopicTag",
2030
+ "type TopicAuthor",
2031
+ "type Topic",
2032
+ "type ProfileStats",
2033
+ "type ActivityType",
2034
+ "type ProfileActivity",
2035
+ "type ProfileResponse"
2036
+ ]
2037
+ },
2038
+ content: {
2039
+ namedExports: [
2040
+ "ContentCategorySchema",
2041
+ "ContentStatusSchema",
2042
+ "ContentSchema",
2043
+ "CreateContentSchema",
2044
+ "UpdateContentSchema",
2045
+ "ContentListSchema",
2046
+ "ContentDeleteResultSchema",
2047
+ "type ContentCategory",
2048
+ "type ContentStatus",
2049
+ "type Content",
2050
+ "type CreateContentInput",
2051
+ "type UpdateContentInput",
2052
+ "type ContentDeleteResult"
2053
+ ]
2054
+ },
2055
+ dashboard: {
2056
+ namedExports: [
2057
+ "DashboardStatSchema",
2058
+ "RevenueDataSchema",
2059
+ "ActivityStatusSchema",
2060
+ "ActivitySchema",
2061
+ "DashboardResponseSchema",
2062
+ "type DashboardStat",
2063
+ "type RevenueData",
2064
+ "type ActivityStatus",
2065
+ "type Activity",
2066
+ "type DashboardResponse"
2067
+ ]
2068
+ },
2069
+ merchant: {
2070
+ namedExports: [
2071
+ "MerchantSchema",
2072
+ "MerchantLoginSchema",
2073
+ "MerchantLoginResponseSchema",
2074
+ "MerchantStatsSchema",
2075
+ "ProductSchema",
2076
+ "CreateProductSchema",
2077
+ "UpdateProductSchema",
2078
+ "ProductListSchema",
2079
+ "ProductListResponseSchema",
2080
+ "ProductQuerySchema",
2081
+ "type Merchant",
2082
+ "type MerchantLoginInput",
2083
+ "type MerchantLoginResponse",
2084
+ "type MerchantStats",
2085
+ "type Product",
2086
+ "type CreateProductInput",
2087
+ "type UpdateProductInput",
2088
+ "type ProductListResponse",
2089
+ "type ProductQuery"
2090
+ ]
2091
+ },
2092
+ tenant: {
2093
+ namedExports: [
2094
+ "TenantSchema",
2095
+ "TenantStatusSchema",
2096
+ "TenantPlanSchema",
2097
+ "TenantSettingsSchema",
2098
+ "CreateTenantSchema",
2099
+ "UpdateTenantSchema",
2100
+ "TenantIdSchema",
2101
+ "TenantSlugSchema",
2102
+ "TenantListResponseSchema",
2103
+ "TenantQuerySchema",
2104
+ "TenantIdResponseSchema",
2105
+ "type Tenant",
2106
+ "type TenantStatus",
2107
+ "type TenantPlan",
2108
+ "type TenantSettings",
2109
+ "type CreateTenantInput",
2110
+ "type UpdateTenantInput",
2111
+ "type TenantId",
2112
+ "type TenantSlug",
2113
+ "type TenantListResponse",
2114
+ "type TenantQuery",
2115
+ "type TenantIdResponse"
2116
+ ]
2117
+ },
2118
+ dispute: {
2119
+ namedExports: [
2120
+ "DisputeTypeSchema",
2121
+ "DisputeStatusSchema",
2122
+ "DisputeSchema",
2123
+ "CreateDisputeSchema",
2124
+ "UpdateDisputeSchema",
2125
+ "ResolveDisputeSchema",
2126
+ "DisputeListSchema",
2127
+ "DisputeDeleteResultSchema",
2128
+ "type DisputeType",
2129
+ "type DisputeStatus",
2130
+ "type Dispute",
2131
+ "type CreateDisputeInput",
2132
+ "type UpdateDisputeInput",
2133
+ "type ResolveDisputeInput",
2134
+ "type DisputeDeleteResult"
2135
+ ]
2136
+ },
2137
+ order: {
2138
+ namedExports: [
2139
+ "OrderStatusSchema",
2140
+ "OrderSchema",
2141
+ "CreateOrderSchema",
2142
+ "UpdateOrderSchema",
2143
+ "OrderListSchema",
2144
+ "OrderQuerySchema",
2145
+ "OrderDeleteResultSchema",
2146
+ "ProcessOrderSchema",
2147
+ "CancelOrderSchema",
2148
+ "RemoveCartItemResponseSchema",
2149
+ "ECommerceProductSchema",
2150
+ "ECommerceOrderStatusSchema",
2151
+ "ECommerceOrderSchema",
2152
+ "ECommerceOrderListSchema",
2153
+ "type OrderStatus",
2154
+ "type Order",
2155
+ "type CreateOrderInput",
2156
+ "type UpdateOrderInput",
2157
+ "type OrderDeleteResult",
2158
+ "type ProcessOrderInput",
2159
+ "type CancelOrderInput",
2160
+ "type OrderQueryInput",
2161
+ "type RemoveCartItemResponse",
2162
+ "type ECommerceProduct",
2163
+ "type ECommerceOrderStatus",
2164
+ "type ECommerceOrder"
2165
+ ]
2166
+ },
2167
+ permission: {
2168
+ namedExports: [
2169
+ "RoleEnum",
2170
+ "PermissionEnum",
2171
+ "RoleInfoSchema",
2172
+ "PermissionInfoSchema",
2173
+ "UserPermissionsSchema",
2174
+ "MenuItemSchema",
2175
+ "PageActionSchema",
2176
+ "PagePermissionConfigSchema",
2177
+ "PermissionCategorySchema",
2178
+ "RoleListSchema",
2179
+ "PermissionListSchema",
2180
+ "MenuConfigSchema",
2181
+ "PagePermissionsSchema",
2182
+ "PermissionCategoriesSchema",
2183
+ "RoleLabelsSchema",
2184
+ "PermissionLabelsSchema",
2185
+ "PermissionInitSchema",
2186
+ "type PermissionRoleType",
2187
+ "type PermissionType",
2188
+ "type RoleInfo",
2189
+ "type PermissionInfo",
2190
+ "type UserPermissions",
2191
+ "type MenuItem",
2192
+ "type PageAction",
2193
+ "type PagePermissionConfig",
2194
+ "type PermissionCategory",
2195
+ "type PermissionInit",
2196
+ "Role",
2197
+ "Permission",
2198
+ "ROLE_PERMISSIONS",
2199
+ "ROLE_LABELS",
2200
+ "PERMISSION_LABELS",
2201
+ "PERMISSION_CATEGORIES",
2202
+ "getPermissionsByRole",
2203
+ "hasPermission",
2204
+ "hasAnyPermission",
2205
+ "hasAllPermissions"
2206
+ ]
2207
+ },
2208
+ role: {
2209
+ namedExports: [
2210
+ "RoleSchema",
2211
+ "CreateRoleSchema",
2212
+ "UpdateRoleSchema",
2213
+ "UpdateRolePermissionsSchema",
2214
+ "RoleSuccessSchema",
2215
+ "type RoleDataType",
2216
+ "type CreateRoleType",
2217
+ "type UpdateRoleType"
2218
+ ]
2219
+ },
2220
+ ticket: {
2221
+ namedExports: [
2222
+ "TicketStatusSchema",
2223
+ "TicketPrioritySchema",
2224
+ "TicketCategorySchema",
2225
+ "TicketReplySchema",
2226
+ "TicketSchema",
2227
+ "CreateTicketSchema",
2228
+ "UpdateTicketSchema",
2229
+ "ReplyTicketSchema",
2230
+ "TicketListSchema",
2231
+ "TicketDeleteResultSchema",
2232
+ "type TicketStatus",
2233
+ "type TicketPriority",
2234
+ "type TicketCategory",
2235
+ "type TicketReply",
2236
+ "type Ticket",
2237
+ "type CreateTicketInput",
2238
+ "type UpdateTicketInput",
2239
+ "type ReplyTicketInput",
2240
+ "type TicketDeleteResult"
2241
+ ]
2242
+ }
2243
+ };
2244
+ var ADDITIONAL_PATHS_MAP = {
2245
+ permission: ["role", "audit"]
2246
+ };
2247
+ var STANDALONE_SHARED_MODULES = {
2248
+ cart: { pages: ["CartPage.tsx"] },
2249
+ community: { pages: ["TopicsPage.tsx", "ProfilePage.tsx"], serverModules: ["content"] },
2250
+ dashboard: { pages: ["DashboardPage.tsx"] }
2251
+ };
2252
+ var moduleOrder = [
2253
+ "chat",
2254
+ "file",
2255
+ "todos",
2256
+ "notifications",
2257
+ "auth",
2258
+ "plugin",
2259
+ "admin",
2260
+ "audit",
2261
+ "captcha",
2262
+ "cart",
2263
+ "community",
2264
+ "content",
2265
+ "dashboard",
2266
+ "dispute",
2267
+ "merchant",
2268
+ "order",
2269
+ "permission",
2270
+ "role",
2271
+ "tenant",
2272
+ "ticket"
2273
+ ];
2274
+ function generateSharedSchemasIndex(resolved) {
2275
+ const header = `// Re-export interfaces from implementation files
2276
+ export type { WSClient, WSProtocol, WSStatus } from '../core/ws-client'
2277
+ export type { SSEClient, SSEProtocol } from '../core/sse-client'
2278
+
2279
+ // Re-export core
2280
+ export {
2281
+ ApiSuccessSchema,
2282
+ ApiErrorSchema,
2283
+ ApiResponseSchema,
2284
+ type ApiSuccess,
2285
+ type ApiError,
2286
+ type ApiResponse,
2287
+ type RpcMethod,
2288
+ type EventName,
2289
+ type RpcInput,
2290
+ type RpcOutput,
2291
+ type EventPayload,
2292
+ createWSClient,
2293
+ createSSEClient,
2294
+ } from '../core'
2295
+
2296
+ // Re-export modules
2297
+ `;
2298
+ const moduleLines = [];
2299
+ const exportedNames = /* @__PURE__ */ new Set();
2300
+ const firstSeenModule = /* @__PURE__ */ new Map();
2301
+ const droppedExports = [];
2302
+ for (const moduleName of moduleOrder) {
2303
+ const exports = MODULE_EXPORTS2[moduleName];
2304
+ if (!exports) continue;
2305
+ const shouldInclude = shouldIncludeModule(moduleName, resolved);
2306
+ if (!shouldInclude) continue;
2307
+ const uniqueExports = exports.namedExports.filter((name) => {
2308
+ if (exportedNames.has(name)) {
2309
+ const originalModule = firstSeenModule.get(name) ?? "unknown";
2310
+ droppedExports.push({ name, moduleName, originalModule });
2311
+ return false;
2312
+ }
2313
+ exportedNames.add(name);
2314
+ firstSeenModule.set(name, moduleName);
2315
+ return true;
2316
+ });
2317
+ if (uniqueExports.length === 0) continue;
2318
+ const importPath = getImportPath(moduleName, resolved);
2319
+ moduleLines.push(
2320
+ `export {
2321
+ ${uniqueExports.join(",\n ")},
2322
+ } from '../modules/${importPath}'`
2323
+ );
2324
+ }
2325
+ for (const { name, moduleName, originalModule } of droppedExports) {
2326
+ console.warn(
2327
+ `\u26A0\uFE0F Schema collision: "${name}" defined in both ${originalModule} and ${moduleName}. Using ${originalModule}'s version. Rename to avoid ambiguity.`
2328
+ );
2329
+ }
2330
+ return header + moduleLines.join("\n") + "\n";
2331
+ }
2332
+ function shouldIncludeModule(moduleName, resolved) {
2333
+ if (resolved.modules.has(moduleName)) return true;
2334
+ const standalone = STANDALONE_SHARED_MODULES[moduleName];
2335
+ if (standalone) {
2336
+ const hasRelevantPage = standalone.pages && [...resolved.modules.values()].some(
2337
+ (m) => m.clientPages?.some((p) => standalone.pages.includes(p.name + ".tsx")) ?? false
2338
+ );
2339
+ const hasRelevantServerModule = standalone.serverModules?.some((sm) => resolved.modules.has(sm)) ?? false;
2340
+ return !!(hasRelevantPage || hasRelevantServerModule);
2341
+ }
2342
+ for (const [parentModule, additionalPaths] of Object.entries(ADDITIONAL_PATHS_MAP)) {
2343
+ if (additionalPaths.includes(moduleName) && resolved.modules.has(parentModule)) {
2344
+ return true;
2345
+ }
2346
+ }
2347
+ return false;
2348
+ }
2349
+ function getImportPath(moduleName, resolved) {
2350
+ const manifest = resolved.modules.get(moduleName);
2351
+ if (manifest?.sharedSchemas?.path) {
2352
+ return manifest.sharedSchemas.path;
2353
+ }
2354
+ return moduleName;
2355
+ }
2356
+
2357
+ // src/generators/middleware-index.ts
2358
+ function generateMiddlewareIndex(resolved) {
2359
+ const lines = [];
2360
+ const hasAuthOrPermission = resolved.modules.has("auth") || resolved.hasPermission;
2361
+ lines.push(`export { corsMiddleware, createCorsMiddleware, type CorsOptions } from './cors'`);
2362
+ lines.push(
2363
+ `export { loggerMiddleware, createLoggerMiddleware, type LoggerOptions } from './logger'`
2364
+ );
2365
+ lines.push(
2366
+ `export {
2367
+ errorHandlerMiddleware,
2368
+ createErrorHandlerMiddleware,
2369
+ type ErrorHandlerOptions,
2370
+ } from './error-handler'`
2371
+ );
2372
+ if (hasAuthOrPermission) {
2373
+ lines.push(
2374
+ `export {
2375
+ authMiddleware,
2376
+ requireSuperAdminMiddleware,
2377
+ requireCustomerServiceMiddleware,
2378
+ requirePermissionsMiddleware,
2379
+ type AuthUser,
2380
+ type AuthMiddlewareOptions,
2381
+ } from './auth'`
2382
+ );
2383
+ }
2384
+ if (resolved.hasCaptcha) {
2385
+ lines.push(
2386
+ `export {
2387
+ captchaMiddleware,
2388
+ markCaptchaVerifiedMiddleware,
2389
+ clearCaptchaSessionMiddleware,
2390
+ type CaptchaConfig,
2391
+ } from './captcha'`
2392
+ );
2393
+ }
2394
+ if (resolved.hasPermission) {
2395
+ lines.push(`export { permissionMiddleware } from './permission'`);
2396
+ }
2397
+ lines.push(`export { rateLimitMiddleware, type RateLimitOptions } from './rate-limit'`);
2398
+ if (hasAuthOrPermission) {
2399
+ lines.push(`export { getAuthUser } from '../utils/auth'`);
2400
+ }
2401
+ return lines.join("\n") + "\n";
2402
+ }
2403
+
2404
+ // src/generators/auth-middleware.ts
2405
+ function generateAuthMiddleware(resolved) {
2406
+ if (resolved.modules.has("auth") && !resolved.hasPermission) {
2407
+ return generateSimplifiedAuthMiddleware();
2408
+ }
2409
+ return generateNoopAuthMiddleware();
2410
+ }
2411
+ function generateSimplifiedAuthMiddleware() {
2412
+ return `import type { MiddlewareHandler } from 'hono'
2413
+ import { createModuleLoggerSync } from '../utils/logger'
2414
+
2415
+ export type UserRole = 'user' | 'admin'
2416
+
2417
+ export interface AuthUser {
2418
+ id: string
2419
+ username: string
2420
+ email: string
2421
+ role: UserRole
2422
+ avatar?: string
2423
+ }
2424
+
2425
+ export interface AuthMiddlewareOptions {
2426
+ requiredRole?: UserRole
2427
+ }
2428
+
2429
+ declare module 'hono' {
2430
+ interface ContextVariableMap {
2431
+ authUser: AuthUser
2432
+ }
2433
+ }
2434
+
2435
+ function extractToken(authHeader: string | undefined): string | null {
2436
+ if (!authHeader) return null
2437
+ if (!authHeader.startsWith('Bearer ')) return null
2438
+ return authHeader.slice(7)
2439
+ }
2440
+
2441
+ export function authMiddleware(_options: AuthMiddlewareOptions = {}): MiddlewareHandler {
2442
+ const log = createModuleLoggerSync('auth')
2443
+
2444
+ return async (c, next) => {
2445
+ const token = extractToken(c.req.header('Authorization'))
2446
+
2447
+ if (!token) {
2448
+ log.warn({ path: c.req.path, method: c.req.method }, 'Missing auth token')
2449
+ return c.json({ success: false, error: 'Authentication required', status: 401 }, 401)
2450
+ }
2451
+
2452
+ try {
2453
+ const jwt = await import('jsonwebtoken')
2454
+ const secretKey = process.env.AUTH_SECRET_KEY || 'dev-secret-key-change-in-production'
2455
+ const decoded = jwt.verify(token, secretKey) as {
2456
+ userId: string
2457
+ username: string
2458
+ email: string
2459
+ role: string
2460
+ }
2461
+
2462
+ const user: AuthUser = {
2463
+ id: decoded.userId,
2464
+ username: decoded.username,
2465
+ email: decoded.email,
2466
+ role: decoded.role as UserRole,
2467
+ }
2468
+
2469
+ c.set('authUser', user)
2470
+ log.info({ userId: user.id, path: c.req.path }, 'User authenticated')
2471
+ await next()
2472
+ } catch {
2473
+ log.warn({ path: c.req.path, method: c.req.method }, 'Invalid auth token')
2474
+ return c.json({ success: false, error: 'Invalid or expired token', status: 401 }, 401)
2475
+ }
2476
+ }
2477
+ }
2478
+
2479
+ export function requireSuperAdminMiddleware(): MiddlewareHandler {
2480
+ return authMiddleware()
2481
+ }
2482
+
2483
+ export function requireCustomerServiceMiddleware(): MiddlewareHandler {
2484
+ return authMiddleware()
2485
+ }
2486
+
2487
+ export function requirePermissionsMiddleware(): MiddlewareHandler {
2488
+ return authMiddleware()
2489
+ }
2490
+ `;
2491
+ }
2492
+ function generateNoopAuthMiddleware() {
2493
+ return `import type { MiddlewareHandler } from 'hono'
2494
+ import { createModuleLoggerSync } from '../utils/logger'
2495
+
2496
+ export type UserRole = 'user' | 'admin'
2497
+
2498
+ export interface AuthUser {
2499
+ id: string
2500
+ username: string
2501
+ email: string
2502
+ role: UserRole
2503
+ avatar?: string
2504
+ }
2505
+
2506
+ export interface AuthMiddlewareOptions {
2507
+ requiredRole?: UserRole
2508
+ }
2509
+
2510
+ declare module 'hono' {
2511
+ interface ContextVariableMap {
2512
+ authUser: AuthUser
2513
+ }
2514
+ }
2515
+
2516
+ const DEV_USER: AuthUser = {
2517
+ id: 'dev-user-1',
2518
+ username: 'devuser',
2519
+ email: 'dev@example.com',
2520
+ role: 'admin',
2521
+ avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=dev',
2522
+ }
2523
+
2524
+ function extractToken(authHeader: string | undefined): string | null {
2525
+ if (!authHeader) return null
2526
+ if (!authHeader.startsWith('Bearer ')) return null
2527
+ return authHeader.slice(7)
2528
+ }
2529
+
2530
+ export function authMiddleware(_options: AuthMiddlewareOptions = {}): MiddlewareHandler {
2531
+ const log = createModuleLoggerSync('auth')
2532
+
2533
+ return async (c, next) => {
2534
+ const token = extractToken(c.req.header('Authorization'))
2535
+
2536
+ if (!token) {
2537
+ c.set('authUser', { ...DEV_USER, id: 'anonymous' })
2538
+ log.info({ path: c.req.path }, 'Anonymous access')
2539
+ await next()
2540
+ return
2541
+ }
2542
+
2543
+ c.set('authUser', DEV_USER)
2544
+ log.info({ userId: DEV_USER.id, path: c.req.path }, 'Dev user authenticated')
2545
+ await next()
2546
+ }
2547
+ }
2548
+
2549
+ export function requireSuperAdminMiddleware(): MiddlewareHandler {
2550
+ return authMiddleware()
2551
+ }
2552
+
2553
+ export function requireCustomerServiceMiddleware(): MiddlewareHandler {
2554
+ return authMiddleware()
2555
+ }
2556
+
2557
+ export function requirePermissionsMiddleware(): MiddlewareHandler {
2558
+ return authMiddleware()
2559
+ }
2560
+ `;
2561
+ }
2562
+
2563
+ // src/generators/auth-utils.ts
2564
+ function generateAuthUtils(resolved) {
2565
+ const hasAuthOrPermission = resolved.modules.has("auth") || resolved.hasPermission;
2566
+ if (!hasAuthOrPermission) {
2567
+ return `import type { Context } from 'hono'
2568
+ import type { AuthUser } from '../middleware/auth'
2569
+
2570
+ export function getAuthUser(c: Context): AuthUser {
2571
+ return c.get('authUser')
2572
+ }
2573
+ `;
2574
+ }
2575
+ if (resolved.modules.has("auth") && !resolved.hasPermission) {
2576
+ return `import type { Context } from 'hono'
2577
+ import type { AuthUser } from '../middleware/auth'
2578
+
2579
+ export function getAuthUser(c: Context): AuthUser {
2580
+ return c.get('authUser')
2581
+ }
2582
+
2583
+ export function getOptionalAuthUser(c: Context): AuthUser | null {
2584
+ try {
2585
+ return c.get('authUser')
2586
+ } catch {
2587
+ return null
2588
+ }
2589
+ }
2590
+ `;
2591
+ }
2592
+ return `import type { Context } from 'hono'
2593
+ import type { AuthUser } from '../middleware/auth'
2594
+ import { Role } from '@shared/modules/permission'
2595
+
2596
+ interface MockUser {
2597
+ id: string
2598
+ username: string
2599
+ email: string
2600
+ role: string
2601
+ status: string
2602
+ avatar: string
2603
+ createdAt: string
2604
+ updatedAt: string
2605
+ }
2606
+
2607
+ const mockUsers: MockUser[] = [
2608
+ {
2609
+ id: '1',
2610
+ username: 'superadmin',
2611
+ email: 'superadmin@example.com',
2612
+ role: Role.SUPER_ADMIN,
2613
+ status: 'active',
2614
+ avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=superadmin',
2615
+ createdAt: '2024-01-01T00:00:00Z',
2616
+ updatedAt: '2024-01-01T00:00:00Z',
2617
+ },
2618
+ {
2619
+ id: '2',
2620
+ username: 'customerservice',
2621
+ email: 'customerservice@example.com',
2622
+ role: Role.CUSTOMER_SERVICE,
2623
+ status: 'active',
2624
+ avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=customerservice',
2625
+ createdAt: '2024-01-02T00:00:00Z',
2626
+ updatedAt: '2024-01-02T00:00:00Z',
2627
+ },
2628
+ {
2629
+ id: '3',
2630
+ username: 'user1',
2631
+ email: 'user1@example.com',
2632
+ role: Role.USER,
2633
+ status: 'active',
2634
+ avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=user1',
2635
+ createdAt: '2024-01-03T00:00:00Z',
2636
+ updatedAt: '2024-01-03T00:00:00Z',
2637
+ },
2638
+ ]
2639
+
2640
+ const mockTokens: Map<string, string> = new Map([
2641
+ ['super-admin-token', '1'],
2642
+ ['customer-service-token', '2'],
2643
+ ['user-token', '3'],
2644
+ ])
2645
+
2646
+ export function getAuthUser(c: Context): AuthUser {
2647
+ return c.get('authUser')
2648
+ }
2649
+
2650
+ export function verifyToken(token: string): MockUser | null {
2651
+ const userId = mockTokens.get(token)
2652
+
2653
+ if (!userId) {
2654
+ return null
2655
+ }
2656
+
2657
+ return mockUsers.find(u => u.id === userId) || null
2658
+ }
2659
+
2660
+ export function getMockUsers(): MockUser[] {
2661
+ return mockUsers
2662
+ }
2663
+
2664
+ export function getMockTokens(): Map<string, string> {
2665
+ return mockTokens
2666
+ }
2667
+ `;
2668
+ }
2669
+
2670
+ // src/generators/client-components-index.ts
2671
+ function generateClientComponentsIndex(resolved) {
2672
+ const lines = [];
2673
+ lines.push(`export { StatusBadge, type ColorScheme } from './StatusBadge'`);
2674
+ lines.push(`export { LoadingSpinner } from './LoadingSpinner'`);
2675
+ lines.push(`export { EmptyState } from './EmptyState'`);
2676
+ if (resolved.modules.has("chat") || resolved.modules.has("notifications")) {
2677
+ lines.push(`export { ConnectionStatus } from './ConnectionStatus'`);
2678
+ }
2679
+ if (resolved.modules.has("chat")) {
2680
+ lines.push(`export { MessageCard } from './MessageCard'`);
2681
+ }
2682
+ if (resolved.modules.has("admin") || resolved.modules.has("auth")) {
2683
+ lines.push(`export { AuthButton } from './AuthButton'`);
2684
+ }
2685
+ return lines.join("\n") + "\n";
2686
+ }
2687
+
2688
+ // src/generators/cli-modules-index.ts
2689
+ var ALWAYS_INCLUDED = {
2690
+ dir: "config",
2691
+ registerFunction: "registerConfigCommands"
2692
+ };
2693
+ function generateCliModulesIndex(resolved) {
2694
+ const modules = [];
2695
+ const registrations = [];
2696
+ for (const [, manifest] of resolved.modules) {
2697
+ if (manifest.cliModule) {
2698
+ const { dir, registerFunction } = manifest.cliModule;
2699
+ modules.push(`import { ${registerFunction} } from './${dir}'`);
2700
+ registrations.push(`${registerFunction}(site)`);
2701
+ }
2702
+ }
2703
+ modules.push(`import { ${ALWAYS_INCLUDED.registerFunction} } from './${ALWAYS_INCLUDED.dir}'`);
2704
+ registrations.push(`${ALWAYS_INCLUDED.registerFunction}(site)`);
2705
+ const exports = modules.map((m) => {
2706
+ const match = m.match(/\{ (\w+) \}/);
2707
+ return match ? match[1] : "";
2708
+ }).filter(Boolean);
2709
+ return `import type { Core } from '@dyyz1993/xcli-core'
2710
+ ${modules.join("\n")}
2711
+
2712
+ /**
2713
+ * Register all builtin CLI commands to xcli-core.
2714
+ * Each register function receives a SiteInstance for command registration.
2715
+ */
2716
+ export function registerBuiltinCommands(app: Core) {
2717
+ const api = app.loader.getAPI()
2718
+
2719
+ const site = api.createSite({
2720
+ name: 'local-server',
2721
+ url: 'http://localhost:3010',
2722
+ })
2723
+
2724
+ ${registrations.map((r) => ` ${r}`).join("\n")}
2725
+ }
2726
+
2727
+ export { ${exports.join(", ")} }
2728
+ `;
2729
+ }
2730
+
2731
+ // src/generators/package-json.ts
2732
+ var MODULE_PACKAGES = {
2733
+ admin: ["bcryptjs"],
2734
+ auth: ["bcryptjs"]
2735
+ };
2736
+ var ADMIN_PANEL_PACKAGES = ["antd"];
2737
+ var CLI_PACKAGES = ["commander"];
2738
+ var UNUSED_PACKAGES = ["lodash-es", "chalk", "mysql2"];
2739
+ var CLIENT_PACKAGES = [
2740
+ "react",
2741
+ "react-dom",
2742
+ "react-helmet-async",
2743
+ "react-router-dom",
2744
+ "lucide-react",
2745
+ "zustand"
2746
+ ];
2747
+ var CLIENT_DEV_PACKAGES = [
2748
+ "@vitejs/plugin-react",
2749
+ "@testing-library/react",
2750
+ "@testing-library/jest-dom",
2751
+ "@testing-library/dom",
2752
+ "vite",
2753
+ "jsdom",
2754
+ "tailwindcss",
2755
+ "@tailwindcss/postcss",
2756
+ "postcss",
2757
+ "autoprefixer",
2758
+ "@playwright/test",
2759
+ "playwright",
2760
+ "@prerenderer/renderer-jsdom",
2761
+ "@prerenderer/renderer-puppeteer",
2762
+ "@prerenderer/rollup-plugin",
2763
+ "eventsource"
2764
+ ];
2765
+ var CLIENT_TYPE_PACKAGES = ["@types/react", "@types/react-dom"];
2766
+ function filterPackageJson(pkg, resolved) {
2767
+ const result = { ...pkg };
2768
+ const packagesToRemove = new Set(UNUSED_PACKAGES);
2769
+ const packageToModules = {};
2770
+ for (const [module, packages] of Object.entries(MODULE_PACKAGES)) {
2771
+ for (const pkg2 of packages) {
2772
+ if (!packageToModules[pkg2]) packageToModules[pkg2] = [];
2773
+ packageToModules[pkg2].push(module);
2774
+ }
2775
+ }
2776
+ for (const [pkg2, modules] of Object.entries(packageToModules)) {
2777
+ if (!modules.some((m) => resolved.modules.has(m))) {
2778
+ packagesToRemove.add(pkg2);
2779
+ }
2780
+ }
2781
+ const hasAntdConsumer = resolved.modules.has("admin") || resolved.modules.has("tenant") || resolved.modules.has("merchant");
2782
+ if (!hasAntdConsumer) {
2783
+ for (const pkg2 of ADMIN_PANEL_PACKAGES) {
2784
+ packagesToRemove.add(pkg2);
2785
+ }
2786
+ }
2787
+ for (const pkg2 of CLI_PACKAGES) {
2788
+ packagesToRemove.add(pkg2);
2789
+ }
2790
+ if (!resolved.hasClient) {
2791
+ for (const pkg2 of CLIENT_PACKAGES) {
2792
+ packagesToRemove.add(pkg2);
2793
+ }
2794
+ }
2795
+ if (result.dependencies && typeof result.dependencies === "object") {
2796
+ const deps = { ...result.dependencies };
2797
+ for (const pkg2 of packagesToRemove) {
2798
+ delete deps[pkg2];
2799
+ }
2800
+ result.dependencies = deps;
2801
+ }
2802
+ if (result.devDependencies && typeof result.devDependencies === "object") {
2803
+ const devDeps = { ...result.devDependencies };
2804
+ if (!resolved.modules.has("admin")) {
2805
+ delete devDeps["@testing-library/user-event"];
2806
+ }
2807
+ if (!resolved.hasClient) {
2808
+ for (const pkg2 of CLIENT_DEV_PACKAGES) {
2809
+ delete devDeps[pkg2];
2810
+ }
2811
+ for (const pkg2 of CLIENT_TYPE_PACKAGES) {
2812
+ delete devDeps[pkg2];
2813
+ }
2814
+ }
2815
+ result.devDependencies = devDeps;
2816
+ }
2817
+ if (!resolved.hasClient && result.scripts && typeof result.scripts === "object") {
2818
+ const scripts = { ...result.scripts };
2819
+ scripts["dev"] = "NODE_ENV=development node --import tsx src/server/entries/node.ts";
2820
+ scripts["build"] = "npm run build:server && npm run build:cli";
2821
+ scripts["build:all"] = "npm run build:server && npm run build:cli";
2822
+ delete scripts["build:client"];
2823
+ delete scripts["build:cloudflare"];
2824
+ delete scripts["preview"];
2825
+ delete scripts["dev:todo"];
2826
+ delete scripts["dev:plugin"];
2827
+ delete scripts["dev:ecommerce"];
2828
+ delete scripts["dev:community"];
2829
+ delete scripts["dev:forum"];
2830
+ delete scripts["dev:saas"];
2831
+ delete scripts["dev:cf"];
2832
+ delete scripts["deploy:cf"];
2833
+ delete scripts["test:e2e"];
2834
+ delete scripts["test:e2e:ui"];
2835
+ delete scripts["test:e2e:debug"];
2836
+ delete scripts["test:full"];
2837
+ result.scripts = scripts;
2838
+ }
2839
+ return result;
2840
+ }
2841
+ function generateViteConfig(resolved, templateDir) {
2842
+ const originalPath = join(templateDir, "vite.config.ts");
2843
+ let content = readFileSync(originalPath, "utf-8");
2844
+ const entriesToRemove = [];
2845
+ const aliasesToRemove = [];
2846
+ if (!resolved.modules.has("admin")) {
2847
+ entriesToRemove.push("admin");
2848
+ aliasesToRemove.push("@admin");
2849
+ }
2850
+ if (!resolved.modules.has("tenant")) {
2851
+ entriesToRemove.push("tenant");
2852
+ aliasesToRemove.push("@tenant");
2853
+ }
2854
+ if (!resolved.modules.has("merchant")) {
2855
+ entriesToRemove.push("merchant");
2856
+ aliasesToRemove.push("@merchant");
2857
+ }
2858
+ for (const name of entriesToRemove) {
2859
+ const re = new RegExp(
2860
+ `^\\s*${name}:\\s*path\\.resolve\\(__dirname,\\s*['"]${name}\\.html['"]\\),\\s*$\\n?`,
2861
+ "gm"
2862
+ );
2863
+ content = content.replace(re, "");
2864
+ }
2865
+ for (const alias of aliasesToRemove) {
2866
+ const escapedAlias = alias.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2867
+ const dirName = alias.replace("@", "");
2868
+ const re = new RegExp(
2869
+ `^\\s*'${escapedAlias}':\\s*path\\.resolve\\(__dirname,\\s*['"]src\\/${dirName}['"]\\),\\s*$\\n?`,
2870
+ "gm"
2871
+ );
2872
+ content = content.replace(re, "");
2873
+ }
2874
+ return content;
2875
+ }
2876
+
2877
+ // src/generators/client-preset-ui-config.ts
2878
+ function getPresetType(presetId) {
2879
+ const map = {
2880
+ "todo-app": "todo",
2881
+ "xbrowser-marketplace": "plugin",
2882
+ ecommerce: "ecommerce",
2883
+ "fullstack-admin": "saas",
2884
+ forum: "community",
2885
+ minimal: "todo"
2886
+ };
2887
+ return map[presetId] || "todo";
2888
+ }
2889
+ function getThemeForPresetType(presetType) {
2890
+ const themes = {
2891
+ todo: {
2892
+ constName: "TODO_THEME",
2893
+ theme: `{
2894
+ primaryColor: '#6366f1',
2895
+ primaryHover: '#4f46e5',
2896
+ bgColor: '#ffffff',
2897
+ textColor: '#111827',
2898
+ secondaryBg: '#f9fafb',
2899
+ borderColor: '#e5e7eb',
2900
+ borderRadius: '12px',
2901
+ logoText: 'Biomimic',
2902
+ fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",
2903
+ }`
2904
+ },
2905
+ plugin: {
2906
+ constName: "PLUGIN_MARKET_THEME",
2907
+ theme: `{
2908
+ primaryColor: '#3b82f6',
2909
+ primaryHover: '#2563eb',
2910
+ bgColor: '#ffffff',
2911
+ textColor: '#111827',
2912
+ secondaryBg: '#f0f9ff',
2913
+ borderColor: '#bae6fd',
2914
+ borderRadius: '12px',
2915
+ logoText: 'PluginHub',
2916
+ fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",
2917
+ }`
2918
+ },
2919
+ ecommerce: {
2920
+ constName: "ECOMMERCE_THEME",
2921
+ theme: `{
2922
+ primaryColor: '#f59e0b',
2923
+ primaryHover: '#d97706',
2924
+ bgColor: '#ffffff',
2925
+ textColor: '#111827',
2926
+ secondaryBg: '#fffbeb',
2927
+ borderColor: '#fde68a',
2928
+ borderRadius: '12px',
2929
+ logoText: 'ShopMart',
2930
+ fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",
2931
+ }`
2932
+ },
2933
+ saas: {
2934
+ constName: "SAAS_ADMIN_THEME",
2935
+ theme: `{
2936
+ primaryColor: '#1f2937',
2937
+ primaryHover: '#374151',
2938
+ bgColor: '#f9fafb',
2939
+ textColor: '#111827',
2940
+ secondaryBg: '#f3f4f6',
2941
+ borderColor: '#e5e7eb',
2942
+ borderRadius: '8px',
2943
+ logoText: 'AdminPanel',
2944
+ fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",
2945
+ }`
2946
+ },
2947
+ community: {
2948
+ constName: "COMMUNITY_THEME",
2949
+ theme: `{
2950
+ primaryColor: '#f97316',
2951
+ primaryHover: '#ea580c',
2952
+ bgColor: '#ffffff',
2953
+ textColor: '#111827',
2954
+ secondaryBg: '#fff7ed',
2955
+ borderColor: '#fed7aa',
2956
+ borderRadius: '12px',
2957
+ logoText: 'CommunityHub',
2958
+ fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",
2959
+ }`
2960
+ }
2961
+ };
2962
+ return themes[presetType] || themes.todo;
2963
+ }
2964
+ function getRoutesForPreset(presetType, resolved) {
2965
+ const hasModule = (m) => resolved.modules.has(m);
2966
+ const loginRoute = {
2967
+ path: "/login",
2968
+ importPath: "./pages/LoginPage",
2969
+ componentName: "LoginPage",
2970
+ label: "Login"
2971
+ };
2972
+ const registerRoute = {
2973
+ path: "/register",
2974
+ importPath: "./pages/RegisterPage",
2975
+ componentName: "RegisterPage",
2976
+ label: "Register"
2977
+ };
2978
+ const maybeAuthRoutes = hasModule("auth") ? [
2979
+ loginRoute,
2980
+ registerRoute,
2981
+ {
2982
+ path: "/profile",
2983
+ importPath: "./pages/ProfilePage",
2984
+ componentName: "ProfilePage",
2985
+ label: "Profile"
2986
+ }
2987
+ ] : [];
2988
+ switch (presetType) {
2989
+ case "todo": {
2990
+ const routes = [
2991
+ ...maybeAuthRoutes,
2992
+ {
2993
+ path: "/todos",
2994
+ importPath: "./pages/TodoPage",
2995
+ componentName: "TodoPage",
2996
+ label: "Todos"
2997
+ }
2998
+ ];
2999
+ if (hasModule("notifications")) {
3000
+ routes.push({
3001
+ path: "/notifications",
3002
+ importPath: "./pages/NotificationPage",
3003
+ componentName: "NotificationPage",
3004
+ label: "Notifications"
3005
+ });
3006
+ }
3007
+ if (hasModule("chat")) {
3008
+ routes.push({
3009
+ path: "/websocket",
3010
+ importPath: "./pages/WebSocketPage",
3011
+ componentName: "WebSocketPage",
3012
+ label: "WebSocket"
3013
+ });
3014
+ }
3015
+ return routes;
3016
+ }
3017
+ case "plugin": {
3018
+ const routes = [];
3019
+ if (hasModule("plugin")) {
3020
+ routes.push(
3021
+ {
3022
+ path: "/",
3023
+ importPath: "./pages/PluginsPage",
3024
+ componentName: "PluginsPage",
3025
+ label: "Home"
3026
+ },
3027
+ {
3028
+ path: "/plugins",
3029
+ importPath: "./pages/PluginsPage",
3030
+ componentName: "PluginsPage",
3031
+ label: "Plugins"
3032
+ },
3033
+ {
3034
+ path: "/plugins/:slug",
3035
+ importPath: "./pages/PluginDetailPage",
3036
+ componentName: "PluginDetailPage",
3037
+ label: "Plugin Detail"
3038
+ },
3039
+ {
3040
+ path: "/categories",
3041
+ importPath: "./pages/CategoriesPage",
3042
+ componentName: "CategoriesPage",
3043
+ label: "Categories"
3044
+ },
3045
+ {
3046
+ path: "/search",
3047
+ importPath: "./pages/SearchPage",
3048
+ componentName: "SearchPage",
3049
+ label: "Search"
3050
+ },
3051
+ {
3052
+ path: "/publish",
3053
+ importPath: "./pages/PublishPage",
3054
+ componentName: "PublishPage",
3055
+ label: "Publish"
3056
+ },
3057
+ {
3058
+ path: "/developer",
3059
+ importPath: "./pages/DeveloperDashboardPage",
3060
+ componentName: "DeveloperDashboardPage",
3061
+ label: "Developer"
3062
+ }
3063
+ );
3064
+ }
3065
+ routes.push(...maybeAuthRoutes);
3066
+ if (hasModule("notifications")) {
3067
+ routes.push({
3068
+ path: "/notifications",
3069
+ importPath: "./pages/NotificationPage",
3070
+ componentName: "NotificationPage",
3071
+ label: "Notifications"
3072
+ });
3073
+ }
3074
+ return routes;
3075
+ }
3076
+ case "ecommerce": {
3077
+ const routes = [];
3078
+ if (hasModule("content")) {
3079
+ routes.push(
3080
+ {
3081
+ path: "/",
3082
+ importPath: "./pages/ContentListPage",
3083
+ componentName: "ContentListPage",
3084
+ label: "Home"
3085
+ },
3086
+ {
3087
+ path: "/products",
3088
+ importPath: "./pages/ContentListPage",
3089
+ componentName: "ContentListPage",
3090
+ label: "Products"
3091
+ },
3092
+ {
3093
+ path: "/products/:id",
3094
+ importPath: "./pages/ContentDetailPage",
3095
+ componentName: "ContentDetailPage",
3096
+ label: "Product Detail"
3097
+ },
3098
+ {
3099
+ path: "/content",
3100
+ importPath: "./pages/ContentListPage",
3101
+ componentName: "ContentListPage",
3102
+ label: "Content"
3103
+ },
3104
+ {
3105
+ path: "/content/:id",
3106
+ importPath: "./pages/ContentDetailPage",
3107
+ componentName: "ContentDetailPage",
3108
+ label: "Content Detail"
3109
+ }
3110
+ );
3111
+ }
3112
+ if (hasModule("order")) {
3113
+ routes.push(
3114
+ {
3115
+ path: "/cart",
3116
+ importPath: "./pages/CartPage",
3117
+ componentName: "CartPage",
3118
+ label: "Cart"
3119
+ },
3120
+ {
3121
+ path: "/orders",
3122
+ importPath: "./pages/OrdersPage",
3123
+ componentName: "OrdersPage",
3124
+ label: "Orders"
3125
+ }
3126
+ );
3127
+ }
3128
+ routes.push(...maybeAuthRoutes);
3129
+ return routes;
3130
+ }
3131
+ case "saas": {
3132
+ const routes = [];
3133
+ if (hasModule("admin")) {
3134
+ routes.push(
3135
+ {
3136
+ path: "/dashboard",
3137
+ importPath: "./pages/DashboardPage",
3138
+ componentName: "DashboardPage",
3139
+ label: "Dashboard"
3140
+ },
3141
+ {
3142
+ path: "/settings",
3143
+ importPath: "./pages/SettingsPage",
3144
+ componentName: "SettingsPage",
3145
+ label: "Settings"
3146
+ }
3147
+ );
3148
+ }
3149
+ routes.push(...maybeAuthRoutes);
3150
+ return routes;
3151
+ }
3152
+ case "community": {
3153
+ const routes = [];
3154
+ if (hasModule("content")) {
3155
+ routes.push(
3156
+ {
3157
+ path: "/",
3158
+ importPath: "./pages/ContentListPage",
3159
+ componentName: "ContentListPage",
3160
+ label: "Home"
3161
+ },
3162
+ {
3163
+ path: "/topics",
3164
+ importPath: "./pages/ContentListPage",
3165
+ componentName: "ContentListPage",
3166
+ label: "Topics"
3167
+ },
3168
+ {
3169
+ path: "/topics/:id",
3170
+ importPath: "./pages/ContentDetailPage",
3171
+ componentName: "ContentDetailPage",
3172
+ label: "Topic Detail"
3173
+ },
3174
+ {
3175
+ path: "/popular",
3176
+ importPath: "./pages/ContentListPage",
3177
+ componentName: "ContentListPage",
3178
+ label: "Popular"
3179
+ },
3180
+ {
3181
+ path: "/content",
3182
+ importPath: "./pages/ContentListPage",
3183
+ componentName: "ContentListPage",
3184
+ label: "Content"
3185
+ },
3186
+ {
3187
+ path: "/content/:id",
3188
+ importPath: "./pages/ContentDetailPage",
3189
+ componentName: "ContentDetailPage",
3190
+ label: "Content Detail"
3191
+ }
3192
+ );
3193
+ }
3194
+ if (hasModule("chat")) {
3195
+ routes.push({
3196
+ path: "/websocket",
3197
+ importPath: "./pages/WebSocketPage",
3198
+ componentName: "WebSocketPage",
3199
+ label: "WebSocket"
3200
+ });
3201
+ }
3202
+ if (hasModule("notifications")) {
3203
+ routes.push({
3204
+ path: "/notifications",
3205
+ importPath: "./pages/NotificationPage",
3206
+ componentName: "NotificationPage",
3207
+ label: "Notifications"
3208
+ });
3209
+ }
3210
+ routes.push(...maybeAuthRoutes);
3211
+ return routes;
3212
+ }
3213
+ default:
3214
+ return [...maybeAuthRoutes];
3215
+ }
3216
+ }
3217
+ function getNavConfigForPreset(presetType, hasAuth) {
3218
+ switch (presetType) {
3219
+ case "todo":
3220
+ return {
3221
+ name: "Todo App",
3222
+ appType: "client",
3223
+ layout: "top-nav",
3224
+ navigationObj: hasAuth ? "{ visible: true, showLogo: true, showSearch: false, showCart: false, authStyle: 'buttons', navItems: 'desktop' }" : "{ visible: true, showLogo: true, showSearch: false, showCart: false, authStyle: 'none', navItems: 'desktop' }",
3225
+ desktopNav: [
3226
+ "{ label: 'Todos', icon: 'CheckSquare', path: '/todos' }",
3227
+ "{ label: 'SSE Demo', icon: 'Bell', path: '/notifications' }",
3228
+ "{ label: 'WebSocket', icon: 'Zap', path: '/websocket' }"
3229
+ ],
3230
+ mobileTabs: [
3231
+ "{ label: 'Todos', icon: 'CheckSquare', path: '/todos' }",
3232
+ "{ label: 'SSE', icon: 'Bell', path: '/notifications' }",
3233
+ "{ label: 'WS', icon: 'Zap', path: '/websocket' }"
3234
+ ],
3235
+ defaultRoute: "/todos"
3236
+ };
3237
+ case "plugin":
3238
+ return {
3239
+ name: "Plugin Market",
3240
+ appType: "client",
3241
+ layout: "top-nav",
3242
+ navigationObj: "{ visible: true, showLogo: true, showSearch: true, showCart: false, authStyle: 'text-link', navItems: 'desktop' }",
3243
+ desktopNav: [
3244
+ "{ label: 'Discover', icon: 'Compass', path: '/plugins' }",
3245
+ "{ label: 'Plugins', icon: 'Puzzle', path: '/plugins/list' }",
3246
+ "{ label: 'Categories', icon: 'Tags', path: '/categories' }",
3247
+ "{ label: 'Search', icon: 'Search', path: '/search' }",
3248
+ "{ label: 'Publish', icon: 'PlusCircle', path: '/publish' }",
3249
+ "{ label: 'Developer', icon: 'Code', path: '/developer' }"
3250
+ ],
3251
+ mobileTabs: [
3252
+ "{ label: 'Discover', icon: 'Compass', path: '/plugins' }",
3253
+ "{ label: 'Plugins', icon: 'Puzzle', path: '/plugins/list' }",
3254
+ "{ label: 'Categories', icon: 'Tags', path: '/categories' }",
3255
+ "{ label: 'Search', icon: 'Search', path: '/search' }",
3256
+ "{ label: 'My', icon: 'User', path: '/developer' }"
3257
+ ],
3258
+ defaultRoute: "/plugins"
3259
+ };
3260
+ case "ecommerce":
3261
+ return {
3262
+ name: "E-Commerce",
3263
+ appType: "client",
3264
+ layout: "top-nav",
3265
+ navigationObj: "{ visible: true, showLogo: true, showSearch: true, showCart: true, authStyle: 'icon', navItems: 'desktop' }",
3266
+ desktopNav: [
3267
+ "{ label: 'Home', icon: 'Home', path: '/' }",
3268
+ "{ label: 'Products', icon: 'ShoppingBag', path: '/products' }",
3269
+ "{ label: 'Cart', icon: 'ShoppingCart', path: '/cart' }",
3270
+ "{ label: 'Orders', icon: 'Package', path: '/orders' }",
3271
+ "{ label: 'Account', icon: 'User', path: '/content' }"
3272
+ ],
3273
+ mobileTabs: [
3274
+ "{ label: 'Home', icon: 'Home', path: '/' }",
3275
+ "{ label: 'Products', icon: 'ShoppingBag', path: '/products' }",
3276
+ "{ label: 'Cart', icon: 'ShoppingCart', path: '/cart' }",
3277
+ "{ label: 'Orders', icon: 'Package', path: '/orders' }",
3278
+ "{ label: 'Me', icon: 'User', path: '/content' }"
3279
+ ],
3280
+ defaultRoute: "/"
3281
+ };
3282
+ case "saas":
3283
+ return {
3284
+ name: "SaaS Admin",
3285
+ appType: "admin",
3286
+ layout: "minimal",
3287
+ navigationObj: "{ visible: false, showLogo: false, showSearch: false, showCart: false, authStyle: 'none', navItems: 'none' }",
3288
+ desktopNav: [
3289
+ "{ label: 'Dashboard', icon: 'LayoutDashboard', path: '/dashboard' }",
3290
+ "{ label: 'Settings', icon: 'Settings', path: '/settings' }"
3291
+ ],
3292
+ mobileTabs: [
3293
+ "{ label: 'Dashboard', icon: 'LayoutDashboard', path: '/dashboard' }",
3294
+ "{ label: 'Settings', icon: 'Settings', path: '/settings' }"
3295
+ ],
3296
+ defaultRoute: "/dashboard"
3297
+ };
3298
+ case "community":
3299
+ return {
3300
+ name: "Community Forum",
3301
+ appType: "client",
3302
+ layout: "top-nav",
3303
+ navigationObj: "{ visible: true, showLogo: true, showSearch: true, showCart: false, authStyle: 'text-link', navItems: 'desktop' }",
3304
+ desktopNav: [
3305
+ "{ label: 'Home', icon: 'Home', path: '/' }",
3306
+ "{ label: 'Topics', icon: 'MessageSquare', path: '/topics' }",
3307
+ "{ label: 'Popular', icon: 'Flame', path: '/popular' }",
3308
+ "{ label: 'Profile', icon: 'User', path: '/profile' }",
3309
+ "{ label: 'Chat', icon: 'MessageCircle', path: '/websocket' }"
3310
+ ],
3311
+ mobileTabs: [
3312
+ "{ label: 'Home', icon: 'Home', path: '/' }",
3313
+ "{ label: 'Topics', icon: 'MessageSquare', path: '/topics' }",
3314
+ "{ label: 'Popular', icon: 'Flame', path: '/popular' }",
3315
+ "{ label: 'Profile', icon: 'User', path: '/profile' }",
3316
+ "{ label: 'Chat', icon: 'MessageCircle', path: '/websocket' }"
3317
+ ],
3318
+ defaultRoute: "/"
3319
+ };
3320
+ default:
3321
+ return {
3322
+ name: "App",
3323
+ appType: "client",
3324
+ layout: "top-nav",
3325
+ navigationObj: "{ visible: true, showLogo: true, showSearch: false, showCart: false, authStyle: 'none', navItems: 'desktop' }",
3326
+ desktopNav: ["{ label: 'Home', icon: 'Home', path: '/' }"],
3327
+ mobileTabs: ["{ label: 'Home', icon: 'Home', path: '/' }"],
3328
+ defaultRoute: "/"
3329
+ };
3330
+ }
3331
+ }
3332
+ function filterNavByModules(navItems, resolved) {
3333
+ const hasModule = (path3) => {
3334
+ if (path3 === "/todos") return resolved.modules.has("todos");
3335
+ if (path3 === "/notifications") return resolved.modules.has("notifications");
3336
+ if (path3 === "/websocket") return resolved.modules.has("chat");
3337
+ if (path3.startsWith("/plugins") || path3 === "/categories" || path3 === "/search" || path3 === "/publish" || path3 === "/developer")
3338
+ return resolved.modules.has("plugin");
3339
+ if (path3 === "/cart" || path3 === "/orders") return resolved.modules.has("order");
3340
+ if (path3.startsWith("/content") || path3 === "/products" || path3 === "/")
3341
+ return resolved.modules.has("content");
3342
+ if (path3 === "/topics" || path3 === "/popular" || path3 === "/profile")
3343
+ return resolved.modules.has("content");
3344
+ if (path3 === "/dashboard" || path3 === "/settings") return resolved.modules.has("admin");
3345
+ return true;
3346
+ };
3347
+ return navItems.filter((item) => {
3348
+ const pathMatch = item.match(/path:\s*'([^']+)'/);
3349
+ if (!pathMatch) return true;
3350
+ return hasModule(pathMatch[1]);
3351
+ });
3352
+ }
3353
+ function generatePresetUIConfig(resolved, presetId) {
3354
+ const presetType = getPresetType(presetId);
3355
+ const { constName, theme } = getThemeForPresetType(presetType);
3356
+ const hasAuth = resolved.modules.has("auth");
3357
+ const navConfig = getNavConfigForPreset(presetType, hasAuth);
3358
+ const routes = getRoutesForPreset(presetType, resolved);
3359
+ const aliases = {};
3360
+ if (presetId !== presetType) {
3361
+ aliases[presetId] = presetType;
3362
+ }
3363
+ const allAliases = {
3364
+ "todo-app": "todo",
3365
+ "xbrowser-marketplace": "plugin",
3366
+ ecommerce: "ecommerce",
3367
+ "fullstack-admin": "saas",
3368
+ forum: "community",
3369
+ minimal: "todo",
3370
+ saas: "saas"
3371
+ };
3372
+ for (const [alias, type] of Object.entries(allAliases)) {
3373
+ if (type === presetType && alias !== presetType) {
3374
+ aliases[alias] = presetType;
3375
+ }
3376
+ }
3377
+ const desktopNav = filterNavByModules(navConfig.desktopNav, resolved);
3378
+ const mobileTabs = filterNavByModules(navConfig.mobileTabs, resolved);
3379
+ const routeDefs = routes.map((r) => {
3380
+ return ` {
3381
+ path: '${r.path}',
3382
+ component: lazy(() => import('${r.importPath}').then(m => ({ default: m.${r.componentName} }))),
3383
+ label: '${r.label}',
3384
+ }`;
3385
+ });
3386
+ return `import { lazy, type ComponentType } from 'react'
3387
+
3388
+ export type PresetType = '${presetType}'
3389
+
3390
+ export type AppType = 'client' | 'admin'
3391
+ export type LayoutType = 'top-nav' | 'minimal'
3392
+ export type AuthStyle = 'buttons' | 'text-link' | 'icon' | 'avatar' | 'none'
3393
+
3394
+ // ClientNavItem is intentionally simpler than admin MenuItem (no permissions/children needed for client nav)
3395
+ // eslint-disable-next-line local-rules/prefer-shared-types
3396
+ export interface ClientNavItem {
3397
+ label: string
3398
+ icon: string
3399
+ path: string
3400
+ }
3401
+
3402
+ export type TabItem = ClientNavItem
3403
+
3404
+ export interface NavigationConfig {
3405
+ visible: boolean
3406
+ showLogo: boolean
3407
+ showSearch: boolean
3408
+ showCart: boolean
3409
+ authStyle: AuthStyle
3410
+ navItems: 'desktop' | 'none'
3411
+ }
3412
+
3413
+ export interface PresetTheme {
3414
+ primaryColor: string
3415
+ primaryHover: string
3416
+ bgColor: string
3417
+ textColor: string
3418
+ secondaryBg: string
3419
+ borderColor: string
3420
+ borderRadius: string
3421
+ logoText: string
3422
+ fontFamily: string
3423
+ }
3424
+
3425
+ export interface RouteDef {
3426
+ path: string
3427
+ component: ComponentType<Record<string, unknown>> | null
3428
+ label: string
3429
+ }
3430
+
3431
+ export interface PresetUIConfig {
3432
+ id: PresetType
3433
+ name: string
3434
+ appType: AppType
3435
+ layout: LayoutType
3436
+ theme: PresetTheme
3437
+ navigation: NavigationConfig
3438
+ desktopNav: ClientNavItem[]
3439
+ mobileTabs: ClientNavItem[]
3440
+ routes: RouteDef[]
3441
+ defaultRoute: string
3442
+ }
3443
+
3444
+ const ${constName}: PresetTheme = ${theme}
3445
+
3446
+ // Preset ID aliases: allows dev:xxx scripts and VITE_PRESET to use config IDs
3447
+ const PRESET_ALIASES: Record<string, '${presetType}'> = {
3448
+ ${Object.entries(aliases).map(([k, v]) => ` '${k}': '${v}',`).join("\n")}
3449
+ }
3450
+
3451
+ export const PRESET_UI_CONFIGS: Record<PresetType, PresetUIConfig> = {
3452
+ ${presetType}: {
3453
+ id: '${presetType}',
3454
+ name: '${navConfig.name}',
3455
+ appType: '${navConfig.appType}',
3456
+ layout: '${navConfig.layout}',
3457
+ theme: ${constName},
3458
+ navigation: ${navConfig.navigationObj},
3459
+ desktopNav: [
3460
+ ${desktopNav.map((i) => ` ${i}`).join(",\n")}
3461
+ ],
3462
+ mobileTabs: [
3463
+ ${mobileTabs.map((i) => ` ${i}`).join(",\n")}
3464
+ ],
3465
+ defaultRoute: '${navConfig.defaultRoute}',
3466
+ routes: [
3467
+ ${routeDefs.join(",\n")}
3468
+ ],
3469
+ },
3470
+ }
3471
+
3472
+ export function getPresetUIConfig(id: string): PresetUIConfig {
3473
+ const resolvedId = PRESET_ALIASES[id] ?? (id as PresetType)
3474
+ return PRESET_UI_CONFIGS[resolvedId] ?? PRESET_UI_CONFIGS['${presetType}']
3475
+ }
3476
+
3477
+ export function getPresetUIConfigs(): Record<PresetType, PresetUIConfig> {
3478
+ return PRESET_UI_CONFIGS
3479
+ }
3480
+ `;
3481
+ }
3482
+
3483
+ // src/generators/client-main.ts
3484
+ function getPresetType2(presetId) {
3485
+ const map = {
3486
+ "todo-app": "todo",
3487
+ "xbrowser-marketplace": "plugin",
3488
+ ecommerce: "ecommerce",
3489
+ "fullstack-admin": "saas",
3490
+ forum: "community",
3491
+ minimal: "todo"
3492
+ };
3493
+ return map[presetId] || "todo";
3494
+ }
3495
+ function generateClientMain(resolved, presetId) {
3496
+ const presetType = getPresetType2(presetId);
3497
+ const isSaas = presetType === "saas";
3498
+ const authTokenBlock = isSaas ? "" : `
3499
+ if (preset !== 'saas') {
3500
+ try {
3501
+ const raw = localStorage.getItem('auth-token')
3502
+ const parsed = raw ? JSON.parse(raw) : null
3503
+ if (!parsed?.state?.token) {
3504
+ localStorage.setItem('auth-token', JSON.stringify({
3505
+ state: {
3506
+ token: 'user-token',
3507
+ isAuthenticated: true,
3508
+ user: { id: 'user-1', username: 'Demo User', role: 'USER' },
3509
+ loading: false,
3510
+ error: null,
3511
+ },
3512
+ version: 0,
3513
+ }))
3514
+ }
3515
+ } catch {
3516
+ localStorage.setItem('auth-token', JSON.stringify({
3517
+ state: { token: 'user-token', isAuthenticated: true, user: { id: 'user-1', username: 'Demo User', role: 'USER' }, loading: false, error: null },
3518
+ version: 0,
3519
+ }))
3520
+ }
3521
+ }
3522
+ `;
3523
+ if (isSaas) {
3524
+ return `import React from 'react'
3525
+ import ReactDOM from 'react-dom/client'
3526
+ import './index.css'
3527
+
3528
+ const AdminApp = React.lazy(() => import('@admin/App').then(m => ({ default: m.App })))
3529
+
3530
+ const RootApp = () => {
3531
+ return (
3532
+ <React.Suspense fallback={<div className="flex items-center justify-center h-screen text-gray-400">Loading...</div>}>
3533
+ <AdminApp basePath="/" />
3534
+ </React.Suspense>
3535
+ )
3536
+ }
3537
+
3538
+ ReactDOM.createRoot(document.getElementById('root')!).render(
3539
+ <React.StrictMode>
3540
+ <RootApp />
3541
+ </React.StrictMode>,
3542
+ )
3543
+
3544
+ if (typeof window !== 'undefined') {
3545
+ requestAnimationFrame(() => {
3546
+ setTimeout(() => {
3547
+ document.dispatchEvent(new CustomEvent('prerender-ready'))
3548
+ }, 100)
3549
+ })
3550
+ }
3551
+ `;
3552
+ }
3553
+ return `import React from 'react'
3554
+ import ReactDOM from 'react-dom/client'
3555
+ import { HelmetProvider } from 'react-helmet-async'
3556
+ import { App as ClientApp } from './App'
3557
+ import './index.css'
3558
+
3559
+ const preset = import.meta.env.VITE_PRESET || '${presetType}'
3560
+ ${authTokenBlock}
3561
+ const RootApp = () => {
3562
+ return (
3563
+ <HelmetProvider>
3564
+ <ClientApp presetId={preset} />
3565
+ </HelmetProvider>
3566
+ )
3567
+ }
3568
+
3569
+ ReactDOM.createRoot(document.getElementById('root')!).render(
3570
+ <React.StrictMode>
3571
+ <RootApp />
3572
+ </React.StrictMode>,
3573
+ )
3574
+
3575
+ if (typeof window !== 'undefined') {
3576
+ requestAnimationFrame(() => {
3577
+ setTimeout(() => {
3578
+ document.dispatchEvent(new CustomEvent('prerender-ready'))
3579
+ }, 100)
3580
+ })
3581
+ }
3582
+ `;
3583
+ }
3584
+
3585
+ // src/commands/create.ts
3586
+ var __filename$1 = fileURLToPath(import.meta.url);
3587
+ var __dirname$1 = path.dirname(__filename$1);
3588
+ var TEMPLATE_PROJECT_NAME = "biomimic-todo-app";
3589
+ var TEMPLATE_DB_NAME = "biomimic-todo-db";
3590
+ var ScaffoldError = class extends Error {
3591
+ constructor(message) {
3592
+ super(message);
3593
+ this.name = "ScaffoldError";
3594
+ }
3595
+ };
3596
+ function validateProjectName(name) {
3597
+ if (!name || name.trim().length === 0) {
3598
+ throw new ScaffoldError("Project name cannot be empty");
3599
+ }
3600
+ const validNameRegex = /^(@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/;
3601
+ if (!validNameRegex.test(name)) {
3602
+ throw new ScaffoldError(
3603
+ `Invalid project name "${name}". Use lowercase letters, numbers, hyphens, and underscores only.`
3604
+ );
3605
+ }
3606
+ if (name.length > 214) {
3607
+ throw new ScaffoldError("Project name must be 214 characters or less");
3608
+ }
3609
+ if (name.includes("..") || name.includes("/") || name.includes("\\")) {
3610
+ throw new ScaffoldError("Project name cannot contain path separators");
3611
+ }
3612
+ }
3613
+ function parseGitignore(content) {
3614
+ const negatePatterns = [];
3615
+ const includePatterns = content.split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("#")).map((line) => {
3616
+ if (line.startsWith("!")) {
3617
+ negatePatterns.push(line.slice(1));
3618
+ return null;
3619
+ }
3620
+ return line;
3621
+ }).filter((line) => line !== null).map((pattern) => pattern.replace(/\/$/, "")).map((pattern) => pattern.replace(/^\*\./, "")).map((pattern) => pattern.replace(/^\/+/, "")).filter((pattern) => !pattern.includes("*"));
3622
+ return [...includePatterns, ...negatePatterns.map((p) => `!${p}`)];
3623
+ }
3624
+ function generateDbName(projectName) {
3625
+ const sanitized = projectName.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
3626
+ return `${sanitized}-db`;
3627
+ }
3628
+ async function updateWranglerToml(targetDir, projectName) {
3629
+ const wranglerPath = path.join(targetDir, "wrangler.toml");
3630
+ if (!await fs.pathExists(wranglerPath)) {
3631
+ return;
3632
+ }
3633
+ let content = await fs.readFile(wranglerPath, "utf-8");
3634
+ const dbName = generateDbName(projectName);
3635
+ content = content.replace(
3636
+ new RegExp(`^name = "${TEMPLATE_PROJECT_NAME}"`, "m"),
3637
+ `name = "${projectName}"`
3638
+ );
3639
+ content = content.replace(
3640
+ new RegExp(`database_name = "${TEMPLATE_DB_NAME}"`, "g"),
3641
+ `database_name = "${dbName}"`
3642
+ );
3643
+ content = content.replace(
3644
+ /database_id = "[^"]+"/,
3645
+ `database_id = "" # TODO: Run 'wrangler d1 create ${dbName}' and paste the ID here`
3646
+ );
3647
+ await fs.writeFile(wranglerPath, content);
3648
+ }
3649
+ async function updatePackageJson(targetDir, projectName, resolved) {
3650
+ const pkgJsonPath = path.join(targetDir, "package.json");
3651
+ if (!await fs.pathExists(pkgJsonPath)) {
3652
+ return;
3653
+ }
3654
+ let pkgJson = await fs.readJson(pkgJsonPath);
3655
+ pkgJson = filterPackageJson(pkgJson, resolved);
3656
+ pkgJson.name = projectName;
3657
+ if (pkgJson.bin) {
3658
+ delete pkgJson.bin;
3659
+ }
3660
+ await fs.writeJson(pkgJsonPath, pkgJson, { spaces: 2 });
3661
+ }
3662
+ async function updatePackageLockJson(targetDir, projectName) {
3663
+ const lockFilePath = path.join(targetDir, "package-lock.json");
3664
+ if (!await fs.pathExists(lockFilePath)) {
3665
+ return;
3666
+ }
3667
+ const lockFile = await fs.readJson(lockFilePath);
3668
+ if (lockFile.name === TEMPLATE_PROJECT_NAME) {
3669
+ lockFile.name = projectName;
3670
+ }
3671
+ if (lockFile.packages?.[""]?.name === TEMPLATE_PROJECT_NAME) {
3672
+ lockFile.packages[""].name = projectName;
3673
+ }
3674
+ await fs.writeJson(lockFilePath, lockFile, { spaces: 2 });
3675
+ }
3676
+ async function updateReadme(targetDir, projectName) {
3677
+ const readmePath = path.join(targetDir, "README.md");
3678
+ if (!await fs.pathExists(readmePath)) {
3679
+ return;
3680
+ }
3681
+ let content = await fs.readFile(readmePath, "utf-8");
3682
+ content = content.replace(/^# (.+)$/m, `# ${projectName}`);
3683
+ await fs.writeFile(readmePath, content);
3684
+ }
3685
+ async function createProject(projectNameOrOptions, useCurrentDir = false, preset) {
3686
+ let projectName;
3687
+ let currentDir;
3688
+ let presetId;
3689
+ let outputDir;
3690
+ let dryRun;
3691
+ let install;
3692
+ if (typeof projectNameOrOptions === "string") {
3693
+ projectName = projectNameOrOptions;
3694
+ currentDir = useCurrentDir;
3695
+ presetId = preset;
3696
+ dryRun = false;
3697
+ install = true;
3698
+ } else {
3699
+ projectName = projectNameOrOptions.projectName;
3700
+ currentDir = projectNameOrOptions.currentDir;
3701
+ presetId = projectNameOrOptions.preset;
3702
+ outputDir = projectNameOrOptions.outputDir;
3703
+ dryRun = projectNameOrOptions.dryRun ?? false;
3704
+ install = projectNameOrOptions.install ?? true;
3705
+ }
3706
+ if (!currentDir) {
3707
+ validateProjectName(projectName);
3708
+ }
3709
+ const templateDir = path.join(__dirname$1, "../../template");
3710
+ let targetDir;
3711
+ if (currentDir) {
3712
+ targetDir = process.cwd();
3713
+ projectName = path.basename(targetDir);
3714
+ } else if (outputDir) {
3715
+ targetDir = path.resolve(outputDir);
3716
+ if (await fs.pathExists(targetDir)) {
3717
+ throw new ScaffoldError(`Directory ${outputDir} already exists`);
3718
+ }
3719
+ } else {
3720
+ targetDir = path.resolve(process.cwd(), projectName);
3721
+ if (await fs.pathExists(targetDir)) {
3722
+ throw new ScaffoldError(`Directory ${projectName} already exists`);
3723
+ }
3724
+ }
3725
+ try {
3726
+ const manifestSpinner = ora("Loading module manifests...").start();
3727
+ const allManifests = await loadManifests(templateDir);
3728
+ const presets = await loadPresets(templateDir);
3729
+ const selectedPresetId = presetId || "fullstack-admin";
3730
+ const selectedPreset = presets.find((p) => p.id === selectedPresetId);
3731
+ if (!selectedPreset) {
3732
+ throw new ScaffoldError(
3733
+ `Unknown preset: ${selectedPresetId}. Available: ${presets.map((p) => p.id).join(", ")}`
3734
+ );
3735
+ }
3736
+ const resolved = resolvePreset(selectedPreset, allManifests);
3737
+ manifestSpinner.succeed(
3738
+ chalk.green(`Using preset: ${selectedPreset.name} (${resolved.modules.size} modules)`)
3739
+ );
3740
+ if (dryRun) {
3741
+ const resolvedModuleNames = [...resolved.modules.keys()];
3742
+ const generatedFiles2 = getGeneratedFiles(resolved);
3743
+ console.log("");
3744
+ console.log(chalk.blue("\u{1F4CB} Dry Run - Files that would be generated:\n"));
3745
+ for (const file of generatedFiles2) {
3746
+ console.log(` ${chalk.green("\u2713")} ${file}`);
3747
+ }
3748
+ const gitignorePath2 = path.join(templateDir, ".gitignore");
3749
+ let ignorePatterns2 = [];
3750
+ if (await fs.pathExists(gitignorePath2)) {
3751
+ const gitignoreContent = await fs.readFile(gitignorePath2, "utf-8");
3752
+ ignorePatterns2 = parseGitignore(gitignoreContent);
3753
+ }
3754
+ ignorePatterns2.push("node_modules", ".wrangler");
3755
+ const excludePatterns2 = getExcludePatterns(resolved, allManifests);
3756
+ let templateFileCount = 0;
3757
+ const templateFiles = await fs.readdir(templateDir, { recursive: true });
3758
+ for (const file of templateFiles) {
3759
+ const relative = String(file);
3760
+ if (!relative) continue;
3761
+ const negated = ignorePatterns2.filter((p) => p.startsWith("!"));
3762
+ const gitIgnored = ignorePatterns2.filter(
3763
+ (p) => !p.startsWith("!") && relative.startsWith(p)
3764
+ );
3765
+ if (gitIgnored.length > 0) {
3766
+ const allowed = negated.some((p) => relative === p.slice(1));
3767
+ if (!allowed) continue;
3768
+ }
3769
+ const normalizedRelative = relative.replace(/\\/g, "/");
3770
+ let excluded = false;
3771
+ for (const pattern of excludePatterns2) {
3772
+ const normalizedPattern = pattern.replace(/\\/g, "/");
3773
+ if (normalizedRelative === normalizedPattern || normalizedRelative.startsWith(normalizedPattern + "/")) {
3774
+ excluded = true;
3775
+ break;
3776
+ }
3777
+ }
3778
+ if (!excluded) templateFileCount++;
3779
+ }
3780
+ console.log("");
3781
+ console.log(chalk.blue("\u{1F4C1} Template files that would be copied:\n"));
3782
+ console.log(` ${chalk.green("\u2713")} ${templateFileCount} template files`);
3783
+ console.log("");
3784
+ console.log(chalk.yellow(` Total generated files: ${generatedFiles2.length}`));
3785
+ console.log(chalk.yellow(` Preset: ${selectedPreset.name}`));
3786
+ console.log(chalk.yellow(` Modules: ${resolvedModuleNames.join(", ")}`));
3787
+ console.log("");
3788
+ return;
3789
+ }
3790
+ if (!currentDir) {
3791
+ const dirSpinner = ora("Creating project directory...").start();
3792
+ await fs.ensureDir(targetDir);
3793
+ dirSpinner.succeed(chalk.green("Project directory created"));
3794
+ }
3795
+ const copySpinner = ora("Copying template files...").start();
3796
+ const gitignorePath = path.join(templateDir, ".gitignore");
3797
+ let ignorePatterns = [];
3798
+ if (await fs.pathExists(gitignorePath)) {
3799
+ const gitignoreContent = await fs.readFile(gitignorePath, "utf-8");
3800
+ ignorePatterns = parseGitignore(gitignoreContent);
3801
+ }
3802
+ ignorePatterns.push("node_modules", ".wrangler");
3803
+ const excludePatterns = getExcludePatterns(resolved, allManifests);
3804
+ await fs.copy(templateDir, targetDir, {
3805
+ filter: (src) => {
3806
+ const relative = path.relative(templateDir, src);
3807
+ if (relative === "") return true;
3808
+ const negated = ignorePatterns.filter((p) => p.startsWith("!"));
3809
+ const gitIgnored = ignorePatterns.filter((p) => !p.startsWith("!") && relative.startsWith(p));
3810
+ if (gitIgnored.length > 0) {
3811
+ const allowed = negated.some((p) => relative === p.slice(1));
3812
+ if (!allowed) return false;
3813
+ }
3814
+ const normalizedRelative = relative.replace(/\\/g, "/");
3815
+ for (const pattern of excludePatterns) {
3816
+ const normalizedPattern = pattern.replace(/\\/g, "/");
3817
+ if (normalizedRelative === normalizedPattern || normalizedRelative.startsWith(normalizedPattern + "/")) {
3818
+ return false;
3819
+ }
3820
+ }
3821
+ return true;
3822
+ },
3823
+ dereference: false
3824
+ });
3825
+ copySpinner.succeed(chalk.green("Template files copied"));
3826
+ const genSpinner = ora("Generating module-specific files...").start();
3827
+ const routeRegistryContent = generateRouteRegistry(resolved);
3828
+ await fs.writeFile(path.join(targetDir, "src/server/route-registry.ts"), routeRegistryContent);
3829
+ const dbSchemaContent = generateDbSchemaBarrel(resolved);
3830
+ await fs.writeFile(path.join(targetDir, "src/server/db/schema/index.ts"), dbSchemaContent);
3831
+ if (resolved.hasClient) {
3832
+ const clientNavContent = generateClientNavigation(resolved);
3833
+ await fs.writeFile(
3834
+ path.join(targetDir, "src/client/components/Navigation.tsx"),
3835
+ clientNavContent
3836
+ );
3837
+ const clientAppTestContent = generateClientAppTest(resolved);
3838
+ await fs.ensureDir(path.join(targetDir, "src/client/components/__tests__"));
3839
+ await fs.writeFile(
3840
+ path.join(targetDir, "src/client/components/__tests__/App.test.tsx"),
3841
+ clientAppTestContent
3842
+ );
3843
+ const clientNavTestContent = generateClientNavigationTest(resolved);
3844
+ await fs.writeFile(
3845
+ path.join(targetDir, "src/client/components/__tests__/Navigation.test.tsx"),
3846
+ clientNavTestContent
3847
+ );
3848
+ const presetUIConfigContent = generatePresetUIConfig(resolved, selectedPreset.id);
3849
+ await fs.writeFile(
3850
+ path.join(targetDir, "src/client/preset-ui-config.ts"),
3851
+ presetUIConfigContent
3852
+ );
3853
+ const clientMainContent = generateClientMain(resolved, selectedPreset.id);
3854
+ await fs.writeFile(path.join(targetDir, "src/client/main.tsx"), clientMainContent);
3855
+ }
3856
+ if (resolved.hasClient && resolved.modules.has("admin")) {
3857
+ const adminAppContent = generateAdminApp(resolved);
3858
+ if (adminAppContent) {
3859
+ await fs.ensureDir(path.join(targetDir, "src/admin"));
3860
+ await fs.writeFile(path.join(targetDir, "src/admin/App.tsx"), adminAppContent);
3861
+ }
3862
+ const adminComponentsIndex = generateAdminComponentsIndex(resolved);
3863
+ if (adminComponentsIndex) {
3864
+ await fs.ensureDir(path.join(targetDir, "src/admin/components"));
3865
+ await fs.writeFile(
3866
+ path.join(targetDir, "src/admin/components/index.ts"),
3867
+ adminComponentsIndex
3868
+ );
3869
+ }
3870
+ const adminApiClient = generateAdminApiClient(resolved);
3871
+ if (adminApiClient) {
3872
+ await fs.ensureDir(path.join(targetDir, "src/admin/services"));
3873
+ await fs.writeFile(path.join(targetDir, "src/admin/services/apiClient.ts"), adminApiClient);
3874
+ }
3875
+ }
3876
+ const serverAppContent = generateServerApp(resolved);
3877
+ await fs.writeFile(path.join(targetDir, "src/server/app.ts"), serverAppContent);
3878
+ const generatedFiles = getGeneratedFiles(resolved);
3879
+ if (generatedFiles.includes("src/server/db/init.ts")) {
3880
+ const dbInitContent = generateDbInit(resolved);
3881
+ await fs.writeFile(path.join(targetDir, "src/server/db/init.ts"), dbInitContent);
3882
+ }
3883
+ const sharedModulesContent = generateSharedModulesIndex(resolved);
3884
+ await fs.writeFile(path.join(targetDir, "src/shared/modules/index.ts"), sharedModulesContent);
3885
+ const sharedSchemasContent = generateSharedSchemasIndex(resolved);
3886
+ await fs.writeFile(path.join(targetDir, "src/shared/schemas/index.ts"), sharedSchemasContent);
3887
+ const middlewareIndexContent = generateMiddlewareIndex(resolved);
3888
+ await fs.writeFile(
3889
+ path.join(targetDir, "src/server/middleware/index.ts"),
3890
+ middlewareIndexContent
3891
+ );
3892
+ if (generatedFiles.includes("src/server/middleware/auth.ts")) {
3893
+ const authMiddlewareContent = generateAuthMiddleware(resolved);
3894
+ await fs.writeFile(
3895
+ path.join(targetDir, "src/server/middleware/auth.ts"),
3896
+ authMiddlewareContent
3897
+ );
3898
+ }
3899
+ if (generatedFiles.includes("src/server/utils/auth.ts")) {
3900
+ const authUtilsContent = generateAuthUtils(resolved);
3901
+ await fs.writeFile(path.join(targetDir, "src/server/utils/auth.ts"), authUtilsContent);
3902
+ }
3903
+ if (resolved.hasClient) {
3904
+ const clientComponentsContent = generateClientComponentsIndex(resolved);
3905
+ await fs.writeFile(
3906
+ path.join(targetDir, "src/client/components/index.ts"),
3907
+ clientComponentsContent
3908
+ );
3909
+ }
3910
+ const cliModulesContent = generateCliModulesIndex(resolved);
3911
+ await fs.writeFile(path.join(targetDir, "src/cli/modules/index.ts"), cliModulesContent);
3912
+ if (resolved.hasClient && generatedFiles.includes("vite.config.ts")) {
3913
+ const viteConfigContent = generateViteConfig(resolved, templateDir);
3914
+ await fs.writeFile(path.join(targetDir, "vite.config.ts"), viteConfigContent);
3915
+ }
3916
+ genSpinner.succeed(chalk.green("Module-specific files generated"));
3917
+ const pkgSpinner = ora("Configuring package.json...").start();
3918
+ await updatePackageJson(targetDir, projectName, resolved);
3919
+ pkgSpinner.succeed(chalk.green("package.json configured"));
3920
+ const lockSpinner = ora("Configuring package-lock.json...").start();
3921
+ await updatePackageLockJson(targetDir, projectName);
3922
+ lockSpinner.succeed(chalk.green("package-lock.json configured"));
3923
+ const wranglerSpinner = ora("Configuring wrangler.toml...").start();
3924
+ await updateWranglerToml(targetDir, projectName);
3925
+ wranglerSpinner.succeed(chalk.green("wrangler.toml configured"));
3926
+ const readmeSpinner = ora("Configuring README.md...").start();
3927
+ await updateReadme(targetDir, projectName);
3928
+ readmeSpinner.succeed(chalk.green("README.md configured"));
3929
+ let installSucceeded = false;
3930
+ if (install && !dryRun) {
3931
+ const installSpinner = ora("Installing dependencies...").start();
3932
+ try {
3933
+ const { execSync } = await import('child_process');
3934
+ execSync("npm install --legacy-peer-deps", {
3935
+ cwd: targetDir,
3936
+ stdio: "pipe",
3937
+ timeout: 3e5
3938
+ });
3939
+ installSpinner.succeed(chalk.green("Dependencies installed"));
3940
+ installSucceeded = true;
3941
+ } catch {
3942
+ installSpinner.warn(
3943
+ chalk.yellow("Dependency installation failed (you can run npm install manually)")
3944
+ );
3945
+ }
3946
+ }
3947
+ if (installSucceeded) {
3948
+ const patchesDir = path.join(targetDir, "patches");
3949
+ if (await fs.pathExists(patchesDir)) {
3950
+ try {
3951
+ const { execSync } = await import('child_process');
3952
+ execSync("npx patch-package", {
3953
+ cwd: targetDir,
3954
+ stdio: "pipe",
3955
+ timeout: 6e4
3956
+ });
3957
+ } catch {
3958
+ }
3959
+ }
3960
+ }
3961
+ console.log("");
3962
+ console.log(chalk.green(" \u2713 Project created successfully!"));
3963
+ console.log(chalk.gray(` Preset: ${selectedPreset.name}`));
3964
+ console.log(chalk.gray(` Modules: ${[...resolved.modules.keys()].join(", ")}`));
3965
+ console.log("");
3966
+ console.log(chalk.cyan(" Next steps:"));
3967
+ if (!currentDir && !outputDir) {
3968
+ console.log(chalk.white(` cd ${projectName}`));
3969
+ }
3970
+ if (!install || !installSucceeded) {
3971
+ console.log(chalk.white(" npm install"));
3972
+ }
3973
+ if (resolved.hasClient) {
3974
+ console.log(chalk.white(" npm run dev"));
3975
+ console.log("");
3976
+ console.log(chalk.yellow(" \u26A0\uFE0F Cloudflare Setup:"));
3977
+ console.log(
3978
+ chalk.white(` 1. Create D1 database: wrangler d1 create ${generateDbName(projectName)}`)
3979
+ );
3980
+ console.log(chalk.white(" 2. Copy the database ID to wrangler.toml"));
3981
+ console.log(chalk.white(" 3. Deploy: npm run deploy:cf"));
3982
+ } else {
3983
+ console.log(chalk.white(" npm run build"));
3984
+ console.log(chalk.white(" npm start"));
3985
+ console.log("");
3986
+ console.log(chalk.cyan(" CLI usage:"));
3987
+ console.log(chalk.white(" node dist/cli/index.js config status"));
3988
+ console.log(chalk.white(" node dist/cli/index.js todo list"));
3989
+ console.log(chalk.white(" node dist/cli/index.js --help"));
3990
+ }
3991
+ console.log("");
3992
+ console.log(chalk.gray(" Happy coding! \u{1F41F}"));
3993
+ console.log("");
3994
+ } catch (error) {
3995
+ if (error instanceof ScaffoldError) throw error;
3996
+ throw new ScaffoldError(`Error creating project: ${error}`);
3997
+ }
3998
+ }
3999
+
4000
+ // src/index.ts
4001
+ var __filename2 = fileURLToPath(import.meta.url);
4002
+ var __dirname2 = path.dirname(__filename2);
4003
+ var rootDir = __dirname2.endsWith(path.join("src")) ? path.resolve(__dirname2, "..") : __dirname2.endsWith(path.join("dist", "cli")) ? path.resolve(__dirname2, "..", "..") : path.resolve(__dirname2, "..", "..");
4004
+ var packageJson = JSON.parse(readFileSync(path.join(rootDir, "package.json"), "utf-8"));
4005
+ var program = new Command();
4006
+ program.name("create-fullstack-scaffold").description("Create a new full-stack scaffold app with Todo List example").version(packageJson.version).argument("[project-name]", "Name of your project").option("-c, --current-dir", "Create project in current directory").option(
4007
+ "-p, --preset <preset>",
4008
+ "Template preset to use (fullstack-admin, todo-app, ecommerce, xbrowser-marketplace, forum, cli-only, minimal, saas)"
4009
+ ).option("-o, --output-dir <path>", "Output directory (defaults to project name)").option("--dry-run", "Show what would be generated without creating files").option("--no-install", "Skip automatic dependency installation").action(
4010
+ async (projectName = "my-fullstack-app", options) => {
4011
+ console.log("");
4012
+ console.log(chalk.cyan.bold(" \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"));
4013
+ console.log(chalk.cyan.bold(" \u2551 Create Fullstack Scaffold App \u2551"));
4014
+ console.log(chalk.cyan.bold(" \u2551 React + Hono + Vite + Zustand + TS \u2551"));
4015
+ console.log(chalk.cyan.bold(" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
4016
+ console.log("");
4017
+ let preset = options.preset;
4018
+ if (!preset && process.stdin.isTTY) {
4019
+ const templateDir = path.join(rootDir, "template");
4020
+ const presets = await loadPresets(templateDir);
4021
+ preset = await select({
4022
+ message: "Choose a template preset:",
4023
+ choices: presets.map((p) => ({
4024
+ value: p.id,
4025
+ name: `${p.name} \u2014 ${p.description}`
4026
+ }))
4027
+ });
4028
+ }
4029
+ if (!preset) {
4030
+ preset = "fullstack-admin";
4031
+ }
4032
+ try {
4033
+ await createProject({
4034
+ projectName,
4035
+ currentDir: options.currentDir ?? false,
4036
+ preset,
4037
+ outputDir: options.outputDir,
4038
+ dryRun: options.dryRun ?? false,
4039
+ install: options.install
4040
+ });
4041
+ } catch (error) {
4042
+ if (error instanceof ScaffoldError) {
4043
+ console.error(chalk.red(` \u2716 ${error.message}`));
4044
+ process.exit(1);
4045
+ }
4046
+ throw error;
4047
+ }
4048
+ }
4049
+ );
4050
+ program.command("presets").description("List available template presets").action(async () => {
4051
+ const templateDir = path.join(rootDir, "template");
4052
+ const presets = await loadPresets(templateDir);
4053
+ console.log(chalk.cyan("\nAvailable presets:\n"));
4054
+ for (const preset of presets) {
4055
+ console.log(` ${chalk.green(preset.id.padEnd(20))} ${preset.name}`);
4056
+ console.log(` ${" ".repeat(20)} ${preset.description}`);
4057
+ console.log(` ${" ".repeat(20)} Modules: ${preset.modules.join(", ")}`);
4058
+ console.log();
4059
+ }
4060
+ });
4061
+ program.parse();
4062
+ //# sourceMappingURL=index.js.map
4063
+ //# sourceMappingURL=index.js.map