create-windy 0.3.19 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (234) 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/server/Dockerfile +1 -0
  9. package/template/apps/server/package.json +1 -0
  10. package/template/apps/server/scripts/validate-module-composition-once.ts +4 -0
  11. package/template/apps/server/scripts/validate-module-composition.ts +28 -0
  12. package/template/apps/server/src/agent/data-scope-routes.test.ts +20 -5
  13. package/template/apps/server/src/agent/routes.test.ts +20 -7
  14. package/template/apps/server/src/agent/routes.ts +14 -0
  15. package/template/apps/server/src/agent/runtime-bootstrap.ts +22 -6
  16. package/template/apps/server/src/agent/sandbox-worker.ts +2 -2
  17. package/template/apps/server/src/agent/unavailable-routes.ts +33 -0
  18. package/template/apps/server/src/app/server-composition.ts +56 -0
  19. package/template/apps/server/src/application-services.ts +2 -0
  20. package/template/apps/server/src/bulk-data/service.ts +14 -0
  21. package/template/apps/server/src/data-governance/export/authorization-port.ts +1 -1
  22. package/template/apps/server/src/data-governance/export/postgres-api-fixture.ts +18 -2
  23. package/template/apps/server/src/data-governance/export/postgres-recovery.integration.test.ts +10 -9
  24. package/template/apps/server/src/data-governance/export/routes-test-fixture.ts +6 -8
  25. package/template/apps/server/src/data-governance/user-plaintext-routes.test.ts +4 -2
  26. package/template/apps/server/src/feature/routes.authorization.test.ts +26 -1
  27. package/template/apps/server/src/feature/routes.ts +33 -15
  28. package/template/apps/server/src/foundation-activation.test.ts +60 -0
  29. package/template/apps/server/src/foundation.ts +64 -5
  30. package/template/apps/server/src/guards.test.ts +23 -0
  31. package/template/apps/server/src/guards.ts +12 -0
  32. package/template/apps/server/src/index.ts +28 -34
  33. package/template/apps/server/src/installed-business-modules.ts +13 -24
  34. package/template/apps/server/src/jobs/legacy-adapter.integration.test.ts +1 -0
  35. package/template/apps/server/src/jobs/legacy-adapter.test.ts +106 -0
  36. package/template/apps/server/src/jobs/legacy-service-runtime.ts +40 -0
  37. package/template/apps/server/src/license/activation.test.ts +30 -0
  38. package/template/apps/server/src/license/activation.ts +26 -0
  39. package/template/apps/server/src/license/restriction.test.ts +16 -0
  40. package/template/apps/server/src/license/routes-offline-code.test.ts +6 -2
  41. package/template/apps/server/src/license/routes.ts +20 -4
  42. package/template/apps/server/src/license/runtime-service.test.ts +10 -2
  43. package/template/apps/server/src/module-bootstrap.ts +14 -23
  44. package/template/apps/server/src/module-composition.test.ts +43 -2
  45. package/template/apps/server/src/module-composition.ts +29 -0
  46. package/template/apps/server/src/module-host/durable-context-guard.test.ts +172 -0
  47. package/template/apps/server/src/module-host/durable-context-unavailable.test.ts +50 -1
  48. package/template/apps/server/src/module-host/durable-context.test.ts +42 -25
  49. package/template/apps/server/src/module-host/durable-context.ts +42 -2
  50. package/template/apps/server/src/module-host/durable-handler-registration.ts +96 -5
  51. package/template/apps/server/src/module-host/implementation-completeness.test.ts +185 -0
  52. package/template/apps/server/src/module-host/implementation-completeness.ts +132 -0
  53. package/template/apps/server/src/module-host/initialize-contracts.ts +14 -0
  54. package/template/apps/server/src/module-host/initialize.test.ts +78 -2
  55. package/template/apps/server/src/module-host/initialize.ts +15 -4
  56. package/template/apps/server/src/module-host/project-attachments.test.ts +92 -0
  57. package/template/apps/server/src/module-host/project-attachments.ts +58 -0
  58. package/template/apps/server/src/module-host/project-composition-package.test.ts +186 -0
  59. package/template/apps/server/src/module-host/project-composition.test.ts +260 -0
  60. package/template/apps/server/src/module-host/project-composition.ts +311 -0
  61. package/template/apps/server/src/module-host/request-context.ts +3 -0
  62. package/template/apps/server/src/module-host/route-declaration.ts +15 -6
  63. package/template/apps/server/src/module-host/route-installer.test.ts +15 -2
  64. package/template/apps/server/src/module-host/route-installer.ts +12 -2
  65. package/template/apps/server/src/module-host/route-registration.test.ts +13 -4
  66. package/template/apps/server/src/module-host/runtime-composition-compatibility.test.ts +63 -0
  67. package/template/apps/server/src/module-host/runtime-composition-compatibility.ts +46 -0
  68. package/template/apps/server/src/module-host/runtime-composition.ts +34 -0
  69. package/template/apps/server/src/module-host/search-provider-registration.test.ts +64 -0
  70. package/template/apps/server/src/module-host/search-provider-registration.ts +35 -0
  71. package/template/apps/server/src/module-host/static-route-authorization.test.ts +99 -0
  72. package/template/apps/server/src/module-host/static-route-authorization.ts +90 -0
  73. package/template/apps/server/src/module-host/task-definitions.ts +1 -0
  74. package/template/apps/server/src/module-host/user-directory-composition.test.ts +4 -1
  75. package/template/apps/server/src/module-host.test.ts +2 -0
  76. package/template/apps/server/src/module-preflight.test.ts +289 -0
  77. package/template/apps/server/src/module-preflight.ts +96 -0
  78. package/template/apps/server/src/module-runtime-validation.ts +1 -0
  79. package/template/apps/server/src/platform-app-bootstrap.ts +50 -0
  80. package/template/apps/server/src/platform-http-runtime.ts +32 -7
  81. package/template/apps/server/src/route-guards.test.ts +22 -0
  82. package/template/apps/server/src/route-guards.ts +10 -3
  83. package/template/apps/server/src/runtime-audit-query.ts +50 -0
  84. package/template/apps/server/src/runtime-feature.test.ts +46 -0
  85. package/template/apps/server/src/runtime-feature.ts +5 -0
  86. package/template/apps/server/src/runtime.ts +10 -49
  87. package/template/apps/server/src/scheduler/audit-action.test.ts +49 -0
  88. package/template/apps/server/src/scheduler/audit.ts +1 -1
  89. package/template/apps/server/src/scheduler/execution-recorder.ts +10 -5
  90. package/template/apps/server/src/scheduler/recovery-service.test.ts +4 -3
  91. package/template/apps/server/src/scheduler/service-idle.test.ts +4 -3
  92. package/template/apps/server/src/scheduler/service.test.ts +1 -0
  93. package/template/apps/server/src/scheduler/service.ts +18 -21
  94. package/template/apps/server/src/scheduler/types.ts +5 -1
  95. package/template/apps/server/src/settings/drizzle-watermark-repository.integration.test.ts +4 -0
  96. package/template/apps/server/src/settings/drizzle-watermark-repository.ts +11 -3
  97. package/template/apps/server/src/settings/watermark-routes.test.ts +54 -1
  98. package/template/apps/server/src/settings/watermark-routes.ts +29 -1
  99. package/template/apps/server/src/startup-composition-validation.test.ts +35 -0
  100. package/template/apps/server/src/startup-composition-validation.ts +54 -0
  101. package/template/apps/server/src/startup-preflight.test.ts +36 -0
  102. package/template/apps/server/src/startup-preflight.ts +37 -0
  103. package/template/apps/server/src/system/audit-routes.test.ts +9 -3
  104. package/template/apps/server/src/system/department-routes.test.ts +5 -3
  105. package/template/apps/server/src/system/feature-routes.test.ts +4 -3
  106. package/template/apps/server/src/system/interface-catalog.test.ts +5 -3
  107. package/template/apps/server/src/system/interface-catalog.ts +3 -0
  108. package/template/apps/server/src/system/menu-routes.test.ts +5 -3
  109. package/template/apps/server/src/system/route-test-runtime.ts +24 -0
  110. package/template/apps/server/src/system/routes-validation.test.ts +7 -3
  111. package/template/apps/server/src/system/routes.test.ts +21 -11
  112. package/template/apps/server/src/system/routes.ts +11 -2
  113. package/template/apps/server/src/work-order/foundation-modules.test.ts +6 -4
  114. package/template/apps/server/src/work-order/routes.test.ts +8 -0
  115. package/template/apps/server/src/work-order/task.test.ts +6 -1
  116. package/template/apps/web/Dockerfile +5 -0
  117. package/template/apps/web/package.json +1 -0
  118. package/template/apps/web/scripts/validate-admin-ui-composition.ts +106 -0
  119. package/template/apps/web/src/app/web-composition.ts +24 -0
  120. package/template/apps/web/src/app/web-composition.webtest.ts +15 -0
  121. package/template/apps/web/src/composables/useWatermarkSettings.webtest.ts +14 -0
  122. package/template/apps/web/src/composables/watermark-settings.ts +40 -11
  123. package/template/apps/web/src/composables/watermark-settings.webtest.ts +19 -6
  124. package/template/apps/web/src/layout/AppSidebar.vue +8 -1
  125. package/template/apps/web/src/layout/GlobalWatermark.layering.webtest.ts +2 -0
  126. package/template/apps/web/src/layout/GlobalWatermark.vue +4 -8
  127. package/template/apps/web/src/layout/GlobalWatermark.webtest.ts +12 -5
  128. package/template/apps/web/src/layout/NavigationSearchDialog.vue +8 -1
  129. package/template/apps/web/src/layout/navigation.ts +29 -4
  130. package/template/apps/web/src/layout/navigation.webtest.ts +51 -0
  131. package/template/apps/web/src/pages/dashboard/dashboard-presenter.ts +6 -1
  132. package/template/apps/web/src/pages/settings/WatermarkPreview.vue +72 -0
  133. package/template/apps/web/src/pages/settings/WatermarkPreview.webtest.ts +55 -0
  134. package/template/apps/web/src/pages/settings/WatermarkSettingsSection.vue +80 -24
  135. package/template/apps/web/src/pages/settings/WatermarkSettingsSection.webtest.ts +115 -0
  136. package/template/apps/web/src/pages/work-orders/WorkOrderListPage.vue +202 -0
  137. package/template/apps/web/src/pages/work-orders/WorkOrderListPage.webtest.ts +51 -0
  138. package/template/apps/web/src/router/admin-extension-loader.ts +44 -0
  139. package/template/apps/web/src/router/admin-extension-loader.webtest.ts +49 -0
  140. package/template/apps/web/src/router/admin-ui-composition-plugin.ts +33 -0
  141. package/template/apps/web/src/router/admin-ui-composition-plugin.webtest.ts +20 -0
  142. package/template/apps/web/src/router/admin-ui-composition.ts +313 -0
  143. package/template/apps/web/src/router/admin-ui-composition.webtest.ts +225 -0
  144. package/template/apps/web/src/router/admin-ui-module-registry-plugin.ts +55 -0
  145. package/template/apps/web/src/router/admin-ui-module-registry-plugin.webtest.ts +46 -0
  146. package/template/apps/web/src/router/business-routes.ts +32 -45
  147. package/template/apps/web/src/router/business-routes.webtest.ts +40 -44
  148. package/template/apps/web/src/router/index.ts +64 -54
  149. package/template/apps/web/src/router/manifest-routes.ts +27 -7
  150. package/template/apps/web/src/router/manifest-routes.webtest.ts +10 -2
  151. package/template/apps/web/src/router/platform-admin-route-bindings.ts +58 -0
  152. package/template/apps/web/src/router/reserved-route-paths.ts +14 -0
  153. package/template/apps/web/src/router/shared-scaffold-neutrality.webtest.ts +10 -4
  154. package/template/apps/web/src/router/web-composition-host.ts +20 -0
  155. package/template/apps/web/src/services/work-order-api.ts +16 -0
  156. package/template/apps/web/src/services/work-order-api.webtest.ts +21 -0
  157. package/template/apps/web/src/virtual-admin-ui-modules.d.ts +5 -0
  158. package/template/apps/web/vite.config.ts +22 -1
  159. package/template/docs/architecture/migration-bundle.md +10 -7
  160. package/template/docs/architecture/module-manifest-composition.md +85 -3
  161. package/template/docs/architecture/search-provider-protocol.md +6 -1
  162. package/template/docs/architecture/server-module-interface.md +70 -7
  163. package/template/docs/development/business-web-routes.md +53 -50
  164. package/template/docs/development/module-lifecycle.md +48 -54
  165. package/template/docs/platform/navigation-registry.md +5 -4
  166. package/template/docs/platform/platform-shell-settings.md +15 -10
  167. package/template/docs/platform/web-system-crud.md +1 -1
  168. package/template/package.json +1 -1
  169. package/template/packages/config/package.json +1 -1
  170. package/template/packages/database/drizzle/0044_sweet_tarantula.sql +6 -0
  171. package/template/packages/database/drizzle/meta/0044_snapshot.json +8349 -0
  172. package/template/packages/database/drizzle/meta/_journal.json +7 -0
  173. package/template/packages/database/package.json +1 -1
  174. package/template/packages/database/src/schema/platform-settings.ts +3 -0
  175. package/template/packages/example-work-order/package.json +6 -0
  176. package/template/packages/example-work-order/src/admin-page.ts +78 -0
  177. package/template/packages/example-work-order/src/admin.ts +35 -0
  178. package/template/packages/example-work-order/src/manifest.test.ts +62 -5
  179. package/template/packages/example-work-order/src/manifest.ts +54 -7
  180. package/template/packages/example-work-order/src/migration-bundle.ts +8 -0
  181. package/template/packages/example-work-order/src/server-module.ts +2 -2
  182. package/template/packages/jobs/package.json +1 -1
  183. package/template/packages/modules/index.ts +4 -0
  184. package/template/packages/modules/package.json +3 -2
  185. package/template/packages/modules/src/admin-ui-entry.ts +8 -0
  186. package/template/packages/modules/src/admin-ui-manifest.test.ts +52 -0
  187. package/template/packages/modules/src/admin-ui-manifest.ts +53 -0
  188. package/template/packages/modules/src/capability-audit-validation.test.ts +127 -0
  189. package/template/packages/modules/src/capability-audit-validation.ts +38 -0
  190. package/template/packages/modules/src/capability-guard-validation.ts +148 -0
  191. package/template/packages/modules/src/capability-key-validation.test.ts +150 -0
  192. package/template/packages/modules/src/composition-fixtures.test-support.ts +7 -5
  193. package/template/packages/modules/src/composition.test.ts +74 -9
  194. package/template/packages/modules/src/composition.ts +8 -0
  195. package/template/packages/modules/src/manifest.ts +1 -0
  196. package/template/packages/modules/src/migration-bundle-validation.test.ts +23 -0
  197. package/template/packages/modules/src/module-host-range.test.ts +57 -0
  198. package/template/packages/modules/src/module-host-range.ts +125 -0
  199. package/template/packages/modules/src/project-module-assembly.test.ts +133 -0
  200. package/template/packages/modules/src/project-module-assembly.ts +126 -0
  201. package/template/packages/modules/src/system-admin-routes.ts +6 -5
  202. package/template/packages/modules/src/system-admin-ui-catalog.ts +72 -0
  203. package/template/packages/modules/src/system-agent-tools.ts +2 -2
  204. package/template/packages/modules/src/system-api-binding.ts +43 -9
  205. package/template/packages/modules/src/system-api-control-plane.ts +202 -0
  206. package/template/packages/modules/src/system-api-data-governance.ts +4 -2
  207. package/template/packages/modules/src/system-api-operational.ts +57 -8
  208. package/template/packages/modules/src/system-api-permissions.ts +115 -21
  209. package/template/packages/modules/src/system-api-watermark.ts +3 -0
  210. package/template/packages/modules/src/system-audit-actions.ts +7 -0
  211. package/template/packages/modules/src/system-data-governance.ts +5 -2
  212. package/template/packages/modules/src/system-features.ts +31 -0
  213. package/template/packages/modules/src/system-governed-export-bindings.ts +8 -4
  214. package/template/packages/modules/src/system-module-catalog.test.ts +39 -4
  215. package/template/packages/modules/src/system-module-catalog.ts +68 -7
  216. package/template/packages/modules/src/system-module-tasks.ts +8 -1
  217. package/template/packages/modules/src/system-modules-access.test.ts +4 -0
  218. package/template/packages/modules/src/system-modules.test.ts +10 -2
  219. package/template/packages/modules/src/system-modules.ts +6 -5
  220. package/template/packages/modules/src/system-permissions.ts +4 -0
  221. package/template/packages/modules/user/module.ts +19 -2
  222. package/template/packages/server-sdk/index.ts +8 -0
  223. package/template/packages/server-sdk/package.json +2 -1
  224. package/template/packages/server-sdk/src/durable-handler.ts +16 -1
  225. package/template/packages/server-sdk/src/module-adapter.test.ts +44 -0
  226. package/template/packages/server-sdk/src/module-adapter.ts +59 -0
  227. package/template/packages/server-sdk/src/route-definition.ts +3 -2
  228. package/template/packages/shared/package.json +1 -1
  229. package/template/packages/shared/src/audit.test.ts +7 -0
  230. package/template/packages/shared/src/audit.ts +16 -0
  231. package/template/packages/shared/src/migration-bundle-validation.ts +9 -11
  232. package/template/packages/shared/src/platform-settings.ts +17 -0
  233. package/template/packages/shared/src/rbac.ts +2 -0
  234. package/template/apps/web/src/app/business-routes.ts +0 -22
@@ -0,0 +1,58 @@
1
+ import type {
2
+ WindyServerModuleAdapter,
3
+ WindyServerModuleAttachment,
4
+ } from "@southwind-ai/server-sdk";
5
+
6
+ export async function attachProjectServerModules<TRuntime>(input: {
7
+ readonly attachments: readonly WindyServerModuleAttachment<TRuntime>[];
8
+ readonly adapters: readonly WindyServerModuleAdapter<unknown>[];
9
+ readonly enabledModuleNames: ReadonlySet<string>;
10
+ readonly runtime: TRuntime;
11
+ }): Promise<void> {
12
+ const active = validateProjectServerAttachments(input);
13
+ for (const attachment of active) {
14
+ await attachment.attach(input.runtime);
15
+ }
16
+ }
17
+
18
+ export function validateProjectServerAttachments<TRuntime>(input: {
19
+ readonly attachments: readonly WindyServerModuleAttachment<TRuntime>[];
20
+ readonly adapters: readonly WindyServerModuleAdapter<unknown>[];
21
+ readonly enabledModuleNames: ReadonlySet<string>;
22
+ }): readonly WindyServerModuleAttachment<TRuntime>[] {
23
+ const adapters = new Map(
24
+ input.adapters.map((adapter) => [adapter.name, adapter]),
25
+ );
26
+ const seen = new Set<string>();
27
+ for (const attachment of input.attachments) {
28
+ if (seen.has(attachment.name)) {
29
+ throw new Error(`项目 Server Module attachment 重复:${attachment.name}`);
30
+ }
31
+ seen.add(attachment.name);
32
+ const adapter = adapters.get(attachment.name);
33
+ if (!adapter) {
34
+ throw new Error(
35
+ `项目 Server Module attachment 缺少本地 Adapter:${attachment.name}`,
36
+ );
37
+ }
38
+ if (adapter.runtimeMode !== "project-attachment") {
39
+ throw new Error(
40
+ `项目 Server Module attachment 不能挂载 self-contained Adapter:${attachment.name}`,
41
+ );
42
+ }
43
+ }
44
+ for (const adapter of input.adapters) {
45
+ if (
46
+ adapter.runtimeMode === "project-attachment" &&
47
+ input.enabledModuleNames.has(adapter.name) &&
48
+ !seen.has(adapter.name)
49
+ ) {
50
+ throw new Error(
51
+ `项目 Server Module 缺少 runtime attachment:${adapter.name}`,
52
+ );
53
+ }
54
+ }
55
+ return input.attachments.filter(({ name }) =>
56
+ input.enabledModuleNames.has(name),
57
+ );
58
+ }
@@ -0,0 +1,186 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import type { ModuleManifest } from "@southwind-ai/modules";
3
+ import type { InstallableServerModule } from "@southwind-ai/server-sdk";
4
+ import {
5
+ SERVER_MODULE_HOST_VERSION,
6
+ resolveProjectServerComposition,
7
+ } from "./project-composition.js";
8
+
9
+ describe("package Server Module 标准导出", () => {
10
+ test("从 package 根加载 Adapter,从 ./manifest 加载唯一 Manifest", async () => {
11
+ const exports = packageExports("case", `^${SERVER_MODULE_HOST_VERSION}`);
12
+ const composition = await resolveProjectServerComposition({
13
+ assembly: assembly(),
14
+ adapters: [],
15
+ platformModules: platformModules(),
16
+ importPackage: packageImporter(exports),
17
+ });
18
+
19
+ expect(composition.modules[0]?.manifest).toBe(exports.windyModuleManifest);
20
+ });
21
+
22
+ test("Host range、标准导出与 Manifest 身份漂移时 fail closed", async () => {
23
+ const incompatible = packageExports("case", "^9.0.0");
24
+ await expect(resolve(incompatible)).rejects.toThrow(
25
+ "要求 Server Host ^9.0.0",
26
+ );
27
+
28
+ const valid = packageExports("case", "^0.1.0");
29
+ await expect(
30
+ resolveProjectServerComposition({
31
+ assembly: assembly(),
32
+ adapters: [],
33
+ platformModules: platformModules(),
34
+ importPackage: async (specifier) =>
35
+ specifier.endsWith("/manifest")
36
+ ? { windyModuleManifest: valid.windyModuleManifest }
37
+ : {},
38
+ }),
39
+ ).rejects.toThrow("缺少 windyServerModule 标准导出");
40
+
41
+ await expect(
42
+ resolve({
43
+ ...valid,
44
+ windyServerModule: {
45
+ ...valid.windyServerModule,
46
+ create: () => installable("other"),
47
+ },
48
+ }),
49
+ ).rejects.toThrow("返回了错误 Manifest:other");
50
+
51
+ await expect(
52
+ resolveProjectServerComposition({
53
+ assembly: assembly(),
54
+ adapters: [],
55
+ platformModules: platformModules(),
56
+ importPackage: async (specifier) =>
57
+ specifier.endsWith("/manifest") ? {} : valid,
58
+ }),
59
+ ).rejects.toThrow("缺少 ./manifest windyModuleManifest 标准导出");
60
+
61
+ await expect(
62
+ resolve({
63
+ ...valid,
64
+ windyServerModule: {
65
+ ...valid.windyServerModule,
66
+ create: () => installable("case"),
67
+ },
68
+ }),
69
+ ).rejects.toThrow("未使用 windyModuleManifest 同一声明");
70
+
71
+ await expect(
72
+ resolveProjectServerComposition({
73
+ assembly: assembly(),
74
+ adapters: [],
75
+ platformModules: platformModules(),
76
+ importPackage: async (specifier) =>
77
+ specifier.endsWith("/manifest")
78
+ ? { windyModuleManifest: installable("case").manifest }
79
+ : valid,
80
+ }),
81
+ ).rejects.toThrow("根导出与 ./manifest 不一致");
82
+ });
83
+
84
+ test("本地 Adapter 仍必须复用 package ./manifest", async () => {
85
+ const canonical = installable("case");
86
+ await expect(
87
+ resolveProjectServerComposition({
88
+ assembly: assembly(),
89
+ adapters: [
90
+ {
91
+ name: "case",
92
+ hostRange: "^0.1.0",
93
+ runtimeMode: "project-attachment",
94
+ create: () => installable("case"),
95
+ },
96
+ ],
97
+ platformModules: platformModules(),
98
+ importPackage: async (specifier) => {
99
+ expect(specifier).toBe("@customer/case/manifest");
100
+ return { windyModuleManifest: canonical.manifest };
101
+ },
102
+ }),
103
+ ).rejects.toThrow("未使用 windyModuleManifest 同一声明");
104
+ });
105
+ });
106
+
107
+ function resolve(exports: ReturnType<typeof packageExports>) {
108
+ return resolveProjectServerComposition({
109
+ assembly: assembly(),
110
+ adapters: [],
111
+ platformModules: platformModules(),
112
+ importPackage: packageImporter(exports),
113
+ });
114
+ }
115
+
116
+ function packageImporter(exports: ReturnType<typeof packageExports>) {
117
+ return async (specifier: string) =>
118
+ specifier.endsWith("/manifest")
119
+ ? { windyModuleManifest: exports.windyModuleManifest }
120
+ : exports;
121
+ }
122
+
123
+ function packageExports(name: string, hostRange: string) {
124
+ const module = installable(name);
125
+ return {
126
+ windyModuleManifest: module.manifest,
127
+ windyServerModule: {
128
+ name,
129
+ hostRange,
130
+ runtimeMode: "self-contained" as const,
131
+ create: () => module,
132
+ },
133
+ };
134
+ }
135
+
136
+ function assembly() {
137
+ return {
138
+ schemaVersion: 1 as const,
139
+ modules: [
140
+ registration("system", null, 10),
141
+ registration("system.identity", null, 20),
142
+ registration("case", "@customer/case", 100),
143
+ ],
144
+ };
145
+ }
146
+
147
+ function registration(name: string, packageName: string | null, order: number) {
148
+ return {
149
+ name,
150
+ package: packageName,
151
+ enabled: true,
152
+ order,
153
+ admin: { visible: true },
154
+ config: {},
155
+ };
156
+ }
157
+
158
+ function platformModules(): readonly ModuleManifest[] {
159
+ return [
160
+ installable("system").manifest,
161
+ installable("system.identity").manifest,
162
+ ];
163
+ }
164
+
165
+ function installable(name: string): InstallableServerModule<ModuleManifest> {
166
+ return {
167
+ manifest: {
168
+ name,
169
+ version: "1.0.0",
170
+ label: name,
171
+ description: `${name} module`,
172
+ menus: [],
173
+ adminRoutes: [],
174
+ permissions: [],
175
+ apiPermissions: [],
176
+ features: [],
177
+ migrations: [],
178
+ schemaExports: [],
179
+ tasks: [],
180
+ tools: [],
181
+ providers: [],
182
+ auditActions: [],
183
+ },
184
+ setup() {},
185
+ };
186
+ }
@@ -0,0 +1,260 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import {
3
+ // @windy-module system.data-governance begin
4
+ systemModuleManifests,
5
+ // @windy-module system.data-governance end
6
+ type ModuleManifest,
7
+ } from "@southwind-ai/modules";
8
+ import type { InstallableServerModule } from "@southwind-ai/server-sdk";
9
+ import { resolveProjectServerComposition } from "./project-composition.js";
10
+
11
+ describe("project-owned Server Composition", () => {
12
+ test("按装配清单启用状态与顺序加载项目 Adapter", async () => {
13
+ const adapters = [adapter("disabled"), adapter("second"), adapter("first")];
14
+ const composition = await resolveProjectServerComposition({
15
+ assembly: assembly([
16
+ registration("disabled", false, 100),
17
+ registration("second", true, 300),
18
+ registration("first", true, 200),
19
+ ]),
20
+ adapters,
21
+ platformModules: platformModules(),
22
+ importPackage: localManifestImporter(adapters),
23
+ });
24
+
25
+ expect(composition.modules.map(({ manifest }) => manifest.name)).toEqual([
26
+ "first",
27
+ "second",
28
+ ]);
29
+ });
30
+
31
+ test("平台 Module 与 package 注册必须双向一致", async () => {
32
+ await expect(
33
+ resolveProjectServerComposition({
34
+ assembly: assembly([]),
35
+ adapters: [],
36
+ platformModules: platformModules(
37
+ "system",
38
+ "system.identity",
39
+ "system.audit",
40
+ ),
41
+ importPackage: unexpectedImport,
42
+ }),
43
+ ).rejects.toThrow("平台 Module 未在 windy.modules.json 注册:system.audit");
44
+
45
+ await expect(
46
+ resolveProjectServerComposition({
47
+ assembly: {
48
+ ...assembly([]),
49
+ modules: [
50
+ ...assembly([]).modules,
51
+ {
52
+ name: "ghost",
53
+ package: null,
54
+ enabled: true,
55
+ order: 500,
56
+ admin: { visible: true },
57
+ config: {},
58
+ },
59
+ ],
60
+ },
61
+ adapters: [],
62
+ platformModules: platformModules(),
63
+ importPackage: unexpectedImport,
64
+ }),
65
+ ).rejects.toThrow("引用了未知平台 Module:ghost");
66
+
67
+ await expect(
68
+ resolveProjectServerComposition({
69
+ assembly: {
70
+ ...assembly([]),
71
+ modules: assembly([]).modules.map((module) =>
72
+ module.name === "system.identity"
73
+ ? { ...module, enabled: false }
74
+ : module,
75
+ ),
76
+ },
77
+ adapters: [],
78
+ platformModules: platformModules(),
79
+ importPackage: unexpectedImport,
80
+ }),
81
+ ).rejects.toThrow(
82
+ "windy.modules.json 必须启用平台 Module:system.identity",
83
+ );
84
+ });
85
+
86
+ // @windy-module system.data-governance begin
87
+ test("跨模块 Feature 贡献不能声明为运行时停用", async () => {
88
+ const platform = systemModuleManifests().filter(({ name }) =>
89
+ ["system", "system.identity", "system.data-governance"].includes(name),
90
+ );
91
+ await expect(
92
+ resolveProjectServerComposition({
93
+ assembly: {
94
+ ...assembly([]),
95
+ modules: [
96
+ ...assembly([]).modules,
97
+ {
98
+ name: "system.data-governance",
99
+ package: null,
100
+ enabled: false,
101
+ order: 500,
102
+ admin: { visible: true },
103
+ config: {},
104
+ },
105
+ ],
106
+ },
107
+ adapters: [],
108
+ platformModules: platform,
109
+ importPackage: unexpectedImport,
110
+ }),
111
+ ).rejects.toThrow(
112
+ "平台 Module 不能仅通过运行时停用:system.data-governance",
113
+ );
114
+ });
115
+ // @windy-module system.data-governance end
116
+
117
+ test("启用模块的 Manifest 依赖必须同时启用", async () => {
118
+ const caseModule = installable("case");
119
+ caseModule.manifest.dependencies = ["system.scheduler"];
120
+ const caseAdapter = {
121
+ ...adapter("case"),
122
+ create: () => caseModule,
123
+ };
124
+ await expect(
125
+ resolveProjectServerComposition({
126
+ assembly: {
127
+ ...assembly([registration("case", true, 600)]),
128
+ modules: [
129
+ ...assembly([]).modules,
130
+ {
131
+ name: "system.scheduler",
132
+ package: null,
133
+ enabled: false,
134
+ order: 500,
135
+ admin: { visible: true },
136
+ config: {},
137
+ },
138
+ registration("case", true, 600),
139
+ ],
140
+ },
141
+ adapters: [caseAdapter],
142
+ platformModules: platformModules(
143
+ "system",
144
+ "system.identity",
145
+ "system.scheduler",
146
+ ),
147
+ importPackage: localManifestImporter([caseAdapter]),
148
+ }),
149
+ ).rejects.toThrow("Module case 依赖未启用 Module:system.scheduler");
150
+ });
151
+
152
+ test("装配清单拒绝重复 order、负数 order 和非法 package", async () => {
153
+ for (const modules of [
154
+ [registration("first", true, 100), registration("second", true, 100)],
155
+ [registration("negative", true, -1)],
156
+ [{ ...registration("bad", true, 100), package: "../bad" }],
157
+ [{ ...registration("uppercase", true, 100), package: "@Customer/case" }],
158
+ ]) {
159
+ await expect(
160
+ resolveProjectServerComposition({
161
+ assembly: assembly(modules),
162
+ adapters: [],
163
+ platformModules: platformModules(),
164
+ importPackage: unexpectedImport,
165
+ }),
166
+ ).rejects.toThrow("windy.modules.json");
167
+ }
168
+ });
169
+ });
170
+
171
+ function assembly(modules: Array<ReturnType<typeof registration>>) {
172
+ return {
173
+ schemaVersion: 1 as const,
174
+ modules: [
175
+ {
176
+ name: "system",
177
+ package: null,
178
+ enabled: true,
179
+ order: 10,
180
+ admin: { visible: true },
181
+ config: {},
182
+ },
183
+ {
184
+ name: "system.identity",
185
+ package: null,
186
+ enabled: true,
187
+ order: 20,
188
+ admin: { visible: true },
189
+ config: {},
190
+ },
191
+ ...modules,
192
+ ],
193
+ };
194
+ }
195
+
196
+ function registration(name: string, enabled: boolean, order: number) {
197
+ return {
198
+ name,
199
+ package: `@customer/${name}`,
200
+ enabled,
201
+ order,
202
+ admin: { visible: true },
203
+ config: {},
204
+ };
205
+ }
206
+
207
+ function adapter(name: string) {
208
+ const module = installable(name);
209
+ return {
210
+ name,
211
+ hostRange: "^0.1.0",
212
+ runtimeMode: "project-attachment" as const,
213
+ create: () => module,
214
+ };
215
+ }
216
+
217
+ function localManifestImporter(
218
+ adapters: readonly ReturnType<typeof adapter>[],
219
+ ) {
220
+ return async (specifier: string) => {
221
+ const adapter = adapters.find(({ name }) =>
222
+ specifier.endsWith(`/${name}/manifest`),
223
+ );
224
+ if (!adapter) throw new Error(`未找到测试 Adapter:${specifier}`);
225
+ return { windyModuleManifest: (await adapter.create()).manifest };
226
+ };
227
+ }
228
+
229
+ function installable(name: string): InstallableServerModule<ModuleManifest> {
230
+ return {
231
+ manifest: {
232
+ name,
233
+ version: "1.0.0",
234
+ label: name,
235
+ description: `${name} module`,
236
+ menus: [],
237
+ adminRoutes: [],
238
+ permissions: [],
239
+ apiPermissions: [],
240
+ features: [],
241
+ migrations: [],
242
+ schemaExports: [],
243
+ tasks: [],
244
+ tools: [],
245
+ providers: [],
246
+ auditActions: [],
247
+ },
248
+ setup() {},
249
+ };
250
+ }
251
+
252
+ async function unexpectedImport(): Promise<never> {
253
+ throw new Error("不应加载 package");
254
+ }
255
+
256
+ function platformModules(...names: string[]): readonly ModuleManifest[] {
257
+ return (names.length ? names : ["system", "system.identity"])
258
+ .map(installable)
259
+ .map(({ manifest }) => manifest);
260
+ }