@actiondock/cli 2.7.0 → 2.9.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 +6 -7
- package/bin/ad.js +52 -23
- package/dist/commands/action/create.js +4 -5
- package/dist/commands/add.js +9 -17
- package/dist/commands/build.js +3 -18
- package/dist/commands/config/delete.js +8 -7
- package/dist/commands/config/env.js +7 -11
- package/dist/commands/config/get.js +8 -5
- package/dist/commands/config/list.js +16 -22
- package/dist/commands/config/schema.d.ts +7 -1
- package/dist/commands/config/schema.js +44 -67
- package/dist/commands/config/set.js +9 -7
- package/dist/commands/describe.js +10 -9
- package/dist/commands/doctor.js +4 -3
- package/dist/commands/export.js +5 -13
- package/dist/commands/generate.js +4 -3
- package/dist/commands/info.js +3 -1
- package/dist/commands/init.js +3 -3
- package/dist/commands/link.js +4 -4
- package/dist/commands/list.js +19 -10
- package/dist/commands/mcp.js +27 -66
- package/dist/commands/pack.js +3 -12
- package/dist/commands/playbook.js +30 -10
- package/dist/commands/profile.js +10 -19
- package/dist/commands/remove.js +5 -14
- package/dist/commands/run.d.ts +5 -2
- package/dist/commands/run.js +19 -20
- package/dist/commands/runs.js +26 -35
- package/dist/commands/serve.js +177 -57
- package/dist/commands/state-list.d.ts +2 -2
- package/dist/commands/state-list.js +10 -8
- package/dist/commands/state.d.ts +2 -2
- package/dist/commands/state.js +110 -103
- package/dist/commands/test.js +1 -1
- package/dist/commands/validate.js +4 -17
- package/dist/errors.d.ts +14 -0
- package/dist/errors.js +46 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +3 -3
- package/dist/renderer.d.ts +1 -1
- package/dist/renderer.js +1 -1
- package/dist/services/config-merge.d.ts +6 -5
- package/dist/services/config-merge.js +10 -8
- package/dist/services/inspect.js +3 -1
- package/dist/standalone/index.d.ts +4 -14
- package/dist/standalone/index.js +5 -44
- package/dist/types.d.ts +2 -1
- package/dist/types.js +1 -1
- package/dist/utils/cert.js +1 -1
- package/dist/utils/duration.d.ts +1 -1
- package/dist/utils/duration.js +1 -1
- package/dist/utils/index.d.ts +1 -2
- package/dist/utils/index.js +1 -2
- package/dist/utils/input.d.ts +9 -1
- package/dist/utils/input.js +12 -1
- package/dist/utils/server-launch.d.ts +114 -0
- package/dist/utils/server-launch.js +275 -0
- package/dist/utils/target.d.ts +14 -13
- package/dist/utils/target.js +20 -39
- package/dist/utils/utils.d.ts +101 -1
- package/dist/utils/utils.js +97 -7
- package/package.json +5 -6
- package/dist/utils/bytes.d.ts +0 -1
- package/dist/utils/bytes.js +0 -1
- package/dist/utils/filter.d.ts +0 -1
- package/dist/utils/filter.js +0 -1
package/dist/commands/state.js
CHANGED
|
@@ -1,32 +1,78 @@
|
|
|
1
|
-
import { decodeStateKey,
|
|
1
|
+
import { decodeStateKey, } from "@actiondock/sdk";
|
|
2
|
+
import { findProjectRoot, loadProjectConfig, } from "@actiondock/core";
|
|
3
|
+
import { resolvePackageRoot, } from "@actiondock/core/registry";
|
|
2
4
|
import { ArgumentError, ExecutionError, packageNotFoundError } from "../errors.js";
|
|
3
5
|
import { renderResult, renderStateList, writeStderr, writeStdout } from "../renderer.js";
|
|
4
|
-
import { applyTargetOptions, getEffectiveOptions, getTargetRoot, resolveIntent,
|
|
6
|
+
import { applyTargetOptions, getEffectiveOptions, getTargetRoot, remoteTargetLabel, resolveFallbackStrategy, resolveIntent, resolveTargetFromOptions, withService, } from "../utils/index.js";
|
|
5
7
|
import { renderLinkedPackagesStateList, renderProjectScopedStateList, } from "./state-list.js";
|
|
6
8
|
/**
|
|
7
|
-
*
|
|
9
|
+
* 统一预解码状态键寻址(get、set、delete 共享的单一事实源)。
|
|
8
10
|
*
|
|
9
|
-
*
|
|
11
|
+
* 三命令在 CLI 层即完成 `ns:key` 复合键解耦:
|
|
12
|
+
* - 显式 `-n/--namespace` 指定时命名空间以其为准,键保持原串不拆解;
|
|
13
|
+
* - 未显式指定时按复合键语法预解码出命名空间与裸键;
|
|
14
|
+
* - 多冒号歧义键降级为整串裸键并提示。
|
|
15
|
+
*
|
|
16
|
+
* 歧义键(如 `review:owner/repo:42`)不再直接崩溃:
|
|
10
17
|
* 降级为「整串作为裸 key」并在 stderr 提示可用 -n/--namespace 显式指定,
|
|
11
18
|
* 保持与本地存储层歧义兜底(deleteStateSmart 的纯键回退)一致的容错语义。
|
|
19
|
+
*
|
|
20
|
+
* 本地与远端分支都消费解耦后的 namespace + key,
|
|
21
|
+
* 消除「set 预解码而 get/delete 依赖存储层回退」的语义不对称。
|
|
12
22
|
*/
|
|
13
|
-
function
|
|
23
|
+
function resolveStateAddress(rawKey, options, context) {
|
|
24
|
+
if (options.namespace !== undefined) {
|
|
25
|
+
return { namespace: options.namespace, key: rawKey };
|
|
26
|
+
}
|
|
14
27
|
try {
|
|
15
28
|
const decoded = decodeStateKey(rawKey);
|
|
16
29
|
return { namespace: decoded.namespace || undefined, key: decoded.key };
|
|
17
30
|
}
|
|
18
31
|
catch {
|
|
19
|
-
|
|
20
|
-
writeStderr(`[WARN] State key '${rawKey}' contains multiple colon delimiters; treating it as a bare key. Use -n/--namespace to specify the namespace explicitly.`, context);
|
|
21
|
-
}
|
|
32
|
+
writeStderr(`[WARN] State key '${rawKey}' contains multiple colon delimiters; treating it as a bare key. Use -n/--namespace to specify the namespace explicitly.`, context);
|
|
22
33
|
return { namespace: undefined, key: rawKey };
|
|
23
34
|
}
|
|
24
35
|
}
|
|
36
|
+
/**
|
|
37
|
+
* 解析状态命令目标作用域(remote 与 local 双分支模板的单一事实源)。
|
|
38
|
+
*
|
|
39
|
+
* 远端分支直接以 -P 参数寻址,消息后缀固定为 "on remote server";
|
|
40
|
+
* 本地分支基于 getTargetRoot 完成包寻址(含从键中剥离 pkg/ 前缀),
|
|
41
|
+
* 以工程配置 id 寻址,消息后缀为 "in/from package '...'"。
|
|
42
|
+
*
|
|
43
|
+
* @param resolved 目标拓扑解析结果
|
|
44
|
+
* @param options 命令选项视图
|
|
45
|
+
* @param rawKey 原始键(本地分支用于剥离 pkg/ 前缀)
|
|
46
|
+
* @returns 作用域解析结果(含包标识、消息后缀、本地根目录与工程配置)
|
|
47
|
+
*/
|
|
48
|
+
function resolveStateScope(resolved, options, rawKey) {
|
|
49
|
+
if (resolved.type === "remote") {
|
|
50
|
+
return {
|
|
51
|
+
packageId: options.package || "",
|
|
52
|
+
scopeSuffix: "on remote server",
|
|
53
|
+
deleteSuffix: "on remote server",
|
|
54
|
+
effectiveKey: rawKey || "",
|
|
55
|
+
projConfig: undefined,
|
|
56
|
+
localRoot: undefined,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
// 本地项目模式:getTargetRoot 会从键中剥离 pkg/ 前缀,寻址基于解耦后的裸键
|
|
60
|
+
const { root, key: effectiveKey } = getTargetRoot(options.package, rawKey);
|
|
61
|
+
const projConfig = loadProjectConfig(root);
|
|
62
|
+
return {
|
|
63
|
+
packageId: projConfig.id,
|
|
64
|
+
scopeSuffix: `in package '${projConfig.id}'`,
|
|
65
|
+
deleteSuffix: `from package '${projConfig.id}'`,
|
|
66
|
+
effectiveKey,
|
|
67
|
+
projConfig,
|
|
68
|
+
localRoot: root,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
25
71
|
/**
|
|
26
72
|
* 注册 state 状态管理命令(get、set、delete、clear、keys、list)。
|
|
27
73
|
*
|
|
28
|
-
* @param program Commander
|
|
29
|
-
* @param context
|
|
74
|
+
* @param program Commander 根程序对象
|
|
75
|
+
* @param context CLI 上下文
|
|
30
76
|
*/
|
|
31
77
|
export function registerStateCommands(program, context) {
|
|
32
78
|
const stateCmd = program
|
|
@@ -36,18 +82,18 @@ export function registerStateCommands(program, context) {
|
|
|
36
82
|
const handleListKeys = async (prefix = "", rawOptions, cmd) => {
|
|
37
83
|
const options = getEffectiveOptions(rawOptions, cmd);
|
|
38
84
|
const effectiveIntent = resolveIntent(options.intent, prefix ? [prefix] : []);
|
|
39
|
-
const shouldFallback = options
|
|
40
|
-
await
|
|
85
|
+
const { shouldFallback } = resolveFallbackStrategy(options);
|
|
86
|
+
await withService(options, context, async (service, resolved) => {
|
|
41
87
|
const actionId = options.action || "";
|
|
42
88
|
// 远端服务模式:直接列举远端包作用域键
|
|
43
89
|
if (resolved.type === "remote") {
|
|
44
|
-
const keys = await
|
|
90
|
+
const keys = (await service.management?.state.list(options.package || "", actionId, {
|
|
45
91
|
namespace: options.namespace,
|
|
46
92
|
prefix,
|
|
47
|
-
});
|
|
93
|
+
})) ?? [];
|
|
48
94
|
renderResult(keys, {
|
|
49
95
|
json: options.json,
|
|
50
|
-
humanFormatter: () => renderStateList(keys,
|
|
96
|
+
humanFormatter: () => renderStateList(keys, remoteTargetLabel(resolved), false, effectiveIntent),
|
|
51
97
|
context,
|
|
52
98
|
});
|
|
53
99
|
return;
|
|
@@ -65,7 +111,7 @@ export function registerStateCommands(program, context) {
|
|
|
65
111
|
}
|
|
66
112
|
if (targetRoot) {
|
|
67
113
|
await renderProjectScopedStateList({
|
|
68
|
-
|
|
114
|
+
service,
|
|
69
115
|
targetRoot,
|
|
70
116
|
actionId,
|
|
71
117
|
prefix,
|
|
@@ -77,7 +123,7 @@ export function registerStateCommands(program, context) {
|
|
|
77
123
|
return;
|
|
78
124
|
}
|
|
79
125
|
await renderLinkedPackagesStateList({
|
|
80
|
-
|
|
126
|
+
service,
|
|
81
127
|
actionId,
|
|
82
128
|
prefix,
|
|
83
129
|
options,
|
|
@@ -96,6 +142,7 @@ export function registerStateCommands(program, context) {
|
|
|
96
142
|
.option("-n, --namespace <ns>", "Filter keys under specific namespace (omit to list all namespaces)"))
|
|
97
143
|
.option("-i, --intent <pattern>", "Regex or fuzzy intent filter; falls back to full list when no match")
|
|
98
144
|
.option("--detail", "Include metadata (ttl, expiresAt, size, updatedAt) in JSON output")
|
|
145
|
+
.option("--fallback", "Enable fallback to full list when no items match intent")
|
|
99
146
|
.option("--no-fallback", "Disable fallback to full list when no items match intent")
|
|
100
147
|
.option("--data-dir <path>", "Custom database storage directory")
|
|
101
148
|
.option("--json", "Output as JSON")
|
|
@@ -109,6 +156,7 @@ export function registerStateCommands(program, context) {
|
|
|
109
156
|
.option("-n, --namespace <ns>", "Filter keys under specific namespace"))
|
|
110
157
|
.option("-i, --intent <pattern>", "Regex or fuzzy intent filter")
|
|
111
158
|
.option("--detail", "Include metadata in JSON output")
|
|
159
|
+
.option("--fallback", "Enable fallback to full list when no items match intent")
|
|
112
160
|
.option("--no-fallback", "Disable fallback")
|
|
113
161
|
.option("--data-dir <path>", "Custom database storage directory")
|
|
114
162
|
.option("--json", "Output as JSON")
|
|
@@ -127,45 +175,28 @@ export function registerStateCommands(program, context) {
|
|
|
127
175
|
if (!rawKey) {
|
|
128
176
|
throw new ArgumentError("State key is required");
|
|
129
177
|
}
|
|
130
|
-
|
|
178
|
+
// 惰性作用域缓存:仅本地分支求值一次,避免远端模式触发包寻址副作用
|
|
179
|
+
let scope;
|
|
180
|
+
const resolveScope = (resolved) => (scope ??= resolveStateScope(resolved, options, rawKey));
|
|
181
|
+
await withService(options, context, async (service, resolved) => {
|
|
131
182
|
const actionId = options.action || "";
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
const entry = await target.getState(options.package || "", actionId, actualKey, {
|
|
137
|
-
namespace: effectiveNamespace,
|
|
138
|
-
detail: true,
|
|
139
|
-
});
|
|
140
|
-
if (entry === undefined || entry.value === undefined) {
|
|
141
|
-
throw new ExecutionError(`State key '${rawKey}' not found on remote server`);
|
|
142
|
-
}
|
|
143
|
-
const val = entry.value;
|
|
144
|
-
renderResult({ key: rawKey, value: val, namespace: entry.namespace || effectiveNamespace }, {
|
|
145
|
-
json: options.json,
|
|
146
|
-
humanFormatter: () => (typeof val === "object" ? JSON.stringify(val, null, 2) : String(val)),
|
|
147
|
-
context,
|
|
148
|
-
});
|
|
149
|
-
return;
|
|
150
|
-
}
|
|
151
|
-
// 本地项目模式
|
|
152
|
-
const { root, key: effectiveKey } = getTargetRoot(options.package, rawKey);
|
|
153
|
-
const projConfig = loadProjectConfig(root);
|
|
154
|
-
const entry = await target.getState(projConfig.id, actionId, effectiveKey, {
|
|
155
|
-
namespace: options.namespace,
|
|
183
|
+
const targetScope = resolveScope(resolved);
|
|
184
|
+
const address = resolveStateAddress(targetScope.effectiveKey, options, context);
|
|
185
|
+
const entry = await service.management?.state.get(targetScope.packageId, actionId, address.key, {
|
|
186
|
+
namespace: address.namespace,
|
|
156
187
|
detail: true,
|
|
157
188
|
});
|
|
158
189
|
if (entry === undefined || entry.value === undefined) {
|
|
159
|
-
throw new ExecutionError(`State key '${rawKey}' not found
|
|
190
|
+
throw new ExecutionError(`State key '${rawKey}' not found ${targetScope.scopeSuffix}`);
|
|
160
191
|
}
|
|
161
192
|
const val = entry.value;
|
|
162
|
-
const matchedNamespace = entry.namespace;
|
|
193
|
+
const matchedNamespace = resolved.type === "remote" ? (entry.namespace || address.namespace) : entry.namespace;
|
|
163
194
|
renderResult({ key: rawKey, value: val, namespace: matchedNamespace }, {
|
|
164
195
|
json: options.json,
|
|
165
196
|
humanFormatter: () => (typeof val === "object" ? JSON.stringify(val, null, 2) : String(val)),
|
|
166
197
|
context,
|
|
167
198
|
});
|
|
168
|
-
}, { localRoot: () =>
|
|
199
|
+
}, { localRoot: () => resolveScope(resolveTargetFromOptions(options, context)).localRoot });
|
|
169
200
|
});
|
|
170
201
|
// state set <key> <value>
|
|
171
202
|
applyTargetOptions(stateCmd
|
|
@@ -192,36 +223,21 @@ export function registerStateCommands(program, context) {
|
|
|
192
223
|
if (options.ttl && (isNaN(ttlSec) || ttlSec <= 0)) {
|
|
193
224
|
throw new ArgumentError(`Invalid --ttl value: '${options.ttl}'. Must be a positive integer.`);
|
|
194
225
|
}
|
|
195
|
-
|
|
226
|
+
// 惰性作用域缓存:仅本地分支求值一次,避免远端模式触发包寻址副作用
|
|
227
|
+
let scope;
|
|
228
|
+
const resolveScope = (resolved) => (scope ??= resolveStateScope(resolved, options, rawKey));
|
|
229
|
+
await withService(options, context, async (service, resolved) => {
|
|
196
230
|
const actionId = options.action || "";
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
await target.setState(options.package || "", actionId, actualKey, parsedVal, {
|
|
202
|
-
namespace: effectiveNamespace,
|
|
203
|
-
ttl: ttlSec,
|
|
204
|
-
});
|
|
205
|
-
writeStdout(`[OK] State '${rawKey}' updated on remote server`, context);
|
|
206
|
-
return;
|
|
207
|
-
}
|
|
208
|
-
// 本地项目模式
|
|
209
|
-
const { root, key: effectiveKey } = getTargetRoot(options.package, rawKey);
|
|
210
|
-
const projConfig = loadProjectConfig(root);
|
|
211
|
-
let actualNamespace = options.namespace;
|
|
212
|
-
let finalKey = effectiveKey;
|
|
213
|
-
if (options.namespace === undefined && effectiveKey.includes(":")) {
|
|
214
|
-
const decoded = decodeStateKeyWithFallback(effectiveKey, false, context);
|
|
215
|
-
actualNamespace = decoded.namespace;
|
|
216
|
-
finalKey = decoded.key;
|
|
217
|
-
}
|
|
218
|
-
await target.setState(projConfig.id, actionId, finalKey, parsedVal, {
|
|
219
|
-
namespace: actualNamespace,
|
|
231
|
+
const targetScope = resolveScope(resolved);
|
|
232
|
+
const address = resolveStateAddress(targetScope.effectiveKey, options, context);
|
|
233
|
+
await service.management?.state.set(targetScope.packageId, actionId, address.key, parsedVal, {
|
|
234
|
+
namespace: address.namespace,
|
|
220
235
|
ttl: ttlSec,
|
|
221
236
|
});
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
237
|
+
// 远端回显原始键;本地回显解耦后的 ns:key 复合视图
|
|
238
|
+
const displayKey = resolved.type === "remote" ? rawKey : (address.namespace ? `${address.namespace}:${address.key}` : address.key);
|
|
239
|
+
writeStdout(`[OK] State '${displayKey}' updated ${targetScope.scopeSuffix}`, context);
|
|
240
|
+
}, { localRoot: () => resolveScope(resolveTargetFromOptions(options, context)).localRoot });
|
|
225
241
|
});
|
|
226
242
|
// state delete <key>
|
|
227
243
|
applyTargetOptions(stateCmd
|
|
@@ -237,32 +253,21 @@ export function registerStateCommands(program, context) {
|
|
|
237
253
|
if (!rawKey) {
|
|
238
254
|
throw new ArgumentError("State key is required");
|
|
239
255
|
}
|
|
240
|
-
|
|
256
|
+
// 惰性作用域缓存:仅本地分支求值一次,避免远端模式触发包寻址副作用
|
|
257
|
+
let scope;
|
|
258
|
+
const resolveScope = (resolved) => (scope ??= resolveStateScope(resolved, options, rawKey));
|
|
259
|
+
await withService(options, context, async (service, resolved) => {
|
|
241
260
|
const actionId = options.action || "";
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
const deleted = await target.deleteState(options.package || "", actionId, actualKey, {
|
|
247
|
-
namespace: effectiveNamespace,
|
|
248
|
-
});
|
|
249
|
-
if (!deleted) {
|
|
250
|
-
throw new ExecutionError(`State key '${rawKey}' not found on remote server`);
|
|
251
|
-
}
|
|
252
|
-
writeStdout(`[OK] State '${rawKey}' deleted on remote server`, context);
|
|
253
|
-
return;
|
|
254
|
-
}
|
|
255
|
-
// 本地项目模式
|
|
256
|
-
const { root, key: effectiveKey } = getTargetRoot(options.package, rawKey);
|
|
257
|
-
const projConfig = loadProjectConfig(root);
|
|
258
|
-
const deleted = await target.deleteState(projConfig.id, actionId, effectiveKey, {
|
|
259
|
-
namespace: options.namespace,
|
|
261
|
+
const targetScope = resolveScope(resolved);
|
|
262
|
+
const address = resolveStateAddress(targetScope.effectiveKey, options, context);
|
|
263
|
+
const deleted = await service.management?.state.delete(targetScope.packageId, actionId, address.key, {
|
|
264
|
+
namespace: address.namespace,
|
|
260
265
|
});
|
|
261
266
|
if (!deleted) {
|
|
262
|
-
throw new ExecutionError(`State key '${rawKey}' not found
|
|
267
|
+
throw new ExecutionError(`State key '${rawKey}' not found ${targetScope.scopeSuffix}`);
|
|
263
268
|
}
|
|
264
|
-
writeStdout(`[OK] State '${rawKey}' deleted
|
|
265
|
-
}, { localRoot: () =>
|
|
269
|
+
writeStdout(`[OK] State '${rawKey}' deleted ${targetScope.deleteSuffix}`, context);
|
|
270
|
+
}, { localRoot: () => resolveScope(resolveTargetFromOptions(options, context)).localRoot });
|
|
266
271
|
});
|
|
267
272
|
// state clear
|
|
268
273
|
applyTargetOptions(stateCmd
|
|
@@ -279,27 +284,29 @@ export function registerStateCommands(program, context) {
|
|
|
279
284
|
if (!options.all && options.namespace === undefined) {
|
|
280
285
|
throw new ArgumentError("Must specify either -n/--namespace <name> to clear specific namespace, or --all to clear entire package state store.");
|
|
281
286
|
}
|
|
282
|
-
|
|
287
|
+
// 惰性作用域缓存:仅本地分支求值一次,避免远端模式触发包寻址副作用
|
|
288
|
+
let scope;
|
|
289
|
+
const resolveScope = (resolved) => (scope ??= resolveStateScope(resolved, options));
|
|
290
|
+
await withService(options, context, async (service, resolved) => {
|
|
283
291
|
const actionId = options.action || "";
|
|
284
292
|
if (resolved.type === "remote") {
|
|
285
|
-
const count = await
|
|
293
|
+
const count = (await service.management?.state.clear(options.package || "", actionId, {
|
|
286
294
|
namespace: options.namespace,
|
|
287
295
|
all: Boolean(options.all),
|
|
288
|
-
});
|
|
296
|
+
})) ?? 0;
|
|
289
297
|
writeStdout(`[OK] Cleared ${count} state entry(s) on remote server`, context);
|
|
290
298
|
return;
|
|
291
299
|
}
|
|
292
300
|
// 本地项目模式
|
|
293
|
-
const
|
|
294
|
-
const
|
|
295
|
-
const count = await target.clearState(projConfig.id, actionId, {
|
|
301
|
+
const targetScope = resolveScope(resolved);
|
|
302
|
+
const count = (await service.management?.state.clear(targetScope.packageId, actionId, {
|
|
296
303
|
namespace: options.namespace,
|
|
297
304
|
all: Boolean(options.all),
|
|
298
|
-
});
|
|
305
|
+
})) ?? 0;
|
|
299
306
|
const scopeDesc = options.all
|
|
300
307
|
? "all namespaces"
|
|
301
308
|
: `namespace '${options.namespace}'`;
|
|
302
|
-
writeStdout(`[OK] Cleared ${count} state entry(s) in ${scopeDesc} for package '${projConfig.id}'`, context);
|
|
303
|
-
}, { localRoot: () =>
|
|
309
|
+
writeStdout(`[OK] Cleared ${count} state entry(s) in ${scopeDesc} for package '${targetScope.projConfig.id}'`, context);
|
|
310
|
+
}, { localRoot: () => resolveScope(resolveTargetFromOptions(options, context)).localRoot });
|
|
304
311
|
});
|
|
305
312
|
}
|
package/dist/commands/test.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
-
import { findProjectRoot } from "@actiondock/core";
|
|
3
|
+
import { findProjectRoot, } from "@actiondock/core";
|
|
4
4
|
import { writeStderr } from "../renderer.js";
|
|
5
5
|
import { spawnAsync } from "../utils/index.js";
|
|
6
6
|
export function registerTestCommand(program) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { checkGeneratedTypes,
|
|
2
|
-
import { ArgumentError, ExecutionError
|
|
1
|
+
import { checkGeneratedTypes, GENERATED_TYPES_OUTDATED_CODE, loadActions, } from "@actiondock/core/project";
|
|
2
|
+
import { ArgumentError, ExecutionError } from "../errors.js";
|
|
3
3
|
import { renderActionValidation, renderResult } from "../renderer.js";
|
|
4
|
-
import { getEffectiveOptions } from "../utils/index.js";
|
|
4
|
+
import { getEffectiveOptions, requirePackageRoot } from "../utils/index.js";
|
|
5
5
|
/**
|
|
6
6
|
* 挂载 validate 子命令至指定 Commander 节点。
|
|
7
7
|
*
|
|
@@ -17,20 +17,7 @@ export function attachValidateCommand(parent, context) {
|
|
|
17
17
|
.option("--data-dir <path>", "Custom database storage directory")
|
|
18
18
|
.action(async (id, rawOptions, cmd) => {
|
|
19
19
|
const options = getEffectiveOptions(rawOptions, cmd);
|
|
20
|
-
|
|
21
|
-
if (options.package) {
|
|
22
|
-
root = resolvePackageRoot(options.package);
|
|
23
|
-
if (!root) {
|
|
24
|
-
throw packageNotFoundError(options.package);
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
else {
|
|
28
|
-
root = findProjectRoot();
|
|
29
|
-
}
|
|
30
|
-
if (!root) {
|
|
31
|
-
throw notInProjectError("Please specify -P, --package <id> or cd into a project directory.");
|
|
32
|
-
}
|
|
33
|
-
const config = loadProjectConfig(root);
|
|
20
|
+
const { root, projConfig: config } = requirePackageRoot(options.package, { loadConfig: true });
|
|
34
21
|
const actions = await loadActions(root, config.actionsDir);
|
|
35
22
|
let toValidate = [];
|
|
36
23
|
if (id) {
|
package/dist/errors.d.ts
CHANGED
|
@@ -20,6 +20,20 @@ export declare class ArgumentError extends CliError {
|
|
|
20
20
|
export declare class ExecutionError extends CliError {
|
|
21
21
|
constructor(message: string, details?: unknown, code?: string);
|
|
22
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* 将任意异常统一包裹为 CLI 执行错误(错误码保留辅助)。
|
|
25
|
+
*
|
|
26
|
+
* 包裹规则:
|
|
27
|
+
* - CliError 与 ActionDockError 原样透传(保留原始 code、details 与退出码映射);
|
|
28
|
+
* - 其余异常包裹为 ExecutionError,并保留 cause 链接与数字型 code 字段,
|
|
29
|
+
* 避免 core 抛出的结构化错误码被一律降级为 EXECUTION_FAILURE。
|
|
30
|
+
* - messagePrefix 仅对需要包裹的分支生效(附加命令语境说明);
|
|
31
|
+
* 透传分支保持原始实例不变,不重建消息。
|
|
32
|
+
*
|
|
33
|
+
* @param err 待包裹的原始异常
|
|
34
|
+
* @param messagePrefix 可选的消息前缀(仅包裹分支生效)
|
|
35
|
+
*/
|
|
36
|
+
export declare function wrapAsExecutionError(err: unknown, messagePrefix?: string): Error;
|
|
23
37
|
/**
|
|
24
38
|
* 用户中断信号错误(退出码为 130)。
|
|
25
39
|
*/
|
package/dist/errors.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ActionDockError, INVALID_ARGUMENT, INVALID_PACKAGE_ID, PACKAGE_NOT_FOUND, ACTION_NOT_FOUND, NOT_FOUND, PATH_TRAVERSAL, INVALID_FLAT_ARGUMENT, INVALID_JSON, INVALID_JSON_LITERAL, INPUT_PATH_CONFLICT, FLAT_INPUT_LIMIT_EXCEEDED, INPUT_LIMIT_EXCEEDED, INPUT_POLICY_VIOLATION, INPUT_CONFLICT, INPUT_FILE_NOT_FOUND, INPUT_FILE_READ_FAILED, } from "@actiondock/core";
|
|
2
|
+
import { InputError } from "@actiondock/core/project";
|
|
2
3
|
import { ExitCode } from "./types.js";
|
|
3
4
|
/**
|
|
4
5
|
* 扁平与解析输入错误码集合。
|
|
@@ -55,6 +56,33 @@ export class ExecutionError extends CliError {
|
|
|
55
56
|
this.name = "ExecutionError";
|
|
56
57
|
}
|
|
57
58
|
}
|
|
59
|
+
/**
|
|
60
|
+
* 将任意异常统一包裹为 CLI 执行错误(错误码保留辅助)。
|
|
61
|
+
*
|
|
62
|
+
* 包裹规则:
|
|
63
|
+
* - CliError 与 ActionDockError 原样透传(保留原始 code、details 与退出码映射);
|
|
64
|
+
* - 其余异常包裹为 ExecutionError,并保留 cause 链接与数字型 code 字段,
|
|
65
|
+
* 避免 core 抛出的结构化错误码被一律降级为 EXECUTION_FAILURE。
|
|
66
|
+
* - messagePrefix 仅对需要包裹的分支生效(附加命令语境说明);
|
|
67
|
+
* 透传分支保持原始实例不变,不重建消息。
|
|
68
|
+
*
|
|
69
|
+
* @param err 待包裹的原始异常
|
|
70
|
+
* @param messagePrefix 可选的消息前缀(仅包裹分支生效)
|
|
71
|
+
*/
|
|
72
|
+
export function wrapAsExecutionError(err, messagePrefix) {
|
|
73
|
+
if (err instanceof CliError || err instanceof ActionDockError) {
|
|
74
|
+
return err;
|
|
75
|
+
}
|
|
76
|
+
const originalMessage = err instanceof Error ? err.message : String(err);
|
|
77
|
+
const message = messagePrefix ? `${messagePrefix}${originalMessage}` : originalMessage;
|
|
78
|
+
const rawCode = err?.code;
|
|
79
|
+
const code = typeof rawCode === "string" && rawCode ? rawCode : "EXECUTION_FAILURE";
|
|
80
|
+
const wrapped = new ExecutionError(message, undefined, code);
|
|
81
|
+
if (err instanceof Error) {
|
|
82
|
+
wrapped.cause = err;
|
|
83
|
+
}
|
|
84
|
+
return wrapped;
|
|
85
|
+
}
|
|
58
86
|
/**
|
|
59
87
|
* 用户中断信号错误(退出码为 130)。
|
|
60
88
|
*/
|
|
@@ -104,6 +132,21 @@ export function formatError(err) {
|
|
|
104
132
|
details: err.details,
|
|
105
133
|
};
|
|
106
134
|
}
|
|
135
|
+
if (err instanceof ActionDockError) {
|
|
136
|
+
const isArgument = err.code === INVALID_ARGUMENT ||
|
|
137
|
+
err.code === INVALID_PACKAGE_ID ||
|
|
138
|
+
err.code === PACKAGE_NOT_FOUND ||
|
|
139
|
+
err.code === ACTION_NOT_FOUND ||
|
|
140
|
+
err.code === NOT_FOUND ||
|
|
141
|
+
err.code === PATH_TRAVERSAL ||
|
|
142
|
+
FLAT_ERROR_CODES.has(err.code);
|
|
143
|
+
return {
|
|
144
|
+
code: err.code,
|
|
145
|
+
message: err.message,
|
|
146
|
+
exitCode: isArgument ? ExitCode.INVALID_ARGUMENT : ExitCode.FAILURE,
|
|
147
|
+
details: err.details,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
107
150
|
// Commander.js 原生错误处理
|
|
108
151
|
if (typeof err === "object" && err !== null && "code" in err && typeof err.code === "string") {
|
|
109
152
|
const code = err.code;
|
|
@@ -140,9 +183,10 @@ export function formatError(err) {
|
|
|
140
183
|
}
|
|
141
184
|
if (err instanceof Error) {
|
|
142
185
|
return {
|
|
143
|
-
code: CLI_CODE_ERROR,
|
|
186
|
+
code: err.code || CLI_CODE_ERROR,
|
|
144
187
|
message: err.message,
|
|
145
188
|
exitCode: ExitCode.FAILURE,
|
|
189
|
+
details: err.details,
|
|
146
190
|
};
|
|
147
191
|
}
|
|
148
192
|
return {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type InvocationControl } from "./types.js";
|
|
1
|
+
import { type InvocationControl, type CliContext } from "./types.js";
|
|
2
2
|
export * from "./types.js";
|
|
3
3
|
export * from "./errors.js";
|
|
4
4
|
export * from "./renderer.js";
|
|
@@ -14,7 +14,7 @@ export * from "./commands/index.js";
|
|
|
14
14
|
* @param control 可选的中断与取消控制契约
|
|
15
15
|
* @returns 进程退出码
|
|
16
16
|
*/
|
|
17
|
-
export declare function main(argv?: string[], control?: InvocationControl): Promise<number>;
|
|
17
|
+
export declare function main(argv?: string[], control?: InvocationControl, contextOptions?: Partial<CliContext>): Promise<number>;
|
|
18
18
|
/**
|
|
19
19
|
* 命令行独立进程适配器。
|
|
20
20
|
* 仅在命令行可执行入口调用,负责全局 SIGINT 监听与退出状态码写入。
|
package/dist/index.js
CHANGED
|
@@ -18,8 +18,8 @@ export * from "./commands/index.js";
|
|
|
18
18
|
* @param control 可选的中断与取消控制契约
|
|
19
19
|
* @returns 进程退出码
|
|
20
20
|
*/
|
|
21
|
-
export async function main(argv = process.argv, control) {
|
|
22
|
-
const context = { control, exitCode: 0 };
|
|
21
|
+
export async function main(argv = process.argv, control, contextOptions) {
|
|
22
|
+
const context = { control, exitCode: 0, ...contextOptions };
|
|
23
23
|
const program = createCliProgram(context);
|
|
24
24
|
const isMachine = argv.includes("--json");
|
|
25
25
|
const normalizedArgv = argv.length >= 2 &&
|
|
@@ -43,7 +43,7 @@ export async function main(argv = process.argv, control) {
|
|
|
43
43
|
}
|
|
44
44
|
catch (err) {
|
|
45
45
|
if (control?.cancellationSource === "sigint" &&
|
|
46
|
-
(control?.signal?.aborted || err?.name === "AbortError" || err?.
|
|
46
|
+
(control?.signal?.aborted || err?.name === "AbortError" || err?.code === "SIGINT_INTERRUPTED" || err instanceof SigintError)) {
|
|
47
47
|
return ExitCode.SIGINT;
|
|
48
48
|
}
|
|
49
49
|
if (err instanceof CommanderError) {
|
package/dist/renderer.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { RunRecord } from "@actiondock/sdk";
|
|
2
|
-
import { type RegistryStatusReport } from "@actiondock/core";
|
|
2
|
+
import { type RegistryStatusReport } from "@actiondock/core/registry";
|
|
3
3
|
import type { Envelope, ProjectDetailInfo, AggregatedPackage, EnvCheckItem, CliContext } from "./types.js";
|
|
4
4
|
/**
|
|
5
5
|
* 构造标准成功结果信封。
|
package/dist/renderer.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type ConfigItemDefinition } from "@actiondock/core/project";
|
|
2
|
+
import { type ActionDockService } from "@actiondock/core";
|
|
2
3
|
/**
|
|
3
4
|
* 配置合并视图条目:跨作用域合并后的键值展示结构。
|
|
4
5
|
* source 标签语义:project(包级持久化)> global(全局持久化)> env(环境变量)> default(声明默认值)。
|
|
@@ -19,10 +20,10 @@ export interface MergedConfigEntry {
|
|
|
19
20
|
* @param key 配置键名
|
|
20
21
|
* @param packageId 包标识(用于包级作用域寻址与环境变量解析)
|
|
21
22
|
* @param declaredItem 工程声明的配置项定义
|
|
22
|
-
* @param
|
|
23
|
+
* @param service 已创建的 Service 服务实例
|
|
23
24
|
* @param reveal 是否揭示敏感值明文(为假且敏感键时打码)
|
|
24
25
|
*/
|
|
25
|
-
export declare function resolveMergedConfigEntry(key: string, packageId: string, declaredItem: ConfigItemDefinition | undefined,
|
|
26
|
+
export declare function resolveMergedConfigEntry(key: string, packageId: string, declaredItem: ConfigItemDefinition | undefined, service: ActionDockService, reveal: boolean): Promise<{
|
|
26
27
|
value: unknown;
|
|
27
28
|
source: MergedConfigEntry["source"];
|
|
28
29
|
secret: boolean;
|
|
@@ -35,7 +36,7 @@ export declare function resolveMergedConfigEntry(key: string, packageId: string,
|
|
|
35
36
|
* 消除 config get 与 config list 两命令间的合并实现差异。
|
|
36
37
|
*
|
|
37
38
|
* @param root 工程根目录
|
|
38
|
-
* @param
|
|
39
|
+
* @param service 已创建的 Service 服务实例
|
|
39
40
|
* @param reveal 是否揭示敏感值明文
|
|
40
41
|
*/
|
|
41
|
-
export declare function buildMergedConfigEntries(root: string,
|
|
42
|
+
export declare function buildMergedConfigEntries(root: string, service: ActionDockService, reveal: boolean): Promise<MergedConfigEntry[]>;
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import { isSecretConfigKey,
|
|
1
|
+
import { isSecretConfigKey, maskSecretValue, } from "@actiondock/core/project";
|
|
2
|
+
import { loadProjectConfig, } from "@actiondock/core";
|
|
3
|
+
import { resolveEnvValue, } from "@actiondock/core/profile";
|
|
2
4
|
/**
|
|
3
5
|
* 解析单个配置键的合并视图(项目包级 > 全局持久化 > 环境变量 > 声明默认值)。
|
|
4
6
|
*
|
|
@@ -8,11 +10,11 @@ import { isSecretConfigKey, loadProjectConfig, maskSecretValue, resolveEnvValue,
|
|
|
8
10
|
* @param key 配置键名
|
|
9
11
|
* @param packageId 包标识(用于包级作用域寻址与环境变量解析)
|
|
10
12
|
* @param declaredItem 工程声明的配置项定义
|
|
11
|
-
* @param
|
|
13
|
+
* @param service 已创建的 Service 服务实例
|
|
12
14
|
* @param reveal 是否揭示敏感值明文(为假且敏感键时打码)
|
|
13
15
|
*/
|
|
14
|
-
export async function resolveMergedConfigEntry(key, packageId, declaredItem,
|
|
15
|
-
const confView = await
|
|
16
|
+
export async function resolveMergedConfigEntry(key, packageId, declaredItem, service, reveal) {
|
|
17
|
+
const confView = await service.management?.config.get(packageId, key);
|
|
16
18
|
const envResolved = resolveEnvValue(key, declaredItem, packageId);
|
|
17
19
|
let rawValue;
|
|
18
20
|
let source;
|
|
@@ -40,14 +42,14 @@ export async function resolveMergedConfigEntry(key, packageId, declaredItem, tar
|
|
|
40
42
|
* 消除 config get 与 config list 两命令间的合并实现差异。
|
|
41
43
|
*
|
|
42
44
|
* @param root 工程根目录
|
|
43
|
-
* @param
|
|
45
|
+
* @param service 已创建的 Service 服务实例
|
|
44
46
|
* @param reveal 是否揭示敏感值明文
|
|
45
47
|
*/
|
|
46
|
-
export async function buildMergedConfigEntries(root,
|
|
48
|
+
export async function buildMergedConfigEntries(root, service, reveal) {
|
|
47
49
|
const projConfig = loadProjectConfig(root);
|
|
48
50
|
const declared = projConfig.config || {};
|
|
49
|
-
const projectConfigList = await
|
|
50
|
-
const globalConfigList = await
|
|
51
|
+
const projectConfigList = (await service.management?.config.list(projConfig.id)) ?? [];
|
|
52
|
+
const globalConfigList = (await service.management?.config.list("global")) ?? [];
|
|
51
53
|
const projectConfigMap = new Map(projectConfigList.map((c) => [c.key, c]));
|
|
52
54
|
const globalConfigMap = new Map(globalConfigList.map((c) => [c.key, c]));
|
|
53
55
|
const allKeys = new Set([
|
package/dist/services/inspect.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
|
-
import { findProjectRoot,
|
|
2
|
+
import { findProjectRoot, loadProjectConfig, } from "@actiondock/core";
|
|
3
|
+
import { listLinkedPackages, } from "@actiondock/core/registry";
|
|
4
|
+
import { loadManifest, loadPlaybooks, } from "@actiondock/core/project";
|
|
3
5
|
/**
|
|
4
6
|
* 装配单个工程的详情自省信息(清单、动作映射与 Playbook 映射)。
|
|
5
7
|
*
|
|
@@ -1,22 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { type InvocationControl } from "@actiondock/core/server";
|
|
2
2
|
import type { StandaloneOptions } from "../types.js";
|
|
3
3
|
export { type InvocationControl };
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
6
|
-
*/
|
|
7
|
-
export declare class StandaloneDispatcher extends CoreStandaloneDispatcher {
|
|
8
|
-
constructor(options: StandaloneOptions);
|
|
9
|
-
}
|
|
10
|
-
/**
|
|
11
|
-
* 运行独立二进制参数解析分发器并返回退出状态码。
|
|
5
|
+
* 运行独立二进制参数解析分发器并返回退出状态码(委托 Core 统一实现)。
|
|
12
6
|
*/
|
|
13
7
|
export declare function runStandaloneCli(argv: string[] | undefined, options: StandaloneOptions, control?: InvocationControl): Promise<number>;
|
|
14
8
|
/**
|
|
15
|
-
*
|
|
16
|
-
*/
|
|
17
|
-
export declare function createStandaloneDispatcher(options: StandaloneOptions): StandaloneDispatcher;
|
|
18
|
-
/**
|
|
19
|
-
* 独立二进制进程入口适配器。
|
|
9
|
+
* 独立二进制进程入口适配器(事实源位于 core 包,此处 re-export 维持既有导入路径兼容)。
|
|
20
10
|
* 仅在命令行可执行入口调用,负责全局 SIGINT 监听与退出状态码写入。
|
|
21
11
|
*/
|
|
22
|
-
export
|
|
12
|
+
export { runStandaloneProcess } from "@actiondock/core/server";
|