viza 1.7.45 → 1.7.47

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.
@@ -9,6 +9,7 @@ import { registerInfraCommand } from "../commands/infra/register.js";
9
9
  import { registerAgeCommand } from "../commands/age/register.js";
10
10
  import { registerBillingCommand } from "../commands/billing/register.js";
11
11
  import { registerGithubCommand } from "../commands/github/register.js";
12
+ import { registerDispatchCommand } from "../commands/dispatch/register.js";
12
13
  export function createProgram() {
13
14
  const program = new Command();
14
15
  program
@@ -20,6 +21,7 @@ export function createProgram() {
20
21
  registerAwsCommand(program);
21
22
  registerBillingCommand(program);
22
23
  registerBootstrapCommand(program);
24
+ registerDispatchCommand(program);
23
25
  registerGithubCommand(program);
24
26
  registerInfraCommand(program);
25
27
  registerLoginCommand(program);
@@ -0,0 +1,62 @@
1
+ import { resolveEnv } from "../../../context/env.js";
2
+ import { resolveResourceHubIntent } from "../../../context/hubIntent.js";
3
+ import { dispatchIntentAndWait } from "../../../core/dispatch.js";
4
+ const TARGET_TEAMS = {
5
+ "dev": [
6
+ "viza-designer",
7
+ "viza-deployer",
8
+ "viza-manager",
9
+ "viza-admin",
10
+ "viza-super"
11
+ ],
12
+ "prod": [
13
+ "viza-publisher",
14
+ "viza-admin",
15
+ "viza-super"
16
+ ]
17
+ };
18
+ /**
19
+ * viza dispatch logs <runId>
20
+ *
21
+ * Flow:
22
+ * 1) Resolve env (deterministic)
23
+ * 2) If --app → show GitHub Actions page locally (no dispatch)
24
+ * 3) Otherwise dispatch intent to hub to fetch log artifact
25
+ */
26
+ export async function logsCommand(runId, options) {
27
+ // 1️⃣ Resolve environment
28
+ const env = resolveEnv(options);
29
+ const intent = resolveResourceHubIntent(env);
30
+ // Resolve allowed teams (same contract as other commands)
31
+ const allowedTeams = TARGET_TEAMS[env];
32
+ // 2️⃣ Handle --app locally (do NOT dispatch)
33
+ if (options.app === true) {
34
+ const url = env === "prod"
35
+ ? "https://github.com/Modo-Infra/publish-app/actions"
36
+ : "https://github.com/Modo-Infra/build-app/actions";
37
+ console.log("\n📦 App pipeline runs:");
38
+ console.log(url);
39
+ console.log();
40
+ return;
41
+ }
42
+ // 3️⃣ Dispatch intent to hub
43
+ const result = await dispatchIntentAndWait({
44
+ intent,
45
+ commandType: "dispatch.logs",
46
+ infraKey: "core",
47
+ targetEnv: env,
48
+ allowedTeams,
49
+ selfHosted: options.selfHosted === true,
50
+ keepLog: options.removeLog !== true,
51
+ flowGates: {
52
+ secrets: false,
53
+ },
54
+ payload: {
55
+ runId
56
+ },
57
+ }, {
58
+ status: false,
59
+ log: "show",
60
+ });
61
+ return result;
62
+ }
@@ -0,0 +1,18 @@
1
+ import { logsCommand } from "./logs.js";
2
+ import { getResolvedOptions } from "../../../cli/resolveOptions.js";
3
+ /**
4
+ * Register:
5
+ * viza dispatch logs <runId>
6
+ */
7
+ export function registerDispatchLogsCommand(program) {
8
+ program
9
+ .command("logs <runId>")
10
+ .description("Download logs for a dispatch workflow run")
11
+ .option("--prod", "Use production environment")
12
+ .option("--dev", "Use development environment")
13
+ .option("--app", "Open GitHub Actions page for app pipelines instead of querying hub")
14
+ .action(async (runId, opts, command) => {
15
+ const fullOpts = getResolvedOptions(command);
16
+ await logsCommand(runId, fullOpts);
17
+ });
18
+ }
@@ -0,0 +1,9 @@
1
+ import { registerDispatchRunsCommand } from "./runs/register.js";
2
+ import { registerDispatchLogsCommand } from "./logs/register.js";
3
+ export function registerDispatchCommand(program) {
4
+ const dispatch = program
5
+ .command("dispatch")
6
+ .description("Dispatch operations");
7
+ registerDispatchRunsCommand(dispatch);
8
+ registerDispatchLogsCommand(dispatch);
9
+ }
@@ -0,0 +1,19 @@
1
+ import { runsCommand } from "./runs.js";
2
+ import { getResolvedOptions } from "../../../cli/resolveOptions.js";
3
+ /**
4
+ * Register:
5
+ * viza dispatch runs
6
+ */
7
+ export function registerDispatchRunsCommand(program) {
8
+ program
9
+ .command("runs")
10
+ .description("List latest dispatch workflow runs")
11
+ .option("--prod", "Use production environment")
12
+ .option("--dev", "Use development environment")
13
+ .option("--app", "Open GitHub Actions page for app pipelines instead of querying hub")
14
+ .option("--limit <n>", "Limit number of runs to fetch", "20")
15
+ .action(async (opts, command) => {
16
+ const fullOpts = getResolvedOptions(command);
17
+ await runsCommand(fullOpts);
18
+ });
19
+ }
@@ -0,0 +1,72 @@
1
+ import { resolveEnv } from "../../../context/env.js";
2
+ import { resolveRuntimeHubIntent } from "../../../context/hubIntent.js";
3
+ import { dispatchIntentAndWait } from "../../../core/dispatch.js";
4
+ import { showDispatchRuns } from "./show-runs.js";
5
+ const TARGET_TEAMS = {
6
+ "dev": [
7
+ "viza-designer",
8
+ "viza-deployer",
9
+ "viza-manager",
10
+ "viza-admin",
11
+ "viza-super"
12
+ ],
13
+ "prod": [
14
+ "viza-publisher",
15
+ "viza-admin",
16
+ "viza-super"
17
+ ]
18
+ };
19
+ /**
20
+ * viza dispatch runs
21
+ *
22
+ * Flow:
23
+ * 1) Resolve env (deterministic)
24
+ * 2) If --app → show GitHub Actions link locally (no dispatch)
25
+ * 3) Otherwise dispatch intent to hub
26
+ */
27
+ export async function runsCommand(options) {
28
+ // 1️⃣ Resolve environment
29
+ const env = resolveEnv(options);
30
+ const intent = resolveRuntimeHubIntent();
31
+ // Resolve allowed teams (same contract as other commands)
32
+ const allowedTeams = TARGET_TEAMS[env];
33
+ // 2️⃣ Handle --app locally (do NOT dispatch)
34
+ if (options.app === true) {
35
+ const url = env === "prod"
36
+ ? "https://github.com/Modo-Infra/publish-app/actions"
37
+ : "https://github.com/Modo-Infra/build-app/actions";
38
+ console.log("\n📦 App pipeline runs:");
39
+ console.log(url);
40
+ console.log();
41
+ return;
42
+ }
43
+ // Parse limit option
44
+ let limit = undefined;
45
+ if (options.limit !== undefined) {
46
+ const parsed = Number(options.limit);
47
+ if (!Number.isNaN(parsed) && parsed > 0) {
48
+ limit = parsed;
49
+ }
50
+ }
51
+ // 3️⃣ Dispatch intent to hub
52
+ const result = await dispatchIntentAndWait({
53
+ intent,
54
+ commandType: "dispatch.runs",
55
+ infraKey: "core",
56
+ targetEnv: env,
57
+ allowedTeams,
58
+ selfHosted: options.selfHosted === true,
59
+ keepLog: options.removeLog !== true,
60
+ flowGates: {
61
+ secrets: false,
62
+ },
63
+ payload: {
64
+ ...(limit ? { limit } : {}),
65
+ },
66
+ }, {
67
+ status: false,
68
+ log: "show",
69
+ });
70
+ await showDispatchRuns(result);
71
+ return result;
72
+ }
@@ -0,0 +1,111 @@
1
+ function formatAge(iso) {
2
+ const diff = Date.now() - new Date(iso).getTime();
3
+ const sec = Math.floor(diff / 1000);
4
+ if (sec < 60)
5
+ return `${sec}s`;
6
+ const min = Math.floor(sec / 60);
7
+ const remSec = sec % 60;
8
+ // < 10 minutes → "Xm Ys"
9
+ if (min < 10)
10
+ return `${min}m ${remSec}s`;
11
+ // < 60 minutes → "Xm"
12
+ if (min < 60)
13
+ return `${min}m`;
14
+ const hr = Math.floor(min / 60);
15
+ const remMin = min % 60;
16
+ // < 10 hours → "Xh Ym"
17
+ if (hr < 10)
18
+ return `${hr}h ${remMin}m`;
19
+ // ≥ 10 hours → "Xh"
20
+ return `${hr}h`;
21
+ }
22
+ function formatDuration(start, end) {
23
+ const diff = new Date(end).getTime() - new Date(start).getTime();
24
+ const sec = Math.floor(diff / 1000);
25
+ if (sec < 60)
26
+ return `${sec}s`;
27
+ const min = Math.floor(sec / 60);
28
+ const rem = sec % 60;
29
+ return `${min}m ${rem}s`;
30
+ }
31
+ function pad(v, n) {
32
+ return v.padEnd(n, " ");
33
+ }
34
+ export async function showDispatchRuns(result) {
35
+ const env = result;
36
+ if (env.status !== "success" ||
37
+ env.kind !== "runtime" ||
38
+ !env.data?.result) {
39
+ return;
40
+ }
41
+ const { runs } = env.data.result;
42
+ if (!Array.isArray(runs)) {
43
+ throw new Error("invalid_dispatch_runs_result_shape");
44
+ }
45
+ console.log("\n📋 Dispatch Runs");
46
+ console.log("─".repeat(125));
47
+ const header = [
48
+ pad("RUN", 15),
49
+ pad("STATUS", 18),
50
+ pad("CONCLUSION", 15),
51
+ pad("COMMITTER", 38),
52
+ pad("AGE", 12),
53
+ pad("DURATION", 12),
54
+ "ATTEMPT",
55
+ ].join(" ");
56
+ console.log(header);
57
+ console.log("─".repeat(125));
58
+ for (const run of runs) {
59
+ let status;
60
+ let conclusion;
61
+ if (run.status === "completed") {
62
+ status = "completed";
63
+ switch (run.conclusion) {
64
+ case "success":
65
+ conclusion = "✅ success";
66
+ break;
67
+ case "failure":
68
+ conclusion = "❌ failure";
69
+ break;
70
+ case "cancelled":
71
+ conclusion = "⚪ cancelled";
72
+ break;
73
+ case "timed_out":
74
+ conclusion = "⏱ timed_out";
75
+ break;
76
+ case "skipped":
77
+ conclusion = "⏭ skipped";
78
+ break;
79
+ default:
80
+ conclusion = run.conclusion ?? "-";
81
+ }
82
+ }
83
+ else if (run.status === "in_progress") {
84
+ status = "🟡 in_progress";
85
+ conclusion = "-";
86
+ }
87
+ else {
88
+ status = "⏳ queued";
89
+ conclusion = "-";
90
+ }
91
+ const committer = run.committer
92
+ ? `${run.committer.name} <${run.committer.email}>`
93
+ : "unknown";
94
+ const age = formatAge(run.createdAt);
95
+ const duration = run.status === "completed"
96
+ ? formatDuration(run.createdAt, run.updatedAt)
97
+ : "-";
98
+ const row = [
99
+ pad(String(run.id), 15),
100
+ pad(status, 18),
101
+ pad(conclusion, 14),
102
+ pad(committer, 38),
103
+ pad(age, 12),
104
+ pad(duration, 12),
105
+ `#${run.attempt}`,
106
+ ].join(" ");
107
+ console.log(row);
108
+ }
109
+ console.log("─".repeat(125));
110
+ console.log();
111
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -26,6 +26,24 @@ export function renderLog(zipBuffer, options) {
26
26
  }
27
27
  // Print detailed workflow log
28
28
  parseAndPrintDeployLog(zipBuffer);
29
+ // Attempt to detect dispatcher from log content
30
+ let detectedDispatcher;
31
+ try {
32
+ const zip = new AdmZip(zipBuffer);
33
+ for (const entry of zip.getEntries()) {
34
+ if (entry.isDirectory)
35
+ continue;
36
+ const text = entry.getData().toString("utf8");
37
+ const m = text.match(/Dispatch initiated by actor:\s*([a-zA-Z0-9_-]+)/);
38
+ if (m) {
39
+ detectedDispatcher = m[1];
40
+ break;
41
+ }
42
+ }
43
+ }
44
+ catch {
45
+ // ignore parsing errors
46
+ }
29
47
  // Print final status banner
30
48
  const status = options.status ?? "unknown";
31
49
  let color = chalk.gray;
@@ -36,6 +54,12 @@ export function renderLog(zipBuffer, options) {
36
54
  else if (status === "cancelled")
37
55
  color = chalk.yellowBright;
38
56
  console.log(color(`\n────── DEPLOY STATUS: ${String(status).toUpperCase()} ─────────────────────────────────────────────────────────────────────────────────────────────\n`));
57
+ // Warn if the run was dispatched by someone else
58
+ if (detectedDispatcher &&
59
+ options.currentUser &&
60
+ detectedDispatcher !== options.currentUser) {
61
+ console.log(chalk.yellowBright(`⚠️ This run was dispatched by '${detectedDispatcher}', not by you (${options.currentUser}).`));
62
+ }
39
63
  }
40
64
  const RUNNER_TIMESTAMP_REGEX = /^\uFEFF?\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d+Z\s*/;
41
65
  const MARKERS_TO_REMOVE = [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "viza",
3
- "version": "1.7.45",
3
+ "version": "1.7.47",
4
4
  "type": "module",
5
5
  "description": "Viza unified command line interface",
6
6
  "bin": {
@@ -17,7 +17,7 @@
17
17
  "release:full": "rm -rf dist && npx npm-check-updates -u && npm install && git add package.json package-lock.json && git commit -m 'chore(deps): auto update dependencies before release' || echo 'No changes' && node versioning.js && npm login && npm publish --tag latest --access public && git push"
18
18
  },
19
19
  "dependencies": {
20
- "@vizamodo/viza-dispatcher": "^1.5.25",
20
+ "@vizamodo/viza-dispatcher": "^1.5.27",
21
21
  "adm-zip": "^0.5.16",
22
22
  "chalk": "^5.6.2",
23
23
  "clipboardy": "^5.3.1",