feat(mosaic): unified first-run UX wizard -> gateway install -> verify (#418)
This commit was merged in pull request #418.
This commit is contained in:
117
packages/mosaic/src/commands/gateway/verify.ts
Normal file
117
packages/mosaic/src/commands/gateway/verify.ts
Normal file
@@ -0,0 +1,117 @@
|
||||
import { readMeta } from './daemon.js';
|
||||
|
||||
// ANSI colour helpers (gracefully degrade when not a TTY)
|
||||
const isTTY = Boolean(process.stdout.isTTY);
|
||||
const G = isTTY ? '\x1b[0;32m' : '';
|
||||
const R = isTTY ? '\x1b[0;31m' : '';
|
||||
const BOLD = isTTY ? '\x1b[1m' : '';
|
||||
const RESET = isTTY ? '\x1b[0m' : '';
|
||||
|
||||
function ok(label: string): void {
|
||||
process.stdout.write(` ${G}✔${RESET} ${label.padEnd(36)}${G}[ok]${RESET}\n`);
|
||||
}
|
||||
|
||||
function fail(label: string, hint: string): void {
|
||||
process.stdout.write(` ${R}✖${RESET} ${label.padEnd(36)}${R}[FAIL]${RESET}\n`);
|
||||
process.stdout.write(` ${R}↳ ${hint}${RESET}\n`);
|
||||
}
|
||||
|
||||
function sleep(ms: number): Promise<void> {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
async function fetchWithRetry(
|
||||
url: string,
|
||||
opts: RequestInit = {},
|
||||
retries = 3,
|
||||
delayMs = 1000,
|
||||
): Promise<Response | null> {
|
||||
for (let attempt = 0; attempt < retries; attempt++) {
|
||||
try {
|
||||
const res = await fetch(url, opts);
|
||||
// Retry on non-OK responses too — the gateway may still be starting up
|
||||
// (e.g. 503 before the app bootstrap completes).
|
||||
if (res.ok) return res;
|
||||
} catch {
|
||||
// Network-level error — not ready yet, will retry
|
||||
}
|
||||
if (attempt < retries - 1) await sleep(delayMs);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export interface VerifyResult {
|
||||
gatewayHealthy: boolean;
|
||||
adminTokenOnFile: boolean;
|
||||
bootstrapReachable: boolean;
|
||||
allPassed: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run post-install verification checks.
|
||||
*
|
||||
* @param host - Gateway hostname (e.g. "localhost")
|
||||
* @param port - Gateway port (e.g. 14242)
|
||||
* @returns VerifyResult — callers can inspect individual flags
|
||||
*/
|
||||
export async function runPostInstallVerification(
|
||||
host: string,
|
||||
port: number,
|
||||
): Promise<VerifyResult> {
|
||||
const baseUrl = `http://${host}:${port.toString()}`;
|
||||
|
||||
console.log(`\n${BOLD}Mosaic installation verified:${RESET}`);
|
||||
|
||||
// ─── Check 1: Gateway /health ─────────────────────────────────────────────
|
||||
const healthRes = await fetchWithRetry(`${baseUrl}/health`);
|
||||
const gatewayHealthy = healthRes !== null && healthRes.ok;
|
||||
if (gatewayHealthy) {
|
||||
ok('gateway healthy');
|
||||
} else {
|
||||
fail('gateway healthy', 'Run: mosaic gateway status / mosaic gateway logs');
|
||||
}
|
||||
|
||||
// ─── Check 2: Admin token on file ─────────────────────────────────────────
|
||||
const meta = readMeta();
|
||||
const adminTokenOnFile = Boolean(meta?.adminToken && meta.adminToken.length > 0);
|
||||
if (adminTokenOnFile) {
|
||||
ok('admin token on file');
|
||||
} else {
|
||||
fail('admin token on file', 'Run: mosaic gateway config recover-token');
|
||||
}
|
||||
|
||||
// ─── Check 3: Bootstrap endpoint reachable ────────────────────────────────
|
||||
const bootstrapRes = await fetchWithRetry(`${baseUrl}/api/bootstrap/status`);
|
||||
const bootstrapReachable = bootstrapRes !== null && bootstrapRes.ok;
|
||||
if (bootstrapReachable) {
|
||||
ok('bootstrap endpoint reach');
|
||||
} else {
|
||||
fail('bootstrap endpoint reach', 'Run: mosaic gateway status / mosaic gateway logs');
|
||||
}
|
||||
|
||||
const allPassed = gatewayHealthy && adminTokenOnFile && bootstrapReachable;
|
||||
|
||||
if (!allPassed) {
|
||||
console.log(
|
||||
`\n${R}One or more checks failed.${RESET} Recovery commands listed above.\n` +
|
||||
`Use ${BOLD}mosaic gateway status${RESET} and ${BOLD}mosaic gateway config recover-token${RESET} to investigate.\n`,
|
||||
);
|
||||
}
|
||||
|
||||
return { gatewayHealthy, adminTokenOnFile, bootstrapReachable, allPassed };
|
||||
}
|
||||
|
||||
/**
|
||||
* Standalone entry point for `mosaic gateway verify`.
|
||||
* Reads host/port from meta.json if not provided.
|
||||
*/
|
||||
export async function runVerify(opts: { host?: string; port?: number }): Promise<void> {
|
||||
const meta = readMeta();
|
||||
const host = opts.host ?? meta?.host ?? 'localhost';
|
||||
const port = opts.port ?? meta?.port ?? 14242;
|
||||
|
||||
const result = await runPostInstallVerification(host, port);
|
||||
if (!result.allPassed) {
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user