create-fullstack-scaffold 0.4.25 → 0.5.1
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 +432 -15
- package/dist/cli/index.js.map +1 -1
- package/package.json +10 -13
- package/template/drizzle.config.ts +10 -4
- package/template/eslint-rules/__tests__/no-merged-api-type-export.test.ts +96 -0
- package/template/eslint-rules/no-cross-module-service-import.js +4 -0
- package/template/eslint-rules/no-merged-api-type-export.js +190 -0
- package/template/eslint.config.js +3 -0
- package/template/package.json +9 -6
- package/template/scripts/sync-agent-hooks.mjs +189 -0
- package/template/src/admin/components/__tests__/StatsCard.test.tsx +2 -2
- package/template/src/admin/pages/ContentPage.tsx +1 -1
- package/template/src/admin/pages/DisputesPage.tsx +1 -1
- package/template/src/admin/pages/OrdersPage.tsx +1 -1
- package/template/src/admin/pages/TicketsPage.tsx +1 -1
- package/template/src/admin/pages/__tests__/ContentPage.test.tsx +5 -1
- package/template/src/admin/pages/__tests__/DashboardPage.test.tsx +62 -8
- package/template/src/admin/pages/__tests__/DisputesPage.test.tsx +8 -1
- package/template/src/admin/pages/__tests__/OrdersPage.test.tsx +4 -2
- package/template/src/admin/pages/__tests__/RegisterPage.test.tsx +40 -43
- package/template/src/admin/pages/__tests__/SettingsPage.test.tsx +83 -21
- package/template/src/admin/pages/__tests__/TicketsPage.test.tsx +3 -1
- package/template/src/admin/services/apiClient.ts +2 -3
- package/template/src/cli/modules/content/index.ts +3 -3
- package/template/src/cli/modules/dispute/index.ts +3 -3
- package/template/src/cli/modules/ticket/index.ts +3 -3
- package/template/src/cli/modules/todo/index.ts +1 -1
- package/template/src/cli/rpc/client.ts +3 -4
- package/template/src/cli/rpc/index.ts +1 -1
- package/template/src/client/App.tsx +3 -39
- package/template/src/client/AppRoutes.tsx +53 -0
- package/template/src/client/entry-server.tsx +75 -0
- package/template/src/client/main.tsx +3 -1
- package/template/src/client/pages/SearchPage.tsx +1 -1
- package/template/src/client/services/apiClient.ts +12 -4
- package/template/src/client/stores/__tests__/todoStore.test.ts +12 -3
- package/template/src/client/stores/entry-stores.ts +36 -0
- package/template/src/client/stores/notificationStore.ts +4 -4
- package/template/src/client/stores/todoStore.ts +2 -2
- package/template/src/merchant/pages/DisputesPage.tsx +3 -3
- package/template/src/merchant/pages/OrdersPage.tsx +1 -1
- package/template/src/merchant/pages/ProductsPage.tsx +1 -1
- package/template/src/merchant/pages/SettingsPage.tsx +1 -1
- package/template/src/server/__tests__/integration/isr-full-flow.test.ts +251 -0
- package/template/src/server/__tests__/integration/todos-api.test.ts +7 -4
- package/template/src/server/app.ts +6 -4
- package/template/src/server/core/__tests__/isr-cache-cf.test.ts +96 -0
- package/template/src/server/core/__tests__/isr-cache.test.ts +96 -92
- package/template/src/server/core/__tests__/isr-invalidation.test.ts +29 -4
- package/template/src/server/core/__tests__/isr-registry.test.ts +215 -0
- package/template/src/server/core/__tests__/isr-renderer.test.ts +121 -0
- package/template/src/server/core/isr-cache.ts +61 -18
- package/template/src/server/core/isr-invalidation.ts +6 -12
- package/template/src/server/core/isr-registry.ts +104 -0
- package/template/src/server/core/isr-renderer.ts +75 -0
- package/template/src/server/db/schema/contents.ts +28 -20
- package/template/src/server/db/schema/disputes.ts +30 -22
- package/template/src/server/db/schema/notifications.ts +20 -14
- package/template/src/server/db/schema/orders.ts +23 -16
- package/template/src/server/db/schema/plugins.ts +56 -38
- package/template/src/server/db/schema/products.ts +24 -17
- package/template/src/server/db/schema/tickets.ts +44 -31
- package/template/src/server/db/schema/todos.ts +26 -18
- package/template/src/server/entries/cloudflare.ts +97 -21
- package/template/src/server/entries/node.ts +0 -2
- package/template/src/server/index.ts +0 -1
- package/template/src/server/isr-modules.ts +10 -0
- package/template/src/server/module-admin/__tests__/admin-service.test.ts +36 -12
- package/template/src/server/module-admin/routes/admin-notification-routes.ts +2 -1
- package/template/src/server/module-admin/routes/admin-routes.ts +3 -0
- package/template/src/server/module-admin/routes/dashboard-routes.ts +3 -0
- package/template/src/server/module-admin/services/admin-service.ts +57 -13
- package/template/src/server/module-auth/routes/auth-routes.ts +3 -0
- package/template/src/server/module-auth/routes/profile-routes.ts +3 -0
- package/template/src/server/module-captcha/routes/captcha-routes.ts +3 -0
- package/template/src/server/module-chat/routes/chat-routes.ts +4 -1
- package/template/src/server/module-content/__tests__/content-route.test.ts +2 -1
- package/template/src/server/module-content/__tests__/content-service.test.ts +2 -1
- package/template/src/server/module-content/__tests__/isr.test.ts +138 -0
- package/template/src/server/module-content/isr.ts +63 -0
- package/template/src/server/module-content/routes/content-routes.ts +10 -10
- package/template/src/server/module-content/routes/public-content-routes.ts +8 -4
- package/template/src/server/module-content/routes/topics-routes.ts +3 -0
- package/template/src/server/module-content/services/content-service.ts +23 -10
- package/template/src/server/module-dispute/__tests__/dispute-route.test.ts +2 -1
- package/template/src/server/module-dispute/__tests__/dispute-service.test.ts +2 -1
- package/template/src/server/module-dispute/routes/dispute-routes.ts +11 -10
- package/template/src/server/module-dispute/services/dispute-service.ts +15 -3
- package/template/src/server/module-file/routes/file-routes.ts +3 -0
- package/template/src/server/module-merchant/routes/merchant-routes.ts +3 -0
- package/template/src/server/module-merchant/services/merchant-service.ts +8 -8
- package/template/src/server/module-notifications/routes/notification-routes.ts +5 -1
- package/template/src/server/module-order/__tests__/order-route.test.ts +8 -8
- package/template/src/server/module-order/__tests__/order-service.test.ts +4 -3
- package/template/src/server/module-order/routes/cart-routes.ts +3 -0
- package/template/src/server/module-order/routes/order-routes.ts +9 -4
- package/template/src/server/module-order/routes/orders-mock-routes.ts +3 -0
- package/template/src/server/module-order/services/order-service.ts +25 -13
- package/template/src/server/module-permission/__tests__/audit-log-service.test.ts +464 -0
- package/template/src/server/module-permission/__tests__/role-service.test.ts +348 -0
- package/template/src/server/module-permission/routes/audit-log-routes.ts +3 -0
- package/template/src/server/module-permission/routes/permission-routes.ts +3 -0
- package/template/src/server/module-permission/routes/role-routes.ts +3 -0
- package/template/src/server/module-plugin/routes/plugin-admin-routes.ts +3 -0
- package/template/src/server/module-plugin/routes/plugin-routes.ts +3 -0
- package/template/src/server/module-plugin/services/admin-plugin-service.ts +10 -18
- package/template/src/server/module-plugin/services/admin-stats-service.ts +28 -9
- package/template/src/server/module-plugin/services/plugin-query-service.ts +42 -66
- package/template/src/server/module-tenant/routes/tenant-routes.ts +5 -1
- package/template/src/server/module-ticket/__tests__/ticket-route.test.ts +2 -1
- package/template/src/server/module-ticket/__tests__/ticket-service.test.ts +4 -3
- package/template/src/server/module-ticket/routes/ticket-routes.ts +11 -10
- package/template/src/server/module-ticket/services/ticket-service.ts +16 -5
- package/template/src/server/module-todos/__tests__/isr.test.ts +105 -0
- package/template/src/server/module-todos/__tests__/todo-service.test.ts +12 -9
- package/template/src/server/module-todos/__tests__/todos-route-rpc.test.ts +6 -6
- package/template/src/server/module-todos/isr.ts +41 -0
- package/template/src/server/module-todos/routes/todos-routes.ts +12 -5
- package/template/src/server/module-todos/services/todo-service.ts +31 -10
- package/template/src/server/route-registry.ts +0 -4
- package/template/src/server/rpc-merge.ts +27 -0
- package/template/src/server/rpc-surface.ts +117 -0
- package/template/src/server/rpc-type-canary.ts +80 -0
- package/template/src/server/test-utils/test-client.ts +7 -9
- package/template/src/server/test-utils/test-isr-helper.ts +140 -0
- package/template/src/shared/modules/content/schemas.ts +14 -0
- package/template/src/shared/modules/dispute/schemas.ts +14 -0
- package/template/src/shared/modules/order/schemas.ts +9 -1
- package/template/src/shared/modules/ticket/schemas.ts +14 -0
- package/template/src/shared/modules/todos/index.ts +4 -0
- package/template/src/shared/modules/todos/schemas.ts +14 -0
- package/template/src/shared/schemas/index.ts +18 -0
- package/template/tsup.config.ts +48 -1
- package/template/vitest.config.ts +19 -3
- package/template/vitest.setup.ts +68 -5
- package/template/patches/typescript+5.9.3.patch +0 -24
- /package/template/patches/{hono+4.12.16.patch → hono+4.12.34.patch} +0 -0
package/dist/cli/index.js
CHANGED
|
@@ -149,6 +149,15 @@ function getAdminPages(resolved) {
|
|
|
149
149
|
// src/generators/file-filter.ts
|
|
150
150
|
function getExcludePatterns(resolved, allManifests) {
|
|
151
151
|
const excludes = [];
|
|
152
|
+
if (!resolved.hasClient) {
|
|
153
|
+
excludes.push(
|
|
154
|
+
"playwright.config.ts",
|
|
155
|
+
"src/shared/hooks",
|
|
156
|
+
"src/server/entries/cloudflare.ts",
|
|
157
|
+
"tests/e2e"
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
excludes.push("src/server/rpc-type-canary.ts");
|
|
152
161
|
for (const [name, manifest] of allManifests) {
|
|
153
162
|
if (resolved.modules.has(name)) continue;
|
|
154
163
|
excludes.push(`src/server/module-${name}`);
|
|
@@ -274,6 +283,9 @@ function getExcludePatterns(resolved, allManifests) {
|
|
|
274
283
|
function getGeneratedFiles(resolved) {
|
|
275
284
|
const files = [
|
|
276
285
|
"src/server/route-registry.ts",
|
|
286
|
+
"src/server/rpc-surface.ts",
|
|
287
|
+
"src/server/isr-modules.ts",
|
|
288
|
+
"src/client/stores/entry-stores.ts",
|
|
277
289
|
"src/server/db/schema/index.ts",
|
|
278
290
|
"src/shared/modules/index.ts",
|
|
279
291
|
"src/shared/schemas/index.ts",
|
|
@@ -358,9 +370,11 @@ function generateRouteRegistry(resolved) {
|
|
|
358
370
|
let content = imports.join("\n") + "\n\n";
|
|
359
371
|
content += `const apiRateLimit = rateLimitMiddleware({
|
|
360
372
|
`;
|
|
361
|
-
content += ` windowMs:
|
|
373
|
+
content += ` windowMs: 60_000,
|
|
362
374
|
`;
|
|
363
375
|
content += ` max: 100,
|
|
376
|
+
`;
|
|
377
|
+
content += ` message: 'Too many requests, please try again later',
|
|
364
378
|
`;
|
|
365
379
|
content += `})
|
|
366
380
|
|
|
@@ -393,9 +407,7 @@ function generateRouteRegistry(resolved) {
|
|
|
393
407
|
|
|
394
408
|
`;
|
|
395
409
|
}
|
|
396
|
-
content +=
|
|
397
|
-
`;
|
|
398
|
-
content += `export type AdminApiRoutes = typeof adminApiRoutes
|
|
410
|
+
content += `// \u7C7B\u578B\u51FA\u53E3\u5DF2\u79FB\u9664\uFF1A\u5DE8\u578B merge \u7C7B\u578B\u662F TS2589 \u6839\u6E90\uFF0C\u5BA2\u6237\u7AEF\u6539\u7528 rpc-surface.ts \u95E8\u9762
|
|
399
411
|
`;
|
|
400
412
|
return content;
|
|
401
413
|
}
|
|
@@ -413,6 +425,211 @@ function getStandaloneRoutes(resolved) {
|
|
|
413
425
|
return routes;
|
|
414
426
|
}
|
|
415
427
|
|
|
428
|
+
// src/generators/rpc-surface.ts
|
|
429
|
+
function extractApiTypes(content) {
|
|
430
|
+
const re = /export type (\w+ApiType)\s*=\s*typeof\s+(\w+)/g;
|
|
431
|
+
const names = [];
|
|
432
|
+
let m;
|
|
433
|
+
while ((m = re.exec(content)) !== null) names.push(m[1]);
|
|
434
|
+
return names;
|
|
435
|
+
}
|
|
436
|
+
function extractSegments(content, readRouteFile, baseDir, depth = 0) {
|
|
437
|
+
const segs = /* @__PURE__ */ new Set();
|
|
438
|
+
const pathRe = /path:\s*'\/([a-zA-Z][a-zA-Z-]*)/g;
|
|
439
|
+
let m;
|
|
440
|
+
while ((m = pathRe.exec(content)) !== null) segs.add(m[1]);
|
|
441
|
+
if (depth < 3) {
|
|
442
|
+
const importRe = /import\s*\{([^}]+)\}\s*from\s*'(\.\/[a-zA-Z-]+)'/g;
|
|
443
|
+
while ((m = importRe.exec(content)) !== null) {
|
|
444
|
+
const names = m[1].split(",").map((s) => s.trim().split(" as ")[0]).filter(Boolean);
|
|
445
|
+
const subPath = m[2].replace(/^\.\//, "");
|
|
446
|
+
const isMounted = names.some(
|
|
447
|
+
(n) => new RegExp(`\\.route\\(\\s*'/'\\s*,\\s*${n}\\s*\\)`).test(content)
|
|
448
|
+
);
|
|
449
|
+
if (!isMounted) continue;
|
|
450
|
+
const sub = readRouteFile(`${baseDir}/${subPath}`);
|
|
451
|
+
if (sub) {
|
|
452
|
+
for (const s of extractSegments(sub, readRouteFile, baseDir, depth + 1)) segs.add(s);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
return [...segs];
|
|
457
|
+
}
|
|
458
|
+
function generateRpcSurface(resolved, readRouteFile) {
|
|
459
|
+
const sources = [];
|
|
460
|
+
const usedNames = /* @__PURE__ */ new Set();
|
|
461
|
+
const collect = (moduleName, manifest, key2) => {
|
|
462
|
+
const raw = manifest.routes[key2];
|
|
463
|
+
if (!raw) return;
|
|
464
|
+
const list = Array.isArray(raw) ? raw : [raw];
|
|
465
|
+
for (const entry of list) {
|
|
466
|
+
const routePath = `module-${moduleName}/${entry.importPath.replace(/^\.\//, "").replace(/\.ts$/, "")}`;
|
|
467
|
+
let localName = usedNames.has(entry.exportName) ? `${routePath.split("/").pop().replace(
|
|
468
|
+
/-([a-z])/g,
|
|
469
|
+
(_, c) => c.toUpperCase()
|
|
470
|
+
)}${entry.exportName.charAt(0).toUpperCase()}${entry.exportName.slice(1)}` : entry.exportName;
|
|
471
|
+
while (usedNames.has(localName)) localName += "X";
|
|
472
|
+
usedNames.add(localName);
|
|
473
|
+
const content = readRouteFile(routePath);
|
|
474
|
+
if (!content) continue;
|
|
475
|
+
const typeNames = extractApiTypes(content);
|
|
476
|
+
const segments = extractSegments(
|
|
477
|
+
content,
|
|
478
|
+
readRouteFile,
|
|
479
|
+
routePath.split("/").slice(0, -1).join("/")
|
|
480
|
+
);
|
|
481
|
+
for (const typeName of typeNames) {
|
|
482
|
+
sources.push({ routePath, localName, typeName, segments });
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
};
|
|
486
|
+
for (const [moduleName, manifest] of [...resolved.modules.entries()]) {
|
|
487
|
+
collect(moduleName, manifest, "client");
|
|
488
|
+
collect(moduleName, manifest, "admin");
|
|
489
|
+
}
|
|
490
|
+
const segmentOwners = /* @__PURE__ */ new Map();
|
|
491
|
+
sources.forEach((s, idx) => {
|
|
492
|
+
for (const seg of s.segments) {
|
|
493
|
+
const list = segmentOwners.get(seg) ?? [];
|
|
494
|
+
list.push({ localName: s.localName, typeName: s.typeName, routePath: s.routePath, idx });
|
|
495
|
+
segmentOwners.set(seg, list);
|
|
496
|
+
}
|
|
497
|
+
});
|
|
498
|
+
const hasMergedSegments = [...segmentOwners.values()].some((owners) => owners.length > 1);
|
|
499
|
+
const imports = [
|
|
500
|
+
`import { hc } from 'hono/client'`,
|
|
501
|
+
...hasMergedSegments ? [`import { mergeRpcObjects } from './rpc-merge'`] : []
|
|
502
|
+
];
|
|
503
|
+
for (const s of sources) {
|
|
504
|
+
imports.push(`import type { ${s.typeName} } from './${s.routePath}'`);
|
|
505
|
+
}
|
|
506
|
+
const clients = [];
|
|
507
|
+
sources.forEach((s, i) => {
|
|
508
|
+
clients.push(` const ${s.localName}Client${i} = hc<${s.typeName}>(api, options)`);
|
|
509
|
+
});
|
|
510
|
+
const entries = [];
|
|
511
|
+
const key = (seg) => /^[a-zA-Z][a-zA-Z0-9]*$/.test(seg) ? seg : `'${seg}'`;
|
|
512
|
+
const access = (seg) => /^[a-zA-Z][a-zA-Z0-9]*$/.test(seg) ? `.${seg}` : `['${seg}']`;
|
|
513
|
+
for (const [seg, owners] of segmentOwners) {
|
|
514
|
+
owners.sort((a, b) => a.idx - b.idx);
|
|
515
|
+
const refs = owners.map((o) => `${o.localName}Client${o.idx}${access(seg)}`);
|
|
516
|
+
entries.push(
|
|
517
|
+
refs.length === 1 ? ` ${key(seg)}: ${refs[0]},` : ` ${key(seg)}: mergeRpcObjects(${refs.join(", ")}),`
|
|
518
|
+
);
|
|
519
|
+
}
|
|
520
|
+
return `/**
|
|
521
|
+
* @framework-baseline rpc-surface-v1
|
|
522
|
+
*
|
|
523
|
+
* \u6309\u6A21\u5757\u62C6\u5206\u7684\u7C7B\u578B\u5B89\u5168 RPC \u95E8\u9762\uFF08\u672C\u6587\u4EF6\u7531 CLI \u751F\u6210\uFF0C\u52FF\u624B\u6539\uFF09\u3002
|
|
524
|
+
* \u751F\u6210\u5668: src/generators/rpc-surface.ts
|
|
525
|
+
*
|
|
526
|
+
* \u6BCF\u4E2A\u6A21\u5757\u5355\u72EC\u5B9E\u4F8B\u5316\u7A84\u5BA2\u6237\u7AEF\uFF08\u6DF1\u5EA6 = 1 \u4E2A\u6A21\u5757\uFF09\uFF0C\u7EC4\u88C5\u6210\u4E0E\u65E7
|
|
527
|
+
* \`hc<MergedApiType>\` \u5B8C\u5168\u540C\u5F62\u7684\u95E8\u9762\u3002\u7981\u6B62\u5BFC\u51FA\u94FE\u5F0F merge \u7C7B\u578B
|
|
528
|
+
* \uFF08eslint: no-merged-api-type-export\uFF09\u3002
|
|
529
|
+
*/
|
|
530
|
+
|
|
531
|
+
${imports.join("\n")}
|
|
532
|
+
|
|
533
|
+
/** hono hc \u7684\u9009\u9879\u7C7B\u578B\uFF08fetch / webSocket / sse / headers \u7B49\uFF09 */
|
|
534
|
+
export type RpcClientOptions = NonNullable<Parameters<typeof hc>[1]>
|
|
535
|
+
|
|
536
|
+
/**
|
|
537
|
+
* \u521B\u5EFA\u6309\u6A21\u5757\u62C6\u5206\u7684 RPC \u95E8\u9762\u3002
|
|
538
|
+
* \u8C03\u7528\u5F62\u6001\u4E0E\u65E7 mega \u5BA2\u6237\u7AEF\u517C\u5BB9\uFF1Afacade.api.todos.$get()\u3002
|
|
539
|
+
*/
|
|
540
|
+
export function createApiFacade(baseUrl: string, options: RpcClientOptions = {}) {
|
|
541
|
+
const api = \`\${baseUrl.replace(/\\/$/, '')}/api\`
|
|
542
|
+
|
|
543
|
+
${clients.join("\n")}
|
|
544
|
+
|
|
545
|
+
return {
|
|
546
|
+
api: {
|
|
547
|
+
${entries.join("\n")}
|
|
548
|
+
},
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
export type ApiFacade = ReturnType<typeof createApiFacade>
|
|
553
|
+
`;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
// src/generators/isr-modules.ts
|
|
557
|
+
function generateIsrModules(resolved, moduleFileExists) {
|
|
558
|
+
const imports = [];
|
|
559
|
+
for (const [moduleName] of [...resolved.modules.entries()]) {
|
|
560
|
+
if (moduleFileExists(moduleName, "isr.ts")) {
|
|
561
|
+
imports.push(`import '@server/module-${moduleName}/isr'`);
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
return `/**
|
|
565
|
+
* ISR \u6A21\u5757\u6CE8\u518C\u6C47\u603B\uFF08\u672C\u6587\u4EF6\u7531 CLI \u751F\u6210\uFF0C\u52FF\u624B\u6539\uFF09\u3002
|
|
566
|
+
* \u751F\u6210\u5668: src/generators/isr-modules.ts
|
|
567
|
+
*
|
|
568
|
+
* \u6309 preset \u53EA\u5BFC\u5165\u9009\u4E2D\u6A21\u5757\u7684 ISR \u6CE8\u518C\u526F\u4F5C\u7528\uFF1B\u672A\u88AB\u88C1\u526A\u98CE\u9669\u7684\u5355\u4E00\u5165\u53E3
|
|
569
|
+
* \u7531 entries/cloudflare.ts import \u672C\u6587\u4EF6\u3002
|
|
570
|
+
*/
|
|
571
|
+
|
|
572
|
+
${imports.join("\n")}
|
|
573
|
+
`;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
// src/generators/entry-stores.ts
|
|
577
|
+
function generateEntryStores(resolved, moduleFileExists) {
|
|
578
|
+
const hasTodos = resolved.modules.has("todos") && moduleFileExists("todos", "todoStore.ts");
|
|
579
|
+
if (!hasTodos) {
|
|
580
|
+
return `/**
|
|
581
|
+
* Entry SSR store \u79CD\u5B50\uFF08\u672C\u6587\u4EF6\u7531 CLI \u751F\u6210\uFF0C\u52FF\u624B\u6539\uFF09\u3002
|
|
582
|
+
* \u5F53\u524D preset \u4E0D\u542B todo \u6A21\u5757\uFF1A\u65E0 store \u9700\u8981\u9884\u586B\u5145\uFF0C\u5168\u90E8\u4E3A\u7A7A\u5B9E\u73B0\u3002
|
|
583
|
+
*/
|
|
584
|
+
|
|
585
|
+
export interface SSRData {
|
|
586
|
+
[key: string]: unknown
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
export function snapshotEntryStores(): Record<string, unknown> {
|
|
590
|
+
return {}
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
export function seedEntryStores(_data: SSRData): void {}
|
|
594
|
+
|
|
595
|
+
export function restoreEntryStores(_snapshot: Record<string, unknown>): void {}
|
|
596
|
+
`;
|
|
597
|
+
}
|
|
598
|
+
return `/**
|
|
599
|
+
* Entry SSR store \u79CD\u5B50\uFF08\u672C\u6587\u4EF6\u7531 CLI \u751F\u6210\uFF0C\u52FF\u624B\u6539\uFF09\u3002
|
|
600
|
+
* todo \u6A21\u5757\u5728\u9009\u4E2D\u5217\u8868\u4E2D\uFF1A\u9884\u586B\u5145 todoStore \u4F9B ISR \u6E32\u67D3\u771F\u5B9E\u5185\u5BB9\u3002
|
|
601
|
+
*/
|
|
602
|
+
|
|
603
|
+
import { useTodoStore } from './todoStore'
|
|
604
|
+
import type { Todo } from '@shared/schemas'
|
|
605
|
+
|
|
606
|
+
export interface SSRData {
|
|
607
|
+
todos?: Todo[]
|
|
608
|
+
[key: string]: unknown
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
export interface EntryStoreSnapshot {
|
|
612
|
+
todos: Todo[]
|
|
613
|
+
loading: boolean
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
export function snapshotEntryStores(): EntryStoreSnapshot {
|
|
617
|
+
const state = useTodoStore.getState()
|
|
618
|
+
return { todos: state.todos, loading: state.loading }
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
export function seedEntryStores(data: SSRData): void {
|
|
622
|
+
if (data.todos) {
|
|
623
|
+
useTodoStore.setState({ todos: data.todos, loading: false })
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
export function restoreEntryStores(snapshot: EntryStoreSnapshot): void {
|
|
628
|
+
useTodoStore.setState({ todos: snapshot.todos, loading: snapshot.loading })
|
|
629
|
+
}
|
|
630
|
+
`;
|
|
631
|
+
}
|
|
632
|
+
|
|
416
633
|
// src/generators/client-navigation.ts
|
|
417
634
|
var DEFAULT_ICON = "Circle";
|
|
418
635
|
var ICON_MAP = {
|
|
@@ -589,7 +806,10 @@ describe('App Component', () => {
|
|
|
589
806
|
describe('Initial Render', () => {
|
|
590
807
|
it('should render navigation', () => {
|
|
591
808
|
render(<App />)
|
|
592
|
-
|
|
809
|
+
// \u5E03\u5C40\u611F\u77E5\uFF1Atop-nav \u6709 app-nav\uFF1Bsidebar \u5E03\u5C40\u6CA1\u6709\u9876\u680F\uFF08\u6709 app-container/app-main\uFF09
|
|
810
|
+
expect(
|
|
811
|
+
document.querySelector('[data-testid="app-nav"], [data-testid="app-container"]')
|
|
812
|
+
).toBeTruthy()
|
|
593
813
|
})
|
|
594
814
|
|
|
595
815
|
it('should render main content area', () => {
|
|
@@ -606,7 +826,11 @@ describe('App Component', () => {
|
|
|
606
826
|
describe('Navigation Links', () => {
|
|
607
827
|
it('should render footer', () => {
|
|
608
828
|
render(<App />)
|
|
609
|
-
|
|
829
|
+
// \u9875\u811A\u4EC5 top-nav \u5E03\u5C40\u6E32\u67D3\uFF08Layout: showFooter = layout === 'top-nav'\uFF09
|
|
830
|
+
expect(
|
|
831
|
+
document.querySelector('[data-testid="app-footer"]') ||
|
|
832
|
+
document.querySelector('[data-testid="app-container"]')
|
|
833
|
+
).toBeTruthy()
|
|
610
834
|
})
|
|
611
835
|
})
|
|
612
836
|
})
|
|
@@ -668,7 +892,11 @@ describe('Navigation', () => {
|
|
|
668
892
|
})
|
|
669
893
|
|
|
670
894
|
it('should render nav items', () => {
|
|
671
|
-
renderWithRouter(
|
|
895
|
+
renderWithRouter(
|
|
896
|
+
<Navigation
|
|
897
|
+
items={[{ label: '${firstLabel}', icon: 'CheckSquare', path: '${firstPage.route}' }]}
|
|
898
|
+
/>
|
|
899
|
+
)
|
|
672
900
|
expect(screen.getByText('${firstLabel}')).toBeInTheDocument()
|
|
673
901
|
})
|
|
674
902
|
})
|
|
@@ -771,11 +999,10 @@ function generateAdminApiClient(resolved) {
|
|
|
771
999
|
* @impact Captcha module excluded = no captchaStore import
|
|
772
1000
|
*/
|
|
773
1001
|
|
|
774
|
-
import { hc } from 'hono/client'
|
|
775
1002
|
import { WSClientImpl } from '@shared/core/ws-client'
|
|
776
1003
|
import { SSEClientImpl } from '@shared/core/sse-client'
|
|
777
1004
|
import { createRequestInterceptor } from './requestInterceptor'
|
|
778
|
-
${captchaImport}import
|
|
1005
|
+
${captchaImport}import { createApiFacade } from '@server/rpc-surface'
|
|
779
1006
|
|
|
780
1007
|
const baseUrl = import.meta.env.API_BASE_URL || window.location.origin
|
|
781
1008
|
|
|
@@ -807,7 +1034,7 @@ ${captchaFetchSetup} return createRequestInterceptor({
|
|
|
807
1034
|
${captchaHandler} })
|
|
808
1035
|
}
|
|
809
1036
|
|
|
810
|
-
export const apiClient =
|
|
1037
|
+
export const apiClient = createApiFacade(baseUrl, {
|
|
811
1038
|
fetch: createCustomFetch() as typeof fetch,
|
|
812
1039
|
webSocket: url => new WSClientImpl(url) as unknown as WebSocket,
|
|
813
1040
|
sse: url => {
|
|
@@ -1371,7 +1598,10 @@ export function createApp<T extends AppBindings = AppBindings>(_options: CreateA
|
|
|
1371
1598
|
return c.json({ status: 'ok', timestamp: new Date().toISOString(), db: 'not configured' })
|
|
1372
1599
|
}
|
|
1373
1600
|
})
|
|
1374
|
-
|
|
1601
|
+
// \u6D4B\u8BD5\u8F85\u52A9\u7AEF\u70B9\uFF1A\u4EC5\u5F00\u53D1/\u6D4B\u8BD5\u73AF\u5883\u6CE8\u518C\uFF08Cloudflare \u6784\u5EFA\u65F6 NODE_ENV \u88AB\u66FF\u6362\u4E3A
|
|
1602
|
+
// "production"\uFF0C\u6B64\u7AEF\u70B9\u4E0D\u4F1A\u51FA\u73B0\u5728\u751F\u4EA7 bundle \u4E2D\uFF09
|
|
1603
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
1604
|
+
app.post('/api/__test__/cleanup', async c => {
|
|
1375
1605
|
try {
|
|
1376
1606
|
const { cleanupTestDatabase } = await import('./db/test-setup')
|
|
1377
1607
|
await cleanupTestDatabase()
|
|
@@ -1381,6 +1611,7 @@ export function createApp<T extends AppBindings = AppBindings>(_options: CreateA
|
|
|
1381
1611
|
return c.json({ success: false as const, message: 'Failed to cleanup database' }, 500)
|
|
1382
1612
|
}
|
|
1383
1613
|
})
|
|
1614
|
+
}
|
|
1384
1615
|
|
|
1385
1616
|
autoRegisterRealtime(app as unknown as Parameters<typeof autoRegisterRealtime>[0])
|
|
1386
1617
|
|
|
@@ -1427,9 +1658,6 @@ export function createApp<T extends AppBindings = AppBindings>(_options: CreateA
|
|
|
1427
1658
|
|
|
1428
1659
|
return app
|
|
1429
1660
|
}
|
|
1430
|
-
export type AdminApiType = typeof adminApiRoutes
|
|
1431
|
-
export type ClientApiType = typeof clientApiRoutes
|
|
1432
|
-
export type AppType = ReturnType<typeof createApp>
|
|
1433
1661
|
`;
|
|
1434
1662
|
}
|
|
1435
1663
|
|
|
@@ -1562,6 +1790,39 @@ var MODULE_EXPORTS = {
|
|
|
1562
1790
|
"type PluginListQuery"
|
|
1563
1791
|
]
|
|
1564
1792
|
},
|
|
1793
|
+
plugins: {
|
|
1794
|
+
namedExports: [
|
|
1795
|
+
"PluginSchema",
|
|
1796
|
+
"PluginStatusSchema",
|
|
1797
|
+
"CreatePluginSchema",
|
|
1798
|
+
"UpdatePluginSchema",
|
|
1799
|
+
"PluginVersionStatusSchema",
|
|
1800
|
+
"VersionSchema",
|
|
1801
|
+
"CategorySchema",
|
|
1802
|
+
"ReviewSchema",
|
|
1803
|
+
"CreateReviewSchema",
|
|
1804
|
+
"MarketplaceStatsSchema",
|
|
1805
|
+
"PluginListResponseSchema",
|
|
1806
|
+
"AdminPluginSchema",
|
|
1807
|
+
"AdminDashboardStatsSchema",
|
|
1808
|
+
"PluginListQuerySchema",
|
|
1809
|
+
"PluginSlugSchema",
|
|
1810
|
+
"type Plugin",
|
|
1811
|
+
"type PluginStatus",
|
|
1812
|
+
"type CreatePluginInput",
|
|
1813
|
+
"type UpdatePluginInput",
|
|
1814
|
+
"type PluginVersionStatus",
|
|
1815
|
+
"type Version",
|
|
1816
|
+
"type Category",
|
|
1817
|
+
"type Review",
|
|
1818
|
+
"type CreateReviewInput",
|
|
1819
|
+
"type MarketplaceStats",
|
|
1820
|
+
"type PluginListResponse",
|
|
1821
|
+
"type AdminPlugin",
|
|
1822
|
+
"type AdminDashboardStats",
|
|
1823
|
+
"type PluginListQuery"
|
|
1824
|
+
]
|
|
1825
|
+
},
|
|
1565
1826
|
merchant: {
|
|
1566
1827
|
namedExports: [
|
|
1567
1828
|
"ProductSchema",
|
|
@@ -1773,6 +2034,7 @@ function generateSharedModulesIndex(resolved) {
|
|
|
1773
2034
|
"permission",
|
|
1774
2035
|
"auth",
|
|
1775
2036
|
"plugin",
|
|
2037
|
+
"plugins",
|
|
1776
2038
|
"merchant",
|
|
1777
2039
|
"tenant",
|
|
1778
2040
|
"order",
|
|
@@ -1863,6 +2125,10 @@ var MODULE_EXPORTS2 = {
|
|
|
1863
2125
|
"TodoWithAttachmentsSchema",
|
|
1864
2126
|
"UploadFileSchema",
|
|
1865
2127
|
"AttachmentIdResponseSchema",
|
|
2128
|
+
"TodoListResponseSchema",
|
|
2129
|
+
"TodoListQuerySchema",
|
|
2130
|
+
"type TodoListResponse",
|
|
2131
|
+
"type TodoListQuery",
|
|
1866
2132
|
"type Todo",
|
|
1867
2133
|
"type TodoStatus",
|
|
1868
2134
|
"type CreateTodoInput",
|
|
@@ -1958,6 +2224,56 @@ var MODULE_EXPORTS2 = {
|
|
|
1958
2224
|
"type PluginListQuery"
|
|
1959
2225
|
]
|
|
1960
2226
|
},
|
|
2227
|
+
plugins: {
|
|
2228
|
+
namedExports: [
|
|
2229
|
+
"PluginSchema",
|
|
2230
|
+
"PluginStatusSchema",
|
|
2231
|
+
"CreatePluginSchema",
|
|
2232
|
+
"UpdatePluginSchema",
|
|
2233
|
+
"PluginVersionStatusSchema",
|
|
2234
|
+
"VersionSchema",
|
|
2235
|
+
"CategorySchema",
|
|
2236
|
+
"ReviewSchema",
|
|
2237
|
+
"CreateReviewSchema",
|
|
2238
|
+
"MarketplaceStatsSchema",
|
|
2239
|
+
"PluginListResponseSchema",
|
|
2240
|
+
"AdminPluginSchema",
|
|
2241
|
+
"AdminDashboardStatsSchema",
|
|
2242
|
+
"PluginListQuerySchema",
|
|
2243
|
+
"PluginSlugSchema",
|
|
2244
|
+
"PluginSearchQuerySchema",
|
|
2245
|
+
"PluginDeleteResponseSchema",
|
|
2246
|
+
"ReviewIdParamsSchema",
|
|
2247
|
+
"ReviewDeleteResponseSchema",
|
|
2248
|
+
"CategorySlugParamsSchema",
|
|
2249
|
+
"CategoryPluginsQuerySchema",
|
|
2250
|
+
"PluginListAdminSchema",
|
|
2251
|
+
"AdminListQuerySchema",
|
|
2252
|
+
"AdminListAllQuerySchema",
|
|
2253
|
+
"RejectPluginBodySchema",
|
|
2254
|
+
"BulkApproveBodySchema",
|
|
2255
|
+
"BulkRejectBodySchema",
|
|
2256
|
+
"BulkResponseSchema",
|
|
2257
|
+
"CreateCategoryBodySchema",
|
|
2258
|
+
"UpdateCategoryBodySchema",
|
|
2259
|
+
"CategoryIdParamsSchema",
|
|
2260
|
+
"CategoryIdResponseSchema",
|
|
2261
|
+
"type Plugin",
|
|
2262
|
+
"type PluginStatus",
|
|
2263
|
+
"type CreatePluginInput",
|
|
2264
|
+
"type UpdatePluginInput",
|
|
2265
|
+
"type PluginVersionStatus",
|
|
2266
|
+
"type Version",
|
|
2267
|
+
"type Category",
|
|
2268
|
+
"type Review",
|
|
2269
|
+
"type CreateReviewInput",
|
|
2270
|
+
"type MarketplaceStats",
|
|
2271
|
+
"type PluginListResponse",
|
|
2272
|
+
"type AdminPlugin",
|
|
2273
|
+
"type AdminDashboardStats",
|
|
2274
|
+
"type PluginListQuery"
|
|
2275
|
+
]
|
|
2276
|
+
},
|
|
1961
2277
|
admin: {
|
|
1962
2278
|
namedExports: [
|
|
1963
2279
|
"SystemStatsSchema",
|
|
@@ -2256,6 +2572,7 @@ var moduleOrder = [
|
|
|
2256
2572
|
"notifications",
|
|
2257
2573
|
"auth",
|
|
2258
2574
|
"plugin",
|
|
2575
|
+
"plugins",
|
|
2259
2576
|
"admin",
|
|
2260
2577
|
"audit",
|
|
2261
2578
|
"captcha",
|
|
@@ -2438,6 +2755,34 @@ function extractToken(authHeader: string | undefined): string | null {
|
|
|
2438
2755
|
return authHeader.slice(7)
|
|
2439
2756
|
}
|
|
2440
2757
|
|
|
2758
|
+
// dev tokens\uFF1A\u4E0E\u6A21\u677F\u5B8C\u6574\u7248 auth.ts \u8BED\u4E49\u4E00\u81F4\uFF08\u5F00\u53D1/\u6D4B\u8BD5\u4FBF\u5229\u540E\u95E8\uFF0Cproduction \u9ED8\u8BA4\u7981\u7528\uFF09
|
|
2759
|
+
function isDevTokensEnabled(): boolean {
|
|
2760
|
+
if (process.env.ENABLE_DEV_TOKENS === 'true') return true
|
|
2761
|
+
if (process.env.ENABLE_DEV_TOKENS === 'false') return false
|
|
2762
|
+
if (process.env.NODE_ENV === 'production') return false
|
|
2763
|
+
return true
|
|
2764
|
+
}
|
|
2765
|
+
|
|
2766
|
+
function verifyDevToken(token: string): AuthUser | null {
|
|
2767
|
+
if (token === 'admin-token' || token === 'super-admin-token') {
|
|
2768
|
+
return {
|
|
2769
|
+
id: 'super-admin-1',
|
|
2770
|
+
username: 'superadmin',
|
|
2771
|
+
email: 'superadmin@example.com',
|
|
2772
|
+
role: 'admin',
|
|
2773
|
+
}
|
|
2774
|
+
}
|
|
2775
|
+
if (token === 'user-token') {
|
|
2776
|
+
return {
|
|
2777
|
+
id: 'user-1',
|
|
2778
|
+
username: 'Demo User',
|
|
2779
|
+
email: 'demo@example.com',
|
|
2780
|
+
role: 'user',
|
|
2781
|
+
}
|
|
2782
|
+
}
|
|
2783
|
+
return null
|
|
2784
|
+
}
|
|
2785
|
+
|
|
2441
2786
|
export function authMiddleware(_options: AuthMiddlewareOptions = {}): MiddlewareHandler {
|
|
2442
2787
|
const log = createModuleLoggerSync('auth')
|
|
2443
2788
|
|
|
@@ -2449,6 +2794,16 @@ export function authMiddleware(_options: AuthMiddlewareOptions = {}): Middleware
|
|
|
2449
2794
|
return c.json({ success: false, error: 'Authentication required', status: 401 }, 401)
|
|
2450
2795
|
}
|
|
2451
2796
|
|
|
2797
|
+
if (isDevTokensEnabled()) {
|
|
2798
|
+
const devUser = verifyDevToken(token)
|
|
2799
|
+
if (devUser) {
|
|
2800
|
+
c.set('authUser', devUser)
|
|
2801
|
+
log.warn({ module: 'auth' }, 'DEV TOKEN USED - This should not appear in production!')
|
|
2802
|
+
await next()
|
|
2803
|
+
return
|
|
2804
|
+
}
|
|
2805
|
+
}
|
|
2806
|
+
|
|
2452
2807
|
try {
|
|
2453
2808
|
const jwt = await import('jsonwebtoken')
|
|
2454
2809
|
const secretKey = process.env.AUTH_SECRET_KEY || 'dev-secret-key-change-in-production'
|
|
@@ -2733,7 +3088,7 @@ var MODULE_PACKAGES = {
|
|
|
2733
3088
|
admin: ["bcryptjs"],
|
|
2734
3089
|
auth: ["bcryptjs"]
|
|
2735
3090
|
};
|
|
2736
|
-
var ADMIN_PANEL_PACKAGES = ["antd"];
|
|
3091
|
+
var ADMIN_PANEL_PACKAGES = ["antd", "@ant-design/icons"];
|
|
2737
3092
|
var CLI_PACKAGES = ["commander"];
|
|
2738
3093
|
var UNUSED_PACKAGES = ["lodash-es", "chalk", "mysql2"];
|
|
2739
3094
|
var CLIENT_PACKAGES = [
|
|
@@ -2759,6 +3114,7 @@ var CLIENT_DEV_PACKAGES = [
|
|
|
2759
3114
|
"playwright",
|
|
2760
3115
|
"@prerenderer/renderer-jsdom",
|
|
2761
3116
|
"@prerenderer/renderer-puppeteer",
|
|
3117
|
+
"puppeteer",
|
|
2762
3118
|
"@prerenderer/rollup-plugin",
|
|
2763
3119
|
"eventsource"
|
|
2764
3120
|
];
|
|
@@ -3833,6 +4189,50 @@ async function createProject(projectNameOrOptions, useCurrentDir = false, preset
|
|
|
3833
4189
|
const genSpinner = ora("Generating module-specific files...").start();
|
|
3834
4190
|
const routeRegistryContent = generateRouteRegistry(resolved);
|
|
3835
4191
|
await fs.writeFile(path.join(targetDir, "src/server/route-registry.ts"), routeRegistryContent);
|
|
4192
|
+
const rpcSurfaceContent = generateRpcSurface(resolved, (relPath) => {
|
|
4193
|
+
const full = path.join(targetDir, "src/server", `${relPath}.ts`);
|
|
4194
|
+
return fs.existsSync(full) ? fs.readFileSync(full, "utf-8") : null;
|
|
4195
|
+
});
|
|
4196
|
+
await fs.writeFile(path.join(targetDir, "src/server/rpc-surface.ts"), rpcSurfaceContent);
|
|
4197
|
+
for (const [rel, from, to] of [
|
|
4198
|
+
["src/client/App.tsx", "presetId = 'todo'", `presetId = '${selectedPreset.id}'`],
|
|
4199
|
+
[
|
|
4200
|
+
"src/client/entry-server.tsx",
|
|
4201
|
+
"const preset = 'todo'",
|
|
4202
|
+
`const preset = '${selectedPreset.id}'`
|
|
4203
|
+
]
|
|
4204
|
+
]) {
|
|
4205
|
+
const p = path.join(targetDir, rel);
|
|
4206
|
+
if (await fs.pathExists(p)) {
|
|
4207
|
+
let content = await fs.readFile(p, "utf-8");
|
|
4208
|
+
content = content.split(from).join(to);
|
|
4209
|
+
await fs.writeFile(p, content);
|
|
4210
|
+
}
|
|
4211
|
+
}
|
|
4212
|
+
if (!resolved.hasClient) {
|
|
4213
|
+
const barrelPath = path.join(targetDir, "src/shared/index.ts");
|
|
4214
|
+
if (await fs.pathExists(barrelPath)) {
|
|
4215
|
+
let barrel = await fs.readFile(barrelPath, "utf-8");
|
|
4216
|
+
barrel = barrel.replaceAll("export * from './hooks'\n", "");
|
|
4217
|
+
await fs.writeFile(barrelPath, barrel);
|
|
4218
|
+
}
|
|
4219
|
+
}
|
|
4220
|
+
const isrModulesContent = generateIsrModules(
|
|
4221
|
+
resolved,
|
|
4222
|
+
(moduleName, relPath) => fs.existsSync(path.join(targetDir, "src/server", `module-${moduleName}`, relPath))
|
|
4223
|
+
);
|
|
4224
|
+
await fs.writeFile(path.join(targetDir, "src/server/isr-modules.ts"), isrModulesContent);
|
|
4225
|
+
if (resolved.hasClient) {
|
|
4226
|
+
const entryStoresContent = generateEntryStores(
|
|
4227
|
+
resolved,
|
|
4228
|
+
(_moduleName, relPath) => fs.existsSync(path.join(targetDir, "src/client/stores", relPath))
|
|
4229
|
+
);
|
|
4230
|
+
await fs.ensureDir(path.join(targetDir, "src/client/stores"));
|
|
4231
|
+
await fs.writeFile(
|
|
4232
|
+
path.join(targetDir, "src/client/stores/entry-stores.ts"),
|
|
4233
|
+
entryStoresContent
|
|
4234
|
+
);
|
|
4235
|
+
}
|
|
3836
4236
|
const dbSchemaContent = generateDbSchemaBarrel(resolved);
|
|
3837
4237
|
await fs.writeFile(path.join(targetDir, "src/server/db/schema/index.ts"), dbSchemaContent);
|
|
3838
4238
|
if (resolved.hasClient) {
|
|
@@ -3939,6 +4339,7 @@ async function createProject(projectNameOrOptions, useCurrentDir = false, preset
|
|
|
3939
4339
|
try {
|
|
3940
4340
|
const { execSync } = await import('child_process');
|
|
3941
4341
|
execSync("npm install --legacy-peer-deps", {
|
|
4342
|
+
env: { ...process.env, PUPPETEER_SKIP_DOWNLOAD: "1" },
|
|
3942
4343
|
cwd: targetDir,
|
|
3943
4344
|
stdio: "pipe",
|
|
3944
4345
|
timeout: 3e5
|
|
@@ -3997,6 +4398,22 @@ async function createProject(projectNameOrOptions, useCurrentDir = false, preset
|
|
|
3997
4398
|
}
|
|
3998
4399
|
console.log("");
|
|
3999
4400
|
console.log(chalk.gray(" Happy coding! \u{1F41F}"));
|
|
4401
|
+
const home = process.env.HOME || "";
|
|
4402
|
+
const agents = [];
|
|
4403
|
+
if (home) {
|
|
4404
|
+
if (await fs.pathExists(path.join(home, ".zcode"))) agents.push("ZCode");
|
|
4405
|
+
if (await fs.pathExists(path.join(home, ".codex"))) agents.push("Codex");
|
|
4406
|
+
if (await fs.pathExists(path.join(home, ".claude"))) agents.push("Claude Code");
|
|
4407
|
+
if (await fs.pathExists(path.join(home, ".cursor"))) agents.push("Cursor");
|
|
4408
|
+
}
|
|
4409
|
+
if (agents.length > 0) {
|
|
4410
|
+
console.log("");
|
|
4411
|
+
console.log(chalk.cyan(` \u{1FA9D} Agent hooks (\u68C0\u6D4B\u5230 ${agents.join(" / ")}):`));
|
|
4412
|
+
console.log(
|
|
4413
|
+
chalk.gray(" \u5DE5\u4F5C\u533A\u94A9\u5B50\u5DF2\u751F\u6210\uFF08.zcode/\uFF0C\u7981\u6B62 --no-verify \u63D0\u4EA4\uFF09\uFF0CZCode \u81EA\u52A8\u751F\u6548")
|
|
4414
|
+
);
|
|
4415
|
+
console.log(chalk.gray(" \u540C\u6B65\u5230\u5168\u5C40\uFF08Codex \u7B49\uFF09\uFF1Anpm run hooks:sync -- --global"));
|
|
4416
|
+
}
|
|
4000
4417
|
console.log("");
|
|
4001
4418
|
} catch (error) {
|
|
4002
4419
|
if (error instanceof ScaffoldError) throw error;
|