Files

61 lines
4.2 KiB
JavaScript

// Sage, 2026-10-20: row 186 landed (269134d8d, pushed in b367f5730..9760d4967, #1684), and mos-0955 split its two
// follow-ups into P19 405 and 406. Comments on P19 404 that it closes, then marks it done. Sage's own key (bot user 13). The update sends the task as the v1
// GET returned it with only done changed; the sync key reads title, description and done back. Rollback is
// marking it undone. Prints no secret.
// Usage: node close-404.mjs https://tasks.woltje.com SAGE_TOKEN_FILE SYNC_TOKEN_FILE OUT_JSON
import { readFileSync, writeFileSync, existsSync } from "node:fs";
const [ORIGIN, KEY, SYNC, OUT] = process.argv.slice(2);
if (!/^https:\/\/tasks\.woltje\.com$/.test(ORIGIN)) throw new Error("tasks.woltje.com only");
if (!OUT || existsSync(OUT)) throw new Error("OUT_JSON missing or exists; refusing");
const TASK = 404;
const SIG = "Created by Sage (T3 1ef1e4f8) with Sage's own key (bot user 13).";
const esc = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
const p = (...xs) => xs.map((x) => `<p>${esc(x)}</p>`).join("") + `<p>${esc(SIG)}</p>`;
const TEXT = [
"Closing: N1 and N2 landed with row 186 (269134d8d, comment 354). The two follow-ups from its review are P19 405 and 406 (comment 355), owner Sage, so nothing stays open here.",
];
const key = readFileSync(KEY, "utf8").trim(), sync = readFileSync(SYNC, "utf8").trim();
async function api(method, path, body, auth) {
const r = await fetch(ORIGIN + path, { method, headers: { Authorization: `Bearer ${auth}`, "Content-Type": "application/json" }, body: body === undefined ? undefined : JSON.stringify(body), redirect: "error" });
const t = await r.text(); let json = null; try { json = JSON.parse(t); } catch {}
return { status: r.status, json };
}
const list = (j) => j?.items ?? (Array.isArray(j) ? j : []);
const rec = { at: new Date().toISOString(), task: TASK, title: null, problems: [] };
const save = () => {
const out = JSON.stringify(rec, null, 1) + "\n";
if (out.includes(key) || out.includes(sync)) throw new Error("secret in the record; not written");
writeFileSync(OUT, out);
};
const task = await api("GET", `/api/v2/tasks/${TASK}`, undefined, sync);
if (task.status !== 200 || task.json?.project_id !== 19 || !/^stack-release --plan backup line/.test(task.json?.title ?? "")) throw new Error(`task ${TASK}: ${task.status}, project ${task.json?.project_id}, title isn't 404's`);
if (task.json.done) throw new Error(`task ${TASK} is already done; refusing`);
const before = { title: task.json.title, description: task.json.description };
rec.title = task.json.title;
writeFileSync(OUT, "", { flag: "wx" });
const c = await api("PUT", `/api/v1/tasks/${TASK}/comments`, { comment: p(...TEXT) }, key);
rec.comment = { status: c.status, id: c.json?.id ?? null };
if (c.status >= 300 || !Number.isSafeInteger(c.json?.id)) rec.problems.push(`comment: ${c.status} ${c.json?.message ?? ""}`);
else {
const back = await api("GET", `/api/v2/tasks/${TASK}/comments?per_page=50`, undefined, sync);
const found = list(back.json).find((x) => x.id === c.json.id);
Object.assign(rec.comment, { readBack: back.status, found: Boolean(found) });
if (back.status !== 200 || !found) rec.problems.push(`comment read-back: ${back.status}`);
}
save();
if (!rec.problems.length) {
const full = await api("GET", `/api/v1/tasks/${TASK}`, undefined, key);
if (full.status !== 200 || full.json?.id !== TASK) rec.problems.push(`v1 get: ${full.status}`);
else {
const u = await api("POST", `/api/v1/tasks/${TASK}`, { ...full.json, done: true }, key);
rec.update = { status: u.status, inverse: `mark undone: POST /api/v1/tasks/${TASK} done false (sage key)` };
const back = await api("GET", `/api/v2/tasks/${TASK}`, undefined, sync);
Object.assign(rec.update, { readBack: back.status, done: back.json?.done, titleSame: back.json?.title === before.title, descriptionSame: back.json?.description === before.description });
if (u.status >= 300 || back.status !== 200 || back.json?.done !== true || !rec.update.titleSame || !rec.update.descriptionSame) rec.problems.push("update or read-back differs");
}
}
save();
process.stdout.write(JSON.stringify(rec, null, 1) + "\n");
if (rec.problems.length) process.exit(2);