103 lines
3.4 KiB
TypeScript
103 lines
3.4 KiB
TypeScript
import { existsSync, readFileSync } from "node:fs";
|
|
|
|
export type PersistedDevServerStatus = {
|
|
dirty: boolean;
|
|
lastChangedAt: string | null;
|
|
changedPathCount: number;
|
|
changedPathsSample: string[];
|
|
pendingMigrations: string[];
|
|
lastRestartAt: string | null;
|
|
};
|
|
|
|
export type DevServerHealthStatus = {
|
|
enabled: true;
|
|
restartRequired: boolean;
|
|
reason: "backend_changes" | "pending_migrations" | "backend_changes_and_pending_migrations" | null;
|
|
lastChangedAt: string | null;
|
|
changedPathCount: number;
|
|
changedPathsSample: string[];
|
|
pendingMigrations: string[];
|
|
autoRestartEnabled: boolean;
|
|
activeRunCount: number;
|
|
waitingForIdle: boolean;
|
|
lastRestartAt: string | null;
|
|
};
|
|
|
|
function normalizeStringArray(value: unknown): string[] {
|
|
if (!Array.isArray(value)) return [];
|
|
return value
|
|
.filter((entry): entry is string => typeof entry === "string")
|
|
.map((entry) => entry.trim())
|
|
.filter((entry) => entry.length > 0);
|
|
}
|
|
|
|
function normalizeTimestamp(value: unknown): string | null {
|
|
if (typeof value !== "string") return null;
|
|
const trimmed = value.trim();
|
|
return trimmed.length > 0 ? trimmed : null;
|
|
}
|
|
|
|
export function readPersistedDevServerStatus(
|
|
env: NodeJS.ProcessEnv = process.env,
|
|
): PersistedDevServerStatus | null {
|
|
const filePath = env.PAPERCLIP_DEV_SERVER_STATUS_FILE?.trim();
|
|
if (!filePath || !existsSync(filePath)) return null;
|
|
|
|
try {
|
|
const raw = JSON.parse(readFileSync(filePath, "utf8")) as Record<string, unknown>;
|
|
const changedPathsSample = normalizeStringArray(raw.changedPathsSample).slice(0, 5);
|
|
const pendingMigrations = normalizeStringArray(raw.pendingMigrations);
|
|
const changedPathCountRaw = raw.changedPathCount;
|
|
const changedPathCount =
|
|
typeof changedPathCountRaw === "number" && Number.isFinite(changedPathCountRaw)
|
|
? Math.max(0, Math.trunc(changedPathCountRaw))
|
|
: changedPathsSample.length;
|
|
const dirtyRaw = raw.dirty;
|
|
const dirty =
|
|
typeof dirtyRaw === "boolean"
|
|
? dirtyRaw
|
|
: changedPathCount > 0 || pendingMigrations.length > 0;
|
|
|
|
return {
|
|
dirty,
|
|
lastChangedAt: normalizeTimestamp(raw.lastChangedAt),
|
|
changedPathCount,
|
|
changedPathsSample,
|
|
pendingMigrations,
|
|
lastRestartAt: normalizeTimestamp(raw.lastRestartAt),
|
|
};
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export function toDevServerHealthStatus(
|
|
persisted: PersistedDevServerStatus,
|
|
opts: { autoRestartEnabled: boolean; activeRunCount: number },
|
|
): DevServerHealthStatus {
|
|
const hasPathChanges = persisted.changedPathCount > 0;
|
|
const hasPendingMigrations = persisted.pendingMigrations.length > 0;
|
|
const reason =
|
|
hasPathChanges && hasPendingMigrations
|
|
? "backend_changes_and_pending_migrations"
|
|
: hasPendingMigrations
|
|
? "pending_migrations"
|
|
: hasPathChanges
|
|
? "backend_changes"
|
|
: null;
|
|
const restartRequired = persisted.dirty || reason !== null;
|
|
|
|
return {
|
|
enabled: true,
|
|
restartRequired,
|
|
reason,
|
|
lastChangedAt: persisted.lastChangedAt,
|
|
changedPathCount: persisted.changedPathCount,
|
|
changedPathsSample: persisted.changedPathsSample,
|
|
pendingMigrations: persisted.pendingMigrations,
|
|
autoRestartEnabled: opts.autoRestartEnabled,
|
|
activeRunCount: opts.activeRunCount,
|
|
waitingForIdle: restartRequired && opts.autoRestartEnabled && opts.activeRunCount > 0,
|
|
lastRestartAt: persisted.lastRestartAt,
|
|
};
|
|
}
|