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
package/dist/cli.js CHANGED
@@ -206,6 +206,39 @@ var starterProfile = {
206
206
  };
207
207
  var projectLicenseKeys = ["UNLICENSED", "MIT", "Apache-2.0"];
208
208
 
209
+ // src/module-distribution-validation.ts
210
+ function validateModuleDistributionCatalog(manifests) {
211
+ const names = new Set;
212
+ for (const manifest of manifests) {
213
+ if (names.has(manifest.name)) {
214
+ throw new Error(`\u5206\u53D1\u6A21\u5757\u91CD\u590D\uFF1A${manifest.name}`);
215
+ }
216
+ names.add(manifest.name);
217
+ }
218
+ for (const manifest of manifests) {
219
+ for (const dependency of manifest.dependencies) {
220
+ if (!names.has(dependency)) {
221
+ throw new Error(`${manifest.name} \u4F9D\u8D56\u672A\u77E5\u6A21\u5757\uFF1A${dependency}`);
222
+ }
223
+ if (dependency === manifest.name) {
224
+ throw new Error(`${manifest.name} \u4E0D\u80FD\u4F9D\u8D56\u81EA\u8EAB`);
225
+ }
226
+ }
227
+ for (const conflict of manifest.conflicts) {
228
+ if (!names.has(conflict)) {
229
+ throw new Error(`${manifest.name} \u51B2\u7A81\u6A21\u5757\u672A\u77E5\uFF1A${conflict}`);
230
+ }
231
+ }
232
+ if (!manifest.installRecipe) {
233
+ throw new Error(`${manifest.name} \u7F3A\u5C11\u5B89\u88C5 recipe`);
234
+ }
235
+ if (!manifest.uninstallRecipe) {
236
+ throw new Error(`${manifest.name} \u7F3A\u5C11\u5378\u8F7D recipe`);
237
+ }
238
+ }
239
+ return manifests;
240
+ }
241
+
209
242
  // src/optional-module-distribution.ts
210
243
  var optionalModuleManifests = [
211
244
  {
@@ -213,6 +246,7 @@ var optionalModuleManifests = [
213
246
  "system",
214
247
  "system.settings"
215
248
  ]),
249
+ runtimeActivation: "physical-only",
216
250
  fileRoots: [
217
251
  "apps/server/src/storage",
218
252
  "apps/server/src/module-host/route-installer.test.ts",
@@ -300,6 +334,7 @@ var optionalModuleManifests = [
300
334
  "apps/server/src/application-services.ts",
301
335
  "apps/server/src/module-host/durable-context.ts",
302
336
  "apps/server/src/module-host/durable-context.test.ts",
337
+ "apps/server/src/module-host/durable-context-guard.test.ts",
303
338
  "apps/server/src/module-host/durable-context-unavailable.test.ts",
304
339
  "apps/server/src/module-host/job-port.test.ts",
305
340
  "apps/server/src/module-host/task-definitions.ts",
@@ -436,6 +471,7 @@ var optionalModuleManifests = [
436
471
  "system.workflow",
437
472
  "system.scheduler"
438
473
  ]),
474
+ runtimeActivation: "physical-only",
439
475
  fileRoots: [
440
476
  "apps/server/src/data-governance",
441
477
  "apps/server/src/module-host/governed-resource-routes.ts",
@@ -460,11 +496,17 @@ var optionalModuleManifests = [
460
496
  "system.scheduler",
461
497
  "system.storage"
462
498
  ]),
499
+ deploymentInstall: "project-composition-required",
500
+ runtimePackage: "@southwind-ai/example-work-order",
463
501
  packages: ["packages/example-work-order"],
464
502
  fileRoots: [
465
503
  "apps/server/src/work-order",
466
504
  "apps/server/src/example-modules.ts",
467
- "apps/server/src/example-permissions.ts"
505
+ "apps/server/src/example-permissions.ts",
506
+ "apps/web/src/pages/work-orders",
507
+ "apps/web/src/app/web-composition.webtest.ts",
508
+ "apps/web/src/services/work-order-api.ts",
509
+ "apps/web/src/services/work-order-api.webtest.ts"
468
510
  ],
469
511
  documentation: ["docs/development/example-work-order.md"],
470
512
  schemaFiles: ["packages/example-work-order/src/drizzle-schema.ts"],
@@ -478,12 +520,15 @@ function module(name, label, description, dependencies) {
478
520
  return {
479
521
  name,
480
522
  version: "0.1.0",
481
- windyRange: "^0.3.17",
523
+ windyRange: "^0.4.0",
482
524
  label,
483
525
  description,
484
526
  selection,
527
+ runtimeActivation: "toggle",
528
+ deploymentInstall: "project-composition-required",
485
529
  dependencies,
486
530
  conflicts: [],
531
+ runtimePackage: null,
487
532
  packages: [],
488
533
  registryDependencies: [],
489
534
  fileRoots: [],
@@ -514,6 +559,7 @@ var platformModules = [
514
559
  module2("system.identity", "\u8EAB\u4EFD\u4E0E\u6743\u9650", "\u8BA4\u8BC1\u3001\u7528\u6237\u3001\u89D2\u8272\u3001\u7EC4\u7EC7\u548C\u6570\u636E\u8303\u56F4\u3002", "required", ["system"]),
515
560
  {
516
561
  ...module2("system.settings", "\u5E73\u53F0\u8BBE\u7F6E", "\u54C1\u724C\u3001Logo \u4E0E\u6C34\u5370\u3002", "recommended", ["system"]),
562
+ runtimeActivation: "physical-only",
517
563
  fileRoots: [
518
564
  "apps/server/src/settings",
519
565
  "apps/web/src/pages/settings",
@@ -647,7 +693,7 @@ var platformModules = [
647
693
  },
648
694
  ...optionalModuleManifests
649
695
  ];
650
- var moduleDistributionCatalog = validateCatalog(platformModules);
696
+ var moduleDistributionCatalog = validateModuleDistributionCatalog(platformModules);
651
697
  function resolveModuleSelection(input = {}) {
652
698
  const known = new Map(moduleDistributionCatalog.map((item) => [item.name, item]));
653
699
  const selected = new Set(requiredModuleNames);
@@ -702,12 +748,15 @@ function module2(name, label, description, selection, dependencies = []) {
702
748
  return {
703
749
  name,
704
750
  version: "0.1.0",
705
- windyRange: "^0.3.17",
751
+ windyRange: "^0.4.0",
706
752
  label,
707
753
  description,
708
754
  selection,
755
+ runtimeActivation: "toggle",
756
+ deploymentInstall: "project-composition-required",
709
757
  dependencies,
710
758
  conflicts: [],
759
+ runtimePackage: null,
711
760
  packages: [],
712
761
  registryDependencies: [],
713
762
  fileRoots: [],
@@ -724,33 +773,6 @@ function module2(name, label, description, selection, dependencies = []) {
724
773
  uninstallRecipe: `module-${name}-uninstall`
725
774
  };
726
775
  }
727
- function validateCatalog(manifests) {
728
- const names = new Set;
729
- for (const manifest of manifests) {
730
- if (names.has(manifest.name))
731
- throw new Error(`\u5206\u53D1\u6A21\u5757\u91CD\u590D\uFF1A${manifest.name}`);
732
- names.add(manifest.name);
733
- }
734
- for (const manifest of manifests) {
735
- for (const dependency of manifest.dependencies) {
736
- if (!names.has(dependency))
737
- throw new Error(`${manifest.name} \u4F9D\u8D56\u672A\u77E5\u6A21\u5757\uFF1A${dependency}`);
738
- if (dependency === manifest.name)
739
- throw new Error(`${manifest.name} \u4E0D\u80FD\u4F9D\u8D56\u81EA\u8EAB`);
740
- }
741
- for (const conflict of manifest.conflicts) {
742
- if (!names.has(conflict))
743
- throw new Error(`${manifest.name} \u51B2\u7A81\u6A21\u5757\u672A\u77E5\uFF1A${conflict}`);
744
- }
745
- if (!manifest.installRecipe) {
746
- throw new Error(`${manifest.name} \u7F3A\u5C11\u5B89\u88C5 recipe`);
747
- }
748
- if (!manifest.uninstallRecipe) {
749
- throw new Error(`${manifest.name} \u7F3A\u5C11\u5378\u8F7D recipe`);
750
- }
751
- }
752
- return manifests;
753
- }
754
776
 
755
777
  // src/arguments.ts
756
778
  function parseArguments(args) {
@@ -833,9 +855,12 @@ var helpText = `create-windy <\u9879\u76EE\u540D> [\u9009\u9879]
833
855
  create-windy update --dry-run [--to <\u7248\u672C>] [--cwd <\u76EE\u5F55>]
834
856
  create-windy update [--to <\u7248\u672C>] [--cwd <\u76EE\u5F55>]
835
857
  create-windy module list [--cwd <\u76EE\u5F55>]
836
- create-windy module add <key...> [--dry-run] [--cwd <\u76EE\u5F55>]
837
- create-windy module update [key...] [--dry-run] [--cwd <\u76EE\u5F55>]
838
- create-windy module remove <key...> [--dry-run] [--data-disposition <\u7B56\u7565>]
858
+ create-windy module add <key...> [--dry-run] [--skip-install] [--yes]
859
+ create-windy module enable <key...> [--dry-run] [--yes] [--cwd <\u76EE\u5F55>]
860
+ create-windy module disable <key...> [--dry-run] [--yes] [--cwd <\u76EE\u5F55>]
861
+ create-windy module update [key...] [--dry-run] [--skip-install] [--yes]
862
+ create-windy module remove <key...> [--dry-run] [--skip-install] [--yes]
863
+ [--data-disposition <\u7B56\u7565>]
839
864
  [--data-plan <\u6587\u4EF6>] [--confirm-data-disposition] [--cwd <\u76EE\u5F55>]
840
865
  create-windy schema handoff [--config <JSON>] [--cwd <\u76EE\u5F55>]
841
866
 
@@ -1214,12 +1239,12 @@ import {
1214
1239
  copyFile,
1215
1240
  lstat as lstat5,
1216
1241
  mkdir as mkdir3,
1217
- readFile as readFile9,
1242
+ readFile as readFile10,
1218
1243
  readdir as readdir5,
1219
1244
  rm as rm4,
1220
- writeFile as writeFile8
1245
+ writeFile as writeFile9
1221
1246
  } from "fs/promises";
1222
- import { basename as basename5, extname as extname3, join as join10 } from "path";
1247
+ import { basename as basename5, extname as extname3, join as join11 } from "path";
1223
1248
 
1224
1249
  // src/customer-docker.ts
1225
1250
  import { readFile as readFile3, writeFile as writeFile2 } from "fs/promises";
@@ -1304,9 +1329,11 @@ var ignoredFiles = new Set([
1304
1329
  ".windy/update-state.json"
1305
1330
  ]);
1306
1331
  var projectOwnedRoots = [
1332
+ "windy.modules.json",
1307
1333
  "module-schema.lineages.json",
1308
1334
  "module-schema",
1309
- "apps/web/src/app",
1335
+ "apps/web/src",
1336
+ "apps/server/src/app",
1310
1337
  "apps/agent-server/project",
1311
1338
  "apps/server/src/installed-business-modules.ts",
1312
1339
  "apps/server/src/domain-schema",
@@ -1323,8 +1350,6 @@ var sharedScaffoldRoots = [
1323
1350
  "apps/server/Dockerfile",
1324
1351
  "apps/server/src/index.ts",
1325
1352
  "apps/web/Dockerfile",
1326
- "apps/web/src/layout",
1327
- "apps/web/src/router",
1328
1353
  "packages/database/src/schema/index.ts"
1329
1354
  ];
1330
1355
  async function writeManagedFilesManifest(targetDirectory, generatorVersion) {
@@ -2688,9 +2713,10 @@ MCP \u6BCF\u6B21\u8C03\u7528\u90FD\u4F1A\u91CD\u65B0\u6267\u884C Feature\u3001RB
2688
2713
  \u548C \`dev:agent\` \u662F\u53E6\u4E00\u6761\u53EF\u9009\u94FE\u8DEF\u3002\u5B8C\u6574\u8FB9\u754C\u89C1
2689
2714
  [MCP \u4E1A\u52A1\u80FD\u529B](./docs/architecture/mcp-business-capabilities.md) \u4E0E
2690
2715
  [Agent Tool](./docs/platform/agent-tools.md)\u3002` : `\u5F53\u524D\u7EC4\u5408\u672A\u5305\u542B \`system.agent\`\uFF0CMCP Server\u3001Agent Tool Host \u548C\u76F8\u5173\u6587\u6863\u5DF2\u88AB
2691
- \u7269\u7406\u88C1\u526A\u3002\u521B\u5EFA\u65F6\u53EF\u589E\u52A0 \`--module system.agent\` \u6216\u4F7F\u7528 \`--all-modules\`\uFF1B\u5DF2\u90E8\u7F72
2692
- \u9879\u76EE\u4F7F\u7528 \`bun run windy module add system.agent\`\uFF0C\u4E0D\u8981\u624B\u5DE5\u590D\u5236 MCP Adapter \u6216
2693
- \u53E6\u5EFA\u6388\u6743\u94FE\u3002\u4ED3\u5E93 Skill \u4ECD\u53EF\u7528\u4E8E\u666E\u901A\u4E1A\u52A1\u6A21\u5757\u5F00\u53D1\u3002`}
2716
+ \u7269\u7406\u88C1\u526A\u3002\u521B\u5EFA\u65F6\u53EF\u589E\u52A0 \`--module system.agent\` \u6216\u4F7F\u7528 \`--all-modules\`\u3002\u5F53\u524D\u6CA1\u6709
2717
+ self-contained H3 \u6A21\u5757\uFF0C\u5DF2\u90E8\u7F72\u9879\u76EE\u5F53\u524D\u4E0D\u80FD\u901A\u8FC7\u6A21\u5757\u547D\u4EE4\u8865\u88C5 \`system.agent\`\uFF1B
2718
+ \`module add\` \u4F1A\u5728\u5199\u5165\u524D fail closed\u3002\u4E0D\u8981\u624B\u5DE5\u590D\u5236 MCP Adapter \u6216\u53E6\u5EFA\u6388\u6743\u94FE\u3002\u4ED3\u5E93
2719
+ Skill \u4ECD\u53EF\u7528\u4E8E\u666E\u901A\u4E1A\u52A1\u6A21\u5757\u5F00\u53D1\u3002`}
2694
2720
  `;
2695
2721
  }
2696
2722
 
@@ -3076,7 +3102,14 @@ function starterReadme(projectName, includeExample = true, includeOxc = true, pr
3076
3102
  const selected = new Set(selectedModules);
3077
3103
  const includeAgent = selected.has("system.agent");
3078
3104
  const includeSearch = selected.has("system.search");
3079
- const moduleRows = moduleDistributionCatalog.map(({ name, description, selection }) => `| \`${name}\` | ${selected.has(name) ? "\u5DF2\u5305\u542B" : "\u672A\u5305\u542B"} | ${selection === "required" ? "H0" : name === "work-order" ? "H3" : "H2"} | ${description} |`).join(`
3105
+ const toggleableModule = moduleDistributionCatalog.find(({ name, selection, runtimeActivation }) => selected.has(name) && selection !== "required" && runtimeActivation === "toggle" && !moduleDistributionCatalog.some((candidate) => selected.has(candidate.name) && candidate.dependencies.includes(name)))?.name;
3106
+ const moduleActivationCommands = toggleableModule ? [
3107
+ `bun run windy module disable ${toggleableModule} --dry-run`,
3108
+ `bun run windy module disable ${toggleableModule}`,
3109
+ `bun run windy module enable ${toggleableModule}`
3110
+ ].join(`
3111
+ `) : "# \u5F53\u524D\u7EC4\u5408\u6CA1\u6709\u53EF\u5B89\u5168 runtime disable \u7684\u5DF2\u5B89\u88C5\u53EF\u9009\u6A21\u5757";
3112
+ const moduleRows = moduleDistributionCatalog.map(({ name, description, selection }) => `| \`${name}\` | ${selected.has(name) ? "\u5DF2\u5305\u542B" : "\u672A\u5305\u542B"} | ${selection === "required" ? "H0" : "H2"} | ${description} |`).join(`
3080
3113
  `);
3081
3114
  return `# ${projectName}
3082
3115
 
@@ -3242,22 +3275,29 @@ create-windy \u5728\u672C\u673A \`.env\` \u4E2D\u968F\u673A\u751F\u6210\u7684 \`
3242
3275
  | \u6CD5\u5F8B\u8BB8\u53EF | \`${projectLicense}\` |
3243
3276
 
3244
3277
  \u5E73\u53F0\u5F53\u524D\u6CE8\u518C\u7684\u5168\u90E8\u6A21\u5757\u5982\u4E0B\u3002H0 \u8868\u793A profile \u9501\u5B9A\u6838\u5FC3\uFF1BH2 \u8868\u793A\u521B\u5EFA\u65F6\u53EF\u4EE5\u7269\u7406\u5305\u542B\u6216
3245
- \u6392\u9664\uFF1BH3 \u8FD8\u5177\u5907\u90E8\u7F72\u671F\u5B89\u88C5\u3001\u66F4\u65B0\u3001\u6570\u636E\u4FDD\u7559\u5378\u8F7D\u548C\u91CD\u88C5\u8BC1\u636E\u3002\u901A\u7528\u751F\u547D\u5468\u671F\u547D\u4EE4\u4F1A\u4FDD\u5B88
3246
- \u5904\u7406\u6240\u6709\u76EE\u5F55\u6A21\u5757\uFF0C\u4F46\u53EA\u6709\u901A\u8FC7\u5B8C\u6574 H3 \u56DE\u5F52\u7684\u6A21\u5757\u624D\u6807\u8BB0\u4E3A H3\u3002
3278
+ \u6392\u9664\uFF1BH3 \u8FD8\u5177\u5907\u90E8\u7F72\u671F\u5B89\u88C5\u3001\u66F4\u65B0\u3001\u6570\u636E\u4FDD\u7559\u5378\u8F7D\u548C\u91CD\u88C5\u8BC1\u636E\u3002\u5F53\u524D\u6CA1\u6709 H3 \u6A21\u5757\uFF0C\u4E5F\u4E0D
3279
+ \u652F\u6301\u4EFB\u610F npm package \u540D\u79F0\u5B89\u88C5\u3002
3247
3280
 
3248
3281
  | Module Key | \u5F53\u524D\u9879\u76EE | \u70ED\u63D2\u62D4\u53EF\u9760\u5EA6 | \u8BF4\u660E |
3249
3282
  | ---------- | -------- | ------------ | ---- |
3250
3283
  ${moduleRows}
3251
3284
 
3252
- \u9879\u76EE\u81EA\u6709\u4E1A\u52A1\u6A21\u5757\u4EE5 \`apps/server/src/installed-business-modules.ts\` \u88C5\u914D\u7684 Module Manifest
3253
- \u4E3A\u51C6\uFF1B\u5BF9\u5E94 Vue \u9875\u9762\u5728 project-owned \u7684
3254
- \`apps/web/src/app/business-routes.ts\` \u6CE8\u518C\uFF0C\u4FA7\u680F\u7EE7\u7EED\u4ECE\u670D\u52A1\u7AEF Manifest \u5FEB\u7167\u751F\u6210\u3002
3255
- \u4E0D\u8981\u4E3A\u589E\u52A0\u4E1A\u52A1\u9875\u9762\u4FEE\u6539 shared-scaffold Router \u6216 AppSidebar\u3002shared-scaffold README
3256
- \u4E0D\u590D\u5236\u9879\u76EE\u4E1A\u52A1 Key\uFF0C\u907F\u514D\u5E73\u53F0\u5347\u7EA7\u56FA\u5316\u4EA7\u54C1\u547D\u540D\u3002
3257
-
3258
- \u7CBE\u786E\u751F\u6210\u6765\u6E90\u548C\u9009\u62E9\u8BB0\u5F55\u5728 \`.windy/generation.json\`\u3002\u5F53\u524D\u7248\u672C\u5DF2\u63D0\u4F9B\u53EF\u56DE\u6EDA\u7684
3259
- \`windy update\` \u4E0E \`windy module list/add/update/remove\`\u3002Feature Flag \u5173\u95ED\u4ECD\u4E0D
3260
- \u7B49\u4E8E\u6A21\u5757\u5378\u8F7D\u3002
3285
+ \u9879\u76EE\u88C5\u914D\u9009\u62E9\u8BB0\u5F55\u5728 project-owned \u7684 \`windy.modules.json\`\uFF0C\u53EF\u63D0\u4EA4 Git \u5E76\u7ED3\u6784\u5316\u7F16\u8F91\uFF1B
3286
+ \u5B83\u53EA\u4FDD\u5B58\u65E0\u7248\u672C package \u5165\u53E3\u3001\u542F\u7528\u72B6\u6001\u3001\u987A\u5E8F\u3001Admin \u53EF\u89C1\u6027\u4E0E\u6A21\u5757\u914D\u7F6E\u3002\u4F9D\u8D56\u7248\u672C\u4E8B\u5B9E
3287
+ \u4ECD\u53EA\u6765\u81EA \`package.json\` \u4E0E \`bun.lock\`\u3002Server \u9ED8\u8BA4\u4ECE
3288
+ \`apps/server/src/app/server-composition.ts\` \u88C5\u914D Adapter\uFF1BWeb \u901A\u8FC7 package \u7684
3289
+ browser-safe \`./admin\` \u4E0E project-owned \u7684
3290
+ \`apps/web/src/app/web-composition.ts\` \u88C5\u914D\u9875\u9762\uFF0C\u4E0D\u590D\u5236\u7B2C\u4E8C\u4EFD Feature\u3001Permission
3291
+ \u6216\u8DEF\u7531\u6E05\u5355\u3002\u6574\u4E2A \`apps/web/src\` \u4E0E\u6574\u4E2A \`apps/server/src/app\` \u90FD\u662F
3292
+ project-owned\uFF0C\u5BA2\u6237\u53EF\u4EE5\u4FEE\u6539 Router\u3001layout\u3001\u9875\u9762\u3001Host \u548C Server \u7EC4\u5408\uFF1B\u666E\u901A
3293
+ \`windy update\` \u4E0D\u4F1A\u8986\u76D6\u6216\u4E09\u65B9\u5408\u5E76\u8FD9\u4E9B\u6587\u4EF6\u3002
3294
+
3295
+ \u7CBE\u786E\u751F\u6210\u6765\u6E90\u4FDD\u7559\u5728 \`.windy/generation.json\`\u3002\u5F53\u524D\u7248\u672C\u5DF2\u63D0\u4F9B\u53EF\u56DE\u6EDA\u7684
3296
+ \`windy update\` \u4E0E \`windy module list/enable/disable\`\u3002\u5F53\u524D\u5185\u7F6E\u6A21\u5757\u4ECD\u4F9D\u8D56
3297
+ source/template \u6216 project-owned Composition\uFF1B\u5F53\u524D\u5185\u7F6E\u6A21\u5757\u7684 add/update/remove
3298
+ \u5B9E\u9645\u96C6\u5408\u53D8\u5316\u4F1A\u5728\u5199\u5165\u524D fail closed\u3002Feature Flag \u5173\u95ED\u3001module disable \u4E0E\u7269\u7406\u5378\u8F7D\u662F
3299
+ \u4E09\u4E2A\u4E0D\u540C\u52A8\u4F5C\uFF1B\`system.settings\`\u3001\`system.storage\` \u548C
3300
+ \`system.data-governance\` \u53EA\u80FD\u5728\u521B\u5EFA\u671F\u7269\u7406\u6392\u9664\uFF0C\u90E8\u7F72\u540E\u4E0D\u80FD\u53EA\u6539 registry \u5B89\u5168\u505C\u7528\u3002
3261
3301
 
3262
3302
  ## \u5E38\u7528\u547D\u4EE4
3263
3303
 
@@ -3274,8 +3314,7 @@ bun run windy doctor
3274
3314
  bun run windy update --check
3275
3315
  bun run windy update --dry-run
3276
3316
  bun run windy module list
3277
- bun run windy module add work-order --dry-run
3278
- bun run windy module remove work-order --dry-run
3317
+ ${moduleActivationCommands}
3279
3318
  ${includeOxc ? `bun run lint
3280
3319
  bun run format` : "# \u672C\u9879\u76EE\u672A\u9884\u88C5 lint/format \u5DE5\u5177\uFF0C\u53EF\u6309\u56E2\u961F\u9700\u8981\u81EA\u884C\u9009\u62E9\u3002"}
3281
3320
  \`\`\`
@@ -3284,7 +3323,9 @@ ${includeOxc ? "\u63D0\u4EA4\u65F6 Husky \u4F1A\u901A\u8FC7 lint-staged \u4E0E O
3284
3323
 
3285
3324
  \u66F4\u65B0\u524D\u65E0\u9700\u63D0\u4EA4\u3001\u6682\u5B58\u6216\u6E05\u7406\u4E1A\u52A1\u6539\u52A8\u3002\`--dry-run\` \u53EA\u751F\u6210\u8BA1\u5212\uFF0C\u4E0D\u5199\u9879\u76EE\uFF1B\u65E0\u5173 WIP
3286
3325
  \u4FDD\u6301\u539F\u6837\uFF0C\u547D\u4E2D\u540C\u4E00\u8DEF\u5F84\u65F6\u6309\u51BB\u7ED3\u5FEB\u7167\u4E0E\u4E09\u65B9\u5408\u5E76 fail closed\u3002\u6B63\u5F0F\u66F4\u65B0\u5148\u5907\u4EFD\uFF0C\u518D\u505A
3287
- package \u5B57\u6BB5\u7EA7\u5408\u5E76\u3001\u5171\u4EAB\u811A\u624B\u67B6\u4E09\u65B9\u5408\u5E76\u4E0E\u5B8C\u6574\u9A8C\u8BC1\u3002\u4EFB\u4F55\u51B2\u7A81\u90FD\u4F1A\u5728\u5199\u5165\u524D\u4E2D\u6B62\uFF0C
3326
+ package \u5B57\u6BB5\u7EA7\u5408\u5E76\u3001\u5171\u4EAB\u811A\u624B\u67B6\u4E09\u65B9\u5408\u5E76\u4E0E\u5B8C\u6574\u9A8C\u8BC1\u3002\u6574\u4E2A \`apps/web/src\` \u548C
3327
+ \`apps/server/src/app\` \u4FDD\u6301\u9879\u76EE\u7248\u672C\u3002\u4EFB\u4F55\u51B2\u7A81\u90FD\u4F1A\u5728\u5199\u5165\u524D\u4E2D\u6B62\uFF0C\u6A21\u5757\u542F\u505C\u5728\u4EA4\u4E92
3328
+ \u7EC8\u7AEF\u786E\u8BA4\uFF1BCI\u3001Agent \u6216\u5176\u5B83\u975E TTY \u73AF\u5883\u5FC5\u987B\u663E\u5F0F\u4F20\u5165 \`--yes\`\u3002
3288
3329
  \u65E0\u5E76\u53D1\u53D8\u5316\u7684\u9A8C\u8BC1\u5931\u8D25\u4F1A\u81EA\u52A8\u56DE\u6EDA\uFF1B\u4E2D\u65AD\u6062\u590D\u4F7F\u7528
3289
3330
  \`bun run windy doctor --repair\`\u3002\u8BE5\u547D\u4EE4\u5728\u63D0\u4EA4\u70B9\u524D\u4FDD\u7559\u5E76\u53D1\u7F16\u8F91\u5E76\u56DE\u6EDA\u5176\u4F59\u8DEF\u5F84\uFF0C
3290
3331
  \u5728 committed \u63D0\u4EA4\u70B9\u540E\u53EA\u5B8C\u6210\u4E8B\u52A1\u72B6\u6001\u4E0E\u5907\u4EFD\u6E05\u7406\uFF0C\u4E0D\u56DE\u6EDA\u9879\u76EE\u6587\u4EF6\u3002
@@ -3307,9 +3348,10 @@ create-windy \u9ED8\u8BA4\u5EFA\u7ACB \`main\` \u5206\u652F\u5E76\u521B\u5EFA \`
3307
3348
  \u6CA1\u6709\u914D\u7F6E Git \u7528\u6237\u8EAB\u4EFD\uFF0C\u6587\u4EF6\u4F1A\u4FDD\u7559\u5728\u6682\u5B58\u533A\uFF1B\u914D\u7F6E\u8EAB\u4EFD\u540E\u624B\u52A8\u63D0\u4EA4\u5373\u53EF\u3002
3308
3349
 
3309
3350
  \u5F53\u524D\u5DF2\u4EA4\u4ED8\u7684 Windy \u66F4\u65B0\u5668\u4F1A\u6839\u636E Project Lock \u548C\u6587\u4EF6\u6240\u6709\u6743\uFF0C\u4ECE Project Lock \u6307\u5411\u7684
3310
- \u7CBE\u786E npm artifact \u91CD\u5EFA Base \u5E76\u6267\u884C\u4E09\u65B9\u5408\u5E76\uFF1B\u5B83\u4E0D\u8FDE\u63A5\u79C1\u6709\u6E90\u4ED3\u5E93\uFF0C\u4E5F\u4E0D\u4F1A\u9759\u9ED8\u8986\u76D6\u9879\u76EE\u4EE3\u7801\u3002\u6A21\u5757
3311
- \u751F\u547D\u5468\u671F\u547D\u4EE4\u590D\u7528\u540C\u4E00\u8BA1\u5212\u3001\u4E8B\u52A1\u3001\u9A8C\u8BC1\u4E0E\u6062\u590D\u8FB9\u754C\uFF1B\u5378\u8F7D\u9ED8\u8BA4\u4FDD\u7559\u6570\u636E\u5E93\u6570\u636E\uFF0C\u5E76\u8981\u6C42
3312
- \u663E\u5F0F\u786E\u8BA4\u6570\u636E\u5904\u7F6E\u8BA1\u5212\u3002
3351
+ \u7CBE\u786E npm artifact \u91CD\u5EFA Base \u5E76\u6267\u884C\u4E09\u65B9\u5408\u5E76\uFF1B\u5B83\u4E0D\u8FDE\u63A5\u79C1\u6709\u6E90\u4ED3\u5E93\u3002
3352
+ \u66F4\u65B0\u5668\u4E0D\u4F1A\u9759\u9ED8\u8986\u76D6\u9879\u76EE\u4EE3\u7801\u3002\`module enable/disable\` \u53EA\u4EE5 CAS \u4E8B\u52A1\u66F4\u65B0
3353
+ \`windy.modules.json\`\uFF1B\u5F53\u524D\u5185\u7F6E\u6A21\u5757\u7684 add/update/remove \u5B9E\u9645\u96C6\u5408\u53D8\u5316\u5728\u751F\u6210\u66F4\u65B0
3354
+ \u8BA1\u5212\u524D\u5373\u88AB\u62D2\u7EDD\u3002
3313
3355
 
3314
3356
  ## License
3315
3357
 
@@ -3666,6 +3708,109 @@ async function collectFiles3(root) {
3666
3708
  }
3667
3709
  }
3668
3710
 
3711
+ // src/project-module-registry.ts
3712
+ import { createHash as createHash3 } from "crypto";
3713
+ import { readFile as readFile9, writeFile as writeFile8 } from "fs/promises";
3714
+ import { join as join10 } from "path";
3715
+ var projectModuleRegistryPath = "windy.modules.json";
3716
+ async function readProjectModuleRegistrySnapshot(projectDirectory, installedNames) {
3717
+ const path = join10(projectDirectory, projectModuleRegistryPath);
3718
+ let content;
3719
+ try {
3720
+ content = await readFile9(path);
3721
+ } catch (error) {
3722
+ if (isMissing4(error)) {
3723
+ throw new Error(`\u9879\u76EE\u7F3A\u5C11 ${projectModuleRegistryPath}\uFF1B\u8BF7\u5148\u8FD0\u884C bun run windy update`, { cause: error });
3724
+ }
3725
+ throw error;
3726
+ }
3727
+ const registry = parseProjectModuleRegistry(content);
3728
+ assertRegistryMatchesInstalled(registry, installedNames);
3729
+ return {
3730
+ content,
3731
+ hash: createHash3("sha256").update(content).digest("hex"),
3732
+ registry
3733
+ };
3734
+ }
3735
+ function parseProjectModuleRegistry(content) {
3736
+ const parsed = JSON.parse(typeof content === "string" ? content : new TextDecoder().decode(content));
3737
+ if (!parsed || typeof parsed !== "object")
3738
+ invalidRegistry();
3739
+ const value = parsed;
3740
+ if (value.schemaVersion !== 1 || !Array.isArray(value.modules)) {
3741
+ invalidRegistry();
3742
+ }
3743
+ const names = new Set;
3744
+ const orders = new Set;
3745
+ for (const item of value.modules) {
3746
+ if (!isRegistration(item))
3747
+ invalidRegistry();
3748
+ if (names.has(item.name)) {
3749
+ throw new Error(`\u6A21\u5757\u88C5\u914D\u6E05\u5355\u5B58\u5728\u91CD\u590D name\uFF1A${item.name}`);
3750
+ }
3751
+ if (orders.has(item.order)) {
3752
+ throw new Error(`\u6A21\u5757\u88C5\u914D\u6E05\u5355\u5B58\u5728\u91CD\u590D order\uFF1A${item.order}`);
3753
+ }
3754
+ names.add(item.name);
3755
+ orders.add(item.order);
3756
+ }
3757
+ return value;
3758
+ }
3759
+ async function writeInitialProjectModuleRegistry(targetDirectory, selectedNames) {
3760
+ const manifests = selectedModuleManifests(selectedNames);
3761
+ const modules = await Promise.all(manifests.map(async (manifest, index) => ({
3762
+ name: manifest.name,
3763
+ package: manifest.runtimePackage,
3764
+ enabled: true,
3765
+ order: (index + 1) * 100,
3766
+ admin: { visible: true },
3767
+ config: {}
3768
+ })));
3769
+ const registry = { schemaVersion: 1, modules };
3770
+ await writeFile8(join10(targetDirectory, projectModuleRegistryPath), `${JSON.stringify(registry, null, 2)}
3771
+ `);
3772
+ }
3773
+ function assertRegistryMatchesInstalled(registry, installedNames) {
3774
+ const installed = new Set(installedNames);
3775
+ const registered = new Set(registry.modules.map(({ name }) => name));
3776
+ const extra = registry.modules.map(({ name }) => name).filter((name) => !installed.has(name));
3777
+ if (extra.length) {
3778
+ throw new Error(`\u6A21\u5757\u88C5\u914D\u6E05\u5355\u5305\u542B\u672A\u5B89\u88C5\u6A21\u5757\uFF1A${extra.join("\u3001")}`);
3779
+ }
3780
+ const missing = installedNames.filter((name) => !registered.has(name));
3781
+ if (missing.length) {
3782
+ throw new Error(`\u5DF2\u5B89\u88C5\u6A21\u5757\u7F3A\u5C11\u88C5\u914D\u58F0\u660E\uFF1A${missing.join("\u3001")}`);
3783
+ }
3784
+ }
3785
+ function isRegistration(value) {
3786
+ if (!value || typeof value !== "object")
3787
+ return false;
3788
+ const item = value;
3789
+ const admin = item.admin;
3790
+ return typeof item.name === "string" && item.name.length > 0 && (item.package === null || typeof item.package === "string" && isPackageName(item.package)) && typeof item.enabled === "boolean" && Number.isSafeInteger(item.order) && Number(item.order) >= 0 && admin !== undefined && typeof admin === "object" && typeof admin.visible === "boolean" && typeof item.config === "object" && item.config !== null && !Array.isArray(item.config);
3791
+ }
3792
+ function isPackageName(value) {
3793
+ if (!value || value.length > 214 || value !== value.toLowerCase()) {
3794
+ return false;
3795
+ }
3796
+ const parts = value.startsWith("@") ? value.slice(1).split("/") : [value];
3797
+ if (parts.length !== (value.startsWith("@") ? 2 : 1))
3798
+ return false;
3799
+ return parts.every(isPackageNamePart);
3800
+ }
3801
+ function isPackageNamePart(value) {
3802
+ if (!value || value === "node_modules" || value === "favicon.ico" || value.startsWith(".") || value.startsWith("_")) {
3803
+ return false;
3804
+ }
3805
+ return /^[a-z0-9!~*'()._-]+$/.test(value);
3806
+ }
3807
+ function invalidRegistry() {
3808
+ throw new Error("\u9879\u76EE\u6A21\u5757\u88C5\u914D\u6E05\u5355\u65E0\u6548");
3809
+ }
3810
+ function isMissing4(error) {
3811
+ return error instanceof Error && "code" in error && error.code === "ENOENT";
3812
+ }
3813
+
3669
3814
  // src/generator.ts
3670
3815
  async function generateProject(input) {
3671
3816
  validateProjectName(input.projectName);
@@ -3678,39 +3823,40 @@ async function generateProject(input) {
3678
3823
  const selectedModules = input.selectedModules ?? templateMetadata.modules.filter(({ name }) => requestedIncludeExample || name !== "work-order").map(({ name }) => name);
3679
3824
  const includeExample = selectedModules.includes("work-order");
3680
3825
  for (const relativePath2 of generationTemplatePaths(true, includeOxc)) {
3681
- await copyTree(await resolveTemplateSource(input.sourceRoot, relativePath2), join10(input.targetDirectory, relativePath2));
3826
+ await copyTree(await resolveTemplateSource(input.sourceRoot, relativePath2), join11(input.targetDirectory, relativePath2));
3682
3827
  }
3683
3828
  await materializeSelectedModules(input.targetDirectory, selectedModules);
3684
3829
  await materializeRegistryDependencies(input.targetDirectory, selectedModuleManifests(selectedModules).flatMap(({ registryDependencies }) => registryDependencies));
3685
- const packagePath = join10(input.targetDirectory, "package.json");
3686
- const packageJson = JSON.parse(await readFile9(packagePath, "utf8"));
3687
- await writeFile8(packagePath, `${JSON.stringify(createStarterPackage(packageJson, input.projectName, includeOxc, projectLicense, selectedModules, templateMetadata.generatorVersion), null, 2)}
3830
+ const packagePath = join11(input.targetDirectory, "package.json");
3831
+ const packageJson = JSON.parse(await readFile10(packagePath, "utf8"));
3832
+ await writeFile9(packagePath, `${JSON.stringify(createStarterPackage(packageJson, input.projectName, includeOxc, projectLicense, selectedModules, templateMetadata.generatorVersion), null, 2)}
3688
3833
  `);
3689
- await writeFile8(join10(input.targetDirectory, "docker-compose.yml"), starterComposeFor(selectedModules.includes("system.agent"), selectedModules.includes("system.search")));
3690
- await writeFile8(join10(input.targetDirectory, ".env.example"), environmentExample(includeExample, selectedModules.includes("system.agent"), selectedModules.includes("system.search")));
3691
- await writeFile8(join10(input.targetDirectory, "README.md"), starterReadme(input.projectName, includeExample, includeOxc, projectLicense, selectedModules));
3692
- await writeFile8(join10(input.targetDirectory, "docs/README.md"), starterDocsReadme(selectedModules.includes("system.license"), selectedModules.includes("system.agent"), selectedModules.includes("system.operations")));
3834
+ await writeFile9(join11(input.targetDirectory, "docker-compose.yml"), starterComposeFor(selectedModules.includes("system.agent"), selectedModules.includes("system.search")));
3835
+ await writeFile9(join11(input.targetDirectory, ".env.example"), environmentExample(includeExample, selectedModules.includes("system.agent"), selectedModules.includes("system.search")));
3836
+ await writeFile9(join11(input.targetDirectory, "README.md"), starterReadme(input.projectName, includeExample, includeOxc, projectLicense, selectedModules));
3837
+ await writeFile9(join11(input.targetDirectory, "docs/README.md"), starterDocsReadme(selectedModules.includes("system.license"), selectedModules.includes("system.agent"), selectedModules.includes("system.operations")));
3693
3838
  await reflectCodeQualityChoice(input.targetDirectory, includeOxc);
3694
3839
  await applyWindyBaseLicense(input.targetDirectory);
3695
3840
  await applyProjectLicense(input.targetDirectory, projectLicense, input.licenseHolder);
3696
3841
  await prepareCustomerDockerfiles(input.targetDirectory);
3697
3842
  await writeGenerationProvenance(input.targetDirectory, templateMetadata, input.profile ?? starterProfile.key, selectedModules, includeOxc, projectLicense);
3698
- await rm4(join10(input.targetDirectory, "bun.lock"), { force: true });
3843
+ await writeInitialProjectModuleRegistry(input.targetDirectory, selectedModules);
3844
+ await rm4(join11(input.targetDirectory, "bun.lock"), { force: true });
3699
3845
  await writeManagedFilesManifest(input.targetDirectory, templateMetadata.generatorVersion);
3700
3846
  }
3701
3847
  async function resolveTemplateSource(sourceRoot, relativePath2) {
3702
- const source = join10(sourceRoot, relativePath2);
3848
+ const source = join11(sourceRoot, relativePath2);
3703
3849
  try {
3704
3850
  await lstat5(source);
3705
3851
  return source;
3706
3852
  } catch (error) {
3707
- if (!isMissing4(error))
3853
+ if (!isMissing5(error))
3708
3854
  throw error;
3709
3855
  }
3710
3856
  const alias = npmTemplateAliases[relativePath2];
3711
3857
  if (!alias)
3712
3858
  return source;
3713
- return join10(sourceRoot, alias);
3859
+ return join11(sourceRoot, alias);
3714
3860
  }
3715
3861
  async function assertGeneratedProjectIsClean(targetDirectory) {
3716
3862
  const violations = [];
@@ -3739,7 +3885,7 @@ async function assertTargetAvailable(targetDirectory) {
3739
3885
  if (entries.length)
3740
3886
  throw new Error("\u76EE\u6807\u76EE\u5F55\u4E0D\u662F\u7A7A\u76EE\u5F55");
3741
3887
  } catch (error) {
3742
- if (isMissing4(error))
3888
+ if (isMissing5(error))
3743
3889
  return;
3744
3890
  throw error;
3745
3891
  }
@@ -3752,11 +3898,11 @@ async function copyTree(source, target) {
3752
3898
  if (metadata.isDirectory()) {
3753
3899
  await mkdir3(target, { recursive: true });
3754
3900
  for (const child of await readdir5(source)) {
3755
- await copyTree(join10(source, child), join10(target, child));
3901
+ await copyTree(join11(source, child), join11(target, child));
3756
3902
  }
3757
3903
  return;
3758
3904
  }
3759
- await mkdir3(join10(target, ".."), { recursive: true });
3905
+ await mkdir3(join11(target, ".."), { recursive: true });
3760
3906
  await copyFile(source, target);
3761
3907
  }
3762
3908
  function shouldExclude(name) {
@@ -3764,13 +3910,13 @@ function shouldExclude(name) {
3764
3910
  }
3765
3911
  async function walk(directory, visit) {
3766
3912
  for (const name of await readdir5(directory)) {
3767
- const path = join10(directory, name);
3913
+ const path = join11(directory, name);
3768
3914
  await visit(path);
3769
3915
  if ((await lstat5(path)).isDirectory())
3770
3916
  await walk(path, visit);
3771
3917
  }
3772
3918
  }
3773
- function isMissing4(error) {
3919
+ function isMissing5(error) {
3774
3920
  return error instanceof Error && "code" in error && error.code === "ENOENT";
3775
3921
  }
3776
3922
 
@@ -3850,7 +3996,7 @@ async function inspectTarget(path) {
3850
3996
  try {
3851
3997
  return (await readdir6(path)).length === 0 ? "empty" : "non-empty";
3852
3998
  } catch (error) {
3853
- if (isMissing5(error))
3999
+ if (isMissing6(error))
3854
4000
  return "missing";
3855
4001
  throw error;
3856
4002
  }
@@ -3872,7 +4018,7 @@ function stageError(stage, error) {
3872
4018
  function sanitizeReason(reason) {
3873
4019
  return reason.replace(/\b([A-Z][A-Z0-9_]*(?:PASSWORD|SECRET|TOKEN|DATABASE_URL)[A-Z0-9_]*)=\S+/g, "$1=[\u5DF2\u9690\u85CF]").replace(/:\/\/([^\s:/]+):([^\s@]+)@/g, "://$1:[\u5DF2\u9690\u85CF]@");
3874
4020
  }
3875
- function isMissing5(error) {
4021
+ function isMissing6(error) {
3876
4022
  return error instanceof Error && "code" in error && error.code === "ENOENT";
3877
4023
  }
3878
4024
 
@@ -5620,8 +5766,8 @@ import { rm as rm10 } from "fs/promises";
5620
5766
  import { resolve as resolve8 } from "path";
5621
5767
 
5622
5768
  // src/update/doctor.ts
5623
- import { readFile as readFile10 } from "fs/promises";
5624
- import { join as join14 } from "path";
5769
+ import { readFile as readFile11 } from "fs/promises";
5770
+ import { join as join15 } from "path";
5625
5771
 
5626
5772
  // src/update/process.ts
5627
5773
  import { spawn as spawn2 } from "child_process";
@@ -6163,12 +6309,12 @@ function casError(path2, artifactPath, cause) {
6163
6309
 
6164
6310
  // src/update/transaction-runtime.ts
6165
6311
  import { randomUUID as randomUUID2 } from "crypto";
6166
- import { join as join12 } from "path";
6312
+ import { join as join13 } from "path";
6167
6313
 
6168
6314
  // src/update/transaction-state-temporaries.ts
6169
6315
  import { randomUUID } from "crypto";
6170
6316
  import { readdir as readdir7 } from "fs/promises";
6171
- import { join as join11 } from "path";
6317
+ import { join as join12 } from "path";
6172
6318
  function serializeTransactionState(state) {
6173
6319
  return `${JSON.stringify(state, null, 2)}
6174
6320
  `;
@@ -6284,7 +6430,7 @@ function stateTemporaryName(identity, cleanup) {
6284
6430
  return `${prefix}.${identity.kind}.${identity.ownerToken}.${identity.nonce}.tmp`;
6285
6431
  }
6286
6432
  async function listWindyDirectory(root) {
6287
- return readdir7(join11(root, ".windy")).catch((error) => {
6433
+ return readdir7(join12(root, ".windy")).catch((error) => {
6288
6434
  if (hasFsCode3(error, "ENOENT"))
6289
6435
  return [];
6290
6436
  throw error;
@@ -6318,7 +6464,7 @@ function createTransactionState(root, updateId, entries, manifestHash) {
6318
6464
  return {
6319
6465
  schemaVersion: 1,
6320
6466
  updateId,
6321
- backupDirectory: join12(root, backupRelativePath(updateId)),
6467
+ backupDirectory: join13(root, backupRelativePath(updateId)),
6322
6468
  entries,
6323
6469
  ownerToken: randomUUID2(),
6324
6470
  phase: "claiming",
@@ -6377,7 +6523,7 @@ async function backupEntriesToDisk(root, backupPath, entries, afterEntry) {
6377
6523
  if (entry.mode !== undefined && await safeFileMode(root, entry.path) !== entry.mode) {
6378
6524
  throw new TransactionCasError(`\u5F85\u5907\u4EFD\u6587\u4EF6 mode \u5DF2\u53D8\u5316\uFF1A${entry.path}`, entry.path);
6379
6525
  }
6380
- await writeExclusive(root, join12(backupPath, "files", entry.path), content, undefined, entry.mode);
6526
+ await writeExclusive(root, join13(backupPath, "files", entry.path), content, undefined, entry.mode);
6381
6527
  await afterEntry?.();
6382
6528
  }
6383
6529
  }
@@ -6685,7 +6831,7 @@ async function removeOwnedResidue(root, evidence, hash, mode) {
6685
6831
  }
6686
6832
 
6687
6833
  // src/update/transaction-recovery-files.ts
6688
- import { join as join13 } from "path";
6834
+ import { join as join14 } from "path";
6689
6835
  function matchesExpected(actualHash, actualMode, expected, expectedMode) {
6690
6836
  return expected.kind === "missing" ? actualHash === undefined : actualHash === expected.value && (expectedMode === undefined || actualMode === expectedMode);
6691
6837
  }
@@ -6697,7 +6843,7 @@ async function readRecoveryBackups(root, backupPath, state, preserved) {
6697
6843
  await inspectSafePath(root, entry.path);
6698
6844
  if (!entry.existed)
6699
6845
  continue;
6700
- const backupFile = join13(backupPath, "files", entry.path);
6846
+ const backupFile = join14(backupPath, "files", entry.path);
6701
6847
  const content = await readSafeOptional(root, backupFile);
6702
6848
  if (!content)
6703
6849
  throw new Error(`\u4E8B\u52A1\u5907\u4EFD\u7F3A\u5931\uFF1A${entry.path}`);
@@ -7328,7 +7474,7 @@ async function diagnoseProject(projectDirectory, repair = false) {
7328
7474
  if (file.ownership === "project-owned")
7329
7475
  continue;
7330
7476
  try {
7331
- const changed = hashContent(await readFile10(join14(projectDirectory, file.path))) !== file.hash;
7477
+ const changed = hashContent(await readFile11(join15(projectDirectory, file.path))) !== file.hash;
7332
7478
  if (!changed)
7333
7479
  continue;
7334
7480
  modifiedManagedFiles.push(file.path);
@@ -7368,6 +7514,16 @@ var recipes = [
7368
7514
  id: "starter-0.3.17-to-0.3.18",
7369
7515
  from: "0.3.17",
7370
7516
  to: "0.3.18"
7517
+ },
7518
+ {
7519
+ id: "starter-0.3.18-to-0.3.19",
7520
+ from: "0.3.18",
7521
+ to: "0.3.19"
7522
+ },
7523
+ {
7524
+ id: "starter-0.3.19-to-0.4.0",
7525
+ from: "0.3.19",
7526
+ to: "0.4.0"
7371
7527
  }
7372
7528
  ];
7373
7529
  function resolveRecipeChain(sourceVersion, targetVersion) {
@@ -7413,18 +7569,18 @@ function parseVersion2(version) {
7413
7569
  }
7414
7570
 
7415
7571
  // src/update/updater.ts
7416
- import { readFile as readFile20, rm as rm8 } from "fs/promises";
7417
- import { join as join25 } from "path";
7572
+ import { readFile as readFile21, rm as rm8 } from "fs/promises";
7573
+ import { join as join26 } from "path";
7418
7574
 
7419
7575
  // src/update/artifacts.ts
7420
- import { mkdtemp, readFile as readFile13 } from "fs/promises";
7576
+ import { mkdtemp, readFile as readFile14 } from "fs/promises";
7421
7577
  import { tmpdir } from "os";
7422
- import { basename as basename8, join as join17 } from "path";
7578
+ import { basename as basename8, join as join18 } from "path";
7423
7579
 
7424
7580
  // src/update/published-artifacts.ts
7425
- import { createHash as createHash3 } from "crypto";
7426
- import { mkdir as mkdir6, readFile as readFile11, writeFile as writeFile9 } from "fs/promises";
7427
- import { basename as basename7, join as join15 } from "path";
7581
+ import { createHash as createHash4 } from "crypto";
7582
+ import { mkdir as mkdir6, readFile as readFile12, writeFile as writeFile10 } from "fs/promises";
7583
+ import { basename as basename7, join as join16 } from "path";
7428
7584
 
7429
7585
  // src/update/safe-archive.ts
7430
7586
  import { mkdir as mkdir5 } from "fs/promises";
@@ -7476,17 +7632,17 @@ async function downloadPublicArtifact(temporaryRoot, label, version, expectedInt
7476
7632
  }
7477
7633
  const archive = await readBoundedResponse(response, `create-windy@${version} artifact `);
7478
7634
  verifyArchive(metadata, archive);
7479
- const destination = join15(temporaryRoot, `${label}-artifact`);
7480
- const archivePath = join15(temporaryRoot, `${label}.tgz`);
7481
- await writeFile9(archivePath, archive);
7635
+ const destination = join16(temporaryRoot, `${label}-artifact`);
7636
+ const archivePath = join16(temporaryRoot, `${label}.tgz`);
7637
+ await writeFile10(archivePath, archive);
7482
7638
  await extractSafeTarArchive({
7483
7639
  archivePath,
7484
7640
  destination,
7485
7641
  cwd: temporaryRoot
7486
7642
  });
7487
- const packageRoot2 = join15(destination, "package");
7488
- const packageJson = JSON.parse(await readFile11(join15(packageRoot2, "package.json"), "utf8"));
7489
- const template = await readTemplateMetadata(join15(packageRoot2, "template"));
7643
+ const packageRoot2 = join16(destination, "package");
7644
+ const packageJson = JSON.parse(await readFile12(join16(packageRoot2, "package.json"), "utf8"));
7645
+ const template = await readTemplateMetadata(join16(packageRoot2, "template"));
7490
7646
  if (packageJson.name !== "create-windy" || packageJson.version !== version || template.generatorVersion !== version) {
7491
7647
  throw new Error(`create-windy@${version} \u5305\u8EAB\u4EFD\u6216\u6A21\u677F\u5143\u6570\u636E\u4E0D\u4E00\u81F4`);
7492
7648
  }
@@ -7611,17 +7767,17 @@ function boundedSizeError(label) {
7611
7767
  return new Error(`${label}\u8D85\u8FC7\u5B89\u5168\u4E0A\u9650`);
7612
7768
  }
7613
7769
  function verifyArchive(metadata, archive) {
7614
- const integrity = `sha512-${createHash3("sha512").update(archive).digest("base64")}`;
7615
- const shasum = createHash3("sha1").update(archive).digest("hex");
7770
+ const integrity = `sha512-${createHash4("sha512").update(archive).digest("base64")}`;
7771
+ const shasum = createHash4("sha1").update(archive).digest("hex");
7616
7772
  if (integrity !== metadata.dist.integrity || shasum !== metadata.dist.shasum) {
7617
7773
  throw new Error(`create-windy@${metadata.version} artifact \u5B8C\u6574\u6027\u6821\u9A8C\u5931\u8D25`);
7618
7774
  }
7619
7775
  }
7620
7776
  async function generatePublicArtifactProject(temporaryRoot, label, projectName, packageRoot2, provenance) {
7621
- const parent = join15(temporaryRoot, label);
7777
+ const parent = join16(temporaryRoot, label);
7622
7778
  await mkdir6(parent, { recursive: true });
7623
7779
  const args = [
7624
- join15(packageRoot2, "dist/cli.js"),
7780
+ join16(packageRoot2, "dist/cli.js"),
7625
7781
  projectName,
7626
7782
  "--yes",
7627
7783
  "--skip-install",
@@ -7641,7 +7797,7 @@ async function generatePublicArtifactProject(temporaryRoot, label, projectName,
7641
7797
  }
7642
7798
  }
7643
7799
  await assertProcess("bun", args, parent);
7644
- return join15(parent, projectName);
7800
+ return join16(parent, projectName);
7645
7801
  }
7646
7802
 
7647
7803
  // src/module-history.ts
@@ -7649,18 +7805,18 @@ import {
7649
7805
  lstat as lstat6,
7650
7806
  mkdir as mkdir7,
7651
7807
  readdir as readdir8,
7652
- readFile as readFile12,
7653
- writeFile as writeFile10
7808
+ readFile as readFile13,
7809
+ writeFile as writeFile11
7654
7810
  } from "fs/promises";
7655
- import { dirname as dirname6, join as join16, relative as relative4, sep as sep4 } from "path";
7811
+ import { dirname as dirname6, join as join17, relative as relative4, sep as sep4 } from "path";
7656
7812
  async function archiveRemovedModuleMigrations(input) {
7657
7813
  await assertExistingHistoryIntegrity(input.projectDirectory);
7658
- let changed = await copyMigrationTree(input.projectDirectory, ".windy/module-history", join16(input.targetDirectory, ".windy/module-history"));
7814
+ let changed = await copyMigrationTree(input.projectDirectory, ".windy/module-history", join17(input.targetDirectory, ".windy/module-history"));
7659
7815
  const target = new Set(input.targetModules);
7660
7816
  const removed = moduleDistributionCatalog.filter(({ name }) => input.sourceModules.includes(name) && !target.has(name));
7661
7817
  for (const manifest of removed) {
7662
7818
  for (const root of manifest.migrationRoots) {
7663
- changed = await copyMigrationTree(input.projectDirectory, root, join16(input.targetDirectory, ".windy/module-history", manifest.name, root)) || changed;
7819
+ changed = await copyMigrationTree(input.projectDirectory, root, join17(input.targetDirectory, ".windy/module-history", manifest.name, root)) || changed;
7664
7820
  }
7665
7821
  }
7666
7822
  if (changed) {
@@ -7668,7 +7824,7 @@ async function archiveRemovedModuleMigrations(input) {
7668
7824
  }
7669
7825
  }
7670
7826
  async function assertExistingHistoryIntegrity(projectDirectory) {
7671
- const historyRoot = join16(projectDirectory, ".windy/module-history");
7827
+ const historyRoot = join17(projectDirectory, ".windy/module-history");
7672
7828
  const metadata = await readOptionalMetadata(historyRoot);
7673
7829
  if (!metadata)
7674
7830
  return;
@@ -7683,13 +7839,13 @@ async function assertExistingHistoryIntegrity(projectDirectory) {
7683
7839
  if (!entry || isMissingManagedFile(entry)) {
7684
7840
  throw new Error(`\u6A21\u5757 migration \u5386\u53F2\u672A\u767B\u8BB0\u5230\u53D7\u7BA1\u6E05\u5355\uFF1A${relativePath2}`);
7685
7841
  }
7686
- if (hashContent(await readFile12(path2)) !== entry.hash) {
7842
+ if (hashContent(await readFile13(path2)) !== entry.hash) {
7687
7843
  throw new Error(`\u6A21\u5757 migration \u5386\u53F2\u5DF2\u88AB\u4FEE\u6539\uFF1A${relativePath2}`);
7688
7844
  }
7689
7845
  }
7690
7846
  }
7691
7847
  async function copyMigrationTree(projectRoot, relativeRoot, targetRoot) {
7692
- const sourceRoot = join16(projectRoot, relativeRoot);
7848
+ const sourceRoot = join17(projectRoot, relativeRoot);
7693
7849
  const rootMetadata = await readOptionalMetadata(sourceRoot);
7694
7850
  if (!rootMetadata)
7695
7851
  return false;
@@ -7697,9 +7853,9 @@ async function copyMigrationTree(projectRoot, relativeRoot, targetRoot) {
7697
7853
  throw new Error(`\u6A21\u5757 migration \u6839\u76EE\u5F55\u65E0\u6548\uFF1A${relativeRoot}`);
7698
7854
  }
7699
7855
  for (const source of await collectRegularFiles(sourceRoot)) {
7700
- const target = join16(targetRoot, relative4(sourceRoot, source).split(sep4).join("/"));
7856
+ const target = join17(targetRoot, relative4(sourceRoot, source).split(sep4).join("/"));
7701
7857
  await mkdir7(dirname6(target), { recursive: true });
7702
- await writeFile10(target, await readFile12(source));
7858
+ await writeFile11(target, await readFile13(source));
7703
7859
  }
7704
7860
  return true;
7705
7861
  }
@@ -7709,7 +7865,7 @@ async function collectRegularFiles(root) {
7709
7865
  return files.sort();
7710
7866
  async function visit(directory) {
7711
7867
  for (const name of (await readdir8(directory)).sort()) {
7712
- const path2 = join16(directory, name);
7868
+ const path2 = join17(directory, name);
7713
7869
  const metadata = await lstat6(path2);
7714
7870
  if (metadata.isSymbolicLink()) {
7715
7871
  throw new Error(`\u6A21\u5757 migration \u4E0D\u5141\u8BB8\u7B26\u53F7\u94FE\u63A5\uFF1A${path2}`);
@@ -7739,10 +7895,10 @@ class PublishedArtifactProvider {
7739
7895
  this.#fetch = fetcher;
7740
7896
  }
7741
7897
  async prepare(projectDirectory, provenance, targetModules) {
7742
- const temporaryRoot = await mkdtemp(join17(tmpdir(), "windy-update-"));
7898
+ const temporaryRoot = await mkdtemp(join18(tmpdir(), "windy-update-"));
7743
7899
  const projectName = await readProjectName2(projectDirectory);
7744
- const baseDirectory = join17(temporaryRoot, "base", projectName);
7745
- const targetDirectory = join17(temporaryRoot, "target", projectName);
7900
+ const baseDirectory = join18(temporaryRoot, "base", projectName);
7901
+ const targetDirectory = join18(temporaryRoot, "target", projectName);
7746
7902
  const baseArtifact = await downloadPublicArtifact(temporaryRoot, "base", provenance.generatorVersion, undefined, this.#fetch);
7747
7903
  if (baseArtifact.identity.templateCommit !== provenance.templateCommit) {
7748
7904
  throw new Error("Project Lock \u4E0E\u6765\u6E90 registry artifact \u7684 template commit \u4E0D\u4E00\u81F4");
@@ -7770,13 +7926,13 @@ class PublishedArtifactProvider {
7770
7926
  }
7771
7927
  }
7772
7928
  async function readProjectName2(projectDirectory) {
7773
- const source = JSON.parse(await readFile13(join17(projectDirectory, "package.json"), "utf8"));
7929
+ const source = JSON.parse(await readFile14(join18(projectDirectory, "package.json"), "utf8"));
7774
7930
  return source.name || basename8(projectDirectory);
7775
7931
  }
7776
7932
 
7777
7933
  // src/update/planner.ts
7778
- import { readFile as readFile18 } from "fs/promises";
7779
- import { dirname as dirname7, join as join23 } from "path";
7934
+ import { readFile as readFile19 } from "fs/promises";
7935
+ import { dirname as dirname7, join as join24 } from "path";
7780
7936
 
7781
7937
  // src/update/docker-copy-update-plan.ts
7782
7938
  async function deriveDockerfilePlans(files, snapshots, paths, targetWorkspaceManifests) {
@@ -7864,12 +8020,12 @@ function matchesWorkspace(pattern, directory) {
7864
8020
  }
7865
8021
 
7866
8022
  // src/update/migration-reconciler.ts
7867
- import { readFile as readFile15 } from "fs/promises";
7868
- import { join as join19 } from "path";
8023
+ import { readFile as readFile16 } from "fs/promises";
8024
+ import { join as join20 } from "path";
7869
8025
 
7870
8026
  // src/update/migration-project-file.ts
7871
- import { readFile as readFile14 } from "fs/promises";
7872
- import { join as join18 } from "path";
8027
+ import { readFile as readFile15 } from "fs/promises";
8028
+ import { join as join19 } from "path";
7873
8029
  async function readProjectJson(input, path2) {
7874
8030
  const content = await readProjectFile(input, path2);
7875
8031
  if (!content) {
@@ -7885,7 +8041,7 @@ async function readProjectFile(input, path2) {
7885
8041
  }
7886
8042
  let content;
7887
8043
  try {
7888
- content = await readFile14(join18(input.projectDirectory, path2));
8044
+ content = await readFile15(join19(input.projectDirectory, path2));
7889
8045
  } catch (error) {
7890
8046
  if (!isMissingFileError(error))
7891
8047
  throw error;
@@ -8004,7 +8160,7 @@ async function reconcileDrizzleMigrations(input) {
8004
8160
  const tag = `${prefix}_${name}`;
8005
8161
  const sqlPath = `${drizzleRoot}/${tag}.sql`;
8006
8162
  const snapshotPath = `${drizzleRoot}/meta/${prefix}_snapshot.json`;
8007
- const sql = await readFile15(join19(input.targetDirectory, `${drizzleRoot}/${addition.tag}.sql`));
8163
+ const sql = await readFile16(join20(input.targetDirectory, `${drizzleRoot}/${addition.tag}.sql`));
8008
8164
  merged.id = targetAfter.id;
8009
8165
  merged.prevId = currentSnapshot.id;
8010
8166
  const snapshot = encodeJson(merged);
@@ -8051,9 +8207,9 @@ function unchanged(targetManifest) {
8051
8207
  async function readJournals(input) {
8052
8208
  try {
8053
8209
  const [base, project, target] = await Promise.all([
8054
- readJson(join19(input.baseDirectory, journalPath)),
8210
+ readJson(join20(input.baseDirectory, journalPath)),
8055
8211
  readProjectJson(input, journalPath),
8056
- readJson(join19(input.targetDirectory, journalPath))
8212
+ readJson(join20(input.targetDirectory, journalPath))
8057
8213
  ]);
8058
8214
  return { base, project, target };
8059
8215
  } catch (error) {
@@ -8091,7 +8247,7 @@ async function readSnapshot(root, tag) {
8091
8247
  const match = migrationPattern.exec(tag);
8092
8248
  if (!match)
8093
8249
  throw new Error(`${journalPath}\uFF1Amigration tag \u683C\u5F0F\u65E0\u6548\uFF1A${tag}`);
8094
- return await readJson(join19(root, `${drizzleRoot}/meta/${match[1]}_snapshot.json`));
8250
+ return await readJson(join20(root, `${drizzleRoot}/meta/${match[1]}_snapshot.json`));
8095
8251
  }
8096
8252
  function validateUniqueNames(journal, label, conflicts) {
8097
8253
  const seen = new Set;
@@ -8132,27 +8288,27 @@ function encodeJson(value) {
8132
8288
  `);
8133
8289
  }
8134
8290
  async function readJson(path2) {
8135
- return JSON.parse(await readFile15(path2, "utf8"));
8291
+ return JSON.parse(await readFile16(path2, "utf8"));
8136
8292
  }
8137
8293
 
8138
8294
  // src/update/planner-file.ts
8139
- import { readFile as readFile16 } from "fs/promises";
8140
- import { join as join21 } from "path";
8295
+ import { readFile as readFile17 } from "fs/promises";
8296
+ import { join as join22 } from "path";
8141
8297
 
8142
8298
  // src/update/merge.ts
8143
- import { mkdtemp as mkdtemp2, rm as rm6, writeFile as writeFile11 } from "fs/promises";
8299
+ import { mkdtemp as mkdtemp2, rm as rm6, writeFile as writeFile12 } from "fs/promises";
8144
8300
  import { tmpdir as tmpdir2 } from "os";
8145
- import { join as join20 } from "path";
8301
+ import { join as join21 } from "path";
8146
8302
  async function mergeTextFile(base, ours, theirs) {
8147
- const root = await mkdtemp2(join20(tmpdir2(), "windy-merge-"));
8303
+ const root = await mkdtemp2(join21(tmpdir2(), "windy-merge-"));
8148
8304
  try {
8149
- const oursPath = join20(root, "ours");
8150
- const basePath = join20(root, "base");
8151
- const theirsPath = join20(root, "theirs");
8305
+ const oursPath = join21(root, "ours");
8306
+ const basePath = join21(root, "base");
8307
+ const theirsPath = join21(root, "theirs");
8152
8308
  await Promise.all([
8153
- writeFile11(oursPath, ours),
8154
- writeFile11(basePath, base),
8155
- writeFile11(theirsPath, theirs)
8309
+ writeFile12(oursPath, ours),
8310
+ writeFile12(basePath, base),
8311
+ writeFile12(theirsPath, theirs)
8156
8312
  ]);
8157
8313
  const result2 = await captureProcess("git", ["merge-file", "-p", "--diff3", oursPath, basePath, theirsPath], root);
8158
8314
  if (result2.code === 0) {
@@ -8406,10 +8562,10 @@ function conflict(path2, reason, expectedProjectContent) {
8406
8562
  // src/update/planner-file.ts
8407
8563
  async function planFile(path2, baseRoot, targetRoot, source, target, ours, provenance, targetContentOverride, manageGeneratedProjectOwned = false, forcePlatformBaseline = false) {
8408
8564
  const [base, theirs] = await Promise.all([
8409
- readOptional(join21(baseRoot, path2)),
8410
- targetContentOverride ? Promise.resolve(targetContentOverride) : readOptional(join21(targetRoot, path2))
8565
+ readOptional(join22(baseRoot, path2)),
8566
+ targetContentOverride ? Promise.resolve(targetContentOverride) : readOptional(join22(targetRoot, path2))
8411
8567
  ]);
8412
- const ownership = source?.ownership === "project-owned" ? source.ownership : target?.ownership || source?.ownership || classifyOwnership(path2);
8568
+ const ownership = source?.ownership ?? target?.ownership ?? classifyOwnership(path2);
8413
8569
  const expected = expectationForSnapshot(ours);
8414
8570
  if (ownership === "project-owned") {
8415
8571
  if (manageGeneratedProjectOwned) {
@@ -8514,7 +8670,7 @@ function conflict2(path2, ownership, reason, expectedProjectContent) {
8514
8670
  }
8515
8671
  async function readOptional(path2) {
8516
8672
  try {
8517
- return await readFile16(path2);
8673
+ return await readFile17(path2);
8518
8674
  } catch (error) {
8519
8675
  if (error instanceof Error && "code" in error && error.code === "ENOENT") {
8520
8676
  return;
@@ -8523,28 +8679,43 @@ async function readOptional(path2) {
8523
8679
  }
8524
8680
  }
8525
8681
 
8682
+ // src/update/planner-modules.ts
8683
+ function resolveModuleRecipeIds(request) {
8684
+ const lifecycle = request.lifecycle;
8685
+ if (!lifecycle)
8686
+ return [];
8687
+ const catalog = new Map(moduleDistributionCatalog.map((manifest) => [manifest.name, manifest]));
8688
+ if (lifecycle.action === "add") {
8689
+ return lifecycle.addedModules.map((name) => catalog.get(name).installRecipe);
8690
+ }
8691
+ if (lifecycle.action === "remove") {
8692
+ return lifecycle.removedModules.map((name) => catalog.get(name).uninstallRecipe);
8693
+ }
8694
+ return lifecycle.updatedModules.flatMap((name) => catalog.get(name).updateRecipes);
8695
+ }
8696
+
8526
8697
  // src/update/planner-snapshots.ts
8527
- import { readFile as readFile17, stat as stat2 } from "fs/promises";
8528
- import { join as join22 } from "path";
8698
+ import { readFile as readFile18, stat as stat2 } from "fs/promises";
8699
+ import { join as join23 } from "path";
8529
8700
  async function snapshotProjectFiles(project, paths, seed) {
8530
8701
  const snapshots = new Map(seed);
8531
8702
  await Promise.all([...paths].filter((path2) => !snapshots.has(path2)).map(async (path2) => {
8532
- snapshots.set(path2, await readOptional2(join22(project, path2)));
8703
+ snapshots.set(path2, await readOptional2(join23(project, path2)));
8533
8704
  }));
8534
8705
  return snapshots;
8535
8706
  }
8536
8707
  async function snapshotProjectModes(project, paths) {
8537
- return new Map(await Promise.all([...paths].map(async (path2) => [path2, await readModeOptional(join22(project, path2))])));
8708
+ return new Map(await Promise.all([...paths].map(async (path2) => [path2, await readModeOptional(join23(project, path2))])));
8538
8709
  }
8539
8710
  async function assertPlanSnapshots(project, expectations) {
8540
8711
  const changed = [];
8541
8712
  for (const expectation2 of expectations) {
8542
- const actual = expectationForSnapshot(await readOptional2(join22(project, expectation2.path)));
8713
+ const actual = expectationForSnapshot(await readOptional2(join23(project, expectation2.path)));
8543
8714
  if (JSON.stringify(actual) !== JSON.stringify(expectation2.expected)) {
8544
8715
  changed.push(expectation2.path);
8545
8716
  continue;
8546
8717
  }
8547
- if ("mode" in expectation2 && await readModeOptional(join22(project, expectation2.path)) !== expectation2.mode) {
8718
+ if ("mode" in expectation2 && await readModeOptional(join23(project, expectation2.path)) !== expectation2.mode) {
8548
8719
  changed.push(expectation2.path);
8549
8720
  }
8550
8721
  }
@@ -8556,7 +8727,7 @@ async function readModeOptional(path2) {
8556
8727
  try {
8557
8728
  return (await stat2(path2)).mode & 511;
8558
8729
  } catch (error) {
8559
- if (isMissing6(error))
8730
+ if (isMissing7(error))
8560
8731
  return;
8561
8732
  throw error;
8562
8733
  }
@@ -8569,21 +8740,21 @@ async function readSourceParentMode(path2) {
8569
8740
  }
8570
8741
  return info.mode & 493 | 493;
8571
8742
  } catch (error) {
8572
- if (isMissing6(error))
8743
+ if (isMissing7(error))
8573
8744
  return 493;
8574
8745
  throw error;
8575
8746
  }
8576
8747
  }
8577
8748
  async function readOptional2(path2) {
8578
8749
  try {
8579
- return await readFile17(path2);
8750
+ return await readFile18(path2);
8580
8751
  } catch (error) {
8581
- if (isMissing6(error))
8752
+ if (isMissing7(error))
8582
8753
  return;
8583
8754
  throw error;
8584
8755
  }
8585
8756
  }
8586
- function isMissing6(error) {
8757
+ function isMissing7(error) {
8587
8758
  return error instanceof Error && "code" in error && error.code === "ENOENT";
8588
8759
  }
8589
8760
 
@@ -8620,8 +8791,8 @@ var oxcManagedPaths = new Set([
8620
8791
  ]);
8621
8792
  async function createUpdatePlan(request, artifacts) {
8622
8793
  await assertProjectReadyForUpdate(request.projectDirectory);
8623
- const manifestPath = join23(request.projectDirectory, ".windy/files.json");
8624
- const expectedManifestHash = hashContent(await readFile18(manifestPath));
8794
+ const manifestPath = join24(request.projectDirectory, ".windy/files.json");
8795
+ const expectedManifestHash = hashContent(await readFile19(manifestPath));
8625
8796
  const generationSnapshot = await readGenerationProvenanceSnapshot(request.projectDirectory);
8626
8797
  const provenance = generationSnapshot.provenance;
8627
8798
  const targetModules = resolveUpdateTargetModules(provenance.modules.map(({ name }) => name), request.targetModules);
@@ -8667,8 +8838,8 @@ async function createUpdatePlan(request, artifacts) {
8667
8838
  const paths = new Set([...sourceEntries.keys(), ...targetEntries.keys()]);
8668
8839
  for (const path2 of paths) {
8669
8840
  if (!projectSnapshots.has(path2)) {
8670
- projectSnapshots.set(path2, await readOptional2(join23(request.projectDirectory, path2)));
8671
- projectModes.set(path2, await readModeOptional(join23(request.projectDirectory, path2)));
8841
+ projectSnapshots.set(path2, await readOptional2(join24(request.projectDirectory, path2)));
8842
+ projectModes.set(path2, await readModeOptional(join24(request.projectDirectory, path2)));
8672
8843
  }
8673
8844
  }
8674
8845
  const files = await planFiles({
@@ -8690,10 +8861,10 @@ async function createUpdatePlan(request, artifacts) {
8690
8861
  for (const file of files) {
8691
8862
  file.expectedProjectMode = projectModes.get(file.path);
8692
8863
  if (file.action === "create" || file.action === "update") {
8693
- file.mode = file.expectedProjectMode ?? await readModeOptional(join23(artifactSet.targetDirectory, file.path));
8864
+ file.mode = file.expectedProjectMode ?? await readModeOptional(join24(artifactSet.targetDirectory, file.path));
8694
8865
  }
8695
8866
  if (file.action === "create") {
8696
- file.parentMode = await readSourceParentMode(join23(artifactSet.targetDirectory, dirname7(file.path)));
8867
+ file.parentMode = await readSourceParentMode(join24(artifactSet.targetDirectory, dirname7(file.path)));
8697
8868
  }
8698
8869
  }
8699
8870
  const plannedPaths = new Set(files.map(({ path: path2 }) => path2));
@@ -8713,7 +8884,7 @@ async function createUpdatePlan(request, artifacts) {
8713
8884
  ...migrations.conflicts,
8714
8885
  ...files.flatMap((file) => file.conflict ? [`${file.path}\uFF1A${file.conflict}`] : [])
8715
8886
  ];
8716
- if (hashContent(await readFile18(manifestPath)) !== expectedManifestHash) {
8887
+ if (hashContent(await readFile19(manifestPath)) !== expectedManifestHash) {
8717
8888
  throw new Error("\u751F\u6210\u66F4\u65B0\u8BA1\u5212\u671F\u95F4\u53D7\u7BA1\u6E05\u5355\u53D1\u751F\u53D8\u5316");
8718
8889
  }
8719
8890
  await assertPlanSnapshots(request.projectDirectory, [
@@ -8745,19 +8916,6 @@ async function createUpdatePlan(request, artifacts) {
8745
8916
  lifecycle: request.lifecycle
8746
8917
  };
8747
8918
  }
8748
- function resolveModuleRecipeIds(request) {
8749
- const lifecycle = request.lifecycle;
8750
- if (!lifecycle)
8751
- return [];
8752
- const catalog = new Map(moduleDistributionCatalog.map((manifest) => [manifest.name, manifest]));
8753
- if (lifecycle.action === "add") {
8754
- return lifecycle.addedModules.map((name) => catalog.get(name).installRecipe);
8755
- }
8756
- if (lifecycle.action === "remove") {
8757
- return lifecycle.removedModules.map((name) => catalog.get(name).uninstallRecipe);
8758
- }
8759
- return lifecycle.updatedModules.flatMap((name) => catalog.get(name).updateRecipes);
8760
- }
8761
8919
  async function planFiles(input) {
8762
8920
  const files = [];
8763
8921
  for (const path2 of [...input.paths].sort()) {
@@ -8851,9 +9009,9 @@ function frozenManifestHash(file, finalizedHashes) {
8851
9009
  }
8852
9010
 
8853
9011
  // src/update/format-stabilizer.ts
8854
- import { mkdir as mkdir8, mkdtemp as mkdtemp3, readFile as readFile19, rm as rm7, writeFile as writeFile12 } from "fs/promises";
9012
+ import { mkdir as mkdir8, mkdtemp as mkdtemp3, readFile as readFile20, rm as rm7, writeFile as writeFile13 } from "fs/promises";
8855
9013
  import { tmpdir as tmpdir3 } from "os";
8856
- import { dirname as dirname8, extname as extname4, join as join24, resolve as resolve6, sep as sep5 } from "path";
9014
+ import { dirname as dirname8, extname as extname4, join as join25, resolve as resolve6, sep as sep5 } from "path";
8857
9015
  var formattedExtensions = new Set([
8858
9016
  ".cjs",
8859
9017
  ".css",
@@ -8892,7 +9050,7 @@ async function stabilizeManagedContent(result2, execute, writeProjectFile) {
8892
9050
  }
8893
9051
  }
8894
9052
  inputs.set(".windy/generation.json", new TextEncoder().encode(generationContent));
8895
- const temporaryRoot = await mkdtemp3(join24(tmpdir3(), "windy-update-format-"));
9053
+ const temporaryRoot = await mkdtemp3(join25(tmpdir3(), "windy-update-format-"));
8896
9054
  try {
8897
9055
  const stagedPaths = await stageInputs(temporaryRoot, inputs);
8898
9056
  for (let offset = 0;offset < stagedPaths.length; offset += 80) {
@@ -8900,14 +9058,14 @@ async function stabilizeManagedContent(result2, execute, writeProjectFile) {
8900
9058
  "x",
8901
9059
  "oxfmt",
8902
9060
  "--write",
8903
- `--config=${join24(result2.plan.projectDirectory, ".oxfmtrc.json")}`,
9061
+ `--config=${join25(result2.plan.projectDirectory, ".oxfmtrc.json")}`,
8904
9062
  ...stagedPaths.slice(offset, offset + 80)
8905
9063
  ], result2.plan.projectDirectory);
8906
9064
  }
8907
9065
  const hashes = plannedContentHashes(result2);
8908
9066
  let formattedGeneration = generationContent;
8909
9067
  for (const [path2, original] of inputs) {
8910
- const formatted = await readFile19(safeTemporaryPath(temporaryRoot, path2));
9068
+ const formatted = await readFile20(safeTemporaryPath(temporaryRoot, path2));
8911
9069
  if (path2 === ".windy/generation.json") {
8912
9070
  formattedGeneration = formatted.toString();
8913
9071
  continue;
@@ -8934,7 +9092,7 @@ async function stageInputs(temporaryRoot, inputs) {
8934
9092
  for (const [path2, content] of inputs) {
8935
9093
  const target = safeTemporaryPath(temporaryRoot, path2);
8936
9094
  await mkdir8(dirname8(target), { recursive: true });
8937
- await writeFile12(target, content);
9095
+ await writeFile13(target, content);
8938
9096
  paths.push(target);
8939
9097
  }
8940
9098
  return paths;
@@ -9061,7 +9219,7 @@ class DefaultStarterUpdater {
9061
9219
  const plannedPaths = new Set(result2.plan.files.map(({ path: path2 }) => path2));
9062
9220
  replaceExpectations(result2, verificationSnapshot.filter(({ path: path2 }) => !metadataPaths2.has(path2) && !plannedPaths.has(path2)));
9063
9221
  verificationExpectationsFrozen = true;
9064
- const packageJson = JSON.parse(await readFile20(join25(result2.plan.projectDirectory, "package.json"), "utf8"));
9222
+ const packageJson = JSON.parse(await readFile21(join26(result2.plan.projectDirectory, "package.json"), "utf8"));
9065
9223
  for (const name of ["lint", "typecheck", "test", "build"]) {
9066
9224
  if (!packageJson.scripts?.[name]) {
9067
9225
  steps.push({ name: `bun run ${name}`, status: "skipped" });
@@ -9145,8 +9303,10 @@ function expectedForPath(result2, path2) {
9145
9303
  }
9146
9304
 
9147
9305
  // src/module-lifecycle.ts
9148
- import { access as access2, rm as rm9 } from "fs/promises";
9149
- import { resolve as resolve7 } from "path";
9306
+ import { rm as rm9 } from "fs/promises";
9307
+
9308
+ // src/project-module-activation.ts
9309
+ import { randomUUID as randomUUID4 } from "crypto";
9150
9310
 
9151
9311
  // src/module-version-range.ts
9152
9312
  function isVersionInCaretRange(version, range) {
@@ -9172,67 +9332,199 @@ function compareVersion(left, right) {
9172
9332
  return 0;
9173
9333
  }
9174
9334
 
9175
- // src/module-lifecycle.ts
9176
- async function runModuleLifecycleCommand(args, projectDirectory, dependencies = {}) {
9177
- const action = args[0];
9178
- if (!isAction(action)) {
9179
- throw new Error("module \u53EA\u652F\u6301 list\u3001add\u3001update\u3001remove");
9180
- }
9181
- const log = dependencies.log ?? console.log;
9182
- const provenance = await readGenerationProvenance(projectDirectory);
9183
- const installed = provenance.modules.map(({ name }) => name);
9184
- if (action === "list") {
9185
- printModuleList(provenance.modules, log);
9186
- return;
9335
+ // src/project-module-activation.ts
9336
+ function planProjectModuleActivation(input) {
9337
+ if (input.requestedModules.length === 0) {
9338
+ throw new Error(`module ${input.action} \u81F3\u5C11\u9700\u8981\u4E00\u4E2A\u6A21\u5757 key`);
9187
9339
  }
9188
- const cliVersion = dependencies.cliVersion ?? (await readTemplateMetadata(await resolveTemplateRoot())).generatorVersion;
9189
- if (provenance.generatorVersion !== cliVersion || provenance.windyVersion !== cliVersion) {
9190
- throw new Error(`module \u547D\u4EE4\u8981\u6C42 CLI \u4E0E\u9879\u76EE Windy \u7248\u672C\u4E00\u81F4\uFF1A\u9879\u76EE ${provenance.windyVersion}\uFF0CCLI ${cliVersion}\uFF1B\u8BF7\u5148\u8FD0\u884C bun run windy update`);
9340
+ const catalog = new Map(moduleDistributionCatalog.map((manifest) => [manifest.name, manifest]));
9341
+ const installed = new Set(input.registry.modules.map(({ name }) => name));
9342
+ for (const name of input.requestedModules) {
9343
+ const manifest = catalog.get(name);
9344
+ if (!manifest)
9345
+ throw new Error(`\u672A\u77E5\u6A21\u5757\uFF1A${name}`);
9346
+ if (!installed.has(name))
9347
+ throw new Error(`\u672A\u5B89\u88C5\u6A21\u5757\u4E0D\u80FD\u542F\u505C\uFF1A${name}`);
9348
+ assertCompatible(input.windyVersion, manifest.windyRange, name);
9349
+ }
9350
+ const target = new Map(input.registry.modules.map(({ name, enabled }) => [name, enabled]));
9351
+ if (input.action === "enable") {
9352
+ for (const name of input.requestedModules) {
9353
+ enableWithDependencies(name, target, installed, catalog, new Set);
9354
+ }
9355
+ } else {
9356
+ assertOptionalModules(input.requestedModules);
9357
+ assertRuntimeToggleSupported(input.requestedModules, catalog);
9358
+ for (const name of input.requestedModules)
9359
+ target.set(name, false);
9360
+ assertNoEnabledDependents(target, catalog);
9361
+ }
9362
+ assertEnabledCompatibility(target, input.windyVersion, catalog);
9363
+ assertEnabledConflicts(target, catalog);
9364
+ const modules = input.registry.modules.map((registration) => ({
9365
+ ...registration,
9366
+ enabled: target.get(registration.name)
9367
+ }));
9368
+ const changedModules = modules.filter((registration, index) => registration.enabled !== input.registry.modules[index]?.enabled).map(({ name }) => name);
9369
+ const registry = {
9370
+ schemaVersion: 1,
9371
+ modules
9372
+ };
9373
+ return {
9374
+ action: input.action,
9375
+ requestedModules: input.requestedModules,
9376
+ changedModules,
9377
+ expectedRegistryHash: input.expectedRegistryHash,
9378
+ registry,
9379
+ content: `${JSON.stringify(registry, null, 2)}
9380
+ `
9381
+ };
9382
+ }
9383
+ function assertRuntimeToggleSupported(names, catalog) {
9384
+ const unsupported = names.filter((name) => catalog.get(name)?.runtimeActivation === "physical-only");
9385
+ if (unsupported.length) {
9386
+ throw new Error(`${unsupported.join("\u3001")} \u4E0D\u652F\u6301\u5B89\u5168 runtime disable\uFF1B\u5DF2\u90E8\u7F72\u9879\u76EE\u9700\u7531\u4EBA\u5DE5\u8FC1\u79FB\u5B8C\u6210\u7269\u7406\u5378\u8F7D`);
9191
9387
  }
9192
- const requested = positionalModules(args.slice(1));
9193
- const lifecycle = await resolveLifecyclePlan({
9194
- action,
9195
- requested,
9196
- installed,
9197
- windyVersion: provenance.windyVersion,
9198
- args,
9199
- projectDirectory
9388
+ }
9389
+ async function applyProjectModuleActivation(projectDirectory, plan) {
9390
+ const state = await applyMetadataTransaction({
9391
+ id: `module-${plan.action}-${randomUUID4()}`,
9392
+ projectDirectory,
9393
+ guards: [
9394
+ {
9395
+ path: projectModuleRegistryPath,
9396
+ hash: plan.expectedRegistryHash
9397
+ }
9398
+ ],
9399
+ writes: [{ path: projectModuleRegistryPath, content: plan.content }]
9200
9400
  });
9201
- printDataPlan(lifecycle, log);
9202
- if (!hasChanges(lifecycle)) {
9203
- log("\u6A21\u5757\u96C6\u5408\u5DF2\u6EE1\u8DB3\u8BF7\u6C42\uFF0C\u65E0\u9700\u4FEE\u6539");
9204
- return;
9401
+ if (!state.ownerToken) {
9402
+ throw new Error("\u6A21\u5757\u88C5\u914D\u4E8B\u52A1\u7F3A\u5C11\u6240\u6709\u6743\u4EE4\u724C");
9205
9403
  }
9206
- const targetModules = resolveTargetModules(installed, lifecycle);
9207
- const updater = dependencies.updater ?? new DefaultStarterUpdater;
9208
- const plan = await updater.plan({
9404
+ await finalizeOwnedTransaction({
9209
9405
  projectDirectory,
9210
- targetVersion: provenance.windyVersion,
9211
- targetModules,
9212
- lifecycle
9406
+ ownerToken: state.ownerToken,
9407
+ expectations: [
9408
+ expectationForContent(projectModuleRegistryPath, plan.content)
9409
+ ]
9213
9410
  });
9214
- printModulePlan(plan, log);
9215
- if (args.includes("--dry-run")) {
9216
- await rm9(plan.temporaryRoot, { recursive: true, force: true });
9217
- log(`
9218
- \u6F14\u7EC3\u5B8C\u6210\uFF1A\u672A\u5199\u5165\u9879\u76EE\u6587\u4EF6`);
9219
- return;
9411
+ }
9412
+ function enableWithDependencies(name, target, installed, catalog, visiting) {
9413
+ if (visiting.has(name))
9414
+ throw new Error(`\u6A21\u5757\u4F9D\u8D56\u5B58\u5728\u5FAA\u73AF\uFF1A${name}`);
9415
+ visiting.add(name);
9416
+ const manifest = catalog.get(name);
9417
+ for (const dependency of manifest.dependencies) {
9418
+ if (!installed.has(dependency)) {
9419
+ throw new Error(`${name} \u7F3A\u5C11\u5DF2\u5B89\u88C5\u4F9D\u8D56\uFF1A${dependency}`);
9420
+ }
9421
+ enableWithDependencies(dependency, target, installed, catalog, visiting);
9220
9422
  }
9221
- if (action === "remove" && !args.includes("--confirm-data-disposition")) {
9222
- await rm9(plan.temporaryRoot, { recursive: true, force: true });
9223
- throw new Error("\u5378\u8F7D\u524D\u5FC5\u987B\u5BA1\u9605\u6570\u636E\u5904\u7F6E\u8BA1\u5212\u5E76\u4F20\u5165 --confirm-data-disposition");
9423
+ visiting.delete(name);
9424
+ target.set(name, true);
9425
+ }
9426
+ function assertOptionalModules(names) {
9427
+ const required = names.filter((name) => requiredModuleNames.includes(name));
9428
+ if (required.length) {
9429
+ throw new Error(`\u6838\u5FC3\u6A21\u5757\u4E0D\u53EF\u505C\u7528\uFF1A${required.join("\u3001")}`);
9224
9430
  }
9225
- if (plan.conflicts.length) {
9226
- await rm9(plan.temporaryRoot, { recursive: true, force: true });
9227
- throw new Error("\u5B58\u5728\u6A21\u5757\u751F\u547D\u5468\u671F\u51B2\u7A81\uFF0C\u9879\u76EE\u672A\u88AB\u4FEE\u6539");
9431
+ }
9432
+ function assertNoEnabledDependents(target, catalog) {
9433
+ const blockers = [...target].filter(([, enabled]) => enabled).flatMap(([name]) => {
9434
+ const disabled = catalog.get(name).dependencies.filter((dependency) => !target.get(dependency));
9435
+ return disabled.map((dependency) => `${name}\u2192${dependency}`);
9436
+ });
9437
+ if (blockers.length) {
9438
+ throw new Error(`\u542F\u7528\u6A21\u5757\u4ECD\u4F9D\u8D56\u5F85\u505C\u7528\u6A21\u5757\uFF1A${blockers.join("\u3001")}`);
9228
9439
  }
9229
- const result2 = await updater.apply(plan);
9230
- const report = await updater.verify(result2);
9231
- log(`
9232
- \u6A21\u5757${actionLabel(action)}\u5B8C\u6210\uFF1A${requested.join("\u3001")}`);
9233
- log(`\u62A5\u544A\uFF1A${report.reportPath}`);
9234
9440
  }
9235
- async function resolveLifecyclePlan(input) {
9441
+ function assertEnabledCompatibility(target, version, catalog) {
9442
+ for (const [name, enabled] of target) {
9443
+ if (!enabled)
9444
+ continue;
9445
+ const manifest = catalog.get(name);
9446
+ assertCompatible(version, manifest.windyRange, name);
9447
+ }
9448
+ }
9449
+ function assertEnabledConflicts(target, catalog) {
9450
+ for (const [name, enabled] of target) {
9451
+ if (!enabled)
9452
+ continue;
9453
+ const conflict3 = catalog.get(name).conflicts.find((candidate) => target.get(candidate));
9454
+ if (conflict3)
9455
+ throw new Error(`\u6A21\u5757\u51B2\u7A81\uFF1A${name} \u4E0E ${conflict3}`);
9456
+ }
9457
+ }
9458
+ function assertCompatible(version, range, name) {
9459
+ if (!isVersionInCaretRange(version, range)) {
9460
+ throw new Error(`${name} \u8981\u6C42 Windy ${range}\uFF0C\u5F53\u524D\u4E3A ${version}`);
9461
+ }
9462
+ }
9463
+
9464
+ // src/module-lifecycle-plan.ts
9465
+ function reconcileProjectModuleRegistryPlan(plan, current, lifecycle) {
9466
+ const registryFile = plan.files.find(({ path: path2 }) => path2 === projectModuleRegistryPath);
9467
+ if (!registryFile) {
9468
+ throw new Error("\u6A21\u5757\u751F\u547D\u5468\u671F\u8BA1\u5212\u7F3A\u5C11 project-owned \u88C5\u914D\u9009\u62E9");
9469
+ }
9470
+ plan.conflicts = plan.conflicts.filter((conflict3) => !conflict3.startsWith(`${projectModuleRegistryPath}\uFF1A`));
9471
+ delete registryFile.conflict;
9472
+ if (lifecycle.action === "update") {
9473
+ registryFile.action = "preserve";
9474
+ registryFile.reason = "\u6A21\u5757\u66F4\u65B0\u4FDD\u7559\u5BA2\u6237\u88C5\u914D\u9009\u62E9";
9475
+ delete registryFile.content;
9476
+ return;
9477
+ }
9478
+ const removed = new Set(lifecycle.removedModules);
9479
+ const modules = current.modules.filter(({ name }) => !removed.has(name));
9480
+ let nextOrder = Math.max(0, ...modules.map(({ order }) => order)) + 100;
9481
+ const catalog = new Map(moduleDistributionCatalog.map((manifest) => [manifest.name, manifest]));
9482
+ for (const name of lifecycle.addedModules) {
9483
+ if (modules.some((registration) => registration.name === name))
9484
+ continue;
9485
+ const manifest = catalog.get(name);
9486
+ if (!manifest)
9487
+ throw new Error(`\u672A\u77E5\u6A21\u5757\uFF1A${name}`);
9488
+ modules.push(newRegistration(name, manifest.runtimePackage, nextOrder));
9489
+ nextOrder += 100;
9490
+ }
9491
+ const registry = { schemaVersion: 1, modules };
9492
+ registryFile.action = "update";
9493
+ registryFile.reason = "\u6309 module name \u5408\u5E76\u9879\u76EE\u88C5\u914D\u9009\u62E9";
9494
+ registryFile.content = new TextEncoder().encode(`${JSON.stringify(registry, null, 2)}
9495
+ `);
9496
+ }
9497
+ function assertModulePlanUsesStableComposition(plan) {
9498
+ const unsafe = plan.files.filter(({ action, path: path2, reason }) => action !== "preserve" && (isCustomerApplicationOrHost(path2) || isDockerfile(path2) && reason !== "\u6309\u4E8B\u52A1\u5B89\u5168 workspace \u89C6\u56FE\u540C\u6B65 Docker COPY \u6E05\u5355")).map(({ path: path2 }) => path2);
9499
+ if (unsafe.length === 0)
9500
+ return;
9501
+ throw new Error([
9502
+ "\u6A21\u5757\u5305/Host \u5C1A\u672A\u6EE1\u8DB3\u65B0\u534F\u8BAE\uFF0C\u62D2\u7EDD\u901A\u8FC7\u6A21\u677F\u518D\u751F\u6210\u6539\u5199\u5BA2\u6237 Admin \u6216 Host\uFF1A",
9503
+ ...unsafe.map((path2) => ` ${path2}`)
9504
+ ].join(`
9505
+ `));
9506
+ }
9507
+ function isDockerfile(path2) {
9508
+ return path2 === "apps/web/Dockerfile" || path2 === "apps/server/Dockerfile";
9509
+ }
9510
+ function isCustomerApplicationOrHost(path2) {
9511
+ return path2 === "apps/web/src" || path2.startsWith("apps/web/src/") || path2 === "apps/server/src/index.ts" || path2 === "apps/server/src/installed-business-modules.ts" || path2 === "apps/server/src/app" || path2.startsWith("apps/server/src/app/");
9512
+ }
9513
+ function newRegistration(name, runtimePackage, order) {
9514
+ return {
9515
+ name,
9516
+ package: runtimePackage,
9517
+ enabled: true,
9518
+ order,
9519
+ admin: { visible: true },
9520
+ config: {}
9521
+ };
9522
+ }
9523
+
9524
+ // src/module-lifecycle-resolver.ts
9525
+ import { access as access2 } from "fs/promises";
9526
+ import { resolve as resolve7 } from "path";
9527
+ async function resolveModuleLifecyclePlan(input) {
9236
9528
  const known = new Map(moduleDistributionCatalog.map((manifest) => [manifest.name, manifest]));
9237
9529
  const requested = input.action === "update" && input.requested.length === 0 ? [...input.installed] : input.requested;
9238
9530
  if (!requested.length) {
@@ -9253,10 +9545,12 @@ async function resolveLifecyclePlan(input) {
9253
9545
  const manifest = known.get(name);
9254
9546
  assertWindyCompatible(input.windyVersion, manifest.windyRange, manifest.name);
9255
9547
  }
9548
+ const addedModules = target.filter((name) => !input.installed.includes(name));
9549
+ assertDeploymentLifecycleSupported(addedModules, known);
9256
9550
  return {
9257
9551
  action: input.action,
9258
9552
  requestedModules: requested,
9259
- addedModules: target.filter((name) => !input.installed.includes(name)),
9553
+ addedModules,
9260
9554
  updatedModules: [],
9261
9555
  removedModules: []
9262
9556
  };
@@ -9266,6 +9560,7 @@ async function resolveLifecyclePlan(input) {
9266
9560
  if (absent.length) {
9267
9561
  throw new Error(`\u672A\u5B89\u88C5\u6A21\u5757\u4E0D\u80FD\u66F4\u65B0\uFF1A${absent.join("\u3001")}`);
9268
9562
  }
9563
+ assertDeploymentLifecycleSupported(requested, known);
9269
9564
  return {
9270
9565
  action: input.action,
9271
9566
  requestedModules: requested,
@@ -9279,6 +9574,7 @@ async function resolveLifecyclePlan(input) {
9279
9574
  throw new Error(`\u6838\u5FC3\u6A21\u5757\u4E0D\u53EF\u5378\u8F7D\uFF1A${required.join("\u3001")}`);
9280
9575
  }
9281
9576
  const removed = requested.filter((name) => input.installed.includes(name));
9577
+ assertDeploymentLifecycleSupported(removed, known);
9282
9578
  assertRemovalDependencies(input.installed, removed);
9283
9579
  const dataDisposition = resolveDataDisposition(removed.map((name) => known.get(name).uninstallPolicy), readValue2(input.args, "--data-disposition"));
9284
9580
  const dataPlan = readValue2(input.args, "--data-plan");
@@ -9298,7 +9594,13 @@ async function resolveLifecyclePlan(input) {
9298
9594
  dataPlan
9299
9595
  };
9300
9596
  }
9301
- function resolveTargetModules(installed, lifecycle) {
9597
+ function assertDeploymentLifecycleSupported(names, catalog) {
9598
+ const unsupported = names.filter((name) => catalog.get(name)?.deploymentInstall === "project-composition-required");
9599
+ if (unsupported.length) {
9600
+ throw new Error(`${unsupported.join("\u3001")} \u5C1A\u672A\u63D0\u4F9B self-contained Server Adapter\uFF1B\u90E8\u7F72\u540E add/update/remove \u4F1A\u6539\u5199 project-owned \u7EC4\u5408\u58F0\u660E\uFF0C\u5F53\u524D fail closed`);
9601
+ }
9602
+ }
9603
+ function resolveModuleLifecycleTarget(installed, lifecycle) {
9302
9604
  if (lifecycle.action === "add") {
9303
9605
  return resolveModuleSelection({
9304
9606
  requested: [...installed, ...lifecycle.addedModules],
@@ -9311,6 +9613,19 @@ function resolveTargetModules(installed, lifecycle) {
9311
9613
  }
9312
9614
  return installed;
9313
9615
  }
9616
+ function readPositionalModules(args) {
9617
+ const valueFlags = new Set(["--data-disposition", "--data-plan", "--cwd"]);
9618
+ const output = [];
9619
+ for (let index = 0;index < args.length; index += 1) {
9620
+ const value = args[index];
9621
+ if (valueFlags.has(value)) {
9622
+ index += 1;
9623
+ } else if (!value.startsWith("-")) {
9624
+ output.push(value);
9625
+ }
9626
+ }
9627
+ return [...new Set(output)];
9628
+ }
9314
9629
  function resolveDataDisposition(policies, requested) {
9315
9630
  const disposition = requested ?? strongestPolicy(policies);
9316
9631
  if (disposition !== "retain-data" && disposition !== "archive-data" && disposition !== "manual") {
@@ -9346,27 +9661,219 @@ function assertWindyCompatible(version, range, moduleName) {
9346
9661
  throw new Error(`${moduleName} \u8981\u6C42 Windy ${range}\uFF0C\u5F53\u524D\u4E3A ${version}`);
9347
9662
  }
9348
9663
  }
9349
- function positionalModules(args) {
9350
- const valueFlags = new Set(["--data-disposition", "--data-plan", "--cwd"]);
9351
- const output = [];
9352
- for (let index = 0;index < args.length; index += 1) {
9664
+ function readValue2(args, flag) {
9665
+ const index = args.indexOf(flag);
9666
+ return index >= 0 ? args[index + 1] : undefined;
9667
+ }
9668
+
9669
+ // src/module-lifecycle-help.ts
9670
+ var moduleLifecycleHelpText = `create-windy module <\u547D\u4EE4> [\u9009\u9879]
9671
+
9672
+ \u547D\u4EE4\uFF1A
9673
+ module list [--cwd <\u76EE\u5F55>]
9674
+ module add <key...> [--dry-run] [--skip-install] [--yes]
9675
+ module enable <key...> [--dry-run] [--yes]
9676
+ module disable <key...> [--dry-run] [--yes]
9677
+ module update [key...] [--dry-run] [--skip-install] [--yes]
9678
+ module remove <key...> [--dry-run] [--skip-install] [--yes]
9679
+ [--data-disposition <\u7B56\u7565>] [--data-plan <\u6587\u4EF6>]
9680
+ [--confirm-data-disposition]
9681
+
9682
+ \u8FB9\u754C\uFF1A
9683
+ \u5F53\u524D\u5185\u7F6E\u6E90\u7801\u6A21\u5757\u53EA\u652F\u6301\u521B\u5EFA\u9879\u76EE\u65F6\u9009\u62E9
9684
+ \u90E8\u7F72\u540E add/update/remove \u4EC5\u5BF9 self-contained \u6A21\u5757\u5F00\u653E\uFF0C\u5426\u5219 fail closed
9685
+ system.settings\u3001system.storage\u3001system.data-governance \u4E0D\u652F\u6301 runtime disable
9686
+ \u5DF2\u90E8\u7F72\u9879\u76EE\u4E0D\u4F1A\u88AB\u81EA\u52A8\u6539\u5199
9687
+
9688
+ \u6B63\u5F0F\u53D8\u66F4\u5728\u4EA4\u4E92\u7EC8\u7AEF\u786E\u8BA4\u540E\u6267\u884C\uFF1B\u975E\u4EA4\u4E92\u73AF\u5883\u5FC5\u987B\u4F20\u5165 --yes\u3002`;
9689
+ function assertKnownModuleArguments(args) {
9690
+ const action = args[0];
9691
+ const allowed = new Set(["--cwd", "--help", "-h"]);
9692
+ if (action !== "list") {
9693
+ allowed.add("--dry-run");
9694
+ allowed.add("--yes");
9695
+ }
9696
+ if (action === "add" || action === "update" || action === "remove") {
9697
+ allowed.add("--skip-install");
9698
+ }
9699
+ if (action === "remove") {
9700
+ allowed.add("--data-disposition");
9701
+ allowed.add("--data-plan");
9702
+ allowed.add("--confirm-data-disposition");
9703
+ }
9704
+ const valueFlags = new Set(["--cwd", "--data-disposition", "--data-plan"]);
9705
+ for (let index = 1;index < args.length; index += 1) {
9353
9706
  const value = args[index];
9354
- if (valueFlags.has(value)) {
9355
- index += 1;
9356
- } else if (!value.startsWith("-")) {
9357
- output.push(value);
9707
+ if (!value.startsWith("-"))
9708
+ continue;
9709
+ if (!allowed.has(value))
9710
+ throw new Error(`\u672A\u77E5 module \u53C2\u6570\uFF1A${value}`);
9711
+ if (!valueFlags.has(value))
9712
+ continue;
9713
+ const next = args[index + 1];
9714
+ if (!next || next.startsWith("-")) {
9715
+ throw new Error(`${value} \u7F3A\u5C11\u53C2\u6570\u503C`);
9358
9716
  }
9717
+ index += 1;
9359
9718
  }
9360
- return [...new Set(output)];
9361
9719
  }
9362
- function printModuleList(installed, log) {
9720
+
9721
+ // src/module-lifecycle-confirmation.ts
9722
+ import { createInterface as createInterface3 } from "readline/promises";
9723
+ async function confirmModuleMutation(args, dependencies, log) {
9724
+ if (args.includes("--yes"))
9725
+ return true;
9726
+ const isTTY = dependencies.isTTY ?? Boolean(process.stdin.isTTY && process.stdout.isTTY);
9727
+ if (!isTTY) {
9728
+ throw new Error("\u975E\u4EA4\u4E92\u73AF\u5883\u6B63\u5F0F\u53D8\u66F4\u5FC5\u987B\u4F20\u5165 --yes");
9729
+ }
9730
+ const confirm = dependencies.confirm ?? promptForConfirmation;
9731
+ const accepted = await confirm("\u786E\u8BA4\u6267\u884C\u4EE5\u4E0A\u6A21\u5757\u8BA1\u5212\uFF1F");
9732
+ if (!accepted)
9733
+ log("\u5DF2\u53D6\u6D88\uFF1A\u9879\u76EE\u672A\u88AB\u4FEE\u6539");
9734
+ return accepted;
9735
+ }
9736
+ async function promptForConfirmation(message) {
9737
+ const readline3 = createInterface3({
9738
+ input: process.stdin,
9739
+ output: process.stdout
9740
+ });
9741
+ try {
9742
+ const answer = await readline3.question(`${message} [y/N] `);
9743
+ return answer.trim().toLowerCase() === "y";
9744
+ } finally {
9745
+ readline3.close();
9746
+ }
9747
+ }
9748
+
9749
+ // src/module-lifecycle.ts
9750
+ async function runModuleLifecycleCommand(args, projectDirectory, dependencies = {}) {
9751
+ const log = dependencies.log ?? console.log;
9752
+ if (args.includes("--help") || args.includes("-h")) {
9753
+ log(moduleLifecycleHelpText);
9754
+ return;
9755
+ }
9756
+ const action = args[0];
9757
+ if (!isAction(action)) {
9758
+ throw new Error("module \u53EA\u652F\u6301 list\u3001add\u3001enable\u3001disable\u3001update\u3001remove");
9759
+ }
9760
+ assertKnownModuleArguments(args);
9761
+ const provenance = await readGenerationProvenance(projectDirectory);
9762
+ const installed = provenance.modules.map(({ name }) => name);
9763
+ const registrySnapshot = await readProjectModuleRegistrySnapshot(projectDirectory, installed);
9764
+ if (action === "list") {
9765
+ printModuleList(provenance.modules, registrySnapshot.registry.modules, log);
9766
+ return;
9767
+ }
9768
+ const cliVersion = dependencies.cliVersion ?? (await readTemplateMetadata(await resolveTemplateRoot())).generatorVersion;
9769
+ if (provenance.generatorVersion !== cliVersion || provenance.windyVersion !== cliVersion) {
9770
+ throw new Error(`module \u547D\u4EE4\u8981\u6C42 CLI \u4E0E\u9879\u76EE Windy \u7248\u672C\u4E00\u81F4\uFF1A\u9879\u76EE ${provenance.windyVersion}\uFF0CCLI ${cliVersion}\uFF1B\u8BF7\u5148\u8FD0\u884C bun run windy update`);
9771
+ }
9772
+ const requested = readPositionalModules(args.slice(1));
9773
+ if (action === "enable" || action === "disable") {
9774
+ const activation = planProjectModuleActivation({
9775
+ action,
9776
+ requestedModules: requested,
9777
+ windyVersion: provenance.windyVersion,
9778
+ expectedRegistryHash: registrySnapshot.hash,
9779
+ registry: registrySnapshot.registry
9780
+ });
9781
+ printActivationPlan(activation, log);
9782
+ if (!activation.changedModules.length) {
9783
+ log("\u6A21\u5757\u88C5\u914D\u5DF2\u6EE1\u8DB3\u8BF7\u6C42\uFF0C\u65E0\u9700\u4FEE\u6539");
9784
+ return;
9785
+ }
9786
+ if (args.includes("--dry-run")) {
9787
+ log(`
9788
+ \u6F14\u7EC3\u5B8C\u6210\uFF1A\u672A\u5199\u5165\u9879\u76EE\u6587\u4EF6`);
9789
+ return;
9790
+ }
9791
+ if (!await confirmModuleMutation(args, dependencies, log))
9792
+ return;
9793
+ await applyProjectModuleActivation(projectDirectory, activation);
9794
+ log(`
9795
+ \u6A21\u5757${action === "enable" ? "\u542F\u7528" : "\u505C\u7528"}\u5B8C\u6210`);
9796
+ return;
9797
+ }
9798
+ const lifecycle = await resolveModuleLifecyclePlan({
9799
+ action,
9800
+ requested,
9801
+ installed,
9802
+ windyVersion: provenance.windyVersion,
9803
+ args,
9804
+ projectDirectory
9805
+ });
9806
+ printDataPlan(lifecycle, log);
9807
+ if (!hasChanges(lifecycle)) {
9808
+ log("\u6A21\u5757\u96C6\u5408\u5DF2\u6EE1\u8DB3\u8BF7\u6C42\uFF0C\u65E0\u9700\u4FEE\u6539");
9809
+ return;
9810
+ }
9811
+ const targetModules = resolveModuleLifecycleTarget(installed, lifecycle);
9812
+ const updater = dependencies.updater ?? dependencies.updaterFactory?.({
9813
+ skipInstall: args.includes("--skip-install")
9814
+ }) ?? new DefaultStarterUpdater({
9815
+ skipInstall: args.includes("--skip-install")
9816
+ });
9817
+ const plan = await updater.plan({
9818
+ projectDirectory,
9819
+ targetVersion: provenance.windyVersion,
9820
+ targetModules,
9821
+ lifecycle
9822
+ });
9823
+ try {
9824
+ reconcileProjectModuleRegistryPlan(plan, registrySnapshot.registry, lifecycle);
9825
+ printModulePlan(plan, log);
9826
+ assertModulePlanUsesStableComposition(plan);
9827
+ } catch (error) {
9828
+ await rm9(plan.temporaryRoot, { recursive: true, force: true });
9829
+ throw error;
9830
+ }
9831
+ if (args.includes("--dry-run")) {
9832
+ await rm9(plan.temporaryRoot, { recursive: true, force: true });
9833
+ log(`
9834
+ \u6F14\u7EC3\u5B8C\u6210\uFF1A\u672A\u5199\u5165\u9879\u76EE\u6587\u4EF6`);
9835
+ return;
9836
+ }
9837
+ if (action === "remove" && !args.includes("--confirm-data-disposition")) {
9838
+ await rm9(plan.temporaryRoot, { recursive: true, force: true });
9839
+ throw new Error("\u5378\u8F7D\u524D\u5FC5\u987B\u5BA1\u9605\u6570\u636E\u5904\u7F6E\u8BA1\u5212\u5E76\u4F20\u5165 --confirm-data-disposition");
9840
+ }
9841
+ if (plan.conflicts.length) {
9842
+ await rm9(plan.temporaryRoot, { recursive: true, force: true });
9843
+ throw new Error("\u5B58\u5728\u6A21\u5757\u751F\u547D\u5468\u671F\u51B2\u7A81\uFF0C\u9879\u76EE\u672A\u88AB\u4FEE\u6539");
9844
+ }
9845
+ let confirmed;
9846
+ try {
9847
+ confirmed = await confirmModuleMutation(args, dependencies, log);
9848
+ } catch (error) {
9849
+ await rm9(plan.temporaryRoot, { recursive: true, force: true });
9850
+ throw error;
9851
+ }
9852
+ if (!confirmed) {
9853
+ await rm9(plan.temporaryRoot, { recursive: true, force: true });
9854
+ return;
9855
+ }
9856
+ const result2 = await updater.apply(plan);
9857
+ const report = await updater.verify(result2);
9858
+ log(`
9859
+ \u6A21\u5757${actionLabel(action)}\u5B8C\u6210\uFF1A${requested.join("\u3001")}`);
9860
+ log(`\u62A5\u544A\uFF1A${report.reportPath}`);
9861
+ }
9862
+ function printModuleList(installed, registered, log) {
9363
9863
  const installedVersions = new Map(installed.map(({ name, version }) => [name, version]));
9864
+ const catalog = new Map(moduleDistributionCatalog.map((manifest) => [manifest.name, manifest]));
9364
9865
  log("Windy \u6A21\u5757\uFF1A");
9866
+ for (const registration of [...registered].sort((left, right) => left.order - right.order)) {
9867
+ const manifest = catalog.get(registration.name);
9868
+ const installedVersion = installedVersions.get(registration.name);
9869
+ const status = registration.enabled ? "installed" : "disabled";
9870
+ log(` ${status.padEnd(9)} ${registration.name}@${installedVersion}\uFF08catalog ${manifest.version}\uFF09`);
9871
+ }
9365
9872
  for (const manifest of moduleDistributionCatalog) {
9366
- const installedVersion = installedVersions.get(manifest.name);
9367
- const status = installedVersion ? "installed" : "available";
9368
- const version = installedVersion ? `${manifest.name}@${installedVersion}\uFF08catalog ${manifest.version}\uFF09` : `${manifest.name}@${manifest.version}`;
9369
- log(` ${status.padEnd(9)} ${version}`);
9873
+ if (installedVersions.has(manifest.name))
9874
+ continue;
9875
+ const status = manifest.deploymentInstall === "supported" ? "available" : "creation-only";
9876
+ log(` ${status.padEnd(9)} ${manifest.name}@${manifest.version}\uFF08catalog ${manifest.version}\uFF09`);
9370
9877
  }
9371
9878
  }
9372
9879
  function printDataPlan(lifecycle, log) {
@@ -9379,6 +9886,11 @@ function printDataPlan(lifecycle, log) {
9379
9886
  if (lifecycle.dataPlan)
9380
9887
  log(` \u8FD0\u7EF4\u5904\u7F6E\u6587\u6863\uFF1A${lifecycle.dataPlan}`);
9381
9888
  }
9889
+ function printActivationPlan(plan, log) {
9890
+ log(`\u6A21\u5757\u88C5\u914D\u8BA1\u5212\uFF1A${plan.action} ${plan.requestedModules.join("\u3001")}`);
9891
+ log(` \u53EA\u5199\u5165 windy.modules.json`);
9892
+ log(` \u72B6\u6001\u53D8\u5316\uFF1A${plan.changedModules.join("\u3001") || "\u65E0"}`);
9893
+ }
9382
9894
  function printModulePlan(plan, log) {
9383
9895
  const changed = plan.files.filter(({ action }) => action !== "preserve");
9384
9896
  log(`\u6A21\u5757\u6587\u4EF6\u8BA1\u5212\uFF1A${changed.length} \u4E2A\u53D8\u66F4\uFF1B${plan.conflicts.length} \u4E2A\u51B2\u7A81`);
@@ -9391,12 +9903,8 @@ function printModulePlan(plan, log) {
9391
9903
  function hasChanges(lifecycle) {
9392
9904
  return lifecycle.addedModules.length > 0 || lifecycle.updatedModules.length > 0 || lifecycle.removedModules.length > 0;
9393
9905
  }
9394
- function readValue2(args, flag) {
9395
- const index = args.indexOf(flag);
9396
- return index >= 0 ? args[index + 1] : undefined;
9397
- }
9398
9906
  function isAction(value) {
9399
- return value === "list" || value === "add" || value === "update" || value === "remove";
9907
+ return value === "list" || value === "add" || value === "enable" || value === "disable" || value === "update" || value === "remove";
9400
9908
  }
9401
9909
  function actionLabel(action) {
9402
9910
  return action === "add" ? "\u5B89\u88C5" : action === "update" ? "\u66F4\u65B0" : "\u5378\u8F7D";
@@ -9629,20 +10137,20 @@ import {
9629
10137
  cp,
9630
10138
  mkdir as mkdir9,
9631
10139
  mkdtemp as mkdtemp4,
9632
- readFile as readFile22,
10140
+ readFile as readFile23,
9633
10141
  readdir as readdir9,
9634
10142
  rename as rename2,
9635
10143
  rm as rm11,
9636
- writeFile as writeFile13
10144
+ writeFile as writeFile14
9637
10145
  } from "fs/promises";
9638
- import { dirname as dirname9, join as join26, relative as relative6 } from "path";
10146
+ import { dirname as dirname9, join as join27, relative as relative6 } from "path";
9639
10147
 
9640
10148
  // src/module-schema/config.ts
9641
- import { readFile as readFile21 } from "fs/promises";
10149
+ import { readFile as readFile22 } from "fs/promises";
9642
10150
  import { isAbsolute as isAbsolute2, relative as relative5, resolve as resolve9 } from "path";
9643
10151
  async function readModuleSchemaLineageConfig(projectDirectory, configPath) {
9644
10152
  const absolutePath = resolveProjectPath(projectDirectory, configPath);
9645
- const parsed = JSON.parse(await readFile21(absolutePath, "utf8"));
10153
+ const parsed = JSON.parse(await readFile22(absolutePath, "utf8"));
9646
10154
  if (!isConfig(parsed)) {
9647
10155
  throw new Error("Module schema lineage \u914D\u7F6E\u65E0\u6548");
9648
10156
  }
@@ -9776,17 +10284,17 @@ async function handoffModuleSchemaLineages(options) {
9776
10284
  async function stageModuleLineages(project, temporaryRoot, config, execute) {
9777
10285
  const outputs = [];
9778
10286
  for (const [index, module3] of config.modules.entries()) {
9779
- const output = join26(temporaryRoot, `module-${index}`);
10287
+ const output = join27(temporaryRoot, `module-${index}`);
9780
10288
  await mkdir9(output, { recursive: true });
9781
- const generatedConfig = join26(temporaryRoot, `module-${index}.config.ts`);
10289
+ const generatedConfig = join27(temporaryRoot, `module-${index}.config.ts`);
9782
10290
  await writeDrizzleConfig(generatedConfig, project, module3.schema, relative6(temporaryRoot, output));
9783
10291
  await execute(generatedConfig, temporaryRoot);
9784
10292
  const sqlFiles = await listSqlFiles(output);
9785
10293
  if (sqlFiles.length !== 1) {
9786
10294
  throw new Error(`${module3.key} baseline \u5FC5\u987B\u6070\u597D\u751F\u6210\u4E00\u4E2A migration`);
9787
10295
  }
9788
- await writeFile13(join26(output, sqlFiles[0]), metadataOnlySql("module"));
9789
- await writeFile13(join26(output, "lineage.json"), `${JSON.stringify({
10296
+ await writeFile14(join27(output, sqlFiles[0]), metadataOnlySql("module"));
10297
+ await writeFile14(join27(output, "lineage.json"), `${JSON.stringify({
9790
10298
  schemaVersion: 1,
9791
10299
  moduleKey: module3.key,
9792
10300
  schema: module3.schema,
@@ -9800,25 +10308,25 @@ async function stageModuleLineages(project, temporaryRoot, config, execute) {
9800
10308
  }
9801
10309
  async function stagePlatformLineage(project, temporaryRoot, config, execute) {
9802
10310
  const source = resolveProjectPath(project, config.platform.out);
9803
- const output = join26(temporaryRoot, "platform");
10311
+ const output = join27(temporaryRoot, "platform");
9804
10312
  await cp(source, output, { recursive: true });
9805
10313
  const before = new Set(await listSqlFiles(output));
9806
- const generatedConfig = join26(temporaryRoot, "platform.config.ts");
10314
+ const generatedConfig = join27(temporaryRoot, "platform.config.ts");
9807
10315
  await writeDrizzleConfig(generatedConfig, project, config.platform.schema, relative6(temporaryRoot, output));
9808
10316
  await execute(generatedConfig, temporaryRoot);
9809
10317
  const created = (await listSqlFiles(output)).filter((path2) => !before.has(path2));
9810
10318
  if (created.length !== 1) {
9811
10319
  throw new Error("\u5E73\u53F0 lineage handoff \u5FC5\u987B\u6070\u597D\u751F\u6210\u4E00\u4E2A migration");
9812
10320
  }
9813
- const sqlPath = join26(output, created[0]);
10321
+ const sqlPath = join27(output, created[0]);
9814
10322
  const allowed = config.modules.flatMap(({ legacyPlatformTables }) => legacyPlatformTables);
9815
- validatePlatformHandoffSql(await readFile22(sqlPath, "utf8"), allowed);
9816
- await writeFile13(sqlPath, metadataOnlySql("platform"));
10323
+ validatePlatformHandoffSql(await readFile23(sqlPath, "utf8"), allowed);
10324
+ await writeFile14(sqlPath, metadataOnlySql("platform"));
9817
10325
  return output;
9818
10326
  }
9819
10327
  async function installStagedLineages(project, temporaryRoot, config, stagedModules, stagedPlatform) {
9820
10328
  const platform = resolveProjectPath(project, config.platform.out);
9821
- const backup = join26(temporaryRoot, "platform-backup");
10329
+ const backup = join27(temporaryRoot, "platform-backup");
9822
10330
  const installed = [];
9823
10331
  await rename2(platform, backup);
9824
10332
  try {
@@ -9842,7 +10350,7 @@ async function installStagedLineages(project, temporaryRoot, config, stagedModul
9842
10350
  }
9843
10351
  async function writeDrizzleConfig(path2, project, schemas, out) {
9844
10352
  const absoluteSchemas = schemas.map((schema) => resolveProjectPath(project, schema));
9845
- await writeFile13(path2, `import { defineConfig } from "drizzle-kit";
10353
+ await writeFile14(path2, `import { defineConfig } from "drizzle-kit";
9846
10354
  export default defineConfig(${JSON.stringify({
9847
10355
  dialect: "postgresql",
9848
10356
  schema: absoluteSchemas,
@@ -9865,9 +10373,9 @@ async function validateSchemaSources(project, config) {
9865
10373
  }
9866
10374
  }
9867
10375
  async function createTemporaryRoot(project) {
9868
- const windy = join26(project, ".windy");
10376
+ const windy = join27(project, ".windy");
9869
10377
  await mkdir9(windy, { recursive: true });
9870
- return await mkdtemp4(join26(windy, "module-schema-handoff-"));
10378
+ return await mkdtemp4(join27(windy, "module-schema-handoff-"));
9871
10379
  }
9872
10380
  async function listSqlFiles(root) {
9873
10381
  return (await readdir9(root)).filter((path2) => path2.endsWith(".sql")).sort();