create-fullstack-scaffold 0.4.9 → 0.4.11

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.
Files changed (139) hide show
  1. package/dist/cli/index.js +499 -100
  2. package/dist/cli/index.js.map +1 -1
  3. package/package.json +2 -2
  4. package/template/CLAUDE.md +18 -6
  5. package/template/drizzle/0004_add_merchants_products.sql +30 -0
  6. package/template/drizzle/meta/_journal.json +7 -0
  7. package/template/eslint-rules/module-boundary.js +6 -2
  8. package/template/eslint-rules/no-cross-module-service-import.js +105 -0
  9. package/template/eslint-rules/no-disable-type-safe-client.js +5 -0
  10. package/template/eslint.config.js +9 -0
  11. package/template/lint-scripts/config/project.config.ts +21 -0
  12. package/template/lint-scripts/validate-all.ts +48 -12
  13. package/template/lint-scripts/validators/index.ts +29 -0
  14. package/template/lint-scripts/validators/module-public-api.validator.ts +103 -0
  15. package/template/lint-scripts/validators/schema-uniqueness.validator.ts +147 -0
  16. package/template/modules.config.ts +2 -0
  17. package/template/package.json +2 -0
  18. package/template/playwright.config.ts +14 -0
  19. package/template/src/admin/App.tsx +40 -3
  20. package/template/src/admin/components/LanguageSwitcher.tsx +22 -0
  21. package/template/src/admin/components/NotificationDrawer.tsx +108 -38
  22. package/template/src/admin/components/StatsCard.tsx +3 -1
  23. package/template/src/admin/components/ThemeToggle.tsx +28 -0
  24. package/template/src/admin/hooks/useRoles.ts +2 -2
  25. package/template/src/admin/i18n/index.ts +18 -0
  26. package/template/src/admin/i18n/locales/en-US.json +381 -0
  27. package/template/src/admin/i18n/locales/zh-CN.json +381 -0
  28. package/template/src/admin/i18n/useLanguage.ts +21 -0
  29. package/template/src/admin/layouts/Header.tsx +37 -16
  30. package/template/src/admin/layouts/Layout.tsx +7 -3
  31. package/template/src/admin/layouts/Sidebar.tsx +70 -68
  32. package/template/src/admin/main.tsx +1 -0
  33. package/template/src/admin/pages/ContentPage.tsx +68 -56
  34. package/template/src/admin/pages/DashboardPage.tsx +82 -76
  35. package/template/src/admin/pages/DisputesPage.tsx +61 -53
  36. package/template/src/admin/pages/LoginPage.tsx +41 -33
  37. package/template/src/admin/pages/OrdersPage.tsx +69 -64
  38. package/template/src/admin/pages/PermissionsPage.tsx +10 -8
  39. package/template/src/admin/pages/PluginDashboardPage.tsx +3 -1
  40. package/template/src/admin/pages/PluginManagementPage.tsx +3 -1
  41. package/template/src/admin/pages/RegisterPage.tsx +18 -16
  42. package/template/src/admin/pages/RolesPage.tsx +51 -49
  43. package/template/src/admin/pages/SettingsPage.tsx +22 -22
  44. package/template/src/admin/pages/SystemLogsPage.tsx +32 -30
  45. package/template/src/admin/pages/TicketsPage.tsx +67 -59
  46. package/template/src/admin/pages/UsersPage.tsx +63 -53
  47. package/template/src/admin/pages/__tests__/RolesPage.test.tsx +2 -2
  48. package/template/src/admin/stores/themeStore.ts +32 -0
  49. package/template/src/cli/modules/admin/index.ts +200 -0
  50. package/template/src/cli/modules/captcha/index.ts +40 -0
  51. package/template/src/cli/modules/chat/index.ts +21 -0
  52. package/template/src/cli/modules/content/index.ts +144 -0
  53. package/template/src/cli/modules/dispute/index.ts +150 -0
  54. package/template/src/cli/modules/file/index.ts +55 -0
  55. package/template/src/cli/modules/index.ts +30 -5
  56. package/template/src/cli/modules/order/index.ts +170 -0
  57. package/template/src/cli/modules/permission/index.ts +195 -0
  58. package/template/src/cli/modules/tenant/index.ts +142 -0
  59. package/template/src/cli/modules/ticket/index.ts +167 -0
  60. package/template/src/cli/rpc/client.ts +2 -2
  61. package/template/src/client/index.css +73 -0
  62. package/template/src/merchant/App.tsx +2 -0
  63. package/template/src/merchant/components/MerchantGuard.tsx +2 -6
  64. package/template/src/merchant/components/__tests__/MerchantGuard.test.tsx +117 -0
  65. package/template/src/merchant/layouts/Header.tsx +2 -2
  66. package/template/src/merchant/pages/DashboardPage.tsx +2 -2
  67. package/template/src/merchant/pages/DisputesPage.tsx +14 -10
  68. package/template/src/merchant/pages/LoginPage.tsx +49 -0
  69. package/template/src/merchant/pages/OrdersPage.tsx +17 -10
  70. package/template/src/merchant/pages/ProductsPage.tsx +31 -8
  71. package/template/src/merchant/pages/SettingsPage.tsx +3 -7
  72. package/template/src/merchant/stores/__tests__/merchantStore.test.ts +317 -0
  73. package/template/src/merchant/stores/merchantStore.ts +24 -27
  74. package/template/src/server/db/schema/index.ts +2 -0
  75. package/template/src/server/db/schema/merchants.ts +26 -0
  76. package/template/src/server/db/schema/products.ts +25 -0
  77. package/template/src/server/db/test-setup.ts +237 -0
  78. package/template/src/server/middleware/__tests__/audit-log.test.ts +144 -0
  79. package/template/src/server/middleware/__tests__/cors.test.ts +84 -0
  80. package/template/src/server/middleware/__tests__/logger.test.ts +117 -0
  81. package/template/src/server/middleware/__tests__/rate-limit.test.ts +73 -0
  82. package/template/src/server/middleware/__tests__/realtime-env.test.ts +56 -0
  83. package/template/src/server/middleware/__tests__/tenant-isolation.test.ts +150 -0
  84. package/template/src/server/module-admin/__tests__/admin-routes.test.ts +1 -1
  85. package/template/src/server/module-admin/module.ts +4 -0
  86. package/template/src/server/module-admin/routes/admin-notification-routes.ts +3 -3
  87. package/template/src/server/module-admin/routes/user-management-routes.ts +2 -2
  88. package/template/src/server/module-auth/__tests__/auth-routes.test.ts +315 -0
  89. package/template/src/server/module-auth/__tests__/profile-routes.test.ts +83 -0
  90. package/template/src/server/module-auth/module.ts +2 -0
  91. package/template/src/server/module-content/module.ts +1 -0
  92. package/template/src/server/module-content/routes/content-routes.ts +2 -2
  93. package/template/src/server/module-dispute/module.ts +1 -0
  94. package/template/src/server/module-dispute/routes/dispute-routes.ts +2 -2
  95. package/template/src/server/module-merchant/__tests__/merchant-routes.test.ts +218 -0
  96. package/template/src/server/module-merchant/__tests__/merchant-service.test.ts +183 -0
  97. package/template/src/server/module-merchant/index.ts +10 -0
  98. package/template/src/server/module-merchant/module.ts +33 -0
  99. package/template/src/server/module-merchant/routes/merchant-routes.ts +138 -0
  100. package/template/src/server/module-merchant/services/merchant-service.ts +292 -0
  101. package/template/src/server/module-notifications/index.ts +18 -0
  102. package/template/src/server/module-notifications/module.ts +2 -0
  103. package/template/src/server/module-order/module.ts +1 -0
  104. package/template/src/server/module-order/routes/order-routes.ts +2 -2
  105. package/template/src/server/module-permission/routes/role-routes.ts +3 -3
  106. package/template/src/server/module-plugin/__tests__/admin-category-service.test.ts +181 -0
  107. package/template/src/server/module-plugin/__tests__/admin-plugin-service.test.ts +277 -0
  108. package/template/src/server/module-plugin/__tests__/admin-stats-service.test.ts +170 -0
  109. package/template/src/server/module-plugin/__tests__/plugin-review-service.test.ts +260 -0
  110. package/template/src/server/module-plugin/__tests__/plugin-seed-service.test.ts +170 -0
  111. package/template/src/server/module-plugin/module.ts +3 -0
  112. package/template/src/server/module-tenant/__tests__/tenant-routes.test.ts +142 -0
  113. package/template/src/server/module-tenant/__tests__/tenant-service.test.ts +152 -0
  114. package/template/src/server/module-tenant/module.ts +1 -0
  115. package/template/src/server/module-tenant/services/tenant-service.ts +1 -37
  116. package/template/src/server/module-ticket/module.ts +1 -0
  117. package/template/src/server/module-ticket/routes/ticket-routes.ts +2 -2
  118. package/template/src/server/module-todos/module.ts +3 -0
  119. package/template/src/server/route-registry.ts +4 -0
  120. package/template/src/server/utils/__tests__/date.test.ts +130 -0
  121. package/template/src/server/utils/__tests__/env.test.ts +25 -0
  122. package/template/src/server/utils/__tests__/generate.test.ts +82 -0
  123. package/template/src/server/utils/__tests__/id-helpers.test.ts +29 -0
  124. package/template/src/server/utils/__tests__/json.test.ts +49 -0
  125. package/template/src/server/utils/__tests__/permission-utils.test.ts +26 -0
  126. package/template/src/server/utils/__tests__/uuid.test.ts +25 -0
  127. package/template/src/shared/core/module-manifest.ts +15 -0
  128. package/template/src/shared/modules/admin/schemas.ts +1 -1
  129. package/template/src/shared/modules/content/schemas.ts +2 -2
  130. package/template/src/shared/modules/dispute/schemas.ts +2 -2
  131. package/template/src/shared/modules/index.ts +181 -1
  132. package/template/src/shared/modules/merchant/index.ts +12 -0
  133. package/template/src/shared/modules/merchant/schemas.ts +63 -0
  134. package/template/src/shared/modules/order/schemas.ts +2 -2
  135. package/template/src/shared/modules/permission/index.ts +1 -1
  136. package/template/src/shared/modules/permission/schemas.ts +1 -1
  137. package/template/src/shared/modules/role/schemas.ts +2 -2
  138. package/template/src/shared/modules/ticket/schemas.ts +2 -2
  139. package/template/src/shared/schemas/index.ts +74 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-fullstack-scaffold",
3
- "version": "0.4.9",
3
+ "version": "0.4.11",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "create-fullstack-scaffold": "dist/cli/index.js"
@@ -86,7 +86,7 @@
86
86
  "validate:modules": "npx tsx src/server/core/module-loader.ts",
87
87
  "check:refs": "node --import tsx/esm lint-scripts/check-refs.ts",
88
88
  "create:module": "node --import tsx/esm scripts/create-template.ts",
89
- "postinstall": "patch-package",
89
+ "postinstall": "node -e \"try{require.resolve('patch-package')&&require('child_process').execSync('npx patch-package',{stdio:'pipe'})}catch(e){}\"",
90
90
  "prepare": "husky",
91
91
  "db:generate": "drizzle-kit generate",
92
92
  "db:migrate": "drizzle-kit migrate",
@@ -33,6 +33,17 @@ src/
33
33
  │ ├── hooks/ # Custom hooks
34
34
  │ ├── pages/ # Page components
35
35
  │ └── App.tsx
36
+ ├── merchant/ # Merchant dashboard (Ant Design)
37
+ │ ├── components/ # Merchant components
38
+ │ ├── stores/ # Merchant state (merchantStore)
39
+ │ ├── pages/ # Merchant pages
40
+ │ ├── layouts/ # Merchant layouts
41
+ │ └── App.tsx
42
+ ├── admin/ # Admin dashboard (Ant Design)
43
+ │ ├── components/ # Admin components
44
+ │ ├── stores/ # Admin state
45
+ │ ├── pages/ # Admin pages
46
+ │ └── App.tsx
36
47
  ├── server/ # Hono backend
37
48
  │ ├── module-todos/ # Todo module
38
49
  │ ├── module-chat/ # WebSocket chat module
@@ -173,12 +184,12 @@ Each module under `src/server/module-*/` has a `module.ts` manifest declaring:
173
184
 
174
185
  #### Module Categories
175
186
 
176
- | Category | Modules |
177
- | ------------- | -------------------------------- |
178
- | core | todos |
179
- | communication | chat, notifications |
180
- | business | order, ticket, dispute, content |
181
- | system | permission, admin, captcha, file |
187
+ | Category | Modules |
188
+ | ------------- | ----------------------------------------- |
189
+ | core | todos |
190
+ | communication | chat, notifications |
191
+ | business | order, ticket, dispute, content, merchant |
192
+ | system | permission, admin, captcha, file |
182
193
 
183
194
  #### Dependency Graph
184
195
 
@@ -194,6 +205,7 @@ order ──→ permission
194
205
  ticket ──→ permission
195
206
  dispute ──→ permission
196
207
  content ──→ permission
208
+ merchant ──→ auth + permission
197
209
  ```
198
210
 
199
211
  #### Validation
@@ -0,0 +1,30 @@
1
+ -- Create merchants table
2
+ CREATE TABLE `merchants` (
3
+ `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
4
+ `user_id` text NOT NULL,
5
+ `tenant_id` integer NOT NULL,
6
+ `business_name` text NOT NULL,
7
+ `business_type` text NOT NULL,
8
+ `status` text NOT NULL DEFAULT 'active',
9
+ `description` text,
10
+ `phone` text,
11
+ `email` text,
12
+ `address` text,
13
+ `password` text NOT NULL,
14
+ `created_at` text NOT NULL,
15
+ `updated_at` text NOT NULL
16
+ );
17
+
18
+ -- Create products table
19
+ CREATE TABLE `products` (
20
+ `id` text PRIMARY KEY NOT NULL,
21
+ `merchant_id` integer NOT NULL,
22
+ `name` text NOT NULL,
23
+ `description` text,
24
+ `price` real NOT NULL,
25
+ `status` text NOT NULL DEFAULT 'active',
26
+ `stock` integer NOT NULL DEFAULT 0,
27
+ `image_url` text,
28
+ `created_at` integer NOT NULL,
29
+ `updated_at` integer NOT NULL
30
+ );
@@ -29,6 +29,13 @@
29
29
  "when": 1778923351174,
30
30
  "tag": "0003_ambiguous_magdalene",
31
31
  "breakpoints": true
32
+ },
33
+ {
34
+ "idx": 4,
35
+ "version": "6",
36
+ "when": 1779000000000,
37
+ "tag": "0004_add_merchants_products",
38
+ "breakpoints": true
32
39
  }
33
40
  ]
34
41
  }
@@ -6,7 +6,7 @@
6
6
  * ESLint 规则:模块边界约束
7
7
  *
8
8
  * 规则:
9
- * 1. client、cli、admin 三个模块之间不能存在直接引用关系
9
+ * 1. client、cli、admin、merchant、tenant 模块之间不能存在直接引用关系
10
10
  * 2. 模块间共享代码必须通过 shared 目录
11
11
  * 3. 违规引用将报错并提示使用 shared 中转
12
12
  */
@@ -15,7 +15,7 @@ export const moduleBoundary = {
15
15
  meta: {
16
16
  type: 'problem',
17
17
  docs: {
18
- description: 'Enforce boundary between client, cli and admin modules',
18
+ description: 'Enforce boundary between client, cli, admin, merchant and tenant modules',
19
19
  recommended: true,
20
20
  },
21
21
  messages: {
@@ -35,12 +35,16 @@ export const moduleBoundary = {
35
35
  client: '/client/',
36
36
  cli: '/cli/',
37
37
  admin: '/admin/',
38
+ merchant: '/merchant/',
39
+ tenant: '/tenant/',
38
40
  }
39
41
 
40
42
  const moduleNames = {
41
43
  client: 'client',
42
44
  cli: 'cli',
43
45
  admin: 'admin',
46
+ merchant: 'merchant',
47
+ tenant: 'tenant',
44
48
  }
45
49
 
46
50
  function getCurrentModule(filePath) {
@@ -0,0 +1,105 @@
1
+ /**
2
+ * @fileoverview Prevent cross-module service internal imports
3
+ * @author create-biomimic-app
4
+ */
5
+
6
+ 'use strict'
7
+
8
+ const FORBIDDEN_INTERNAL_PATHS = ['services', 'routes', 'middleware']
9
+
10
+ export const noCrossModuleServiceImport = {
11
+ meta: {
12
+ type: 'problem',
13
+ docs: {
14
+ description: 'Prevent modules from importing service internals from other modules',
15
+ recommended: true,
16
+ },
17
+ messages: {
18
+ crossModuleServiceImport:
19
+ "Cross-module service import detected: '{{importPath}}'. " +
20
+ "Import from '{{otherModule}}' should use its public API ('@server/module-{{otherModule}}') instead of internal paths.",
21
+ },
22
+ schema: [],
23
+ },
24
+ create(context) {
25
+ const filename = context.filename || context.getFilename()
26
+
27
+ const currentModuleMatch = filename.match(/\/(module-[^/]+)\//)
28
+ if (!currentModuleMatch) return {}
29
+
30
+ const currentModule = currentModuleMatch[1]
31
+
32
+ function checkImport(importPath, node) {
33
+ let normalizedPath = importPath
34
+
35
+ if (importPath.startsWith('.')) {
36
+ const baseParts = filename.split('/')
37
+ baseParts.pop()
38
+ const parts = [...baseParts]
39
+
40
+ for (const segment of importPath.split('/')) {
41
+ if (segment === '..') {
42
+ parts.pop()
43
+ } else if (segment !== '.') {
44
+ parts.push(segment)
45
+ }
46
+ }
47
+
48
+ normalizedPath = parts.join('/')
49
+ }
50
+
51
+ const aliasMatch = normalizedPath.match(/@server\/(module-([^/]+))(?:\/(.+))?$/)
52
+ const relativeMatch = normalizedPath.match(/(module-([^/]+))\/(.+)$/)
53
+
54
+ const match = aliasMatch || relativeMatch
55
+ if (!match) return
56
+
57
+ const otherModuleName = aliasMatch ? aliasMatch[2] : relativeMatch[2]
58
+ const subPath = aliasMatch ? aliasMatch[3] : relativeMatch[3]
59
+
60
+ if (otherModuleName === currentModule.replace('module-', '')) return
61
+ if (!subPath) return
62
+
63
+ if (subPath === 'module.ts' || subPath === 'index.ts' || subPath === 'index') return
64
+
65
+ const firstSegment = subPath.split('/')[0]
66
+ if (FORBIDDEN_INTERNAL_PATHS.includes(firstSegment)) {
67
+ context.report({
68
+ node,
69
+ messageId: 'crossModuleServiceImport',
70
+ data: {
71
+ importPath,
72
+ otherModule: otherModuleName,
73
+ },
74
+ })
75
+ }
76
+ }
77
+
78
+ return {
79
+ ImportDeclaration(node) {
80
+ const source = node.source
81
+ if (!source || source.type !== 'Literal') return
82
+ if (typeof source.value !== 'string') return
83
+ checkImport(source.value, node)
84
+ },
85
+ ImportExpression(node) {
86
+ if (node.source.type !== 'Literal') return
87
+ if (typeof node.source.value !== 'string') return
88
+ checkImport(node.source.value, node)
89
+ },
90
+ CallExpression(node) {
91
+ if (
92
+ node.callee.type === 'Identifier' &&
93
+ node.callee.name === 'require' &&
94
+ node.arguments.length > 0 &&
95
+ node.arguments[0].type === 'Literal' &&
96
+ typeof node.arguments[0].value === 'string'
97
+ ) {
98
+ checkImport(node.arguments[0].value, node)
99
+ }
100
+ },
101
+ }
102
+ },
103
+ }
104
+
105
+ export default noCrossModuleServiceImport
@@ -26,8 +26,13 @@ export const noDisableTypeSafeClient = {
26
26
  const isMiddlewareTest =
27
27
  filename.includes('/middleware/__tests__') || filename.includes('/middleware/')
28
28
 
29
+ const isFileUploadTest =
30
+ filename.includes('file-upload') ||
31
+ filename.includes('file-routes.test')
32
+
29
33
  const isRouteTestFile =
30
34
  !isMiddlewareTest &&
35
+ !isFileUploadTest &&
31
36
  (filename.includes('-rpc.test.') ||
32
37
  filename.includes('-route.test.') ||
33
38
  (filename.includes('__tests__') && filename.includes('.test.')))
@@ -36,6 +36,9 @@ import { moduleBoundary } from './eslint-rules/module-boundary.js'
36
36
  import { limitTypeComplexity } from './eslint-rules/limit-type-complexity.js'
37
37
  import { requireAntdGenericTypes } from './eslint-rules/require-antd-generic-types.js'
38
38
  import { noDeepRelativeImports } from './eslint-rules/no-deep-relative-imports.js'
39
+ import { noCrossModuleServiceImport } from './eslint-rules/no-cross-module-service-import.js'
40
+ import { noDisableTypeSafeClient } from './eslint-rules/no-disable-type-safe-client.js'
41
+ import { routeLocation } from './eslint-rules/route-location.js'
39
42
 
40
43
  const localRules = {
41
44
  rules: {
@@ -71,6 +74,9 @@ const localRules = {
71
74
  'limit-type-complexity': limitTypeComplexity,
72
75
  'require-antd-generic-types': requireAntdGenericTypes,
73
76
  'no-deep-relative-imports': noDeepRelativeImports,
77
+ 'no-cross-module-service-import': noCrossModuleServiceImport,
78
+ 'no-disable-type-safe-client': noDisableTypeSafeClient,
79
+ 'route-location': routeLocation,
74
80
  },
75
81
  }
76
82
 
@@ -118,6 +124,8 @@ export default tseslint.config(
118
124
  'local-rules/flat-routes-services': 'error',
119
125
  'local-rules/no-middleware-in-routes': 'error',
120
126
  'local-rules/no-new-old-service-naming': 'error',
127
+ 'local-rules/no-cross-module-service-import': 'error',
128
+ 'local-rules/route-location': 'error',
121
129
  'local-rules/limit-type-complexity': ['warn', { maxRouteChainLength: 15 }],
122
130
  },
123
131
  },
@@ -153,6 +161,7 @@ export default tseslint.config(
153
161
  rules: {
154
162
  'no-console': 'off',
155
163
  'local-rules/require-type-safe-test-client': 'error',
164
+ 'local-rules/no-disable-type-safe-client': 'error',
156
165
  'local-rules/require-hono-chain-syntax': 'off',
157
166
  },
158
167
  },
@@ -16,6 +16,8 @@ import type {
16
16
  TestQualityConfig,
17
17
  ClientTestsConfig,
18
18
  MdRefsConfig,
19
+ SchemaUniquenessConfig,
20
+ ModulePublicApiConfig,
19
21
  } from '../validators/index.js'
20
22
 
21
23
  // ============================================
@@ -338,6 +340,23 @@ export const consoleLogConfig: ConsoleLogConfig = {
338
340
  checkDirs: ['src/server'],
339
341
  }
340
342
 
343
+ // ============================================
344
+ // Schema 命名唯一性验证配置
345
+ // ============================================
346
+ export const schemaUniquenessConfig: SchemaUniquenessConfig = {
347
+ modulesDir: 'src/shared/modules',
348
+ checkDirs: ['src/shared/modules'],
349
+ ignoreDirs: ['node_modules', 'dist'],
350
+ }
351
+
352
+ // ============================================
353
+ // 模块公共 API 验证配置
354
+ // ============================================
355
+ export const modulePublicApiConfig: ModulePublicApiConfig = {
356
+ serverDir: 'src/server',
357
+ checkDirs: ['src/server'],
358
+ }
359
+
341
360
  // ============================================
342
361
  // 统一导出
343
362
  // ============================================
@@ -353,6 +372,8 @@ export const projectConfig = {
353
372
  clientTests: clientTestsConfig,
354
373
  mdRefs: mdRefsConfig,
355
374
  consoleLog: consoleLogConfig,
375
+ schemaUniqueness: schemaUniquenessConfig,
376
+ modulePublicApi: modulePublicApiConfig,
356
377
  } as const
357
378
 
358
379
  export default projectConfig
@@ -28,6 +28,14 @@ import {
28
28
  formatAPICoverageErrors,
29
29
  } from './validators/api-coverage.validator.js'
30
30
  import { validateConsoleLog, formatConsoleLogErrors } from './validators/console-log.validator.js'
31
+ import {
32
+ validateSchemaUniqueness,
33
+ formatSchemaUniquenessErrors,
34
+ } from './validators/schema-uniqueness.validator.js'
35
+ import {
36
+ validateModulePublicApi,
37
+ formatModulePublicApiErrors,
38
+ } from './validators/module-public-api.validator.js'
31
39
  import projectConfig from './config/project.config.js'
32
40
 
33
41
  interface ValidatorResult {
@@ -41,7 +49,7 @@ async function runAllValidators(): Promise<ValidatorResult[]> {
41
49
  const results: ValidatorResult[] = []
42
50
 
43
51
  // 1. TODO 验证
44
- console.log('🔍 [1/12] Checking TODO/FIXME comments...')
52
+ console.log('🔍 [1/14] Checking TODO/FIXME comments...')
45
53
  const todoErrors = validateTodos(projectConfig.todos, rootPath)
46
54
  results.push({
47
55
  name: 'TODO/FIXME',
@@ -55,7 +63,7 @@ async function runAllValidators(): Promise<ValidatorResult[]> {
55
63
  }
56
64
 
57
65
  // 2. 敏感信息验证
58
- console.log('🔍 [2/12] Checking for sensitive data...')
66
+ console.log('🔍 [2/14] Checking for sensitive data...')
59
67
  const sensitiveErrors = await validateSensitive(projectConfig.sensitive, rootPath)
60
68
  results.push({
61
69
  name: 'Sensitive Data',
@@ -69,7 +77,7 @@ async function runAllValidators(): Promise<ValidatorResult[]> {
69
77
  }
70
78
 
71
79
  // 3. 导入路径验证
72
- console.log('🔍 [3/12] Checking import paths...')
80
+ console.log('🔍 [3/14] Checking import paths...')
73
81
  const importErrors = validateImports(projectConfig.imports, rootPath)
74
82
  results.push({
75
83
  name: 'Import Paths',
@@ -83,7 +91,7 @@ async function runAllValidators(): Promise<ValidatorResult[]> {
83
91
  }
84
92
 
85
93
  // 4. 服务端 RPC 验证
86
- console.log('🔍 [4/12] Checking server RPC patterns...')
94
+ console.log('🔍 [4/14] Checking server RPC patterns...')
87
95
  const serverRPCErrors = validateServerRPC(projectConfig.serverRPC, rootPath)
88
96
  results.push({
89
97
  name: 'Server RPC',
@@ -97,7 +105,7 @@ async function runAllValidators(): Promise<ValidatorResult[]> {
97
105
  }
98
106
 
99
107
  // 5. 客户端 RPC 验证
100
- console.log('🔍 [5/12] Checking client RPC usage...')
108
+ console.log('🔍 [5/14] Checking client RPC usage...')
101
109
  const clientRPCErrors = validateClientRPC(projectConfig.clientRPC, rootPath)
102
110
  results.push({
103
111
  name: 'Client RPC',
@@ -111,7 +119,7 @@ async function runAllValidators(): Promise<ValidatorResult[]> {
111
119
  }
112
120
 
113
121
  // 6. 目录结构验证
114
- console.log('🔍 [6/12] Checking directory structure...')
122
+ console.log('🔍 [6/14] Checking directory structure...')
115
123
  const { directoryErrors, forbiddenErrors } = validateDirectoryStructure(
116
124
  projectConfig.directory,
117
125
  rootPath
@@ -129,7 +137,7 @@ async function runAllValidators(): Promise<ValidatorResult[]> {
129
137
  }
130
138
 
131
139
  // 7. 模块测试文件验证
132
- console.log('🔍 [7/12] Checking module test files...')
140
+ console.log('🔍 [7/14] Checking module test files...')
133
141
  const moduleTestErrors = validateModuleTests(projectConfig.moduleTests, rootPath)
134
142
  results.push({
135
143
  name: 'Module Tests',
@@ -143,7 +151,7 @@ async function runAllValidators(): Promise<ValidatorResult[]> {
143
151
  }
144
152
 
145
153
  // 8. 测试质量验证
146
- console.log('🔍 [8/12] Checking test quality...')
154
+ console.log('🔍 [8/14] Checking test quality...')
147
155
  const { errors: testQualityErrors, warnings: testQualityWarnings } = validateTestQuality(
148
156
  projectConfig.testQuality,
149
157
  rootPath
@@ -161,7 +169,7 @@ async function runAllValidators(): Promise<ValidatorResult[]> {
161
169
  }
162
170
 
163
171
  // 9. Client 测试覆盖验证
164
- console.log('🔍 [9/12] Checking client test coverage...')
172
+ console.log('🔍 [9/14] Checking client test coverage...')
165
173
  const clientTestErrors = validateClientTests(projectConfig.clientTests, rootPath)
166
174
  results.push({
167
175
  name: 'Client Tests',
@@ -175,7 +183,7 @@ async function runAllValidators(): Promise<ValidatorResult[]> {
175
183
  }
176
184
 
177
185
  // 10. API 覆盖率验证
178
- console.log('🔍 [10/12] Checking API coverage...')
186
+ console.log('🔍 [10/14] Checking API coverage...')
179
187
  const apiCoverageErrors = validateAPICoverage(projectConfig.moduleTests, rootPath)
180
188
  results.push({
181
189
  name: 'API Coverage',
@@ -189,7 +197,7 @@ async function runAllValidators(): Promise<ValidatorResult[]> {
189
197
  }
190
198
 
191
199
  // 11. Markdown 引用路径验证
192
- console.log('🔍 [11/12] Checking markdown references...')
200
+ console.log('🔍 [11/14] Checking markdown references...')
193
201
  const mdRefErrors = validateMdRefs(projectConfig.mdRefs, rootPath)
194
202
  results.push({
195
203
  name: 'Markdown References',
@@ -203,7 +211,7 @@ async function runAllValidators(): Promise<ValidatorResult[]> {
203
211
  }
204
212
 
205
213
  // 12. Console.log 验证
206
- console.log('🔍 [12/12] Checking for console.log statements...')
214
+ console.log('🔍 [12/14] Checking for console.log statements...')
207
215
  const consoleLogErrors = validateConsoleLog(projectConfig.consoleLog, rootPath)
208
216
  results.push({
209
217
  name: 'Console.log',
@@ -216,6 +224,34 @@ async function runAllValidators(): Promise<ValidatorResult[]> {
216
224
  console.log(' ✅ No console.log statements found\n')
217
225
  }
218
226
 
227
+ // 13. Schema 命名唯一性验证
228
+ console.log('🔍 [13/14] Checking schema name uniqueness across modules...')
229
+ const schemaUniquenessErrors = validateSchemaUniqueness(projectConfig.schemaUniqueness, rootPath)
230
+ results.push({
231
+ name: 'Schema Uniqueness',
232
+ passed: schemaUniquenessErrors.length === 0,
233
+ errors: schemaUniquenessErrors.length,
234
+ })
235
+ if (schemaUniquenessErrors.length > 0) {
236
+ console.error(formatSchemaUniquenessErrors(schemaUniquenessErrors))
237
+ } else {
238
+ console.log(' ✅ No cross-module schema name collisions\n')
239
+ }
240
+
241
+ // 14. 模块公共 API 验证
242
+ console.log('🔍 [14/14] Checking module public API barrels...')
243
+ const modulePublicApiErrors = validateModulePublicApi(projectConfig.modulePublicApi, rootPath)
244
+ results.push({
245
+ name: 'Module Public API',
246
+ passed: modulePublicApiErrors.length === 0,
247
+ errors: modulePublicApiErrors.length,
248
+ })
249
+ if (modulePublicApiErrors.length > 0) {
250
+ console.error(formatModulePublicApiErrors(modulePublicApiErrors))
251
+ } else {
252
+ console.log(' ✅ All depended-upon modules have index.ts barrels\n')
253
+ }
254
+
219
255
  return results
220
256
  }
221
257
 
@@ -269,3 +269,32 @@ export interface ConsoleLogError {
269
269
  message: string
270
270
  content: string
271
271
  }
272
+
273
+ // ============================================
274
+ // Schema 命名唯一性验证配置
275
+ // ============================================
276
+ export interface SchemaUniquenessConfig {
277
+ modulesDir: string
278
+ checkDirs: string[]
279
+ ignoreDirs: string[]
280
+ }
281
+
282
+ export interface SchemaUniquenessError {
283
+ exportName: string
284
+ modules: string[]
285
+ suggestion: string
286
+ }
287
+
288
+ // ============================================
289
+ // 模块公共 API 验证配置
290
+ // ============================================
291
+ export interface ModulePublicApiConfig {
292
+ serverDir: string
293
+ checkDirs: string[]
294
+ }
295
+
296
+ export interface ModulePublicApiError {
297
+ module: string
298
+ dependedBy: string[]
299
+ suggestion: string
300
+ }
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Module Public API 验证器
3
+ *
4
+ * 确保被其他模块依赖(dependsOn)的服务端模块都有 index.ts 公共 API barrel 文件。
5
+ * 模块应通过干净的公共接口被消费,而非深层内部导入。
6
+ */
7
+
8
+ import { readFileSync, readdirSync, existsSync } from 'node:fs'
9
+ import { join } from 'node:path'
10
+
11
+ export interface ModulePublicApiConfig {
12
+ serverDir: string
13
+ checkDirs: string[]
14
+ }
15
+
16
+ export interface ModulePublicApiError {
17
+ module: string
18
+ dependedBy: string[]
19
+ suggestion: string
20
+ }
21
+
22
+ interface ParsedManifest {
23
+ name: string
24
+ dependsOn: string[]
25
+ }
26
+
27
+ function parseManifest(filePath: string): ParsedManifest | null {
28
+ const content = readFileSync(filePath, 'utf-8')
29
+
30
+ const nameMatch = content.match(/name:\s*['"`]([^'"`]+)['"`]/)
31
+ if (!nameMatch) return null
32
+
33
+ const dependsOnMatch = content.match(/dependsOn:\s*\[([^\]]*)\]/)
34
+ const dependsOn = dependsOnMatch
35
+ ? [...dependsOnMatch[1].matchAll(/['"`]([^'"`]+)['"`]/g)].map(m => m[1])
36
+ : []
37
+
38
+ return { name: nameMatch[1], dependsOn }
39
+ }
40
+
41
+ export function validateModulePublicApi(
42
+ config: ModulePublicApiConfig,
43
+ rootPath: string
44
+ ): ModulePublicApiError[] {
45
+ const serverPath = join(rootPath, config.serverDir)
46
+ if (!existsSync(serverPath)) return []
47
+
48
+ const entries = readdirSync(serverPath, { withFileTypes: true })
49
+ const moduleDirs = entries
50
+ .filter(e => e.isDirectory() && e.name.startsWith('module-'))
51
+ .map(e => e.name)
52
+
53
+ const manifests = new Map<string, ParsedManifest>()
54
+ for (const dir of moduleDirs) {
55
+ const manifestPath = join(serverPath, dir, 'module.ts')
56
+ if (!existsSync(manifestPath)) continue
57
+ const parsed = parseManifest(manifestPath)
58
+ if (parsed) manifests.set(parsed.name, parsed)
59
+ }
60
+
61
+ const dependedUpon = new Map<string, string[]>()
62
+ for (const [moduleName, manifest] of manifests) {
63
+ for (const dep of manifest.dependsOn) {
64
+ const consumers = dependedUpon.get(dep) ?? []
65
+ consumers.push(moduleName)
66
+ dependedUpon.set(dep, consumers)
67
+ }
68
+ }
69
+
70
+ const errors: ModulePublicApiError[] = []
71
+ for (const [depName, consumers] of dependedUpon) {
72
+ const barrelPath = join(serverPath, `module-${depName}`, 'index.ts')
73
+ if (!existsSync(barrelPath)) {
74
+ errors.push({
75
+ module: depName,
76
+ dependedBy: consumers,
77
+ suggestion: `Create template/src/server/module-${depName}/index.ts to expose a clean public API for consumers: ${consumers.join(
78
+ ', '
79
+ )}`,
80
+ })
81
+ }
82
+ }
83
+
84
+ return errors
85
+ }
86
+
87
+ export function formatModulePublicApiErrors(errors: ModulePublicApiError[]): string {
88
+ if (errors.length === 0) return ''
89
+
90
+ let output = `❌ Found ${errors.length} module(s) missing public API barrel (index.ts):\n\n`
91
+
92
+ for (const err of errors) {
93
+ output += ` module-${err.module}\n`
94
+ output += ` → Depended by: ${err.dependedBy.join(', ')}\n`
95
+ output += ` → Fix: ${err.suggestion}\n\n`
96
+ }
97
+
98
+ output += '📋 Guidelines:\n'
99
+ output += ' Every module listed in dependsOn should have an index.ts barrel file\n'
100
+ output += ' that re-exports its public API (routes, services, types).\n'
101
+
102
+ return output
103
+ }