create-windy 0.3.18 → 0.4.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.
Files changed (216) hide show
  1. package/README.md +28 -13
  2. package/dist/cli.js +788 -280
  3. package/package.json +1 -1
  4. package/template/.agents/skills/windy-business-module/SKILL.md +34 -10
  5. package/template/.windy-template.json +2 -2
  6. package/template/AGENTS.md +18 -8
  7. package/template/README.md +32 -22
  8. package/template/apps/agent-server/src/southwind_agent_server/adk_debug.py +38 -5
  9. package/template/apps/agent-server/tests/test_adk_debug_result_polling.py +139 -0
  10. package/template/apps/server/Dockerfile +1 -0
  11. package/template/apps/server/package.json +1 -0
  12. package/template/apps/server/scripts/validate-module-composition-once.ts +4 -0
  13. package/template/apps/server/scripts/validate-module-composition.ts +28 -0
  14. package/template/apps/server/src/agent/data-scope-routes.test.ts +20 -5
  15. package/template/apps/server/src/agent/routes.test.ts +20 -7
  16. package/template/apps/server/src/agent/routes.ts +14 -0
  17. package/template/apps/server/src/agent/runtime-bootstrap.ts +22 -6
  18. package/template/apps/server/src/agent/sandbox-worker.ts +2 -2
  19. package/template/apps/server/src/agent/unavailable-routes.ts +33 -0
  20. package/template/apps/server/src/app/server-composition.ts +56 -0
  21. package/template/apps/server/src/application-services.ts +2 -0
  22. package/template/apps/server/src/bulk-data/service.ts +14 -0
  23. package/template/apps/server/src/data-governance/export/authorization-port.ts +1 -1
  24. package/template/apps/server/src/data-governance/export/postgres-api-fixture.ts +18 -2
  25. package/template/apps/server/src/data-governance/export/postgres-recovery.integration.test.ts +10 -9
  26. package/template/apps/server/src/data-governance/export/routes-test-fixture.ts +6 -8
  27. package/template/apps/server/src/data-governance/user-plaintext-routes.test.ts +4 -2
  28. package/template/apps/server/src/feature/routes.authorization.test.ts +26 -1
  29. package/template/apps/server/src/feature/routes.ts +33 -15
  30. package/template/apps/server/src/foundation-activation.test.ts +60 -0
  31. package/template/apps/server/src/foundation.ts +64 -5
  32. package/template/apps/server/src/guards.test.ts +23 -0
  33. package/template/apps/server/src/guards.ts +12 -0
  34. package/template/apps/server/src/index.ts +28 -34
  35. package/template/apps/server/src/installed-business-modules.ts +13 -24
  36. package/template/apps/server/src/jobs/legacy-adapter.integration.test.ts +1 -0
  37. package/template/apps/server/src/jobs/legacy-adapter.test.ts +106 -0
  38. package/template/apps/server/src/jobs/legacy-service-runtime.ts +40 -0
  39. package/template/apps/server/src/license/activation.test.ts +30 -0
  40. package/template/apps/server/src/license/activation.ts +26 -0
  41. package/template/apps/server/src/license/restriction.test.ts +16 -0
  42. package/template/apps/server/src/license/routes-offline-code.test.ts +6 -2
  43. package/template/apps/server/src/license/routes.ts +20 -4
  44. package/template/apps/server/src/license/runtime-service.test.ts +10 -2
  45. package/template/apps/server/src/module-bootstrap.ts +14 -23
  46. package/template/apps/server/src/module-composition.test.ts +43 -2
  47. package/template/apps/server/src/module-composition.ts +29 -0
  48. package/template/apps/server/src/module-host/durable-context-guard.test.ts +172 -0
  49. package/template/apps/server/src/module-host/durable-context-unavailable.test.ts +50 -1
  50. package/template/apps/server/src/module-host/durable-context.test.ts +42 -25
  51. package/template/apps/server/src/module-host/durable-context.ts +42 -2
  52. package/template/apps/server/src/module-host/durable-handler-registration.ts +96 -5
  53. package/template/apps/server/src/module-host/implementation-completeness.test.ts +185 -0
  54. package/template/apps/server/src/module-host/implementation-completeness.ts +132 -0
  55. package/template/apps/server/src/module-host/initialize-contracts.ts +14 -0
  56. package/template/apps/server/src/module-host/initialize.test.ts +78 -2
  57. package/template/apps/server/src/module-host/initialize.ts +15 -4
  58. package/template/apps/server/src/module-host/project-attachments.test.ts +92 -0
  59. package/template/apps/server/src/module-host/project-attachments.ts +58 -0
  60. package/template/apps/server/src/module-host/project-composition-package.test.ts +186 -0
  61. package/template/apps/server/src/module-host/project-composition.test.ts +260 -0
  62. package/template/apps/server/src/module-host/project-composition.ts +311 -0
  63. package/template/apps/server/src/module-host/request-context.ts +3 -0
  64. package/template/apps/server/src/module-host/route-declaration.ts +15 -6
  65. package/template/apps/server/src/module-host/route-installer.test.ts +15 -2
  66. package/template/apps/server/src/module-host/route-installer.ts +12 -2
  67. package/template/apps/server/src/module-host/route-registration.test.ts +13 -4
  68. package/template/apps/server/src/module-host/runtime-composition-compatibility.test.ts +63 -0
  69. package/template/apps/server/src/module-host/runtime-composition-compatibility.ts +46 -0
  70. package/template/apps/server/src/module-host/runtime-composition.ts +34 -0
  71. package/template/apps/server/src/module-host/search-provider-registration.test.ts +64 -0
  72. package/template/apps/server/src/module-host/search-provider-registration.ts +35 -0
  73. package/template/apps/server/src/module-host/static-route-authorization.test.ts +99 -0
  74. package/template/apps/server/src/module-host/static-route-authorization.ts +90 -0
  75. package/template/apps/server/src/module-host/task-definitions.ts +1 -0
  76. package/template/apps/server/src/module-host/user-directory-composition.test.ts +4 -1
  77. package/template/apps/server/src/module-host.test.ts +2 -0
  78. package/template/apps/server/src/module-preflight.test.ts +289 -0
  79. package/template/apps/server/src/module-preflight.ts +96 -0
  80. package/template/apps/server/src/module-runtime-validation.ts +1 -0
  81. package/template/apps/server/src/platform-app-bootstrap.ts +50 -0
  82. package/template/apps/server/src/platform-http-runtime.ts +32 -7
  83. package/template/apps/server/src/route-guards.test.ts +22 -0
  84. package/template/apps/server/src/route-guards.ts +10 -3
  85. package/template/apps/server/src/runtime-audit-query.ts +50 -0
  86. package/template/apps/server/src/runtime-feature.test.ts +46 -0
  87. package/template/apps/server/src/runtime-feature.ts +5 -0
  88. package/template/apps/server/src/runtime.ts +10 -49
  89. package/template/apps/server/src/scheduler/audit-action.test.ts +49 -0
  90. package/template/apps/server/src/scheduler/audit.ts +1 -1
  91. package/template/apps/server/src/scheduler/execution-recorder.ts +10 -5
  92. package/template/apps/server/src/scheduler/recovery-service.test.ts +4 -3
  93. package/template/apps/server/src/scheduler/service-idle.test.ts +4 -3
  94. package/template/apps/server/src/scheduler/service.test.ts +1 -0
  95. package/template/apps/server/src/scheduler/service.ts +18 -21
  96. package/template/apps/server/src/scheduler/types.ts +5 -1
  97. package/template/apps/server/src/startup-composition-validation.test.ts +35 -0
  98. package/template/apps/server/src/startup-composition-validation.ts +54 -0
  99. package/template/apps/server/src/startup-preflight.test.ts +36 -0
  100. package/template/apps/server/src/startup-preflight.ts +37 -0
  101. package/template/apps/server/src/system/audit-routes.test.ts +9 -3
  102. package/template/apps/server/src/system/department-routes.test.ts +5 -3
  103. package/template/apps/server/src/system/feature-routes.test.ts +4 -3
  104. package/template/apps/server/src/system/interface-catalog.test.ts +5 -3
  105. package/template/apps/server/src/system/interface-catalog.ts +3 -0
  106. package/template/apps/server/src/system/menu-routes.test.ts +5 -3
  107. package/template/apps/server/src/system/route-test-runtime.ts +24 -0
  108. package/template/apps/server/src/system/routes-validation.test.ts +7 -3
  109. package/template/apps/server/src/system/routes.test.ts +21 -11
  110. package/template/apps/server/src/system/routes.ts +11 -2
  111. package/template/apps/server/src/work-order/foundation-modules.test.ts +6 -4
  112. package/template/apps/server/src/work-order/routes.test.ts +8 -0
  113. package/template/apps/server/src/work-order/task.test.ts +6 -1
  114. package/template/apps/web/Dockerfile +5 -0
  115. package/template/apps/web/package.json +1 -0
  116. package/template/apps/web/scripts/validate-admin-ui-composition.ts +106 -0
  117. package/template/apps/web/src/app/web-composition.ts +24 -0
  118. package/template/apps/web/src/app/web-composition.webtest.ts +15 -0
  119. package/template/apps/web/src/layout/AppSidebar.vue +8 -1
  120. package/template/apps/web/src/layout/NavigationSearchDialog.vue +8 -1
  121. package/template/apps/web/src/layout/navigation.ts +29 -4
  122. package/template/apps/web/src/layout/navigation.webtest.ts +51 -0
  123. package/template/apps/web/src/pages/dashboard/dashboard-presenter.ts +6 -1
  124. package/template/apps/web/src/pages/work-orders/WorkOrderListPage.vue +202 -0
  125. package/template/apps/web/src/pages/work-orders/WorkOrderListPage.webtest.ts +51 -0
  126. package/template/apps/web/src/router/admin-extension-loader.ts +44 -0
  127. package/template/apps/web/src/router/admin-extension-loader.webtest.ts +49 -0
  128. package/template/apps/web/src/router/admin-ui-composition-plugin.ts +33 -0
  129. package/template/apps/web/src/router/admin-ui-composition-plugin.webtest.ts +20 -0
  130. package/template/apps/web/src/router/admin-ui-composition.ts +313 -0
  131. package/template/apps/web/src/router/admin-ui-composition.webtest.ts +225 -0
  132. package/template/apps/web/src/router/admin-ui-module-registry-plugin.ts +55 -0
  133. package/template/apps/web/src/router/admin-ui-module-registry-plugin.webtest.ts +46 -0
  134. package/template/apps/web/src/router/business-routes.ts +32 -45
  135. package/template/apps/web/src/router/business-routes.webtest.ts +40 -44
  136. package/template/apps/web/src/router/index.ts +64 -54
  137. package/template/apps/web/src/router/manifest-routes.ts +27 -7
  138. package/template/apps/web/src/router/manifest-routes.webtest.ts +10 -2
  139. package/template/apps/web/src/router/platform-admin-route-bindings.ts +58 -0
  140. package/template/apps/web/src/router/reserved-route-paths.ts +14 -0
  141. package/template/apps/web/src/router/shared-scaffold-neutrality.webtest.ts +10 -4
  142. package/template/apps/web/src/router/web-composition-host.ts +20 -0
  143. package/template/apps/web/src/services/work-order-api.ts +16 -0
  144. package/template/apps/web/src/services/work-order-api.webtest.ts +21 -0
  145. package/template/apps/web/src/virtual-admin-ui-modules.d.ts +5 -0
  146. package/template/apps/web/vite.config.ts +22 -1
  147. package/template/docs/architecture/migration-bundle.md +10 -7
  148. package/template/docs/architecture/module-manifest-composition.md +85 -3
  149. package/template/docs/architecture/search-provider-protocol.md +6 -1
  150. package/template/docs/architecture/server-module-interface.md +70 -7
  151. package/template/docs/development/business-web-routes.md +53 -50
  152. package/template/docs/development/module-lifecycle.md +48 -54
  153. package/template/docs/platform/agent-runtime.md +2 -0
  154. package/template/docs/platform/navigation-registry.md +5 -4
  155. package/template/package.json +1 -1
  156. package/template/packages/config/package.json +1 -1
  157. package/template/packages/database/package.json +1 -1
  158. package/template/packages/example-work-order/package.json +6 -0
  159. package/template/packages/example-work-order/src/admin-page.ts +78 -0
  160. package/template/packages/example-work-order/src/admin.ts +35 -0
  161. package/template/packages/example-work-order/src/manifest.test.ts +62 -5
  162. package/template/packages/example-work-order/src/manifest.ts +54 -7
  163. package/template/packages/example-work-order/src/migration-bundle.ts +8 -0
  164. package/template/packages/example-work-order/src/server-module.ts +2 -2
  165. package/template/packages/jobs/package.json +1 -1
  166. package/template/packages/modules/index.ts +4 -0
  167. package/template/packages/modules/package.json +3 -2
  168. package/template/packages/modules/src/admin-ui-entry.ts +8 -0
  169. package/template/packages/modules/src/admin-ui-manifest.test.ts +52 -0
  170. package/template/packages/modules/src/admin-ui-manifest.ts +53 -0
  171. package/template/packages/modules/src/capability-audit-validation.test.ts +127 -0
  172. package/template/packages/modules/src/capability-audit-validation.ts +38 -0
  173. package/template/packages/modules/src/capability-guard-validation.ts +148 -0
  174. package/template/packages/modules/src/capability-key-validation.test.ts +150 -0
  175. package/template/packages/modules/src/composition-fixtures.test-support.ts +7 -5
  176. package/template/packages/modules/src/composition.test.ts +74 -9
  177. package/template/packages/modules/src/composition.ts +8 -0
  178. package/template/packages/modules/src/manifest.ts +1 -0
  179. package/template/packages/modules/src/migration-bundle-validation.test.ts +23 -0
  180. package/template/packages/modules/src/module-host-range.test.ts +57 -0
  181. package/template/packages/modules/src/module-host-range.ts +125 -0
  182. package/template/packages/modules/src/project-module-assembly.test.ts +133 -0
  183. package/template/packages/modules/src/project-module-assembly.ts +126 -0
  184. package/template/packages/modules/src/system-admin-routes.ts +6 -5
  185. package/template/packages/modules/src/system-admin-ui-catalog.ts +72 -0
  186. package/template/packages/modules/src/system-agent-tools.ts +2 -2
  187. package/template/packages/modules/src/system-api-binding.ts +43 -9
  188. package/template/packages/modules/src/system-api-control-plane.ts +202 -0
  189. package/template/packages/modules/src/system-api-data-governance.ts +4 -2
  190. package/template/packages/modules/src/system-api-operational.ts +57 -8
  191. package/template/packages/modules/src/system-api-permissions.ts +115 -21
  192. package/template/packages/modules/src/system-api-watermark.ts +3 -0
  193. package/template/packages/modules/src/system-audit-actions.ts +7 -0
  194. package/template/packages/modules/src/system-data-governance.ts +5 -2
  195. package/template/packages/modules/src/system-features.ts +31 -0
  196. package/template/packages/modules/src/system-governed-export-bindings.ts +8 -4
  197. package/template/packages/modules/src/system-module-catalog.test.ts +39 -4
  198. package/template/packages/modules/src/system-module-catalog.ts +68 -7
  199. package/template/packages/modules/src/system-module-tasks.ts +8 -1
  200. package/template/packages/modules/src/system-modules-access.test.ts +4 -0
  201. package/template/packages/modules/src/system-modules.test.ts +10 -2
  202. package/template/packages/modules/src/system-modules.ts +6 -5
  203. package/template/packages/modules/src/system-permissions.ts +4 -0
  204. package/template/packages/modules/user/module.ts +19 -2
  205. package/template/packages/server-sdk/index.ts +8 -0
  206. package/template/packages/server-sdk/package.json +2 -1
  207. package/template/packages/server-sdk/src/durable-handler.ts +16 -1
  208. package/template/packages/server-sdk/src/module-adapter.test.ts +44 -0
  209. package/template/packages/server-sdk/src/module-adapter.ts +59 -0
  210. package/template/packages/server-sdk/src/route-definition.ts +3 -2
  211. package/template/packages/shared/package.json +1 -1
  212. package/template/packages/shared/src/audit.test.ts +7 -0
  213. package/template/packages/shared/src/audit.ts +16 -0
  214. package/template/packages/shared/src/migration-bundle-validation.ts +9 -11
  215. package/template/packages/shared/src/rbac.ts +2 -0
  216. package/template/apps/web/src/app/business-routes.ts +0 -22
@@ -0,0 +1,55 @@
1
+ import type { ProjectModuleAssembly } from "@southwind-ai/modules/admin-ui";
2
+
3
+ interface AdminUiModuleRegistryPlugin {
4
+ readonly name: string;
5
+ readonly enforce: "pre";
6
+ resolveId(id: string): string | undefined;
7
+ load(id: string): string | undefined;
8
+ }
9
+
10
+ export const adminUiModuleRegistryId = "virtual:windy-admin-ui-modules";
11
+ const resolvedRegistryId = `\0${adminUiModuleRegistryId}`;
12
+
13
+ export function adminUiModuleRegistryPlugin(
14
+ assembly: ProjectModuleAssembly,
15
+ ): AdminUiModuleRegistryPlugin {
16
+ const source = renderAdminUiModuleRegistry(assembly);
17
+ return {
18
+ name: "windy-admin-ui-module-registry",
19
+ enforce: "pre",
20
+ resolveId(id) {
21
+ return id === adminUiModuleRegistryId ? resolvedRegistryId : undefined;
22
+ },
23
+ load(id) {
24
+ return id === resolvedRegistryId ? source : undefined;
25
+ },
26
+ };
27
+ }
28
+
29
+ export function renderAdminUiModuleRegistry(
30
+ assembly: ProjectModuleAssembly,
31
+ ): string {
32
+ const packages = enabledPackageRegistrations(assembly);
33
+ const imports = packages.map(
34
+ ({ package: packageName }, index) =>
35
+ `import { windyAdminUiModule as module${index} } from ${JSON.stringify(
36
+ `${packageName}/admin`,
37
+ )};`,
38
+ );
39
+ const identifiers = packages.map((_, index) => `module${index}`).join(", ");
40
+ return [
41
+ ...imports,
42
+ `export const installedAdminUiModules = [${identifiers}];`,
43
+ "",
44
+ ].join("\n");
45
+ }
46
+
47
+ function enabledPackageRegistrations(assembly: ProjectModuleAssembly) {
48
+ return assembly.modules.filter(
49
+ (
50
+ entry,
51
+ ): entry is typeof entry & {
52
+ readonly package: string;
53
+ } => entry.enabled && entry.package !== null,
54
+ );
55
+ }
@@ -0,0 +1,46 @@
1
+ import { describe, expect, test } from "vitest";
2
+ import { validateProjectModuleAssembly } from "@southwind-ai/modules/admin-ui";
3
+ import { renderAdminUiModuleRegistry } from "./admin-ui-module-registry-plugin";
4
+
5
+ describe("Admin UI package 虚拟注册表", () => {
6
+ test("只静态导入已启用 package 的标准 ./admin 导出", () => {
7
+ const source = renderAdminUiModuleRegistry({
8
+ schemaVersion: 1,
9
+ modules: [
10
+ entry("enabled", "@customer/enabled", true, 100),
11
+ entry("disabled", "@customer/disabled", false, 200),
12
+ entry("system", null, true, 300),
13
+ ],
14
+ });
15
+
16
+ expect(source).toContain('from "@customer/enabled/admin";');
17
+ expect(source).not.toContain("@customer/disabled");
18
+ expect(source).not.toContain("system/admin");
19
+ expect(source).toContain("windyAdminUiModule as module0");
20
+ });
21
+
22
+ test("无效装配清单在 Vite 解析前 fail closed", () => {
23
+ expect(() =>
24
+ validateProjectModuleAssembly({
25
+ schemaVersion: 1,
26
+ modules: [entry("one", "@customer/one", true, -1)],
27
+ }),
28
+ ).toThrow("order 无效");
29
+ });
30
+ });
31
+
32
+ function entry(
33
+ name: string,
34
+ packageName: string | null,
35
+ enabled: boolean,
36
+ order: number,
37
+ ) {
38
+ return {
39
+ name,
40
+ package: packageName,
41
+ enabled,
42
+ order,
43
+ admin: { visible: true },
44
+ config: {},
45
+ };
46
+ }
@@ -1,23 +1,39 @@
1
1
  import type { RouteRecordRaw } from "vue-router";
2
- import type { BusinessAdminRouteRegistration } from "@/app/business-routes";
2
+ import type { AdminUiModule } from "./admin-ui-composition";
3
+ import { isolateAdminPageLoader } from "./admin-extension-loader";
3
4
 
4
5
  export function materializeBusinessAdminRoutes(
5
- registrations: readonly BusinessAdminRouteRegistration[],
6
+ modules: readonly AdminUiModule[],
6
7
  ): RouteRecordRaw[] {
7
- assertBusinessAdminRouteRegistrations(registrations);
8
- return registrations.map((registration) => ({
9
- path: registration.path,
10
- name: registration.name,
11
- component: registration.component,
12
- props: registration.props,
13
- meta: {
14
- title: registration.title,
15
- layout: "admin",
16
- businessRouteKey: registration.key,
17
- featureKey: registration.featureKey,
18
- permissionKey: registration.permissionKey,
19
- },
20
- }));
8
+ return modules.flatMap((module) =>
9
+ module.manifest.adminRoutes.map((definition) => {
10
+ const binding = module.pages.find(
11
+ ({ routeKey }) => routeKey === definition.key,
12
+ );
13
+ if (!binding) {
14
+ throw new Error(
15
+ `${module.manifest.name} 缺少 Admin Route 页面绑定:${definition.key}`,
16
+ );
17
+ }
18
+ const guarded = definition.directAccess !== "authenticated";
19
+ return {
20
+ path: definition.path,
21
+ name: definition.key,
22
+ component: isolateAdminPageLoader(
23
+ module.manifest.name,
24
+ definition.key,
25
+ binding.load,
26
+ ),
27
+ meta: {
28
+ title: definition.title,
29
+ layout: "admin",
30
+ businessRouteKey: definition.key,
31
+ featureKey: guarded ? definition.featureKey : undefined,
32
+ permissionKey: guarded ? definition.permissionKey : undefined,
33
+ },
34
+ };
35
+ }),
36
+ );
21
37
  }
22
38
 
23
39
  export function assertNoRouteCollisions(
@@ -37,32 +53,3 @@ export function assertNoRouteCollisions(
37
53
  names.add(route.name);
38
54
  }
39
55
  }
40
-
41
- function assertBusinessAdminRouteRegistrations(
42
- registrations: readonly BusinessAdminRouteRegistration[],
43
- ): void {
44
- const keys = new Set<string>();
45
- for (const registration of registrations) {
46
- for (const [field, value] of [
47
- ["key", registration.key],
48
- ["path", registration.path],
49
- ["title", registration.title],
50
- ["featureKey", registration.featureKey],
51
- ["permissionKey", registration.permissionKey],
52
- ] as const) {
53
- if (!value.trim()) {
54
- throw new Error(`业务 Web Route 的 ${field} 不能为空`);
55
- }
56
- }
57
- if (!registration.path.startsWith("/")) {
58
- throw new Error(`业务 Web Route 必须使用绝对路径:${registration.path}`);
59
- }
60
- if (registration.path.includes(":pathMatch")) {
61
- throw new Error(`业务 Web Route 不能注册兜底路径:${registration.path}`);
62
- }
63
- if (keys.has(registration.key)) {
64
- throw new Error(`业务 Web Route Key 重复:${registration.key}`);
65
- }
66
- keys.add(registration.key);
67
- }
68
- }
@@ -3,24 +3,17 @@ import {
3
3
  assertNoRouteCollisions,
4
4
  materializeBusinessAdminRoutes,
5
5
  } from "./business-routes";
6
+ import type { AdminUiModule } from "./admin-ui-composition";
6
7
 
7
8
  describe("project-owned 业务路由接缝", () => {
8
- test("统一生成带 Feature 与 RBAC Guard 的管理路由", () => {
9
- const [route] = materializeBusinessAdminRoutes([
10
- {
11
- key: "case.record",
12
- path: "/cases",
13
- name: "case-records",
14
- title: "案件",
15
- component: {},
16
- featureKey: "case.record",
17
- permissionKey: "case.record.read",
18
- },
19
- ]);
9
+ test("只用 routeKey 和隔离后的 lazy loader 绑定 Manifest 路由", async () => {
10
+ const load = async () => ({ default: {} });
11
+ const [route] = materializeBusinessAdminRoutes([adminUiModule(load)]);
20
12
 
21
13
  expect(route).toMatchObject({
22
14
  path: "/cases",
23
- name: "case-records",
15
+ name: "case.record",
16
+ component: expect.any(Function),
24
17
  meta: {
25
18
  title: "案件",
26
19
  layout: "admin",
@@ -29,32 +22,21 @@ describe("project-owned 业务路由接缝", () => {
29
22
  permissionKey: "case.record.read",
30
23
  },
31
24
  });
25
+ expect(
26
+ await (route!.component as AdminUiModule["pages"][number]["load"])(),
27
+ ).toEqual({ default: {} });
32
28
  });
33
29
 
34
- test("拒绝空 Guard、重复 Key 与兜底路径", () => {
35
- expect(() =>
36
- materializeBusinessAdminRoutes([
37
- {
38
- key: "case.record",
39
- path: "/cases",
40
- title: "案件",
41
- component: {},
42
- featureKey: "",
43
- permissionKey: "case.record.read",
44
- },
45
- ]),
46
- ).toThrow("featureKey 不能为空");
47
- expect(() =>
48
- materializeBusinessAdminRoutes([
49
- registration("/cases"),
50
- registration("/other"),
51
- ]),
52
- ).toThrow("业务 Web Route Key 重复");
53
- expect(() =>
54
- materializeBusinessAdminRoutes([
55
- registration("/:pathMatch(.*)*"),
56
- ]),
57
- ).toThrow("不能注册兜底路径");
30
+ test("authenticated 路由不附加 Feature 与 Permission Guard", () => {
31
+ const module = adminUiModule(
32
+ async () => ({ default: {} }),
33
+ "authenticated",
34
+ );
35
+
36
+ const [route] = materializeBusinessAdminRoutes([module]);
37
+
38
+ expect(route?.meta?.featureKey).toBeUndefined();
39
+ expect(route?.meta?.permissionKey).toBeUndefined();
58
40
  });
59
41
 
60
42
  test("组合时拒绝与平台路由重复的路径或名称", () => {
@@ -73,13 +55,27 @@ describe("project-owned 业务路由接缝", () => {
73
55
  });
74
56
  });
75
57
 
76
- function registration(path: string) {
58
+ function adminUiModule(
59
+ load: AdminUiModule["pages"][number]["load"],
60
+ directAccess: "guarded" | "authenticated" = "guarded",
61
+ ): AdminUiModule {
77
62
  return {
78
- key: "case.record",
79
- path,
80
- title: "案件",
81
- component: {},
82
- featureKey: "case.record",
83
- permissionKey: "case.record.read",
63
+ hostRange: "^0.1.0",
64
+ manifest: {
65
+ name: "case",
66
+ version: "1.2.3",
67
+ adminRoutes: [
68
+ {
69
+ key: "case.record",
70
+ path: "/cases",
71
+ title: "案件",
72
+ surface: "sidebar",
73
+ directAccess,
74
+ featureKey: "case.record",
75
+ permissionKey: "case.record.read",
76
+ },
77
+ ],
78
+ },
79
+ pages: [{ routeKey: "case.record", load }],
84
80
  };
85
81
  }
@@ -4,9 +4,9 @@ import {
4
4
  type RouterHistory,
5
5
  type RouteRecordRaw,
6
6
  } from "vue-router";
7
+ import type { Component } from "vue";
7
8
  import DashboardPage from "@/pages/dashboard/DashboardPage.vue";
8
9
  import HomePage from "@/app/home/HomePage.vue";
9
- import { installedBusinessAdminRoutes } from "@/app/business-routes";
10
10
  import { loginPageComponent } from "@/app/auth/login-page";
11
11
  import { errorPageComponents } from "@/app/error-pages";
12
12
  import AccessDeniedPage from "@/pages/AccessDeniedPage.vue";
@@ -29,7 +29,6 @@ import PersonalSettingsPage from "@/pages/profile/PersonalSettingsPage.vue";
29
29
  // @windy-module system.configuration begin
30
30
  import ModuleConfigurationPage from "@/pages/configuration/ModuleConfigurationPage.vue";
31
31
  // @windy-module system.configuration end
32
- import type { SystemResourceKey } from "@/pages/system/resource-config";
33
32
  import { useAccessSnapshot } from "@/composables/useAccessSnapshot";
34
33
  import {
35
34
  useAuthSession,
@@ -50,6 +49,12 @@ import {
50
49
  manifestAdminRoute,
51
50
  validateWebModuleComposition,
52
51
  } from "./manifest-routes";
52
+ import {
53
+ platformAdminRouteBindings,
54
+ type PlatformAdminPageKey,
55
+ } from "./platform-admin-route-bindings";
56
+ import { reservedWebRoutePaths } from "./reserved-route-paths";
57
+ import { webAdminUiComposition } from "./web-composition-host";
53
58
 
54
59
  export interface RouterAccessProvider {
55
60
  refresh(): Promise<WebAccessSnapshot>;
@@ -66,46 +71,70 @@ export interface RouterAuthProvider {
66
71
  clear(): void;
67
72
  }
68
73
 
69
- const systemRoute = (
70
- key: string,
71
- resourceKey: SystemResourceKey,
72
- ): RouteRecordRaw =>
73
- manifestAdminRoute(key, {
74
- component: SystemResourceRoutePage,
75
- props: { resourceKey },
74
+ const platformAdminPageComponents: Partial<
75
+ Record<PlatformAdminPageKey, Component>
76
+ > = {
77
+ resource: SystemResourceRoutePage,
78
+ profile: PersonalSettingsPage,
79
+ // @windy-module system.settings begin
80
+ settings: GlobalSettingsPage,
81
+ // @windy-module system.settings end
82
+ // @windy-module system.license begin
83
+ license: LicensePage,
84
+ // @windy-module system.license end
85
+ // @windy-module system.notification begin
86
+ notification: SystemNotificationsPage,
87
+ // @windy-module system.notification end
88
+ // @windy-module system.operations begin
89
+ operations: SystemOperationsPage,
90
+ // @windy-module system.operations end
91
+ // @windy-module system.configuration begin
92
+ configuration: ModuleConfigurationPage,
93
+ // @windy-module system.configuration end
94
+ };
95
+
96
+ const platformAdminRoutes = platformAdminRouteBindings.map((binding) => {
97
+ const component = platformAdminPageComponents[binding.page];
98
+ if (!component) {
99
+ throw new Error(`平台 Admin 页面未绑定:${binding.routeKey}`);
100
+ }
101
+ return manifestAdminRoute(binding.routeKey, {
102
+ component,
103
+ name: binding.name,
104
+ ...(binding.resourceKey
105
+ ? { props: { resourceKey: binding.resourceKey } }
106
+ : {}),
76
107
  });
108
+ });
77
109
 
78
- export const routes: RouteRecordRaw[] = [
110
+ const declaredRoutes: RouteRecordRaw[] = [
79
111
  {
80
- path: "/login",
112
+ path: reservedWebRoutePaths.login,
81
113
  name: "login",
82
114
  component: loginPageComponent,
83
115
  meta: { title: "登录", layout: "auth", public: true, guestOnly: true },
84
116
  },
85
117
  {
86
- path: "/change-password",
118
+ path: reservedWebRoutePaths.changePassword,
87
119
  name: "change-password",
88
120
  component: ChangePasswordPage,
89
121
  meta: { title: "修改密码", layout: "auth" },
90
122
  },
91
123
  {
92
- path: "/",
124
+ path: reservedWebRoutePaths.home,
93
125
  name: "home",
94
126
  component: HomePage,
95
127
  meta: { title: "首页", layout: "app" },
96
128
  },
97
129
  {
98
- path: "/admin",
130
+ path: reservedWebRoutePaths.adminHome,
99
131
  name: "admin-home",
100
132
  component: DashboardPage,
101
133
  meta: { title: "工作台", layout: "admin" },
102
134
  },
103
- // @windy-module system.settings begin
104
- manifestAdminRoute("system.settings", { component: GlobalSettingsPage }),
105
- // @windy-module system.settings end
106
- manifestAdminRoute("system.profile", { component: PersonalSettingsPage }),
135
+ ...platformAdminRoutes,
107
136
  {
108
- path: "/access-denied",
137
+ path: reservedWebRoutePaths.accessDenied,
109
138
  name: "access-denied",
110
139
  component: AccessDeniedPage,
111
140
  props: (route) => ({
@@ -115,7 +144,7 @@ export const routes: RouteRecordRaw[] = [
115
144
  meta: { title: "无法访问", layout: "auth" },
116
145
  },
117
146
  {
118
- path: "/service-unavailable",
147
+ path: reservedWebRoutePaths.serviceUnavailable,
119
148
  name: "service-unavailable",
120
149
  component: ServiceUnavailablePage,
121
150
  props: (route) => ({
@@ -125,7 +154,7 @@ export const routes: RouteRecordRaw[] = [
125
154
  meta: { title: "服务暂不可用", layout: "auth", public: true },
126
155
  },
127
156
  {
128
- path: "/error/internal",
157
+ path: reservedWebRoutePaths.internalError,
129
158
  name: "internal-error",
130
159
  component: errorPageComponents.internal,
131
160
  props: (route) => ({
@@ -141,7 +170,7 @@ export const routes: RouteRecordRaw[] = [
141
170
  },
142
171
  },
143
172
  {
144
- path: "/error/maintenance",
173
+ path: reservedWebRoutePaths.maintenance,
145
174
  name: "maintenance",
146
175
  component: errorPageComponents.maintenance,
147
176
  props: (route) => ({
@@ -157,7 +186,7 @@ export const routes: RouteRecordRaw[] = [
157
186
  },
158
187
  },
159
188
  {
160
- path: "/error/network",
189
+ path: reservedWebRoutePaths.networkError,
161
190
  name: "network-error",
162
191
  component: errorPageComponents.network,
163
192
  props: (route) => ({
@@ -172,37 +201,9 @@ export const routes: RouteRecordRaw[] = [
172
201
  errorPage: true,
173
202
  },
174
203
  },
175
- systemRoute("system.user", "users"),
176
- systemRoute("system.role", "roles"),
177
- systemRoute("system.department", "departments"),
178
- systemRoute("system.menu", "menus"),
179
- systemRoute("system.dictionary", "dictionaries"),
180
- systemRoute("system.feature", "features"),
181
- // @windy-module system.license begin
182
- manifestAdminRoute("system.license", {
183
- name: "runtime-license",
184
- component: LicensePage,
185
- }),
186
- // @windy-module system.license end
187
- // @windy-module system.audit begin
188
- systemRoute("system.audit", "auditLogs"),
189
- // @windy-module system.audit end
190
- // @windy-module system.notification begin
191
- manifestAdminRoute("system.notification", {
192
- component: SystemNotificationsPage,
193
- }),
194
- // @windy-module system.notification end
195
- // @windy-module system.operations begin
196
- manifestAdminRoute("system.ops", { component: SystemOperationsPage }),
197
- // @windy-module system.operations end
198
- // @windy-module system.configuration begin
199
- manifestAdminRoute("system.config", {
200
- component: ModuleConfigurationPage,
201
- }),
202
- // @windy-module system.configuration end
203
- ...materializeBusinessAdminRoutes(installedBusinessAdminRoutes),
204
+ ...materializeBusinessAdminRoutes(webAdminUiComposition.modules),
204
205
  {
205
- path: "/:pathMatch(.*)*",
206
+ path: reservedWebRoutePaths.notFound,
206
207
  name: "not-found",
207
208
  component: errorPageComponents.notFound,
208
209
  props: (route) => ({
@@ -218,8 +219,17 @@ export const routes: RouteRecordRaw[] = [
218
219
  },
219
220
  ];
220
221
 
222
+ validateWebModuleComposition(declaredRoutes);
223
+
224
+ export const routes = declaredRoutes.filter((route) => {
225
+ const routeKey = route.meta?.manifestRouteKey;
226
+ return (
227
+ typeof routeKey !== "string" ||
228
+ webAdminUiComposition.activePlatformRouteKeys.has(routeKey)
229
+ );
230
+ });
231
+
221
232
  assertNoRouteCollisions(routes);
222
- validateWebModuleComposition(routes);
223
233
 
224
234
  export function createAppRouter(
225
235
  history: RouterHistory = createWebHistory(),
@@ -1,12 +1,11 @@
1
1
  import {
2
- systemModuleManifests,
3
- validateModuleComposition,
2
+ systemAdminUiManifests,
4
3
  type ModuleAdminRouteDefinition,
5
- } from "@southwind-ai/modules";
4
+ } from "@southwind-ai/modules/admin-ui";
6
5
  import type { Component } from "vue";
7
6
  import type { RouteRecordRaw } from "vue-router";
8
7
 
9
- export const webModuleManifests = systemModuleManifests();
8
+ export const webModuleManifests = systemAdminUiManifests();
10
9
 
11
10
  const adminRouteByKey = new Map(
12
11
  webModuleManifests.flatMap((module) =>
@@ -44,12 +43,33 @@ export function manifestAdminRoute(
44
43
  export function validateWebModuleComposition(
45
44
  routes: readonly RouteRecordRaw[],
46
45
  ): void {
47
- validateModuleComposition(webModuleManifests, {
48
- adminRoutes: routes.flatMap((route) => {
46
+ validatePlatformAdminRouteKeys(
47
+ routes.flatMap((route) => {
49
48
  const key = route.meta?.manifestRouteKey;
50
49
  return typeof key === "string" ? [key] : [];
51
50
  }),
52
- });
51
+ );
52
+ }
53
+
54
+ export function validatePlatformAdminRouteKeys(
55
+ implementedKeys: readonly string[],
56
+ ): void {
57
+ const declared = new Set(adminRouteByKey.keys());
58
+ const implemented = new Set<string>();
59
+ for (const key of implementedKeys) {
60
+ if (!declared.has(key)) {
61
+ throw new Error(`Admin Route 实现未在窄 Manifest 声明:${key}`);
62
+ }
63
+ if (implemented.has(key)) {
64
+ throw new Error(`Admin Route Handler Key 重复:${key}`);
65
+ }
66
+ implemented.add(key);
67
+ }
68
+ for (const key of declared) {
69
+ if (!implemented.has(key)) {
70
+ throw new Error(`Admin Route ${key} 缺少 Handler`);
71
+ }
72
+ }
53
73
  }
54
74
 
55
75
  function findAdminRoute(key: string): ModuleAdminRouteDefinition {
@@ -47,7 +47,15 @@ describe("Manifest 菜单与 Router 契约", () => {
47
47
  expect(definitions.some(({ path }) => path === "/admin/profile")).toBe(
48
48
  true,
49
49
  );
50
- expect(definitions.some(({ path }) => path === "/work-orders")).toBe(false);
51
- expect(routes.some(({ path }) => path === "/work-orders")).toBe(false);
50
+ expect(definitions.some(({ path }) => path === "/admin/work-orders")).toBe(
51
+ false,
52
+ );
53
+ // @windy-module work-order begin
54
+ expect(
55
+ routes.find(
56
+ ({ meta }) => meta?.businessRouteKey === "work-order.ticket.list",
57
+ )?.path,
58
+ ).toBe("/admin/work-orders");
59
+ // @windy-module work-order end
52
60
  });
53
61
  });
@@ -0,0 +1,58 @@
1
+ import type { SystemResourceKey } from "@/pages/system/resource-config";
2
+
3
+ export type PlatformAdminPageKey =
4
+ | "resource"
5
+ | "settings"
6
+ | "profile"
7
+ | "license"
8
+ | "notification"
9
+ | "operations"
10
+ | "configuration";
11
+
12
+ export interface PlatformAdminRouteBinding {
13
+ readonly routeKey: string;
14
+ readonly page: PlatformAdminPageKey;
15
+ readonly resourceKey?: SystemResourceKey;
16
+ readonly name?: string;
17
+ }
18
+
19
+ export const platformAdminRouteBindings: readonly PlatformAdminRouteBinding[] =
20
+ [
21
+ { routeKey: "system.profile", page: "profile" },
22
+ // @windy-module system.settings begin
23
+ { routeKey: "system.settings", page: "settings" },
24
+ // @windy-module system.settings end
25
+ { routeKey: "system.user", page: "resource", resourceKey: "users" },
26
+ { routeKey: "system.role", page: "resource", resourceKey: "roles" },
27
+ {
28
+ routeKey: "system.department",
29
+ page: "resource",
30
+ resourceKey: "departments",
31
+ },
32
+ { routeKey: "system.menu", page: "resource", resourceKey: "menus" },
33
+ {
34
+ routeKey: "system.dictionary",
35
+ page: "resource",
36
+ resourceKey: "dictionaries",
37
+ },
38
+ { routeKey: "system.feature", page: "resource", resourceKey: "features" },
39
+ // @windy-module system.license begin
40
+ {
41
+ routeKey: "system.license",
42
+ page: "license",
43
+ name: "runtime-license",
44
+ },
45
+ // @windy-module system.license end
46
+ // @windy-module system.audit begin
47
+ { routeKey: "system.audit", page: "resource", resourceKey: "auditLogs" },
48
+ // @windy-module system.audit end
49
+ // @windy-module system.notification begin
50
+ { routeKey: "system.notification", page: "notification" },
51
+ // @windy-module system.notification end
52
+ // @windy-module system.operations begin
53
+ { routeKey: "system.ops", page: "operations" },
54
+ // @windy-module system.operations end
55
+ // @windy-module system.configuration begin
56
+ { routeKey: "system.config", page: "configuration" },
57
+ // @windy-module system.configuration end
58
+ ];
@@ -0,0 +1,14 @@
1
+ export const reservedWebRoutePaths = {
2
+ login: "/login",
3
+ changePassword: "/change-password",
4
+ home: "/",
5
+ adminHome: "/admin",
6
+ accessDenied: "/access-denied",
7
+ serviceUnavailable: "/service-unavailable",
8
+ internalError: "/error/internal",
9
+ maintenance: "/error/maintenance",
10
+ networkError: "/error/network",
11
+ notFound: "/:pathMatch(.*)*",
12
+ } as const;
13
+
14
+ export const reservedWebRoutePathList = Object.values(reservedWebRoutePaths);