fix(cli): daemon output + health colors
This commit is contained in:
@@ -18,6 +18,29 @@ import {
|
|||||||
} from "./discover.js";
|
} from "./discover.js";
|
||||||
import { addGatewayRunCommand } from "./run.js";
|
import { addGatewayRunCommand } from "./run.js";
|
||||||
|
|
||||||
|
function styleHealthChannelLine(line: string, rich: boolean): string {
|
||||||
|
if (!rich) return line;
|
||||||
|
const colon = line.indexOf(":");
|
||||||
|
if (colon === -1) return line;
|
||||||
|
|
||||||
|
const label = line.slice(0, colon + 1);
|
||||||
|
const detail = line.slice(colon + 1).trimStart();
|
||||||
|
const normalized = detail.toLowerCase();
|
||||||
|
|
||||||
|
const applyPrefix = (prefix: string, color: (value: string) => string) =>
|
||||||
|
`${label} ${color(detail.slice(0, prefix.length))}${detail.slice(prefix.length)}`;
|
||||||
|
|
||||||
|
if (normalized.startsWith("failed")) return applyPrefix("failed", theme.error);
|
||||||
|
if (normalized.startsWith("ok")) return applyPrefix("ok", theme.success);
|
||||||
|
if (normalized.startsWith("linked")) return applyPrefix("linked", theme.success);
|
||||||
|
if (normalized.startsWith("configured")) return applyPrefix("configured", theme.success);
|
||||||
|
if (normalized.startsWith("not linked")) return applyPrefix("not linked", theme.warn);
|
||||||
|
if (normalized.startsWith("not configured")) return applyPrefix("not configured", theme.muted);
|
||||||
|
if (normalized.startsWith("unknown")) return applyPrefix("unknown", theme.warn);
|
||||||
|
|
||||||
|
return line;
|
||||||
|
}
|
||||||
|
|
||||||
export function registerGatewayCli(program: Command) {
|
export function registerGatewayCli(program: Command) {
|
||||||
const gateway = addGatewayRunCommand(
|
const gateway = addGatewayRunCommand(
|
||||||
program
|
program
|
||||||
@@ -84,7 +107,7 @@ export function registerGatewayCli(program: Command) {
|
|||||||
);
|
);
|
||||||
if (obj.channels && typeof obj.channels === "object") {
|
if (obj.channels && typeof obj.channels === "object") {
|
||||||
for (const line of formatHealthChannelLines(obj as HealthSummary)) {
|
for (const line of formatHealthChannelLines(obj as HealthSummary)) {
|
||||||
defaultRuntime.log(line);
|
defaultRuntime.log(styleHealthChannelLine(line, rich));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { beforeEach, describe, expect, it, vi } from "vitest";
|
|||||||
|
|
||||||
import type { HealthSummary } from "./health.js";
|
import type { HealthSummary } from "./health.js";
|
||||||
import { healthCommand } from "./health.js";
|
import { healthCommand } from "./health.js";
|
||||||
|
import { stripAnsi } from "../terminal/ansi.js";
|
||||||
|
|
||||||
const callGatewayMock = vi.fn();
|
const callGatewayMock = vi.fn();
|
||||||
const logWebSelfIdMock = vi.fn();
|
const logWebSelfIdMock = vi.fn();
|
||||||
@@ -70,7 +71,9 @@ describe("healthCommand (coverage)", () => {
|
|||||||
await healthCommand({ json: false, timeoutMs: 1000 }, runtime as never);
|
await healthCommand({ json: false, timeoutMs: 1000 }, runtime as never);
|
||||||
|
|
||||||
expect(runtime.exit).not.toHaveBeenCalled();
|
expect(runtime.exit).not.toHaveBeenCalled();
|
||||||
expect(runtime.log.mock.calls.map((c) => String(c[0])).join("\n")).toMatch(/WhatsApp: linked/i);
|
expect(stripAnsi(runtime.log.mock.calls.map((c) => String(c[0])).join("\n"))).toMatch(
|
||||||
|
/WhatsApp: linked/i,
|
||||||
|
);
|
||||||
expect(logWebSelfIdMock).toHaveBeenCalled();
|
expect(logWebSelfIdMock).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { buildGatewayConnectionDetails, callGateway } from "../gateway/call.js";
|
|||||||
import { info } from "../globals.js";
|
import { info } from "../globals.js";
|
||||||
import { formatErrorMessage } from "../infra/errors.js";
|
import { formatErrorMessage } from "../infra/errors.js";
|
||||||
import type { RuntimeEnv } from "../runtime.js";
|
import type { RuntimeEnv } from "../runtime.js";
|
||||||
|
import { theme } from "../terminal/theme.js";
|
||||||
import { resolveHeartbeatSeconds } from "../web/reconnect.js";
|
import { resolveHeartbeatSeconds } from "../web/reconnect.js";
|
||||||
|
|
||||||
export type ChannelHealthSummary = {
|
export type ChannelHealthSummary = {
|
||||||
@@ -79,6 +80,28 @@ const formatProbeLine = (probe: unknown): string | null => {
|
|||||||
return label;
|
return label;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function styleHealthChannelLine(line: string): string {
|
||||||
|
const colon = line.indexOf(":");
|
||||||
|
if (colon === -1) return line;
|
||||||
|
|
||||||
|
const label = line.slice(0, colon + 1);
|
||||||
|
const detail = line.slice(colon + 1).trimStart();
|
||||||
|
const normalized = detail.toLowerCase();
|
||||||
|
|
||||||
|
const applyPrefix = (prefix: string, color: (value: string) => string) =>
|
||||||
|
`${label} ${color(detail.slice(0, prefix.length))}${detail.slice(prefix.length)}`;
|
||||||
|
|
||||||
|
if (normalized.startsWith("failed")) return applyPrefix("failed", theme.error);
|
||||||
|
if (normalized.startsWith("ok")) return applyPrefix("ok", theme.success);
|
||||||
|
if (normalized.startsWith("linked")) return applyPrefix("linked", theme.success);
|
||||||
|
if (normalized.startsWith("configured")) return applyPrefix("configured", theme.success);
|
||||||
|
if (normalized.startsWith("not linked")) return applyPrefix("not linked", theme.warn);
|
||||||
|
if (normalized.startsWith("not configured")) return applyPrefix("not configured", theme.muted);
|
||||||
|
if (normalized.startsWith("unknown")) return applyPrefix("unknown", theme.warn);
|
||||||
|
|
||||||
|
return line;
|
||||||
|
}
|
||||||
|
|
||||||
export const formatHealthChannelLines = (summary: HealthSummary): string[] => {
|
export const formatHealthChannelLines = (summary: HealthSummary): string[] => {
|
||||||
const channels = summary.channels ?? {};
|
const channels = summary.channels ?? {};
|
||||||
const channelOrder =
|
const channelOrder =
|
||||||
@@ -263,7 +286,7 @@ export async function healthCommand(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (const line of formatHealthChannelLines(summary)) {
|
for (const line of formatHealthChannelLines(summary)) {
|
||||||
runtime.log(line);
|
runtime.log(styleHealthChannelLine(line));
|
||||||
}
|
}
|
||||||
const cfg = loadConfig();
|
const cfg = loadConfig();
|
||||||
for (const plugin of listChannelPlugins()) {
|
for (const plugin of listChannelPlugins()) {
|
||||||
|
|||||||
@@ -415,6 +415,8 @@ export async function installLaunchAgent({
|
|||||||
}
|
}
|
||||||
await execLaunchctl(["kickstart", "-k", `${domain}/${label}`]);
|
await execLaunchctl(["kickstart", "-k", `${domain}/${label}`]);
|
||||||
|
|
||||||
|
// Ensure we don't end up writing to a clack spinner line (wizards show progress without a newline).
|
||||||
|
stdout.write("\n");
|
||||||
stdout.write(`${formatLine("Installed LaunchAgent", plistPath)}\n`);
|
stdout.write(`${formatLine("Installed LaunchAgent", plistPath)}\n`);
|
||||||
stdout.write(`${formatLine("Logs", stdoutPath)}\n`);
|
stdout.write(`${formatLine("Logs", stdoutPath)}\n`);
|
||||||
return { plistPath };
|
return { plistPath };
|
||||||
|
|||||||
@@ -240,6 +240,8 @@ export async function installScheduledTask({
|
|||||||
}
|
}
|
||||||
|
|
||||||
await execSchtasks(["/Run", "/TN", taskName]);
|
await execSchtasks(["/Run", "/TN", taskName]);
|
||||||
|
// Ensure we don't end up writing to a clack spinner line (wizards show progress without a newline).
|
||||||
|
stdout.write("\n");
|
||||||
stdout.write(`${formatLine("Installed Scheduled Task", taskName)}\n`);
|
stdout.write(`${formatLine("Installed Scheduled Task", taskName)}\n`);
|
||||||
stdout.write(`${formatLine("Task script", scriptPath)}\n`);
|
stdout.write(`${formatLine("Task script", scriptPath)}\n`);
|
||||||
return { scriptPath };
|
return { scriptPath };
|
||||||
|
|||||||
@@ -238,6 +238,8 @@ export async function installSystemdService({
|
|||||||
throw new Error(`systemctl restart failed: ${restart.stderr || restart.stdout}`.trim());
|
throw new Error(`systemctl restart failed: ${restart.stderr || restart.stdout}`.trim());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ensure we don't end up writing to a clack spinner line (wizards show progress without a newline).
|
||||||
|
stdout.write("\n");
|
||||||
stdout.write(`${formatLine("Installed systemd service", unitPath)}\n`);
|
stdout.write(`${formatLine("Installed systemd service", unitPath)}\n`);
|
||||||
return { unitPath };
|
return { unitPath };
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user