@tomflow/proflow-platform-cli 0.1.13 → 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 +329 -1237
- 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 -30
- package/dist/src/lifecycle/dispatch.js +0 -120
- 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 -79
- 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,1346 +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
|
-
|
|
88
|
+
status,
|
|
89
|
+
...(workspaceRoot === undefined ? {} : { workspaceRoot }),
|
|
90
|
+
...(data === undefined ? {} : { data }),
|
|
133
91
|
};
|
|
134
92
|
}
|
|
135
|
-
function
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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,
|
|
151
|
-
};
|
|
152
|
-
if (module.source.path !== undefined)
|
|
153
|
-
source.path = module.source.path;
|
|
154
|
-
return source;
|
|
155
|
-
}
|
|
156
|
-
async function loadDescriptors(catalog, modules) {
|
|
157
|
-
const descriptors = [];
|
|
158
|
-
for (const module of modules) {
|
|
159
|
-
const raw = await catalog.loadDescriptor(moduleSourceOf(module));
|
|
160
|
-
descriptors.push(parseModuleDescriptor(raw));
|
|
161
|
-
}
|
|
162
|
-
return descriptors;
|
|
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`);
|
|
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);
|
|
171
98
|
}
|
|
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`);
|
|
99
|
+
catch (error) {
|
|
100
|
+
throw new PlatformError("WORKSPACE_NOT_FOUND", `Workspace does not exist: ${candidate} (${error instanceof Error ? error.message : String(error)})`);
|
|
179
101
|
}
|
|
180
|
-
|
|
181
|
-
|
|
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;
|
|
102
|
+
if (!info.isDirectory()) {
|
|
103
|
+
throw new PlatformError("WORKSPACE_NOT_FOUND", `Workspace is not a directory: ${candidate}`);
|
|
190
104
|
}
|
|
191
|
-
return
|
|
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);
|
|
237
|
-
}
|
|
238
|
-
async function handleModules(ctx, args) {
|
|
239
|
-
const modules = await discoverModules({ catalog: ctx.catalog });
|
|
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
|
-
})));
|
|
127
|
+
const catalog = new AutoModuleCatalog(root, bindings);
|
|
128
|
+
const modules = await discoverModules({ workspaceRoot: root, catalog });
|
|
129
|
+
return { catalog, modules };
|
|
252
130
|
}
|
|
253
|
-
|
|
254
|
-
|
|
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
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
const requestedWorkspace = await resolveRequestedInstallWorkspace(args, runtime);
|
|
579
|
-
const current = await loadGlobalBinding(runtime.globalRoot);
|
|
580
|
-
if (current !== undefined &&
|
|
581
|
-
current.workspaceRealPath !== requestedWorkspace) {
|
|
582
|
-
throw new PlatformError("WORKSPACE_ALREADY_BOUND", `ProFlow is already bound to ${current.workspaceRealPath}; uninstall it before planning an install for ${requestedWorkspace}`);
|
|
583
|
-
}
|
|
584
|
-
const ctx = await buildContext(current?.workspaceRealPath ?? requestedWorkspace);
|
|
585
|
-
const result = await handlePlan(ctx, args);
|
|
586
|
-
return current === undefined ? result : withWorkspace(result, current);
|
|
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 });
|
|
587
178
|
}
|
|
588
|
-
async function
|
|
589
|
-
const
|
|
590
|
-
|
|
591
|
-
|
|
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(", ")}`);
|
|
592
184
|
}
|
|
593
|
-
const
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
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);
|
|
695
|
-
}
|
|
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 });
|
|
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}`);
|
|
709
194
|
}
|
|
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
|
-
const modules = await discoverModules({ catalog: ctx.catalog });
|
|
911
|
-
const selected = selectModules(modules, args.positional[0]);
|
|
912
|
-
const result = primitive === "start"
|
|
913
|
-
? await startModules(ctx.catalog, selected)
|
|
914
|
-
: primitive === "stop"
|
|
915
|
-
? await stopModules(ctx.catalog, selected)
|
|
916
|
-
: primitive === "restart"
|
|
917
|
-
? await restartModules(ctx.catalog, selected)
|
|
918
|
-
: await statusModules(ctx.catalog, selected);
|
|
919
|
-
const statuses = result.flatMap((item) => item.result ? [item.result.status] : []);
|
|
920
|
-
const status = aggregateStatus(statuses);
|
|
921
|
-
if (primitive === "status" && !args.positional[0]) {
|
|
922
|
-
await writeDeploymentObserverSummary({
|
|
923
|
-
paths: ctx.paths,
|
|
924
|
-
source: "status",
|
|
925
|
-
selectedModuleCount: selected.length,
|
|
926
|
-
totalModuleCount: modules.length,
|
|
927
|
-
modules: result.flatMap((item) => item.result
|
|
928
|
-
? [{ moduleRef: item.moduleRef, status: item.result.status }]
|
|
929
|
-
: []),
|
|
930
|
-
});
|
|
931
|
-
}
|
|
932
|
-
return outcome(primitive, status, result);
|
|
933
|
-
}
|
|
934
|
-
async function handleVerify(ctx, args) {
|
|
935
|
-
const modules = await discoverModules({ catalog: ctx.catalog });
|
|
936
|
-
const selected = selectModules(modules, args.positional[0]);
|
|
937
|
-
const result = await verifyModules(ctx.catalog, selected, ctx.paths);
|
|
938
|
-
const status = aggregateStatus(result.map((item) => item.result.status));
|
|
939
|
-
if (!args.positional[0]) {
|
|
940
|
-
await writeDeploymentObserverSummary({
|
|
941
|
-
paths: ctx.paths,
|
|
942
|
-
source: "verify",
|
|
943
|
-
selectedModuleCount: selected.length,
|
|
944
|
-
totalModuleCount: modules.length,
|
|
945
|
-
modules: result.map((item) => ({
|
|
946
|
-
moduleRef: item.moduleRef,
|
|
947
|
-
status: item.result.status,
|
|
948
|
-
})),
|
|
949
|
-
});
|
|
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)}`);
|
|
950
272
|
}
|
|
951
|
-
return outcome("verify", status, result);
|
|
952
273
|
}
|
|
953
|
-
async function
|
|
954
|
-
const
|
|
955
|
-
const
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
}
|
|
970
|
-
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
|
+
});
|
|
971
291
|
}
|
|
972
|
-
async function
|
|
973
|
-
const modules = await
|
|
974
|
-
const
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
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>]",
|
|
981
307
|
});
|
|
982
|
-
await generateInstallDoc({ modules: selected, config, paths: ctx.paths });
|
|
983
|
-
switch (manifest.status) {
|
|
984
|
-
case "READY":
|
|
985
|
-
return outcome("manifest", "SUCCEEDED", manifest);
|
|
986
|
-
case "ACTION_REQUIRED":
|
|
987
|
-
return outcome("manifest", "ACTION_REQUIRED", manifest);
|
|
988
|
-
case "DEGRADED":
|
|
989
|
-
return outcome("manifest", "BLOCKED", manifest);
|
|
990
|
-
case "NOT_READY":
|
|
991
|
-
return outcome("manifest", "FAILED", manifest);
|
|
992
|
-
}
|
|
993
308
|
}
|
|
994
|
-
async function
|
|
995
|
-
let
|
|
309
|
+
export async function runCli(argv, runtime = {}) {
|
|
310
|
+
let parsed;
|
|
996
311
|
try {
|
|
997
|
-
|
|
312
|
+
parsed = parseArgs(argv);
|
|
998
313
|
}
|
|
999
314
|
catch (error) {
|
|
1000
|
-
return
|
|
1001
|
-
}
|
|
1002
|
-
if (!COMMANDS.includes(args.command)) {
|
|
1003
|
-
return {
|
|
1004
|
-
command: args.command,
|
|
1005
|
-
status: "FAILED",
|
|
1006
|
-
error: {
|
|
1007
|
-
code: "INVALID_REQUEST",
|
|
1008
|
-
message: `unknown command: ${args.command}`,
|
|
1009
|
-
},
|
|
1010
|
-
};
|
|
315
|
+
return JSON.stringify(errorOutcome("unknown", error));
|
|
1011
316
|
}
|
|
1012
317
|
try {
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
return await handleInstallerPreflight(root);
|
|
1026
|
-
}
|
|
1027
|
-
if (args.command === "plan") {
|
|
1028
|
-
if (args.intent === "install") {
|
|
1029
|
-
return await handleInstallPlanBeforeBinding(args, runtime);
|
|
1030
|
-
}
|
|
1031
|
-
if (args.intent === undefined ||
|
|
1032
|
-
!["configure", "upgrade", "uninstall", "repair"].includes(args.intent)) {
|
|
1033
|
-
const current = await loadGlobalBinding(runtime.globalRoot);
|
|
1034
|
-
const root = args.workspace ??
|
|
1035
|
-
current?.workspaceRealPath ??
|
|
1036
|
-
runtime.cwd ??
|
|
1037
|
-
process.cwd();
|
|
1038
|
-
return await handlePlan(await buildContext(root), args);
|
|
1039
|
-
}
|
|
1040
|
-
}
|
|
1041
|
-
if (args.command === "apply") {
|
|
1042
|
-
return await handleApplyWithGlobalBinding(args, runtime);
|
|
1043
|
-
}
|
|
1044
|
-
if (args.command === "install") {
|
|
1045
|
-
return await handleInstallWithGlobalBinding(args, runtime);
|
|
1046
|
-
}
|
|
1047
|
-
if (args.command === "status") {
|
|
1048
|
-
const observation = await observeBoundWorkspace(runtime.globalRoot);
|
|
1049
|
-
if (observation === undefined) {
|
|
1050
|
-
return outcome("status", "SUCCEEDED", {
|
|
1051
|
-
installed: false,
|
|
1052
|
-
bindingState: "UNBOUND",
|
|
1053
|
-
boundWorkspace: null,
|
|
1054
|
-
nextAction: "Run platform install [--workspace <path>]",
|
|
1055
|
-
});
|
|
1056
|
-
}
|
|
1057
|
-
if (!observation.workspaceExists) {
|
|
1058
|
-
return withWorkspace(outcome("status", "BLOCKED", {
|
|
1059
|
-
installed: false,
|
|
1060
|
-
code: "BOUND_WORKSPACE_MISSING",
|
|
1061
|
-
boundWorkspace: observation.binding.workspaceRealPath,
|
|
1062
|
-
nextAction: "Restore the Workspace or run platform uninstall --forget to clear only the stale binding",
|
|
1063
|
-
}), observation.binding);
|
|
1064
|
-
}
|
|
1065
|
-
}
|
|
1066
|
-
if (args.command === "uninstall" && args.forget) {
|
|
1067
|
-
if (args.positional.length > 0 || args.workspace !== undefined) {
|
|
1068
|
-
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");
|
|
1069
|
-
}
|
|
1070
|
-
const operationLock = await acquireGlobalOperationLock(runtime.globalRoot);
|
|
1071
|
-
try {
|
|
1072
|
-
const forgotten = await forgetMissingWorkspaceBinding({
|
|
1073
|
-
globalRoot: runtime.globalRoot,
|
|
1074
|
-
});
|
|
1075
|
-
return outcome("uninstall", "SUCCEEDED", {
|
|
1076
|
-
forgottenWorkspace: forgotten.workspaceRealPath,
|
|
1077
|
-
bindingCleared: true,
|
|
1078
|
-
resourcesCleaned: false,
|
|
1079
|
-
});
|
|
1080
|
-
}
|
|
1081
|
-
finally {
|
|
1082
|
-
await operationLock.release();
|
|
1083
|
-
}
|
|
1084
|
-
}
|
|
1085
|
-
if (args.command === "uninstall" && args.positional.length === 0) {
|
|
1086
|
-
const current = await loadGlobalBinding(runtime.globalRoot);
|
|
1087
|
-
if (current === undefined) {
|
|
1088
|
-
return outcome("uninstall", "SUCCEEDED", {
|
|
1089
|
-
alreadyUninstalled: true,
|
|
1090
|
-
bindingCleared: true,
|
|
1091
|
-
});
|
|
1092
|
-
}
|
|
1093
|
-
return await handlePlatformInstanceUninstall(args, runtime);
|
|
1094
|
-
}
|
|
1095
|
-
if (args.command === "upgrade" ||
|
|
1096
|
-
(args.command === "uninstall" && args.positional.length > 0) ||
|
|
1097
|
-
args.command === "start" ||
|
|
1098
|
-
args.command === "stop" ||
|
|
1099
|
-
args.command === "restart") {
|
|
1100
|
-
return await handleBoundMutatingCommand(args, runtime);
|
|
1101
|
-
}
|
|
1102
|
-
const { ctx, binding } = await resolveBoundContext(runtime, args.workspace);
|
|
1103
|
-
let result;
|
|
1104
|
-
switch (args.command) {
|
|
1105
|
-
case "search":
|
|
1106
|
-
case "install":
|
|
1107
|
-
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) {
|
|
1108
330
|
case "modules":
|
|
1109
|
-
|
|
1110
|
-
break;
|
|
331
|
+
return JSON.stringify(await handleModules(root));
|
|
1111
332
|
case "docs":
|
|
1112
|
-
|
|
1113
|
-
|
|
333
|
+
return JSON.stringify(await handleDocs(root));
|
|
334
|
+
case "install":
|
|
335
|
+
return JSON.stringify(await handleInstall(root, runtime));
|
|
1114
336
|
case "uninstall":
|
|
1115
|
-
|
|
337
|
+
return JSON.stringify(await handleUninstall(root, runtime));
|
|
1116
338
|
case "start":
|
|
339
|
+
return JSON.stringify(await handleStart(root));
|
|
1117
340
|
case "stop":
|
|
1118
|
-
|
|
1119
|
-
throw new PlatformError("COMMAND_FAILED", "unreachable mutating command routing");
|
|
1120
|
-
case "preflight":
|
|
1121
|
-
result = await handlePreflight(ctx, args);
|
|
1122
|
-
break;
|
|
1123
|
-
case "plan":
|
|
1124
|
-
result = await handlePlan(ctx, args);
|
|
1125
|
-
break;
|
|
1126
|
-
case "apply":
|
|
1127
|
-
result = await handleApply(ctx, args);
|
|
1128
|
-
break;
|
|
1129
|
-
case "status":
|
|
1130
|
-
result = await handleLifecycle(ctx, args, "status");
|
|
1131
|
-
break;
|
|
1132
|
-
case "verify":
|
|
1133
|
-
result = await handleVerify(ctx, args);
|
|
1134
|
-
break;
|
|
1135
|
-
case "doctor":
|
|
1136
|
-
result = await handleDoctor(ctx, args);
|
|
1137
|
-
break;
|
|
1138
|
-
case "manifest":
|
|
1139
|
-
result = await handleManifest(ctx, args);
|
|
1140
|
-
break;
|
|
341
|
+
return JSON.stringify(await handleStop(root));
|
|
1141
342
|
}
|
|
1142
|
-
if (result === undefined) {
|
|
1143
|
-
throw new PlatformError("COMMAND_FAILED", "unreachable command routing");
|
|
1144
|
-
}
|
|
1145
|
-
return withWorkspace(result, binding);
|
|
1146
343
|
}
|
|
1147
344
|
catch (error) {
|
|
1148
|
-
return
|
|
345
|
+
return JSON.stringify(errorOutcome(parsed.command, error));
|
|
1149
346
|
}
|
|
1150
347
|
}
|
|
1151
|
-
function
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
?
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
description: "one or more steps require a human action",
|
|
1168
|
-
},
|
|
1169
|
-
}
|
|
1170
|
-
: {}),
|
|
1171
|
-
...(outcomeResult.error !== undefined
|
|
1172
|
-
? {
|
|
1173
|
-
error: {
|
|
1174
|
-
code: outcomeResult.error.code,
|
|
1175
|
-
message: outcomeResult.error.message,
|
|
1176
|
-
retryable: false,
|
|
1177
|
-
},
|
|
1178
|
-
}
|
|
1179
|
-
: {}),
|
|
1180
|
-
});
|
|
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
|
+
};
|
|
1181
364
|
}
|
|
1182
365
|
function isRecord(value) {
|
|
1183
366
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1184
367
|
}
|
|
1185
|
-
function
|
|
1186
|
-
if (!isRecord(data))
|
|
1187
|
-
return
|
|
1188
|
-
|
|
1189
|
-
const
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
"bindingCleared",
|
|
1199
|
-
"resourcesCleaned",
|
|
1200
|
-
"planRef",
|
|
1201
|
-
"outcome",
|
|
1202
|
-
]) {
|
|
1203
|
-
const value = data[key];
|
|
1204
|
-
if (typeof value === "string" ||
|
|
1205
|
-
typeof value === "number" ||
|
|
1206
|
-
typeof value === "boolean") {
|
|
1207
|
-
lines.push(`${key}: ${String(value)}`);
|
|
1208
|
-
}
|
|
1209
|
-
}
|
|
1210
|
-
if (typeof data.nextAction === "string" && data.nextAction !== "") {
|
|
1211
|
-
lines.push(`Next: ${data.nextAction}`);
|
|
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))
|
|
374
|
+
continue;
|
|
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}`);
|
|
1212
381
|
}
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
382
|
+
return lines.join("\n");
|
|
383
|
+
}
|
|
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))
|
|
1219
396
|
continue;
|
|
1220
|
-
|
|
1221
|
-
? finding.severity.toUpperCase()
|
|
1222
|
-
: "INFO";
|
|
1223
|
-
const code = typeof finding.code === "string" ? finding.code : "CHECK";
|
|
1224
|
-
if (code === "CONFIG_MISSING")
|
|
1225
|
-
hasMissingConfig = true;
|
|
1226
|
-
const moduleRef = typeof finding.moduleRef === "string" ? ` ${finding.moduleRef}` : "";
|
|
1227
|
-
const message = typeof finding.message === "string" ? finding.message : "";
|
|
1228
|
-
lines.push(`- [${severity}] ${code}${moduleRef}${message === "" ? "" : `: ${message}`}`);
|
|
1229
|
-
}
|
|
1230
|
-
}
|
|
1231
|
-
if (hasMissingConfig) {
|
|
1232
|
-
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>.");
|
|
1233
|
-
}
|
|
1234
|
-
const preflight = data.preflight;
|
|
1235
|
-
if (isRecord(preflight)) {
|
|
1236
|
-
const nested = humanDetailLines(preflight);
|
|
1237
|
-
if (nested.length > 0) {
|
|
1238
|
-
lines.push("Preflight:", ...nested.map((line) => ` ${line}`));
|
|
397
|
+
lines.push("", `### ${String(document.id)}`, String(document.content ?? ""));
|
|
1239
398
|
}
|
|
1240
399
|
}
|
|
1241
|
-
return lines;
|
|
400
|
+
return lines.join("\n");
|
|
1242
401
|
}
|
|
1243
|
-
export function renderHumanResult(
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
return machineOutput;
|
|
1247
|
-
const data = parsed.data;
|
|
1248
|
-
if (isRecord(data) && typeof data.version === "string") {
|
|
1249
|
-
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}` : ""}`;
|
|
1250
405
|
}
|
|
1251
|
-
if (
|
|
1252
|
-
const commands = Array.isArray(data.commands)
|
|
1253
|
-
? data.commands.filter((item) => typeof item === "string")
|
|
1254
|
-
: [];
|
|
1255
|
-
const examples = Array.isArray(data.examples)
|
|
1256
|
-
? data.examples.filter((item) => typeof item === "string")
|
|
1257
|
-
: [];
|
|
406
|
+
if (result.command === "help") {
|
|
1258
407
|
return [
|
|
1259
408
|
"ProFlow Platform CLI",
|
|
1260
409
|
"",
|
|
1261
|
-
`
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
...(examples.length === 0
|
|
1266
|
-
? []
|
|
1267
|
-
: ["", "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",
|
|
1268
414
|
].join("\n");
|
|
1269
415
|
}
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
if (
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
lines.push(`Version: ${MODULE_VERSION}`, "Next: run platform --help");
|
|
1285
|
-
}
|
|
1286
|
-
return lines.join("\n");
|
|
1287
|
-
}
|
|
1288
|
-
export async function runCli(argv, runtime = {}) {
|
|
1289
|
-
const filtered = argv.filter((argument) => argument !== "--json");
|
|
1290
|
-
if (filtered.length === 1 &&
|
|
1291
|
-
(filtered[0] === "--version" || filtered[0] === "-v")) {
|
|
1292
|
-
return JSON.stringify({
|
|
1293
|
-
contract: "deployment.result.v1",
|
|
1294
|
-
ok: true,
|
|
1295
|
-
status: "SUCCEEDED",
|
|
1296
|
-
moduleRef: MODULE_REF,
|
|
1297
|
-
moduleVersion: MODULE_VERSION,
|
|
1298
|
-
data: { version: MODULE_VERSION },
|
|
1299
|
-
});
|
|
1300
|
-
}
|
|
1301
|
-
if (argv.includes("--help") || argv.includes("-h")) {
|
|
1302
|
-
return JSON.stringify({
|
|
1303
|
-
contract: "deployment.result.v1",
|
|
1304
|
-
ok: true,
|
|
1305
|
-
status: "SUCCEEDED",
|
|
1306
|
-
moduleRef: MODULE_REF,
|
|
1307
|
-
moduleVersion: MODULE_VERSION,
|
|
1308
|
-
data: {
|
|
1309
|
-
usage: "platform <command> [module|package] [--workspace <path>] [--json]",
|
|
1310
|
-
commands: [...COMMANDS],
|
|
1311
|
-
examples: [
|
|
1312
|
-
"platform preflight --intent install --workspace <path>",
|
|
1313
|
-
"platform install [module|package] [--workspace <path>]",
|
|
1314
|
-
"platform preflight # startup/runtime readiness for the bound Workspace",
|
|
1315
|
-
"platform plan --intent configure --config <file>",
|
|
1316
|
-
"platform apply <planRef>",
|
|
1317
|
-
"platform start | status | verify | doctor | stop | restart",
|
|
1318
|
-
"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",
|
|
1319
|
-
"append --json for the stable machine-readable contract",
|
|
1320
|
-
],
|
|
1321
|
-
},
|
|
1322
|
-
});
|
|
1323
|
-
}
|
|
1324
|
-
if (filtered.length === 0) {
|
|
1325
|
-
return JSON.stringify({
|
|
1326
|
-
contract: "deployment.result.v1",
|
|
1327
|
-
ok: true,
|
|
1328
|
-
status: "SUCCEEDED",
|
|
1329
|
-
moduleRef: MODULE_REF,
|
|
1330
|
-
moduleVersion: MODULE_VERSION,
|
|
1331
|
-
});
|
|
1332
|
-
}
|
|
1333
|
-
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");
|
|
1334
430
|
}
|
|
1335
431
|
if (import.meta.main) {
|
|
1336
432
|
const argv = process.argv.slice(2);
|
|
1337
433
|
const output = await runCli(argv);
|
|
1338
|
-
const
|
|
1339
|
-
process.stdout.write(`${
|
|
1340
|
-
|
|
1341
|
-
if (typeof parsed === "object" &&
|
|
1342
|
-
parsed !== null &&
|
|
1343
|
-
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")
|
|
1344
437
|
process.exitCode = 1;
|
|
1345
|
-
}
|
|
1346
438
|
}
|