@vizamodo/viza-cli 1.2.6
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 +9 -0
- package/dist/bin/viza.js +35 -0
- package/dist/src/commands/admin/index.js +5 -0
- package/dist/src/commands/billing/index.js +5 -0
- package/dist/src/commands/bootstrap/index.js +5 -0
- package/dist/src/commands/design/index.js +5 -0
- package/dist/src/commands/dev/index.js +12 -0
- package/dist/src/commands/dev/login/aws/index.js +26 -0
- package/dist/src/commands/dev/login/index.js +8 -0
- package/dist/src/commands/manage/index.js +5 -0
- package/dist/src/commands/publish/index.js +5 -0
- package/dist/src/commands/super/index.js +5 -0
- package/dist/src/core/commandDescriptor.js +1 -0
- package/dist/src/core/dispatch.js +65 -0
- package/dist/src/core/renderHint.js +9 -0
- package/dist/src/core/version.js +53 -0
- package/dist/src/errors/handleError.js +6 -0
- package/dist/src/index.js +2 -0
- package/dist/src/platforms/aws/bootstrap.js +1 -0
- package/dist/src/platforms/aws/deploy.js +1 -0
- package/dist/src/platforms/aws/login.js +29 -0
- package/dist/src/platforms/aws/secret.js +1 -0
- package/dist/src/platforms/cloudflare/bootstrap.js +1 -0
- package/dist/src/platforms/cloudflare/deploy.js +1 -0
- package/dist/src/platforms/cloudflare/login.js +1 -0
- package/dist/src/platforms/registry.js +9 -0
- package/dist/src/policy/confirmDangerous.js +3 -0
- package/dist/src/policy/requireEnv.js +3 -0
- package/dist/src/policy/requireRole.js +3 -0
- package/dist/src/ui/banner.js +99 -0
- package/dist/src/ui/index.js +22 -0
- package/dist/src/ui/logRenderer.js +174 -0
- package/dist/src/ui/spinner.js +22 -0
- package/dist/src/ui/theme.js +1 -0
- package/dist/src/utils/fs.js +3 -0
- package/package.json +32 -0
package/README.md
ADDED
package/dist/bin/viza.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import { handleError } from "../src/errors/handleError.js";
|
|
4
|
+
import { bootstrapCommand } from "../src/commands/bootstrap/index.js";
|
|
5
|
+
import { devCommand } from "../src/commands/dev/index.js";
|
|
6
|
+
import fs from "node:fs";
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
import { fileURLToPath } from "node:url";
|
|
9
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
+
const __dirname = path.dirname(__filename);
|
|
11
|
+
function readCliVersion() {
|
|
12
|
+
try {
|
|
13
|
+
const pkgPath = path.resolve(__dirname, "../package.json");
|
|
14
|
+
const raw = fs.readFileSync(pkgPath, "utf8");
|
|
15
|
+
const json = JSON.parse(raw);
|
|
16
|
+
return typeof json.version === "string" && json.version.trim() ? json.version.trim() : "dev";
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return "dev";
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
const CLI_VERSION = readCliVersion();
|
|
23
|
+
const program = new Command();
|
|
24
|
+
program
|
|
25
|
+
.name("viza")
|
|
26
|
+
.description("Viza CLI")
|
|
27
|
+
.version(CLI_VERSION);
|
|
28
|
+
program.addCommand(devCommand());
|
|
29
|
+
program
|
|
30
|
+
.command("bootstrap")
|
|
31
|
+
.description("Bootstrap Viza configuration")
|
|
32
|
+
.action(async () => {
|
|
33
|
+
await bootstrapCommand();
|
|
34
|
+
});
|
|
35
|
+
program.parseAsync(process.argv).catch(handleError);
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// src/commands/dev/index.ts
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import { devLoginCommand } from "./login/index.js";
|
|
4
|
+
const DEV_CTX = {
|
|
5
|
+
targetTeam: "viza-deployer",
|
|
6
|
+
};
|
|
7
|
+
export function devCommand(ctx = DEV_CTX) {
|
|
8
|
+
const cmd = new Command("dev");
|
|
9
|
+
cmd.description("Development commands");
|
|
10
|
+
cmd.addCommand(devLoginCommand(ctx));
|
|
11
|
+
return cmd;
|
|
12
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// src/commands/dev/aws/index.ts
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import { awsLogin, awsCreateSsoUrl } from "../../../../platforms/aws/login.js";
|
|
4
|
+
export function devLoginAwsCommand(ctx) {
|
|
5
|
+
const aws = new Command("aws");
|
|
6
|
+
aws
|
|
7
|
+
.description("Login AWS via remote dispatcher (SSO/session bootstrap)")
|
|
8
|
+
.option("--status", "Check status only (do not dispatch new run)")
|
|
9
|
+
.action(async (opts) => {
|
|
10
|
+
await awsLogin({
|
|
11
|
+
targetTeam: ctx.targetTeam,
|
|
12
|
+
mode: opts.status ? "status" : "dispatch",
|
|
13
|
+
});
|
|
14
|
+
});
|
|
15
|
+
aws
|
|
16
|
+
.command("sso-url")
|
|
17
|
+
.description("Create AWS SSO login URL via remote dispatcher")
|
|
18
|
+
.option("--status", "Check status only (do not dispatch new run)")
|
|
19
|
+
.action(async (opts) => {
|
|
20
|
+
await awsCreateSsoUrl({
|
|
21
|
+
targetTeam: ctx.targetTeam,
|
|
22
|
+
mode: opts.status ? "status" : "dispatch",
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
return aws;
|
|
26
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { devLoginAwsCommand } from "./aws/index.js";
|
|
3
|
+
export function devLoginCommand(ctx) {
|
|
4
|
+
const cmd = new Command("login");
|
|
5
|
+
cmd.description("Login commands");
|
|
6
|
+
cmd.addCommand(devLoginAwsCommand(ctx));
|
|
7
|
+
return cmd;
|
|
8
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { dispatcherDispatch, } from "@vizamodo/viza-dispatcher";
|
|
2
|
+
import { startSpinner, stopSpinner } from "../ui/spinner.js";
|
|
3
|
+
import { renderLog } from "../ui/logRenderer.js";
|
|
4
|
+
import { showDispatchBanner } from "../ui/banner.js";
|
|
5
|
+
import { getCliVersion } from "./version.js";
|
|
6
|
+
/**
|
|
7
|
+
* KISS log rendering.
|
|
8
|
+
* - success + hide => no log
|
|
9
|
+
* - otherwise => render log (if present)
|
|
10
|
+
*/
|
|
11
|
+
function maybeRenderLog(result, policy) {
|
|
12
|
+
// KISS: only skip logs when success + hide.
|
|
13
|
+
if (result.status === "success" && policy === "hide") {
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
if (result.logBuffer) {
|
|
17
|
+
renderLog(result.logBuffer, { status: result.status });
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
async function dispatchIntent(input, mode = "dispatch") {
|
|
21
|
+
const dispatchInput = {
|
|
22
|
+
intent: input.intent,
|
|
23
|
+
eventType: input.eventType,
|
|
24
|
+
payload: input.payload,
|
|
25
|
+
runnerLabel: input.runnerLabel,
|
|
26
|
+
};
|
|
27
|
+
const handle = await dispatcherDispatch(dispatchInput, {
|
|
28
|
+
auth: {
|
|
29
|
+
targetTeam: input.targetTeam,
|
|
30
|
+
},
|
|
31
|
+
}, mode);
|
|
32
|
+
return handle;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Dispatch and wait for completion.
|
|
36
|
+
*
|
|
37
|
+
* This is the recommended "one door" for most viza-cli commands
|
|
38
|
+
* so that waiting/log/result handling does not spread across the codebase.
|
|
39
|
+
*/
|
|
40
|
+
export async function dispatchIntentAndWait(input, opts = {}) {
|
|
41
|
+
const policy = opts.log ?? "hide";
|
|
42
|
+
const mode = opts.mode ?? "dispatch";
|
|
43
|
+
const cliVersion = getCliVersion();
|
|
44
|
+
const meta = {
|
|
45
|
+
cliVersion,
|
|
46
|
+
subtitle: input.eventType,
|
|
47
|
+
clearScreen: true,
|
|
48
|
+
};
|
|
49
|
+
showDispatchBanner(input, meta);
|
|
50
|
+
const handle = await dispatchIntent(input, mode);
|
|
51
|
+
const spinner = startSpinner("Waiting for dispatch session");
|
|
52
|
+
try {
|
|
53
|
+
const result = await handle.wait();
|
|
54
|
+
stopSpinner(spinner, result.status === "success" ? "✅ Done" : "❌ Failed");
|
|
55
|
+
maybeRenderLog(result, policy);
|
|
56
|
+
if (result.status !== "success") {
|
|
57
|
+
throw new Error(`Dispatch failed: ${result.status}`);
|
|
58
|
+
}
|
|
59
|
+
return result;
|
|
60
|
+
}
|
|
61
|
+
catch (err) {
|
|
62
|
+
stopSpinner(spinner, "❌ Failed");
|
|
63
|
+
throw err;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export function renderHint(cmd) {
|
|
2
|
+
console.log("\n" + cmd.name + ": " + cmd.description + "\n");
|
|
3
|
+
if (cmd.children?.length) {
|
|
4
|
+
console.log("Available commands:");
|
|
5
|
+
for (const c of cmd.children) {
|
|
6
|
+
console.log(" • " + c.name + " — " + c.description);
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
let _cached;
|
|
5
|
+
/**
|
|
6
|
+
* Returns the current viza-cli version.
|
|
7
|
+
*
|
|
8
|
+
* - Prefers build-time injected `VIZA_CLI_VERSION`.
|
|
9
|
+
* - Falls back to reading nearest package.json.
|
|
10
|
+
* - Always returns a non-empty string ("dev" as last resort).
|
|
11
|
+
*/
|
|
12
|
+
export function getCliVersion() {
|
|
13
|
+
if (_cached)
|
|
14
|
+
return _cached;
|
|
15
|
+
const injected = process.env.VIZA_CLI_VERSION;
|
|
16
|
+
if (injected && injected.trim()) {
|
|
17
|
+
_cached = injected.trim();
|
|
18
|
+
return _cached;
|
|
19
|
+
}
|
|
20
|
+
try {
|
|
21
|
+
const here = fileURLToPath(import.meta.url);
|
|
22
|
+
const dir = resolve(here, "..");
|
|
23
|
+
const candidates = [
|
|
24
|
+
resolve(dir, "../../package.json"),
|
|
25
|
+
resolve(dir, "../../../package.json"),
|
|
26
|
+
resolve(dir, "../../../../package.json"),
|
|
27
|
+
];
|
|
28
|
+
let raw;
|
|
29
|
+
let usedPath;
|
|
30
|
+
for (const p of candidates) {
|
|
31
|
+
try {
|
|
32
|
+
raw = readFileSync(p, "utf8");
|
|
33
|
+
usedPath = p;
|
|
34
|
+
break;
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
// try next
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
if (!raw || !usedPath) {
|
|
41
|
+
throw new Error(`package.json not found (tried: ${candidates.join(", ")})`);
|
|
42
|
+
}
|
|
43
|
+
const json = JSON.parse(raw);
|
|
44
|
+
if (json.version && String(json.version).trim()) {
|
|
45
|
+
_cached = String(json.version).trim();
|
|
46
|
+
return _cached;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
catch (err) {
|
|
50
|
+
}
|
|
51
|
+
_cached = "dev";
|
|
52
|
+
return _cached;
|
|
53
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// src/platforms/aws/login.ts
|
|
2
|
+
import { dispatchIntentAndWait } from "../../core/dispatch.js";
|
|
3
|
+
export async function awsLogin(ctx) {
|
|
4
|
+
const plainPayload = {
|
|
5
|
+
action: "login",
|
|
6
|
+
provider: "aws",
|
|
7
|
+
type: "sso",
|
|
8
|
+
};
|
|
9
|
+
await dispatchIntentAndWait({
|
|
10
|
+
intent: "hub",
|
|
11
|
+
eventType: "viza.aws.login",
|
|
12
|
+
targetTeam: ctx.targetTeam,
|
|
13
|
+
runnerLabel: "native",
|
|
14
|
+
payload: plainPayload,
|
|
15
|
+
}, { log: "show", mode: ctx.mode ?? "dispatch" });
|
|
16
|
+
}
|
|
17
|
+
export async function awsCreateSsoUrl(ctx) {
|
|
18
|
+
const plainPayload = {
|
|
19
|
+
action: "sso-url",
|
|
20
|
+
provider: "aws",
|
|
21
|
+
};
|
|
22
|
+
await dispatchIntentAndWait({
|
|
23
|
+
intent: "hub",
|
|
24
|
+
eventType: "viza.aws.sso-url",
|
|
25
|
+
targetTeam: ctx.targetTeam,
|
|
26
|
+
runnerLabel: "native",
|
|
27
|
+
payload: plainPayload,
|
|
28
|
+
}, { log: "show", mode: ctx.mode ?? "dispatch" });
|
|
29
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import figlet from "figlet";
|
|
3
|
+
const TEAM_BANNER_CONFIG = {
|
|
4
|
+
"viza-deployer": { title: "Viza Deployer", color: "cyanBright", env: "dev" },
|
|
5
|
+
"viza-designer": { title: "Viza Designer", color: "greenBright", env: "dev" },
|
|
6
|
+
"viza-billing": { title: "Viza Billing", color: "yellowBright", env: "dev" },
|
|
7
|
+
"viza-publisher": { title: "Viza Publisher", color: "redBright", env: "prod" },
|
|
8
|
+
"viza-manager": { title: "Viza Manager", color: "cyanBright", env: "dev" },
|
|
9
|
+
"viza-admin": { title: "Viza Administrator", color: "yellowBright", env: "sys" },
|
|
10
|
+
"viza-super": { title: "Viza Supervisor", color: "redBright", env: "sys" },
|
|
11
|
+
};
|
|
12
|
+
function formatEnvLabelForTeam(team) {
|
|
13
|
+
// Keep env values constrained to BannerOptions, while giving a clear mapping.
|
|
14
|
+
// dev: Development/Designer/Billing/Manager
|
|
15
|
+
// prod: Production
|
|
16
|
+
// sys: System/Root
|
|
17
|
+
if (team === "viza-publisher")
|
|
18
|
+
return "prod";
|
|
19
|
+
if (team === "viza-admin" || team === "viza-super")
|
|
20
|
+
return "sys";
|
|
21
|
+
return "dev";
|
|
22
|
+
}
|
|
23
|
+
function pickBannerConfig(targetTeam) {
|
|
24
|
+
const cfg = TEAM_BANNER_CONFIG[targetTeam];
|
|
25
|
+
if (cfg)
|
|
26
|
+
return cfg;
|
|
27
|
+
// Default: derive something reasonable and safe.
|
|
28
|
+
const prettyTitle = targetTeam
|
|
29
|
+
.split("-")
|
|
30
|
+
.map((x) => (x ? x[0].toUpperCase() + x.slice(1) : x))
|
|
31
|
+
.join(" ");
|
|
32
|
+
return {
|
|
33
|
+
title: prettyTitle || "Viza",
|
|
34
|
+
color: "cyanBright",
|
|
35
|
+
env: formatEnvLabelForTeam(targetTeam) ?? "dev",
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Show a banner right before calling dispatchIntent.
|
|
40
|
+
* Maps targetTeam -> title/color/env, and renders meta (github login + viza-cli version).
|
|
41
|
+
*/
|
|
42
|
+
export function showDispatchBanner(input, meta) {
|
|
43
|
+
const cfg = pickBannerConfig(input.targetTeam);
|
|
44
|
+
// Default subtitle: event type (or caller-provided subtitle)
|
|
45
|
+
const subtitle = meta?.subtitle ?? `Command: ${input.eventType}`;
|
|
46
|
+
showBanner({
|
|
47
|
+
title: cfg.title,
|
|
48
|
+
subtitle,
|
|
49
|
+
color: cfg.color,
|
|
50
|
+
env: cfg.env,
|
|
51
|
+
runner: input.runnerLabel === "native"
|
|
52
|
+
? { type: "github" }
|
|
53
|
+
: {
|
|
54
|
+
type: "self-hosted",
|
|
55
|
+
label: input.runnerLabel,
|
|
56
|
+
},
|
|
57
|
+
meta: {
|
|
58
|
+
version: meta?.cliVersion,
|
|
59
|
+
},
|
|
60
|
+
clearScreen: meta?.clearScreen ?? true,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
export function showBanner(opts) {
|
|
64
|
+
const { title, subtitle, color = "cyanBright", env, runner, meta, clearScreen = true, } = opts;
|
|
65
|
+
if (clearScreen) {
|
|
66
|
+
process.stdout.write("\x1Bc");
|
|
67
|
+
}
|
|
68
|
+
const bannerText = figlet.textSync(title, { font: "Standard" });
|
|
69
|
+
console.log(chalk[color](bannerText));
|
|
70
|
+
if (subtitle) {
|
|
71
|
+
console.log(chalk.gray("Command:"), chalk.magenta(subtitle));
|
|
72
|
+
}
|
|
73
|
+
// Environment line removed; replaced by user-friendly info line below
|
|
74
|
+
if (runner) {
|
|
75
|
+
if (runner.type === "github") {
|
|
76
|
+
console.log(chalk.gray("Runner:"), chalk.yellow("GitHub-hosted"));
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
console.log(chalk.gray("Runner:"), chalk.yellow("self-hosted"), runner.label ? chalk.gray("| label:") : "", runner.label ? chalk.cyan(runner.label) : "");
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
// User-friendly info line
|
|
83
|
+
{
|
|
84
|
+
const parts = [];
|
|
85
|
+
if (meta?.version) {
|
|
86
|
+
parts.push(`Version: ${chalk.cyan(meta.version)}`);
|
|
87
|
+
}
|
|
88
|
+
if (env) {
|
|
89
|
+
const envColor = env === "prod" ? chalk.redBright :
|
|
90
|
+
env === "dev" ? chalk.greenBright :
|
|
91
|
+
chalk.yellowBright;
|
|
92
|
+
parts.push(`Environment: ${envColor(env.toUpperCase())}`);
|
|
93
|
+
}
|
|
94
|
+
if (parts.length) {
|
|
95
|
+
console.log(chalk.gray(parts.join(chalk.gray(" | "))));
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
console.log(chalk.gray("────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────"));
|
|
99
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// public exports (optional)
|
|
2
|
+
import { showBanner } from "./banner.js";
|
|
3
|
+
import { startSpinner, stopSpinner } from "./spinner.js";
|
|
4
|
+
import { renderLog } from "./logRenderer.js";
|
|
5
|
+
export function beginUi(opts) {
|
|
6
|
+
if (opts.banner) {
|
|
7
|
+
showBanner(opts.banner);
|
|
8
|
+
}
|
|
9
|
+
let spinner;
|
|
10
|
+
if (opts.spinnerMessage) {
|
|
11
|
+
spinner = startSpinner(opts.spinnerMessage);
|
|
12
|
+
}
|
|
13
|
+
return { spinner };
|
|
14
|
+
}
|
|
15
|
+
export function endUi(session, opts) {
|
|
16
|
+
if (session.spinner) {
|
|
17
|
+
stopSpinner(session.spinner, opts?.finalMessage);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export function renderArtifactLog(buffer, status) {
|
|
21
|
+
renderLog(buffer, { status });
|
|
22
|
+
}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import AdmZip from "adm-zip";
|
|
3
|
+
/**
|
|
4
|
+
* Render deployment log artifact.
|
|
5
|
+
*
|
|
6
|
+
* This function:
|
|
7
|
+
* - Unzips workflow log artifact
|
|
8
|
+
* - Delegates step-level parsing to parseDeployLog
|
|
9
|
+
* - Prints a final status banner
|
|
10
|
+
*
|
|
11
|
+
* It does NOT:
|
|
12
|
+
* - fetch logs
|
|
13
|
+
* - manage spinner
|
|
14
|
+
* - exit process
|
|
15
|
+
*/
|
|
16
|
+
export function renderLog(zipBuffer, options) {
|
|
17
|
+
if (!Buffer.isBuffer(zipBuffer)) {
|
|
18
|
+
throw new Error("Invalid log artifact: expected Buffer");
|
|
19
|
+
}
|
|
20
|
+
// Validate ZIP structure early
|
|
21
|
+
try {
|
|
22
|
+
new AdmZip(zipBuffer);
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
throw new Error("Invalid log artifact: not a ZIP file");
|
|
26
|
+
}
|
|
27
|
+
// Print detailed workflow log
|
|
28
|
+
parseAndPrintDeployLog(zipBuffer);
|
|
29
|
+
// Print final status banner
|
|
30
|
+
const status = options.status ?? "unknown";
|
|
31
|
+
let color = chalk.gray;
|
|
32
|
+
if (status === "success")
|
|
33
|
+
color = chalk.greenBright;
|
|
34
|
+
else if (status === "failure")
|
|
35
|
+
color = chalk.redBright;
|
|
36
|
+
else if (status === "cancelled")
|
|
37
|
+
color = chalk.yellowBright;
|
|
38
|
+
console.log(color(`\n────── DEPLOY STATUS: ${String(status).toUpperCase()} ─────────────────────────────────────────────────────────────────────────────────────────────\n`));
|
|
39
|
+
}
|
|
40
|
+
const RUNNER_TIMESTAMP_REGEX = /^\uFEFF?\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d+Z\s*/;
|
|
41
|
+
const MARKERS_TO_REMOVE = [
|
|
42
|
+
{ type: "startsWith", value: "0_" },
|
|
43
|
+
{ type: "endsWith", value: "system.txt" },
|
|
44
|
+
{ type: "endsWith", value: "_Complete job.txt" },
|
|
45
|
+
{ type: "includes", value: "_📤 Dispatch log collector" },
|
|
46
|
+
{ type: "includes", value: "_Post " },
|
|
47
|
+
];
|
|
48
|
+
export function parseAndPrintDeployLog(zipBuffer) {
|
|
49
|
+
const zip = new AdmZip(zipBuffer);
|
|
50
|
+
const entries = zip.getEntries();
|
|
51
|
+
// Sort entries like GitHub steps
|
|
52
|
+
const sorted = entries
|
|
53
|
+
.filter(e => !e.isDirectory)
|
|
54
|
+
.sort((a, b) => a.entryName.localeCompare(b.entryName, undefined, { numeric: true }));
|
|
55
|
+
// Filter unwanted files
|
|
56
|
+
const filtered = sorted.filter(e => {
|
|
57
|
+
const n = e.entryName;
|
|
58
|
+
return !MARKERS_TO_REMOVE.some(rule => {
|
|
59
|
+
if (rule.type === "startsWith")
|
|
60
|
+
return n.startsWith(rule.value);
|
|
61
|
+
if (rule.type === "endsWith")
|
|
62
|
+
return n.endsWith(rule.value);
|
|
63
|
+
if (rule.type === "includes")
|
|
64
|
+
return n.includes(rule.value);
|
|
65
|
+
return false;
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
const GROUP_START = /^##\[group\]\s*(.*)/;
|
|
69
|
+
const GROUP_END = /^##\[endgroup\]/;
|
|
70
|
+
// Group entries by job (top-level folder)
|
|
71
|
+
const jobs = new Map();
|
|
72
|
+
for (const e of filtered) {
|
|
73
|
+
const parts = e.entryName.split("/");
|
|
74
|
+
const jobName = parts.length > 1 ? parts[0] : "__root__";
|
|
75
|
+
if (!jobs.has(jobName))
|
|
76
|
+
jobs.set(jobName, []);
|
|
77
|
+
jobs.get(jobName).push(e);
|
|
78
|
+
}
|
|
79
|
+
const sortedJobs = [...jobs.entries()].sort((a, b) => {
|
|
80
|
+
const numA = parseInt(a[0]);
|
|
81
|
+
const numB = parseInt(b[0]);
|
|
82
|
+
return (isNaN(numA) ? 9999 : numA) - (isNaN(numB) ? 9999 : numB);
|
|
83
|
+
});
|
|
84
|
+
for (const [jobName, entriesOfJob] of sortedJobs) {
|
|
85
|
+
let printedJobName;
|
|
86
|
+
if (jobName === "__root__") {
|
|
87
|
+
printedJobName = "Global Steps";
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
// Remove leading numbering like "1. ", "2. "
|
|
91
|
+
const cleaned = jobName.replace(/^[0-9]+\.\s*/, "");
|
|
92
|
+
// Replace dots with arrows for readability
|
|
93
|
+
const beautified = cleaned.replace(/\./g, " → ");
|
|
94
|
+
printedJobName = beautified.trim();
|
|
95
|
+
}
|
|
96
|
+
console.log(chalk.yellow(`\n\n────── ${printedJobName} ───────────────────────────────────────────────────────────────────────────────────────────────────────────\n`));
|
|
97
|
+
for (const entry of entriesOfJob) {
|
|
98
|
+
let stepTitle = entry.entryName.replace(".txt", "");
|
|
99
|
+
// Remove leading directory (job folder)
|
|
100
|
+
if (stepTitle.includes("/")) {
|
|
101
|
+
stepTitle = stepTitle.split("/").pop();
|
|
102
|
+
}
|
|
103
|
+
// Replace first "_" with ". "
|
|
104
|
+
const underscoreIndex = stepTitle.indexOf("_");
|
|
105
|
+
if (underscoreIndex > 0) {
|
|
106
|
+
const num = stepTitle.slice(0, underscoreIndex);
|
|
107
|
+
const title = stepTitle.slice(underscoreIndex + 1);
|
|
108
|
+
stepTitle = `${num}. ${title}`;
|
|
109
|
+
}
|
|
110
|
+
console.log(chalk.cyan(`\n────── ${stepTitle} ───────────────────────────────────────────────────────────────────────────────────────────────────────────`));
|
|
111
|
+
const raw = entry.getData().toString("utf8");
|
|
112
|
+
const lines = raw.split(/\r?\n/);
|
|
113
|
+
let inGroup = false;
|
|
114
|
+
let currentGroupTitle = "";
|
|
115
|
+
let displayIdx = 1;
|
|
116
|
+
for (let line of lines) {
|
|
117
|
+
// Remove timestamps & ANSI codes
|
|
118
|
+
line = line
|
|
119
|
+
.replace(RUNNER_TIMESTAMP_REGEX, "")
|
|
120
|
+
.replace(/\x1b\[[0-9;]*m/g, "");
|
|
121
|
+
if (!line.trim()) {
|
|
122
|
+
const ln = chalk.gray(String(displayIdx++).padStart(3, " "));
|
|
123
|
+
console.log(`${ln} `);
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
const groupStart = line.match(GROUP_START);
|
|
127
|
+
if (groupStart) {
|
|
128
|
+
inGroup = true;
|
|
129
|
+
currentGroupTitle = groupStart[1] || "";
|
|
130
|
+
console.log(chalk.magenta(`\n▼`), chalk.whiteBright(`${currentGroupTitle}`));
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
if (GROUP_END.test(line)) {
|
|
134
|
+
inGroup = false;
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
const ln = chalk.gray(String(displayIdx++).padStart(5, " "));
|
|
138
|
+
if (inGroup) {
|
|
139
|
+
// Inside a group block — indent and highlight commands
|
|
140
|
+
if (/^Run /.test(line)) {
|
|
141
|
+
console.log(`${ln} ${chalk.blue(line)}`);
|
|
142
|
+
}
|
|
143
|
+
else if (/^\[ERROR\]/.test(line)) {
|
|
144
|
+
console.log(`${ln} ${chalk.redBright(line)}`);
|
|
145
|
+
}
|
|
146
|
+
else if (/\[INFO\]/.test(line)) {
|
|
147
|
+
console.log(`${ln} ${chalk.green(line)}`);
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
console.log(`${ln} ${chalk.white(line)}`);
|
|
151
|
+
}
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
// Normal (non-group) line
|
|
155
|
+
if (/##\[error\]/i.test(line)) {
|
|
156
|
+
console.log(`${ln} ${chalk.redBright(line)}`);
|
|
157
|
+
}
|
|
158
|
+
else if (/^Run /.test(line)) {
|
|
159
|
+
console.log(`${ln} ${chalk.blueBright(line)}`);
|
|
160
|
+
}
|
|
161
|
+
else if (/^\[ERROR\]/.test(line)) {
|
|
162
|
+
console.log(`${ln} ${chalk.redBright(line)}`);
|
|
163
|
+
}
|
|
164
|
+
else if (/\[INFO\]/.test(line)) {
|
|
165
|
+
console.log(`${ln} ${chalk.green(line)}`);
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
console.log(`${ln} ${chalk.white(line)}`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return true;
|
|
174
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
export function startSpinner(message, delayX = 500) {
|
|
3
|
+
const spinner = ['|', '/', '-', '\\'];
|
|
4
|
+
const dotStages = ['. ', '.. ', '...'];
|
|
5
|
+
let i = 0;
|
|
6
|
+
let dotIdx = 0;
|
|
7
|
+
const startTime = Date.now();
|
|
8
|
+
return setInterval(() => {
|
|
9
|
+
const elapsed = Math.floor((Date.now() - startTime) / 1000);
|
|
10
|
+
const dots = dotStages[dotIdx % dotStages.length];
|
|
11
|
+
process.stdout.write(`\r${spinner[i % spinner.length]} ${message} [${chalk.magenta(`${elapsed}s`)}] ${dots}`);
|
|
12
|
+
i++;
|
|
13
|
+
dotIdx++;
|
|
14
|
+
}, delayX);
|
|
15
|
+
}
|
|
16
|
+
export function stopSpinner(spinner, finalMessage) {
|
|
17
|
+
clearInterval(spinner);
|
|
18
|
+
process.stdout.write('\r\x1b[2K');
|
|
19
|
+
if (finalMessage) {
|
|
20
|
+
console.log(finalMessage);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const theme = {};
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@vizamodo/viza-cli",
|
|
3
|
+
"version": "1.2.6",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Viza unified command line interface",
|
|
6
|
+
"bin": {
|
|
7
|
+
"viza": "dist/bin/viza.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist"
|
|
11
|
+
],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"build": "tsc",
|
|
14
|
+
"prepublishOnly": "npm run build",
|
|
15
|
+
"dev": "ts-node bin/viza.ts",
|
|
16
|
+
"release:prod": "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"
|
|
17
|
+
},
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"@vizamodo/viza-dispatcher": "^1.4.23",
|
|
20
|
+
"adm-zip": "^0.5.16",
|
|
21
|
+
"chalk": "^5.6.2",
|
|
22
|
+
"commander": "^14.0.2",
|
|
23
|
+
"figlet": "^1.9.4"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@types/adm-zip": "^0.5.7",
|
|
27
|
+
"@types/figlet": "^1.7.0",
|
|
28
|
+
"@types/node": "^25.0.9",
|
|
29
|
+
"ts-node": "^10.9.2",
|
|
30
|
+
"typescript": "^5.9.3"
|
|
31
|
+
}
|
|
32
|
+
}
|