@actiondock/cli 2.6.0 → 2.8.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 +34 -22
- package/bin/ad.js +4 -4
- package/dist/commands/action/create.js +9 -25
- 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 +15 -26
- 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 +6 -2
- package/dist/commands/run.js +60 -37
- package/dist/commands/runs.js +26 -35
- package/dist/commands/serve.js +66 -44
- 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 +78 -2
- package/dist/index.d.ts +8 -1
- package/dist/index.js +60 -8
- package/dist/renderer.d.ts +3 -2
- package/dist/renderer.js +4 -19
- 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 +7 -11
- package/dist/standalone/index.js +8 -17
- package/dist/types.d.ts +7 -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 +5 -42
- package/dist/utils/input.js +7 -84
- package/dist/utils/server-launch.d.ts +61 -0
- package/dist/utils/server-launch.js +114 -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/remove.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { ArgumentError, ExecutionError, notInProjectError } from "../errors.js";
|
|
1
|
+
import { beginTransaction, getInstallCommand, loadLockfile, saveLockfile, saveManifest, } from "@actiondock/core/project";
|
|
2
|
+
import { ArgumentError, ExecutionError } from "../errors.js";
|
|
4
3
|
import { renderResult, writeStderr } from "../renderer.js";
|
|
5
|
-
import { assertSafePackageSpec, getEffectiveOptions, spawnAsync } from "../utils/index.js";
|
|
4
|
+
import { assertSafePackageSpec, getEffectiveOptions, requireProjectManifestRoot, rollbackAndRethrow, spawnAsync, } from "../utils/index.js";
|
|
6
5
|
/**
|
|
7
6
|
* 注册 ad remove 依赖移除命令。
|
|
8
7
|
* 遵循规范:移除依赖并更新锁文件,保留该包的历史存储数据,检查反向引用防止误删。
|
|
@@ -15,14 +14,7 @@ export function registerRemoveCommand(program, context) {
|
|
|
15
14
|
.option("--json", "Output as JSON")
|
|
16
15
|
.action(async (packageIdentifier, rawOptions, cmd) => {
|
|
17
16
|
const options = getEffectiveOptions(rawOptions, cmd);
|
|
18
|
-
const root
|
|
19
|
-
if (!root) {
|
|
20
|
-
throw notInProjectError("Please specify -P, --package <path> or cd into a project directory.");
|
|
21
|
-
}
|
|
22
|
-
const manifest = loadManifest(root);
|
|
23
|
-
if (!manifest) {
|
|
24
|
-
throw new ArgumentError(`actiondock.json not found in ${root}`);
|
|
25
|
-
}
|
|
17
|
+
const { root, manifest } = requireProjectManifestRoot(options.package);
|
|
26
18
|
// 寻址逻辑包标识
|
|
27
19
|
let targetPackageId = null;
|
|
28
20
|
let npmPackageName = packageIdentifier;
|
|
@@ -115,8 +107,7 @@ export function registerRemoveCommand(program, context) {
|
|
|
115
107
|
});
|
|
116
108
|
}
|
|
117
109
|
catch (err) {
|
|
118
|
-
await tx
|
|
119
|
-
throw new ExecutionError(err.message, err, err.code || "REMOVE_DEPENDENCY_FAILED");
|
|
110
|
+
await rollbackAndRethrow(tx, err, "REMOVE_DEPENDENCY_FAILED");
|
|
120
111
|
}
|
|
121
112
|
});
|
|
122
113
|
}
|
package/dist/commands/run.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type InvocationControl } from "@actiondock/core/server";
|
|
1
2
|
import type { ExecutionResult } from "@actiondock/sdk";
|
|
2
3
|
import { Command } from "commander";
|
|
3
4
|
import type { CliContext } from "../types.js";
|
|
@@ -9,9 +10,12 @@ import type { CliContext } from "../types.js";
|
|
|
9
10
|
*/
|
|
10
11
|
export declare function renderRawExecutionResult(targetRef: string, result: ExecutionResult, context?: CliContext): void;
|
|
11
12
|
/**
|
|
12
|
-
* 统一执行 Action 核心逻辑(使用
|
|
13
|
+
* 统一执行 Action 核心逻辑(使用 ActionDockService 服务端口)。
|
|
14
|
+
*
|
|
15
|
+
* @param id 目标 Action 标识
|
|
16
|
+
* @param options 执行选项(由 Command 统一构造)
|
|
13
17
|
*/
|
|
14
|
-
export declare function executeAction(id: string, options: any, context?: CliContext): Promise<void>;
|
|
18
|
+
export declare function executeAction(id: string, options: any, context?: CliContext, flatArgs?: string[], control?: InvocationControl): Promise<void>;
|
|
15
19
|
/**
|
|
16
20
|
* 挂载 run 子命令至指定 Commander 节点。
|
|
17
21
|
*
|
package/dist/commands/run.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { validateActionInputValue, mapInputValidationFailure, } from "@actiondock/core/project";
|
|
2
|
+
import { resolveTarget, } from "@actiondock/core/profile";
|
|
2
3
|
import { ArgumentError, ExecutionError, SigintError, packageNotFoundError } from "../errors.js";
|
|
3
4
|
import { writeStderr, writeStdout } from "../renderer.js";
|
|
4
|
-
import { applyTargetOptions, getEffectiveOptions, resolveActionInput, resolveLocalPackageRoot,
|
|
5
|
+
import { applyTargetOptions, getEffectiveOptions, resolveActionInput, resolveLocalPackageRoot, resolveTimeoutMs, withService, } from "../utils/index.js";
|
|
5
6
|
/**
|
|
6
7
|
* 以原始纯文本形式渲染 Action 执行终态结果(默认纯文本模式)。
|
|
7
8
|
*
|
|
@@ -86,30 +87,38 @@ export function renderRawExecutionResult(targetRef, result, context) {
|
|
|
86
87
|
? result.error.details
|
|
87
88
|
: JSON.stringify(result.error.details, null, 2), context);
|
|
88
89
|
}
|
|
89
|
-
|
|
90
|
+
if (context) {
|
|
91
|
+
context.exitCode = 1;
|
|
92
|
+
}
|
|
93
|
+
if (!context?.control) {
|
|
94
|
+
process.exitCode = 1;
|
|
95
|
+
}
|
|
90
96
|
}
|
|
91
97
|
}
|
|
92
98
|
/**
|
|
93
|
-
* 统一执行 Action 核心逻辑(使用
|
|
99
|
+
* 统一执行 Action 核心逻辑(使用 ActionDockService 服务端口)。
|
|
100
|
+
*
|
|
101
|
+
* @param id 目标 Action 标识
|
|
102
|
+
* @param options 执行选项(由 Command 统一构造)
|
|
94
103
|
*/
|
|
95
|
-
export async function executeAction(id, options, context) {
|
|
104
|
+
export async function executeAction(id, options, context, flatArgs, control) {
|
|
96
105
|
if (!id) {
|
|
97
106
|
throw new ArgumentError("Action ID is required for run");
|
|
98
107
|
}
|
|
108
|
+
const effectiveControl = control ?? context?.control;
|
|
109
|
+
const effectiveSignal = effectiveControl?.signal;
|
|
110
|
+
const effectiveFlatArgs = flatArgs ?? options.flatArgs;
|
|
99
111
|
const input = await resolveActionInput({
|
|
100
112
|
input: options.input,
|
|
101
113
|
inputFile: options.inputFile,
|
|
114
|
+
flatArgs: effectiveFlatArgs && effectiveFlatArgs.length > 0 ? effectiveFlatArgs : undefined,
|
|
102
115
|
stdin: context?.stdin,
|
|
103
116
|
});
|
|
104
|
-
|
|
105
|
-
if (
|
|
106
|
-
|
|
107
|
-
timeoutMs = parseDuration(options.timeout);
|
|
108
|
-
}
|
|
109
|
-
catch (err) {
|
|
110
|
-
throw new ArgumentError(`Invalid timeout format: ${err.message}`);
|
|
111
|
-
}
|
|
117
|
+
const check = validateActionInputValue(input);
|
|
118
|
+
if (!check.valid) {
|
|
119
|
+
throw mapInputValidationFailure("cli-pre-target", check);
|
|
112
120
|
}
|
|
121
|
+
const timeoutMs = resolveTimeoutMs(options.timeout);
|
|
113
122
|
const configOverrides = {};
|
|
114
123
|
if (options.config) {
|
|
115
124
|
const list = Array.isArray(options.config) ? options.config : [options.config];
|
|
@@ -119,13 +128,6 @@ export async function executeAction(id, options, context) {
|
|
|
119
128
|
configOverrides[k] = v.join("=");
|
|
120
129
|
}
|
|
121
130
|
}
|
|
122
|
-
const controller = new AbortController();
|
|
123
|
-
let receivedSigint = false;
|
|
124
|
-
const sigintHandler = () => {
|
|
125
|
-
receivedSigint = true;
|
|
126
|
-
controller.abort(new Error("Interrupted by SIGINT"));
|
|
127
|
-
};
|
|
128
|
-
process.once("SIGINT", sigintHandler);
|
|
129
131
|
try {
|
|
130
132
|
// 异步执行仅支持远端目标,本地模式直接拒绝
|
|
131
133
|
const resolvedTopology = resolveTarget({
|
|
@@ -143,15 +145,15 @@ export async function executeAction(id, options, context) {
|
|
|
143
145
|
throw packageNotFoundError(options.package);
|
|
144
146
|
}
|
|
145
147
|
let targetRef = id;
|
|
146
|
-
if (options.package && !id.includes("/")
|
|
148
|
+
if (options.package && !id.includes("/")) {
|
|
147
149
|
targetRef = `${options.package}/${id}`;
|
|
148
150
|
}
|
|
149
|
-
// 通过
|
|
150
|
-
await
|
|
151
|
+
// 通过 Service 统一执行
|
|
152
|
+
await withService(options, context, async (service) => {
|
|
151
153
|
const isMachine = Boolean(options.json);
|
|
152
154
|
if (options.async) {
|
|
153
|
-
const ticket = await
|
|
154
|
-
signal:
|
|
155
|
+
const ticket = await service.execution.start(targetRef, input, {
|
|
156
|
+
signal: effectiveSignal,
|
|
155
157
|
timeoutMs,
|
|
156
158
|
config: configOverrides,
|
|
157
159
|
requestId: options.requestId,
|
|
@@ -169,17 +171,23 @@ export async function executeAction(id, options, context) {
|
|
|
169
171
|
writeStdout(ticket.runId, context);
|
|
170
172
|
}
|
|
171
173
|
if (ticket.status === "failed") {
|
|
172
|
-
|
|
174
|
+
if (context) {
|
|
175
|
+
context.exitCode = 1;
|
|
176
|
+
}
|
|
173
177
|
return;
|
|
174
178
|
}
|
|
175
179
|
}
|
|
176
180
|
else {
|
|
177
|
-
const result = await
|
|
178
|
-
signal:
|
|
181
|
+
const result = await service.execution.run(targetRef, input, {
|
|
182
|
+
signal: effectiveSignal,
|
|
179
183
|
timeoutMs,
|
|
180
184
|
config: configOverrides,
|
|
181
185
|
requestId: options.requestId,
|
|
182
186
|
});
|
|
187
|
+
if (effectiveControl?.cancellationSource === "sigint" &&
|
|
188
|
+
(effectiveSignal?.aborted || (!result.ok && result.error?.code === "ACTION_CANCELLED"))) {
|
|
189
|
+
throw new SigintError();
|
|
190
|
+
}
|
|
183
191
|
if (isMachine) {
|
|
184
192
|
writeStdout(JSON.stringify(result, null, 2), context);
|
|
185
193
|
}
|
|
@@ -187,21 +195,26 @@ export async function executeAction(id, options, context) {
|
|
|
187
195
|
renderRawExecutionResult(targetRef, result, context);
|
|
188
196
|
}
|
|
189
197
|
if (!result.ok) {
|
|
190
|
-
|
|
198
|
+
if (context) {
|
|
199
|
+
context.exitCode = 1;
|
|
200
|
+
}
|
|
191
201
|
return;
|
|
192
202
|
}
|
|
193
203
|
}
|
|
194
204
|
}, { localRoot: targetPackageRoot || undefined, scanLinkedPackages: true, ownDataDir: true });
|
|
195
205
|
}
|
|
196
206
|
catch (err) {
|
|
197
|
-
|
|
207
|
+
// sigint 取消源、中断链路特征错误码或 SigintError 本身,均统一归并为 SigintError
|
|
208
|
+
// 原 if 中额外的「cancellationSource 为 sigint 且 AbortError/aborted/ACTION_CANCELLED」
|
|
209
|
+
// 子句被首项 cancellationSource === "sigint" 完全蕴含,等价化简后移除
|
|
210
|
+
const isSigint = effectiveControl?.cancellationSource === "sigint" ||
|
|
211
|
+
err?.code === "SIGINT_INTERRUPTED" ||
|
|
212
|
+
err instanceof SigintError;
|
|
213
|
+
if (isSigint) {
|
|
198
214
|
throw new SigintError();
|
|
199
215
|
}
|
|
200
216
|
throw err;
|
|
201
217
|
}
|
|
202
|
-
finally {
|
|
203
|
-
process.removeListener("SIGINT", sigintHandler);
|
|
204
|
-
}
|
|
205
218
|
}
|
|
206
219
|
/**
|
|
207
220
|
* 挂载 run 子命令至指定 Commander 节点。
|
|
@@ -211,7 +224,7 @@ export async function executeAction(id, options, context) {
|
|
|
211
224
|
*/
|
|
212
225
|
export function attachRunCommand(parent, context) {
|
|
213
226
|
const cmd = parent
|
|
214
|
-
.command("run <id>")
|
|
227
|
+
.command("run <id> [params...]")
|
|
215
228
|
.description("Execute an action (from current project, linked packages, or remote profile)")
|
|
216
229
|
.option("-P, --package <id>", "Target package ID or path")
|
|
217
230
|
.option("-i, --input <json>", "Action input as inline JSON")
|
|
@@ -223,9 +236,19 @@ export function attachRunCommand(parent, context) {
|
|
|
223
236
|
.option("--async", "Execute asynchronously in background (requires remote server or profile)")
|
|
224
237
|
.option("--data-dir <path>", "Custom database directory")
|
|
225
238
|
.option("--json", "Output as JSON")
|
|
226
|
-
.action(async (id, rawOptions, cmd) => {
|
|
227
|
-
|
|
228
|
-
|
|
239
|
+
.action(async (id, params, rawOptions, cmd) => {
|
|
240
|
+
let flatArgs;
|
|
241
|
+
let effectiveRawOptions = rawOptions;
|
|
242
|
+
let effectiveCmd = cmd;
|
|
243
|
+
if (Array.isArray(params)) {
|
|
244
|
+
flatArgs = params.length > 0 ? params : undefined;
|
|
245
|
+
}
|
|
246
|
+
else {
|
|
247
|
+
effectiveCmd = rawOptions;
|
|
248
|
+
effectiveRawOptions = params;
|
|
249
|
+
}
|
|
250
|
+
const options = getEffectiveOptions(effectiveRawOptions, effectiveCmd);
|
|
251
|
+
await executeAction(id, { ...options, flatArgs }, context, undefined, context?.control);
|
|
229
252
|
});
|
|
230
253
|
}
|
|
231
254
|
/**
|
package/dist/commands/runs.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { findProjectRoot, loadProjectConfig, } from "@actiondock/core";
|
|
2
|
+
import { filterWithFallbackInfo, } from "@actiondock/core/project";
|
|
3
|
+
import { listLinkedPackages, resolvePackageRoot, } from "@actiondock/core/registry";
|
|
4
|
+
import { ArgumentError, ExecutionError, NO_PROJECT_NO_LINKED_MESSAGE, packageNotFoundError, } from "../errors.js";
|
|
3
5
|
import { renderResult, renderRunDetail, renderRunsList } from "../renderer.js";
|
|
4
|
-
import { applyTargetOptions, getEffectiveOptions,
|
|
6
|
+
import { applyTargetOptions, getEffectiveOptions, remoteTargetSuffix, requirePackageRoot, resolveFallbackStrategy, resolveIntent, withService, } from "../utils/index.js";
|
|
5
7
|
/**
|
|
6
8
|
* 解析本地目标包根目录与工程配置(仅在 local 分支调用)。
|
|
7
9
|
* 显式指定包且寻址失败时抛出参数错误;工程清单损坏时降级为链接包视图。
|
|
@@ -50,21 +52,26 @@ export function registerRunsCommands(program, context) {
|
|
|
50
52
|
.option("-i, --intent <pattern>", "Regex or fuzzy intent filter; falls back to full list when no match")
|
|
51
53
|
.option("-a, --action <actionId>", "Filter by action ID")
|
|
52
54
|
.option("-n, --limit <count>", "Maximum number of records to return", "20"))
|
|
55
|
+
.option("--fallback", "Enable fallback to full list when no items match intent")
|
|
53
56
|
.option("--no-fallback", "Disable fallback to full list when no items match intent")
|
|
54
57
|
.option("--data-dir <path>", "Custom database storage directory")
|
|
55
58
|
.option("--json", "Output as JSON")
|
|
56
59
|
.action(async (patterns = [], rawOptions, cmd) => {
|
|
57
60
|
const options = getEffectiveOptions(rawOptions, cmd);
|
|
58
61
|
const effectiveIntent = resolveIntent(options.intent, patterns);
|
|
59
|
-
const shouldFallback = options
|
|
62
|
+
const { shouldFallback } = resolveFallbackStrategy(options);
|
|
60
63
|
const limit = Number.parseInt(options.limit, 10) || 20;
|
|
61
64
|
const scope = resolveLocalRunScope(options.package);
|
|
62
|
-
// 通过
|
|
63
|
-
await
|
|
64
|
-
|
|
65
|
+
// 通过 Service 门面统一访问
|
|
66
|
+
await withService(options, context, async (service, resolved) => {
|
|
67
|
+
// limit 下推查询层:intent 过滤发生在内存(过滤后仍需截断到用户值),
|
|
68
|
+
// 下推时附加合理裕量,保证过滤后可截断数量不少于用户请求
|
|
69
|
+
const FETCH_HEADROOM = 200;
|
|
70
|
+
const queryLimit = effectiveIntent ? limit + FETCH_HEADROOM : limit;
|
|
71
|
+
const records = await service.runs.list({
|
|
65
72
|
packageId: options.package,
|
|
66
73
|
actionId: options.action,
|
|
67
|
-
limit:
|
|
74
|
+
limit: queryLimit,
|
|
68
75
|
});
|
|
69
76
|
// 若处于本地无项目环境且无任何软链接包,则直接输出友好提示
|
|
70
77
|
if (resolved.type === "local" && !scope.targetPackageRoot) {
|
|
@@ -85,7 +92,7 @@ export function registerRunsCommands(program, context) {
|
|
|
85
92
|
humanFormatter: () => {
|
|
86
93
|
let title = "Execution Runs";
|
|
87
94
|
if (resolved.type === "remote") {
|
|
88
|
-
title = `Execution Runs
|
|
95
|
+
title = `Execution Runs ${remoteTargetSuffix(resolved)}`;
|
|
89
96
|
}
|
|
90
97
|
else if (scope.projConfig) {
|
|
91
98
|
title = `Execution Runs in ${scope.projConfig.name} (${scope.projConfig.id})`;
|
|
@@ -112,9 +119,9 @@ export function registerRunsCommands(program, context) {
|
|
|
112
119
|
throw new ArgumentError("Run ID is required");
|
|
113
120
|
}
|
|
114
121
|
const scope = resolveLocalRunScope(options.package);
|
|
115
|
-
// 通过
|
|
116
|
-
await
|
|
117
|
-
const run = await
|
|
122
|
+
// 通过 Service 门面统一查询
|
|
123
|
+
await withService(options, context, async (service, resolved) => {
|
|
124
|
+
const run = await service.runs.get(id);
|
|
118
125
|
if (!run) {
|
|
119
126
|
if (resolved.type === "remote") {
|
|
120
127
|
throw new ExecutionError(`Run record '${id}' not found on remote server`);
|
|
@@ -144,12 +151,8 @@ export function registerRunsCommands(program, context) {
|
|
|
144
151
|
if (!id) {
|
|
145
152
|
throw new ArgumentError("Run ID is required for cancel");
|
|
146
153
|
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
throw new ArgumentError("'ad runs cancel' is only supported for remote execution targets. Use --profile <name> or --server <url>.");
|
|
150
|
-
}
|
|
151
|
-
await withRemoteTarget(options, context, async (target) => {
|
|
152
|
-
const result = await target.cancelRun(id, options.reason);
|
|
154
|
+
await withService(options, context, async (service) => {
|
|
155
|
+
const result = await service.runs.cancel(id, options.reason);
|
|
153
156
|
const isErrorOutcome = result.outcome === "not_found" || result.outcome === "not_owner";
|
|
154
157
|
renderResult(result, {
|
|
155
158
|
json: options.json,
|
|
@@ -167,7 +170,7 @@ export function registerRunsCommands(program, context) {
|
|
|
167
170
|
if (isErrorOutcome) {
|
|
168
171
|
process.exitCode = 1;
|
|
169
172
|
}
|
|
170
|
-
});
|
|
173
|
+
}, { requireRemote: true });
|
|
171
174
|
});
|
|
172
175
|
// runs clear
|
|
173
176
|
applyTargetOptions(runsCmd
|
|
@@ -182,19 +185,7 @@ export function registerRunsCommands(program, context) {
|
|
|
182
185
|
let targetPackageRoot;
|
|
183
186
|
let packageId = options.package;
|
|
184
187
|
if (!options.profile && !options.server) {
|
|
185
|
-
|
|
186
|
-
const root = resolvePackageRoot(options.package);
|
|
187
|
-
if (!root) {
|
|
188
|
-
throw packageNotFoundError(options.package);
|
|
189
|
-
}
|
|
190
|
-
targetPackageRoot = root;
|
|
191
|
-
}
|
|
192
|
-
else {
|
|
193
|
-
targetPackageRoot = findProjectRoot() || undefined;
|
|
194
|
-
if (!targetPackageRoot) {
|
|
195
|
-
throw notInProjectError("Please specify -P, --package <id> or cd into a project directory.");
|
|
196
|
-
}
|
|
197
|
-
}
|
|
188
|
+
targetPackageRoot = requirePackageRoot(options.package).root;
|
|
198
189
|
try {
|
|
199
190
|
packageId = loadProjectConfig(targetPackageRoot).id;
|
|
200
191
|
}
|
|
@@ -202,9 +193,9 @@ export function registerRunsCommands(program, context) {
|
|
|
202
193
|
// 清单损坏时保留原始 -P 参数作为包标识
|
|
203
194
|
}
|
|
204
195
|
}
|
|
205
|
-
await
|
|
206
|
-
const count =
|
|
207
|
-
? await
|
|
196
|
+
await withService(options, context, async (service, resolved) => {
|
|
197
|
+
const count = service.runs.clear
|
|
198
|
+
? await service.runs.clear({ packageId, actionId: options.action })
|
|
208
199
|
: 0;
|
|
209
200
|
const payload = { ok: true, clearedCount: count };
|
|
210
201
|
renderResult(payload, {
|
package/dist/commands/serve.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
3
|
+
import { createActionDock, createNodePlatform, findProjectRoot, loadProjectConfig, startActionDockServer, } from "@actiondock/core";
|
|
4
|
+
import { resolvePackageRoot } from "@actiondock/core/registry";
|
|
5
|
+
import { formatHostForUrl, } from "@actiondock/core/server";
|
|
6
|
+
import { ArgumentError, ExecutionError, packageNotFoundError } from "../errors.js";
|
|
6
7
|
import { writeStderr, writeStdout } from "../renderer.js";
|
|
7
|
-
import { ensureSelfSignedCertificate, getEffectiveOptions,
|
|
8
|
+
import { ensureSelfSignedCertificate, getEffectiveOptions, normalizeCorsOrigins, registerStopSignalHandler, printServerBanner, resolveMaxBodyBytes, resolveServerHost, resolveServerPort, resolveServerToken, parseListOption, } from "../utils/index.js";
|
|
8
9
|
/**
|
|
9
10
|
* 注册 serve HTTP 服务启动命令。
|
|
10
11
|
*
|
|
@@ -31,17 +32,17 @@ export function registerServeCommand(program, context) {
|
|
|
31
32
|
.option("--tls-ca <path>", "Path to TLS CA certificate file (or set ACTIONDOCK_TLS_CA)")
|
|
32
33
|
.option("--tls-passphrase <passphrase>", "Passphrase for TLS private key (or set ACTIONDOCK_TLS_PASSPHRASE)")
|
|
33
34
|
.option("-d, --dir <path>", "Project root directory (default: current working directory)")
|
|
35
|
+
.option("-P, --package <package-id>", "Specific package ID(s) to serve (can be specified multiple times or comma-separated)", parseListOption, [])
|
|
34
36
|
.option("--data-dir <path>", "Custom database storage directory")
|
|
35
37
|
.action(async (rawOptions, cmd) => {
|
|
36
38
|
const options = getEffectiveOptions(rawOptions, cmd);
|
|
37
|
-
const
|
|
38
|
-
const
|
|
39
|
-
const
|
|
40
|
-
const token = options.token || (typeof process !== "undefined" ? process.env?.ACTIONDOCK_TOKEN : undefined);
|
|
39
|
+
const port = resolveServerPort(options.port, 5177);
|
|
40
|
+
const host = resolveServerHost(options.host);
|
|
41
|
+
const token = resolveServerToken(options.token);
|
|
41
42
|
const allowInsecureNoAuth = Boolean(options.allowInsecureNoAuth);
|
|
42
43
|
const allowQueryToken = Boolean(options.allowQueryToken);
|
|
43
44
|
const enableManagement = Boolean(options.management);
|
|
44
|
-
const corsOrigins = options.corsOrigin
|
|
45
|
+
const corsOrigins = normalizeCorsOrigins(options.corsOrigin);
|
|
45
46
|
const exposeDebugInfo = Boolean(options.exposeDebugInfo);
|
|
46
47
|
const httpsEnabled = Boolean(options.https ||
|
|
47
48
|
options.tlsCert ||
|
|
@@ -82,18 +83,46 @@ export function registerServeCommand(program, context) {
|
|
|
82
83
|
};
|
|
83
84
|
}
|
|
84
85
|
}
|
|
85
|
-
|
|
86
|
-
if (options.maxBody) {
|
|
87
|
-
try {
|
|
88
|
-
maxBodyBytes = parseByteSize(options.maxBody);
|
|
89
|
-
}
|
|
90
|
-
catch (err) {
|
|
91
|
-
throw new ArgumentError(`Invalid max-body format: ${err.message}`);
|
|
92
|
-
}
|
|
93
|
-
}
|
|
86
|
+
const maxBodyBytes = resolveMaxBodyBytes(options.maxBody);
|
|
94
87
|
const projectRoot = options.dir
|
|
95
88
|
? resolve(options.dir)
|
|
96
89
|
: findProjectRoot(process.cwd());
|
|
90
|
+
const rawPackages = options.package || [];
|
|
91
|
+
const packageAllowlist = rawPackages.length > 0 ? [] : undefined;
|
|
92
|
+
const resolvedPackageIds = [];
|
|
93
|
+
const explicitPackages = [];
|
|
94
|
+
if (rawPackages.length > 0) {
|
|
95
|
+
for (const pkgId of rawPackages) {
|
|
96
|
+
const root = resolvePackageRoot(pkgId, projectRoot || undefined, context?.customHome);
|
|
97
|
+
if (!root) {
|
|
98
|
+
throw packageNotFoundError(pkgId);
|
|
99
|
+
}
|
|
100
|
+
if (!projectRoot || root !== projectRoot) {
|
|
101
|
+
if (!explicitPackages.some((p) => p.packageRoot === root)) {
|
|
102
|
+
explicitPackages.push({ packageRoot: root });
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
let resolvedId = pkgId;
|
|
106
|
+
try {
|
|
107
|
+
const config = loadProjectConfig(root);
|
|
108
|
+
if (config.id) {
|
|
109
|
+
resolvedId = config.id;
|
|
110
|
+
if (!packageAllowlist.includes(config.id)) {
|
|
111
|
+
packageAllowlist.push(config.id);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
// 降级使用原始标识符
|
|
117
|
+
}
|
|
118
|
+
if (!packageAllowlist.includes(pkgId)) {
|
|
119
|
+
packageAllowlist.push(pkgId);
|
|
120
|
+
}
|
|
121
|
+
if (!resolvedPackageIds.includes(resolvedId)) {
|
|
122
|
+
resolvedPackageIds.push(resolvedId);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
97
126
|
let projectName = "Global Registry Mode";
|
|
98
127
|
if (projectRoot) {
|
|
99
128
|
try {
|
|
@@ -109,13 +138,14 @@ export function registerServeCommand(program, context) {
|
|
|
109
138
|
dataDir: options.dataDir || context?.dataDir,
|
|
110
139
|
rootDir: projectRoot || undefined,
|
|
111
140
|
});
|
|
112
|
-
const
|
|
141
|
+
const service = await createActionDock({
|
|
113
142
|
type: "local",
|
|
114
143
|
projectRoot: projectRoot || undefined,
|
|
144
|
+
packages: explicitPackages.length > 0 ? explicitPackages : undefined,
|
|
115
145
|
customHome: context?.customHome,
|
|
116
146
|
dataDir: options.dataDir || context?.dataDir,
|
|
117
147
|
platform,
|
|
118
|
-
scanLinkedPackages: !projectRoot,
|
|
148
|
+
scanLinkedPackages: !projectRoot || rawPackages.length > 0,
|
|
119
149
|
});
|
|
120
150
|
let mcpHandler;
|
|
121
151
|
const enableMcp = options.mcp !== false;
|
|
@@ -128,7 +158,9 @@ export function registerServeCommand(program, context) {
|
|
|
128
158
|
]);
|
|
129
159
|
const handler = createMcpHandler(() => {
|
|
130
160
|
return createActionDockMcpServer({
|
|
131
|
-
|
|
161
|
+
service,
|
|
162
|
+
packageAllowlist,
|
|
163
|
+
packageIds: packageAllowlist,
|
|
132
164
|
});
|
|
133
165
|
}, {
|
|
134
166
|
onerror: (err) => {
|
|
@@ -140,7 +172,7 @@ export function registerServeCommand(program, context) {
|
|
|
140
172
|
};
|
|
141
173
|
}
|
|
142
174
|
catch (err) {
|
|
143
|
-
await
|
|
175
|
+
await service.close().catch(() => { });
|
|
144
176
|
throw new ExecutionError(`Failed to initialize MCP endpoint: ${err?.message || String(err)}`, err);
|
|
145
177
|
}
|
|
146
178
|
}
|
|
@@ -159,16 +191,19 @@ export function registerServeCommand(program, context) {
|
|
|
159
191
|
mcpHandler,
|
|
160
192
|
tls,
|
|
161
193
|
projectRoot: projectRoot || undefined,
|
|
162
|
-
|
|
194
|
+
service,
|
|
195
|
+
packageAllowlist,
|
|
163
196
|
});
|
|
164
197
|
const displayHost = formatHostForUrl(host);
|
|
165
198
|
const actualEndpointHost = formatHostForUrl(host === "0.0.0.0" ? "127.0.0.1" : host);
|
|
166
199
|
const scheme = tls ? "https" : "http";
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
200
|
+
printServerBanner(`ActionDock 2.0 HTTP Runner Server`, scheme, displayHost, server.port, context);
|
|
201
|
+
if (resolvedPackageIds.length > 0) {
|
|
202
|
+
writeStdout(` * Packages: ${resolvedPackageIds.join(", ")}`, context);
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
writeStdout(` * Project: ${projectName}`, context);
|
|
206
|
+
}
|
|
172
207
|
if (projectRoot && exposeDebugInfo) {
|
|
173
208
|
writeStdout(` * Root Path: ${projectRoot}`, context);
|
|
174
209
|
}
|
|
@@ -183,30 +218,17 @@ export function registerServeCommand(program, context) {
|
|
|
183
218
|
writeStdout(` * Management APIs: ${enableManagement ? "Enabled" : "Disabled (Default)"}`, context);
|
|
184
219
|
writeStdout(` * CORS Origins: ${corsOrigins ? corsOrigins.join(", ") : "Disabled (Default)"}`, context);
|
|
185
220
|
writeStdout(` * Max Body Size: ${options.maxBody || "1mb"}`, context);
|
|
186
|
-
writeStdout(` * Health Endpoint: ${scheme}://${actualEndpointHost}:${server.port}/api/
|
|
221
|
+
writeStdout(` * Health Endpoint: ${scheme}://${actualEndpointHost}:${server.port}/api/v2/health`, context);
|
|
187
222
|
if (enableMcp) {
|
|
188
223
|
writeStdout(` * MCP Endpoint: ${scheme}://${actualEndpointHost}:${server.port}/mcp`, context);
|
|
189
224
|
}
|
|
190
225
|
writeStdout(`======================================================\n`, context);
|
|
191
226
|
writeStdout(`Server is ready to accept remote requests.`, context);
|
|
192
227
|
writeStdout(`Press Ctrl+C to terminate.\n`, context);
|
|
193
|
-
|
|
194
|
-
writeStdout("\nStopping ActionDock server...", context);
|
|
195
|
-
Promise.resolve(server.stop())
|
|
196
|
-
.catch((err) => {
|
|
197
|
-
// 停止失败必须可见:写入 stderr 一行并标记失败退出码
|
|
198
|
-
writeStderr(`[ERROR] Failed to stop ActionDock server gracefully: ${err instanceof Error ? err.message : String(err)}`, context);
|
|
199
|
-
process.exitCode = 1;
|
|
200
|
-
})
|
|
201
|
-
.finally(() => {
|
|
202
|
-
process.exit(typeof process.exitCode === "number" ? process.exitCode : 0);
|
|
203
|
-
});
|
|
204
|
-
};
|
|
205
|
-
process.once("SIGINT", stopSignalHandler);
|
|
206
|
-
process.once("SIGTERM", stopSignalHandler);
|
|
228
|
+
registerStopSignalHandler(server, `ActionDock server`, context);
|
|
207
229
|
}
|
|
208
230
|
catch (err) {
|
|
209
|
-
await
|
|
231
|
+
await service.close().catch(() => { });
|
|
210
232
|
throw new ExecutionError(`Failed to start ActionDock server: ${err.message}`, err);
|
|
211
233
|
}
|
|
212
234
|
});
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type ActionDockService } from "@actiondock/core";
|
|
2
2
|
import type { CliContext } from "../types.js";
|
|
3
3
|
/**
|
|
4
4
|
* 状态键列表渲染参数视图。
|
|
5
5
|
*/
|
|
6
6
|
export interface StateListRenderArgs {
|
|
7
|
-
|
|
7
|
+
service: ActionDockService;
|
|
8
8
|
actionId: string;
|
|
9
9
|
prefix: string;
|
|
10
10
|
options: any;
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { loadProjectConfig, } from "@actiondock/core";
|
|
2
|
+
import { filterWithFallbackInfo, } from "@actiondock/core/project";
|
|
3
|
+
import { listLinkedPackages, } from "@actiondock/core/registry";
|
|
2
4
|
import { existsSync } from "node:fs";
|
|
3
5
|
import { NO_PROJECT_NO_LINKED_MESSAGE } from "../errors.js";
|
|
4
6
|
import { renderResult, renderStateList } from "../renderer.js";
|
|
@@ -6,16 +8,16 @@ import { renderResult, renderStateList } from "../renderer.js";
|
|
|
6
8
|
* 渲染单个项目作用域的状态键列表(含 --detail JSON 明细分支)。
|
|
7
9
|
*/
|
|
8
10
|
export async function renderProjectScopedStateList(args) {
|
|
9
|
-
const {
|
|
11
|
+
const { service, targetRoot, actionId, prefix, options, effectiveIntent, shouldFallback, context } = args;
|
|
10
12
|
const projConfig = loadProjectConfig(targetRoot);
|
|
11
|
-
const allKeys = await
|
|
13
|
+
const allKeys = (await service.management?.state.list(projConfig.id, actionId, {
|
|
12
14
|
namespace: options.namespace !== undefined ? options.namespace : null,
|
|
13
15
|
prefix,
|
|
14
|
-
});
|
|
16
|
+
})) ?? [];
|
|
15
17
|
if (options.detail && options.json) {
|
|
16
18
|
const entries = [];
|
|
17
19
|
for (const k of allKeys) {
|
|
18
|
-
const entry = await
|
|
20
|
+
const entry = await service.management?.state.get(projConfig.id, actionId, k, {
|
|
19
21
|
namespace: options.namespace,
|
|
20
22
|
detail: true,
|
|
21
23
|
});
|
|
@@ -41,7 +43,7 @@ export async function renderProjectScopedStateList(args) {
|
|
|
41
43
|
* 渲染全部链接包聚合的状态键列表(跳过磁盘失效与清单损坏的链接项)。
|
|
42
44
|
*/
|
|
43
45
|
export async function renderLinkedPackagesStateList(args) {
|
|
44
|
-
const {
|
|
46
|
+
const { service, actionId, prefix, options, effectiveIntent, shouldFallback, context } = args;
|
|
45
47
|
// 扫描所有链接的包状态
|
|
46
48
|
const linked = listLinkedPackages();
|
|
47
49
|
if (linked.length === 0) {
|
|
@@ -58,10 +60,10 @@ export async function renderLinkedPackagesStateList(args) {
|
|
|
58
60
|
continue;
|
|
59
61
|
try {
|
|
60
62
|
const config = loadProjectConfig(pkg.path);
|
|
61
|
-
const keys = await
|
|
63
|
+
const keys = (await service.management?.state.list(config.id, actionId, {
|
|
62
64
|
namespace: options.namespace !== undefined ? options.namespace : null,
|
|
63
65
|
prefix,
|
|
64
|
-
});
|
|
66
|
+
})) ?? [];
|
|
65
67
|
for (const k of keys) {
|
|
66
68
|
aggregatedKeys.push(`${config.id}/${k}`);
|
|
67
69
|
}
|
package/dist/commands/state.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type { CliContext } from "../types.js";
|
|
|
3
3
|
/**
|
|
4
4
|
* 注册 state 状态管理命令(get、set、delete、clear、keys、list)。
|
|
5
5
|
*
|
|
6
|
-
* @param program Commander
|
|
7
|
-
* @param context
|
|
6
|
+
* @param program Commander 根程序对象
|
|
7
|
+
* @param context CLI 上下文
|
|
8
8
|
*/
|
|
9
9
|
export declare function registerStateCommands(program: Command, context?: CliContext): void;
|