@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/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @actiondock/cli
|
|
2
2
|
|
|
3
|
-
ActionDock 2.
|
|
3
|
+
ActionDock 2.x 官方命令行门面工具链。
|
|
4
4
|
|
|
5
5
|
[](https://nodejs.org/)
|
|
6
6
|
[](https://www.typescriptlang.org/)
|
|
@@ -15,7 +15,6 @@ ActionDock 2.0 官方命令行门面工具链。
|
|
|
15
15
|
- 原生基于 Node.js >=24.12.0 运行,充分依托原生类型擦除、内置 SQLite 与原生 HTTP 特性。
|
|
16
16
|
- 支持 npm、pnpm 与 yarn 标准包管理器进行全局安装与项目级管理。
|
|
17
17
|
- 日常开发、调试、测试、构建、打包、MCP 服务启动与 HTTP 部署完全基于 Node.js 与 npm 标准工作流,无需安装外部编译器。
|
|
18
|
-
- 单文件独立二进制构建(`--target`、`--bytecode`、`--standalone`)与 `actiondock.manifest.json` 已在 2.0 中彻底移除,由标准的 Node.js 目录交付产物与 npm 打包取代。
|
|
19
18
|
|
|
20
19
|
---
|
|
21
20
|
|
|
@@ -109,6 +108,7 @@ ad pack
|
|
|
109
108
|
|
|
110
109
|
- 严格目标定位:通过 `-P, --package <id|path>` 指定目标包。系统严格区分物理路径与注册表包标识符,若目标不存在则不向当前目录或父级目录隐式回退,直接以参数校验错误退出。
|
|
111
110
|
- 多目标检索契约:在执行多目标检索(如 `ad info` 或 `ad list`)时,若无任何匹配项,在机器模式下始终返回确定性空数组结构并以状态码 0 退出,不因空搜索产生异常中断。
|
|
111
|
+
- 服务端细粒度白名单:通过 `ad serve` 启动微服务时,支持通过 `-P, --package <package-id>` 与 `-A, --action <action-ref>` 分别指定包白名单与动作白名单(支持短名与全限定名,可多次指定或逗号分隔)。同时配置时取权限交集。未授权动作的详情调阅与执行均被阻断并返回 403 错误(错误码 `ACTION_FORBIDDEN`),历史运行记录与 MCP 协议工具暴露同步过滤。
|
|
112
112
|
|
|
113
113
|
### Action 执行入参契约
|
|
114
114
|
|
|
@@ -181,7 +181,7 @@ $data | ConvertTo-Json -Depth 100 | ad run complex-action --input-file -
|
|
|
181
181
|
| `ad export skill` | 导出 Agent Skill 技能包(支持 `--mode source` 与 `--mode node`) |
|
|
182
182
|
| `ad link` / `unlink` | 注册或注销工作区全局路由与符号链接 |
|
|
183
183
|
| `ad profile` | 管理远程执行节点凭证与环境配置 |
|
|
184
|
-
| `ad serve` | 启动远程 HTTP/HTTPS
|
|
184
|
+
| `ad serve` | 启动远程 HTTP/HTTPS 执行调度微服务(支持 `-P` 包白名单与 `-A` 动作白名单细粒度权限控制,原生支持 `--https` 自签名与生产证书) |
|
|
185
185
|
| `ad mcp` | 以 STDIO 或 HTTP 协议启动 MCP 服务 |
|
|
186
186
|
|
|
187
187
|
---
|
|
@@ -190,10 +190,9 @@ $data | ConvertTo-Json -Depth 100 | ad run complex-action --input-file -
|
|
|
190
190
|
|
|
191
191
|
作为顶层门面与独立运行分发器,`@actiondock/cli` 串联以下子包:
|
|
192
192
|
|
|
193
|
-
-
|
|
194
|
-
-
|
|
195
|
-
-
|
|
196
|
-
- 协议服务:通过 [@actiondock/mcp](../mcp/README.md) 启动协议监听。
|
|
193
|
+
- 领域内核与运行时:依赖 [@actiondock/core](../core/README.md),统一对接标准服务端口体系(`DiscoveryPort`、`ExecutionPort`、`RunsPort`、`ConfigPort`、`StatePort`)与统一服务门面(`createActionDock`、`connectActionDock`),承载原生运行时、存储驱动与受管进程治理。
|
|
194
|
+
- 构建与打包:依赖 [@actiondock/builder](../builder/README.md),完成依赖闭包规划、Node 目录交付产物构建、npm 打包与 Agent Skill 资产导出。
|
|
195
|
+
- 协议服务:通过 [@actiondock/mcp](../mcp/README.md) 启动 Model Context Protocol 协议监听与适配。
|
|
197
196
|
|
|
198
197
|
---
|
|
199
198
|
|
package/bin/ad.js
CHANGED
|
@@ -1,42 +1,71 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
3
|
import { existsSync } from "node:fs";
|
|
4
4
|
import { createRequire } from "node:module";
|
|
5
5
|
import { resolve } from "node:path";
|
|
6
6
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
7
7
|
|
|
8
|
-
const
|
|
8
|
+
const isBun = typeof process.versions.bun !== "undefined";
|
|
9
|
+
const hasTsx =
|
|
10
|
+
process.execArgv.some((arg, i) => arg === "--import" && process.execArgv[i + 1]?.includes("tsx")) ||
|
|
11
|
+
process.execArgv.some((arg) => arg.includes("tsx"));
|
|
9
12
|
|
|
10
|
-
if (
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
process.execArgv.some((arg, i) => arg === "--import" && process.execArgv[i + 1]?.includes("tsx")) ||
|
|
17
|
-
process.execArgv.some((arg) => arg.includes("tsx"));
|
|
18
|
-
|
|
19
|
-
if (!isBun && !hasTsx) {
|
|
20
|
-
const require = createRequire(import.meta.url);
|
|
21
|
-
let tsxSpecifier = null;
|
|
13
|
+
if (!isBun && !hasTsx) {
|
|
14
|
+
const require = createRequire(import.meta.url);
|
|
15
|
+
let tsxSpecifier = null;
|
|
16
|
+
try {
|
|
17
|
+
tsxSpecifier = pathToFileURL(require.resolve("tsx", { paths: [import.meta.dirname, process.cwd()] })).href;
|
|
18
|
+
} catch {
|
|
22
19
|
try {
|
|
23
20
|
tsxSpecifier = pathToFileURL(require.resolve("tsx")).href;
|
|
24
21
|
} catch {
|
|
25
22
|
// 未检测到 tsx 模块
|
|
26
23
|
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (tsxSpecifier) {
|
|
27
|
+
const child = spawn(
|
|
28
|
+
process.execPath,
|
|
29
|
+
["--import", tsxSpecifier, fileURLToPath(import.meta.url), ...process.argv.slice(2)],
|
|
30
|
+
{
|
|
31
|
+
stdio: "inherit",
|
|
32
|
+
}
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
const forwardSignal = (sig) => {
|
|
36
|
+
if (child.pid && !child.killed) {
|
|
37
|
+
try {
|
|
38
|
+
child.kill(sig);
|
|
39
|
+
} catch {}
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
process.on("SIGTERM", () => forwardSignal("SIGTERM"));
|
|
44
|
+
process.on("SIGINT", () => forwardSignal("SIGINT"));
|
|
45
|
+
process.on("SIGHUP", () => forwardSignal("SIGHUP"));
|
|
27
46
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
47
|
+
await new Promise((resolvePromise) => {
|
|
48
|
+
child.on("exit", (code, signal) => {
|
|
49
|
+
if (signal) {
|
|
50
|
+
process.removeListener("SIGTERM", forwardSignal);
|
|
51
|
+
process.removeListener("SIGINT", forwardSignal);
|
|
52
|
+
process.removeListener("SIGHUP", forwardSignal);
|
|
53
|
+
process.kill(process.pid, signal);
|
|
54
|
+
} else {
|
|
55
|
+
process.exit(code ?? 0);
|
|
34
56
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}
|
|
57
|
+
resolvePromise();
|
|
58
|
+
});
|
|
59
|
+
});
|
|
38
60
|
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const distEntry = resolve(import.meta.dirname, "../dist/index.js");
|
|
39
64
|
|
|
65
|
+
if (existsSync(distEntry)) {
|
|
66
|
+
const { runCliProcess } = await import(pathToFileURL(distEntry).href);
|
|
67
|
+
await runCliProcess(process.argv);
|
|
68
|
+
} else {
|
|
40
69
|
const { runCliProcess } = await import("../src/index.ts");
|
|
41
70
|
await runCliProcess(process.argv);
|
|
42
71
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
3
|
-
import { assertPathWithinRoot,
|
|
4
|
-
import {
|
|
3
|
+
import { assertPathWithinRoot, getPackageSlug, loadManifest, saveManifest, writeActionTypes, } from "@actiondock/core/project";
|
|
4
|
+
import { findProjectRoot, loadProjectConfig, } from "@actiondock/core";
|
|
5
|
+
import { ExecutionError, notInProjectError, wrapAsExecutionError } from "../../errors.js";
|
|
5
6
|
import { writeStdout } from "../../renderer.js";
|
|
6
7
|
import { attachDescribeCommand } from "../describe.js";
|
|
7
8
|
import { attachListCommand } from "../list.js";
|
|
@@ -210,8 +211,6 @@ ${returnBody}
|
|
|
210
211
|
writeStdout(` 复杂输入或大段文本:ad run ${id} --json --input-file input.json`, context);
|
|
211
212
|
}
|
|
212
213
|
catch (err) {
|
|
213
|
-
|
|
214
|
-
throw err;
|
|
215
|
-
throw new ExecutionError(err.message);
|
|
214
|
+
throw wrapAsExecutionError(err);
|
|
216
215
|
}
|
|
217
216
|
}
|
package/dist/commands/add.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
-
import { join
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { PackageGraphBuilder, } from "@actiondock/core/graph";
|
|
4
|
+
import { beginTransaction, computeManifestDigest, getInstallCommand, loadLockfile, MANIFEST_FILE_NAME, parseJsonWithoutDuplicates, saveLockfile, saveManifest, } from "@actiondock/core/project";
|
|
5
5
|
import { renderResult } from "../renderer.js";
|
|
6
|
-
import { assertSafePackageSpec, getEffectiveOptions, spawnAsync } from "../utils/index.js";
|
|
6
|
+
import { assertSafePackageSpec, getEffectiveOptions, requireProjectManifestRoot, rollbackAndRethrow, spawnAsync, } from "../utils/index.js";
|
|
7
7
|
/**
|
|
8
8
|
* 从安装说明符中提取基础 npm 包名(去除版本号及前缀范围)。
|
|
9
9
|
*/
|
|
@@ -36,14 +36,7 @@ export function registerAddCommand(program, context) {
|
|
|
36
36
|
.option("--json", "Output as JSON")
|
|
37
37
|
.action(async (packageSpec, rawOptions, cmd) => {
|
|
38
38
|
const options = getEffectiveOptions(rawOptions, cmd);
|
|
39
|
-
const root
|
|
40
|
-
if (!root) {
|
|
41
|
-
throw notInProjectError("Please specify -P, --package <path> or cd into a project directory.");
|
|
42
|
-
}
|
|
43
|
-
const manifest = loadManifest(root);
|
|
44
|
-
if (!manifest) {
|
|
45
|
-
throw new ArgumentError(`actiondock.json not found in ${root}`);
|
|
46
|
-
}
|
|
39
|
+
const { root, manifest } = requireProjectManifestRoot(options.package);
|
|
47
40
|
// 包说明符白名单前置校验:拒绝 shell 元字符,消除 win32 shell 拼接注入面
|
|
48
41
|
assertSafePackageSpec(packageSpec);
|
|
49
42
|
const npmPackageName = extractNpmPackageName(packageSpec);
|
|
@@ -97,9 +90,9 @@ export function registerAddCommand(program, context) {
|
|
|
97
90
|
manifest.dependencies = manifest.dependencies || {};
|
|
98
91
|
manifest.dependencies[targetPackageId] =
|
|
99
92
|
targetPackageId === npmPackageName ? `^${targetVersion}` : npmPackageName;
|
|
100
|
-
//
|
|
101
|
-
const
|
|
102
|
-
const graph =
|
|
93
|
+
// 触发依赖图构建及版本冲突检测
|
|
94
|
+
const builder = new PackageGraphBuilder({ projectRoot: root, manifest });
|
|
95
|
+
const graph = builder.buildSync();
|
|
103
96
|
// 更新 actiondock.lock.json
|
|
104
97
|
const lockfile = loadLockfile(root) || {
|
|
105
98
|
lockfileVersion: 1,
|
|
@@ -137,8 +130,7 @@ export function registerAddCommand(program, context) {
|
|
|
137
130
|
});
|
|
138
131
|
}
|
|
139
132
|
catch (err) {
|
|
140
|
-
await tx
|
|
141
|
-
throw new ExecutionError(err.message, err, err.code || "ADD_DEPENDENCY_FAILED");
|
|
133
|
+
await rollbackAndRethrow(tx, err, "ADD_DEPENDENCY_FAILED");
|
|
142
134
|
}
|
|
143
135
|
});
|
|
144
136
|
}
|
package/dist/commands/build.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { ExecutionError, notInProjectError, packageNotFoundError } from "../errors.js";
|
|
1
|
+
import { ExecutionError } from "../errors.js";
|
|
3
2
|
import { renderResult, writeStdout } from "../renderer.js";
|
|
4
|
-
import { getEffectiveOptions, parseListOption } from "../utils/index.js";
|
|
3
|
+
import { getEffectiveOptions, parseListOption, requirePackageRoot } from "../utils/index.js";
|
|
5
4
|
export function registerBuildCommand(program, context) {
|
|
6
5
|
program
|
|
7
6
|
.command("build")
|
|
@@ -14,23 +13,9 @@ export function registerBuildCommand(program, context) {
|
|
|
14
13
|
.option("--vendor-deps", "Materialize locked production dependencies into the output")
|
|
15
14
|
.option("--allow-install-scripts", "Allow lifecycle install scripts to run during vendor-deps")
|
|
16
15
|
.option("--require-reproducible", "Require reproducible build and fail if install scripts must run")
|
|
17
|
-
.option("-t, --target <target>", "Target compilation platform (removed)")
|
|
18
|
-
.option("--bytecode", "Bytecode compilation (removed)")
|
|
19
16
|
.action(async (rawOptions, cmd) => {
|
|
20
17
|
const options = getEffectiveOptions(rawOptions, cmd);
|
|
21
|
-
|
|
22
|
-
if (options.target !== undefined || options.bytecode !== undefined) {
|
|
23
|
-
throw new ExecutionError("Unsupported build mode: '--target' and '--bytecode' standalone single-file binary compilation have been removed in ActionDock 2.0. Directory-based Node.js builds are now the standard distribution format.", undefined, "UNSUPPORTED_BUILD_MODE");
|
|
24
|
-
}
|
|
25
|
-
const root = options.package
|
|
26
|
-
? resolvePackageRoot(options.package)
|
|
27
|
-
: findProjectRoot();
|
|
28
|
-
if (!root) {
|
|
29
|
-
if (options.package) {
|
|
30
|
-
throw packageNotFoundError(options.package);
|
|
31
|
-
}
|
|
32
|
-
throw notInProjectError("Please specify -P, --package <id> or cd into a project directory.");
|
|
33
|
-
}
|
|
18
|
+
const { root } = requirePackageRoot(options.package);
|
|
34
19
|
const isMachine = Boolean(options.json);
|
|
35
20
|
if (!isMachine) {
|
|
36
21
|
writeStdout("Building Node.js delivery artifact...");
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { loadProjectConfig,
|
|
1
|
+
import { loadProjectConfig, } from "@actiondock/core";
|
|
2
|
+
import { resolvePackageRoot, } from "@actiondock/core/registry";
|
|
2
3
|
import { ArgumentError, packageNotFoundError } from "../../errors.js";
|
|
3
4
|
import { writeStdout } from "../../renderer.js";
|
|
4
|
-
import { applyTargetOptions, getEffectiveOptions,
|
|
5
|
+
import { applyTargetOptions, getEffectiveOptions, withService } from "../../utils/index.js";
|
|
5
6
|
/**
|
|
6
7
|
* 注册 config delete 子命令:删除配置项。
|
|
7
8
|
*
|
|
@@ -22,14 +23,14 @@ export function registerConfigDeleteCommand(configCmd, context) {
|
|
|
22
23
|
throw new ArgumentError("Configuration key is required");
|
|
23
24
|
}
|
|
24
25
|
const root = resolvePackageRoot(options.package);
|
|
25
|
-
await
|
|
26
|
+
await withService(options, context, async (service, resolved) => {
|
|
26
27
|
if (resolved.type === "remote") {
|
|
27
|
-
await
|
|
28
|
+
await service.management?.config.delete(options.package || "", key);
|
|
28
29
|
writeStdout(`[OK] Configuration '${key}' deleted from remote server`, context);
|
|
29
30
|
return;
|
|
30
31
|
}
|
|
31
32
|
if (options.global) {
|
|
32
|
-
await
|
|
33
|
+
await service.management?.config.delete("global", key);
|
|
33
34
|
writeStdout(`[OK] Global configuration '${key}' deleted`, context);
|
|
34
35
|
return;
|
|
35
36
|
}
|
|
@@ -37,12 +38,12 @@ export function registerConfigDeleteCommand(configCmd, context) {
|
|
|
37
38
|
if (options.package) {
|
|
38
39
|
throw packageNotFoundError(options.package);
|
|
39
40
|
}
|
|
40
|
-
await
|
|
41
|
+
await service.management?.config.delete("global", key);
|
|
41
42
|
writeStdout(`[OK] Global configuration '${key}' deleted`, context);
|
|
42
43
|
return;
|
|
43
44
|
}
|
|
44
45
|
const projConfig = loadProjectConfig(root);
|
|
45
|
-
await
|
|
46
|
+
await service.management?.config.delete(projConfig.id, key);
|
|
46
47
|
writeStdout(`[OK] Configuration '${key}' deleted for package '${projConfig.id}'`, context);
|
|
47
48
|
});
|
|
48
49
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { fetchRemoteConfigEnv,
|
|
2
|
-
import {
|
|
1
|
+
import { fetchRemoteConfigEnv, resolveEnvValue, } from "@actiondock/core/profile";
|
|
2
|
+
import { isSecretConfigKey, } from "@actiondock/core/project";
|
|
3
3
|
import { renderConfigEnv, renderResult } from "../../renderer.js";
|
|
4
|
-
import { applyTargetOptions, getEffectiveOptions, resolveTargetFromOptions } from "../../utils/index.js";
|
|
4
|
+
import { applyTargetOptions, getEffectiveOptions, requirePackageRoot, resolveTargetFromOptions } from "../../utils/index.js";
|
|
5
5
|
/**
|
|
6
6
|
* 注册 config env 子命令:诊断环境变量对声明配置的满足率。
|
|
7
7
|
*
|
|
@@ -36,14 +36,10 @@ export function registerConfigEnvCommand(configCmd, context) {
|
|
|
36
36
|
return;
|
|
37
37
|
}
|
|
38
38
|
// 本地项目分支
|
|
39
|
-
const root =
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}
|
|
44
|
-
throw notInProjectError("Usage: ad config env [package-id] or cd into a project directory.");
|
|
45
|
-
}
|
|
46
|
-
const projConfig = loadProjectConfig(root);
|
|
39
|
+
const { root, projConfig } = requirePackageRoot(targetPkg, {
|
|
40
|
+
loadConfig: true,
|
|
41
|
+
hint: "Usage: ad config env [package-id] or cd into a project directory.",
|
|
42
|
+
});
|
|
47
43
|
const declared = projConfig.config || {};
|
|
48
44
|
const envChecks = [];
|
|
49
45
|
for (const [key, itemDef] of Object.entries(declared)) {
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
import { fetchRemoteConfig,
|
|
1
|
+
import { fetchRemoteConfig, } from "@actiondock/core/profile";
|
|
2
|
+
import { isSecretConfigKey, maskSecretValue, } from "@actiondock/core/project";
|
|
3
|
+
import { loadProjectConfig, } from "@actiondock/core";
|
|
4
|
+
import { resolvePackageRoot, } from "@actiondock/core/registry";
|
|
2
5
|
import { ArgumentError, packageNotFoundError } from "../../errors.js";
|
|
3
6
|
import { renderResult } from "../../renderer.js";
|
|
4
7
|
import { resolveMergedConfigEntry } from "../../services/config-merge.js";
|
|
5
|
-
import { applyTargetOptions, getEffectiveOptions, resolveTargetFromOptions,
|
|
8
|
+
import { applyTargetOptions, getEffectiveOptions, resolveTargetFromOptions, withService, } from "../../utils/index.js";
|
|
6
9
|
/**
|
|
7
10
|
* 注册 config get 子命令:查询指定配置键的生效值。
|
|
8
11
|
*
|
|
@@ -49,12 +52,12 @@ export function registerConfigGetCommand(configCmd, context) {
|
|
|
49
52
|
}
|
|
50
53
|
// 本地分支
|
|
51
54
|
const root = resolvePackageRoot(options.package);
|
|
52
|
-
await
|
|
55
|
+
await withService(options, context, async (service) => {
|
|
53
56
|
if (options.global || !root) {
|
|
54
57
|
if (!options.global && options.package && !root) {
|
|
55
58
|
throw packageNotFoundError(options.package);
|
|
56
59
|
}
|
|
57
|
-
const confView = await
|
|
60
|
+
const confView = await service.management?.config.get("global", key);
|
|
58
61
|
const val = confView?.value;
|
|
59
62
|
const isSecret = confView?.secret ?? isSecretConfigKey(key);
|
|
60
63
|
const displayValue = !reveal && isSecret && val !== undefined ? maskSecretValue(val) : val;
|
|
@@ -74,7 +77,7 @@ export function registerConfigGetCommand(configCmd, context) {
|
|
|
74
77
|
const projConfig = loadProjectConfig(root);
|
|
75
78
|
const declaredItem = projConfig.config?.[key];
|
|
76
79
|
// 合并优先级链单一事实源:项目包级 > 全局持久化 > 环境变量 > 声明默认值
|
|
77
|
-
const { value: displayValue, source, secret: isSecret } = await resolveMergedConfigEntry(key, projConfig.id, declaredItem,
|
|
80
|
+
const { value: displayValue, source, secret: isSecret } = await resolveMergedConfigEntry(key, projConfig.id, declaredItem, service, reveal);
|
|
78
81
|
const payload = {
|
|
79
82
|
key,
|
|
80
83
|
value: displayValue,
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
import { fetchRemoteConfig,
|
|
1
|
+
import { fetchRemoteConfig, } from "@actiondock/core/profile";
|
|
2
|
+
import { findProjectRoot, loadProjectConfig, } from "@actiondock/core";
|
|
3
|
+
import { filterWithFallbackInfo, isSecretConfigKey, maskSecretValue, } from "@actiondock/core/project";
|
|
4
|
+
import { resolvePackageRoot, } from "@actiondock/core/registry";
|
|
2
5
|
import { packageNotFoundError } from "../../errors.js";
|
|
3
6
|
import { renderConfigList, renderResult } from "../../renderer.js";
|
|
4
7
|
import { buildMergedConfigEntries } from "../../services/config-merge.js";
|
|
5
|
-
import { applyTargetOptions, getEffectiveOptions, resolveIntent, resolveTargetFromOptions,
|
|
8
|
+
import { applyTargetOptions, getEffectiveOptions, remoteTargetLabel, resolveFallbackStrategy, resolveIntent, resolveTargetFromOptions, withService, } from "../../utils/index.js";
|
|
6
9
|
/**
|
|
7
10
|
* 构造单条配置项的展示视图(含掩码决策,reveal 为假且为敏感键时打码)。
|
|
8
11
|
* 掩码决策收敛于命令层,renderer 仅负责值的字符串格式化。
|
|
@@ -31,13 +34,14 @@ export function registerConfigListCommand(configCmd, context) {
|
|
|
31
34
|
.option("-g, --global", "Show only global configurations"))
|
|
32
35
|
.option("-i, --intent <pattern>", "Regex or fuzzy intent filter; falls back to full list when no match")
|
|
33
36
|
.option("--reveal, --show-secrets", "Reveal plain text values for secrets")
|
|
37
|
+
.option("--fallback", "Enable fallback to full list when no items match intent")
|
|
34
38
|
.option("--no-fallback", "Disable fallback to full list when no items match intent")
|
|
35
39
|
.option("--data-dir <path>", "Custom database storage directory")
|
|
36
40
|
.option("--json", "Output as JSON")
|
|
37
41
|
.action(async (patterns = [], rawOptions, cmd) => {
|
|
38
42
|
const options = getEffectiveOptions(rawOptions, cmd);
|
|
39
43
|
const effectiveIntent = resolveIntent(options.intent, patterns);
|
|
40
|
-
const shouldFallback = options
|
|
44
|
+
const { shouldFallback } = resolveFallbackStrategy(options);
|
|
41
45
|
const reveal = Boolean(options.reveal || options.showSecrets);
|
|
42
46
|
// 远端服务分支不经过 Target 门面,直接查询远端配置接口
|
|
43
47
|
const remoteTargetInfo = resolveTargetFromOptions(options, context);
|
|
@@ -49,42 +53,32 @@ export function registerConfigListCommand(configCmd, context) {
|
|
|
49
53
|
const entries = Object.entries(res.values || {}).map(([k, v]) => toDisplayEntry(k, v, "remote", isSecretConfigKey(k, res.declared?.[k]), reveal, res.declared?.[k]?.description || ""));
|
|
50
54
|
renderResult(entries, {
|
|
51
55
|
json: options.json,
|
|
52
|
-
humanFormatter: () => renderConfigList(entries,
|
|
56
|
+
humanFormatter: () => renderConfigList(entries, remoteTargetLabel(remoteTargetInfo), false, effectiveIntent),
|
|
53
57
|
context,
|
|
54
58
|
});
|
|
55
59
|
return;
|
|
56
60
|
}
|
|
57
|
-
// 本地分支(通过
|
|
58
|
-
await
|
|
61
|
+
// 本地分支(通过 Service 门面统一访问)
|
|
62
|
+
await withService(options, context, async (service) => {
|
|
59
63
|
const root = options.package ? resolvePackageRoot(options.package) : findProjectRoot();
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
const filterRes = filterWithFallbackInfo(entries, effectiveIntent, [(c) => c.key, (c) => c.value], shouldFallback);
|
|
64
|
-
renderResult(filterRes.items, {
|
|
65
|
-
json: options.json,
|
|
66
|
-
humanFormatter: () => renderConfigList(filterRes.items, "Global Scope", filterRes.isFallback, effectiveIntent),
|
|
67
|
-
context,
|
|
68
|
-
});
|
|
69
|
-
return;
|
|
70
|
-
}
|
|
71
|
-
if (!root) {
|
|
72
|
-
if (options.package) {
|
|
64
|
+
// 全局作用域分支:显式 --global 或无工程回退时仅列举全局配置
|
|
65
|
+
if (options.global || !root) {
|
|
66
|
+
if (!options.global && options.package) {
|
|
73
67
|
throw packageNotFoundError(options.package);
|
|
74
68
|
}
|
|
75
|
-
const all = await
|
|
69
|
+
const all = (await service.management?.config.list("global")) ?? [];
|
|
76
70
|
const entries = all.map((item) => toDisplayEntry(item.key, item.value, "global", item.secret || isSecretConfigKey(item.key), reveal));
|
|
77
71
|
const filterRes = filterWithFallbackInfo(entries, effectiveIntent, [(c) => c.key, (c) => c.value], shouldFallback);
|
|
78
72
|
renderResult(filterRes.items, {
|
|
79
73
|
json: options.json,
|
|
80
|
-
humanFormatter: () => renderConfigList(filterRes.items, "Global Scope (No project found)", filterRes.isFallback, effectiveIntent),
|
|
74
|
+
humanFormatter: () => renderConfigList(filterRes.items, options.global ? "Global Scope" : "Global Scope (No project found)", filterRes.isFallback, effectiveIntent),
|
|
81
75
|
context,
|
|
82
76
|
});
|
|
83
77
|
return;
|
|
84
78
|
}
|
|
85
79
|
const projConfig = loadProjectConfig(root);
|
|
86
80
|
// 跨作用域合并视图单一事实源:项目包级 > 全局持久化 > 环境变量 > 声明默认值
|
|
87
|
-
const merged = await buildMergedConfigEntries(root,
|
|
81
|
+
const merged = await buildMergedConfigEntries(root, service, reveal);
|
|
88
82
|
const filterRes = filterWithFallbackInfo(merged, effectiveIntent, [(c) => c.key, (c) => c.value, (c) => c.description, (c) => c.source], shouldFallback);
|
|
89
83
|
renderResult(filterRes.items, {
|
|
90
84
|
json: options.json,
|
|
@@ -3,7 +3,13 @@ import type { CliContext } from "../../types.js";
|
|
|
3
3
|
/**
|
|
4
4
|
* 注册 config schema 子命令:检查声明配置的解析状态。
|
|
5
5
|
*
|
|
6
|
+
* 状态推导复用 config-merge 的跨作用域合并视图单一事实源
|
|
7
|
+
* (项目包级 > 全局持久化 > 环境变量 > 声明默认值):
|
|
8
|
+
* - source 命中 project、global 或 env 时状态为 SET;
|
|
9
|
+
* - source 为 default(仅声明默认值兜底)时状态为 DEFAULT;
|
|
10
|
+
* - 键无任何来源(不在合并视图中,即未声明默认值也未持久化)时状态为 MISSING。
|
|
11
|
+
*
|
|
6
12
|
* @param configCmd config 命令实例
|
|
7
|
-
* @param context
|
|
13
|
+
* @param context CLI 上下文
|
|
8
14
|
*/
|
|
9
15
|
export declare function registerConfigSchemaCommand(configCmd: Command, context?: CliContext): void;
|
|
@@ -1,80 +1,63 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { isSecretConfigKey, } from "@actiondock/core/project";
|
|
2
|
+
import { notInProjectError } from "../../errors.js";
|
|
3
3
|
import { renderConfigSchema, renderResult, writeStdout } from "../../renderer.js";
|
|
4
|
-
import {
|
|
4
|
+
import { buildMergedConfigEntries } from "../../services/config-merge.js";
|
|
5
|
+
import { applyTargetOptions, getEffectiveOptions, requirePackageRoot, withService } from "../../utils/index.js";
|
|
5
6
|
/**
|
|
6
7
|
* 注册 config schema 子命令:检查声明配置的解析状态。
|
|
7
8
|
*
|
|
9
|
+
* 状态推导复用 config-merge 的跨作用域合并视图单一事实源
|
|
10
|
+
* (项目包级 > 全局持久化 > 环境变量 > 声明默认值):
|
|
11
|
+
* - source 命中 project、global 或 env 时状态为 SET;
|
|
12
|
+
* - source 为 default(仅声明默认值兜底)时状态为 DEFAULT;
|
|
13
|
+
* - 键无任何来源(不在合并视图中,即未声明默认值也未持久化)时状态为 MISSING。
|
|
14
|
+
*
|
|
8
15
|
* @param configCmd config 命令实例
|
|
9
|
-
* @param context
|
|
16
|
+
* @param context CLI 上下文
|
|
10
17
|
*/
|
|
11
18
|
export function registerConfigSchemaCommand(configCmd, context) {
|
|
12
|
-
configCmd
|
|
19
|
+
applyTargetOptions(configCmd
|
|
13
20
|
.command("schema [identifier]")
|
|
14
21
|
.alias("check")
|
|
15
22
|
.description("Inspect declared configuration requirements and check resolution status")
|
|
16
|
-
.option("-P, --package <id>", "Target package ID or path")
|
|
23
|
+
.option("-P, --package <id>", "Target package ID or path"))
|
|
17
24
|
.option("--data-dir <path>", "Custom database storage directory")
|
|
18
25
|
.option("--json", "Output as JSON")
|
|
19
26
|
.action(async (identifier, rawOptions, cmd) => {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
dataDir: options.dataDir || context?.dataDir,
|
|
38
|
-
});
|
|
39
|
-
let globalConfig = [];
|
|
40
|
-
let projectConfig = [];
|
|
41
|
-
try {
|
|
42
|
-
globalConfig = await target.listConfig("global");
|
|
43
|
-
projectConfig = await target.listConfig(projConfig.id);
|
|
44
|
-
}
|
|
45
|
-
finally {
|
|
46
|
-
await target.close();
|
|
47
|
-
}
|
|
48
|
-
const projectConfigMap = new Map(projectConfig.map((c) => [c.key, c]));
|
|
49
|
-
const globalConfigMap = new Map(globalConfig.map((c) => [c.key, c]));
|
|
50
|
-
const items = declaredKeys.map((key) => {
|
|
27
|
+
const options = getEffectiveOptions(rawOptions, cmd);
|
|
28
|
+
const targetPkg = identifier || options.package;
|
|
29
|
+
const { root, projConfig } = requirePackageRoot(targetPkg, {
|
|
30
|
+
loadConfig: true,
|
|
31
|
+
hint: "Usage: ad config schema [package-id] or cd into a project directory.",
|
|
32
|
+
});
|
|
33
|
+
const declared = projConfig.config || {};
|
|
34
|
+
// 远端目标显式拒绝:schema 状态推导依赖本地工程声明与环境变量解析,
|
|
35
|
+
// 远端作用域无法获得等价视图,静默落到本地会误导调用方
|
|
36
|
+
if (options.profile || options.server) {
|
|
37
|
+
throw notInProjectError("'ad config schema' only supports local projects. Remote targets are not supported for schema inspection.");
|
|
38
|
+
}
|
|
39
|
+
// 跨作用域合并视图单一事实源:与 config list 共享同一条优先级链
|
|
40
|
+
await withService(options, context, async (service) => {
|
|
41
|
+
const merged = await buildMergedConfigEntries(root, service, true);
|
|
42
|
+
const mergedByKey = new Map(merged.map((entry) => [entry.key, entry]));
|
|
43
|
+
const items = Object.keys(declared).map((key) => {
|
|
51
44
|
const itemDef = declared[key];
|
|
52
45
|
const isSecret = isSecretConfigKey(key, itemDef);
|
|
53
|
-
|
|
46
|
+
const mergedEntry = mergedByKey.get(key);
|
|
54
47
|
let source = "missing";
|
|
55
48
|
let status = "MISSING";
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
}
|
|
69
|
-
else if (envResolved !== undefined) {
|
|
70
|
-
resolvedValue = envResolved.value;
|
|
71
|
-
source = "env";
|
|
72
|
-
status = "SET";
|
|
73
|
-
}
|
|
74
|
-
else if (itemDef.default !== undefined) {
|
|
75
|
-
resolvedValue = itemDef.default;
|
|
76
|
-
source = "default";
|
|
77
|
-
status = "DEFAULT";
|
|
49
|
+
let resolvedValue;
|
|
50
|
+
if (mergedEntry) {
|
|
51
|
+
source = mergedEntry.source;
|
|
52
|
+
resolvedValue = mergedEntry.value;
|
|
53
|
+
// 合并视图的 default 来源仅是链尾兑底标签:
|
|
54
|
+
// 真正的 DEFAULT 状态要求声明确实携带默认值,否则视为 MISSING
|
|
55
|
+
if (mergedEntry.source !== "default") {
|
|
56
|
+
status = "SET";
|
|
57
|
+
}
|
|
58
|
+
else if (itemDef.default !== undefined) {
|
|
59
|
+
status = "DEFAULT";
|
|
60
|
+
}
|
|
78
61
|
}
|
|
79
62
|
return {
|
|
80
63
|
key,
|
|
@@ -108,12 +91,6 @@ export function registerConfigSchemaCommand(configCmd, context) {
|
|
|
108
91
|
if (!ok) {
|
|
109
92
|
process.exitCode = 1;
|
|
110
93
|
}
|
|
111
|
-
}
|
|
112
|
-
catch (err) {
|
|
113
|
-
if (err instanceof ArgumentError || err instanceof ExecutionError) {
|
|
114
|
-
throw err;
|
|
115
|
-
}
|
|
116
|
-
throw new ExecutionError(err.message);
|
|
117
|
-
}
|
|
94
|
+
}, { localRoot: root });
|
|
118
95
|
});
|
|
119
96
|
}
|