viza 1.9.32 → 1.9.34

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.
@@ -24,26 +24,27 @@ export async function createProgram() {
24
24
  * Build command tree for hinting and inject leaf commands into Commander.
25
25
  */
26
26
  const commandTree = buildCommandTree();
27
- /**
28
- * Hook: show next-level command hints when command is incomplete.
29
- * Example:
30
- * viza aws
31
- * show available subcommands under "aws"
32
- */
33
- program.on("command:*", () => {
34
- const parts = program.args;
35
- const findNode = (nodes, depth = 0) => {
36
- for (const node of nodes) {
37
- const name = node.command.split(" ").pop();
38
- if (name === parts[depth]) {
39
- if (depth === parts.length - 1)
40
- return node;
41
- if (node.children)
42
- return findNode(node.children, depth + 1);
43
- }
27
+ // ------------------------------------------------
28
+ // Build command index (O(1) lookup)
29
+ // ------------------------------------------------
30
+ const nodeMap = new Map();
31
+ const buildIndex = (nodes) => {
32
+ for (const n of nodes) {
33
+ if (!Array.isArray(n.path) || n.path.length === 0) {
34
+ throw new Error("invalid_node_missing_path");
35
+ }
36
+ const key = n.path.join(":"); // safe key
37
+ nodeMap.set(key, n);
38
+ if (n.children?.length) {
39
+ buildIndex(n.children);
44
40
  }
45
- };
46
- const node = findNode(commandTree);
41
+ }
42
+ };
43
+ buildIndex(commandTree);
44
+ program.on("command:*", () => {
45
+ const parts = process.argv.slice(2);
46
+ const key = parts.join(":");
47
+ const node = nodeMap.get(key);
47
48
  if (node) {
48
49
  renderHint(node);
49
50
  }
@@ -55,7 +56,10 @@ export async function createProgram() {
55
56
  const argRegistry = new WeakMap();
56
57
  const walk = (nodes, parent) => {
57
58
  for (const node of nodes) {
58
- const name = node.command.split(" ").pop(); // chỉ lấy tên cuối
59
+ if (!node.path?.length) {
60
+ throw new Error("invalid_node_missing_path");
61
+ }
62
+ const name = node.path[node.path.length - 1];
59
63
  // Tìm hoặc tạo subcommand ở level này
60
64
  let sub = parent.commands.find((c) => c.name() === name);
61
65
  if (!sub) {
@@ -1,7 +1,7 @@
1
1
  import { registerCommand } from "../../../core/commandRegistry.js";
2
2
  import { bootstrapAgeCommand } from "./bootstrap.js";
3
3
  const descriptor = {
4
- command: "age bootstrap",
4
+ path: ["age", "bootstrap"],
5
5
  description: "Bootstrap age keypair and infrastructure",
6
6
  run: bootstrapAgeCommand
7
7
  };
@@ -1,7 +1,7 @@
1
1
  import { registerCommand } from "../../../../core/commandRegistry.js";
2
2
  import { bootstrapAwsRolesAnywhereCommand } from "./bootstrap.js";
3
3
  const descriptor = {
4
- command: "aws rolesanywhere bootstrap",
4
+ path: ["aws", "rolesanywhere", "bootstrap"],
5
5
  description: "Bootstrap AWS RolesAnywhere infrastructure",
6
6
  run: bootstrapAwsRolesAnywhereCommand
7
7
  };
@@ -1,7 +1,7 @@
1
1
  import { registerCommand } from "../../../../core/commandRegistry.js";
2
2
  import { rebootstrapAwsRolesAnywhereCommand } from "./rebootstrap.js";
3
3
  const descriptor = {
4
- command: "aws rolesanywhere rebootstrap",
4
+ path: ["aws", "rolesanywhere", "rebootstrap"],
5
5
  description: "Rebootstrap AWS RolesAnywhere infrastructure",
6
6
  run: rebootstrapAwsRolesAnywhereCommand
7
7
  };
@@ -1,7 +1,7 @@
1
1
  import { registerCommand } from "../../../../core/commandRegistry.js";
2
2
  import { rotateAwsRolesAnywhereCommand } from "./rotate.js";
3
3
  const descriptor = {
4
- command: "aws rolesanywhere rotate",
4
+ path: ["aws", "rolesanywhere", "rotate"],
5
5
  description: "Rotate AWS RolesAnywhere certificates and credentials",
6
6
  run: rotateAwsRolesAnywhereCommand
7
7
  };
@@ -1,7 +1,7 @@
1
1
  import { registerCommand } from "../../../../core/commandRegistry.js";
2
2
  import { updateAwsRolesAnywhereRoleCommand } from "./update-role.js";
3
3
  const descriptor = {
4
- command: "aws rolesanywhere update-role",
4
+ path: ["aws", "rolesanywhere", "update-role"],
5
5
  description: "Update AWS RolesAnywhere role configuration",
6
6
  run: updateAwsRolesAnywhereRoleCommand
7
7
  };
@@ -1,7 +1,7 @@
1
1
  import { registerCommand } from "../../../../core/commandRegistry.js";
2
2
  import { loginBillingAwsCommand } from "./aws.js";
3
3
  const descriptor = {
4
- command: "billing login aws",
4
+ path: ["billing", "login", "aws"],
5
5
  description: "Login to AWS billing account",
6
6
  options: [
7
7
  {
@@ -2,7 +2,7 @@ import { registerCommand } from "../../core/commandRegistry.js";
2
2
  import { vizaBootstrapCommand } from "./index.js";
3
3
  import { getEnv } from "../../context/env.js";
4
4
  const descriptor = {
5
- command: "bootstrap",
5
+ path: ["bootstrap"],
6
6
  description: "Bootstrap infrastructure or system components",
7
7
  run: async () => {
8
8
  const runtimeEnv = getEnv();
@@ -0,0 +1,52 @@
1
+ import { getEnv } from "../../../context/env.js";
2
+ import { getRunner, RUNTIME_WORKER_CONTROL_INTENT } from "../../../context/hubIntent.js";
3
+ import { dispatchIntentAndWait } from "../../../core/dispatch.js";
4
+ import { policy } from "./policy.js";
5
+ import { showDispatchRuns } from "./show-runs.js";
6
+ /**
7
+ * viza dispatch runs
8
+ *
9
+ * Flow:
10
+ * 1) Resolve env (deterministic)
11
+ * 2) If --app → show GitHub Actions link locally (no dispatch)
12
+ * 3) Otherwise dispatch intent to hub
13
+ */
14
+ export async function runsCommand(options) {
15
+ // 1️⃣ Resolve environment
16
+ const env = getEnv();
17
+ const intent = RUNTIME_WORKER_CONTROL_INTENT;
18
+ const runner = getRunner();
19
+ // Resolve allowed teams (same contract as other commands)
20
+ const allowedTeams = Array.from(policy.byEnv[env]);
21
+ // Parse limit option
22
+ let limit = undefined;
23
+ if (options.limit !== undefined) {
24
+ const parsed = Number(options.limit);
25
+ if (!Number.isNaN(parsed) && parsed > 0) {
26
+ limit = parsed;
27
+ }
28
+ }
29
+ // 3️⃣ Dispatch intent to hub
30
+ const result = await dispatchIntentAndWait({
31
+ intent,
32
+ commandType: "dispatch.runs",
33
+ infraKey: "core",
34
+ runType: "runtime",
35
+ targetEnv: env,
36
+ allowedTeams,
37
+ selfHosted: false,
38
+ keepLog: false,
39
+ flowGates: {
40
+ secrets: false,
41
+ },
42
+ payload: {
43
+ runner,
44
+ ...(limit ? { limit } : {}),
45
+ },
46
+ }, {
47
+ mode: "dispatch",
48
+ log: "show",
49
+ });
50
+ await showDispatchRuns(result);
51
+ return result;
52
+ }
@@ -0,0 +1,17 @@
1
+ import { registerCommand } from "../../../core/commandRegistry.js";
2
+ import { runsCommand } from "./cancel-run.js";
3
+ const descriptor = {
4
+ path: ["dispatch", "cancel-run"],
5
+ description: "Cancel a queued GitHub Actions run by runId (admin only)",
6
+ args: [
7
+ {
8
+ name: "runId",
9
+ required: true,
10
+ description: "Run ID to cancel"
11
+ }
12
+ ],
13
+ options: [],
14
+ run: runsCommand
15
+ };
16
+ registerCommand(descriptor);
17
+ export default descriptor;
@@ -0,0 +1,14 @@
1
+ export const policy = {
2
+ byEnv: {
3
+ "dev": [
4
+ "viza-manager",
5
+ "viza-admin",
6
+ "viza-super"
7
+ ],
8
+ "prod": [
9
+ "viza-publisher",
10
+ "viza-admin",
11
+ "viza-super"
12
+ ]
13
+ }
14
+ };
@@ -0,0 +1,112 @@
1
+ import chalk from "chalk";
2
+ import { formatDateTime, renderCommandInline } from "../../../ui/primitives/render-command.js";
3
+ import { formatAge, formatDuration, pad } from "../../../ui/theme.js";
4
+ export async function showDispatchRuns(result) {
5
+ const env = result;
6
+ if (env.status !== "success" ||
7
+ env.kind !== "runtime" ||
8
+ !env.data?.result) {
9
+ return;
10
+ }
11
+ const { runs } = env.data.result;
12
+ if (!Array.isArray(runs)) {
13
+ throw new Error("invalid_dispatch_runs_result_shape");
14
+ }
15
+ console.log("\n📋 Dispatch Runs");
16
+ console.log(chalk.gray("─".repeat(131)));
17
+ const header = [
18
+ pad("RUN", 15),
19
+ pad("STATUS", 18),
20
+ pad("CONCLUSION", 15),
21
+ pad("COMMITTER", 18),
22
+ pad("CREATED_AT", 22),
23
+ pad("AGE", 13),
24
+ pad("DURATION", 13),
25
+ "ATTEMPT",
26
+ ].join(" ");
27
+ console.log(header);
28
+ console.log(chalk.gray("─".repeat(131)));
29
+ if (runs.length === 0) {
30
+ const emptyRow = chalk.gray(" (no dispatch runs found)");
31
+ console.log(emptyRow);
32
+ console.log(chalk.gray("─".repeat(131)));
33
+ console.log();
34
+ return;
35
+ }
36
+ for (const run of runs) {
37
+ let status;
38
+ let conclusion;
39
+ if (run.status === "completed") {
40
+ status = "completed";
41
+ switch (run.conclusion) {
42
+ case "success":
43
+ conclusion = "✅ success";
44
+ break;
45
+ case "failure":
46
+ conclusion = "❌ failure";
47
+ break;
48
+ case "cancelled":
49
+ conclusion = "⚪ cancelled";
50
+ break;
51
+ case "timed_out":
52
+ conclusion = "⏱ timed_out";
53
+ break;
54
+ case "skipped":
55
+ conclusion = "⏭ skipped";
56
+ break;
57
+ default:
58
+ conclusion = run.conclusion ?? "-";
59
+ }
60
+ }
61
+ else if (run.status === "in_progress") {
62
+ status = "⏳ in progress";
63
+ conclusion = "...";
64
+ }
65
+ else {
66
+ status = "⏳ queued";
67
+ conclusion = "-";
68
+ }
69
+ const committer = run.committer
70
+ ? `${run.committer.name}`
71
+ : "unknown";
72
+ const age = formatAge(run.createdAt);
73
+ const duration = run.status === "completed"
74
+ ? formatDuration(run.createdAt, run.updatedAt)
75
+ : "-";
76
+ const row = [
77
+ pad(String(run.id), 15),
78
+ pad(status, 18),
79
+ pad(conclusion, 14),
80
+ pad(committer, 18),
81
+ pad(formatDateTime(run.createdAt), 22),
82
+ pad(age, 13),
83
+ pad(duration, 13),
84
+ `#${run.attempt}`,
85
+ ].join(" ");
86
+ console.log(row);
87
+ }
88
+ console.log(chalk.gray("─".repeat(131)));
89
+ console.log();
90
+ // Hint section
91
+ if (runs.length > 0) {
92
+ const [firstRun] = runs;
93
+ if (!firstRun)
94
+ return;
95
+ const firstRunId = firstRun.id;
96
+ // derive binary name from process.argv (fallback safe)
97
+ const bin = (process.argv[1] || "viza").split("/").pop();
98
+ const sampleCmd = `${bin} dispatch logs ${firstRunId}`;
99
+ console.log();
100
+ console.log("💡 " + "\x1b[1mQuick Hint\x1b[0m");
101
+ console.log();
102
+ console.log(chalk.gray(" 👉 To list fewer or more runs, use --limit:"));
103
+ console.log(` ${renderCommandInline(`${bin} dispatch runs --limit <n>`)}`);
104
+ console.log();
105
+ console.log(chalk.gray(" 👉 To view detailed logs for a specific run, use:"));
106
+ console.log(` ${renderCommandInline(`${bin} dispatch logs <runId>`)}`);
107
+ console.log();
108
+ console.log(chalk.gray(" 👉 Example:"));
109
+ console.log(` ${renderCommandInline(sampleCmd)}`);
110
+ console.log();
111
+ }
112
+ }
@@ -1,7 +1,7 @@
1
1
  import { registerCommand } from "../../../core/commandRegistry.js";
2
2
  import { logsCommand } from "./logs.js";
3
3
  const descriptor = {
4
- command: "dispatch logs",
4
+ path: ["dispatch", "logs"],
5
5
  description: "View dispatch logs",
6
6
  args: [
7
7
  {
@@ -1,7 +1,7 @@
1
1
  import { registerCommand } from "../../../core/commandRegistry.js";
2
2
  import { runsCommand } from "./runs.js";
3
3
  const descriptor = {
4
- command: "dispatch runs",
4
+ path: ["dispatch", "runs"],
5
5
  description: "List and inspect dispatch runs",
6
6
  options: [
7
7
  {
@@ -1,7 +1,7 @@
1
1
  import { registerCommand } from "../../../../core/commandRegistry.js";
2
2
  import { backupGithubSecretsCommand } from "./backup.js";
3
3
  const descriptor = {
4
- command: "github secrets backup",
4
+ path: ["github", "secrets", "backup"],
5
5
  description: "Backup GitHub secrets to ssm parameter store",
6
6
  run: backupGithubSecretsCommand
7
7
  };
@@ -1,7 +1,7 @@
1
1
  import { registerCommand } from "../../../../core/commandRegistry.js";
2
2
  import { restoreGithubSecretsCommand } from "./restore.js";
3
3
  const descriptor = {
4
- command: "github secrets restore",
4
+ path: ["github", "secrets", "restore"],
5
5
  description: "Restore GitHub secrets from ssm parameter store",
6
6
  options: [
7
7
  { flags: "--core", description: "Restore core secrets" },
@@ -1,7 +1,7 @@
1
1
  import { registerCommand } from "../../../../core/commandRegistry.js";
2
2
  import { deployCommandHubCommand } from "./command-hub.js";
3
3
  const descriptor = {
4
- command: "infra deploy command-hub",
4
+ path: ["infra", "deploy", "command-hub"],
5
5
  description: "Deploy command hub worker to Cloudflare",
6
6
  options: [
7
7
  {
@@ -1,7 +1,7 @@
1
1
  import { registerCommand } from "../../../../core/commandRegistry.js";
2
2
  import { usageCommand } from "./usage.js";
3
3
  const descriptor = {
4
- command: "actions usage",
4
+ path: ["actions", "usage"],
5
5
  description: "Show GitHub Actions usage (monthly, by OS)",
6
6
  options: [],
7
7
  run: usageCommand
@@ -24,7 +24,7 @@ export async function showRuntimeUsage(result) {
24
24
  const values = usage.map((m) => {
25
25
  const val = `${m.totalMinutes} min`;
26
26
  if (m.month === latest?.month) {
27
- return chalk.bold.green(pad(val, 24));
27
+ return chalk.bold.white(pad(val, 24));
28
28
  }
29
29
  return pad(val, 24);
30
30
  }).join("");
@@ -46,7 +46,7 @@ export async function showRuntimeUsage(result) {
46
46
  return null;
47
47
  const text = `${m.month}: ${os.minutes} min`;
48
48
  if (m.month === latest?.month) {
49
- return chalk.bold.green(text);
49
+ return chalk.bold.white(text);
50
50
  }
51
51
  return chalk.gray(text);
52
52
  }).filter(Boolean);
@@ -67,7 +67,7 @@ export async function showRuntimeUsage(result) {
67
67
  console.log("\n🏢 Usage by Organization");
68
68
  console.log(chalk.gray("─".repeat(120)));
69
69
  for (const org of data.byOrg) {
70
- console.log(`\n${chalk.bold(org.org)}`);
70
+ console.log(`\n${chalk.bold.magentaBright(org.org)}`);
71
71
  const months = [...(org.months || [])].sort((a, b) => a.month.localeCompare(b.month));
72
72
  const latest = months[months.length - 1];
73
73
  const header = months.map((m) => pad(m.month, 24)).join("");
@@ -75,7 +75,7 @@ export async function showRuntimeUsage(result) {
75
75
  const values = months.map((m) => {
76
76
  const val = `${m.totalMinutes} min`;
77
77
  if (m.month === latest?.month) {
78
- return chalk.bold.green(pad(val, 24));
78
+ return chalk.bold.cyanBright(pad(val, 24));
79
79
  }
80
80
  return pad(val, 24);
81
81
  }).join("");
@@ -1,7 +1,7 @@
1
1
  import { registerCommand } from "../../../core/commandRegistry.js";
2
2
  import { loginAwsCommand } from "./aws.js";
3
3
  const descriptor = {
4
- command: "login aws",
4
+ path: ["login", "aws"],
5
5
  description: "Login to AWS",
6
6
  options: [
7
7
  {
@@ -1,7 +1,7 @@
1
1
  import { registerCommand } from "../../core/commandRegistry.js";
2
2
  import { whoamiCommand } from "./index.js";
3
3
  const descriptor = {
4
- command: "whoami",
4
+ path: ["whoami"],
5
5
  description: "Display current user information and team memberships",
6
6
  run: whoamiCommand
7
7
  };
@@ -33,16 +33,19 @@ export function getCommandRegistry() {
33
33
  export function buildCommandTree() {
34
34
  const rootMap = new Map();
35
35
  for (const cmd of registry) {
36
- const parts = cmd.command.trim().split(/\s+/);
36
+ if (!Array.isArray(cmd.path) || cmd.path.length === 0) {
37
+ throw new Error("invalid_command_descriptor_missing_path");
38
+ }
39
+ const parts = cmd.path;
37
40
  let currentMap = rootMap;
38
41
  let parentNode = null;
39
42
  for (let i = 0; i < parts.length; i++) {
40
43
  const part = parts[i];
41
- const full = parts.slice(0, i + 1).join(" ");
44
+ const fullPath = parts.slice(0, i + 1);
42
45
  let node = currentMap.get(part);
43
46
  if (!node) {
44
47
  node = {
45
- command: full,
48
+ path: fullPath,
46
49
  description: "",
47
50
  run: undefined,
48
51
  children: [],
@@ -1,6 +1,8 @@
1
1
  function getName(cmd) {
2
- const parts = cmd.command.trim().split(/\s+/);
3
- return parts[parts.length - 1] ?? cmd.command;
2
+ if (!Array.isArray(cmd.path) || cmd.path.length === 0) {
3
+ throw new Error("invalid_command_descriptor_missing_path");
4
+ }
5
+ return cmd.path[cmd.path.length - 1];
4
6
  }
5
7
  export function renderHint(cmd) {
6
8
  const name = getName(cmd);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "viza",
3
- "version": "1.9.32",
3
+ "version": "1.9.34",
4
4
  "type": "module",
5
5
  "description": "Viza unified command line interface",
6
6
  "bin": {