@actiondock/core 2.18.0 → 2.20.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/client/runs.d.ts +9 -0
- package/dist/client/runs.js +7 -0
- package/dist/errors.d.ts +3 -1
- package/dist/errors.js +2 -0
- package/dist/execution/service.js +8 -5
- package/dist/execution/types.d.ts +3 -3
- package/dist/host/host.d.ts +9 -1
- package/dist/host/host.js +266 -45
- package/dist/host/types.d.ts +3 -0
- package/dist/index.d.ts +4 -3
- package/dist/index.js +1 -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 +2 -0
- package/dist/invocation/types.js +1 -0
- package/dist/package/index.d.ts +1 -1
- package/dist/package/runtime.js +105 -15
- package/dist/platform/types.d.ts +48 -1
- package/dist/project/config-declarations.d.ts +61 -0
- package/dist/project/config-declarations.js +297 -0
- package/dist/project/index.d.ts +5 -2
- package/dist/project/index.js +5 -2
- package/dist/project/init.js +2 -2
- package/dist/project/loader.js +4 -0
- package/dist/project/manifest.js +10 -0
- package/dist/project/types.d.ts +30 -0
- package/dist/runtime/context.d.ts +6 -3
- package/dist/runtime/context.js +60 -13
- package/dist/runtime/env.js +15 -1
- 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/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 +65 -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 +112 -18
- package/dist/server/server.d.ts +2 -1
- package/dist/server/server.js +38 -55
- package/dist/server/types.d.ts +6 -6
- package/dist/service/factory.js +1 -0
- package/dist/service/remote.js +28 -29
- package/dist/service/types.d.ts +34 -5
- package/dist/service/types.js +8 -0
- package/dist/storage/mask.d.ts +33 -6
- package/dist/storage/mask.js +298 -16
- package/dist/storage/sqlite-runs.d.ts +5 -17
- package/dist/storage/sqlite-runs.js +127 -5
- package/dist/storage/sqlite.d.ts +5 -17
- package/dist/storage/sqlite.js +2 -2
- package/dist/storage/types.d.ts +31 -16
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +2 -2
package/dist/package/runtime.js
CHANGED
|
@@ -5,7 +5,8 @@ import { createNodePlatform } from "../platform/index.js";
|
|
|
5
5
|
import { findProjectRoot, loadProjectConfig } from "../project/loader.js";
|
|
6
6
|
import { RuntimeConfig } from "../runtime/context.js";
|
|
7
7
|
import { normalizeActionCollection } from "../runtime/action-collection.js";
|
|
8
|
-
import {
|
|
8
|
+
import { redactConfigValue, sanitizeConfigDefinitions } from "../storage/mask.js";
|
|
9
|
+
import { assertConcreteConfigKey, assertValidConfigDeclarations, isConfigTemplate, resolveConfigDeclaration, validateConfigValue, } from "../project/config-declarations.js";
|
|
9
10
|
import { decodeStateKey, SqliteRuntimeStorage } from "../storage/sqlite.js";
|
|
10
11
|
import { createPackageIdentity } from "../runtime/identity.js";
|
|
11
12
|
import { ACTION_NOT_FOUND, ActionDockError, CAPABILITY_UNAVAILABLE, INVALID_ARGUMENT, INVOCATION_UNSUPPORTED, NOT_FOUND, SERVICE_CLOSED, } from "../errors.js";
|
|
@@ -127,6 +128,9 @@ export class DefaultPackageRuntime {
|
|
|
127
128
|
};
|
|
128
129
|
}
|
|
129
130
|
this.packageRoot = packageRoot;
|
|
131
|
+
if (projectConfig.config !== undefined) {
|
|
132
|
+
assertValidConfigDeclarations(projectConfig.config);
|
|
133
|
+
}
|
|
130
134
|
this.projectConfig = projectConfig;
|
|
131
135
|
// 兼容未在公开选项契约中显式声明的包实例标识与代系标识内部参数透传
|
|
132
136
|
this.identity = options.identity || createPackageIdentity({
|
|
@@ -679,9 +683,18 @@ export class DefaultPackageRuntime {
|
|
|
679
683
|
}
|
|
680
684
|
async listConfig() {
|
|
681
685
|
this.assertOpen();
|
|
682
|
-
const
|
|
683
|
-
const
|
|
684
|
-
const
|
|
686
|
+
const packageStorage = await this.prepareStorageAsync();
|
|
687
|
+
const globalStorage = await this.ensureGlobalStorage();
|
|
688
|
+
const declaredKeys = Object.keys(this.projectConfig.config || {}).filter((k) => !isConfigTemplate(k));
|
|
689
|
+
const packageStoredKeys = Object.keys(packageStorage.listConfig());
|
|
690
|
+
const globalStoredKeys = Object.keys(globalStorage.listConfig());
|
|
691
|
+
const overrideKeys = Object.keys(this.options.configOverrides || {});
|
|
692
|
+
const allKeys = Array.from(new Set([
|
|
693
|
+
...declaredKeys,
|
|
694
|
+
...packageStoredKeys,
|
|
695
|
+
...globalStoredKeys,
|
|
696
|
+
...overrideKeys,
|
|
697
|
+
])).filter((k) => !isConfigTemplate(k));
|
|
685
698
|
const views = [];
|
|
686
699
|
for (const key of allKeys) {
|
|
687
700
|
const item = await this.getConfig(key);
|
|
@@ -693,27 +706,33 @@ export class DefaultPackageRuntime {
|
|
|
693
706
|
}
|
|
694
707
|
async getConfig(key) {
|
|
695
708
|
this.assertOpen();
|
|
696
|
-
|
|
697
|
-
const
|
|
709
|
+
assertConcreteConfigKey(key);
|
|
710
|
+
const decl = resolveConfigDeclaration(this.projectConfig.config, key);
|
|
698
711
|
// 委托 RuntimeConfig 五层优先级链单一事实源,避免重复实现解析链
|
|
699
712
|
const resolved = this.ensureRuntimeConfig().describe(key);
|
|
700
|
-
//
|
|
701
|
-
|
|
702
|
-
const configured = resolved.source !== "default";
|
|
713
|
+
// configured 显式判定:仅真实显式来源(override/package/global/env)为 true,
|
|
714
|
+
// default 与整链未命中(source null)均为 false,不得依赖与 "default" 的不等比较
|
|
715
|
+
const configured = resolved.source !== null && resolved.source !== "default";
|
|
716
|
+
const redacted = redactConfigValue(resolved.value, decl?.item);
|
|
703
717
|
return {
|
|
704
718
|
key,
|
|
705
719
|
configured,
|
|
706
|
-
secret:
|
|
707
|
-
source,
|
|
708
|
-
value:
|
|
720
|
+
secret: redacted.secret,
|
|
721
|
+
source: resolved.source,
|
|
722
|
+
value: redacted.value,
|
|
723
|
+
redactedPaths: redacted.redactedPaths,
|
|
709
724
|
};
|
|
710
725
|
}
|
|
711
726
|
async setConfig(key, value) {
|
|
712
727
|
this.assertOpen();
|
|
728
|
+
assertConcreteConfigKey(key);
|
|
729
|
+
const decl = resolveConfigDeclaration(this.projectConfig.config, key);
|
|
730
|
+
validateConfigValue(key, value, decl?.item);
|
|
713
731
|
await (await this.prepareStorageAsync()).setConfig(key, value);
|
|
714
732
|
}
|
|
715
733
|
async deleteConfig(key) {
|
|
716
734
|
this.assertOpen();
|
|
735
|
+
assertConcreteConfigKey(key);
|
|
717
736
|
return await (await this.prepareStorageAsync()).deleteConfig(key);
|
|
718
737
|
}
|
|
719
738
|
/**
|
|
@@ -851,13 +870,31 @@ export class DefaultPackageRuntime {
|
|
|
851
870
|
if (typeof storage.listRunsByRequestIds !== "function") {
|
|
852
871
|
return [];
|
|
853
872
|
}
|
|
854
|
-
let records = storage.listRunsByRequestIds(options.requestIds);
|
|
873
|
+
let records = storage.listRunsByRequestIds(options.requestIds, options);
|
|
855
874
|
if (options.actionId) {
|
|
856
875
|
records = records.filter((r) => r.actionId === options.actionId);
|
|
857
876
|
}
|
|
858
877
|
if (options.status) {
|
|
859
878
|
records = records.filter((r) => r.status === options.status);
|
|
860
879
|
}
|
|
880
|
+
if (options.channel) {
|
|
881
|
+
records = records.filter((r) => r.origin?.channel === options.channel);
|
|
882
|
+
}
|
|
883
|
+
if (options.view) {
|
|
884
|
+
records = records.filter((r) => r.origin?.viewName === options.view);
|
|
885
|
+
}
|
|
886
|
+
if (options.since) {
|
|
887
|
+
records = records.filter((r) => r.startedAt >= options.since);
|
|
888
|
+
}
|
|
889
|
+
if (options.until) {
|
|
890
|
+
records = records.filter((r) => r.startedAt <= options.until);
|
|
891
|
+
}
|
|
892
|
+
if (options.rootRunId) {
|
|
893
|
+
records = records.filter((r) => r.rootRunId === options.rootRunId);
|
|
894
|
+
}
|
|
895
|
+
if (options.parentRunId) {
|
|
896
|
+
records = records.filter((r) => r.parentRunId === options.parentRunId);
|
|
897
|
+
}
|
|
861
898
|
records.sort((a, b) => new Date(b.startedAt).getTime() - new Date(a.startedAt).getTime());
|
|
862
899
|
if (typeof options.offset === "number" && options.offset > 0) {
|
|
863
900
|
records = records.slice(options.offset);
|
|
@@ -870,6 +907,12 @@ export class DefaultPackageRuntime {
|
|
|
870
907
|
const records = storage.listRuns({
|
|
871
908
|
actionId: options?.actionId,
|
|
872
909
|
status: options?.status,
|
|
910
|
+
channel: options?.channel,
|
|
911
|
+
view: options?.view,
|
|
912
|
+
since: options?.since,
|
|
913
|
+
until: options?.until,
|
|
914
|
+
rootRunId: options?.rootRunId,
|
|
915
|
+
parentRunId: options?.parentRunId,
|
|
873
916
|
limit: options?.limit,
|
|
874
917
|
offset: options?.offset,
|
|
875
918
|
});
|
|
@@ -897,18 +940,42 @@ export class DefaultPackageRuntime {
|
|
|
897
940
|
return storage.countRuns({
|
|
898
941
|
actionId: options?.actionId,
|
|
899
942
|
status: options?.status,
|
|
943
|
+
channel: options?.channel,
|
|
944
|
+
view: options?.view,
|
|
945
|
+
since: options?.since,
|
|
946
|
+
until: options?.until,
|
|
947
|
+
rootRunId: options?.rootRunId,
|
|
948
|
+
parentRunId: options?.parentRunId,
|
|
900
949
|
requestIds: options?.requestIds,
|
|
901
950
|
});
|
|
902
951
|
}
|
|
903
952
|
if (options?.requestIds && options.requestIds.length > 0) {
|
|
904
953
|
if (typeof storage.listRunsByRequestIds === "function") {
|
|
905
|
-
let records = storage.listRunsByRequestIds(options.requestIds);
|
|
954
|
+
let records = storage.listRunsByRequestIds(options.requestIds, options);
|
|
906
955
|
if (options.actionId) {
|
|
907
956
|
records = records.filter((r) => r.actionId === options.actionId);
|
|
908
957
|
}
|
|
909
958
|
if (options.status) {
|
|
910
959
|
records = records.filter((r) => r.status === options.status);
|
|
911
960
|
}
|
|
961
|
+
if (options.channel) {
|
|
962
|
+
records = records.filter((r) => r.origin?.channel === options.channel);
|
|
963
|
+
}
|
|
964
|
+
if (options.view) {
|
|
965
|
+
records = records.filter((r) => r.origin?.viewName === options.view);
|
|
966
|
+
}
|
|
967
|
+
if (options.since) {
|
|
968
|
+
records = records.filter((r) => r.startedAt >= options.since);
|
|
969
|
+
}
|
|
970
|
+
if (options.until) {
|
|
971
|
+
records = records.filter((r) => r.startedAt <= options.until);
|
|
972
|
+
}
|
|
973
|
+
if (options.rootRunId) {
|
|
974
|
+
records = records.filter((r) => r.rootRunId === options.rootRunId);
|
|
975
|
+
}
|
|
976
|
+
if (options.parentRunId) {
|
|
977
|
+
records = records.filter((r) => r.parentRunId === options.parentRunId);
|
|
978
|
+
}
|
|
912
979
|
return records.length;
|
|
913
980
|
}
|
|
914
981
|
return 0;
|
|
@@ -916,9 +983,32 @@ export class DefaultPackageRuntime {
|
|
|
916
983
|
const records = storage.listRuns({
|
|
917
984
|
actionId: options?.actionId,
|
|
918
985
|
status: options?.status,
|
|
986
|
+
channel: options?.channel,
|
|
987
|
+
view: options?.view,
|
|
988
|
+
since: options?.since,
|
|
989
|
+
until: options?.until,
|
|
990
|
+
rootRunId: options?.rootRunId,
|
|
991
|
+
parentRunId: options?.parentRunId,
|
|
919
992
|
limit: 100_000,
|
|
920
993
|
});
|
|
921
|
-
|
|
994
|
+
let filtered = records;
|
|
995
|
+
if (options?.actionId)
|
|
996
|
+
filtered = filtered.filter((r) => r.actionId === options.actionId);
|
|
997
|
+
if (options?.status)
|
|
998
|
+
filtered = filtered.filter((r) => r.status === options.status);
|
|
999
|
+
if (options?.channel)
|
|
1000
|
+
filtered = filtered.filter((r) => r.origin?.channel === options.channel);
|
|
1001
|
+
if (options?.view)
|
|
1002
|
+
filtered = filtered.filter((r) => r.origin?.viewName === options.view);
|
|
1003
|
+
if (options?.since)
|
|
1004
|
+
filtered = filtered.filter((r) => r.startedAt >= options.since);
|
|
1005
|
+
if (options?.until)
|
|
1006
|
+
filtered = filtered.filter((r) => r.startedAt <= options.until);
|
|
1007
|
+
if (options?.rootRunId)
|
|
1008
|
+
filtered = filtered.filter((r) => r.rootRunId === options.rootRunId);
|
|
1009
|
+
if (options?.parentRunId)
|
|
1010
|
+
filtered = filtered.filter((r) => r.parentRunId === options.parentRunId);
|
|
1011
|
+
return filtered.length;
|
|
922
1012
|
}
|
|
923
1013
|
async clearRuns(options) {
|
|
924
1014
|
this.assertOpen();
|
package/dist/platform/types.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import type { ProcessAPI } from "@actiondock/sdk";
|
|
1
|
+
import type { ActionInvokeOptions, ProcessAPI } from "@actiondock/sdk";
|
|
2
2
|
import type { Clock } from "../storage/clock.js";
|
|
3
3
|
import type { ModuleLoader } from "./module-loader.js";
|
|
4
4
|
import type { RunsRetentionPolicy, RuntimeStorage } from "../storage/types.js";
|
|
5
5
|
import type { EventSink } from "../runtime/events.js";
|
|
6
|
+
import type { PackageRuntime } from "../package/types.js";
|
|
6
7
|
/**
|
|
7
8
|
* 跨运行时持久化存储工厂配置选项。
|
|
8
9
|
*/
|
|
@@ -42,6 +43,48 @@ export interface StorageFactory {
|
|
|
42
43
|
*/
|
|
43
44
|
createGlobalStorage(options?: GlobalStorageFactoryOptions): RuntimeStorage;
|
|
44
45
|
}
|
|
46
|
+
/**
|
|
47
|
+
* 动作调用目标解析结果。
|
|
48
|
+
*/
|
|
49
|
+
export interface InvocationTargetResolution {
|
|
50
|
+
/** 目标 PackageRuntime 实例 */
|
|
51
|
+
readonly runtime: PackageRuntime;
|
|
52
|
+
/** 可选调用作用域,用于区分调用目标与环路死锁检测身份 */
|
|
53
|
+
readonly scope?: string;
|
|
54
|
+
/** 是否强制要求调用方显式声明 uses 依赖 */
|
|
55
|
+
readonly requiresExplicitUses?: boolean;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* 动作调用发起方元数据信息。
|
|
59
|
+
*/
|
|
60
|
+
export interface InvocationCallerInfo {
|
|
61
|
+
/** 调用发起方包标识 */
|
|
62
|
+
readonly packageId: string;
|
|
63
|
+
/** 调用发起方 Action 动作标识 */
|
|
64
|
+
readonly actionId?: string;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* 运行时平台调用目标解析器端口契约。
|
|
68
|
+
* 允许底层运行时平台在 Host 选择执行目标阶段介入目标解析,提供自定义 PackageRuntime。
|
|
69
|
+
*/
|
|
70
|
+
export interface InvocationTargetResolver {
|
|
71
|
+
/**
|
|
72
|
+
* 解析指定 Action 引用与调用选项对应的目标 PackageRuntime。
|
|
73
|
+
*
|
|
74
|
+
* @param action 规范化 Action 引用元数据
|
|
75
|
+
* @param options 动作调用选项(包含命名 binding 等)
|
|
76
|
+
* @param caller 发起调用的调用方元数据
|
|
77
|
+
* @returns 目标解析结果;返回的 Runtime 由 Host 接管生命周期;未命中返回 undefined 交由默认主链处理
|
|
78
|
+
*/
|
|
79
|
+
resolve(action: {
|
|
80
|
+
packageId: string;
|
|
81
|
+
actionId: string;
|
|
82
|
+
}, options: ActionInvokeOptions | undefined, caller: InvocationCallerInfo): InvocationTargetResolution | undefined;
|
|
83
|
+
/**
|
|
84
|
+
* 清理端口缓存和附属资源,不重复关闭已移交 Host 的 Runtime。
|
|
85
|
+
*/
|
|
86
|
+
close?(): Promise<void>;
|
|
87
|
+
}
|
|
45
88
|
/**
|
|
46
89
|
* 运行时底层环境核心标准契约。
|
|
47
90
|
* 提供统一的运行时契约,屏蔽宿主平台与测试沙箱环境的实现差异。
|
|
@@ -61,4 +104,8 @@ export interface RuntimePlatform {
|
|
|
61
104
|
* 可选执行事件接收器,宿主可注入,缺省由上层默认构造。
|
|
62
105
|
*/
|
|
63
106
|
readonly eventSink?: EventSink;
|
|
107
|
+
/**
|
|
108
|
+
* 可选动作调用目标解析器端口,用于宿主环境自定义动作调用路由(例如测试具名 Mock 模拟目标)。
|
|
109
|
+
*/
|
|
110
|
+
readonly invocationTargets?: InvocationTargetResolver;
|
|
64
111
|
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { ConfigItemDefinition } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* 判定指定配置键名是否为通配模板键。
|
|
4
|
+
*
|
|
5
|
+
* @param key 待检测的配置键名
|
|
6
|
+
*/
|
|
7
|
+
export declare function isConfigTemplate(key: string): boolean;
|
|
8
|
+
/**
|
|
9
|
+
* 断言键名为具体配置键,拒绝空字符串、非字符串以及模板通配键。
|
|
10
|
+
*
|
|
11
|
+
* @param key 待检查的具体配置键名
|
|
12
|
+
*/
|
|
13
|
+
export declare function assertConcreteConfigKey(key: string): void;
|
|
14
|
+
/**
|
|
15
|
+
* 解析配置项命中结果结构。
|
|
16
|
+
*/
|
|
17
|
+
export interface ResolvedConfigDeclaration {
|
|
18
|
+
/** 命中声明所登记的原始键名(具体键或模板键) */
|
|
19
|
+
key: string;
|
|
20
|
+
/** 声明的配置定义元数据 */
|
|
21
|
+
item: ConfigItemDefinition;
|
|
22
|
+
/** 若通过模板匹配命中,则返回命中的模板表达式 */
|
|
23
|
+
template?: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* 解析并匹配指定配置键在声明字典中的定义项。
|
|
27
|
+
*
|
|
28
|
+
* 匹配优先级规则:
|
|
29
|
+
* - 具体声明键严格优先于任何模板通配键;
|
|
30
|
+
* - 模板只允许包含单个星号(*),匹配一个或多个合法标识字符([A-Za-z0-9_.-]);
|
|
31
|
+
* - 多个模板同时命中时,优先选取前缀最长者,前缀长度相同时选取后缀最长者,杜绝对象枚举遍历顺序影响。
|
|
32
|
+
*
|
|
33
|
+
* @param config 声明的配置定义字典
|
|
34
|
+
* @param key 实际访问或配置的具体键名
|
|
35
|
+
*/
|
|
36
|
+
export declare function resolveConfigDeclaration(config: Record<string, ConfigItemDefinition> | undefined, key: string): ResolvedConfigDeclaration | undefined;
|
|
37
|
+
/**
|
|
38
|
+
* 校验配置声明集合中的通配模板与受限 Schema 规范。
|
|
39
|
+
* 针对历史无模板、无 Schema 项保持完全兼容。
|
|
40
|
+
*
|
|
41
|
+
* @param config 待校验的配置声明定义
|
|
42
|
+
* @returns 错误信息字符串数组,无错误返回空数组
|
|
43
|
+
*/
|
|
44
|
+
export declare function validateConfigDeclarations(config: unknown): string[];
|
|
45
|
+
/**
|
|
46
|
+
* 断言配置声明集合完全合法。
|
|
47
|
+
* 若存在非法模板或不合规 Schema 抛出 MANIFEST_INVALID 异常。
|
|
48
|
+
*
|
|
49
|
+
* @param config 待校验的配置声明定义
|
|
50
|
+
*/
|
|
51
|
+
export declare function assertValidConfigDeclarations(config: unknown): void;
|
|
52
|
+
/**
|
|
53
|
+
* 校验指定配置数据值是否符合绑定的 Schema 契约。
|
|
54
|
+
* 仅在声明了 schema 且数据值不为 undefined 时执行校验。
|
|
55
|
+
* 错误信息输出键名与失败路径原因,严禁包含实际数据值。
|
|
56
|
+
*
|
|
57
|
+
* @param key 配置项键名
|
|
58
|
+
* @param value 待校验的具体数据值
|
|
59
|
+
* @param item 配置项元数据定义
|
|
60
|
+
*/
|
|
61
|
+
export declare function validateConfigValue(key: string, value: unknown, item?: ConfigItemDefinition): void;
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
import { ActionDockError, CONFIG_VALUE_INVALID, INVALID_ARGUMENT, MANIFEST_INVALID, } from "../errors.js";
|
|
2
|
+
import { validateSchema, validateSchemaOnly } from "../schema/validator.js";
|
|
3
|
+
/**
|
|
4
|
+
* 判定指定配置键名是否为通配模板键。
|
|
5
|
+
*
|
|
6
|
+
* @param key 待检测的配置键名
|
|
7
|
+
*/
|
|
8
|
+
export function isConfigTemplate(key) {
|
|
9
|
+
return typeof key === "string" && key.includes("*");
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* 断言键名为具体配置键,拒绝空字符串、非字符串以及模板通配键。
|
|
13
|
+
*
|
|
14
|
+
* @param key 待检查的具体配置键名
|
|
15
|
+
*/
|
|
16
|
+
export function assertConcreteConfigKey(key) {
|
|
17
|
+
if (typeof key !== "string" || key.length === 0 || key.includes("*")) {
|
|
18
|
+
throw new ActionDockError(INVALID_ARGUMENT, `Concrete configuration key expected, received invalid key: '${key}'`);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function escapeRegex(str) {
|
|
22
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* 解析并匹配指定配置键在声明字典中的定义项。
|
|
26
|
+
*
|
|
27
|
+
* 匹配优先级规则:
|
|
28
|
+
* - 具体声明键严格优先于任何模板通配键;
|
|
29
|
+
* - 模板只允许包含单个星号(*),匹配一个或多个合法标识字符([A-Za-z0-9_.-]);
|
|
30
|
+
* - 多个模板同时命中时,优先选取前缀最长者,前缀长度相同时选取后缀最长者,杜绝对象枚举遍历顺序影响。
|
|
31
|
+
*
|
|
32
|
+
* @param config 声明的配置定义字典
|
|
33
|
+
* @param key 实际访问或配置的具体键名
|
|
34
|
+
*/
|
|
35
|
+
export function resolveConfigDeclaration(config, key) {
|
|
36
|
+
if (!config || typeof config !== "object" || typeof key !== "string") {
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
// 具体键严格优先
|
|
40
|
+
if (Object.prototype.hasOwnProperty.call(config, key)) {
|
|
41
|
+
return {
|
|
42
|
+
key,
|
|
43
|
+
item: config[key],
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
const candidates = [];
|
|
47
|
+
for (const [declaredKey, item] of Object.entries(config)) {
|
|
48
|
+
if (!isConfigTemplate(declaredKey)) {
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
const parts = declaredKey.split("*");
|
|
52
|
+
if (parts.length !== 2) {
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
const [prefix, suffix] = parts;
|
|
56
|
+
const pattern = new RegExp(`^${escapeRegex(prefix)}([A-Za-z0-9_.-]+)${escapeRegex(suffix)}$`);
|
|
57
|
+
if (pattern.exec(key)?.[0] === key) {
|
|
58
|
+
candidates.push({
|
|
59
|
+
templateKey: declaredKey,
|
|
60
|
+
item,
|
|
61
|
+
prefixLength: prefix.length,
|
|
62
|
+
suffixLength: suffix.length,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (candidates.length === 0) {
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
// 判定最长前缀再最长后缀,稳定按字典序平局兜底
|
|
70
|
+
candidates.sort((a, b) => {
|
|
71
|
+
if (b.prefixLength !== a.prefixLength) {
|
|
72
|
+
return b.prefixLength - a.prefixLength;
|
|
73
|
+
}
|
|
74
|
+
if (b.suffixLength !== a.suffixLength) {
|
|
75
|
+
return b.suffixLength - a.suffixLength;
|
|
76
|
+
}
|
|
77
|
+
return a.templateKey.localeCompare(b.templateKey);
|
|
78
|
+
});
|
|
79
|
+
const best = candidates[0];
|
|
80
|
+
return {
|
|
81
|
+
key: best.templateKey,
|
|
82
|
+
item: best.item,
|
|
83
|
+
template: best.templateKey,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
/** 受限 JSON Schema 允许的白名单关键字集合 */
|
|
87
|
+
const ALLOWED_SCHEMA_KEYWORDS = new Set([
|
|
88
|
+
"type",
|
|
89
|
+
"properties",
|
|
90
|
+
"required",
|
|
91
|
+
"additionalProperties",
|
|
92
|
+
"items",
|
|
93
|
+
"enum",
|
|
94
|
+
"minLength",
|
|
95
|
+
"maxLength",
|
|
96
|
+
"minimum",
|
|
97
|
+
"maximum",
|
|
98
|
+
"secret",
|
|
99
|
+
]);
|
|
100
|
+
/** 模板键中严禁声明的保留字段 */
|
|
101
|
+
const FORBIDDEN_TEMPLATE_FIELDS = ["required", "default", "env"];
|
|
102
|
+
function validateSchemaNode(schemaNode, path, errors) {
|
|
103
|
+
if (typeof schemaNode !== "object" || schemaNode === null || Array.isArray(schemaNode)) {
|
|
104
|
+
errors.push(`Schema at '${path}' must be an object`);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
for (const k of Object.keys(schemaNode)) {
|
|
108
|
+
if (!ALLOWED_SCHEMA_KEYWORDS.has(k)) {
|
|
109
|
+
errors.push(`Schema at '${path}' contains unsupported keyword '${k}'`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
const node = schemaNode;
|
|
113
|
+
if (node.type !== undefined) {
|
|
114
|
+
if (typeof node.type !== "string" ||
|
|
115
|
+
!["object", "array", "string", "integer", "number", "boolean"].includes(node.type)) {
|
|
116
|
+
errors.push(`Schema at '${path}' property 'type' must be one of "object", "array", "string", "integer", "number", "boolean"`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
if (node.properties !== undefined) {
|
|
120
|
+
if (typeof node.properties !== "object" ||
|
|
121
|
+
node.properties === null ||
|
|
122
|
+
Array.isArray(node.properties)) {
|
|
123
|
+
errors.push(`Schema at '${path}' property 'properties' must be an object`);
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
for (const [propName, child] of Object.entries(node.properties)) {
|
|
127
|
+
validateSchemaNode(child, `${path}/properties/${propName}`, errors);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (node.required !== undefined) {
|
|
132
|
+
if (!Array.isArray(node.required) ||
|
|
133
|
+
node.required.some((r) => typeof r !== "string")) {
|
|
134
|
+
errors.push(`Schema at '${path}' property 'required' must be an array of strings`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
if (node.additionalProperties !== undefined) {
|
|
138
|
+
if (typeof node.additionalProperties === "boolean") {
|
|
139
|
+
// 允许布尔配置
|
|
140
|
+
}
|
|
141
|
+
else if (typeof node.additionalProperties === "object" &&
|
|
142
|
+
node.additionalProperties !== null &&
|
|
143
|
+
!Array.isArray(node.additionalProperties)) {
|
|
144
|
+
validateSchemaNode(node.additionalProperties, `${path}/additionalProperties`, errors);
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
errors.push(`Schema at '${path}' property 'additionalProperties' must be a boolean or a schema object`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
if (node.items !== undefined) {
|
|
151
|
+
if (typeof node.items === "object" &&
|
|
152
|
+
node.items !== null &&
|
|
153
|
+
!Array.isArray(node.items)) {
|
|
154
|
+
validateSchemaNode(node.items, `${path}/items`, errors);
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
errors.push(`Schema at '${path}' property 'items' must be a schema object`);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
if (node.enum !== undefined) {
|
|
161
|
+
if (!Array.isArray(node.enum)) {
|
|
162
|
+
errors.push(`Schema at '${path}' property 'enum' must be an array`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
if (node.minLength !== undefined) {
|
|
166
|
+
if (typeof node.minLength !== "number" ||
|
|
167
|
+
!Number.isInteger(node.minLength) ||
|
|
168
|
+
node.minLength < 0) {
|
|
169
|
+
errors.push(`Schema at '${path}' property 'minLength' must be a non-negative integer`);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
if (node.maxLength !== undefined) {
|
|
173
|
+
if (typeof node.maxLength !== "number" ||
|
|
174
|
+
!Number.isInteger(node.maxLength) ||
|
|
175
|
+
node.maxLength < 0) {
|
|
176
|
+
errors.push(`Schema at '${path}' property 'maxLength' must be a non-negative integer`);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
if (node.minimum !== undefined) {
|
|
180
|
+
if (typeof node.minimum !== "number" || !Number.isFinite(node.minimum)) {
|
|
181
|
+
errors.push(`Schema at '${path}' property 'minimum' must be a number`);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
if (node.maximum !== undefined) {
|
|
185
|
+
if (typeof node.maximum !== "number" || !Number.isFinite(node.maximum)) {
|
|
186
|
+
errors.push(`Schema at '${path}' property 'maximum' must be a number`);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
if (node.secret !== undefined) {
|
|
190
|
+
if (typeof node.secret !== "boolean") {
|
|
191
|
+
errors.push(`Schema at '${path}' property 'secret' must be a boolean`);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* 校验配置声明集合中的通配模板与受限 Schema 规范。
|
|
197
|
+
* 针对历史无模板、无 Schema 项保持完全兼容。
|
|
198
|
+
*
|
|
199
|
+
* @param config 待校验的配置声明定义
|
|
200
|
+
* @returns 错误信息字符串数组,无错误返回空数组
|
|
201
|
+
*/
|
|
202
|
+
export function validateConfigDeclarations(config) {
|
|
203
|
+
if (config === undefined || config === null) {
|
|
204
|
+
return [];
|
|
205
|
+
}
|
|
206
|
+
if (typeof config !== "object" || Array.isArray(config)) {
|
|
207
|
+
return ["Config declaration must be an object"];
|
|
208
|
+
}
|
|
209
|
+
const errors = [];
|
|
210
|
+
for (const [key, item] of Object.entries(config)) {
|
|
211
|
+
const isTemplate = isConfigTemplate(key);
|
|
212
|
+
if (isTemplate) {
|
|
213
|
+
const starCount = (key.match(/\*/g) || []).length;
|
|
214
|
+
if (starCount !== 1) {
|
|
215
|
+
errors.push(`Config template key '${key}' must contain exactly one '*' wildcard`);
|
|
216
|
+
}
|
|
217
|
+
if (item && typeof item === "object") {
|
|
218
|
+
for (const forbidden of FORBIDDEN_TEMPLATE_FIELDS) {
|
|
219
|
+
if (forbidden in item) {
|
|
220
|
+
errors.push(`Config template '${key}' must not declare '${forbidden}' property`);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
if (!item || typeof item !== "object" || Array.isArray(item)) {
|
|
226
|
+
if (isTemplate)
|
|
227
|
+
errors.push(`Config template '${key}' must declare an object`);
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
const itemDef = item;
|
|
231
|
+
if ((isTemplate || "schema" in itemDef) && itemDef.secret !== undefined && typeof itemDef.secret !== "boolean") {
|
|
232
|
+
errors.push(`Config item '${key}' property 'secret' must be a boolean`);
|
|
233
|
+
}
|
|
234
|
+
if ("schema" in itemDef && itemDef.schema !== undefined) {
|
|
235
|
+
if (typeof itemDef.schema !== "object" ||
|
|
236
|
+
itemDef.schema === null ||
|
|
237
|
+
Array.isArray(itemDef.schema)) {
|
|
238
|
+
errors.push(`Config item '${key}' schema must be an object`);
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
241
|
+
// 递归白名单与属性类型校验
|
|
242
|
+
validateSchemaNode(itemDef.schema, `config['${key}'].schema`, errors);
|
|
243
|
+
// 外层 type 与根 schema.type 一致性检查(外层 number 与模式 integer 允许兼容)
|
|
244
|
+
if (itemDef.type !== undefined && itemDef.schema.type !== undefined) {
|
|
245
|
+
if (itemDef.type === "number") {
|
|
246
|
+
if (itemDef.schema.type !== "number" && itemDef.schema.type !== "integer") {
|
|
247
|
+
errors.push(`Config item '${key}' outer type 'number' is incompatible with schema type '${itemDef.schema.type}'`);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
else if (itemDef.type !== itemDef.schema.type) {
|
|
251
|
+
errors.push(`Config item '${key}' outer type '${itemDef.type}' is incompatible with schema type '${itemDef.schema.type}'`);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
// 利用现有 Ajv 进行 Schema 编译合法性校验
|
|
255
|
+
const ajvCheck = validateSchemaOnly(itemDef.schema, {});
|
|
256
|
+
if (!ajvCheck.valid && ajvCheck.errors) {
|
|
257
|
+
for (const err of ajvCheck.errors) {
|
|
258
|
+
if (err.startsWith("Schema compilation error:")) {
|
|
259
|
+
errors.push(`Config item '${key}' schema failed compilation: ${err}`);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
return errors;
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* 断言配置声明集合完全合法。
|
|
269
|
+
* 若存在非法模板或不合规 Schema 抛出 MANIFEST_INVALID 异常。
|
|
270
|
+
*
|
|
271
|
+
* @param config 待校验的配置声明定义
|
|
272
|
+
*/
|
|
273
|
+
export function assertValidConfigDeclarations(config) {
|
|
274
|
+
const errors = validateConfigDeclarations(config);
|
|
275
|
+
if (errors.length > 0) {
|
|
276
|
+
throw new ActionDockError(MANIFEST_INVALID, `Invalid configuration declarations: ${errors.join("; ")}`, { errors });
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* 校验指定配置数据值是否符合绑定的 Schema 契约。
|
|
281
|
+
* 仅在声明了 schema 且数据值不为 undefined 时执行校验。
|
|
282
|
+
* 错误信息输出键名与失败路径原因,严禁包含实际数据值。
|
|
283
|
+
*
|
|
284
|
+
* @param key 配置项键名
|
|
285
|
+
* @param value 待校验的具体数据值
|
|
286
|
+
* @param item 配置项元数据定义
|
|
287
|
+
*/
|
|
288
|
+
export function validateConfigValue(key, value, item) {
|
|
289
|
+
if (!item?.schema || value === undefined) {
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
const result = validateSchema(item.schema, value);
|
|
293
|
+
if (!result.valid) {
|
|
294
|
+
const reasons = (result.errors || []).join("; ");
|
|
295
|
+
throw new ActionDockError(CONFIG_VALUE_INVALID, `Configuration value for '${key}' failed schema validation: ${reasons}`, { key, errors: result.errors });
|
|
296
|
+
}
|
|
297
|
+
}
|
package/dist/project/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export { loadActionBindings, validateActionBindings, resolveActionBinding, hasRemoteActionBinding, validateCanonicalActionRef, validateBindingName, type ActionBindings, type ActionBindingTarget, } from "../invocation/bindings.js";
|
|
1
2
|
export * from "./digest.js";
|
|
2
3
|
export * from "./init.js";
|
|
3
4
|
export * from "./loader.js";
|
|
@@ -7,10 +8,12 @@ export { SNAPSHOT_TRACKED_FILES, type TransactionFileRecord, type TransactionMet
|
|
|
7
8
|
export * from "./types.js";
|
|
8
9
|
export * from "./types-generator.js";
|
|
9
10
|
export { assertPathWithinRoot, isPathOutsideBoundary, getPackageSlug, parseDuration, traverseDirectory, type TraverseDirectoryEntry, type TraverseDirectoryOptions, } from "../utils/index.js";
|
|
10
|
-
export { isSecretConfigKey, maskSecretValue, sanitizeConfigDefinitions, } from "../storage/mask.js";
|
|
11
|
+
export { isSecretConfigKey, maskSecretValue, sanitizeConfigDefinitions, redactConfigValue, redactConfigValueForDeclarations, type RedactResult, } from "../storage/mask.js";
|
|
12
|
+
export { isConfigTemplate, resolveConfigDeclaration, validateConfigDeclarations, assertValidConfigDeclarations, assertConcreteConfigKey, validateConfigValue, type ResolvedConfigDeclaration, } from "./config-declarations.js";
|
|
11
13
|
export { runDoctorChecks, } from "../doctor/doctor.js";
|
|
12
|
-
export { resolveActionInput, buildActionDescribePayload, formatActionDetail, type FormatActionDetailOptions, ACTION_DESCRIBE_SYNTAX_REFERENCE, inspectActionTextFieldAnnotation, analyzeActionOutputContract, type AnnotationInspectionResult, type ActionOutputContractInput, type ActionOutputContractAnalysis, type OutputContractDiagnostic, mapInputValidationFailure, stripBom, readStdinBounded, type ResolveActionInputOptions, type ReadStdinBoundedOptions, } from "../input/index.js";
|
|
14
|
+
export { resolveInput, resolveActionInput, resolveValueInput, hasValueInput, parseValueLiteral, buildActionDescribePayload, formatActionDetail, type FormatActionDetailOptions, ACTION_DESCRIBE_SYNTAX_REFERENCE, inspectActionTextFieldAnnotation, analyzeActionOutputContract, type AnnotationInspectionResult, type ActionOutputContractInput, type ActionOutputContractAnalysis, type OutputContractDiagnostic, mapInputValidationFailure, stripBom, readStdinBounded, type ResolveInputOptions, type ResolveValueInputOptions, type ResolveActionInputOptions, type InputResolutionPolicy, type ReadStdinBoundedOptions, } from "../input/index.js";
|
|
13
15
|
export { validateActionInputValue } from "../value-validator.js";
|
|
14
16
|
export { parseJson } from "../input/input-resolver.js";
|
|
17
|
+
export { parseConfigOverrideAssignment } from "../input/config-override.js";
|
|
15
18
|
export { InputError, FlatInputError, } from "../input/flat-errors.js";
|
|
16
19
|
export { filterWithFallbackInfo, compileIntentRegex, matchIntent, type Extractor, } from "../utils/intent.js";
|