@tomflow/proflow-platform-cli 0.1.14 → 0.1.15
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.
- package/README.md +48 -43
- package/dist/deployment/adapter.d.ts +5 -5
- package/dist/deployment/adapter.js +2 -1
- package/dist/deployment/descriptor.d.ts +2 -3
- package/dist/deployment/descriptor.js +2 -3
- package/dist/src/binding/production-bindings.d.ts +17 -15
- package/dist/src/binding/production-bindings.js +18 -59
- package/dist/src/cli.d.ts +7 -9
- package/dist/src/cli.js +327 -1316
- package/dist/src/contracts.d.ts +4 -97
- package/dist/src/discovery/discover.js +0 -1
- package/dist/src/docs/aggregate.d.ts +18 -0
- package/dist/src/docs/aggregate.js +44 -0
- package/dist/src/docs/docs.d.ts +0 -31
- package/dist/src/docs/docs.js +0 -61
- package/dist/src/docs/index.d.ts +2 -1
- package/dist/src/docs/index.js +1 -1
- package/dist/src/errors.d.ts +1 -1
- package/dist/src/errors.js +0 -18
- package/dist/src/graph/graph.d.ts +1 -4
- package/dist/src/graph/graph.js +1 -13
- package/dist/src/graph/index.d.ts +2 -2
- package/dist/src/graph/index.js +1 -1
- package/dist/src/index.d.ts +4 -24
- package/dist/src/index.js +2 -16
- package/dist/src/install/package-manager.d.ts +21 -0
- package/dist/src/install/package-manager.js +153 -0
- package/dist/src/lifecycle/dispatch.d.ts +0 -31
- package/dist/src/lifecycle/dispatch.js +0 -125
- package/dist/src/lifecycle/index.d.ts +4 -3
- package/dist/src/lifecycle/index.js +2 -2
- package/dist/src/lifecycle/thin.d.ts +11 -0
- package/dist/src/lifecycle/thin.js +68 -0
- package/dist/src/paths.d.ts +0 -14
- package/dist/src/paths.js +0 -39
- package/dist/src/persistence/config.d.ts +0 -6
- package/dist/src/persistence/config.js +0 -14
- package/dist/src/persistence/guards.d.ts +0 -6
- package/dist/src/persistence/guards.js +0 -114
- package/dist/src/persistence/index.d.ts +4 -6
- package/dist/src/persistence/index.js +2 -5
- package/dist/src/persistence/workspace-metadata.d.ts +7 -0
- package/dist/src/persistence/workspace-metadata.js +39 -0
- package/dist/src/registry/index.d.ts +1 -1
- package/dist/src/registry/index.js +1 -1
- package/dist/src/registry/npm-registry.d.ts +0 -14
- package/dist/src/registry/npm-registry.js +1 -132
- package/package.json +2 -6
- package/proflow.module.json +2 -3
- package/CHANGELOG.md +0 -85
- package/dist/src/apply/apply.d.ts +0 -26
- package/dist/src/apply/apply.js +0 -314
- package/dist/src/apply/cleanup.d.ts +0 -12
- package/dist/src/apply/cleanup.js +0 -37
- package/dist/src/apply/current.d.ts +0 -5
- package/dist/src/apply/current.js +0 -95
- package/dist/src/apply/driver.d.ts +0 -28
- package/dist/src/apply/driver.js +0 -239
- package/dist/src/apply/execute.d.ts +0 -36
- package/dist/src/apply/execute.js +0 -152
- package/dist/src/apply/index.d.ts +0 -8
- package/dist/src/apply/index.js +0 -4
- package/dist/src/apply/reality.d.ts +0 -20
- package/dist/src/apply/reality.js +0 -105
- package/dist/src/binding/global-binding.d.ts +0 -69
- package/dist/src/binding/global-binding.js +0 -349
- package/dist/src/doctor/doctor.d.ts +0 -41
- package/dist/src/doctor/doctor.js +0 -80
- package/dist/src/doctor/index.d.ts +0 -2
- package/dist/src/doctor/index.js +0 -1
- package/dist/src/install/bootstrap.d.ts +0 -10
- package/dist/src/install/bootstrap.js +0 -79
- package/dist/src/install/environment.d.ts +0 -30
- package/dist/src/install/environment.js +0 -230
- package/dist/src/install/index.d.ts +0 -6
- package/dist/src/install/index.js +0 -3
- package/dist/src/install/install.d.ts +0 -19
- package/dist/src/install/install.js +0 -172
- package/dist/src/lifecycle/service-process.d.ts +0 -11
- package/dist/src/lifecycle/service-process.js +0 -356
- package/dist/src/manifest/index.d.ts +0 -2
- package/dist/src/manifest/index.js +0 -1
- package/dist/src/manifest/manifest.d.ts +0 -61
- package/dist/src/manifest/manifest.js +0 -167
- package/dist/src/observer/deployment-summary.d.ts +0 -31
- package/dist/src/observer/deployment-summary.js +0 -34
- package/dist/src/persistence/atomic.d.ts +0 -2
- package/dist/src/persistence/atomic.js +0 -16
- package/dist/src/persistence/effective-config.d.ts +0 -3
- package/dist/src/persistence/effective-config.js +0 -20
- package/dist/src/persistence/generated.d.ts +0 -2
- package/dist/src/persistence/generated.js +0 -4
- package/dist/src/persistence/plans.d.ts +0 -5
- package/dist/src/persistence/plans.js +0 -34
- package/dist/src/persistence/state.d.ts +0 -7
- package/dist/src/persistence/state.js +0 -34
- package/dist/src/persistence/verification.d.ts +0 -5
- package/dist/src/persistence/verification.js +0 -47
- package/dist/src/planner/assemble.d.ts +0 -16
- package/dist/src/planner/assemble.js +0 -107
- package/dist/src/planner/check.d.ts +0 -20
- package/dist/src/planner/check.js +0 -107
- package/dist/src/planner/fingerprint.d.ts +0 -12
- package/dist/src/planner/fingerprint.js +0 -164
- package/dist/src/planner/index.d.ts +0 -13
- package/dist/src/planner/index.js +0 -7
- package/dist/src/planner/plan.d.ts +0 -15
- package/dist/src/planner/plan.js +0 -144
- package/dist/src/planner/repair.d.ts +0 -28
- package/dist/src/planner/repair.js +0 -131
- package/dist/src/planner/stale.d.ts +0 -7
- package/dist/src/planner/stale.js +0 -23
- package/dist/src/planner/steps.d.ts +0 -37
- package/dist/src/planner/steps.js +0 -165
- package/dist/src/planner/target-catalog.d.ts +0 -13
- package/dist/src/planner/target-catalog.js +0 -48
- package/dist/src/planner/upgrade.d.ts +0 -18
- package/dist/src/planner/upgrade.js +0 -114
- package/dist/src/preflight/config.d.ts +0 -11
- package/dist/src/preflight/config.js +0 -88
- package/dist/src/preflight/findings.d.ts +0 -26
- package/dist/src/preflight/findings.js +0 -1
- package/dist/src/preflight/index.d.ts +0 -7
- package/dist/src/preflight/index.js +0 -3
- package/dist/src/preflight/preflight.d.ts +0 -10
- package/dist/src/preflight/preflight.js +0 -190
- package/dist/src/preflight/requirements.d.ts +0 -11
- package/dist/src/preflight/requirements.js +0 -231
- package/dist/src/ready/index.d.ts +0 -2
- package/dist/src/ready/index.js +0 -1
- package/dist/src/ready/ready.d.ts +0 -40
- package/dist/src/ready/ready.js +0 -201
- package/dist/src/security/index.d.ts +0 -3
- package/dist/src/security/index.js +0 -2
- package/dist/src/security/lock.d.ts +0 -9
- package/dist/src/security/lock.js +0 -127
- package/dist/src/security/redact.d.ts +0 -6
- package/dist/src/security/redact.js +0 -48
- package/dist/src/verification/index.d.ts +0 -2
- package/dist/src/verification/index.js +0 -1
- package/dist/src/verification/verify.d.ts +0 -33
- package/dist/src/verification/verify.js +0 -114
package/dist/src/cli.js
CHANGED
|
@@ -1,1427 +1,438 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { readFile,
|
|
3
|
-
import {
|
|
2
|
+
import { readFile, realpath, stat } from "node:fs/promises";
|
|
3
|
+
import { resolve } from "node:path";
|
|
4
|
+
import { moduleStatusObservationSchema } from "@tomflow/proflow-module-contract";
|
|
4
5
|
import { descriptor as platformCliDescriptor } from "../deployment/descriptor.js";
|
|
5
|
-
import { applyPlan } from "./apply/apply.js";
|
|
6
|
-
import { rebuildCurrentAssumptions } from "./apply/current.js";
|
|
7
|
-
import { cleanupWorkspacePackageManagerArtifacts, createWorkspacePackageManagerDriver, } from "./apply/driver.js";
|
|
8
|
-
import { acquireGlobalOperationLock, canonicalizeWorkspace, claimWorkspaceBinding, clearGlobalBinding, forgetMissingWorkspaceBinding, loadGlobalBinding, observeBoundWorkspace, requireBoundWorkspace, updateGlobalBindingState, } from "./binding/global-binding.js";
|
|
9
6
|
import { buildProductionBindings, importRawAdapter, } from "./binding/production-bindings.js";
|
|
10
7
|
import { AutoModuleCatalog, discoverModules } from "./discovery/discover.js";
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
8
|
+
import { InstalledModuleCatalog } from "./discovery/installed.js";
|
|
9
|
+
import { aggregateModuleDocs } from "./docs/aggregate.js";
|
|
13
10
|
import { PlatformError } from "./errors.js";
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import { preflightInstallerEnvironment } from "./install/environment.js";
|
|
17
|
-
import { generateInstallDoc } from "./install/install.js";
|
|
18
|
-
import { restartModules, startModules, statusModules, stopModules, } from "./lifecycle/dispatch.js";
|
|
19
|
-
import { buildManifest } from "./manifest/manifest.js";
|
|
20
|
-
import { writeDeploymentObserverSummary } from "./observer/deployment-summary.js";
|
|
11
|
+
import { observeWorkspaceInstalledVersion, removeWorkspacePackages, syncWorkspacePackages, } from "./install/package-manager.js";
|
|
12
|
+
import { observeStatuses, preflightAndStartModules, stopModulesThin, } from "./lifecycle/thin.js";
|
|
21
13
|
import { workspacePaths } from "./paths.js";
|
|
22
14
|
import { loadConfig } from "./persistence/config.js";
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
25
|
-
import { planDeployment } from "./planner/plan.js";
|
|
26
|
-
import { diagnoseRepair } from "./planner/repair.js";
|
|
27
|
-
import { runPreflight } from "./preflight/preflight.js";
|
|
28
|
-
import { discoverRegistryInstallClosure, discoverRegistryModules, loadRegistryModuleDescriptor, } from "./registry/index.js";
|
|
29
|
-
import { verifyModules } from "./verification/verify.js";
|
|
30
|
-
const MODULE_REF = platformCliDescriptor.moduleRef;
|
|
31
|
-
const MODULE_VERSION = platformCliDescriptor.moduleVersion;
|
|
15
|
+
import { ensureWorkspaceMetadata } from "./persistence/workspace-metadata.js";
|
|
16
|
+
import { discoverRegistryModules, PRO_FLOW_PACKAGE_PREFIX, } from "./registry/index.js";
|
|
32
17
|
const COMMANDS = [
|
|
33
|
-
"search",
|
|
34
18
|
"modules",
|
|
35
19
|
"docs",
|
|
36
20
|
"install",
|
|
37
21
|
"uninstall",
|
|
38
|
-
"upgrade",
|
|
39
|
-
"preflight",
|
|
40
|
-
"plan",
|
|
41
|
-
"apply",
|
|
42
22
|
"start",
|
|
43
23
|
"stop",
|
|
44
|
-
"restart",
|
|
45
|
-
"status",
|
|
46
|
-
"verify",
|
|
47
|
-
"doctor",
|
|
48
|
-
"manifest",
|
|
49
24
|
];
|
|
50
25
|
function parseArgs(argv) {
|
|
26
|
+
let json = false;
|
|
27
|
+
let workspace;
|
|
28
|
+
let special;
|
|
51
29
|
const positional = [];
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
positional,
|
|
59
|
-
};
|
|
60
|
-
for (let index = 1; index < argv.length; index += 1) {
|
|
61
|
-
const token = argv[index];
|
|
62
|
-
if (token === undefined)
|
|
30
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
31
|
+
const value = argv[index];
|
|
32
|
+
if (value === undefined)
|
|
33
|
+
continue;
|
|
34
|
+
if (value === "--json") {
|
|
35
|
+
json = true;
|
|
63
36
|
continue;
|
|
64
|
-
if (token === "--workspace") {
|
|
65
|
-
parsed.workspace = requiredOptionValue(argv, index, token);
|
|
66
|
-
index += 1;
|
|
67
|
-
}
|
|
68
|
-
else if (token === "--intent") {
|
|
69
|
-
parsed.intent = requiredOptionValue(argv, index, token);
|
|
70
|
-
index += 1;
|
|
71
37
|
}
|
|
72
|
-
|
|
73
|
-
|
|
38
|
+
if (value === "--workspace") {
|
|
39
|
+
const next = argv[index + 1];
|
|
40
|
+
if (!next || next.startsWith("-")) {
|
|
41
|
+
throw new PlatformError("INVALID_REQUEST", "--workspace requires a path");
|
|
42
|
+
}
|
|
43
|
+
workspace = next;
|
|
74
44
|
index += 1;
|
|
45
|
+
continue;
|
|
75
46
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
47
|
+
if (value === "--help" || value === "-h") {
|
|
48
|
+
if (special !== undefined && special !== "help")
|
|
49
|
+
throw new PlatformError("INVALID_REQUEST", "help and version flags cannot be combined");
|
|
50
|
+
special = "help";
|
|
51
|
+
continue;
|
|
81
52
|
}
|
|
82
|
-
|
|
83
|
-
|
|
53
|
+
if (value === "--version" || value === "-v") {
|
|
54
|
+
if (special !== undefined && special !== "version")
|
|
55
|
+
throw new PlatformError("INVALID_REQUEST", "help and version flags cannot be combined");
|
|
56
|
+
special = "version";
|
|
57
|
+
continue;
|
|
84
58
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
}
|
|
111
|
-
function
|
|
112
|
-
if (statuses.includes("FAILED"))
|
|
113
|
-
return "FAILED";
|
|
114
|
-
if (statuses.includes("BLOCKED"))
|
|
115
|
-
return "BLOCKED";
|
|
116
|
-
if (statuses.includes("ACTION_REQUIRED"))
|
|
117
|
-
return "ACTION_REQUIRED";
|
|
118
|
-
return "SUCCEEDED";
|
|
119
|
-
}
|
|
120
|
-
function failure(command, error) {
|
|
121
|
-
if (error instanceof PlatformError) {
|
|
122
|
-
return {
|
|
123
|
-
command,
|
|
124
|
-
status: "FAILED",
|
|
125
|
-
error: { code: error.code, message: error.message },
|
|
126
|
-
};
|
|
127
|
-
}
|
|
128
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
59
|
+
if (value.startsWith("-"))
|
|
60
|
+
throw new PlatformError("INVALID_REQUEST", `unknown option ${value}`);
|
|
61
|
+
positional.push(value);
|
|
62
|
+
}
|
|
63
|
+
if (special !== undefined) {
|
|
64
|
+
if (workspace !== undefined || positional.length > 0)
|
|
65
|
+
throw new PlatformError("INVALID_REQUEST", `${special} flag cannot be combined with a command or --workspace`);
|
|
66
|
+
return { command: special, json };
|
|
67
|
+
}
|
|
68
|
+
if (positional.length === 0) {
|
|
69
|
+
if (workspace !== undefined)
|
|
70
|
+
throw new PlatformError("INVALID_REQUEST", "--workspace is only valid with install");
|
|
71
|
+
return { command: "help", json };
|
|
72
|
+
}
|
|
73
|
+
if (positional.length !== 1)
|
|
74
|
+
throw new PlatformError("INVALID_REQUEST", "commands accept no positional arguments");
|
|
75
|
+
const raw = positional[0] ?? "";
|
|
76
|
+
if (!COMMANDS.includes(raw))
|
|
77
|
+
throw new PlatformError("INVALID_REQUEST", `unknown command ${raw}`);
|
|
78
|
+
const command = raw;
|
|
79
|
+
if (workspace !== undefined && command !== "install")
|
|
80
|
+
throw new PlatformError("INVALID_REQUEST", "--workspace is only valid with install");
|
|
81
|
+
return workspace === undefined
|
|
82
|
+
? { command, json }
|
|
83
|
+
: { command, workspace, json };
|
|
84
|
+
}
|
|
85
|
+
function outcome(command, status, workspaceRoot, data) {
|
|
129
86
|
return {
|
|
130
87
|
command,
|
|
131
|
-
status
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
}
|
|
135
|
-
function selectModules(modules, filter) {
|
|
136
|
-
if (filter === undefined)
|
|
137
|
-
return modules;
|
|
138
|
-
const selected = modules.filter((module) => module.moduleRef === filter || module.packageName === filter);
|
|
139
|
-
if (selected.length === 0) {
|
|
140
|
-
throw new PlatformError("INVALID_REQUEST", `module or package "${filter}" not found`);
|
|
141
|
-
}
|
|
142
|
-
return selected;
|
|
143
|
-
}
|
|
144
|
-
function moduleSourceOf(module) {
|
|
145
|
-
if (module.source.type === "registry") {
|
|
146
|
-
throw new PlatformError("DESCRIPTOR_INVALID", `registry bootstrap target ${module.packageName} has no local descriptor`);
|
|
147
|
-
}
|
|
148
|
-
const source = {
|
|
149
|
-
type: module.source.type,
|
|
150
|
-
packageName: module.packageName,
|
|
88
|
+
status,
|
|
89
|
+
...(workspaceRoot === undefined ? {} : { workspaceRoot }),
|
|
90
|
+
...(data === undefined ? {} : { data }),
|
|
151
91
|
};
|
|
152
|
-
if (module.source.path !== undefined)
|
|
153
|
-
source.path = module.source.path;
|
|
154
|
-
return source;
|
|
155
92
|
}
|
|
156
|
-
async function
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
93
|
+
async function canonicalWorkspace(cwd, explicit) {
|
|
94
|
+
const candidate = explicit === undefined ? cwd : resolve(cwd, explicit);
|
|
95
|
+
let info;
|
|
96
|
+
try {
|
|
97
|
+
info = await stat(candidate);
|
|
161
98
|
}
|
|
162
|
-
|
|
163
|
-
}
|
|
164
|
-
async function loadConfigFile(path) {
|
|
165
|
-
if (path === undefined)
|
|
166
|
-
return {};
|
|
167
|
-
const raw = await readFile(path, "utf8");
|
|
168
|
-
const value = JSON.parse(raw);
|
|
169
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
170
|
-
throw new PlatformError("INVALID_REQUEST", `config file ${path} is not an object`);
|
|
99
|
+
catch (error) {
|
|
100
|
+
throw new PlatformError("WORKSPACE_NOT_FOUND", `Workspace does not exist: ${candidate} (${error instanceof Error ? error.message : String(error)})`);
|
|
171
101
|
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
return {};
|
|
175
|
-
if (typeof modules !== "object" ||
|
|
176
|
-
modules === null ||
|
|
177
|
-
Array.isArray(modules)) {
|
|
178
|
-
throw new PlatformError("INVALID_REQUEST", `config file ${path} "modules" is not an object`);
|
|
102
|
+
if (!info.isDirectory()) {
|
|
103
|
+
throw new PlatformError("WORKSPACE_NOT_FOUND", `Workspace is not a directory: ${candidate}`);
|
|
179
104
|
}
|
|
180
|
-
|
|
181
|
-
for (const [ref, slots] of Object.entries(modules)) {
|
|
182
|
-
if (typeof slots !== "object" || slots === null || Array.isArray(slots))
|
|
183
|
-
continue;
|
|
184
|
-
const values = {};
|
|
185
|
-
for (const [key, value] of Object.entries(slots)) {
|
|
186
|
-
if (typeof value === "string")
|
|
187
|
-
values[key] = value;
|
|
188
|
-
}
|
|
189
|
-
result[ref] = values;
|
|
190
|
-
}
|
|
191
|
-
return result;
|
|
105
|
+
return realpath(candidate);
|
|
192
106
|
}
|
|
193
|
-
async function
|
|
107
|
+
async function loadConfigMap(root, modules) {
|
|
194
108
|
const paths = workspacePaths(root);
|
|
195
|
-
const catalog = await buildCatalogWithProductionBindings(root, paths);
|
|
196
|
-
return { catalog, paths };
|
|
197
|
-
}
|
|
198
|
-
/**
|
|
199
|
-
* The shipped CLI production composition root. Instead of constructing an empty
|
|
200
|
-
* `AutoModuleCatalog` (whose binding seam would otherwise be used only by tests),
|
|
201
|
-
* it discovers the governed modules, loads their materialized config, runs the
|
|
202
|
-
* production binding factory against each module's own deployment adapter, and
|
|
203
|
-
* passes the resulting real bindings into the catalog. Modules without a
|
|
204
|
-
* production factory stay unbound and fail closed when operated on.
|
|
205
|
-
*/
|
|
206
|
-
async function buildCatalogWithProductionBindings(root, paths) {
|
|
207
|
-
const modules = await discoverModules({ workspaceRoot: root });
|
|
208
109
|
const configByModuleRef = new Map();
|
|
209
110
|
for (const module of modules) {
|
|
210
111
|
const config = await loadConfig(paths, module.moduleRef);
|
|
211
112
|
if (config === undefined)
|
|
212
113
|
continue;
|
|
213
|
-
configByModuleRef.set(module.moduleRef,
|
|
214
|
-
...config.publicValues,
|
|
215
|
-
...config.secretValues,
|
|
216
|
-
});
|
|
114
|
+
configByModuleRef.set(module.moduleRef, config);
|
|
217
115
|
}
|
|
116
|
+
return configByModuleRef;
|
|
117
|
+
}
|
|
118
|
+
async function buildContext(root) {
|
|
119
|
+
const discovered = await discoverModules({ workspaceRoot: root });
|
|
120
|
+
const configByModuleRef = await loadConfigMap(root, discovered);
|
|
218
121
|
const bindings = await buildProductionBindings({
|
|
219
122
|
workspaceRoot: root,
|
|
220
|
-
modules,
|
|
123
|
+
modules: discovered,
|
|
221
124
|
configByModuleRef,
|
|
222
125
|
importAdapter: (packageName, source) => importRawAdapter(packageName, source, root),
|
|
223
126
|
});
|
|
224
|
-
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
const result = await discoverRegistryModules({
|
|
228
|
-
workspaceRoot: root,
|
|
229
|
-
...(args.positional[0] === undefined
|
|
230
|
-
? {}
|
|
231
|
-
: { packageName: args.positional[0] }),
|
|
232
|
-
});
|
|
233
|
-
if (args.positional[0] !== undefined && result.candidates.length === 0) {
|
|
234
|
-
return outcome("search", "BLOCKED", result);
|
|
235
|
-
}
|
|
236
|
-
return outcome("search", "SUCCEEDED", result);
|
|
127
|
+
const catalog = new AutoModuleCatalog(root, bindings);
|
|
128
|
+
const modules = await discoverModules({ workspaceRoot: root, catalog });
|
|
129
|
+
return { catalog, modules };
|
|
237
130
|
}
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
const selected = selectModules(modules, args.positional[0]);
|
|
241
|
-
const descriptors = await loadDescriptors(ctx.catalog, selected);
|
|
242
|
-
return outcome("modules", "SUCCEEDED", descriptors.map((descriptor) => ({
|
|
243
|
-
moduleRef: descriptor.moduleRef,
|
|
244
|
-
packageName: descriptor.packageName,
|
|
245
|
-
moduleVersion: descriptor.moduleVersion,
|
|
246
|
-
kind: descriptor.kind,
|
|
247
|
-
installClass: descriptor.installClass,
|
|
248
|
-
identity: descriptor.identity,
|
|
249
|
-
lifecycle: descriptor.lifecycle.supported,
|
|
250
|
-
documentation: descriptor.documentation,
|
|
251
|
-
})));
|
|
252
|
-
}
|
|
253
|
-
async function handleDocs(ctx, args) {
|
|
254
|
-
if (args.positional.length > 2) {
|
|
255
|
-
throw new PlatformError("INVALID_REQUEST", "docs accepts at most <moduleRef|packageName> [documentId]");
|
|
256
|
-
}
|
|
257
|
-
const modules = await discoverModules({ catalog: ctx.catalog });
|
|
258
|
-
const moduleFilter = args.positional[0];
|
|
259
|
-
const documentId = args.positional[1];
|
|
260
|
-
if (documentId !== undefined && moduleFilter === undefined) {
|
|
261
|
-
throw new PlatformError("INVALID_REQUEST", "docs document read requires a module");
|
|
262
|
-
}
|
|
263
|
-
const selected = selectModules(modules, moduleFilter);
|
|
264
|
-
const descriptors = await loadDescriptors(ctx.catalog, selected);
|
|
265
|
-
if (documentId !== undefined) {
|
|
266
|
-
const module = selected[0];
|
|
267
|
-
const descriptor = descriptors[0];
|
|
268
|
-
if (module === undefined || descriptor === undefined) {
|
|
269
|
-
throw new PlatformError("COMMAND_FAILED", "docs module selection invariant failed");
|
|
270
|
-
}
|
|
271
|
-
return outcome("docs", "SUCCEEDED", await readModuleDocument({
|
|
272
|
-
workspaceRoot: ctx.paths.root,
|
|
273
|
-
source: moduleSourceOf(module),
|
|
274
|
-
descriptor,
|
|
275
|
-
documentId,
|
|
276
|
-
}));
|
|
277
|
-
}
|
|
278
|
-
const views = await Promise.all(selected.map((module, index) => describeModule({
|
|
279
|
-
workspaceRoot: ctx.paths.root,
|
|
280
|
-
source: moduleSourceOf(module),
|
|
281
|
-
descriptor: descriptors[index] ??
|
|
282
|
-
(() => {
|
|
283
|
-
throw new Error(`Missing module descriptor for ${module.packageName}`);
|
|
284
|
-
})(),
|
|
285
|
-
})));
|
|
286
|
-
if (moduleFilter !== undefined) {
|
|
287
|
-
return outcome("docs", "SUCCEEDED", views[0]);
|
|
288
|
-
}
|
|
289
|
-
return outcome("docs", "SUCCEEDED", views.map((view) => ({
|
|
290
|
-
moduleRef: view.moduleRef,
|
|
291
|
-
packageName: view.packageName,
|
|
292
|
-
moduleVersion: view.moduleVersion,
|
|
293
|
-
kind: view.kind,
|
|
294
|
-
installClass: view.installClass,
|
|
295
|
-
identity: view.identity,
|
|
296
|
-
commands: view.commands,
|
|
297
|
-
publicApiEntries: view.publicApiEntries,
|
|
298
|
-
provides: view.provides,
|
|
299
|
-
requires: view.requires,
|
|
300
|
-
lifecycle: view.lifecycle,
|
|
301
|
-
documentation: view.documentation,
|
|
302
|
-
})));
|
|
303
|
-
}
|
|
304
|
-
async function handleInstallerPreflight(root) {
|
|
305
|
-
const result = await preflightInstallerEnvironment({ workspaceRoot: root });
|
|
306
|
-
const status = result.status === "READY"
|
|
131
|
+
function statusFromModule(value) {
|
|
132
|
+
return value === "SUCCEEDED"
|
|
307
133
|
? "SUCCEEDED"
|
|
308
|
-
:
|
|
134
|
+
: value === "ACTION_REQUIRED"
|
|
309
135
|
? "ACTION_REQUIRED"
|
|
310
|
-
: "BLOCKED"
|
|
311
|
-
return outcome("preflight", status, result);
|
|
312
|
-
}
|
|
313
|
-
async function loadEffectiveConfig(paths, modules, configFile) {
|
|
314
|
-
return mergeEffectiveConfig(paths, modules, await loadConfigFile(configFile));
|
|
315
|
-
}
|
|
316
|
-
async function handlePreflight(ctx, args) {
|
|
317
|
-
const modules = await discoverModules({ catalog: ctx.catalog });
|
|
318
|
-
const selected = selectModules(modules, args.positional[0]);
|
|
319
|
-
const config = await loadEffectiveConfig(ctx.paths, selected, args.configFile);
|
|
320
|
-
const result = await runPreflight(selected, {
|
|
321
|
-
config,
|
|
322
|
-
catalog: ctx.catalog,
|
|
323
|
-
paths: ctx.paths,
|
|
324
|
-
});
|
|
325
|
-
const status = result.status === "READY"
|
|
326
|
-
? "SUCCEEDED"
|
|
327
|
-
: result.status === "ACTION_REQUIRED"
|
|
328
|
-
? "ACTION_REQUIRED"
|
|
329
|
-
: "BLOCKED";
|
|
330
|
-
return outcome("preflight", status, result);
|
|
331
|
-
}
|
|
332
|
-
async function handlePlan(ctx, args) {
|
|
333
|
-
const intent = args.intent;
|
|
334
|
-
if (intent !== "install" &&
|
|
335
|
-
intent !== "configure" &&
|
|
336
|
-
intent !== "upgrade" &&
|
|
337
|
-
intent !== "uninstall" &&
|
|
338
|
-
intent !== "repair") {
|
|
339
|
-
throw new PlatformError("INVALID_REQUEST", "plan requires --intent install|configure|upgrade|uninstall|repair");
|
|
340
|
-
}
|
|
341
|
-
if (intent === "install") {
|
|
342
|
-
const installer = await preflightInstallerEnvironment({
|
|
343
|
-
workspaceRoot: ctx.paths.root,
|
|
344
|
-
});
|
|
345
|
-
if (installer.status !== "READY") {
|
|
346
|
-
return outcome("plan", installer.status === "ACTION_REQUIRED" ? "ACTION_REQUIRED" : "BLOCKED", installer);
|
|
347
|
-
}
|
|
348
|
-
const registry = args.positional[0] === undefined
|
|
349
|
-
? await discoverRegistryModules({ workspaceRoot: ctx.paths.root })
|
|
350
|
-
: await discoverRegistryInstallClosure({
|
|
351
|
-
workspaceRoot: ctx.paths.root,
|
|
352
|
-
packageName: args.positional[0],
|
|
353
|
-
});
|
|
354
|
-
const bootstrapModules = selectBootstrapModules(registry.candidates, args.positional[0]);
|
|
355
|
-
const plan = planDeployment({
|
|
356
|
-
intent: "install",
|
|
357
|
-
modules: bootstrapModules,
|
|
358
|
-
});
|
|
359
|
-
await savePlan(ctx.paths, plan);
|
|
360
|
-
return outcome("plan", "SUCCEEDED", {
|
|
361
|
-
planRef: plan.planRef,
|
|
362
|
-
registry: registry.registry,
|
|
363
|
-
plan,
|
|
364
|
-
});
|
|
365
|
-
}
|
|
366
|
-
const modules = await discoverModules({ catalog: ctx.catalog });
|
|
367
|
-
const selected = selectModules(modules, args.positional[0]);
|
|
368
|
-
const config = args.intent === "upgrade"
|
|
369
|
-
? await loadEffectiveConfig(ctx.paths, selected, args.configFile)
|
|
370
|
-
: await loadConfigFile(args.configFile);
|
|
371
|
-
if (intent === "uninstall") {
|
|
372
|
-
if (args.positional[0] === undefined) {
|
|
373
|
-
throw new PlatformError("INVALID_REQUEST", "uninstall plan requires <moduleRef|packageName>");
|
|
374
|
-
}
|
|
375
|
-
if (selected.every((module) => module.installClass !== "core")) {
|
|
376
|
-
const removing = new Set(selected.map((module) => module.moduleRef));
|
|
377
|
-
const remaining = modules.filter((module) => !removing.has(module.moduleRef));
|
|
378
|
-
// Rebuilding the remaining graph is the uninstall dependency gate: if a
|
|
379
|
-
// required contract or moduleRef binding would disappear, graph
|
|
380
|
-
// construction fails closed before any removal plan is persisted.
|
|
381
|
-
buildDependencyGraph(remaining);
|
|
382
|
-
}
|
|
383
|
-
const plan = planDeployment({ intent, modules: selected, config });
|
|
384
|
-
await savePlan(ctx.paths, plan);
|
|
385
|
-
return outcome("plan", "SUCCEEDED", { planRef: plan.planRef, plan });
|
|
386
|
-
}
|
|
387
|
-
if (intent === "upgrade") {
|
|
388
|
-
const installer = await preflightInstallerEnvironment({
|
|
389
|
-
workspaceRoot: ctx.paths.root,
|
|
390
|
-
});
|
|
391
|
-
if (installer.status !== "READY") {
|
|
392
|
-
return outcome("plan", installer.status === "ACTION_REQUIRED" ? "ACTION_REQUIRED" : "BLOCKED", installer);
|
|
393
|
-
}
|
|
394
|
-
const currentDescriptors = await loadDescriptors(ctx.catalog, modules);
|
|
395
|
-
const currentByRef = new Map(currentDescriptors.map((descriptor) => [
|
|
396
|
-
descriptor.moduleRef,
|
|
397
|
-
descriptor,
|
|
398
|
-
]));
|
|
399
|
-
const targetByRef = new Map(currentByRef);
|
|
400
|
-
const targets = [];
|
|
401
|
-
let registryUrl;
|
|
402
|
-
for (const module of selected) {
|
|
403
|
-
const discovered = await discoverRegistryModules({
|
|
404
|
-
workspaceRoot: ctx.paths.root,
|
|
405
|
-
packageName: module.packageName,
|
|
406
|
-
});
|
|
407
|
-
registryUrl ??= discovered.registry;
|
|
408
|
-
const candidate = discovered.candidates[0];
|
|
409
|
-
if (candidate === undefined) {
|
|
410
|
-
throw new PlatformError("PACKAGE_NOT_FOUND", `no installable Registry target found for managed package ${module.packageName}`);
|
|
411
|
-
}
|
|
412
|
-
targets.push({
|
|
413
|
-
moduleRef: module.moduleRef,
|
|
414
|
-
targetVersion: candidate.moduleVersion,
|
|
415
|
-
});
|
|
416
|
-
if (candidate.moduleVersion === module.moduleVersion)
|
|
417
|
-
continue;
|
|
418
|
-
const targetDescriptor = await loadRegistryModuleDescriptor({
|
|
419
|
-
workspaceRoot: ctx.paths.root,
|
|
420
|
-
candidate,
|
|
421
|
-
});
|
|
422
|
-
if (targetDescriptor.moduleRef !== module.moduleRef) {
|
|
423
|
-
throw new PlatformError("REGISTRY_RESPONSE_INVALID", `Registry target ${module.packageName}@${candidate.moduleVersion} changed moduleRef from ${module.moduleRef} to ${targetDescriptor.moduleRef}`);
|
|
424
|
-
}
|
|
425
|
-
targetByRef.set(module.moduleRef, targetDescriptor);
|
|
426
|
-
}
|
|
427
|
-
const targetDescriptors = modules.map((module) => {
|
|
428
|
-
const descriptor = targetByRef.get(module.moduleRef);
|
|
429
|
-
if (descriptor === undefined) {
|
|
430
|
-
throw new PlatformError("COMMAND_FAILED", `upgrade target descriptor invariant failed for ${module.moduleRef}`);
|
|
431
|
-
}
|
|
432
|
-
return descriptor;
|
|
433
|
-
});
|
|
434
|
-
const plan = planDeployment({
|
|
435
|
-
intent,
|
|
436
|
-
currentDescriptors,
|
|
437
|
-
targetDescriptors,
|
|
438
|
-
targets,
|
|
439
|
-
config,
|
|
440
|
-
});
|
|
441
|
-
await savePlan(ctx.paths, plan);
|
|
442
|
-
return outcome("plan", "SUCCEEDED", {
|
|
443
|
-
planRef: plan.planRef,
|
|
444
|
-
registry: registryUrl,
|
|
445
|
-
plan,
|
|
446
|
-
});
|
|
447
|
-
}
|
|
448
|
-
if (intent === "configure") {
|
|
449
|
-
const plan = planDeployment({ intent, modules: selected, config });
|
|
450
|
-
await savePlan(ctx.paths, plan);
|
|
451
|
-
return outcome("plan", "SUCCEEDED", { planRef: plan.planRef, plan });
|
|
452
|
-
}
|
|
453
|
-
const preflight = await runPreflight(selected, {
|
|
454
|
-
config,
|
|
455
|
-
catalog: ctx.catalog,
|
|
456
|
-
paths: ctx.paths,
|
|
457
|
-
});
|
|
458
|
-
if (preflight.status === "NOT_READY" ||
|
|
459
|
-
preflight.status === "ACTION_REQUIRED") {
|
|
460
|
-
return outcome("plan", preflight.status === "ACTION_REQUIRED" ? "ACTION_REQUIRED" : "BLOCKED", preflight);
|
|
461
|
-
}
|
|
462
|
-
let input;
|
|
463
|
-
if (intent === "repair") {
|
|
464
|
-
const diagnosis = diagnoseRepair(await doctorModules(ctx.catalog, selected));
|
|
465
|
-
if (diagnosis.blocked.length > 0) {
|
|
466
|
-
return outcome("plan", "BLOCKED", diagnosis);
|
|
467
|
-
}
|
|
468
|
-
input = { intent, modules: selected, facts: diagnosis.facts, config };
|
|
469
|
-
}
|
|
470
|
-
else {
|
|
471
|
-
input = { intent, modules: selected, config };
|
|
472
|
-
}
|
|
473
|
-
const plan = planDeployment(input);
|
|
474
|
-
await savePlan(ctx.paths, plan);
|
|
475
|
-
return outcome("plan", "SUCCEEDED", { planRef: plan.planRef, plan });
|
|
476
|
-
}
|
|
477
|
-
async function handleApply(ctx, args) {
|
|
478
|
-
const planRef = args.positional[0];
|
|
479
|
-
if (planRef === undefined) {
|
|
480
|
-
throw new PlatformError("INVALID_REQUEST", "apply requires <planRef>");
|
|
481
|
-
}
|
|
482
|
-
const plan = await loadPlan(ctx.paths, planRef);
|
|
483
|
-
if (plan === undefined) {
|
|
484
|
-
throw new PlatformError("PLAN_NOT_FOUND", `plan ${planRef} not found`);
|
|
485
|
-
}
|
|
486
|
-
const current = await rebuildCurrentAssumptions(ctx.catalog, plan, ctx.paths);
|
|
487
|
-
const result = await applyPlan({
|
|
488
|
-
paths: ctx.paths,
|
|
489
|
-
planRef,
|
|
490
|
-
catalog: ctx.catalog,
|
|
491
|
-
current,
|
|
492
|
-
driver: createWorkspacePackageManagerDriver({
|
|
493
|
-
workspaceRoot: ctx.paths.root,
|
|
494
|
-
}),
|
|
495
|
-
...(plan.intent === "upgrade"
|
|
496
|
-
? {
|
|
497
|
-
refreshCatalog: async () => (await buildContext(ctx.paths.root)).catalog,
|
|
498
|
-
}
|
|
499
|
-
: {}),
|
|
500
|
-
});
|
|
501
|
-
const status = result.outcome === "COMPLETE"
|
|
502
|
-
? "SUCCEEDED"
|
|
503
|
-
: result.outcome === "ACTION_REQUIRED"
|
|
504
|
-
? "ACTION_REQUIRED"
|
|
505
|
-
: result.outcome === "BLOCKED"
|
|
136
|
+
: value === "BLOCKED"
|
|
506
137
|
? "BLOCKED"
|
|
507
138
|
: "FAILED";
|
|
508
|
-
return outcome("apply", status, result);
|
|
509
139
|
}
|
|
510
|
-
function
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
const
|
|
529
|
-
if (
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
});
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
|
-
}
|
|
544
|
-
const planRef = planRefFromOutcome(planned);
|
|
545
|
-
const applied = await handleApply(ctx, {
|
|
546
|
-
...args,
|
|
547
|
-
command: "apply",
|
|
548
|
-
intent: undefined,
|
|
549
|
-
positional: [planRef],
|
|
550
|
-
});
|
|
551
|
-
if (applied.status !== "SUCCEEDED") {
|
|
552
|
-
return outcome(intent, applied.status, {
|
|
553
|
-
plan: planned.data,
|
|
554
|
-
apply: applied.data,
|
|
555
|
-
});
|
|
556
|
-
}
|
|
557
|
-
// Package mutations change Workspace package reality. Rebuild the catalog
|
|
558
|
-
// before reporting the managed set so the result can never be sourced from
|
|
559
|
-
// the pre-mutation catalog.
|
|
560
|
-
const refreshed = await buildContext(ctx.paths.root);
|
|
561
|
-
const managed = await handleModules(refreshed, {
|
|
562
|
-
...args,
|
|
563
|
-
command: "modules",
|
|
564
|
-
intent: undefined,
|
|
565
|
-
positional: [],
|
|
566
|
-
});
|
|
567
|
-
return outcome(intent, "SUCCEEDED", {
|
|
568
|
-
plan: planned.data,
|
|
569
|
-
apply: applied.data,
|
|
570
|
-
managedModules: managed.data,
|
|
140
|
+
function failedLifecycleStatus(results) {
|
|
141
|
+
const last = results.at(-1);
|
|
142
|
+
return last === undefined
|
|
143
|
+
? "SUCCEEDED"
|
|
144
|
+
: statusFromModule(last.result.status);
|
|
145
|
+
}
|
|
146
|
+
async function handleModules(root) {
|
|
147
|
+
const { catalog, modules } = await buildContext(root);
|
|
148
|
+
const observed = await observeStatuses(catalog, modules);
|
|
149
|
+
const byRef = new Map(modules.map((module) => [module.moduleRef, module]));
|
|
150
|
+
const output = observed.map((item) => {
|
|
151
|
+
if (item.result.status !== "SUCCEEDED") {
|
|
152
|
+
throw new PlatformError("COMMAND_FAILED", `module ${item.moduleRef} status observation did not return SUCCEEDED`);
|
|
153
|
+
}
|
|
154
|
+
const parsed = moduleStatusObservationSchema.safeParse(item.result.data);
|
|
155
|
+
if (!parsed.success) {
|
|
156
|
+
throw new PlatformError("COMMAND_FAILED", `module ${item.moduleRef} returned an invalid status observation: ${parsed.error.message}`);
|
|
157
|
+
}
|
|
158
|
+
const module = byRef.get(item.moduleRef);
|
|
159
|
+
if (module === undefined)
|
|
160
|
+
throw new PlatformError("COMMAND_FAILED", `unknown module ${item.moduleRef}`);
|
|
161
|
+
return {
|
|
162
|
+
moduleRef: item.moduleRef,
|
|
163
|
+
version: module.moduleVersion,
|
|
164
|
+
configStatus: parsed.data.configStatus,
|
|
165
|
+
...(parsed.data.missingConfig === undefined
|
|
166
|
+
? {}
|
|
167
|
+
: { missingConfig: parsed.data.missingConfig }),
|
|
168
|
+
runtimeStatus: parsed.data.runtimeStatus,
|
|
169
|
+
};
|
|
571
170
|
});
|
|
171
|
+
return outcome("modules", "SUCCEEDED", root, { modules: output });
|
|
572
172
|
}
|
|
573
|
-
async function
|
|
574
|
-
const
|
|
575
|
-
|
|
173
|
+
async function handleDocs(root) {
|
|
174
|
+
const catalog = new AutoModuleCatalog(root);
|
|
175
|
+
const modules = await discoverModules({ workspaceRoot: root, catalog });
|
|
176
|
+
const docs = await aggregateModuleDocs(root, catalog, modules);
|
|
177
|
+
return outcome("docs", "SUCCEEDED", root, { modules: docs });
|
|
576
178
|
}
|
|
577
|
-
async function
|
|
578
|
-
const
|
|
579
|
-
const
|
|
580
|
-
if (
|
|
581
|
-
|
|
582
|
-
throw new PlatformError("WORKSPACE_ALREADY_BOUND", `ProFlow is already bound to ${current.workspaceRealPath}; uninstall it before planning an install for ${requestedWorkspace}`);
|
|
179
|
+
async function validateInstalledPackageSet(root, candidates, previousManaged) {
|
|
180
|
+
const expectedNames = candidates.map((item) => item.packageName).sort();
|
|
181
|
+
const declaredNames = await workspaceProFlowDependencies(root);
|
|
182
|
+
if (JSON.stringify(declaredNames) !== JSON.stringify(expectedNames)) {
|
|
183
|
+
throw new PlatformError("COMMAND_FAILED", `managed dependency set mismatch: expected ${expectedNames.join(", ")}, observed ${declaredNames.join(", ")}`);
|
|
583
184
|
}
|
|
584
|
-
const
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
}
|
|
588
|
-
async function handleApplyWithGlobalBinding(args, runtime) {
|
|
589
|
-
const planRef = args.positional[0];
|
|
590
|
-
if (planRef === undefined) {
|
|
591
|
-
throw new PlatformError("INVALID_REQUEST", "apply requires <planRef>");
|
|
592
|
-
}
|
|
593
|
-
const operationLock = await acquireGlobalOperationLock(runtime.globalRoot);
|
|
594
|
-
let binding = await loadGlobalBinding(runtime.globalRoot);
|
|
595
|
-
try {
|
|
596
|
-
let ctx;
|
|
597
|
-
if (binding === undefined) {
|
|
598
|
-
const requestedWorkspace = await resolveRequestedInstallWorkspace(args, runtime);
|
|
599
|
-
ctx = await buildContext(requestedWorkspace);
|
|
600
|
-
const plan = await loadPlan(ctx.paths, planRef);
|
|
601
|
-
if (plan === undefined) {
|
|
602
|
-
throw new PlatformError("PLAN_NOT_FOUND", `plan ${planRef} not found`);
|
|
603
|
-
}
|
|
604
|
-
if (plan.intent !== "install") {
|
|
605
|
-
throw new PlatformError("WORKSPACE_NOT_BOUND", "only an install plan may establish the first global Workspace binding");
|
|
606
|
-
}
|
|
607
|
-
binding = (await claimWorkspaceBinding({
|
|
608
|
-
workspace: requestedWorkspace,
|
|
609
|
-
globalRoot: runtime.globalRoot,
|
|
610
|
-
})).binding;
|
|
611
|
-
}
|
|
612
|
-
else {
|
|
613
|
-
if (args.workspace !== undefined) {
|
|
614
|
-
const requested = await canonicalizeWorkspace(args.workspace);
|
|
615
|
-
if (requested.workspaceRealPath !== binding.workspaceRealPath) {
|
|
616
|
-
throw new PlatformError("WORKSPACE_ALREADY_BOUND", `command targets ${requested.workspaceRealPath}, but the global Platform Instance is bound to ${binding.workspaceRealPath}`);
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
ctx = await buildContext(binding.workspaceRealPath);
|
|
620
|
-
}
|
|
621
|
-
const plan = await loadPlan(ctx.paths, planRef);
|
|
622
|
-
if (plan === undefined) {
|
|
623
|
-
throw new PlatformError("PLAN_NOT_FOUND", `plan ${planRef} not found`);
|
|
624
|
-
}
|
|
625
|
-
const controlsPlatformState = plan.intent === "install" && binding.state !== "INSTALLED";
|
|
626
|
-
if (binding.state === "UNINSTALLING") {
|
|
627
|
-
throw new PlatformError("GLOBAL_OPERATION_LOCKED", "the bound Platform Instance is marked UNINSTALLING; finish recovery before apply");
|
|
628
|
-
}
|
|
629
|
-
if (controlsPlatformState && binding.state !== "INSTALLING") {
|
|
630
|
-
binding = await updateGlobalBindingState({
|
|
631
|
-
workspaceInstanceId: binding.workspaceInstanceId,
|
|
632
|
-
state: "INSTALLING",
|
|
633
|
-
globalRoot: runtime.globalRoot,
|
|
634
|
-
});
|
|
635
|
-
}
|
|
636
|
-
try {
|
|
637
|
-
const result = await handleApply(ctx, args);
|
|
638
|
-
if (controlsPlatformState) {
|
|
639
|
-
if (result.status === "SUCCEEDED") {
|
|
640
|
-
binding = await updateGlobalBindingState({
|
|
641
|
-
workspaceInstanceId: binding.workspaceInstanceId,
|
|
642
|
-
state: "INSTALLED",
|
|
643
|
-
globalRoot: runtime.globalRoot,
|
|
644
|
-
});
|
|
645
|
-
}
|
|
646
|
-
else if (result.status === "FAILED" || result.status === "BLOCKED") {
|
|
647
|
-
binding = await updateGlobalBindingState({
|
|
648
|
-
workspaceInstanceId: binding.workspaceInstanceId,
|
|
649
|
-
state: "BROKEN",
|
|
650
|
-
globalRoot: runtime.globalRoot,
|
|
651
|
-
failure: {
|
|
652
|
-
code: result.error?.code ?? result.status,
|
|
653
|
-
message: result.error?.message ??
|
|
654
|
-
"install apply did not reach a successful postcondition",
|
|
655
|
-
},
|
|
656
|
-
});
|
|
657
|
-
}
|
|
658
|
-
}
|
|
659
|
-
return withWorkspace(result, binding);
|
|
660
|
-
}
|
|
661
|
-
catch (error) {
|
|
662
|
-
if (controlsPlatformState) {
|
|
663
|
-
binding = await updateGlobalBindingState({
|
|
664
|
-
workspaceInstanceId: binding.workspaceInstanceId,
|
|
665
|
-
state: "BROKEN",
|
|
666
|
-
globalRoot: runtime.globalRoot,
|
|
667
|
-
failure: {
|
|
668
|
-
code: error instanceof PlatformError ? error.code : "COMMAND_FAILED",
|
|
669
|
-
message: error instanceof Error ? error.message : String(error),
|
|
670
|
-
},
|
|
671
|
-
});
|
|
672
|
-
}
|
|
673
|
-
throw error;
|
|
185
|
+
for (const candidate of candidates) {
|
|
186
|
+
const observed = await observeWorkspaceInstalledVersion(root, candidate.packageName);
|
|
187
|
+
if (observed !== candidate.moduleVersion) {
|
|
188
|
+
throw new PlatformError("COMMAND_FAILED", `installed version mismatch for ${candidate.packageName}: expected ${candidate.moduleVersion}, observed ${observed ?? "missing"}`);
|
|
674
189
|
}
|
|
675
190
|
}
|
|
676
|
-
|
|
677
|
-
await
|
|
678
|
-
|
|
679
|
-
}
|
|
680
|
-
async function handleInstallWithGlobalBinding(args, runtime) {
|
|
681
|
-
const operationLock = await acquireGlobalOperationLock(runtime.globalRoot);
|
|
682
|
-
try {
|
|
683
|
-
const requestedWorkspace = await resolveRequestedInstallWorkspace(args, runtime);
|
|
684
|
-
const existing = await loadGlobalBinding(runtime.globalRoot);
|
|
685
|
-
if (existing !== undefined &&
|
|
686
|
-
existing.workspaceRealPath !== requestedWorkspace) {
|
|
687
|
-
throw new PlatformError("WORKSPACE_ALREADY_BOUND", `ProFlow is already bound to ${existing.workspaceRealPath}; uninstall it before installing ${requestedWorkspace}`);
|
|
688
|
-
}
|
|
689
|
-
if (existing?.state === "INSTALLED" && args.positional[0] === undefined) {
|
|
690
|
-
return withWorkspace(outcome("install", "SUCCEEDED", {
|
|
691
|
-
alreadyInstalled: true,
|
|
692
|
-
changed: false,
|
|
693
|
-
boundWorkspace: existing.workspaceRealPath,
|
|
694
|
-
}), existing);
|
|
191
|
+
for (const stale of previousManaged.filter((name) => !expectedNames.includes(name))) {
|
|
192
|
+
if ((await observeWorkspaceInstalledVersion(root, stale)) !== undefined) {
|
|
193
|
+
throw new PlatformError("COMMAND_FAILED", `stale managed package remains installed after synchronization: ${stale}`);
|
|
695
194
|
}
|
|
696
|
-
if (existing?.state === "UNINSTALLING") {
|
|
697
|
-
throw new PlatformError("GLOBAL_OPERATION_LOCKED", "the bound Platform Instance is marked UNINSTALLING; finish recovery before installing");
|
|
698
|
-
}
|
|
699
|
-
// Gate A runs before claiming a fresh global binding. Deterministic local
|
|
700
|
-
// failures (package-manager conflict, missing executable, registry down,
|
|
701
|
-
// unwritable Workspace) therefore do not leave a fake BROKEN installation.
|
|
702
|
-
// The global operation lock keeps this preflight + claim sequence atomic
|
|
703
|
-
// against other Platform mutations.
|
|
704
|
-
const installer = await preflightInstallerEnvironment({
|
|
705
|
-
workspaceRoot: requestedWorkspace,
|
|
706
|
-
});
|
|
707
|
-
if (installer.status !== "READY") {
|
|
708
|
-
return outcome("install", installer.status === "ACTION_REQUIRED" ? "ACTION_REQUIRED" : "BLOCKED", { requestedWorkspace, preflight: installer });
|
|
709
|
-
}
|
|
710
|
-
const claimed = await claimWorkspaceBinding({
|
|
711
|
-
workspace: requestedWorkspace,
|
|
712
|
-
globalRoot: runtime.globalRoot,
|
|
713
|
-
});
|
|
714
|
-
const initialBinding = claimed.binding;
|
|
715
|
-
let activeBinding = initialBinding;
|
|
716
|
-
const controlsPlatformState = !claimed.alreadyBound || initialBinding.state !== "INSTALLED";
|
|
717
|
-
if (controlsPlatformState && initialBinding.state !== "INSTALLING") {
|
|
718
|
-
activeBinding = await updateGlobalBindingState({
|
|
719
|
-
workspaceInstanceId: initialBinding.workspaceInstanceId,
|
|
720
|
-
state: "INSTALLING",
|
|
721
|
-
globalRoot: runtime.globalRoot,
|
|
722
|
-
});
|
|
723
|
-
}
|
|
724
|
-
try {
|
|
725
|
-
const ctx = await buildContext(activeBinding.workspaceRealPath);
|
|
726
|
-
const result = await handleManagedMutation(ctx, args, "install");
|
|
727
|
-
if (controlsPlatformState) {
|
|
728
|
-
if (result.status === "SUCCEEDED") {
|
|
729
|
-
activeBinding = await updateGlobalBindingState({
|
|
730
|
-
workspaceInstanceId: activeBinding.workspaceInstanceId,
|
|
731
|
-
state: "INSTALLED",
|
|
732
|
-
globalRoot: runtime.globalRoot,
|
|
733
|
-
});
|
|
734
|
-
}
|
|
735
|
-
else if (result.status === "FAILED" || result.status === "BLOCKED") {
|
|
736
|
-
activeBinding = await updateGlobalBindingState({
|
|
737
|
-
workspaceInstanceId: activeBinding.workspaceInstanceId,
|
|
738
|
-
state: "BROKEN",
|
|
739
|
-
globalRoot: runtime.globalRoot,
|
|
740
|
-
failure: {
|
|
741
|
-
code: result.error?.code ?? result.status,
|
|
742
|
-
message: result.error?.message ??
|
|
743
|
-
"Platform install did not reach a successful postcondition",
|
|
744
|
-
},
|
|
745
|
-
});
|
|
746
|
-
}
|
|
747
|
-
}
|
|
748
|
-
return withWorkspace(result, activeBinding);
|
|
749
|
-
}
|
|
750
|
-
catch (error) {
|
|
751
|
-
if (controlsPlatformState) {
|
|
752
|
-
activeBinding = await updateGlobalBindingState({
|
|
753
|
-
workspaceInstanceId: activeBinding.workspaceInstanceId,
|
|
754
|
-
state: "BROKEN",
|
|
755
|
-
globalRoot: runtime.globalRoot,
|
|
756
|
-
failure: {
|
|
757
|
-
code: error instanceof PlatformError ? error.code : "COMMAND_FAILED",
|
|
758
|
-
message: error instanceof Error ? error.message : String(error),
|
|
759
|
-
},
|
|
760
|
-
});
|
|
761
|
-
}
|
|
762
|
-
throw error;
|
|
763
|
-
}
|
|
764
|
-
}
|
|
765
|
-
finally {
|
|
766
|
-
await operationLock.release();
|
|
767
195
|
}
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
const
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
196
|
+
const catalog = new InstalledModuleCatalog(root);
|
|
197
|
+
const sources = await catalog.sources();
|
|
198
|
+
const modules = await discoverModules({ catalog, sources });
|
|
199
|
+
const byPackage = new Map(modules.map((module) => [module.packageName, module]));
|
|
200
|
+
for (const candidate of candidates) {
|
|
201
|
+
const module = byPackage.get(candidate.packageName);
|
|
202
|
+
if (module === undefined ||
|
|
203
|
+
module.moduleVersion !== candidate.moduleVersion) {
|
|
204
|
+
throw new PlatformError("DESCRIPTOR_INVALID", `installed descriptor mismatch for ${candidate.packageName}@${candidate.moduleVersion}`);
|
|
775
205
|
}
|
|
776
206
|
}
|
|
777
|
-
return {
|
|
778
|
-
ctx: await buildContext(binding.workspaceRealPath),
|
|
779
|
-
binding,
|
|
780
|
-
};
|
|
781
207
|
}
|
|
782
|
-
async function
|
|
783
|
-
const
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
result = await handleManagedMutation(ctx, args, args.command);
|
|
792
|
-
}
|
|
793
|
-
else {
|
|
794
|
-
result = await handleLifecycle(ctx, args, args.command);
|
|
795
|
-
}
|
|
796
|
-
return withWorkspace(result, binding);
|
|
208
|
+
async function handleInstall(root, runtime) {
|
|
209
|
+
const discovered = await discoverRegistryModules({
|
|
210
|
+
workspaceRoot: root,
|
|
211
|
+
...(runtime.registryRunner === undefined
|
|
212
|
+
? {}
|
|
213
|
+
: { runner: runtime.registryRunner }),
|
|
214
|
+
});
|
|
215
|
+
if (discovered.rejected.length > 0) {
|
|
216
|
+
throw new PlatformError("REGISTRY_RESPONSE_INVALID", `registry contains rejected ProFlow packages: ${discovered.rejected.map((item) => `${item.packageName}:${item.reason}`).join(", ")}`);
|
|
797
217
|
}
|
|
798
|
-
|
|
799
|
-
|
|
218
|
+
if (discovered.candidates.length === 0) {
|
|
219
|
+
throw new PlatformError("PACKAGE_NOT_FOUND", "no ProFlow packages were discovered in the configured scope");
|
|
800
220
|
}
|
|
221
|
+
const previousManaged = await workspaceProFlowDependencies(root);
|
|
222
|
+
const mutation = await syncWorkspacePackages({
|
|
223
|
+
workspaceRoot: root,
|
|
224
|
+
packages: discovered.candidates.map((item) => ({
|
|
225
|
+
packageName: item.packageName,
|
|
226
|
+
version: item.moduleVersion,
|
|
227
|
+
})),
|
|
228
|
+
...(runtime.packageRunner === undefined
|
|
229
|
+
? {}
|
|
230
|
+
: { runner: runtime.packageRunner }),
|
|
231
|
+
...(runtime.executableAvailable === undefined
|
|
232
|
+
? {}
|
|
233
|
+
: { executableAvailable: runtime.executableAvailable }),
|
|
234
|
+
});
|
|
235
|
+
await validateInstalledPackageSet(root, discovered.candidates, previousManaged);
|
|
236
|
+
const metadata = await ensureWorkspaceMetadata(root);
|
|
237
|
+
return outcome("install", "SUCCEEDED", root, {
|
|
238
|
+
registry: discovered.registry,
|
|
239
|
+
packageManager: mutation.packageManager,
|
|
240
|
+
packages: discovered.candidates.map((item) => ({
|
|
241
|
+
packageName: item.packageName,
|
|
242
|
+
version: item.moduleVersion,
|
|
243
|
+
})),
|
|
244
|
+
workspace: metadata,
|
|
245
|
+
next: "platform modules",
|
|
246
|
+
});
|
|
801
247
|
}
|
|
802
|
-
async function
|
|
803
|
-
const operationLock = await acquireGlobalOperationLock(runtime.globalRoot);
|
|
804
|
-
let binding;
|
|
248
|
+
async function workspaceProFlowDependencies(root) {
|
|
805
249
|
try {
|
|
806
|
-
|
|
807
|
-
if (
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
const ctx = await buildContext(binding.workspaceRealPath);
|
|
819
|
-
const modules = await discoverModules({ catalog: ctx.catalog });
|
|
820
|
-
let removedModules = [];
|
|
821
|
-
if (modules.length > 0) {
|
|
822
|
-
const plan = planDeployment({
|
|
823
|
-
intent: "uninstall",
|
|
824
|
-
modules,
|
|
825
|
-
uninstallScope: "platform-instance",
|
|
826
|
-
});
|
|
827
|
-
await savePlan(ctx.paths, plan);
|
|
828
|
-
const current = await rebuildCurrentAssumptions(ctx.catalog, plan, ctx.paths);
|
|
829
|
-
const applied = await applyPlan({
|
|
830
|
-
paths: ctx.paths,
|
|
831
|
-
planRef: plan.planRef,
|
|
832
|
-
catalog: ctx.catalog,
|
|
833
|
-
current,
|
|
834
|
-
driver: createWorkspacePackageManagerDriver({
|
|
835
|
-
workspaceRoot: ctx.paths.root,
|
|
836
|
-
}),
|
|
837
|
-
});
|
|
838
|
-
if (applied.outcome !== "COMPLETE") {
|
|
839
|
-
throw new PlatformError("UNINSTALL_FAILED", `Platform Instance uninstall stopped with ${applied.outcome}`);
|
|
840
|
-
}
|
|
841
|
-
removedModules = modules.map((module) => module.moduleRef);
|
|
842
|
-
}
|
|
843
|
-
const refreshed = await buildContext(binding.workspaceRealPath);
|
|
844
|
-
const remaining = await discoverModules({ catalog: refreshed.catalog });
|
|
845
|
-
if (remaining.length > 0) {
|
|
846
|
-
throw new PlatformError("UNINSTALL_FAILED", `Platform Instance uninstall left managed modules: ${remaining.map((module) => module.moduleRef).join(", ")}`);
|
|
847
|
-
}
|
|
848
|
-
if (modules.length > 0) {
|
|
849
|
-
await cleanupWorkspacePackageManagerArtifacts({
|
|
850
|
-
workspaceRoot: binding.workspaceRealPath,
|
|
851
|
-
removedModules: modules,
|
|
852
|
-
});
|
|
853
|
-
}
|
|
854
|
-
// Deployment-owned plans/state/verification/instance identity must not leak
|
|
855
|
-
// into a future install of the same directory. Business/domain data outside
|
|
856
|
-
// `.proflow/deployment` is intentionally preserved.
|
|
857
|
-
await rm(ctx.paths.deployment, { recursive: true, force: true });
|
|
858
|
-
for (const path of [
|
|
859
|
-
ctx.paths.logsDeployment,
|
|
860
|
-
ctx.paths.logs,
|
|
861
|
-
ctx.paths.config,
|
|
862
|
-
ctx.paths.data,
|
|
863
|
-
ctx.paths.runtime,
|
|
864
|
-
ctx.paths.cache,
|
|
865
|
-
ctx.paths.tmp,
|
|
866
|
-
ctx.paths.proflow,
|
|
867
|
-
]) {
|
|
868
|
-
try {
|
|
869
|
-
await rmdir(path);
|
|
870
|
-
}
|
|
871
|
-
catch (error) {
|
|
872
|
-
const code = typeof error === "object" && error !== null
|
|
873
|
-
? Reflect.get(error, "code")
|
|
874
|
-
: undefined;
|
|
875
|
-
if (code !== "ENOENT" && code !== "ENOTEMPTY")
|
|
876
|
-
throw error;
|
|
250
|
+
const parsed = JSON.parse(await readFile(resolve(root, "package.json"), "utf8"));
|
|
251
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
|
|
252
|
+
return [];
|
|
253
|
+
const record = parsed;
|
|
254
|
+
const names = new Set();
|
|
255
|
+
for (const field of ["dependencies", "devDependencies"]) {
|
|
256
|
+
const value = record[field];
|
|
257
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
258
|
+
continue;
|
|
259
|
+
for (const name of Object.keys(value)) {
|
|
260
|
+
if (name.startsWith(PRO_FLOW_PACKAGE_PREFIX))
|
|
261
|
+
names.add(name);
|
|
877
262
|
}
|
|
878
263
|
}
|
|
879
|
-
|
|
880
|
-
await clearGlobalBinding({
|
|
881
|
-
workspaceInstanceId: binding.workspaceInstanceId,
|
|
882
|
-
globalRoot: runtime.globalRoot,
|
|
883
|
-
});
|
|
884
|
-
binding = undefined;
|
|
885
|
-
return outcome("uninstall", "SUCCEEDED", {
|
|
886
|
-
uninstalledWorkspace: completed.workspaceRealPath,
|
|
887
|
-
removedModules,
|
|
888
|
-
bindingCleared: true,
|
|
889
|
-
});
|
|
264
|
+
return [...names].sort();
|
|
890
265
|
}
|
|
891
266
|
catch (error) {
|
|
892
|
-
if (
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
failure: {
|
|
898
|
-
code: error instanceof PlatformError ? error.code : "UNINSTALL_FAILED",
|
|
899
|
-
message: error instanceof Error ? error.message : String(error),
|
|
900
|
-
},
|
|
901
|
-
});
|
|
902
|
-
}
|
|
903
|
-
throw error;
|
|
904
|
-
}
|
|
905
|
-
finally {
|
|
906
|
-
await operationLock.release();
|
|
907
|
-
}
|
|
908
|
-
}
|
|
909
|
-
async function handleLifecycle(ctx, args, primitive) {
|
|
910
|
-
if (primitive === "start") {
|
|
911
|
-
const preflight = await handlePreflight(ctx, args);
|
|
912
|
-
if (preflight.status !== "SUCCEEDED") {
|
|
913
|
-
return outcome("start", preflight.status, preflight.data);
|
|
914
|
-
}
|
|
267
|
+
if (typeof error === "object" &&
|
|
268
|
+
error !== null &&
|
|
269
|
+
Reflect.get(error, "code") === "ENOENT")
|
|
270
|
+
return [];
|
|
271
|
+
throw new PlatformError("INVALID_REQUEST", `cannot read Workspace package.json: ${error instanceof Error ? error.message : String(error)}`);
|
|
915
272
|
}
|
|
916
|
-
const modules = await discoverModules({ catalog: ctx.catalog });
|
|
917
|
-
const selected = selectModules(modules, args.positional[0]);
|
|
918
|
-
const result = primitive === "start"
|
|
919
|
-
? await startModules(ctx.catalog, selected)
|
|
920
|
-
: primitive === "stop"
|
|
921
|
-
? await stopModules(ctx.catalog, selected)
|
|
922
|
-
: primitive === "restart"
|
|
923
|
-
? await restartModules(ctx.catalog, selected)
|
|
924
|
-
: await statusModules(ctx.catalog, selected);
|
|
925
|
-
const statuses = result.flatMap((item) => item.result ? [item.result.status] : []);
|
|
926
|
-
const status = aggregateStatus(statuses);
|
|
927
|
-
if (primitive === "status" && !args.positional[0]) {
|
|
928
|
-
await writeDeploymentObserverSummary({
|
|
929
|
-
paths: ctx.paths,
|
|
930
|
-
source: "status",
|
|
931
|
-
selectedModuleCount: selected.length,
|
|
932
|
-
totalModuleCount: modules.length,
|
|
933
|
-
modules: result.flatMap((item) => item.result
|
|
934
|
-
? [{ moduleRef: item.moduleRef, status: item.result.status }]
|
|
935
|
-
: []),
|
|
936
|
-
});
|
|
937
|
-
}
|
|
938
|
-
return outcome(primitive, status, result);
|
|
939
273
|
}
|
|
940
|
-
async function
|
|
941
|
-
const
|
|
942
|
-
const
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
}
|
|
957
|
-
return outcome("verify", status, result);
|
|
958
|
-
}
|
|
959
|
-
async function handleDoctor(ctx, args) {
|
|
960
|
-
const modules = await discoverModules({ catalog: ctx.catalog });
|
|
961
|
-
const selected = selectModules(modules, args.positional[0]);
|
|
962
|
-
const result = await doctorModules(ctx.catalog, selected);
|
|
963
|
-
const status = aggregateStatus(result.map((item) => item.status));
|
|
964
|
-
if (!args.positional[0]) {
|
|
965
|
-
await writeDeploymentObserverSummary({
|
|
966
|
-
paths: ctx.paths,
|
|
967
|
-
source: "doctor",
|
|
968
|
-
selectedModuleCount: selected.length,
|
|
969
|
-
totalModuleCount: modules.length,
|
|
970
|
-
modules: result.map((item) => ({
|
|
971
|
-
moduleRef: item.moduleRef,
|
|
972
|
-
status: item.status,
|
|
973
|
-
})),
|
|
974
|
-
});
|
|
975
|
-
}
|
|
976
|
-
return outcome("doctor", status, result);
|
|
274
|
+
async function handleUninstall(root, runtime) {
|
|
275
|
+
const packageNames = await workspaceProFlowDependencies(root);
|
|
276
|
+
const mutation = await removeWorkspacePackages({
|
|
277
|
+
workspaceRoot: root,
|
|
278
|
+
packageNames,
|
|
279
|
+
...(runtime.packageRunner === undefined
|
|
280
|
+
? {}
|
|
281
|
+
: { runner: runtime.packageRunner }),
|
|
282
|
+
...(runtime.executableAvailable === undefined
|
|
283
|
+
? {}
|
|
284
|
+
: { executableAvailable: runtime.executableAvailable }),
|
|
285
|
+
});
|
|
286
|
+
return outcome("uninstall", "SUCCEEDED", root, {
|
|
287
|
+
packageManager: mutation.packageManager,
|
|
288
|
+
removed: packageNames,
|
|
289
|
+
preserved: [".proflow"],
|
|
290
|
+
});
|
|
977
291
|
}
|
|
978
|
-
async function
|
|
979
|
-
const modules = await
|
|
980
|
-
const
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
292
|
+
async function handleStart(root) {
|
|
293
|
+
const { catalog, modules } = await buildContext(root);
|
|
294
|
+
const result = await preflightAndStartModules(catalog, modules);
|
|
295
|
+
return outcome("start", result.completed ? "SUCCEEDED" : failedLifecycleStatus(result.results), root, result);
|
|
296
|
+
}
|
|
297
|
+
async function handleStop(root) {
|
|
298
|
+
const { catalog, modules } = await buildContext(root);
|
|
299
|
+
const result = await stopModulesThin(catalog, modules);
|
|
300
|
+
return outcome("stop", result.completed ? "SUCCEEDED" : failedLifecycleStatus(result.results), root, result);
|
|
301
|
+
}
|
|
302
|
+
function helpOutcome() {
|
|
303
|
+
return outcome("help", "SUCCEEDED", undefined, {
|
|
304
|
+
usage: "platform <modules|docs|install|uninstall|start|stop> [--json]",
|
|
305
|
+
commands: [...COMMANDS],
|
|
306
|
+
install: "platform install [--workspace <path>]",
|
|
987
307
|
});
|
|
988
|
-
await generateInstallDoc({ modules: selected, config, paths: ctx.paths });
|
|
989
|
-
switch (manifest.status) {
|
|
990
|
-
case "READY":
|
|
991
|
-
return outcome("manifest", "SUCCEEDED", manifest);
|
|
992
|
-
case "ACTION_REQUIRED":
|
|
993
|
-
return outcome("manifest", "ACTION_REQUIRED", manifest);
|
|
994
|
-
case "DEGRADED":
|
|
995
|
-
return outcome("manifest", "BLOCKED", manifest);
|
|
996
|
-
case "NOT_READY":
|
|
997
|
-
return outcome("manifest", "FAILED", manifest);
|
|
998
|
-
}
|
|
999
308
|
}
|
|
1000
|
-
async function
|
|
1001
|
-
let
|
|
309
|
+
export async function runCli(argv, runtime = {}) {
|
|
310
|
+
let parsed;
|
|
1002
311
|
try {
|
|
1003
|
-
|
|
312
|
+
parsed = parseArgs(argv);
|
|
1004
313
|
}
|
|
1005
314
|
catch (error) {
|
|
1006
|
-
return
|
|
1007
|
-
}
|
|
1008
|
-
if (!COMMANDS.includes(args.command)) {
|
|
1009
|
-
return {
|
|
1010
|
-
command: args.command,
|
|
1011
|
-
status: "FAILED",
|
|
1012
|
-
error: {
|
|
1013
|
-
code: "INVALID_REQUEST",
|
|
1014
|
-
message: `unknown command: ${args.command}`,
|
|
1015
|
-
},
|
|
1016
|
-
};
|
|
315
|
+
return JSON.stringify(errorOutcome("unknown", error));
|
|
1017
316
|
}
|
|
1018
317
|
try {
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
return await handleInstallerPreflight(root);
|
|
1032
|
-
}
|
|
1033
|
-
if (args.command === "plan") {
|
|
1034
|
-
if (args.intent === "install") {
|
|
1035
|
-
return await handleInstallPlanBeforeBinding(args, runtime);
|
|
1036
|
-
}
|
|
1037
|
-
if (args.intent === undefined ||
|
|
1038
|
-
!["configure", "upgrade", "uninstall", "repair"].includes(args.intent)) {
|
|
1039
|
-
const current = await loadGlobalBinding(runtime.globalRoot);
|
|
1040
|
-
const root = args.workspace ??
|
|
1041
|
-
current?.workspaceRealPath ??
|
|
1042
|
-
runtime.cwd ??
|
|
1043
|
-
process.cwd();
|
|
1044
|
-
return await handlePlan(await buildContext(root), args);
|
|
1045
|
-
}
|
|
1046
|
-
}
|
|
1047
|
-
if (args.command === "apply") {
|
|
1048
|
-
return await handleApplyWithGlobalBinding(args, runtime);
|
|
1049
|
-
}
|
|
1050
|
-
if (args.command === "install") {
|
|
1051
|
-
return await handleInstallWithGlobalBinding(args, runtime);
|
|
1052
|
-
}
|
|
1053
|
-
if (args.command === "status") {
|
|
1054
|
-
const observation = await observeBoundWorkspace(runtime.globalRoot);
|
|
1055
|
-
if (observation === undefined) {
|
|
1056
|
-
return outcome("status", "SUCCEEDED", {
|
|
1057
|
-
installed: false,
|
|
1058
|
-
bindingState: "UNBOUND",
|
|
1059
|
-
boundWorkspace: null,
|
|
1060
|
-
nextAction: "Run platform install [--workspace <path>]",
|
|
1061
|
-
});
|
|
1062
|
-
}
|
|
1063
|
-
if (!observation.workspaceExists) {
|
|
1064
|
-
return withWorkspace(outcome("status", "BLOCKED", {
|
|
1065
|
-
installed: false,
|
|
1066
|
-
code: "BOUND_WORKSPACE_MISSING",
|
|
1067
|
-
boundWorkspace: observation.binding.workspaceRealPath,
|
|
1068
|
-
nextAction: "Restore the Workspace or run platform uninstall --forget to clear only the stale binding",
|
|
1069
|
-
}), observation.binding);
|
|
1070
|
-
}
|
|
1071
|
-
}
|
|
1072
|
-
if (args.command === "uninstall" && args.forget) {
|
|
1073
|
-
if (args.positional.length > 0 || args.workspace !== undefined) {
|
|
1074
|
-
throw new PlatformError("INVALID_REQUEST", "platform uninstall --forget only clears the current stale global binding; do not combine it with a module/package or --workspace");
|
|
1075
|
-
}
|
|
1076
|
-
const operationLock = await acquireGlobalOperationLock(runtime.globalRoot);
|
|
1077
|
-
try {
|
|
1078
|
-
const forgotten = await forgetMissingWorkspaceBinding({
|
|
1079
|
-
globalRoot: runtime.globalRoot,
|
|
1080
|
-
});
|
|
1081
|
-
return outcome("uninstall", "SUCCEEDED", {
|
|
1082
|
-
forgottenWorkspace: forgotten.workspaceRealPath,
|
|
1083
|
-
bindingCleared: true,
|
|
1084
|
-
resourcesCleaned: false,
|
|
1085
|
-
});
|
|
1086
|
-
}
|
|
1087
|
-
finally {
|
|
1088
|
-
await operationLock.release();
|
|
1089
|
-
}
|
|
1090
|
-
}
|
|
1091
|
-
if (args.command === "uninstall" && args.positional.length === 0) {
|
|
1092
|
-
const current = await loadGlobalBinding(runtime.globalRoot);
|
|
1093
|
-
if (current === undefined) {
|
|
1094
|
-
return outcome("uninstall", "SUCCEEDED", {
|
|
1095
|
-
alreadyUninstalled: true,
|
|
1096
|
-
bindingCleared: true,
|
|
1097
|
-
});
|
|
1098
|
-
}
|
|
1099
|
-
return await handlePlatformInstanceUninstall(args, runtime);
|
|
1100
|
-
}
|
|
1101
|
-
if (args.command === "upgrade" ||
|
|
1102
|
-
(args.command === "uninstall" && args.positional.length > 0) ||
|
|
1103
|
-
args.command === "start" ||
|
|
1104
|
-
args.command === "stop" ||
|
|
1105
|
-
args.command === "restart") {
|
|
1106
|
-
return await handleBoundMutatingCommand(args, runtime);
|
|
1107
|
-
}
|
|
1108
|
-
const { ctx, binding } = await resolveBoundContext(runtime, args.workspace);
|
|
1109
|
-
let result;
|
|
1110
|
-
switch (args.command) {
|
|
1111
|
-
case "search":
|
|
1112
|
-
case "install":
|
|
1113
|
-
throw new PlatformError("COMMAND_FAILED", "unreachable command routing");
|
|
318
|
+
if (parsed.command === "help")
|
|
319
|
+
return JSON.stringify(helpOutcome());
|
|
320
|
+
if (parsed.command === "version") {
|
|
321
|
+
return JSON.stringify(outcome("version", "SUCCEEDED", undefined, {
|
|
322
|
+
version: platformCliDescriptor.moduleVersion,
|
|
323
|
+
}));
|
|
324
|
+
}
|
|
325
|
+
const cwd = await canonicalWorkspace(runtime.cwd ?? process.cwd());
|
|
326
|
+
const root = parsed.command === "install" && parsed.workspace !== undefined
|
|
327
|
+
? await canonicalWorkspace(cwd, parsed.workspace)
|
|
328
|
+
: cwd;
|
|
329
|
+
switch (parsed.command) {
|
|
1114
330
|
case "modules":
|
|
1115
|
-
|
|
1116
|
-
break;
|
|
331
|
+
return JSON.stringify(await handleModules(root));
|
|
1117
332
|
case "docs":
|
|
1118
|
-
|
|
1119
|
-
|
|
333
|
+
return JSON.stringify(await handleDocs(root));
|
|
334
|
+
case "install":
|
|
335
|
+
return JSON.stringify(await handleInstall(root, runtime));
|
|
1120
336
|
case "uninstall":
|
|
1121
|
-
|
|
337
|
+
return JSON.stringify(await handleUninstall(root, runtime));
|
|
1122
338
|
case "start":
|
|
339
|
+
return JSON.stringify(await handleStart(root));
|
|
1123
340
|
case "stop":
|
|
1124
|
-
|
|
1125
|
-
throw new PlatformError("COMMAND_FAILED", "unreachable mutating command routing");
|
|
1126
|
-
case "preflight":
|
|
1127
|
-
result = await handlePreflight(ctx, args);
|
|
1128
|
-
break;
|
|
1129
|
-
case "plan":
|
|
1130
|
-
result = await handlePlan(ctx, args);
|
|
1131
|
-
break;
|
|
1132
|
-
case "apply":
|
|
1133
|
-
result = await handleApply(ctx, args);
|
|
1134
|
-
break;
|
|
1135
|
-
case "status":
|
|
1136
|
-
result = await handleLifecycle(ctx, args, "status");
|
|
1137
|
-
break;
|
|
1138
|
-
case "verify":
|
|
1139
|
-
result = await handleVerify(ctx, args);
|
|
1140
|
-
break;
|
|
1141
|
-
case "doctor":
|
|
1142
|
-
result = await handleDoctor(ctx, args);
|
|
1143
|
-
break;
|
|
1144
|
-
case "manifest":
|
|
1145
|
-
result = await handleManifest(ctx, args);
|
|
1146
|
-
break;
|
|
341
|
+
return JSON.stringify(await handleStop(root));
|
|
1147
342
|
}
|
|
1148
|
-
if (result === undefined) {
|
|
1149
|
-
throw new PlatformError("COMMAND_FAILED", "unreachable command routing");
|
|
1150
|
-
}
|
|
1151
|
-
return withWorkspace(result, binding);
|
|
1152
343
|
}
|
|
1153
344
|
catch (error) {
|
|
1154
|
-
return
|
|
345
|
+
return JSON.stringify(errorOutcome(parsed.command, error));
|
|
1155
346
|
}
|
|
1156
347
|
}
|
|
1157
|
-
function
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
?
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
description: "one or more steps require a human action",
|
|
1174
|
-
},
|
|
1175
|
-
}
|
|
1176
|
-
: {}),
|
|
1177
|
-
...(outcomeResult.error !== undefined
|
|
1178
|
-
? {
|
|
1179
|
-
error: {
|
|
1180
|
-
code: outcomeResult.error.code,
|
|
1181
|
-
message: outcomeResult.error.message,
|
|
1182
|
-
retryable: false,
|
|
1183
|
-
},
|
|
1184
|
-
}
|
|
1185
|
-
: {}),
|
|
1186
|
-
});
|
|
348
|
+
function errorOutcome(command, error) {
|
|
349
|
+
if (error instanceof PlatformError) {
|
|
350
|
+
return {
|
|
351
|
+
command,
|
|
352
|
+
status: "FAILED",
|
|
353
|
+
error: { code: error.code, message: error.message },
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
return {
|
|
357
|
+
command,
|
|
358
|
+
status: "FAILED",
|
|
359
|
+
error: {
|
|
360
|
+
code: "COMMAND_FAILED",
|
|
361
|
+
message: error instanceof Error ? error.message : String(error),
|
|
362
|
+
},
|
|
363
|
+
};
|
|
1187
364
|
}
|
|
1188
365
|
function isRecord(value) {
|
|
1189
366
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1190
367
|
}
|
|
1191
|
-
function
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
const lines = [
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
let skippedUnsupported = 0;
|
|
1198
|
-
for (const item of data) {
|
|
1199
|
-
if (!isRecord(item))
|
|
368
|
+
function renderModules(data) {
|
|
369
|
+
if (!isRecord(data) || !Array.isArray(data.modules))
|
|
370
|
+
return "No modules.";
|
|
371
|
+
const lines = ["ProFlow Modules", ""];
|
|
372
|
+
for (const raw of data.modules) {
|
|
373
|
+
if (!isRecord(raw))
|
|
1200
374
|
continue;
|
|
1201
|
-
const
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
}
|
|
1207
|
-
const result = isRecord(item.result) ? item.result : undefined;
|
|
1208
|
-
const resultStatus = result !== undefined && typeof result.status === "string"
|
|
1209
|
-
? result.status
|
|
1210
|
-
: runStatus;
|
|
1211
|
-
let detail = "";
|
|
1212
|
-
const actionRequired = result !== undefined && isRecord(result.actionRequired)
|
|
1213
|
-
? result.actionRequired
|
|
1214
|
-
: undefined;
|
|
1215
|
-
const error = result !== undefined && isRecord(result.error) ? result.error : undefined;
|
|
1216
|
-
if (actionRequired !== undefined) {
|
|
1217
|
-
const action = typeof actionRequired.action === "string"
|
|
1218
|
-
? actionRequired.action
|
|
1219
|
-
: "action-required";
|
|
1220
|
-
const description = typeof actionRequired.description === "string"
|
|
1221
|
-
? actionRequired.description
|
|
1222
|
-
: "human action required";
|
|
1223
|
-
detail = ` — ${action}: ${description}`;
|
|
1224
|
-
}
|
|
1225
|
-
else if (error !== undefined) {
|
|
1226
|
-
const code = typeof error.code === "string" ? error.code : "COMMAND_FAILED";
|
|
1227
|
-
const message = typeof error.message === "string" ? error.message : "Unknown error";
|
|
1228
|
-
detail = ` — ${code}: ${message}`;
|
|
1229
|
-
}
|
|
1230
|
-
else if (isRecord(item.record) &&
|
|
1231
|
-
typeof item.record.summary === "string") {
|
|
1232
|
-
detail = ` — ${item.record.summary}`;
|
|
1233
|
-
}
|
|
1234
|
-
lines.push(`- ${moduleRef}: ${resultStatus}${detail}`);
|
|
1235
|
-
if (Array.isArray(item.errors)) {
|
|
1236
|
-
for (const doctorError of item.errors) {
|
|
1237
|
-
if (!isRecord(doctorError))
|
|
1238
|
-
continue;
|
|
1239
|
-
const code = typeof doctorError.code === "string"
|
|
1240
|
-
? doctorError.code
|
|
1241
|
-
: "COMMAND_FAILED";
|
|
1242
|
-
const message = typeof doctorError.message === "string"
|
|
1243
|
-
? doctorError.message
|
|
1244
|
-
: "Unknown error";
|
|
1245
|
-
lines.push(` Error: ${code}: ${message}`);
|
|
1246
|
-
}
|
|
1247
|
-
}
|
|
1248
|
-
if (isRecord(item.nextAction)) {
|
|
1249
|
-
if (item.nextAction.kind === "human-action" &&
|
|
1250
|
-
typeof item.nextAction.action === "string" &&
|
|
1251
|
-
typeof item.nextAction.description === "string") {
|
|
1252
|
-
lines.push(` Next: ${item.nextAction.action} — ${item.nextAction.description}`);
|
|
1253
|
-
}
|
|
1254
|
-
else if (item.nextAction.kind === "repair-plan" &&
|
|
1255
|
-
typeof item.nextAction.summary === "string") {
|
|
1256
|
-
lines.push(` Next: platform plan --intent repair — ${item.nextAction.summary}`);
|
|
1257
|
-
}
|
|
1258
|
-
}
|
|
375
|
+
const missing = Array.isArray(raw.missingConfig)
|
|
376
|
+
? raw.missingConfig
|
|
377
|
+
.filter((item) => typeof item === "string")
|
|
378
|
+
.join(",")
|
|
379
|
+
: "-";
|
|
380
|
+
lines.push(`${String(raw.moduleRef)} ${String(raw.version)} config=${String(raw.configStatus)} runtime=${String(raw.runtimeStatus)} missing=${missing}`);
|
|
1259
381
|
}
|
|
1260
|
-
|
|
1261
|
-
lines.push(`Skipped unsupported: ${skippedUnsupported}`);
|
|
1262
|
-
return lines;
|
|
382
|
+
return lines.join("\n");
|
|
1263
383
|
}
|
|
1264
|
-
function
|
|
1265
|
-
if (Array.isArray(data))
|
|
1266
|
-
return
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
"alreadyInstalled",
|
|
1277
|
-
"alreadyLatest",
|
|
1278
|
-
"changed",
|
|
1279
|
-
"bindingCleared",
|
|
1280
|
-
"resourcesCleaned",
|
|
1281
|
-
"planRef",
|
|
1282
|
-
"outcome",
|
|
1283
|
-
]) {
|
|
1284
|
-
const value = data[key];
|
|
1285
|
-
if (typeof value === "string" ||
|
|
1286
|
-
typeof value === "number" ||
|
|
1287
|
-
typeof value === "boolean") {
|
|
1288
|
-
lines.push(`${key}: ${String(value)}`);
|
|
1289
|
-
}
|
|
1290
|
-
}
|
|
1291
|
-
if (typeof data.nextAction === "string" && data.nextAction !== "") {
|
|
1292
|
-
lines.push(`Next: ${data.nextAction}`);
|
|
1293
|
-
}
|
|
1294
|
-
const findings = data.findings;
|
|
1295
|
-
let hasMissingConfig = false;
|
|
1296
|
-
if (Array.isArray(findings) && findings.length > 0) {
|
|
1297
|
-
lines.push("Findings:");
|
|
1298
|
-
for (const finding of findings) {
|
|
1299
|
-
if (!isRecord(finding))
|
|
384
|
+
function renderDocs(data) {
|
|
385
|
+
if (!isRecord(data) || !Array.isArray(data.modules))
|
|
386
|
+
return "No module docs.";
|
|
387
|
+
const lines = ["ProFlow Docs"];
|
|
388
|
+
for (const raw of data.modules) {
|
|
389
|
+
if (!isRecord(raw))
|
|
390
|
+
continue;
|
|
391
|
+
lines.push("", `## ${String(raw.moduleRef)} @ ${String(raw.version)}`);
|
|
392
|
+
if (!Array.isArray(raw.documents))
|
|
393
|
+
continue;
|
|
394
|
+
for (const document of raw.documents) {
|
|
395
|
+
if (!isRecord(document))
|
|
1300
396
|
continue;
|
|
1301
|
-
|
|
1302
|
-
? finding.severity.toUpperCase()
|
|
1303
|
-
: "INFO";
|
|
1304
|
-
const code = typeof finding.code === "string" ? finding.code : "CHECK";
|
|
1305
|
-
if (code === "CONFIG_MISSING")
|
|
1306
|
-
hasMissingConfig = true;
|
|
1307
|
-
const moduleRef = typeof finding.moduleRef === "string" ? ` ${finding.moduleRef}` : "";
|
|
1308
|
-
const message = typeof finding.message === "string" ? finding.message : "";
|
|
1309
|
-
lines.push(`- [${severity}] ${code}${moduleRef}${message === "" ? "" : `: ${message}`}`);
|
|
397
|
+
lines.push("", `### ${String(document.id)}`, String(document.content ?? ""));
|
|
1310
398
|
}
|
|
1311
399
|
}
|
|
1312
|
-
|
|
1313
|
-
lines.push("Next: review .proflow/deployment/generated/INSTALL.md, create the required config file, then run platform plan --intent configure --config <file> and platform apply <planRef>.");
|
|
1314
|
-
}
|
|
1315
|
-
const preflight = data.preflight;
|
|
1316
|
-
if (isRecord(preflight)) {
|
|
1317
|
-
const nested = humanDetailLines(preflight);
|
|
1318
|
-
if (nested.length > 0) {
|
|
1319
|
-
lines.push("Preflight:", ...nested.map((line) => ` ${line}`));
|
|
1320
|
-
}
|
|
1321
|
-
}
|
|
1322
|
-
return lines;
|
|
400
|
+
return lines.join("\n");
|
|
1323
401
|
}
|
|
1324
|
-
export function renderHumanResult(
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
return machineOutput;
|
|
1328
|
-
const data = parsed.data;
|
|
1329
|
-
if (isRecord(data) && typeof data.version === "string") {
|
|
1330
|
-
return `ProFlow Platform CLI ${data.version}`;
|
|
402
|
+
export function renderHumanResult(result) {
|
|
403
|
+
if (result.status === "FAILED") {
|
|
404
|
+
return `${result.command.toUpperCase()} FAILED${result.error ? ` [${result.error.code}] ${result.error.message}` : ""}`;
|
|
1331
405
|
}
|
|
1332
|
-
if (
|
|
1333
|
-
const commands = Array.isArray(data.commands)
|
|
1334
|
-
? data.commands.filter((item) => typeof item === "string")
|
|
1335
|
-
: [];
|
|
1336
|
-
const examples = Array.isArray(data.examples)
|
|
1337
|
-
? data.examples.filter((item) => typeof item === "string")
|
|
1338
|
-
: [];
|
|
406
|
+
if (result.command === "help") {
|
|
1339
407
|
return [
|
|
1340
408
|
"ProFlow Platform CLI",
|
|
1341
409
|
"",
|
|
1342
|
-
`
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
...(examples.length === 0
|
|
1347
|
-
? []
|
|
1348
|
-
: ["", "Common flows:", ...examples.map((item) => ` ${item}`)]),
|
|
410
|
+
...COMMANDS.map((command) => `platform ${command}`),
|
|
411
|
+
"",
|
|
412
|
+
"platform install --workspace <path>",
|
|
413
|
+
"append --json for machine-readable output",
|
|
1349
414
|
].join("\n");
|
|
1350
415
|
}
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
if (
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
lines.push(`Version: ${MODULE_VERSION}`, "Next: run platform --help");
|
|
1366
|
-
}
|
|
1367
|
-
return lines.join("\n");
|
|
1368
|
-
}
|
|
1369
|
-
export async function runCli(argv, runtime = {}) {
|
|
1370
|
-
const filtered = argv.filter((argument) => argument !== "--json");
|
|
1371
|
-
if (filtered.length === 1 &&
|
|
1372
|
-
(filtered[0] === "--version" || filtered[0] === "-v")) {
|
|
1373
|
-
return JSON.stringify({
|
|
1374
|
-
contract: "deployment.result.v1",
|
|
1375
|
-
ok: true,
|
|
1376
|
-
status: "SUCCEEDED",
|
|
1377
|
-
moduleRef: MODULE_REF,
|
|
1378
|
-
moduleVersion: MODULE_VERSION,
|
|
1379
|
-
data: { version: MODULE_VERSION },
|
|
1380
|
-
});
|
|
1381
|
-
}
|
|
1382
|
-
if (argv.includes("--help") || argv.includes("-h")) {
|
|
1383
|
-
return JSON.stringify({
|
|
1384
|
-
contract: "deployment.result.v1",
|
|
1385
|
-
ok: true,
|
|
1386
|
-
status: "SUCCEEDED",
|
|
1387
|
-
moduleRef: MODULE_REF,
|
|
1388
|
-
moduleVersion: MODULE_VERSION,
|
|
1389
|
-
data: {
|
|
1390
|
-
usage: "platform <command> [module|package] [--workspace <path>] [--json]",
|
|
1391
|
-
commands: [...COMMANDS],
|
|
1392
|
-
examples: [
|
|
1393
|
-
"platform preflight --intent install --workspace <path>",
|
|
1394
|
-
"platform install [module|package] [--workspace <path>]",
|
|
1395
|
-
"platform preflight # startup/runtime readiness for the bound Workspace",
|
|
1396
|
-
"platform plan --intent configure --config <file>",
|
|
1397
|
-
"platform apply <planRef>",
|
|
1398
|
-
"platform start | status | verify | doctor | stop | restart",
|
|
1399
|
-
"platform uninstall # remove managed modules and .proflow/deployment state; preserve business/domain data outside .proflow/deployment; clear binding; keep the global CLI; v1 has no purge mode",
|
|
1400
|
-
"append --json for the stable machine-readable contract",
|
|
1401
|
-
],
|
|
1402
|
-
},
|
|
1403
|
-
});
|
|
1404
|
-
}
|
|
1405
|
-
if (filtered.length === 0) {
|
|
1406
|
-
return JSON.stringify({
|
|
1407
|
-
contract: "deployment.result.v1",
|
|
1408
|
-
ok: true,
|
|
1409
|
-
status: "SUCCEEDED",
|
|
1410
|
-
moduleRef: MODULE_REF,
|
|
1411
|
-
moduleVersion: MODULE_VERSION,
|
|
1412
|
-
});
|
|
1413
|
-
}
|
|
1414
|
-
return toMachineResult(await dispatchCommand(filtered, runtime));
|
|
416
|
+
if (result.command === "version" && isRecord(result.data)) {
|
|
417
|
+
return String(result.data.version ?? platformCliDescriptor.moduleVersion);
|
|
418
|
+
}
|
|
419
|
+
if (result.command === "modules")
|
|
420
|
+
return renderModules(result.data);
|
|
421
|
+
if (result.command === "docs")
|
|
422
|
+
return renderDocs(result.data);
|
|
423
|
+
return [
|
|
424
|
+
`${result.command.toUpperCase()} ${result.status}`,
|
|
425
|
+
...(result.workspaceRoot ? [`Workspace: ${result.workspaceRoot}`] : []),
|
|
426
|
+
...(result.data === undefined
|
|
427
|
+
? []
|
|
428
|
+
: [JSON.stringify(result.data, null, 2)]),
|
|
429
|
+
].join("\n");
|
|
1415
430
|
}
|
|
1416
431
|
if (import.meta.main) {
|
|
1417
432
|
const argv = process.argv.slice(2);
|
|
1418
433
|
const output = await runCli(argv);
|
|
1419
|
-
const
|
|
1420
|
-
process.stdout.write(`${
|
|
1421
|
-
|
|
1422
|
-
if (typeof parsed === "object" &&
|
|
1423
|
-
parsed !== null &&
|
|
1424
|
-
Reflect.get(parsed, "ok") === false) {
|
|
434
|
+
const result = JSON.parse(output);
|
|
435
|
+
process.stdout.write(`${argv.includes("--json") ? output : renderHumanResult(result)}\n`);
|
|
436
|
+
if (result.status !== "SUCCEEDED")
|
|
1425
437
|
process.exitCode = 1;
|
|
1426
|
-
}
|
|
1427
438
|
}
|