create-fullstack-scaffold 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +1673 -696
- package/dist/cli/index.js.map +1 -1
- package/package.json +16 -10
- package/template/.husky/pre-commit +1 -1
- package/template/modules.config.ts +29 -2
- package/template/package.json +12 -12
- package/template/patches/{typescript+5.8.3.patch → typescript+5.9.3.patch} +2 -4
- package/template/playwright.config.ts +7 -1
- package/template/src/admin/App.tsx +2 -2
- package/template/src/admin/components/CaptchaModal.tsx +7 -9
- package/template/src/admin/layouts/Header.tsx +56 -22
- package/template/src/admin/layouts/Layout.tsx +14 -9
- package/template/src/admin/layouts/Sidebar.tsx +122 -105
- package/template/src/admin/pages/CategoryManagementPage.tsx +241 -0
- package/template/src/admin/pages/ContentPage.tsx +2 -0
- package/template/src/admin/pages/DashboardPage.tsx +2 -0
- package/template/src/admin/pages/DisputesPage.tsx +2 -0
- package/template/src/admin/pages/MediaTestPage.tsx +1 -1
- package/template/src/admin/pages/OrdersPage.tsx +2 -0
- package/template/src/admin/pages/PluginDashboardPage.tsx +297 -0
- package/template/src/admin/pages/PluginManagementPage.tsx +340 -0
- package/template/src/admin/pages/PluginReviewPage.tsx +255 -0
- package/template/src/admin/pages/SystemLogsPage.tsx +11 -2
- package/template/src/admin/pages/TicketsPage.tsx +2 -0
- package/template/src/admin/pages/UsersPage.tsx +3 -2
- package/template/src/admin/stores/adminStore.ts +5 -0
- package/template/src/cli/index.ts +17 -19
- package/template/src/cli/modules/auth/index.ts +65 -0
- package/template/src/cli/modules/config/index.ts +74 -77
- package/template/src/cli/modules/index.ts +28 -6
- package/template/src/cli/modules/notification/index.ts +95 -79
- package/template/src/cli/modules/plugin/index.ts +111 -0
- package/template/src/cli/modules/todo/index.ts +99 -51
- package/template/src/cli/utils/auto-command.ts +7 -25
- package/template/src/cli/utils/index.ts +3 -1
- package/template/src/client/App.tsx +36 -16
- package/template/src/client/Layout.tsx +67 -10
- package/template/src/client/components/AuthButton.tsx +25 -18
- package/template/src/client/components/BottomTabBar.tsx +107 -0
- package/template/src/client/components/Navigation.tsx +162 -52
- package/template/src/client/components/__tests__/App.test.tsx +48 -32
- package/template/src/client/components/__tests__/AuthButton.test.tsx +78 -77
- package/template/src/client/components/__tests__/Navigation.test.tsx +31 -20
- package/template/src/client/components/index.ts +1 -0
- package/template/src/client/contexts/PresetContext.tsx +10 -0
- package/template/src/client/main.tsx +63 -8
- package/template/src/client/pages/CartPage.tsx +244 -0
- package/template/src/client/pages/CategoriesPage.tsx +100 -0
- package/template/src/client/pages/ContentDetailPage.tsx +9 -14
- package/template/src/client/pages/ContentListPage.tsx +4 -11
- package/template/src/client/pages/DashboardPage.tsx +261 -0
- package/template/src/client/pages/DeveloperDashboardPage.tsx +211 -0
- package/template/src/client/pages/LoginPage.tsx +127 -0
- package/template/src/client/pages/OrdersPage.tsx +196 -0
- package/template/src/client/pages/PluginDetailPage.tsx +345 -0
- package/template/src/client/pages/PluginsPage.tsx +223 -0
- package/template/src/client/pages/ProfilePage.tsx +206 -0
- package/template/src/client/pages/PublishPage.tsx +336 -0
- package/template/src/client/pages/RegisterPage.tsx +136 -0
- package/template/src/client/pages/SearchPage.tsx +204 -0
- package/template/src/client/pages/SettingsPage.tsx +220 -0
- package/template/src/client/pages/TopicsPage.tsx +180 -0
- package/template/src/client/pages/__tests__/LoginPage.test.tsx +170 -0
- package/template/src/client/pages/__tests__/RegisterPage.test.tsx +168 -0
- package/template/src/client/preset-ui-config.ts +492 -0
- package/template/src/client/services/apiClient.ts +14 -4
- package/template/src/client/stores/__tests__/authStore.test.ts +293 -38
- package/template/src/client/stores/__tests__/todoStore.test.ts +7 -17
- package/template/src/client/stores/authStore.ts +58 -5
- package/template/src/client/stores/chatWSStore.ts +9 -0
- package/template/src/client/stores/notificationStore.ts +4 -0
- package/template/src/client/stores/pluginStore.ts +279 -0
- package/template/src/client/stores/todoStore.ts +2 -6
- package/template/src/server/core/__tests__/isr-cache.test.ts +117 -0
- package/template/src/server/core/__tests__/isr-invalidation.test.ts +72 -0
- package/template/src/server/core/__tests__/ssr-renderer.test.ts +89 -0
- package/template/src/server/core/isr-cache.ts +239 -0
- package/template/src/server/core/isr-invalidation.ts +45 -0
- package/template/src/server/core/module-loader.ts +14 -7
- package/template/src/server/core/ssr-renderer.ts +240 -0
- package/template/src/server/db/init.ts +257 -10
- package/template/src/server/db/schema/developers.ts +20 -0
- package/template/src/server/db/schema/index.ts +2 -0
- package/template/src/server/db/schema/plugins.ts +114 -0
- package/template/src/server/db/test-setup.ts +91 -0
- package/template/src/server/entries/cloudflare.ts +79 -7
- package/template/src/server/entries/node.ts +48 -5
- package/template/src/server/middleware/__tests__/captcha.test.ts +23 -13
- package/template/src/server/middleware/auth.ts +8 -1
- package/template/src/server/middleware/captcha.ts +14 -4
- package/template/src/server/middleware/rate-limit.ts +7 -2
- package/template/src/server/module-admin/module.ts +9 -5
- package/template/src/server/module-admin/routes/admin-notification-routes.ts +43 -14
- package/template/src/server/module-admin/routes/admin-routes.ts +0 -2
- package/template/src/server/module-admin/routes/client-auth-routes.ts +90 -0
- package/template/src/server/module-admin/routes/dashboard-routes.ts +79 -0
- package/template/src/server/module-admin/services/admin-service.ts +68 -11
- package/template/src/server/module-auth/__tests__/auth-service.test.ts +239 -0
- package/template/src/server/module-auth/index.ts +7 -0
- package/template/src/server/module-auth/module.ts +40 -0
- package/template/src/server/module-auth/routes/auth-routes.ts +94 -0
- package/template/src/server/module-auth/routes/profile-routes.ts +31 -0
- package/template/src/server/module-auth/services/auth-service.ts +100 -0
- package/template/src/server/module-content/module.ts +10 -4
- package/template/src/server/module-content/routes/public-content-routes.ts +2 -2
- package/template/src/server/module-content/routes/topics-routes.ts +205 -0
- package/template/src/server/module-content/services/content-service.ts +18 -2
- package/template/src/server/module-dispute/routes/dispute-routes.ts +2 -1
- package/template/src/server/module-dispute/services/dispute-service.ts +1 -1
- package/template/src/server/module-notifications/__tests__/sse-rpc.test.ts +14 -14
- package/template/src/server/module-notifications/routes/notification-routes.ts +34 -8
- package/template/src/server/module-order/__tests__/order-route.test.ts +22 -2
- package/template/src/server/module-order/module.ts +15 -0
- package/template/src/server/module-order/routes/cart-routes.ts +103 -0
- package/template/src/server/module-order/routes/orders-mock-routes.ts +67 -0
- package/template/src/server/module-order/services/order-service.ts +1 -1
- package/template/src/server/module-plugin/__tests__/plugin-query-service.test.ts +203 -0
- package/template/src/server/module-plugin/__tests__/plugin-service.test.ts +234 -0
- package/template/src/server/module-plugin/index.ts +2 -0
- package/template/src/server/module-plugin/module.ts +52 -0
- package/template/src/server/module-plugin/routes/plugin-admin-routes.ts +261 -0
- package/template/src/server/module-plugin/routes/plugin-routes.ts +354 -0
- package/template/src/server/module-plugin/services/admin-category-service.ts +99 -0
- package/template/src/server/module-plugin/services/admin-plugin-service.ts +170 -0
- package/template/src/server/module-plugin/services/admin-stats-service.ts +41 -0
- package/template/src/server/module-plugin/services/plugin-query-service.ts +360 -0
- package/template/src/server/module-plugin/services/plugin-review-service.ts +95 -0
- package/template/src/server/module-plugin/services/plugin-service.ts +163 -0
- package/template/src/server/module-ticket/services/ticket-service.ts +1 -1
- package/template/src/server/module-todos/routes/todos-routes.ts +0 -4
- package/template/src/server/route-registry.ts +16 -1
- package/template/src/server/test-utils/test-client.ts +1 -2
- package/template/src/server/utils/auth.ts +6 -0
- package/template/src/server/utils/json.ts +13 -0
- package/template/src/shared/core/module-manifest.ts +3 -6
- package/template/src/shared/modules/auth/index.ts +12 -0
- package/template/src/shared/modules/auth/schemas.ts +50 -0
- package/template/src/shared/modules/cart/index.ts +1 -0
- package/template/src/shared/modules/cart/schemas.ts +41 -0
- package/template/src/shared/modules/community/index.ts +1 -0
- package/template/src/shared/modules/community/schemas.ts +58 -0
- package/template/src/shared/modules/dashboard/index.ts +1 -0
- package/template/src/shared/modules/dashboard/schemas.ts +35 -0
- package/template/src/shared/modules/index.ts +41 -0
- package/template/src/shared/modules/order/schemas.ts +29 -0
- package/template/src/shared/modules/plugins/index.ts +48 -0
- package/template/src/shared/modules/plugins/schemas.ts +227 -0
- package/template/src/shared/schemas/index.ts +130 -0
- package/template/tests/e2e/todo.spec.ts +23 -18
- package/template/tests/e2e/visual-screenshots.spec.ts +1824 -0
- package/template/uploads/.gitkeep +0 -0
- package/template/vite.config.ts +2 -1
- package/template/vitest.setup.ts +11 -0
- package/template/wrangler.toml +4 -3
- package/template/package-lock.json +0 -14554
package/dist/cli/index.js
CHANGED
|
@@ -1,433 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { z } from '@hono/zod-openapi';
|
|
5
|
-
import fs, { readdirSync, statSync, existsSync, readFileSync } from 'fs';
|
|
6
|
-
import path2, { join } from 'path';
|
|
7
|
-
import os from 'os';
|
|
2
|
+
import { readFileSync, existsSync, readdirSync, statSync } from 'fs';
|
|
3
|
+
import path, { join } from 'path';
|
|
8
4
|
import { fileURLToPath, pathToFileURL } from 'url';
|
|
9
|
-
import
|
|
5
|
+
import { Command } from 'commander';
|
|
10
6
|
import chalk from 'chalk';
|
|
7
|
+
import { select } from '@inquirer/prompts';
|
|
8
|
+
import fs from 'fs-extra';
|
|
11
9
|
import ora from 'ora';
|
|
12
10
|
|
|
13
|
-
function createRPCClient(baseUrl) {
|
|
14
|
-
return hc(baseUrl);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
// src/cli/utils/api.ts
|
|
18
|
-
var DEFAULT_BASE_URL = "http://localhost:3010";
|
|
19
|
-
var globalBaseUrl = process.env.BIOMIMIC_API_URL || DEFAULT_BASE_URL;
|
|
20
|
-
function setBaseUrl(url) {
|
|
21
|
-
globalBaseUrl = url;
|
|
22
|
-
}
|
|
23
|
-
function getBaseUrl() {
|
|
24
|
-
return globalBaseUrl;
|
|
25
|
-
}
|
|
26
|
-
function getClient() {
|
|
27
|
-
return createRPCClient(globalBaseUrl);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// src/cli/utils/logger.ts
|
|
31
|
-
var Logger = class {
|
|
32
|
-
level = "info";
|
|
33
|
-
constructor(options = {}) {
|
|
34
|
-
if (options.debug || process.env.BIOMIMIC_DEBUG === "true") {
|
|
35
|
-
this.level = "debug";
|
|
36
|
-
} else if (options.verbose || process.env.BIOMIMIC_VERBOSE === "true") {
|
|
37
|
-
this.level = "info";
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
setLevel(level) {
|
|
41
|
-
this.level = level;
|
|
42
|
-
}
|
|
43
|
-
shouldLog(level) {
|
|
44
|
-
const levels = ["silent", "error", "warn", "info", "debug"];
|
|
45
|
-
return levels.indexOf(level) <= levels.indexOf(this.level);
|
|
46
|
-
}
|
|
47
|
-
info(message, ...args) {
|
|
48
|
-
if (this.shouldLog("info")) {
|
|
49
|
-
process.stdout.write(`${message}${args.length ? " " + args.join(" ") : ""}
|
|
50
|
-
`);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
debug(message, ...args) {
|
|
54
|
-
if (this.shouldLog("debug")) {
|
|
55
|
-
process.stdout.write(`[debug] ${message}${args.length ? " " + args.join(" ") : ""}
|
|
56
|
-
`);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
warn(message) {
|
|
60
|
-
if (this.shouldLog("warn")) {
|
|
61
|
-
process.stderr.write(`[warn] ${message}
|
|
62
|
-
`);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
error(message) {
|
|
66
|
-
if (this.shouldLog("error")) {
|
|
67
|
-
process.stderr.write(`[error] ${message}
|
|
68
|
-
`);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
success(message, ...args) {
|
|
72
|
-
if (this.shouldLog("info")) {
|
|
73
|
-
process.stdout.write(`\u2713 ${message}${args.length ? " " + args.join(" ") : ""}
|
|
74
|
-
`);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
fail(message) {
|
|
78
|
-
if (this.shouldLog("error")) {
|
|
79
|
-
process.stderr.write(`\u2717 ${message}
|
|
80
|
-
`);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
};
|
|
84
|
-
var logger = null;
|
|
85
|
-
function createLogger(options = {}) {
|
|
86
|
-
logger = new Logger(options);
|
|
87
|
-
return logger;
|
|
88
|
-
}
|
|
89
|
-
function getLogger() {
|
|
90
|
-
if (!logger) {
|
|
91
|
-
logger = new Logger();
|
|
92
|
-
}
|
|
93
|
-
return logger;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// src/cli/utils/auto-command.ts
|
|
97
|
-
function extractZodInfo(schema) {
|
|
98
|
-
const def = schema._def;
|
|
99
|
-
if (def.typeName === "ZodOptional") {
|
|
100
|
-
const inner = schema._def.innerType;
|
|
101
|
-
const info = extractZodInfo(inner);
|
|
102
|
-
return { ...info, required: false };
|
|
103
|
-
}
|
|
104
|
-
if (def.typeName === "ZodDefault") {
|
|
105
|
-
const innerDef = schema._def;
|
|
106
|
-
const defaultValue = innerDef.defaultValue();
|
|
107
|
-
const info = extractZodInfo(innerDef.innerType);
|
|
108
|
-
return { ...info, required: false, defaultValue };
|
|
109
|
-
}
|
|
110
|
-
if (def.typeName === "ZodEnum") {
|
|
111
|
-
const enumValues = [...schema._def.values];
|
|
112
|
-
return { type: "enum", required: true, enumValues };
|
|
113
|
-
}
|
|
114
|
-
if (def.typeName === "ZodString") return { type: "string", required: true };
|
|
115
|
-
if (def.typeName === "ZodNumber") return { type: "number", required: true };
|
|
116
|
-
if (def.typeName === "ZodBoolean") return { type: "boolean", required: true };
|
|
117
|
-
if (def.typeName === "ZodObject") return { type: "object", required: true };
|
|
118
|
-
return { type: "string", required: true };
|
|
119
|
-
}
|
|
120
|
-
function schemaToOptions(schema) {
|
|
121
|
-
const options = [];
|
|
122
|
-
const shape = schema.shape;
|
|
123
|
-
for (const [key, zodType] of Object.entries(shape)) {
|
|
124
|
-
const info = extractZodInfo(zodType);
|
|
125
|
-
const longFlag = key.replace(/([A-Z])/g, "-$1").toLowerCase();
|
|
126
|
-
let flags = `--${longFlag} <value>`;
|
|
127
|
-
if (info.type === "boolean") {
|
|
128
|
-
flags = `--${longFlag}`;
|
|
129
|
-
}
|
|
130
|
-
let description = String(info.type);
|
|
131
|
-
if (info.enumValues) {
|
|
132
|
-
description = `${info.type} (${info.enumValues.join("|")})`;
|
|
133
|
-
}
|
|
134
|
-
options.push({
|
|
135
|
-
flags,
|
|
136
|
-
description,
|
|
137
|
-
required: info.required,
|
|
138
|
-
defaultValue: info.defaultValue
|
|
139
|
-
});
|
|
140
|
-
}
|
|
141
|
-
return options;
|
|
142
|
-
}
|
|
143
|
-
function pathToApiCall(client, _method, path3) {
|
|
144
|
-
const pathParts = path3.split("/").filter(Boolean);
|
|
145
|
-
let current = client.api;
|
|
146
|
-
for (const part of pathParts) {
|
|
147
|
-
if (part.startsWith("{") && part.endsWith("}")) {
|
|
148
|
-
continue;
|
|
149
|
-
}
|
|
150
|
-
current = current[part] || current[`:${part}`];
|
|
151
|
-
}
|
|
152
|
-
return current;
|
|
153
|
-
}
|
|
154
|
-
function createCommandFromRoute(config) {
|
|
155
|
-
const options = [];
|
|
156
|
-
const arguments_ = [];
|
|
157
|
-
if (config.params) {
|
|
158
|
-
const shape = config.params.shape;
|
|
159
|
-
for (const [key] of Object.entries(shape)) {
|
|
160
|
-
arguments_.push({
|
|
161
|
-
name: key,
|
|
162
|
-
description: `${key} parameter`,
|
|
163
|
-
required: true
|
|
164
|
-
});
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
if (config.body) {
|
|
168
|
-
const bodySchema = config.body;
|
|
169
|
-
if (bodySchema.shape) {
|
|
170
|
-
options.push(...schemaToOptions(bodySchema) || []);
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
if (config.query) {
|
|
174
|
-
options.push(...schemaToOptions(config.query) || []);
|
|
175
|
-
}
|
|
176
|
-
return {
|
|
177
|
-
name: config.command,
|
|
178
|
-
description: config.description,
|
|
179
|
-
options,
|
|
180
|
-
arguments: arguments_,
|
|
181
|
-
action: async (opts, args) => {
|
|
182
|
-
const logger2 = getLogger();
|
|
183
|
-
const client = getClient();
|
|
184
|
-
const param = {};
|
|
185
|
-
if (config.params) {
|
|
186
|
-
const shape = config.params.shape;
|
|
187
|
-
const keys = Object.keys(shape);
|
|
188
|
-
keys.forEach((key, i) => {
|
|
189
|
-
param[key] = args[i] || String(opts[key]);
|
|
190
|
-
});
|
|
191
|
-
}
|
|
192
|
-
const json = {};
|
|
193
|
-
if (config.body && opts) {
|
|
194
|
-
const bodySchema = config.body;
|
|
195
|
-
if (bodySchema.shape) {
|
|
196
|
-
const shape = bodySchema.shape;
|
|
197
|
-
for (const key of Object.keys(shape)) {
|
|
198
|
-
if (opts[key] !== void 0) {
|
|
199
|
-
json[key] = opts[key];
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
const query = {};
|
|
205
|
-
if (config.query && opts) {
|
|
206
|
-
const shape = config.query.shape;
|
|
207
|
-
for (const key of Object.keys(shape)) {
|
|
208
|
-
if (opts[key] !== void 0) {
|
|
209
|
-
query[key] = String(opts[key]);
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
const apiCall = pathToApiCall(client, config.method, config.path);
|
|
214
|
-
const methodCall = apiCall[`$${config.method.charAt(0).toUpperCase() + config.method.slice(1)}`] || apiCall.$get;
|
|
215
|
-
const res = await methodCall.call(apiCall, {
|
|
216
|
-
param: Object.keys(param).length > 0 ? param : void 0,
|
|
217
|
-
query: Object.keys(query).length > 0 ? query : void 0,
|
|
218
|
-
json: Object.keys(json).length > 0 ? json : void 0
|
|
219
|
-
});
|
|
220
|
-
const data = await res.json();
|
|
221
|
-
logger2.info(JSON.stringify(data, null, 2));
|
|
222
|
-
}
|
|
223
|
-
};
|
|
224
|
-
}
|
|
225
|
-
function registerAutoCommand(program2, config) {
|
|
226
|
-
const cmd = createCommandFromRoute(config);
|
|
227
|
-
const command = program2.command(cmd.name).description(cmd.description).action(async (firstArg, opts) => {
|
|
228
|
-
const args = typeof firstArg === "string" ? [firstArg] : [];
|
|
229
|
-
const options = (typeof firstArg === "object" ? firstArg : opts) || {};
|
|
230
|
-
await cmd.action(options, args);
|
|
231
|
-
});
|
|
232
|
-
cmd.options?.forEach((opt) => {
|
|
233
|
-
if (opt.required) {
|
|
234
|
-
command.requiredOption(opt.flags, opt.description, opt.defaultValue);
|
|
235
|
-
} else {
|
|
236
|
-
command.option(opt.flags, opt.description, opt.defaultValue);
|
|
237
|
-
}
|
|
238
|
-
});
|
|
239
|
-
cmd.arguments?.forEach((arg) => {
|
|
240
|
-
command.argument(arg.required ? `<${arg.name}>` : `[${arg.name}]`, arg.description);
|
|
241
|
-
});
|
|
242
|
-
}
|
|
243
|
-
var TodoStatusSchema = z.enum(["pending", "in_progress", "completed"]);
|
|
244
|
-
var todoRoutes = [
|
|
245
|
-
{
|
|
246
|
-
method: "get",
|
|
247
|
-
path: "/todos",
|
|
248
|
-
command: "list",
|
|
249
|
-
description: "List all todos"
|
|
250
|
-
},
|
|
251
|
-
{
|
|
252
|
-
method: "get",
|
|
253
|
-
path: "/todos/{id}",
|
|
254
|
-
command: "get",
|
|
255
|
-
description: "Get a todo by ID",
|
|
256
|
-
params: z.object({ id: z.string() })
|
|
257
|
-
},
|
|
258
|
-
{
|
|
259
|
-
method: "post",
|
|
260
|
-
path: "/todos",
|
|
261
|
-
command: "create",
|
|
262
|
-
description: "Create a new todo",
|
|
263
|
-
body: z.object({
|
|
264
|
-
title: z.string().min(1),
|
|
265
|
-
description: z.string().optional()
|
|
266
|
-
})
|
|
267
|
-
},
|
|
268
|
-
{
|
|
269
|
-
method: "put",
|
|
270
|
-
path: "/todos/{id}",
|
|
271
|
-
command: "update",
|
|
272
|
-
description: "Update a todo",
|
|
273
|
-
params: z.object({ id: z.string() }),
|
|
274
|
-
body: z.object({
|
|
275
|
-
title: z.string().optional(),
|
|
276
|
-
description: z.string().optional(),
|
|
277
|
-
status: TodoStatusSchema.optional()
|
|
278
|
-
})
|
|
279
|
-
},
|
|
280
|
-
{
|
|
281
|
-
method: "delete",
|
|
282
|
-
path: "/todos/{id}",
|
|
283
|
-
command: "delete",
|
|
284
|
-
description: "Delete a todo",
|
|
285
|
-
params: z.object({ id: z.string() })
|
|
286
|
-
}
|
|
287
|
-
];
|
|
288
|
-
function registerTodoCommands(program2) {
|
|
289
|
-
const todo = program2.command("todo").description("Todo management commands");
|
|
290
|
-
for (const route of todoRoutes) {
|
|
291
|
-
registerAutoCommand(todo, route);
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
// src/cli/modules/notification/index.ts
|
|
296
|
-
function registerNotificationCommands(program2) {
|
|
297
|
-
const notification = program2.command("notification").description("Notification management commands");
|
|
298
|
-
notification.command("list").description("List all notifications").option("--unread-only", "Show only unread notifications").option("--limit <number>", "Limit number of results", "20").action(async (options) => {
|
|
299
|
-
const logger2 = getLogger();
|
|
300
|
-
const client = getClient();
|
|
301
|
-
const unreadOnly = Boolean(options.unreadOnly);
|
|
302
|
-
const limit = parseInt(options.limit || "20");
|
|
303
|
-
const res = await client.api.notifications.$get({
|
|
304
|
-
query: { unreadOnly: String(unreadOnly), limit: String(limit) }
|
|
305
|
-
});
|
|
306
|
-
const data = await res.json();
|
|
307
|
-
logger2.info(JSON.stringify(data, null, 2));
|
|
308
|
-
});
|
|
309
|
-
notification.command("create").description("Create a new notification").requiredOption("-t, --title <title>", "Notification title").requiredOption("-m, --message <message>", "Notification message").option("--type <type>", "Notification type (info|warning|success|error)", "info").action(async (options) => {
|
|
310
|
-
const logger2 = getLogger();
|
|
311
|
-
const client = getClient();
|
|
312
|
-
const res = await client.api.notifications.$post({
|
|
313
|
-
json: {
|
|
314
|
-
type: options.type,
|
|
315
|
-
title: options.title,
|
|
316
|
-
message: options.message
|
|
317
|
-
}
|
|
318
|
-
});
|
|
319
|
-
const data = await res.json();
|
|
320
|
-
logger2.success("Notification created");
|
|
321
|
-
logger2.info(JSON.stringify(data, null, 2));
|
|
322
|
-
});
|
|
323
|
-
notification.command("unread-count").description("Get unread notification count").action(async () => {
|
|
324
|
-
const logger2 = getLogger();
|
|
325
|
-
const client = getClient();
|
|
326
|
-
const res = await client.api.notifications["unread-count"].$get();
|
|
327
|
-
const data = await res.json();
|
|
328
|
-
logger2.info(JSON.stringify(data, null, 2));
|
|
329
|
-
});
|
|
330
|
-
notification.command("mark-read").description("Mark a notification as read").argument("<id>", "Notification ID").action(async (id) => {
|
|
331
|
-
const logger2 = getLogger();
|
|
332
|
-
const client = getClient();
|
|
333
|
-
const res = await client.api.notifications[":id"].read.$patch({ param: { id } });
|
|
334
|
-
const data = await res.json();
|
|
335
|
-
logger2.success("Notification marked as read");
|
|
336
|
-
logger2.info(JSON.stringify(data, null, 2));
|
|
337
|
-
});
|
|
338
|
-
notification.command("delete").description("Delete a notification").argument("<id>", "Notification ID").action(async (id) => {
|
|
339
|
-
const logger2 = getLogger();
|
|
340
|
-
const client = getClient();
|
|
341
|
-
const res = await client.api.notifications[":id"].$delete({ param: { id } });
|
|
342
|
-
const data = await res.json();
|
|
343
|
-
logger2.success("Notification deleted");
|
|
344
|
-
logger2.info(JSON.stringify(data, null, 2));
|
|
345
|
-
});
|
|
346
|
-
}
|
|
347
|
-
var CONFIG_DIR = path2.join(os.homedir(), ".biomimic");
|
|
348
|
-
var CONFIG_FILE = path2.join(CONFIG_DIR, "config.json");
|
|
349
|
-
function loadConfig() {
|
|
350
|
-
try {
|
|
351
|
-
if (fs.existsSync(CONFIG_FILE)) {
|
|
352
|
-
const content = fs.readFileSync(CONFIG_FILE, "utf-8");
|
|
353
|
-
return JSON.parse(content);
|
|
354
|
-
}
|
|
355
|
-
} catch {
|
|
356
|
-
}
|
|
357
|
-
return { baseUrl: "http://localhost:3010" };
|
|
358
|
-
}
|
|
359
|
-
function saveConfig(config) {
|
|
360
|
-
if (!fs.existsSync(CONFIG_DIR)) {
|
|
361
|
-
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
362
|
-
}
|
|
363
|
-
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
364
|
-
}
|
|
365
|
-
function registerConfigCommands(program2) {
|
|
366
|
-
const config = program2.command("config").description("CLI configuration and service management");
|
|
367
|
-
config.command("get").description("Show current configuration").option("-k, --key <key>", "Get specific config key").action((options) => {
|
|
368
|
-
const logger2 = getLogger();
|
|
369
|
-
const cfg = loadConfig();
|
|
370
|
-
if (options.key) {
|
|
371
|
-
const value = cfg[options.key];
|
|
372
|
-
logger2.info(`${options.key}: ${value ?? "not set"}`);
|
|
373
|
-
} else {
|
|
374
|
-
logger2.info(JSON.stringify(cfg, null, 2));
|
|
375
|
-
}
|
|
376
|
-
});
|
|
377
|
-
config.command("set").description("Set configuration value").option("-u, --url <url>", "Set server URL").action((options) => {
|
|
378
|
-
const logger2 = getLogger();
|
|
379
|
-
const cfg = loadConfig();
|
|
380
|
-
if (options.url) {
|
|
381
|
-
cfg.baseUrl = options.url;
|
|
382
|
-
setBaseUrl(options.url);
|
|
383
|
-
logger2.success(`Server URL set to: ${options.url}`);
|
|
384
|
-
}
|
|
385
|
-
saveConfig(cfg);
|
|
386
|
-
});
|
|
387
|
-
config.command("url").description("Show or set server URL").argument("[url]", "New server URL").action((url) => {
|
|
388
|
-
const logger2 = getLogger();
|
|
389
|
-
if (url) {
|
|
390
|
-
const cfg = loadConfig();
|
|
391
|
-
cfg.baseUrl = url;
|
|
392
|
-
setBaseUrl(url);
|
|
393
|
-
saveConfig(cfg);
|
|
394
|
-
logger2.success(`Server URL set to: ${url}`);
|
|
395
|
-
} else {
|
|
396
|
-
logger2.info(`Current server URL: ${getBaseUrl()}`);
|
|
397
|
-
}
|
|
398
|
-
});
|
|
399
|
-
config.command("status").description("Check server connection status").action(async () => {
|
|
400
|
-
const logger2 = getLogger();
|
|
401
|
-
const client = getClient();
|
|
402
|
-
try {
|
|
403
|
-
const res = await client.health.$get();
|
|
404
|
-
const data = await res.json();
|
|
405
|
-
logger2.success("Server is reachable");
|
|
406
|
-
logger2.info(JSON.stringify(data, null, 2));
|
|
407
|
-
} catch (error) {
|
|
408
|
-
logger2.error(`Server not reachable: ${getBaseUrl()}`);
|
|
409
|
-
logger2.error(String(error));
|
|
410
|
-
}
|
|
411
|
-
});
|
|
412
|
-
config.command("reset").description("Reset configuration to defaults").action(() => {
|
|
413
|
-
const logger2 = getLogger();
|
|
414
|
-
const defaultConfig = { baseUrl: "http://localhost:3010" };
|
|
415
|
-
saveConfig(defaultConfig);
|
|
416
|
-
setBaseUrl(defaultConfig.baseUrl);
|
|
417
|
-
logger2.success("Configuration reset to defaults");
|
|
418
|
-
});
|
|
419
|
-
config.command("path").description("Show config file path").action(() => {
|
|
420
|
-
const logger2 = getLogger();
|
|
421
|
-
logger2.info(`Config file: ${CONFIG_FILE}`);
|
|
422
|
-
});
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
// src/cli/modules/index.ts
|
|
426
|
-
function registerModules(program2) {
|
|
427
|
-
registerTodoCommands(program2);
|
|
428
|
-
registerNotificationCommands(program2);
|
|
429
|
-
registerConfigCommands(program2);
|
|
430
|
-
}
|
|
431
11
|
var tsImportFn;
|
|
432
12
|
async function getTsImport() {
|
|
433
13
|
if (tsImportFn) return tsImportFn;
|
|
@@ -525,11 +105,13 @@ function resolvePreset(preset, allManifests) {
|
|
|
525
105
|
if (manifest.adminPages && manifest.adminPages.length > 0) hasAdmin = true;
|
|
526
106
|
if (manifest.routes.admin && manifest.routes.admin.length > 0) hasAdmin = true;
|
|
527
107
|
}
|
|
108
|
+
const isCliOnly = preset.id === "cli-only";
|
|
528
109
|
return {
|
|
529
110
|
preset,
|
|
530
111
|
modules,
|
|
531
112
|
hasAdmin,
|
|
532
|
-
hasClient:
|
|
113
|
+
hasClient: !isCliOnly,
|
|
114
|
+
hasCli: true,
|
|
533
115
|
hasSSE,
|
|
534
116
|
hasWebSocket,
|
|
535
117
|
hasPermission: modules.has("permission"),
|
|
@@ -563,13 +145,6 @@ function getAdminPages(resolved) {
|
|
|
563
145
|
}
|
|
564
146
|
return pages;
|
|
565
147
|
}
|
|
566
|
-
function getDefaultRoute(resolved) {
|
|
567
|
-
const pages = getClientPages(resolved);
|
|
568
|
-
if (pages.length > 0) {
|
|
569
|
-
return pages[0].route;
|
|
570
|
-
}
|
|
571
|
-
return "/";
|
|
572
|
-
}
|
|
573
148
|
|
|
574
149
|
// src/generators/file-filter.ts
|
|
575
150
|
function getExcludePatterns(resolved, allManifests) {
|
|
@@ -617,22 +192,54 @@ function getExcludePatterns(resolved, allManifests) {
|
|
|
617
192
|
if (name === "notifications") {
|
|
618
193
|
excludes.push("src/cli/modules/notification");
|
|
619
194
|
}
|
|
195
|
+
if (name === "todos") {
|
|
196
|
+
excludes.push("src/cli/modules/todo");
|
|
197
|
+
excludes.push("src/server/__tests__/integration/todos-api.test.ts");
|
|
198
|
+
}
|
|
199
|
+
if (name === "auth") {
|
|
200
|
+
excludes.push("src/cli/modules/auth");
|
|
201
|
+
}
|
|
202
|
+
if (name === "plugin") {
|
|
203
|
+
excludes.push("src/cli/modules/plugin");
|
|
204
|
+
}
|
|
620
205
|
}
|
|
621
206
|
if (!resolved.modules.has("admin")) {
|
|
622
|
-
excludes.push("src/client/components/AuthButton.tsx");
|
|
623
207
|
excludes.push("src/admin");
|
|
624
208
|
excludes.push("admin.html");
|
|
625
209
|
excludes.push("auth-inject.html");
|
|
210
|
+
}
|
|
211
|
+
if (!resolved.hasClient) {
|
|
212
|
+
excludes.push("src/client");
|
|
213
|
+
excludes.push("index.html");
|
|
214
|
+
excludes.push("admin.html");
|
|
215
|
+
excludes.push("auth-inject.html");
|
|
216
|
+
excludes.push("src/admin");
|
|
217
|
+
excludes.push("vite.config.ts");
|
|
218
|
+
excludes.push("postcss.config.js");
|
|
219
|
+
excludes.push("tailwind.config.js");
|
|
220
|
+
}
|
|
221
|
+
if (!resolved.hasClient) {
|
|
222
|
+
excludes.push("src/client/components/AuthButton.tsx");
|
|
223
|
+
excludes.push("src/client/components/__tests__/AuthButton.test.tsx");
|
|
224
|
+
} else if (!resolved.modules.has("admin") && !resolved.modules.has("auth")) {
|
|
225
|
+
excludes.push("src/client/components/AuthButton.tsx");
|
|
626
226
|
excludes.push("src/client/components/__tests__/AuthButton.test.tsx");
|
|
627
|
-
excludes.push("src/cli");
|
|
628
227
|
excludes.push("src/server/utils/auth.ts");
|
|
629
228
|
}
|
|
630
|
-
|
|
229
|
+
excludes.push("src/client/preset-ui-config.ts");
|
|
230
|
+
if (!resolved.hasPermission && !resolved.modules.has("auth")) {
|
|
631
231
|
excludes.push("src/server/utils/permission-utils.ts");
|
|
632
232
|
excludes.push("src/server/middleware/__tests__/auth-simple.test.ts");
|
|
633
233
|
excludes.push("src/server/middleware/__tests__/auth.test.ts");
|
|
634
234
|
excludes.push("src/server/middleware/__tests__/error-response-format.test.ts");
|
|
635
235
|
excludes.push("src/server/utils/__tests__/auth.test.ts");
|
|
236
|
+
} else if (!resolved.hasPermission && resolved.modules.has("auth")) {
|
|
237
|
+
excludes.push("src/server/utils/permission-utils.ts");
|
|
238
|
+
excludes.push("src/server/middleware/__tests__/auth-simple.test.ts");
|
|
239
|
+
excludes.push("src/server/middleware/__tests__/auth.test.ts");
|
|
240
|
+
excludes.push("src/server/middleware/__tests__/error-response-format.test.ts");
|
|
241
|
+
excludes.push("src/server/utils/__tests__/auth.test.ts");
|
|
242
|
+
excludes.push("src/server/module-auth/__tests__/auth-service.test.ts");
|
|
636
243
|
} else if (!resolved.modules.has("admin")) {
|
|
637
244
|
excludes.push("src/server/middleware/__tests__/error-response-format.test.ts");
|
|
638
245
|
}
|
|
@@ -645,16 +252,29 @@ function getGeneratedFiles(resolved) {
|
|
|
645
252
|
const files = [
|
|
646
253
|
"src/server/route-registry.ts",
|
|
647
254
|
"src/server/db/schema/index.ts",
|
|
648
|
-
"src/client/App.tsx",
|
|
649
|
-
"src/client/components/Navigation.tsx",
|
|
650
255
|
"src/shared/modules/index.ts",
|
|
651
256
|
"src/shared/schemas/index.ts",
|
|
652
257
|
"src/server/middleware/index.ts",
|
|
653
|
-
"src/
|
|
258
|
+
"src/server/app.ts",
|
|
259
|
+
"src/cli/modules/index.ts"
|
|
654
260
|
];
|
|
655
|
-
if (resolved.
|
|
261
|
+
if (resolved.hasClient) {
|
|
262
|
+
files.push(
|
|
263
|
+
"src/client/App.tsx",
|
|
264
|
+
"src/client/components/Navigation.tsx",
|
|
265
|
+
"src/client/components/index.ts",
|
|
266
|
+
"src/client/Layout.tsx",
|
|
267
|
+
"src/client/components/__tests__/App.test.tsx",
|
|
268
|
+
"src/client/components/__tests__/Navigation.test.tsx",
|
|
269
|
+
"src/client/main.tsx",
|
|
270
|
+
"src/client/preset-ui-config.ts"
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
if (resolved.hasClient && resolved.modules.has("admin")) {
|
|
656
274
|
files.push("src/admin/App.tsx");
|
|
657
|
-
|
|
275
|
+
}
|
|
276
|
+
if (resolved.hasClient && !resolved.modules.has("admin")) {
|
|
277
|
+
files.push("vite.config.ts");
|
|
658
278
|
}
|
|
659
279
|
if (!resolved.hasPermission) {
|
|
660
280
|
files.push("src/server/middleware/auth.ts");
|
|
@@ -663,10 +283,6 @@ function getGeneratedFiles(resolved) {
|
|
|
663
283
|
if (!resolved.modules.has("admin") && resolved.hasPermission) {
|
|
664
284
|
files.push("src/server/utils/auth.ts");
|
|
665
285
|
}
|
|
666
|
-
files.push("src/server/app.ts");
|
|
667
|
-
if (!resolved.modules.has("admin")) {
|
|
668
|
-
files.push("vite.config.ts");
|
|
669
|
-
}
|
|
670
286
|
const seedModules = ["order", "ticket", "dispute", "content"];
|
|
671
287
|
if (seedModules.some((m) => !resolved.modules.has(m)) || !resolved.hasPermission) {
|
|
672
288
|
files.push("src/server/db/init.ts");
|
|
@@ -686,11 +302,14 @@ function generateRouteRegistry(resolved) {
|
|
|
686
302
|
for (const [name, manifest] of moduleEntries) {
|
|
687
303
|
const moduleDir = `module-${name}`;
|
|
688
304
|
if (manifest.routes.client) {
|
|
689
|
-
const
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
305
|
+
const clientRouteList = Array.isArray(manifest.routes.client) ? manifest.routes.client : [manifest.routes.client];
|
|
306
|
+
for (const route of clientRouteList) {
|
|
307
|
+
const { importPath, exportName } = route;
|
|
308
|
+
imports.push(
|
|
309
|
+
`import { ${exportName} } from './${moduleDir}/${importPath.replace(/^\.\//, "")}'`
|
|
310
|
+
);
|
|
311
|
+
clientRoutes.push(` .route('/api', ${exportName})`);
|
|
312
|
+
}
|
|
694
313
|
}
|
|
695
314
|
if (manifest.routes.admin) {
|
|
696
315
|
for (const route of manifest.routes.admin) {
|
|
@@ -747,130 +366,148 @@ function generateRouteRegistry(resolved) {
|
|
|
747
366
|
return content;
|
|
748
367
|
}
|
|
749
368
|
|
|
750
|
-
// src/generators/client-app.ts
|
|
751
|
-
function generateClientApp(resolved) {
|
|
752
|
-
const pages = getClientPages(resolved);
|
|
753
|
-
const defaultRoute = getDefaultRoute(resolved);
|
|
754
|
-
const imports = [
|
|
755
|
-
`import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom'`,
|
|
756
|
-
`import { Layout } from './Layout'`
|
|
757
|
-
];
|
|
758
|
-
for (const page of pages) {
|
|
759
|
-
imports.push(`import { ${page.name} } from './pages/${page.name}'`);
|
|
760
|
-
}
|
|
761
|
-
const routeElements = [];
|
|
762
|
-
routeElements.push(
|
|
763
|
-
` <Route path="/" element={<Navigate to="${defaultRoute}" replace />} />`
|
|
764
|
-
);
|
|
765
|
-
for (const page of pages) {
|
|
766
|
-
routeElements.push(
|
|
767
|
-
` <Route path="${page.route}" element={<${page.name} />} />`
|
|
768
|
-
);
|
|
769
|
-
}
|
|
770
|
-
return `${imports.join("\n")}
|
|
771
|
-
|
|
772
|
-
export function App() {
|
|
773
|
-
return (
|
|
774
|
-
<BrowserRouter>
|
|
775
|
-
<Layout>
|
|
776
|
-
<Routes>
|
|
777
|
-
${routeElements.join("\n")}
|
|
778
|
-
</Routes>
|
|
779
|
-
</Layout>
|
|
780
|
-
</BrowserRouter>
|
|
781
|
-
)
|
|
782
|
-
}
|
|
783
|
-
`;
|
|
784
|
-
}
|
|
785
|
-
|
|
786
369
|
// src/generators/client-navigation.ts
|
|
787
370
|
var DEFAULT_ICON = "Circle";
|
|
788
371
|
var ICON_MAP = {
|
|
789
372
|
TodoPage: "CheckCircle",
|
|
790
373
|
NotificationPage: "Bell",
|
|
791
374
|
WebSocketPage: "Plug",
|
|
792
|
-
ContentListPage: "FileText"
|
|
375
|
+
ContentListPage: "FileText",
|
|
376
|
+
PluginsPage: "Package",
|
|
377
|
+
CategoriesPage: "Grid",
|
|
378
|
+
SearchPage: "Search",
|
|
379
|
+
PublishPage: "Upload",
|
|
380
|
+
DeveloperDashboardPage: "Code",
|
|
381
|
+
PluginDetailPage: "Package",
|
|
382
|
+
TopicsPage: "Hash",
|
|
383
|
+
ProfilePage: "User",
|
|
384
|
+
DashboardPage: "LayoutDashboard",
|
|
385
|
+
SettingsPage: "Settings",
|
|
386
|
+
CartPage: "ShoppingCart",
|
|
387
|
+
OrdersPage: "Package"
|
|
793
388
|
};
|
|
794
389
|
function generateClientNavigation(resolved) {
|
|
795
390
|
const pages = getClientPages(resolved).filter((p) => !p.route.includes(":"));
|
|
796
|
-
const
|
|
797
|
-
|
|
798
|
-
iconsNeeded.add("Github");
|
|
391
|
+
const hasAuth = resolved.modules.has("auth");
|
|
392
|
+
const navItems = [];
|
|
799
393
|
for (const page of pages) {
|
|
800
394
|
const icon = ICON_MAP[page.name] || DEFAULT_ICON;
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
const
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
395
|
+
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);
|
|
396
|
+
navItems.push(` { label: '${label}', icon: '${icon}', path: '${page.route}' },`);
|
|
397
|
+
}
|
|
398
|
+
const authImport = hasAuth ? `
|
|
399
|
+
import { useAuthStore } from '../stores/authStore'` : "";
|
|
400
|
+
const authSection = hasAuth ? `
|
|
401
|
+
function AuthSection({ style }: { style: string }) {
|
|
402
|
+
const isAuthenticated = useAuthStore((state: any) => state.isAuthenticated)
|
|
403
|
+
const user = useAuthStore((state: any) => state.user)
|
|
404
|
+
const logout = useAuthStore((state: any) => state.logout)
|
|
405
|
+
|
|
406
|
+
if (style === 'none') return null
|
|
407
|
+
|
|
408
|
+
if (isAuthenticated) {
|
|
409
|
+
return (
|
|
410
|
+
<div className="flex items-center gap-2">
|
|
411
|
+
<span className="text-sm text-gray-600">{user?.username}</span>
|
|
412
|
+
<button onClick={logout} className="text-xs text-gray-400 hover:text-red-500">Sign Out</button>
|
|
413
|
+
</div>
|
|
414
|
+
)
|
|
812
415
|
}
|
|
813
|
-
const iconsStr = [...iconsNeeded].join(", ");
|
|
814
|
-
const authButtonImport = resolved.modules.has("admin") ? `
|
|
815
|
-
import { AuthButton } from './AuthButton'` : "";
|
|
816
|
-
const authButtonElement = resolved.modules.has("admin") ? `
|
|
817
|
-
<AuthButton />` : "";
|
|
818
|
-
return `import { NavLink } from 'react-router-dom'
|
|
819
|
-
import { ${iconsStr} } from 'lucide-react'${authButtonImport}
|
|
820
416
|
|
|
821
|
-
|
|
417
|
+
if (style === 'buttons') {
|
|
418
|
+
return (
|
|
419
|
+
<div className="flex items-center gap-2">
|
|
420
|
+
<Link
|
|
421
|
+
to="/login"
|
|
422
|
+
className="px-3 py-1 text-sm bg-blue-500 text-white rounded hover:bg-blue-600"
|
|
423
|
+
>
|
|
424
|
+
Sign In
|
|
425
|
+
</Link>
|
|
426
|
+
<Link
|
|
427
|
+
to="/register"
|
|
428
|
+
className="px-3 py-1 text-sm border border-gray-300 text-gray-700 rounded hover:bg-gray-50"
|
|
429
|
+
>
|
|
430
|
+
Sign Up
|
|
431
|
+
</Link>
|
|
432
|
+
</div>
|
|
433
|
+
)
|
|
434
|
+
}
|
|
822
435
|
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
>
|
|
827
|
-
|
|
436
|
+
return (
|
|
437
|
+
<Link to="/login" className="text-sm text-gray-500 hover:text-gray-900">
|
|
438
|
+
Login
|
|
439
|
+
</Link>
|
|
440
|
+
)
|
|
828
441
|
}
|
|
442
|
+
` : `
|
|
443
|
+
function AuthSection(_style: { style: string }) {
|
|
444
|
+
return null
|
|
445
|
+
}
|
|
446
|
+
`;
|
|
447
|
+
return `import { NavLink${hasAuth ? ", Link" : ""} } from 'react-router-dom'
|
|
448
|
+
import { Rocket, Sparkles } from 'lucide-react'${authImport}
|
|
449
|
+
import type { PresetTheme, NavigationConfig, ClientNavItem } from '../preset-ui-config'
|
|
450
|
+
|
|
451
|
+
interface NavigationProps {
|
|
452
|
+
preset?: string
|
|
453
|
+
items?: ClientNavItem[]
|
|
454
|
+
theme?: PresetTheme
|
|
455
|
+
navigation?: NavigationConfig
|
|
456
|
+
}
|
|
457
|
+
${authSection}
|
|
458
|
+
export const Navigation: React.FC<NavigationProps> = ({
|
|
459
|
+
items,
|
|
460
|
+
theme,
|
|
461
|
+
navigation,
|
|
462
|
+
}) => {
|
|
463
|
+
const navItems = navigation?.navItems === 'none' ? [] : (items ?? [])
|
|
464
|
+
const primaryColor = theme?.primaryColor ?? '#6366f1'
|
|
465
|
+
const logoText = theme?.logoText ?? 'Biomimic'
|
|
466
|
+
const showLogo = navigation?.showLogo !== false
|
|
467
|
+
const authStyle = navigation?.authStyle ?? 'none'
|
|
829
468
|
|
|
830
|
-
export const Navigation: React.FC = () => {
|
|
831
469
|
return (
|
|
832
|
-
<nav className="bg-white border-b border-gray-200 sticky top-0 z-50" data-testid="app-nav">
|
|
833
|
-
<div className="max-w-
|
|
834
|
-
|
|
835
|
-
<
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
>
|
|
871
|
-
<Github className="w-5 h-5" />
|
|
872
|
-
</a>
|
|
470
|
+
<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">
|
|
471
|
+
<div className="max-w-7xl mx-auto px-4 h-16 flex items-center justify-between gap-3">
|
|
472
|
+
{showLogo && (
|
|
473
|
+
<NavLink to="/" className="flex items-center gap-2 group shrink-0" data-testid="app-title">
|
|
474
|
+
<div
|
|
475
|
+
className="w-8 h-8 rounded-lg flex items-center justify-center shadow-sm group-hover:shadow-md transition-shadow"
|
|
476
|
+
style={{ backgroundColor: primaryColor }}
|
|
477
|
+
>
|
|
478
|
+
<Rocket className="w-4 h-4 text-white" />
|
|
479
|
+
</div>
|
|
480
|
+
<span className="text-lg font-semibold text-gray-900 tracking-tight whitespace-nowrap">
|
|
481
|
+
{logoText}
|
|
482
|
+
</span>
|
|
483
|
+
<Sparkles className="w-3.5 h-3.5 shrink-0" style={{ color: primaryColor }} />
|
|
484
|
+
</NavLink>
|
|
485
|
+
)}
|
|
486
|
+
|
|
487
|
+
<div className="flex items-center gap-0.5 overflow-x-auto flex-1 min-w-0 scrollbar-hide">
|
|
488
|
+
{navItems.map(item => (
|
|
489
|
+
<NavLink
|
|
490
|
+
key={item.path}
|
|
491
|
+
to={item.path}
|
|
492
|
+
data-testid={\`nav-\${item.label.toLowerCase().replace(/\\s+/g, '-')}-button\`}
|
|
493
|
+
className={({ isActive }: { isActive: boolean }) =>
|
|
494
|
+
\`px-3 py-1.5 rounded-lg text-sm font-medium transition-all duration-200 shrink-0 whitespace-nowrap \${
|
|
495
|
+
isActive ? 'text-white' : 'text-gray-500 hover:text-gray-900 hover:bg-gray-50'
|
|
496
|
+
}\`
|
|
497
|
+
}
|
|
498
|
+
style={
|
|
499
|
+
(({ isActive }: { isActive: boolean }) =>
|
|
500
|
+
isActive
|
|
501
|
+
? { backgroundColor: \`\${primaryColor}15\`, color: primaryColor }
|
|
502
|
+
: undefined) as never
|
|
503
|
+
}
|
|
504
|
+
>
|
|
505
|
+
{item.label}
|
|
506
|
+
</NavLink>
|
|
507
|
+
))}
|
|
873
508
|
</div>
|
|
509
|
+
|
|
510
|
+
<AuthSection style={authStyle} />
|
|
874
511
|
</div>
|
|
875
512
|
</nav>
|
|
876
513
|
)
|
|
@@ -878,6 +515,119 @@ export const Navigation: React.FC = () => {
|
|
|
878
515
|
`;
|
|
879
516
|
}
|
|
880
517
|
|
|
518
|
+
// src/generators/client-app-test.ts
|
|
519
|
+
function generateClientAppTest(resolved) {
|
|
520
|
+
const pages = getClientPages(resolved);
|
|
521
|
+
const mocks = pages.map(
|
|
522
|
+
(p) => `vi.mock('@client/pages/${p.name}', () => ({
|
|
523
|
+
${p.name}: () => <div data-testid="${p.name.toLowerCase()}-page">${p.name}</div>,
|
|
524
|
+
}))`
|
|
525
|
+
).join("\n\n ");
|
|
526
|
+
return `import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
|
|
527
|
+
import { render, screen, cleanup } from '@testing-library/react'
|
|
528
|
+
import '@testing-library/jest-dom'
|
|
529
|
+
import { App } from '@client/App'
|
|
530
|
+
|
|
531
|
+
${mocks}
|
|
532
|
+
|
|
533
|
+
describe('App Component', () => {
|
|
534
|
+
beforeEach(() => {
|
|
535
|
+
vi.clearAllMocks()
|
|
536
|
+
})
|
|
537
|
+
|
|
538
|
+
afterEach(() => {
|
|
539
|
+
cleanup()
|
|
540
|
+
})
|
|
541
|
+
|
|
542
|
+
describe('Initial Render', () => {
|
|
543
|
+
it('should render navigation', () => {
|
|
544
|
+
render(<App />)
|
|
545
|
+
expect(screen.getByTestId('app-nav')).toBeInTheDocument()
|
|
546
|
+
})
|
|
547
|
+
|
|
548
|
+
it('should render main content area', () => {
|
|
549
|
+
render(<App />)
|
|
550
|
+
expect(screen.getByTestId('app-main')).toBeInTheDocument()
|
|
551
|
+
})
|
|
552
|
+
|
|
553
|
+
it('should render container', () => {
|
|
554
|
+
render(<App />)
|
|
555
|
+
expect(screen.getByTestId('app-container')).toBeInTheDocument()
|
|
556
|
+
})
|
|
557
|
+
})
|
|
558
|
+
|
|
559
|
+
describe('Navigation Links', () => {
|
|
560
|
+
it('should render footer', () => {
|
|
561
|
+
render(<App />)
|
|
562
|
+
expect(screen.getByTestId('app-footer')).toBeInTheDocument()
|
|
563
|
+
})
|
|
564
|
+
})
|
|
565
|
+
})
|
|
566
|
+
`;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
// src/generators/client-navigation-test.ts
|
|
570
|
+
var LABEL_MAP = {
|
|
571
|
+
TodoPage: "Todos",
|
|
572
|
+
NotificationPage: "Notifications",
|
|
573
|
+
WebSocketPage: "WebSocket",
|
|
574
|
+
PluginsPage: "Plugins",
|
|
575
|
+
CategoriesPage: "Categories",
|
|
576
|
+
SearchPage: "Search",
|
|
577
|
+
PublishPage: "Publish",
|
|
578
|
+
DeveloperDashboardPage: "Developer",
|
|
579
|
+
TopicsPage: "Topics",
|
|
580
|
+
ProfilePage: "Profile",
|
|
581
|
+
DashboardPage: "Dashboard",
|
|
582
|
+
SettingsPage: "Settings",
|
|
583
|
+
CartPage: "Cart",
|
|
584
|
+
OrdersPage: "Orders"
|
|
585
|
+
};
|
|
586
|
+
function generateClientNavigationTest(resolved) {
|
|
587
|
+
const pages = getClientPages(resolved).filter((p) => !p.route.includes(":"));
|
|
588
|
+
const firstPage = pages[0];
|
|
589
|
+
if (!firstPage) {
|
|
590
|
+
return `import { describe, it, expect } from 'vitest'
|
|
591
|
+
import { render, screen } from '@testing-library/react'
|
|
592
|
+
import { BrowserRouter } from 'react-router-dom'
|
|
593
|
+
import { Navigation } from '../Navigation'
|
|
594
|
+
|
|
595
|
+
const renderWithRouter = (component: React.ReactNode) => {
|
|
596
|
+
return render(<BrowserRouter>{component}</BrowserRouter>)
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
describe('Navigation', () => {
|
|
600
|
+
it('should render navigation', () => {
|
|
601
|
+
renderWithRouter(<Navigation />)
|
|
602
|
+
expect(screen.getByTestId('app-nav')).toBeInTheDocument()
|
|
603
|
+
})
|
|
604
|
+
})
|
|
605
|
+
`;
|
|
606
|
+
}
|
|
607
|
+
const firstLabel = LABEL_MAP[firstPage.name] || firstPage.name.replace("Page", "");
|
|
608
|
+
return `import { describe, it, expect } from 'vitest'
|
|
609
|
+
import { render, screen } from '@testing-library/react'
|
|
610
|
+
import { BrowserRouter } from 'react-router-dom'
|
|
611
|
+
import { Navigation } from '../Navigation'
|
|
612
|
+
|
|
613
|
+
const renderWithRouter = (component: React.ReactNode) => {
|
|
614
|
+
return render(<BrowserRouter>{component}</BrowserRouter>)
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
describe('Navigation', () => {
|
|
618
|
+
it('should render navigation', () => {
|
|
619
|
+
renderWithRouter(<Navigation />)
|
|
620
|
+
expect(screen.getByTestId('app-nav')).toBeInTheDocument()
|
|
621
|
+
})
|
|
622
|
+
|
|
623
|
+
it('should render nav items', () => {
|
|
624
|
+
renderWithRouter(<Navigation />)
|
|
625
|
+
expect(screen.getByText('${firstLabel}')).toBeInTheDocument()
|
|
626
|
+
})
|
|
627
|
+
})
|
|
628
|
+
`;
|
|
629
|
+
}
|
|
630
|
+
|
|
881
631
|
// src/generators/admin-app.ts
|
|
882
632
|
function generateAdminApp(resolved) {
|
|
883
633
|
if (!resolved.hasAdmin) return null;
|
|
@@ -906,7 +656,7 @@ function generateAdminApp(resolved) {
|
|
|
906
656
|
);
|
|
907
657
|
return `${imports.join("\n")}
|
|
908
658
|
|
|
909
|
-
export const App: React.FC = () => {
|
|
659
|
+
export const App: React.FC<{ basePath?: string }> = ({ basePath = '/admin' }) => {
|
|
910
660
|
return (
|
|
911
661
|
<ConfigProvider
|
|
912
662
|
theme={{
|
|
@@ -915,7 +665,7 @@ export const App: React.FC = () => {
|
|
|
915
665
|
},
|
|
916
666
|
}}
|
|
917
667
|
>
|
|
918
|
-
<BrowserRouter basename=
|
|
668
|
+
<BrowserRouter basename={basePath}>
|
|
919
669
|
<Routes>
|
|
920
670
|
${publicRouteLines.join("\n")}
|
|
921
671
|
<Route
|
|
@@ -943,8 +693,8 @@ ${protectedRouteElements.join("\n")}
|
|
|
943
693
|
// src/generators/db-schema-barrel.ts
|
|
944
694
|
function generateDbSchemaBarrel(resolved) {
|
|
945
695
|
const files = getDbSchemaFiles(resolved);
|
|
946
|
-
const exports
|
|
947
|
-
return exports
|
|
696
|
+
const exports = files.map((f) => `export * from './${f}'`);
|
|
697
|
+
return exports.join("\n") + "\n";
|
|
948
698
|
}
|
|
949
699
|
|
|
950
700
|
// src/generators/db-init.ts
|
|
@@ -1629,19 +1379,73 @@ var MODULE_EXPORTS = {
|
|
|
1629
1379
|
"type PermissionInfo",
|
|
1630
1380
|
"type UserPermissions"
|
|
1631
1381
|
]
|
|
1382
|
+
},
|
|
1383
|
+
auth: {
|
|
1384
|
+
namedExports: [
|
|
1385
|
+
"DeveloperProfileSchema",
|
|
1386
|
+
"LoginSchema",
|
|
1387
|
+
"RegisterSchema",
|
|
1388
|
+
"TokenResponseSchema",
|
|
1389
|
+
"type DeveloperProfile",
|
|
1390
|
+
"type LoginInput",
|
|
1391
|
+
"type RegisterInput",
|
|
1392
|
+
"type TokenResponse"
|
|
1393
|
+
]
|
|
1394
|
+
},
|
|
1395
|
+
plugin: {
|
|
1396
|
+
namedExports: [
|
|
1397
|
+
"PluginSchema",
|
|
1398
|
+
"PluginStatusSchema",
|
|
1399
|
+
"CreatePluginSchema",
|
|
1400
|
+
"UpdatePluginSchema",
|
|
1401
|
+
"PluginVersionStatusSchema",
|
|
1402
|
+
"VersionSchema",
|
|
1403
|
+
"CategorySchema",
|
|
1404
|
+
"ReviewSchema",
|
|
1405
|
+
"CreateReviewSchema",
|
|
1406
|
+
"MarketplaceStatsSchema",
|
|
1407
|
+
"PluginListResponseSchema",
|
|
1408
|
+
"AdminPluginSchema",
|
|
1409
|
+
"AdminDashboardStatsSchema",
|
|
1410
|
+
"PluginListQuerySchema",
|
|
1411
|
+
"PluginSlugSchema",
|
|
1412
|
+
"type Plugin",
|
|
1413
|
+
"type PluginStatus",
|
|
1414
|
+
"type CreatePluginInput",
|
|
1415
|
+
"type UpdatePluginInput",
|
|
1416
|
+
"type PluginVersionStatus",
|
|
1417
|
+
"type Version",
|
|
1418
|
+
"type Category",
|
|
1419
|
+
"type Review",
|
|
1420
|
+
"type CreateReviewInput",
|
|
1421
|
+
"type MarketplaceStats",
|
|
1422
|
+
"type PluginListResponse",
|
|
1423
|
+
"type AdminPlugin",
|
|
1424
|
+
"type AdminDashboardStats",
|
|
1425
|
+
"type PluginListQuery"
|
|
1426
|
+
]
|
|
1632
1427
|
}
|
|
1633
1428
|
};
|
|
1634
1429
|
function generateSharedModulesIndex(resolved) {
|
|
1635
1430
|
const lines = [];
|
|
1636
|
-
const
|
|
1637
|
-
|
|
1431
|
+
const moduleOrder2 = [
|
|
1432
|
+
"chat",
|
|
1433
|
+
"todos",
|
|
1434
|
+
"file",
|
|
1435
|
+
"notifications",
|
|
1436
|
+
"admin",
|
|
1437
|
+
"permission",
|
|
1438
|
+
"auth",
|
|
1439
|
+
"plugin"
|
|
1440
|
+
];
|
|
1441
|
+
for (const moduleName of moduleOrder2) {
|
|
1638
1442
|
if (!resolved.modules.has(moduleName)) continue;
|
|
1639
1443
|
const manifest = resolved.modules.get(moduleName);
|
|
1640
1444
|
const exportKey = manifest.sharedSchemas?.path ?? moduleName;
|
|
1641
|
-
const exports
|
|
1642
|
-
if (!exports
|
|
1445
|
+
const exports = MODULE_EXPORTS[exportKey];
|
|
1446
|
+
if (!exports) continue;
|
|
1643
1447
|
lines.push(`export {
|
|
1644
|
-
${exports
|
|
1448
|
+
${exports.namedExports.join(",\n ")},
|
|
1645
1449
|
} from './${exportKey}'`);
|
|
1646
1450
|
}
|
|
1647
1451
|
return lines.join("\n") + "\n";
|
|
@@ -1713,8 +1517,329 @@ var MODULE_EXPORTS2 = {
|
|
|
1713
1517
|
"type NotificationId",
|
|
1714
1518
|
"type UnreadCountEvent"
|
|
1715
1519
|
]
|
|
1520
|
+
},
|
|
1521
|
+
auth: {
|
|
1522
|
+
namedExports: [
|
|
1523
|
+
"DeveloperProfileSchema",
|
|
1524
|
+
"LoginSchema",
|
|
1525
|
+
"RegisterSchema",
|
|
1526
|
+
"TokenResponseSchema",
|
|
1527
|
+
"ProfileSchema",
|
|
1528
|
+
"type DeveloperProfile",
|
|
1529
|
+
"type LoginInput",
|
|
1530
|
+
"type RegisterInput",
|
|
1531
|
+
"type TokenResponse",
|
|
1532
|
+
"type Profile"
|
|
1533
|
+
]
|
|
1534
|
+
},
|
|
1535
|
+
plugin: {
|
|
1536
|
+
namedExports: [
|
|
1537
|
+
"PluginSchema",
|
|
1538
|
+
"PluginStatusSchema",
|
|
1539
|
+
"CreatePluginSchema",
|
|
1540
|
+
"UpdatePluginSchema",
|
|
1541
|
+
"PluginVersionStatusSchema",
|
|
1542
|
+
"VersionSchema",
|
|
1543
|
+
"CategorySchema",
|
|
1544
|
+
"ReviewSchema",
|
|
1545
|
+
"CreateReviewSchema",
|
|
1546
|
+
"MarketplaceStatsSchema",
|
|
1547
|
+
"PluginListResponseSchema",
|
|
1548
|
+
"AdminPluginSchema",
|
|
1549
|
+
"AdminDashboardStatsSchema",
|
|
1550
|
+
"PluginListQuerySchema",
|
|
1551
|
+
"PluginSlugSchema",
|
|
1552
|
+
"PluginSearchQuerySchema",
|
|
1553
|
+
"PluginDeleteResponseSchema",
|
|
1554
|
+
"ReviewIdParamsSchema",
|
|
1555
|
+
"ReviewDeleteResponseSchema",
|
|
1556
|
+
"CategorySlugParamsSchema",
|
|
1557
|
+
"CategoryPluginsQuerySchema",
|
|
1558
|
+
"PluginListAdminSchema",
|
|
1559
|
+
"AdminListQuerySchema",
|
|
1560
|
+
"AdminListAllQuerySchema",
|
|
1561
|
+
"RejectPluginBodySchema",
|
|
1562
|
+
"BulkApproveBodySchema",
|
|
1563
|
+
"BulkRejectBodySchema",
|
|
1564
|
+
"BulkResponseSchema",
|
|
1565
|
+
"CreateCategoryBodySchema",
|
|
1566
|
+
"UpdateCategoryBodySchema",
|
|
1567
|
+
"CategoryIdParamsSchema",
|
|
1568
|
+
"CategoryIdResponseSchema",
|
|
1569
|
+
"type Plugin",
|
|
1570
|
+
"type PluginStatus",
|
|
1571
|
+
"type CreatePluginInput",
|
|
1572
|
+
"type UpdatePluginInput",
|
|
1573
|
+
"type PluginVersionStatus",
|
|
1574
|
+
"type Version",
|
|
1575
|
+
"type Category",
|
|
1576
|
+
"type Review",
|
|
1577
|
+
"type CreateReviewInput",
|
|
1578
|
+
"type MarketplaceStats",
|
|
1579
|
+
"type PluginListResponse",
|
|
1580
|
+
"type AdminPlugin",
|
|
1581
|
+
"type AdminDashboardStats",
|
|
1582
|
+
"type PluginListQuery"
|
|
1583
|
+
]
|
|
1584
|
+
},
|
|
1585
|
+
admin: {
|
|
1586
|
+
namedExports: [
|
|
1587
|
+
"SystemStatsSchema",
|
|
1588
|
+
"HealthCheckSchema",
|
|
1589
|
+
"RecentActivityItemSchema",
|
|
1590
|
+
"RecentActivitySchema",
|
|
1591
|
+
"AuthUserSchema",
|
|
1592
|
+
"LoginRequestSchema",
|
|
1593
|
+
"LoginResponseSchema",
|
|
1594
|
+
"RegisterRequestSchema",
|
|
1595
|
+
"UserSchema",
|
|
1596
|
+
"UserListSchema",
|
|
1597
|
+
"UpdateUserRequestSchema",
|
|
1598
|
+
"CreateUserRequestSchema",
|
|
1599
|
+
"ClearTodosResultSchema",
|
|
1600
|
+
"SuccessSchema",
|
|
1601
|
+
"DownloadTokenSchema",
|
|
1602
|
+
"type SystemStats",
|
|
1603
|
+
"type HealthCheck",
|
|
1604
|
+
"type RecentActivityItem",
|
|
1605
|
+
"type AuthUserResponse",
|
|
1606
|
+
"type CreateUserRequest",
|
|
1607
|
+
"type LoginRequest",
|
|
1608
|
+
"type LoginResponse",
|
|
1609
|
+
"type RegisterRequest",
|
|
1610
|
+
"type User",
|
|
1611
|
+
"type UpdateUserRequest",
|
|
1612
|
+
"type ClearTodosResult"
|
|
1613
|
+
]
|
|
1614
|
+
},
|
|
1615
|
+
audit: {
|
|
1616
|
+
namedExports: ["ResourceTypeSchema", "ActionTypeSchema", "AuditLogSchema", "type AuditLogType"]
|
|
1617
|
+
},
|
|
1618
|
+
captcha: {
|
|
1619
|
+
namedExports: [
|
|
1620
|
+
"CaptchaResponseSchema",
|
|
1621
|
+
"VerifyCaptchaRequestSchema",
|
|
1622
|
+
"CaptchaVerifyResponseSchema",
|
|
1623
|
+
"type CaptchaResponse",
|
|
1624
|
+
"type VerifyCaptchaRequest",
|
|
1625
|
+
"type CaptchaVerifyResponse"
|
|
1626
|
+
]
|
|
1627
|
+
},
|
|
1628
|
+
cart: {
|
|
1629
|
+
namedExports: [
|
|
1630
|
+
"CartItemSchema",
|
|
1631
|
+
"CartSummarySchema",
|
|
1632
|
+
"CartResponseSchema",
|
|
1633
|
+
"AddCartItemSchema",
|
|
1634
|
+
"CartItemIdSchema",
|
|
1635
|
+
"type CartItem",
|
|
1636
|
+
"type CartSummary",
|
|
1637
|
+
"type CartResponse",
|
|
1638
|
+
"type AddCartItemInput"
|
|
1639
|
+
]
|
|
1640
|
+
},
|
|
1641
|
+
community: {
|
|
1642
|
+
namedExports: [
|
|
1643
|
+
"TopicStatusSchema",
|
|
1644
|
+
"TopicTagSchema",
|
|
1645
|
+
"TopicAuthorSchema",
|
|
1646
|
+
"TopicSchema",
|
|
1647
|
+
"TopicsResponseSchema",
|
|
1648
|
+
"ProfileStatsSchema",
|
|
1649
|
+
"ActivityTypeSchema",
|
|
1650
|
+
"ProfileActivitySchema",
|
|
1651
|
+
"ProfileResponseSchema",
|
|
1652
|
+
"type TopicStatus",
|
|
1653
|
+
"type TopicTag",
|
|
1654
|
+
"type TopicAuthor",
|
|
1655
|
+
"type Topic",
|
|
1656
|
+
"type ProfileStats",
|
|
1657
|
+
"type ActivityType",
|
|
1658
|
+
"type ProfileActivity",
|
|
1659
|
+
"type ProfileResponse"
|
|
1660
|
+
]
|
|
1661
|
+
},
|
|
1662
|
+
content: {
|
|
1663
|
+
namedExports: [
|
|
1664
|
+
"ContentCategorySchema",
|
|
1665
|
+
"ContentStatusSchema",
|
|
1666
|
+
"ContentSchema",
|
|
1667
|
+
"CreateContentSchema",
|
|
1668
|
+
"UpdateContentSchema",
|
|
1669
|
+
"ContentListSchema",
|
|
1670
|
+
"DeleteResultSchema",
|
|
1671
|
+
"type ContentCategory",
|
|
1672
|
+
"type ContentStatus",
|
|
1673
|
+
"type Content",
|
|
1674
|
+
"type CreateContentInput",
|
|
1675
|
+
"type UpdateContentInput",
|
|
1676
|
+
"type DeleteResult"
|
|
1677
|
+
]
|
|
1678
|
+
},
|
|
1679
|
+
dashboard: {
|
|
1680
|
+
namedExports: [
|
|
1681
|
+
"DashboardStatSchema",
|
|
1682
|
+
"RevenueDataSchema",
|
|
1683
|
+
"ActivityStatusSchema",
|
|
1684
|
+
"ActivitySchema",
|
|
1685
|
+
"DashboardResponseSchema",
|
|
1686
|
+
"type DashboardStat",
|
|
1687
|
+
"type RevenueData",
|
|
1688
|
+
"type ActivityStatus",
|
|
1689
|
+
"type Activity",
|
|
1690
|
+
"type DashboardResponse"
|
|
1691
|
+
]
|
|
1692
|
+
},
|
|
1693
|
+
dispute: {
|
|
1694
|
+
namedExports: [
|
|
1695
|
+
"DisputeTypeSchema",
|
|
1696
|
+
"DisputeStatusSchema",
|
|
1697
|
+
"DisputeSchema",
|
|
1698
|
+
"CreateDisputeSchema",
|
|
1699
|
+
"UpdateDisputeSchema",
|
|
1700
|
+
"ResolveDisputeSchema",
|
|
1701
|
+
"DisputeListSchema",
|
|
1702
|
+
"DeleteResultSchema",
|
|
1703
|
+
"type DisputeType",
|
|
1704
|
+
"type DisputeStatus",
|
|
1705
|
+
"type Dispute",
|
|
1706
|
+
"type CreateDisputeInput",
|
|
1707
|
+
"type UpdateDisputeInput",
|
|
1708
|
+
"type ResolveDisputeInput",
|
|
1709
|
+
"type DeleteResult"
|
|
1710
|
+
]
|
|
1711
|
+
},
|
|
1712
|
+
order: {
|
|
1713
|
+
namedExports: [
|
|
1714
|
+
"OrderStatusSchema",
|
|
1715
|
+
"OrderSchema",
|
|
1716
|
+
"CreateOrderSchema",
|
|
1717
|
+
"UpdateOrderSchema",
|
|
1718
|
+
"OrderListSchema",
|
|
1719
|
+
"OrderQuerySchema",
|
|
1720
|
+
"DeleteResultSchema",
|
|
1721
|
+
"ProcessOrderSchema",
|
|
1722
|
+
"CancelOrderSchema",
|
|
1723
|
+
"RemoveCartItemResponseSchema",
|
|
1724
|
+
"ECommerceProductSchema",
|
|
1725
|
+
"ECommerceOrderStatusSchema",
|
|
1726
|
+
"ECommerceOrderSchema",
|
|
1727
|
+
"ECommerceOrderListSchema",
|
|
1728
|
+
"type OrderStatus",
|
|
1729
|
+
"type Order",
|
|
1730
|
+
"type CreateOrderInput",
|
|
1731
|
+
"type UpdateOrderInput",
|
|
1732
|
+
"type DeleteResult",
|
|
1733
|
+
"type ProcessOrderInput",
|
|
1734
|
+
"type CancelOrderInput",
|
|
1735
|
+
"type OrderQueryInput",
|
|
1736
|
+
"type RemoveCartItemResponse",
|
|
1737
|
+
"type ECommerceProduct",
|
|
1738
|
+
"type ECommerceOrderStatus",
|
|
1739
|
+
"type ECommerceOrder"
|
|
1740
|
+
]
|
|
1741
|
+
},
|
|
1742
|
+
permission: {
|
|
1743
|
+
namedExports: [
|
|
1744
|
+
"RoleEnum",
|
|
1745
|
+
"PermissionEnum",
|
|
1746
|
+
"RoleInfoSchema",
|
|
1747
|
+
"PermissionInfoSchema",
|
|
1748
|
+
"UserPermissionsSchema",
|
|
1749
|
+
"MenuItemSchema",
|
|
1750
|
+
"PageActionSchema",
|
|
1751
|
+
"PagePermissionConfigSchema",
|
|
1752
|
+
"PermissionCategorySchema",
|
|
1753
|
+
"RoleListSchema",
|
|
1754
|
+
"PermissionListSchema",
|
|
1755
|
+
"MenuConfigSchema",
|
|
1756
|
+
"PagePermissionsSchema",
|
|
1757
|
+
"PermissionCategoriesSchema",
|
|
1758
|
+
"RoleLabelsSchema",
|
|
1759
|
+
"PermissionLabelsSchema",
|
|
1760
|
+
"PermissionInitSchema",
|
|
1761
|
+
"type RoleType",
|
|
1762
|
+
"type PermissionType",
|
|
1763
|
+
"type RoleInfo",
|
|
1764
|
+
"type PermissionInfo",
|
|
1765
|
+
"type UserPermissions",
|
|
1766
|
+
"type MenuItem",
|
|
1767
|
+
"type PageAction",
|
|
1768
|
+
"type PagePermissionConfig",
|
|
1769
|
+
"type PermissionCategory",
|
|
1770
|
+
"type PermissionInit",
|
|
1771
|
+
"Role",
|
|
1772
|
+
"Permission",
|
|
1773
|
+
"ROLE_PERMISSIONS",
|
|
1774
|
+
"ROLE_LABELS",
|
|
1775
|
+
"PERMISSION_LABELS",
|
|
1776
|
+
"PERMISSION_CATEGORIES",
|
|
1777
|
+
"getPermissionsByRole",
|
|
1778
|
+
"hasPermission",
|
|
1779
|
+
"hasAnyPermission",
|
|
1780
|
+
"hasAllPermissions"
|
|
1781
|
+
]
|
|
1782
|
+
},
|
|
1783
|
+
role: {
|
|
1784
|
+
namedExports: [
|
|
1785
|
+
"RoleSchema",
|
|
1786
|
+
"CreateRoleSchema",
|
|
1787
|
+
"UpdateRoleSchema",
|
|
1788
|
+
"UpdateRolePermissionsSchema",
|
|
1789
|
+
"SuccessSchema",
|
|
1790
|
+
"type RoleType",
|
|
1791
|
+
"type CreateRoleType",
|
|
1792
|
+
"type UpdateRoleType"
|
|
1793
|
+
]
|
|
1794
|
+
},
|
|
1795
|
+
ticket: {
|
|
1796
|
+
namedExports: [
|
|
1797
|
+
"TicketStatusSchema",
|
|
1798
|
+
"TicketPrioritySchema",
|
|
1799
|
+
"TicketCategorySchema",
|
|
1800
|
+
"TicketReplySchema",
|
|
1801
|
+
"TicketSchema",
|
|
1802
|
+
"CreateTicketSchema",
|
|
1803
|
+
"UpdateTicketSchema",
|
|
1804
|
+
"ReplyTicketSchema",
|
|
1805
|
+
"TicketListSchema",
|
|
1806
|
+
"DeleteResultSchema",
|
|
1807
|
+
"type TicketStatus",
|
|
1808
|
+
"type TicketPriority",
|
|
1809
|
+
"type TicketCategory",
|
|
1810
|
+
"type TicketReply",
|
|
1811
|
+
"type Ticket",
|
|
1812
|
+
"type CreateTicketInput",
|
|
1813
|
+
"type UpdateTicketInput",
|
|
1814
|
+
"type ReplyTicketInput",
|
|
1815
|
+
"type DeleteResult"
|
|
1816
|
+
]
|
|
1716
1817
|
}
|
|
1717
1818
|
};
|
|
1819
|
+
var ADDITIONAL_PATHS_MAP = {
|
|
1820
|
+
permission: ["role", "audit"]
|
|
1821
|
+
};
|
|
1822
|
+
var STANDALONE_SHARED_MODULES = /* @__PURE__ */ new Set(["cart", "community", "dashboard"]);
|
|
1823
|
+
var moduleOrder = [
|
|
1824
|
+
"chat",
|
|
1825
|
+
"file",
|
|
1826
|
+
"todos",
|
|
1827
|
+
"notifications",
|
|
1828
|
+
"auth",
|
|
1829
|
+
"plugin",
|
|
1830
|
+
"admin",
|
|
1831
|
+
"audit",
|
|
1832
|
+
"captcha",
|
|
1833
|
+
"cart",
|
|
1834
|
+
"community",
|
|
1835
|
+
"content",
|
|
1836
|
+
"dashboard",
|
|
1837
|
+
"dispute",
|
|
1838
|
+
"order",
|
|
1839
|
+
"permission",
|
|
1840
|
+
"role",
|
|
1841
|
+
"ticket"
|
|
1842
|
+
];
|
|
1718
1843
|
function generateSharedSchemasIndex(resolved) {
|
|
1719
1844
|
const header = `// Re-export interfaces from implementation files
|
|
1720
1845
|
export type { WSClient, WSProtocol, WSStatus } from '../core/ws-client'
|
|
@@ -1740,25 +1865,49 @@ export {
|
|
|
1740
1865
|
// Re-export modules
|
|
1741
1866
|
`;
|
|
1742
1867
|
const moduleLines = [];
|
|
1743
|
-
const
|
|
1868
|
+
const exportedNames = /* @__PURE__ */ new Set();
|
|
1744
1869
|
for (const moduleName of moduleOrder) {
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
const
|
|
1870
|
+
const exports = MODULE_EXPORTS2[moduleName];
|
|
1871
|
+
if (!exports) continue;
|
|
1872
|
+
const shouldInclude = shouldIncludeModule(moduleName, resolved);
|
|
1873
|
+
if (!shouldInclude) continue;
|
|
1874
|
+
const uniqueExports = exports.namedExports.filter((name) => {
|
|
1875
|
+
if (exportedNames.has(name)) return false;
|
|
1876
|
+
exportedNames.add(name);
|
|
1877
|
+
return true;
|
|
1878
|
+
});
|
|
1879
|
+
if (uniqueExports.length === 0) continue;
|
|
1880
|
+
const importPath = getImportPath(moduleName, resolved);
|
|
1750
1881
|
moduleLines.push(
|
|
1751
1882
|
`export {
|
|
1752
|
-
${
|
|
1883
|
+
${uniqueExports.join(",\n ")},
|
|
1753
1884
|
} from '../modules/${importPath}'`
|
|
1754
1885
|
);
|
|
1755
1886
|
}
|
|
1756
1887
|
return header + moduleLines.join("\n") + "\n";
|
|
1757
1888
|
}
|
|
1889
|
+
function shouldIncludeModule(moduleName, resolved) {
|
|
1890
|
+
if (resolved.modules.has(moduleName)) return true;
|
|
1891
|
+
if (STANDALONE_SHARED_MODULES.has(moduleName)) return true;
|
|
1892
|
+
for (const [parentModule, additionalPaths] of Object.entries(ADDITIONAL_PATHS_MAP)) {
|
|
1893
|
+
if (additionalPaths.includes(moduleName) && resolved.modules.has(parentModule)) {
|
|
1894
|
+
return true;
|
|
1895
|
+
}
|
|
1896
|
+
}
|
|
1897
|
+
return false;
|
|
1898
|
+
}
|
|
1899
|
+
function getImportPath(moduleName, resolved) {
|
|
1900
|
+
const manifest = resolved.modules.get(moduleName);
|
|
1901
|
+
if (manifest?.sharedSchemas?.path) {
|
|
1902
|
+
return manifest.sharedSchemas.path;
|
|
1903
|
+
}
|
|
1904
|
+
return moduleName;
|
|
1905
|
+
}
|
|
1758
1906
|
|
|
1759
1907
|
// src/generators/middleware-index.ts
|
|
1760
1908
|
function generateMiddlewareIndex(resolved) {
|
|
1761
1909
|
const lines = [];
|
|
1910
|
+
const hasAuthOrPermission = resolved.modules.has("auth") || resolved.hasPermission;
|
|
1762
1911
|
lines.push(`export { corsMiddleware, createCorsMiddleware, type CorsOptions } from './cors'`);
|
|
1763
1912
|
lines.push(
|
|
1764
1913
|
`export { loggerMiddleware, createLoggerMiddleware, type LoggerOptions } from './logger'`
|
|
@@ -1770,7 +1919,7 @@ function generateMiddlewareIndex(resolved) {
|
|
|
1770
1919
|
type ErrorHandlerOptions,
|
|
1771
1920
|
} from './error-handler'`
|
|
1772
1921
|
);
|
|
1773
|
-
if (
|
|
1922
|
+
if (hasAuthOrPermission) {
|
|
1774
1923
|
lines.push(
|
|
1775
1924
|
`export {
|
|
1776
1925
|
authMiddleware,
|
|
@@ -1796,14 +1945,101 @@ function generateMiddlewareIndex(resolved) {
|
|
|
1796
1945
|
lines.push(`export { permissionMiddleware } from './permission'`);
|
|
1797
1946
|
}
|
|
1798
1947
|
lines.push(`export { rateLimitMiddleware, type RateLimitOptions } from './rate-limit'`);
|
|
1799
|
-
if (
|
|
1948
|
+
if (hasAuthOrPermission) {
|
|
1800
1949
|
lines.push(`export { getAuthUser } from '../utils/auth'`);
|
|
1801
1950
|
}
|
|
1802
1951
|
return lines.join("\n") + "\n";
|
|
1803
1952
|
}
|
|
1804
1953
|
|
|
1805
1954
|
// src/generators/auth-middleware.ts
|
|
1806
|
-
function generateAuthMiddleware(
|
|
1955
|
+
function generateAuthMiddleware(resolved) {
|
|
1956
|
+
if (resolved.modules.has("auth") && !resolved.hasPermission) {
|
|
1957
|
+
return generateSimplifiedAuthMiddleware();
|
|
1958
|
+
}
|
|
1959
|
+
return generateNoopAuthMiddleware();
|
|
1960
|
+
}
|
|
1961
|
+
function generateSimplifiedAuthMiddleware() {
|
|
1962
|
+
return `import type { MiddlewareHandler } from 'hono'
|
|
1963
|
+
import { createModuleLoggerSync } from '../utils/logger'
|
|
1964
|
+
|
|
1965
|
+
export type UserRole = 'user' | 'admin'
|
|
1966
|
+
|
|
1967
|
+
export interface AuthUser {
|
|
1968
|
+
id: string
|
|
1969
|
+
username: string
|
|
1970
|
+
email: string
|
|
1971
|
+
role: UserRole
|
|
1972
|
+
avatar?: string
|
|
1973
|
+
}
|
|
1974
|
+
|
|
1975
|
+
export interface AuthMiddlewareOptions {
|
|
1976
|
+
requiredRole?: UserRole
|
|
1977
|
+
}
|
|
1978
|
+
|
|
1979
|
+
declare module 'hono' {
|
|
1980
|
+
interface ContextVariableMap {
|
|
1981
|
+
authUser: AuthUser
|
|
1982
|
+
}
|
|
1983
|
+
}
|
|
1984
|
+
|
|
1985
|
+
function extractToken(authHeader: string | undefined): string | null {
|
|
1986
|
+
if (!authHeader) return null
|
|
1987
|
+
if (!authHeader.startsWith('Bearer ')) return null
|
|
1988
|
+
return authHeader.slice(7)
|
|
1989
|
+
}
|
|
1990
|
+
|
|
1991
|
+
export function authMiddleware(_options: AuthMiddlewareOptions = {}): MiddlewareHandler {
|
|
1992
|
+
const log = createModuleLoggerSync('auth')
|
|
1993
|
+
|
|
1994
|
+
return async (c, next) => {
|
|
1995
|
+
const token = extractToken(c.req.header('Authorization'))
|
|
1996
|
+
|
|
1997
|
+
if (!token) {
|
|
1998
|
+
log.warn({ path: c.req.path, method: c.req.method }, 'Missing auth token')
|
|
1999
|
+
return c.json({ success: false, error: 'Authentication required', status: 401 }, 401)
|
|
2000
|
+
}
|
|
2001
|
+
|
|
2002
|
+
try {
|
|
2003
|
+
const jwt = await import('jsonwebtoken')
|
|
2004
|
+
const secretKey = process.env.AUTH_SECRET_KEY || 'dev-secret-key-change-in-production'
|
|
2005
|
+
const decoded = jwt.verify(token, secretKey) as {
|
|
2006
|
+
userId: string
|
|
2007
|
+
username: string
|
|
2008
|
+
email: string
|
|
2009
|
+
role: string
|
|
2010
|
+
}
|
|
2011
|
+
|
|
2012
|
+
const user: AuthUser = {
|
|
2013
|
+
id: decoded.userId,
|
|
2014
|
+
username: decoded.username,
|
|
2015
|
+
email: decoded.email,
|
|
2016
|
+
role: decoded.role as UserRole,
|
|
2017
|
+
}
|
|
2018
|
+
|
|
2019
|
+
c.set('authUser', user)
|
|
2020
|
+
log.info({ userId: user.id, path: c.req.path }, 'User authenticated')
|
|
2021
|
+
await next()
|
|
2022
|
+
} catch {
|
|
2023
|
+
log.warn({ path: c.req.path, method: c.req.method }, 'Invalid auth token')
|
|
2024
|
+
return c.json({ success: false, error: 'Invalid or expired token', status: 401 }, 401)
|
|
2025
|
+
}
|
|
2026
|
+
}
|
|
2027
|
+
}
|
|
2028
|
+
|
|
2029
|
+
export function requireSuperAdminMiddleware(): MiddlewareHandler {
|
|
2030
|
+
return authMiddleware()
|
|
2031
|
+
}
|
|
2032
|
+
|
|
2033
|
+
export function requireCustomerServiceMiddleware(): MiddlewareHandler {
|
|
2034
|
+
return authMiddleware()
|
|
2035
|
+
}
|
|
2036
|
+
|
|
2037
|
+
export function requirePermissionsMiddleware(): MiddlewareHandler {
|
|
2038
|
+
return authMiddleware()
|
|
2039
|
+
}
|
|
2040
|
+
`;
|
|
2041
|
+
}
|
|
2042
|
+
function generateNoopAuthMiddleware() {
|
|
1807
2043
|
return `import type { MiddlewareHandler } from 'hono'
|
|
1808
2044
|
import { createModuleLoggerSync } from '../utils/logger'
|
|
1809
2045
|
|
|
@@ -1876,13 +2112,31 @@ export function requirePermissionsMiddleware(): MiddlewareHandler {
|
|
|
1876
2112
|
|
|
1877
2113
|
// src/generators/auth-utils.ts
|
|
1878
2114
|
function generateAuthUtils(resolved) {
|
|
1879
|
-
|
|
2115
|
+
const hasAuthOrPermission = resolved.modules.has("auth") || resolved.hasPermission;
|
|
2116
|
+
if (!hasAuthOrPermission) {
|
|
1880
2117
|
return `import type { Context } from 'hono'
|
|
1881
2118
|
import type { AuthUser } from '../middleware/auth'
|
|
1882
2119
|
|
|
1883
2120
|
export function getAuthUser(c: Context): AuthUser {
|
|
1884
2121
|
return c.get('authUser')
|
|
1885
2122
|
}
|
|
2123
|
+
`;
|
|
2124
|
+
}
|
|
2125
|
+
if (resolved.modules.has("auth") && !resolved.hasPermission) {
|
|
2126
|
+
return `import type { Context } from 'hono'
|
|
2127
|
+
import type { AuthUser } from '../middleware/auth'
|
|
2128
|
+
|
|
2129
|
+
export function getAuthUser(c: Context): AuthUser {
|
|
2130
|
+
return c.get('authUser')
|
|
2131
|
+
}
|
|
2132
|
+
|
|
2133
|
+
export function getOptionalAuthUser(c: Context): AuthUser | null {
|
|
2134
|
+
try {
|
|
2135
|
+
return c.get('authUser')
|
|
2136
|
+
} catch {
|
|
2137
|
+
return null
|
|
2138
|
+
}
|
|
2139
|
+
}
|
|
1886
2140
|
`;
|
|
1887
2141
|
}
|
|
1888
2142
|
return `import type { Context } from 'hono'
|
|
@@ -1975,7 +2229,7 @@ function generateClientComponentsIndex(resolved) {
|
|
|
1975
2229
|
if (resolved.modules.has("chat")) {
|
|
1976
2230
|
lines.push(`export { MessageCard } from './MessageCard'`);
|
|
1977
2231
|
}
|
|
1978
|
-
if (resolved.modules.has("admin")) {
|
|
2232
|
+
if (resolved.modules.has("admin") || resolved.modules.has("auth")) {
|
|
1979
2233
|
lines.push(`export { AuthButton } from './AuthButton'`);
|
|
1980
2234
|
}
|
|
1981
2235
|
return lines.join("\n") + "\n";
|
|
@@ -1987,47 +2241,96 @@ function generateCliModulesIndex(resolved) {
|
|
|
1987
2241
|
const registrations = [];
|
|
1988
2242
|
if (resolved.modules.has("todos")) {
|
|
1989
2243
|
modules.push("import { registerTodoCommands } from './todo'");
|
|
1990
|
-
registrations.push("registerTodoCommands(
|
|
2244
|
+
registrations.push("registerTodoCommands(site)");
|
|
1991
2245
|
}
|
|
1992
2246
|
if (resolved.modules.has("notifications")) {
|
|
1993
|
-
modules.push(
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
2247
|
+
modules.push("import { registerNotificationCommands } from './notification'");
|
|
2248
|
+
registrations.push("registerNotificationCommands(site)");
|
|
2249
|
+
}
|
|
2250
|
+
if (resolved.modules.has("auth")) {
|
|
2251
|
+
modules.push("import { registerAuthCommands } from './auth'");
|
|
2252
|
+
registrations.push("registerAuthCommands(site)");
|
|
2253
|
+
}
|
|
2254
|
+
if (resolved.modules.has("plugin")) {
|
|
2255
|
+
modules.push("import { registerPluginCommands } from './plugin'");
|
|
2256
|
+
registrations.push("registerPluginCommands(site)");
|
|
1997
2257
|
}
|
|
1998
2258
|
modules.push("import { registerConfigCommands } from './config'");
|
|
1999
|
-
registrations.push("registerConfigCommands(
|
|
2000
|
-
const
|
|
2001
|
-
${modules.join("\n")}`;
|
|
2002
|
-
const exports$1 = modules.map((m) => {
|
|
2259
|
+
registrations.push("registerConfigCommands(site)");
|
|
2260
|
+
const exports = modules.map((m) => {
|
|
2003
2261
|
const match = m.match(/\{ (\w+) \}/);
|
|
2004
2262
|
return match ? match[1] : "";
|
|
2005
2263
|
}).filter(Boolean);
|
|
2006
|
-
return
|
|
2264
|
+
return `import type { Core } from '@dyyz1993/xcli-core'
|
|
2265
|
+
${modules.join("\n")}
|
|
2266
|
+
|
|
2267
|
+
/**
|
|
2268
|
+
* Register all builtin CLI commands to xcli-core.
|
|
2269
|
+
* Each register function receives a SiteInstance for command registration.
|
|
2270
|
+
*/
|
|
2271
|
+
export function registerBuiltinCommands(app: Core) {
|
|
2272
|
+
const api = app.loader.getAPI()
|
|
2273
|
+
|
|
2274
|
+
const site = api.createSite({
|
|
2275
|
+
name: 'local-server',
|
|
2276
|
+
url: 'http://localhost:3010',
|
|
2277
|
+
})
|
|
2007
2278
|
|
|
2008
|
-
export function registerModules(program: Command) {
|
|
2009
2279
|
${registrations.map((r) => ` ${r}`).join("\n")}
|
|
2010
2280
|
}
|
|
2011
2281
|
|
|
2012
|
-
export { ${exports
|
|
2282
|
+
export { ${exports.join(", ")} }
|
|
2013
2283
|
`;
|
|
2014
2284
|
}
|
|
2015
2285
|
|
|
2016
2286
|
// src/generators/package-json.ts
|
|
2017
2287
|
var MODULE_PACKAGES = {
|
|
2018
|
-
admin: ["bcryptjs"]
|
|
2288
|
+
admin: ["bcryptjs"],
|
|
2289
|
+
auth: ["bcryptjs"]
|
|
2019
2290
|
};
|
|
2020
2291
|
var ADMIN_PANEL_PACKAGES = ["antd"];
|
|
2021
2292
|
var CLI_PACKAGES = ["commander"];
|
|
2022
2293
|
var UNUSED_PACKAGES = ["lodash-es", "chalk", "mysql2"];
|
|
2294
|
+
var CLIENT_PACKAGES = [
|
|
2295
|
+
"react",
|
|
2296
|
+
"react-dom",
|
|
2297
|
+
"react-helmet-async",
|
|
2298
|
+
"react-router-dom",
|
|
2299
|
+
"lucide-react",
|
|
2300
|
+
"zustand"
|
|
2301
|
+
];
|
|
2302
|
+
var CLIENT_DEV_PACKAGES = [
|
|
2303
|
+
"@vitejs/plugin-react",
|
|
2304
|
+
"@testing-library/react",
|
|
2305
|
+
"@testing-library/jest-dom",
|
|
2306
|
+
"@testing-library/dom",
|
|
2307
|
+
"vite",
|
|
2308
|
+
"jsdom",
|
|
2309
|
+
"tailwindcss",
|
|
2310
|
+
"@tailwindcss/postcss",
|
|
2311
|
+
"postcss",
|
|
2312
|
+
"autoprefixer",
|
|
2313
|
+
"@playwright/test",
|
|
2314
|
+
"playwright",
|
|
2315
|
+
"@prerenderer/renderer-jsdom",
|
|
2316
|
+
"@prerenderer/renderer-puppeteer",
|
|
2317
|
+
"@prerenderer/rollup-plugin",
|
|
2318
|
+
"eventsource"
|
|
2319
|
+
];
|
|
2320
|
+
var CLIENT_TYPE_PACKAGES = ["@types/react", "@types/react-dom"];
|
|
2023
2321
|
function filterPackageJson(pkg, resolved) {
|
|
2024
2322
|
const result = { ...pkg };
|
|
2025
2323
|
const packagesToRemove = new Set(UNUSED_PACKAGES);
|
|
2324
|
+
const packageToModules = {};
|
|
2026
2325
|
for (const [module, packages] of Object.entries(MODULE_PACKAGES)) {
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2326
|
+
for (const pkg2 of packages) {
|
|
2327
|
+
if (!packageToModules[pkg2]) packageToModules[pkg2] = [];
|
|
2328
|
+
packageToModules[pkg2].push(module);
|
|
2329
|
+
}
|
|
2330
|
+
}
|
|
2331
|
+
for (const [pkg2, modules] of Object.entries(packageToModules)) {
|
|
2332
|
+
if (!modules.some((m) => resolved.modules.has(m))) {
|
|
2333
|
+
packagesToRemove.add(pkg2);
|
|
2031
2334
|
}
|
|
2032
2335
|
}
|
|
2033
2336
|
if (!resolved.modules.has("admin")) {
|
|
@@ -2035,8 +2338,11 @@ function filterPackageJson(pkg, resolved) {
|
|
|
2035
2338
|
packagesToRemove.add(pkg2);
|
|
2036
2339
|
}
|
|
2037
2340
|
}
|
|
2038
|
-
|
|
2039
|
-
|
|
2341
|
+
for (const pkg2 of CLI_PACKAGES) {
|
|
2342
|
+
packagesToRemove.add(pkg2);
|
|
2343
|
+
}
|
|
2344
|
+
if (!resolved.hasClient) {
|
|
2345
|
+
for (const pkg2 of CLIENT_PACKAGES) {
|
|
2040
2346
|
packagesToRemove.add(pkg2);
|
|
2041
2347
|
}
|
|
2042
2348
|
}
|
|
@@ -2052,8 +2358,37 @@ function filterPackageJson(pkg, resolved) {
|
|
|
2052
2358
|
if (!resolved.modules.has("admin")) {
|
|
2053
2359
|
delete devDeps["@testing-library/user-event"];
|
|
2054
2360
|
}
|
|
2361
|
+
if (!resolved.hasClient) {
|
|
2362
|
+
for (const pkg2 of CLIENT_DEV_PACKAGES) {
|
|
2363
|
+
delete devDeps[pkg2];
|
|
2364
|
+
}
|
|
2365
|
+
for (const pkg2 of CLIENT_TYPE_PACKAGES) {
|
|
2366
|
+
delete devDeps[pkg2];
|
|
2367
|
+
}
|
|
2368
|
+
}
|
|
2055
2369
|
result.devDependencies = devDeps;
|
|
2056
2370
|
}
|
|
2371
|
+
if (!resolved.hasClient && result.scripts && typeof result.scripts === "object") {
|
|
2372
|
+
const scripts = { ...result.scripts };
|
|
2373
|
+
scripts["dev"] = "NODE_ENV=development node --import tsx src/server/entries/node.ts";
|
|
2374
|
+
scripts["build"] = "npm run build:server && npm run build:cli";
|
|
2375
|
+
scripts["build:all"] = "npm run build:server && npm run build:cli";
|
|
2376
|
+
delete scripts["build:client"];
|
|
2377
|
+
delete scripts["build:cloudflare"];
|
|
2378
|
+
delete scripts["preview"];
|
|
2379
|
+
delete scripts["dev:todo"];
|
|
2380
|
+
delete scripts["dev:plugin"];
|
|
2381
|
+
delete scripts["dev:ecommerce"];
|
|
2382
|
+
delete scripts["dev:community"];
|
|
2383
|
+
delete scripts["dev:saas"];
|
|
2384
|
+
delete scripts["dev:cf"];
|
|
2385
|
+
delete scripts["deploy:cf"];
|
|
2386
|
+
delete scripts["test:e2e"];
|
|
2387
|
+
delete scripts["test:e2e:ui"];
|
|
2388
|
+
delete scripts["test:e2e:debug"];
|
|
2389
|
+
delete scripts["test:full"];
|
|
2390
|
+
result.scripts = scripts;
|
|
2391
|
+
}
|
|
2057
2392
|
return result;
|
|
2058
2393
|
}
|
|
2059
2394
|
function generateViteConfig(resolved, templateDir) {
|
|
@@ -2068,9 +2403,594 @@ function generateViteConfig(resolved, templateDir) {
|
|
|
2068
2403
|
return content;
|
|
2069
2404
|
}
|
|
2070
2405
|
|
|
2406
|
+
// src/generators/client-preset-ui-config.ts
|
|
2407
|
+
function getPresetType(presetId) {
|
|
2408
|
+
const map = {
|
|
2409
|
+
"todo-app": "todo",
|
|
2410
|
+
"xbrowser-marketplace": "plugin",
|
|
2411
|
+
ecommerce: "ecommerce",
|
|
2412
|
+
"fullstack-admin": "saas",
|
|
2413
|
+
minimal: "todo"
|
|
2414
|
+
};
|
|
2415
|
+
return map[presetId] || "todo";
|
|
2416
|
+
}
|
|
2417
|
+
function getThemeForPresetType(presetType) {
|
|
2418
|
+
const themes = {
|
|
2419
|
+
todo: {
|
|
2420
|
+
constName: "TODO_THEME",
|
|
2421
|
+
theme: `{
|
|
2422
|
+
primaryColor: '#6366f1',
|
|
2423
|
+
primaryHover: '#4f46e5',
|
|
2424
|
+
bgColor: '#ffffff',
|
|
2425
|
+
textColor: '#111827',
|
|
2426
|
+
secondaryBg: '#f9fafb',
|
|
2427
|
+
borderColor: '#e5e7eb',
|
|
2428
|
+
borderRadius: '12px',
|
|
2429
|
+
logoText: 'Biomimic',
|
|
2430
|
+
fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",
|
|
2431
|
+
}`
|
|
2432
|
+
},
|
|
2433
|
+
plugin: {
|
|
2434
|
+
constName: "PLUGIN_MARKET_THEME",
|
|
2435
|
+
theme: `{
|
|
2436
|
+
primaryColor: '#3b82f6',
|
|
2437
|
+
primaryHover: '#2563eb',
|
|
2438
|
+
bgColor: '#ffffff',
|
|
2439
|
+
textColor: '#111827',
|
|
2440
|
+
secondaryBg: '#f0f9ff',
|
|
2441
|
+
borderColor: '#bae6fd',
|
|
2442
|
+
borderRadius: '12px',
|
|
2443
|
+
logoText: 'PluginHub',
|
|
2444
|
+
fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",
|
|
2445
|
+
}`
|
|
2446
|
+
},
|
|
2447
|
+
ecommerce: {
|
|
2448
|
+
constName: "ECOMMERCE_THEME",
|
|
2449
|
+
theme: `{
|
|
2450
|
+
primaryColor: '#f59e0b',
|
|
2451
|
+
primaryHover: '#d97706',
|
|
2452
|
+
bgColor: '#ffffff',
|
|
2453
|
+
textColor: '#111827',
|
|
2454
|
+
secondaryBg: '#fffbeb',
|
|
2455
|
+
borderColor: '#fde68a',
|
|
2456
|
+
borderRadius: '12px',
|
|
2457
|
+
logoText: 'ShopMart',
|
|
2458
|
+
fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",
|
|
2459
|
+
}`
|
|
2460
|
+
},
|
|
2461
|
+
saas: {
|
|
2462
|
+
constName: "SAAS_ADMIN_THEME",
|
|
2463
|
+
theme: `{
|
|
2464
|
+
primaryColor: '#1f2937',
|
|
2465
|
+
primaryHover: '#374151',
|
|
2466
|
+
bgColor: '#f9fafb',
|
|
2467
|
+
textColor: '#111827',
|
|
2468
|
+
secondaryBg: '#f3f4f6',
|
|
2469
|
+
borderColor: '#e5e7eb',
|
|
2470
|
+
borderRadius: '8px',
|
|
2471
|
+
logoText: 'AdminPanel',
|
|
2472
|
+
fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",
|
|
2473
|
+
}`
|
|
2474
|
+
}
|
|
2475
|
+
};
|
|
2476
|
+
return themes[presetType] || themes.todo;
|
|
2477
|
+
}
|
|
2478
|
+
function getRoutesForPreset(presetType, resolved) {
|
|
2479
|
+
const hasModule = (m) => resolved.modules.has(m);
|
|
2480
|
+
const loginRoute = {
|
|
2481
|
+
path: "/login",
|
|
2482
|
+
importPath: "./pages/LoginPage",
|
|
2483
|
+
componentName: "LoginPage",
|
|
2484
|
+
label: "Login"
|
|
2485
|
+
};
|
|
2486
|
+
const registerRoute = {
|
|
2487
|
+
path: "/register",
|
|
2488
|
+
importPath: "./pages/RegisterPage",
|
|
2489
|
+
componentName: "RegisterPage",
|
|
2490
|
+
label: "Register"
|
|
2491
|
+
};
|
|
2492
|
+
const maybeAuthRoutes = hasModule("auth") ? [
|
|
2493
|
+
loginRoute,
|
|
2494
|
+
registerRoute,
|
|
2495
|
+
{
|
|
2496
|
+
path: "/profile",
|
|
2497
|
+
importPath: "./pages/ProfilePage",
|
|
2498
|
+
componentName: "ProfilePage",
|
|
2499
|
+
label: "Profile"
|
|
2500
|
+
}
|
|
2501
|
+
] : [];
|
|
2502
|
+
switch (presetType) {
|
|
2503
|
+
case "todo": {
|
|
2504
|
+
const routes = [
|
|
2505
|
+
...maybeAuthRoutes,
|
|
2506
|
+
{
|
|
2507
|
+
path: "/todos",
|
|
2508
|
+
importPath: "./pages/TodoPage",
|
|
2509
|
+
componentName: "TodoPage",
|
|
2510
|
+
label: "Todos"
|
|
2511
|
+
}
|
|
2512
|
+
];
|
|
2513
|
+
if (hasModule("notifications")) {
|
|
2514
|
+
routes.push({
|
|
2515
|
+
path: "/notifications",
|
|
2516
|
+
importPath: "./pages/NotificationPage",
|
|
2517
|
+
componentName: "NotificationPage",
|
|
2518
|
+
label: "Notifications"
|
|
2519
|
+
});
|
|
2520
|
+
}
|
|
2521
|
+
if (hasModule("chat")) {
|
|
2522
|
+
routes.push({
|
|
2523
|
+
path: "/websocket",
|
|
2524
|
+
importPath: "./pages/WebSocketPage",
|
|
2525
|
+
componentName: "WebSocketPage",
|
|
2526
|
+
label: "WebSocket"
|
|
2527
|
+
});
|
|
2528
|
+
}
|
|
2529
|
+
return routes;
|
|
2530
|
+
}
|
|
2531
|
+
case "plugin": {
|
|
2532
|
+
const routes = [];
|
|
2533
|
+
if (hasModule("plugin")) {
|
|
2534
|
+
routes.push(
|
|
2535
|
+
{
|
|
2536
|
+
path: "/",
|
|
2537
|
+
importPath: "./pages/PluginsPage",
|
|
2538
|
+
componentName: "PluginsPage",
|
|
2539
|
+
label: "Home"
|
|
2540
|
+
},
|
|
2541
|
+
{
|
|
2542
|
+
path: "/plugins",
|
|
2543
|
+
importPath: "./pages/PluginsPage",
|
|
2544
|
+
componentName: "PluginsPage",
|
|
2545
|
+
label: "Plugins"
|
|
2546
|
+
},
|
|
2547
|
+
{
|
|
2548
|
+
path: "/plugins/:slug",
|
|
2549
|
+
importPath: "./pages/PluginDetailPage",
|
|
2550
|
+
componentName: "PluginDetailPage",
|
|
2551
|
+
label: "Plugin Detail"
|
|
2552
|
+
},
|
|
2553
|
+
{
|
|
2554
|
+
path: "/categories",
|
|
2555
|
+
importPath: "./pages/CategoriesPage",
|
|
2556
|
+
componentName: "CategoriesPage",
|
|
2557
|
+
label: "Categories"
|
|
2558
|
+
},
|
|
2559
|
+
{
|
|
2560
|
+
path: "/search",
|
|
2561
|
+
importPath: "./pages/SearchPage",
|
|
2562
|
+
componentName: "SearchPage",
|
|
2563
|
+
label: "Search"
|
|
2564
|
+
},
|
|
2565
|
+
{
|
|
2566
|
+
path: "/publish",
|
|
2567
|
+
importPath: "./pages/PublishPage",
|
|
2568
|
+
componentName: "PublishPage",
|
|
2569
|
+
label: "Publish"
|
|
2570
|
+
},
|
|
2571
|
+
{
|
|
2572
|
+
path: "/developer",
|
|
2573
|
+
importPath: "./pages/DeveloperDashboardPage",
|
|
2574
|
+
componentName: "DeveloperDashboardPage",
|
|
2575
|
+
label: "Developer"
|
|
2576
|
+
}
|
|
2577
|
+
);
|
|
2578
|
+
}
|
|
2579
|
+
routes.push(...maybeAuthRoutes);
|
|
2580
|
+
if (hasModule("notifications")) {
|
|
2581
|
+
routes.push({
|
|
2582
|
+
path: "/notifications",
|
|
2583
|
+
importPath: "./pages/NotificationPage",
|
|
2584
|
+
componentName: "NotificationPage",
|
|
2585
|
+
label: "Notifications"
|
|
2586
|
+
});
|
|
2587
|
+
}
|
|
2588
|
+
return routes;
|
|
2589
|
+
}
|
|
2590
|
+
case "ecommerce": {
|
|
2591
|
+
const routes = [];
|
|
2592
|
+
if (hasModule("content")) {
|
|
2593
|
+
routes.push(
|
|
2594
|
+
{
|
|
2595
|
+
path: "/",
|
|
2596
|
+
importPath: "./pages/ContentListPage",
|
|
2597
|
+
componentName: "ContentListPage",
|
|
2598
|
+
label: "Home"
|
|
2599
|
+
},
|
|
2600
|
+
{
|
|
2601
|
+
path: "/products",
|
|
2602
|
+
importPath: "./pages/ContentListPage",
|
|
2603
|
+
componentName: "ContentListPage",
|
|
2604
|
+
label: "Products"
|
|
2605
|
+
},
|
|
2606
|
+
{
|
|
2607
|
+
path: "/products/:id",
|
|
2608
|
+
importPath: "./pages/ContentDetailPage",
|
|
2609
|
+
componentName: "ContentDetailPage",
|
|
2610
|
+
label: "Product Detail"
|
|
2611
|
+
},
|
|
2612
|
+
{
|
|
2613
|
+
path: "/content",
|
|
2614
|
+
importPath: "./pages/ContentListPage",
|
|
2615
|
+
componentName: "ContentListPage",
|
|
2616
|
+
label: "Content"
|
|
2617
|
+
},
|
|
2618
|
+
{
|
|
2619
|
+
path: "/content/:id",
|
|
2620
|
+
importPath: "./pages/ContentDetailPage",
|
|
2621
|
+
componentName: "ContentDetailPage",
|
|
2622
|
+
label: "Content Detail"
|
|
2623
|
+
}
|
|
2624
|
+
);
|
|
2625
|
+
}
|
|
2626
|
+
if (hasModule("order")) {
|
|
2627
|
+
routes.push(
|
|
2628
|
+
{
|
|
2629
|
+
path: "/cart",
|
|
2630
|
+
importPath: "./pages/CartPage",
|
|
2631
|
+
componentName: "CartPage",
|
|
2632
|
+
label: "Cart"
|
|
2633
|
+
},
|
|
2634
|
+
{
|
|
2635
|
+
path: "/orders",
|
|
2636
|
+
importPath: "./pages/OrdersPage",
|
|
2637
|
+
componentName: "OrdersPage",
|
|
2638
|
+
label: "Orders"
|
|
2639
|
+
}
|
|
2640
|
+
);
|
|
2641
|
+
}
|
|
2642
|
+
routes.push(...maybeAuthRoutes);
|
|
2643
|
+
return routes;
|
|
2644
|
+
}
|
|
2645
|
+
case "saas": {
|
|
2646
|
+
const routes = [];
|
|
2647
|
+
if (hasModule("admin")) {
|
|
2648
|
+
routes.push(
|
|
2649
|
+
{
|
|
2650
|
+
path: "/dashboard",
|
|
2651
|
+
importPath: "./pages/DashboardPage",
|
|
2652
|
+
componentName: "DashboardPage",
|
|
2653
|
+
label: "Dashboard"
|
|
2654
|
+
},
|
|
2655
|
+
{
|
|
2656
|
+
path: "/settings",
|
|
2657
|
+
importPath: "./pages/SettingsPage",
|
|
2658
|
+
componentName: "SettingsPage",
|
|
2659
|
+
label: "Settings"
|
|
2660
|
+
}
|
|
2661
|
+
);
|
|
2662
|
+
}
|
|
2663
|
+
routes.push(...maybeAuthRoutes);
|
|
2664
|
+
return routes;
|
|
2665
|
+
}
|
|
2666
|
+
default:
|
|
2667
|
+
return [...maybeAuthRoutes];
|
|
2668
|
+
}
|
|
2669
|
+
}
|
|
2670
|
+
function getNavConfigForPreset(presetType, hasAuth) {
|
|
2671
|
+
switch (presetType) {
|
|
2672
|
+
case "todo":
|
|
2673
|
+
return {
|
|
2674
|
+
name: "Todo App",
|
|
2675
|
+
appType: "client",
|
|
2676
|
+
layout: "top-nav",
|
|
2677
|
+
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' }",
|
|
2678
|
+
desktopNav: [
|
|
2679
|
+
"{ label: 'Todos', icon: 'CheckSquare', path: '/todos' }",
|
|
2680
|
+
"{ label: 'SSE Demo', icon: 'Bell', path: '/notifications' }",
|
|
2681
|
+
"{ label: 'WebSocket', icon: 'Zap', path: '/websocket' }"
|
|
2682
|
+
],
|
|
2683
|
+
mobileTabs: [
|
|
2684
|
+
"{ label: 'Todos', icon: 'CheckSquare', path: '/todos' }",
|
|
2685
|
+
"{ label: 'SSE', icon: 'Bell', path: '/notifications' }",
|
|
2686
|
+
"{ label: 'WS', icon: 'Zap', path: '/websocket' }"
|
|
2687
|
+
],
|
|
2688
|
+
defaultRoute: "/todos"
|
|
2689
|
+
};
|
|
2690
|
+
case "plugin":
|
|
2691
|
+
return {
|
|
2692
|
+
name: "Plugin Market",
|
|
2693
|
+
appType: "client",
|
|
2694
|
+
layout: "top-nav",
|
|
2695
|
+
navigationObj: "{ visible: true, showLogo: true, showSearch: true, showCart: false, authStyle: 'text-link', navItems: 'desktop' }",
|
|
2696
|
+
desktopNav: [
|
|
2697
|
+
"{ label: 'Discover', icon: 'Compass', path: '/plugins' }",
|
|
2698
|
+
"{ label: 'Plugins', icon: 'Puzzle', path: '/plugins/list' }",
|
|
2699
|
+
"{ label: 'Categories', icon: 'Tags', path: '/categories' }",
|
|
2700
|
+
"{ label: 'Search', icon: 'Search', path: '/search' }",
|
|
2701
|
+
"{ label: 'Publish', icon: 'PlusCircle', path: '/publish' }",
|
|
2702
|
+
"{ label: 'Developer', icon: 'Code', path: '/developer' }"
|
|
2703
|
+
],
|
|
2704
|
+
mobileTabs: [
|
|
2705
|
+
"{ label: 'Discover', icon: 'Compass', path: '/plugins' }",
|
|
2706
|
+
"{ label: 'Plugins', icon: 'Puzzle', path: '/plugins/list' }",
|
|
2707
|
+
"{ label: 'Categories', icon: 'Tags', path: '/categories' }",
|
|
2708
|
+
"{ label: 'Search', icon: 'Search', path: '/search' }",
|
|
2709
|
+
"{ label: 'My', icon: 'User', path: '/developer' }"
|
|
2710
|
+
],
|
|
2711
|
+
defaultRoute: "/plugins"
|
|
2712
|
+
};
|
|
2713
|
+
case "ecommerce":
|
|
2714
|
+
return {
|
|
2715
|
+
name: "E-Commerce",
|
|
2716
|
+
appType: "client",
|
|
2717
|
+
layout: "top-nav",
|
|
2718
|
+
navigationObj: "{ visible: true, showLogo: true, showSearch: true, showCart: true, authStyle: 'icon', navItems: 'desktop' }",
|
|
2719
|
+
desktopNav: [
|
|
2720
|
+
"{ label: 'Home', icon: 'Home', path: '/' }",
|
|
2721
|
+
"{ label: 'Products', icon: 'ShoppingBag', path: '/products' }",
|
|
2722
|
+
"{ label: 'Cart', icon: 'ShoppingCart', path: '/cart' }",
|
|
2723
|
+
"{ label: 'Orders', icon: 'Package', path: '/orders' }",
|
|
2724
|
+
"{ label: 'Account', icon: 'User', path: '/content' }"
|
|
2725
|
+
],
|
|
2726
|
+
mobileTabs: [
|
|
2727
|
+
"{ label: 'Home', icon: 'Home', path: '/' }",
|
|
2728
|
+
"{ label: 'Products', icon: 'ShoppingBag', path: '/products' }",
|
|
2729
|
+
"{ label: 'Cart', icon: 'ShoppingCart', path: '/cart' }",
|
|
2730
|
+
"{ label: 'Orders', icon: 'Package', path: '/orders' }",
|
|
2731
|
+
"{ label: 'Me', icon: 'User', path: '/content' }"
|
|
2732
|
+
],
|
|
2733
|
+
defaultRoute: "/"
|
|
2734
|
+
};
|
|
2735
|
+
case "saas":
|
|
2736
|
+
return {
|
|
2737
|
+
name: "SaaS Admin",
|
|
2738
|
+
appType: "admin",
|
|
2739
|
+
layout: "minimal",
|
|
2740
|
+
navigationObj: "{ visible: false, showLogo: false, showSearch: false, showCart: false, authStyle: 'none', navItems: 'none' }",
|
|
2741
|
+
desktopNav: [
|
|
2742
|
+
"{ label: 'Dashboard', icon: 'LayoutDashboard', path: '/dashboard' }",
|
|
2743
|
+
"{ label: 'Settings', icon: 'Settings', path: '/settings' }"
|
|
2744
|
+
],
|
|
2745
|
+
mobileTabs: [
|
|
2746
|
+
"{ label: 'Dashboard', icon: 'LayoutDashboard', path: '/dashboard' }",
|
|
2747
|
+
"{ label: 'Settings', icon: 'Settings', path: '/settings' }"
|
|
2748
|
+
],
|
|
2749
|
+
defaultRoute: "/dashboard"
|
|
2750
|
+
};
|
|
2751
|
+
default:
|
|
2752
|
+
return {
|
|
2753
|
+
name: "App",
|
|
2754
|
+
appType: "client",
|
|
2755
|
+
layout: "top-nav",
|
|
2756
|
+
navigationObj: "{ visible: true, showLogo: true, showSearch: false, showCart: false, authStyle: 'none', navItems: 'desktop' }",
|
|
2757
|
+
desktopNav: ["{ label: 'Home', icon: 'Home', path: '/' }"],
|
|
2758
|
+
mobileTabs: ["{ label: 'Home', icon: 'Home', path: '/' }"],
|
|
2759
|
+
defaultRoute: "/"
|
|
2760
|
+
};
|
|
2761
|
+
}
|
|
2762
|
+
}
|
|
2763
|
+
function filterNavByModules(navItems, resolved) {
|
|
2764
|
+
const hasModule = (path3) => {
|
|
2765
|
+
if (path3 === "/todos") return resolved.modules.has("todos");
|
|
2766
|
+
if (path3 === "/notifications") return resolved.modules.has("notifications");
|
|
2767
|
+
if (path3 === "/websocket") return resolved.modules.has("chat");
|
|
2768
|
+
if (path3.startsWith("/plugins") || path3 === "/categories" || path3 === "/search" || path3 === "/publish" || path3 === "/developer")
|
|
2769
|
+
return resolved.modules.has("plugin");
|
|
2770
|
+
if (path3 === "/cart" || path3 === "/orders") return resolved.modules.has("order");
|
|
2771
|
+
if (path3.startsWith("/content") || path3 === "/products" || path3 === "/")
|
|
2772
|
+
return resolved.modules.has("content");
|
|
2773
|
+
if (path3 === "/topics" || path3 === "/popular" || path3 === "/profile")
|
|
2774
|
+
return resolved.modules.has("content");
|
|
2775
|
+
if (path3 === "/dashboard" || path3 === "/settings") return resolved.modules.has("admin");
|
|
2776
|
+
return true;
|
|
2777
|
+
};
|
|
2778
|
+
return navItems.filter((item) => {
|
|
2779
|
+
const pathMatch = item.match(/path:\s*'([^']+)'/);
|
|
2780
|
+
if (!pathMatch) return true;
|
|
2781
|
+
return hasModule(pathMatch[1]);
|
|
2782
|
+
});
|
|
2783
|
+
}
|
|
2784
|
+
function generatePresetUIConfig(resolved, presetId) {
|
|
2785
|
+
const presetType = getPresetType(presetId);
|
|
2786
|
+
const { constName, theme } = getThemeForPresetType(presetType);
|
|
2787
|
+
const hasAuth = resolved.modules.has("auth");
|
|
2788
|
+
const navConfig = getNavConfigForPreset(presetType, hasAuth);
|
|
2789
|
+
const routes = getRoutesForPreset(presetType, resolved);
|
|
2790
|
+
const desktopNav = filterNavByModules(navConfig.desktopNav, resolved);
|
|
2791
|
+
const mobileTabs = filterNavByModules(navConfig.mobileTabs, resolved);
|
|
2792
|
+
const routeDefs = routes.map((r) => {
|
|
2793
|
+
return ` {
|
|
2794
|
+
path: '${r.path}',
|
|
2795
|
+
component: lazy(() => import('${r.importPath}').then(m => ({ default: m.${r.componentName} }))),
|
|
2796
|
+
label: '${r.label}',
|
|
2797
|
+
}`;
|
|
2798
|
+
});
|
|
2799
|
+
return `import { lazy, type ComponentType } from 'react'
|
|
2800
|
+
|
|
2801
|
+
export type PresetType = '${presetType}'
|
|
2802
|
+
|
|
2803
|
+
export type AppType = 'client' | 'admin'
|
|
2804
|
+
export type LayoutType = 'top-nav' | 'minimal'
|
|
2805
|
+
export type AuthStyle = 'buttons' | 'text-link' | 'icon' | 'avatar' | 'none'
|
|
2806
|
+
|
|
2807
|
+
// ClientNavItem is intentionally simpler than admin MenuItem (no permissions/children needed for client nav)
|
|
2808
|
+
// eslint-disable-next-line local-rules/prefer-shared-types
|
|
2809
|
+
export interface ClientNavItem {
|
|
2810
|
+
label: string
|
|
2811
|
+
icon: string
|
|
2812
|
+
path: string
|
|
2813
|
+
}
|
|
2814
|
+
|
|
2815
|
+
export type TabItem = ClientNavItem
|
|
2816
|
+
|
|
2817
|
+
export interface NavigationConfig {
|
|
2818
|
+
visible: boolean
|
|
2819
|
+
showLogo: boolean
|
|
2820
|
+
showSearch: boolean
|
|
2821
|
+
showCart: boolean
|
|
2822
|
+
authStyle: AuthStyle
|
|
2823
|
+
navItems: 'desktop' | 'none'
|
|
2824
|
+
}
|
|
2825
|
+
|
|
2826
|
+
export interface PresetTheme {
|
|
2827
|
+
primaryColor: string
|
|
2828
|
+
primaryHover: string
|
|
2829
|
+
bgColor: string
|
|
2830
|
+
textColor: string
|
|
2831
|
+
secondaryBg: string
|
|
2832
|
+
borderColor: string
|
|
2833
|
+
borderRadius: string
|
|
2834
|
+
logoText: string
|
|
2835
|
+
fontFamily: string
|
|
2836
|
+
}
|
|
2837
|
+
|
|
2838
|
+
export interface RouteDef {
|
|
2839
|
+
path: string
|
|
2840
|
+
component: ComponentType<Record<string, unknown>> | null
|
|
2841
|
+
label: string
|
|
2842
|
+
}
|
|
2843
|
+
|
|
2844
|
+
export interface PresetUIConfig {
|
|
2845
|
+
id: PresetType
|
|
2846
|
+
name: string
|
|
2847
|
+
appType: AppType
|
|
2848
|
+
layout: LayoutType
|
|
2849
|
+
theme: PresetTheme
|
|
2850
|
+
navigation: NavigationConfig
|
|
2851
|
+
desktopNav: ClientNavItem[]
|
|
2852
|
+
mobileTabs: ClientNavItem[]
|
|
2853
|
+
routes: RouteDef[]
|
|
2854
|
+
defaultRoute: string
|
|
2855
|
+
}
|
|
2856
|
+
|
|
2857
|
+
const ${constName}: PresetTheme = ${theme}
|
|
2858
|
+
|
|
2859
|
+
export const PRESET_UI_CONFIGS: Record<PresetType, PresetUIConfig> = {
|
|
2860
|
+
${presetType}: {
|
|
2861
|
+
id: '${presetType}',
|
|
2862
|
+
name: '${navConfig.name}',
|
|
2863
|
+
appType: '${navConfig.appType}',
|
|
2864
|
+
layout: '${navConfig.layout}',
|
|
2865
|
+
theme: ${constName},
|
|
2866
|
+
navigation: ${navConfig.navigationObj},
|
|
2867
|
+
desktopNav: [
|
|
2868
|
+
${desktopNav.map((i) => ` ${i}`).join(",\n")}
|
|
2869
|
+
],
|
|
2870
|
+
mobileTabs: [
|
|
2871
|
+
${mobileTabs.map((i) => ` ${i}`).join(",\n")}
|
|
2872
|
+
],
|
|
2873
|
+
defaultRoute: '${navConfig.defaultRoute}',
|
|
2874
|
+
routes: [
|
|
2875
|
+
${routeDefs.join(",\n")}
|
|
2876
|
+
],
|
|
2877
|
+
},
|
|
2878
|
+
}
|
|
2879
|
+
|
|
2880
|
+
export function getPresetUIConfig(id: string): PresetUIConfig {
|
|
2881
|
+
return PRESET_UI_CONFIGS[id as PresetType] ?? PRESET_UI_CONFIGS[Object.keys(PRESET_UI_CONFIGS)[0] as PresetType]
|
|
2882
|
+
}
|
|
2883
|
+
|
|
2884
|
+
export function getPresetUIConfigs(): Record<PresetType, PresetUIConfig> {
|
|
2885
|
+
return PRESET_UI_CONFIGS
|
|
2886
|
+
}
|
|
2887
|
+
`;
|
|
2888
|
+
}
|
|
2889
|
+
|
|
2890
|
+
// src/generators/client-main.ts
|
|
2891
|
+
function getPresetType2(presetId) {
|
|
2892
|
+
const map = {
|
|
2893
|
+
"todo-app": "todo",
|
|
2894
|
+
"xbrowser-marketplace": "plugin",
|
|
2895
|
+
ecommerce: "ecommerce",
|
|
2896
|
+
"fullstack-admin": "saas",
|
|
2897
|
+
minimal: "todo"
|
|
2898
|
+
};
|
|
2899
|
+
return map[presetId] || "todo";
|
|
2900
|
+
}
|
|
2901
|
+
function generateClientMain(resolved, presetId) {
|
|
2902
|
+
const presetType = getPresetType2(presetId);
|
|
2903
|
+
const isSaas = presetType === "saas";
|
|
2904
|
+
const authTokenBlock = isSaas ? "" : `
|
|
2905
|
+
if (preset !== 'saas') {
|
|
2906
|
+
try {
|
|
2907
|
+
const raw = localStorage.getItem('auth-token')
|
|
2908
|
+
const parsed = raw ? JSON.parse(raw) : null
|
|
2909
|
+
if (!parsed?.state?.token) {
|
|
2910
|
+
localStorage.setItem('auth-token', JSON.stringify({
|
|
2911
|
+
state: {
|
|
2912
|
+
token: 'user-token',
|
|
2913
|
+
isAuthenticated: true,
|
|
2914
|
+
user: { id: 'user-1', username: 'Demo User', role: 'USER' },
|
|
2915
|
+
loading: false,
|
|
2916
|
+
error: null,
|
|
2917
|
+
},
|
|
2918
|
+
version: 0,
|
|
2919
|
+
}))
|
|
2920
|
+
}
|
|
2921
|
+
} catch {
|
|
2922
|
+
localStorage.setItem('auth-token', JSON.stringify({
|
|
2923
|
+
state: { token: 'user-token', isAuthenticated: true, user: { id: 'user-1', username: 'Demo User', role: 'USER' }, loading: false, error: null },
|
|
2924
|
+
version: 0,
|
|
2925
|
+
}))
|
|
2926
|
+
}
|
|
2927
|
+
}
|
|
2928
|
+
`;
|
|
2929
|
+
if (isSaas) {
|
|
2930
|
+
return `import React from 'react'
|
|
2931
|
+
import ReactDOM from 'react-dom/client'
|
|
2932
|
+
import './index.css'
|
|
2933
|
+
|
|
2934
|
+
const AdminApp = React.lazy(() => import('@admin/App').then(m => ({ default: m.App })))
|
|
2935
|
+
|
|
2936
|
+
const RootApp = () => {
|
|
2937
|
+
return (
|
|
2938
|
+
<React.Suspense fallback={<div className="flex items-center justify-center h-screen text-gray-400">Loading...</div>}>
|
|
2939
|
+
<AdminApp basePath="/" />
|
|
2940
|
+
</React.Suspense>
|
|
2941
|
+
)
|
|
2942
|
+
}
|
|
2943
|
+
|
|
2944
|
+
ReactDOM.createRoot(document.getElementById('root')!).render(
|
|
2945
|
+
<React.StrictMode>
|
|
2946
|
+
<RootApp />
|
|
2947
|
+
</React.StrictMode>,
|
|
2948
|
+
)
|
|
2949
|
+
|
|
2950
|
+
if (typeof window !== 'undefined') {
|
|
2951
|
+
requestAnimationFrame(() => {
|
|
2952
|
+
setTimeout(() => {
|
|
2953
|
+
document.dispatchEvent(new CustomEvent('prerender-ready'))
|
|
2954
|
+
}, 100)
|
|
2955
|
+
})
|
|
2956
|
+
}
|
|
2957
|
+
`;
|
|
2958
|
+
}
|
|
2959
|
+
return `import React from 'react'
|
|
2960
|
+
import ReactDOM from 'react-dom/client'
|
|
2961
|
+
import { HelmetProvider } from 'react-helmet-async'
|
|
2962
|
+
import { App as ClientApp } from './App'
|
|
2963
|
+
import './index.css'
|
|
2964
|
+
|
|
2965
|
+
const preset = import.meta.env.VITE_PRESET || '${presetType}'
|
|
2966
|
+
${authTokenBlock}
|
|
2967
|
+
const RootApp = () => {
|
|
2968
|
+
return (
|
|
2969
|
+
<HelmetProvider>
|
|
2970
|
+
<ClientApp presetId={preset} />
|
|
2971
|
+
</HelmetProvider>
|
|
2972
|
+
)
|
|
2973
|
+
}
|
|
2974
|
+
|
|
2975
|
+
ReactDOM.createRoot(document.getElementById('root')!).render(
|
|
2976
|
+
<React.StrictMode>
|
|
2977
|
+
<RootApp />
|
|
2978
|
+
</React.StrictMode>,
|
|
2979
|
+
)
|
|
2980
|
+
|
|
2981
|
+
if (typeof window !== 'undefined') {
|
|
2982
|
+
requestAnimationFrame(() => {
|
|
2983
|
+
setTimeout(() => {
|
|
2984
|
+
document.dispatchEvent(new CustomEvent('prerender-ready'))
|
|
2985
|
+
}, 100)
|
|
2986
|
+
})
|
|
2987
|
+
}
|
|
2988
|
+
`;
|
|
2989
|
+
}
|
|
2990
|
+
|
|
2071
2991
|
// src/commands/create.ts
|
|
2072
2992
|
var __filename$1 = fileURLToPath(import.meta.url);
|
|
2073
|
-
var __dirname$1 =
|
|
2993
|
+
var __dirname$1 = path.dirname(__filename$1);
|
|
2074
2994
|
var TEMPLATE_PROJECT_NAME = "biomimic-todo-app";
|
|
2075
2995
|
var TEMPLATE_DB_NAME = "biomimic-todo-db";
|
|
2076
2996
|
var ScaffoldError = class extends Error {
|
|
@@ -2112,11 +3032,11 @@ function generateDbName(projectName) {
|
|
|
2112
3032
|
return `${sanitized}-db`;
|
|
2113
3033
|
}
|
|
2114
3034
|
async function updateWranglerToml(targetDir, projectName) {
|
|
2115
|
-
const wranglerPath =
|
|
2116
|
-
if (!await
|
|
3035
|
+
const wranglerPath = path.join(targetDir, "wrangler.toml");
|
|
3036
|
+
if (!await fs.pathExists(wranglerPath)) {
|
|
2117
3037
|
return;
|
|
2118
3038
|
}
|
|
2119
|
-
let content = await
|
|
3039
|
+
let content = await fs.readFile(wranglerPath, "utf-8");
|
|
2120
3040
|
const dbName = generateDbName(projectName);
|
|
2121
3041
|
content = content.replace(
|
|
2122
3042
|
new RegExp(`^name = "${TEMPLATE_PROJECT_NAME}"`, "m"),
|
|
@@ -2130,43 +3050,43 @@ async function updateWranglerToml(targetDir, projectName) {
|
|
|
2130
3050
|
/database_id = "[^"]+"/,
|
|
2131
3051
|
`database_id = "" # TODO: Run 'wrangler d1 create ${dbName}' and paste the ID here`
|
|
2132
3052
|
);
|
|
2133
|
-
await
|
|
3053
|
+
await fs.writeFile(wranglerPath, content);
|
|
2134
3054
|
}
|
|
2135
3055
|
async function updatePackageJson(targetDir, projectName, resolved) {
|
|
2136
|
-
const pkgJsonPath =
|
|
2137
|
-
if (!await
|
|
3056
|
+
const pkgJsonPath = path.join(targetDir, "package.json");
|
|
3057
|
+
if (!await fs.pathExists(pkgJsonPath)) {
|
|
2138
3058
|
return;
|
|
2139
3059
|
}
|
|
2140
|
-
let pkgJson = await
|
|
3060
|
+
let pkgJson = await fs.readJson(pkgJsonPath);
|
|
2141
3061
|
pkgJson = filterPackageJson(pkgJson, resolved);
|
|
2142
3062
|
pkgJson.name = projectName;
|
|
2143
3063
|
if (pkgJson.bin) {
|
|
2144
3064
|
delete pkgJson.bin;
|
|
2145
3065
|
}
|
|
2146
|
-
await
|
|
3066
|
+
await fs.writeJson(pkgJsonPath, pkgJson, { spaces: 2 });
|
|
2147
3067
|
}
|
|
2148
3068
|
async function updatePackageLockJson(targetDir, projectName) {
|
|
2149
|
-
const lockFilePath =
|
|
2150
|
-
if (!await
|
|
3069
|
+
const lockFilePath = path.join(targetDir, "package-lock.json");
|
|
3070
|
+
if (!await fs.pathExists(lockFilePath)) {
|
|
2151
3071
|
return;
|
|
2152
3072
|
}
|
|
2153
|
-
const lockFile = await
|
|
3073
|
+
const lockFile = await fs.readJson(lockFilePath);
|
|
2154
3074
|
if (lockFile.name === TEMPLATE_PROJECT_NAME) {
|
|
2155
3075
|
lockFile.name = projectName;
|
|
2156
3076
|
}
|
|
2157
3077
|
if (lockFile.packages?.[""]?.name === TEMPLATE_PROJECT_NAME) {
|
|
2158
3078
|
lockFile.packages[""].name = projectName;
|
|
2159
3079
|
}
|
|
2160
|
-
await
|
|
3080
|
+
await fs.writeJson(lockFilePath, lockFile, { spaces: 2 });
|
|
2161
3081
|
}
|
|
2162
3082
|
async function updateReadme(targetDir, projectName) {
|
|
2163
|
-
const readmePath =
|
|
2164
|
-
if (!await
|
|
3083
|
+
const readmePath = path.join(targetDir, "README.md");
|
|
3084
|
+
if (!await fs.pathExists(readmePath)) {
|
|
2165
3085
|
return;
|
|
2166
3086
|
}
|
|
2167
|
-
let content = await
|
|
3087
|
+
let content = await fs.readFile(readmePath, "utf-8");
|
|
2168
3088
|
content = content.replace(/^# (.+)$/m, `# ${projectName}`);
|
|
2169
|
-
await
|
|
3089
|
+
await fs.writeFile(readmePath, content);
|
|
2170
3090
|
}
|
|
2171
3091
|
async function createProject(projectNameOrOptions, useCurrentDir = false, preset) {
|
|
2172
3092
|
let projectName;
|
|
@@ -2189,19 +3109,19 @@ async function createProject(projectNameOrOptions, useCurrentDir = false, preset
|
|
|
2189
3109
|
if (!currentDir) {
|
|
2190
3110
|
validateProjectName(projectName);
|
|
2191
3111
|
}
|
|
2192
|
-
const templateDir =
|
|
3112
|
+
const templateDir = path.join(__dirname$1, "../../template");
|
|
2193
3113
|
let targetDir;
|
|
2194
3114
|
if (currentDir) {
|
|
2195
3115
|
targetDir = process.cwd();
|
|
2196
|
-
projectName =
|
|
3116
|
+
projectName = path.basename(targetDir);
|
|
2197
3117
|
} else if (outputDir) {
|
|
2198
|
-
targetDir =
|
|
2199
|
-
if (await
|
|
3118
|
+
targetDir = path.resolve(outputDir);
|
|
3119
|
+
if (await fs.pathExists(targetDir)) {
|
|
2200
3120
|
throw new ScaffoldError(`Directory ${outputDir} already exists`);
|
|
2201
3121
|
}
|
|
2202
3122
|
} else {
|
|
2203
|
-
targetDir =
|
|
2204
|
-
if (await
|
|
3123
|
+
targetDir = path.resolve(process.cwd(), projectName);
|
|
3124
|
+
if (await fs.pathExists(targetDir)) {
|
|
2205
3125
|
throw new ScaffoldError(`Directory ${projectName} already exists`);
|
|
2206
3126
|
}
|
|
2207
3127
|
}
|
|
@@ -2228,16 +3148,16 @@ async function createProject(projectNameOrOptions, useCurrentDir = false, preset
|
|
|
2228
3148
|
for (const file of generatedFiles2) {
|
|
2229
3149
|
console.log(` ${chalk.green("\u2713")} ${file}`);
|
|
2230
3150
|
}
|
|
2231
|
-
const gitignorePath2 =
|
|
3151
|
+
const gitignorePath2 = path.join(templateDir, ".gitignore");
|
|
2232
3152
|
let ignorePatterns2 = [];
|
|
2233
|
-
if (await
|
|
2234
|
-
const gitignoreContent = await
|
|
3153
|
+
if (await fs.pathExists(gitignorePath2)) {
|
|
3154
|
+
const gitignoreContent = await fs.readFile(gitignorePath2, "utf-8");
|
|
2235
3155
|
ignorePatterns2 = parseGitignore(gitignoreContent);
|
|
2236
3156
|
}
|
|
2237
3157
|
ignorePatterns2.push("node_modules", ".wrangler");
|
|
2238
3158
|
const excludePatterns2 = getExcludePatterns(resolved, allManifests);
|
|
2239
3159
|
let templateFileCount = 0;
|
|
2240
|
-
const templateFiles = await
|
|
3160
|
+
const templateFiles = await fs.readdir(templateDir, { recursive: true });
|
|
2241
3161
|
for (const file of templateFiles) {
|
|
2242
3162
|
const relative = String(file);
|
|
2243
3163
|
if (!relative) continue;
|
|
@@ -2272,21 +3192,21 @@ async function createProject(projectNameOrOptions, useCurrentDir = false, preset
|
|
|
2272
3192
|
}
|
|
2273
3193
|
if (!currentDir) {
|
|
2274
3194
|
const dirSpinner = ora("Creating project directory...").start();
|
|
2275
|
-
await
|
|
3195
|
+
await fs.ensureDir(targetDir);
|
|
2276
3196
|
dirSpinner.succeed(chalk.green("Project directory created"));
|
|
2277
3197
|
}
|
|
2278
3198
|
const copySpinner = ora("Copying template files...").start();
|
|
2279
|
-
const gitignorePath =
|
|
3199
|
+
const gitignorePath = path.join(templateDir, ".gitignore");
|
|
2280
3200
|
let ignorePatterns = [];
|
|
2281
|
-
if (await
|
|
2282
|
-
const gitignoreContent = await
|
|
3201
|
+
if (await fs.pathExists(gitignorePath)) {
|
|
3202
|
+
const gitignoreContent = await fs.readFile(gitignorePath, "utf-8");
|
|
2283
3203
|
ignorePatterns = parseGitignore(gitignoreContent);
|
|
2284
3204
|
}
|
|
2285
3205
|
ignorePatterns.push("node_modules", ".wrangler");
|
|
2286
3206
|
const excludePatterns = getExcludePatterns(resolved, allManifests);
|
|
2287
|
-
await
|
|
3207
|
+
await fs.copy(templateDir, targetDir, {
|
|
2288
3208
|
filter: (src) => {
|
|
2289
|
-
const relative =
|
|
3209
|
+
const relative = path.relative(templateDir, src);
|
|
2290
3210
|
if (relative === "") return true;
|
|
2291
3211
|
const negated = ignorePatterns.filter((p) => p.startsWith("!"));
|
|
2292
3212
|
const gitIgnored = ignorePatterns.filter((p) => !p.startsWith("!") && relative.startsWith(p));
|
|
@@ -2308,62 +3228,80 @@ async function createProject(projectNameOrOptions, useCurrentDir = false, preset
|
|
|
2308
3228
|
copySpinner.succeed(chalk.green("Template files copied"));
|
|
2309
3229
|
const genSpinner = ora("Generating module-specific files...").start();
|
|
2310
3230
|
const routeRegistryContent = generateRouteRegistry(resolved);
|
|
2311
|
-
await
|
|
3231
|
+
await fs.writeFile(path.join(targetDir, "src/server/route-registry.ts"), routeRegistryContent);
|
|
2312
3232
|
const dbSchemaContent = generateDbSchemaBarrel(resolved);
|
|
2313
|
-
await
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
3233
|
+
await fs.writeFile(path.join(targetDir, "src/server/db/schema/index.ts"), dbSchemaContent);
|
|
3234
|
+
if (resolved.hasClient) {
|
|
3235
|
+
const clientNavContent = generateClientNavigation(resolved);
|
|
3236
|
+
await fs.writeFile(
|
|
3237
|
+
path.join(targetDir, "src/client/components/Navigation.tsx"),
|
|
3238
|
+
clientNavContent
|
|
3239
|
+
);
|
|
3240
|
+
const clientAppTestContent = generateClientAppTest(resolved);
|
|
3241
|
+
await fs.ensureDir(path.join(targetDir, "src/client/components/__tests__"));
|
|
3242
|
+
await fs.writeFile(
|
|
3243
|
+
path.join(targetDir, "src/client/components/__tests__/App.test.tsx"),
|
|
3244
|
+
clientAppTestContent
|
|
3245
|
+
);
|
|
3246
|
+
const clientNavTestContent = generateClientNavigationTest(resolved);
|
|
3247
|
+
await fs.writeFile(
|
|
3248
|
+
path.join(targetDir, "src/client/components/__tests__/Navigation.test.tsx"),
|
|
3249
|
+
clientNavTestContent
|
|
3250
|
+
);
|
|
3251
|
+
const presetUIConfigContent = generatePresetUIConfig(resolved, selectedPreset.id);
|
|
3252
|
+
await fs.writeFile(
|
|
3253
|
+
path.join(targetDir, "src/client/preset-ui-config.ts"),
|
|
3254
|
+
presetUIConfigContent
|
|
3255
|
+
);
|
|
3256
|
+
const clientMainContent = generateClientMain(resolved, selectedPreset.id);
|
|
3257
|
+
await fs.writeFile(path.join(targetDir, "src/client/main.tsx"), clientMainContent);
|
|
3258
|
+
}
|
|
3259
|
+
if (resolved.hasClient && resolved.modules.has("admin")) {
|
|
2322
3260
|
const adminAppContent = generateAdminApp(resolved);
|
|
2323
3261
|
if (adminAppContent) {
|
|
2324
|
-
await
|
|
2325
|
-
await
|
|
3262
|
+
await fs.ensureDir(path.join(targetDir, "src/admin"));
|
|
3263
|
+
await fs.writeFile(path.join(targetDir, "src/admin/App.tsx"), adminAppContent);
|
|
2326
3264
|
}
|
|
2327
3265
|
}
|
|
2328
3266
|
const serverAppContent = generateServerApp(resolved);
|
|
2329
|
-
await
|
|
3267
|
+
await fs.writeFile(path.join(targetDir, "src/server/app.ts"), serverAppContent);
|
|
2330
3268
|
const generatedFiles = getGeneratedFiles(resolved);
|
|
2331
3269
|
if (generatedFiles.includes("src/server/db/init.ts")) {
|
|
2332
3270
|
const dbInitContent = generateDbInit(resolved);
|
|
2333
|
-
await
|
|
3271
|
+
await fs.writeFile(path.join(targetDir, "src/server/db/init.ts"), dbInitContent);
|
|
2334
3272
|
}
|
|
2335
3273
|
const sharedModulesContent = generateSharedModulesIndex(resolved);
|
|
2336
|
-
await
|
|
3274
|
+
await fs.writeFile(path.join(targetDir, "src/shared/modules/index.ts"), sharedModulesContent);
|
|
2337
3275
|
const sharedSchemasContent = generateSharedSchemasIndex(resolved);
|
|
2338
|
-
await
|
|
3276
|
+
await fs.writeFile(path.join(targetDir, "src/shared/schemas/index.ts"), sharedSchemasContent);
|
|
2339
3277
|
const middlewareIndexContent = generateMiddlewareIndex(resolved);
|
|
2340
|
-
await
|
|
2341
|
-
|
|
3278
|
+
await fs.writeFile(
|
|
3279
|
+
path.join(targetDir, "src/server/middleware/index.ts"),
|
|
2342
3280
|
middlewareIndexContent
|
|
2343
3281
|
);
|
|
2344
3282
|
if (generatedFiles.includes("src/server/middleware/auth.ts")) {
|
|
2345
3283
|
const authMiddlewareContent = generateAuthMiddleware(resolved);
|
|
2346
|
-
await
|
|
2347
|
-
|
|
3284
|
+
await fs.writeFile(
|
|
3285
|
+
path.join(targetDir, "src/server/middleware/auth.ts"),
|
|
2348
3286
|
authMiddlewareContent
|
|
2349
3287
|
);
|
|
2350
3288
|
}
|
|
2351
3289
|
if (generatedFiles.includes("src/server/utils/auth.ts")) {
|
|
2352
3290
|
const authUtilsContent = generateAuthUtils(resolved);
|
|
2353
|
-
await
|
|
3291
|
+
await fs.writeFile(path.join(targetDir, "src/server/utils/auth.ts"), authUtilsContent);
|
|
2354
3292
|
}
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
const cliModulesContent = generateCliModulesIndex(resolved);
|
|
2362
|
-
await fs2.writeFile(path2.join(targetDir, "src/cli/modules/index.ts"), cliModulesContent);
|
|
3293
|
+
if (resolved.hasClient) {
|
|
3294
|
+
const clientComponentsContent = generateClientComponentsIndex(resolved);
|
|
3295
|
+
await fs.writeFile(
|
|
3296
|
+
path.join(targetDir, "src/client/components/index.ts"),
|
|
3297
|
+
clientComponentsContent
|
|
3298
|
+
);
|
|
2363
3299
|
}
|
|
2364
|
-
|
|
3300
|
+
const cliModulesContent = generateCliModulesIndex(resolved);
|
|
3301
|
+
await fs.writeFile(path.join(targetDir, "src/cli/modules/index.ts"), cliModulesContent);
|
|
3302
|
+
if (resolved.hasClient && generatedFiles.includes("vite.config.ts")) {
|
|
2365
3303
|
const viteConfigContent = generateViteConfig(resolved, templateDir);
|
|
2366
|
-
await
|
|
3304
|
+
await fs.writeFile(path.join(targetDir, "vite.config.ts"), viteConfigContent);
|
|
2367
3305
|
}
|
|
2368
3306
|
genSpinner.succeed(chalk.green("Module-specific files generated"));
|
|
2369
3307
|
const pkgSpinner = ora("Configuring package.json...").start();
|
|
@@ -2388,14 +3326,24 @@ async function createProject(projectNameOrOptions, useCurrentDir = false, preset
|
|
|
2388
3326
|
console.log(chalk.white(` cd ${projectName}`));
|
|
2389
3327
|
}
|
|
2390
3328
|
console.log(chalk.white(" npm install"));
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
)
|
|
2397
|
-
|
|
2398
|
-
|
|
3329
|
+
if (resolved.hasClient) {
|
|
3330
|
+
console.log(chalk.white(" npm run dev"));
|
|
3331
|
+
console.log("");
|
|
3332
|
+
console.log(chalk.yellow(" \u26A0\uFE0F Cloudflare Setup:"));
|
|
3333
|
+
console.log(
|
|
3334
|
+
chalk.white(` 1. Create D1 database: wrangler d1 create ${generateDbName(projectName)}`)
|
|
3335
|
+
);
|
|
3336
|
+
console.log(chalk.white(" 2. Copy the database ID to wrangler.toml"));
|
|
3337
|
+
console.log(chalk.white(" 3. Deploy: npm run deploy:cf"));
|
|
3338
|
+
} else {
|
|
3339
|
+
console.log(chalk.white(" npm run build"));
|
|
3340
|
+
console.log(chalk.white(" npm start"));
|
|
3341
|
+
console.log("");
|
|
3342
|
+
console.log(chalk.cyan(" CLI usage:"));
|
|
3343
|
+
console.log(chalk.white(" node dist/cli/index.js config status"));
|
|
3344
|
+
console.log(chalk.white(" node dist/cli/index.js todo list"));
|
|
3345
|
+
console.log(chalk.white(" node dist/cli/index.js --help"));
|
|
3346
|
+
}
|
|
2399
3347
|
console.log("");
|
|
2400
3348
|
console.log(chalk.gray(" Happy coding! \u{1F41F}"));
|
|
2401
3349
|
console.log("");
|
|
@@ -2405,37 +3353,66 @@ async function createProject(projectNameOrOptions, useCurrentDir = false, preset
|
|
|
2405
3353
|
}
|
|
2406
3354
|
}
|
|
2407
3355
|
|
|
2408
|
-
// src/
|
|
2409
|
-
var
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
3356
|
+
// src/index.ts
|
|
3357
|
+
var __filename2 = fileURLToPath(import.meta.url);
|
|
3358
|
+
var __dirname2 = path.dirname(__filename2);
|
|
3359
|
+
var rootDir = __dirname2.endsWith(path.join("src")) || __dirname2.endsWith(path.join("dist")) ? path.resolve(__dirname2, "..") : path.resolve(__dirname2, "..", "..");
|
|
3360
|
+
var packageJson = JSON.parse(readFileSync(path.join(rootDir, "package.json"), "utf-8"));
|
|
3361
|
+
var program = new Command();
|
|
3362
|
+
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(
|
|
3363
|
+
"-p, --preset <preset>",
|
|
3364
|
+
"Template preset to use (fullstack-admin, todo-app, cli-only, minimal)"
|
|
3365
|
+
).option("-o, --output-dir <path>", "Output directory (defaults to project name)").option("--dry-run", "Show what would be generated without creating files").action(
|
|
3366
|
+
async (projectName = "my-fullstack-app", options) => {
|
|
3367
|
+
console.log("");
|
|
3368
|
+
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"));
|
|
3369
|
+
console.log(chalk.cyan.bold(" \u2551 Create Fullstack Scaffold App \u2551"));
|
|
3370
|
+
console.log(chalk.cyan.bold(" \u2551 React + Hono + Vite + Zustand + TS \u2551"));
|
|
3371
|
+
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"));
|
|
3372
|
+
console.log("");
|
|
3373
|
+
let preset = options.preset;
|
|
3374
|
+
if (!preset && process.stdin.isTTY) {
|
|
3375
|
+
const templateDir = path.join(__dirname2, "../template");
|
|
3376
|
+
const presets = await loadPresets(templateDir);
|
|
3377
|
+
preset = await select({
|
|
3378
|
+
message: "Choose a template preset:",
|
|
3379
|
+
choices: presets.map((p) => ({
|
|
3380
|
+
value: p.id,
|
|
3381
|
+
name: `${p.name} \u2014 ${p.description}`
|
|
3382
|
+
}))
|
|
3383
|
+
});
|
|
3384
|
+
}
|
|
3385
|
+
if (!preset) {
|
|
3386
|
+
preset = "fullstack-admin";
|
|
3387
|
+
}
|
|
3388
|
+
try {
|
|
3389
|
+
await createProject({
|
|
3390
|
+
projectName,
|
|
3391
|
+
currentDir: options.currentDir ?? false,
|
|
3392
|
+
preset,
|
|
3393
|
+
outputDir: options.outputDir,
|
|
3394
|
+
dryRun: options.dryRun ?? false
|
|
3395
|
+
});
|
|
3396
|
+
} catch (error) {
|
|
3397
|
+
if (error instanceof ScaffoldError) {
|
|
3398
|
+
console.error(chalk.red(` \u2716 ${error.message}`));
|
|
3399
|
+
process.exit(1);
|
|
3400
|
+
}
|
|
3401
|
+
throw error;
|
|
3402
|
+
}
|
|
2429
3403
|
}
|
|
2430
|
-
|
|
2431
|
-
program.
|
|
2432
|
-
const
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
3404
|
+
);
|
|
3405
|
+
program.command("presets").description("List available template presets").action(async () => {
|
|
3406
|
+
const templateDir = path.join(__dirname2, "../template");
|
|
3407
|
+
const presets = await loadPresets(templateDir);
|
|
3408
|
+
console.log(chalk.cyan("\nAvailable presets:\n"));
|
|
3409
|
+
for (const preset of presets) {
|
|
3410
|
+
console.log(` ${chalk.green(preset.id.padEnd(20))} ${preset.name}`);
|
|
3411
|
+
console.log(` ${" ".repeat(20)} ${preset.description}`);
|
|
3412
|
+
console.log(` ${" ".repeat(20)} Modules: ${preset.modules.join(", ")}`);
|
|
3413
|
+
console.log();
|
|
2436
3414
|
}
|
|
2437
3415
|
});
|
|
2438
|
-
registerModules(program);
|
|
2439
3416
|
program.parse();
|
|
2440
3417
|
//# sourceMappingURL=index.js.map
|
|
2441
3418
|
//# sourceMappingURL=index.js.map
|