@actiondock/core 2.9.1 → 2.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -2
- package/dist/catalog/action-catalog.js +3 -33
- package/dist/catalog/discovery.d.ts +0 -3
- package/dist/catalog/discovery.js +4 -4
- package/dist/catalog/graph.d.ts +0 -4
- package/dist/catalog/graph.js +12 -15
- package/dist/{profile/client-actions.d.ts → client/actions.d.ts} +1 -1
- package/dist/{profile/client-actions.js → client/actions.js} +2 -8
- package/dist/{profile/client-config.d.ts → client/config.d.ts} +1 -1
- package/dist/{profile/client-config.js → client/config.js} +1 -1
- package/dist/client/dispatcher.d.ts +4 -0
- package/dist/client/dispatcher.js +21 -0
- package/dist/client/health.d.ts +38 -0
- package/dist/{profile/client-health.js → client/health.js} +9 -6
- package/dist/client/index.d.ts +16 -0
- package/dist/client/index.js +20 -0
- package/dist/{profile/client-playbooks.d.ts → client/playbooks.d.ts} +1 -1
- package/dist/{profile/client-playbooks.js → client/playbooks.js} +1 -1
- package/dist/{profile/client-runs.d.ts → client/runs.d.ts} +4 -2
- package/dist/{profile/client-runs.js → client/runs.js} +7 -1
- package/dist/{profile/client-state.d.ts → client/state.d.ts} +1 -1
- package/dist/{profile/client-state.js → client/state.js} +1 -1
- package/dist/{profile/client-transport.d.ts → client/transport.d.ts} +29 -10
- package/dist/{profile/client-transport.js → client/transport.js} +125 -35
- package/dist/doctor/checks/index.d.ts +1 -1
- package/dist/doctor/checks/index.js +1 -1
- package/dist/doctor/checks/project.js +24 -92
- package/dist/doctor/checks/registry.js +1 -1
- package/dist/doctor/checks/runtime.d.ts +0 -4
- package/dist/doctor/checks/runtime.js +0 -28
- package/dist/doctor/doctor.js +1 -2
- package/dist/errors.d.ts +85 -4
- package/dist/errors.js +114 -4
- package/dist/execution/service.d.ts +55 -37
- package/dist/execution/service.js +601 -212
- package/dist/execution/types.d.ts +24 -5
- package/dist/host/host.d.ts +42 -22
- package/dist/host/host.js +565 -150
- package/dist/host/routing.js +6 -6
- package/dist/host/types.d.ts +25 -13
- package/dist/index.d.ts +9 -4
- package/dist/index.js +5 -2
- package/dist/input/advice.d.ts +0 -44
- package/dist/input/advice.js +7 -141
- package/dist/input/describe.d.ts +6 -0
- package/dist/input/describe.js +26 -1
- package/dist/input/flat-materializer.js +1 -1
- package/dist/input/flat-parser.js +1 -1
- package/dist/input/index.d.ts +0 -1
- package/dist/input/index.js +0 -1
- package/dist/input/input-resolver.js +2 -2
- package/dist/input/validation-mapper.d.ts +1 -1
- package/dist/invocation/policy.js +2 -0
- package/dist/ipc/diagnostic.d.ts +8 -21
- package/dist/ipc/diagnostic.js +24 -57
- package/dist/ipc/host.js +21 -7
- package/dist/ipc/service.d.ts +14 -0
- package/dist/ipc/service.js +132 -26
- package/dist/ipc/types.d.ts +0 -4
- package/dist/package/index.d.ts +4 -5
- package/dist/package/index.js +1 -2
- package/dist/package/runtime.d.ts +94 -23
- package/dist/package/runtime.js +628 -274
- package/dist/package/static-index.d.ts +3 -6
- package/dist/package/static-index.js +11 -40
- package/dist/package/types.d.ts +19 -4
- package/dist/platform/index.d.ts +2 -1
- package/dist/platform/index.js +2 -1
- package/dist/platform/loader-hook.d.ts +32 -0
- package/dist/platform/loader-hook.js +90 -0
- package/dist/{node → platform}/module-loader.d.ts +13 -0
- package/dist/{node → platform}/module-loader.js +51 -4
- package/dist/platform/node.d.ts +19 -8
- package/dist/platform/node.js +50 -20
- package/dist/platform/types.d.ts +7 -47
- package/dist/process/context-process.d.ts +3 -18
- package/dist/process/context-process.js +3 -44
- package/dist/process/control.d.ts +21 -0
- package/dist/process/control.js +34 -0
- package/dist/process/converters.d.ts +23 -0
- package/dist/process/converters.js +92 -0
- package/dist/process/cursor.js +2 -22
- package/dist/process/{diagnostics-sink.d.ts → diagnostics.d.ts} +0 -7
- package/dist/process/{diagnostics-sink.js → diagnostics.js} +6 -8
- package/dist/process/index.d.ts +1 -3
- package/dist/process/index.js +1 -3
- package/dist/process/input-dispatcher.d.ts +4 -13
- package/dist/process/input-dispatcher.js +48 -69
- package/dist/process/managed-record.d.ts +3 -31
- package/dist/process/metadata-store.d.ts +0 -41
- package/dist/process/metadata-store.js +2 -341
- package/dist/process/process-driver.js +2 -2
- package/dist/process/process-manager.d.ts +8 -50
- package/dist/process/process-manager.js +28 -417
- package/dist/process/quotas.d.ts +14 -0
- package/dist/process/quotas.js +29 -0
- package/dist/process/run-executor.js +0 -1
- package/dist/process/terminal-output-cache.d.ts +20 -34
- package/dist/process/terminal-output-cache.js +43 -64
- package/dist/profile/index.d.ts +1 -2
- package/dist/profile/index.js +1 -2
- package/dist/profile/manager.d.ts +2 -4
- package/dist/profile/manager.js +10 -40
- package/dist/profile/types.d.ts +1 -17
- package/dist/project/digest.d.ts +5 -1
- package/dist/project/digest.js +81 -219
- package/dist/project/index.d.ts +4 -5
- package/dist/project/index.js +4 -5
- package/dist/project/init.js +2 -2
- package/dist/project/loader.d.ts +3 -3
- package/dist/project/loader.js +72 -50
- package/dist/project/lockfile.js +6 -5
- package/dist/project/manifest.js +5 -4
- package/dist/project/transactions.d.ts +4 -42
- package/dist/project/transactions.js +83 -138
- package/dist/project/types-generator.js +2 -1
- package/dist/project/types.d.ts +2 -0
- package/dist/registry/index.d.ts +0 -1
- package/dist/registry/index.js +0 -1
- package/dist/registry/lock.d.ts +2 -11
- package/dist/registry/lock.js +14 -183
- package/dist/registry/registry.d.ts +1 -6
- package/dist/registry/registry.js +5 -17
- package/dist/runtime/action-registry.d.ts +1 -5
- package/dist/runtime/action-registry.js +0 -12
- package/dist/runtime/context.js +2 -2
- package/dist/runtime/events.d.ts +11 -50
- package/dist/runtime/events.js +88 -368
- package/dist/runtime/index.d.ts +0 -4
- package/dist/runtime/index.js +0 -4
- package/dist/runtime/run-persistence.d.ts +2 -0
- package/dist/runtime/run-persistence.js +2 -1
- package/dist/runtime/standalone-commands.d.ts +18 -0
- package/dist/runtime/standalone-commands.js +466 -0
- package/dist/runtime/standalone-help.d.ts +20 -0
- package/dist/runtime/standalone-help.js +72 -0
- package/dist/runtime/standalone.d.ts +1 -44
- package/dist/runtime/standalone.js +31 -526
- package/dist/schema/validator.d.ts +2 -2
- package/dist/schema/validator.js +2 -9
- package/dist/server/index.d.ts +0 -2
- package/dist/server/index.js +0 -2
- package/dist/server/routes/actions.js +1 -1
- package/dist/server/routes/info.js +1 -1
- package/dist/server/routes/playbooks.js +1 -1
- package/dist/server/routes/runs.js +123 -16
- package/dist/server/routes/state.js +43 -18
- package/dist/server/security.d.ts +1 -1
- package/dist/server/security.js +2 -2
- package/dist/server/server.js +3 -7
- package/dist/service/factory.js +4 -3
- package/dist/service/index.d.ts +1 -1
- package/dist/service/index.js +1 -1
- package/dist/service/remote-errors.d.ts +1 -1
- package/dist/service/remote-errors.js +6 -6
- package/dist/service/remote-polling.d.ts +3 -0
- package/dist/service/remote-polling.js +10 -5
- package/dist/service/remote.d.ts +5 -1
- package/dist/service/remote.js +108 -17
- package/dist/service/sse-stream.js +2 -2
- package/dist/service/types.d.ts +22 -19
- package/dist/service/types.js +1 -24
- package/dist/storage/clock.d.ts +1 -1
- package/dist/storage/clock.js +1 -1
- package/dist/storage/data-dir-lock.d.ts +10 -22
- package/dist/storage/data-dir-lock.js +28 -56
- package/dist/storage/file-lock.d.ts +64 -0
- package/dist/storage/file-lock.js +447 -0
- package/dist/storage/index.d.ts +2 -2
- package/dist/storage/index.js +5 -4
- package/dist/storage/run-liveness.d.ts +48 -0
- package/dist/storage/run-liveness.js +44 -0
- package/dist/storage/sqlite-config.d.ts +16 -0
- package/dist/storage/sqlite-config.js +59 -0
- package/dist/storage/sqlite-driver.d.ts +3 -1
- package/dist/storage/sqlite-driver.js +14 -4
- package/dist/storage/sqlite-events.d.ts +13 -0
- package/dist/storage/sqlite-events.js +48 -0
- package/dist/storage/sqlite-runs.d.ts +107 -0
- package/dist/storage/sqlite-runs.js +622 -0
- package/dist/storage/sqlite-schema.d.ts +5 -0
- package/dist/storage/sqlite-schema.js +143 -0
- package/dist/storage/sqlite-state.d.ts +33 -0
- package/dist/storage/sqlite-state.js +270 -0
- package/dist/storage/sqlite.d.ts +30 -57
- package/dist/storage/sqlite.js +84 -741
- package/dist/storage/types.d.ts +35 -2
- package/dist/utils/index.d.ts +18 -14
- package/dist/utils/index.js +37 -29
- package/dist/utils/semver.d.ts +18 -0
- package/dist/utils/semver.js +19 -0
- package/dist/utils/warning.d.ts +1 -0
- package/dist/utils/warning.js +52 -0
- package/dist/{json/value-validator.d.ts → value-validator.d.ts} +9 -0
- package/dist/value-validator.js +330 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +10 -12
- package/dist/execution/index.d.ts +0 -2
- package/dist/execution/index.js +0 -2
- package/dist/filter/index.d.ts +0 -1
- package/dist/filter/index.js +0 -1
- package/dist/graph/index.d.ts +0 -4
- package/dist/graph/index.js +0 -4
- package/dist/input/metadata.d.ts +0 -99
- package/dist/input/metadata.js +0 -99
- package/dist/invocation/index.d.ts +0 -2
- package/dist/invocation/index.js +0 -2
- package/dist/ipc/index.d.ts +0 -4
- package/dist/ipc/index.js +0 -4
- package/dist/json/index.d.ts +0 -1
- package/dist/json/index.js +0 -1
- package/dist/json/value-validator.js +0 -330
- package/dist/node/index.d.ts +0 -1
- package/dist/node/index.js +0 -1
- package/dist/platform/node-fs.d.ts +0 -37
- package/dist/platform/node-fs.js +0 -81
- package/dist/process/control-arbiter.d.ts +0 -108
- package/dist/process/control-arbiter.js +0 -250
- package/dist/process/process-executor.d.ts +0 -47
- package/dist/process/process-executor.js +0 -296
- package/dist/profile/client-health.d.ts +0 -14
- package/dist/profile/client-query.d.ts +0 -30
- package/dist/profile/client-query.js +0 -69
- package/dist/profile/client.d.ts +0 -26
- package/dist/profile/client.js +0 -31
- package/dist/project/closure.d.ts +0 -25
- package/dist/project/closure.js +0 -74
- package/dist/registry/store.d.ts +0 -42
- package/dist/registry/store.js +0 -43
- package/dist/runtime/clock.d.ts +0 -7
- package/dist/runtime/clock.js +0 -7
- package/dist/runtime/module-loader.d.ts +0 -2
- package/dist/runtime/module-loader.js +0 -1
- package/dist/runtime/process.d.ts +0 -8
- package/dist/runtime/process.js +0 -1
- package/dist/runtime/runner.d.ts +0 -234
- package/dist/runtime/runner.js +0 -609
- package/dist/schema/index.d.ts +0 -1
- package/dist/schema/index.js +0 -1
- package/dist/server/dispatcher.d.ts +0 -25
- package/dist/server/dispatcher.js +0 -54
- package/dist/service/local.d.ts +0 -27
- package/dist/service/local.js +0 -141
- package/dist/storage/driver.d.ts +0 -10
- package/dist/storage/driver.js +0 -10
- package/dist/storage/lazy.d.ts +0 -8
- package/dist/storage/lazy.js +0 -45
- package/dist/storage/lock-core.d.ts +0 -158
- package/dist/storage/lock-core.js +0 -872
- package/dist/utils/net.d.ts +0 -10
- package/dist/utils/net.js +0 -16
- package/dist/utils/process.d.ts +0 -10
- package/dist/utils/process.js +0 -21
- /package/dist/{filter → utils}/intent.d.ts +0 -0
- /package/dist/{filter → utils}/intent.js +0 -0
package/dist/project/digest.js
CHANGED
|
@@ -1,241 +1,103 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
/**
|
|
3
3
|
* 解析 JSON 文本并在检测到重复键时抛出错误。
|
|
4
|
-
* 遵循 RFC 8785
|
|
4
|
+
* 遵循 RFC 8785 清单校验规范:解析阶段必须率先拦截并拒绝重复键。
|
|
5
|
+
*
|
|
6
|
+
* 实现策略:JSON.parse 主解析 + 轻量键名扫描器做重复键检测。
|
|
7
|
+
* 扫描器以状态机遍历原始文本,仅在对象键位置(冒号前的字符串字面量)记录键名,
|
|
8
|
+
* 同一对象层级内出现重复键名即抛错;字符串内部与转义字符不参与键名提取。
|
|
5
9
|
*/
|
|
6
10
|
export function parseJsonWithoutDuplicates(jsonText) {
|
|
7
|
-
|
|
11
|
+
detectDuplicateKeys(jsonText);
|
|
12
|
+
return JSON.parse(jsonText);
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* 扫描 JSON 文本,检测同一对象层级内的重复键名。
|
|
16
|
+
*
|
|
17
|
+
* 状态机只关心「是否处于字符串内」与「对象层级」:
|
|
18
|
+
* - 遇到未转义引号时切换 inString 状态;
|
|
19
|
+
* - 字符串闭合后若紧邻(跳过空白)冒号,则该字符串是键:提取键名登记到当前层级;
|
|
20
|
+
* - 对象层级入栈出栈维护各自已见键集合。
|
|
21
|
+
*/
|
|
22
|
+
function detectDuplicateKeys(jsonText) {
|
|
8
23
|
const len = jsonText.length;
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
24
|
+
let pos = 0;
|
|
25
|
+
let inString = false;
|
|
26
|
+
let depth = -1;
|
|
27
|
+
// 每层对象的已见键集合;keyStartStack 记录每个未闭合字符串字面量的起点
|
|
28
|
+
const seenPerDepth = [];
|
|
29
|
+
const keyStartStack = [];
|
|
30
|
+
while (pos < len) {
|
|
31
|
+
const ch = jsonText[pos];
|
|
32
|
+
if (inString) {
|
|
33
|
+
if (ch === "\\") {
|
|
34
|
+
pos += 2;
|
|
35
|
+
continue;
|
|
17
36
|
}
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
function parseString() {
|
|
21
|
-
if (jsonText[pos] !== '"') {
|
|
22
|
-
throw new SyntaxError(`Expected string at position ${pos}`);
|
|
23
|
-
}
|
|
24
|
-
const start = pos;
|
|
25
|
-
pos++; // 跳过开头的引号
|
|
26
|
-
let result = "";
|
|
27
|
-
while (pos < len) {
|
|
28
|
-
const ch = jsonText[pos];
|
|
29
37
|
if (ch === '"') {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
const esc = jsonText[pos];
|
|
39
|
-
if (esc === '"' || esc === "\\" || esc === "/") {
|
|
40
|
-
result += esc;
|
|
41
|
-
pos++;
|
|
42
|
-
}
|
|
43
|
-
else if (esc === "b") {
|
|
44
|
-
result += "\b";
|
|
45
|
-
pos++;
|
|
46
|
-
}
|
|
47
|
-
else if (esc === "f") {
|
|
48
|
-
result += "\f";
|
|
49
|
-
pos++;
|
|
38
|
+
inString = false;
|
|
39
|
+
const start = keyStartStack.pop();
|
|
40
|
+
// 判断该字符串是否为对象键:向后跳过空白,若紧邻冒号则为键
|
|
41
|
+
let look = pos + 1;
|
|
42
|
+
while (look < len && /\s/.test(jsonText[look]))
|
|
43
|
+
look++;
|
|
44
|
+
if (start !== undefined && look < len && jsonText[look] === ":") {
|
|
45
|
+
registerKey(jsonText, start, pos, seenPerDepth, depth);
|
|
50
46
|
}
|
|
51
|
-
else if (esc === "n") {
|
|
52
|
-
result += "\n";
|
|
53
|
-
pos++;
|
|
54
|
-
}
|
|
55
|
-
else if (esc === "r") {
|
|
56
|
-
result += "\r";
|
|
57
|
-
pos++;
|
|
58
|
-
}
|
|
59
|
-
else if (esc === "t") {
|
|
60
|
-
result += "\t";
|
|
61
|
-
pos++;
|
|
62
|
-
}
|
|
63
|
-
else if (esc === "u") {
|
|
64
|
-
pos++;
|
|
65
|
-
const hex = jsonText.slice(pos, pos + 4);
|
|
66
|
-
if (hex.length < 4 || !/^[0-9a-fA-F]{4}$/.test(hex)) {
|
|
67
|
-
throw new SyntaxError(`Invalid unicode escape sequence \\u${hex} at ${pos}`);
|
|
68
|
-
}
|
|
69
|
-
result += String.fromCharCode(parseInt(hex, 16));
|
|
70
|
-
pos += 4;
|
|
71
|
-
}
|
|
72
|
-
else {
|
|
73
|
-
throw new SyntaxError(`Invalid escape character '\\${esc}' at ${pos}`);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
else {
|
|
77
|
-
result += ch;
|
|
78
|
-
pos++;
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
throw new SyntaxError(`Unterminated string starting at ${start}`);
|
|
82
|
-
}
|
|
83
|
-
function parseNumber() {
|
|
84
|
-
const start = pos;
|
|
85
|
-
if (jsonText[pos] === "-") {
|
|
86
|
-
pos++;
|
|
87
|
-
}
|
|
88
|
-
if (pos >= len) {
|
|
89
|
-
throw new SyntaxError(`Expected digits at position ${pos}`);
|
|
90
|
-
}
|
|
91
|
-
if (jsonText[pos] === "0") {
|
|
92
|
-
pos++;
|
|
93
|
-
}
|
|
94
|
-
else if (jsonText[pos] >= "1" && jsonText[pos] <= "9") {
|
|
95
|
-
while (pos < len && jsonText[pos] >= "0" && jsonText[pos] <= "9") {
|
|
96
|
-
pos++;
|
|
97
47
|
}
|
|
98
|
-
}
|
|
99
|
-
else {
|
|
100
|
-
throw new SyntaxError(`Invalid number at position ${pos}`);
|
|
101
|
-
}
|
|
102
|
-
if (pos < len && jsonText[pos] === ".") {
|
|
103
48
|
pos++;
|
|
104
|
-
|
|
105
|
-
throw new SyntaxError(`Expected digit after decimal point at position ${pos}`);
|
|
106
|
-
}
|
|
107
|
-
while (pos < len && jsonText[pos] >= "0" && jsonText[pos] <= "9") {
|
|
108
|
-
pos++;
|
|
109
|
-
}
|
|
49
|
+
continue;
|
|
110
50
|
}
|
|
111
|
-
if (
|
|
51
|
+
if (ch === '"') {
|
|
52
|
+
inString = true;
|
|
53
|
+
keyStartStack.push(pos + 1);
|
|
112
54
|
pos++;
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
const numStr = jsonText.slice(start, pos);
|
|
124
|
-
const num = Number(numStr);
|
|
125
|
-
if (!Number.isFinite(num)) {
|
|
126
|
-
throw new SyntaxError(`Number out of range: ${numStr}`);
|
|
127
|
-
}
|
|
128
|
-
return num;
|
|
129
|
-
}
|
|
130
|
-
function parseObject() {
|
|
131
|
-
const obj = {};
|
|
132
|
-
const keys = new Set();
|
|
133
|
-
pos++; // 跳过 '{'
|
|
134
|
-
skipWhitespace();
|
|
135
|
-
if (pos < len && jsonText[pos] === "}") {
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
if (ch === "{") {
|
|
58
|
+
depth++;
|
|
59
|
+
if (seenPerDepth.length <= depth)
|
|
60
|
+
seenPerDepth.push(new Set());
|
|
61
|
+
else
|
|
62
|
+
seenPerDepth[depth] = new Set();
|
|
136
63
|
pos++;
|
|
137
|
-
|
|
64
|
+
continue;
|
|
138
65
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
if (jsonText[pos] !== '"') {
|
|
142
|
-
throw new SyntaxError(`Expected string key in object at position ${pos}`);
|
|
143
|
-
}
|
|
144
|
-
const key = parseString();
|
|
145
|
-
if (keys.has(key)) {
|
|
146
|
-
throw new Error(`Duplicate key '${key}' detected in JSON`);
|
|
147
|
-
}
|
|
148
|
-
keys.add(key);
|
|
149
|
-
skipWhitespace();
|
|
150
|
-
if (pos >= len || jsonText[pos] !== ":") {
|
|
151
|
-
throw new SyntaxError(`Expected ':' after key '${key}' at position ${pos}`);
|
|
152
|
-
}
|
|
153
|
-
pos++; // 跳过 ':'
|
|
154
|
-
skipWhitespace();
|
|
155
|
-
const val = parseValue();
|
|
156
|
-
obj[key] = val;
|
|
157
|
-
skipWhitespace();
|
|
158
|
-
if (pos >= len) {
|
|
159
|
-
throw new SyntaxError("Unexpected end of JSON in object");
|
|
160
|
-
}
|
|
161
|
-
if (jsonText[pos] === ",") {
|
|
162
|
-
pos++;
|
|
163
|
-
skipWhitespace();
|
|
164
|
-
}
|
|
165
|
-
else if (jsonText[pos] === "}") {
|
|
166
|
-
pos++;
|
|
167
|
-
return obj;
|
|
168
|
-
}
|
|
169
|
-
else {
|
|
170
|
-
throw new SyntaxError(`Expected ',' or '}' in object at position ${pos}`);
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
throw new SyntaxError("Unterminated object in JSON");
|
|
174
|
-
}
|
|
175
|
-
function parseArray() {
|
|
176
|
-
const arr = [];
|
|
177
|
-
pos++; // 跳过 '['
|
|
178
|
-
skipWhitespace();
|
|
179
|
-
if (pos < len && jsonText[pos] === "]") {
|
|
66
|
+
if (ch === "}") {
|
|
67
|
+
depth--;
|
|
180
68
|
pos++;
|
|
181
|
-
|
|
69
|
+
continue;
|
|
182
70
|
}
|
|
183
|
-
|
|
184
|
-
skipWhitespace();
|
|
185
|
-
const val = parseValue();
|
|
186
|
-
arr.push(val);
|
|
187
|
-
skipWhitespace();
|
|
188
|
-
if (pos >= len) {
|
|
189
|
-
throw new SyntaxError("Unexpected end of JSON in array");
|
|
190
|
-
}
|
|
191
|
-
if (jsonText[pos] === ",") {
|
|
192
|
-
pos++;
|
|
193
|
-
skipWhitespace();
|
|
194
|
-
}
|
|
195
|
-
else if (jsonText[pos] === "]") {
|
|
196
|
-
pos++;
|
|
197
|
-
return arr;
|
|
198
|
-
}
|
|
199
|
-
else {
|
|
200
|
-
throw new SyntaxError(`Expected ',' or ']' in array at position ${pos}`);
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
throw new SyntaxError("Unterminated array in JSON");
|
|
71
|
+
pos++;
|
|
204
72
|
}
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
return null;
|
|
230
|
-
}
|
|
231
|
-
throw new SyntaxError(`Unexpected token '${ch}' at position ${pos}`);
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* 登记一个对象键:回溯提取键名,在当前层级做重复检测。
|
|
76
|
+
*/
|
|
77
|
+
function registerKey(jsonText, start, quoteEndPos, seenPerDepth, depth) {
|
|
78
|
+
if (depth < 0)
|
|
79
|
+
return;
|
|
80
|
+
// quoteEndPos 是闭合引号的位置,键内容为 [start, quoteEndPos)
|
|
81
|
+
if (quoteEndPos <= start)
|
|
82
|
+
return;
|
|
83
|
+
const raw = jsonText.slice(start, quoteEndPos);
|
|
84
|
+
const key = unescapeJsonKey(raw);
|
|
85
|
+
const seen = seenPerDepth[depth];
|
|
86
|
+
if (seen.has(key)) {
|
|
87
|
+
throw new SyntaxError(`Duplicate key '${key}' in object at depth ${depth}`);
|
|
88
|
+
}
|
|
89
|
+
seen.add(key);
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* 反转义 JSON 键名字符串字面量内容。
|
|
93
|
+
*/
|
|
94
|
+
function unescapeJsonKey(raw) {
|
|
95
|
+
try {
|
|
96
|
+
return JSON.parse(`"${raw}"`);
|
|
232
97
|
}
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
if (pos < len) {
|
|
236
|
-
throw new SyntaxError(`Unexpected trailing characters at position ${pos}`);
|
|
98
|
+
catch {
|
|
99
|
+
return raw;
|
|
237
100
|
}
|
|
238
|
-
return result;
|
|
239
101
|
}
|
|
240
102
|
/**
|
|
241
103
|
* 依据 RFC 8785 规范化 JSON 数据。
|
|
@@ -256,7 +118,7 @@ export function canonicalizeJson(value) {
|
|
|
256
118
|
}
|
|
257
119
|
if (type === "number") {
|
|
258
120
|
if (!Number.isFinite(value)) {
|
|
259
|
-
throw new TypeError("RFC 8785 JSON
|
|
121
|
+
throw new TypeError("RFC 8785 JSON canonicalization rejects non-finite numbers (NaN or Infinity)");
|
|
260
122
|
}
|
|
261
123
|
if (Object.is(value, -0) || value === 0) {
|
|
262
124
|
return "0";
|
package/dist/project/index.d.ts
CHANGED
|
@@ -1,17 +1,16 @@
|
|
|
1
|
-
export * from "./closure.js";
|
|
2
1
|
export * from "./digest.js";
|
|
3
2
|
export * from "./init.js";
|
|
4
3
|
export * from "./loader.js";
|
|
5
4
|
export * from "./lockfile.js";
|
|
6
5
|
export * from "./manifest.js";
|
|
7
|
-
export { SNAPSHOT_TRACKED_FILES, type TransactionFileRecord, type TransactionMetadata, type ProjectTransaction,
|
|
6
|
+
export { SNAPSHOT_TRACKED_FILES, type TransactionFileRecord, type TransactionMetadata, type ProjectTransaction, isProjectLockHeld, acquireProjectLock, runFrozenInstall, hasPendingTransactions, beginTransaction, recoverPendingTransactions, } from "./transactions.js";
|
|
8
7
|
export * from "./types.js";
|
|
9
8
|
export * from "./types-generator.js";
|
|
10
9
|
export { assertPathWithinRoot, isPathOutsideBoundary, getPackageSlug, parseDuration, traverseDirectory, type TraverseDirectoryEntry, type TraverseDirectoryOptions, } from "../utils/index.js";
|
|
11
10
|
export { isSecretConfigKey, maskSecretValue, sanitizeConfigDefinitions, } from "../storage/mask.js";
|
|
12
11
|
export { runDoctorChecks, } from "../doctor/doctor.js";
|
|
13
|
-
export { resolveActionInput,
|
|
14
|
-
export { validateActionInputValue } from "../
|
|
12
|
+
export { resolveActionInput, buildActionDescribePayload, ACTION_DESCRIBE_SYNTAX_REFERENCE, mapInputValidationFailure, stripBom, readStdinBounded, type ResolveActionInputOptions, type ReadStdinBoundedOptions, } from "../input/index.js";
|
|
13
|
+
export { validateActionInputValue } from "../value-validator.js";
|
|
15
14
|
export { parseJson } from "../input/input-resolver.js";
|
|
16
15
|
export { InputError, FlatInputError, } from "../input/flat-errors.js";
|
|
17
|
-
export { filterWithFallbackInfo, compileIntentRegex, matchIntent, type Extractor, } from "../
|
|
16
|
+
export { filterWithFallbackInfo, compileIntentRegex, matchIntent, type Extractor, } from "../utils/intent.js";
|
package/dist/project/index.js
CHANGED
|
@@ -1,19 +1,18 @@
|
|
|
1
|
-
export * from "./closure.js";
|
|
2
1
|
export * from "./digest.js";
|
|
3
2
|
export * from "./init.js";
|
|
4
3
|
export * from "./loader.js";
|
|
5
4
|
export * from "./lockfile.js";
|
|
6
5
|
export * from "./manifest.js";
|
|
7
|
-
export { SNAPSHOT_TRACKED_FILES,
|
|
6
|
+
export { SNAPSHOT_TRACKED_FILES, isProjectLockHeld, acquireProjectLock, runFrozenInstall, hasPendingTransactions, beginTransaction, recoverPendingTransactions, } from "./transactions.js";
|
|
8
7
|
export * from "./types.js";
|
|
9
8
|
export * from "./types-generator.js";
|
|
10
9
|
export { assertPathWithinRoot, isPathOutsideBoundary, getPackageSlug, parseDuration, traverseDirectory, } from "../utils/index.js";
|
|
11
10
|
export { isSecretConfigKey, maskSecretValue, sanitizeConfigDefinitions, } from "../storage/mask.js";
|
|
12
11
|
export { runDoctorChecks, } from "../doctor/doctor.js";
|
|
13
12
|
// 动作入参解析、校验与格式化(含 BOM 剥离与标准输入有界读取的单一事实源转引)
|
|
14
|
-
export { resolveActionInput,
|
|
15
|
-
export { validateActionInputValue } from "../
|
|
13
|
+
export { resolveActionInput, buildActionDescribePayload, ACTION_DESCRIBE_SYNTAX_REFERENCE, mapInputValidationFailure, stripBom, readStdinBounded, } from "../input/index.js";
|
|
14
|
+
export { validateActionInputValue } from "../value-validator.js";
|
|
16
15
|
export { parseJson } from "../input/input-resolver.js";
|
|
17
16
|
export { InputError, FlatInputError, } from "../input/flat-errors.js";
|
|
18
17
|
// 意图过滤与查询
|
|
19
|
-
export { filterWithFallbackInfo, compileIntentRegex, matchIntent, } from "../
|
|
18
|
+
export { filterWithFallbackInfo, compileIntentRegex, matchIntent, } from "../utils/intent.js";
|
package/dist/project/init.js
CHANGED
|
@@ -86,10 +86,10 @@ export function initProject(targetDir, options = {}) {
|
|
|
86
86
|
node: ">=24.12.0",
|
|
87
87
|
},
|
|
88
88
|
dependencies: {
|
|
89
|
-
"@actiondock/sdk": "^2.
|
|
89
|
+
"@actiondock/sdk": "^2.11.0",
|
|
90
90
|
},
|
|
91
91
|
devDependencies: {
|
|
92
|
-
"@actiondock/testing": "^2.
|
|
92
|
+
"@actiondock/testing": "^2.11.0",
|
|
93
93
|
"@types/node": "^22.13.0",
|
|
94
94
|
"tsx": "^4.19.0",
|
|
95
95
|
"typescript": "^5.7.0",
|
package/dist/project/loader.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ActionDefinition } from "@actiondock/sdk";
|
|
2
|
-
import { type ModuleLoader } from "../
|
|
2
|
+
import { type ModuleLoader } from "../platform/module-loader.js";
|
|
3
3
|
import type { ActionDockManifest, PlaybookDefinition, PlaybookManifestEntry, ProjectConfig } from "./types.js";
|
|
4
4
|
/**
|
|
5
5
|
* 从指定目录开始向上逐级递归查找包含 `actiondock.json` 的项目根目录。
|
|
@@ -22,9 +22,9 @@ export declare const ALLOWED_INSTALLERS: Set<string>;
|
|
|
22
22
|
* 优先级:
|
|
23
23
|
* - 环境变量 ACTIONDOCK_INSTALLER 显式指定(严格白名单校验:npm, bun);
|
|
24
24
|
* - 依据项目根目录下现存的锁文件进行精确匹配(bun.lock/bun.lockb -> bun, package-lock.json/npm-shrinkwrap.json -> npm);
|
|
25
|
-
* -
|
|
25
|
+
* - 候选回退优先级异步探测(npm > bun)。
|
|
26
26
|
*/
|
|
27
|
-
export declare function getInstallCommand(projectRoot?: string): string[]
|
|
27
|
+
export declare function getInstallCommand(projectRoot?: string): Promise<string[]>;
|
|
28
28
|
/**
|
|
29
29
|
* 发现并检索项目 actions 目录下的所有 Action 源码文件(.ts)。
|
|
30
30
|
*
|
package/dist/project/loader.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
2
|
import { existsSync, readFileSync } from "node:fs";
|
|
3
3
|
import { basename, dirname, join, resolve } from "node:path";
|
|
4
|
-
import { NodeModuleLoader, unwrapDefaultExport } from "../
|
|
5
|
-
import { ACTION_LOAD_FAILED,
|
|
4
|
+
import { NodeModuleLoader, unwrapDefaultExport } from "../platform/module-loader.js";
|
|
5
|
+
import { ActionDockError, ACTION_LOAD_FAILED, describeActionLoadFailure, INVALID_ACTION_ID, INVALID_JSON, INVALID_PACKAGE_ID, INVALID_PLAYBOOK_ID, MANIFEST_LOAD_FAILED, PLAYBOOK_NOT_FOUND, PROJECT_CONFIG_NOT_FOUND, } from "../errors.js";
|
|
6
6
|
import { loadManifest, ACTION_ID_REGEX, PLAYBOOK_ID_REGEX } from "./manifest.js";
|
|
7
7
|
/**
|
|
8
8
|
* 从指定目录开始向上逐级递归查找包含 `actiondock.json` 的项目根目录。
|
|
@@ -47,14 +47,14 @@ import { PACKAGE_ID_REGEX, assertPathWithinRoot, assertWithinProjectRoot, traver
|
|
|
47
47
|
export function loadProjectConfig(projectRoot) {
|
|
48
48
|
const configPath = join(projectRoot, "actiondock.json");
|
|
49
49
|
if (!existsSync(configPath)) {
|
|
50
|
-
throw new
|
|
50
|
+
throw new ActionDockError(PROJECT_CONFIG_NOT_FOUND, `actiondock.json not found in ${projectRoot}`);
|
|
51
51
|
}
|
|
52
52
|
const content = readFileSync(configPath, "utf-8");
|
|
53
53
|
try {
|
|
54
54
|
const parsed = JSON.parse(content);
|
|
55
55
|
const isScoped = /^@[a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-]+$/.test(parsed.id);
|
|
56
56
|
if (!parsed.id || typeof parsed.id !== "string" || (!PACKAGE_ID_REGEX.test(parsed.id) && !isScoped)) {
|
|
57
|
-
throw new
|
|
57
|
+
throw new ActionDockError(INVALID_PACKAGE_ID, `actiondock.json invalid or missing 'id': '${parsed?.id}' (must match ${PACKAGE_ID_REGEX})`);
|
|
58
58
|
}
|
|
59
59
|
if (!parsed.name || typeof parsed.name !== "string") {
|
|
60
60
|
parsed.name = parsed.id;
|
|
@@ -71,18 +71,55 @@ export function loadProjectConfig(projectRoot) {
|
|
|
71
71
|
return parsed;
|
|
72
72
|
}
|
|
73
73
|
catch (err) {
|
|
74
|
-
|
|
74
|
+
if (err instanceof ActionDockError) {
|
|
75
|
+
throw err;
|
|
76
|
+
}
|
|
77
|
+
throw new ActionDockError(INVALID_JSON, `Failed to parse actiondock.json: ${err.message}`);
|
|
75
78
|
}
|
|
76
79
|
}
|
|
77
80
|
export const ALLOWED_INSTALLERS = new Set(["npm", "bun"]);
|
|
81
|
+
/**
|
|
82
|
+
* 异步探测包管理器可用性(通过执行 --version 并检查退出码)。
|
|
83
|
+
*
|
|
84
|
+
* 异步化取舍说明:
|
|
85
|
+
* - 替换同步阻塞子进程调用,避免阻塞 Node.js 事件循环;
|
|
86
|
+
* - Windows 兼容性保留:npm 在 Windows 上是 .cmd 批处理脚本,在 shell 开启下执行以避免直接调用报错;
|
|
87
|
+
* - 探测命令限制于白名单内部候选,杜绝外部输入注入风险。
|
|
88
|
+
*/
|
|
89
|
+
function probePackageManager(command) {
|
|
90
|
+
return new Promise((resolve) => {
|
|
91
|
+
try {
|
|
92
|
+
const proc = spawn(command, ["--version"], {
|
|
93
|
+
stdio: "pipe",
|
|
94
|
+
shell: process.platform === "win32",
|
|
95
|
+
});
|
|
96
|
+
let settled = false;
|
|
97
|
+
proc.on("error", () => {
|
|
98
|
+
if (!settled) {
|
|
99
|
+
settled = true;
|
|
100
|
+
resolve(false);
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
proc.on("close", (code) => {
|
|
104
|
+
if (!settled) {
|
|
105
|
+
settled = true;
|
|
106
|
+
resolve(code === 0);
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
resolve(false);
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
}
|
|
78
115
|
/**
|
|
79
116
|
* 探测宿主系统中可用的包管理工具。
|
|
80
117
|
* 优先级:
|
|
81
118
|
* - 环境变量 ACTIONDOCK_INSTALLER 显式指定(严格白名单校验:npm, bun);
|
|
82
119
|
* - 依据项目根目录下现存的锁文件进行精确匹配(bun.lock/bun.lockb -> bun, package-lock.json/npm-shrinkwrap.json -> npm);
|
|
83
|
-
* -
|
|
120
|
+
* - 候选回退优先级异步探测(npm > bun)。
|
|
84
121
|
*/
|
|
85
|
-
export function getInstallCommand(projectRoot) {
|
|
122
|
+
export async function getInstallCommand(projectRoot) {
|
|
86
123
|
const preferred = process.env.ACTIONDOCK_INSTALLER?.trim();
|
|
87
124
|
if (preferred) {
|
|
88
125
|
if (ALLOWED_INSTALLERS.has(preferred)) {
|
|
@@ -108,19 +145,9 @@ export function getInstallCommand(projectRoot) {
|
|
|
108
145
|
["bun", "install"],
|
|
109
146
|
];
|
|
110
147
|
for (const [pm, action] of candidates) {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
// Windows 兼容:npm 是 .cmd 脚本,无 shell 直接 spawn 必然 ENOENT,
|
|
115
|
-
// 会导致探测误判 npm 缺失而错误回退选择 bun
|
|
116
|
-
shell: process.platform === "win32",
|
|
117
|
-
});
|
|
118
|
-
if (check.status === 0) {
|
|
119
|
-
return [pm, action];
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
catch {
|
|
123
|
-
// 继续探测下一个候选包管理器
|
|
148
|
+
const available = await probePackageManager(pm);
|
|
149
|
+
if (available) {
|
|
150
|
+
return [pm, action];
|
|
124
151
|
}
|
|
125
152
|
}
|
|
126
153
|
return ["npm", "install"];
|
|
@@ -178,46 +205,39 @@ export async function loadActions(projectRoot, _actionsDir = "actions", options
|
|
|
178
205
|
manifest = loadManifest(projectRoot);
|
|
179
206
|
}
|
|
180
207
|
catch (err) {
|
|
181
|
-
throw new
|
|
208
|
+
throw new ActionDockError(MANIFEST_LOAD_FAILED, `Failed to load manifest in ${projectRoot}: ${err.message}`);
|
|
182
209
|
}
|
|
183
210
|
// 以 actiondock.json 为唯一事实源读取 Action 的元数据契约与入口
|
|
184
211
|
if (manifest?.actions && Object.keys(manifest.actions).length > 0) {
|
|
185
212
|
for (const [actionId, item] of Object.entries(manifest.actions)) {
|
|
186
213
|
if (!ACTION_ID_REGEX.test(actionId)) {
|
|
187
|
-
throw new
|
|
214
|
+
throw new ActionDockError(INVALID_ACTION_ID, `Invalid action ID '${actionId}' in actiondock.json. Action IDs must match ${ACTION_ID_REGEX}`);
|
|
188
215
|
}
|
|
189
216
|
const entryPath = resolve(projectRoot, item.entry);
|
|
190
217
|
assertPathWithinRoot(projectRoot, entryPath, `action entry '${item.entry}'`);
|
|
191
218
|
if (!existsSync(entryPath)) {
|
|
192
|
-
|
|
193
|
-
error.code = ACTION_LOAD_FAILED;
|
|
194
|
-
error.details = {
|
|
219
|
+
throw new ActionDockError(ACTION_LOAD_FAILED, `Action '${actionId}' entry file not found: ${item.entry}`, {
|
|
195
220
|
actionId,
|
|
196
221
|
entry: item.entry,
|
|
197
222
|
entryPath,
|
|
198
|
-
hint:
|
|
199
|
-
};
|
|
200
|
-
throw error;
|
|
223
|
+
hint: `Check whether the entry configuration '${item.entry}' for action '${actionId}' in actiondock.json is correct.`,
|
|
224
|
+
});
|
|
201
225
|
}
|
|
202
226
|
let imported;
|
|
203
227
|
try {
|
|
204
228
|
imported = await loader.load(entryPath);
|
|
205
229
|
}
|
|
206
230
|
catch (err) {
|
|
207
|
-
|
|
208
|
-
const msg = String(err.message || "");
|
|
209
|
-
const error = new Error(`Failed to load action '${actionId}' from '${item.entry}': ${msg}`);
|
|
210
|
-
error.code = ACTION_LOAD_FAILED;
|
|
211
|
-
error.details = {
|
|
231
|
+
const failure = describeActionLoadFailure(err, {
|
|
212
232
|
actionId,
|
|
233
|
+
packageId: manifest?.id || basename(projectRoot),
|
|
234
|
+
projectRoot,
|
|
235
|
+
});
|
|
236
|
+
throw new ActionDockError(failure.code, failure.message, {
|
|
237
|
+
...failure.details,
|
|
213
238
|
entry: item.entry,
|
|
214
239
|
entryPath,
|
|
215
|
-
|
|
216
|
-
hint: isMissingModuleError(msg)
|
|
217
|
-
? `项目依赖缺失,请在 '${projectRoot}' 目录下运行 npm install 安装依赖。`
|
|
218
|
-
: undefined,
|
|
219
|
-
};
|
|
220
|
-
throw error;
|
|
240
|
+
}, undefined, failure.details.hint);
|
|
221
241
|
}
|
|
222
242
|
const exported = unwrapDefaultExport(imported);
|
|
223
243
|
let runFn;
|
|
@@ -228,15 +248,12 @@ export async function loadActions(projectRoot, _actionsDir = "actions", options
|
|
|
228
248
|
runFn = exported.run.bind(exported);
|
|
229
249
|
}
|
|
230
250
|
if (!runFn) {
|
|
231
|
-
|
|
232
|
-
error.code = ACTION_LOAD_FAILED;
|
|
233
|
-
error.details = {
|
|
251
|
+
throw new ActionDockError(ACTION_LOAD_FAILED, `Action '${actionId}' in '${item.entry}' does not export a runnable handler`, {
|
|
234
252
|
actionId,
|
|
235
253
|
entry: item.entry,
|
|
236
254
|
entryPath,
|
|
237
|
-
hint:
|
|
238
|
-
};
|
|
239
|
-
throw error;
|
|
255
|
+
hint: `Ensure that '${item.entry}' exports a runnable handler using export default defineAction(...).`,
|
|
256
|
+
});
|
|
240
257
|
}
|
|
241
258
|
const def = {
|
|
242
259
|
run: runFn,
|
|
@@ -267,7 +284,7 @@ export function parsePlaybookContent(content, filePath, metadata) {
|
|
|
267
284
|
const defaultId = filename.replace(/\.md$/, "");
|
|
268
285
|
const playbookId = metadata?.id || defaultId;
|
|
269
286
|
if (!PLAYBOOK_ID_REGEX.test(playbookId)) {
|
|
270
|
-
throw new
|
|
287
|
+
throw new ActionDockError(INVALID_PLAYBOOK_ID, `Invalid playbook ID '${playbookId}' found in ${filePath}. Playbook IDs must match ${PLAYBOOK_ID_REGEX}`);
|
|
271
288
|
}
|
|
272
289
|
// 规程正文为纯 Markdown,剔除可能残存的旧式 Frontmatter 包裹块
|
|
273
290
|
const body = content.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/, "");
|
|
@@ -299,7 +316,12 @@ export function loadPlaybooks(projectRoot, _playbooksDir = "playbooks", customMa
|
|
|
299
316
|
try {
|
|
300
317
|
manifest = loadManifest(projectRoot);
|
|
301
318
|
}
|
|
302
|
-
catch {
|
|
319
|
+
catch (err) {
|
|
320
|
+
// 容错与可观测性取舍说明:
|
|
321
|
+
// - 工程无清单时 loadManifest 返回 null,按空规程集处理属正常场景;
|
|
322
|
+
// - 清单文件存在但内容损坏或解析失败时,记录可观测警告后安全降级,避免静默吞没异常。
|
|
323
|
+
const reason = err instanceof Error ? err.message : String(err ?? "Unknown error");
|
|
324
|
+
process.stderr.write(`[actiondock] Warning: Failed to load manifest in ${projectRoot}: ${reason}\n`);
|
|
303
325
|
return playbooks;
|
|
304
326
|
}
|
|
305
327
|
}
|
|
@@ -307,12 +329,12 @@ export function loadPlaybooks(projectRoot, _playbooksDir = "playbooks", customMa
|
|
|
307
329
|
if (manifest?.playbooks && Object.keys(manifest.playbooks).length > 0) {
|
|
308
330
|
for (const [playbookId, pbEntry] of Object.entries(manifest.playbooks)) {
|
|
309
331
|
if (!PLAYBOOK_ID_REGEX.test(playbookId)) {
|
|
310
|
-
throw new
|
|
332
|
+
throw new ActionDockError(INVALID_PLAYBOOK_ID, `Invalid playbook ID '${playbookId}' in actiondock.json. Playbook IDs must match ${PLAYBOOK_ID_REGEX}`);
|
|
311
333
|
}
|
|
312
334
|
const fullPath = resolve(projectRoot, pbEntry.entry);
|
|
313
335
|
assertPathWithinRoot(projectRoot, fullPath, `playbook entry '${pbEntry.entry}'`);
|
|
314
336
|
if (!existsSync(fullPath)) {
|
|
315
|
-
throw new
|
|
337
|
+
throw new ActionDockError(PLAYBOOK_NOT_FOUND, `Playbook file '${pbEntry.entry}' for '${playbookId}' not found in ${projectRoot}`);
|
|
316
338
|
}
|
|
317
339
|
const content = readFileSync(fullPath, "utf-8");
|
|
318
340
|
const def = parsePlaybookContent(content, fullPath, {
|