chore: Enable "curly" rule to avoid single-statement if confusion/errors.

This commit is contained in:
cpojer
2026-01-31 16:19:20 +09:00
parent 009b16fab8
commit 5ceff756e1
1266 changed files with 27871 additions and 9393 deletions

View File

@@ -29,25 +29,35 @@ export function assertSupportedJobSpec(job: Pick<CronJob, "sessionTarget" | "pay
export function findJobOrThrow(state: CronServiceState, id: string) {
const job = state.store?.jobs.find((j) => j.id === id);
if (!job) throw new Error(`unknown cron job id: ${id}`);
if (!job) {
throw new Error(`unknown cron job id: ${id}`);
}
return job;
}
export function computeJobNextRunAtMs(job: CronJob, nowMs: number): number | undefined {
if (!job.enabled) return undefined;
if (!job.enabled) {
return undefined;
}
if (job.schedule.kind === "at") {
// One-shot jobs stay due until they successfully finish.
if (job.state.lastStatus === "ok" && job.state.lastRunAtMs) return undefined;
if (job.state.lastStatus === "ok" && job.state.lastRunAtMs) {
return undefined;
}
return job.schedule.atMs;
}
return computeNextRunAtMs(job.schedule, nowMs);
}
export function recomputeNextRuns(state: CronServiceState) {
if (!state.store) return;
if (!state.store) {
return;
}
const now = state.deps.nowMs();
for (const job of state.store.jobs) {
if (!job.state) job.state = {};
if (!job.state) {
job.state = {};
}
if (!job.enabled) {
job.state.nextRunAtMs = undefined;
job.state.runningAtMs = undefined;
@@ -68,7 +78,9 @@ export function recomputeNextRuns(state: CronServiceState) {
export function nextWakeAtMs(state: CronServiceState) {
const jobs = state.store?.jobs ?? [];
const enabled = jobs.filter((j) => j.enabled && typeof j.state.nextRunAtMs === "number");
if (enabled.length === 0) return undefined;
if (enabled.length === 0) {
return undefined;
}
return enabled.reduce(
(min, j) => Math.min(min, j.state.nextRunAtMs as number),
enabled[0].state.nextRunAtMs as number,
@@ -102,16 +114,36 @@ export function createJob(state: CronServiceState, input: CronJobCreate): CronJo
}
export function applyJobPatch(job: CronJob, patch: CronJobPatch) {
if ("name" in patch) job.name = normalizeRequiredName(patch.name);
if ("description" in patch) job.description = normalizeOptionalText(patch.description);
if (typeof patch.enabled === "boolean") job.enabled = patch.enabled;
if (typeof patch.deleteAfterRun === "boolean") job.deleteAfterRun = patch.deleteAfterRun;
if (patch.schedule) job.schedule = patch.schedule;
if (patch.sessionTarget) job.sessionTarget = patch.sessionTarget;
if (patch.wakeMode) job.wakeMode = patch.wakeMode;
if (patch.payload) job.payload = mergeCronPayload(job.payload, patch.payload);
if (patch.isolation) job.isolation = patch.isolation;
if (patch.state) job.state = { ...job.state, ...patch.state };
if ("name" in patch) {
job.name = normalizeRequiredName(patch.name);
}
if ("description" in patch) {
job.description = normalizeOptionalText(patch.description);
}
if (typeof patch.enabled === "boolean") {
job.enabled = patch.enabled;
}
if (typeof patch.deleteAfterRun === "boolean") {
job.deleteAfterRun = patch.deleteAfterRun;
}
if (patch.schedule) {
job.schedule = patch.schedule;
}
if (patch.sessionTarget) {
job.sessionTarget = patch.sessionTarget;
}
if (patch.wakeMode) {
job.wakeMode = patch.wakeMode;
}
if (patch.payload) {
job.payload = mergeCronPayload(job.payload, patch.payload);
}
if (patch.isolation) {
job.isolation = patch.isolation;
}
if (patch.state) {
job.state = { ...job.state, ...patch.state };
}
if ("agentId" in patch) {
job.agentId = normalizeOptionalAgentId((patch as { agentId?: unknown }).agentId);
}
@@ -136,13 +168,27 @@ function mergeCronPayload(existing: CronPayload, patch: CronPayloadPatch): CronP
}
const next: Extract<CronPayload, { kind: "agentTurn" }> = { ...existing };
if (typeof patch.message === "string") next.message = patch.message;
if (typeof patch.model === "string") next.model = patch.model;
if (typeof patch.thinking === "string") next.thinking = patch.thinking;
if (typeof patch.timeoutSeconds === "number") next.timeoutSeconds = patch.timeoutSeconds;
if (typeof patch.deliver === "boolean") next.deliver = patch.deliver;
if (typeof patch.channel === "string") next.channel = patch.channel;
if (typeof patch.to === "string") next.to = patch.to;
if (typeof patch.message === "string") {
next.message = patch.message;
}
if (typeof patch.model === "string") {
next.model = patch.model;
}
if (typeof patch.thinking === "string") {
next.thinking = patch.thinking;
}
if (typeof patch.timeoutSeconds === "number") {
next.timeoutSeconds = patch.timeoutSeconds;
}
if (typeof patch.deliver === "boolean") {
next.deliver = patch.deliver;
}
if (typeof patch.channel === "string") {
next.channel = patch.channel;
}
if (typeof patch.to === "string") {
next.to = patch.to;
}
if (typeof patch.bestEffortDeliver === "boolean") {
next.bestEffortDeliver = patch.bestEffortDeliver;
}
@@ -175,12 +221,16 @@ function buildPayloadFromPatch(patch: CronPayloadPatch): CronPayload {
}
export function isJobDue(job: CronJob, nowMs: number, opts: { forced: boolean }) {
if (opts.forced) return true;
if (opts.forced) {
return true;
}
return job.enabled && typeof job.state.nextRunAtMs === "number" && nowMs >= job.state.nextRunAtMs;
}
export function resolveJobPayloadTextForMain(job: CronJob): string | undefined {
if (job.payload.kind !== "systemEvent") return undefined;
if (job.payload.kind !== "systemEvent") {
return undefined;
}
const text = normalizePayloadToSystemText(job.payload);
return text.trim() ? text : undefined;
}

View File

@@ -3,27 +3,39 @@ import { truncateUtf16Safe } from "../../utils.js";
import type { CronPayload } from "../types.js";
export function normalizeRequiredName(raw: unknown) {
if (typeof raw !== "string") throw new Error("cron job name is required");
if (typeof raw !== "string") {
throw new Error("cron job name is required");
}
const name = raw.trim();
if (!name) throw new Error("cron job name is required");
if (!name) {
throw new Error("cron job name is required");
}
return name;
}
export function normalizeOptionalText(raw: unknown) {
if (typeof raw !== "string") return undefined;
if (typeof raw !== "string") {
return undefined;
}
const trimmed = raw.trim();
return trimmed ? trimmed : undefined;
}
function truncateText(input: string, maxLen: number) {
if (input.length <= maxLen) return input;
if (input.length <= maxLen) {
return input;
}
return `${truncateUtf16Safe(input, Math.max(0, maxLen - 1)).trimEnd()}`;
}
export function normalizeOptionalAgentId(raw: unknown) {
if (typeof raw !== "string") return undefined;
if (typeof raw !== "string") {
return undefined;
}
const trimmed = raw.trim();
if (!trimmed) return undefined;
if (!trimmed) {
return undefined;
}
return normalizeAgentId(trimmed);
}
@@ -42,18 +54,26 @@ export function inferLegacyName(job: {
.split("\n")
.map((l) => l.trim())
.find(Boolean) ?? "";
if (firstLine) return truncateText(firstLine, 60);
if (firstLine) {
return truncateText(firstLine, 60);
}
const kind = typeof job?.schedule?.kind === "string" ? job.schedule.kind : "";
if (kind === "cron" && typeof job?.schedule?.expr === "string")
if (kind === "cron" && typeof job?.schedule?.expr === "string") {
return `Cron: ${truncateText(job.schedule.expr, 52)}`;
if (kind === "every" && typeof job?.schedule?.everyMs === "number")
}
if (kind === "every" && typeof job?.schedule?.everyMs === "number") {
return `Every: ${job.schedule.everyMs}ms`;
if (kind === "at") return "One-shot";
}
if (kind === "at") {
return "One-shot";
}
return "Cron job";
}
export function normalizePayloadToSystemText(payload: CronPayload) {
if (payload.kind === "systemEvent") return payload.text.trim();
if (payload.kind === "systemEvent") {
return payload.text.trim();
}
return payload.message.trim();
}

View File

@@ -107,12 +107,16 @@ export async function remove(state: CronServiceState, id: string) {
warnIfDisabled(state, "remove");
await ensureLoaded(state);
const before = state.store?.jobs.length ?? 0;
if (!state.store) return { ok: false, removed: false } as const;
if (!state.store) {
return { ok: false, removed: false } as const;
}
state.store.jobs = state.store.jobs.filter((j) => j.id !== id);
const removed = (state.store.jobs.length ?? 0) !== before;
await persist(state);
armTimer(state);
if (removed) emit(state, { jobId: id, action: "removed" });
if (removed) {
emit(state, { jobId: id, action: "removed" });
}
return { ok: true, removed } as const;
});
}
@@ -124,7 +128,9 @@ export async function run(state: CronServiceState, id: string, mode?: "due" | "f
const job = findJobOrThrow(state, id);
const now = state.deps.nowMs();
const due = isJobDue(job, now, { forced: mode === "force" });
if (!due) return { ok: true, ran: false, reason: "not-due" as const };
if (!due) {
return { ok: true, ran: false, reason: "not-due" as const };
}
await executeJob(state, job, now, { forced: mode === "force" });
await persist(state);
armTimer(state);

View File

@@ -7,7 +7,9 @@ import type { CronServiceState } from "./state.js";
const storeCache = new Map<string, { version: 1; jobs: CronJob[] }>();
export async function ensureLoaded(state: CronServiceState) {
if (state.store) return;
if (state.store) {
return;
}
const cached = storeCache.get(state.deps.storePath);
if (cached) {
state.store = cached;
@@ -43,12 +45,18 @@ export async function ensureLoaded(state: CronServiceState) {
}
state.store = { version: 1, jobs: jobs as unknown as CronJob[] };
storeCache.set(state.deps.storePath, state.store);
if (mutated) await persist(state);
if (mutated) {
await persist(state);
}
}
export function warnIfDisabled(state: CronServiceState, action: string) {
if (state.deps.cronEnabled) return;
if (state.warnedDisabled) return;
if (state.deps.cronEnabled) {
return;
}
if (state.warnedDisabled) {
return;
}
state.warnedDisabled = true;
state.deps.log.warn(
{ enabled: false, action, storePath: state.deps.storePath },
@@ -57,6 +65,8 @@ export function warnIfDisabled(state: CronServiceState, action: string) {
}
export async function persist(state: CronServiceState) {
if (!state.store) return;
if (!state.store) {
return;
}
await saveCronStore(state.deps.storePath, state.store);
}

View File

@@ -8,11 +8,17 @@ import { ensureLoaded, persist } from "./store.js";
const MAX_TIMEOUT_MS = 2 ** 31 - 1;
export function armTimer(state: CronServiceState) {
if (state.timer) clearTimeout(state.timer);
if (state.timer) {
clearTimeout(state.timer);
}
state.timer = null;
if (!state.deps.cronEnabled) return;
if (!state.deps.cronEnabled) {
return;
}
const nextAt = nextWakeAtMs(state);
if (!nextAt) return;
if (!nextAt) {
return;
}
const delay = Math.max(nextAt - state.deps.nowMs(), 0);
// Avoid TimeoutOverflowWarning when a job is far in the future.
const clampedDelay = Math.min(delay, MAX_TIMEOUT_MS);
@@ -25,7 +31,9 @@ export function armTimer(state: CronServiceState) {
}
export async function onTimer(state: CronServiceState) {
if (state.running) return;
if (state.running) {
return;
}
state.running = true;
try {
await locked(state, async () => {
@@ -40,11 +48,17 @@ export async function onTimer(state: CronServiceState) {
}
export async function runDueJobs(state: CronServiceState) {
if (!state.store) return;
if (!state.store) {
return;
}
const now = state.deps.nowMs();
const due = state.store.jobs.filter((j) => {
if (!j.enabled) return false;
if (typeof j.state.runningAtMs === "number") return false;
if (!j.enabled) {
return false;
}
if (typeof j.state.runningAtMs === "number") {
return false;
}
const next = j.state.nextRunAtMs;
return typeof next === "number" && now >= next;
});
@@ -199,10 +213,13 @@ export async function executeJob(
job,
message: job.payload.message,
});
if (res.status === "ok") await finish("ok", undefined, res.summary, res.outputText);
else if (res.status === "skipped")
if (res.status === "ok") {
await finish("ok", undefined, res.summary, res.outputText);
} else if (res.status === "skipped") {
await finish("skipped", undefined, res.summary, res.outputText);
else await finish("error", res.error ?? "cron job failed", res.summary, res.outputText);
} else {
await finish("error", res.error ?? "cron job failed", res.summary, res.outputText);
}
} catch (err) {
await finish("error", String(err));
} finally {
@@ -219,7 +236,9 @@ export function wake(
opts: { mode: "now" | "next-heartbeat"; text: string },
) {
const text = opts.text.trim();
if (!text) return { ok: false } as const;
if (!text) {
return { ok: false } as const;
}
state.deps.enqueueSystemEvent(text);
if (opts.mode === "now") {
state.deps.requestHeartbeatNow({ reason: "wake" });
@@ -228,7 +247,9 @@ export function wake(
}
export function stopTimer(state: CronServiceState) {
if (state.timer) clearTimeout(state.timer);
if (state.timer) {
clearTimeout(state.timer);
}
state.timer = null;
}