Record a heartbeat so a silent update checker is distinguishable from a dead one
There was no way to tell "running, nothing to pull" from "not running". The no-change path logs nothing by design, and PM2 reports a cron-restart process as `stopped` with ↺ 0 even while firing on schedule — I verified that against PM2 7.0.4: a one-minute cron fired four times without the counter moving once. scripts/.last-check is now rewritten on every run with the outcome. It is a single overwritten line, so it needs no trimming. The outcome is set by each exit path and written once in `finally`, rather than calling record at each return — the first version of this did the latter and already missed the build-failure path. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
27a61fd25c
commit
0ba27161ce
@@ -20,6 +20,7 @@ import { ensurePythonDeps } from './ensure-python-deps.mjs';
|
||||
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
||||
const LOG_FILE = path.join(ROOT, 'scripts', 'update.log');
|
||||
const LOCK_FILE = path.join(ROOT, 'scripts', '.update.lock');
|
||||
const LAST_CHECK_FILE = path.join(ROOT, 'scripts', '.last-check');
|
||||
const BRANCH = process.env.AUTOFIRMER_BRANCH ?? 'master';
|
||||
const PORT = process.env.AUTOFIRMER_PORT ?? '3000';
|
||||
const BASE = `http://127.0.0.1:${PORT}`;
|
||||
@@ -50,6 +51,21 @@ function log(msg) {
|
||||
try { appendFileSync(LOG_FILE, line + '\n'); } catch { /* logging must never throw */ }
|
||||
}
|
||||
|
||||
/**
|
||||
* Record that a check happened, whether or not it found anything.
|
||||
*
|
||||
* Overwritten rather than appended, so it stays one line and needs no trimming.
|
||||
* Without it there is no way to tell "running, nothing to pull" from "not
|
||||
* running at all": the no-change path logs nothing by design, and PM2 reports a
|
||||
* cron-restart process as `stopped` with a restart count of 0 even while it is
|
||||
* firing on schedule.
|
||||
*/
|
||||
function recordCheck(status) {
|
||||
try {
|
||||
writeFileSync(LAST_CHECK_FILE, `${new Date().toISOString()} ${status}\n`);
|
||||
} catch { /* never let bookkeeping break an update */ }
|
||||
}
|
||||
|
||||
function run(cmd, args, { capture = false } = {}) {
|
||||
return spawnSync(cmd, args, {
|
||||
cwd: ROOT,
|
||||
@@ -88,12 +104,17 @@ async function main() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let outcome = 'interrupted';
|
||||
|
||||
try {
|
||||
git('fetch', 'origin', BRANCH);
|
||||
const local = git('rev-parse', 'HEAD');
|
||||
const remote = git('rev-parse', `origin/${BRANCH}`);
|
||||
|
||||
if (local === remote) return 0; // the common path: silent no-op
|
||||
if (local === remote) {
|
||||
outcome = `up to date at ${local.slice(0, 8)}`;
|
||||
return 0; // silent in the log by design
|
||||
}
|
||||
|
||||
log(`update available: ${local.slice(0, 8)} -> ${remote.slice(0, 8)}`);
|
||||
const changed = git('diff', '--name-only', local, remote).split('\n').filter(Boolean);
|
||||
@@ -115,7 +136,7 @@ async function main() {
|
||||
|
||||
if (changed.includes('package-lock.json')) {
|
||||
log('package-lock.json changed — npm install');
|
||||
if (run('npm', ['install']).status !== 0) { log('ABORTED: npm install failed'); return 1; }
|
||||
if (run('npm', ['install']).status !== 0) { log('ABORTED: npm install failed'); outcome = 'npm install failed'; return 1; }
|
||||
}
|
||||
|
||||
if (changed.includes('clicker/requirements.txt')) {
|
||||
@@ -128,11 +149,12 @@ async function main() {
|
||||
log('building');
|
||||
if (run('npm', ['run', 'build']).status !== 0) {
|
||||
log('ABORTED: build failed — the previous build is still serving, nothing was restarted');
|
||||
outcome = 'build failed — not deployed';
|
||||
return 1;
|
||||
}
|
||||
|
||||
log('restarting autofirmer');
|
||||
if (run('pm2', ['restart', 'autofirmer']).status !== 0) { log('pm2 restart autofirmer failed'); return 1; }
|
||||
if (run('pm2', ['restart', 'autofirmer']).status !== 0) { log('pm2 restart autofirmer failed'); outcome = 'pm2 restart failed'; return 1; }
|
||||
|
||||
if (changed.some((f) => f.startsWith('clicker/'))) {
|
||||
log('clicker changed — restarting it too');
|
||||
@@ -141,12 +163,17 @@ async function main() {
|
||||
|
||||
await warmUp();
|
||||
log(`updated to ${remote.slice(0, 8)}`);
|
||||
outcome = `updated to ${remote.slice(0, 8)}`;
|
||||
return 0;
|
||||
} catch (err) {
|
||||
log(`ERROR: ${err.message}`);
|
||||
outcome = `ERROR: ${err.message}`;
|
||||
return 1;
|
||||
} finally {
|
||||
if (!DRY_RUN) releaseLock();
|
||||
if (!DRY_RUN) {
|
||||
releaseLock();
|
||||
recordCheck(outcome);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user