@actiondock/cli 2.5.1 → 2.6.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 +44 -3
- package/dist/commands/action/create.js +2 -2
- package/dist/commands/add.js +5 -5
- package/dist/commands/build.js +4 -5
- package/dist/commands/config/delete.js +2 -2
- package/dist/commands/config/env.js +6 -15
- package/dist/commands/config/get.js +8 -39
- package/dist/commands/config/list.js +8 -55
- package/dist/commands/config/schema.js +4 -6
- package/dist/commands/config/set.js +2 -2
- package/dist/commands/describe.js +2 -4
- package/dist/commands/doctor.js +3 -10
- package/dist/commands/export.js +4 -7
- package/dist/commands/generate.js +2 -4
- package/dist/commands/index.js +2 -5
- package/dist/commands/info.js +7 -15
- package/dist/commands/list.js +3 -5
- package/dist/commands/mcp.js +10 -3
- package/dist/commands/pack.js +4 -5
- package/dist/commands/playbook.js +14 -36
- package/dist/commands/profile.js +4 -11
- package/dist/commands/remove.js +13 -9
- package/dist/commands/run.d.ts +8 -0
- package/dist/commands/run.js +118 -33
- package/dist/commands/runs.js +10 -25
- package/dist/commands/serve.js +9 -1
- package/dist/commands/state-list.js +2 -5
- package/dist/commands/state.js +29 -16
- package/dist/commands/test.js +6 -2
- package/dist/commands/validate.js +3 -5
- package/dist/errors.d.ts +14 -1
- package/dist/errors.js +20 -2
- package/dist/index.js +1 -2
- package/dist/renderer.d.ts +0 -2
- package/dist/renderer.js +3 -15
- package/dist/services/config-merge.d.ts +41 -0
- package/dist/services/config-merge.js +93 -0
- package/dist/services/index.d.ts +1 -0
- package/dist/services/index.js +1 -0
- package/dist/types.d.ts +0 -2
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +1 -0
- package/dist/utils/input.d.ts +46 -0
- package/dist/utils/input.js +89 -0
- package/dist/utils/spawn.d.ts +13 -0
- package/dist/utils/spawn.js +31 -0
- package/dist/utils/target.d.ts +21 -0
- package/dist/utils/target.js +29 -14
- package/dist/utils/utils.d.ts +2 -3
- package/dist/utils/utils.js +6 -6
- package/package.json +6 -6
package/dist/index.js
CHANGED
|
@@ -18,7 +18,7 @@ export * from "./commands/index.js";
|
|
|
18
18
|
*/
|
|
19
19
|
export async function main(argv = process.argv) {
|
|
20
20
|
const program = createCliProgram();
|
|
21
|
-
const isMachine = argv.includes("--json")
|
|
21
|
+
const isMachine = argv.includes("--json");
|
|
22
22
|
try {
|
|
23
23
|
await program.parseAsync(argv);
|
|
24
24
|
return typeof process.exitCode === "number" ? process.exitCode : 0;
|
|
@@ -32,7 +32,6 @@ export async function main(argv = process.argv) {
|
|
|
32
32
|
// 复用 renderer 的 renderError 统一错误信封,消除手工拼装的双实现
|
|
33
33
|
renderError(err, {
|
|
34
34
|
json: isMachine,
|
|
35
|
-
envelope: isMachine,
|
|
36
35
|
});
|
|
37
36
|
process.exitCode = formatError(err).exitCode;
|
|
38
37
|
return process.exitCode;
|
package/dist/renderer.d.ts
CHANGED
|
@@ -37,7 +37,6 @@ export declare function writeStderr(message: string, context?: CliContext): void
|
|
|
37
37
|
*/
|
|
38
38
|
export declare function renderResult<T>(data: T, options: {
|
|
39
39
|
json?: boolean;
|
|
40
|
-
envelope?: boolean;
|
|
41
40
|
humanFormatter?: () => string;
|
|
42
41
|
context?: CliContext;
|
|
43
42
|
}): void;
|
|
@@ -46,7 +45,6 @@ export declare function renderResult<T>(data: T, options: {
|
|
|
46
45
|
*/
|
|
47
46
|
export declare function renderError(err: unknown, options: {
|
|
48
47
|
json?: boolean;
|
|
49
|
-
envelope?: boolean;
|
|
50
48
|
context?: CliContext;
|
|
51
49
|
}): void;
|
|
52
50
|
/**
|
package/dist/renderer.js
CHANGED
|
@@ -72,21 +72,9 @@ export function writeStderr(message, context) {
|
|
|
72
72
|
* 统一根据输出模式进行渲染输出。
|
|
73
73
|
*/
|
|
74
74
|
export function renderResult(data, options) {
|
|
75
|
-
const isJson = Boolean(options.json
|
|
76
|
-
const useEnvelope = Boolean(options.envelope || (isJson && options.context?.defaultEnvelope));
|
|
75
|
+
const isJson = Boolean(options.json);
|
|
77
76
|
if (isJson) {
|
|
78
|
-
|
|
79
|
-
// 避免重复包装已有信封
|
|
80
|
-
if (typeof data === "object" && data !== null && "ok" in data) {
|
|
81
|
-
writeStdout(formatJson(data), options.context);
|
|
82
|
-
}
|
|
83
|
-
else {
|
|
84
|
-
writeStdout(formatJson(createSuccessEnvelope(data)), options.context);
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
else {
|
|
88
|
-
writeStdout(formatJson(data), options.context);
|
|
89
|
-
}
|
|
77
|
+
writeStdout(formatJson(data), options.context);
|
|
90
78
|
}
|
|
91
79
|
else {
|
|
92
80
|
if (options.humanFormatter) {
|
|
@@ -102,7 +90,7 @@ export function renderResult(data, options) {
|
|
|
102
90
|
*/
|
|
103
91
|
export function renderError(err, options) {
|
|
104
92
|
const formatted = formatError(err);
|
|
105
|
-
const isMachine = Boolean(options.json
|
|
93
|
+
const isMachine = Boolean(options.json);
|
|
106
94
|
if (isMachine) {
|
|
107
95
|
const errorEnv = createErrorEnvelope(formatted.code, formatted.message, formatted.details);
|
|
108
96
|
writeStdout(formatJson(errorEnv), options.context);
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { type ActionDockTarget, type ConfigItemDefinition } from "@actiondock/core";
|
|
2
|
+
/**
|
|
3
|
+
* 配置合并视图条目:跨作用域合并后的键值展示结构。
|
|
4
|
+
* source 标签语义:project(包级持久化)> global(全局持久化)> env(环境变量)> default(声明默认值)。
|
|
5
|
+
*/
|
|
6
|
+
export interface MergedConfigEntry {
|
|
7
|
+
key: string;
|
|
8
|
+
value: unknown;
|
|
9
|
+
source: "project" | "global" | "env" | "default";
|
|
10
|
+
secret: boolean;
|
|
11
|
+
description?: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* 解析单个配置键的合并视图(项目包级 > 全局持久化 > 环境变量 > 声明默认值)。
|
|
15
|
+
*
|
|
16
|
+
* 与 config list 的整表合并保持同一优先级链:包级持久化命中即短路,
|
|
17
|
+
* 全局持久化次之,环境变量再次,最后回退声明默认值。
|
|
18
|
+
*
|
|
19
|
+
* @param key 配置键名
|
|
20
|
+
* @param packageId 包标识(用于包级作用域寻址与环境变量解析)
|
|
21
|
+
* @param declaredItem 工程声明的配置项定义
|
|
22
|
+
* @param target 已创建的 Target 门面实例
|
|
23
|
+
* @param reveal 是否揭示敏感值明文(为假且敏感键时打码)
|
|
24
|
+
*/
|
|
25
|
+
export declare function resolveMergedConfigEntry(key: string, packageId: string, declaredItem: ConfigItemDefinition | undefined, target: ActionDockTarget, reveal: boolean): Promise<{
|
|
26
|
+
value: unknown;
|
|
27
|
+
source: MergedConfigEntry["source"];
|
|
28
|
+
secret: boolean;
|
|
29
|
+
}>;
|
|
30
|
+
/**
|
|
31
|
+
* 构造工程范围的完整配置合并视图(config list 单一事实源)。
|
|
32
|
+
*
|
|
33
|
+
* 键全集 = 工程声明键 ∪ 包级持久化键 ∪ 全局持久化键,
|
|
34
|
+
* 每个键按 resolveMergedConfigEntry 相同的优先级链取值,
|
|
35
|
+
* 消除 config get 与 config list 两命令间的合并实现差异。
|
|
36
|
+
*
|
|
37
|
+
* @param root 工程根目录
|
|
38
|
+
* @param target 已创建的 Target 门面实例
|
|
39
|
+
* @param reveal 是否揭示敏感值明文
|
|
40
|
+
*/
|
|
41
|
+
export declare function buildMergedConfigEntries(root: string, target: ActionDockTarget, reveal: boolean): Promise<MergedConfigEntry[]>;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { isSecretConfigKey, loadProjectConfig, maskSecretValue, resolveEnvValue, } from "@actiondock/core";
|
|
2
|
+
/**
|
|
3
|
+
* 解析单个配置键的合并视图(项目包级 > 全局持久化 > 环境变量 > 声明默认值)。
|
|
4
|
+
*
|
|
5
|
+
* 与 config list 的整表合并保持同一优先级链:包级持久化命中即短路,
|
|
6
|
+
* 全局持久化次之,环境变量再次,最后回退声明默认值。
|
|
7
|
+
*
|
|
8
|
+
* @param key 配置键名
|
|
9
|
+
* @param packageId 包标识(用于包级作用域寻址与环境变量解析)
|
|
10
|
+
* @param declaredItem 工程声明的配置项定义
|
|
11
|
+
* @param target 已创建的 Target 门面实例
|
|
12
|
+
* @param reveal 是否揭示敏感值明文(为假且敏感键时打码)
|
|
13
|
+
*/
|
|
14
|
+
export async function resolveMergedConfigEntry(key, packageId, declaredItem, target, reveal) {
|
|
15
|
+
const confView = await target.getConfig(packageId, key);
|
|
16
|
+
const envResolved = resolveEnvValue(key, declaredItem, packageId);
|
|
17
|
+
let rawValue;
|
|
18
|
+
let source;
|
|
19
|
+
if (confView && confView.configured) {
|
|
20
|
+
rawValue = confView.value;
|
|
21
|
+
source = confView.source === "package" ? "project" : confView.source;
|
|
22
|
+
}
|
|
23
|
+
else if (envResolved !== undefined) {
|
|
24
|
+
rawValue = envResolved.value;
|
|
25
|
+
source = "env";
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
rawValue = declaredItem?.default;
|
|
29
|
+
source = "default";
|
|
30
|
+
}
|
|
31
|
+
const isSecret = confView?.secret ?? isSecretConfigKey(key, declaredItem);
|
|
32
|
+
const displayValue = !reveal && isSecret && rawValue !== undefined ? maskSecretValue(rawValue) : rawValue;
|
|
33
|
+
return { value: displayValue, source, secret: isSecret };
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* 构造工程范围的完整配置合并视图(config list 单一事实源)。
|
|
37
|
+
*
|
|
38
|
+
* 键全集 = 工程声明键 ∪ 包级持久化键 ∪ 全局持久化键,
|
|
39
|
+
* 每个键按 resolveMergedConfigEntry 相同的优先级链取值,
|
|
40
|
+
* 消除 config get 与 config list 两命令间的合并实现差异。
|
|
41
|
+
*
|
|
42
|
+
* @param root 工程根目录
|
|
43
|
+
* @param target 已创建的 Target 门面实例
|
|
44
|
+
* @param reveal 是否揭示敏感值明文
|
|
45
|
+
*/
|
|
46
|
+
export async function buildMergedConfigEntries(root, target, reveal) {
|
|
47
|
+
const projConfig = loadProjectConfig(root);
|
|
48
|
+
const declared = projConfig.config || {};
|
|
49
|
+
const projectConfigList = await target.listConfig(projConfig.id);
|
|
50
|
+
const globalConfigList = await target.listConfig("global");
|
|
51
|
+
const projectConfigMap = new Map(projectConfigList.map((c) => [c.key, c]));
|
|
52
|
+
const globalConfigMap = new Map(globalConfigList.map((c) => [c.key, c]));
|
|
53
|
+
const allKeys = new Set([
|
|
54
|
+
...Object.keys(declared),
|
|
55
|
+
...projectConfigList.map((c) => c.key),
|
|
56
|
+
...globalConfigList.map((c) => c.key),
|
|
57
|
+
]);
|
|
58
|
+
const entries = [];
|
|
59
|
+
for (const key of allKeys) {
|
|
60
|
+
const declaredItem = declared[key];
|
|
61
|
+
const envResolved = resolveEnvValue(key, declaredItem, projConfig.id);
|
|
62
|
+
const projItem = projectConfigMap.get(key);
|
|
63
|
+
const globItem = globalConfigMap.get(key);
|
|
64
|
+
let rawValue;
|
|
65
|
+
let source;
|
|
66
|
+
if (projItem && projItem.configured && projItem.source === "package") {
|
|
67
|
+
rawValue = projItem.value;
|
|
68
|
+
source = "project";
|
|
69
|
+
}
|
|
70
|
+
else if (globItem && globItem.configured) {
|
|
71
|
+
rawValue = globItem.value;
|
|
72
|
+
source = "global";
|
|
73
|
+
}
|
|
74
|
+
else if (envResolved !== undefined) {
|
|
75
|
+
rawValue = envResolved.value;
|
|
76
|
+
source = "env";
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
rawValue = declaredItem?.default;
|
|
80
|
+
source = "default";
|
|
81
|
+
}
|
|
82
|
+
const isSecret = isSecretConfigKey(key, declaredItem);
|
|
83
|
+
const displayValue = !reveal && isSecret && rawValue !== undefined ? maskSecretValue(rawValue) : rawValue;
|
|
84
|
+
entries.push({
|
|
85
|
+
key,
|
|
86
|
+
value: displayValue,
|
|
87
|
+
source,
|
|
88
|
+
secret: isSecret,
|
|
89
|
+
...(declaredItem?.description !== undefined ? { description: declaredItem.description } : {}),
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
return entries;
|
|
93
|
+
}
|
package/dist/services/index.d.ts
CHANGED
package/dist/services/index.js
CHANGED
package/dist/types.d.ts
CHANGED
package/dist/utils/index.d.ts
CHANGED
package/dist/utils/index.js
CHANGED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { JsonValue } from "@actiondock/sdk";
|
|
2
|
+
/**
|
|
3
|
+
* 获取异常的标准描述信息。
|
|
4
|
+
*
|
|
5
|
+
* @param err 待提取消息的异常对象
|
|
6
|
+
*/
|
|
7
|
+
export declare function getErrorMessage(err: unknown): string;
|
|
8
|
+
/**
|
|
9
|
+
* 剔除 UTF-8 文本起始处的 BOM 字节标记(\uFEFF)。
|
|
10
|
+
*
|
|
11
|
+
* @param text 原始输入文本
|
|
12
|
+
*/
|
|
13
|
+
export declare function stripBom(text: string): string;
|
|
14
|
+
/**
|
|
15
|
+
* 统一解析 JSON 字符串为标准 JSON 值对象,并在失败时抛出结构化参数异常。
|
|
16
|
+
*
|
|
17
|
+
* @param text 待解析文本内容
|
|
18
|
+
* @param source 输入源描述(如 "--input"、"stdin" 或文件物理路径)
|
|
19
|
+
*/
|
|
20
|
+
export declare function parseJson(text: string, source: string): JsonValue;
|
|
21
|
+
/**
|
|
22
|
+
* 从标准输入流中完整读取全部数据并转换为 UTF-8 字符串。
|
|
23
|
+
*
|
|
24
|
+
* @param stream 标准输入可读流,默认为 process.stdin
|
|
25
|
+
*/
|
|
26
|
+
export declare function readStdin(stream?: NodeJS.ReadableStream): Promise<string>;
|
|
27
|
+
/**
|
|
28
|
+
* Action 执行输入解析选项。
|
|
29
|
+
*/
|
|
30
|
+
export interface ResolveActionInputOptions {
|
|
31
|
+
input?: string;
|
|
32
|
+
inputFile?: string;
|
|
33
|
+
stdin?: NodeJS.ReadableStream;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* 统一解析 Action 执行入参。
|
|
37
|
+
*
|
|
38
|
+
* 遵循确定性入参契约:
|
|
39
|
+
* - 当未指定任何输入参数时,默认返回空对象 `{}`。
|
|
40
|
+
* - 简单输入通过 `--input` 传递内联 JSON 字符串。
|
|
41
|
+
* - 复杂输入或标准输入通过 `--input-file` 传递(`-` 表示标准输入 stdin)。
|
|
42
|
+
* - 严禁同时指定 `--input` 与 `--input-file`,检测到冲突时明确抛出参数错误。
|
|
43
|
+
*
|
|
44
|
+
* @param options 输入解析选项
|
|
45
|
+
*/
|
|
46
|
+
export declare function resolveActionInput(options: ResolveActionInputOptions): Promise<JsonValue>;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { ArgumentError } from "../errors.js";
|
|
3
|
+
/**
|
|
4
|
+
* 获取异常的标准描述信息。
|
|
5
|
+
*
|
|
6
|
+
* @param err 待提取消息的异常对象
|
|
7
|
+
*/
|
|
8
|
+
export function getErrorMessage(err) {
|
|
9
|
+
if (err instanceof Error) {
|
|
10
|
+
return err.message;
|
|
11
|
+
}
|
|
12
|
+
return String(err);
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* 剔除 UTF-8 文本起始处的 BOM 字节标记(\uFEFF)。
|
|
16
|
+
*
|
|
17
|
+
* @param text 原始输入文本
|
|
18
|
+
*/
|
|
19
|
+
export function stripBom(text) {
|
|
20
|
+
return text.replace(/^\uFEFF/, "");
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* 统一解析 JSON 字符串为标准 JSON 值对象,并在失败时抛出结构化参数异常。
|
|
24
|
+
*
|
|
25
|
+
* @param text 待解析文本内容
|
|
26
|
+
* @param source 输入源描述(如 "--input"、"stdin" 或文件物理路径)
|
|
27
|
+
*/
|
|
28
|
+
export function parseJson(text, source) {
|
|
29
|
+
try {
|
|
30
|
+
return JSON.parse(stripBom(text));
|
|
31
|
+
}
|
|
32
|
+
catch (err) {
|
|
33
|
+
throw new ArgumentError(`Invalid JSON input from ${source}: ${getErrorMessage(err)}`, undefined, "INVALID_JSON");
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* 从标准输入流中完整读取全部数据并转换为 UTF-8 字符串。
|
|
38
|
+
*
|
|
39
|
+
* @param stream 标准输入可读流,默认为 process.stdin
|
|
40
|
+
*/
|
|
41
|
+
export async function readStdin(stream = process.stdin) {
|
|
42
|
+
const chunks = [];
|
|
43
|
+
for await (const chunk of stream) {
|
|
44
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
45
|
+
}
|
|
46
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* 统一解析 Action 执行入参。
|
|
50
|
+
*
|
|
51
|
+
* 遵循确定性入参契约:
|
|
52
|
+
* - 当未指定任何输入参数时,默认返回空对象 `{}`。
|
|
53
|
+
* - 简单输入通过 `--input` 传递内联 JSON 字符串。
|
|
54
|
+
* - 复杂输入或标准输入通过 `--input-file` 传递(`-` 表示标准输入 stdin)。
|
|
55
|
+
* - 严禁同时指定 `--input` 与 `--input-file`,检测到冲突时明确抛出参数错误。
|
|
56
|
+
*
|
|
57
|
+
* @param options 输入解析选项
|
|
58
|
+
*/
|
|
59
|
+
export async function resolveActionInput(options) {
|
|
60
|
+
if (options.input !== undefined && options.inputFile !== undefined) {
|
|
61
|
+
throw new ArgumentError("--input and --input-file cannot be used together", undefined, "INPUT_CONFLICT");
|
|
62
|
+
}
|
|
63
|
+
if (options.input !== undefined) {
|
|
64
|
+
return parseJson(options.input, "--input");
|
|
65
|
+
}
|
|
66
|
+
if (options.inputFile !== undefined) {
|
|
67
|
+
let text;
|
|
68
|
+
if (options.inputFile === "-") {
|
|
69
|
+
try {
|
|
70
|
+
text = await readStdin(options.stdin || process.stdin);
|
|
71
|
+
}
|
|
72
|
+
catch (err) {
|
|
73
|
+
throw new ArgumentError(`Failed to read stdin: ${getErrorMessage(err)}`, undefined, "INPUT_FILE_READ_FAILED");
|
|
74
|
+
}
|
|
75
|
+
return parseJson(text, "stdin");
|
|
76
|
+
}
|
|
77
|
+
try {
|
|
78
|
+
text = await readFile(options.inputFile, "utf8");
|
|
79
|
+
}
|
|
80
|
+
catch (err) {
|
|
81
|
+
if (err && err.code === "ENOENT") {
|
|
82
|
+
throw new ArgumentError(`Input file not found: ${options.inputFile}`, undefined, "INPUT_FILE_NOT_FOUND");
|
|
83
|
+
}
|
|
84
|
+
throw new ArgumentError(`Failed to read input file '${options.inputFile}': ${getErrorMessage(err)}`, undefined, "INPUT_FILE_READ_FAILED");
|
|
85
|
+
}
|
|
86
|
+
return parseJson(text, options.inputFile);
|
|
87
|
+
}
|
|
88
|
+
return {};
|
|
89
|
+
}
|
package/dist/utils/spawn.d.ts
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 校验包管理器依赖说明符仅包含白名单字符。
|
|
3
|
+
* 非法字符直接抛出参数错误,不进入子进程拼接链路。
|
|
4
|
+
*
|
|
5
|
+
* @param spec 待校验的包说明符(如 `@scope/pkg@^1.2.3`)
|
|
6
|
+
*/
|
|
7
|
+
export declare function assertSafePackageSpec(spec: string): void;
|
|
1
8
|
/**
|
|
2
9
|
* 子进程执行结果视图。
|
|
3
10
|
*/
|
|
@@ -16,6 +23,12 @@ export interface SpawnAsyncResult {
|
|
|
16
23
|
*
|
|
17
24
|
* 与 spawnSync 语义对齐:stdio 默认 pipe,进程结束(而非继承输出)后返回聚合结果。
|
|
18
25
|
*
|
|
26
|
+
* win32 shell 取舍说明:npm 在 Windows 上是 `.cmd` 批处理脚本,Node 20.12+
|
|
27
|
+
* 针对 CVE-2024-27980 的缓解措施使 `shell:false` 直接 spawn `.cmd` 抛出 EINVAL,
|
|
28
|
+
* 因此 win32 保留 shell 拼接模式;命令注入面由调用方的
|
|
29
|
+
* assertSafePackageSpec 白名单前置校验关闭(包说明符仅允许
|
|
30
|
+
* 字母数字与 `@/:/-_/.+`,全部 shell 元字符被拒绝)。
|
|
31
|
+
*
|
|
19
32
|
* @param command 可执行文件名
|
|
20
33
|
* @param args 参数列表
|
|
21
34
|
* @param options 附加选项(cwd 与 shell 策略)
|
package/dist/utils/spawn.js
CHANGED
|
@@ -1,9 +1,40 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
|
+
import { ArgumentError } from "../errors.js";
|
|
3
|
+
/**
|
|
4
|
+
* npm/bun 包说明符白名单字符集。
|
|
5
|
+
*
|
|
6
|
+
* 覆盖 npm 依赖说明符语法所需的安全字符:
|
|
7
|
+
* - 字母数字与 `.`(名称与版本组成)
|
|
8
|
+
* - `@`(scope 前缀与版本定界符)、`/`(scope 分隔符)
|
|
9
|
+
* - `:`(git 协议与 tag 前缀)、`-` 与 `_`(名称连字符与版本预发布段)
|
|
10
|
+
* - `+`(semver build 元数据)
|
|
11
|
+
*
|
|
12
|
+
* 显式拒绝全部 shell 元字符(空格、引号、`$`、反引号、`&`、`|`、`<`、`>`、`(`、`)` 等),
|
|
13
|
+
* 从源头消除 win32 shell 拼接模式下的命令注入面。
|
|
14
|
+
*/
|
|
15
|
+
const PACKAGE_SPEC_PATTERN = /^[a-zA-Z0-9@/:\-_.+]+$/;
|
|
16
|
+
/**
|
|
17
|
+
* 校验包管理器依赖说明符仅包含白名单字符。
|
|
18
|
+
* 非法字符直接抛出参数错误,不进入子进程拼接链路。
|
|
19
|
+
*
|
|
20
|
+
* @param spec 待校验的包说明符(如 `@scope/pkg@^1.2.3`)
|
|
21
|
+
*/
|
|
22
|
+
export function assertSafePackageSpec(spec) {
|
|
23
|
+
if (!PACKAGE_SPEC_PATTERN.test(spec)) {
|
|
24
|
+
throw new ArgumentError(`Invalid package specifier '${spec}': only letters, digits, '@', '/', ':', '-', '_', '.', '+' are allowed`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
2
27
|
/**
|
|
3
28
|
* 异步执行子进程(Promise 化 spawn),保留 win32 shell 处理与退出码透传。
|
|
4
29
|
*
|
|
5
30
|
* 与 spawnSync 语义对齐:stdio 默认 pipe,进程结束(而非继承输出)后返回聚合结果。
|
|
6
31
|
*
|
|
32
|
+
* win32 shell 取舍说明:npm 在 Windows 上是 `.cmd` 批处理脚本,Node 20.12+
|
|
33
|
+
* 针对 CVE-2024-27980 的缓解措施使 `shell:false` 直接 spawn `.cmd` 抛出 EINVAL,
|
|
34
|
+
* 因此 win32 保留 shell 拼接模式;命令注入面由调用方的
|
|
35
|
+
* assertSafePackageSpec 白名单前置校验关闭(包说明符仅允许
|
|
36
|
+
* 字母数字与 `@/:/-_/.+`,全部 shell 元字符被拒绝)。
|
|
37
|
+
*
|
|
7
38
|
* @param command 可执行文件名
|
|
8
39
|
* @param args 参数列表
|
|
9
40
|
* @param options 附加选项(cwd 与 shell 策略)
|
package/dist/utils/target.d.ts
CHANGED
|
@@ -29,12 +29,27 @@ export interface TargetResolutionOptions {
|
|
|
29
29
|
/** 是否允许向非回环地址发送明文 HTTP 请求(对应 --allow-insecure-http) */
|
|
30
30
|
allowInsecureHttp?: boolean;
|
|
31
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* 目标拓扑解析单一入口:从命令选项视图解析远端/本地目标拓扑信息。
|
|
34
|
+
*
|
|
35
|
+
* 收拢全部解析字段(profile/server/token/insecure/allowInsecureHttp),
|
|
36
|
+
* 消除命令间逐字段内联复制导致的漂移(如漏传 allowInsecureHttp)。
|
|
37
|
+
*
|
|
38
|
+
* @param options 命令选项视图(支持直接传入 Commander 解析后的 options 对象)
|
|
39
|
+
* @param context CLI 上下文
|
|
40
|
+
* @returns 解析后的目标拓扑信息(仅信息解析,不创建 Target 实例)
|
|
41
|
+
*/
|
|
42
|
+
export declare function resolveTargetFromOptions(options: TargetResolutionOptions, context?: CliContext): ResolvedTarget;
|
|
32
43
|
/**
|
|
33
44
|
* 高阶目标执行辅助:统一封装「目标解析 → Target 创建 → 业务回调 → try/finally close」样板。
|
|
34
45
|
*
|
|
35
46
|
* 回调获得创建好的 target 与解析后的目标拓扑信息;无论回调成功或抛出,
|
|
36
47
|
* 都保证 target 资源被正确释放,业务异常原样透传。
|
|
37
48
|
*
|
|
49
|
+
* 默认旁观打开:内部创建的本地 Host 以非收割模式打开存储(不置 recoverOrphans),
|
|
50
|
+
* 供 ad state / ad runs / ad config 等查询命令与运行中的 serve 进程并发访问同一库;
|
|
51
|
+
* 需要持有者语义的执行命令通过 localOptions.ownDataDir 显式声明。
|
|
52
|
+
*
|
|
38
53
|
* @param options 命令选项视图(profile/server/token/package/dataDir/insecure/allowInsecureHttp)
|
|
39
54
|
* @param context CLI 上下文
|
|
40
55
|
* @param fn 业务回调(target 为已创建的门面实例,resolved 为拓扑解析结果)
|
|
@@ -45,6 +60,12 @@ export declare function withTarget(options: TargetResolutionOptions, context: Cl
|
|
|
45
60
|
localRoot?: string | (() => string | undefined);
|
|
46
61
|
/** 是否扫描全局链接包(对应 scanLinkedPackages) */
|
|
47
62
|
scanLinkedPackages?: boolean;
|
|
63
|
+
/**
|
|
64
|
+
* 是否以数据目录持有者身份打开本地 Host:true 时 App 存储打开阶段收割遗留孤儿运行。
|
|
65
|
+
* 执行类命令(ad run)应置 true;查询类命令(state/runs/config/list/describe)
|
|
66
|
+
* 保持缺省 false,避免误收割并发 serve 进程的在途运行记录。
|
|
67
|
+
*/
|
|
68
|
+
ownDataDir?: boolean;
|
|
48
69
|
}): Promise<void>;
|
|
49
70
|
/**
|
|
50
71
|
* 仅远端模式目标执行辅助:创建远端 Target 并保证资源释放。
|
package/dist/utils/target.js
CHANGED
|
@@ -15,31 +15,51 @@ export function applyTargetOptions(command) {
|
|
|
15
15
|
.option("-k, --insecure", "Allow insecure server connections (skip TLS certificate validation)")
|
|
16
16
|
.option("--allow-insecure-http", "Allow sending auth token over unencrypted HTTP to non-loopback hosts");
|
|
17
17
|
}
|
|
18
|
+
/**
|
|
19
|
+
* 目标拓扑解析单一入口:从命令选项视图解析远端/本地目标拓扑信息。
|
|
20
|
+
*
|
|
21
|
+
* 收拢全部解析字段(profile/server/token/insecure/allowInsecureHttp),
|
|
22
|
+
* 消除命令间逐字段内联复制导致的漂移(如漏传 allowInsecureHttp)。
|
|
23
|
+
*
|
|
24
|
+
* @param options 命令选项视图(支持直接传入 Commander 解析后的 options 对象)
|
|
25
|
+
* @param context CLI 上下文
|
|
26
|
+
* @returns 解析后的目标拓扑信息(仅信息解析,不创建 Target 实例)
|
|
27
|
+
*/
|
|
28
|
+
export function resolveTargetFromOptions(options, context) {
|
|
29
|
+
return resolveTarget({
|
|
30
|
+
profile: options.profile,
|
|
31
|
+
server: options.server,
|
|
32
|
+
token: options.token,
|
|
33
|
+
insecure: options.insecure,
|
|
34
|
+
allowInsecureHttp: options.allowInsecureHttp,
|
|
35
|
+
}, context?.customHome);
|
|
36
|
+
}
|
|
18
37
|
/**
|
|
19
38
|
* 高阶目标执行辅助:统一封装「目标解析 → Target 创建 → 业务回调 → try/finally close」样板。
|
|
20
39
|
*
|
|
21
40
|
* 回调获得创建好的 target 与解析后的目标拓扑信息;无论回调成功或抛出,
|
|
22
41
|
* 都保证 target 资源被正确释放,业务异常原样透传。
|
|
23
42
|
*
|
|
43
|
+
* 默认旁观打开:内部创建的本地 Host 以非收割模式打开存储(不置 recoverOrphans),
|
|
44
|
+
* 供 ad state / ad runs / ad config 等查询命令与运行中的 serve 进程并发访问同一库;
|
|
45
|
+
* 需要持有者语义的执行命令通过 localOptions.ownDataDir 显式声明。
|
|
46
|
+
*
|
|
24
47
|
* @param options 命令选项视图(profile/server/token/package/dataDir/insecure/allowInsecureHttp)
|
|
25
48
|
* @param context CLI 上下文
|
|
26
49
|
* @param fn 业务回调(target 为已创建的门面实例,resolved 为拓扑解析结果)
|
|
27
50
|
* @param localOptions 本地分支附加选项(如 scanLinkedPackages)与 localRoot 回退策略
|
|
28
51
|
*/
|
|
29
52
|
export async function withTarget(options, context, fn, localOptions) {
|
|
30
|
-
const resolved =
|
|
31
|
-
profile: options.profile,
|
|
32
|
-
server: options.server,
|
|
33
|
-
token: options.token,
|
|
34
|
-
insecure: options.insecure,
|
|
35
|
-
allowInsecureHttp: options.allowInsecureHttp,
|
|
36
|
-
}, context?.customHome);
|
|
53
|
+
const resolved = resolveTargetFromOptions(options, context);
|
|
37
54
|
// localRoot 仅在 local 分支求值,避免远端模式下触发包寻址副作用
|
|
38
55
|
const localRoot = resolved.type === "local"
|
|
39
56
|
? (typeof localOptions?.localRoot === "function" ? localOptions.localRoot() : localOptions?.localRoot) ??
|
|
40
57
|
findProjectRoot() ??
|
|
41
58
|
undefined
|
|
42
59
|
: undefined;
|
|
60
|
+
// 本地执行命令声明持有者身份时,向 App 层透传收割开关;
|
|
61
|
+
// 查询命令保持缺省不传,存储以旁观模式打开
|
|
62
|
+
const appRecoverOrphans = localOptions?.ownDataDir === true ? { recoverOrphans: true } : undefined;
|
|
43
63
|
const target = await createActionDockTarget(resolved.type === "remote"
|
|
44
64
|
? {
|
|
45
65
|
type: "remote",
|
|
@@ -61,6 +81,7 @@ export async function withTarget(options, context, fn, localOptions) {
|
|
|
61
81
|
...(localOptions?.scanLinkedPackages !== undefined
|
|
62
82
|
? { scanLinkedPackages: localOptions.scanLinkedPackages }
|
|
63
83
|
: undefined),
|
|
84
|
+
...(appRecoverOrphans ? { recoverOrphans: true } : undefined),
|
|
64
85
|
});
|
|
65
86
|
try {
|
|
66
87
|
await fn(target, resolved);
|
|
@@ -74,13 +95,7 @@ export async function withTarget(options, context, fn, localOptions) {
|
|
|
74
95
|
* 适用于 runs cancel 等明确要求远端目标的命令。
|
|
75
96
|
*/
|
|
76
97
|
export async function withRemoteTarget(options, context, fn) {
|
|
77
|
-
const resolved =
|
|
78
|
-
profile: options.profile,
|
|
79
|
-
server: options.server,
|
|
80
|
-
token: options.token,
|
|
81
|
-
insecure: options.insecure,
|
|
82
|
-
allowInsecureHttp: options.allowInsecureHttp,
|
|
83
|
-
}, context?.customHome);
|
|
98
|
+
const resolved = resolveTargetFromOptions(options, context);
|
|
84
99
|
const target = await createActionDockTarget({
|
|
85
100
|
type: "remote",
|
|
86
101
|
serverUrl: resolved.serverUrl,
|
package/dist/utils/utils.d.ts
CHANGED
|
@@ -20,18 +20,17 @@ export declare function getTargetRoot(packageOption?: string, keyHint?: string):
|
|
|
20
20
|
key: string;
|
|
21
21
|
};
|
|
22
22
|
/**
|
|
23
|
-
* 聚合命令自身选项与根程序全局选项(如 --json、--
|
|
23
|
+
* 聚合命令自身选项与根程序全局选项(如 --json、--data-dir 等)。
|
|
24
24
|
*/
|
|
25
25
|
export declare function getEffectiveOptions(rawOptions: any, cmd?: any): any;
|
|
26
26
|
/**
|
|
27
27
|
* 解析意图过滤回退策略。
|
|
28
|
-
* 机器输出模式(--json
|
|
28
|
+
* 机器输出模式(--json)仅在显式传 --fallback 时回退,
|
|
29
29
|
* 人类交互模式默认回退(可被 --no-fallback 关闭)。
|
|
30
30
|
*/
|
|
31
31
|
export declare function resolveFallbackStrategy(options: {
|
|
32
32
|
fallback?: boolean;
|
|
33
33
|
json?: boolean;
|
|
34
|
-
envelope?: boolean;
|
|
35
34
|
}): {
|
|
36
35
|
isMachine: boolean;
|
|
37
36
|
shouldFallback: boolean;
|
package/dist/utils/utils.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { resolvePackageRoot, findProjectRoot } from "@actiondock/core";
|
|
2
|
-
import { ArgumentError } from "../errors.js";
|
|
2
|
+
import { ArgumentError, notInProjectError, packageNotFoundError } from "../errors.js";
|
|
3
3
|
/**
|
|
4
4
|
* 解析意图字符串(从显式 --intent 或位置模式参数聚合)。
|
|
5
5
|
* 多个关键字使用 '|'(正则逻辑或)连接。
|
|
@@ -78,16 +78,16 @@ export function getTargetRoot(packageOption, keyHint) {
|
|
|
78
78
|
const root = resolvePackageRoot(targetPackage) || (targetPackage ? null : findProjectRoot());
|
|
79
79
|
if (!root) {
|
|
80
80
|
if (targetPackage) {
|
|
81
|
-
throw
|
|
81
|
+
throw packageNotFoundError(targetPackage);
|
|
82
82
|
}
|
|
83
83
|
else {
|
|
84
|
-
throw
|
|
84
|
+
throw notInProjectError("Please specify -P, --package <id> or cd into a project directory.");
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
87
|
return { root, key: effectiveKey };
|
|
88
88
|
}
|
|
89
89
|
/**
|
|
90
|
-
* 聚合命令自身选项与根程序全局选项(如 --json、--
|
|
90
|
+
* 聚合命令自身选项与根程序全局选项(如 --json、--data-dir 等)。
|
|
91
91
|
*/
|
|
92
92
|
export function getEffectiveOptions(rawOptions, cmd) {
|
|
93
93
|
if (cmd && typeof cmd.optsWithGlobals === "function") {
|
|
@@ -97,11 +97,11 @@ export function getEffectiveOptions(rawOptions, cmd) {
|
|
|
97
97
|
}
|
|
98
98
|
/**
|
|
99
99
|
* 解析意图过滤回退策略。
|
|
100
|
-
* 机器输出模式(--json
|
|
100
|
+
* 机器输出模式(--json)仅在显式传 --fallback 时回退,
|
|
101
101
|
* 人类交互模式默认回退(可被 --no-fallback 关闭)。
|
|
102
102
|
*/
|
|
103
103
|
export function resolveFallbackStrategy(options) {
|
|
104
|
-
const isMachine = Boolean(options.json
|
|
104
|
+
const isMachine = Boolean(options.json);
|
|
105
105
|
const fallbackExplicit = options.fallback === true ||
|
|
106
106
|
(Array.isArray(process.argv) && process.argv.includes("--fallback"));
|
|
107
107
|
return {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@actiondock/cli",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.6.0",
|
|
4
4
|
"description": "ActionDock CLI facade for developing, testing, building, and exporting standalone Agent Actions and Skills",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -34,11 +34,11 @@
|
|
|
34
34
|
"test": "node ../../scripts/run-tests.ts"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@actiondock/builder": "^2.
|
|
38
|
-
"@actiondock/core": "^2.
|
|
39
|
-
"@actiondock/mcp": "^2.
|
|
40
|
-
"@actiondock/runtime-node": "^2.
|
|
41
|
-
"@actiondock/sdk": "^2.
|
|
37
|
+
"@actiondock/builder": "^2.6.0",
|
|
38
|
+
"@actiondock/core": "^2.6.0",
|
|
39
|
+
"@actiondock/mcp": "^2.6.0",
|
|
40
|
+
"@actiondock/runtime-node": "^2.6.0",
|
|
41
|
+
"@actiondock/sdk": "^2.6.0",
|
|
42
42
|
"commander": "^13.1.0",
|
|
43
43
|
"selfsigned": "^5.5.0"
|
|
44
44
|
},
|