create-windy 0.3.19 → 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 (213) hide show
  1. package/README.md +28 -13
  2. package/dist/cli.js +783 -280
  3. package/package.json +1 -1
  4. package/template/.agents/skills/windy-business-module/SKILL.md +34 -10
  5. package/template/.windy-template.json +2 -2
  6. package/template/AGENTS.md +18 -8
  7. package/template/README.md +32 -22
  8. package/template/apps/server/Dockerfile +1 -0
  9. package/template/apps/server/package.json +1 -0
  10. package/template/apps/server/scripts/validate-module-composition-once.ts +4 -0
  11. package/template/apps/server/scripts/validate-module-composition.ts +28 -0
  12. package/template/apps/server/src/agent/data-scope-routes.test.ts +20 -5
  13. package/template/apps/server/src/agent/routes.test.ts +20 -7
  14. package/template/apps/server/src/agent/routes.ts +14 -0
  15. package/template/apps/server/src/agent/runtime-bootstrap.ts +22 -6
  16. package/template/apps/server/src/agent/sandbox-worker.ts +2 -2
  17. package/template/apps/server/src/agent/unavailable-routes.ts +33 -0
  18. package/template/apps/server/src/app/server-composition.ts +56 -0
  19. package/template/apps/server/src/application-services.ts +2 -0
  20. package/template/apps/server/src/bulk-data/service.ts +14 -0
  21. package/template/apps/server/src/data-governance/export/authorization-port.ts +1 -1
  22. package/template/apps/server/src/data-governance/export/postgres-api-fixture.ts +18 -2
  23. package/template/apps/server/src/data-governance/export/postgres-recovery.integration.test.ts +10 -9
  24. package/template/apps/server/src/data-governance/export/routes-test-fixture.ts +6 -8
  25. package/template/apps/server/src/data-governance/user-plaintext-routes.test.ts +4 -2
  26. package/template/apps/server/src/feature/routes.authorization.test.ts +26 -1
  27. package/template/apps/server/src/feature/routes.ts +33 -15
  28. package/template/apps/server/src/foundation-activation.test.ts +60 -0
  29. package/template/apps/server/src/foundation.ts +64 -5
  30. package/template/apps/server/src/guards.test.ts +23 -0
  31. package/template/apps/server/src/guards.ts +12 -0
  32. package/template/apps/server/src/index.ts +28 -34
  33. package/template/apps/server/src/installed-business-modules.ts +13 -24
  34. package/template/apps/server/src/jobs/legacy-adapter.integration.test.ts +1 -0
  35. package/template/apps/server/src/jobs/legacy-adapter.test.ts +106 -0
  36. package/template/apps/server/src/jobs/legacy-service-runtime.ts +40 -0
  37. package/template/apps/server/src/license/activation.test.ts +30 -0
  38. package/template/apps/server/src/license/activation.ts +26 -0
  39. package/template/apps/server/src/license/restriction.test.ts +16 -0
  40. package/template/apps/server/src/license/routes-offline-code.test.ts +6 -2
  41. package/template/apps/server/src/license/routes.ts +20 -4
  42. package/template/apps/server/src/license/runtime-service.test.ts +10 -2
  43. package/template/apps/server/src/module-bootstrap.ts +14 -23
  44. package/template/apps/server/src/module-composition.test.ts +43 -2
  45. package/template/apps/server/src/module-composition.ts +29 -0
  46. package/template/apps/server/src/module-host/durable-context-guard.test.ts +172 -0
  47. package/template/apps/server/src/module-host/durable-context-unavailable.test.ts +50 -1
  48. package/template/apps/server/src/module-host/durable-context.test.ts +42 -25
  49. package/template/apps/server/src/module-host/durable-context.ts +42 -2
  50. package/template/apps/server/src/module-host/durable-handler-registration.ts +96 -5
  51. package/template/apps/server/src/module-host/implementation-completeness.test.ts +185 -0
  52. package/template/apps/server/src/module-host/implementation-completeness.ts +132 -0
  53. package/template/apps/server/src/module-host/initialize-contracts.ts +14 -0
  54. package/template/apps/server/src/module-host/initialize.test.ts +78 -2
  55. package/template/apps/server/src/module-host/initialize.ts +15 -4
  56. package/template/apps/server/src/module-host/project-attachments.test.ts +92 -0
  57. package/template/apps/server/src/module-host/project-attachments.ts +58 -0
  58. package/template/apps/server/src/module-host/project-composition-package.test.ts +186 -0
  59. package/template/apps/server/src/module-host/project-composition.test.ts +260 -0
  60. package/template/apps/server/src/module-host/project-composition.ts +311 -0
  61. package/template/apps/server/src/module-host/request-context.ts +3 -0
  62. package/template/apps/server/src/module-host/route-declaration.ts +15 -6
  63. package/template/apps/server/src/module-host/route-installer.test.ts +15 -2
  64. package/template/apps/server/src/module-host/route-installer.ts +12 -2
  65. package/template/apps/server/src/module-host/route-registration.test.ts +13 -4
  66. package/template/apps/server/src/module-host/runtime-composition-compatibility.test.ts +63 -0
  67. package/template/apps/server/src/module-host/runtime-composition-compatibility.ts +46 -0
  68. package/template/apps/server/src/module-host/runtime-composition.ts +34 -0
  69. package/template/apps/server/src/module-host/search-provider-registration.test.ts +64 -0
  70. package/template/apps/server/src/module-host/search-provider-registration.ts +35 -0
  71. package/template/apps/server/src/module-host/static-route-authorization.test.ts +99 -0
  72. package/template/apps/server/src/module-host/static-route-authorization.ts +90 -0
  73. package/template/apps/server/src/module-host/task-definitions.ts +1 -0
  74. package/template/apps/server/src/module-host/user-directory-composition.test.ts +4 -1
  75. package/template/apps/server/src/module-host.test.ts +2 -0
  76. package/template/apps/server/src/module-preflight.test.ts +289 -0
  77. package/template/apps/server/src/module-preflight.ts +96 -0
  78. package/template/apps/server/src/module-runtime-validation.ts +1 -0
  79. package/template/apps/server/src/platform-app-bootstrap.ts +50 -0
  80. package/template/apps/server/src/platform-http-runtime.ts +32 -7
  81. package/template/apps/server/src/route-guards.test.ts +22 -0
  82. package/template/apps/server/src/route-guards.ts +10 -3
  83. package/template/apps/server/src/runtime-audit-query.ts +50 -0
  84. package/template/apps/server/src/runtime-feature.test.ts +46 -0
  85. package/template/apps/server/src/runtime-feature.ts +5 -0
  86. package/template/apps/server/src/runtime.ts +10 -49
  87. package/template/apps/server/src/scheduler/audit-action.test.ts +49 -0
  88. package/template/apps/server/src/scheduler/audit.ts +1 -1
  89. package/template/apps/server/src/scheduler/execution-recorder.ts +10 -5
  90. package/template/apps/server/src/scheduler/recovery-service.test.ts +4 -3
  91. package/template/apps/server/src/scheduler/service-idle.test.ts +4 -3
  92. package/template/apps/server/src/scheduler/service.test.ts +1 -0
  93. package/template/apps/server/src/scheduler/service.ts +18 -21
  94. package/template/apps/server/src/scheduler/types.ts +5 -1
  95. package/template/apps/server/src/startup-composition-validation.test.ts +35 -0
  96. package/template/apps/server/src/startup-composition-validation.ts +54 -0
  97. package/template/apps/server/src/startup-preflight.test.ts +36 -0
  98. package/template/apps/server/src/startup-preflight.ts +37 -0
  99. package/template/apps/server/src/system/audit-routes.test.ts +9 -3
  100. package/template/apps/server/src/system/department-routes.test.ts +5 -3
  101. package/template/apps/server/src/system/feature-routes.test.ts +4 -3
  102. package/template/apps/server/src/system/interface-catalog.test.ts +5 -3
  103. package/template/apps/server/src/system/interface-catalog.ts +3 -0
  104. package/template/apps/server/src/system/menu-routes.test.ts +5 -3
  105. package/template/apps/server/src/system/route-test-runtime.ts +24 -0
  106. package/template/apps/server/src/system/routes-validation.test.ts +7 -3
  107. package/template/apps/server/src/system/routes.test.ts +21 -11
  108. package/template/apps/server/src/system/routes.ts +11 -2
  109. package/template/apps/server/src/work-order/foundation-modules.test.ts +6 -4
  110. package/template/apps/server/src/work-order/routes.test.ts +8 -0
  111. package/template/apps/server/src/work-order/task.test.ts +6 -1
  112. package/template/apps/web/Dockerfile +5 -0
  113. package/template/apps/web/package.json +1 -0
  114. package/template/apps/web/scripts/validate-admin-ui-composition.ts +106 -0
  115. package/template/apps/web/src/app/web-composition.ts +24 -0
  116. package/template/apps/web/src/app/web-composition.webtest.ts +15 -0
  117. package/template/apps/web/src/layout/AppSidebar.vue +8 -1
  118. package/template/apps/web/src/layout/NavigationSearchDialog.vue +8 -1
  119. package/template/apps/web/src/layout/navigation.ts +29 -4
  120. package/template/apps/web/src/layout/navigation.webtest.ts +51 -0
  121. package/template/apps/web/src/pages/dashboard/dashboard-presenter.ts +6 -1
  122. package/template/apps/web/src/pages/work-orders/WorkOrderListPage.vue +202 -0
  123. package/template/apps/web/src/pages/work-orders/WorkOrderListPage.webtest.ts +51 -0
  124. package/template/apps/web/src/router/admin-extension-loader.ts +44 -0
  125. package/template/apps/web/src/router/admin-extension-loader.webtest.ts +49 -0
  126. package/template/apps/web/src/router/admin-ui-composition-plugin.ts +33 -0
  127. package/template/apps/web/src/router/admin-ui-composition-plugin.webtest.ts +20 -0
  128. package/template/apps/web/src/router/admin-ui-composition.ts +313 -0
  129. package/template/apps/web/src/router/admin-ui-composition.webtest.ts +225 -0
  130. package/template/apps/web/src/router/admin-ui-module-registry-plugin.ts +55 -0
  131. package/template/apps/web/src/router/admin-ui-module-registry-plugin.webtest.ts +46 -0
  132. package/template/apps/web/src/router/business-routes.ts +32 -45
  133. package/template/apps/web/src/router/business-routes.webtest.ts +40 -44
  134. package/template/apps/web/src/router/index.ts +64 -54
  135. package/template/apps/web/src/router/manifest-routes.ts +27 -7
  136. package/template/apps/web/src/router/manifest-routes.webtest.ts +10 -2
  137. package/template/apps/web/src/router/platform-admin-route-bindings.ts +58 -0
  138. package/template/apps/web/src/router/reserved-route-paths.ts +14 -0
  139. package/template/apps/web/src/router/shared-scaffold-neutrality.webtest.ts +10 -4
  140. package/template/apps/web/src/router/web-composition-host.ts +20 -0
  141. package/template/apps/web/src/services/work-order-api.ts +16 -0
  142. package/template/apps/web/src/services/work-order-api.webtest.ts +21 -0
  143. package/template/apps/web/src/virtual-admin-ui-modules.d.ts +5 -0
  144. package/template/apps/web/vite.config.ts +22 -1
  145. package/template/docs/architecture/migration-bundle.md +10 -7
  146. package/template/docs/architecture/module-manifest-composition.md +85 -3
  147. package/template/docs/architecture/search-provider-protocol.md +6 -1
  148. package/template/docs/architecture/server-module-interface.md +70 -7
  149. package/template/docs/development/business-web-routes.md +53 -50
  150. package/template/docs/development/module-lifecycle.md +48 -54
  151. package/template/docs/platform/navigation-registry.md +5 -4
  152. package/template/package.json +1 -1
  153. package/template/packages/config/package.json +1 -1
  154. package/template/packages/database/package.json +1 -1
  155. package/template/packages/example-work-order/package.json +6 -0
  156. package/template/packages/example-work-order/src/admin-page.ts +78 -0
  157. package/template/packages/example-work-order/src/admin.ts +35 -0
  158. package/template/packages/example-work-order/src/manifest.test.ts +62 -5
  159. package/template/packages/example-work-order/src/manifest.ts +54 -7
  160. package/template/packages/example-work-order/src/migration-bundle.ts +8 -0
  161. package/template/packages/example-work-order/src/server-module.ts +2 -2
  162. package/template/packages/jobs/package.json +1 -1
  163. package/template/packages/modules/index.ts +4 -0
  164. package/template/packages/modules/package.json +3 -2
  165. package/template/packages/modules/src/admin-ui-entry.ts +8 -0
  166. package/template/packages/modules/src/admin-ui-manifest.test.ts +52 -0
  167. package/template/packages/modules/src/admin-ui-manifest.ts +53 -0
  168. package/template/packages/modules/src/capability-audit-validation.test.ts +127 -0
  169. package/template/packages/modules/src/capability-audit-validation.ts +38 -0
  170. package/template/packages/modules/src/capability-guard-validation.ts +148 -0
  171. package/template/packages/modules/src/capability-key-validation.test.ts +150 -0
  172. package/template/packages/modules/src/composition-fixtures.test-support.ts +7 -5
  173. package/template/packages/modules/src/composition.test.ts +74 -9
  174. package/template/packages/modules/src/composition.ts +8 -0
  175. package/template/packages/modules/src/manifest.ts +1 -0
  176. package/template/packages/modules/src/migration-bundle-validation.test.ts +23 -0
  177. package/template/packages/modules/src/module-host-range.test.ts +57 -0
  178. package/template/packages/modules/src/module-host-range.ts +125 -0
  179. package/template/packages/modules/src/project-module-assembly.test.ts +133 -0
  180. package/template/packages/modules/src/project-module-assembly.ts +126 -0
  181. package/template/packages/modules/src/system-admin-routes.ts +6 -5
  182. package/template/packages/modules/src/system-admin-ui-catalog.ts +72 -0
  183. package/template/packages/modules/src/system-agent-tools.ts +2 -2
  184. package/template/packages/modules/src/system-api-binding.ts +43 -9
  185. package/template/packages/modules/src/system-api-control-plane.ts +202 -0
  186. package/template/packages/modules/src/system-api-data-governance.ts +4 -2
  187. package/template/packages/modules/src/system-api-operational.ts +57 -8
  188. package/template/packages/modules/src/system-api-permissions.ts +115 -21
  189. package/template/packages/modules/src/system-api-watermark.ts +3 -0
  190. package/template/packages/modules/src/system-audit-actions.ts +7 -0
  191. package/template/packages/modules/src/system-data-governance.ts +5 -2
  192. package/template/packages/modules/src/system-features.ts +31 -0
  193. package/template/packages/modules/src/system-governed-export-bindings.ts +8 -4
  194. package/template/packages/modules/src/system-module-catalog.test.ts +39 -4
  195. package/template/packages/modules/src/system-module-catalog.ts +68 -7
  196. package/template/packages/modules/src/system-module-tasks.ts +8 -1
  197. package/template/packages/modules/src/system-modules-access.test.ts +4 -0
  198. package/template/packages/modules/src/system-modules.test.ts +10 -2
  199. package/template/packages/modules/src/system-modules.ts +6 -5
  200. package/template/packages/modules/src/system-permissions.ts +4 -0
  201. package/template/packages/modules/user/module.ts +19 -2
  202. package/template/packages/server-sdk/index.ts +8 -0
  203. package/template/packages/server-sdk/package.json +2 -1
  204. package/template/packages/server-sdk/src/durable-handler.ts +16 -1
  205. package/template/packages/server-sdk/src/module-adapter.test.ts +44 -0
  206. package/template/packages/server-sdk/src/module-adapter.ts +59 -0
  207. package/template/packages/server-sdk/src/route-definition.ts +3 -2
  208. package/template/packages/shared/package.json +1 -1
  209. package/template/packages/shared/src/audit.test.ts +7 -0
  210. package/template/packages/shared/src/audit.ts +16 -0
  211. package/template/packages/shared/src/migration-bundle-validation.ts +9 -11
  212. package/template/packages/shared/src/rbac.ts +2 -0
  213. 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);
@@ -7373,6 +7519,11 @@ var recipes = [
7373
7519
  id: "starter-0.3.18-to-0.3.19",
7374
7520
  from: "0.3.18",
7375
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"
7376
7527
  }
7377
7528
  ];
7378
7529
  function resolveRecipeChain(sourceVersion, targetVersion) {
@@ -7418,18 +7569,18 @@ function parseVersion2(version) {
7418
7569
  }
7419
7570
 
7420
7571
  // src/update/updater.ts
7421
- import { readFile as readFile20, rm as rm8 } from "fs/promises";
7422
- import { join as join25 } from "path";
7572
+ import { readFile as readFile21, rm as rm8 } from "fs/promises";
7573
+ import { join as join26 } from "path";
7423
7574
 
7424
7575
  // src/update/artifacts.ts
7425
- import { mkdtemp, readFile as readFile13 } from "fs/promises";
7576
+ import { mkdtemp, readFile as readFile14 } from "fs/promises";
7426
7577
  import { tmpdir } from "os";
7427
- import { basename as basename8, join as join17 } from "path";
7578
+ import { basename as basename8, join as join18 } from "path";
7428
7579
 
7429
7580
  // src/update/published-artifacts.ts
7430
- import { createHash as createHash3 } from "crypto";
7431
- import { mkdir as mkdir6, readFile as readFile11, writeFile as writeFile9 } from "fs/promises";
7432
- 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";
7433
7584
 
7434
7585
  // src/update/safe-archive.ts
7435
7586
  import { mkdir as mkdir5 } from "fs/promises";
@@ -7481,17 +7632,17 @@ async function downloadPublicArtifact(temporaryRoot, label, version, expectedInt
7481
7632
  }
7482
7633
  const archive = await readBoundedResponse(response, `create-windy@${version} artifact `);
7483
7634
  verifyArchive(metadata, archive);
7484
- const destination = join15(temporaryRoot, `${label}-artifact`);
7485
- const archivePath = join15(temporaryRoot, `${label}.tgz`);
7486
- await writeFile9(archivePath, archive);
7635
+ const destination = join16(temporaryRoot, `${label}-artifact`);
7636
+ const archivePath = join16(temporaryRoot, `${label}.tgz`);
7637
+ await writeFile10(archivePath, archive);
7487
7638
  await extractSafeTarArchive({
7488
7639
  archivePath,
7489
7640
  destination,
7490
7641
  cwd: temporaryRoot
7491
7642
  });
7492
- const packageRoot2 = join15(destination, "package");
7493
- const packageJson = JSON.parse(await readFile11(join15(packageRoot2, "package.json"), "utf8"));
7494
- 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"));
7495
7646
  if (packageJson.name !== "create-windy" || packageJson.version !== version || template.generatorVersion !== version) {
7496
7647
  throw new Error(`create-windy@${version} \u5305\u8EAB\u4EFD\u6216\u6A21\u677F\u5143\u6570\u636E\u4E0D\u4E00\u81F4`);
7497
7648
  }
@@ -7616,17 +7767,17 @@ function boundedSizeError(label) {
7616
7767
  return new Error(`${label}\u8D85\u8FC7\u5B89\u5168\u4E0A\u9650`);
7617
7768
  }
7618
7769
  function verifyArchive(metadata, archive) {
7619
- const integrity = `sha512-${createHash3("sha512").update(archive).digest("base64")}`;
7620
- 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");
7621
7772
  if (integrity !== metadata.dist.integrity || shasum !== metadata.dist.shasum) {
7622
7773
  throw new Error(`create-windy@${metadata.version} artifact \u5B8C\u6574\u6027\u6821\u9A8C\u5931\u8D25`);
7623
7774
  }
7624
7775
  }
7625
7776
  async function generatePublicArtifactProject(temporaryRoot, label, projectName, packageRoot2, provenance) {
7626
- const parent = join15(temporaryRoot, label);
7777
+ const parent = join16(temporaryRoot, label);
7627
7778
  await mkdir6(parent, { recursive: true });
7628
7779
  const args = [
7629
- join15(packageRoot2, "dist/cli.js"),
7780
+ join16(packageRoot2, "dist/cli.js"),
7630
7781
  projectName,
7631
7782
  "--yes",
7632
7783
  "--skip-install",
@@ -7646,7 +7797,7 @@ async function generatePublicArtifactProject(temporaryRoot, label, projectName,
7646
7797
  }
7647
7798
  }
7648
7799
  await assertProcess("bun", args, parent);
7649
- return join15(parent, projectName);
7800
+ return join16(parent, projectName);
7650
7801
  }
7651
7802
 
7652
7803
  // src/module-history.ts
@@ -7654,18 +7805,18 @@ import {
7654
7805
  lstat as lstat6,
7655
7806
  mkdir as mkdir7,
7656
7807
  readdir as readdir8,
7657
- readFile as readFile12,
7658
- writeFile as writeFile10
7808
+ readFile as readFile13,
7809
+ writeFile as writeFile11
7659
7810
  } from "fs/promises";
7660
- 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";
7661
7812
  async function archiveRemovedModuleMigrations(input) {
7662
7813
  await assertExistingHistoryIntegrity(input.projectDirectory);
7663
- 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"));
7664
7815
  const target = new Set(input.targetModules);
7665
7816
  const removed = moduleDistributionCatalog.filter(({ name }) => input.sourceModules.includes(name) && !target.has(name));
7666
7817
  for (const manifest of removed) {
7667
7818
  for (const root of manifest.migrationRoots) {
7668
- 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;
7669
7820
  }
7670
7821
  }
7671
7822
  if (changed) {
@@ -7673,7 +7824,7 @@ async function archiveRemovedModuleMigrations(input) {
7673
7824
  }
7674
7825
  }
7675
7826
  async function assertExistingHistoryIntegrity(projectDirectory) {
7676
- const historyRoot = join16(projectDirectory, ".windy/module-history");
7827
+ const historyRoot = join17(projectDirectory, ".windy/module-history");
7677
7828
  const metadata = await readOptionalMetadata(historyRoot);
7678
7829
  if (!metadata)
7679
7830
  return;
@@ -7688,13 +7839,13 @@ async function assertExistingHistoryIntegrity(projectDirectory) {
7688
7839
  if (!entry || isMissingManagedFile(entry)) {
7689
7840
  throw new Error(`\u6A21\u5757 migration \u5386\u53F2\u672A\u767B\u8BB0\u5230\u53D7\u7BA1\u6E05\u5355\uFF1A${relativePath2}`);
7690
7841
  }
7691
- if (hashContent(await readFile12(path2)) !== entry.hash) {
7842
+ if (hashContent(await readFile13(path2)) !== entry.hash) {
7692
7843
  throw new Error(`\u6A21\u5757 migration \u5386\u53F2\u5DF2\u88AB\u4FEE\u6539\uFF1A${relativePath2}`);
7693
7844
  }
7694
7845
  }
7695
7846
  }
7696
7847
  async function copyMigrationTree(projectRoot, relativeRoot, targetRoot) {
7697
- const sourceRoot = join16(projectRoot, relativeRoot);
7848
+ const sourceRoot = join17(projectRoot, relativeRoot);
7698
7849
  const rootMetadata = await readOptionalMetadata(sourceRoot);
7699
7850
  if (!rootMetadata)
7700
7851
  return false;
@@ -7702,9 +7853,9 @@ async function copyMigrationTree(projectRoot, relativeRoot, targetRoot) {
7702
7853
  throw new Error(`\u6A21\u5757 migration \u6839\u76EE\u5F55\u65E0\u6548\uFF1A${relativeRoot}`);
7703
7854
  }
7704
7855
  for (const source of await collectRegularFiles(sourceRoot)) {
7705
- const target = join16(targetRoot, relative4(sourceRoot, source).split(sep4).join("/"));
7856
+ const target = join17(targetRoot, relative4(sourceRoot, source).split(sep4).join("/"));
7706
7857
  await mkdir7(dirname6(target), { recursive: true });
7707
- await writeFile10(target, await readFile12(source));
7858
+ await writeFile11(target, await readFile13(source));
7708
7859
  }
7709
7860
  return true;
7710
7861
  }
@@ -7714,7 +7865,7 @@ async function collectRegularFiles(root) {
7714
7865
  return files.sort();
7715
7866
  async function visit(directory) {
7716
7867
  for (const name of (await readdir8(directory)).sort()) {
7717
- const path2 = join16(directory, name);
7868
+ const path2 = join17(directory, name);
7718
7869
  const metadata = await lstat6(path2);
7719
7870
  if (metadata.isSymbolicLink()) {
7720
7871
  throw new Error(`\u6A21\u5757 migration \u4E0D\u5141\u8BB8\u7B26\u53F7\u94FE\u63A5\uFF1A${path2}`);
@@ -7744,10 +7895,10 @@ class PublishedArtifactProvider {
7744
7895
  this.#fetch = fetcher;
7745
7896
  }
7746
7897
  async prepare(projectDirectory, provenance, targetModules) {
7747
- const temporaryRoot = await mkdtemp(join17(tmpdir(), "windy-update-"));
7898
+ const temporaryRoot = await mkdtemp(join18(tmpdir(), "windy-update-"));
7748
7899
  const projectName = await readProjectName2(projectDirectory);
7749
- const baseDirectory = join17(temporaryRoot, "base", projectName);
7750
- const targetDirectory = join17(temporaryRoot, "target", projectName);
7900
+ const baseDirectory = join18(temporaryRoot, "base", projectName);
7901
+ const targetDirectory = join18(temporaryRoot, "target", projectName);
7751
7902
  const baseArtifact = await downloadPublicArtifact(temporaryRoot, "base", provenance.generatorVersion, undefined, this.#fetch);
7752
7903
  if (baseArtifact.identity.templateCommit !== provenance.templateCommit) {
7753
7904
  throw new Error("Project Lock \u4E0E\u6765\u6E90 registry artifact \u7684 template commit \u4E0D\u4E00\u81F4");
@@ -7775,13 +7926,13 @@ class PublishedArtifactProvider {
7775
7926
  }
7776
7927
  }
7777
7928
  async function readProjectName2(projectDirectory) {
7778
- const source = JSON.parse(await readFile13(join17(projectDirectory, "package.json"), "utf8"));
7929
+ const source = JSON.parse(await readFile14(join18(projectDirectory, "package.json"), "utf8"));
7779
7930
  return source.name || basename8(projectDirectory);
7780
7931
  }
7781
7932
 
7782
7933
  // src/update/planner.ts
7783
- import { readFile as readFile18 } from "fs/promises";
7784
- 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";
7785
7936
 
7786
7937
  // src/update/docker-copy-update-plan.ts
7787
7938
  async function deriveDockerfilePlans(files, snapshots, paths, targetWorkspaceManifests) {
@@ -7869,12 +8020,12 @@ function matchesWorkspace(pattern, directory) {
7869
8020
  }
7870
8021
 
7871
8022
  // src/update/migration-reconciler.ts
7872
- import { readFile as readFile15 } from "fs/promises";
7873
- import { join as join19 } from "path";
8023
+ import { readFile as readFile16 } from "fs/promises";
8024
+ import { join as join20 } from "path";
7874
8025
 
7875
8026
  // src/update/migration-project-file.ts
7876
- import { readFile as readFile14 } from "fs/promises";
7877
- import { join as join18 } from "path";
8027
+ import { readFile as readFile15 } from "fs/promises";
8028
+ import { join as join19 } from "path";
7878
8029
  async function readProjectJson(input, path2) {
7879
8030
  const content = await readProjectFile(input, path2);
7880
8031
  if (!content) {
@@ -7890,7 +8041,7 @@ async function readProjectFile(input, path2) {
7890
8041
  }
7891
8042
  let content;
7892
8043
  try {
7893
- content = await readFile14(join18(input.projectDirectory, path2));
8044
+ content = await readFile15(join19(input.projectDirectory, path2));
7894
8045
  } catch (error) {
7895
8046
  if (!isMissingFileError(error))
7896
8047
  throw error;
@@ -8009,7 +8160,7 @@ async function reconcileDrizzleMigrations(input) {
8009
8160
  const tag = `${prefix}_${name}`;
8010
8161
  const sqlPath = `${drizzleRoot}/${tag}.sql`;
8011
8162
  const snapshotPath = `${drizzleRoot}/meta/${prefix}_snapshot.json`;
8012
- const sql = await readFile15(join19(input.targetDirectory, `${drizzleRoot}/${addition.tag}.sql`));
8163
+ const sql = await readFile16(join20(input.targetDirectory, `${drizzleRoot}/${addition.tag}.sql`));
8013
8164
  merged.id = targetAfter.id;
8014
8165
  merged.prevId = currentSnapshot.id;
8015
8166
  const snapshot = encodeJson(merged);
@@ -8056,9 +8207,9 @@ function unchanged(targetManifest) {
8056
8207
  async function readJournals(input) {
8057
8208
  try {
8058
8209
  const [base, project, target] = await Promise.all([
8059
- readJson(join19(input.baseDirectory, journalPath)),
8210
+ readJson(join20(input.baseDirectory, journalPath)),
8060
8211
  readProjectJson(input, journalPath),
8061
- readJson(join19(input.targetDirectory, journalPath))
8212
+ readJson(join20(input.targetDirectory, journalPath))
8062
8213
  ]);
8063
8214
  return { base, project, target };
8064
8215
  } catch (error) {
@@ -8096,7 +8247,7 @@ async function readSnapshot(root, tag) {
8096
8247
  const match = migrationPattern.exec(tag);
8097
8248
  if (!match)
8098
8249
  throw new Error(`${journalPath}\uFF1Amigration tag \u683C\u5F0F\u65E0\u6548\uFF1A${tag}`);
8099
- return await readJson(join19(root, `${drizzleRoot}/meta/${match[1]}_snapshot.json`));
8250
+ return await readJson(join20(root, `${drizzleRoot}/meta/${match[1]}_snapshot.json`));
8100
8251
  }
8101
8252
  function validateUniqueNames(journal, label, conflicts) {
8102
8253
  const seen = new Set;
@@ -8137,27 +8288,27 @@ function encodeJson(value) {
8137
8288
  `);
8138
8289
  }
8139
8290
  async function readJson(path2) {
8140
- return JSON.parse(await readFile15(path2, "utf8"));
8291
+ return JSON.parse(await readFile16(path2, "utf8"));
8141
8292
  }
8142
8293
 
8143
8294
  // src/update/planner-file.ts
8144
- import { readFile as readFile16 } from "fs/promises";
8145
- import { join as join21 } from "path";
8295
+ import { readFile as readFile17 } from "fs/promises";
8296
+ import { join as join22 } from "path";
8146
8297
 
8147
8298
  // src/update/merge.ts
8148
- 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";
8149
8300
  import { tmpdir as tmpdir2 } from "os";
8150
- import { join as join20 } from "path";
8301
+ import { join as join21 } from "path";
8151
8302
  async function mergeTextFile(base, ours, theirs) {
8152
- const root = await mkdtemp2(join20(tmpdir2(), "windy-merge-"));
8303
+ const root = await mkdtemp2(join21(tmpdir2(), "windy-merge-"));
8153
8304
  try {
8154
- const oursPath = join20(root, "ours");
8155
- const basePath = join20(root, "base");
8156
- const theirsPath = join20(root, "theirs");
8305
+ const oursPath = join21(root, "ours");
8306
+ const basePath = join21(root, "base");
8307
+ const theirsPath = join21(root, "theirs");
8157
8308
  await Promise.all([
8158
- writeFile11(oursPath, ours),
8159
- writeFile11(basePath, base),
8160
- writeFile11(theirsPath, theirs)
8309
+ writeFile12(oursPath, ours),
8310
+ writeFile12(basePath, base),
8311
+ writeFile12(theirsPath, theirs)
8161
8312
  ]);
8162
8313
  const result2 = await captureProcess("git", ["merge-file", "-p", "--diff3", oursPath, basePath, theirsPath], root);
8163
8314
  if (result2.code === 0) {
@@ -8411,10 +8562,10 @@ function conflict(path2, reason, expectedProjectContent) {
8411
8562
  // src/update/planner-file.ts
8412
8563
  async function planFile(path2, baseRoot, targetRoot, source, target, ours, provenance, targetContentOverride, manageGeneratedProjectOwned = false, forcePlatformBaseline = false) {
8413
8564
  const [base, theirs] = await Promise.all([
8414
- readOptional(join21(baseRoot, path2)),
8415
- targetContentOverride ? Promise.resolve(targetContentOverride) : readOptional(join21(targetRoot, path2))
8565
+ readOptional(join22(baseRoot, path2)),
8566
+ targetContentOverride ? Promise.resolve(targetContentOverride) : readOptional(join22(targetRoot, path2))
8416
8567
  ]);
8417
- const ownership = source?.ownership === "project-owned" ? source.ownership : target?.ownership || source?.ownership || classifyOwnership(path2);
8568
+ const ownership = source?.ownership ?? target?.ownership ?? classifyOwnership(path2);
8418
8569
  const expected = expectationForSnapshot(ours);
8419
8570
  if (ownership === "project-owned") {
8420
8571
  if (manageGeneratedProjectOwned) {
@@ -8519,7 +8670,7 @@ function conflict2(path2, ownership, reason, expectedProjectContent) {
8519
8670
  }
8520
8671
  async function readOptional(path2) {
8521
8672
  try {
8522
- return await readFile16(path2);
8673
+ return await readFile17(path2);
8523
8674
  } catch (error) {
8524
8675
  if (error instanceof Error && "code" in error && error.code === "ENOENT") {
8525
8676
  return;
@@ -8528,28 +8679,43 @@ async function readOptional(path2) {
8528
8679
  }
8529
8680
  }
8530
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
+
8531
8697
  // src/update/planner-snapshots.ts
8532
- import { readFile as readFile17, stat as stat2 } from "fs/promises";
8533
- import { join as join22 } from "path";
8698
+ import { readFile as readFile18, stat as stat2 } from "fs/promises";
8699
+ import { join as join23 } from "path";
8534
8700
  async function snapshotProjectFiles(project, paths, seed) {
8535
8701
  const snapshots = new Map(seed);
8536
8702
  await Promise.all([...paths].filter((path2) => !snapshots.has(path2)).map(async (path2) => {
8537
- snapshots.set(path2, await readOptional2(join22(project, path2)));
8703
+ snapshots.set(path2, await readOptional2(join23(project, path2)));
8538
8704
  }));
8539
8705
  return snapshots;
8540
8706
  }
8541
8707
  async function snapshotProjectModes(project, paths) {
8542
- 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))])));
8543
8709
  }
8544
8710
  async function assertPlanSnapshots(project, expectations) {
8545
8711
  const changed = [];
8546
8712
  for (const expectation2 of expectations) {
8547
- const actual = expectationForSnapshot(await readOptional2(join22(project, expectation2.path)));
8713
+ const actual = expectationForSnapshot(await readOptional2(join23(project, expectation2.path)));
8548
8714
  if (JSON.stringify(actual) !== JSON.stringify(expectation2.expected)) {
8549
8715
  changed.push(expectation2.path);
8550
8716
  continue;
8551
8717
  }
8552
- 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) {
8553
8719
  changed.push(expectation2.path);
8554
8720
  }
8555
8721
  }
@@ -8561,7 +8727,7 @@ async function readModeOptional(path2) {
8561
8727
  try {
8562
8728
  return (await stat2(path2)).mode & 511;
8563
8729
  } catch (error) {
8564
- if (isMissing6(error))
8730
+ if (isMissing7(error))
8565
8731
  return;
8566
8732
  throw error;
8567
8733
  }
@@ -8574,21 +8740,21 @@ async function readSourceParentMode(path2) {
8574
8740
  }
8575
8741
  return info.mode & 493 | 493;
8576
8742
  } catch (error) {
8577
- if (isMissing6(error))
8743
+ if (isMissing7(error))
8578
8744
  return 493;
8579
8745
  throw error;
8580
8746
  }
8581
8747
  }
8582
8748
  async function readOptional2(path2) {
8583
8749
  try {
8584
- return await readFile17(path2);
8750
+ return await readFile18(path2);
8585
8751
  } catch (error) {
8586
- if (isMissing6(error))
8752
+ if (isMissing7(error))
8587
8753
  return;
8588
8754
  throw error;
8589
8755
  }
8590
8756
  }
8591
- function isMissing6(error) {
8757
+ function isMissing7(error) {
8592
8758
  return error instanceof Error && "code" in error && error.code === "ENOENT";
8593
8759
  }
8594
8760
 
@@ -8625,8 +8791,8 @@ var oxcManagedPaths = new Set([
8625
8791
  ]);
8626
8792
  async function createUpdatePlan(request, artifacts) {
8627
8793
  await assertProjectReadyForUpdate(request.projectDirectory);
8628
- const manifestPath = join23(request.projectDirectory, ".windy/files.json");
8629
- const expectedManifestHash = hashContent(await readFile18(manifestPath));
8794
+ const manifestPath = join24(request.projectDirectory, ".windy/files.json");
8795
+ const expectedManifestHash = hashContent(await readFile19(manifestPath));
8630
8796
  const generationSnapshot = await readGenerationProvenanceSnapshot(request.projectDirectory);
8631
8797
  const provenance = generationSnapshot.provenance;
8632
8798
  const targetModules = resolveUpdateTargetModules(provenance.modules.map(({ name }) => name), request.targetModules);
@@ -8672,8 +8838,8 @@ async function createUpdatePlan(request, artifacts) {
8672
8838
  const paths = new Set([...sourceEntries.keys(), ...targetEntries.keys()]);
8673
8839
  for (const path2 of paths) {
8674
8840
  if (!projectSnapshots.has(path2)) {
8675
- projectSnapshots.set(path2, await readOptional2(join23(request.projectDirectory, path2)));
8676
- 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)));
8677
8843
  }
8678
8844
  }
8679
8845
  const files = await planFiles({
@@ -8695,10 +8861,10 @@ async function createUpdatePlan(request, artifacts) {
8695
8861
  for (const file of files) {
8696
8862
  file.expectedProjectMode = projectModes.get(file.path);
8697
8863
  if (file.action === "create" || file.action === "update") {
8698
- file.mode = file.expectedProjectMode ?? await readModeOptional(join23(artifactSet.targetDirectory, file.path));
8864
+ file.mode = file.expectedProjectMode ?? await readModeOptional(join24(artifactSet.targetDirectory, file.path));
8699
8865
  }
8700
8866
  if (file.action === "create") {
8701
- file.parentMode = await readSourceParentMode(join23(artifactSet.targetDirectory, dirname7(file.path)));
8867
+ file.parentMode = await readSourceParentMode(join24(artifactSet.targetDirectory, dirname7(file.path)));
8702
8868
  }
8703
8869
  }
8704
8870
  const plannedPaths = new Set(files.map(({ path: path2 }) => path2));
@@ -8718,7 +8884,7 @@ async function createUpdatePlan(request, artifacts) {
8718
8884
  ...migrations.conflicts,
8719
8885
  ...files.flatMap((file) => file.conflict ? [`${file.path}\uFF1A${file.conflict}`] : [])
8720
8886
  ];
8721
- if (hashContent(await readFile18(manifestPath)) !== expectedManifestHash) {
8887
+ if (hashContent(await readFile19(manifestPath)) !== expectedManifestHash) {
8722
8888
  throw new Error("\u751F\u6210\u66F4\u65B0\u8BA1\u5212\u671F\u95F4\u53D7\u7BA1\u6E05\u5355\u53D1\u751F\u53D8\u5316");
8723
8889
  }
8724
8890
  await assertPlanSnapshots(request.projectDirectory, [
@@ -8750,19 +8916,6 @@ async function createUpdatePlan(request, artifacts) {
8750
8916
  lifecycle: request.lifecycle
8751
8917
  };
8752
8918
  }
8753
- function resolveModuleRecipeIds(request) {
8754
- const lifecycle = request.lifecycle;
8755
- if (!lifecycle)
8756
- return [];
8757
- const catalog = new Map(moduleDistributionCatalog.map((manifest) => [manifest.name, manifest]));
8758
- if (lifecycle.action === "add") {
8759
- return lifecycle.addedModules.map((name) => catalog.get(name).installRecipe);
8760
- }
8761
- if (lifecycle.action === "remove") {
8762
- return lifecycle.removedModules.map((name) => catalog.get(name).uninstallRecipe);
8763
- }
8764
- return lifecycle.updatedModules.flatMap((name) => catalog.get(name).updateRecipes);
8765
- }
8766
8919
  async function planFiles(input) {
8767
8920
  const files = [];
8768
8921
  for (const path2 of [...input.paths].sort()) {
@@ -8856,9 +9009,9 @@ function frozenManifestHash(file, finalizedHashes) {
8856
9009
  }
8857
9010
 
8858
9011
  // src/update/format-stabilizer.ts
8859
- 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";
8860
9013
  import { tmpdir as tmpdir3 } from "os";
8861
- 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";
8862
9015
  var formattedExtensions = new Set([
8863
9016
  ".cjs",
8864
9017
  ".css",
@@ -8897,7 +9050,7 @@ async function stabilizeManagedContent(result2, execute, writeProjectFile) {
8897
9050
  }
8898
9051
  }
8899
9052
  inputs.set(".windy/generation.json", new TextEncoder().encode(generationContent));
8900
- const temporaryRoot = await mkdtemp3(join24(tmpdir3(), "windy-update-format-"));
9053
+ const temporaryRoot = await mkdtemp3(join25(tmpdir3(), "windy-update-format-"));
8901
9054
  try {
8902
9055
  const stagedPaths = await stageInputs(temporaryRoot, inputs);
8903
9056
  for (let offset = 0;offset < stagedPaths.length; offset += 80) {
@@ -8905,14 +9058,14 @@ async function stabilizeManagedContent(result2, execute, writeProjectFile) {
8905
9058
  "x",
8906
9059
  "oxfmt",
8907
9060
  "--write",
8908
- `--config=${join24(result2.plan.projectDirectory, ".oxfmtrc.json")}`,
9061
+ `--config=${join25(result2.plan.projectDirectory, ".oxfmtrc.json")}`,
8909
9062
  ...stagedPaths.slice(offset, offset + 80)
8910
9063
  ], result2.plan.projectDirectory);
8911
9064
  }
8912
9065
  const hashes = plannedContentHashes(result2);
8913
9066
  let formattedGeneration = generationContent;
8914
9067
  for (const [path2, original] of inputs) {
8915
- const formatted = await readFile19(safeTemporaryPath(temporaryRoot, path2));
9068
+ const formatted = await readFile20(safeTemporaryPath(temporaryRoot, path2));
8916
9069
  if (path2 === ".windy/generation.json") {
8917
9070
  formattedGeneration = formatted.toString();
8918
9071
  continue;
@@ -8939,7 +9092,7 @@ async function stageInputs(temporaryRoot, inputs) {
8939
9092
  for (const [path2, content] of inputs) {
8940
9093
  const target = safeTemporaryPath(temporaryRoot, path2);
8941
9094
  await mkdir8(dirname8(target), { recursive: true });
8942
- await writeFile12(target, content);
9095
+ await writeFile13(target, content);
8943
9096
  paths.push(target);
8944
9097
  }
8945
9098
  return paths;
@@ -9066,7 +9219,7 @@ class DefaultStarterUpdater {
9066
9219
  const plannedPaths = new Set(result2.plan.files.map(({ path: path2 }) => path2));
9067
9220
  replaceExpectations(result2, verificationSnapshot.filter(({ path: path2 }) => !metadataPaths2.has(path2) && !plannedPaths.has(path2)));
9068
9221
  verificationExpectationsFrozen = true;
9069
- 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"));
9070
9223
  for (const name of ["lint", "typecheck", "test", "build"]) {
9071
9224
  if (!packageJson.scripts?.[name]) {
9072
9225
  steps.push({ name: `bun run ${name}`, status: "skipped" });
@@ -9150,8 +9303,10 @@ function expectedForPath(result2, path2) {
9150
9303
  }
9151
9304
 
9152
9305
  // src/module-lifecycle.ts
9153
- import { access as access2, rm as rm9 } from "fs/promises";
9154
- 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";
9155
9310
 
9156
9311
  // src/module-version-range.ts
9157
9312
  function isVersionInCaretRange(version, range) {
@@ -9177,67 +9332,199 @@ function compareVersion(left, right) {
9177
9332
  return 0;
9178
9333
  }
9179
9334
 
9180
- // src/module-lifecycle.ts
9181
- async function runModuleLifecycleCommand(args, projectDirectory, dependencies = {}) {
9182
- const action = args[0];
9183
- if (!isAction(action)) {
9184
- throw new Error("module \u53EA\u652F\u6301 list\u3001add\u3001update\u3001remove");
9185
- }
9186
- const log = dependencies.log ?? console.log;
9187
- const provenance = await readGenerationProvenance(projectDirectory);
9188
- const installed = provenance.modules.map(({ name }) => name);
9189
- if (action === "list") {
9190
- printModuleList(provenance.modules, log);
9191
- 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`);
9192
9339
  }
9193
- const cliVersion = dependencies.cliVersion ?? (await readTemplateMetadata(await resolveTemplateRoot())).generatorVersion;
9194
- if (provenance.generatorVersion !== cliVersion || provenance.windyVersion !== cliVersion) {
9195
- 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`);
9196
9387
  }
9197
- const requested = positionalModules(args.slice(1));
9198
- const lifecycle = await resolveLifecyclePlan({
9199
- action,
9200
- requested,
9201
- installed,
9202
- windyVersion: provenance.windyVersion,
9203
- args,
9204
- 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 }]
9205
9400
  });
9206
- printDataPlan(lifecycle, log);
9207
- if (!hasChanges(lifecycle)) {
9208
- log("\u6A21\u5757\u96C6\u5408\u5DF2\u6EE1\u8DB3\u8BF7\u6C42\uFF0C\u65E0\u9700\u4FEE\u6539");
9209
- return;
9401
+ if (!state.ownerToken) {
9402
+ throw new Error("\u6A21\u5757\u88C5\u914D\u4E8B\u52A1\u7F3A\u5C11\u6240\u6709\u6743\u4EE4\u724C");
9210
9403
  }
9211
- const targetModules = resolveTargetModules(installed, lifecycle);
9212
- const updater = dependencies.updater ?? new DefaultStarterUpdater;
9213
- const plan = await updater.plan({
9404
+ await finalizeOwnedTransaction({
9214
9405
  projectDirectory,
9215
- targetVersion: provenance.windyVersion,
9216
- targetModules,
9217
- lifecycle
9406
+ ownerToken: state.ownerToken,
9407
+ expectations: [
9408
+ expectationForContent(projectModuleRegistryPath, plan.content)
9409
+ ]
9218
9410
  });
9219
- printModulePlan(plan, log);
9220
- if (args.includes("--dry-run")) {
9221
- await rm9(plan.temporaryRoot, { recursive: true, force: true });
9222
- log(`
9223
- \u6F14\u7EC3\u5B8C\u6210\uFF1A\u672A\u5199\u5165\u9879\u76EE\u6587\u4EF6`);
9224
- 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);
9225
9422
  }
9226
- if (action === "remove" && !args.includes("--confirm-data-disposition")) {
9227
- await rm9(plan.temporaryRoot, { recursive: true, force: true });
9228
- 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")}`);
9229
9430
  }
9230
- if (plan.conflicts.length) {
9231
- await rm9(plan.temporaryRoot, { recursive: true, force: true });
9232
- 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")}`);
9233
9439
  }
9234
- const result2 = await updater.apply(plan);
9235
- const report = await updater.verify(result2);
9236
- log(`
9237
- \u6A21\u5757${actionLabel(action)}\u5B8C\u6210\uFF1A${requested.join("\u3001")}`);
9238
- log(`\u62A5\u544A\uFF1A${report.reportPath}`);
9239
9440
  }
9240
- 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) {
9241
9528
  const known = new Map(moduleDistributionCatalog.map((manifest) => [manifest.name, manifest]));
9242
9529
  const requested = input.action === "update" && input.requested.length === 0 ? [...input.installed] : input.requested;
9243
9530
  if (!requested.length) {
@@ -9258,10 +9545,12 @@ async function resolveLifecyclePlan(input) {
9258
9545
  const manifest = known.get(name);
9259
9546
  assertWindyCompatible(input.windyVersion, manifest.windyRange, manifest.name);
9260
9547
  }
9548
+ const addedModules = target.filter((name) => !input.installed.includes(name));
9549
+ assertDeploymentLifecycleSupported(addedModules, known);
9261
9550
  return {
9262
9551
  action: input.action,
9263
9552
  requestedModules: requested,
9264
- addedModules: target.filter((name) => !input.installed.includes(name)),
9553
+ addedModules,
9265
9554
  updatedModules: [],
9266
9555
  removedModules: []
9267
9556
  };
@@ -9271,6 +9560,7 @@ async function resolveLifecyclePlan(input) {
9271
9560
  if (absent.length) {
9272
9561
  throw new Error(`\u672A\u5B89\u88C5\u6A21\u5757\u4E0D\u80FD\u66F4\u65B0\uFF1A${absent.join("\u3001")}`);
9273
9562
  }
9563
+ assertDeploymentLifecycleSupported(requested, known);
9274
9564
  return {
9275
9565
  action: input.action,
9276
9566
  requestedModules: requested,
@@ -9284,6 +9574,7 @@ async function resolveLifecyclePlan(input) {
9284
9574
  throw new Error(`\u6838\u5FC3\u6A21\u5757\u4E0D\u53EF\u5378\u8F7D\uFF1A${required.join("\u3001")}`);
9285
9575
  }
9286
9576
  const removed = requested.filter((name) => input.installed.includes(name));
9577
+ assertDeploymentLifecycleSupported(removed, known);
9287
9578
  assertRemovalDependencies(input.installed, removed);
9288
9579
  const dataDisposition = resolveDataDisposition(removed.map((name) => known.get(name).uninstallPolicy), readValue2(input.args, "--data-disposition"));
9289
9580
  const dataPlan = readValue2(input.args, "--data-plan");
@@ -9303,7 +9594,13 @@ async function resolveLifecyclePlan(input) {
9303
9594
  dataPlan
9304
9595
  };
9305
9596
  }
9306
- 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) {
9307
9604
  if (lifecycle.action === "add") {
9308
9605
  return resolveModuleSelection({
9309
9606
  requested: [...installed, ...lifecycle.addedModules],
@@ -9316,6 +9613,19 @@ function resolveTargetModules(installed, lifecycle) {
9316
9613
  }
9317
9614
  return installed;
9318
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
+ }
9319
9629
  function resolveDataDisposition(policies, requested) {
9320
9630
  const disposition = requested ?? strongestPolicy(policies);
9321
9631
  if (disposition !== "retain-data" && disposition !== "archive-data" && disposition !== "manual") {
@@ -9351,27 +9661,219 @@ function assertWindyCompatible(version, range, moduleName) {
9351
9661
  throw new Error(`${moduleName} \u8981\u6C42 Windy ${range}\uFF0C\u5F53\u524D\u4E3A ${version}`);
9352
9662
  }
9353
9663
  }
9354
- function positionalModules(args) {
9355
- const valueFlags = new Set(["--data-disposition", "--data-plan", "--cwd"]);
9356
- const output = [];
9357
- 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) {
9358
9706
  const value = args[index];
9359
- if (valueFlags.has(value)) {
9360
- index += 1;
9361
- } else if (!value.startsWith("-")) {
9362
- 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`);
9363
9716
  }
9717
+ index += 1;
9718
+ }
9719
+ }
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();
9364
9746
  }
9365
- return [...new Set(output)];
9366
9747
  }
9367
- function printModuleList(installed, log) {
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) {
9368
9863
  const installedVersions = new Map(installed.map(({ name, version }) => [name, version]));
9864
+ const catalog = new Map(moduleDistributionCatalog.map((manifest) => [manifest.name, manifest]));
9369
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
+ }
9370
9872
  for (const manifest of moduleDistributionCatalog) {
9371
- const installedVersion = installedVersions.get(manifest.name);
9372
- const status = installedVersion ? "installed" : "available";
9373
- const version = installedVersion ? `${manifest.name}@${installedVersion}\uFF08catalog ${manifest.version}\uFF09` : `${manifest.name}@${manifest.version}`;
9374
- 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`);
9375
9877
  }
9376
9878
  }
9377
9879
  function printDataPlan(lifecycle, log) {
@@ -9384,6 +9886,11 @@ function printDataPlan(lifecycle, log) {
9384
9886
  if (lifecycle.dataPlan)
9385
9887
  log(` \u8FD0\u7EF4\u5904\u7F6E\u6587\u6863\uFF1A${lifecycle.dataPlan}`);
9386
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
+ }
9387
9894
  function printModulePlan(plan, log) {
9388
9895
  const changed = plan.files.filter(({ action }) => action !== "preserve");
9389
9896
  log(`\u6A21\u5757\u6587\u4EF6\u8BA1\u5212\uFF1A${changed.length} \u4E2A\u53D8\u66F4\uFF1B${plan.conflicts.length} \u4E2A\u51B2\u7A81`);
@@ -9396,12 +9903,8 @@ function printModulePlan(plan, log) {
9396
9903
  function hasChanges(lifecycle) {
9397
9904
  return lifecycle.addedModules.length > 0 || lifecycle.updatedModules.length > 0 || lifecycle.removedModules.length > 0;
9398
9905
  }
9399
- function readValue2(args, flag) {
9400
- const index = args.indexOf(flag);
9401
- return index >= 0 ? args[index + 1] : undefined;
9402
- }
9403
9906
  function isAction(value) {
9404
- return value === "list" || value === "add" || value === "update" || value === "remove";
9907
+ return value === "list" || value === "add" || value === "enable" || value === "disable" || value === "update" || value === "remove";
9405
9908
  }
9406
9909
  function actionLabel(action) {
9407
9910
  return action === "add" ? "\u5B89\u88C5" : action === "update" ? "\u66F4\u65B0" : "\u5378\u8F7D";
@@ -9634,20 +10137,20 @@ import {
9634
10137
  cp,
9635
10138
  mkdir as mkdir9,
9636
10139
  mkdtemp as mkdtemp4,
9637
- readFile as readFile22,
10140
+ readFile as readFile23,
9638
10141
  readdir as readdir9,
9639
10142
  rename as rename2,
9640
10143
  rm as rm11,
9641
- writeFile as writeFile13
10144
+ writeFile as writeFile14
9642
10145
  } from "fs/promises";
9643
- 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";
9644
10147
 
9645
10148
  // src/module-schema/config.ts
9646
- import { readFile as readFile21 } from "fs/promises";
10149
+ import { readFile as readFile22 } from "fs/promises";
9647
10150
  import { isAbsolute as isAbsolute2, relative as relative5, resolve as resolve9 } from "path";
9648
10151
  async function readModuleSchemaLineageConfig(projectDirectory, configPath) {
9649
10152
  const absolutePath = resolveProjectPath(projectDirectory, configPath);
9650
- const parsed = JSON.parse(await readFile21(absolutePath, "utf8"));
10153
+ const parsed = JSON.parse(await readFile22(absolutePath, "utf8"));
9651
10154
  if (!isConfig(parsed)) {
9652
10155
  throw new Error("Module schema lineage \u914D\u7F6E\u65E0\u6548");
9653
10156
  }
@@ -9781,17 +10284,17 @@ async function handoffModuleSchemaLineages(options) {
9781
10284
  async function stageModuleLineages(project, temporaryRoot, config, execute) {
9782
10285
  const outputs = [];
9783
10286
  for (const [index, module3] of config.modules.entries()) {
9784
- const output = join26(temporaryRoot, `module-${index}`);
10287
+ const output = join27(temporaryRoot, `module-${index}`);
9785
10288
  await mkdir9(output, { recursive: true });
9786
- const generatedConfig = join26(temporaryRoot, `module-${index}.config.ts`);
10289
+ const generatedConfig = join27(temporaryRoot, `module-${index}.config.ts`);
9787
10290
  await writeDrizzleConfig(generatedConfig, project, module3.schema, relative6(temporaryRoot, output));
9788
10291
  await execute(generatedConfig, temporaryRoot);
9789
10292
  const sqlFiles = await listSqlFiles(output);
9790
10293
  if (sqlFiles.length !== 1) {
9791
10294
  throw new Error(`${module3.key} baseline \u5FC5\u987B\u6070\u597D\u751F\u6210\u4E00\u4E2A migration`);
9792
10295
  }
9793
- await writeFile13(join26(output, sqlFiles[0]), metadataOnlySql("module"));
9794
- 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({
9795
10298
  schemaVersion: 1,
9796
10299
  moduleKey: module3.key,
9797
10300
  schema: module3.schema,
@@ -9805,25 +10308,25 @@ async function stageModuleLineages(project, temporaryRoot, config, execute) {
9805
10308
  }
9806
10309
  async function stagePlatformLineage(project, temporaryRoot, config, execute) {
9807
10310
  const source = resolveProjectPath(project, config.platform.out);
9808
- const output = join26(temporaryRoot, "platform");
10311
+ const output = join27(temporaryRoot, "platform");
9809
10312
  await cp(source, output, { recursive: true });
9810
10313
  const before = new Set(await listSqlFiles(output));
9811
- const generatedConfig = join26(temporaryRoot, "platform.config.ts");
10314
+ const generatedConfig = join27(temporaryRoot, "platform.config.ts");
9812
10315
  await writeDrizzleConfig(generatedConfig, project, config.platform.schema, relative6(temporaryRoot, output));
9813
10316
  await execute(generatedConfig, temporaryRoot);
9814
10317
  const created = (await listSqlFiles(output)).filter((path2) => !before.has(path2));
9815
10318
  if (created.length !== 1) {
9816
10319
  throw new Error("\u5E73\u53F0 lineage handoff \u5FC5\u987B\u6070\u597D\u751F\u6210\u4E00\u4E2A migration");
9817
10320
  }
9818
- const sqlPath = join26(output, created[0]);
10321
+ const sqlPath = join27(output, created[0]);
9819
10322
  const allowed = config.modules.flatMap(({ legacyPlatformTables }) => legacyPlatformTables);
9820
- validatePlatformHandoffSql(await readFile22(sqlPath, "utf8"), allowed);
9821
- await writeFile13(sqlPath, metadataOnlySql("platform"));
10323
+ validatePlatformHandoffSql(await readFile23(sqlPath, "utf8"), allowed);
10324
+ await writeFile14(sqlPath, metadataOnlySql("platform"));
9822
10325
  return output;
9823
10326
  }
9824
10327
  async function installStagedLineages(project, temporaryRoot, config, stagedModules, stagedPlatform) {
9825
10328
  const platform = resolveProjectPath(project, config.platform.out);
9826
- const backup = join26(temporaryRoot, "platform-backup");
10329
+ const backup = join27(temporaryRoot, "platform-backup");
9827
10330
  const installed = [];
9828
10331
  await rename2(platform, backup);
9829
10332
  try {
@@ -9847,7 +10350,7 @@ async function installStagedLineages(project, temporaryRoot, config, stagedModul
9847
10350
  }
9848
10351
  async function writeDrizzleConfig(path2, project, schemas, out) {
9849
10352
  const absoluteSchemas = schemas.map((schema) => resolveProjectPath(project, schema));
9850
- await writeFile13(path2, `import { defineConfig } from "drizzle-kit";
10353
+ await writeFile14(path2, `import { defineConfig } from "drizzle-kit";
9851
10354
  export default defineConfig(${JSON.stringify({
9852
10355
  dialect: "postgresql",
9853
10356
  schema: absoluteSchemas,
@@ -9870,9 +10373,9 @@ async function validateSchemaSources(project, config) {
9870
10373
  }
9871
10374
  }
9872
10375
  async function createTemporaryRoot(project) {
9873
- const windy = join26(project, ".windy");
10376
+ const windy = join27(project, ".windy");
9874
10377
  await mkdir9(windy, { recursive: true });
9875
- return await mkdtemp4(join26(windy, "module-schema-handoff-"));
10378
+ return await mkdtemp4(join27(windy, "module-schema-handoff-"));
9876
10379
  }
9877
10380
  async function listSqlFiles(root) {
9878
10381
  return (await readdir9(root)).filter((path2) => path2.endsWith(".sql")).sort();