@actiondock/cli 2.5.1 → 2.7.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.
Files changed (55) hide show
  1. package/README.md +60 -5
  2. package/bin/ad.js +4 -4
  3. package/dist/commands/action/create.js +7 -22
  4. package/dist/commands/add.js +5 -5
  5. package/dist/commands/build.js +4 -5
  6. package/dist/commands/config/delete.js +2 -2
  7. package/dist/commands/config/env.js +6 -15
  8. package/dist/commands/config/get.js +8 -39
  9. package/dist/commands/config/list.js +8 -55
  10. package/dist/commands/config/schema.js +4 -6
  11. package/dist/commands/config/set.js +2 -2
  12. package/dist/commands/describe.js +8 -22
  13. package/dist/commands/doctor.js +3 -10
  14. package/dist/commands/export.js +4 -7
  15. package/dist/commands/generate.js +2 -4
  16. package/dist/commands/index.js +2 -5
  17. package/dist/commands/info.js +7 -15
  18. package/dist/commands/list.js +3 -5
  19. package/dist/commands/mcp.js +10 -3
  20. package/dist/commands/pack.js +4 -5
  21. package/dist/commands/playbook.js +14 -36
  22. package/dist/commands/profile.js +4 -11
  23. package/dist/commands/remove.js +13 -9
  24. package/dist/commands/run.d.ts +10 -1
  25. package/dist/commands/run.js +161 -52
  26. package/dist/commands/runs.js +10 -25
  27. package/dist/commands/serve.js +9 -1
  28. package/dist/commands/state-list.js +2 -5
  29. package/dist/commands/state.js +29 -16
  30. package/dist/commands/test.js +6 -2
  31. package/dist/commands/validate.js +3 -5
  32. package/dist/errors.d.ts +14 -1
  33. package/dist/errors.js +53 -3
  34. package/dist/index.d.ts +8 -1
  35. package/dist/index.js +61 -10
  36. package/dist/renderer.d.ts +3 -4
  37. package/dist/renderer.js +7 -34
  38. package/dist/services/config-merge.d.ts +41 -0
  39. package/dist/services/config-merge.js +93 -0
  40. package/dist/services/index.d.ts +1 -0
  41. package/dist/services/index.js +1 -0
  42. package/dist/standalone/index.d.ts +8 -2
  43. package/dist/standalone/index.js +32 -2
  44. package/dist/types.d.ts +6 -3
  45. package/dist/utils/index.d.ts +1 -0
  46. package/dist/utils/index.js +1 -0
  47. package/dist/utils/input.d.ts +1 -0
  48. package/dist/utils/input.js +1 -0
  49. package/dist/utils/spawn.d.ts +13 -0
  50. package/dist/utils/spawn.js +31 -0
  51. package/dist/utils/target.d.ts +21 -0
  52. package/dist/utils/target.js +29 -14
  53. package/dist/utils/utils.d.ts +2 -3
  54. package/dist/utils/utils.js +6 -6
  55. package/package.json +6 -6
@@ -1,5 +1,5 @@
1
1
  import { checkGeneratedTypes, findProjectRoot, GENERATED_TYPES_OUTDATED_CODE, loadActions, loadProjectConfig, resolvePackageRoot, } from "@actiondock/core";
2
- import { ArgumentError, ExecutionError } from "../errors.js";
2
+ import { ArgumentError, ExecutionError, notInProjectError, packageNotFoundError } from "../errors.js";
3
3
  import { renderActionValidation, renderResult } from "../renderer.js";
4
4
  import { getEffectiveOptions } from "../utils/index.js";
5
5
  /**
@@ -14,7 +14,6 @@ export function attachValidateCommand(parent, context) {
14
14
  .description("Validate action schemas and definitions")
15
15
  .option("-P, --package <id>", "Target package ID or path")
16
16
  .option("--json", "Output as JSON")
17
- .option("--envelope", "Wrap JSON output in standard envelope")
18
17
  .option("--data-dir <path>", "Custom database storage directory")
19
18
  .action(async (id, rawOptions, cmd) => {
20
19
  const options = getEffectiveOptions(rawOptions, cmd);
@@ -22,14 +21,14 @@ export function attachValidateCommand(parent, context) {
22
21
  if (options.package) {
23
22
  root = resolvePackageRoot(options.package);
24
23
  if (!root) {
25
- throw new ArgumentError(`Package '${options.package}' not found in linked packages or path`);
24
+ throw packageNotFoundError(options.package);
26
25
  }
27
26
  }
28
27
  else {
29
28
  root = findProjectRoot();
30
29
  }
31
30
  if (!root) {
32
- throw new ArgumentError("Not in an ActionDock project (actiondock.json not found).\nPlease specify -P, --package <id> or cd into a project directory.");
31
+ throw notInProjectError("Please specify -P, --package <id> or cd into a project directory.");
33
32
  }
34
33
  const config = loadProjectConfig(root);
35
34
  const actions = await loadActions(root, config.actionsDir);
@@ -73,7 +72,6 @@ export function attachValidateCommand(parent, context) {
73
72
  const validationPayload = { valid: allValid, results };
74
73
  renderResult(validationPayload, {
75
74
  json: options.json,
76
- envelope: options.envelope,
77
75
  humanFormatter: () => renderActionValidation(results),
78
76
  context,
79
77
  });
package/dist/errors.d.ts CHANGED
@@ -12,7 +12,7 @@ export declare class CliError extends Error {
12
12
  * 命令行参数与选项校验错误(退出码为 2)。
13
13
  */
14
14
  export declare class ArgumentError extends CliError {
15
- constructor(message: string, details?: unknown);
15
+ constructor(message: string, details?: unknown, code?: string);
16
16
  }
17
17
  /**
18
18
  * 业务逻辑或框架执行失败错误(退出码为 1)。
@@ -35,6 +35,19 @@ export interface FormattedError {
35
35
  exitCode: ExitCodeValue;
36
36
  details?: unknown;
37
37
  }
38
+ /**
39
+ * 目标包寻址失败错误(-P 指定的包不在链接注册表也无法按路径寻址)。
40
+ */
41
+ export declare function packageNotFoundError(pkg: string): ArgumentError;
42
+ /**
43
+ * 当前目录不在 ActionDock 工程内错误。
44
+ * `hint` 为命令专属的补救措施提示行(如 -P 参数用法)。
45
+ */
46
+ export declare function notInProjectError(hint?: string): ArgumentError;
47
+ /**
48
+ * 当前目录无工程且全局注册表无任何链接包提示文案(非错误场景使用)。
49
+ */
50
+ export declare const NO_PROJECT_NO_LINKED_MESSAGE = "No ActionDock project in current directory, and no packages linked.";
38
51
  /**
39
52
  * 解析并格式化任意异常为结构化错误对象。
40
53
  *
package/dist/errors.js CHANGED
@@ -1,4 +1,20 @@
1
+ import { InputError, INVALID_FLAT_ARGUMENT, INVALID_JSON, INVALID_JSON_LITERAL, INPUT_PATH_CONFLICT, FLAT_INPUT_LIMIT_EXCEEDED, INPUT_LIMIT_EXCEEDED, INPUT_POLICY_VIOLATION, INPUT_CONFLICT, INPUT_FILE_NOT_FOUND, INPUT_FILE_READ_FAILED, } from "@actiondock/core";
1
2
  import { ExitCode } from "./types.js";
3
+ /**
4
+ * 扁平与解析输入错误码集合。
5
+ */
6
+ const FLAT_ERROR_CODES = new Set([
7
+ INVALID_FLAT_ARGUMENT,
8
+ INVALID_JSON,
9
+ INVALID_JSON_LITERAL,
10
+ INPUT_PATH_CONFLICT,
11
+ FLAT_INPUT_LIMIT_EXCEEDED,
12
+ INPUT_LIMIT_EXCEEDED,
13
+ INPUT_POLICY_VIOLATION,
14
+ INPUT_CONFLICT,
15
+ INPUT_FILE_NOT_FOUND,
16
+ INPUT_FILE_READ_FAILED,
17
+ ]);
2
18
  /**
3
19
  * CLI 信封错误码常量(仅 CLI 输出协议使用,属包内部码,就地常量化管理)。
4
20
  */
@@ -25,8 +41,8 @@ export class CliError extends Error {
25
41
  * 命令行参数与选项校验错误(退出码为 2)。
26
42
  */
27
43
  export class ArgumentError extends CliError {
28
- constructor(message, details) {
29
- super(message, ExitCode.INVALID_ARGUMENT, "INVALID_ARGUMENT", details);
44
+ constructor(message, details, code = "INVALID_ARGUMENT") {
45
+ super(message, ExitCode.INVALID_ARGUMENT, code, details);
30
46
  this.name = "ArgumentError";
31
47
  }
32
48
  }
@@ -48,6 +64,24 @@ export class SigintError extends CliError {
48
64
  this.name = "SigintError";
49
65
  }
50
66
  }
67
+ /**
68
+ * 目标包寻址失败错误(-P 指定的包不在链接注册表也无法按路径寻址)。
69
+ */
70
+ export function packageNotFoundError(pkg) {
71
+ return new ArgumentError(`Package '${pkg}' not found in linked packages or path`);
72
+ }
73
+ /**
74
+ * 当前目录不在 ActionDock 工程内错误。
75
+ * `hint` 为命令专属的补救措施提示行(如 -P 参数用法)。
76
+ */
77
+ export function notInProjectError(hint) {
78
+ const base = "Not in an ActionDock project (actiondock.json not found)";
79
+ return new ArgumentError(hint ? `${base}.\n${hint}` : base);
80
+ }
81
+ /**
82
+ * 当前目录无工程且全局注册表无任何链接包提示文案(非错误场景使用)。
83
+ */
84
+ export const NO_PROJECT_NO_LINKED_MESSAGE = "No ActionDock project in current directory, and no packages linked.";
51
85
  /**
52
86
  * 解析并格式化任意异常为结构化错误对象。
53
87
  *
@@ -62,10 +96,26 @@ export function formatError(err) {
62
96
  details: err.details,
63
97
  };
64
98
  }
99
+ if (err instanceof InputError) {
100
+ return {
101
+ code: err.code,
102
+ message: err.message,
103
+ exitCode: ExitCode.INVALID_ARGUMENT,
104
+ details: err.details,
105
+ };
106
+ }
65
107
  // Commander.js 原生错误处理
66
108
  if (typeof err === "object" && err !== null && "code" in err && typeof err.code === "string") {
109
+ const code = err.code;
110
+ if (FLAT_ERROR_CODES.has(code)) {
111
+ return {
112
+ code,
113
+ message: err.message || String(err),
114
+ exitCode: ExitCode.INVALID_ARGUMENT,
115
+ details: err.details,
116
+ };
117
+ }
67
118
  const commanderErr = err;
68
- const code = commanderErr.code;
69
119
  // 参数类异常
70
120
  if (code.startsWith("commander.unknownOption") ||
71
121
  code.startsWith("commander.missingArgument") ||
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { type InvocationControl } from "./types.js";
1
2
  export * from "./types.js";
2
3
  export * from "./errors.js";
3
4
  export * from "./renderer.js";
@@ -10,6 +11,12 @@ export * from "./commands/index.js";
10
11
  * ActionDock CLI 主执行入口函数。
11
12
  *
12
13
  * @param argv 命令行参数数组(默认使用 process.argv)
14
+ * @param control 可选的中断与取消控制契约
13
15
  * @returns 进程退出码
14
16
  */
15
- export declare function main(argv?: string[]): Promise<number>;
17
+ export declare function main(argv?: string[], control?: InvocationControl): Promise<number>;
18
+ /**
19
+ * 命令行独立进程适配器。
20
+ * 仅在命令行可执行入口调用,负责全局 SIGINT 监听与退出状态码写入。
21
+ */
22
+ export declare function runCliProcess(argv?: string[]): Promise<void>;
package/dist/index.js CHANGED
@@ -1,7 +1,8 @@
1
1
  import { CommanderError } from "commander";
2
2
  import { createCliProgram } from "./commands/index.js";
3
- import { formatError } from "./errors.js";
3
+ import { formatError, SigintError } from "./errors.js";
4
4
  import { renderError } from "./renderer.js";
5
+ import { ExitCode } from "./types.js";
5
6
  export * from "./types.js";
6
7
  export * from "./errors.js";
7
8
  export * from "./renderer.js";
@@ -14,16 +15,37 @@ export * from "./commands/index.js";
14
15
  * ActionDock CLI 主执行入口函数。
15
16
  *
16
17
  * @param argv 命令行参数数组(默认使用 process.argv)
18
+ * @param control 可选的中断与取消控制契约
17
19
  * @returns 进程退出码
18
20
  */
19
- export async function main(argv = process.argv) {
20
- const program = createCliProgram();
21
- const isMachine = argv.includes("--json") || argv.includes("--envelope");
21
+ export async function main(argv = process.argv, control) {
22
+ const context = { control, exitCode: 0 };
23
+ const program = createCliProgram(context);
24
+ const isMachine = argv.includes("--json");
25
+ const normalizedArgv = argv.length >= 2 &&
26
+ (argv[0].endsWith("node") ||
27
+ argv[0].endsWith("bun") ||
28
+ argv[0].endsWith("tsx") ||
29
+ argv[1].includes("/") ||
30
+ argv[1].includes("\\") ||
31
+ argv[1].endsWith(".js") ||
32
+ argv[1].endsWith(".ts"))
33
+ ? argv
34
+ : ["node", "ad", ...argv];
22
35
  try {
23
- await program.parseAsync(argv);
24
- return typeof process.exitCode === "number" ? process.exitCode : 0;
36
+ await program.parseAsync(normalizedArgv);
37
+ if (control?.cancellationSource === "sigint" && control?.signal?.aborted) {
38
+ return ExitCode.SIGINT;
39
+ }
40
+ return (context.exitCode && context.exitCode !== 0)
41
+ ? context.exitCode
42
+ : (typeof process.exitCode === "number" ? process.exitCode : 0);
25
43
  }
26
44
  catch (err) {
45
+ if (control?.cancellationSource === "sigint" &&
46
+ (control?.signal?.aborted || err?.name === "AbortError" || err?.message?.includes("SIGINT") || err instanceof SigintError)) {
47
+ return ExitCode.SIGINT;
48
+ }
27
49
  if (err instanceof CommanderError) {
28
50
  if (err.exitCode === 0) {
29
51
  return 0;
@@ -32,10 +54,39 @@ export async function main(argv = process.argv) {
32
54
  // 复用 renderer 的 renderError 统一错误信封,消除手工拼装的双实现
33
55
  renderError(err, {
34
56
  json: isMachine,
35
- envelope: isMachine,
57
+ context,
36
58
  });
37
- process.exitCode = formatError(err).exitCode;
38
- return process.exitCode;
59
+ return formatError(err).exitCode;
60
+ }
61
+ }
62
+ /**
63
+ * 命令行独立进程适配器。
64
+ * 仅在命令行可执行入口调用,负责全局 SIGINT 监听与退出状态码写入。
65
+ */
66
+ export async function runCliProcess(argv = process.argv) {
67
+ const controller = new AbortController();
68
+ const control = {
69
+ signal: controller.signal,
70
+ };
71
+ let sigintCount = 0;
72
+ const sigintHandler = () => {
73
+ sigintCount++;
74
+ if (sigintCount === 1) {
75
+ control.cancellationSource = "sigint";
76
+ controller.abort(new Error("Interrupted by SIGINT"));
77
+ }
78
+ else {
79
+ process.exitCode = 130;
80
+ process.exit(130);
81
+ }
82
+ };
83
+ process.on("SIGINT", sigintHandler);
84
+ try {
85
+ const exitCode = await main(argv, control);
86
+ process.exitCode = exitCode;
87
+ }
88
+ finally {
89
+ process.removeListener("SIGINT", sigintHandler);
39
90
  }
40
91
  }
41
92
  import { fileURLToPath } from "node:url";
@@ -43,7 +94,7 @@ import { resolve } from "node:path";
43
94
  const isDirectRun = Boolean(process.argv[1]) &&
44
95
  resolve(process.argv[1]) === fileURLToPath(import.meta.url);
45
96
  if (isDirectRun) {
46
- main().catch((err) => {
97
+ runCliProcess().catch((err) => {
47
98
  console.error("Fatal error:", err);
48
99
  process.exitCode = 1;
49
100
  });
@@ -1,5 +1,5 @@
1
1
  import type { RunRecord } from "@actiondock/sdk";
2
- import type { RegistryStatusReport } from "@actiondock/core";
2
+ import { type RegistryStatusReport } from "@actiondock/core";
3
3
  import type { Envelope, ProjectDetailInfo, AggregatedPackage, EnvCheckItem, CliContext } from "./types.js";
4
4
  /**
5
5
  * 构造标准成功结果信封。
@@ -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
  /**
@@ -114,7 +112,8 @@ export declare function renderActionList(items: Array<{
114
112
  packageId?: string;
115
113
  }>, title?: string, isFallback?: boolean, intent?: string): string;
116
114
  /**
117
- * 格式化渲染 Action 详情。
115
+ * 格式化渲染 Action 详情(编码顾问 Encoding Advisor)。
116
+ * 统一复用 core 下沉的 formatActionDetail 实现,与独立分发器保持完全一致。
118
117
  */
119
118
  export declare function renderActionDetail(action: {
120
119
  id: string;
package/dist/renderer.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { formatActionDetail } from "@actiondock/core";
1
2
  import { formatError } from "./errors.js";
2
3
  /**
3
4
  * 构造标准成功结果信封。
@@ -72,21 +73,9 @@ export function writeStderr(message, context) {
72
73
  * 统一根据输出模式进行渲染输出。
73
74
  */
74
75
  export function renderResult(data, options) {
75
- const isJson = Boolean(options.json || options.envelope);
76
- const useEnvelope = Boolean(options.envelope || (isJson && options.context?.defaultEnvelope));
76
+ const isJson = Boolean(options.json);
77
77
  if (isJson) {
78
- if (useEnvelope) {
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
- }
78
+ writeStdout(formatJson(data), options.context);
90
79
  }
91
80
  else {
92
81
  if (options.humanFormatter) {
@@ -102,7 +91,7 @@ export function renderResult(data, options) {
102
91
  */
103
92
  export function renderError(err, options) {
104
93
  const formatted = formatError(err);
105
- const isMachine = Boolean(options.json || options.envelope || options.context?.defaultEnvelope);
94
+ const isMachine = Boolean(options.json);
106
95
  if (isMachine) {
107
96
  const errorEnv = createErrorEnvelope(formatted.code, formatted.message, formatted.details);
108
97
  writeStdout(formatJson(errorEnv), options.context);
@@ -266,27 +255,11 @@ export function renderActionList(items, title = "Actions", isFallback = false, i
266
255
  return lines.join("\n");
267
256
  }
268
257
  /**
269
- * 格式化渲染 Action 详情。
258
+ * 格式化渲染 Action 详情(编码顾问 Encoding Advisor)。
259
+ * 统一复用 core 下沉的 formatActionDetail 实现,与独立分发器保持完全一致。
270
260
  */
271
261
  export function renderActionDetail(action) {
272
- const lines = [];
273
- lines.push(`Action: ${action.id}`);
274
- if (action.packageId) {
275
- const rootDesc = action.projectRoot ? ` (${action.projectRoot})` : "";
276
- lines.push(`Package: ${action.packageId}${rootDesc}`);
277
- }
278
- if (action.description) {
279
- lines.push(`Description: ${action.description}`);
280
- }
281
- if (action.inputSchema) {
282
- lines.push("\nInput Schema:");
283
- lines.push(JSON.stringify(action.inputSchema, null, 2));
284
- }
285
- if (action.outputSchema) {
286
- lines.push("\nOutput Schema:");
287
- lines.push(JSON.stringify(action.outputSchema, null, 2));
288
- }
289
- return lines.join("\n");
262
+ return formatActionDetail(action);
290
263
  }
291
264
  /**
292
265
  * 格式化渲染 Action 校验结果。
@@ -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
+ }
@@ -1 +1,2 @@
1
1
  export * from "./inspect.js";
2
+ export * from "./config-merge.js";
@@ -1 +1,2 @@
1
1
  export * from "./inspect.js";
2
+ export * from "./config-merge.js";
@@ -1,5 +1,6 @@
1
- import { StandaloneDispatcher as CoreStandaloneDispatcher } from "@actiondock/core";
1
+ import { StandaloneDispatcher as CoreStandaloneDispatcher, type InvocationControl } from "@actiondock/core";
2
2
  import type { StandaloneOptions } from "../types.js";
3
+ export { type InvocationControl };
3
4
  /**
4
5
  * 独立二进制轻量参数解析分发器(委托 Core 统一实现,消除重复逻辑)。
5
6
  */
@@ -9,8 +10,13 @@ export declare class StandaloneDispatcher extends CoreStandaloneDispatcher {
9
10
  /**
10
11
  * 运行独立二进制参数解析分发器并返回退出状态码。
11
12
  */
12
- export declare function runStandaloneCli(argv: string[] | undefined, options: StandaloneOptions): Promise<number>;
13
+ export declare function runStandaloneCli(argv: string[] | undefined, options: StandaloneOptions, control?: InvocationControl): Promise<number>;
13
14
  /**
14
15
  * 创建独立二进制运行时分发器实例。
15
16
  */
16
17
  export declare function createStandaloneDispatcher(options: StandaloneOptions): StandaloneDispatcher;
18
+ /**
19
+ * 独立二进制进程入口适配器。
20
+ * 仅在命令行可执行入口调用,负责全局 SIGINT 监听与退出状态码写入。
21
+ */
22
+ export declare function runStandaloneProcess(argv: string[] | undefined, options: StandaloneOptions): Promise<void>;
@@ -10,9 +10,9 @@ export class StandaloneDispatcher extends CoreStandaloneDispatcher {
10
10
  /**
11
11
  * 运行独立二进制参数解析分发器并返回退出状态码。
12
12
  */
13
- export async function runStandaloneCli(argv = process.argv.slice(2), options) {
13
+ export async function runStandaloneCli(argv = process.argv.slice(2), options, control) {
14
14
  const dispatcher = new StandaloneDispatcher(options);
15
- return dispatcher.dispatch(argv);
15
+ return dispatcher.dispatch(argv, control);
16
16
  }
17
17
  /**
18
18
  * 创建独立二进制运行时分发器实例。
@@ -20,3 +20,33 @@ export async function runStandaloneCli(argv = process.argv.slice(2), options) {
20
20
  export function createStandaloneDispatcher(options) {
21
21
  return new StandaloneDispatcher(options);
22
22
  }
23
+ /**
24
+ * 独立二进制进程入口适配器。
25
+ * 仅在命令行可执行入口调用,负责全局 SIGINT 监听与退出状态码写入。
26
+ */
27
+ export async function runStandaloneProcess(argv = process.argv.slice(2), options) {
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
+ }
package/dist/types.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import type { ActionDefinition } from "@actiondock/sdk";
2
- import type { ConfigItemDefinition } from "@actiondock/core";
2
+ import type { ConfigItemDefinition, InvocationControl } from "@actiondock/core";
3
+ export type { InvocationControl };
3
4
  /**
4
5
  * 统一退出码定义(事实源位于 core 包 StandaloneDispatcher,此处仅 re-export 维持既有导入路径兼容)。
5
6
  * - SUCCESS: 执行成功(0)
@@ -67,8 +68,6 @@ export interface CliContext {
67
68
  dataDir?: string;
68
69
  /** 自定义 ActionDock 用户家目录(用于测试隔离与多租户环境) */
69
70
  customHome?: string;
70
- /** 默认信封包装输出模式 */
71
- defaultEnvelope?: boolean;
72
71
  /** 自定义标准输出写入函数 */
73
72
  stdout?: (msg: string) => void;
74
73
  /** 自定义标准错误写入函数 */
@@ -80,6 +79,10 @@ export interface CliContext {
80
79
  secret?: boolean;
81
80
  mask?: string;
82
81
  }) => Promise<string>;
82
+ /** 可选的中断与取消控制契约 */
83
+ control?: InvocationControl;
84
+ /** 退出码收集器(避免污染全局 process.exitCode) */
85
+ exitCode?: number;
83
86
  }
84
87
  /**
85
88
  * 工程自省详情信息结构。
@@ -5,3 +5,4 @@ export * from "./filter.js";
5
5
  export * from "./target.js";
6
6
  export * from "./spawn.js";
7
7
  export * from "./cert.js";
8
+ export * from "./input.js";
@@ -5,3 +5,4 @@ export * from "./filter.js";
5
5
  export * from "./target.js";
6
6
  export * from "./spawn.js";
7
7
  export * from "./cert.js";
8
+ export * from "./input.js";
@@ -0,0 +1 @@
1
+ export { resolveActionInput, type ResolveActionInputOptions, parseJson, readStdin, stripBom, InputError, FlatInputError, buildActionInputAdvice, formatActionDetail, } from "@actiondock/core";
@@ -0,0 +1 @@
1
+ export { resolveActionInput, parseJson, readStdin, stripBom, InputError, FlatInputError, buildActionInputAdvice, formatActionDetail, } from "@actiondock/core";
@@ -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 策略)