@tomflow/proflow-platform-cli 0.1.33 → 0.1.34
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 +6 -0
- package/DOCS.md +34 -6
- package/dist/deployment/adapter.d.ts +7 -7
- package/dist/deployment/descriptor.d.ts +1 -1
- package/dist/deployment/descriptor.js +1 -1
- package/dist/src/cli.d.ts +5 -1
- package/dist/src/cli.js +228 -49
- package/dist/src/install/package-manager.d.ts +7 -1
- package/dist/src/install/package-manager.js +49 -11
- package/dist/src/lifecycle/thin.d.ts +1 -0
- package/dist/src/lifecycle/thin.js +5 -1
- package/dist/src/progress.d.ts +3 -0
- package/dist/src/registry/npm-registry.d.ts +6 -0
- package/dist/src/registry/npm-registry.js +24 -8
- package/dist/src/terminal.js +40 -21
- package/package.json +4 -4
- package/proflow.module.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# @tomflow/proflow-platform-cli
|
|
2
2
|
|
|
3
|
+
## 0.1.34
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Upgrade the ProFlow terminal experience, stream Registry and package-manager progress, publish structured Chinese Module documentation, and provide numbered package-owned Setup assistants.
|
|
8
|
+
|
|
3
9
|
## 0.1.33
|
|
4
10
|
|
|
5
11
|
### Patch Changes
|
package/DOCS.md
CHANGED
|
@@ -1,11 +1,39 @@
|
|
|
1
|
-
# platform-cli
|
|
1
|
+
# platform-cli — ProFlow 管理命令行
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
## 模块定位与作用
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
ProFlow 的薄管理面,负责从 Registry 发现 Module、同步 npm 包、解析依赖顺序,并转发和聚合七标准命令。
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
## 主要能力
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
- 安装、卸载并验证完整 Module 包集合。
|
|
10
|
+
- 聚合 status、docs 和 setup,不解释 Module 私有业务。
|
|
11
|
+
- 按依赖顺序启动、按逆依赖顺序停止,并支持幂等重试。
|
|
10
12
|
|
|
11
|
-
|
|
13
|
+
## 提供的 API 与 Public Contract
|
|
14
|
+
|
|
15
|
+
- 不提供领域业务 Contract。
|
|
16
|
+
- 提供 `platform` CLI;`runCli()` 返回强类型 `CliOutcome`,终端层负责中文渲染。
|
|
17
|
+
|
|
18
|
+
## 依赖的 Module、Contract 和外部资源
|
|
19
|
+
|
|
20
|
+
- 依赖 npm Registry、当前 Workspace package manager 和所有 discovered Module 的公开 adapter。
|
|
21
|
+
- 不 deep import Module 私有代码,不读取 Module 私有配置。
|
|
22
|
+
|
|
23
|
+
## 运行形态与生命周期
|
|
24
|
+
|
|
25
|
+
类型为 CLI,命令执行结束后退出;不是业务 Runtime 或长期守护进程。
|
|
26
|
+
|
|
27
|
+
## 使用方式
|
|
28
|
+
|
|
29
|
+
推荐顺序为 `platform install`、`status`、`docs`、`setup`、`start`、`stop`、`uninstall`。
|
|
30
|
+
|
|
31
|
+
## 职责边界与限制
|
|
32
|
+
|
|
33
|
+
不代理业务 API、不保存 Module setup 输入、不建立 preflight/doctor 第二真源,也不提供 JSON 输出选项。
|
|
34
|
+
|
|
35
|
+
## 术语
|
|
36
|
+
|
|
37
|
+
- Discovery(发现):从 Registry 与 Workspace 识别 governed Module。
|
|
38
|
+
- Aggregation(聚合):保留各 Module 结果并以统一终端形式展示。
|
|
39
|
+
- Dependency Order(依赖顺序):Provider 先于 Consumer 的生命周期顺序。
|
|
@@ -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.34";
|
|
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.34";
|
|
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.34";
|
|
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.34";
|
|
44
44
|
};
|
|
45
45
|
observedEffects: never[];
|
|
46
46
|
}>;
|
|
@@ -50,7 +50,7 @@ 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.34";
|
|
54
54
|
data: {
|
|
55
55
|
docs: string;
|
|
56
56
|
};
|
|
@@ -63,7 +63,7 @@ export declare const behaviorAdapter: {
|
|
|
63
63
|
readonly ok: true;
|
|
64
64
|
readonly status: "SUCCEEDED";
|
|
65
65
|
readonly moduleRef: "platform-cli";
|
|
66
|
-
readonly moduleVersion: "0.1.
|
|
66
|
+
readonly moduleVersion: "0.1.34";
|
|
67
67
|
};
|
|
68
68
|
observedEffects: never[];
|
|
69
69
|
}>;
|
|
@@ -73,7 +73,7 @@ export declare const behaviorAdapter: {
|
|
|
73
73
|
readonly ok: true;
|
|
74
74
|
readonly status: "SUCCEEDED";
|
|
75
75
|
readonly moduleRef: "platform-cli";
|
|
76
|
-
readonly moduleVersion: "0.1.
|
|
76
|
+
readonly moduleVersion: "0.1.34";
|
|
77
77
|
};
|
|
78
78
|
observedEffects: never[];
|
|
79
79
|
}>;
|
|
@@ -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.34";
|
|
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.34",
|
|
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
|
@@ -21,4 +21,8 @@ export interface CliRuntimeOptions {
|
|
|
21
21
|
onProgress?: PlatformProgressReporter;
|
|
22
22
|
}
|
|
23
23
|
export declare function runCli(argv: readonly string[], runtime?: CliRuntimeOptions): Promise<CliOutcome>;
|
|
24
|
-
export
|
|
24
|
+
export interface HumanRenderOptions {
|
|
25
|
+
color?: boolean;
|
|
26
|
+
width?: number;
|
|
27
|
+
}
|
|
28
|
+
export declare function renderHumanResult(result: CliOutcome, options?: HumanRenderOptions): string;
|
package/dist/src/cli.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
2
3
|
import { readFile, realpath, stat } from "node:fs/promises";
|
|
3
4
|
import { resolve } from "node:path";
|
|
4
5
|
import { moduleDocsDataSchema, moduleSetupPlanDataSchema, moduleStatusObservationSchema, } from "@tomflow/proflow-module-contract";
|
|
@@ -230,21 +231,50 @@ async function validateInstalledPackageSet(root, candidates, previousManaged) {
|
|
|
230
231
|
}
|
|
231
232
|
}
|
|
232
233
|
async function handleInstall(root, runtime) {
|
|
234
|
+
const startedAt = Date.now();
|
|
233
235
|
reportProgress(runtime.onProgress, {
|
|
234
236
|
command: "install",
|
|
235
237
|
phase: "workspace",
|
|
238
|
+
kind: "phase",
|
|
236
239
|
status: "STARTED",
|
|
237
240
|
message: "正在校验 Workspace",
|
|
238
241
|
});
|
|
239
242
|
const metadata = await ensureWorkspaceMetadata(root);
|
|
243
|
+
reportProgress(runtime.onProgress, {
|
|
244
|
+
command: "install",
|
|
245
|
+
phase: "workspace",
|
|
246
|
+
kind: "phase",
|
|
247
|
+
status: "SUCCEEDED",
|
|
248
|
+
message: "Workspace 校验完成",
|
|
249
|
+
elapsedMs: Date.now() - startedAt,
|
|
250
|
+
});
|
|
240
251
|
reportProgress(runtime.onProgress, {
|
|
241
252
|
command: "install",
|
|
242
253
|
phase: "registry",
|
|
254
|
+
kind: "phase",
|
|
243
255
|
status: "STARTED",
|
|
244
|
-
message: "
|
|
256
|
+
message: "正在连接 Registry",
|
|
245
257
|
});
|
|
246
258
|
const discovered = await discoverRegistryModules({
|
|
247
259
|
workspaceRoot: root,
|
|
260
|
+
onSearchComplete: (total) => reportProgress(runtime.onProgress, {
|
|
261
|
+
command: "install",
|
|
262
|
+
phase: "registry",
|
|
263
|
+
kind: "detail",
|
|
264
|
+
status: "STARTED",
|
|
265
|
+
total,
|
|
266
|
+
message: `Registry 返回 ${total} 个候选模块,正在核验`,
|
|
267
|
+
}),
|
|
268
|
+
onPackageChecked: ({ current, total, packageName }) => reportProgress(runtime.onProgress, {
|
|
269
|
+
command: "install",
|
|
270
|
+
phase: "registry",
|
|
271
|
+
kind: "detail",
|
|
272
|
+
status: "SUCCEEDED",
|
|
273
|
+
current,
|
|
274
|
+
total,
|
|
275
|
+
moduleRef: packageName.replace(PRO_FLOW_PACKAGE_PREFIX, ""),
|
|
276
|
+
message: `核验 ${packageName.replace(PRO_FLOW_PACKAGE_PREFIX, "")}`,
|
|
277
|
+
}),
|
|
248
278
|
...(runtime.registryRunner === undefined
|
|
249
279
|
? {}
|
|
250
280
|
: { runner: runtime.registryRunner }),
|
|
@@ -252,6 +282,7 @@ async function handleInstall(root, runtime) {
|
|
|
252
282
|
reportProgress(runtime.onProgress, {
|
|
253
283
|
command: "install",
|
|
254
284
|
phase: "registry",
|
|
285
|
+
kind: "phase",
|
|
255
286
|
status: "SUCCEEDED",
|
|
256
287
|
message: `已发现 ${discovered.candidates.length} 个注册模块`,
|
|
257
288
|
});
|
|
@@ -264,6 +295,7 @@ async function handleInstall(root, runtime) {
|
|
|
264
295
|
reportProgress(runtime.onProgress, {
|
|
265
296
|
command: "install",
|
|
266
297
|
phase: "packages",
|
|
298
|
+
kind: "phase",
|
|
267
299
|
status: "STARTED",
|
|
268
300
|
message: "正在同步依赖",
|
|
269
301
|
});
|
|
@@ -273,6 +305,7 @@ async function handleInstall(root, runtime) {
|
|
|
273
305
|
packageName: item.packageName,
|
|
274
306
|
version: item.moduleVersion,
|
|
275
307
|
})),
|
|
308
|
+
onOutput: (line) => reportPackageManagerOutput(runtime, "install", line),
|
|
276
309
|
...(runtime.packageRunner === undefined
|
|
277
310
|
? {}
|
|
278
311
|
: { runner: runtime.packageRunner }),
|
|
@@ -283,12 +316,20 @@ async function handleInstall(root, runtime) {
|
|
|
283
316
|
reportProgress(runtime.onProgress, {
|
|
284
317
|
command: "install",
|
|
285
318
|
phase: "packages",
|
|
319
|
+
kind: "phase",
|
|
286
320
|
status: "SUCCEEDED",
|
|
287
321
|
message: "依赖同步完成",
|
|
288
322
|
});
|
|
289
323
|
await recordPnpmPolicyOwnership(root, pnpmPolicyBefore);
|
|
290
324
|
await validateInstalledPackageSet(root, discovered.candidates, previousManaged);
|
|
291
325
|
const { catalog, modules } = await buildContext(root);
|
|
326
|
+
reportProgress(runtime.onProgress, {
|
|
327
|
+
command: "install",
|
|
328
|
+
phase: "validation",
|
|
329
|
+
kind: "phase",
|
|
330
|
+
status: "SUCCEEDED",
|
|
331
|
+
message: `已验证 ${modules.length} 个已安装模块`,
|
|
332
|
+
});
|
|
292
333
|
const moduleInstall = await installModulesThin(catalog, modules, root, runtime.onProgress);
|
|
293
334
|
return outcome("install", batchStatus(moduleInstall), root, {
|
|
294
335
|
registry: discovered.registry,
|
|
@@ -302,6 +343,18 @@ async function handleInstall(root, runtime) {
|
|
|
302
343
|
next: "platform status",
|
|
303
344
|
});
|
|
304
345
|
}
|
|
346
|
+
const usefulPackageOutput = /(?:resolved|downloaded|reused|linked|added|removed|packages:|progress:|warn|warning|error|ERR_|done|completed|audited|changed)/i;
|
|
347
|
+
function reportPackageManagerOutput(runtime, command, output) {
|
|
348
|
+
if (!usefulPackageOutput.test(output.line))
|
|
349
|
+
return;
|
|
350
|
+
reportProgress(runtime.onProgress, {
|
|
351
|
+
command,
|
|
352
|
+
phase: "packages",
|
|
353
|
+
kind: "subprocess",
|
|
354
|
+
status: /(?:error|ERR_)/i.test(output.line) ? "FAILED" : "STARTED",
|
|
355
|
+
message: output.line,
|
|
356
|
+
});
|
|
357
|
+
}
|
|
305
358
|
async function workspaceProFlowDependencies(root) {
|
|
306
359
|
try {
|
|
307
360
|
const parsed = JSON.parse(await readFile(resolve(root, "package.json"), "utf8"));
|
|
@@ -339,6 +392,7 @@ async function handleUninstall(root, runtime) {
|
|
|
339
392
|
const mutation = await removeWorkspacePackages({
|
|
340
393
|
workspaceRoot: root,
|
|
341
394
|
packageNames,
|
|
395
|
+
onOutput: (line) => reportPackageManagerOutput(runtime, "uninstall", line),
|
|
342
396
|
...(runtime.packageRunner === undefined
|
|
343
397
|
? {}
|
|
344
398
|
: { runner: runtime.packageRunner }),
|
|
@@ -447,7 +501,32 @@ function errorOutcome(command, error) {
|
|
|
447
501
|
};
|
|
448
502
|
}
|
|
449
503
|
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
450
|
-
|
|
504
|
+
const ansi = {
|
|
505
|
+
reset: "\u001b[0m",
|
|
506
|
+
bold: "\u001b[1m",
|
|
507
|
+
dim: "\u001b[2m",
|
|
508
|
+
cyan: "\u001b[36m",
|
|
509
|
+
blue: "\u001b[34m",
|
|
510
|
+
green: "\u001b[32m",
|
|
511
|
+
yellow: "\u001b[33m",
|
|
512
|
+
red: "\u001b[31m",
|
|
513
|
+
};
|
|
514
|
+
function humanTheme(options = {}) {
|
|
515
|
+
const enabled = options.color === true;
|
|
516
|
+
const paint = (tone, value) => enabled ? `${ansi[tone]}${value}${ansi.reset}` : value;
|
|
517
|
+
return {
|
|
518
|
+
width: Math.max(60, options.width ?? 100),
|
|
519
|
+
paint,
|
|
520
|
+
title: (value) => paint("bold", paint("cyan", value)),
|
|
521
|
+
section: (value) => paint("bold", value),
|
|
522
|
+
command: (value) => paint("blue", value),
|
|
523
|
+
muted: (value) => paint("dim", value),
|
|
524
|
+
success: (value) => paint("green", value),
|
|
525
|
+
warning: (value) => paint("yellow", value),
|
|
526
|
+
failure: (value) => paint("red", value),
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
function renderStatus(data, theme) {
|
|
451
530
|
if (!isRecord(data) || !Array.isArray(data.modules))
|
|
452
531
|
return "未发现模块。";
|
|
453
532
|
const setupLabels = {
|
|
@@ -462,8 +541,15 @@ function renderStatus(data) {
|
|
|
462
541
|
NOT_APPLICABLE: "无独立进程",
|
|
463
542
|
};
|
|
464
543
|
const modules = data.modules.filter(isRecord);
|
|
465
|
-
const
|
|
466
|
-
|
|
544
|
+
const unhealthy = modules.filter((item) => item.setupStatus !== "READY" || item.runtimeStatus === "FAILED");
|
|
545
|
+
const running = modules.filter((item) => item.setupStatus === "READY" && item.runtimeStatus === "RUNNING");
|
|
546
|
+
const healthy = modules.filter((item) => item.setupStatus === "READY" &&
|
|
547
|
+
item.runtimeStatus !== "RUNNING" &&
|
|
548
|
+
item.runtimeStatus !== "FAILED");
|
|
549
|
+
const lines = [theme.title("ProFlow 状态"), ""];
|
|
550
|
+
if (unhealthy.length > 0)
|
|
551
|
+
lines.push(theme.section("需要处理"), "");
|
|
552
|
+
for (const raw of unhealthy) {
|
|
467
553
|
const moduleRef = String(raw.moduleRef);
|
|
468
554
|
const setupStatus = String(raw.setupStatus);
|
|
469
555
|
const runtimeStatus = String(raw.runtimeStatus);
|
|
@@ -478,43 +564,69 @@ function renderStatus(data) {
|
|
|
478
564
|
: runtimeStatus === "NOT_APPLICABLE"
|
|
479
565
|
? "○"
|
|
480
566
|
: "✓";
|
|
481
|
-
|
|
567
|
+
const tone = failed ? theme.failure : theme.warning;
|
|
568
|
+
lines.push(`${tone(icon)} ${theme.section(moduleRef)} ${theme.muted(String(raw.version))}`);
|
|
569
|
+
lines.push(` ${tone(setupLabels[setupStatus] ?? "未知")} · ${runtimeLabels[runtimeStatus] ?? "未知"}`);
|
|
482
570
|
if (failed) {
|
|
483
|
-
lines.push(` 原因:${setupStatus === "FAILED" ? "模块配置检查失败" : "模块运行状态异常"}`, `
|
|
571
|
+
lines.push(` 原因:${setupStatus === "FAILED" ? "模块配置检查失败" : "模块运行状态异常"}`, ` 下一步:${theme.command(`platform setup --module ${moduleRef}`)}`);
|
|
484
572
|
}
|
|
485
573
|
else if (actionRequired) {
|
|
486
|
-
lines.push(" 原因:模块尚未完成配置", `
|
|
574
|
+
lines.push(" 原因:模块尚未完成配置", ` 下一步:${theme.command(`platform setup --module ${moduleRef}`)}`);
|
|
487
575
|
}
|
|
576
|
+
lines.push("");
|
|
488
577
|
}
|
|
578
|
+
const compact = (title, entries, symbol) => {
|
|
579
|
+
if (entries.length === 0)
|
|
580
|
+
return;
|
|
581
|
+
lines.push(theme.section(title), "");
|
|
582
|
+
for (const item of entries) {
|
|
583
|
+
const runtime = runtimeLabels[String(item.runtimeStatus)] ?? "未知";
|
|
584
|
+
lines.push(`${theme.success(symbol)} ${String(item.moduleRef).padEnd(32)} ${theme.muted(String(item.version).padEnd(9))} ${runtime}`);
|
|
585
|
+
}
|
|
586
|
+
lines.push("");
|
|
587
|
+
};
|
|
588
|
+
compact("运行中", running, "●");
|
|
589
|
+
compact("已就绪", healthy, "○");
|
|
489
590
|
const ready = modules.filter((item) => item.setupStatus === "READY").length;
|
|
490
591
|
const action = modules.filter((item) => item.setupStatus === "ACTION_REQUIRED").length;
|
|
491
592
|
const failed = modules.filter((item) => item.setupStatus === "FAILED").length;
|
|
492
|
-
lines.push("",
|
|
593
|
+
lines.push(theme.section("汇总"), `${theme.success(`${ready} 已就绪`)} · ${theme.warning(`${action} 需要操作`)} · ${failed > 0 ? theme.failure(`${failed} 失败`) : `${failed} 失败`}`);
|
|
493
594
|
return lines.join("\n");
|
|
494
595
|
}
|
|
495
|
-
function renderTerminalMarkdown(source) {
|
|
596
|
+
function renderTerminalMarkdown(source, theme) {
|
|
597
|
+
let inCode = false;
|
|
496
598
|
return source
|
|
497
599
|
.split(/\r?\n/)
|
|
498
600
|
.map((line) => {
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
return
|
|
502
|
-
|
|
601
|
+
if (line.trim().startsWith("```")) {
|
|
602
|
+
inCode = !inCode;
|
|
603
|
+
return "";
|
|
604
|
+
}
|
|
605
|
+
if (inCode)
|
|
606
|
+
return ` ${theme.command(line)}`;
|
|
607
|
+
const heading = line.match(/^(#{1,6})\s+(.+)$/);
|
|
608
|
+
if (heading?.[2])
|
|
609
|
+
return heading[1]?.length === 1
|
|
610
|
+
? theme.title(heading[2].replaceAll("`", ""))
|
|
611
|
+
: theme.section(heading[2].replaceAll("`", ""));
|
|
612
|
+
const bullet = line.match(/^\s*[-*]\s+(.+)$/);
|
|
503
613
|
if (bullet?.[1])
|
|
504
|
-
return `
|
|
505
|
-
|
|
614
|
+
return ` ${theme.paint("cyan", "•")} ${bullet[1]}`;
|
|
615
|
+
if (/^\|.*\|$/.test(line))
|
|
616
|
+
return theme.muted(line);
|
|
617
|
+
return line.replace(/`([^`]+)`/g, (_match, code) => theme.command(code));
|
|
506
618
|
})
|
|
507
619
|
.join("\n")
|
|
508
620
|
.replace(/\n{3,}/g, "\n\n")
|
|
509
621
|
.trim();
|
|
510
622
|
}
|
|
511
|
-
function renderDocs(data) {
|
|
623
|
+
function renderDocs(data, theme) {
|
|
512
624
|
if (!isRecord(data) || !Array.isArray(data.modules))
|
|
513
625
|
return "未发现模块文档。";
|
|
514
|
-
const lines = ["ProFlow 模块文档"];
|
|
626
|
+
const lines = [theme.title("ProFlow 模块文档")];
|
|
515
627
|
for (const raw of data.modules)
|
|
516
628
|
if (isRecord(raw))
|
|
517
|
-
lines.push("", "─".repeat(
|
|
629
|
+
lines.push("", theme.muted("─".repeat(Math.min(theme.width, 96))), `${theme.section(String(raw.moduleRef))} ${theme.muted(String(raw.version))}`, "", renderTerminalMarkdown(String(raw.docs ?? ""), theme));
|
|
518
630
|
if (Array.isArray(data.errors) && data.errors.length > 0)
|
|
519
631
|
lines.push("", "文档读取失败:", ...data.errors
|
|
520
632
|
.filter(isRecord)
|
|
@@ -555,10 +667,10 @@ const setupCommands = {
|
|
|
555
667
|
inputs: "Custom GPT URL",
|
|
556
668
|
},
|
|
557
669
|
};
|
|
558
|
-
function renderSetup(data) {
|
|
670
|
+
function renderSetup(data, theme) {
|
|
559
671
|
if (!isRecord(data) || !Array.isArray(data.results))
|
|
560
672
|
return "没有需要执行的配置步骤。";
|
|
561
|
-
const lines = ["ProFlow 配置", ""];
|
|
673
|
+
const lines = [theme.title("ProFlow 配置"), ""];
|
|
562
674
|
const skipped = Array.isArray(data.skipped) ? data.skipped : [];
|
|
563
675
|
let ready = skipped.filter((item) => isRecord(item) && item.reason === "READY").length;
|
|
564
676
|
let needsAction = 0;
|
|
@@ -576,19 +688,31 @@ function renderSetup(data) {
|
|
|
576
688
|
blocked += 1;
|
|
577
689
|
else
|
|
578
690
|
needsAction += 1;
|
|
579
|
-
lines.push(`${status === "FAILED" ? "✕" : "◆"} ${moduleRef}`);
|
|
691
|
+
lines.push(`${status === "FAILED" ? theme.failure("✕") : theme.warning("◆")} ${theme.section(moduleRef)}`);
|
|
580
692
|
const plan = moduleSetupPlanDataSchema.safeParse(raw.result.data);
|
|
581
693
|
if (plan.success) {
|
|
582
694
|
for (const [index, step] of plan.data.steps.entries()) {
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
695
|
+
const marker = step.state === "DONE"
|
|
696
|
+
? theme.success("✓")
|
|
697
|
+
: step.state === "BLOCKED"
|
|
698
|
+
? theme.failure("✕")
|
|
699
|
+
: index ===
|
|
700
|
+
plan.data.steps.findIndex((item) => item.state === "TODO")
|
|
701
|
+
? theme.warning("→")
|
|
702
|
+
: theme.muted("○");
|
|
703
|
+
lines.push(` ${marker} ${String(index + 1).padStart(2, "0")} ${step.title}`);
|
|
704
|
+
if (step.description)
|
|
705
|
+
lines.push(` ${step.description}`);
|
|
706
|
+
lines.push(` 运行:${theme.command(step.execution.interactive)}`);
|
|
707
|
+
lines.push(` AI:${theme.command(step.execution.nonInteractive)}`);
|
|
586
708
|
if (step.requiredInputs.length > 0)
|
|
587
|
-
lines.push(`
|
|
588
|
-
|
|
589
|
-
|
|
709
|
+
lines.push(` 需要:${step.requiredInputs.map((item) => item.description).join("、")}`);
|
|
710
|
+
if (step.humanAction)
|
|
711
|
+
lines.push(` 人工操作:${step.humanAction}`);
|
|
712
|
+
lines.push(` 验证:${theme.command(step.verify)}`);
|
|
713
|
+
lines.push(` 完成:${step.successCondition}`);
|
|
590
714
|
if (step.blockedReason)
|
|
591
|
-
lines.push(`
|
|
715
|
+
lines.push(` 原因:${theme.failure(step.blockedReason)}`);
|
|
592
716
|
}
|
|
593
717
|
lines.push("");
|
|
594
718
|
continue;
|
|
@@ -619,34 +743,49 @@ function renderSetup(data) {
|
|
|
619
743
|
}
|
|
620
744
|
if (needsAction === 0 && blocked === 0)
|
|
621
745
|
lines.push("全部模块均已就绪。");
|
|
622
|
-
lines.push(`汇总:${ready}
|
|
746
|
+
lines.push(theme.section("汇总"), `汇总:${theme.success(`${ready} 个已就绪`)},${theme.warning(`${needsAction} 个需要操作`)},${blocked > 0 ? theme.failure(`${blocked} 个系统阻塞`) : `${blocked} 个系统阻塞`}`);
|
|
623
747
|
return lines.join("\n").trimEnd();
|
|
624
748
|
}
|
|
625
|
-
export function renderHumanResult(result) {
|
|
749
|
+
export function renderHumanResult(result, options = {}) {
|
|
750
|
+
const theme = humanTheme(options);
|
|
626
751
|
if (result.command === "setup" &&
|
|
627
752
|
isRecord(result.data) &&
|
|
628
753
|
Array.isArray(result.data.results))
|
|
629
|
-
return renderSetup(result.data);
|
|
754
|
+
return renderSetup(result.data, theme);
|
|
630
755
|
if (result.command === "start" && isRecord(result.data)) {
|
|
631
756
|
const blockers = Array.isArray(result.data.blockers)
|
|
632
757
|
? result.data.blockers.filter(isRecord)
|
|
633
758
|
: [];
|
|
634
759
|
if (blockers.length > 0)
|
|
635
760
|
return [
|
|
636
|
-
"平台未启动:存在未就绪模块",
|
|
761
|
+
theme.failure("平台未启动:存在未就绪模块"),
|
|
637
762
|
"",
|
|
638
|
-
...blockers.
|
|
763
|
+
...blockers.flatMap((item) => [
|
|
764
|
+
`${String(item.setupStatus) === "FAILED" ? theme.failure("✕") : theme.warning("◆")} ${theme.section(String(item.moduleRef))} ${String(item.setupStatus) === "FAILED" ? theme.failure("失败") : theme.warning("需要操作")}`,
|
|
765
|
+
` 原因:${typeof item.reason === "string" ? item.reason : "模块配置尚未完成"}`,
|
|
766
|
+
` 下一步:${theme.command(`platform setup --module ${String(item.moduleRef)}`)}`,
|
|
767
|
+
]),
|
|
639
768
|
"",
|
|
640
|
-
|
|
769
|
+
`处理方式:${theme.command(`platform setup${result.workspaceRoot ? ` --workspace "${result.workspaceRoot}"` : ""}`)}`,
|
|
641
770
|
].join("\n");
|
|
642
771
|
}
|
|
643
772
|
if (result.status === "FAILED" && result.error)
|
|
644
|
-
return
|
|
773
|
+
return [
|
|
774
|
+
theme.failure(`╭─ ${result.command} 执行失败`),
|
|
775
|
+
`${theme.failure("│")} ${theme.section(result.error.code)}`,
|
|
776
|
+
`${theme.failure("│")} ${result.error.message}`,
|
|
777
|
+
theme.failure("╰─ 请根据提示修复后重试"),
|
|
778
|
+
].join("\n");
|
|
645
779
|
if (result.command === "help")
|
|
646
780
|
return [
|
|
647
|
-
"ProFlow 平台命令行",
|
|
781
|
+
theme.title("ProFlow 平台命令行"),
|
|
782
|
+
"管理 ProFlow 模块的安装、配置、文档和运行生命周期。",
|
|
648
783
|
"",
|
|
649
|
-
"
|
|
784
|
+
theme.section("用法"),
|
|
785
|
+
` ${theme.command("platform <command> [--workspace <路径>]")}`,
|
|
786
|
+
"",
|
|
787
|
+
theme.section("命令"),
|
|
788
|
+
`${theme.command("platform install")} 安装并初始化全部 ProFlow 模块`,
|
|
650
789
|
"platform uninstall 卸载模块包,保留 Workspace 数据",
|
|
651
790
|
"platform status 查看模块配置与运行状态",
|
|
652
791
|
"platform setup 自动配置并列出全部剩余步骤",
|
|
@@ -654,20 +793,32 @@ export function renderHumanResult(result) {
|
|
|
654
793
|
"platform start 完成全量检查后启动平台",
|
|
655
794
|
"platform stop 按逆依赖顺序停止平台",
|
|
656
795
|
"",
|
|
657
|
-
"
|
|
658
|
-
"
|
|
659
|
-
"
|
|
660
|
-
"
|
|
661
|
-
"
|
|
796
|
+
theme.section("公共参数"),
|
|
797
|
+
" --workspace <路径> 指定 ProFlow Workspace",
|
|
798
|
+
" setup --module <模块名> 只查看指定模块的配置步骤",
|
|
799
|
+
"",
|
|
800
|
+
theme.section("推荐流程"),
|
|
801
|
+
` ${theme.command("install → status → docs → setup → start → status → stop")}`,
|
|
802
|
+
"",
|
|
803
|
+
theme.section("人工配置示例"),
|
|
804
|
+
` 人工:${theme.command("pnpm exec -- proflow-chatgpt-carrier setup")}`,
|
|
805
|
+
` AI: ${theme.command("pnpm exec -- proflow-chatgpt-carrier setup --carrier-url <url>")}`,
|
|
806
|
+
"",
|
|
807
|
+
theme.section("状态图例"),
|
|
808
|
+
` ${theme.success("已就绪")} 配置完成 ${theme.warning("需要操作")} 运行 setup`,
|
|
809
|
+
` ${theme.failure("失败")} 存在阻塞 无独立进程 无需启动`,
|
|
810
|
+
"",
|
|
811
|
+
theme.section("遇到问题"),
|
|
812
|
+
` 先运行 ${theme.command("platform status")};配置问题运行 ${theme.command("platform setup")}。`,
|
|
662
813
|
].join("\n");
|
|
663
814
|
if (result.command === "version" && isRecord(result.data))
|
|
664
815
|
return String(result.data.version ?? platformCliDescriptor.moduleVersion);
|
|
665
816
|
if (result.command === "status")
|
|
666
|
-
return renderStatus(result.data);
|
|
817
|
+
return renderStatus(result.data, theme);
|
|
667
818
|
if (result.command === "setup")
|
|
668
|
-
return renderSetup(result.data);
|
|
819
|
+
return renderSetup(result.data, theme);
|
|
669
820
|
if (result.command === "docs")
|
|
670
|
-
return renderDocs(result.data);
|
|
821
|
+
return renderDocs(result.data, theme);
|
|
671
822
|
if ((result.command === "start" || result.command === "stop") &&
|
|
672
823
|
isRecord(result.data)) {
|
|
673
824
|
const skippedItems = Array.isArray(result.data.skipped)
|
|
@@ -684,8 +835,10 @@ export function renderHumanResult(result) {
|
|
|
684
835
|
!skippedRefs.has(String(item.moduleRef))).length;
|
|
685
836
|
const failed = operations.find((item) => isRecord(item.result) && item.result.status !== "SUCCEEDED");
|
|
686
837
|
return [
|
|
687
|
-
|
|
688
|
-
|
|
838
|
+
failed
|
|
839
|
+
? theme.failure(`平台${result.command === "start" ? "启动" : "停止"}未完成`)
|
|
840
|
+
: theme.success(`✓ 平台${result.command === "start" ? "启动" : "停止"}完成`),
|
|
841
|
+
`${theme.success(`成功:${succeeded}`)} ${theme.muted(`跳过:${skipped}`)} ${failed ? theme.failure(`失败:${String(failed.moduleRef)}`) : theme.muted("失败:0")}`,
|
|
689
842
|
].join("\n");
|
|
690
843
|
}
|
|
691
844
|
const labels = {
|
|
@@ -694,14 +847,40 @@ export function renderHumanResult(result) {
|
|
|
694
847
|
start: "启动",
|
|
695
848
|
stop: "停止",
|
|
696
849
|
};
|
|
697
|
-
return `${labels[result.command] ?? result.command}${result.status === "SUCCEEDED" ? "成功" : "未完成"}${result.workspaceRoot ? `\
|
|
850
|
+
return `${result.status === "SUCCEEDED" ? theme.success("✓") : theme.failure("✕")} ${labels[result.command] ?? result.command}${result.status === "SUCCEEDED" ? "成功" : "未完成"}${result.workspaceRoot ? `\n${theme.muted("Workspace")} ${result.workspaceRoot}` : ""}`;
|
|
698
851
|
}
|
|
699
852
|
if (import.meta.main) {
|
|
700
853
|
const argv = process.argv.slice(2);
|
|
701
854
|
const reporter = createTerminalProgressReporter();
|
|
702
855
|
const result = await runCli(argv, { onProgress: reporter });
|
|
703
856
|
reporter.close();
|
|
704
|
-
process.stdout.
|
|
857
|
+
const color = process.stdout.isTTY === true &&
|
|
858
|
+
process.env.NO_COLOR === undefined &&
|
|
859
|
+
process.env.TERM !== "dumb";
|
|
860
|
+
const rendered = `${renderHumanResult(result, {
|
|
861
|
+
color,
|
|
862
|
+
width: process.stdout.columns,
|
|
863
|
+
})}\n`;
|
|
864
|
+
if (result.command === "docs" &&
|
|
865
|
+
process.stdout.isTTY === true &&
|
|
866
|
+
rendered.split("\n").length > (process.stdout.rows ?? 24)) {
|
|
867
|
+
await writeThroughPager(rendered);
|
|
868
|
+
}
|
|
869
|
+
else
|
|
870
|
+
process.stdout.write(rendered);
|
|
705
871
|
if (result.status !== "SUCCEEDED")
|
|
706
872
|
process.exitCode = 1;
|
|
707
873
|
}
|
|
874
|
+
async function writeThroughPager(content) {
|
|
875
|
+
await new Promise((resolvePromise) => {
|
|
876
|
+
const pager = spawn("less", ["-FIRX"], {
|
|
877
|
+
stdio: ["pipe", "inherit", "inherit"],
|
|
878
|
+
});
|
|
879
|
+
pager.once("error", () => {
|
|
880
|
+
process.stdout.write(content);
|
|
881
|
+
resolvePromise();
|
|
882
|
+
});
|
|
883
|
+
pager.once("close", () => resolvePromise());
|
|
884
|
+
pager.stdin.end(content);
|
|
885
|
+
});
|
|
886
|
+
}
|
|
@@ -6,7 +6,11 @@ export interface WorkspacePackageManagerSelection {
|
|
|
6
6
|
declared?: string;
|
|
7
7
|
}
|
|
8
8
|
export interface PackageCommandRunner {
|
|
9
|
-
run(command: string, args: readonly string[], cwd: string): Promise<string>;
|
|
9
|
+
run(command: string, args: readonly string[], cwd: string, onOutput?: (line: PackageCommandOutput) => void): Promise<string>;
|
|
10
|
+
}
|
|
11
|
+
export interface PackageCommandOutput {
|
|
12
|
+
stream: "stdout" | "stderr";
|
|
13
|
+
line: string;
|
|
10
14
|
}
|
|
11
15
|
export declare function systemPackageCommandRunner(): PackageCommandRunner;
|
|
12
16
|
export declare function readWorkspacePackageManagerSelection(workspaceRoot: string): Promise<WorkspacePackageManagerSelection>;
|
|
@@ -24,11 +28,13 @@ export declare function syncWorkspacePackages(options: {
|
|
|
24
28
|
packages: readonly WorkspacePackageTarget[];
|
|
25
29
|
runner?: PackageCommandRunner;
|
|
26
30
|
executableAvailable?: (command: string) => boolean;
|
|
31
|
+
onOutput?: (line: PackageCommandOutput) => void;
|
|
27
32
|
}): Promise<WorkspacePackageMutationResult>;
|
|
28
33
|
export declare function removeWorkspacePackages(options: {
|
|
29
34
|
workspaceRoot: string;
|
|
30
35
|
packageNames: readonly string[];
|
|
31
36
|
runner?: PackageCommandRunner;
|
|
32
37
|
executableAvailable?: (command: string) => boolean;
|
|
38
|
+
onOutput?: (line: PackageCommandOutput) => void;
|
|
33
39
|
}): Promise<WorkspacePackageMutationResult>;
|
|
34
40
|
export declare function observeWorkspaceInstalledVersion(workspaceRoot: string, packageName: string): Promise<string | undefined>;
|
|
@@ -1,20 +1,58 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
2
|
import { accessSync, constants } from "node:fs";
|
|
3
3
|
import { access, readFile } from "node:fs/promises";
|
|
4
4
|
import { delimiter, join } from "node:path";
|
|
5
|
-
import { promisify } from "node:util";
|
|
6
5
|
import { PlatformError } from "../errors.js";
|
|
7
6
|
import { atomicWrite } from "../paths.js";
|
|
8
|
-
const execFileAsync = promisify(execFile);
|
|
9
7
|
export function systemPackageCommandRunner() {
|
|
10
8
|
return {
|
|
11
|
-
async run(command, args, cwd) {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
9
|
+
async run(command, args, cwd, onOutput) {
|
|
10
|
+
return new Promise((resolvePromise, rejectPromise) => {
|
|
11
|
+
const child = spawn(command, [...args], {
|
|
12
|
+
cwd,
|
|
13
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
14
|
+
});
|
|
15
|
+
let stdout = "";
|
|
16
|
+
let stderr = "";
|
|
17
|
+
const pending = {
|
|
18
|
+
stdout: "",
|
|
19
|
+
stderr: "",
|
|
20
|
+
};
|
|
21
|
+
const consume = (stream, chunk) => {
|
|
22
|
+
const text = chunk.toString("utf8");
|
|
23
|
+
if (stream === "stdout")
|
|
24
|
+
stdout += text;
|
|
25
|
+
else
|
|
26
|
+
stderr += text;
|
|
27
|
+
const parts = `${pending[stream]}${text}`.split(/\r?\n|\r/);
|
|
28
|
+
pending[stream] = parts.pop() ?? "";
|
|
29
|
+
for (const line of parts) {
|
|
30
|
+
const normalized = line.trim();
|
|
31
|
+
if (normalized)
|
|
32
|
+
onOutput?.({ stream, line: normalized });
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
const flush = () => {
|
|
36
|
+
for (const stream of ["stdout", "stderr"]) {
|
|
37
|
+
const line = pending[stream].trim();
|
|
38
|
+
if (line)
|
|
39
|
+
onOutput?.({ stream, line });
|
|
40
|
+
pending[stream] = "";
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
child.stdout.on("data", (chunk) => consume("stdout", chunk));
|
|
44
|
+
child.stderr.on("data", (chunk) => consume("stderr", chunk));
|
|
45
|
+
child.once("error", rejectPromise);
|
|
46
|
+
child.once("close", (code, signal) => {
|
|
47
|
+
flush();
|
|
48
|
+
if (code === 0)
|
|
49
|
+
resolvePromise(stdout);
|
|
50
|
+
else {
|
|
51
|
+
const detail = stderr.trim() || stdout.trim();
|
|
52
|
+
rejectPromise(new Error(`${command} exited with ${code ?? signal ?? "unknown"}${detail ? `: ${detail}` : ""}`));
|
|
53
|
+
}
|
|
54
|
+
});
|
|
16
55
|
});
|
|
17
|
-
return result.stdout;
|
|
18
56
|
},
|
|
19
57
|
};
|
|
20
58
|
}
|
|
@@ -125,7 +163,7 @@ export async function syncWorkspacePackages(options) {
|
|
|
125
163
|
await writeManagedPackageSet(options.workspaceRoot, targets);
|
|
126
164
|
const args = await packageManagerInstallArgs(runner, manager, options.workspaceRoot);
|
|
127
165
|
try {
|
|
128
|
-
await runner.run(manager.name, args, options.workspaceRoot);
|
|
166
|
+
await runner.run(manager.name, args, options.workspaceRoot, options.onOutput);
|
|
129
167
|
}
|
|
130
168
|
catch (error) {
|
|
131
169
|
throw new PlatformError("COMMAND_FAILED", `${manager.name} install failed: ${packageMutationError(error)}`);
|
|
@@ -177,7 +215,7 @@ export async function removeWorkspacePackages(options) {
|
|
|
177
215
|
const manager = await requireWorkspacePackageManager(options.workspaceRoot, options.executableAvailable ?? findExecutable);
|
|
178
216
|
const args = await batchPackageManagerArgs(runner, manager, options.workspaceRoot, "remove", packageNames);
|
|
179
217
|
try {
|
|
180
|
-
await runner.run(manager.name, args, options.workspaceRoot);
|
|
218
|
+
await runner.run(manager.name, args, options.workspaceRoot, options.onOutput);
|
|
181
219
|
}
|
|
182
220
|
catch (error) {
|
|
183
221
|
throw new PlatformError("UNINSTALL_FAILED", `${manager.name} remove failed: ${packageMutationError(error)}`);
|
|
@@ -250,7 +250,11 @@ export async function startModulesThin(catalog, modules, workspaceRoot, reporter
|
|
|
250
250
|
message: `${module.moduleRef}`,
|
|
251
251
|
});
|
|
252
252
|
if (!succeeded(status.result)) {
|
|
253
|
-
blockers.push({
|
|
253
|
+
blockers.push({
|
|
254
|
+
moduleRef: module.moduleRef,
|
|
255
|
+
setupStatus: "FAILED",
|
|
256
|
+
reason: status.result.error?.message ?? "模块状态检查失败",
|
|
257
|
+
});
|
|
254
258
|
continue;
|
|
255
259
|
}
|
|
256
260
|
const observed = moduleStatusObservationSchema.parse(status.result.data);
|
package/dist/src/progress.d.ts
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
export type PlatformProgressStatus = "STARTED" | "SUCCEEDED" | "ACTION_REQUIRED" | "FAILED" | "SKIPPED";
|
|
2
|
+
export type PlatformProgressKind = "phase" | "detail" | "subprocess";
|
|
2
3
|
export interface PlatformProgressEvent {
|
|
3
4
|
command: string;
|
|
4
5
|
phase: string;
|
|
6
|
+
kind?: PlatformProgressKind;
|
|
5
7
|
current?: number;
|
|
6
8
|
total?: number;
|
|
7
9
|
moduleRef?: string;
|
|
10
|
+
elapsedMs?: number;
|
|
8
11
|
status: PlatformProgressStatus;
|
|
9
12
|
message: string;
|
|
10
13
|
}
|
|
@@ -34,4 +34,10 @@ export declare function discoverRegistryModules(options: {
|
|
|
34
34
|
packageName?: string;
|
|
35
35
|
runner?: NpmCommandRunner;
|
|
36
36
|
nodeVersion?: string;
|
|
37
|
+
onSearchComplete?: (total: number) => void;
|
|
38
|
+
onPackageChecked?: (input: {
|
|
39
|
+
current: number;
|
|
40
|
+
total: number;
|
|
41
|
+
packageName: string;
|
|
42
|
+
}) => void;
|
|
37
43
|
}): Promise<RegistryDiscoveryResult>;
|
|
@@ -42,16 +42,32 @@ export async function discoverRegistryModules(options) {
|
|
|
42
42
|
const names = options.packageName === undefined
|
|
43
43
|
? await searchPackageNames(options.workspaceRoot, registry, runner)
|
|
44
44
|
: [validateRequestedPackageName(options.packageName)];
|
|
45
|
+
options.onSearchComplete?.(names.length);
|
|
45
46
|
const candidates = [];
|
|
46
47
|
const rejected = [];
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
48
|
+
let nextIndex = 0;
|
|
49
|
+
let completed = 0;
|
|
50
|
+
const worker = async () => {
|
|
51
|
+
while (nextIndex < names.length) {
|
|
52
|
+
const index = nextIndex++;
|
|
53
|
+
const packageName = names[index];
|
|
54
|
+
if (packageName === undefined)
|
|
55
|
+
return;
|
|
56
|
+
const manifest = await viewManifest(options.workspaceRoot, registry, packageName, runner);
|
|
57
|
+
const assessed = assessManifest(manifest, registry, options.nodeVersion ?? process.versions.node);
|
|
58
|
+
if ("candidate" in assessed)
|
|
59
|
+
candidates.push(assessed.candidate);
|
|
60
|
+
else
|
|
61
|
+
rejected.push(assessed.rejected);
|
|
62
|
+
completed += 1;
|
|
63
|
+
options.onPackageChecked?.({
|
|
64
|
+
current: completed,
|
|
65
|
+
total: names.length,
|
|
66
|
+
packageName,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
await Promise.all(Array.from({ length: Math.min(4, names.length) }, () => worker()));
|
|
55
71
|
candidates.sort((left, right) => left.packageName.localeCompare(right.packageName));
|
|
56
72
|
rejected.sort((left, right) => left.packageName.localeCompare(right.packageName));
|
|
57
73
|
return { registry, candidates, rejected };
|
package/dist/src/terminal.js
CHANGED
|
@@ -1,14 +1,22 @@
|
|
|
1
1
|
import { clearLine, cursorTo } from "node:readline";
|
|
2
2
|
const colors = {
|
|
3
|
+
cyan: "\u001b[36m",
|
|
4
|
+
blue: "\u001b[34m",
|
|
3
5
|
green: "\u001b[32m",
|
|
4
6
|
red: "\u001b[31m",
|
|
5
7
|
yellow: "\u001b[33m",
|
|
8
|
+
bold: "\u001b[1m",
|
|
6
9
|
dim: "\u001b[2m",
|
|
7
10
|
reset: "\u001b[0m",
|
|
8
11
|
};
|
|
9
12
|
const spinnerFrames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
10
13
|
function progressLine(event, color, spinner = "") {
|
|
11
|
-
const prefix = event.current && event.total
|
|
14
|
+
const prefix = event.current && event.total
|
|
15
|
+
? `[${String(event.current).padStart(String(event.total).length, "0")}/${event.total}] `
|
|
16
|
+
: "";
|
|
17
|
+
const elapsed = event.elapsedMs !== undefined
|
|
18
|
+
? ` · ${(event.elapsedMs / 1000).toFixed(1)}s`
|
|
19
|
+
: "";
|
|
12
20
|
const suffix = event.status === "SUCCEEDED"
|
|
13
21
|
? "完成"
|
|
14
22
|
: event.status === "ACTION_REQUIRED"
|
|
@@ -18,23 +26,27 @@ function progressLine(event, color, spinner = "") {
|
|
|
18
26
|
: event.status === "SKIPPED"
|
|
19
27
|
? "跳过"
|
|
20
28
|
: "";
|
|
21
|
-
const symbol = event.
|
|
22
|
-
?
|
|
23
|
-
: event.status === "
|
|
24
|
-
?
|
|
25
|
-
: event.status === "
|
|
26
|
-
? "
|
|
27
|
-
: event.status === "
|
|
28
|
-
? "
|
|
29
|
-
: "
|
|
29
|
+
const symbol = event.kind === "subprocess"
|
|
30
|
+
? "│"
|
|
31
|
+
: event.status === "STARTED"
|
|
32
|
+
? spinner
|
|
33
|
+
: event.status === "SUCCEEDED"
|
|
34
|
+
? "✓"
|
|
35
|
+
: event.status === "FAILED"
|
|
36
|
+
? "✕"
|
|
37
|
+
: event.status === "ACTION_REQUIRED"
|
|
38
|
+
? "◆"
|
|
39
|
+
: "○";
|
|
30
40
|
if (!color)
|
|
31
|
-
return `${symbol ? `${symbol} ` : ""}${prefix}${event.message}${suffix ? ` · ${suffix}` : ""}`;
|
|
41
|
+
return `${symbol ? `${symbol} ` : ""}${prefix}${event.message}${suffix ? ` · ${suffix}` : ""}${elapsed}`;
|
|
32
42
|
const tone = event.status === "SUCCEEDED"
|
|
33
43
|
? colors.green
|
|
34
44
|
: event.status === "FAILED"
|
|
35
45
|
? colors.red
|
|
36
|
-
:
|
|
37
|
-
|
|
46
|
+
: event.kind === "subprocess"
|
|
47
|
+
? colors.blue
|
|
48
|
+
: colors.yellow;
|
|
49
|
+
return `${tone}${symbol}${colors.reset} ${colors.dim}${prefix}${colors.reset}${event.message}${suffix ? ` · ${tone}${suffix}${colors.reset}` : ""}${event.elapsedMs === undefined ? "" : `${colors.dim}${elapsed}${colors.reset}`}`;
|
|
38
50
|
}
|
|
39
51
|
export function createTerminalProgressReporter(stream = process.stderr) {
|
|
40
52
|
const interactive = stream.isTTY === true;
|
|
@@ -58,7 +70,10 @@ export function createTerminalProgressReporter(stream = process.stderr) {
|
|
|
58
70
|
};
|
|
59
71
|
const reporter = ((event) => {
|
|
60
72
|
clearSpinner();
|
|
61
|
-
|
|
73
|
+
const previousActive = active;
|
|
74
|
+
if (event.kind !== "subprocess") {
|
|
75
|
+
active = event.status === "STARTED" ? event : undefined;
|
|
76
|
+
}
|
|
62
77
|
const line = progressLine(event, color, interactive ? spinnerFrames[frame++ % spinnerFrames.length] : "›");
|
|
63
78
|
if (!interactive) {
|
|
64
79
|
stream.write(`${line}\n`);
|
|
@@ -67,18 +82,22 @@ export function createTerminalProgressReporter(stream = process.stderr) {
|
|
|
67
82
|
cursorTo(stream, 0);
|
|
68
83
|
clearLine(stream, 0);
|
|
69
84
|
stream.write(line);
|
|
85
|
+
if (event.kind === "subprocess") {
|
|
86
|
+
stream.write("\n");
|
|
87
|
+
active = previousActive;
|
|
88
|
+
if (active) {
|
|
89
|
+
paint();
|
|
90
|
+
timer = setInterval(paint, 80);
|
|
91
|
+
timer.unref();
|
|
92
|
+
}
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
70
95
|
if (event.status === "STARTED") {
|
|
71
96
|
timer = setInterval(paint, 80);
|
|
72
97
|
timer.unref();
|
|
73
98
|
return;
|
|
74
99
|
}
|
|
75
|
-
|
|
76
|
-
event.total !== undefined &&
|
|
77
|
-
event.current < event.total &&
|
|
78
|
-
event.status !== "FAILED" &&
|
|
79
|
-
event.status !== "ACTION_REQUIRED";
|
|
80
|
-
if (!compactModuleProgress)
|
|
81
|
-
stream.write("\n");
|
|
100
|
+
stream.write("\n");
|
|
82
101
|
});
|
|
83
102
|
reporter.close = () => {
|
|
84
103
|
clearSpinner();
|
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.34",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -24,11 +24,11 @@
|
|
|
24
24
|
"SETUP.md"
|
|
25
25
|
],
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@tomflow/proflow-module-contract": "^0.1.
|
|
27
|
+
"@tomflow/proflow-module-contract": "^0.1.11"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
|
-
"@tomflow/proflow-
|
|
31
|
-
"@tomflow/proflow-
|
|
30
|
+
"@tomflow/proflow-deployment-conformance": "^0.1.9",
|
|
31
|
+
"@tomflow/proflow-module-template": "^0.1.9"
|
|
32
32
|
},
|
|
33
33
|
"description": "Thin Platform CLI for Module discovery, documentation, package synchronization and lifecycle orchestration.",
|
|
34
34
|
"keywords": [
|
package/proflow.module.json
CHANGED