import { randomBytes } from "node:crypto"; import fs from "node:fs"; import path from "node:path"; import { expandHomePrefix } from "../infra/home-dir.js"; import { resolveConfigDir } from "../utils.js"; import { parseJsonWithJson5Fallback } from "../utils/parse-json-compat.js"; import type { CronStoreFile } from "./types.js";
function resolveUpdatedAtMs(job: CronStoreFile["jobs"][number], updatedAtMs: unknown): number { if (typeof updatedAtMs === "number" && Number.isFinite(updatedAtMs)) { return updatedAtMs;
} if (typeof job.updatedAtMs === "number" && Number.isFinite(job.updatedAtMs)) { return job.updatedAtMs;
} returntypeof job.createdAtMs === "number" && Number.isFinite(job.createdAtMs)
? job.createdAtMs
: Date.now();
}
export async function loadCronStore(storePath: string): Promise<CronStoreFile> { try { const raw = await fs.promises.readFile(storePath, "utf-8");
let parsed: unknown; try {
parsed = parseJsonWithJson5Fallback(raw);
} catch (err) { thrownew Error(`Failed to parse cron store at ${storePath}: ${String(err)}`, {
cause: err,
});
} const parsedRecord =
parsed && typeof parsed === "object" && !Array.isArray(parsed)
? (parsed as Record<string, unknown>)
: {}; const jobs = Array.isArray(parsedRecord.jobs) ? (parsedRecord.jobs as never[]) : []; const store = {
version: 1 as const,
jobs: jobs.filter(Boolean) as never as CronStoreFile["jobs"],
};
// Load state file and merge. const statePath = resolveStatePath(storePath); const stateFile = await loadStateFile(statePath); const hasLegacyInlineState =
!stateFile && hasInlineState(jobs as unknown as Array<Record<string, unknown>>);
if (stateFile) { // State file exists: merge state by job ID. Inline state in jobs.json is ignored. for (const job of store.jobs) { const entry = stateFile.jobs[job.id]; if (entry) {
job.updatedAtMs = resolveUpdatedAtMs(job, entry.updatedAtMs);
job.state = (entry.state ?? {}) as never;
} else {
backfillMissingRuntimeFields(job);
}
}
} elseif (!hasLegacyInlineState) { // No state file, no inline state: fresh clone or first run. for (const job of store.jobs) {
backfillMissingRuntimeFields(job);
}
} // else: migration mode — no state file but jobs.json has inline state. Use as-is.
// Ensure every job has a state object (defensive). for (const job of store.jobs) {
ensureJobStateObject(job);
}
// Write state first so migration never leaves stripped config without runtime state. if (stateNeedsWrite || migrating) {
await atomicWrite(statePath, stateJson);
updatedCache.stateJson = stateJson;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.