@actiondock/core 2.19.0 → 2.21.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/dist/client/config.d.ts +17 -14
- package/dist/client/config.js +112 -14
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.js +1 -1
- package/dist/errors.d.ts +13 -1
- package/dist/errors.js +25 -0
- package/dist/execution/service.js +44 -10
- package/dist/execution/types.d.ts +3 -3
- package/dist/host/host.d.ts +20 -1
- package/dist/host/host.js +430 -66
- package/dist/host/types.d.ts +13 -1
- package/dist/index.d.ts +11 -3
- package/dist/index.js +5 -1
- package/dist/input/config-override.d.ts +5 -0
- package/dist/input/config-override.js +23 -0
- package/dist/input/input-resolver.d.ts +24 -7
- package/dist/input/input-resolver.js +73 -5
- package/dist/invocation/bindings.d.ts +53 -0
- package/dist/invocation/bindings.js +248 -0
- package/dist/invocation/policy.d.ts +5 -1
- package/dist/invocation/policy.js +16 -9
- package/dist/invocation/remote.d.ts +40 -0
- package/dist/invocation/remote.js +489 -0
- package/dist/invocation/types.d.ts +33 -0
- package/dist/invocation/types.js +11 -1
- package/dist/package/index.d.ts +2 -2
- package/dist/package/runtime.js +48 -15
- package/dist/platform/node.d.ts +6 -0
- package/dist/platform/node.js +59 -16
- package/dist/platform/types.d.ts +59 -1
- package/dist/process/command-executor.d.ts +35 -0
- package/dist/process/command-executor.js +326 -0
- package/dist/process/index.d.ts +1 -0
- package/dist/process/index.js +1 -0
- package/dist/process/process-driver.d.ts +19 -6
- package/dist/process/process-driver.js +223 -54
- package/dist/process/run-executor.js +101 -47
- package/dist/project/config-declarations.d.ts +61 -0
- package/dist/project/config-declarations.js +297 -0
- package/dist/project/dependencies.d.ts +92 -0
- package/dist/project/dependencies.js +543 -0
- package/dist/project/index.d.ts +8 -2
- package/dist/project/index.js +8 -2
- package/dist/project/init.js +2 -2
- package/dist/project/loader.js +19 -30
- package/dist/project/manifest.js +10 -0
- package/dist/project/package-manager.d.ts +40 -0
- package/dist/project/package-manager.js +224 -0
- package/dist/project/preparation.d.ts +8 -0
- package/dist/project/preparation.js +130 -0
- package/dist/project/transactions.d.ts +5 -1
- package/dist/project/transactions.js +35 -75
- package/dist/project/types.d.ts +30 -0
- package/dist/runtime/context.d.ts +27 -5
- package/dist/runtime/context.js +98 -26
- package/dist/runtime/env.js +15 -1
- package/dist/runtime/run-persistence.d.ts +1 -1
- package/dist/runtime/run-persistence.js +4 -3
- package/dist/runtime/standalone-commands.d.ts +2 -2
- package/dist/runtime/standalone-commands.js +113 -47
- package/dist/runtime/standalone-help.d.ts +1 -0
- package/dist/runtime/standalone-help.js +15 -9
- package/dist/runtime/standalone.js +2 -2
- package/dist/security/config-cipher.d.ts +54 -0
- package/dist/security/config-cipher.js +190 -0
- package/dist/security/config-key.d.ts +35 -0
- package/dist/security/config-key.js +469 -0
- package/dist/server/policy.d.ts +24 -107
- package/dist/server/policy.js +134 -291
- package/dist/server/routes/actions.js +10 -8
- package/dist/server/routes/common.d.ts +5 -20
- package/dist/server/routes/common.js +22 -58
- package/dist/server/routes/config.js +84 -7
- package/dist/server/routes/doctor.d.ts +1 -0
- package/dist/server/routes/doctor.js +4 -3
- package/dist/server/routes/info.js +100 -13
- package/dist/server/routes/playbooks.js +1 -3
- package/dist/server/routes/runs.js +96 -18
- package/dist/server/server.js +25 -53
- package/dist/server/types.d.ts +6 -6
- package/dist/service/factory.js +4 -0
- package/dist/service/remote.js +14 -29
- package/dist/service/types.d.ts +28 -5
- package/dist/service/types.js +8 -0
- package/dist/storage/config-value.d.ts +33 -0
- package/dist/storage/config-value.js +102 -0
- package/dist/storage/file-lock.js +35 -21
- package/dist/storage/index.d.ts +24 -2
- package/dist/storage/index.js +65 -2
- package/dist/storage/mask.d.ts +33 -6
- package/dist/storage/mask.js +298 -16
- package/dist/storage/migrations/index.d.ts +40 -0
- package/dist/storage/migrations/index.js +96 -0
- package/dist/storage/migrations/v2-to-v3.d.ts +16 -0
- package/dist/storage/migrations/v2-to-v3.js +116 -0
- package/dist/storage/sqlite-config.d.ts +62 -3
- package/dist/storage/sqlite-config.js +163 -9
- package/dist/storage/sqlite-driver.d.ts +5 -2
- package/dist/storage/sqlite-driver.js +24 -3
- package/dist/storage/sqlite-schema.d.ts +20 -2
- package/dist/storage/sqlite-schema.js +129 -132
- package/dist/storage/sqlite.d.ts +7 -2
- package/dist/storage/sqlite.js +64 -13
- package/dist/storage/types.d.ts +72 -4
- package/dist/storage/types.js +7 -2
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +2 -2
package/dist/host/types.d.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import type { ActionRef, ExecutionEvent, ExecutionResult, JsonValue, Logger, ProcessAPI, RunRecord } from "@actiondock/sdk";
|
|
2
2
|
import type { PackageInfo, PackageRuntime, PackageRuntimeOptions, ActionSpec, ActionSummary, ListActionsOptions, PlaybookSpec, PlaybookSummary } from "../package/types.js";
|
|
3
3
|
import type { CancelResult, ExecutionTicket } from "../execution/types.js";
|
|
4
|
-
import type { RunOptions } from "../invocation/types.js";
|
|
4
|
+
import type { LocalPackagePreparer, RunOptions } from "../invocation/types.js";
|
|
5
5
|
import type { Clock } from "../storage/clock.js";
|
|
6
6
|
import type { EventSink } from "../runtime/events.js";
|
|
7
7
|
import type { RuntimePlatform } from "../platform/types.js";
|
|
8
8
|
import type { ActionDockService, ConfigValueView, EventsPort, ListRunsOptions, StateScopeOptions } from "../service/types.js";
|
|
9
9
|
import type { StateEntry } from "../storage/types.js";
|
|
10
|
+
import type { ActionBindings } from "../invocation/bindings.js";
|
|
10
11
|
/**
|
|
11
12
|
* 宿主事件服务端口(既作为独立方法调用,又满足 EventsPort 规范)。
|
|
12
13
|
*/
|
|
@@ -21,6 +22,8 @@ export interface HostEventsPort extends EventsPort {
|
|
|
21
22
|
* ActionDock 宿主容器初始化配置选项。
|
|
22
23
|
*/
|
|
23
24
|
export interface ActionDockHostOptions {
|
|
25
|
+
/** 按调用方包覆盖运行时命名绑定;未覆盖的包读取其 .actiondock/bindings.json。 */
|
|
26
|
+
actionBindings?: Readonly<Record<string, ActionBindings>>;
|
|
24
27
|
/** 显式预注册的包列表(PackageRuntime 实例、PackageRuntimeOptions 配置或包目录物理路径)。Host 统一管理其生命周期,关闭时统一释放。 */
|
|
25
28
|
packages?: Array<PackageRuntime | PackageRuntimeOptions | string>;
|
|
26
29
|
/** 当前工程根目录绝对物理路径 */
|
|
@@ -57,6 +60,13 @@ export interface ActionDockHostOptions {
|
|
|
57
60
|
eventSink?: EventSink;
|
|
58
61
|
/** 是否开启配置与状态管理端口(默认 true) */
|
|
59
62
|
enableManagement?: boolean;
|
|
63
|
+
/**
|
|
64
|
+
* 是否允许执行依赖自动安装(主要控制启动与事务恢复中的依赖安装;
|
|
65
|
+
* 本地包运行期准备由 prepareLocalPackage 注入器根据自身配置决定,Host 不约束外部注入函数副作用)。
|
|
66
|
+
*/
|
|
67
|
+
allowInstall?: boolean;
|
|
68
|
+
/** 本地包准备协调器回调,在 Action 执行前准备本地包环境 */
|
|
69
|
+
prepareLocalPackage?: LocalPackagePreparer;
|
|
60
70
|
}
|
|
61
71
|
/**
|
|
62
72
|
* ActionDock 多包宿主容器领域契约。
|
|
@@ -66,6 +76,8 @@ export interface ActionDockHostOptions {
|
|
|
66
76
|
export interface ActionDockHost extends ActionDockService {
|
|
67
77
|
/** 宿主容器初始化配置项(只读) */
|
|
68
78
|
readonly options?: ActionDockHostOptions;
|
|
79
|
+
/** 是否允许依赖安装(主要控制启动与事务恢复) */
|
|
80
|
+
readonly allowInstall?: boolean;
|
|
69
81
|
/** 统一事件订阅端口(兼作原生订阅函数与事件服务端口) */
|
|
70
82
|
readonly events: HostEventsPort;
|
|
71
83
|
/** 获取所有已注册包的元数据信息列表 */
|
package/dist/index.d.ts
CHANGED
|
@@ -7,21 +7,29 @@
|
|
|
7
7
|
*/
|
|
8
8
|
export { ACTIONDOCK_VERSION } from "./version.js";
|
|
9
9
|
export { createActionDock, connectActionDock, } from "./service/factory.js";
|
|
10
|
-
export type { ActionDockService, ActionDockService as ActionDock, DiscoveryPort, ExecutionPort, RunsPort, EventsPort, RunEventSubscriptionOptions, ConfigPort, StatePort, CreateActionDockOptions, ConnectActionDockOptions, RemoteServiceOptions, ConfigValueView, ListRunsOptions, RunReadOptions, StateScopeOptions, } from "./service/types.js";
|
|
10
|
+
export type { ActionDockService, ActionDockService as ActionDock, DiscoveryPort, ExecutionPort, RunsPort, EventsPort, RunEventSubscriptionOptions, ConfigPort, StatePort, CreateActionDockOptions, ConnectActionDockOptions, RemoteServiceOptions, ConfigValueView, ConfigSource, ListRunsOptions, RunReadOptions, StateScopeOptions, } from "./service/types.js";
|
|
11
11
|
export { isTerminalRunStatus, DEFAULT_RUNS_RETENTION_MS, DEFAULT_MAX_RUNS_PER_PACKAGE, DEFAULT_MIN_RETAIN_RUNS, } from "./storage/types.js";
|
|
12
12
|
export { isRunHostDead } from "./storage/run-liveness.js";
|
|
13
13
|
export type { TerminalRunStatus } from "./storage/types.js";
|
|
14
14
|
export { parseDuration } from "./utils/index.js";
|
|
15
15
|
export { toSnakeUpperCase, resolveEnvValue } from "./runtime/env.js";
|
|
16
16
|
export type { ResolvedEnv } from "./runtime/env.js";
|
|
17
|
-
export type { ActionRef, ResolvedActionRef, ExecutionResult, RunRecord, RunOrigin, ExecutionEvent, } from "@actiondock/sdk";
|
|
17
|
+
export type { ActionInvokeOptions, ActionRef, ResolvedActionRef, ExecutionResult, RunRecord, RunOrigin, ExecutionEvent, } from "@actiondock/sdk";
|
|
18
18
|
export type { RunOptions, ExecutionTicket, CancelResult, } from "./execution/types.js";
|
|
19
|
+
export type { ActionBindings, ActionBindingTarget } from "./invocation/bindings.js";
|
|
20
|
+
export type { LocalPackagePreparationTarget, LocalPackagePreparationContext, LocalPackagePreparer, } from "./invocation/types.js";
|
|
19
21
|
export type { ActionSpec, ActionSummary, PlaybookSpec, PlaybookSummary, PackageInfo, ListActionsOptions, } from "./package/types.js";
|
|
20
22
|
export { createNodePlatform, } from "./platform/node.js";
|
|
21
23
|
export { startActionDockServer, } from "./server/server.js";
|
|
22
24
|
export type { ServerOptions, ServerOptions as ActionDockServerOptions, ActionDockServerInstance, } from "./server/types.js";
|
|
23
25
|
export { initProject } from "./project/init.js";
|
|
24
26
|
export { loadProjectConfig, findProjectRoot, } from "./project/loader.js";
|
|
27
|
+
export { ensureProjectDependencies, } from "./project/dependencies.js";
|
|
28
|
+
export type { EnsureProjectDependenciesOptions, EnsureProjectDependenciesResult, } from "./project/dependencies.js";
|
|
25
29
|
export type { ProjectConfig, } from "./project/types.js";
|
|
26
30
|
export type { PackageGraph, } from "./catalog/graph.js";
|
|
27
|
-
export { ActionDockError, ProcessError, PACKAGE_NOT_FOUND, ACTION_PACKAGE_VERSION_CONFLICT, ACTION_NOT_FOUND, ACTION_LOAD_FAILED, ACTION_FAILED, ACTION_TIMEOUT, ACTION_CANCELLED, INPUT_VALIDATION_FAILED, OUTPUT_VALIDATION_FAILED, INPUT_NOT_JSON, OUTPUT_NOT_JSON, ACTION_SUBRUN_LIMIT, ACTION_CALL_CYCLE, UNDECLARED_ACTION_DEPENDENCY, INVALID_ACTION_REF, IDEMPOTENCY_CONFLICT, EXECUTION_FAILED, UNHANDLED_EXECUTION_ERROR, RUN_REPOSITORY_UNAVAILABLE, RUN_PERSISTENCE_FAILED, STANDALONE_ASYNC_UNSUPPORTED, HOST_PROCESS_EXITED, EXECUTION_ABORTED, TIMEOUT, NETWORK_ERROR, CAPABILITY_UNAVAILABLE, STATE_KEY_NOT_FOUND, STORAGE_BUSY, PROJECT_BUSY, PROJECT_RECOVERY_REQUIRED, EVENT_BACKPRESSURE_LIMIT, EVENT_CURSOR_EXPIRED, PROCESS_OUTPUT_LIMIT, PROCESS_SPAWN_ERROR, PROCESS_CANCELLED, PROCESS_TIMEOUT, UNAUTHORIZED, NOT_FOUND, SERVER_ERROR, PACKAGE_FORBIDDEN, ACTION_FORBIDDEN, PLAYBOOK_NOT_FOUND, REQUEST_TOO_LARGE, ACTION_START_FAILED, ACTION_EXECUTION_ERROR, DATA_DIR_IN_USE, DATA_DIR_RECOVERY_REQUIRED, PROCESS_FAILED, MCP_TOOL_NAME_COLLISION, ACCESS_DENIED, 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, INVALID_ARGUMENT, PACKAGE_NOT_ALLOWED, INVALID_PACKAGE_ID, PATH_TRAVERSAL, AMBIGUOUS_STATE_KEY, RUN_NOT_FOUND, RUN_ALREADY_FINISHED, PROJECT_ROOT_NOT_FOUND, describeActionLoadFailure, createErrorEnvelope, resolveExecutionHint, } from "./errors.js";
|
|
31
|
+
export { ActionDockError, ProcessError, PACKAGE_NOT_FOUND, ACTION_PACKAGE_VERSION_CONFLICT, ACTION_NOT_FOUND, ACTION_LOAD_FAILED, ACTION_FAILED, ACTION_TIMEOUT, ACTION_CANCELLED, CONFIG_VALUE_INVALID, INPUT_VALIDATION_FAILED, OUTPUT_VALIDATION_FAILED, INPUT_NOT_JSON, OUTPUT_NOT_JSON, ACTION_SUBRUN_LIMIT, ACTION_CALL_CYCLE, UNDECLARED_ACTION_DEPENDENCY, INVALID_ACTION_REF, IDEMPOTENCY_CONFLICT, EXECUTION_FAILED, UNHANDLED_EXECUTION_ERROR, RUN_REPOSITORY_UNAVAILABLE, RUN_PERSISTENCE_FAILED, STANDALONE_ASYNC_UNSUPPORTED, HOST_PROCESS_EXITED, EXECUTION_ABORTED, TIMEOUT, NETWORK_ERROR, CAPABILITY_UNAVAILABLE, STATE_KEY_NOT_FOUND, STORAGE_BUSY, DEPENDENCIES_NOT_READY, DEPENDENCY_INSTALL_FAILED, PROJECT_BUSY, PROJECT_RECOVERY_REQUIRED, EVENT_BACKPRESSURE_LIMIT, EVENT_CURSOR_EXPIRED, PROCESS_OUTPUT_LIMIT, PROCESS_SPAWN_ERROR, PROCESS_CANCELLED, PROCESS_TIMEOUT, UNAUTHORIZED, CONFIG_ENCRYPTION_ERROR, CONFIG_DECRYPT_FAILED, NOT_FOUND, SERVER_ERROR, PACKAGE_FORBIDDEN, ACTION_FORBIDDEN, PLAYBOOK_NOT_FOUND, REQUEST_TOO_LARGE, ACTION_START_FAILED, ACTION_EXECUTION_ERROR, DATA_DIR_IN_USE, DATA_DIR_RECOVERY_REQUIRED, PROCESS_FAILED, MCP_TOOL_NAME_COLLISION, ACCESS_DENIED, 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, INVALID_ARGUMENT, PACKAGE_NOT_ALLOWED, INVALID_PACKAGE_ID, PATH_TRAVERSAL, AMBIGUOUS_STATE_KEY, RUN_NOT_FOUND, RUN_ALREADY_FINISHED, PROJECT_ROOT_NOT_FOUND, describeActionLoadFailure, createErrorEnvelope, resolveExecutionHint, } from "./errors.js";
|
|
32
|
+
export { executeCommand } from "./process/command-executor.js";
|
|
33
|
+
export type { ExecuteCommandOptions, CommandExecutionResult, } from "./process/command-executor.js";
|
|
34
|
+
export type { ConfigEncryptionKey, ConfigKeyProvider, } from "./security/config-cipher.js";
|
|
35
|
+
export { createConfigKeyProvider } from "./security/config-key.js";
|
package/dist/index.js
CHANGED
|
@@ -19,5 +19,9 @@ export { startActionDockServer, } from "./server/server.js";
|
|
|
19
19
|
// 6. 核心工程辅助
|
|
20
20
|
export { initProject } from "./project/init.js";
|
|
21
21
|
export { loadProjectConfig, findProjectRoot, } from "./project/loader.js";
|
|
22
|
+
export { ensureProjectDependencies, } from "./project/dependencies.js";
|
|
22
23
|
// 8. 统一错误模型与核心标准错误码常量
|
|
23
|
-
export { ActionDockError, ProcessError, PACKAGE_NOT_FOUND, ACTION_PACKAGE_VERSION_CONFLICT, ACTION_NOT_FOUND, ACTION_LOAD_FAILED, ACTION_FAILED, ACTION_TIMEOUT, ACTION_CANCELLED, INPUT_VALIDATION_FAILED, OUTPUT_VALIDATION_FAILED, INPUT_NOT_JSON, OUTPUT_NOT_JSON, ACTION_SUBRUN_LIMIT, ACTION_CALL_CYCLE, UNDECLARED_ACTION_DEPENDENCY, INVALID_ACTION_REF, IDEMPOTENCY_CONFLICT, EXECUTION_FAILED, UNHANDLED_EXECUTION_ERROR, RUN_REPOSITORY_UNAVAILABLE, RUN_PERSISTENCE_FAILED, STANDALONE_ASYNC_UNSUPPORTED, HOST_PROCESS_EXITED, EXECUTION_ABORTED, TIMEOUT, NETWORK_ERROR, CAPABILITY_UNAVAILABLE, STATE_KEY_NOT_FOUND, STORAGE_BUSY, PROJECT_BUSY, PROJECT_RECOVERY_REQUIRED, EVENT_BACKPRESSURE_LIMIT, EVENT_CURSOR_EXPIRED, PROCESS_OUTPUT_LIMIT, PROCESS_SPAWN_ERROR, PROCESS_CANCELLED, PROCESS_TIMEOUT, UNAUTHORIZED, NOT_FOUND, SERVER_ERROR, PACKAGE_FORBIDDEN, ACTION_FORBIDDEN, PLAYBOOK_NOT_FOUND, REQUEST_TOO_LARGE, ACTION_START_FAILED, ACTION_EXECUTION_ERROR, DATA_DIR_IN_USE, DATA_DIR_RECOVERY_REQUIRED, PROCESS_FAILED, MCP_TOOL_NAME_COLLISION, ACCESS_DENIED, 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, INVALID_ARGUMENT, PACKAGE_NOT_ALLOWED, INVALID_PACKAGE_ID, PATH_TRAVERSAL, AMBIGUOUS_STATE_KEY, RUN_NOT_FOUND, RUN_ALREADY_FINISHED, PROJECT_ROOT_NOT_FOUND, describeActionLoadFailure, createErrorEnvelope, resolveExecutionHint, } from "./errors.js";
|
|
24
|
+
export { ActionDockError, ProcessError, PACKAGE_NOT_FOUND, ACTION_PACKAGE_VERSION_CONFLICT, ACTION_NOT_FOUND, ACTION_LOAD_FAILED, ACTION_FAILED, ACTION_TIMEOUT, ACTION_CANCELLED, CONFIG_VALUE_INVALID, INPUT_VALIDATION_FAILED, OUTPUT_VALIDATION_FAILED, INPUT_NOT_JSON, OUTPUT_NOT_JSON, ACTION_SUBRUN_LIMIT, ACTION_CALL_CYCLE, UNDECLARED_ACTION_DEPENDENCY, INVALID_ACTION_REF, IDEMPOTENCY_CONFLICT, EXECUTION_FAILED, UNHANDLED_EXECUTION_ERROR, RUN_REPOSITORY_UNAVAILABLE, RUN_PERSISTENCE_FAILED, STANDALONE_ASYNC_UNSUPPORTED, HOST_PROCESS_EXITED, EXECUTION_ABORTED, TIMEOUT, NETWORK_ERROR, CAPABILITY_UNAVAILABLE, STATE_KEY_NOT_FOUND, STORAGE_BUSY, DEPENDENCIES_NOT_READY, DEPENDENCY_INSTALL_FAILED, PROJECT_BUSY, PROJECT_RECOVERY_REQUIRED, EVENT_BACKPRESSURE_LIMIT, EVENT_CURSOR_EXPIRED, PROCESS_OUTPUT_LIMIT, PROCESS_SPAWN_ERROR, PROCESS_CANCELLED, PROCESS_TIMEOUT, UNAUTHORIZED, CONFIG_ENCRYPTION_ERROR, CONFIG_DECRYPT_FAILED, NOT_FOUND, SERVER_ERROR, PACKAGE_FORBIDDEN, ACTION_FORBIDDEN, PLAYBOOK_NOT_FOUND, REQUEST_TOO_LARGE, ACTION_START_FAILED, ACTION_EXECUTION_ERROR, DATA_DIR_IN_USE, DATA_DIR_RECOVERY_REQUIRED, PROCESS_FAILED, MCP_TOOL_NAME_COLLISION, ACCESS_DENIED, 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, INVALID_ARGUMENT, PACKAGE_NOT_ALLOWED, INVALID_PACKAGE_ID, PATH_TRAVERSAL, AMBIGUOUS_STATE_KEY, RUN_NOT_FOUND, RUN_ALREADY_FINISHED, PROJECT_ROOT_NOT_FOUND, describeActionLoadFailure, createErrorEnvelope, resolveExecutionHint, } from "./errors.js";
|
|
25
|
+
// 一次性工程命令执行工具
|
|
26
|
+
export { executeCommand } from "./process/command-executor.js";
|
|
27
|
+
export { createConfigKeyProvider } from "./security/config-key.js";
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { ActionDockError, INVALID_ARGUMENT } from "../errors.js";
|
|
2
|
+
import { assertConcreteConfigKey } from "../project/config-declarations.js";
|
|
3
|
+
import { parseJson } from "./input-resolver.js";
|
|
4
|
+
/** 配置赋值只识别等号左侧的 :=,不物化路径,也不把解析失败回退为字符串。 */
|
|
5
|
+
export function parseConfigOverrideAssignment(pair) {
|
|
6
|
+
const equals = pair.indexOf("=");
|
|
7
|
+
if (equals < 0) {
|
|
8
|
+
throw new ActionDockError(INVALID_ARGUMENT, "Expected configuration assignment 'KEY=value' or 'KEY:=json'");
|
|
9
|
+
}
|
|
10
|
+
const json = equals > 0 && pair[equals - 1] === ":";
|
|
11
|
+
const key = pair.slice(0, json ? equals - 1 : equals);
|
|
12
|
+
assertConcreteConfigKey(key);
|
|
13
|
+
const rawValue = pair.slice(equals + 1);
|
|
14
|
+
if (!json)
|
|
15
|
+
return { key, value: rawValue };
|
|
16
|
+
try {
|
|
17
|
+
return { key, value: parseJson(rawValue, "configuration override") };
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
// JSON.parse 和通用输入校验的诊断可能含输入片段,不能直接回显或挂载 cause。
|
|
21
|
+
throw new ActionDockError(INVALID_ARGUMENT, `Invalid JSON configuration override for '${key}'`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -16,7 +16,7 @@ export interface InputResolutionPolicy {
|
|
|
16
16
|
sanitizeInputErrors?: boolean;
|
|
17
17
|
/** 是否仅允许字节流输入(默认 false) */
|
|
18
18
|
byteStreamOnly?: boolean;
|
|
19
|
-
/** 原始 stdin 文本模式:保留开头 BOM,UTF-8
|
|
19
|
+
/** 原始 stdin 文本模式:保留开头 BOM,UTF-8 编码失败视为读取失败(stdinField 默认启用) */
|
|
20
20
|
stdinRawText?: boolean;
|
|
21
21
|
}
|
|
22
22
|
/**
|
|
@@ -26,19 +26,21 @@ export interface InputResolutionPolicy {
|
|
|
26
26
|
*/
|
|
27
27
|
export declare function readStdin(stream?: NodeJS.ReadableStream): Promise<string>;
|
|
28
28
|
/**
|
|
29
|
-
*
|
|
29
|
+
* 通用 JSON 值输入解析选项;扁平参数与字段绑定产生对象,完整 JSON 可以是任意合法值。
|
|
30
30
|
*/
|
|
31
|
-
export interface
|
|
31
|
+
export interface ResolveInputOptions {
|
|
32
32
|
input?: string;
|
|
33
33
|
inputFile?: string;
|
|
34
34
|
flatArgs?: string[];
|
|
35
|
-
/** 将 stdin
|
|
35
|
+
/** 将 stdin 完整原始文本绑定为指定顶层字段的字符串值(与 input / inputFile 互斥,可与 flatArgs 组合) */
|
|
36
36
|
stdinField?: string;
|
|
37
37
|
stdin?: NodeJS.ReadableStream;
|
|
38
38
|
flatOptions?: FlatParserOptions & FlatMaterializerOptions;
|
|
39
39
|
policy?: InputResolutionPolicy;
|
|
40
40
|
signal?: AbortSignal;
|
|
41
41
|
}
|
|
42
|
+
/** Action 输入沿用通用解码选项,Action 专属契约校验由执行边界负责。 */
|
|
43
|
+
export type ResolveActionInputOptions = ResolveInputOptions;
|
|
42
44
|
/**
|
|
43
45
|
* 解析完整 JSON 文档字符串。
|
|
44
46
|
* 若解析失败抛出 INVALID_JSON 异常。
|
|
@@ -49,7 +51,12 @@ export interface ResolveActionInputOptions {
|
|
|
49
51
|
*/
|
|
50
52
|
export declare function parseJson(text: string, source: string, policy?: InputResolutionPolicy): JsonValue;
|
|
51
53
|
/**
|
|
52
|
-
*
|
|
54
|
+
* 解析一个完整值:合法 JSON 解码为对应类型,非 JSON 文本保留为字符串。
|
|
55
|
+
* 只有语法错误允许回退;大小、深度、有限数和危险属性校验失败必须透传。
|
|
56
|
+
*/
|
|
57
|
+
export declare function parseValueLiteral(text: string, policy?: InputResolutionPolicy): JsonValue;
|
|
58
|
+
/**
|
|
59
|
+
* 统一解析 JSON 值及结构化输入,不承担 Action、配置或状态的业务校验。
|
|
53
60
|
*
|
|
54
61
|
* 输入模式仲裁规则(Input Mode Arbitration):
|
|
55
62
|
* - effectiveFlat: flatArgs 存在且 length > 0
|
|
@@ -66,6 +73,16 @@ export declare function parseJson(text: string, source: string, policy?: InputRe
|
|
|
66
73
|
* - stdinField 的 stdin 读取为原始文本绑定:不做 JSON 解析、不剥 BOM、保留首尾空白,空 stdin 得到空字符串。
|
|
67
74
|
*
|
|
68
75
|
* @param options 输入解析选项
|
|
69
|
-
* @returns 解析后的 JSON
|
|
76
|
+
* @returns 解析后的 JSON 值
|
|
70
77
|
*/
|
|
71
|
-
export declare function
|
|
78
|
+
export declare function resolveInput(options: ResolveInputOptions): Promise<JsonValue>;
|
|
79
|
+
/** Action 入参复用同一解码器,避免各命令的扁平路径与 stdin 绑定语义漂移。 */
|
|
80
|
+
export declare const resolveActionInput: typeof resolveInput;
|
|
81
|
+
/** 完整值写入的输入选项;业务层仍负责声明校验与整键替换。 */
|
|
82
|
+
export interface ResolveValueInputOptions extends Pick<ResolveInputOptions, "flatArgs" | "stdinField" | "stdin" | "signal" | "policy" | "flatOptions"> {
|
|
83
|
+
value?: string;
|
|
84
|
+
fromStdin?: boolean;
|
|
85
|
+
}
|
|
86
|
+
export declare function hasValueInput(input: ResolveValueInputOptions): boolean;
|
|
87
|
+
/** 先仲裁来源再读流,配置、状态及独立运行入口使用同一完整值解码规则。 */
|
|
88
|
+
export declare function resolveValueInput(options: ResolveValueInputOptions): Promise<JsonValue>;
|
|
@@ -2,7 +2,7 @@ import { inputConflict, inputLimitExceeded, inputPathConflict, invalidFlatArgume
|
|
|
2
2
|
import { isFlatPathPropertyName, isForbiddenActionInputPropertyName, } from "./flat-predicates.js";
|
|
3
3
|
import { decodeFlatInput } from "./flat-decode.js";
|
|
4
4
|
import { assertTopLevelLeafAvailable, assertMaterializedInputWithinLimits, } from "./flat-materializer.js";
|
|
5
|
-
import { validateJsonValue, DEFAULT_MAX_JSON_DEPTH, } from "../value-validator.js";
|
|
5
|
+
import { validateJsonValue, hasDangerousKeys, DEFAULT_MAX_JSON_DEPTH, } from "../value-validator.js";
|
|
6
6
|
import { scanJsonDepth } from "./json-depth-scanner.js";
|
|
7
7
|
import { decodeUtf8Strict, stripBom } from "./utf8.js";
|
|
8
8
|
import { readRegularFileBounded, DEFAULT_MAX_INPUT_BYTES, } from "./file-input.js";
|
|
@@ -203,8 +203,29 @@ export function parseJson(text, source, policy) {
|
|
|
203
203
|
}
|
|
204
204
|
return parsed;
|
|
205
205
|
}
|
|
206
|
+
function checkValueProperties(value) {
|
|
207
|
+
if (hasDangerousKeys(value)) {
|
|
208
|
+
throw invalidJson("Input value contains prohibited prototype pollution properties", { reason: "FORBIDDEN_PROPERTY" });
|
|
209
|
+
}
|
|
210
|
+
return value;
|
|
211
|
+
}
|
|
206
212
|
/**
|
|
207
|
-
*
|
|
213
|
+
* 解析一个完整值:合法 JSON 解码为对应类型,非 JSON 文本保留为字符串。
|
|
214
|
+
* 只有语法错误允许回退;大小、深度、有限数和危险属性校验失败必须透传。
|
|
215
|
+
*/
|
|
216
|
+
export function parseValueLiteral(text, policy) {
|
|
217
|
+
try {
|
|
218
|
+
return checkValueProperties(parseJson(text, "value", policy));
|
|
219
|
+
}
|
|
220
|
+
catch (error) {
|
|
221
|
+
if (error instanceof InputError && error.code === "INVALID_JSON" && !Array.isArray(error.details) && error.details?.reason === "SYNTAX_ERROR") {
|
|
222
|
+
return text;
|
|
223
|
+
}
|
|
224
|
+
throw policy?.sanitizeInputErrors ? sanitizeError(error, "inline-json") : error;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* 统一解析 JSON 值及结构化输入,不承担 Action、配置或状态的业务校验。
|
|
208
229
|
*
|
|
209
230
|
* 输入模式仲裁规则(Input Mode Arbitration):
|
|
210
231
|
* - effectiveFlat: flatArgs 存在且 length > 0
|
|
@@ -221,16 +242,16 @@ export function parseJson(text, source, policy) {
|
|
|
221
242
|
* - stdinField 的 stdin 读取为原始文本绑定:不做 JSON 解析、不剥 BOM、保留首尾空白,空 stdin 得到空字符串。
|
|
222
243
|
*
|
|
223
244
|
* @param options 输入解析选项
|
|
224
|
-
* @returns 解析后的 JSON
|
|
245
|
+
* @returns 解析后的 JSON 值
|
|
225
246
|
*/
|
|
226
|
-
export async function
|
|
247
|
+
export async function resolveInput(options) {
|
|
227
248
|
const effectiveFlat = (options.flatArgs?.length ?? 0) > 0;
|
|
228
249
|
const hasInlineJson = options.input !== undefined;
|
|
229
250
|
const hasFileJson = options.inputFile !== undefined;
|
|
230
251
|
const hasStdinField = options.stdinField !== undefined;
|
|
231
252
|
// stdin 字段绑定与完整 JSON 来源(内联 / 文件 / stdin JSON)严格互斥
|
|
232
253
|
if (hasStdinField && (hasInlineJson || hasFileJson)) {
|
|
233
|
-
const err = inputConflict("Input options conflict: --stdin-field binds raw stdin text to
|
|
254
|
+
const err = inputConflict("Input options conflict: --stdin-field binds raw stdin text to a top-level field and cannot be combined with inline JSON (-i/--input) or file input (-f/--input-file).", {
|
|
234
255
|
reason: "MULTIPLE_INPUT_MODES",
|
|
235
256
|
hasFlatArgs: effectiveFlat,
|
|
236
257
|
hasInput: hasInlineJson,
|
|
@@ -371,3 +392,50 @@ export async function resolveActionInput(options) {
|
|
|
371
392
|
}
|
|
372
393
|
return {};
|
|
373
394
|
}
|
|
395
|
+
/** Action 入参复用同一解码器,避免各命令的扁平路径与 stdin 绑定语义漂移。 */
|
|
396
|
+
export const resolveActionInput = resolveInput;
|
|
397
|
+
export function hasValueInput(input) {
|
|
398
|
+
return input.value !== undefined || input.flatArgs !== undefined || Boolean(input.fromStdin) || input.stdinField !== undefined;
|
|
399
|
+
}
|
|
400
|
+
/** 先仲裁来源再读流,配置、状态及独立运行入口使用同一完整值解码规则。 */
|
|
401
|
+
export async function resolveValueInput(options) {
|
|
402
|
+
options.signal?.throwIfAborted();
|
|
403
|
+
const wholeModes = Number(options.value !== undefined) + Number(Boolean(options.fromStdin));
|
|
404
|
+
const structured = options.flatArgs !== undefined || options.stdinField !== undefined;
|
|
405
|
+
if (wholeModes + Number(structured) > 1) {
|
|
406
|
+
throw inputConflict("Complete value, --stdin, and structured input are mutually exclusive; --stdin-field may only be combined with flat assignments.", { reason: "MULTIPLE_INPUT_MODES" });
|
|
407
|
+
}
|
|
408
|
+
if (structured) {
|
|
409
|
+
if (options.stdinField === undefined && options.flatArgs?.length === 0) {
|
|
410
|
+
throw invalidFlatArgument("At least one flat assignment is required after '--'.", { reason: "MISSING_ASSIGNMENT" });
|
|
411
|
+
}
|
|
412
|
+
const value = checkValueProperties(await resolveInput(options));
|
|
413
|
+
options.signal?.throwIfAborted();
|
|
414
|
+
return value;
|
|
415
|
+
}
|
|
416
|
+
let text = options.value;
|
|
417
|
+
if (options.fromStdin) {
|
|
418
|
+
try {
|
|
419
|
+
text = await readStdinBounded(options.stdin || process.stdin, {
|
|
420
|
+
maxInputBytes: options.policy?.maxInputBytes ?? DEFAULT_MAX_INPUT_BYTES,
|
|
421
|
+
signal: options.signal,
|
|
422
|
+
strictUtf8: options.policy?.strictUtf8 ?? false,
|
|
423
|
+
rawText: true,
|
|
424
|
+
byteStreamOnly: options.policy?.byteStreamOnly,
|
|
425
|
+
});
|
|
426
|
+
// 完整 stdin 值保留原配置命令的末尾换行裁剪;字段绑定则原样保留文本。
|
|
427
|
+
text = text.replace(/[\r\n]+$/, "");
|
|
428
|
+
}
|
|
429
|
+
catch (error) {
|
|
430
|
+
throw options.policy?.sanitizeInputErrors ? sanitizeError(error, "stdin") : error;
|
|
431
|
+
}
|
|
432
|
+
if (text.length === 0)
|
|
433
|
+
throw invalidFlatArgument("No data received from standard input", { reason: "MISSING_VALUE" });
|
|
434
|
+
}
|
|
435
|
+
if (text === undefined) {
|
|
436
|
+
throw invalidFlatArgument("A complete value, flat assignments, --stdin, or --stdin-field is required.", { reason: "MISSING_VALUE" });
|
|
437
|
+
}
|
|
438
|
+
const value = parseValueLiteral(text, options.policy);
|
|
439
|
+
options.signal?.throwIfAborted();
|
|
440
|
+
return value;
|
|
441
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 动作绑定目标描述。
|
|
3
|
+
*/
|
|
4
|
+
export type ActionBindingTarget = {
|
|
5
|
+
type: "local";
|
|
6
|
+
} | {
|
|
7
|
+
type: "remote";
|
|
8
|
+
profile: string;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* 动作命名绑定字典单一事实源类型。
|
|
12
|
+
*/
|
|
13
|
+
export type ActionBindings = Readonly<Record<string, Readonly<Record<string, ActionBindingTarget>>>>;
|
|
14
|
+
/** 绑定文件名与目录常量 */
|
|
15
|
+
export declare const BINDINGS_DIR_NAME = ".actiondock";
|
|
16
|
+
export declare const BINDINGS_FILE_NAME = "bindings.json";
|
|
17
|
+
/** 绑定名称正则约束(稳定简单标识符) */
|
|
18
|
+
export declare const ACTION_BINDING_NAME_REGEX: RegExp;
|
|
19
|
+
/** 环境配置名称正则约束 */
|
|
20
|
+
export declare const PROFILE_NAME_REGEX: RegExp;
|
|
21
|
+
/**
|
|
22
|
+
* 校验并规范化完全限定的动作引用字符串。
|
|
23
|
+
* 铁律:必须为完全限定包与动作,禁止通配符、路径逃逸、空白与冒号。
|
|
24
|
+
*/
|
|
25
|
+
export declare function validateCanonicalActionRef(ref: unknown): string;
|
|
26
|
+
/**
|
|
27
|
+
* 校验绑定名称合法性。
|
|
28
|
+
*/
|
|
29
|
+
export declare function validateBindingName(name: unknown, actionRef?: string): string;
|
|
30
|
+
/**
|
|
31
|
+
* 校验单个绑定目标对象的合法性并返回深拷贝冻结快照。
|
|
32
|
+
*/
|
|
33
|
+
export declare function validateBindingTarget(target: unknown, bindingName: string, actionRef: string): ActionBindingTarget;
|
|
34
|
+
/**
|
|
35
|
+
* 校验并深拷贝冻结动作绑定配置快照。
|
|
36
|
+
*/
|
|
37
|
+
export declare function validateActionBindings(value: unknown): ActionBindings;
|
|
38
|
+
/**
|
|
39
|
+
* 从调用方项目目录读取动作绑定配置。
|
|
40
|
+
* 若文件不存在则返回空冻结映射,其余 IO 或 JSON 异常统一转换为 ActionDockError 透传。
|
|
41
|
+
*/
|
|
42
|
+
export declare function loadActionBindings(projectRoot: string): ActionBindings;
|
|
43
|
+
/**
|
|
44
|
+
* 解析目标动作引用在当前项目中的具体绑定。
|
|
45
|
+
* 未显式传入 binding 则仅检索 default;若 default 亦不存在则返回 undefined。
|
|
46
|
+
* 若显式传入的 binding 不存在,抛出 ActionDockError(NOT_FOUND) 并在 details 中携带 action 与 binding。
|
|
47
|
+
*/
|
|
48
|
+
export declare function resolveActionBinding(bindings: ActionBindings, ref: string, binding?: string): ActionBindingTarget | undefined;
|
|
49
|
+
/**
|
|
50
|
+
* 检测指定动作是否存在任意 remote 类型的命名绑定。
|
|
51
|
+
* 仅做完全限定精确匹配,供构建工具快速识别可外置的跨节点依赖。
|
|
52
|
+
*/
|
|
53
|
+
export declare function hasRemoteActionBinding(bindings: ActionBindings, ref: string): boolean;
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { ActionDockError, INVALID_ACTION_REF, INVALID_ARGUMENT, INVALID_JSON, NOT_FOUND, } from "../errors.js";
|
|
4
|
+
import { parseActionRef } from "../catalog/resolve-action.js";
|
|
5
|
+
import { PACKAGE_ID_REGEX } from "../utils/index.js";
|
|
6
|
+
import { ACTION_ID_REGEX } from "../project/manifest.js";
|
|
7
|
+
/** 绑定文件名与目录常量 */
|
|
8
|
+
export const BINDINGS_DIR_NAME = ".actiondock";
|
|
9
|
+
export const BINDINGS_FILE_NAME = "bindings.json";
|
|
10
|
+
/** 绑定名称正则约束(稳定简单标识符) */
|
|
11
|
+
export const ACTION_BINDING_NAME_REGEX = /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/;
|
|
12
|
+
/** 环境配置名称正则约束 */
|
|
13
|
+
export const PROFILE_NAME_REGEX = /^[a-zA-Z0-9_\-\.]+$/;
|
|
14
|
+
/** 空冻结绑定快照单一事实源 */
|
|
15
|
+
const EMPTY_ACTION_BINDINGS = Object.freeze(Object.create(null));
|
|
16
|
+
/**
|
|
17
|
+
* 校验并规范化完全限定的动作引用字符串。
|
|
18
|
+
* 铁律:必须为完全限定包与动作,禁止通配符、路径逃逸、空白与冒号。
|
|
19
|
+
*/
|
|
20
|
+
export function validateCanonicalActionRef(ref) {
|
|
21
|
+
if (typeof ref !== "string") {
|
|
22
|
+
throw new ActionDockError(INVALID_ACTION_REF, "Invalid action reference: must be a string");
|
|
23
|
+
}
|
|
24
|
+
if (ref.length === 0 || /\s/.test(ref)) {
|
|
25
|
+
throw new ActionDockError(INVALID_ACTION_REF, `Invalid action reference '${ref}': cannot be empty or contain whitespace`);
|
|
26
|
+
}
|
|
27
|
+
if (ref.includes(":")) {
|
|
28
|
+
throw new ActionDockError(INVALID_ACTION_REF, `Invalid action reference '${ref}': colon syntax is not permitted, use '/'`);
|
|
29
|
+
}
|
|
30
|
+
if (ref.includes("*") || ref.includes("?")) {
|
|
31
|
+
throw new ActionDockError(INVALID_ACTION_REF, `Invalid action reference '${ref}': wildcards are not permitted`);
|
|
32
|
+
}
|
|
33
|
+
if (ref.includes("..")) {
|
|
34
|
+
throw new ActionDockError(INVALID_ACTION_REF, `Invalid action reference '${ref}': path traversal is not permitted`);
|
|
35
|
+
}
|
|
36
|
+
if (!ref.includes("/")) {
|
|
37
|
+
throw new ActionDockError(INVALID_ACTION_REF, `Invalid action reference '${ref}': must be fully qualified with package and action (e.g. 'pkg/action')`);
|
|
38
|
+
}
|
|
39
|
+
const parsed = parseActionRef(ref);
|
|
40
|
+
if (!parsed.packageId || !parsed.actionId) {
|
|
41
|
+
throw new ActionDockError(INVALID_ACTION_REF, `Invalid action reference '${ref}': must be fully qualified with package and action`);
|
|
42
|
+
}
|
|
43
|
+
const isScoped = /^@[a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-]+$/.test(parsed.packageId);
|
|
44
|
+
if (!PACKAGE_ID_REGEX.test(parsed.packageId) && !isScoped) {
|
|
45
|
+
throw new ActionDockError(INVALID_ACTION_REF, `Invalid package ID '${parsed.packageId}' in action reference '${ref}'`);
|
|
46
|
+
}
|
|
47
|
+
if (!ACTION_ID_REGEX.test(parsed.actionId)) {
|
|
48
|
+
throw new ActionDockError(INVALID_ACTION_REF, `Invalid action ID '${parsed.actionId}' in action reference '${ref}'`);
|
|
49
|
+
}
|
|
50
|
+
return `${parsed.packageId}/${parsed.actionId}`;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* 校验绑定名称合法性。
|
|
54
|
+
*/
|
|
55
|
+
export function validateBindingName(name, actionRef) {
|
|
56
|
+
if (typeof name !== "string") {
|
|
57
|
+
throw new ActionDockError(INVALID_ARGUMENT, "Invalid binding name: must be a string");
|
|
58
|
+
}
|
|
59
|
+
if (name.length === 0 || /\s/.test(name)) {
|
|
60
|
+
throw new ActionDockError(INVALID_ARGUMENT, `Invalid binding name '${name}': cannot be empty or contain whitespace`);
|
|
61
|
+
}
|
|
62
|
+
if (name === "__proto__" || name === "constructor" || name === "prototype") {
|
|
63
|
+
throw new ActionDockError(INVALID_ARGUMENT, `Invalid binding name '${name}': prototype property names are forbidden`);
|
|
64
|
+
}
|
|
65
|
+
if (!ACTION_BINDING_NAME_REGEX.test(name)) {
|
|
66
|
+
const actionMsg = actionRef ? ` for action '${actionRef}'` : "";
|
|
67
|
+
throw new ActionDockError(INVALID_ARGUMENT, `Invalid binding name '${name}'${actionMsg}: must match ${ACTION_BINDING_NAME_REGEX}`);
|
|
68
|
+
}
|
|
69
|
+
return name;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* 校验单个绑定目标对象的合法性并返回深拷贝冻结快照。
|
|
73
|
+
*/
|
|
74
|
+
export function validateBindingTarget(target, bindingName, actionRef) {
|
|
75
|
+
if (target === null || typeof target !== "object" || Array.isArray(target)) {
|
|
76
|
+
throw new ActionDockError(INVALID_ARGUMENT, `Invalid binding target for action '${actionRef}' binding '${bindingName}': expected a JSON object`);
|
|
77
|
+
}
|
|
78
|
+
if (!Object.prototype.hasOwnProperty.call(target, "type")) {
|
|
79
|
+
throw new ActionDockError(INVALID_ARGUMENT, `Missing 'type' in binding '${bindingName}' for action '${actionRef}'`);
|
|
80
|
+
}
|
|
81
|
+
const keys = Object.keys(target);
|
|
82
|
+
const type = target.type;
|
|
83
|
+
if (type === "local") {
|
|
84
|
+
for (const key of keys) {
|
|
85
|
+
if (!Object.prototype.hasOwnProperty.call(target, key))
|
|
86
|
+
continue;
|
|
87
|
+
if (key !== "type") {
|
|
88
|
+
throw new ActionDockError(INVALID_ARGUMENT, `Invalid property '${key}' in local binding '${bindingName}' for action '${actionRef}': local targets do not permit extra properties`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return Object.freeze({ type: "local" });
|
|
92
|
+
}
|
|
93
|
+
if (type === "remote") {
|
|
94
|
+
for (const key of keys) {
|
|
95
|
+
if (!Object.prototype.hasOwnProperty.call(target, key))
|
|
96
|
+
continue;
|
|
97
|
+
if (key !== "type" && key !== "profile") {
|
|
98
|
+
throw new ActionDockError(INVALID_ARGUMENT, `Invalid property '${key}' in remote binding '${bindingName}' for action '${actionRef}': remote targets only permit 'type' and 'profile'`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (!Object.prototype.hasOwnProperty.call(target, "profile")) {
|
|
102
|
+
throw new ActionDockError(INVALID_ARGUMENT, `Missing 'profile' in remote binding '${bindingName}' for action '${actionRef}'`);
|
|
103
|
+
}
|
|
104
|
+
const profile = target.profile;
|
|
105
|
+
if (typeof profile !== "string" ||
|
|
106
|
+
profile.length === 0 ||
|
|
107
|
+
/\s/.test(profile) ||
|
|
108
|
+
!PROFILE_NAME_REGEX.test(profile)) {
|
|
109
|
+
throw new ActionDockError(INVALID_ARGUMENT, `Invalid profile '${String(profile)}' in remote binding '${bindingName}' for action '${actionRef}': profile must be non-empty and cannot contain whitespace`);
|
|
110
|
+
}
|
|
111
|
+
return Object.freeze({ type: "remote", profile });
|
|
112
|
+
}
|
|
113
|
+
throw new ActionDockError(INVALID_ARGUMENT, `Invalid target type '${String(type)}' in binding '${bindingName}' for action '${actionRef}': expected 'local' or 'remote'`);
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* 校验并深拷贝冻结动作绑定配置快照。
|
|
117
|
+
*/
|
|
118
|
+
export function validateActionBindings(value) {
|
|
119
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
120
|
+
throw new ActionDockError(INVALID_ARGUMENT, "Invalid action bindings: expected a JSON object");
|
|
121
|
+
}
|
|
122
|
+
const result = Object.create(null);
|
|
123
|
+
for (const rawRef of Object.keys(value)) {
|
|
124
|
+
if (!Object.prototype.hasOwnProperty.call(value, rawRef)) {
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
const canonicalRef = validateCanonicalActionRef(rawRef);
|
|
128
|
+
const rawGroup = value[rawRef];
|
|
129
|
+
if (rawGroup === null || typeof rawGroup !== "object" || Array.isArray(rawGroup)) {
|
|
130
|
+
throw new ActionDockError(INVALID_ARGUMENT, `Invalid bindings group for action '${canonicalRef}': expected a JSON object`);
|
|
131
|
+
}
|
|
132
|
+
const group = Object.create(null);
|
|
133
|
+
for (const rawBindingName of Object.keys(rawGroup)) {
|
|
134
|
+
if (!Object.prototype.hasOwnProperty.call(rawGroup, rawBindingName)) {
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
const bindingName = validateBindingName(rawBindingName, canonicalRef);
|
|
138
|
+
const rawTarget = rawGroup[rawBindingName];
|
|
139
|
+
const target = validateBindingTarget(rawTarget, bindingName, canonicalRef);
|
|
140
|
+
group[bindingName] = target;
|
|
141
|
+
}
|
|
142
|
+
result[canonicalRef] = Object.freeze(group);
|
|
143
|
+
}
|
|
144
|
+
return Object.freeze(result);
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* 从调用方项目目录读取动作绑定配置。
|
|
148
|
+
* 若文件不存在则返回空冻结映射,其余 IO 或 JSON 异常统一转换为 ActionDockError 透传。
|
|
149
|
+
*/
|
|
150
|
+
export function loadActionBindings(projectRoot) {
|
|
151
|
+
if (typeof projectRoot !== "string" || projectRoot.length === 0) {
|
|
152
|
+
throw new ActionDockError(INVALID_ARGUMENT, "Invalid projectRoot: must be a non-empty string");
|
|
153
|
+
}
|
|
154
|
+
const filePath = join(projectRoot, BINDINGS_DIR_NAME, BINDINGS_FILE_NAME);
|
|
155
|
+
let raw;
|
|
156
|
+
try {
|
|
157
|
+
raw = readFileSync(filePath, "utf-8");
|
|
158
|
+
}
|
|
159
|
+
catch (err) {
|
|
160
|
+
if (err?.code === "ENOENT") {
|
|
161
|
+
return EMPTY_ACTION_BINDINGS;
|
|
162
|
+
}
|
|
163
|
+
throw new ActionDockError(INVALID_ARGUMENT, `Failed to read action bindings at ${filePath}: ${err.message}`);
|
|
164
|
+
}
|
|
165
|
+
let parsed;
|
|
166
|
+
try {
|
|
167
|
+
parsed = JSON.parse(raw);
|
|
168
|
+
}
|
|
169
|
+
catch (err) {
|
|
170
|
+
throw new ActionDockError(INVALID_JSON, `Failed to parse action bindings JSON at ${filePath}: ${err.message}`);
|
|
171
|
+
}
|
|
172
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
173
|
+
throw new ActionDockError(INVALID_ARGUMENT, `Invalid action bindings format in ${filePath}: expected a JSON object`);
|
|
174
|
+
}
|
|
175
|
+
const rootKeys = Object.keys(parsed);
|
|
176
|
+
for (const key of rootKeys) {
|
|
177
|
+
if (!Object.prototype.hasOwnProperty.call(parsed, key)) {
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
if (key === "bindings") {
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
if (key === "schemaVersion") {
|
|
184
|
+
const v = parsed.schemaVersion;
|
|
185
|
+
if (v !== 1) {
|
|
186
|
+
throw new ActionDockError(INVALID_ARGUMENT, `Invalid schemaVersion '${String(v)}' in ${filePath}: only schemaVersion 1 is supported`);
|
|
187
|
+
}
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
throw new ActionDockError(INVALID_ARGUMENT, `Unknown property '${key}' in ${filePath}: only 'bindings' is allowed`);
|
|
191
|
+
}
|
|
192
|
+
if (!Object.prototype.hasOwnProperty.call(parsed, "bindings")) {
|
|
193
|
+
throw new ActionDockError(INVALID_ARGUMENT, `Missing 'bindings' property in ${filePath}`);
|
|
194
|
+
}
|
|
195
|
+
return validateActionBindings(parsed.bindings);
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* 解析目标动作引用在当前项目中的具体绑定。
|
|
199
|
+
* 未显式传入 binding 则仅检索 default;若 default 亦不存在则返回 undefined。
|
|
200
|
+
* 若显式传入的 binding 不存在,抛出 ActionDockError(NOT_FOUND) 并在 details 中携带 action 与 binding。
|
|
201
|
+
*/
|
|
202
|
+
export function resolveActionBinding(bindings, ref, binding) {
|
|
203
|
+
const canonicalRef = validateCanonicalActionRef(ref);
|
|
204
|
+
const group = bindings &&
|
|
205
|
+
typeof bindings === "object" &&
|
|
206
|
+
Object.prototype.hasOwnProperty.call(bindings, canonicalRef)
|
|
207
|
+
? bindings[canonicalRef]
|
|
208
|
+
: undefined;
|
|
209
|
+
if (binding === undefined) {
|
|
210
|
+
if (!group) {
|
|
211
|
+
return undefined;
|
|
212
|
+
}
|
|
213
|
+
return Object.prototype.hasOwnProperty.call(group, "default")
|
|
214
|
+
? group["default"]
|
|
215
|
+
: undefined;
|
|
216
|
+
}
|
|
217
|
+
if (!group || !Object.prototype.hasOwnProperty.call(group, binding)) {
|
|
218
|
+
throw new ActionDockError(NOT_FOUND, `Action binding '${binding}' not found for action '${canonicalRef}'`, { action: canonicalRef, binding });
|
|
219
|
+
}
|
|
220
|
+
return group[binding];
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* 检测指定动作是否存在任意 remote 类型的命名绑定。
|
|
224
|
+
* 仅做完全限定精确匹配,供构建工具快速识别可外置的跨节点依赖。
|
|
225
|
+
*/
|
|
226
|
+
export function hasRemoteActionBinding(bindings, ref) {
|
|
227
|
+
const canonicalRef = validateCanonicalActionRef(ref);
|
|
228
|
+
if (!bindings || typeof bindings !== "object") {
|
|
229
|
+
return false;
|
|
230
|
+
}
|
|
231
|
+
if (!Object.prototype.hasOwnProperty.call(bindings, canonicalRef)) {
|
|
232
|
+
return false;
|
|
233
|
+
}
|
|
234
|
+
const group = bindings[canonicalRef];
|
|
235
|
+
if (!group || typeof group !== "object") {
|
|
236
|
+
return false;
|
|
237
|
+
}
|
|
238
|
+
for (const name of Object.keys(group)) {
|
|
239
|
+
if (!Object.prototype.hasOwnProperty.call(group, name)) {
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
const target = group[name];
|
|
243
|
+
if (target?.type === "remote") {
|
|
244
|
+
return true;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
return false;
|
|
248
|
+
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { type ErrorCode } from "../errors.js";
|
|
2
2
|
import type { PackageGraph } from "../catalog/graph.js";
|
|
3
|
+
/** 动作调用栈键的单一事实源,执行目标作用域不占用额外深度。 */
|
|
4
|
+
export declare function createActionCallKey(actionId: string, packageId?: string, targetScope?: string): string;
|
|
3
5
|
/**
|
|
4
6
|
* 调用治理策略初始化选项。
|
|
5
7
|
*/
|
|
@@ -32,6 +34,8 @@ export interface CallerActionInfo {
|
|
|
32
34
|
export interface TargetActionInfo {
|
|
33
35
|
packageId: string;
|
|
34
36
|
actionId: string;
|
|
37
|
+
/** 跨执行目标时,即使同名包也必须显式声明依赖。 */
|
|
38
|
+
requiresExplicitUses?: boolean;
|
|
35
39
|
}
|
|
36
40
|
/**
|
|
37
41
|
* 调用治理策略错误描述。
|
|
@@ -127,7 +131,7 @@ export declare class InvocationPolicy {
|
|
|
127
131
|
* @param currentPackageId 当前执行发起方包标识
|
|
128
132
|
* @returns 包含规范化 callKey 与可选错误的结构体
|
|
129
133
|
*/
|
|
130
|
-
checkCycle(callStack: readonly string[], targetActionId: string, targetPackageId?: string, currentPackageId?: string): CycleCheckResult;
|
|
134
|
+
checkCycle(callStack: readonly string[], targetActionId: string, targetPackageId?: string, currentPackageId?: string, targetScope?: string): CycleCheckResult;
|
|
131
135
|
/**
|
|
132
136
|
* 并发活跃子任务配额检查(Sub-Run Quota Policy)。
|
|
133
137
|
*
|