@tomflow/proflow-platform-cli 0.1.21 → 0.1.23
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 +12 -0
- package/README.md +1 -1
- package/SETUP.md +8 -25
- package/dist/deployment/adapter.d.ts +7 -8
- package/dist/deployment/adapter.js +7 -1
- package/dist/deployment/descriptor.d.ts +1 -1
- package/dist/deployment/descriptor.js +1 -1
- package/dist/src/cli.d.ts +3 -1
- package/dist/src/cli.js +240 -102
- package/dist/src/command.d.ts +1 -0
- package/dist/src/command.js +3 -0
- package/dist/src/install/pnpm-policy.d.ts +3 -0
- package/dist/src/install/pnpm-policy.js +68 -0
- package/dist/src/lifecycle/thin.d.ts +16 -7
- package/dist/src/lifecycle/thin.js +217 -34
- package/dist/src/progress.d.ts +12 -0
- package/dist/src/progress.js +1 -0
- package/dist/src/terminal.d.ts +2 -0
- package/dist/src/terminal.js +44 -0
- package/package.json +5 -4
- package/proflow.module.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @tomflow/proflow-platform-cli
|
|
2
2
|
|
|
3
|
+
## 0.1.23
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Resolve DOCS.md from both source and built deployment adapters, prove all 24 built adapters return the published body, and announce package synchronization before the package manager blocks.
|
|
8
|
+
|
|
9
|
+
## 0.1.22
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- Replace JSON terminal protocols with typed CLI outcomes, add real-time Chinese progress and lifecycle summaries, publish DOCS content, standardize executable setup plans and package-owned setup commands, and preserve only user-owned pnpm policy during uninstall.
|
|
14
|
+
|
|
3
15
|
## 0.1.21
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -51,7 +51,7 @@ There is no Platform-derived configuration status, missing-input list, overall r
|
|
|
51
51
|
|
|
52
52
|
`platform setup` scans all discovered Modules in dependency order. It skips `READY` Modules, invokes `Module.setup` for every non-ready Module, continues after `ACTION_REQUIRED` or `FAILED`, and returns one complete aggregate.
|
|
53
53
|
|
|
54
|
-
Targeted `platform setup --module <moduleRef
|
|
54
|
+
Targeted `platform setup --module <moduleRef>` re-observes one Module. Human or AI input is collected by the package-owned setup command shown by `platform setup`; Platform does not accept raw JSON input.
|
|
55
55
|
|
|
56
56
|
## Docs
|
|
57
57
|
|
package/SETUP.md
CHANGED
|
@@ -1,27 +1,10 @@
|
|
|
1
1
|
# @tomflow/proflow-platform-cli — Module Setup
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
**Success condition:**
|
|
14
|
-
|
|
15
|
-
`platform status` reports `platform-cli setup=READY`. No user/private path, token, loopback endpoint, or cross-Module fact is requested.
|
|
16
|
-
|
|
17
|
-
## Step 2 — Run the full workspace setup guide
|
|
18
|
-
|
|
19
|
-
**Executable:**
|
|
20
|
-
|
|
21
|
-
```bash
|
|
22
|
-
platform setup
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
**Success condition:**
|
|
26
|
-
|
|
27
|
-
All automatically solvable Module setup work is completed in the same run, and every remaining human/external action is listed together. Re-running the command re-observes reality and continues until all required Modules report `setupStatus=READY`.
|
|
3
|
+
## STEP-PLATFORM-CLI-01 — 完成模块自动配置
|
|
4
|
+
|
|
5
|
+
Responsible: AI
|
|
6
|
+
Interactive executable: `platform setup --module platform-cli`
|
|
7
|
+
Non-interactive executable: `platform setup --module platform-cli`
|
|
8
|
+
Required inputs: none
|
|
9
|
+
Verify: `platform status`
|
|
10
|
+
Success condition: `platform-cli.setupStatus=READY`.
|
|
@@ -6,7 +6,7 @@ export declare const behaviorAdapter: {
|
|
|
6
6
|
readonly ok: true;
|
|
7
7
|
readonly status: "SUCCEEDED";
|
|
8
8
|
readonly moduleRef: "platform-cli";
|
|
9
|
-
readonly moduleVersion: "0.1.
|
|
9
|
+
readonly moduleVersion: "0.1.23";
|
|
10
10
|
};
|
|
11
11
|
observedEffects: never[];
|
|
12
12
|
}>;
|
|
@@ -16,7 +16,7 @@ export declare const behaviorAdapter: {
|
|
|
16
16
|
readonly ok: true;
|
|
17
17
|
readonly status: "SUCCEEDED";
|
|
18
18
|
readonly moduleRef: "platform-cli";
|
|
19
|
-
readonly moduleVersion: "0.1.
|
|
19
|
+
readonly moduleVersion: "0.1.23";
|
|
20
20
|
};
|
|
21
21
|
observedEffects: never[];
|
|
22
22
|
}>;
|
|
@@ -26,7 +26,7 @@ export declare const behaviorAdapter: {
|
|
|
26
26
|
ok: true;
|
|
27
27
|
status: "SUCCEEDED";
|
|
28
28
|
moduleRef: "platform-cli";
|
|
29
|
-
moduleVersion: "0.1.
|
|
29
|
+
moduleVersion: "0.1.23";
|
|
30
30
|
data: {
|
|
31
31
|
readonly setupStatus: "READY";
|
|
32
32
|
readonly runtimeStatus: "NOT_APPLICABLE";
|
|
@@ -40,7 +40,7 @@ export declare const behaviorAdapter: {
|
|
|
40
40
|
readonly ok: true;
|
|
41
41
|
readonly status: "SUCCEEDED";
|
|
42
42
|
readonly moduleRef: "platform-cli";
|
|
43
|
-
readonly moduleVersion: "0.1.
|
|
43
|
+
readonly moduleVersion: "0.1.23";
|
|
44
44
|
};
|
|
45
45
|
observedEffects: never[];
|
|
46
46
|
}>;
|
|
@@ -50,10 +50,9 @@ export declare const behaviorAdapter: {
|
|
|
50
50
|
ok: true;
|
|
51
51
|
status: "SUCCEEDED";
|
|
52
52
|
moduleRef: "platform-cli";
|
|
53
|
-
moduleVersion: "0.1.
|
|
53
|
+
moduleVersion: "0.1.23";
|
|
54
54
|
data: {
|
|
55
55
|
docs: string;
|
|
56
|
-
setup: string;
|
|
57
56
|
};
|
|
58
57
|
};
|
|
59
58
|
observedEffects: never[];
|
|
@@ -64,7 +63,7 @@ export declare const behaviorAdapter: {
|
|
|
64
63
|
readonly ok: true;
|
|
65
64
|
readonly status: "SUCCEEDED";
|
|
66
65
|
readonly moduleRef: "platform-cli";
|
|
67
|
-
readonly moduleVersion: "0.1.
|
|
66
|
+
readonly moduleVersion: "0.1.23";
|
|
68
67
|
};
|
|
69
68
|
observedEffects: never[];
|
|
70
69
|
}>;
|
|
@@ -74,7 +73,7 @@ export declare const behaviorAdapter: {
|
|
|
74
73
|
readonly ok: true;
|
|
75
74
|
readonly status: "SUCCEEDED";
|
|
76
75
|
readonly moduleRef: "platform-cli";
|
|
77
|
-
readonly moduleVersion: "0.1.
|
|
76
|
+
readonly moduleVersion: "0.1.23";
|
|
78
77
|
};
|
|
79
78
|
observedEffects: never[];
|
|
80
79
|
}>;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
1
2
|
import { descriptor } from "./descriptor.js";
|
|
2
3
|
const base = {
|
|
3
4
|
contract: "deployment.result.v1",
|
|
@@ -28,7 +29,12 @@ export const behaviorAdapter = {
|
|
|
28
29
|
observedEffects: [],
|
|
29
30
|
}),
|
|
30
31
|
docs: async (_context) => ({
|
|
31
|
-
result: {
|
|
32
|
+
result: {
|
|
33
|
+
...base,
|
|
34
|
+
data: {
|
|
35
|
+
docs: readFileSync(new URL(import.meta.url.includes("/dist/") ? "../../DOCS.md" : "../DOCS.md", import.meta.url), "utf8"),
|
|
36
|
+
},
|
|
37
|
+
},
|
|
32
38
|
observedEffects: [],
|
|
33
39
|
}),
|
|
34
40
|
start: async (_context) => ({
|
|
@@ -3,7 +3,7 @@ export declare const descriptor: {
|
|
|
3
3
|
readonly contractVersion: "1.0.0";
|
|
4
4
|
readonly moduleRef: "platform-cli";
|
|
5
5
|
readonly packageName: "@tomflow/proflow-platform-cli";
|
|
6
|
-
readonly moduleVersion: "0.1.
|
|
6
|
+
readonly moduleVersion: "0.1.23";
|
|
7
7
|
readonly kind: "cli";
|
|
8
8
|
readonly templateVersion: "1.0.0";
|
|
9
9
|
readonly platformCompatibility: ">=1.0.0 <2.0.0";
|
|
@@ -3,7 +3,7 @@ export const descriptor = {
|
|
|
3
3
|
contractVersion: "1.0.0",
|
|
4
4
|
moduleRef: "platform-cli",
|
|
5
5
|
packageName: "@tomflow/proflow-platform-cli",
|
|
6
|
-
moduleVersion: "0.1.
|
|
6
|
+
moduleVersion: "0.1.23",
|
|
7
7
|
kind: "cli",
|
|
8
8
|
templateVersion: "1.0.0",
|
|
9
9
|
platformCompatibility: ">=1.0.0 <2.0.0",
|
package/dist/src/cli.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { type PackageCommandRunner } from "./install/package-manager.ts";
|
|
3
|
+
import { type PlatformProgressReporter } from "./progress.ts";
|
|
3
4
|
import { type NpmCommandRunner } from "./registry/index.ts";
|
|
4
5
|
export type CliStatus = "SUCCEEDED" | "ACTION_REQUIRED" | "BLOCKED" | "FAILED";
|
|
5
6
|
export interface CliOutcome {
|
|
@@ -17,6 +18,7 @@ export interface CliRuntimeOptions {
|
|
|
17
18
|
registryRunner?: NpmCommandRunner;
|
|
18
19
|
packageRunner?: PackageCommandRunner;
|
|
19
20
|
executableAvailable?: (command: string) => boolean;
|
|
21
|
+
onProgress?: PlatformProgressReporter;
|
|
20
22
|
}
|
|
21
|
-
export declare function runCli(argv: readonly string[], runtime?: CliRuntimeOptions): Promise<
|
|
23
|
+
export declare function runCli(argv: readonly string[], runtime?: CliRuntimeOptions): Promise<CliOutcome>;
|
|
22
24
|
export declare function renderHumanResult(result: CliOutcome): string;
|
package/dist/src/cli.js
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { readFile, realpath, stat } from "node:fs/promises";
|
|
3
3
|
import { resolve } from "node:path";
|
|
4
|
-
import { moduleStatusObservationSchema } from "@tomflow/proflow-module-contract";
|
|
4
|
+
import { moduleDocsDataSchema, moduleSetupPlanDataSchema, moduleStatusObservationSchema, } from "@tomflow/proflow-module-contract";
|
|
5
5
|
import { descriptor as platformCliDescriptor } from "../deployment/descriptor.js";
|
|
6
6
|
import { AutoModuleCatalog, discoverModules } from "./discovery/discover.js";
|
|
7
7
|
import { InstalledModuleCatalog } from "./discovery/installed.js";
|
|
8
8
|
import { PlatformError } from "./errors.js";
|
|
9
9
|
import { observeWorkspaceInstalledVersion, removeWorkspacePackages, syncWorkspacePackages, } from "./install/package-manager.js";
|
|
10
|
+
import { cleanOwnedPnpmPolicy, observeMinimumReleaseAgeExclude, recordPnpmPolicyOwnership, } from "./install/pnpm-policy.js";
|
|
10
11
|
import { installModulesThin, observeDocs, observeStatuses, setupModulesThin, startModulesThin, stopModulesThin, uninstallModulesThin, } from "./lifecycle/index.js";
|
|
11
12
|
import { ensureWorkspaceMetadata } from "./persistence/workspace-metadata.js";
|
|
13
|
+
import { reportProgress } from "./progress.js";
|
|
12
14
|
import { discoverRegistryModules, PRO_FLOW_PACKAGE_PREFIX, } from "./registry/index.js";
|
|
15
|
+
import { createTerminalProgressReporter } from "./terminal.js";
|
|
13
16
|
const COMMANDS = [
|
|
14
17
|
"install",
|
|
15
18
|
"uninstall",
|
|
@@ -20,36 +23,23 @@ const COMMANDS = [
|
|
|
20
23
|
"stop",
|
|
21
24
|
];
|
|
22
25
|
function parseArgs(argv) {
|
|
23
|
-
let
|
|
26
|
+
let workspace, moduleRef;
|
|
24
27
|
let special;
|
|
25
28
|
const positional = [];
|
|
26
29
|
for (let index = 0; index < argv.length; index += 1) {
|
|
27
30
|
const value = argv[index];
|
|
28
31
|
if (value === undefined)
|
|
29
32
|
continue;
|
|
30
|
-
if (value === "--json")
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
34
|
-
if (value === "--workspace" ||
|
|
35
|
-
value === "--module" ||
|
|
36
|
-
value === "--input") {
|
|
33
|
+
if (value === "--json")
|
|
34
|
+
throw new PlatformError("INVALID_REQUEST", "不支持的选项 --json");
|
|
35
|
+
if (value === "--workspace" || value === "--module") {
|
|
37
36
|
const next = argv[index + 1];
|
|
38
|
-
if (!next ||
|
|
37
|
+
if (!next || next.startsWith("-"))
|
|
39
38
|
throw new PlatformError("INVALID_REQUEST", `${value} requires a value`);
|
|
40
39
|
if (value === "--workspace")
|
|
41
40
|
workspace = next;
|
|
42
41
|
else if (value === "--module")
|
|
43
42
|
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
|
-
}
|
|
52
|
-
}
|
|
53
43
|
index += 1;
|
|
54
44
|
continue;
|
|
55
45
|
}
|
|
@@ -68,29 +58,24 @@ function parseArgs(argv) {
|
|
|
68
58
|
if (special !== undefined) {
|
|
69
59
|
if (workspace !== undefined ||
|
|
70
60
|
moduleRef !== undefined ||
|
|
71
|
-
inputSeen ||
|
|
72
61
|
positional.length > 0)
|
|
73
62
|
throw new PlatformError("INVALID_REQUEST", `${special} flag cannot be combined with command options`);
|
|
74
|
-
return { command: special
|
|
63
|
+
return { command: special };
|
|
75
64
|
}
|
|
76
65
|
if (positional.length === 0)
|
|
77
|
-
return { command: "help"
|
|
66
|
+
return { command: "help" };
|
|
78
67
|
if (positional.length !== 1)
|
|
79
68
|
throw new PlatformError("INVALID_REQUEST", "commands accept no positional arguments");
|
|
80
69
|
const raw = positional[0] ?? "";
|
|
81
70
|
if (!COMMANDS.includes(raw))
|
|
82
71
|
throw new PlatformError("INVALID_REQUEST", `unknown command ${raw}`);
|
|
83
72
|
const command = raw;
|
|
84
|
-
if (
|
|
85
|
-
throw new PlatformError("INVALID_REQUEST", "--module
|
|
86
|
-
if (inputSeen && moduleRef === undefined)
|
|
87
|
-
throw new PlatformError("INVALID_REQUEST", "--input requires --module");
|
|
73
|
+
if (moduleRef !== undefined && command !== "setup")
|
|
74
|
+
throw new PlatformError("INVALID_REQUEST", "--module is only valid with setup");
|
|
88
75
|
return {
|
|
89
76
|
command,
|
|
90
|
-
json,
|
|
91
77
|
...(workspace === undefined ? {} : { workspace }),
|
|
92
78
|
...(moduleRef === undefined ? {} : { moduleRef }),
|
|
93
|
-
...(inputSeen ? { input } : {}),
|
|
94
79
|
};
|
|
95
80
|
}
|
|
96
81
|
function outcome(command, status, workspaceRoot, data) {
|
|
@@ -144,9 +129,9 @@ function batchStatus(result) {
|
|
|
144
129
|
return "ACTION_REQUIRED";
|
|
145
130
|
return "FAILED";
|
|
146
131
|
}
|
|
147
|
-
async function handleStatus(root) {
|
|
132
|
+
async function handleStatus(root, runtime) {
|
|
148
133
|
const { catalog, modules } = await buildContext(root);
|
|
149
|
-
const observed = await observeStatuses(catalog, modules, root);
|
|
134
|
+
const observed = await observeStatuses(catalog, modules, root, runtime.onProgress);
|
|
150
135
|
const byRef = new Map(modules.map((module) => [module.moduleRef, module]));
|
|
151
136
|
const output = observed.map((item) => {
|
|
152
137
|
if (item.result.status !== "SUCCEEDED")
|
|
@@ -166,16 +151,32 @@ async function handleStatus(root) {
|
|
|
166
151
|
});
|
|
167
152
|
return outcome("status", "SUCCEEDED", root, { modules: output });
|
|
168
153
|
}
|
|
169
|
-
async function handleDocs(root) {
|
|
170
|
-
const { catalog, modules } = await buildContext(root);
|
|
171
|
-
const docs = await observeDocs(catalog,
|
|
172
|
-
const byRef = new Map(
|
|
173
|
-
|
|
174
|
-
|
|
154
|
+
async function handleDocs(root, runtime) {
|
|
155
|
+
const { catalog, modules: resolvedModules } = await buildContext(root);
|
|
156
|
+
const docs = await observeDocs(catalog, resolvedModules, root, runtime.onProgress);
|
|
157
|
+
const byRef = new Map(resolvedModules.map((module) => [module.moduleRef, module]));
|
|
158
|
+
const modules = [];
|
|
159
|
+
const errors = [];
|
|
160
|
+
for (const item of docs) {
|
|
161
|
+
const parsed = moduleDocsDataSchema.safeParse(item.result.data);
|
|
162
|
+
if (item.result.status !== "SUCCEEDED" || !parsed.success) {
|
|
163
|
+
errors.push({
|
|
164
|
+
moduleRef: item.moduleRef,
|
|
165
|
+
reason: item.result.status !== "SUCCEEDED"
|
|
166
|
+
? (item.result.error?.message ?? "文档读取失败")
|
|
167
|
+
: "Module.docs 返回格式无效",
|
|
168
|
+
});
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
modules.push({
|
|
175
172
|
moduleRef: item.moduleRef,
|
|
176
173
|
version: byRef.get(item.moduleRef)?.moduleVersion,
|
|
177
|
-
docs:
|
|
178
|
-
})
|
|
174
|
+
docs: parsed.data.docs,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
return outcome("docs", errors.length === 0 ? "SUCCEEDED" : "FAILED", root, {
|
|
178
|
+
modules,
|
|
179
|
+
errors,
|
|
179
180
|
});
|
|
180
181
|
}
|
|
181
182
|
async function validateInstalledPackageSet(root, candidates, previousManaged) {
|
|
@@ -203,7 +204,19 @@ async function validateInstalledPackageSet(root, candidates, previousManaged) {
|
|
|
203
204
|
}
|
|
204
205
|
}
|
|
205
206
|
async function handleInstall(root, runtime) {
|
|
207
|
+
reportProgress(runtime.onProgress, {
|
|
208
|
+
command: "install",
|
|
209
|
+
phase: "workspace",
|
|
210
|
+
status: "STARTED",
|
|
211
|
+
message: "正在校验 Workspace",
|
|
212
|
+
});
|
|
206
213
|
const metadata = await ensureWorkspaceMetadata(root);
|
|
214
|
+
reportProgress(runtime.onProgress, {
|
|
215
|
+
command: "install",
|
|
216
|
+
phase: "registry",
|
|
217
|
+
status: "STARTED",
|
|
218
|
+
message: "正在发现 Registry 模块",
|
|
219
|
+
});
|
|
207
220
|
const discovered = await discoverRegistryModules({
|
|
208
221
|
workspaceRoot: root,
|
|
209
222
|
...(runtime.registryRunner === undefined
|
|
@@ -215,6 +228,13 @@ async function handleInstall(root, runtime) {
|
|
|
215
228
|
if (discovered.candidates.length === 0)
|
|
216
229
|
throw new PlatformError("PACKAGE_NOT_FOUND", "no ProFlow packages were discovered in the configured scope");
|
|
217
230
|
const previousManaged = await workspaceProFlowDependencies(root);
|
|
231
|
+
const pnpmPolicyBefore = await observeMinimumReleaseAgeExclude(root);
|
|
232
|
+
reportProgress(runtime.onProgress, {
|
|
233
|
+
command: "install",
|
|
234
|
+
phase: "packages",
|
|
235
|
+
status: "STARTED",
|
|
236
|
+
message: "正在同步依赖",
|
|
237
|
+
});
|
|
218
238
|
const mutation = await syncWorkspacePackages({
|
|
219
239
|
workspaceRoot: root,
|
|
220
240
|
packages: discovered.candidates.map((item) => ({
|
|
@@ -228,9 +248,16 @@ async function handleInstall(root, runtime) {
|
|
|
228
248
|
? {}
|
|
229
249
|
: { executableAvailable: runtime.executableAvailable }),
|
|
230
250
|
});
|
|
251
|
+
reportProgress(runtime.onProgress, {
|
|
252
|
+
command: "install",
|
|
253
|
+
phase: "packages",
|
|
254
|
+
status: "SUCCEEDED",
|
|
255
|
+
message: "依赖同步完成",
|
|
256
|
+
});
|
|
257
|
+
await recordPnpmPolicyOwnership(root, pnpmPolicyBefore);
|
|
231
258
|
await validateInstalledPackageSet(root, discovered.candidates, previousManaged);
|
|
232
259
|
const { catalog, modules } = await buildContext(root);
|
|
233
|
-
const moduleInstall = await installModulesThin(catalog, modules, root);
|
|
260
|
+
const moduleInstall = await installModulesThin(catalog, modules, root, runtime.onProgress);
|
|
234
261
|
return outcome("install", batchStatus(moduleInstall), root, {
|
|
235
262
|
registry: discovered.registry,
|
|
236
263
|
packageManager: mutation.packageManager,
|
|
@@ -271,7 +298,7 @@ async function workspaceProFlowDependencies(root) {
|
|
|
271
298
|
async function handleUninstall(root, runtime) {
|
|
272
299
|
const packageNames = await workspaceProFlowDependencies(root);
|
|
273
300
|
const { catalog, modules } = await buildContext(root);
|
|
274
|
-
const moduleUninstall = await uninstallModulesThin(catalog, modules, root);
|
|
301
|
+
const moduleUninstall = await uninstallModulesThin(catalog, modules, root, runtime.onProgress);
|
|
275
302
|
if (!moduleUninstall.completed)
|
|
276
303
|
return outcome("uninstall", batchStatus(moduleUninstall), root, {
|
|
277
304
|
modules: moduleUninstall,
|
|
@@ -287,40 +314,39 @@ async function handleUninstall(root, runtime) {
|
|
|
287
314
|
? {}
|
|
288
315
|
: { executableAvailable: runtime.executableAvailable }),
|
|
289
316
|
});
|
|
317
|
+
const cleanedPnpmPolicy = await cleanOwnedPnpmPolicy(root);
|
|
290
318
|
return outcome("uninstall", "SUCCEEDED", root, {
|
|
291
319
|
modules: moduleUninstall,
|
|
292
320
|
packageManager: mutation.packageManager,
|
|
293
321
|
removed: packageNames,
|
|
322
|
+
cleanedPnpmPolicy,
|
|
294
323
|
preserved: [".proflow"],
|
|
295
324
|
});
|
|
296
325
|
}
|
|
297
|
-
async function handleSetup(root, parsed) {
|
|
326
|
+
async function handleSetup(root, parsed, runtime) {
|
|
298
327
|
const { catalog, modules } = await buildContext(root);
|
|
299
328
|
const target = parsed.moduleRef === undefined
|
|
300
329
|
? undefined
|
|
301
|
-
: {
|
|
302
|
-
|
|
303
|
-
...(Object.hasOwn(parsed, "input") ? { input: parsed.input } : {}),
|
|
304
|
-
};
|
|
305
|
-
const result = await setupModulesThin(catalog, modules, root, target);
|
|
330
|
+
: { moduleRef: parsed.moduleRef };
|
|
331
|
+
const result = await setupModulesThin(catalog, modules, root, target, runtime.onProgress);
|
|
306
332
|
return outcome("setup", batchStatus(result), root, result);
|
|
307
333
|
}
|
|
308
|
-
async function handleStart(root) {
|
|
334
|
+
async function handleStart(root, runtime) {
|
|
309
335
|
const { catalog, modules } = await buildContext(root);
|
|
310
|
-
const result = await startModulesThin(catalog, modules, root);
|
|
336
|
+
const result = await startModulesThin(catalog, modules, root, runtime.onProgress);
|
|
311
337
|
return outcome("start", batchStatus(result), root, result);
|
|
312
338
|
}
|
|
313
|
-
async function handleStop(root) {
|
|
339
|
+
async function handleStop(root, runtime) {
|
|
314
340
|
const { catalog, modules } = await buildContext(root);
|
|
315
|
-
const result = await stopModulesThin(catalog, modules, root);
|
|
341
|
+
const result = await stopModulesThin(catalog, modules, root, runtime.onProgress);
|
|
316
342
|
return outcome("stop", batchStatus(result), root, result);
|
|
317
343
|
}
|
|
318
344
|
function helpOutcome() {
|
|
319
345
|
return outcome("help", "SUCCEEDED", undefined, {
|
|
320
|
-
usage: "platform <install|uninstall|status|setup|docs|start|stop> [--workspace <path>]
|
|
346
|
+
usage: "platform <install|uninstall|status|setup|docs|start|stop> [--workspace <path>]",
|
|
321
347
|
commands: [...COMMANDS],
|
|
322
348
|
install: "platform install [--workspace <path>]",
|
|
323
|
-
setup: "platform setup [--workspace <path>] [--module <moduleRef>
|
|
349
|
+
setup: "platform setup [--workspace <path>] [--module <moduleRef>]",
|
|
324
350
|
});
|
|
325
351
|
}
|
|
326
352
|
export async function runCli(argv, runtime = {}) {
|
|
@@ -329,35 +355,35 @@ export async function runCli(argv, runtime = {}) {
|
|
|
329
355
|
parsed = parseArgs(argv);
|
|
330
356
|
}
|
|
331
357
|
catch (error) {
|
|
332
|
-
return
|
|
358
|
+
return errorOutcome("unknown", error);
|
|
333
359
|
}
|
|
334
360
|
try {
|
|
335
361
|
if (parsed.command === "help")
|
|
336
|
-
return
|
|
362
|
+
return helpOutcome();
|
|
337
363
|
if (parsed.command === "version")
|
|
338
|
-
return
|
|
364
|
+
return outcome("version", "SUCCEEDED", undefined, {
|
|
339
365
|
version: platformCliDescriptor.moduleVersion,
|
|
340
|
-
})
|
|
366
|
+
});
|
|
341
367
|
const root = await canonicalWorkspace(runtime.cwd ?? process.cwd(), parsed.workspace);
|
|
342
368
|
switch (parsed.command) {
|
|
343
369
|
case "install":
|
|
344
|
-
return
|
|
370
|
+
return await handleInstall(root, runtime);
|
|
345
371
|
case "uninstall":
|
|
346
|
-
return
|
|
372
|
+
return await handleUninstall(root, runtime);
|
|
347
373
|
case "status":
|
|
348
|
-
return
|
|
374
|
+
return await handleStatus(root, runtime);
|
|
349
375
|
case "setup":
|
|
350
|
-
return
|
|
376
|
+
return await handleSetup(root, parsed, runtime);
|
|
351
377
|
case "docs":
|
|
352
|
-
return
|
|
378
|
+
return await handleDocs(root, runtime);
|
|
353
379
|
case "start":
|
|
354
|
-
return
|
|
380
|
+
return await handleStart(root, runtime);
|
|
355
381
|
case "stop":
|
|
356
|
-
return
|
|
382
|
+
return await handleStop(root, runtime);
|
|
357
383
|
}
|
|
358
384
|
}
|
|
359
385
|
catch (error) {
|
|
360
|
-
return
|
|
386
|
+
return errorOutcome(parsed.command, error);
|
|
361
387
|
}
|
|
362
388
|
}
|
|
363
389
|
function errorOutcome(command, error) {
|
|
@@ -379,28 +405,81 @@ function errorOutcome(command, error) {
|
|
|
379
405
|
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
380
406
|
function renderStatus(data) {
|
|
381
407
|
if (!isRecord(data) || !Array.isArray(data.modules))
|
|
382
|
-
return "
|
|
408
|
+
return "未发现模块。";
|
|
409
|
+
const setupLabels = {
|
|
410
|
+
READY: "已就绪",
|
|
411
|
+
ACTION_REQUIRED: "需要操作",
|
|
412
|
+
FAILED: "失败",
|
|
413
|
+
};
|
|
414
|
+
const runtimeLabels = {
|
|
415
|
+
RUNNING: "运行中",
|
|
416
|
+
STOPPED: "已停止",
|
|
417
|
+
FAILED: "运行失败",
|
|
418
|
+
NOT_APPLICABLE: "无独立进程",
|
|
419
|
+
};
|
|
383
420
|
return [
|
|
384
|
-
"ProFlow
|
|
421
|
+
"ProFlow 模块状态",
|
|
385
422
|
"",
|
|
423
|
+
"模块 版本 配置状态 运行状态",
|
|
386
424
|
...data.modules
|
|
387
425
|
.filter(isRecord)
|
|
388
|
-
.map((raw) => `${String(raw.moduleRef)}
|
|
426
|
+
.map((raw) => `${String(raw.moduleRef).padEnd(28)} ${String(raw.version).padEnd(10)} ${(setupLabels[String(raw.setupStatus)] ?? "未知").padEnd(10)} ${runtimeLabels[String(raw.runtimeStatus)] ?? "未知"}`),
|
|
427
|
+
"",
|
|
428
|
+
"需要操作:运行 platform setup",
|
|
429
|
+
"失败:运行 platform setup 查看原因和修复步骤",
|
|
389
430
|
].join("\n");
|
|
390
431
|
}
|
|
391
432
|
function renderDocs(data) {
|
|
392
433
|
if (!isRecord(data) || !Array.isArray(data.modules))
|
|
393
|
-
return "
|
|
394
|
-
const lines = ["ProFlow
|
|
434
|
+
return "未发现模块文档。";
|
|
435
|
+
const lines = ["ProFlow 模块文档"];
|
|
395
436
|
for (const raw of data.modules)
|
|
396
437
|
if (isRecord(raw))
|
|
397
|
-
lines.push("", `## ${String(raw.moduleRef)} @ ${String(raw.version)}`,
|
|
438
|
+
lines.push("", `## ${String(raw.moduleRef)} @ ${String(raw.version)}`, String(raw.docs ?? ""));
|
|
439
|
+
if (Array.isArray(data.errors) && data.errors.length > 0)
|
|
440
|
+
lines.push("", "文档读取失败:", ...data.errors
|
|
441
|
+
.filter(isRecord)
|
|
442
|
+
.map((item) => `- ${String(item.moduleRef)}:${String(item.reason)}`));
|
|
398
443
|
return lines.join("\n");
|
|
399
444
|
}
|
|
445
|
+
const setupCommands = {
|
|
446
|
+
"chatgpt-carrier": {
|
|
447
|
+
ai: "proflow-chatgpt-carrier setup --carrier-url <url>",
|
|
448
|
+
inputs: "Custom GPT URL",
|
|
449
|
+
},
|
|
450
|
+
"dev-tunnel": {
|
|
451
|
+
ai: "proflow-dev-tunnel setup --tunnel-id <id> --public-base-url <url>",
|
|
452
|
+
inputs: "Tunnel ID、公开 HTTPS URL",
|
|
453
|
+
},
|
|
454
|
+
"model-provider-api": {
|
|
455
|
+
ai: "proflow-model-provider-api setup --provider-base-url <url>",
|
|
456
|
+
inputs: "模型服务 Base URL",
|
|
457
|
+
},
|
|
458
|
+
"model-runtime": {
|
|
459
|
+
ai: "proflow-model-runtime setup --fast-model <id> --reason-model <id>",
|
|
460
|
+
inputs: "FAST 模型 ID、REASON 模型 ID",
|
|
461
|
+
},
|
|
462
|
+
"execution-browser-extension": {
|
|
463
|
+
ai: "proflow-execution-browser-extension setup --extension-id <id>",
|
|
464
|
+
inputs: "Chrome Extension ID",
|
|
465
|
+
},
|
|
466
|
+
"agent-controller-dev": {
|
|
467
|
+
ai: "proflow-agent-controller-dev setup --carrier-url <url>",
|
|
468
|
+
inputs: "Custom GPT URL",
|
|
469
|
+
},
|
|
470
|
+
"agent-product": {
|
|
471
|
+
ai: "proflow-agent-product setup --carrier-url <url>",
|
|
472
|
+
inputs: "Custom GPT URL",
|
|
473
|
+
},
|
|
474
|
+
"agent-test-ops": {
|
|
475
|
+
ai: "proflow-agent-test-ops setup --carrier-url <url>",
|
|
476
|
+
inputs: "Custom GPT URL",
|
|
477
|
+
},
|
|
478
|
+
};
|
|
400
479
|
function renderSetup(data) {
|
|
401
480
|
if (!isRecord(data) || !Array.isArray(data.results))
|
|
402
|
-
return "
|
|
403
|
-
const lines = ["ProFlow
|
|
481
|
+
return "没有需要执行的配置步骤。";
|
|
482
|
+
const lines = ["ProFlow 配置", ""];
|
|
404
483
|
let automatic = 0;
|
|
405
484
|
let pending = 0;
|
|
406
485
|
for (const raw of data.results) {
|
|
@@ -413,31 +492,52 @@ function renderSetup(data) {
|
|
|
413
492
|
continue;
|
|
414
493
|
}
|
|
415
494
|
pending += 1;
|
|
416
|
-
lines.push(
|
|
417
|
-
const
|
|
495
|
+
lines.push(moduleRef);
|
|
496
|
+
const plan = moduleSetupPlanDataSchema.safeParse(raw.result.data);
|
|
497
|
+
if (plan.success) {
|
|
498
|
+
for (const [index, step] of plan.data.steps.entries()) {
|
|
499
|
+
lines.push(` [${step.state} ${index + 1}/${plan.data.steps.length}] ${step.title}`);
|
|
500
|
+
lines.push(` 人工执行:${step.execution.interactive}`);
|
|
501
|
+
lines.push(` AI 执行:${step.execution.nonInteractive}`);
|
|
502
|
+
if (step.requiredInputs.length > 0)
|
|
503
|
+
lines.push(` 需要输入:${step.requiredInputs.map((item) => item.description).join("、")}`);
|
|
504
|
+
lines.push(` 验证:${step.verify}`);
|
|
505
|
+
lines.push(` 完成条件:${step.successCondition}`);
|
|
506
|
+
if (step.blockedReason)
|
|
507
|
+
lines.push(` 原因:${step.blockedReason}`);
|
|
508
|
+
}
|
|
509
|
+
lines.push("");
|
|
510
|
+
continue;
|
|
511
|
+
}
|
|
512
|
+
lines.push(` [${status === "FAILED" ? "BLOCKED" : "TODO"} 1/1] ${status === "FAILED" ? "等待上游服务信息" : "完成模块配置"}`);
|
|
418
513
|
const error = raw.result.error;
|
|
419
|
-
if (
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
514
|
+
if (status !== "FAILED") {
|
|
515
|
+
const commands = setupCommands[moduleRef] ?? {
|
|
516
|
+
ai: `proflow-${moduleRef} setup`,
|
|
517
|
+
inputs: "按命令提示提供",
|
|
518
|
+
};
|
|
519
|
+
lines.push(` 人工执行:proflow-${moduleRef} setup`);
|
|
520
|
+
lines.push(` AI 执行:${commands.ai}`);
|
|
521
|
+
lines.push(` 需要输入:${commands.inputs}`);
|
|
522
|
+
lines.push(` 验证:proflow-${moduleRef} verify`);
|
|
523
|
+
lines.push(" 完成条件:配置状态变为“已就绪”");
|
|
424
524
|
}
|
|
425
525
|
else if (isRecord(error)) {
|
|
426
526
|
const code = error.code === undefined ? "FAILED" : String(error.code);
|
|
427
527
|
const message = error.message === undefined
|
|
428
528
|
? "Module setup failed."
|
|
429
529
|
: String(error.message);
|
|
430
|
-
lines.push(`
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
lines.push(JSON.stringify(raw.result.data, null, 2));
|
|
530
|
+
lines.push(` 原因:${code} — ${message}`);
|
|
531
|
+
lines.push(` 执行:platform setup --module ${moduleRef}`);
|
|
532
|
+
lines.push(" 完成条件:配置状态变为“已就绪”");
|
|
434
533
|
}
|
|
435
534
|
lines.push("");
|
|
436
535
|
}
|
|
437
536
|
if (pending === 0)
|
|
438
|
-
lines.push("
|
|
537
|
+
lines.push("全部模块均已就绪。 ");
|
|
439
538
|
if (automatic > 0)
|
|
440
|
-
lines.push(
|
|
539
|
+
lines.push(`本次自动完成:${automatic} 个模块`);
|
|
540
|
+
lines.push(`汇总:${automatic} 个已就绪,${pending} 个待处理`);
|
|
441
541
|
return lines.join("\n").trimEnd();
|
|
442
542
|
}
|
|
443
543
|
export function renderHumanResult(result) {
|
|
@@ -445,17 +545,38 @@ export function renderHumanResult(result) {
|
|
|
445
545
|
isRecord(result.data) &&
|
|
446
546
|
Array.isArray(result.data.results))
|
|
447
547
|
return renderSetup(result.data);
|
|
448
|
-
if (result.
|
|
449
|
-
|
|
548
|
+
if (result.command === "start" && isRecord(result.data)) {
|
|
549
|
+
const blockers = Array.isArray(result.data.blockers)
|
|
550
|
+
? result.data.blockers.filter(isRecord)
|
|
551
|
+
: [];
|
|
552
|
+
if (blockers.length > 0)
|
|
553
|
+
return [
|
|
554
|
+
"平台未启动:存在未就绪模块",
|
|
555
|
+
"",
|
|
556
|
+
...blockers.map((item) => `${String(item.moduleRef).padEnd(28)} ${String(item.setupStatus) === "FAILED" ? "失败" : "需要操作"} 配置尚未完成`),
|
|
557
|
+
"",
|
|
558
|
+
`处理方式:platform setup${result.workspaceRoot ? ` --workspace "${result.workspaceRoot}"` : ""}`,
|
|
559
|
+
].join("\n");
|
|
560
|
+
}
|
|
561
|
+
if (result.status === "FAILED" && result.error)
|
|
562
|
+
return `${result.command} 失败:${result.error.message}`;
|
|
450
563
|
if (result.command === "help")
|
|
451
564
|
return [
|
|
452
|
-
"ProFlow
|
|
565
|
+
"ProFlow 平台命令行",
|
|
453
566
|
"",
|
|
454
|
-
|
|
567
|
+
"platform install 安装并初始化全部 ProFlow 模块",
|
|
568
|
+
"platform uninstall 卸载模块包,保留 Workspace 数据",
|
|
569
|
+
"platform status 查看模块配置与运行状态",
|
|
570
|
+
"platform setup 自动配置并列出全部剩余步骤",
|
|
571
|
+
"platform docs 阅读模块能力文档",
|
|
572
|
+
"platform start 完成全量检查后启动平台",
|
|
573
|
+
"platform stop 按逆依赖顺序停止平台",
|
|
455
574
|
"",
|
|
456
|
-
"
|
|
457
|
-
"
|
|
458
|
-
"
|
|
575
|
+
"常用参数:--workspace <路径>;setup 还支持 --module <模块名>",
|
|
576
|
+
"推荐流程:install → status → docs → setup → start → status → stop",
|
|
577
|
+
"人工配置示例:proflow-chatgpt-carrier setup",
|
|
578
|
+
"AI 配置示例:proflow-chatgpt-carrier setup --carrier-url <url>",
|
|
579
|
+
"状态图例:已就绪=配置完成;需要操作=需运行 setup;失败=存在系统阻塞;无独立进程=无需启动",
|
|
459
580
|
].join("\n");
|
|
460
581
|
if (result.command === "version" && isRecord(result.data))
|
|
461
582
|
return String(result.data.version ?? platformCliDescriptor.moduleVersion);
|
|
@@ -465,19 +586,36 @@ export function renderHumanResult(result) {
|
|
|
465
586
|
return renderSetup(result.data);
|
|
466
587
|
if (result.command === "docs")
|
|
467
588
|
return renderDocs(result.data);
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
589
|
+
if ((result.command === "start" || result.command === "stop") &&
|
|
590
|
+
isRecord(result.data)) {
|
|
591
|
+
const skipped = Array.isArray(result.data.skipped)
|
|
592
|
+
? result.data.skipped.length
|
|
593
|
+
: 0;
|
|
594
|
+
const results = Array.isArray(result.data.results)
|
|
595
|
+
? result.data.results.filter(isRecord)
|
|
596
|
+
: [];
|
|
597
|
+
const operations = results.filter((item) => item.command === result.command);
|
|
598
|
+
const succeeded = operations.filter((item) => isRecord(item.result) && item.result.status === "SUCCEEDED").length;
|
|
599
|
+
const failed = operations.find((item) => isRecord(item.result) && item.result.status !== "SUCCEEDED");
|
|
600
|
+
return [
|
|
601
|
+
`平台${result.command === "start" ? "启动" : "停止"}${failed ? "未完成" : "完成"}`,
|
|
602
|
+
`成功:${succeeded},跳过:${skipped}${failed ? `,失败:${String(failed.moduleRef)}` : ",失败:0"}`,
|
|
603
|
+
].join("\n");
|
|
604
|
+
}
|
|
605
|
+
const labels = {
|
|
606
|
+
install: "安装",
|
|
607
|
+
uninstall: "卸载",
|
|
608
|
+
start: "启动",
|
|
609
|
+
stop: "停止",
|
|
610
|
+
};
|
|
611
|
+
return `${labels[result.command] ?? result.command}${result.status === "SUCCEEDED" ? "成功" : "未完成"}${result.workspaceRoot ? `\nWorkspace:${result.workspaceRoot}` : ""}`;
|
|
475
612
|
}
|
|
476
613
|
if (import.meta.main) {
|
|
477
614
|
const argv = process.argv.slice(2);
|
|
478
|
-
const
|
|
479
|
-
|
|
480
|
-
|
|
615
|
+
const result = await runCli(argv, {
|
|
616
|
+
onProgress: createTerminalProgressReporter(),
|
|
617
|
+
});
|
|
618
|
+
process.stdout.write(`${renderHumanResult(result)}\n`);
|
|
481
619
|
if (result.status !== "SUCCEEDED")
|
|
482
620
|
process.exitCode = 1;
|
|
483
621
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { type CliOutcome, type CliRuntimeOptions, type CliStatus, runCli, } from "./cli.ts";
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export declare function observeMinimumReleaseAgeExclude(root: string): Promise<string[]>;
|
|
2
|
+
export declare function recordPnpmPolicyOwnership(root: string, before: readonly string[]): Promise<void>;
|
|
3
|
+
export declare function cleanOwnedPnpmPolicy(root: string): Promise<string[]>;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { mkdir, readFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { atomicWrite } from "../paths.js";
|
|
4
|
+
const policyFileName = "pnpm-workspace.yaml";
|
|
5
|
+
const ownershipFile = (root) => join(root, ".proflow", "deployment", "pnpm-policy-ownership.json");
|
|
6
|
+
async function text(root) {
|
|
7
|
+
try {
|
|
8
|
+
return await readFile(join(root, policyFileName), "utf8");
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
return undefined;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export async function observeMinimumReleaseAgeExclude(root) {
|
|
15
|
+
const source = await text(root);
|
|
16
|
+
if (!source)
|
|
17
|
+
return [];
|
|
18
|
+
const lines = source.split(/\r?\n/);
|
|
19
|
+
const start = lines.findIndex((line) => /^minimumReleaseAgeExclude:\s*$/.test(line));
|
|
20
|
+
if (start < 0)
|
|
21
|
+
return [];
|
|
22
|
+
const values = [];
|
|
23
|
+
for (let index = start + 1; index < lines.length; index += 1) {
|
|
24
|
+
const line = lines[index] ?? "";
|
|
25
|
+
if (/^\S/.test(line) && line.trim() !== "")
|
|
26
|
+
break;
|
|
27
|
+
const match = line.match(/^\s+-\s+['"]?([^'"]+)['"]?\s*$/);
|
|
28
|
+
if (match?.[1])
|
|
29
|
+
values.push(match[1]);
|
|
30
|
+
}
|
|
31
|
+
return values;
|
|
32
|
+
}
|
|
33
|
+
export async function recordPnpmPolicyOwnership(root, before) {
|
|
34
|
+
const after = await observeMinimumReleaseAgeExclude(root);
|
|
35
|
+
const introduced = after.filter((value) => !before.includes(value));
|
|
36
|
+
if (introduced.length === 0)
|
|
37
|
+
return;
|
|
38
|
+
await mkdir(join(root, ".proflow", "deployment"), { recursive: true });
|
|
39
|
+
await atomicWrite(ownershipFile(root), `${JSON.stringify({ contract: "proflow.pnpm-policy-ownership.v1", introduced }, null, 2)}\n`);
|
|
40
|
+
}
|
|
41
|
+
export async function cleanOwnedPnpmPolicy(root) {
|
|
42
|
+
let introduced;
|
|
43
|
+
try {
|
|
44
|
+
const parsed = JSON.parse(await readFile(ownershipFile(root), "utf8"));
|
|
45
|
+
introduced =
|
|
46
|
+
typeof parsed === "object" &&
|
|
47
|
+
parsed !== null &&
|
|
48
|
+
Array.isArray(Reflect.get(parsed, "introduced"))
|
|
49
|
+
? Reflect.get(parsed, "introduced").filter((value) => typeof value === "string")
|
|
50
|
+
: [];
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return [];
|
|
54
|
+
}
|
|
55
|
+
if (introduced.length === 0)
|
|
56
|
+
return [];
|
|
57
|
+
const source = await text(root);
|
|
58
|
+
if (!source)
|
|
59
|
+
return [];
|
|
60
|
+
const owned = new Set(introduced);
|
|
61
|
+
const lines = source.split(/\r?\n/);
|
|
62
|
+
const next = lines.filter((line) => {
|
|
63
|
+
const match = line.match(/^\s+-\s+['"]?([^'"]+)['"]?\s*$/);
|
|
64
|
+
return !match?.[1] || !owned.has(match[1]);
|
|
65
|
+
});
|
|
66
|
+
await atomicWrite(join(root, policyFileName), next.join("\n"));
|
|
67
|
+
return introduced;
|
|
68
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type ModuleSetupStatus } from "@tomflow/proflow-module-contract";
|
|
2
2
|
import type { ResolvedModule } from "../contracts.ts";
|
|
3
3
|
import type { ModuleCatalog } from "../modules.ts";
|
|
4
|
+
import { type PlatformProgressReporter } from "../progress.ts";
|
|
4
5
|
import { type ModuleDispatchResult } from "./dispatch.ts";
|
|
5
6
|
export interface ModuleBatchResult {
|
|
6
7
|
phase: "install" | "uninstall" | "setup" | "start" | "stop";
|
|
@@ -10,14 +11,22 @@ export interface ModuleBatchResult {
|
|
|
10
11
|
moduleRef: string;
|
|
11
12
|
setupStatus: ModuleSetupStatus;
|
|
12
13
|
};
|
|
14
|
+
blockers?: Array<{
|
|
15
|
+
moduleRef: string;
|
|
16
|
+
setupStatus: ModuleSetupStatus;
|
|
17
|
+
}>;
|
|
18
|
+
skipped?: Array<{
|
|
19
|
+
moduleRef: string;
|
|
20
|
+
reason: "RUNNING" | "STOPPED" | "NOT_APPLICABLE";
|
|
21
|
+
}>;
|
|
13
22
|
}
|
|
14
|
-
export declare function observeStatuses(catalog: ModuleCatalog, modules: readonly ResolvedModule[], workspaceRoot: string): Promise<ModuleDispatchResult[]>;
|
|
15
|
-
export declare function observeDocs(catalog: ModuleCatalog, modules: readonly ResolvedModule[], workspaceRoot: string): Promise<ModuleDispatchResult[]>;
|
|
16
|
-
export declare const installModulesThin: (catalog: ModuleCatalog, modules: readonly ResolvedModule[], workspaceRoot: string) => Promise<ModuleBatchResult>;
|
|
17
|
-
export declare const uninstallModulesThin: (catalog: ModuleCatalog, modules: readonly ResolvedModule[], workspaceRoot: string) => Promise<ModuleBatchResult>;
|
|
18
|
-
export declare
|
|
23
|
+
export declare function observeStatuses(catalog: ModuleCatalog, modules: readonly ResolvedModule[], workspaceRoot: string, reporter?: PlatformProgressReporter): Promise<ModuleDispatchResult[]>;
|
|
24
|
+
export declare function observeDocs(catalog: ModuleCatalog, modules: readonly ResolvedModule[], workspaceRoot: string, reporter?: PlatformProgressReporter): Promise<ModuleDispatchResult[]>;
|
|
25
|
+
export declare const installModulesThin: (catalog: ModuleCatalog, modules: readonly ResolvedModule[], workspaceRoot: string, reporter?: PlatformProgressReporter) => Promise<ModuleBatchResult>;
|
|
26
|
+
export declare const uninstallModulesThin: (catalog: ModuleCatalog, modules: readonly ResolvedModule[], workspaceRoot: string, reporter?: PlatformProgressReporter) => Promise<ModuleBatchResult>;
|
|
27
|
+
export declare function stopModulesThin(catalog: ModuleCatalog, modules: readonly ResolvedModule[], workspaceRoot: string, reporter?: PlatformProgressReporter): Promise<ModuleBatchResult>;
|
|
19
28
|
export declare function setupModulesThin(catalog: ModuleCatalog, modules: readonly ResolvedModule[], workspaceRoot: string, target?: {
|
|
20
29
|
moduleRef: string;
|
|
21
30
|
input?: unknown;
|
|
22
|
-
}): Promise<ModuleBatchResult>;
|
|
23
|
-
export declare function startModulesThin(catalog: ModuleCatalog, modules: readonly ResolvedModule[], workspaceRoot: string): Promise<ModuleBatchResult>;
|
|
31
|
+
}, reporter?: PlatformProgressReporter): Promise<ModuleBatchResult>;
|
|
32
|
+
export declare function startModulesThin(catalog: ModuleCatalog, modules: readonly ResolvedModule[], workspaceRoot: string, reporter?: PlatformProgressReporter): Promise<ModuleBatchResult>;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { moduleStatusObservationSchema, } from "@tomflow/proflow-module-contract";
|
|
2
2
|
import { PlatformError } from "../errors.js";
|
|
3
3
|
import { buildDependencyGraph } from "../graph/graph.js";
|
|
4
|
+
import { reportProgress } from "../progress.js";
|
|
4
5
|
import { dispatchModuleCommand, } from "./dispatch.js";
|
|
5
6
|
const succeeded = (result) => result.status === "SUCCEEDED";
|
|
6
7
|
const context = (workspaceRoot, input) => input === undefined ? { workspaceRoot } : { workspaceRoot, input };
|
|
@@ -12,38 +13,160 @@ function ordered(modules, reverse = false) {
|
|
|
12
13
|
.map((ref) => byRef.get(ref))
|
|
13
14
|
.filter((item) => item !== undefined);
|
|
14
15
|
}
|
|
15
|
-
export async function observeStatuses(catalog, modules, workspaceRoot) {
|
|
16
|
+
export async function observeStatuses(catalog, modules, workspaceRoot, reporter) {
|
|
16
17
|
const results = [];
|
|
17
|
-
|
|
18
|
-
|
|
18
|
+
const modulesInOrder = [...modules].sort((a, b) => a.moduleRef.localeCompare(b.moduleRef));
|
|
19
|
+
for (const [index, module] of modulesInOrder.entries()) {
|
|
20
|
+
reportProgress(reporter, {
|
|
21
|
+
command: "status",
|
|
22
|
+
phase: "status",
|
|
23
|
+
current: index + 1,
|
|
24
|
+
total: modulesInOrder.length,
|
|
25
|
+
moduleRef: module.moduleRef,
|
|
26
|
+
status: "STARTED",
|
|
27
|
+
message: module.moduleRef,
|
|
28
|
+
});
|
|
29
|
+
const result = await dispatchModuleCommand(catalog, module, "status", context(workspaceRoot));
|
|
30
|
+
results.push(result);
|
|
31
|
+
reportProgress(reporter, {
|
|
32
|
+
command: "status",
|
|
33
|
+
phase: "status",
|
|
34
|
+
current: index + 1,
|
|
35
|
+
total: modulesInOrder.length,
|
|
36
|
+
moduleRef: module.moduleRef,
|
|
37
|
+
status: succeeded(result.result) ? "SUCCEEDED" : "FAILED",
|
|
38
|
+
message: module.moduleRef,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
19
41
|
return results;
|
|
20
42
|
}
|
|
21
|
-
export async function observeDocs(catalog, modules, workspaceRoot) {
|
|
43
|
+
export async function observeDocs(catalog, modules, workspaceRoot, reporter) {
|
|
22
44
|
const results = [];
|
|
23
|
-
|
|
24
|
-
|
|
45
|
+
const modulesInOrder = [...modules].sort((a, b) => a.moduleRef.localeCompare(b.moduleRef));
|
|
46
|
+
for (const [index, module] of modulesInOrder.entries()) {
|
|
47
|
+
reportProgress(reporter, {
|
|
48
|
+
command: "docs",
|
|
49
|
+
phase: "docs",
|
|
50
|
+
current: index + 1,
|
|
51
|
+
total: modulesInOrder.length,
|
|
52
|
+
moduleRef: module.moduleRef,
|
|
53
|
+
status: "STARTED",
|
|
54
|
+
message: module.moduleRef,
|
|
55
|
+
});
|
|
56
|
+
const result = await dispatchModuleCommand(catalog, module, "docs", context(workspaceRoot));
|
|
57
|
+
results.push(result);
|
|
58
|
+
reportProgress(reporter, {
|
|
59
|
+
command: "docs",
|
|
60
|
+
phase: "docs",
|
|
61
|
+
current: index + 1,
|
|
62
|
+
total: modulesInOrder.length,
|
|
63
|
+
moduleRef: module.moduleRef,
|
|
64
|
+
status: succeeded(result.result) ? "SUCCEEDED" : "FAILED",
|
|
65
|
+
message: module.moduleRef,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
25
68
|
return results;
|
|
26
69
|
}
|
|
27
|
-
async function runOrdered(catalog, modules, workspaceRoot, command, reverse) {
|
|
70
|
+
async function runOrdered(catalog, modules, workspaceRoot, command, reverse, reporter) {
|
|
28
71
|
const results = [];
|
|
29
|
-
|
|
72
|
+
const modulesInOrder = ordered(modules, reverse);
|
|
73
|
+
for (const [index, module] of modulesInOrder.entries()) {
|
|
74
|
+
reportProgress(reporter, {
|
|
75
|
+
command,
|
|
76
|
+
phase: command,
|
|
77
|
+
current: index + 1,
|
|
78
|
+
total: modulesInOrder.length,
|
|
79
|
+
moduleRef: module.moduleRef,
|
|
80
|
+
status: "STARTED",
|
|
81
|
+
message: `${module.moduleRef}`,
|
|
82
|
+
});
|
|
30
83
|
const result = await dispatchModuleCommand(catalog, module, command, context(workspaceRoot));
|
|
31
84
|
results.push(result);
|
|
85
|
+
reportProgress(reporter, {
|
|
86
|
+
command,
|
|
87
|
+
phase: command,
|
|
88
|
+
current: index + 1,
|
|
89
|
+
total: modulesInOrder.length,
|
|
90
|
+
moduleRef: module.moduleRef,
|
|
91
|
+
status: succeeded(result.result) ? "SUCCEEDED" : "FAILED",
|
|
92
|
+
message: `${module.moduleRef}`,
|
|
93
|
+
});
|
|
32
94
|
if (!succeeded(result.result))
|
|
33
95
|
return { phase: command, results, completed: false };
|
|
34
96
|
}
|
|
35
97
|
return { phase: command, results, completed: true };
|
|
36
98
|
}
|
|
37
|
-
export const installModulesThin = (catalog, modules, workspaceRoot) => runOrdered(catalog, modules, workspaceRoot, "install", false);
|
|
38
|
-
export const uninstallModulesThin = (catalog, modules, workspaceRoot) => runOrdered(catalog, modules, workspaceRoot, "uninstall", true);
|
|
39
|
-
export
|
|
40
|
-
|
|
99
|
+
export const installModulesThin = (catalog, modules, workspaceRoot, reporter) => runOrdered(catalog, modules, workspaceRoot, "install", false, reporter);
|
|
100
|
+
export const uninstallModulesThin = (catalog, modules, workspaceRoot, reporter) => runOrdered(catalog, modules, workspaceRoot, "uninstall", true, reporter);
|
|
101
|
+
export async function stopModulesThin(catalog, modules, workspaceRoot, reporter) {
|
|
102
|
+
const results = [];
|
|
103
|
+
const skipped = [];
|
|
104
|
+
const modulesInOrder = ordered(modules, true);
|
|
105
|
+
for (const [index, module] of modulesInOrder.entries()) {
|
|
106
|
+
reportProgress(reporter, {
|
|
107
|
+
command: "stop",
|
|
108
|
+
phase: "status",
|
|
109
|
+
current: index + 1,
|
|
110
|
+
total: modulesInOrder.length,
|
|
111
|
+
moduleRef: module.moduleRef,
|
|
112
|
+
status: "STARTED",
|
|
113
|
+
message: `${module.moduleRef}`,
|
|
114
|
+
});
|
|
115
|
+
const status = await dispatchModuleCommand(catalog, module, "status", context(workspaceRoot));
|
|
116
|
+
results.push(status);
|
|
117
|
+
if (!succeeded(status.result))
|
|
118
|
+
return { phase: "stop", results, completed: false, skipped };
|
|
119
|
+
const observed = moduleStatusObservationSchema.parse(status.result.data);
|
|
120
|
+
if (observed.runtimeStatus === "STOPPED" ||
|
|
121
|
+
observed.runtimeStatus === "NOT_APPLICABLE") {
|
|
122
|
+
skipped.push({
|
|
123
|
+
moduleRef: module.moduleRef,
|
|
124
|
+
reason: observed.runtimeStatus,
|
|
125
|
+
});
|
|
126
|
+
reportProgress(reporter, {
|
|
127
|
+
command: "stop",
|
|
128
|
+
phase: "stop",
|
|
129
|
+
current: index + 1,
|
|
130
|
+
total: modulesInOrder.length,
|
|
131
|
+
moduleRef: module.moduleRef,
|
|
132
|
+
status: "SKIPPED",
|
|
133
|
+
message: `${module.moduleRef}`,
|
|
134
|
+
});
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
const stopped = await dispatchModuleCommand(catalog, module, "stop", context(workspaceRoot));
|
|
138
|
+
results.push(stopped);
|
|
139
|
+
reportProgress(reporter, {
|
|
140
|
+
command: "stop",
|
|
141
|
+
phase: "stop",
|
|
142
|
+
current: index + 1,
|
|
143
|
+
total: modulesInOrder.length,
|
|
144
|
+
moduleRef: module.moduleRef,
|
|
145
|
+
status: succeeded(stopped.result) ? "SUCCEEDED" : "FAILED",
|
|
146
|
+
message: `${module.moduleRef}`,
|
|
147
|
+
});
|
|
148
|
+
if (!succeeded(stopped.result))
|
|
149
|
+
return { phase: "stop", results, completed: false, skipped };
|
|
150
|
+
}
|
|
151
|
+
return { phase: "stop", results, completed: true, skipped };
|
|
152
|
+
}
|
|
153
|
+
export async function setupModulesThin(catalog, modules, workspaceRoot, target, reporter) {
|
|
41
154
|
const results = [];
|
|
42
155
|
let matched = target === undefined;
|
|
43
156
|
let completed = true;
|
|
44
|
-
|
|
157
|
+
const modulesInOrder = ordered(modules);
|
|
158
|
+
for (const [index, module] of modulesInOrder.entries()) {
|
|
45
159
|
if (target !== undefined && module.moduleRef !== target.moduleRef)
|
|
46
160
|
continue;
|
|
161
|
+
reportProgress(reporter, {
|
|
162
|
+
command: "setup",
|
|
163
|
+
phase: "setup",
|
|
164
|
+
current: index + 1,
|
|
165
|
+
total: modulesInOrder.length,
|
|
166
|
+
moduleRef: module.moduleRef,
|
|
167
|
+
status: "STARTED",
|
|
168
|
+
message: module.moduleRef,
|
|
169
|
+
});
|
|
47
170
|
matched = true;
|
|
48
171
|
const status = await dispatchModuleCommand(catalog, module, "status", context(workspaceRoot));
|
|
49
172
|
if (!succeeded(status.result)) {
|
|
@@ -54,10 +177,33 @@ export async function setupModulesThin(catalog, modules, workspaceRoot, target)
|
|
|
54
177
|
continue;
|
|
55
178
|
}
|
|
56
179
|
const observed = moduleStatusObservationSchema.parse(status.result.data);
|
|
57
|
-
if (observed.setupStatus === "READY" && target?.input === undefined)
|
|
180
|
+
if (observed.setupStatus === "READY" && target?.input === undefined) {
|
|
181
|
+
reportProgress(reporter, {
|
|
182
|
+
command: "setup",
|
|
183
|
+
phase: "setup",
|
|
184
|
+
current: index + 1,
|
|
185
|
+
total: modulesInOrder.length,
|
|
186
|
+
moduleRef: module.moduleRef,
|
|
187
|
+
status: "SKIPPED",
|
|
188
|
+
message: module.moduleRef,
|
|
189
|
+
});
|
|
58
190
|
continue;
|
|
191
|
+
}
|
|
59
192
|
const setup = await dispatchModuleCommand(catalog, module, "setup", context(workspaceRoot, target?.input));
|
|
60
193
|
results.push(setup);
|
|
194
|
+
reportProgress(reporter, {
|
|
195
|
+
command: "setup",
|
|
196
|
+
phase: "setup",
|
|
197
|
+
current: index + 1,
|
|
198
|
+
total: modulesInOrder.length,
|
|
199
|
+
moduleRef: module.moduleRef,
|
|
200
|
+
status: succeeded(setup.result)
|
|
201
|
+
? "SUCCEEDED"
|
|
202
|
+
: setup.result.status === "ACTION_REQUIRED"
|
|
203
|
+
? "SKIPPED"
|
|
204
|
+
: "FAILED",
|
|
205
|
+
message: module.moduleRef,
|
|
206
|
+
});
|
|
61
207
|
if (!succeeded(setup.result)) {
|
|
62
208
|
completed = false;
|
|
63
209
|
if (target !== undefined)
|
|
@@ -68,34 +214,71 @@ export async function setupModulesThin(catalog, modules, workspaceRoot, target)
|
|
|
68
214
|
throw new PlatformError("INVALID_REQUEST", `setup target module ${target?.moduleRef ?? ""} was not discovered`);
|
|
69
215
|
return { phase: "setup", results, completed };
|
|
70
216
|
}
|
|
71
|
-
export async function startModulesThin(catalog, modules, workspaceRoot) {
|
|
217
|
+
export async function startModulesThin(catalog, modules, workspaceRoot, reporter) {
|
|
72
218
|
const results = [];
|
|
73
219
|
const modulesInOrder = ordered(modules);
|
|
74
|
-
|
|
220
|
+
const blockers = [];
|
|
221
|
+
const runtimeByRef = new Map();
|
|
222
|
+
for (const [index, module] of modulesInOrder.entries()) {
|
|
223
|
+
reportProgress(reporter, {
|
|
224
|
+
command: "start",
|
|
225
|
+
phase: "status",
|
|
226
|
+
current: index + 1,
|
|
227
|
+
total: modulesInOrder.length,
|
|
228
|
+
moduleRef: module.moduleRef,
|
|
229
|
+
status: "STARTED",
|
|
230
|
+
message: `${module.moduleRef}`,
|
|
231
|
+
});
|
|
75
232
|
const status = await dispatchModuleCommand(catalog, module, "status", context(workspaceRoot));
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
};
|
|
233
|
+
results.push(status);
|
|
234
|
+
if (!succeeded(status.result)) {
|
|
235
|
+
blockers.push({ moduleRef: module.moduleRef, setupStatus: "FAILED" });
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
82
238
|
const observed = moduleStatusObservationSchema.parse(status.result.data);
|
|
239
|
+
runtimeByRef.set(module.moduleRef, observed.runtimeStatus);
|
|
83
240
|
if (observed.setupStatus !== "READY")
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
blockedBy: {
|
|
89
|
-
moduleRef: module.moduleRef,
|
|
90
|
-
setupStatus: observed.setupStatus,
|
|
91
|
-
},
|
|
92
|
-
};
|
|
241
|
+
blockers.push({
|
|
242
|
+
moduleRef: module.moduleRef,
|
|
243
|
+
setupStatus: observed.setupStatus,
|
|
244
|
+
});
|
|
93
245
|
}
|
|
94
|
-
|
|
246
|
+
if (results.some((item) => !succeeded(item.result)) || blockers.length > 0)
|
|
247
|
+
return {
|
|
248
|
+
phase: "start",
|
|
249
|
+
results,
|
|
250
|
+
completed: false,
|
|
251
|
+
...(blockers[0] ? { blockedBy: blockers[0], blockers } : {}),
|
|
252
|
+
};
|
|
253
|
+
const skipped = [];
|
|
254
|
+
for (const [index, module] of modulesInOrder.entries()) {
|
|
255
|
+
const runtimeStatus = runtimeByRef.get(module.moduleRef);
|
|
256
|
+
if (runtimeStatus === "RUNNING" || runtimeStatus === "NOT_APPLICABLE") {
|
|
257
|
+
skipped.push({ moduleRef: module.moduleRef, reason: runtimeStatus });
|
|
258
|
+
reportProgress(reporter, {
|
|
259
|
+
command: "start",
|
|
260
|
+
phase: "start",
|
|
261
|
+
current: index + 1,
|
|
262
|
+
total: modulesInOrder.length,
|
|
263
|
+
moduleRef: module.moduleRef,
|
|
264
|
+
status: "SKIPPED",
|
|
265
|
+
message: `${module.moduleRef}`,
|
|
266
|
+
});
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
95
269
|
const started = await dispatchModuleCommand(catalog, module, "start", context(workspaceRoot));
|
|
96
270
|
results.push(started);
|
|
271
|
+
reportProgress(reporter, {
|
|
272
|
+
command: "start",
|
|
273
|
+
phase: "start",
|
|
274
|
+
current: index + 1,
|
|
275
|
+
total: modulesInOrder.length,
|
|
276
|
+
moduleRef: module.moduleRef,
|
|
277
|
+
status: succeeded(started.result) ? "SUCCEEDED" : "FAILED",
|
|
278
|
+
message: `${module.moduleRef}`,
|
|
279
|
+
});
|
|
97
280
|
if (!succeeded(started.result))
|
|
98
|
-
return { phase: "start", results, completed: false };
|
|
281
|
+
return { phase: "start", results, completed: false, skipped };
|
|
99
282
|
}
|
|
100
|
-
return { phase: "start", results, completed: true };
|
|
283
|
+
return { phase: "start", results, completed: true, skipped };
|
|
101
284
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type PlatformProgressStatus = "STARTED" | "SUCCEEDED" | "ACTION_REQUIRED" | "FAILED" | "SKIPPED";
|
|
2
|
+
export interface PlatformProgressEvent {
|
|
3
|
+
command: string;
|
|
4
|
+
phase: string;
|
|
5
|
+
current?: number;
|
|
6
|
+
total?: number;
|
|
7
|
+
moduleRef?: string;
|
|
8
|
+
status: PlatformProgressStatus;
|
|
9
|
+
message: string;
|
|
10
|
+
}
|
|
11
|
+
export type PlatformProgressReporter = (event: PlatformProgressEvent) => void;
|
|
12
|
+
export declare const reportProgress: (reporter: PlatformProgressReporter | undefined, event: PlatformProgressEvent) => void | undefined;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const reportProgress = (reporter, event) => reporter?.(event);
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { clearLine, cursorTo } from "node:readline";
|
|
2
|
+
const colors = {
|
|
3
|
+
green: "\u001b[32m",
|
|
4
|
+
red: "\u001b[31m",
|
|
5
|
+
yellow: "\u001b[33m",
|
|
6
|
+
dim: "\u001b[2m",
|
|
7
|
+
reset: "\u001b[0m",
|
|
8
|
+
};
|
|
9
|
+
function progressLine(event, color) {
|
|
10
|
+
const prefix = event.current && event.total
|
|
11
|
+
? `[${String(event.current).padStart(2, "0")}/${event.total}] `
|
|
12
|
+
: "";
|
|
13
|
+
const suffix = event.status === "SUCCEEDED"
|
|
14
|
+
? "完成"
|
|
15
|
+
: event.status === "FAILED"
|
|
16
|
+
? "失败"
|
|
17
|
+
: event.status === "SKIPPED"
|
|
18
|
+
? "跳过"
|
|
19
|
+
: "";
|
|
20
|
+
if (!color || suffix === "")
|
|
21
|
+
return `${prefix}${event.message}${suffix ? `… ${suffix}` : "…"}`;
|
|
22
|
+
const tone = event.status === "SUCCEEDED"
|
|
23
|
+
? colors.green
|
|
24
|
+
: event.status === "FAILED"
|
|
25
|
+
? colors.red
|
|
26
|
+
: colors.yellow;
|
|
27
|
+
return `${colors.dim}${prefix}${colors.reset}${event.message}… ${tone}${suffix}${colors.reset}`;
|
|
28
|
+
}
|
|
29
|
+
export function createTerminalProgressReporter(stream = process.stderr) {
|
|
30
|
+
const interactive = stream.isTTY === true;
|
|
31
|
+
const color = interactive && !("NO_COLOR" in process.env);
|
|
32
|
+
return (event) => {
|
|
33
|
+
const line = progressLine(event, color);
|
|
34
|
+
if (!interactive) {
|
|
35
|
+
stream.write(`${line}\n`);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
cursorTo(stream, 0);
|
|
39
|
+
clearLine(stream, 0);
|
|
40
|
+
stream.write(line);
|
|
41
|
+
if (event.status !== "STARTED")
|
|
42
|
+
stream.write("\n");
|
|
43
|
+
};
|
|
44
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tomflow/proflow-platform-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.23",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
"exports": {
|
|
9
9
|
".": "./dist/src/index.js",
|
|
10
10
|
"./cli": "./dist/src/cli.js",
|
|
11
|
+
"./command": "./dist/src/command.js",
|
|
11
12
|
"./deployment/adapter": "./dist/deployment/adapter.js",
|
|
12
13
|
"./deployment/descriptor": "./dist/deployment/descriptor.js"
|
|
13
14
|
},
|
|
@@ -23,11 +24,11 @@
|
|
|
23
24
|
"SETUP.md"
|
|
24
25
|
],
|
|
25
26
|
"dependencies": {
|
|
26
|
-
"@tomflow/proflow-module-contract": "^0.1.
|
|
27
|
+
"@tomflow/proflow-module-contract": "^0.1.9"
|
|
27
28
|
},
|
|
28
29
|
"devDependencies": {
|
|
29
|
-
"@tomflow/proflow-deployment-conformance": "^0.1.
|
|
30
|
-
"@tomflow/proflow-module-template": "^0.1.
|
|
30
|
+
"@tomflow/proflow-deployment-conformance": "^0.1.7",
|
|
31
|
+
"@tomflow/proflow-module-template": "^0.1.7"
|
|
31
32
|
},
|
|
32
33
|
"description": "Thin Platform CLI for Module discovery, documentation, package synchronization and lifecycle orchestration.",
|
|
33
34
|
"keywords": [
|
package/proflow.module.json
CHANGED