@actiondock/cli 2.0.1 → 2.0.3
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 +67 -41
- package/bin/ad.js +27 -2
- package/package.json +8 -5
- package/src/commands/action.ts +32 -1
- package/src/commands/config.ts +96 -10
- package/src/commands/doctor.ts +24 -6
- package/src/commands/index.ts +1 -1
- package/src/commands/info.ts +94 -115
- package/src/commands/playbook.ts +84 -5
- package/src/commands/profile.ts +1 -1
- package/src/commands/runs.ts +91 -1
- package/src/commands/serve.ts +32 -0
- package/src/commands/state.ts +124 -11
- package/src/commands/test.ts +29 -7
- package/src/index.ts +5 -3
package/src/commands/state.ts
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
2
|
import {
|
|
3
|
+
clearRemoteState,
|
|
3
4
|
createStorage,
|
|
5
|
+
deleteRemoteStateKey,
|
|
6
|
+
fetchRemoteStateList,
|
|
4
7
|
filterWithFallbackInfo,
|
|
5
8
|
findProjectRoot,
|
|
9
|
+
getRemoteStateKey,
|
|
6
10
|
listLinkedPackages,
|
|
7
11
|
loadProjectConfig,
|
|
8
12
|
resolvePackageRoot,
|
|
13
|
+
resolveTarget,
|
|
14
|
+
setRemoteStateKey,
|
|
9
15
|
} from "@actiondock/core";
|
|
10
16
|
import { Command } from "commander";
|
|
11
17
|
import { resolveIntent } from "../utils/filter";
|
|
@@ -47,6 +53,9 @@ export function registerStateCommands(program: Command): void {
|
|
|
47
53
|
.description("List state keys matching prefix, namespace, or intent pattern (in current project or linked packages)")
|
|
48
54
|
.option("-P, --package <id>", "Target package ID or path")
|
|
49
55
|
.option("-n, --namespace <ns>", "Filter by specific namespace")
|
|
56
|
+
.option("-p, --profile <name>", "Query state on a remote target")
|
|
57
|
+
.option("-s, --server <url>", "Remote server URL")
|
|
58
|
+
.option("-t, --token <token>", "Auth token for remote server")
|
|
50
59
|
.option("-i, --intent <pattern>", "Regex or fuzzy intent filter; falls back to full list when no match")
|
|
51
60
|
.option("--no-fallback", "Disable fallback to full list when no items match intent")
|
|
52
61
|
.option("-d, --detail", "Show detailed state entry objects (including namespace, expiration, update time)")
|
|
@@ -56,6 +65,31 @@ export function registerStateCommands(program: Command): void {
|
|
|
56
65
|
const effectiveIntent = resolveIntent(options.intent, prefix ? [prefix] : []);
|
|
57
66
|
const shouldFallback = options.fallback !== false;
|
|
58
67
|
|
|
68
|
+
const target = resolveTarget({
|
|
69
|
+
profile: options.profile,
|
|
70
|
+
server: options.server,
|
|
71
|
+
token: options.token,
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
if (target.type === "remote") {
|
|
75
|
+
const res = await fetchRemoteStateList(target.serverUrl!, target.token, {
|
|
76
|
+
package: options.package,
|
|
77
|
+
namespace: options.namespace,
|
|
78
|
+
prefix,
|
|
79
|
+
});
|
|
80
|
+
if (options.json) {
|
|
81
|
+
console.log(JSON.stringify(res.keys, null, 2));
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
console.log(
|
|
85
|
+
`State keys on remote server ${target.serverUrl}${target.profileName ? ` (Profile: ${target.profileName})` : ""} (${res.keys.length}):\n`
|
|
86
|
+
);
|
|
87
|
+
for (const k of res.keys) {
|
|
88
|
+
console.log(` - ${k}`);
|
|
89
|
+
}
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
59
93
|
const targetRoot = options.package
|
|
60
94
|
? resolvePackageRoot(options.package)
|
|
61
95
|
: findProjectRoot();
|
|
@@ -236,10 +270,32 @@ export function registerStateCommands(program: Command): void {
|
|
|
236
270
|
.description("Get a state value by key, namespace:key, or package/namespace:key")
|
|
237
271
|
.option("-P, --package <id>", "Target package ID or path")
|
|
238
272
|
.option("-n, --namespace <ns>", "Target namespace")
|
|
273
|
+
.option("-p, --profile <name>", "Query state on a remote target")
|
|
274
|
+
.option("-s, --server <url>", "Remote server URL")
|
|
275
|
+
.option("-t, --token <token>", "Auth token for remote server")
|
|
239
276
|
.option("--json", "Output as JSON")
|
|
240
277
|
.action(async (rawKey, options) => {
|
|
241
|
-
const { root, key } = getTargetRoot(options.package, rawKey);
|
|
242
278
|
try {
|
|
279
|
+
const target = resolveTarget({
|
|
280
|
+
profile: options.profile,
|
|
281
|
+
server: options.server,
|
|
282
|
+
token: options.token,
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
if (target.type === "remote") {
|
|
286
|
+
const res = await getRemoteStateKey(target.serverUrl!, rawKey, target.token, {
|
|
287
|
+
package: options.package,
|
|
288
|
+
namespace: options.namespace,
|
|
289
|
+
});
|
|
290
|
+
if (options.json) {
|
|
291
|
+
console.log(JSON.stringify(res, null, 2));
|
|
292
|
+
} else {
|
|
293
|
+
console.log(res.value !== undefined ? JSON.stringify(res.value, null, 2) : "undefined");
|
|
294
|
+
}
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const { root, key } = getTargetRoot(options.package, rawKey);
|
|
243
299
|
const projConfig = loadProjectConfig(root);
|
|
244
300
|
const storage = createStorage(projConfig.id, { projectRoot: root });
|
|
245
301
|
|
|
@@ -290,10 +346,36 @@ export function registerStateCommands(program: Command): void {
|
|
|
290
346
|
.description("Set a state value by key, namespace:key, or package/namespace:key")
|
|
291
347
|
.option("-P, --package <id>", "Target package ID or path")
|
|
292
348
|
.option("-n, --namespace <ns>", "Target namespace")
|
|
349
|
+
.option("-p, --profile <name>", "Set state on a remote target")
|
|
350
|
+
.option("-s, --server <url>", "Remote server URL")
|
|
351
|
+
.option("-t, --token <token>", "Auth token for remote server")
|
|
293
352
|
.option("--ttl <seconds>", "Time to live in seconds", (v) => parseInt(v, 10))
|
|
294
353
|
.action(async (rawKey, rawValue, options) => {
|
|
295
|
-
const { root, key } = getTargetRoot(options.package, rawKey);
|
|
296
354
|
try {
|
|
355
|
+
let parsed: unknown = rawValue;
|
|
356
|
+
try {
|
|
357
|
+
parsed = JSON.parse(rawValue);
|
|
358
|
+
} catch {
|
|
359
|
+
parsed = rawValue;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
const target = resolveTarget({
|
|
363
|
+
profile: options.profile,
|
|
364
|
+
server: options.server,
|
|
365
|
+
token: options.token,
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
if (target.type === "remote") {
|
|
369
|
+
await setRemoteStateKey(target.serverUrl!, rawKey, parsed, target.token, {
|
|
370
|
+
package: options.package,
|
|
371
|
+
namespace: options.namespace,
|
|
372
|
+
ttl: options.ttl,
|
|
373
|
+
});
|
|
374
|
+
console.log(`[OK] State '${rawKey}' updated on remote server ${target.serverUrl}`);
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
const { root, key } = getTargetRoot(options.package, rawKey);
|
|
297
379
|
const projConfig = loadProjectConfig(root);
|
|
298
380
|
const storage = createStorage(projConfig.id, { projectRoot: root });
|
|
299
381
|
|
|
@@ -309,13 +391,6 @@ export function registerStateCommands(program: Command): void {
|
|
|
309
391
|
actualKey = key.slice(colonIdx + 1);
|
|
310
392
|
}
|
|
311
393
|
|
|
312
|
-
let parsed: unknown = rawValue;
|
|
313
|
-
try {
|
|
314
|
-
parsed = JSON.parse(rawValue);
|
|
315
|
-
} catch {
|
|
316
|
-
parsed = rawValue;
|
|
317
|
-
}
|
|
318
|
-
|
|
319
394
|
await storage.setState(ns, actualKey, parsed, options.ttl);
|
|
320
395
|
storage.close();
|
|
321
396
|
|
|
@@ -336,10 +411,28 @@ export function registerStateCommands(program: Command): void {
|
|
|
336
411
|
.description("Delete a state value by key, namespace:key, or package/namespace:key")
|
|
337
412
|
.option("-P, --package <id>", "Target package ID or path")
|
|
338
413
|
.option("-n, --namespace <ns>", "Target namespace")
|
|
414
|
+
.option("-p, --profile <name>", "Delete state on a remote target")
|
|
415
|
+
.option("-s, --server <url>", "Remote server URL")
|
|
416
|
+
.option("-t, --token <token>", "Auth token for remote server")
|
|
339
417
|
.option("--silent", "Do not exit with error if key is not found")
|
|
340
418
|
.action(async (rawKey, options) => {
|
|
341
|
-
const { root, key } = getTargetRoot(options.package, rawKey);
|
|
342
419
|
try {
|
|
420
|
+
const target = resolveTarget({
|
|
421
|
+
profile: options.profile,
|
|
422
|
+
server: options.server,
|
|
423
|
+
token: options.token,
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
if (target.type === "remote") {
|
|
427
|
+
await deleteRemoteStateKey(target.serverUrl!, rawKey, target.token, {
|
|
428
|
+
package: options.package,
|
|
429
|
+
namespace: options.namespace,
|
|
430
|
+
});
|
|
431
|
+
console.log(`[OK] State '${rawKey}' deleted from remote server ${target.serverUrl}`);
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
const { root, key } = getTargetRoot(options.package, rawKey);
|
|
343
436
|
const projConfig = loadProjectConfig(root);
|
|
344
437
|
const storage = createStorage(projConfig.id, { projectRoot: root });
|
|
345
438
|
const deleted = await storage.deleteStateSmart(key, options.namespace);
|
|
@@ -366,10 +459,30 @@ export function registerStateCommands(program: Command): void {
|
|
|
366
459
|
.description("Clear state entries by namespace, prefix, or all")
|
|
367
460
|
.option("-P, --package <id>", "Target package ID or path")
|
|
368
461
|
.option("-n, --namespace <ns>", "Target namespace to clear")
|
|
462
|
+
.option("-p, --profile <name>", "Clear state on a remote target")
|
|
463
|
+
.option("-s, --server <url>", "Remote server URL")
|
|
464
|
+
.option("-t, --token <token>", "Auth token for remote server")
|
|
369
465
|
.option("-a, --all", "Clear all state entries across all namespaces in this package")
|
|
370
466
|
.action(async (prefix = "", options) => {
|
|
371
|
-
const { root } = getTargetRoot(options.package);
|
|
372
467
|
try {
|
|
468
|
+
const target = resolveTarget({
|
|
469
|
+
profile: options.profile,
|
|
470
|
+
server: options.server,
|
|
471
|
+
token: options.token,
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
if (target.type === "remote") {
|
|
475
|
+
const res = await clearRemoteState(target.serverUrl!, target.token, {
|
|
476
|
+
package: options.package,
|
|
477
|
+
namespace: options.namespace,
|
|
478
|
+
prefix,
|
|
479
|
+
all: Boolean(options.all),
|
|
480
|
+
});
|
|
481
|
+
console.log(`[OK] Cleared ${res.clearedCount} state entry(s) on remote server ${target.serverUrl}`);
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
const { root } = getTargetRoot(options.package);
|
|
373
486
|
const projConfig = loadProjectConfig(root);
|
|
374
487
|
const storage = createStorage(projConfig.id, { projectRoot: root });
|
|
375
488
|
const count = await storage.clearState({
|
package/src/commands/test.ts
CHANGED
|
@@ -1,26 +1,48 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
1
4
|
import { findProjectRoot } from "@actiondock/core";
|
|
2
5
|
import { Command } from "commander";
|
|
3
6
|
|
|
4
7
|
export function registerTestCommand(program: Command): void {
|
|
5
8
|
program
|
|
6
9
|
.command("test [pattern]")
|
|
7
|
-
.description("Run project tests using
|
|
10
|
+
.description("Run project tests using configured test runner (node:test or bun test)")
|
|
8
11
|
.action((pattern) => {
|
|
9
12
|
const root = findProjectRoot();
|
|
10
13
|
const cwd = root || process.cwd();
|
|
11
14
|
|
|
12
|
-
const
|
|
15
|
+
const pkgJsonPath = join(cwd, "package.json");
|
|
16
|
+
let testCmd = "npm";
|
|
17
|
+
let testArgs = ["test"];
|
|
18
|
+
|
|
19
|
+
if (existsSync(pkgJsonPath)) {
|
|
20
|
+
try {
|
|
21
|
+
const pkg = JSON.parse(readFileSync(pkgJsonPath, "utf-8"));
|
|
22
|
+
if (pkg.scripts?.test?.includes("bun test") && typeof (globalThis as any).Bun !== "undefined") {
|
|
23
|
+
testCmd = "bun";
|
|
24
|
+
testArgs = ["test"];
|
|
25
|
+
}
|
|
26
|
+
} catch {
|
|
27
|
+
// ignore
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
13
31
|
if (pattern) {
|
|
14
|
-
|
|
32
|
+
if (testCmd === "npm") {
|
|
33
|
+
testArgs.push("--", pattern);
|
|
34
|
+
} else {
|
|
35
|
+
testArgs.push(pattern);
|
|
36
|
+
}
|
|
15
37
|
}
|
|
16
38
|
|
|
17
|
-
const proc =
|
|
39
|
+
const proc = spawnSync(testCmd, testArgs, {
|
|
18
40
|
cwd,
|
|
19
|
-
stdio:
|
|
41
|
+
stdio: "inherit",
|
|
20
42
|
});
|
|
21
43
|
|
|
22
|
-
if (proc.
|
|
23
|
-
process.exit(proc.
|
|
44
|
+
if (proc.status !== 0) {
|
|
45
|
+
process.exit(proc.status ?? 1);
|
|
24
46
|
}
|
|
25
47
|
});
|
|
26
48
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,18 +1,20 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { setupNodeRuntime } from "@actiondock/runtime-node";
|
|
3
2
|
import { createCliProgram } from "./commands";
|
|
4
3
|
|
|
4
|
+
export * from "./commands";
|
|
5
|
+
|
|
5
6
|
/**
|
|
6
7
|
* ActionDock CLI 主执行入口函数。
|
|
7
8
|
*
|
|
8
9
|
* @param argv 命令行参数数组(默认使用 process.argv)
|
|
9
10
|
*/
|
|
10
11
|
export async function main(argv: string[] = process.argv): Promise<void> {
|
|
12
|
+
setupNodeRuntime();
|
|
11
13
|
const program = createCliProgram();
|
|
12
14
|
await program.parseAsync(argv);
|
|
13
15
|
}
|
|
14
16
|
|
|
15
|
-
if (import.meta.main) {
|
|
17
|
+
if ((import.meta as any).main) {
|
|
16
18
|
main().catch((err) => {
|
|
17
19
|
console.error("Fatal error:", err);
|
|
18
20
|
process.exit(1);
|