@actiondock/cli 2.0.12 → 2.2.1
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 +41 -34
- package/bin/ad.js +28 -41
- package/dist/commands/action/create.d.ts +9 -1
- package/dist/commands/action/create.js +120 -0
- package/dist/commands/add.d.ts +7 -0
- package/dist/commands/add.js +144 -0
- package/dist/commands/build.d.ts +2 -1
- package/dist/commands/build.js +83 -0
- package/dist/commands/config/delete.d.ts +9 -0
- package/dist/commands/config/delete.js +52 -0
- package/dist/commands/config/env.d.ts +9 -0
- package/dist/commands/config/env.js +85 -0
- package/dist/commands/config/get.d.ts +9 -0
- package/dist/commands/config/get.js +120 -0
- package/dist/commands/config/index.d.ts +9 -0
- package/dist/commands/config/index.js +23 -0
- package/dist/commands/config/list.d.ts +9 -0
- package/dist/commands/config/list.js +137 -0
- package/dist/commands/config/schema.d.ts +9 -0
- package/dist/commands/config/schema.js +121 -0
- package/dist/commands/config/set.d.ts +9 -0
- package/dist/commands/config/set.js +85 -0
- package/dist/commands/describe.d.ts +9 -0
- package/dist/commands/describe.js +68 -0
- package/dist/commands/doctor.d.ts +2 -1
- package/dist/commands/doctor.js +82 -0
- package/dist/commands/export.d.ts +2 -1
- package/dist/commands/export.js +212 -0
- package/dist/commands/generate.d.ts +9 -0
- package/dist/commands/generate.js +44 -0
- package/dist/commands/index.d.ts +31 -1
- package/dist/commands/index.js +111 -0
- package/dist/commands/info.d.ts +9 -2
- package/dist/commands/info.js +210 -0
- package/dist/commands/init.js +24 -0
- package/dist/commands/link.js +71 -0
- package/dist/commands/list.d.ts +9 -0
- package/dist/commands/list.js +77 -0
- package/dist/commands/mcp.d.ts +9 -2
- package/dist/commands/mcp.js +173 -0
- package/dist/commands/new.d.ts +9 -0
- package/dist/commands/new.js +33 -0
- package/dist/commands/pack.d.ts +3 -0
- package/dist/commands/pack.js +63 -0
- package/dist/commands/playbook.d.ts +7 -3
- package/dist/commands/playbook.js +433 -0
- package/dist/commands/profile.js +253 -0
- package/dist/commands/remove.d.ts +7 -0
- package/dist/commands/remove.js +118 -0
- package/dist/commands/run.d.ts +13 -0
- package/dist/commands/run.js +146 -0
- package/dist/commands/runs.d.ts +9 -2
- package/dist/commands/runs.js +245 -0
- package/dist/commands/serve.d.ts +9 -2
- package/dist/commands/serve.js +135 -0
- package/dist/commands/state-list.d.ts +24 -0
- package/dist/commands/state-list.js +82 -0
- package/dist/commands/state.d.ts +9 -2
- package/dist/commands/state.js +310 -0
- package/dist/commands/test.js +44 -0
- package/dist/commands/validate.d.ts +9 -0
- package/dist/commands/validate.js +106 -0
- package/dist/errors.d.ts +43 -0
- package/dist/errors.js +103 -0
- package/dist/index.d.ts +8 -1
- package/dist/index.js +41 -1000
- package/dist/prompt.d.ts +53 -0
- package/dist/prompt.js +192 -0
- package/dist/renderer.d.ts +197 -0
- package/dist/renderer.js +502 -0
- package/dist/services/index.d.ts +1 -0
- package/dist/services/index.js +1 -0
- package/dist/services/inspect.d.ts +23 -0
- package/dist/services/inspect.js +96 -0
- package/dist/standalone/index.d.ts +16 -0
- package/dist/standalone/index.js +22 -0
- package/dist/types.d.ts +132 -0
- package/dist/types.js +9 -0
- package/dist/utils/bytes.d.ts +1 -1
- package/dist/utils/bytes.js +1 -0
- package/dist/utils/duration.js +1 -0
- package/dist/utils/filter.d.ts +1 -1
- package/dist/utils/filter.js +1 -0
- package/dist/utils/index.d.ts +6 -0
- package/dist/utils/index.js +6 -0
- package/dist/utils/spawn.d.ts +27 -0
- package/dist/utils/spawn.js +35 -0
- package/dist/utils/target.d.ts +45 -0
- package/dist/utils/target.js +78 -0
- package/dist/utils/utils.d.ts +38 -0
- package/dist/utils/utils.js +111 -0
- package/package.json +10 -12
- package/dist/commands/action/index.d.ts +0 -16
- package/dist/commands/action/list.d.ts +0 -5
- package/dist/commands/action/run.d.ts +0 -4
- package/dist/commands/action/show.d.ts +0 -5
- package/dist/commands/action/sync.d.ts +0 -2
- package/dist/commands/action/validate.d.ts +0 -5
- package/dist/commands/action.d.ts +0 -1
- package/dist/commands/config.d.ts +0 -2
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { loadProjectConfig, resolvePackageRoot, } from "@actiondock/core";
|
|
2
|
+
import { ArgumentError } from "../../errors.js";
|
|
3
|
+
import { writeStdout } from "../../renderer.js";
|
|
4
|
+
import { getEffectiveOptions, withTarget } from "../../utils/index.js";
|
|
5
|
+
/**
|
|
6
|
+
* 注册 config delete 子命令:删除配置项。
|
|
7
|
+
*
|
|
8
|
+
* @param configCmd config 命令实例
|
|
9
|
+
* @param context 命令行上下文
|
|
10
|
+
*/
|
|
11
|
+
export function registerConfigDeleteCommand(configCmd, context) {
|
|
12
|
+
configCmd
|
|
13
|
+
.command("delete <key>")
|
|
14
|
+
.alias("rm")
|
|
15
|
+
.description("Delete configuration entry")
|
|
16
|
+
.option("-P, --package <id>", "Target package ID or path")
|
|
17
|
+
.option("-g, --global", "Delete from global configuration")
|
|
18
|
+
.option("-p, --profile <name>", "Delete on a remote target")
|
|
19
|
+
.option("-s, --server <url>", "Remote server URL")
|
|
20
|
+
.option("-t, --token <token>", "Auth token for remote server")
|
|
21
|
+
.option("--data-dir <path>", "Custom database storage directory")
|
|
22
|
+
.action(async (key, rawOptions, cmd) => {
|
|
23
|
+
const options = getEffectiveOptions(rawOptions, cmd);
|
|
24
|
+
if (!key) {
|
|
25
|
+
throw new ArgumentError("Configuration key is required");
|
|
26
|
+
}
|
|
27
|
+
const root = resolvePackageRoot(options.package);
|
|
28
|
+
await withTarget(options, context, async (target, resolved) => {
|
|
29
|
+
if (resolved.type === "remote") {
|
|
30
|
+
await target.deleteConfig(options.package || "", key);
|
|
31
|
+
writeStdout(`[OK] Configuration '${key}' deleted from remote server`, context);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
if (options.global) {
|
|
35
|
+
await target.deleteConfig("global", key);
|
|
36
|
+
writeStdout(`[OK] Global configuration '${key}' deleted`, context);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
if (!root) {
|
|
40
|
+
if (options.package) {
|
|
41
|
+
throw new ArgumentError(`Package '${options.package}' not found in linked packages or path`);
|
|
42
|
+
}
|
|
43
|
+
await target.deleteConfig("global", key);
|
|
44
|
+
writeStdout(`[OK] Global configuration '${key}' deleted`, context);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const projConfig = loadProjectConfig(root);
|
|
48
|
+
await target.deleteConfig(projConfig.id, key);
|
|
49
|
+
writeStdout(`[OK] Configuration '${key}' deleted for package '${projConfig.id}'`, context);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Command } from "commander";
|
|
2
|
+
import type { CliContext } from "../../types.js";
|
|
3
|
+
/**
|
|
4
|
+
* 注册 config env 子命令:诊断环境变量对声明配置的满足率。
|
|
5
|
+
*
|
|
6
|
+
* @param configCmd config 命令实例
|
|
7
|
+
* @param context 命令行上下文
|
|
8
|
+
*/
|
|
9
|
+
export declare function registerConfigEnvCommand(configCmd: Command, context?: CliContext): void;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { fetchRemoteConfigEnv, isSecretConfigKey, loadProjectConfig, resolveEnvValue, resolvePackageRoot, resolveTarget, } from "@actiondock/core";
|
|
2
|
+
import { ArgumentError } from "../../errors.js";
|
|
3
|
+
import { renderConfigEnv, renderResult } from "../../renderer.js";
|
|
4
|
+
import { getEffectiveOptions } from "../../utils/index.js";
|
|
5
|
+
/**
|
|
6
|
+
* 注册 config env 子命令:诊断环境变量对声明配置的满足率。
|
|
7
|
+
*
|
|
8
|
+
* @param configCmd config 命令实例
|
|
9
|
+
* @param context 命令行上下文
|
|
10
|
+
*/
|
|
11
|
+
export function registerConfigEnvCommand(configCmd, context) {
|
|
12
|
+
configCmd
|
|
13
|
+
.command("env [identifier]")
|
|
14
|
+
.description("Diagnose environment variable satisfaction for declared configuration")
|
|
15
|
+
.option("-P, --package <id>", "Target package ID or path")
|
|
16
|
+
.option("-p, --profile <name>", "Query on a remote target")
|
|
17
|
+
.option("-s, --server <url>", "Remote server URL")
|
|
18
|
+
.option("-t, --token <token>", "Auth token for remote server")
|
|
19
|
+
.option("--json", "Output as JSON")
|
|
20
|
+
.option("--envelope", "Wrap JSON output in standard envelope")
|
|
21
|
+
.action(async (identifier, rawOptions, cmd) => {
|
|
22
|
+
const options = getEffectiveOptions(rawOptions, cmd);
|
|
23
|
+
const targetPkg = identifier || options.package;
|
|
24
|
+
// 远端服务分支直接查询远端配置环境诊断接口
|
|
25
|
+
const target = resolveTarget({
|
|
26
|
+
profile: options.profile,
|
|
27
|
+
server: options.server,
|
|
28
|
+
token: options.token,
|
|
29
|
+
}, context?.customHome);
|
|
30
|
+
if (target.type === "remote") {
|
|
31
|
+
const res = await fetchRemoteConfigEnv(target.serverUrl, target.token, targetPkg);
|
|
32
|
+
renderResult(res, {
|
|
33
|
+
json: options.json,
|
|
34
|
+
envelope: options.envelope,
|
|
35
|
+
humanFormatter: () => renderConfigEnv(res.envChecks || [], res.packageId),
|
|
36
|
+
context,
|
|
37
|
+
});
|
|
38
|
+
const allSatisfied = (res.envChecks || []).every((c) => !c.required || c.satisfied);
|
|
39
|
+
if (!allSatisfied) {
|
|
40
|
+
process.exitCode = 1;
|
|
41
|
+
}
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
// 本地项目分支
|
|
45
|
+
const root = resolvePackageRoot(targetPkg);
|
|
46
|
+
if (!root) {
|
|
47
|
+
if (targetPkg) {
|
|
48
|
+
throw new ArgumentError(`Package '${targetPkg}' not found in linked packages or path`);
|
|
49
|
+
}
|
|
50
|
+
throw new ArgumentError("Not in an ActionDock project.\nUsage: ad config env [package-id] or cd into a project directory.");
|
|
51
|
+
}
|
|
52
|
+
const projConfig = loadProjectConfig(root);
|
|
53
|
+
const declared = projConfig.config || {};
|
|
54
|
+
const envChecks = [];
|
|
55
|
+
for (const [key, itemDef] of Object.entries(declared)) {
|
|
56
|
+
const resolved = resolveEnvValue(key, itemDef, projConfig.id);
|
|
57
|
+
const hasDefault = itemDef.default !== undefined;
|
|
58
|
+
const satisfied = resolved !== undefined || hasDefault;
|
|
59
|
+
envChecks.push({
|
|
60
|
+
key,
|
|
61
|
+
required: Boolean(itemDef.required),
|
|
62
|
+
satisfied,
|
|
63
|
+
matchedEnv: resolved ? resolved.matchedKey : null,
|
|
64
|
+
hasDefault,
|
|
65
|
+
secret: isSecretConfigKey(key, itemDef),
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
const allSatisfied = envChecks.every((c) => !c.required || c.satisfied);
|
|
69
|
+
const payload = {
|
|
70
|
+
packageId: projConfig.id,
|
|
71
|
+
projectRoot: root,
|
|
72
|
+
ok: allSatisfied,
|
|
73
|
+
envChecks,
|
|
74
|
+
};
|
|
75
|
+
renderResult(payload, {
|
|
76
|
+
json: options.json,
|
|
77
|
+
envelope: options.envelope,
|
|
78
|
+
humanFormatter: () => renderConfigEnv(envChecks, projConfig.id),
|
|
79
|
+
context,
|
|
80
|
+
});
|
|
81
|
+
if (!allSatisfied) {
|
|
82
|
+
process.exitCode = 1;
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Command } from "commander";
|
|
2
|
+
import type { CliContext } from "../../types.js";
|
|
3
|
+
/**
|
|
4
|
+
* 注册 config get 子命令:查询指定配置键的生效值。
|
|
5
|
+
*
|
|
6
|
+
* @param configCmd config 命令实例
|
|
7
|
+
* @param context 命令行上下文
|
|
8
|
+
*/
|
|
9
|
+
export declare function registerConfigGetCommand(configCmd: Command, context?: CliContext): void;
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { fetchRemoteConfig, isSecretConfigKey, loadProjectConfig, maskSecretValue, resolveEnvValue, resolvePackageRoot, resolveTarget, } from "@actiondock/core";
|
|
2
|
+
import { ArgumentError } from "../../errors.js";
|
|
3
|
+
import { renderResult } from "../../renderer.js";
|
|
4
|
+
import { getEffectiveOptions, withTarget } from "../../utils/index.js";
|
|
5
|
+
/**
|
|
6
|
+
* 解析远端目标拓扑信息(仅信息解析,不创建 Target 实例)。
|
|
7
|
+
*/
|
|
8
|
+
function resolveRemoteTargetInfo(options, context) {
|
|
9
|
+
return resolveTarget({
|
|
10
|
+
profile: options.profile,
|
|
11
|
+
server: options.server,
|
|
12
|
+
token: options.token,
|
|
13
|
+
}, context?.customHome);
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* 注册 config get 子命令:查询指定配置键的生效值。
|
|
17
|
+
*
|
|
18
|
+
* @param configCmd config 命令实例
|
|
19
|
+
* @param context 命令行上下文
|
|
20
|
+
*/
|
|
21
|
+
export function registerConfigGetCommand(configCmd, context) {
|
|
22
|
+
configCmd
|
|
23
|
+
.command("get <key>")
|
|
24
|
+
.description("Get configuration value for key")
|
|
25
|
+
.option("-P, --package <id>", "Target package ID or path")
|
|
26
|
+
.option("-g, --global", "Get from global configuration")
|
|
27
|
+
.option("-p, --profile <name>", "Query config on a remote target")
|
|
28
|
+
.option("-s, --server <url>", "Remote server URL")
|
|
29
|
+
.option("-t, --token <token>", "Auth token for remote server")
|
|
30
|
+
.option("--reveal, --show-secrets", "Reveal plain text values for secrets")
|
|
31
|
+
.option("--data-dir <path>", "Custom database storage directory")
|
|
32
|
+
.option("--json", "Output as JSON")
|
|
33
|
+
.option("--envelope", "Wrap JSON output in standard envelope")
|
|
34
|
+
.action(async (key, rawOptions, cmd) => {
|
|
35
|
+
const options = getEffectiveOptions(rawOptions, cmd);
|
|
36
|
+
if (!key) {
|
|
37
|
+
throw new ArgumentError("Configuration key is required");
|
|
38
|
+
}
|
|
39
|
+
const reveal = Boolean(options.reveal || options.showSecrets);
|
|
40
|
+
// 远端服务分支不经过 Target 门面,直接查询远端配置接口
|
|
41
|
+
const target = resolveRemoteTargetInfo(options, context);
|
|
42
|
+
if (target.type === "remote") {
|
|
43
|
+
const res = await fetchRemoteConfig(target.serverUrl, target.token, options.package);
|
|
44
|
+
const val = res.values?.[key];
|
|
45
|
+
const isSecret = isSecretConfigKey(key, res.declared?.[key]);
|
|
46
|
+
const displayValue = !reveal && isSecret && val !== undefined ? maskSecretValue(val) : val;
|
|
47
|
+
const payload = {
|
|
48
|
+
key,
|
|
49
|
+
value: displayValue,
|
|
50
|
+
source: "remote",
|
|
51
|
+
secret: isSecret,
|
|
52
|
+
};
|
|
53
|
+
renderResult(payload, {
|
|
54
|
+
json: options.json,
|
|
55
|
+
envelope: options.envelope,
|
|
56
|
+
humanFormatter: () => (displayValue !== undefined ? String(displayValue) : ""),
|
|
57
|
+
context,
|
|
58
|
+
});
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
// 本地分支
|
|
62
|
+
const root = resolvePackageRoot(options.package);
|
|
63
|
+
await withTarget(options, context, async (localTarget) => {
|
|
64
|
+
if (options.global || !root) {
|
|
65
|
+
if (!options.global && options.package && !root) {
|
|
66
|
+
throw new ArgumentError(`Package '${options.package}' not found in linked packages or path`);
|
|
67
|
+
}
|
|
68
|
+
const confView = await localTarget.getConfig("global", key);
|
|
69
|
+
const val = confView?.value;
|
|
70
|
+
const isSecret = confView?.secret ?? isSecretConfigKey(key);
|
|
71
|
+
const displayValue = !reveal && isSecret && val !== undefined ? maskSecretValue(val) : val;
|
|
72
|
+
const payload = {
|
|
73
|
+
key,
|
|
74
|
+
value: displayValue,
|
|
75
|
+
source: "global",
|
|
76
|
+
secret: isSecret,
|
|
77
|
+
};
|
|
78
|
+
renderResult(payload, {
|
|
79
|
+
json: options.json,
|
|
80
|
+
envelope: options.envelope,
|
|
81
|
+
humanFormatter: () => (displayValue !== undefined ? String(displayValue) : ""),
|
|
82
|
+
context,
|
|
83
|
+
});
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
const projConfig = loadProjectConfig(root);
|
|
87
|
+
const declaredItem = projConfig.config?.[key];
|
|
88
|
+
const confView = await localTarget.getConfig(projConfig.id, key);
|
|
89
|
+
let resolvedVal = confView?.value;
|
|
90
|
+
let source = confView?.source === "package" ? "project" : (confView?.source || "default");
|
|
91
|
+
const envResolved = resolveEnvValue(key, declaredItem, projConfig.id);
|
|
92
|
+
if (confView && confView.configured) {
|
|
93
|
+
resolvedVal = confView.value;
|
|
94
|
+
source = confView.source === "package" ? "project" : confView.source;
|
|
95
|
+
}
|
|
96
|
+
else if (envResolved !== undefined) {
|
|
97
|
+
resolvedVal = envResolved.value;
|
|
98
|
+
source = "env";
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
resolvedVal = declaredItem?.default;
|
|
102
|
+
source = "default";
|
|
103
|
+
}
|
|
104
|
+
const isSecret = confView?.secret ?? isSecretConfigKey(key, declaredItem);
|
|
105
|
+
const displayValue = !reveal && isSecret && resolvedVal !== undefined ? maskSecretValue(resolvedVal) : resolvedVal;
|
|
106
|
+
const payload = {
|
|
107
|
+
key,
|
|
108
|
+
value: displayValue,
|
|
109
|
+
source,
|
|
110
|
+
secret: isSecret,
|
|
111
|
+
};
|
|
112
|
+
renderResult(payload, {
|
|
113
|
+
json: options.json,
|
|
114
|
+
envelope: options.envelope,
|
|
115
|
+
humanFormatter: () => (displayValue !== undefined ? String(displayValue) : ""),
|
|
116
|
+
context,
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import type { CliContext } from "../../types.js";
|
|
3
|
+
/**
|
|
4
|
+
* 注册 config 配置管理命令(get、set、list、delete、env、schema)。
|
|
5
|
+
*
|
|
6
|
+
* @param program Commander 实例
|
|
7
|
+
* @param context 命令行上下文
|
|
8
|
+
*/
|
|
9
|
+
export declare function registerConfigCommands(program: Command, context?: CliContext): void;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { registerConfigSchemaCommand } from "./schema.js";
|
|
2
|
+
import { registerConfigListCommand } from "./list.js";
|
|
3
|
+
import { registerConfigGetCommand } from "./get.js";
|
|
4
|
+
import { registerConfigSetCommand } from "./set.js";
|
|
5
|
+
import { registerConfigDeleteCommand } from "./delete.js";
|
|
6
|
+
import { registerConfigEnvCommand } from "./env.js";
|
|
7
|
+
/**
|
|
8
|
+
* 注册 config 配置管理命令(get、set、list、delete、env、schema)。
|
|
9
|
+
*
|
|
10
|
+
* @param program Commander 实例
|
|
11
|
+
* @param context 命令行上下文
|
|
12
|
+
*/
|
|
13
|
+
export function registerConfigCommands(program, context) {
|
|
14
|
+
const configCmd = program
|
|
15
|
+
.command("config")
|
|
16
|
+
.description("Manage runtime configuration store (Global & Project-level)");
|
|
17
|
+
registerConfigSchemaCommand(configCmd, context);
|
|
18
|
+
registerConfigListCommand(configCmd, context);
|
|
19
|
+
registerConfigGetCommand(configCmd, context);
|
|
20
|
+
registerConfigSetCommand(configCmd, context);
|
|
21
|
+
registerConfigDeleteCommand(configCmd, context);
|
|
22
|
+
registerConfigEnvCommand(configCmd, context);
|
|
23
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Command } from "commander";
|
|
2
|
+
import type { CliContext } from "../../types.js";
|
|
3
|
+
/**
|
|
4
|
+
* 注册 config list 子命令:列出全局与项目配置项。
|
|
5
|
+
*
|
|
6
|
+
* @param configCmd config 命令实例
|
|
7
|
+
* @param context 命令行上下文
|
|
8
|
+
*/
|
|
9
|
+
export declare function registerConfigListCommand(configCmd: Command, context?: CliContext): void;
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { fetchRemoteConfig, filterWithFallbackInfo, findProjectRoot, isSecretConfigKey, loadProjectConfig, maskSecretValue, resolveEnvValue, resolvePackageRoot, resolveTarget, } from "@actiondock/core";
|
|
2
|
+
import { ArgumentError } from "../../errors.js";
|
|
3
|
+
import { renderConfigList, renderResult } from "../../renderer.js";
|
|
4
|
+
import { getEffectiveOptions, resolveIntent, withTarget } from "../../utils/index.js";
|
|
5
|
+
/**
|
|
6
|
+
* 构造单条配置项的展示视图(含掩码决策,reveal 为假且为敏感键时打码)。
|
|
7
|
+
* 掩码决策收敛于命令层,renderer 仅负责值的字符串格式化。
|
|
8
|
+
*/
|
|
9
|
+
function toDisplayEntry(key, rawValue, source, isSecret, reveal, description) {
|
|
10
|
+
const displayValue = !reveal && isSecret && rawValue !== undefined ? maskSecretValue(rawValue) : rawValue;
|
|
11
|
+
return {
|
|
12
|
+
key,
|
|
13
|
+
value: displayValue,
|
|
14
|
+
source,
|
|
15
|
+
secret: isSecret,
|
|
16
|
+
...(description !== undefined ? { description } : {}),
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* 注册 config list 子命令:列出全局与项目配置项。
|
|
21
|
+
*
|
|
22
|
+
* @param configCmd config 命令实例
|
|
23
|
+
* @param context 命令行上下文
|
|
24
|
+
*/
|
|
25
|
+
export function registerConfigListCommand(configCmd, context) {
|
|
26
|
+
configCmd
|
|
27
|
+
.command("list [patterns...]")
|
|
28
|
+
.description("List configuration entries (Global & Project)")
|
|
29
|
+
.option("-P, --package <id>", "Target package ID or path")
|
|
30
|
+
.option("-g, --global", "Show only global configurations")
|
|
31
|
+
.option("-p, --profile <name>", "Query config on a remote target")
|
|
32
|
+
.option("-s, --server <url>", "Remote server URL")
|
|
33
|
+
.option("-t, --token <token>", "Auth token for remote server")
|
|
34
|
+
.option("-i, --intent <pattern>", "Regex or fuzzy intent filter; falls back to full list when no match")
|
|
35
|
+
.option("--reveal, --show-secrets", "Reveal plain text values for secrets")
|
|
36
|
+
.option("--no-fallback", "Disable fallback to full list when no items match intent")
|
|
37
|
+
.option("--data-dir <path>", "Custom database storage directory")
|
|
38
|
+
.option("--json", "Output as JSON")
|
|
39
|
+
.option("--envelope", "Wrap JSON output in standard envelope")
|
|
40
|
+
.action(async (patterns = [], rawOptions, cmd) => {
|
|
41
|
+
const options = getEffectiveOptions(rawOptions, cmd);
|
|
42
|
+
const effectiveIntent = resolveIntent(options.intent, patterns);
|
|
43
|
+
const shouldFallback = options.fallback !== false;
|
|
44
|
+
const reveal = Boolean(options.reveal || options.showSecrets);
|
|
45
|
+
// 远端服务分支不经过 Target 门面,直接查询远端配置接口
|
|
46
|
+
const remoteTargetInfo = resolveTarget({ profile: options.profile, server: options.server, token: options.token }, context?.customHome);
|
|
47
|
+
if (remoteTargetInfo.type === "remote") {
|
|
48
|
+
const res = await fetchRemoteConfig(remoteTargetInfo.serverUrl, remoteTargetInfo.token, options.package);
|
|
49
|
+
const entries = Object.entries(res.values || {}).map(([k, v]) => toDisplayEntry(k, v, "remote", isSecretConfigKey(k, res.declared?.[k]), reveal, res.declared?.[k]?.description || ""));
|
|
50
|
+
renderResult(entries, {
|
|
51
|
+
json: options.json,
|
|
52
|
+
envelope: options.envelope,
|
|
53
|
+
humanFormatter: () => renderConfigList(entries, `Remote Server ${remoteTargetInfo.serverUrl}${remoteTargetInfo.profileName ? ` (Profile: ${remoteTargetInfo.profileName})` : ""}`, false, effectiveIntent),
|
|
54
|
+
context,
|
|
55
|
+
});
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
// 本地分支(通过 Target 门面统一访问)
|
|
59
|
+
await withTarget(options, context, async (localTarget) => {
|
|
60
|
+
const root = options.package ? resolvePackageRoot(options.package) : findProjectRoot();
|
|
61
|
+
if (options.global) {
|
|
62
|
+
const all = await localTarget.listConfig("global");
|
|
63
|
+
const entries = all.map((item) => toDisplayEntry(item.key, item.value, "global", item.secret || isSecretConfigKey(item.key), reveal));
|
|
64
|
+
const filterRes = filterWithFallbackInfo(entries, effectiveIntent, [(c) => c.key, (c) => c.value], shouldFallback);
|
|
65
|
+
renderResult(filterRes.items, {
|
|
66
|
+
json: options.json,
|
|
67
|
+
envelope: options.envelope,
|
|
68
|
+
humanFormatter: () => renderConfigList(filterRes.items, "Global Scope", filterRes.isFallback, effectiveIntent),
|
|
69
|
+
context,
|
|
70
|
+
});
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (!root) {
|
|
74
|
+
if (options.package) {
|
|
75
|
+
throw new ArgumentError(`Package '${options.package}' not found in linked packages or path`);
|
|
76
|
+
}
|
|
77
|
+
const all = await localTarget.listConfig("global");
|
|
78
|
+
const entries = all.map((item) => toDisplayEntry(item.key, item.value, "global", item.secret || isSecretConfigKey(item.key), reveal));
|
|
79
|
+
const filterRes = filterWithFallbackInfo(entries, effectiveIntent, [(c) => c.key, (c) => c.value], shouldFallback);
|
|
80
|
+
renderResult(filterRes.items, {
|
|
81
|
+
json: options.json,
|
|
82
|
+
envelope: options.envelope,
|
|
83
|
+
humanFormatter: () => renderConfigList(filterRes.items, "Global Scope (No project found)", filterRes.isFallback, effectiveIntent),
|
|
84
|
+
context,
|
|
85
|
+
});
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const projConfig = loadProjectConfig(root);
|
|
89
|
+
const declared = projConfig.config || {};
|
|
90
|
+
const globalConfigList = await localTarget.listConfig("global");
|
|
91
|
+
const projectConfigList = await localTarget.listConfig(projConfig.id);
|
|
92
|
+
const projectConfigMap = new Map(projectConfigList.map((c) => [c.key, c]));
|
|
93
|
+
const globalConfigMap = new Map(globalConfigList.map((c) => [c.key, c]));
|
|
94
|
+
const allKeys = new Set([
|
|
95
|
+
...Object.keys(declared),
|
|
96
|
+
...projectConfigList.map((c) => c.key),
|
|
97
|
+
...globalConfigList.map((c) => c.key),
|
|
98
|
+
]);
|
|
99
|
+
// 配置合并优先级:项目包级 > 全局持久化 > 环境变量 > 声明默认值。
|
|
100
|
+
// core 的 RuntimeConfig.describe 已是五层链事实源,但 Target 门面仅按作用域暴露
|
|
101
|
+
// listConfig(scope),不提供跨作用域合并视图;此处保持命令层合并逻辑,
|
|
102
|
+
// 与 app.getConfig 的 describe 委托链行为语义一致(overrides 层仅存在于 run 链路)。
|
|
103
|
+
const merged = Array.from(allKeys).map((k) => {
|
|
104
|
+
let rawValue;
|
|
105
|
+
let source = "default";
|
|
106
|
+
const envResolved = resolveEnvValue(k, declared[k], projConfig.id);
|
|
107
|
+
const projItem = projectConfigMap.get(k);
|
|
108
|
+
const globItem = globalConfigMap.get(k);
|
|
109
|
+
if (projItem && projItem.configured && projItem.source === "package") {
|
|
110
|
+
rawValue = projItem.value;
|
|
111
|
+
source = "project";
|
|
112
|
+
}
|
|
113
|
+
else if (globItem && globItem.configured) {
|
|
114
|
+
rawValue = globItem.value;
|
|
115
|
+
source = "global";
|
|
116
|
+
}
|
|
117
|
+
else if (envResolved !== undefined) {
|
|
118
|
+
rawValue = envResolved.value;
|
|
119
|
+
source = "env";
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
rawValue = declared[k]?.default;
|
|
123
|
+
source = "default";
|
|
124
|
+
}
|
|
125
|
+
const isSecret = isSecretConfigKey(k, declared[k]);
|
|
126
|
+
return toDisplayEntry(k, rawValue, source, isSecret, reveal, declared[k]?.description || "");
|
|
127
|
+
});
|
|
128
|
+
const filterRes = filterWithFallbackInfo(merged, effectiveIntent, [(c) => c.key, (c) => c.value, (c) => c.description, (c) => c.source], shouldFallback);
|
|
129
|
+
renderResult(filterRes.items, {
|
|
130
|
+
json: options.json,
|
|
131
|
+
envelope: options.envelope,
|
|
132
|
+
humanFormatter: () => renderConfigList(filterRes.items, `${projConfig.name} (${projConfig.id})`, filterRes.isFallback, effectiveIntent),
|
|
133
|
+
context,
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Command } from "commander";
|
|
2
|
+
import type { CliContext } from "../../types.js";
|
|
3
|
+
/**
|
|
4
|
+
* 注册 config schema 子命令:检查声明配置的解析状态。
|
|
5
|
+
*
|
|
6
|
+
* @param configCmd config 命令实例
|
|
7
|
+
* @param context 命令行上下文
|
|
8
|
+
*/
|
|
9
|
+
export declare function registerConfigSchemaCommand(configCmd: Command, context?: CliContext): void;
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { createActionDockTarget, isSecretConfigKey, loadProjectConfig, resolveEnvValue, resolvePackageRoot, } from "@actiondock/core";
|
|
2
|
+
import { ArgumentError, ExecutionError } from "../../errors.js";
|
|
3
|
+
import { renderConfigSchema, renderResult, writeStdout } from "../../renderer.js";
|
|
4
|
+
import { getEffectiveOptions } from "../../utils/index.js";
|
|
5
|
+
/**
|
|
6
|
+
* 注册 config schema 子命令:检查声明配置的解析状态。
|
|
7
|
+
*
|
|
8
|
+
* @param configCmd config 命令实例
|
|
9
|
+
* @param context 命令行上下文
|
|
10
|
+
*/
|
|
11
|
+
export function registerConfigSchemaCommand(configCmd, context) {
|
|
12
|
+
configCmd
|
|
13
|
+
.command("schema [identifier]")
|
|
14
|
+
.alias("check")
|
|
15
|
+
.description("Inspect declared configuration requirements and check resolution status")
|
|
16
|
+
.option("-P, --package <id>", "Target package ID or path")
|
|
17
|
+
.option("--data-dir <path>", "Custom database storage directory")
|
|
18
|
+
.option("--json", "Output as JSON")
|
|
19
|
+
.option("--envelope", "Wrap JSON output in standard envelope")
|
|
20
|
+
.action(async (identifier, rawOptions, cmd) => {
|
|
21
|
+
try {
|
|
22
|
+
const options = getEffectiveOptions(rawOptions, cmd);
|
|
23
|
+
const targetPkg = identifier || options.package;
|
|
24
|
+
const root = resolvePackageRoot(targetPkg);
|
|
25
|
+
if (!root) {
|
|
26
|
+
if (targetPkg) {
|
|
27
|
+
throw new ArgumentError(`Package '${targetPkg}' not found in linked packages or path`);
|
|
28
|
+
}
|
|
29
|
+
throw new ArgumentError("Not in an ActionDock project.\nUsage: ad config schema [package-id] or cd into a project directory.");
|
|
30
|
+
}
|
|
31
|
+
const projConfig = loadProjectConfig(root);
|
|
32
|
+
const declared = projConfig.config || {};
|
|
33
|
+
const declaredKeys = Object.keys(declared);
|
|
34
|
+
const target = await createActionDockTarget({
|
|
35
|
+
type: "local",
|
|
36
|
+
projectRoot: root,
|
|
37
|
+
customHome: context?.customHome,
|
|
38
|
+
dataDir: options.dataDir || context?.dataDir,
|
|
39
|
+
});
|
|
40
|
+
let globalConfig = [];
|
|
41
|
+
let projectConfig = [];
|
|
42
|
+
try {
|
|
43
|
+
globalConfig = await target.listConfig("global");
|
|
44
|
+
projectConfig = await target.listConfig(projConfig.id);
|
|
45
|
+
}
|
|
46
|
+
finally {
|
|
47
|
+
await target.close();
|
|
48
|
+
}
|
|
49
|
+
const projectConfigMap = new Map(projectConfig.map((c) => [c.key, c]));
|
|
50
|
+
const globalConfigMap = new Map(globalConfig.map((c) => [c.key, c]));
|
|
51
|
+
const items = declaredKeys.map((key) => {
|
|
52
|
+
const itemDef = declared[key];
|
|
53
|
+
const isSecret = isSecretConfigKey(key, itemDef);
|
|
54
|
+
let resolvedValue;
|
|
55
|
+
let source = "missing";
|
|
56
|
+
let status = "MISSING";
|
|
57
|
+
const envResolved = resolveEnvValue(key, itemDef, projConfig.id);
|
|
58
|
+
const projItem = projectConfigMap.get(key);
|
|
59
|
+
const globItem = globalConfigMap.get(key);
|
|
60
|
+
if (projItem && projItem.configured && projItem.source === "package") {
|
|
61
|
+
resolvedValue = projItem.value;
|
|
62
|
+
source = "project";
|
|
63
|
+
status = "SET";
|
|
64
|
+
}
|
|
65
|
+
else if (globItem && globItem.configured) {
|
|
66
|
+
resolvedValue = globItem.value;
|
|
67
|
+
source = "global";
|
|
68
|
+
status = "SET";
|
|
69
|
+
}
|
|
70
|
+
else if (envResolved !== undefined) {
|
|
71
|
+
resolvedValue = envResolved.value;
|
|
72
|
+
source = "env";
|
|
73
|
+
status = "SET";
|
|
74
|
+
}
|
|
75
|
+
else if (itemDef.default !== undefined) {
|
|
76
|
+
resolvedValue = itemDef.default;
|
|
77
|
+
source = "default";
|
|
78
|
+
status = "DEFAULT";
|
|
79
|
+
}
|
|
80
|
+
return {
|
|
81
|
+
key,
|
|
82
|
+
required: Boolean(itemDef.required),
|
|
83
|
+
secret: isSecret,
|
|
84
|
+
status,
|
|
85
|
+
source,
|
|
86
|
+
description: itemDef.description || "",
|
|
87
|
+
defaultValue: itemDef.default,
|
|
88
|
+
hasValue: resolvedValue !== undefined,
|
|
89
|
+
};
|
|
90
|
+
});
|
|
91
|
+
const missingRequired = items.filter((i) => i.required && i.status === "MISSING");
|
|
92
|
+
const ok = missingRequired.length === 0;
|
|
93
|
+
const result = {
|
|
94
|
+
packageId: projConfig.id,
|
|
95
|
+
projectRoot: root,
|
|
96
|
+
ok,
|
|
97
|
+
missingCount: missingRequired.length,
|
|
98
|
+
configs: items,
|
|
99
|
+
};
|
|
100
|
+
if (options.json || options.envelope) {
|
|
101
|
+
renderResult(result, {
|
|
102
|
+
json: options.json,
|
|
103
|
+
envelope: options.envelope,
|
|
104
|
+
context,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
writeStdout(renderConfigSchema(items, projConfig.id, root) + "\n", context);
|
|
109
|
+
}
|
|
110
|
+
if (!ok) {
|
|
111
|
+
process.exitCode = 1;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
catch (err) {
|
|
115
|
+
if (err instanceof ArgumentError || err instanceof ExecutionError) {
|
|
116
|
+
throw err;
|
|
117
|
+
}
|
|
118
|
+
throw new ExecutionError(err.message);
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Command } from "commander";
|
|
2
|
+
import type { CliContext } from "../../types.js";
|
|
3
|
+
/**
|
|
4
|
+
* 注册 config set 子命令:写入配置值(支持标准输入与安全提示)。
|
|
5
|
+
*
|
|
6
|
+
* @param configCmd config 命令实例
|
|
7
|
+
* @param context 命令行上下文
|
|
8
|
+
*/
|
|
9
|
+
export declare function registerConfigSetCommand(configCmd: Command, context?: CliContext): void;
|