@tomflow/proflow-platform-cli 0.1.33 → 0.1.35
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/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 +232 -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 +4 -1
- package/dist/src/registry/npm-registry.d.ts +6 -0
- package/dist/src/registry/npm-registry.js +24 -8
- package/dist/src/terminal.js +55 -30
- package/package.json +4 -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.35
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Render retryable package-manager warnings as warnings instead of failed subprocess events.
|
|
8
|
+
|
|
9
|
+
## 0.1.34
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- Upgrade the ProFlow terminal experience, stream Registry and package-manager progress, publish structured Chinese Module documentation, and provide numbered package-owned Setup assistants.
|
|
14
|
+
|
|
3
15
|
## 0.1.33
|
|
4
16
|
|
|
5
17
|
### 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.35";
|
|
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.35";
|
|
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.35";
|
|
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.35";
|
|
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.35";
|
|
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.35";
|
|
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.35";
|
|
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.35";
|
|
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.35",
|
|
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,22 @@ 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: /\b(?:warn|warning)\b/i.test(output.line)
|
|
355
|
+
? "WARNING"
|
|
356
|
+
: /(?:error|ERR_)/i.test(output.line)
|
|
357
|
+
? "FAILED"
|
|
358
|
+
: "STARTED",
|
|
359
|
+
message: output.line,
|
|
360
|
+
});
|
|
361
|
+
}
|
|
305
362
|
async function workspaceProFlowDependencies(root) {
|
|
306
363
|
try {
|
|
307
364
|
const parsed = JSON.parse(await readFile(resolve(root, "package.json"), "utf8"));
|
|
@@ -339,6 +396,7 @@ async function handleUninstall(root, runtime) {
|
|
|
339
396
|
const mutation = await removeWorkspacePackages({
|
|
340
397
|
workspaceRoot: root,
|
|
341
398
|
packageNames,
|
|
399
|
+
onOutput: (line) => reportPackageManagerOutput(runtime, "uninstall", line),
|
|
342
400
|
...(runtime.packageRunner === undefined
|
|
343
401
|
? {}
|
|
344
402
|
: { runner: runtime.packageRunner }),
|
|
@@ -447,7 +505,32 @@ function errorOutcome(command, error) {
|
|
|
447
505
|
};
|
|
448
506
|
}
|
|
449
507
|
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
450
|
-
|
|
508
|
+
const ansi = {
|
|
509
|
+
reset: "\u001b[0m",
|
|
510
|
+
bold: "\u001b[1m",
|
|
511
|
+
dim: "\u001b[2m",
|
|
512
|
+
cyan: "\u001b[36m",
|
|
513
|
+
blue: "\u001b[34m",
|
|
514
|
+
green: "\u001b[32m",
|
|
515
|
+
yellow: "\u001b[33m",
|
|
516
|
+
red: "\u001b[31m",
|
|
517
|
+
};
|
|
518
|
+
function humanTheme(options = {}) {
|
|
519
|
+
const enabled = options.color === true;
|
|
520
|
+
const paint = (tone, value) => enabled ? `${ansi[tone]}${value}${ansi.reset}` : value;
|
|
521
|
+
return {
|
|
522
|
+
width: Math.max(60, options.width ?? 100),
|
|
523
|
+
paint,
|
|
524
|
+
title: (value) => paint("bold", paint("cyan", value)),
|
|
525
|
+
section: (value) => paint("bold", value),
|
|
526
|
+
command: (value) => paint("blue", value),
|
|
527
|
+
muted: (value) => paint("dim", value),
|
|
528
|
+
success: (value) => paint("green", value),
|
|
529
|
+
warning: (value) => paint("yellow", value),
|
|
530
|
+
failure: (value) => paint("red", value),
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
function renderStatus(data, theme) {
|
|
451
534
|
if (!isRecord(data) || !Array.isArray(data.modules))
|
|
452
535
|
return "未发现模块。";
|
|
453
536
|
const setupLabels = {
|
|
@@ -462,8 +545,15 @@ function renderStatus(data) {
|
|
|
462
545
|
NOT_APPLICABLE: "无独立进程",
|
|
463
546
|
};
|
|
464
547
|
const modules = data.modules.filter(isRecord);
|
|
465
|
-
const
|
|
466
|
-
|
|
548
|
+
const unhealthy = modules.filter((item) => item.setupStatus !== "READY" || item.runtimeStatus === "FAILED");
|
|
549
|
+
const running = modules.filter((item) => item.setupStatus === "READY" && item.runtimeStatus === "RUNNING");
|
|
550
|
+
const healthy = modules.filter((item) => item.setupStatus === "READY" &&
|
|
551
|
+
item.runtimeStatus !== "RUNNING" &&
|
|
552
|
+
item.runtimeStatus !== "FAILED");
|
|
553
|
+
const lines = [theme.title("ProFlow 状态"), ""];
|
|
554
|
+
if (unhealthy.length > 0)
|
|
555
|
+
lines.push(theme.section("需要处理"), "");
|
|
556
|
+
for (const raw of unhealthy) {
|
|
467
557
|
const moduleRef = String(raw.moduleRef);
|
|
468
558
|
const setupStatus = String(raw.setupStatus);
|
|
469
559
|
const runtimeStatus = String(raw.runtimeStatus);
|
|
@@ -478,43 +568,69 @@ function renderStatus(data) {
|
|
|
478
568
|
: runtimeStatus === "NOT_APPLICABLE"
|
|
479
569
|
? "○"
|
|
480
570
|
: "✓";
|
|
481
|
-
|
|
571
|
+
const tone = failed ? theme.failure : theme.warning;
|
|
572
|
+
lines.push(`${tone(icon)} ${theme.section(moduleRef)} ${theme.muted(String(raw.version))}`);
|
|
573
|
+
lines.push(` ${tone(setupLabels[setupStatus] ?? "未知")} · ${runtimeLabels[runtimeStatus] ?? "未知"}`);
|
|
482
574
|
if (failed) {
|
|
483
|
-
lines.push(` 原因:${setupStatus === "FAILED" ? "模块配置检查失败" : "模块运行状态异常"}`, `
|
|
575
|
+
lines.push(` 原因:${setupStatus === "FAILED" ? "模块配置检查失败" : "模块运行状态异常"}`, ` 下一步:${theme.command(`platform setup --module ${moduleRef}`)}`);
|
|
484
576
|
}
|
|
485
577
|
else if (actionRequired) {
|
|
486
|
-
lines.push(" 原因:模块尚未完成配置", `
|
|
578
|
+
lines.push(" 原因:模块尚未完成配置", ` 下一步:${theme.command(`platform setup --module ${moduleRef}`)}`);
|
|
487
579
|
}
|
|
580
|
+
lines.push("");
|
|
488
581
|
}
|
|
582
|
+
const compact = (title, entries, symbol) => {
|
|
583
|
+
if (entries.length === 0)
|
|
584
|
+
return;
|
|
585
|
+
lines.push(theme.section(title), "");
|
|
586
|
+
for (const item of entries) {
|
|
587
|
+
const runtime = runtimeLabels[String(item.runtimeStatus)] ?? "未知";
|
|
588
|
+
lines.push(`${theme.success(symbol)} ${String(item.moduleRef).padEnd(32)} ${theme.muted(String(item.version).padEnd(9))} ${runtime}`);
|
|
589
|
+
}
|
|
590
|
+
lines.push("");
|
|
591
|
+
};
|
|
592
|
+
compact("运行中", running, "●");
|
|
593
|
+
compact("已就绪", healthy, "○");
|
|
489
594
|
const ready = modules.filter((item) => item.setupStatus === "READY").length;
|
|
490
595
|
const action = modules.filter((item) => item.setupStatus === "ACTION_REQUIRED").length;
|
|
491
596
|
const failed = modules.filter((item) => item.setupStatus === "FAILED").length;
|
|
492
|
-
lines.push("",
|
|
597
|
+
lines.push(theme.section("汇总"), `${theme.success(`${ready} 已就绪`)} · ${theme.warning(`${action} 需要操作`)} · ${failed > 0 ? theme.failure(`${failed} 失败`) : `${failed} 失败`}`);
|
|
493
598
|
return lines.join("\n");
|
|
494
599
|
}
|
|
495
|
-
function renderTerminalMarkdown(source) {
|
|
600
|
+
function renderTerminalMarkdown(source, theme) {
|
|
601
|
+
let inCode = false;
|
|
496
602
|
return source
|
|
497
603
|
.split(/\r?\n/)
|
|
498
604
|
.map((line) => {
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
return
|
|
502
|
-
|
|
605
|
+
if (line.trim().startsWith("```")) {
|
|
606
|
+
inCode = !inCode;
|
|
607
|
+
return "";
|
|
608
|
+
}
|
|
609
|
+
if (inCode)
|
|
610
|
+
return ` ${theme.command(line)}`;
|
|
611
|
+
const heading = line.match(/^(#{1,6})\s+(.+)$/);
|
|
612
|
+
if (heading?.[2])
|
|
613
|
+
return heading[1]?.length === 1
|
|
614
|
+
? theme.title(heading[2].replaceAll("`", ""))
|
|
615
|
+
: theme.section(heading[2].replaceAll("`", ""));
|
|
616
|
+
const bullet = line.match(/^\s*[-*]\s+(.+)$/);
|
|
503
617
|
if (bullet?.[1])
|
|
504
|
-
return `
|
|
505
|
-
|
|
618
|
+
return ` ${theme.paint("cyan", "•")} ${bullet[1]}`;
|
|
619
|
+
if (/^\|.*\|$/.test(line))
|
|
620
|
+
return theme.muted(line);
|
|
621
|
+
return line.replace(/`([^`]+)`/g, (_match, code) => theme.command(code));
|
|
506
622
|
})
|
|
507
623
|
.join("\n")
|
|
508
624
|
.replace(/\n{3,}/g, "\n\n")
|
|
509
625
|
.trim();
|
|
510
626
|
}
|
|
511
|
-
function renderDocs(data) {
|
|
627
|
+
function renderDocs(data, theme) {
|
|
512
628
|
if (!isRecord(data) || !Array.isArray(data.modules))
|
|
513
629
|
return "未发现模块文档。";
|
|
514
|
-
const lines = ["ProFlow 模块文档"];
|
|
630
|
+
const lines = [theme.title("ProFlow 模块文档")];
|
|
515
631
|
for (const raw of data.modules)
|
|
516
632
|
if (isRecord(raw))
|
|
517
|
-
lines.push("", "─".repeat(
|
|
633
|
+
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
634
|
if (Array.isArray(data.errors) && data.errors.length > 0)
|
|
519
635
|
lines.push("", "文档读取失败:", ...data.errors
|
|
520
636
|
.filter(isRecord)
|
|
@@ -555,10 +671,10 @@ const setupCommands = {
|
|
|
555
671
|
inputs: "Custom GPT URL",
|
|
556
672
|
},
|
|
557
673
|
};
|
|
558
|
-
function renderSetup(data) {
|
|
674
|
+
function renderSetup(data, theme) {
|
|
559
675
|
if (!isRecord(data) || !Array.isArray(data.results))
|
|
560
676
|
return "没有需要执行的配置步骤。";
|
|
561
|
-
const lines = ["ProFlow 配置", ""];
|
|
677
|
+
const lines = [theme.title("ProFlow 配置"), ""];
|
|
562
678
|
const skipped = Array.isArray(data.skipped) ? data.skipped : [];
|
|
563
679
|
let ready = skipped.filter((item) => isRecord(item) && item.reason === "READY").length;
|
|
564
680
|
let needsAction = 0;
|
|
@@ -576,19 +692,31 @@ function renderSetup(data) {
|
|
|
576
692
|
blocked += 1;
|
|
577
693
|
else
|
|
578
694
|
needsAction += 1;
|
|
579
|
-
lines.push(`${status === "FAILED" ? "✕" : "◆"} ${moduleRef}`);
|
|
695
|
+
lines.push(`${status === "FAILED" ? theme.failure("✕") : theme.warning("◆")} ${theme.section(moduleRef)}`);
|
|
580
696
|
const plan = moduleSetupPlanDataSchema.safeParse(raw.result.data);
|
|
581
697
|
if (plan.success) {
|
|
582
698
|
for (const [index, step] of plan.data.steps.entries()) {
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
699
|
+
const marker = step.state === "DONE"
|
|
700
|
+
? theme.success("✓")
|
|
701
|
+
: step.state === "BLOCKED"
|
|
702
|
+
? theme.failure("✕")
|
|
703
|
+
: index ===
|
|
704
|
+
plan.data.steps.findIndex((item) => item.state === "TODO")
|
|
705
|
+
? theme.warning("→")
|
|
706
|
+
: theme.muted("○");
|
|
707
|
+
lines.push(` ${marker} ${String(index + 1).padStart(2, "0")} ${step.title}`);
|
|
708
|
+
if (step.description)
|
|
709
|
+
lines.push(` ${step.description}`);
|
|
710
|
+
lines.push(` 运行:${theme.command(step.execution.interactive)}`);
|
|
711
|
+
lines.push(` AI:${theme.command(step.execution.nonInteractive)}`);
|
|
586
712
|
if (step.requiredInputs.length > 0)
|
|
587
|
-
lines.push(`
|
|
588
|
-
|
|
589
|
-
|
|
713
|
+
lines.push(` 需要:${step.requiredInputs.map((item) => item.description).join("、")}`);
|
|
714
|
+
if (step.humanAction)
|
|
715
|
+
lines.push(` 人工操作:${step.humanAction}`);
|
|
716
|
+
lines.push(` 验证:${theme.command(step.verify)}`);
|
|
717
|
+
lines.push(` 完成:${step.successCondition}`);
|
|
590
718
|
if (step.blockedReason)
|
|
591
|
-
lines.push(`
|
|
719
|
+
lines.push(` 原因:${theme.failure(step.blockedReason)}`);
|
|
592
720
|
}
|
|
593
721
|
lines.push("");
|
|
594
722
|
continue;
|
|
@@ -619,34 +747,49 @@ function renderSetup(data) {
|
|
|
619
747
|
}
|
|
620
748
|
if (needsAction === 0 && blocked === 0)
|
|
621
749
|
lines.push("全部模块均已就绪。");
|
|
622
|
-
lines.push(`汇总:${ready}
|
|
750
|
+
lines.push(theme.section("汇总"), `汇总:${theme.success(`${ready} 个已就绪`)},${theme.warning(`${needsAction} 个需要操作`)},${blocked > 0 ? theme.failure(`${blocked} 个系统阻塞`) : `${blocked} 个系统阻塞`}`);
|
|
623
751
|
return lines.join("\n").trimEnd();
|
|
624
752
|
}
|
|
625
|
-
export function renderHumanResult(result) {
|
|
753
|
+
export function renderHumanResult(result, options = {}) {
|
|
754
|
+
const theme = humanTheme(options);
|
|
626
755
|
if (result.command === "setup" &&
|
|
627
756
|
isRecord(result.data) &&
|
|
628
757
|
Array.isArray(result.data.results))
|
|
629
|
-
return renderSetup(result.data);
|
|
758
|
+
return renderSetup(result.data, theme);
|
|
630
759
|
if (result.command === "start" && isRecord(result.data)) {
|
|
631
760
|
const blockers = Array.isArray(result.data.blockers)
|
|
632
761
|
? result.data.blockers.filter(isRecord)
|
|
633
762
|
: [];
|
|
634
763
|
if (blockers.length > 0)
|
|
635
764
|
return [
|
|
636
|
-
"平台未启动:存在未就绪模块",
|
|
765
|
+
theme.failure("平台未启动:存在未就绪模块"),
|
|
637
766
|
"",
|
|
638
|
-
...blockers.
|
|
767
|
+
...blockers.flatMap((item) => [
|
|
768
|
+
`${String(item.setupStatus) === "FAILED" ? theme.failure("✕") : theme.warning("◆")} ${theme.section(String(item.moduleRef))} ${String(item.setupStatus) === "FAILED" ? theme.failure("失败") : theme.warning("需要操作")}`,
|
|
769
|
+
` 原因:${typeof item.reason === "string" ? item.reason : "模块配置尚未完成"}`,
|
|
770
|
+
` 下一步:${theme.command(`platform setup --module ${String(item.moduleRef)}`)}`,
|
|
771
|
+
]),
|
|
639
772
|
"",
|
|
640
|
-
|
|
773
|
+
`处理方式:${theme.command(`platform setup${result.workspaceRoot ? ` --workspace "${result.workspaceRoot}"` : ""}`)}`,
|
|
641
774
|
].join("\n");
|
|
642
775
|
}
|
|
643
776
|
if (result.status === "FAILED" && result.error)
|
|
644
|
-
return
|
|
777
|
+
return [
|
|
778
|
+
theme.failure(`╭─ ${result.command} 执行失败`),
|
|
779
|
+
`${theme.failure("│")} ${theme.section(result.error.code)}`,
|
|
780
|
+
`${theme.failure("│")} ${result.error.message}`,
|
|
781
|
+
theme.failure("╰─ 请根据提示修复后重试"),
|
|
782
|
+
].join("\n");
|
|
645
783
|
if (result.command === "help")
|
|
646
784
|
return [
|
|
647
|
-
"ProFlow 平台命令行",
|
|
785
|
+
theme.title("ProFlow 平台命令行"),
|
|
786
|
+
"管理 ProFlow 模块的安装、配置、文档和运行生命周期。",
|
|
648
787
|
"",
|
|
649
|
-
"
|
|
788
|
+
theme.section("用法"),
|
|
789
|
+
` ${theme.command("platform <command> [--workspace <路径>]")}`,
|
|
790
|
+
"",
|
|
791
|
+
theme.section("命令"),
|
|
792
|
+
`${theme.command("platform install")} 安装并初始化全部 ProFlow 模块`,
|
|
650
793
|
"platform uninstall 卸载模块包,保留 Workspace 数据",
|
|
651
794
|
"platform status 查看模块配置与运行状态",
|
|
652
795
|
"platform setup 自动配置并列出全部剩余步骤",
|
|
@@ -654,20 +797,32 @@ export function renderHumanResult(result) {
|
|
|
654
797
|
"platform start 完成全量检查后启动平台",
|
|
655
798
|
"platform stop 按逆依赖顺序停止平台",
|
|
656
799
|
"",
|
|
657
|
-
"
|
|
658
|
-
"
|
|
659
|
-
"
|
|
660
|
-
"
|
|
661
|
-
"
|
|
800
|
+
theme.section("公共参数"),
|
|
801
|
+
" --workspace <路径> 指定 ProFlow Workspace",
|
|
802
|
+
" setup --module <模块名> 只查看指定模块的配置步骤",
|
|
803
|
+
"",
|
|
804
|
+
theme.section("推荐流程"),
|
|
805
|
+
` ${theme.command("install → status → docs → setup → start → status → stop")}`,
|
|
806
|
+
"",
|
|
807
|
+
theme.section("人工配置示例"),
|
|
808
|
+
` 人工:${theme.command("pnpm exec -- proflow-chatgpt-carrier setup")}`,
|
|
809
|
+
` AI: ${theme.command("pnpm exec -- proflow-chatgpt-carrier setup --carrier-url <url>")}`,
|
|
810
|
+
"",
|
|
811
|
+
theme.section("状态图例"),
|
|
812
|
+
` ${theme.success("已就绪")} 配置完成 ${theme.warning("需要操作")} 运行 setup`,
|
|
813
|
+
` ${theme.failure("失败")} 存在阻塞 无独立进程 无需启动`,
|
|
814
|
+
"",
|
|
815
|
+
theme.section("遇到问题"),
|
|
816
|
+
` 先运行 ${theme.command("platform status")};配置问题运行 ${theme.command("platform setup")}。`,
|
|
662
817
|
].join("\n");
|
|
663
818
|
if (result.command === "version" && isRecord(result.data))
|
|
664
819
|
return String(result.data.version ?? platformCliDescriptor.moduleVersion);
|
|
665
820
|
if (result.command === "status")
|
|
666
|
-
return renderStatus(result.data);
|
|
821
|
+
return renderStatus(result.data, theme);
|
|
667
822
|
if (result.command === "setup")
|
|
668
|
-
return renderSetup(result.data);
|
|
823
|
+
return renderSetup(result.data, theme);
|
|
669
824
|
if (result.command === "docs")
|
|
670
|
-
return renderDocs(result.data);
|
|
825
|
+
return renderDocs(result.data, theme);
|
|
671
826
|
if ((result.command === "start" || result.command === "stop") &&
|
|
672
827
|
isRecord(result.data)) {
|
|
673
828
|
const skippedItems = Array.isArray(result.data.skipped)
|
|
@@ -684,8 +839,10 @@ export function renderHumanResult(result) {
|
|
|
684
839
|
!skippedRefs.has(String(item.moduleRef))).length;
|
|
685
840
|
const failed = operations.find((item) => isRecord(item.result) && item.result.status !== "SUCCEEDED");
|
|
686
841
|
return [
|
|
687
|
-
|
|
688
|
-
|
|
842
|
+
failed
|
|
843
|
+
? theme.failure(`平台${result.command === "start" ? "启动" : "停止"}未完成`)
|
|
844
|
+
: theme.success(`✓ 平台${result.command === "start" ? "启动" : "停止"}完成`),
|
|
845
|
+
`${theme.success(`成功:${succeeded}`)} ${theme.muted(`跳过:${skipped}`)} ${failed ? theme.failure(`失败:${String(failed.moduleRef)}`) : theme.muted("失败:0")}`,
|
|
689
846
|
].join("\n");
|
|
690
847
|
}
|
|
691
848
|
const labels = {
|
|
@@ -694,14 +851,40 @@ export function renderHumanResult(result) {
|
|
|
694
851
|
start: "启动",
|
|
695
852
|
stop: "停止",
|
|
696
853
|
};
|
|
697
|
-
return `${labels[result.command] ?? result.command}${result.status === "SUCCEEDED" ? "成功" : "未完成"}${result.workspaceRoot ? `\
|
|
854
|
+
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
855
|
}
|
|
699
856
|
if (import.meta.main) {
|
|
700
857
|
const argv = process.argv.slice(2);
|
|
701
858
|
const reporter = createTerminalProgressReporter();
|
|
702
859
|
const result = await runCli(argv, { onProgress: reporter });
|
|
703
860
|
reporter.close();
|
|
704
|
-
process.stdout.
|
|
861
|
+
const color = process.stdout.isTTY === true &&
|
|
862
|
+
process.env.NO_COLOR === undefined &&
|
|
863
|
+
process.env.TERM !== "dumb";
|
|
864
|
+
const rendered = `${renderHumanResult(result, {
|
|
865
|
+
color,
|
|
866
|
+
width: process.stdout.columns,
|
|
867
|
+
})}\n`;
|
|
868
|
+
if (result.command === "docs" &&
|
|
869
|
+
process.stdout.isTTY === true &&
|
|
870
|
+
rendered.split("\n").length > (process.stdout.rows ?? 24)) {
|
|
871
|
+
await writeThroughPager(rendered);
|
|
872
|
+
}
|
|
873
|
+
else
|
|
874
|
+
process.stdout.write(rendered);
|
|
705
875
|
if (result.status !== "SUCCEEDED")
|
|
706
876
|
process.exitCode = 1;
|
|
707
877
|
}
|
|
878
|
+
async function writeThroughPager(content) {
|
|
879
|
+
await new Promise((resolvePromise) => {
|
|
880
|
+
const pager = spawn("less", ["-FIRX"], {
|
|
881
|
+
stdio: ["pipe", "inherit", "inherit"],
|
|
882
|
+
});
|
|
883
|
+
pager.once("error", () => {
|
|
884
|
+
process.stdout.write(content);
|
|
885
|
+
resolvePromise();
|
|
886
|
+
});
|
|
887
|
+
pager.once("close", () => resolvePromise());
|
|
888
|
+
pager.stdin.end(content);
|
|
889
|
+
});
|
|
890
|
+
}
|
|
@@ -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
|
-
export type PlatformProgressStatus = "STARTED" | "SUCCEEDED" | "ACTION_REQUIRED" | "FAILED" | "SKIPPED";
|
|
1
|
+
export type PlatformProgressStatus = "STARTED" | "SUCCEEDED" | "WARNING" | "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,40 +1,58 @@
|
|
|
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
|
-
: event.status === "
|
|
15
|
-
? "
|
|
16
|
-
: event.status === "
|
|
17
|
-
? "
|
|
18
|
-
: event.status === "
|
|
19
|
-
? "
|
|
20
|
-
: ""
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
22
|
+
: event.status === "WARNING"
|
|
23
|
+
? "警告"
|
|
24
|
+
: event.status === "ACTION_REQUIRED"
|
|
25
|
+
? "待处理"
|
|
26
|
+
: event.status === "FAILED"
|
|
27
|
+
? "失败"
|
|
28
|
+
: event.status === "SKIPPED"
|
|
29
|
+
? "跳过"
|
|
30
|
+
: "";
|
|
31
|
+
const symbol = event.kind === "subprocess"
|
|
32
|
+
? "│"
|
|
33
|
+
: event.status === "STARTED"
|
|
34
|
+
? spinner
|
|
35
|
+
: event.status === "SUCCEEDED"
|
|
36
|
+
? "✓"
|
|
37
|
+
: event.status === "WARNING"
|
|
38
|
+
? "!"
|
|
39
|
+
: event.status === "FAILED"
|
|
40
|
+
? "✕"
|
|
41
|
+
: event.status === "ACTION_REQUIRED"
|
|
42
|
+
? "◆"
|
|
43
|
+
: "○";
|
|
30
44
|
if (!color)
|
|
31
|
-
return `${symbol ? `${symbol} ` : ""}${prefix}${event.message}${suffix ? ` · ${suffix}` : ""}`;
|
|
45
|
+
return `${symbol ? `${symbol} ` : ""}${prefix}${event.message}${suffix ? ` · ${suffix}` : ""}${elapsed}`;
|
|
32
46
|
const tone = event.status === "SUCCEEDED"
|
|
33
47
|
? colors.green
|
|
34
|
-
: event.status === "
|
|
35
|
-
? colors.
|
|
36
|
-
:
|
|
37
|
-
|
|
48
|
+
: event.status === "WARNING"
|
|
49
|
+
? colors.yellow
|
|
50
|
+
: event.status === "FAILED"
|
|
51
|
+
? colors.red
|
|
52
|
+
: event.kind === "subprocess"
|
|
53
|
+
? colors.blue
|
|
54
|
+
: colors.yellow;
|
|
55
|
+
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
56
|
}
|
|
39
57
|
export function createTerminalProgressReporter(stream = process.stderr) {
|
|
40
58
|
const interactive = stream.isTTY === true;
|
|
@@ -58,7 +76,10 @@ export function createTerminalProgressReporter(stream = process.stderr) {
|
|
|
58
76
|
};
|
|
59
77
|
const reporter = ((event) => {
|
|
60
78
|
clearSpinner();
|
|
61
|
-
|
|
79
|
+
const previousActive = active;
|
|
80
|
+
if (event.kind !== "subprocess") {
|
|
81
|
+
active = event.status === "STARTED" ? event : undefined;
|
|
82
|
+
}
|
|
62
83
|
const line = progressLine(event, color, interactive ? spinnerFrames[frame++ % spinnerFrames.length] : "›");
|
|
63
84
|
if (!interactive) {
|
|
64
85
|
stream.write(`${line}\n`);
|
|
@@ -67,18 +88,22 @@ export function createTerminalProgressReporter(stream = process.stderr) {
|
|
|
67
88
|
cursorTo(stream, 0);
|
|
68
89
|
clearLine(stream, 0);
|
|
69
90
|
stream.write(line);
|
|
91
|
+
if (event.kind === "subprocess") {
|
|
92
|
+
stream.write("\n");
|
|
93
|
+
active = previousActive;
|
|
94
|
+
if (active) {
|
|
95
|
+
paint();
|
|
96
|
+
timer = setInterval(paint, 80);
|
|
97
|
+
timer.unref();
|
|
98
|
+
}
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
70
101
|
if (event.status === "STARTED") {
|
|
71
102
|
timer = setInterval(paint, 80);
|
|
72
103
|
timer.unref();
|
|
73
104
|
return;
|
|
74
105
|
}
|
|
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");
|
|
106
|
+
stream.write("\n");
|
|
82
107
|
});
|
|
83
108
|
reporter.close = () => {
|
|
84
109
|
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.35",
|
|
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