@actiondock/cli 2.7.0 → 2.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -7
- package/bin/ad.js +52 -23
- package/dist/commands/action/create.js +4 -5
- package/dist/commands/add.js +9 -17
- package/dist/commands/build.js +3 -18
- package/dist/commands/config/delete.js +8 -7
- package/dist/commands/config/env.js +7 -11
- package/dist/commands/config/get.js +8 -5
- package/dist/commands/config/list.js +16 -22
- package/dist/commands/config/schema.d.ts +7 -1
- package/dist/commands/config/schema.js +44 -67
- package/dist/commands/config/set.js +9 -7
- package/dist/commands/describe.js +10 -9
- package/dist/commands/doctor.js +4 -3
- package/dist/commands/export.js +5 -13
- package/dist/commands/generate.js +4 -3
- package/dist/commands/info.js +3 -1
- package/dist/commands/init.js +3 -3
- package/dist/commands/link.js +4 -4
- package/dist/commands/list.js +19 -10
- package/dist/commands/mcp.js +27 -66
- package/dist/commands/pack.js +3 -12
- package/dist/commands/playbook.js +30 -10
- package/dist/commands/profile.js +10 -19
- package/dist/commands/remove.js +5 -14
- package/dist/commands/run.d.ts +5 -2
- package/dist/commands/run.js +19 -20
- package/dist/commands/runs.js +26 -35
- package/dist/commands/serve.js +177 -57
- package/dist/commands/state-list.d.ts +2 -2
- package/dist/commands/state-list.js +10 -8
- package/dist/commands/state.d.ts +2 -2
- package/dist/commands/state.js +110 -103
- package/dist/commands/test.js +1 -1
- package/dist/commands/validate.js +4 -17
- package/dist/errors.d.ts +14 -0
- package/dist/errors.js +46 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +3 -3
- package/dist/renderer.d.ts +1 -1
- package/dist/renderer.js +1 -1
- package/dist/services/config-merge.d.ts +6 -5
- package/dist/services/config-merge.js +10 -8
- package/dist/services/inspect.js +3 -1
- package/dist/standalone/index.d.ts +4 -14
- package/dist/standalone/index.js +5 -44
- package/dist/types.d.ts +2 -1
- package/dist/types.js +1 -1
- package/dist/utils/cert.js +1 -1
- package/dist/utils/duration.d.ts +1 -1
- package/dist/utils/duration.js +1 -1
- package/dist/utils/index.d.ts +1 -2
- package/dist/utils/index.js +1 -2
- package/dist/utils/input.d.ts +9 -1
- package/dist/utils/input.js +12 -1
- package/dist/utils/server-launch.d.ts +114 -0
- package/dist/utils/server-launch.js +275 -0
- package/dist/utils/target.d.ts +14 -13
- package/dist/utils/target.js +20 -39
- package/dist/utils/utils.d.ts +101 -1
- package/dist/utils/utils.js +97 -7
- package/package.json +5 -6
- package/dist/utils/bytes.d.ts +0 -1
- package/dist/utils/bytes.js +0 -1
- package/dist/utils/filter.d.ts +0 -1
- package/dist/utils/filter.js +0 -1
package/dist/standalone/index.js
CHANGED
|
@@ -1,52 +1,13 @@
|
|
|
1
|
-
import { StandaloneDispatcher as CoreStandaloneDispatcher, } from "@actiondock/core";
|
|
1
|
+
import { StandaloneDispatcher as CoreStandaloneDispatcher, } from "@actiondock/core/server";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
*/
|
|
5
|
-
export class StandaloneDispatcher extends CoreStandaloneDispatcher {
|
|
6
|
-
constructor(options) {
|
|
7
|
-
super(options);
|
|
8
|
-
}
|
|
9
|
-
}
|
|
10
|
-
/**
|
|
11
|
-
* 运行独立二进制参数解析分发器并返回退出状态码。
|
|
3
|
+
* 运行独立二进制参数解析分发器并返回退出状态码(委托 Core 统一实现)。
|
|
12
4
|
*/
|
|
13
5
|
export async function runStandaloneCli(argv = process.argv.slice(2), options, control) {
|
|
14
|
-
const dispatcher = new
|
|
6
|
+
const dispatcher = new CoreStandaloneDispatcher(options);
|
|
15
7
|
return dispatcher.dispatch(argv, control);
|
|
16
8
|
}
|
|
17
9
|
/**
|
|
18
|
-
*
|
|
19
|
-
*/
|
|
20
|
-
export function createStandaloneDispatcher(options) {
|
|
21
|
-
return new StandaloneDispatcher(options);
|
|
22
|
-
}
|
|
23
|
-
/**
|
|
24
|
-
* 独立二进制进程入口适配器。
|
|
10
|
+
* 独立二进制进程入口适配器(事实源位于 core 包,此处 re-export 维持既有导入路径兼容)。
|
|
25
11
|
* 仅在命令行可执行入口调用,负责全局 SIGINT 监听与退出状态码写入。
|
|
26
12
|
*/
|
|
27
|
-
export
|
|
28
|
-
const controller = new AbortController();
|
|
29
|
-
const control = {
|
|
30
|
-
signal: controller.signal,
|
|
31
|
-
};
|
|
32
|
-
let sigintCount = 0;
|
|
33
|
-
const sigintHandler = () => {
|
|
34
|
-
sigintCount++;
|
|
35
|
-
if (sigintCount === 1) {
|
|
36
|
-
control.cancellationSource = "sigint";
|
|
37
|
-
controller.abort(new Error("Interrupted by SIGINT"));
|
|
38
|
-
}
|
|
39
|
-
else {
|
|
40
|
-
process.exitCode = 130;
|
|
41
|
-
process.exit(130);
|
|
42
|
-
}
|
|
43
|
-
};
|
|
44
|
-
process.on("SIGINT", sigintHandler);
|
|
45
|
-
try {
|
|
46
|
-
const exitCode = await runStandaloneCli(argv, options, control);
|
|
47
|
-
process.exitCode = exitCode;
|
|
48
|
-
}
|
|
49
|
-
finally {
|
|
50
|
-
process.removeListener("SIGINT", sigintHandler);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
13
|
+
export { runStandaloneProcess } from "@actiondock/core/server";
|
package/dist/types.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ActionDefinition } from "@actiondock/sdk";
|
|
2
|
-
import type { ConfigItemDefinition
|
|
2
|
+
import type { ConfigItemDefinition } from "@actiondock/core/project";
|
|
3
|
+
import type { InvocationControl } from "@actiondock/core/server";
|
|
3
4
|
export type { InvocationControl };
|
|
4
5
|
/**
|
|
5
6
|
* 统一退出码定义(事实源位于 core 包 StandaloneDispatcher,此处仅 re-export 维持既有导入路径兼容)。
|
package/dist/types.js
CHANGED
package/dist/utils/cert.js
CHANGED
|
@@ -3,7 +3,7 @@ import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, writeFileSy
|
|
|
3
3
|
import { isIP } from "node:net";
|
|
4
4
|
import { hostname, networkInterfaces } from "node:os";
|
|
5
5
|
import { join } from "node:path";
|
|
6
|
-
import { getActionDockHome } from "@actiondock/core";
|
|
6
|
+
import { getActionDockHome, } from "@actiondock/core/registry";
|
|
7
7
|
import { generate } from "selfsigned";
|
|
8
8
|
/**
|
|
9
9
|
* 判定字符串是否为有效 IPv4 或 IPv6 地址。
|
package/dist/utils/duration.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { parseDuration } from "@actiondock/core";
|
|
1
|
+
export { parseDuration } from "@actiondock/core/project";
|
package/dist/utils/duration.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { parseDuration } from "@actiondock/core";
|
|
1
|
+
export { parseDuration } from "@actiondock/core/project";
|
package/dist/utils/index.d.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
export * from "./utils.js";
|
|
2
|
-
export * from "./bytes.js";
|
|
3
2
|
export * from "./duration.js";
|
|
4
|
-
export * from "./filter.js";
|
|
5
3
|
export * from "./target.js";
|
|
6
4
|
export * from "./spawn.js";
|
|
7
5
|
export * from "./cert.js";
|
|
8
6
|
export * from "./input.js";
|
|
7
|
+
export * from "./server-launch.js";
|
package/dist/utils/index.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
export * from "./utils.js";
|
|
2
|
-
export * from "./bytes.js";
|
|
3
2
|
export * from "./duration.js";
|
|
4
|
-
export * from "./filter.js";
|
|
5
3
|
export * from "./target.js";
|
|
6
4
|
export * from "./spawn.js";
|
|
7
5
|
export * from "./cert.js";
|
|
8
6
|
export * from "./input.js";
|
|
7
|
+
export * from "./server-launch.js";
|
package/dist/utils/input.d.ts
CHANGED
|
@@ -1 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
import { stripBom } from "@actiondock/core/project";
|
|
2
|
+
export { resolveActionInput, type ResolveActionInputOptions, parseJson, InputError, FlatInputError, buildActionInputAdvice, formatActionDetail, } from "@actiondock/core/project";
|
|
3
|
+
export { stripBom };
|
|
4
|
+
/**
|
|
5
|
+
* 从标准输入流中完整读取全部数据并转换为 UTF-8 字符串。
|
|
6
|
+
* 底层委托 core 的有界读取实现:保持宽松 UTF-8 解码语义与 BOM 剥离行为,
|
|
7
|
+
* 同时获得默认 10MB 输入上限保护,超限时抛出结构化 INPUT_LIMIT_EXCEEDED 异常。
|
|
8
|
+
*/
|
|
9
|
+
export declare function readStdin(stream?: NodeJS.ReadableStream): Promise<string>;
|
package/dist/utils/input.js
CHANGED
|
@@ -1 +1,12 @@
|
|
|
1
|
-
|
|
1
|
+
import { readStdinBounded, stripBom } from "@actiondock/core/project";
|
|
2
|
+
export { resolveActionInput, parseJson, InputError, FlatInputError, buildActionInputAdvice, formatActionDetail, } from "@actiondock/core/project";
|
|
3
|
+
// BOM 剥离单一事实源转引:core 已提供逐字一致的实现,此处仅保留导出面兼容。
|
|
4
|
+
export { stripBom };
|
|
5
|
+
/**
|
|
6
|
+
* 从标准输入流中完整读取全部数据并转换为 UTF-8 字符串。
|
|
7
|
+
* 底层委托 core 的有界读取实现:保持宽松 UTF-8 解码语义与 BOM 剥离行为,
|
|
8
|
+
* 同时获得默认 10MB 输入上限保护,超限时抛出结构化 INPUT_LIMIT_EXCEEDED 异常。
|
|
9
|
+
*/
|
|
10
|
+
export async function readStdin(stream = process.stdin) {
|
|
11
|
+
return stripBom(await readStdinBounded(stream, { strictUtf8: false }));
|
|
12
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import type { ServerViewOptions } from "@actiondock/core/server";
|
|
2
|
+
import type { CliContext } from "../types.js";
|
|
3
|
+
/**
|
|
4
|
+
* 服务类命令(ad serve、ad mcp serve、ad mcp、ad run)的共享启动脚手架(单一事实源)。
|
|
5
|
+
*
|
|
6
|
+
* 收敛端口/主机/token 解析(含 ACTIONDOCK_TOKEN 环境变量回退)、
|
|
7
|
+
* maxBodyBytes 与 timeoutMs 的 try-catch 包装、corsOrigins 归一化、
|
|
8
|
+
* 停止信号注册与横幅打印,消除 serve.ts 与 mcp.ts 的逐行重复。
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* 解析监听端口:解析失败时回退缺省端口。
|
|
12
|
+
*
|
|
13
|
+
* @param rawPort 命令行原始端口串
|
|
14
|
+
* @param defaultPort 缺省端口(ad serve 为 5177,ad mcp serve 为 5178)
|
|
15
|
+
* @param falsyFallsBack 是否对零值也回退(mcp serve 旧行为为 parseInt || default,
|
|
16
|
+
* 端口 0 会回退缺省;ad serve 为 NaN 判定,端口 0 保留)
|
|
17
|
+
*/
|
|
18
|
+
export declare function resolveServerPort(rawPort: string, defaultPort: number, falsyFallsBack?: boolean): number;
|
|
19
|
+
/**
|
|
20
|
+
* 解析监听主机:未指定时回退 127.0.0.1。
|
|
21
|
+
*/
|
|
22
|
+
export declare function resolveServerHost(rawHost: string | undefined): string;
|
|
23
|
+
/**
|
|
24
|
+
* 解析鉴权 Token:显式参数优先,其次 --token-env 指定的环境变量,最后 ACTIONDOCK_TOKEN 兜底。
|
|
25
|
+
*
|
|
26
|
+
* @param token 显式 -t/--token 参数
|
|
27
|
+
* @param tokenEnv --token-env 指定的环境变量名
|
|
28
|
+
*/
|
|
29
|
+
export declare function resolveServerToken(token?: string, tokenEnv?: string): string | undefined;
|
|
30
|
+
/**
|
|
31
|
+
* 解析请求体大小上限(--max-body):格式非法时抛 ArgumentError。
|
|
32
|
+
*/
|
|
33
|
+
export declare function resolveMaxBodyBytes(maxBody: string | undefined): number | undefined;
|
|
34
|
+
/**
|
|
35
|
+
* 解析执行超时(--timeout):格式非法时抛 ArgumentError。
|
|
36
|
+
*/
|
|
37
|
+
export declare function resolveTimeoutMs(timeout: string | undefined): number | undefined;
|
|
38
|
+
/**
|
|
39
|
+
* 归一化 CORS 来源列表:空列表归一为 undefined。
|
|
40
|
+
*/
|
|
41
|
+
export declare function normalizeCorsOrigins(corsOrigin: string[] | undefined): string[] | undefined;
|
|
42
|
+
/**
|
|
43
|
+
* 注册 SIGINT/SIGTERM 停止信号处理器(serve 与 mcp serve 共享同一语义)。
|
|
44
|
+
*
|
|
45
|
+
* 停止失败必须可见:写入 stderr 一行并标记失败退出码;
|
|
46
|
+
* 无论成功与否最终强制进程退出。
|
|
47
|
+
*
|
|
48
|
+
* @param server 目标服务实例(提供 stop 方法)
|
|
49
|
+
* @param serverName 横幅与停止消息中的服务名(如 ActionDock server / MCP HTTP server)
|
|
50
|
+
*/
|
|
51
|
+
export declare function registerStopSignalHandler(server: {
|
|
52
|
+
stop(): Promise<unknown> | unknown;
|
|
53
|
+
}, serverName: string, context?: CliContext): void;
|
|
54
|
+
/**
|
|
55
|
+
* 虚拟视图简要展示信息。
|
|
56
|
+
*/
|
|
57
|
+
export interface ServerViewSummary {
|
|
58
|
+
name: string;
|
|
59
|
+
enableMcp?: boolean;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* 打印服务横幅的可选配置参数。
|
|
63
|
+
*/
|
|
64
|
+
export interface PrintServerBannerOptions {
|
|
65
|
+
views?: Record<string, ServerViewOptions> | ServerViewOptions[];
|
|
66
|
+
endpointHost?: string;
|
|
67
|
+
enableMcp?: boolean;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* 从指定文件路径加载虚拟视图配置字典。
|
|
71
|
+
*
|
|
72
|
+
* @param filePath 视图配置文件路径
|
|
73
|
+
* @returns 解析后的视图字典或数组
|
|
74
|
+
*/
|
|
75
|
+
export declare function loadViewsFromFile(filePath: string): Record<string, ServerViewOptions> | ServerViewOptions[];
|
|
76
|
+
/**
|
|
77
|
+
* 合并项目配置与独立配置文件中的虚拟视图定义。
|
|
78
|
+
* 命令行 --views-file 优先级高于项目配置文件 actiondock.json 中的 server.views。
|
|
79
|
+
*
|
|
80
|
+
* @param configViews 项目配置中的 views
|
|
81
|
+
* @param fileViews 独立配置文件中的 views
|
|
82
|
+
*/
|
|
83
|
+
export declare function mergeServerViews(configViews?: Record<string, ServerViewOptions> | ServerViewOptions[], fileViews?: Record<string, ServerViewOptions> | ServerViewOptions[]): Record<string, ServerViewOptions> | ServerViewOptions[] | undefined;
|
|
84
|
+
/**
|
|
85
|
+
* 从服务视图配置中提取所有命名空间视图列表(排除 default 默认视图)。
|
|
86
|
+
*
|
|
87
|
+
* @param views 视图字典或数组
|
|
88
|
+
* @returns 命名空间视图简要信息列表
|
|
89
|
+
*/
|
|
90
|
+
export declare function extractNamespacedViews(views?: Record<string, ServerViewOptions> | ServerViewOptions[]): ServerViewSummary[];
|
|
91
|
+
/**
|
|
92
|
+
* 打印服务命名空间虚拟视图列表。
|
|
93
|
+
*
|
|
94
|
+
* @param views 视图配置字典或数组
|
|
95
|
+
* @param scheme 访问协议(http 或 https)
|
|
96
|
+
* @param endpointHost 面向调用的端点主机地址
|
|
97
|
+
* @param port 监听端口
|
|
98
|
+
* @param context CLI 上下文
|
|
99
|
+
* @param options 额外选项(如全局 enableMcp)
|
|
100
|
+
*/
|
|
101
|
+
export declare function printServerViews(views: Record<string, ServerViewOptions> | ServerViewOptions[] | undefined, scheme: string, endpointHost: string, port: number, context?: CliContext, options?: {
|
|
102
|
+
enableMcp?: boolean;
|
|
103
|
+
}): void;
|
|
104
|
+
/**
|
|
105
|
+
* 打印服务就绪横幅(顶部固定段,支持展示命名空间视图)。
|
|
106
|
+
*
|
|
107
|
+
* @param title 横幅主体标题(如 ActionDock 2.0 HTTP Runner Server)
|
|
108
|
+
* @param scheme 端点协议(http 或 https)
|
|
109
|
+
* @param displayHost 面向展示的主机地址
|
|
110
|
+
* @param port 实际监听端口
|
|
111
|
+
* @param contextOrOptions 命令行上下文或横幅配置选项
|
|
112
|
+
* @param maybeOptions 横幅配置选项(包含 views 等信息)
|
|
113
|
+
*/
|
|
114
|
+
export declare function printServerBanner(title: string, scheme: string, displayHost: string, port: number, contextOrOptions?: CliContext | PrintServerBannerOptions, maybeOptions?: PrintServerBannerOptions): void;
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { parseDuration, } from "@actiondock/core/project";
|
|
4
|
+
import { ArgumentError } from "../errors.js";
|
|
5
|
+
import { writeStderr, writeStdout } from "../renderer.js";
|
|
6
|
+
import { parseByteSize } from "./utils.js";
|
|
7
|
+
/**
|
|
8
|
+
* 服务类命令(ad serve、ad mcp serve、ad mcp、ad run)的共享启动脚手架(单一事实源)。
|
|
9
|
+
*
|
|
10
|
+
* 收敛端口/主机/token 解析(含 ACTIONDOCK_TOKEN 环境变量回退)、
|
|
11
|
+
* maxBodyBytes 与 timeoutMs 的 try-catch 包装、corsOrigins 归一化、
|
|
12
|
+
* 停止信号注册与横幅打印,消除 serve.ts 与 mcp.ts 的逐行重复。
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* 解析监听端口:解析失败时回退缺省端口。
|
|
16
|
+
*
|
|
17
|
+
* @param rawPort 命令行原始端口串
|
|
18
|
+
* @param defaultPort 缺省端口(ad serve 为 5177,ad mcp serve 为 5178)
|
|
19
|
+
* @param falsyFallsBack 是否对零值也回退(mcp serve 旧行为为 parseInt || default,
|
|
20
|
+
* 端口 0 会回退缺省;ad serve 为 NaN 判定,端口 0 保留)
|
|
21
|
+
*/
|
|
22
|
+
export function resolveServerPort(rawPort, defaultPort, falsyFallsBack) {
|
|
23
|
+
const parsed = parseInt(rawPort, 10);
|
|
24
|
+
if (falsyFallsBack) {
|
|
25
|
+
return parsed || defaultPort;
|
|
26
|
+
}
|
|
27
|
+
return Number.isNaN(parsed) ? defaultPort : parsed;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* 解析监听主机:未指定时回退 127.0.0.1。
|
|
31
|
+
*/
|
|
32
|
+
export function resolveServerHost(rawHost) {
|
|
33
|
+
return rawHost || "127.0.0.1";
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* 解析鉴权 Token:显式参数优先,其次 --token-env 指定的环境变量,最后 ACTIONDOCK_TOKEN 兜底。
|
|
37
|
+
*
|
|
38
|
+
* @param token 显式 -t/--token 参数
|
|
39
|
+
* @param tokenEnv --token-env 指定的环境变量名
|
|
40
|
+
*/
|
|
41
|
+
export function resolveServerToken(token, tokenEnv) {
|
|
42
|
+
return (token ||
|
|
43
|
+
(tokenEnv && typeof process !== "undefined" ? process.env?.[tokenEnv] : undefined) ||
|
|
44
|
+
(typeof process !== "undefined" ? process.env?.ACTIONDOCK_TOKEN : undefined));
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* 解析请求体大小上限(--max-body):格式非法时抛 ArgumentError。
|
|
48
|
+
*/
|
|
49
|
+
export function resolveMaxBodyBytes(maxBody) {
|
|
50
|
+
if (!maxBody)
|
|
51
|
+
return undefined;
|
|
52
|
+
try {
|
|
53
|
+
return parseByteSize(maxBody);
|
|
54
|
+
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
throw new ArgumentError(`Invalid max-body format: ${err.message}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* 解析执行超时(--timeout):格式非法时抛 ArgumentError。
|
|
61
|
+
*/
|
|
62
|
+
export function resolveTimeoutMs(timeout) {
|
|
63
|
+
if (!timeout)
|
|
64
|
+
return undefined;
|
|
65
|
+
try {
|
|
66
|
+
return parseDuration(timeout);
|
|
67
|
+
}
|
|
68
|
+
catch (err) {
|
|
69
|
+
throw new ArgumentError(`Invalid timeout format: ${err.message}`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* 归一化 CORS 来源列表:空列表归一为 undefined。
|
|
74
|
+
*/
|
|
75
|
+
export function normalizeCorsOrigins(corsOrigin) {
|
|
76
|
+
return corsOrigin && corsOrigin.length > 0 ? corsOrigin : undefined;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* 注册 SIGINT/SIGTERM 停止信号处理器(serve 与 mcp serve 共享同一语义)。
|
|
80
|
+
*
|
|
81
|
+
* 停止失败必须可见:写入 stderr 一行并标记失败退出码;
|
|
82
|
+
* 无论成功与否最终强制进程退出。
|
|
83
|
+
*
|
|
84
|
+
* @param server 目标服务实例(提供 stop 方法)
|
|
85
|
+
* @param serverName 横幅与停止消息中的服务名(如 ActionDock server / MCP HTTP server)
|
|
86
|
+
*/
|
|
87
|
+
export function registerStopSignalHandler(server, serverName, context) {
|
|
88
|
+
const stopSignalHandler = () => {
|
|
89
|
+
writeStdout(`\nStopping ${serverName}...`, context);
|
|
90
|
+
Promise.resolve(server.stop())
|
|
91
|
+
.catch((err) => {
|
|
92
|
+
// 停止失败必须可见:写入 stderr 一行并标记失败退出码
|
|
93
|
+
writeStderr(`[ERROR] Failed to stop ${serverName} gracefully: ${err instanceof Error ? err.message : String(err)}`, context);
|
|
94
|
+
process.exitCode = 1;
|
|
95
|
+
})
|
|
96
|
+
.finally(() => {
|
|
97
|
+
process.exit(typeof process.exitCode === "number" ? process.exitCode : 0);
|
|
98
|
+
});
|
|
99
|
+
};
|
|
100
|
+
process.once("SIGINT", stopSignalHandler);
|
|
101
|
+
process.once("SIGTERM", stopSignalHandler);
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* 从指定文件路径加载虚拟视图配置字典。
|
|
105
|
+
*
|
|
106
|
+
* @param filePath 视图配置文件路径
|
|
107
|
+
* @returns 解析后的视图字典或数组
|
|
108
|
+
*/
|
|
109
|
+
export function loadViewsFromFile(filePath) {
|
|
110
|
+
const resolvedPath = resolve(filePath);
|
|
111
|
+
if (!existsSync(resolvedPath)) {
|
|
112
|
+
throw new ArgumentError(`Views file not found: ${resolvedPath}`);
|
|
113
|
+
}
|
|
114
|
+
let parsed;
|
|
115
|
+
try {
|
|
116
|
+
const content = readFileSync(resolvedPath, "utf-8");
|
|
117
|
+
parsed = JSON.parse(content);
|
|
118
|
+
}
|
|
119
|
+
catch (err) {
|
|
120
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
121
|
+
throw new ArgumentError(`Failed to read views file: ${msg}`);
|
|
122
|
+
}
|
|
123
|
+
if (!parsed || typeof parsed !== "object") {
|
|
124
|
+
throw new ArgumentError("Invalid views file: expected a JSON object or array");
|
|
125
|
+
}
|
|
126
|
+
if (Array.isArray(parsed)) {
|
|
127
|
+
return parsed;
|
|
128
|
+
}
|
|
129
|
+
const obj = parsed;
|
|
130
|
+
if (obj.views && typeof obj.views === "object") {
|
|
131
|
+
return obj.views;
|
|
132
|
+
}
|
|
133
|
+
if (obj.server && typeof obj.server === "object" && obj.server.views) {
|
|
134
|
+
const srv = obj.server;
|
|
135
|
+
return srv.views;
|
|
136
|
+
}
|
|
137
|
+
return parsed;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* 合并项目配置与独立配置文件中的虚拟视图定义。
|
|
141
|
+
* 命令行 --views-file 优先级高于项目配置文件 actiondock.json 中的 server.views。
|
|
142
|
+
*
|
|
143
|
+
* @param configViews 项目配置中的 views
|
|
144
|
+
* @param fileViews 独立配置文件中的 views
|
|
145
|
+
*/
|
|
146
|
+
export function mergeServerViews(configViews, fileViews) {
|
|
147
|
+
if (!configViews && !fileViews) {
|
|
148
|
+
return undefined;
|
|
149
|
+
}
|
|
150
|
+
if (!configViews) {
|
|
151
|
+
return fileViews;
|
|
152
|
+
}
|
|
153
|
+
if (!fileViews) {
|
|
154
|
+
return configViews;
|
|
155
|
+
}
|
|
156
|
+
if (!Array.isArray(configViews) && !Array.isArray(fileViews)) {
|
|
157
|
+
return { ...configViews, ...fileViews };
|
|
158
|
+
}
|
|
159
|
+
const merged = {};
|
|
160
|
+
const addEntry = (item, fallbackKey) => {
|
|
161
|
+
const key = item.name?.trim() || fallbackKey;
|
|
162
|
+
merged[key] = { ...item, name: key };
|
|
163
|
+
};
|
|
164
|
+
if (Array.isArray(configViews)) {
|
|
165
|
+
configViews.forEach((item, index) => addEntry(item, item.name || `view_${index}`));
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
Object.entries(configViews).forEach(([k, v]) => addEntry(v, k));
|
|
169
|
+
}
|
|
170
|
+
if (Array.isArray(fileViews)) {
|
|
171
|
+
fileViews.forEach((item, index) => addEntry(item, item.name || `view_${index}`));
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
Object.entries(fileViews).forEach(([k, v]) => addEntry(v, k));
|
|
175
|
+
}
|
|
176
|
+
return merged;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* 从服务视图配置中提取所有命名空间视图列表(排除 default 默认视图)。
|
|
180
|
+
*
|
|
181
|
+
* @param views 视图字典或数组
|
|
182
|
+
* @returns 命名空间视图简要信息列表
|
|
183
|
+
*/
|
|
184
|
+
export function extractNamespacedViews(views) {
|
|
185
|
+
if (!views)
|
|
186
|
+
return [];
|
|
187
|
+
const result = [];
|
|
188
|
+
if (Array.isArray(views)) {
|
|
189
|
+
for (let i = 0; i < views.length; i++) {
|
|
190
|
+
const item = views[i];
|
|
191
|
+
if (!item)
|
|
192
|
+
continue;
|
|
193
|
+
const name = item.name?.trim() || `view_${i}`;
|
|
194
|
+
if (name === "default")
|
|
195
|
+
continue;
|
|
196
|
+
result.push({
|
|
197
|
+
name,
|
|
198
|
+
enableMcp: item.enableMcp,
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
else if (typeof views === "object") {
|
|
203
|
+
for (const [key, item] of Object.entries(views)) {
|
|
204
|
+
if (!item)
|
|
205
|
+
continue;
|
|
206
|
+
const name = item.name?.trim() || key.trim();
|
|
207
|
+
if (name === "default")
|
|
208
|
+
continue;
|
|
209
|
+
result.push({
|
|
210
|
+
name,
|
|
211
|
+
enableMcp: item.enableMcp,
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return result;
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* 打印服务命名空间虚拟视图列表。
|
|
219
|
+
*
|
|
220
|
+
* @param views 视图配置字典或数组
|
|
221
|
+
* @param scheme 访问协议(http 或 https)
|
|
222
|
+
* @param endpointHost 面向调用的端点主机地址
|
|
223
|
+
* @param port 监听端口
|
|
224
|
+
* @param context CLI 上下文
|
|
225
|
+
* @param options 额外选项(如全局 enableMcp)
|
|
226
|
+
*/
|
|
227
|
+
export function printServerViews(views, scheme, endpointHost, port, context, options) {
|
|
228
|
+
const namespaced = extractNamespacedViews(views);
|
|
229
|
+
if (namespaced.length === 0) {
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
writeStdout(` * Views:`, context);
|
|
233
|
+
for (const v of namespaced) {
|
|
234
|
+
const encodedName = encodeURIComponent(v.name);
|
|
235
|
+
writeStdout(` - ${v.name}:`, context);
|
|
236
|
+
writeStdout(` - HTTP Root: ${scheme}://${endpointHost}:${port}/views/${encodedName}`, context);
|
|
237
|
+
const mcpEnabled = options?.enableMcp !== false && v.enableMcp !== false;
|
|
238
|
+
const mcpDesc = mcpEnabled
|
|
239
|
+
? `${scheme}://${endpointHost}:${port}/views/${encodedName}/mcp`
|
|
240
|
+
: "Disabled";
|
|
241
|
+
writeStdout(` - MCP Endpoint: ${mcpDesc}`, context);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* 打印服务就绪横幅(顶部固定段,支持展示命名空间视图)。
|
|
246
|
+
*
|
|
247
|
+
* @param title 横幅主体标题(如 ActionDock 2.0 HTTP Runner Server)
|
|
248
|
+
* @param scheme 端点协议(http 或 https)
|
|
249
|
+
* @param displayHost 面向展示的主机地址
|
|
250
|
+
* @param port 实际监听端口
|
|
251
|
+
* @param contextOrOptions 命令行上下文或横幅配置选项
|
|
252
|
+
* @param maybeOptions 横幅配置选项(包含 views 等信息)
|
|
253
|
+
*/
|
|
254
|
+
export function printServerBanner(title, scheme, displayHost, port, contextOrOptions, maybeOptions) {
|
|
255
|
+
let context;
|
|
256
|
+
let options;
|
|
257
|
+
if (contextOrOptions &&
|
|
258
|
+
("views" in contextOrOptions || "endpointHost" in contextOrOptions || "enableMcp" in contextOrOptions)) {
|
|
259
|
+
options = contextOrOptions;
|
|
260
|
+
}
|
|
261
|
+
else {
|
|
262
|
+
context = contextOrOptions;
|
|
263
|
+
options = maybeOptions;
|
|
264
|
+
}
|
|
265
|
+
writeStdout(`\n======================================================`, context);
|
|
266
|
+
writeStdout(` ${title}`, context);
|
|
267
|
+
writeStdout(`======================================================`, context);
|
|
268
|
+
writeStdout(` * Listening on: ${scheme}://${displayHost}:${port}`, context);
|
|
269
|
+
if (options?.views) {
|
|
270
|
+
const endpointHost = options.endpointHost || displayHost;
|
|
271
|
+
printServerViews(options.views, scheme, endpointHost, port, context, {
|
|
272
|
+
enableMcp: options.enableMcp,
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
}
|
package/dist/utils/target.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type ActionDockService } from "@actiondock/core";
|
|
2
|
+
import { type ResolvedTarget } from "@actiondock/core/profile";
|
|
2
3
|
import type { Command } from "commander";
|
|
3
4
|
import type { CliContext } from "../types.js";
|
|
4
5
|
/**
|
|
@@ -10,7 +11,7 @@ import type { CliContext } from "../types.js";
|
|
|
10
11
|
*/
|
|
11
12
|
export declare function applyTargetOptions(command: Command): Command;
|
|
12
13
|
/**
|
|
13
|
-
* 目标解析与
|
|
14
|
+
* 目标解析与 Service 创建所需的统一选项视图。
|
|
14
15
|
* 与既有命令内联解析逻辑保持逐字段一致,作为唯一事实源供各命令复用。
|
|
15
16
|
*/
|
|
16
17
|
export interface TargetResolutionOptions {
|
|
@@ -37,14 +38,14 @@ export interface TargetResolutionOptions {
|
|
|
37
38
|
*
|
|
38
39
|
* @param options 命令选项视图(支持直接传入 Commander 解析后的 options 对象)
|
|
39
40
|
* @param context CLI 上下文
|
|
40
|
-
* @returns 解析后的目标拓扑信息(仅信息解析,不创建
|
|
41
|
+
* @returns 解析后的目标拓扑信息(仅信息解析,不创建 Service 实例)
|
|
41
42
|
*/
|
|
42
43
|
export declare function resolveTargetFromOptions(options: TargetResolutionOptions, context?: CliContext): ResolvedTarget;
|
|
43
44
|
/**
|
|
44
|
-
*
|
|
45
|
+
* 高阶服务执行辅助:统一封装「目标解析 → Service 创建 → 业务回调 → try/finally close」样板。
|
|
45
46
|
*
|
|
46
|
-
* 回调获得创建好的
|
|
47
|
-
* 都保证
|
|
47
|
+
* 回调获得创建好的 service 与解析后的目标拓扑信息;无论回调成功或抛出,
|
|
48
|
+
* 都保证 service 资源被正确释放,业务异常原样透传。
|
|
48
49
|
*
|
|
49
50
|
* 默认旁观打开:内部创建的本地 Host 以非收割模式打开存储(不置 recoverOrphans),
|
|
50
51
|
* 供 ad state / ad runs / ad config 等查询命令与运行中的 serve 进程并发访问同一库;
|
|
@@ -52,10 +53,10 @@ export declare function resolveTargetFromOptions(options: TargetResolutionOption
|
|
|
52
53
|
*
|
|
53
54
|
* @param options 命令选项视图(profile/server/token/package/dataDir/insecure/allowInsecureHttp)
|
|
54
55
|
* @param context CLI 上下文
|
|
55
|
-
* @param fn 业务回调(
|
|
56
|
+
* @param fn 业务回调(service 为已创建的服务端口实例,resolved 为拓扑解析结果)
|
|
56
57
|
* @param localOptions 本地分支附加选项(如 scanLinkedPackages)与 localRoot 回退策略
|
|
57
58
|
*/
|
|
58
|
-
export declare function
|
|
59
|
+
export declare function withService(options: TargetResolutionOptions, context: CliContext | undefined, fn: (service: ActionDockService, resolved: ResolvedTarget) => Promise<void>, localOptions?: {
|
|
59
60
|
/** 本地工程根目录(支持惰性工厂,仅在 local 分支求值);未提供时按 findProjectRoot 回退 */
|
|
60
61
|
localRoot?: string | (() => string | undefined);
|
|
61
62
|
/** 是否扫描全局链接包(对应 scanLinkedPackages) */
|
|
@@ -66,12 +67,12 @@ export declare function withTarget(options: TargetResolutionOptions, context: Cl
|
|
|
66
67
|
* 保持缺省 false,避免误收割并发 serve 进程的在途运行记录。
|
|
67
68
|
*/
|
|
68
69
|
ownDataDir?: boolean;
|
|
70
|
+
/**
|
|
71
|
+
* 是否强制要求远端目标:true 时 local 分支直接抛 ArgumentError(如 ad runs cancel),
|
|
72
|
+
* 替代原 withRemoteService 的专用实现。
|
|
73
|
+
*/
|
|
74
|
+
requireRemote?: boolean;
|
|
69
75
|
}): Promise<void>;
|
|
70
|
-
/**
|
|
71
|
-
* 仅远端模式目标执行辅助:创建远端 Target 并保证资源释放。
|
|
72
|
-
* 适用于 runs cancel 等明确要求远端目标的命令。
|
|
73
|
-
*/
|
|
74
|
-
export declare function withRemoteTarget(options: TargetResolutionOptions, context: CliContext | undefined, fn: (target: ActionDockTarget, resolved: ResolvedTarget) => Promise<void>): Promise<void>;
|
|
75
76
|
/**
|
|
76
77
|
* 目标包根目录解析(含 -P 显式寻址与当前工程回退)。
|
|
77
78
|
* 供命令在创建 Target 前统一完成包寻址,返回 null 表示当前目录无工程。
|