Something went wrong. Try again.
Monorepo for Aesthetic.Computer aesthetic.computer
Something went wrong. Try again.
JavaScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137#!/usr/bin/env node// toolchain/doctor.mjs — one preflight that pings every moving part of AC.//// Tells you *which* layer is sick before you waste time debugging the wrong// one: local dev servers, production hosts, the asset CDN, and the host tools// the pipelines lean on. Dependency-free (Node built-ins + global fetch).//// npm run doctor full sweep// npm run doctor -- --local only local dev servers + host tooling// npm run doctor -- --prod only production reachability// npm run doctor -- --strict exit non-zero if any CRITICAL check fails//// Checks degrade gracefully: a stopped dev server is a warning, not a failure.// Only checks marked `critical` (prod site + CDN) can fail --strict.
import net from "node:net";import { execFile } from "node:child_process";
const args = new Set(process.argv.slice(2));const ONLY_LOCAL = args.has("--local");const ONLY_PROD = args.has("--prod");const STRICT = args.has("--strict");
// ── probes ──────────────────────────────────────────────────────────────────
// Is a TCP port accepting connections? (the truest "is it up" for local servers)function tcp(host, port, timeout = 1500) { return new Promise((resolve) => { const t0 = Date.now(); const sock = new net.Socket(); const done = (ok, note) => { sock.destroy(); resolve({ ok, ms: Date.now() - t0, note }); }; sock.setTimeout(timeout); sock.once("connect", () => done(true)); sock.once("timeout", () => done(false, "timeout")); sock.once("error", (e) => done(false, e.code || "error")); sock.connect(port, host); });}
// HTTP(S) reachability — reports the status code and round-trip latency.async function http(url, { method = "HEAD", timeout = 6000, expect } = {}) { const t0 = Date.now(); const ctrl = new AbortController(); const timer = setTimeout(() => ctrl.abort(), timeout); try { let res = await fetch(url, { method, signal: ctrl.signal, redirect: "manual" }); // Some hosts reject HEAD — retry once with GET before giving up. if (method === "HEAD" && (res.status === 405 || res.status === 501)) { res = await fetch(url, { method: "GET", signal: ctrl.signal, redirect: "manual" }); } const ms = Date.now() - t0; const reachable = res.status > 0 && res.status < 500; const ok = expect ? res.status === expect : reachable; return { ok, ms, note: `HTTP ${res.status}` }; } catch (e) { return { ok: false, ms: Date.now() - t0, note: e.name === "AbortError" ? "timeout" : (e.cause?.code || e.code || "unreachable") }; } finally { clearTimeout(timer); }}
// Is a command-line tool on PATH? (host tooling the pipelines shell out to)function bin(name) { return new Promise((resolve) => { execFile("command", ["-v", name], { shell: "/bin/sh" }, (err, out) => { resolve({ ok: !err && !!out.trim(), note: err ? "not on PATH" : out.trim() }); }); });}
// ── the checklist ────────────────────────────────────────────────────────────// group · label · run() → {ok, ms?, note?} · critical? · scope (local|prod|tool)
const CHECKS = [ // Local dev servers — advisory: down just means you haven't started them. { group: "Local dev", label: "site (8888)", scope: "local", run: () => tcp("127.0.0.1", 8888) }, { group: "Local dev", label: "session (8889)", scope: "local", run: () => tcp("127.0.0.1", 8889) }, { group: "Local dev", label: "redis (6379)", scope: "local", run: () => tcp("127.0.0.1", 6379) },
// Production reachability — these are the real signal. { group: "Production", label: "aesthetic.computer (lith)", scope: "prod", critical: true, run: () => http("https://aesthetic.computer") }, { group: "Production", label: "assets CDN (DO Spaces)", scope: "prod", critical: true, run: () => http("https://assets.aesthetic.computer") }, { group: "Production", label: "oven (OTA builds)", scope: "prod", run: () => http("https://oven.aesthetic.computer") }, { group: "Production", label: "ai.aesthetic.computer", scope: "prod", run: () => http("https://ai.aesthetic.computer") }, { group: "Production", label: "help (aa bridge)", scope: "prod", run: () => http("https://help.aesthetic.computer") },
// Host tooling — the binaries pipelines shell out to. { group: "Host tooling", label: "node", scope: "tool", run: () => bin("node") }, { group: "Host tooling", label: "redis-server", scope: "tool", run: () => bin("redis-server") }, { group: "Host tooling", label: "ffmpeg", scope: "tool", run: () => bin("ffmpeg") }, { group: "Host tooling", label: "doctl (CDN flush)", scope: "tool", run: () => bin("doctl") }, { group: "Host tooling", label: "gh", scope: "tool", run: () => bin("gh") }, { group: "Host tooling", label: "jq", scope: "tool", run: () => bin("jq") },];
// ── run ──────────────────────────────────────────────────────────────────────
const scopeWanted = (s) => (!ONLY_LOCAL && !ONLY_PROD) || (ONLY_LOCAL && (s === "local" || s === "tool")) || (ONLY_PROD && s === "prod");
const checks = CHECKS.filter((c) => scopeWanted(c.scope));
console.log("\n🩺 aesthetic.computer doctor\n");
const results = await Promise.all( checks.map(async (c) => ({ ...c, ...(await c.run()) })),);
let group = null;let criticalFailed = false;for (const r of results) { if (r.group !== group) { group = r.group; console.log(` ${group}`); } // Down-but-non-critical (e.g. a dev server you didn't start) reads as ⚠️. const icon = r.ok ? "✅" : r.critical ? "❌" : "⚠️ "; if (!r.ok && r.critical) criticalFailed = true; const ms = r.ms != null ? ` ${String(r.ms).padStart(4)}ms` : ""; const note = r.note ? ` ${r.note}` : ""; console.log(` ${icon} ${r.label.padEnd(28)}${ms}${note}`);}
const down = results.filter((r) => !r.ok);console.log( `\n ${results.length - down.length}/${results.length} healthy` + (down.length ? ` · ${down.length} need attention` : " · all green") + "\n",);
if (STRICT && criticalFailed) process.exit(1);