@tomflow/proflow-platform-cli 0.1.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 (144) hide show
  1. package/README.md +37 -0
  2. package/conformance.json +1 -0
  3. package/dist/deployment/adapter.d.ts +57 -0
  4. package/dist/deployment/adapter.js +25 -0
  5. package/dist/deployment/descriptor.d.ts +39 -0
  6. package/dist/deployment/descriptor.js +41 -0
  7. package/dist/src/apply/apply.d.ts +24 -0
  8. package/dist/src/apply/apply.js +271 -0
  9. package/dist/src/apply/cleanup.d.ts +12 -0
  10. package/dist/src/apply/cleanup.js +37 -0
  11. package/dist/src/apply/current.d.ts +4 -0
  12. package/dist/src/apply/current.js +73 -0
  13. package/dist/src/apply/driver.d.ts +23 -0
  14. package/dist/src/apply/driver.js +154 -0
  15. package/dist/src/apply/execute.d.ts +36 -0
  16. package/dist/src/apply/execute.js +124 -0
  17. package/dist/src/apply/index.d.ts +8 -0
  18. package/dist/src/apply/index.js +4 -0
  19. package/dist/src/apply/reality.d.ts +20 -0
  20. package/dist/src/apply/reality.js +93 -0
  21. package/dist/src/binding/production-bindings.d.ts +45 -0
  22. package/dist/src/binding/production-bindings.js +102 -0
  23. package/dist/src/cli.d.ts +12 -0
  24. package/dist/src/cli.js +712 -0
  25. package/dist/src/contracts.d.ts +113 -0
  26. package/dist/src/contracts.js +1 -0
  27. package/dist/src/discovery/catalog.d.ts +10 -0
  28. package/dist/src/discovery/catalog.js +76 -0
  29. package/dist/src/discovery/discover.d.ts +19 -0
  30. package/dist/src/discovery/discover.js +124 -0
  31. package/dist/src/discovery/index.d.ts +5 -0
  32. package/dist/src/discovery/index.js +4 -0
  33. package/dist/src/discovery/installed.d.ts +21 -0
  34. package/dist/src/discovery/installed.js +130 -0
  35. package/dist/src/discovery/workspace.d.ts +10 -0
  36. package/dist/src/discovery/workspace.js +155 -0
  37. package/dist/src/docs/docs.d.ts +47 -0
  38. package/dist/src/docs/docs.js +129 -0
  39. package/dist/src/docs/index.d.ts +1 -0
  40. package/dist/src/docs/index.js +1 -0
  41. package/dist/src/doctor/doctor.d.ts +41 -0
  42. package/dist/src/doctor/doctor.js +80 -0
  43. package/dist/src/doctor/index.d.ts +2 -0
  44. package/dist/src/doctor/index.js +1 -0
  45. package/dist/src/errors.d.ts +6 -0
  46. package/dist/src/errors.js +35 -0
  47. package/dist/src/graph/graph.d.ts +23 -0
  48. package/dist/src/graph/graph.js +132 -0
  49. package/dist/src/graph/index.d.ts +2 -0
  50. package/dist/src/graph/index.js +1 -0
  51. package/dist/src/index.d.ts +28 -0
  52. package/dist/src/index.js +18 -0
  53. package/dist/src/install/bootstrap.d.ts +10 -0
  54. package/dist/src/install/bootstrap.js +79 -0
  55. package/dist/src/install/environment.d.ts +29 -0
  56. package/dist/src/install/environment.js +226 -0
  57. package/dist/src/install/index.d.ts +6 -0
  58. package/dist/src/install/index.js +3 -0
  59. package/dist/src/install/install.d.ts +19 -0
  60. package/dist/src/install/install.js +131 -0
  61. package/dist/src/install/package-manager.d.ts +11 -0
  62. package/dist/src/install/package-manager.js +58 -0
  63. package/dist/src/lifecycle/dispatch.d.ts +47 -0
  64. package/dist/src/lifecycle/dispatch.js +142 -0
  65. package/dist/src/lifecycle/index.d.ts +3 -0
  66. package/dist/src/lifecycle/index.js +2 -0
  67. package/dist/src/lifecycle/service-process.d.ts +11 -0
  68. package/dist/src/lifecycle/service-process.js +326 -0
  69. package/dist/src/manifest/index.d.ts +2 -0
  70. package/dist/src/manifest/index.js +1 -0
  71. package/dist/src/manifest/manifest.d.ts +61 -0
  72. package/dist/src/manifest/manifest.js +160 -0
  73. package/dist/src/modules.d.ts +11 -0
  74. package/dist/src/modules.js +55 -0
  75. package/dist/src/observer/deployment-summary.d.ts +31 -0
  76. package/dist/src/observer/deployment-summary.js +34 -0
  77. package/dist/src/paths.d.ts +21 -0
  78. package/dist/src/paths.js +56 -0
  79. package/dist/src/persistence/atomic.d.ts +2 -0
  80. package/dist/src/persistence/atomic.js +16 -0
  81. package/dist/src/persistence/config.d.ts +12 -0
  82. package/dist/src/persistence/config.js +43 -0
  83. package/dist/src/persistence/generated.d.ts +2 -0
  84. package/dist/src/persistence/generated.js +4 -0
  85. package/dist/src/persistence/guards.d.ts +7 -0
  86. package/dist/src/persistence/guards.js +120 -0
  87. package/dist/src/persistence/index.d.ts +6 -0
  88. package/dist/src/persistence/index.js +5 -0
  89. package/dist/src/persistence/plans.d.ts +5 -0
  90. package/dist/src/persistence/plans.js +34 -0
  91. package/dist/src/persistence/state.d.ts +7 -0
  92. package/dist/src/persistence/state.js +34 -0
  93. package/dist/src/persistence/verification.d.ts +5 -0
  94. package/dist/src/persistence/verification.js +47 -0
  95. package/dist/src/planner/assemble.d.ts +16 -0
  96. package/dist/src/planner/assemble.js +107 -0
  97. package/dist/src/planner/check.d.ts +20 -0
  98. package/dist/src/planner/check.js +96 -0
  99. package/dist/src/planner/fingerprint.d.ts +12 -0
  100. package/dist/src/planner/fingerprint.js +164 -0
  101. package/dist/src/planner/index.d.ts +13 -0
  102. package/dist/src/planner/index.js +7 -0
  103. package/dist/src/planner/plan.d.ts +14 -0
  104. package/dist/src/planner/plan.js +156 -0
  105. package/dist/src/planner/repair.d.ts +28 -0
  106. package/dist/src/planner/repair.js +131 -0
  107. package/dist/src/planner/stale.d.ts +7 -0
  108. package/dist/src/planner/stale.js +23 -0
  109. package/dist/src/planner/steps.d.ts +37 -0
  110. package/dist/src/planner/steps.js +165 -0
  111. package/dist/src/planner/target-catalog.d.ts +13 -0
  112. package/dist/src/planner/target-catalog.js +48 -0
  113. package/dist/src/planner/upgrade.d.ts +18 -0
  114. package/dist/src/planner/upgrade.js +105 -0
  115. package/dist/src/preflight/config.d.ts +10 -0
  116. package/dist/src/preflight/config.js +45 -0
  117. package/dist/src/preflight/findings.d.ts +26 -0
  118. package/dist/src/preflight/findings.js +1 -0
  119. package/dist/src/preflight/index.d.ts +7 -0
  120. package/dist/src/preflight/index.js +3 -0
  121. package/dist/src/preflight/preflight.d.ts +8 -0
  122. package/dist/src/preflight/preflight.js +174 -0
  123. package/dist/src/preflight/requirements.d.ts +11 -0
  124. package/dist/src/preflight/requirements.js +224 -0
  125. package/dist/src/ready/index.d.ts +2 -0
  126. package/dist/src/ready/index.js +1 -0
  127. package/dist/src/ready/ready.d.ts +40 -0
  128. package/dist/src/ready/ready.js +201 -0
  129. package/dist/src/registry/index.d.ts +2 -0
  130. package/dist/src/registry/index.js +1 -0
  131. package/dist/src/registry/npm-registry.d.ts +51 -0
  132. package/dist/src/registry/npm-registry.js +341 -0
  133. package/dist/src/security/index.d.ts +3 -0
  134. package/dist/src/security/index.js +2 -0
  135. package/dist/src/security/lock.d.ts +9 -0
  136. package/dist/src/security/lock.js +89 -0
  137. package/dist/src/security/redact.d.ts +6 -0
  138. package/dist/src/security/redact.js +48 -0
  139. package/dist/src/verification/index.d.ts +2 -0
  140. package/dist/src/verification/index.js +1 -0
  141. package/dist/src/verification/verify.d.ts +33 -0
  142. package/dist/src/verification/verify.js +114 -0
  143. package/package.json +49 -0
  144. package/proflow.module.json +45 -0
@@ -0,0 +1,712 @@
1
+ #!/usr/bin/env node
2
+ import { readFile } from "node:fs/promises";
3
+ import { parseModuleDescriptor, } from "@tomflow/proflow-module-contract";
4
+ import { descriptor as platformCliDescriptor } from "../deployment/descriptor.js";
5
+ import { applyPlan } from "./apply/apply.js";
6
+ import { rebuildCurrentAssumptions } from "./apply/current.js";
7
+ import { createWorkspacePackageManagerDriver } from "./apply/driver.js";
8
+ import { buildProductionBindings, importRawAdapter, } from "./binding/production-bindings.js";
9
+ import { AutoModuleCatalog, discoverModules } from "./discovery/discover.js";
10
+ import { describeModule, readModuleDocument } from "./docs/docs.js";
11
+ import { doctorModules } from "./doctor/doctor.js";
12
+ import { PlatformError } from "./errors.js";
13
+ import { buildDependencyGraph } from "./graph/graph.js";
14
+ import { selectBootstrapModules } from "./install/bootstrap.js";
15
+ import { preflightInstallerEnvironment } from "./install/environment.js";
16
+ import { generateInstallDoc } from "./install/install.js";
17
+ import { restartModules, startModules, statusModules, stopModules, } from "./lifecycle/dispatch.js";
18
+ import { buildManifest } from "./manifest/manifest.js";
19
+ import { writeDeploymentObserverSummary } from "./observer/deployment-summary.js";
20
+ import { workspacePaths } from "./paths.js";
21
+ import { loadConfig } from "./persistence/config.js";
22
+ import { loadPlan, savePlan } from "./persistence/plans.js";
23
+ import { planDeployment } from "./planner/plan.js";
24
+ import { diagnoseRepair } from "./planner/repair.js";
25
+ import { runPreflight } from "./preflight/preflight.js";
26
+ import { discoverRegistryInstallClosure, discoverRegistryModules, loadRegistryModuleDescriptor, } from "./registry/index.js";
27
+ import { verifyModules } from "./verification/verify.js";
28
+ const MODULE_REF = platformCliDescriptor.moduleRef;
29
+ const MODULE_VERSION = platformCliDescriptor.moduleVersion;
30
+ const COMMANDS = [
31
+ "search",
32
+ "modules",
33
+ "docs",
34
+ "install",
35
+ "uninstall",
36
+ "upgrade",
37
+ "preflight",
38
+ "plan",
39
+ "apply",
40
+ "start",
41
+ "stop",
42
+ "restart",
43
+ "status",
44
+ "verify",
45
+ "doctor",
46
+ "manifest",
47
+ ];
48
+ function parseArgs(argv) {
49
+ const positional = [];
50
+ const parsed = {
51
+ command: argv[0] ?? "",
52
+ workspace: undefined,
53
+ intent: undefined,
54
+ configFile: undefined,
55
+ positional,
56
+ };
57
+ for (let index = 1; index < argv.length; index += 1) {
58
+ const token = argv[index];
59
+ if (token === undefined)
60
+ continue;
61
+ if (token === "--workspace") {
62
+ parsed.workspace = argv[index + 1];
63
+ index += 1;
64
+ }
65
+ else if (token === "--intent") {
66
+ parsed.intent = argv[index + 1];
67
+ index += 1;
68
+ }
69
+ else if (token === "--config") {
70
+ parsed.configFile = argv[index + 1];
71
+ index += 1;
72
+ }
73
+ else if (token.startsWith("-")) {
74
+ }
75
+ else {
76
+ positional.push(token);
77
+ }
78
+ }
79
+ return parsed;
80
+ }
81
+ function outcome(command, status, data) {
82
+ const result = { command, status };
83
+ if (data !== undefined)
84
+ result.data = data;
85
+ return result;
86
+ }
87
+ function aggregateStatus(statuses) {
88
+ if (statuses.includes("FAILED"))
89
+ return "FAILED";
90
+ if (statuses.includes("BLOCKED"))
91
+ return "BLOCKED";
92
+ if (statuses.includes("ACTION_REQUIRED"))
93
+ return "ACTION_REQUIRED";
94
+ return "SUCCEEDED";
95
+ }
96
+ function failure(command, error) {
97
+ if (error instanceof PlatformError) {
98
+ return {
99
+ command,
100
+ status: "FAILED",
101
+ error: { code: error.code, message: error.message },
102
+ };
103
+ }
104
+ const message = error instanceof Error ? error.message : String(error);
105
+ return {
106
+ command,
107
+ status: "FAILED",
108
+ error: { code: "COMMAND_FAILED", message },
109
+ };
110
+ }
111
+ function selectModules(modules, filter) {
112
+ if (filter === undefined)
113
+ return modules;
114
+ const selected = modules.filter((module) => module.moduleRef === filter || module.packageName === filter);
115
+ if (selected.length === 0) {
116
+ throw new PlatformError("INVALID_REQUEST", `module or package "${filter}" not found`);
117
+ }
118
+ return selected;
119
+ }
120
+ function moduleSourceOf(module) {
121
+ if (module.source.type === "registry") {
122
+ throw new PlatformError("DESCRIPTOR_INVALID", `registry bootstrap target ${module.packageName} has no local descriptor`);
123
+ }
124
+ const source = {
125
+ type: module.source.type,
126
+ packageName: module.packageName,
127
+ };
128
+ if (module.source.path !== undefined)
129
+ source.path = module.source.path;
130
+ return source;
131
+ }
132
+ async function loadDescriptors(catalog, modules) {
133
+ const descriptors = [];
134
+ for (const module of modules) {
135
+ const raw = await catalog.loadDescriptor(moduleSourceOf(module));
136
+ descriptors.push(parseModuleDescriptor(raw));
137
+ }
138
+ return descriptors;
139
+ }
140
+ async function loadConfigFile(path) {
141
+ if (path === undefined)
142
+ return {};
143
+ const raw = await readFile(path, "utf8");
144
+ const value = JSON.parse(raw);
145
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
146
+ throw new PlatformError("INVALID_REQUEST", `config file ${path} is not an object`);
147
+ }
148
+ const modules = value.modules;
149
+ if (modules === undefined)
150
+ return {};
151
+ if (typeof modules !== "object" ||
152
+ modules === null ||
153
+ Array.isArray(modules)) {
154
+ throw new PlatformError("INVALID_REQUEST", `config file ${path} "modules" is not an object`);
155
+ }
156
+ const result = {};
157
+ for (const [ref, slots] of Object.entries(modules)) {
158
+ if (typeof slots !== "object" || slots === null || Array.isArray(slots))
159
+ continue;
160
+ const values = {};
161
+ for (const [key, value] of Object.entries(slots)) {
162
+ if (typeof value === "string")
163
+ values[key] = value;
164
+ }
165
+ result[ref] = values;
166
+ }
167
+ return result;
168
+ }
169
+ async function buildContext(workspace) {
170
+ const root = workspace ?? process.cwd();
171
+ const paths = workspacePaths(root);
172
+ const catalog = await buildCatalogWithProductionBindings(root, paths);
173
+ return { catalog, paths };
174
+ }
175
+ /**
176
+ * The shipped CLI production composition root. Instead of constructing an empty
177
+ * `AutoModuleCatalog` (whose binding seam would otherwise be used only by tests),
178
+ * it discovers the governed modules, loads their materialized config, runs the
179
+ * production binding factory against each module's own deployment adapter, and
180
+ * passes the resulting real bindings into the catalog. Modules without a
181
+ * production factory stay unbound and fail closed when operated on.
182
+ */
183
+ async function buildCatalogWithProductionBindings(root, paths) {
184
+ const modules = await discoverModules({ workspaceRoot: root });
185
+ const configByModuleRef = new Map();
186
+ for (const module of modules) {
187
+ const config = await loadConfig(paths, module.moduleRef);
188
+ if (config === undefined)
189
+ continue;
190
+ configByModuleRef.set(module.moduleRef, {
191
+ ...config.publicValues,
192
+ ...config.secretValues,
193
+ });
194
+ }
195
+ const bindings = await buildProductionBindings({
196
+ workspaceRoot: root,
197
+ modules,
198
+ configByModuleRef,
199
+ importAdapter: (packageName, source) => importRawAdapter(packageName, source),
200
+ });
201
+ return new AutoModuleCatalog(root, bindings);
202
+ }
203
+ async function handleSearch(root, args) {
204
+ const result = await discoverRegistryModules({
205
+ workspaceRoot: root,
206
+ ...(args.positional[0] === undefined
207
+ ? {}
208
+ : { packageName: args.positional[0] }),
209
+ });
210
+ if (args.positional[0] !== undefined && result.candidates.length === 0) {
211
+ return outcome("search", "BLOCKED", result);
212
+ }
213
+ return outcome("search", "SUCCEEDED", result);
214
+ }
215
+ async function handleModules(ctx, args) {
216
+ const modules = await discoverModules({ catalog: ctx.catalog });
217
+ const selected = selectModules(modules, args.positional[0]);
218
+ const descriptors = await loadDescriptors(ctx.catalog, selected);
219
+ return outcome("modules", "SUCCEEDED", descriptors.map((descriptor) => ({
220
+ moduleRef: descriptor.moduleRef,
221
+ packageName: descriptor.packageName,
222
+ moduleVersion: descriptor.moduleVersion,
223
+ kind: descriptor.kind,
224
+ installClass: descriptor.installClass,
225
+ identity: descriptor.identity,
226
+ lifecycle: descriptor.lifecycle.supported,
227
+ documentation: descriptor.documentation,
228
+ })));
229
+ }
230
+ async function handleDocs(ctx, args) {
231
+ if (args.positional.length > 2) {
232
+ throw new PlatformError("INVALID_REQUEST", "docs accepts at most <moduleRef|packageName> [documentId]");
233
+ }
234
+ const modules = await discoverModules({ catalog: ctx.catalog });
235
+ const moduleFilter = args.positional[0];
236
+ const documentId = args.positional[1];
237
+ if (documentId !== undefined && moduleFilter === undefined) {
238
+ throw new PlatformError("INVALID_REQUEST", "docs document read requires a module");
239
+ }
240
+ const selected = selectModules(modules, moduleFilter);
241
+ const descriptors = await loadDescriptors(ctx.catalog, selected);
242
+ if (documentId !== undefined) {
243
+ const module = selected[0];
244
+ const descriptor = descriptors[0];
245
+ if (module === undefined || descriptor === undefined) {
246
+ throw new PlatformError("COMMAND_FAILED", "docs module selection invariant failed");
247
+ }
248
+ return outcome("docs", "SUCCEEDED", await readModuleDocument({
249
+ workspaceRoot: ctx.paths.root,
250
+ source: moduleSourceOf(module),
251
+ descriptor,
252
+ documentId,
253
+ }));
254
+ }
255
+ const views = await Promise.all(selected.map((module, index) => describeModule({
256
+ workspaceRoot: ctx.paths.root,
257
+ source: moduleSourceOf(module),
258
+ descriptor: descriptors[index] ??
259
+ (() => {
260
+ throw new Error(`Missing module descriptor for ${module.packageName}`);
261
+ })(),
262
+ })));
263
+ if (moduleFilter !== undefined) {
264
+ return outcome("docs", "SUCCEEDED", views[0]);
265
+ }
266
+ return outcome("docs", "SUCCEEDED", views.map((view) => ({
267
+ moduleRef: view.moduleRef,
268
+ packageName: view.packageName,
269
+ moduleVersion: view.moduleVersion,
270
+ kind: view.kind,
271
+ installClass: view.installClass,
272
+ identity: view.identity,
273
+ commands: view.commands,
274
+ publicApiEntries: view.publicApiEntries,
275
+ provides: view.provides,
276
+ requires: view.requires,
277
+ lifecycle: view.lifecycle,
278
+ documentation: view.documentation,
279
+ })));
280
+ }
281
+ async function handleInstallerPreflight(root) {
282
+ const result = await preflightInstallerEnvironment({ workspaceRoot: root });
283
+ const status = result.status === "READY"
284
+ ? "SUCCEEDED"
285
+ : result.status === "ACTION_REQUIRED"
286
+ ? "ACTION_REQUIRED"
287
+ : "BLOCKED";
288
+ return outcome("preflight", status, result);
289
+ }
290
+ async function handlePreflight(ctx, args) {
291
+ const modules = await discoverModules({ catalog: ctx.catalog });
292
+ const selected = selectModules(modules, args.positional[0]);
293
+ const config = await loadConfigFile(args.configFile);
294
+ const result = await runPreflight(selected, { config, catalog: ctx.catalog });
295
+ return result.ok
296
+ ? outcome("preflight", "SUCCEEDED", result)
297
+ : outcome("preflight", "BLOCKED", result);
298
+ }
299
+ async function handlePlan(ctx, args) {
300
+ const intent = args.intent;
301
+ if (intent !== "install" &&
302
+ intent !== "configure" &&
303
+ intent !== "upgrade" &&
304
+ intent !== "uninstall" &&
305
+ intent !== "repair") {
306
+ throw new PlatformError("INVALID_REQUEST", "plan requires --intent install|configure|upgrade|uninstall|repair");
307
+ }
308
+ if (intent === "install") {
309
+ const installer = await preflightInstallerEnvironment({
310
+ workspaceRoot: ctx.paths.root,
311
+ });
312
+ if (installer.status !== "READY") {
313
+ return outcome("plan", installer.status === "ACTION_REQUIRED" ? "ACTION_REQUIRED" : "BLOCKED", installer);
314
+ }
315
+ const registry = args.positional[0] === undefined
316
+ ? await discoverRegistryModules({ workspaceRoot: ctx.paths.root })
317
+ : await discoverRegistryInstallClosure({
318
+ workspaceRoot: ctx.paths.root,
319
+ packageName: args.positional[0],
320
+ });
321
+ const bootstrapModules = selectBootstrapModules(registry.candidates, args.positional[0]);
322
+ const plan = planDeployment({
323
+ intent: "install",
324
+ modules: bootstrapModules,
325
+ });
326
+ await savePlan(ctx.paths, plan);
327
+ return outcome("plan", "SUCCEEDED", {
328
+ planRef: plan.planRef,
329
+ registry: registry.registry,
330
+ plan,
331
+ });
332
+ }
333
+ const modules = await discoverModules({ catalog: ctx.catalog });
334
+ const selected = selectModules(modules, args.positional[0]);
335
+ const config = await loadConfigFile(args.configFile);
336
+ if (intent === "uninstall") {
337
+ if (args.positional[0] === undefined) {
338
+ throw new PlatformError("INVALID_REQUEST", "uninstall plan requires <moduleRef|packageName>");
339
+ }
340
+ if (selected.every((module) => module.installClass !== "core")) {
341
+ const removing = new Set(selected.map((module) => module.moduleRef));
342
+ const remaining = modules.filter((module) => !removing.has(module.moduleRef));
343
+ // Rebuilding the remaining graph is the uninstall dependency gate: if a
344
+ // required contract or moduleRef binding would disappear, graph
345
+ // construction fails closed before any removal plan is persisted.
346
+ buildDependencyGraph(remaining);
347
+ }
348
+ const plan = planDeployment({ intent, modules: selected, config });
349
+ await savePlan(ctx.paths, plan);
350
+ return outcome("plan", "SUCCEEDED", { planRef: plan.planRef, plan });
351
+ }
352
+ if (intent === "upgrade") {
353
+ const installer = await preflightInstallerEnvironment({
354
+ workspaceRoot: ctx.paths.root,
355
+ });
356
+ if (installer.status !== "READY") {
357
+ return outcome("plan", installer.status === "ACTION_REQUIRED" ? "ACTION_REQUIRED" : "BLOCKED", installer);
358
+ }
359
+ const currentDescriptors = await loadDescriptors(ctx.catalog, modules);
360
+ const currentByRef = new Map(currentDescriptors.map((descriptor) => [
361
+ descriptor.moduleRef,
362
+ descriptor,
363
+ ]));
364
+ const targetByRef = new Map(currentByRef);
365
+ const targets = [];
366
+ let registryUrl;
367
+ for (const module of selected) {
368
+ const discovered = await discoverRegistryModules({
369
+ workspaceRoot: ctx.paths.root,
370
+ packageName: module.packageName,
371
+ });
372
+ registryUrl ??= discovered.registry;
373
+ const candidate = discovered.candidates[0];
374
+ if (candidate === undefined) {
375
+ throw new PlatformError("PACKAGE_NOT_FOUND", `no installable Registry target found for managed package ${module.packageName}`);
376
+ }
377
+ targets.push({
378
+ moduleRef: module.moduleRef,
379
+ targetVersion: candidate.moduleVersion,
380
+ });
381
+ if (candidate.moduleVersion === module.moduleVersion)
382
+ continue;
383
+ const targetDescriptor = await loadRegistryModuleDescriptor({
384
+ workspaceRoot: ctx.paths.root,
385
+ candidate,
386
+ });
387
+ if (targetDescriptor.moduleRef !== module.moduleRef) {
388
+ throw new PlatformError("REGISTRY_RESPONSE_INVALID", `Registry target ${module.packageName}@${candidate.moduleVersion} changed moduleRef from ${module.moduleRef} to ${targetDescriptor.moduleRef}`);
389
+ }
390
+ targetByRef.set(module.moduleRef, targetDescriptor);
391
+ }
392
+ const targetDescriptors = modules.map((module) => {
393
+ const descriptor = targetByRef.get(module.moduleRef);
394
+ if (descriptor === undefined) {
395
+ throw new PlatformError("COMMAND_FAILED", `upgrade target descriptor invariant failed for ${module.moduleRef}`);
396
+ }
397
+ return descriptor;
398
+ });
399
+ const plan = planDeployment({
400
+ intent,
401
+ currentDescriptors,
402
+ targetDescriptors,
403
+ targets,
404
+ });
405
+ await savePlan(ctx.paths, plan);
406
+ return outcome("plan", "SUCCEEDED", {
407
+ planRef: plan.planRef,
408
+ registry: registryUrl,
409
+ plan,
410
+ });
411
+ }
412
+ const preflight = await runPreflight(selected, {
413
+ config,
414
+ catalog: ctx.catalog,
415
+ });
416
+ if (preflight.status === "NOT_READY" ||
417
+ preflight.status === "ACTION_REQUIRED") {
418
+ return outcome("plan", preflight.status === "ACTION_REQUIRED" ? "ACTION_REQUIRED" : "BLOCKED", preflight);
419
+ }
420
+ let input;
421
+ if (intent === "repair") {
422
+ const diagnosis = diagnoseRepair(await doctorModules(ctx.catalog, selected));
423
+ if (diagnosis.blocked.length > 0) {
424
+ return outcome("plan", "BLOCKED", diagnosis);
425
+ }
426
+ input = { intent, modules: selected, facts: diagnosis.facts, config };
427
+ }
428
+ else {
429
+ input = { intent, modules: selected, config };
430
+ }
431
+ const plan = planDeployment(input);
432
+ await savePlan(ctx.paths, plan);
433
+ return outcome("plan", "SUCCEEDED", { planRef: plan.planRef, plan });
434
+ }
435
+ async function handleApply(ctx, args) {
436
+ const planRef = args.positional[0];
437
+ if (planRef === undefined) {
438
+ throw new PlatformError("INVALID_REQUEST", "apply requires <planRef>");
439
+ }
440
+ const plan = await loadPlan(ctx.paths, planRef);
441
+ if (plan === undefined) {
442
+ throw new PlatformError("PLAN_NOT_FOUND", `plan ${planRef} not found`);
443
+ }
444
+ const current = await rebuildCurrentAssumptions(ctx.catalog, plan);
445
+ const result = await applyPlan({
446
+ paths: ctx.paths,
447
+ planRef,
448
+ catalog: ctx.catalog,
449
+ current,
450
+ driver: createWorkspacePackageManagerDriver({
451
+ workspaceRoot: ctx.paths.root,
452
+ }),
453
+ });
454
+ const status = result.outcome === "COMPLETE"
455
+ ? "SUCCEEDED"
456
+ : result.outcome === "ACTION_REQUIRED"
457
+ ? "ACTION_REQUIRED"
458
+ : result.outcome === "BLOCKED"
459
+ ? "BLOCKED"
460
+ : "FAILED";
461
+ return outcome("apply", status, result);
462
+ }
463
+ function planRefFromOutcome(result) {
464
+ if (typeof result.data !== "object" ||
465
+ result.data === null ||
466
+ Array.isArray(result.data)) {
467
+ throw new PlatformError("PLAN_INVALID", "planned mutation returned no plan data");
468
+ }
469
+ const planRef = Reflect.get(result.data, "planRef");
470
+ if (typeof planRef !== "string" || planRef === "") {
471
+ throw new PlatformError("PLAN_INVALID", "planned mutation returned no planRef");
472
+ }
473
+ return planRef;
474
+ }
475
+ async function handleManagedMutation(ctx, args, intent) {
476
+ const planned = await handlePlan(ctx, { ...args, command: "plan", intent });
477
+ if (planned.status !== "SUCCEEDED") {
478
+ return { ...planned, command: intent };
479
+ }
480
+ const planRef = planRefFromOutcome(planned);
481
+ const applied = await handleApply(ctx, {
482
+ ...args,
483
+ command: "apply",
484
+ intent: undefined,
485
+ positional: [planRef],
486
+ });
487
+ if (applied.status !== "SUCCEEDED") {
488
+ return outcome(intent, applied.status, {
489
+ plan: planned.data,
490
+ apply: applied.data,
491
+ });
492
+ }
493
+ // Package mutations change Workspace package reality. Rebuild the catalog
494
+ // before reporting the managed set so the result can never be sourced from
495
+ // the pre-mutation catalog.
496
+ const refreshed = await buildContext(args.workspace);
497
+ const managed = await handleModules(refreshed, {
498
+ ...args,
499
+ command: "modules",
500
+ intent: undefined,
501
+ positional: [],
502
+ });
503
+ return outcome(intent, "SUCCEEDED", {
504
+ plan: planned.data,
505
+ apply: applied.data,
506
+ managedModules: managed.data,
507
+ });
508
+ }
509
+ async function handleLifecycle(ctx, args, primitive) {
510
+ const modules = await discoverModules({ catalog: ctx.catalog });
511
+ const selected = selectModules(modules, args.positional[0]);
512
+ const result = primitive === "start"
513
+ ? await startModules(ctx.catalog, selected)
514
+ : primitive === "stop"
515
+ ? await stopModules(ctx.catalog, selected)
516
+ : primitive === "restart"
517
+ ? await restartModules(ctx.catalog, selected)
518
+ : await statusModules(ctx.catalog, selected);
519
+ const statuses = result.flatMap((item) => item.result ? [item.result.status] : []);
520
+ const status = aggregateStatus(statuses);
521
+ if (primitive === "status" && !args.positional[0]) {
522
+ await writeDeploymentObserverSummary({
523
+ paths: ctx.paths,
524
+ source: "status",
525
+ selectedModuleCount: selected.length,
526
+ totalModuleCount: modules.length,
527
+ modules: result.flatMap((item) => item.result
528
+ ? [{ moduleRef: item.moduleRef, status: item.result.status }]
529
+ : []),
530
+ });
531
+ }
532
+ return outcome(primitive, status, result);
533
+ }
534
+ async function handleVerify(ctx, args) {
535
+ const modules = await discoverModules({ catalog: ctx.catalog });
536
+ const selected = selectModules(modules, args.positional[0]);
537
+ const result = await verifyModules(ctx.catalog, selected, ctx.paths);
538
+ const status = aggregateStatus(result.map((item) => item.result.status));
539
+ if (!args.positional[0]) {
540
+ await writeDeploymentObserverSummary({
541
+ paths: ctx.paths,
542
+ source: "verify",
543
+ selectedModuleCount: selected.length,
544
+ totalModuleCount: modules.length,
545
+ modules: result.map((item) => ({
546
+ moduleRef: item.moduleRef,
547
+ status: item.result.status,
548
+ })),
549
+ });
550
+ }
551
+ return outcome("verify", status, result);
552
+ }
553
+ async function handleDoctor(ctx, args) {
554
+ const modules = await discoverModules({ catalog: ctx.catalog });
555
+ const selected = selectModules(modules, args.positional[0]);
556
+ const result = await doctorModules(ctx.catalog, selected);
557
+ const status = aggregateStatus(result.map((item) => item.status));
558
+ if (!args.positional[0]) {
559
+ await writeDeploymentObserverSummary({
560
+ paths: ctx.paths,
561
+ source: "doctor",
562
+ selectedModuleCount: selected.length,
563
+ totalModuleCount: modules.length,
564
+ modules: result.map((item) => ({
565
+ moduleRef: item.moduleRef,
566
+ status: item.status,
567
+ })),
568
+ });
569
+ }
570
+ return outcome("doctor", status, result);
571
+ }
572
+ async function handleManifest(ctx, args) {
573
+ const modules = await discoverModules({ catalog: ctx.catalog });
574
+ const selected = selectModules(modules, args.positional[0]);
575
+ const config = await loadConfigFile(args.configFile);
576
+ const manifest = await buildManifest({
577
+ catalog: ctx.catalog,
578
+ modules: selected,
579
+ paths: ctx.paths,
580
+ config,
581
+ });
582
+ await generateInstallDoc({ modules: selected, config, paths: ctx.paths });
583
+ switch (manifest.status) {
584
+ case "READY":
585
+ return outcome("manifest", "SUCCEEDED", manifest);
586
+ case "ACTION_REQUIRED":
587
+ return outcome("manifest", "ACTION_REQUIRED", manifest);
588
+ case "DEGRADED":
589
+ return outcome("manifest", "BLOCKED", manifest);
590
+ case "NOT_READY":
591
+ return outcome("manifest", "FAILED", manifest);
592
+ }
593
+ }
594
+ async function dispatchCommand(argv) {
595
+ const args = parseArgs(argv);
596
+ if (!COMMANDS.includes(args.command)) {
597
+ return {
598
+ command: args.command,
599
+ status: "FAILED",
600
+ error: {
601
+ code: "INVALID_REQUEST",
602
+ message: `unknown command: ${args.command}`,
603
+ },
604
+ };
605
+ }
606
+ try {
607
+ const root = args.workspace ?? process.cwd();
608
+ if (args.command === "search")
609
+ return await handleSearch(root, args);
610
+ if (args.command === "preflight" && args.intent === "install") {
611
+ return await handleInstallerPreflight(root);
612
+ }
613
+ const ctx = await buildContext(args.workspace);
614
+ switch (args.command) {
615
+ case "search":
616
+ return await handleSearch(root, args);
617
+ case "modules":
618
+ return await handleModules(ctx, args);
619
+ case "docs":
620
+ return await handleDocs(ctx, args);
621
+ case "install":
622
+ case "uninstall":
623
+ case "upgrade":
624
+ return await handleManagedMutation(ctx, args, args.command);
625
+ case "preflight":
626
+ return await handlePreflight(ctx, args);
627
+ case "plan":
628
+ return await handlePlan(ctx, args);
629
+ case "apply":
630
+ return await handleApply(ctx, args);
631
+ case "start":
632
+ case "stop":
633
+ case "restart":
634
+ case "status":
635
+ return await handleLifecycle(ctx, args, args.command);
636
+ case "verify":
637
+ return await handleVerify(ctx, args);
638
+ case "doctor":
639
+ return await handleDoctor(ctx, args);
640
+ case "manifest":
641
+ return await handleManifest(ctx, args);
642
+ }
643
+ }
644
+ catch (error) {
645
+ return failure(args.command, error);
646
+ }
647
+ return failure(args.command, new PlatformError("COMMAND_FAILED", "unreachable"));
648
+ }
649
+ function toMachineResult(outcomeResult) {
650
+ const ok = outcomeResult.status === "SUCCEEDED";
651
+ return JSON.stringify({
652
+ contract: "deployment.result.v1",
653
+ ok,
654
+ status: outcomeResult.status,
655
+ moduleRef: MODULE_REF,
656
+ moduleVersion: MODULE_VERSION,
657
+ ...(outcomeResult.data !== undefined ? { data: outcomeResult.data } : {}),
658
+ ...(outcomeResult.status === "ACTION_REQUIRED"
659
+ ? {
660
+ actionRequired: {
661
+ action: "resolve-blocking-action",
662
+ description: "one or more steps require a human action",
663
+ },
664
+ }
665
+ : {}),
666
+ ...(outcomeResult.error !== undefined
667
+ ? {
668
+ error: {
669
+ code: outcomeResult.error.code,
670
+ message: outcomeResult.error.message,
671
+ retryable: false,
672
+ },
673
+ }
674
+ : {}),
675
+ });
676
+ }
677
+ export async function runCli(argv) {
678
+ if (argv.includes("--help") || argv.includes("-h")) {
679
+ return JSON.stringify({
680
+ contract: "deployment.result.v1",
681
+ ok: true,
682
+ status: "SUCCEEDED",
683
+ moduleRef: MODULE_REF,
684
+ moduleVersion: MODULE_VERSION,
685
+ data: {
686
+ usage: "platform <command> [module|package] [--workspace <path>] [--json]",
687
+ commands: [...COMMANDS],
688
+ },
689
+ });
690
+ }
691
+ const filtered = argv.filter((argument) => argument !== "--json");
692
+ if (filtered.length === 0) {
693
+ return JSON.stringify({
694
+ contract: "deployment.result.v1",
695
+ ok: true,
696
+ status: "SUCCEEDED",
697
+ moduleRef: MODULE_REF,
698
+ moduleVersion: MODULE_VERSION,
699
+ });
700
+ }
701
+ return toMachineResult(await dispatchCommand(filtered));
702
+ }
703
+ if (import.meta.main) {
704
+ const output = await runCli(process.argv.slice(2));
705
+ process.stdout.write(`${output}\n`);
706
+ const parsed = JSON.parse(output);
707
+ if (typeof parsed === "object" &&
708
+ parsed !== null &&
709
+ Reflect.get(parsed, "ok") === false) {
710
+ process.exitCode = 1;
711
+ }
712
+ }