@tomflow/proflow-platform-cli 0.1.16 → 0.1.18
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/CHANGELOG.md +7 -0
- package/DOCS.md +27 -0
- package/SETUP.md +27 -0
- package/dist/deployment/adapter.d.ts +58 -34
- package/dist/deployment/adapter.js +28 -12
- package/dist/deployment/descriptor.d.ts +5 -16
- package/dist/deployment/descriptor.js +10 -21
- package/dist/src/cli.js +173 -140
- package/dist/src/contracts.d.ts +0 -2
- package/dist/src/discovery/discover.d.ts +1 -3
- package/dist/src/discovery/discover.js +1 -10
- package/dist/src/errors.d.ts +1 -1
- package/dist/src/errors.js +0 -2
- package/dist/src/index.d.ts +0 -2
- package/dist/src/index.js +0 -1
- package/dist/src/lifecycle/dispatch.d.ts +4 -11
- package/dist/src/lifecycle/dispatch.js +28 -56
- package/dist/src/lifecycle/index.d.ts +4 -4
- package/dist/src/lifecycle/index.js +2 -2
- package/dist/src/lifecycle/thin.d.ts +19 -7
- package/dist/src/lifecycle/thin.js +83 -53
- package/dist/src/persistence/index.d.ts +0 -2
- package/dist/src/persistence/index.js +0 -1
- package/package.json +7 -5
- package/proflow.module.json +5 -20
- package/dist/src/binding/production-bindings.d.ts +0 -37
- package/dist/src/binding/production-bindings.js +0 -75
- package/dist/src/docs/aggregate.d.ts +0 -18
- package/dist/src/docs/aggregate.js +0 -44
- package/dist/src/docs/docs.d.ts +0 -16
- package/dist/src/docs/docs.js +0 -68
- package/dist/src/docs/index.d.ts +0 -2
- package/dist/src/docs/index.js +0 -1
- package/dist/src/persistence/config.d.ts +0 -6
- package/dist/src/persistence/config.js +0 -54
- package/dist/src/persistence/guards.d.ts +0 -1
- package/dist/src/persistence/guards.js +0 -7
package/dist/src/cli.js
CHANGED
|
@@ -3,28 +3,24 @@ import { readFile, realpath, stat } from "node:fs/promises";
|
|
|
3
3
|
import { resolve } from "node:path";
|
|
4
4
|
import { moduleStatusObservationSchema } from "@tomflow/proflow-module-contract";
|
|
5
5
|
import { descriptor as platformCliDescriptor } from "../deployment/descriptor.js";
|
|
6
|
-
import { buildProductionBindings, importRawAdapter, } from "./binding/production-bindings.js";
|
|
7
6
|
import { AutoModuleCatalog, discoverModules } from "./discovery/discover.js";
|
|
8
7
|
import { InstalledModuleCatalog } from "./discovery/installed.js";
|
|
9
|
-
import { aggregateModuleDocs } from "./docs/aggregate.js";
|
|
10
8
|
import { PlatformError } from "./errors.js";
|
|
11
9
|
import { observeWorkspaceInstalledVersion, removeWorkspacePackages, syncWorkspacePackages, } from "./install/package-manager.js";
|
|
12
|
-
import { observeStatuses,
|
|
13
|
-
import { workspacePaths } from "./paths.js";
|
|
14
|
-
import { loadConfig } from "./persistence/config.js";
|
|
10
|
+
import { installModulesThin, observeDocs, observeStatuses, setupModulesThin, startModulesThin, stopModulesThin, uninstallModulesThin, } from "./lifecycle/index.js";
|
|
15
11
|
import { ensureWorkspaceMetadata } from "./persistence/workspace-metadata.js";
|
|
16
12
|
import { discoverRegistryModules, PRO_FLOW_PACKAGE_PREFIX, } from "./registry/index.js";
|
|
17
13
|
const COMMANDS = [
|
|
18
|
-
"modules",
|
|
19
|
-
"docs",
|
|
20
14
|
"install",
|
|
21
15
|
"uninstall",
|
|
16
|
+
"status",
|
|
17
|
+
"setup",
|
|
18
|
+
"docs",
|
|
22
19
|
"start",
|
|
23
20
|
"stop",
|
|
24
21
|
];
|
|
25
22
|
function parseArgs(argv) {
|
|
26
|
-
let json = false;
|
|
27
|
-
let workspace;
|
|
23
|
+
let json = false, workspace, moduleRef, input, inputSeen = false;
|
|
28
24
|
let special;
|
|
29
25
|
const positional = [];
|
|
30
26
|
for (let index = 0; index < argv.length; index += 1) {
|
|
@@ -35,24 +31,33 @@ function parseArgs(argv) {
|
|
|
35
31
|
json = true;
|
|
36
32
|
continue;
|
|
37
33
|
}
|
|
38
|
-
if (value === "--workspace"
|
|
34
|
+
if (value === "--workspace" ||
|
|
35
|
+
value === "--module" ||
|
|
36
|
+
value === "--input") {
|
|
39
37
|
const next = argv[index + 1];
|
|
40
|
-
if (!next || next.startsWith("-"))
|
|
41
|
-
throw new PlatformError("INVALID_REQUEST",
|
|
38
|
+
if (!next || (value !== "--input" && next.startsWith("-")))
|
|
39
|
+
throw new PlatformError("INVALID_REQUEST", `${value} requires a value`);
|
|
40
|
+
if (value === "--workspace")
|
|
41
|
+
workspace = next;
|
|
42
|
+
else if (value === "--module")
|
|
43
|
+
moduleRef = next;
|
|
44
|
+
else {
|
|
45
|
+
try {
|
|
46
|
+
input = JSON.parse(next);
|
|
47
|
+
inputSeen = true;
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
throw new PlatformError("INVALID_REQUEST", `--input must be valid JSON: ${error instanceof Error ? error.message : String(error)}`);
|
|
51
|
+
}
|
|
42
52
|
}
|
|
43
|
-
workspace = next;
|
|
44
53
|
index += 1;
|
|
45
54
|
continue;
|
|
46
55
|
}
|
|
47
56
|
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
57
|
special = "help";
|
|
51
58
|
continue;
|
|
52
59
|
}
|
|
53
60
|
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
61
|
special = "version";
|
|
57
62
|
continue;
|
|
58
63
|
}
|
|
@@ -61,26 +66,32 @@ function parseArgs(argv) {
|
|
|
61
66
|
positional.push(value);
|
|
62
67
|
}
|
|
63
68
|
if (special !== undefined) {
|
|
64
|
-
if (workspace !== undefined ||
|
|
65
|
-
|
|
69
|
+
if (workspace !== undefined ||
|
|
70
|
+
moduleRef !== undefined ||
|
|
71
|
+
inputSeen ||
|
|
72
|
+
positional.length > 0)
|
|
73
|
+
throw new PlatformError("INVALID_REQUEST", `${special} flag cannot be combined with command options`);
|
|
66
74
|
return { command: special, json };
|
|
67
75
|
}
|
|
68
|
-
if (positional.length === 0)
|
|
69
|
-
if (workspace !== undefined)
|
|
70
|
-
throw new PlatformError("INVALID_REQUEST", "--workspace is only valid with install");
|
|
76
|
+
if (positional.length === 0)
|
|
71
77
|
return { command: "help", json };
|
|
72
|
-
}
|
|
73
78
|
if (positional.length !== 1)
|
|
74
79
|
throw new PlatformError("INVALID_REQUEST", "commands accept no positional arguments");
|
|
75
80
|
const raw = positional[0] ?? "";
|
|
76
81
|
if (!COMMANDS.includes(raw))
|
|
77
82
|
throw new PlatformError("INVALID_REQUEST", `unknown command ${raw}`);
|
|
78
83
|
const command = raw;
|
|
79
|
-
if (
|
|
80
|
-
throw new PlatformError("INVALID_REQUEST", "--
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
+
if ((moduleRef !== undefined || inputSeen) && command !== "setup")
|
|
85
|
+
throw new PlatformError("INVALID_REQUEST", "--module/--input are only valid with setup");
|
|
86
|
+
if (inputSeen && moduleRef === undefined)
|
|
87
|
+
throw new PlatformError("INVALID_REQUEST", "--input requires --module");
|
|
88
|
+
return {
|
|
89
|
+
command,
|
|
90
|
+
json,
|
|
91
|
+
...(workspace === undefined ? {} : { workspace }),
|
|
92
|
+
...(moduleRef === undefined ? {} : { moduleRef }),
|
|
93
|
+
...(inputSeen ? { input } : {}),
|
|
94
|
+
};
|
|
84
95
|
}
|
|
85
96
|
function outcome(command, status, workspaceRoot, data) {
|
|
86
97
|
return {
|
|
@@ -99,32 +110,12 @@ async function canonicalWorkspace(cwd, explicit) {
|
|
|
99
110
|
catch (error) {
|
|
100
111
|
throw new PlatformError("WORKSPACE_NOT_FOUND", `Workspace does not exist: ${candidate} (${error instanceof Error ? error.message : String(error)})`);
|
|
101
112
|
}
|
|
102
|
-
if (!info.isDirectory())
|
|
113
|
+
if (!info.isDirectory())
|
|
103
114
|
throw new PlatformError("WORKSPACE_NOT_FOUND", `Workspace is not a directory: ${candidate}`);
|
|
104
|
-
}
|
|
105
115
|
return realpath(candidate);
|
|
106
116
|
}
|
|
107
|
-
async function loadConfigMap(root, modules) {
|
|
108
|
-
const paths = workspacePaths(root);
|
|
109
|
-
const configByModuleRef = new Map();
|
|
110
|
-
for (const module of modules) {
|
|
111
|
-
const config = await loadConfig(paths, module.moduleRef);
|
|
112
|
-
if (config === undefined)
|
|
113
|
-
continue;
|
|
114
|
-
configByModuleRef.set(module.moduleRef, config);
|
|
115
|
-
}
|
|
116
|
-
return configByModuleRef;
|
|
117
|
-
}
|
|
118
117
|
async function buildContext(root) {
|
|
119
|
-
const
|
|
120
|
-
const configByModuleRef = await loadConfigMap(root, discovered);
|
|
121
|
-
const bindings = await buildProductionBindings({
|
|
122
|
-
workspaceRoot: root,
|
|
123
|
-
modules: discovered,
|
|
124
|
-
configByModuleRef,
|
|
125
|
-
importAdapter: (packageName, source) => importRawAdapter(packageName, source, root),
|
|
126
|
-
});
|
|
127
|
-
const catalog = new AutoModuleCatalog(root, bindings);
|
|
118
|
+
const catalog = new AutoModuleCatalog(root);
|
|
128
119
|
const modules = await discoverModules({ workspaceRoot: root, catalog });
|
|
129
120
|
return { catalog, modules };
|
|
130
121
|
}
|
|
@@ -137,62 +128,69 @@ function statusFromModule(value) {
|
|
|
137
128
|
? "BLOCKED"
|
|
138
129
|
: "FAILED";
|
|
139
130
|
}
|
|
140
|
-
function
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
131
|
+
function batchStatus(result) {
|
|
132
|
+
if (result.completed)
|
|
133
|
+
return "SUCCEEDED";
|
|
134
|
+
if (result.blockedBy)
|
|
135
|
+
return result.blockedBy.setupStatus === "ACTION_REQUIRED"
|
|
136
|
+
? "ACTION_REQUIRED"
|
|
137
|
+
: "FAILED";
|
|
138
|
+
const statuses = result.results.map((item) => statusFromModule(item.result.status));
|
|
139
|
+
if (statuses.includes("FAILED"))
|
|
140
|
+
return "FAILED";
|
|
141
|
+
if (statuses.includes("BLOCKED"))
|
|
142
|
+
return "BLOCKED";
|
|
143
|
+
if (statuses.includes("ACTION_REQUIRED"))
|
|
144
|
+
return "ACTION_REQUIRED";
|
|
145
|
+
return "FAILED";
|
|
145
146
|
}
|
|
146
|
-
async function
|
|
147
|
+
async function handleStatus(root) {
|
|
147
148
|
const { catalog, modules } = await buildContext(root);
|
|
148
|
-
const observed = await observeStatuses(catalog, modules);
|
|
149
|
+
const observed = await observeStatuses(catalog, modules, root);
|
|
149
150
|
const byRef = new Map(modules.map((module) => [module.moduleRef, module]));
|
|
150
151
|
const output = observed.map((item) => {
|
|
151
|
-
if (item.result.status !== "SUCCEEDED")
|
|
152
|
+
if (item.result.status !== "SUCCEEDED")
|
|
152
153
|
throw new PlatformError("COMMAND_FAILED", `module ${item.moduleRef} status observation did not return SUCCEEDED`);
|
|
153
|
-
}
|
|
154
154
|
const parsed = moduleStatusObservationSchema.safeParse(item.result.data);
|
|
155
|
-
if (!parsed.success)
|
|
156
|
-
throw new PlatformError("COMMAND_FAILED", `module ${item.moduleRef} returned
|
|
157
|
-
}
|
|
155
|
+
if (!parsed.success)
|
|
156
|
+
throw new PlatformError("COMMAND_FAILED", `module ${item.moduleRef} returned invalid status: ${parsed.error.message}`);
|
|
158
157
|
const module = byRef.get(item.moduleRef);
|
|
159
|
-
if (module
|
|
158
|
+
if (!module)
|
|
160
159
|
throw new PlatformError("COMMAND_FAILED", `unknown module ${item.moduleRef}`);
|
|
161
160
|
return {
|
|
162
161
|
moduleRef: item.moduleRef,
|
|
163
162
|
version: module.moduleVersion,
|
|
164
|
-
|
|
165
|
-
...(parsed.data.missingConfig === undefined
|
|
166
|
-
? {}
|
|
167
|
-
: { missingConfig: parsed.data.missingConfig }),
|
|
163
|
+
setupStatus: parsed.data.setupStatus,
|
|
168
164
|
runtimeStatus: parsed.data.runtimeStatus,
|
|
169
165
|
};
|
|
170
166
|
});
|
|
171
|
-
return outcome("
|
|
167
|
+
return outcome("status", "SUCCEEDED", root, { modules: output });
|
|
172
168
|
}
|
|
173
169
|
async function handleDocs(root) {
|
|
174
|
-
const catalog =
|
|
175
|
-
const
|
|
176
|
-
const
|
|
177
|
-
return outcome("docs", "SUCCEEDED", root, {
|
|
170
|
+
const { catalog, modules } = await buildContext(root);
|
|
171
|
+
const docs = await observeDocs(catalog, modules, root);
|
|
172
|
+
const byRef = new Map(modules.map((module) => [module.moduleRef, module]));
|
|
173
|
+
return outcome("docs", "SUCCEEDED", root, {
|
|
174
|
+
modules: docs.map((item) => ({
|
|
175
|
+
moduleRef: item.moduleRef,
|
|
176
|
+
version: byRef.get(item.moduleRef)?.moduleVersion,
|
|
177
|
+
docs: item.result.data,
|
|
178
|
+
})),
|
|
179
|
+
});
|
|
178
180
|
}
|
|
179
181
|
async function validateInstalledPackageSet(root, candidates, previousManaged) {
|
|
180
182
|
const expectedNames = candidates.map((item) => item.packageName).sort();
|
|
181
183
|
const declaredNames = await workspaceProFlowDependencies(root);
|
|
182
|
-
if (JSON.stringify(declaredNames) !== JSON.stringify(expectedNames))
|
|
184
|
+
if (JSON.stringify(declaredNames) !== JSON.stringify(expectedNames))
|
|
183
185
|
throw new PlatformError("COMMAND_FAILED", `managed dependency set mismatch: expected ${expectedNames.join(", ")}, observed ${declaredNames.join(", ")}`);
|
|
184
|
-
}
|
|
185
186
|
for (const candidate of candidates) {
|
|
186
187
|
const observed = await observeWorkspaceInstalledVersion(root, candidate.packageName);
|
|
187
|
-
if (observed !== candidate.moduleVersion)
|
|
188
|
+
if (observed !== candidate.moduleVersion)
|
|
188
189
|
throw new PlatformError("COMMAND_FAILED", `installed version mismatch for ${candidate.packageName}: expected ${candidate.moduleVersion}, observed ${observed ?? "missing"}`);
|
|
189
|
-
}
|
|
190
190
|
}
|
|
191
|
-
for (const stale of previousManaged.filter((name) => !expectedNames.includes(name)))
|
|
192
|
-
if ((await observeWorkspaceInstalledVersion(root, stale)) !== undefined)
|
|
191
|
+
for (const stale of previousManaged.filter((name) => !expectedNames.includes(name)))
|
|
192
|
+
if ((await observeWorkspaceInstalledVersion(root, stale)) !== undefined)
|
|
193
193
|
throw new PlatformError("COMMAND_FAILED", `stale managed package remains installed after synchronization: ${stale}`);
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
194
|
const catalog = new InstalledModuleCatalog(root);
|
|
197
195
|
const sources = await catalog.sources();
|
|
198
196
|
const modules = await discoverModules({ catalog, sources });
|
|
@@ -200,9 +198,8 @@ async function validateInstalledPackageSet(root, candidates, previousManaged) {
|
|
|
200
198
|
for (const candidate of candidates) {
|
|
201
199
|
const module = byPackage.get(candidate.packageName);
|
|
202
200
|
if (module === undefined ||
|
|
203
|
-
module.moduleVersion !== candidate.moduleVersion)
|
|
201
|
+
module.moduleVersion !== candidate.moduleVersion)
|
|
204
202
|
throw new PlatformError("DESCRIPTOR_INVALID", `installed descriptor mismatch for ${candidate.packageName}@${candidate.moduleVersion}`);
|
|
205
|
-
}
|
|
206
203
|
}
|
|
207
204
|
}
|
|
208
205
|
async function handleInstall(root, runtime) {
|
|
@@ -212,12 +209,10 @@ async function handleInstall(root, runtime) {
|
|
|
212
209
|
? {}
|
|
213
210
|
: { runner: runtime.registryRunner }),
|
|
214
211
|
});
|
|
215
|
-
if (discovered.rejected.length > 0)
|
|
212
|
+
if (discovered.rejected.length > 0)
|
|
216
213
|
throw new PlatformError("REGISTRY_RESPONSE_INVALID", `registry contains rejected ProFlow packages: ${discovered.rejected.map((item) => `${item.packageName}:${item.reason}`).join(", ")}`);
|
|
217
|
-
|
|
218
|
-
if (discovered.candidates.length === 0) {
|
|
214
|
+
if (discovered.candidates.length === 0)
|
|
219
215
|
throw new PlatformError("PACKAGE_NOT_FOUND", "no ProFlow packages were discovered in the configured scope");
|
|
220
|
-
}
|
|
221
216
|
const previousManaged = await workspaceProFlowDependencies(root);
|
|
222
217
|
const mutation = await syncWorkspacePackages({
|
|
223
218
|
workspaceRoot: root,
|
|
@@ -234,7 +229,9 @@ async function handleInstall(root, runtime) {
|
|
|
234
229
|
});
|
|
235
230
|
await validateInstalledPackageSet(root, discovered.candidates, previousManaged);
|
|
236
231
|
const metadata = await ensureWorkspaceMetadata(root);
|
|
237
|
-
|
|
232
|
+
const { catalog, modules } = await buildContext(root);
|
|
233
|
+
const moduleInstall = await installModulesThin(catalog, modules, root);
|
|
234
|
+
return outcome("install", batchStatus(moduleInstall), root, {
|
|
238
235
|
registry: discovered.registry,
|
|
239
236
|
packageManager: mutation.packageManager,
|
|
240
237
|
packages: discovered.candidates.map((item) => ({
|
|
@@ -242,7 +239,8 @@ async function handleInstall(root, runtime) {
|
|
|
242
239
|
version: item.moduleVersion,
|
|
243
240
|
})),
|
|
244
241
|
workspace: metadata,
|
|
245
|
-
|
|
242
|
+
modules: moduleInstall,
|
|
243
|
+
next: "platform status",
|
|
246
244
|
});
|
|
247
245
|
}
|
|
248
246
|
async function workspaceProFlowDependencies(root) {
|
|
@@ -256,10 +254,9 @@ async function workspaceProFlowDependencies(root) {
|
|
|
256
254
|
const value = record[field];
|
|
257
255
|
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
258
256
|
continue;
|
|
259
|
-
for (const name of Object.keys(value))
|
|
257
|
+
for (const name of Object.keys(value))
|
|
260
258
|
if (name.startsWith(PRO_FLOW_PACKAGE_PREFIX))
|
|
261
259
|
names.add(name);
|
|
262
|
-
}
|
|
263
260
|
}
|
|
264
261
|
return [...names].sort();
|
|
265
262
|
}
|
|
@@ -273,6 +270,13 @@ async function workspaceProFlowDependencies(root) {
|
|
|
273
270
|
}
|
|
274
271
|
async function handleUninstall(root, runtime) {
|
|
275
272
|
const packageNames = await workspaceProFlowDependencies(root);
|
|
273
|
+
const { catalog, modules } = await buildContext(root);
|
|
274
|
+
const moduleUninstall = await uninstallModulesThin(catalog, modules, root);
|
|
275
|
+
if (!moduleUninstall.completed)
|
|
276
|
+
return outcome("uninstall", batchStatus(moduleUninstall), root, {
|
|
277
|
+
modules: moduleUninstall,
|
|
278
|
+
removed: [],
|
|
279
|
+
});
|
|
276
280
|
const mutation = await removeWorkspacePackages({
|
|
277
281
|
workspaceRoot: root,
|
|
278
282
|
packageNames,
|
|
@@ -284,26 +288,39 @@ async function handleUninstall(root, runtime) {
|
|
|
284
288
|
: { executableAvailable: runtime.executableAvailable }),
|
|
285
289
|
});
|
|
286
290
|
return outcome("uninstall", "SUCCEEDED", root, {
|
|
291
|
+
modules: moduleUninstall,
|
|
287
292
|
packageManager: mutation.packageManager,
|
|
288
293
|
removed: packageNames,
|
|
289
294
|
preserved: [".proflow"],
|
|
290
295
|
});
|
|
291
296
|
}
|
|
297
|
+
async function handleSetup(root, parsed) {
|
|
298
|
+
const { catalog, modules } = await buildContext(root);
|
|
299
|
+
const target = parsed.moduleRef === undefined
|
|
300
|
+
? undefined
|
|
301
|
+
: {
|
|
302
|
+
moduleRef: parsed.moduleRef,
|
|
303
|
+
...(Object.hasOwn(parsed, "input") ? { input: parsed.input } : {}),
|
|
304
|
+
};
|
|
305
|
+
const result = await setupModulesThin(catalog, modules, root, target);
|
|
306
|
+
return outcome("setup", batchStatus(result), root, result);
|
|
307
|
+
}
|
|
292
308
|
async function handleStart(root) {
|
|
293
309
|
const { catalog, modules } = await buildContext(root);
|
|
294
|
-
const result = await
|
|
295
|
-
return outcome("start",
|
|
310
|
+
const result = await startModulesThin(catalog, modules, root);
|
|
311
|
+
return outcome("start", batchStatus(result), root, result);
|
|
296
312
|
}
|
|
297
313
|
async function handleStop(root) {
|
|
298
314
|
const { catalog, modules } = await buildContext(root);
|
|
299
|
-
const result = await stopModulesThin(catalog, modules);
|
|
300
|
-
return outcome("stop",
|
|
315
|
+
const result = await stopModulesThin(catalog, modules, root);
|
|
316
|
+
return outcome("stop", batchStatus(result), root, result);
|
|
301
317
|
}
|
|
302
318
|
function helpOutcome() {
|
|
303
319
|
return outcome("help", "SUCCEEDED", undefined, {
|
|
304
|
-
usage: "platform <
|
|
320
|
+
usage: "platform <install|uninstall|status|setup|docs|start|stop> [--workspace <path>] [--json]",
|
|
305
321
|
commands: [...COMMANDS],
|
|
306
322
|
install: "platform install [--workspace <path>]",
|
|
323
|
+
setup: "platform setup [--workspace <path>] [--module <moduleRef> --input '<json>']",
|
|
307
324
|
});
|
|
308
325
|
}
|
|
309
326
|
export async function runCli(argv, runtime = {}) {
|
|
@@ -317,24 +334,22 @@ export async function runCli(argv, runtime = {}) {
|
|
|
317
334
|
try {
|
|
318
335
|
if (parsed.command === "help")
|
|
319
336
|
return JSON.stringify(helpOutcome());
|
|
320
|
-
if (parsed.command === "version")
|
|
337
|
+
if (parsed.command === "version")
|
|
321
338
|
return JSON.stringify(outcome("version", "SUCCEEDED", undefined, {
|
|
322
339
|
version: platformCliDescriptor.moduleVersion,
|
|
323
340
|
}));
|
|
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;
|
|
341
|
+
const root = await canonicalWorkspace(runtime.cwd ?? process.cwd(), parsed.workspace);
|
|
329
342
|
switch (parsed.command) {
|
|
330
|
-
case "modules":
|
|
331
|
-
return JSON.stringify(await handleModules(root));
|
|
332
|
-
case "docs":
|
|
333
|
-
return JSON.stringify(await handleDocs(root));
|
|
334
343
|
case "install":
|
|
335
344
|
return JSON.stringify(await handleInstall(root, runtime));
|
|
336
345
|
case "uninstall":
|
|
337
346
|
return JSON.stringify(await handleUninstall(root, runtime));
|
|
347
|
+
case "status":
|
|
348
|
+
return JSON.stringify(await handleStatus(root));
|
|
349
|
+
case "setup":
|
|
350
|
+
return JSON.stringify(await handleSetup(root, parsed));
|
|
351
|
+
case "docs":
|
|
352
|
+
return JSON.stringify(await handleDocs(root));
|
|
338
353
|
case "start":
|
|
339
354
|
return JSON.stringify(await handleStart(root));
|
|
340
355
|
case "stop":
|
|
@@ -346,13 +361,12 @@ export async function runCli(argv, runtime = {}) {
|
|
|
346
361
|
}
|
|
347
362
|
}
|
|
348
363
|
function errorOutcome(command, error) {
|
|
349
|
-
if (error instanceof PlatformError)
|
|
364
|
+
if (error instanceof PlatformError)
|
|
350
365
|
return {
|
|
351
366
|
command,
|
|
352
367
|
status: "FAILED",
|
|
353
368
|
error: { code: error.code, message: error.message },
|
|
354
369
|
};
|
|
355
|
-
}
|
|
356
370
|
return {
|
|
357
371
|
command,
|
|
358
372
|
status: "FAILED",
|
|
@@ -362,62 +376,81 @@ function errorOutcome(command, error) {
|
|
|
362
376
|
},
|
|
363
377
|
};
|
|
364
378
|
}
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
}
|
|
368
|
-
function renderModules(data) {
|
|
379
|
+
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
380
|
+
function renderStatus(data) {
|
|
369
381
|
if (!isRecord(data) || !Array.isArray(data.modules))
|
|
370
382
|
return "No modules.";
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
.join(",")
|
|
379
|
-
: "-";
|
|
380
|
-
lines.push(`${String(raw.moduleRef)} ${String(raw.version)} config=${String(raw.configStatus)} runtime=${String(raw.runtimeStatus)} missing=${missing}`);
|
|
381
|
-
}
|
|
382
|
-
return lines.join("\n");
|
|
383
|
+
return [
|
|
384
|
+
"ProFlow Modules",
|
|
385
|
+
"",
|
|
386
|
+
...data.modules
|
|
387
|
+
.filter(isRecord)
|
|
388
|
+
.map((raw) => `${String(raw.moduleRef)} ${String(raw.version)} setup=${String(raw.setupStatus)} runtime=${String(raw.runtimeStatus)}`),
|
|
389
|
+
].join("\n");
|
|
383
390
|
}
|
|
384
391
|
function renderDocs(data) {
|
|
385
392
|
if (!isRecord(data) || !Array.isArray(data.modules))
|
|
386
393
|
return "No module docs.";
|
|
387
394
|
const lines = ["ProFlow Docs"];
|
|
388
|
-
for (const raw of data.modules)
|
|
389
|
-
if (
|
|
395
|
+
for (const raw of data.modules)
|
|
396
|
+
if (isRecord(raw))
|
|
397
|
+
lines.push("", `## ${String(raw.moduleRef)} @ ${String(raw.version)}`, JSON.stringify(raw.docs ?? {}, null, 2));
|
|
398
|
+
return lines.join("\n");
|
|
399
|
+
}
|
|
400
|
+
function renderSetup(data) {
|
|
401
|
+
if (!isRecord(data) || !Array.isArray(data.results))
|
|
402
|
+
return "No setup actions are required.";
|
|
403
|
+
const lines = ["ProFlow Setup", ""];
|
|
404
|
+
let automatic = 0;
|
|
405
|
+
let pending = 0;
|
|
406
|
+
for (const raw of data.results) {
|
|
407
|
+
if (!isRecord(raw) || !isRecord(raw.result))
|
|
390
408
|
continue;
|
|
391
|
-
|
|
392
|
-
|
|
409
|
+
const moduleRef = String(raw.moduleRef ?? raw.result.moduleRef ?? "unknown");
|
|
410
|
+
const status = String(raw.result.status ?? "UNKNOWN");
|
|
411
|
+
if (status === "SUCCEEDED") {
|
|
412
|
+
automatic += 1;
|
|
393
413
|
continue;
|
|
394
|
-
for (const document of raw.documents) {
|
|
395
|
-
if (!isRecord(document))
|
|
396
|
-
continue;
|
|
397
|
-
lines.push("", `### ${String(document.id)}`, String(document.content ?? ""));
|
|
398
414
|
}
|
|
415
|
+
pending += 1;
|
|
416
|
+
lines.push(`## ${moduleRef} — ${status}`);
|
|
417
|
+
const actionRequired = raw.result.actionRequired;
|
|
418
|
+
if (isRecord(actionRequired)) {
|
|
419
|
+
if (actionRequired.action !== undefined)
|
|
420
|
+
lines.push(`Action: ${String(actionRequired.action)}`);
|
|
421
|
+
if (actionRequired.description !== undefined)
|
|
422
|
+
lines.push(String(actionRequired.description));
|
|
423
|
+
}
|
|
424
|
+
else if (raw.result.data !== undefined) {
|
|
425
|
+
lines.push(JSON.stringify(raw.result.data, null, 2));
|
|
426
|
+
}
|
|
427
|
+
lines.push("");
|
|
399
428
|
}
|
|
400
|
-
|
|
429
|
+
if (pending === 0)
|
|
430
|
+
lines.push("All discovered Modules are setup-ready.");
|
|
431
|
+
if (automatic > 0)
|
|
432
|
+
lines.push(`Automatically completed in this run: ${automatic}`);
|
|
433
|
+
return lines.join("\n").trimEnd();
|
|
401
434
|
}
|
|
402
435
|
export function renderHumanResult(result) {
|
|
403
|
-
if (result.status === "FAILED")
|
|
436
|
+
if (result.status === "FAILED")
|
|
404
437
|
return `${result.command.toUpperCase()} FAILED${result.error ? ` [${result.error.code}] ${result.error.message}` : ""}`;
|
|
405
|
-
|
|
406
|
-
if (result.command === "help") {
|
|
438
|
+
if (result.command === "help")
|
|
407
439
|
return [
|
|
408
440
|
"ProFlow Platform CLI",
|
|
409
441
|
"",
|
|
410
442
|
...COMMANDS.map((command) => `platform ${command}`),
|
|
411
443
|
"",
|
|
412
444
|
"platform install --workspace <path>",
|
|
445
|
+
"platform setup --module <moduleRef> --input '<json>'",
|
|
413
446
|
"append --json for machine-readable output",
|
|
414
447
|
].join("\n");
|
|
415
|
-
|
|
416
|
-
if (result.command === "version" && isRecord(result.data)) {
|
|
448
|
+
if (result.command === "version" && isRecord(result.data))
|
|
417
449
|
return String(result.data.version ?? platformCliDescriptor.moduleVersion);
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
450
|
+
if (result.command === "status")
|
|
451
|
+
return renderStatus(result.data);
|
|
452
|
+
if (result.command === "setup")
|
|
453
|
+
return renderSetup(result.data);
|
|
421
454
|
if (result.command === "docs")
|
|
422
455
|
return renderDocs(result.data);
|
|
423
456
|
return [
|
package/dist/src/contracts.d.ts
CHANGED
|
@@ -10,8 +10,6 @@ export interface ResolvedModule {
|
|
|
10
10
|
requires: ModuleDescriptor["requires"];
|
|
11
11
|
requirements: ModuleRequirement[];
|
|
12
12
|
configSlots: ConfigSlot[];
|
|
13
|
-
lifecycle: string[];
|
|
14
|
-
verification: ModuleDescriptor["verification"];
|
|
15
13
|
effects: DeploymentEffect[];
|
|
16
14
|
source: {
|
|
17
15
|
type: "workspace" | "installed";
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { DeploymentAdapterBinding } from "../binding/production-bindings.ts";
|
|
2
1
|
import type { ResolvedModule } from "../contracts.ts";
|
|
3
2
|
import type { ModuleCatalog, ModuleSource } from "../modules.ts";
|
|
4
3
|
export interface DiscoverOptions {
|
|
@@ -9,8 +8,7 @@ export interface DiscoverOptions {
|
|
|
9
8
|
export declare class AutoModuleCatalog implements ModuleCatalog {
|
|
10
9
|
private readonly workspace;
|
|
11
10
|
private readonly installed;
|
|
12
|
-
|
|
13
|
-
constructor(root?: string, bindings?: ReadonlyMap<string, DeploymentAdapterBinding>);
|
|
11
|
+
constructor(root?: string);
|
|
14
12
|
sources(): Promise<ModuleSource[]>;
|
|
15
13
|
loadDescriptor(source: ModuleSource): Promise<unknown>;
|
|
16
14
|
loadAdapter(source: ModuleSource): Promise<unknown>;
|
|
@@ -7,11 +7,9 @@ import { readPackageJson } from "./workspace.js";
|
|
|
7
7
|
export class AutoModuleCatalog {
|
|
8
8
|
workspace;
|
|
9
9
|
installed;
|
|
10
|
-
|
|
11
|
-
constructor(root, bindings) {
|
|
10
|
+
constructor(root) {
|
|
12
11
|
this.workspace = new WorkspaceModuleCatalog(root);
|
|
13
12
|
this.installed = new InstalledModuleCatalog(root);
|
|
14
|
-
this.bindings = bindings ?? new Map();
|
|
15
13
|
}
|
|
16
14
|
async sources() {
|
|
17
15
|
const workspaceSources = await this.workspace.sources();
|
|
@@ -32,11 +30,6 @@ export class AutoModuleCatalog {
|
|
|
32
30
|
: this.installed.loadDescriptor(source);
|
|
33
31
|
}
|
|
34
32
|
async loadAdapter(source) {
|
|
35
|
-
// A production binder may supply a bound adapter for a service module
|
|
36
|
-
// (createBehaviorAdapter(realService)); it wins over the unbound default.
|
|
37
|
-
const binding = this.bindings.get(source.packageName);
|
|
38
|
-
if (binding !== undefined)
|
|
39
|
-
return binding;
|
|
40
33
|
return source.type === "workspace"
|
|
41
34
|
? this.workspace.loadAdapter(source)
|
|
42
35
|
: this.installed.loadAdapter(source);
|
|
@@ -96,8 +89,6 @@ function toResolvedModule(descriptor, source) {
|
|
|
96
89
|
requires: descriptor.requires,
|
|
97
90
|
requirements: descriptor.requirements,
|
|
98
91
|
configSlots: descriptor.configSlots,
|
|
99
|
-
lifecycle: descriptor.lifecycle.supported,
|
|
100
|
-
verification: descriptor.verification,
|
|
101
92
|
effects: descriptor.effects,
|
|
102
93
|
source: resolvedSource,
|
|
103
94
|
};
|
package/dist/src/errors.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const platformErrorCodes: readonly ["INVALID_REQUEST", "DESCRIPTOR_INVALID", "DUPLICATE_IDENTITY", "DEPENDENCY_UNRESOLVED", "DEPENDENCY_INCOMPATIBLE", "DEPENDENCY_CYCLE", "
|
|
1
|
+
export declare const platformErrorCodes: readonly ["INVALID_REQUEST", "DESCRIPTOR_INVALID", "DUPLICATE_IDENTITY", "DEPENDENCY_UNRESOLVED", "DEPENDENCY_INCOMPATIBLE", "DEPENDENCY_CYCLE", "WORKSPACE_NOT_FOUND", "WORKSPACE_INSTANCE_INVALID", "PACKAGE_MANAGER_UNSUPPORTED", "PACKAGE_MANAGER_CONFLICT", "PACKAGE_MANAGER_UNAVAILABLE", "UNINSTALL_FAILED", "REGISTRY_UNAVAILABLE", "REGISTRY_AUTH_REQUIRED", "REGISTRY_RESPONSE_INVALID", "PACKAGE_NOT_FOUND", "PACKAGE_NOT_PROFLOW", "COMMAND_FAILED"];
|
|
2
2
|
export type PlatformErrorCode = (typeof platformErrorCodes)[number];
|
|
3
3
|
export declare class PlatformError extends Error {
|
|
4
4
|
readonly code: PlatformErrorCode;
|
package/dist/src/errors.js
CHANGED
|
@@ -5,13 +5,11 @@ export const platformErrorCodes = [
|
|
|
5
5
|
"DEPENDENCY_UNRESOLVED",
|
|
6
6
|
"DEPENDENCY_INCOMPATIBLE",
|
|
7
7
|
"DEPENDENCY_CYCLE",
|
|
8
|
-
"CONFIG_INVALID",
|
|
9
8
|
"WORKSPACE_NOT_FOUND",
|
|
10
9
|
"WORKSPACE_INSTANCE_INVALID",
|
|
11
10
|
"PACKAGE_MANAGER_UNSUPPORTED",
|
|
12
11
|
"PACKAGE_MANAGER_CONFLICT",
|
|
13
12
|
"PACKAGE_MANAGER_UNAVAILABLE",
|
|
14
|
-
"LIFECYCLE_UNSUPPORTED",
|
|
15
13
|
"UNINSTALL_FAILED",
|
|
16
14
|
"REGISTRY_UNAVAILABLE",
|
|
17
15
|
"REGISTRY_AUTH_REQUIRED",
|
package/dist/src/index.d.ts
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
export type { CliOutcome, CliRuntimeOptions, CliStatus, } from "./cli.ts";
|
|
2
2
|
export { renderHumanResult, runCli } from "./cli.ts";
|
|
3
3
|
export { AutoModuleCatalog, discoverModules } from "./discovery/discover.ts";
|
|
4
|
-
export type { AggregatedDocument, AggregatedModuleDocs, } from "./docs/aggregate.ts";
|
|
5
|
-
export { aggregateModuleDocs } from "./docs/aggregate.ts";
|
|
6
4
|
export type { PlatformErrorCode } from "./errors.ts";
|
|
7
5
|
export { PlatformError } from "./errors.ts";
|
|
8
6
|
export type { ModuleCatalog, ModuleSource } from "./modules.ts";
|
package/dist/src/index.js
CHANGED
|
@@ -1,17 +1,10 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type ModuleCommandContext, type ModuleManagementCommand, type ModuleOperationResult } from "@tomflow/proflow-module-contract";
|
|
2
2
|
import type { ResolvedModule } from "../contracts.ts";
|
|
3
3
|
import type { ModuleCatalog } from "../modules.ts";
|
|
4
|
-
export interface
|
|
4
|
+
export interface ModuleDispatchResult {
|
|
5
5
|
moduleRef: string;
|
|
6
|
-
|
|
6
|
+
command: ModuleManagementCommand;
|
|
7
7
|
result: ModuleOperationResult;
|
|
8
8
|
observedEffects: string[];
|
|
9
9
|
}
|
|
10
|
-
|
|
11
|
-
* Dispatches a single lifecycle primitive against one module through its
|
|
12
|
-
* public deployment adapter. The descriptor is the source of truth for what is
|
|
13
|
-
* supported: a primitive not declared in `lifecycle` is rejected with
|
|
14
|
-
* `LIFECYCLE_UNSUPPORTED` rather than faked. The adapter result is always
|
|
15
|
-
* runtime-validated against the Module Operation Result schema.
|
|
16
|
-
*/
|
|
17
|
-
export declare function dispatchLifecycle(catalog: ModuleCatalog, module: ResolvedModule, primitive: LifecyclePrimitive): Promise<LifecycleDispatchResult>;
|
|
10
|
+
export declare function dispatchModuleCommand(catalog: ModuleCatalog, module: ResolvedModule, command: ModuleManagementCommand, context: ModuleCommandContext): Promise<ModuleDispatchResult>;
|