clone

a deno framework for local-first web apps that open instantly, work offline and sync when users choose

fix/175-template-quality-pass
lofi / package / tooling / diagnostics.ts
diagnostics.ts 4.2K
1import { LOFI_VERSION } from "../version.ts";2import { type EnvironmentValidation, loadEnvironment, validateEnvironment } from "./environment.ts";3import { type ProjectCheck, projectChecks } from "./project.ts";45export type Diagnostic = {6 name: string;7 status: "ok" | "pending" | "blocker";8 detail: string;9 remediation?: string;10};1112export type DoctorReport = {13 version: string;14 validation: EnvironmentValidation;15 diagnostics: Diagnostic[];16 blocked: boolean;17};1819function denoSupported(version: string): boolean {20 const [major = 0, minor = 0] = version.split(".").map(Number);21 return major > 2 || (major === 2 && minor >= 9);22}2324function projectDiagnostics(checks: readonly ProjectCheck[]): Diagnostic[] {25 return checks.map((check) => ({26 name: check.name,27 status: check.status,28 detail: check.detail,29 remediation: check.remediation,30 }));31}3233export async function doctorReport(34 options: {35 root?: string;36 environment?: Readonly<Record<string, string>>;37 denoVersion?: string;38 } = {},39): Promise<DoctorReport> {40 const root = options.root ?? Deno.cwd();41 const environment = options.environment ?? await loadEnvironment(`${root}/.env`);42 const validation = validateEnvironment(environment);43 const denoVersion = options.denoVersion ?? Deno.version.deno;44 const diagnostics: Diagnostic[] = [45 {46 name: "Package",47 status: "ok",48 detail: `@nzip/lofi ${LOFI_VERSION}`,49 },50 denoSupported(denoVersion) ? { name: "Deno", status: "ok", detail: denoVersion } : {51 name: "Deno",52 status: "blocker",53 detail: `${denoVersion} is older than the supported 2.9 line`,54 remediation: "upgrade Deno, then rerun `deno task doctor`",55 },56 ...projectDiagnostics(await projectChecks(root, validation.basePath)),57 ];5859 if (validation.ok) {60 diagnostics.push({61 name: "Environment",62 status: "ok",63 detail: validation.mode,64 });65 } else {66 for (const error of validation.errors) {67 diagnostics.push({68 name: "Environment",69 status: "blocker",70 detail: error,71 remediation: "edit `.env` once, then rerun `deno task doctor`",72 });73 }74 }75 diagnostics.push(76 {77 name: "Storage",78 status: "pending",79 detail: "OPFS durable driver requested; browser capability check runs at application boot",80 remediation: "open the printed local or stable HTTPS URL in a supported browser",81 },82 {83 name: "Persistence",84 status: "pending",85 detail: "browser eviction protection has not been queried",86 remediation: "use the in-app device gate to request and verify persistence",87 },88 {89 name: "Identity",90 status: "ok",91 detail: validation.mode === "cloud-configured"92 ? "local-first account; users can back up and recover via a recovery phrase"93 : "local-first account; local-only until a managed Jazz app is configured",94 },95 {96 name: "Sync",97 status: validation.mode === "cloud-configured" ? "ok" : "pending",98 detail: validation.mode === "cloud-configured"99 ? "managed adapter configured; users elect to back up and sync per account"100 : "local-only; no transport convergence claim",101 },102 {103 name: "PWA",104 status: "pending",105 detail:106 `deployment base ${validation.basePath}; development service worker disabled; production activation checked in browser`,107 },108 {109 name: "Device URL",110 status: "pending",111 detail: "not configured; stable HTTPS device preview graduates in M3",112 },113 );114115 return {116 version: LOFI_VERSION,117 validation,118 diagnostics,119 blocked: diagnostics.some((diagnostic) => diagnostic.status === "blocker"),120 };121}122123export function printDoctorReport(report: DoctorReport): void {124 console.log(`lofi doctor ${report.version}`);125 const width = Math.max(...report.diagnostics.map((diagnostic) => diagnostic.name.length));126 for (const diagnostic of report.diagnostics) {127 console.log(128 `${diagnostic.name.padEnd(width)} ${diagnostic.status.padEnd(7)} ${diagnostic.detail}`,129 );130 if (diagnostic.remediation) {131 console.log(`${"".padEnd(width)} action ${diagnostic.remediation}`);132 }133 }134 for (const warning of report.validation.warnings) console.warn(`warning: ${warning}`);135}