Add editable weather captions and manage Threads scheduling with PM2

This commit is contained in:
2026-11-02 04:07:40 +09:00
parent 8c2113a323
commit 6ce32d33fe
17 changed files with 305 additions and 50 deletions
+53
View File
@@ -0,0 +1,53 @@
import { spawn, type ChildProcess } from 'node:child_process';
import { fileURLToPath } from 'node:url';
import { setTimeout as delay } from 'node:timers/promises';
import { nextRunAt } from './schedule.ts';
import { kstFields } from '../../src/ui/time.ts';
import { THREADS } from '../../src/constants/threads.ts';
const stop = new AbortController();
let child: ChildProcess | undefined;
for (const signal of ['SIGINT', 'SIGTERM'] as const) {
process.once(signal, () => { stop.abort(); child?.kill('SIGTERM'); });
}
async function runJob() {
return new Promise<boolean>((resolve, reject) => {
const job = spawn(process.execPath, [fileURLToPath(new URL('./run.ts', import.meta.url))], { stdio: 'inherit' });
child = job;
let timedOut = false;
let forceStop: ReturnType<typeof setTimeout> | undefined;
const deadline = setTimeout(() => {
timedOut = true;
console.error('Daily job exceeded eight hours; terminating before retry');
job.kill('SIGTERM');
forceStop = setTimeout(() => job.kill('SIGKILL'), THREADS.jobStopGraceMs);
}, THREADS.jobTimeoutMs);
const clear = () => { clearTimeout(deadline); clearTimeout(forceStop); child = undefined; };
job.once('error', error => { clear(); reject(error); });
job.once('exit', code => { clear(); resolve(code === 0 && !timedOut); });
});
}
async function main() {
let completedDate: string | undefined;
while (!stop.signal.aborted) {
const next = nextRunAt(new Date(), completedDate);
console.log(`Next daily preparation: ${next.toISOString()} (schedule: 03:00 Asia/Seoul)`);
while (+next > Date.now()) await delay(Math.min(+next - Date.now(), 60_000), undefined, { signal: stop.signal });
if (stop.signal.aborted) break;
const date = kstFields(new Date()).date;
for (let attempt = 1; attempt <= THREADS.retryAttempts && !stop.signal.aborted; attempt++) {
if (await runJob()) break;
console.error(`Daily job failed (${attempt}/${THREADS.retryAttempts})`);
if (attempt < THREADS.retryAttempts) await delay(THREADS.retryMs, undefined, { signal: stop.signal });
}
completedDate = date;
}
}
void main().catch(error => {
if (stop.signal.aborted) return;
console.error(error instanceof Error ? error.message : 'Threads worker failed');
process.exitCode = 1;
});