create-fullstack-scaffold 0.4.24 → 0.5.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 +439 -15
- package/dist/cli/index.js.map +1 -1
- package/package.json +10 -13
- 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.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 +6 -12
- 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 +82 -19
- 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/drizzle/0000_rainy_boomer.sql +0 -101
- package/template/drizzle/0001_add_todo_attachments.sql +0 -12
- package/template/drizzle/0002_chilly_magneto.sql +0 -89
- package/template/drizzle/0003_ambiguous_magdalene.sql +0 -100
- package/template/drizzle/0004_add_merchants_products.sql +0 -30
- package/template/drizzle/meta/0000_snapshot.json +0 -652
- package/template/drizzle/meta/0001_snapshot.json +0 -735
- package/template/drizzle/meta/0002_snapshot.json +0 -1198
- package/template/drizzle/meta/0003_snapshot.json +0 -1837
- package/template/drizzle/meta/_journal.json +0 -41
- package/template/patches/typescript+5.9.3.patch +0 -24
- package/template/pnpm-lock.yaml +0 -7137
- /package/template/patches/{hono+4.12.16.patch → hono+4.12.34.patch} +0 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @framework-baseline 779069c2d2bb618c
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* TS2589 哨兵(编译期早期预警)
|
|
7
|
+
*
|
|
8
|
+
* 历史:route-registry 曾把 N 个模块链式 merge 成巨型类型导出,hc<T> 实例化时
|
|
9
|
+
* 深度展开整条链触发 TS2589(Type instantiation is excessively deep),
|
|
10
|
+
* 甚至一度 patch 了 TypeScript 编译器(已被 no-ts-patch validator 禁止)。
|
|
11
|
+
* 现行架构:每个模块导出窄类型(深度 = 1),rpc-surface 按模块实例化。
|
|
12
|
+
*
|
|
13
|
+
* 本文件的职责:对每个模块的窄类型做一次真实的 hc 值级实例化。
|
|
14
|
+
* 若将来有人把某个模块类型重新指到 merge 链上(例如让 XxxApiType 依赖
|
|
15
|
+
* route-registry 的合并结果),深度爆炸会先在这里报错——错误落在哨兵文件、
|
|
16
|
+
* 语义明确,而不是散落在业务代码里让推导"莫名其妙地失败"。
|
|
17
|
+
*
|
|
18
|
+
* 配套防线:
|
|
19
|
+
* - eslint 规则 no-merged-api-type-export(禁止导出 merge 类型,形态级拦截)
|
|
20
|
+
* - no-ts-patch validator(禁止放宽编译器保险丝)
|
|
21
|
+
* - CI template-checks 的 tsc 耗时预算(拦截"不报错但变慢"的慢性回退)
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import { hc } from 'hono/client'
|
|
25
|
+
import type { AuthApiType } from './module-auth/routes/auth-routes'
|
|
26
|
+
import type { ProfileApiType } from './module-auth/routes/profile-routes'
|
|
27
|
+
import type { ChatApiType } from './module-chat/routes/chat-routes'
|
|
28
|
+
import type { NotificationsApiType } from './module-notifications/routes/notification-routes'
|
|
29
|
+
import type { TodosApiType } from './module-todos/routes/todos-routes'
|
|
30
|
+
import type { PluginsApiType } from './module-plugin/routes/plugin-routes'
|
|
31
|
+
import type { PluginAdminApiType } from './module-plugin/routes/plugin-admin-routes'
|
|
32
|
+
import type { PublicContentApiType } from './module-content/routes/public-content-routes'
|
|
33
|
+
import type { ContentsApiType } from './module-content/routes/content-routes'
|
|
34
|
+
import type { TopicsApiType } from './module-content/routes/topics-routes'
|
|
35
|
+
import type { CartApiType } from './module-order/routes/cart-routes'
|
|
36
|
+
import type { OrdersApiType } from './module-order/routes/order-routes'
|
|
37
|
+
import type { OrdersMockApiType } from './module-order/routes/orders-mock-routes'
|
|
38
|
+
import type { MerchantApiType } from './module-merchant/routes/merchant-routes'
|
|
39
|
+
import type { TicketsApiType } from './module-ticket/routes/ticket-routes'
|
|
40
|
+
import type { DisputesApiType } from './module-dispute/routes/dispute-routes'
|
|
41
|
+
import type { CaptchaApiType } from './module-captcha/routes/captcha-routes'
|
|
42
|
+
import type { PermissionsApiType } from './module-permission/routes/permission-routes'
|
|
43
|
+
import type { RolesApiType } from './module-permission/routes/role-routes'
|
|
44
|
+
import type { AuditLogsApiType } from './module-permission/routes/audit-log-routes'
|
|
45
|
+
import type { AdminRoutesApiType } from './module-admin/routes/admin-routes'
|
|
46
|
+
import type { DashboardApiType } from './module-admin/routes/dashboard-routes'
|
|
47
|
+
import type { TenantApiType } from './module-tenant/routes/tenant-routes'
|
|
48
|
+
import type { FilesApiType } from './module-file/routes/file-routes'
|
|
49
|
+
import { createApiFacade } from './rpc-surface'
|
|
50
|
+
|
|
51
|
+
const api = '/api-canary'
|
|
52
|
+
|
|
53
|
+
// 每个模块一次值级实例化:深度爆炸在此处定点报错(TS2589)
|
|
54
|
+
export const canaryAuth = hc<AuthApiType>(api)
|
|
55
|
+
export const canaryProfile = hc<ProfileApiType>(api)
|
|
56
|
+
export const canaryChat = hc<ChatApiType>(api)
|
|
57
|
+
export const canaryNotifications = hc<NotificationsApiType>(api)
|
|
58
|
+
export const canaryTodos = hc<TodosApiType>(api)
|
|
59
|
+
export const canaryPlugins = hc<PluginsApiType>(api)
|
|
60
|
+
export const canaryPluginAdmin = hc<PluginAdminApiType>(api)
|
|
61
|
+
export const canaryPublicContent = hc<PublicContentApiType>(api)
|
|
62
|
+
export const canaryContents = hc<ContentsApiType>(api)
|
|
63
|
+
export const canaryTopics = hc<TopicsApiType>(api)
|
|
64
|
+
export const canaryCart = hc<CartApiType>(api)
|
|
65
|
+
export const canaryOrders = hc<OrdersApiType>(api)
|
|
66
|
+
export const canaryOrdersMock = hc<OrdersMockApiType>(api)
|
|
67
|
+
export const canaryMerchant = hc<MerchantApiType>(api)
|
|
68
|
+
export const canaryTickets = hc<TicketsApiType>(api)
|
|
69
|
+
export const canaryDisputes = hc<DisputesApiType>(api)
|
|
70
|
+
export const canaryCaptcha = hc<CaptchaApiType>(api)
|
|
71
|
+
export const canaryPermissions = hc<PermissionsApiType>(api)
|
|
72
|
+
export const canaryRoles = hc<RolesApiType>(api)
|
|
73
|
+
export const canaryAuditLogs = hc<AuditLogsApiType>(api)
|
|
74
|
+
export const canaryAdminRoutes = hc<AdminRoutesApiType>(api)
|
|
75
|
+
export const canaryDashboard = hc<DashboardApiType>(api)
|
|
76
|
+
export const canaryTenant = hc<TenantApiType>(api)
|
|
77
|
+
export const canaryFiles = hc<FilesApiType>(api)
|
|
78
|
+
|
|
79
|
+
// 门面整体形状哨兵:所有段必须在门面上可访问
|
|
80
|
+
export const canaryFacade = createApiFacade('/api-canary')
|
|
@@ -6,8 +6,7 @@
|
|
|
6
6
|
* @impact 测试客户端现在支持自定义 headers,用于认证测试
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import {
|
|
10
|
-
import type { AppType } from '@server/index'
|
|
9
|
+
import { createApiFacade, type ApiFacade } from '@server/rpc-surface'
|
|
11
10
|
import { createApp } from '@server/app'
|
|
12
11
|
import { SSEClientImpl } from '@shared/core/sse-client'
|
|
13
12
|
import { setRuntimeAdapter } from '@server/core/runtime'
|
|
@@ -16,12 +15,11 @@ import { getNodeRuntimeAdapter } from '@server/core/runtime-node'
|
|
|
16
15
|
setRuntimeAdapter(getNodeRuntimeAdapter())
|
|
17
16
|
|
|
18
17
|
/**
|
|
19
|
-
* 测试客户端类型
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* 该警告不影响运行时行为,测试客户端可正常工作。
|
|
18
|
+
* 测试客户端类型 —— 按模块拆分的门面(见 rpc-surface.ts)。
|
|
19
|
+
* 旧的 `ReturnType<typeof hc<AppType>>>` 会深度实例化整个 merge 链并触发
|
|
20
|
+
* TS2589,已被 eslint 规则 no-merged-api-type-export 禁止。
|
|
23
21
|
*/
|
|
24
|
-
export type TestClient =
|
|
22
|
+
export type TestClient = ApiFacade
|
|
25
23
|
|
|
26
24
|
export interface TestClientOptions {
|
|
27
25
|
webSocket?: (url: string | URL) => WebSocket
|
|
@@ -44,13 +42,13 @@ export function createTestClient(baseUrl?: string, options?: TestClientOptions)
|
|
|
44
42
|
: (url: string | URL) => new SSEClientImpl(url, defaultHeaders)
|
|
45
43
|
|
|
46
44
|
if (baseUrl) {
|
|
47
|
-
return
|
|
45
|
+
return createApiFacade(baseUrl, {
|
|
48
46
|
headers: defaultHeaders,
|
|
49
47
|
webSocket: options?.webSocket ? (url: string | URL) => options.webSocket!(url) : undefined,
|
|
50
48
|
sse: sseFactory as (url: string) => unknown,
|
|
51
49
|
})
|
|
52
50
|
}
|
|
53
|
-
return
|
|
51
|
+
return createApiFacade('http://localhost', {
|
|
54
52
|
fetch: (input: RequestInfo | URL, init?: RequestInit) => {
|
|
55
53
|
const request = new Request(input, init)
|
|
56
54
|
Object.entries(defaultHeaders).forEach(([key, value]) => {
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @framework-baseline 2c3eaa60a64308e8
|
|
3
|
+
*
|
|
4
|
+
*
|
|
5
|
+
* @framework-modify
|
|
6
|
+
* @reason prettier 格式化与注释结构整理;MockContent 字段收窄为字面量联合
|
|
7
|
+
* @impact 框架文件维护性修改,行为见测试
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* ISR test utilities — mock DB, mock service responses, time manipulation.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { createISRCache } from '@server/core/isr-cache'
|
|
15
|
+
import { createISRRegistry, type ISRRouteEntry } from '@server/core/isr-registry'
|
|
16
|
+
|
|
17
|
+
export interface MockTodo {
|
|
18
|
+
id: number
|
|
19
|
+
title: string
|
|
20
|
+
description?: string
|
|
21
|
+
status: 'pending' | 'in_progress' | 'completed'
|
|
22
|
+
createdAt: string
|
|
23
|
+
updatedAt: string
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface MockContent {
|
|
27
|
+
id: string
|
|
28
|
+
title: string
|
|
29
|
+
content: string
|
|
30
|
+
category: 'article' | 'announcement' | 'tutorial' | 'news' | 'policy'
|
|
31
|
+
status: 'draft' | 'published' | 'archived'
|
|
32
|
+
author: string
|
|
33
|
+
tags: string[]
|
|
34
|
+
viewCount: number
|
|
35
|
+
likeCount: number
|
|
36
|
+
createdAt: string
|
|
37
|
+
updatedAt: string
|
|
38
|
+
publishedAt?: string
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export const mockTodos: MockTodo[] = [
|
|
42
|
+
{
|
|
43
|
+
id: 1,
|
|
44
|
+
title: '学习 ISR',
|
|
45
|
+
status: 'completed',
|
|
46
|
+
createdAt: '2024-01-01T00:00:00.000Z',
|
|
47
|
+
updatedAt: '2024-01-02T00:00:00.000Z',
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
id: 2,
|
|
51
|
+
title: '部署 CF',
|
|
52
|
+
status: 'completed',
|
|
53
|
+
createdAt: '2024-01-03T00:00:00.000Z',
|
|
54
|
+
updatedAt: '2024-01-04T00:00:00.000Z',
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
id: 3,
|
|
58
|
+
title: '写测试',
|
|
59
|
+
status: 'in_progress',
|
|
60
|
+
createdAt: '2024-01-05T00:00:00.000Z',
|
|
61
|
+
updatedAt: '2024-01-06T00:00:00.000Z',
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
id: 4,
|
|
65
|
+
title: '优化 SEO',
|
|
66
|
+
status: 'pending',
|
|
67
|
+
createdAt: '2024-01-07T00:00:00.000Z',
|
|
68
|
+
updatedAt: '2024-01-08T00:00:00.000Z',
|
|
69
|
+
},
|
|
70
|
+
]
|
|
71
|
+
|
|
72
|
+
export const mockContents: MockContent[] = [
|
|
73
|
+
{
|
|
74
|
+
id: 'content-1',
|
|
75
|
+
title: 'ISR 完全指南',
|
|
76
|
+
content: 'ISR 是一种页面渲染策略...',
|
|
77
|
+
category: 'article',
|
|
78
|
+
status: 'published',
|
|
79
|
+
author: '技术团队',
|
|
80
|
+
tags: ['ISR', 'Cloudflare'],
|
|
81
|
+
viewCount: 128,
|
|
82
|
+
likeCount: 32,
|
|
83
|
+
createdAt: '2024-01-01T00:00:00.000Z',
|
|
84
|
+
updatedAt: '2024-01-02T00:00:00.000Z',
|
|
85
|
+
publishedAt: '2024-01-02T00:00:00.000Z',
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
id: 'content-2',
|
|
89
|
+
title: 'CF Workers 最佳实践',
|
|
90
|
+
content: '本文总结 CF Workers 部署经验...',
|
|
91
|
+
category: 'tutorial',
|
|
92
|
+
status: 'published',
|
|
93
|
+
author: '运维团队',
|
|
94
|
+
tags: ['Cloudflare', 'DevOps'],
|
|
95
|
+
viewCount: 256,
|
|
96
|
+
likeCount: 48,
|
|
97
|
+
createdAt: '2024-01-03T00:00:00.000Z',
|
|
98
|
+
updatedAt: '2024-01-04T00:00:00.000Z',
|
|
99
|
+
publishedAt: '2024-01-04T00:00:00.000Z',
|
|
100
|
+
},
|
|
101
|
+
]
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Create a test ISR setup with isolated registry and cache.
|
|
105
|
+
*/
|
|
106
|
+
export function createTestISR() {
|
|
107
|
+
const registry = createISRRegistry()
|
|
108
|
+
const cache = createISRCache({ maxAge: 1, staleWhileRevalidate: 2 })
|
|
109
|
+
|
|
110
|
+
return { registry, cache }
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Create mock ISR route entries for testing.
|
|
115
|
+
*/
|
|
116
|
+
export function createMockTodoEntries(todos: MockTodo[] = mockTodos): ISRRouteEntry[] {
|
|
117
|
+
return [
|
|
118
|
+
{
|
|
119
|
+
module: 'todos',
|
|
120
|
+
match: '/todos',
|
|
121
|
+
fetch: async () => ({ todos }),
|
|
122
|
+
meta: () => ({ title: 'Todo List', description: 'Todos page' }),
|
|
123
|
+
},
|
|
124
|
+
]
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Advance virtual time (for testing cache expiry without real waits).
|
|
129
|
+
* Returns a promise that resolves after the specified ms.
|
|
130
|
+
*/
|
|
131
|
+
export async function advanceTime(ms: number): Promise<void> {
|
|
132
|
+
await new Promise(resolve => setTimeout(resolve, ms))
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Extract text content from HTML tags.
|
|
137
|
+
*/
|
|
138
|
+
export function extractFromHTML(html: string, pattern: RegExp): string[] {
|
|
139
|
+
return html.match(pattern) ?? []
|
|
140
|
+
}
|
|
@@ -41,6 +41,18 @@ export const UpdateContentSchema = z.object({
|
|
|
41
41
|
|
|
42
42
|
export const ContentListSchema = z.array(ContentSchema)
|
|
43
43
|
|
|
44
|
+
export const ContentListResponseSchema = z.object({
|
|
45
|
+
contents: z.array(ContentSchema),
|
|
46
|
+
total: z.number(),
|
|
47
|
+
page: z.number(),
|
|
48
|
+
limit: z.number(),
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
export const ContentListQuerySchema = z.object({
|
|
52
|
+
page: z.coerce.number().int().positive().default(1),
|
|
53
|
+
limit: z.coerce.number().int().positive().max(100).default(20),
|
|
54
|
+
})
|
|
55
|
+
|
|
44
56
|
export const ContentDeleteResultSchema = z.object({
|
|
45
57
|
message: z.string(),
|
|
46
58
|
})
|
|
@@ -50,4 +62,6 @@ export type ContentStatus = z.infer<typeof ContentStatusSchema>
|
|
|
50
62
|
export type Content = z.infer<typeof ContentSchema>
|
|
51
63
|
export type CreateContentInput = z.infer<typeof CreateContentSchema>
|
|
52
64
|
export type UpdateContentInput = z.infer<typeof UpdateContentSchema>
|
|
65
|
+
export type ContentListResponse = z.infer<typeof ContentListResponseSchema>
|
|
66
|
+
export type ContentListQuery = z.infer<typeof ContentListQuerySchema>
|
|
53
67
|
export type ContentDeleteResult = z.infer<typeof ContentDeleteResultSchema>
|
|
@@ -49,6 +49,18 @@ export const ResolveDisputeSchema = z.object({
|
|
|
49
49
|
|
|
50
50
|
export const DisputeListSchema = z.array(DisputeSchema)
|
|
51
51
|
|
|
52
|
+
export const DisputeListResponseSchema = z.object({
|
|
53
|
+
disputes: z.array(DisputeSchema),
|
|
54
|
+
total: z.number(),
|
|
55
|
+
page: z.number(),
|
|
56
|
+
limit: z.number(),
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
export const DisputeListQuerySchema = z.object({
|
|
60
|
+
page: z.coerce.number().int().positive().default(1),
|
|
61
|
+
limit: z.coerce.number().int().positive().max(100).default(20),
|
|
62
|
+
})
|
|
63
|
+
|
|
52
64
|
export const DisputeDeleteResultSchema = z.object({
|
|
53
65
|
message: z.string(),
|
|
54
66
|
})
|
|
@@ -59,4 +71,6 @@ export type Dispute = z.infer<typeof DisputeSchema>
|
|
|
59
71
|
export type CreateDisputeInput = z.infer<typeof CreateDisputeSchema>
|
|
60
72
|
export type UpdateDisputeInput = z.infer<typeof UpdateDisputeSchema>
|
|
61
73
|
export type ResolveDisputeInput = z.infer<typeof ResolveDisputeSchema>
|
|
74
|
+
export type DisputeListResponse = z.infer<typeof DisputeListResponseSchema>
|
|
75
|
+
export type DisputeListQuery = z.infer<typeof DisputeListQuerySchema>
|
|
62
76
|
export type DisputeDeleteResult = z.infer<typeof DisputeDeleteResultSchema>
|
|
@@ -33,11 +33,18 @@ export const UpdateOrderSchema = z.object({
|
|
|
33
33
|
|
|
34
34
|
export const OrderListSchema = z.array(OrderSchema)
|
|
35
35
|
|
|
36
|
+
export const OrderListResponseSchema = z.object({
|
|
37
|
+
orders: z.array(OrderSchema),
|
|
38
|
+
total: z.number(),
|
|
39
|
+
page: z.number(),
|
|
40
|
+
limit: z.number(),
|
|
41
|
+
})
|
|
42
|
+
|
|
36
43
|
export const OrderQuerySchema = z.object({
|
|
37
44
|
status: OrderStatusSchema.nullish(),
|
|
38
45
|
customerName: z.string().nullish(),
|
|
46
|
+
page: z.coerce.number().int().positive().default(1),
|
|
39
47
|
limit: z.coerce.number().int().positive().max(100).default(20),
|
|
40
|
-
offset: z.coerce.number().int().min(0).default(0),
|
|
41
48
|
})
|
|
42
49
|
|
|
43
50
|
export const OrderDeleteResultSchema = z.object({
|
|
@@ -60,6 +67,7 @@ export type UpdateOrderInput = z.infer<typeof UpdateOrderSchema>
|
|
|
60
67
|
export type OrderDeleteResult = z.infer<typeof OrderDeleteResultSchema>
|
|
61
68
|
export type ProcessOrderInput = z.infer<typeof ProcessOrderSchema>
|
|
62
69
|
export type CancelOrderInput = z.infer<typeof CancelOrderSchema>
|
|
70
|
+
export type OrderListResponse = z.infer<typeof OrderListResponseSchema>
|
|
63
71
|
export type OrderQueryInput = z.infer<typeof OrderQuerySchema>
|
|
64
72
|
|
|
65
73
|
export const RemoveCartItemResponseSchema = z.object({ removedId: z.string() })
|
|
@@ -62,6 +62,18 @@ export const ReplyTicketSchema = z.object({
|
|
|
62
62
|
|
|
63
63
|
export const TicketListSchema = z.array(TicketSchema)
|
|
64
64
|
|
|
65
|
+
export const TicketListResponseSchema = z.object({
|
|
66
|
+
tickets: z.array(TicketSchema),
|
|
67
|
+
total: z.number(),
|
|
68
|
+
page: z.number(),
|
|
69
|
+
limit: z.number(),
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
export const TicketListQuerySchema = z.object({
|
|
73
|
+
page: z.coerce.number().int().positive().default(1),
|
|
74
|
+
limit: z.coerce.number().int().positive().max(100).default(20),
|
|
75
|
+
})
|
|
76
|
+
|
|
65
77
|
export const TicketDeleteResultSchema = z.object({
|
|
66
78
|
message: z.string(),
|
|
67
79
|
})
|
|
@@ -74,4 +86,6 @@ export type Ticket = z.infer<typeof TicketSchema>
|
|
|
74
86
|
export type CreateTicketInput = z.infer<typeof CreateTicketSchema>
|
|
75
87
|
export type UpdateTicketInput = z.infer<typeof UpdateTicketSchema>
|
|
76
88
|
export type ReplyTicketInput = z.infer<typeof ReplyTicketSchema>
|
|
89
|
+
export type TicketListResponse = z.infer<typeof TicketListResponseSchema>
|
|
90
|
+
export type TicketListQuery = z.infer<typeof TicketListQuerySchema>
|
|
77
91
|
export type TicketDeleteResult = z.infer<typeof TicketDeleteResultSchema>
|
|
@@ -10,6 +10,8 @@ export {
|
|
|
10
10
|
TodoWithAttachmentsSchema,
|
|
11
11
|
UploadFileSchema,
|
|
12
12
|
AttachmentIdResponseSchema,
|
|
13
|
+
TodoListResponseSchema,
|
|
14
|
+
TodoListQuerySchema,
|
|
13
15
|
type Todo,
|
|
14
16
|
type TodoStatus,
|
|
15
17
|
type CreateTodoInput,
|
|
@@ -17,4 +19,6 @@ export {
|
|
|
17
19
|
type TodoIdResponse,
|
|
18
20
|
type TodoAttachment,
|
|
19
21
|
type TodoWithAttachments,
|
|
22
|
+
type TodoListResponse,
|
|
23
|
+
type TodoListQuery,
|
|
20
24
|
} from './schemas'
|
|
@@ -56,6 +56,18 @@ export const AttachmentIdResponseSchema = z.object({
|
|
|
56
56
|
id: z.number(),
|
|
57
57
|
})
|
|
58
58
|
|
|
59
|
+
export const TodoListResponseSchema = z.object({
|
|
60
|
+
todos: z.array(TodoSchema),
|
|
61
|
+
total: z.number(),
|
|
62
|
+
page: z.number(),
|
|
63
|
+
limit: z.number(),
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
export const TodoListQuerySchema = z.object({
|
|
67
|
+
page: z.coerce.number().int().positive().default(1),
|
|
68
|
+
limit: z.coerce.number().int().positive().max(100).default(20),
|
|
69
|
+
})
|
|
70
|
+
|
|
59
71
|
export type TodoStatus = z.infer<typeof TodoStatusSchema>
|
|
60
72
|
export type Todo = z.infer<typeof TodoSchema>
|
|
61
73
|
export type CreateTodoInput = z.infer<typeof CreateTodoSchema>
|
|
@@ -63,3 +75,5 @@ export type UpdateTodoInput = z.infer<typeof UpdateTodoSchema>
|
|
|
63
75
|
export type TodoIdResponse = z.infer<typeof TodoIdResponseSchema>
|
|
64
76
|
export type TodoAttachment = z.infer<typeof TodoAttachmentSchema>
|
|
65
77
|
export type TodoWithAttachments = z.infer<typeof TodoWithAttachmentsSchema>
|
|
78
|
+
export type TodoListResponse = z.infer<typeof TodoListResponseSchema>
|
|
79
|
+
export type TodoListQuery = z.infer<typeof TodoListQuerySchema>
|
|
@@ -49,6 +49,8 @@ export {
|
|
|
49
49
|
TodoWithAttachmentsSchema,
|
|
50
50
|
UploadFileSchema,
|
|
51
51
|
AttachmentIdResponseSchema,
|
|
52
|
+
TodoListResponseSchema,
|
|
53
|
+
TodoListQuerySchema,
|
|
52
54
|
type Todo,
|
|
53
55
|
type TodoStatus,
|
|
54
56
|
type CreateTodoInput,
|
|
@@ -56,6 +58,8 @@ export {
|
|
|
56
58
|
type TodoIdResponse,
|
|
57
59
|
type TodoAttachment,
|
|
58
60
|
type TodoWithAttachments,
|
|
61
|
+
type TodoListResponse,
|
|
62
|
+
type TodoListQuery,
|
|
59
63
|
} from '../modules/todos'
|
|
60
64
|
export {
|
|
61
65
|
NotificationSchema,
|
|
@@ -171,6 +175,7 @@ export {
|
|
|
171
175
|
CreateOrderSchema,
|
|
172
176
|
UpdateOrderSchema,
|
|
173
177
|
OrderListSchema,
|
|
178
|
+
OrderListResponseSchema,
|
|
174
179
|
OrderQuerySchema,
|
|
175
180
|
OrderDeleteResultSchema,
|
|
176
181
|
ProcessOrderSchema,
|
|
@@ -184,6 +189,7 @@ export {
|
|
|
184
189
|
type CreateOrderInput,
|
|
185
190
|
type UpdateOrderInput,
|
|
186
191
|
type OrderDeleteResult,
|
|
192
|
+
type OrderListResponse,
|
|
187
193
|
type ProcessOrderInput,
|
|
188
194
|
type CancelOrderInput,
|
|
189
195
|
type OrderQueryInput,
|
|
@@ -239,12 +245,16 @@ export {
|
|
|
239
245
|
UpdateDisputeSchema,
|
|
240
246
|
ResolveDisputeSchema,
|
|
241
247
|
DisputeListSchema,
|
|
248
|
+
DisputeListResponseSchema,
|
|
249
|
+
DisputeListQuerySchema,
|
|
242
250
|
type DisputeType,
|
|
243
251
|
type DisputeStatus,
|
|
244
252
|
type Dispute,
|
|
245
253
|
type CreateDisputeInput,
|
|
246
254
|
type UpdateDisputeInput,
|
|
247
255
|
type ResolveDisputeInput,
|
|
256
|
+
type DisputeListResponse,
|
|
257
|
+
type DisputeListQuery,
|
|
248
258
|
} from '../modules/dispute'
|
|
249
259
|
export {
|
|
250
260
|
ProductSchema,
|
|
@@ -277,6 +287,8 @@ export {
|
|
|
277
287
|
UpdateTicketSchema,
|
|
278
288
|
ReplyTicketSchema,
|
|
279
289
|
TicketListSchema,
|
|
290
|
+
TicketListResponseSchema,
|
|
291
|
+
TicketListQuerySchema,
|
|
280
292
|
TicketDeleteResultSchema,
|
|
281
293
|
type TicketStatus,
|
|
282
294
|
type TicketPriority,
|
|
@@ -287,6 +299,8 @@ export {
|
|
|
287
299
|
type UpdateTicketInput,
|
|
288
300
|
type ReplyTicketInput,
|
|
289
301
|
type TicketDeleteResult,
|
|
302
|
+
type TicketListResponse,
|
|
303
|
+
type TicketListQuery,
|
|
290
304
|
} from '../modules/ticket'
|
|
291
305
|
export {
|
|
292
306
|
ContentCategorySchema,
|
|
@@ -295,6 +309,8 @@ export {
|
|
|
295
309
|
CreateContentSchema,
|
|
296
310
|
UpdateContentSchema,
|
|
297
311
|
ContentListSchema,
|
|
312
|
+
ContentListResponseSchema,
|
|
313
|
+
ContentListQuerySchema,
|
|
298
314
|
ContentDeleteResultSchema,
|
|
299
315
|
type ContentCategory,
|
|
300
316
|
type ContentStatus,
|
|
@@ -302,6 +318,8 @@ export {
|
|
|
302
318
|
type CreateContentInput,
|
|
303
319
|
type UpdateContentInput,
|
|
304
320
|
type ContentDeleteResult,
|
|
321
|
+
type ContentListResponse,
|
|
322
|
+
type ContentListQuery,
|
|
305
323
|
} from '../modules/content'
|
|
306
324
|
export {
|
|
307
325
|
CaptchaResponseSchema,
|
package/template/tsup.config.ts
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import { defineConfig, type Options } from 'tsup'
|
|
2
2
|
import { existsSync } from 'fs'
|
|
3
3
|
import { join, resolve } from 'path'
|
|
4
|
+
import { fileURLToPath } from 'url'
|
|
5
|
+
|
|
6
|
+
const __dirname = fileURLToPath(new URL('.', import.meta.url))
|
|
4
7
|
|
|
5
8
|
const root = process.cwd()
|
|
6
9
|
|
|
7
|
-
|
|
10
|
+
// 按入口文件存在性过滤(cli-only 等无 client preset 会排除 cloudflare 入口)
|
|
11
|
+
const rawServerBuildConfigs: Options[] = [
|
|
8
12
|
{
|
|
9
13
|
entry: ['src/server/entries/node.ts'],
|
|
10
14
|
outDir: 'dist/server',
|
|
@@ -48,6 +52,12 @@ const serverBuildConfigs: Options[] = [
|
|
|
48
52
|
'zod',
|
|
49
53
|
'drizzle-orm',
|
|
50
54
|
'drizzle-orm/d1',
|
|
55
|
+
'react',
|
|
56
|
+
'react-dom',
|
|
57
|
+
'react-dom/server',
|
|
58
|
+
'react-router-dom',
|
|
59
|
+
'react-helmet-async',
|
|
60
|
+
'zustand',
|
|
51
61
|
],
|
|
52
62
|
external: [
|
|
53
63
|
'pino',
|
|
@@ -72,10 +82,47 @@ const serverBuildConfigs: Options[] = [
|
|
|
72
82
|
],
|
|
73
83
|
define: {
|
|
74
84
|
'process.env.NODE_ENV': '"production"',
|
|
85
|
+
'import.meta.env.VITE_PRESET': '"todo"',
|
|
86
|
+
},
|
|
87
|
+
esbuildOptions(options) {
|
|
88
|
+
// Strip CSS imports for SSR (CSS is handled by client build)
|
|
89
|
+
options.loader = {
|
|
90
|
+
...options.loader,
|
|
91
|
+
'.css': 'empty',
|
|
92
|
+
'.svg': 'empty',
|
|
93
|
+
'.png': 'empty',
|
|
94
|
+
'.jpg': 'empty',
|
|
95
|
+
'.jpeg': 'empty',
|
|
96
|
+
'.gif': 'empty',
|
|
97
|
+
'.woff': 'empty',
|
|
98
|
+
'.woff2': 'empty',
|
|
99
|
+
'.ttf': 'empty',
|
|
100
|
+
'.eot': 'empty',
|
|
101
|
+
}
|
|
102
|
+
// Path aliases for client/shared modules
|
|
103
|
+
options.alias = {
|
|
104
|
+
'@shared': resolve(root, 'src/shared'),
|
|
105
|
+
'@client': resolve(root, 'src/client'),
|
|
106
|
+
'@server': resolve(root, 'src/server'),
|
|
107
|
+
'@admin': resolve(root, 'src/admin'),
|
|
108
|
+
// Use browser variant of react-dom/server for Cloudflare Workers
|
|
109
|
+
// (Node variant requires 'util' builtin which is unavailable in Workers)
|
|
110
|
+
'react-dom/server': 'react-dom/server.browser',
|
|
111
|
+
}
|
|
75
112
|
},
|
|
76
113
|
},
|
|
77
114
|
]
|
|
78
115
|
|
|
116
|
+
// 按入口文件存在性过滤(cli-only 等无 client 的 preset 会排除 cloudflare 入口文件)
|
|
117
|
+
const serverBuildConfigs: Options[] = rawServerBuildConfigs.filter(c => {
|
|
118
|
+
const raw = c.entry
|
|
119
|
+
const entries = Array.isArray(raw) ? raw : raw ? [raw] : []
|
|
120
|
+
return (
|
|
121
|
+
entries.length === 0 ||
|
|
122
|
+
entries.every(e => typeof e === 'string' && existsSync(resolve(__dirname, e)))
|
|
123
|
+
)
|
|
124
|
+
})
|
|
125
|
+
|
|
79
126
|
// Only build CLI if CLI module is included in preset
|
|
80
127
|
const cliEntryPath = join(process.cwd(), 'src/cli/index.ts')
|
|
81
128
|
if (existsSync(cliEntryPath)) {
|
|
@@ -1,12 +1,28 @@
|
|
|
1
1
|
import { defineConfig } from 'vitest/config'
|
|
2
|
-
import react from '@vitejs/plugin-react'
|
|
3
2
|
import { resolve } from 'node:path'
|
|
3
|
+
import { createRequire } from 'node:module'
|
|
4
|
+
|
|
5
|
+
// react 插件仅含 client 的 preset 安装;cli-only 等服务端 preset 跳过
|
|
6
|
+
// 变量说明符:tsc 不做模块类型解析(cli-only 未安装该依赖)
|
|
7
|
+
const reactPluginPkg = '@vitejs/plugin-react'
|
|
8
|
+
const reactPlugin = (await import(/* @vite-ignore */ reactPluginPkg)
|
|
9
|
+
.then((m: { default: () => unknown }) => m.default())
|
|
10
|
+
.catch(() => null)) as unknown
|
|
4
11
|
|
|
5
12
|
export default defineConfig({
|
|
6
|
-
plugins: [
|
|
13
|
+
plugins: reactPlugin ? [reactPlugin as never] : [],
|
|
7
14
|
test: {
|
|
15
|
+
passWithNoTests: true,
|
|
8
16
|
globals: true,
|
|
9
|
-
|
|
17
|
+
// jsdom 仅含 client 的 preset 安装;缺失时回退 node(服务端测试同样适用)
|
|
18
|
+
environment: (() => {
|
|
19
|
+
try {
|
|
20
|
+
createRequire(import.meta.url).resolve('jsdom')
|
|
21
|
+
return 'jsdom'
|
|
22
|
+
} catch {
|
|
23
|
+
return 'node'
|
|
24
|
+
}
|
|
25
|
+
})(),
|
|
10
26
|
setupFiles: ['./vitest.setup.ts'],
|
|
11
27
|
include: [
|
|
12
28
|
'**/__tests__/**/*.test.ts',
|