Something went wrong. Try again.
Monorepo for Aesthetic.Computer aesthetic.computer
Something went wrong. Try again.
5.3 kB · 169 lines
JavaScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170#!/usr/bin/env node// rebake.mjs — Admin-driven piece media rebake (no Tezos required).//// Usage: node kidlisp-cli/rebake.mjs $2un// node kidlisp-cli/rebake.mjs $2un --api https://localhost:8888//// Calls /api/admin-rebake (admin only) to refresh the oven-baked// artifact + thumbnail for a piece — works for both minted tokens// and unminted pieces. Polls /api/keep-status until the job ends// and prints the resulting IPFS URIs.//// Use this when an oven thumbnail or bundle came back broken// (e.g. the all-black 528-byte WebP from a Puppeteer flake) and// you want a fresh pin without walking the keep-mint UI.//// For minted-token full regen (rebake + on-chain edit_metadata),// use regenerate-media.mjs instead.
import fs from "fs";import path from "path";import os from "os";import { execFileSync } from "child_process";import { fileURLToPath } from "url";
const __dirname = path.dirname(fileURLToPath(import.meta.url));const AC_LOGIN = path.join(__dirname, "..", "tezos", "ac-login.mjs");
const args = process.argv.slice(2);function opt(name, fallback) { const i = args.indexOf(`--${name}`); return i >= 0 && args[i + 1] ? args[i + 1] : fallback;}const API_BASE = opt("api", "https://aesthetic.computer").replace(/\/$/, "");
const flagsWithValues = new Set(["api"]);const pieces = [];for (let i = 0; i < args.length; i++) { const a = args[i]; if (a.startsWith("--")) { if (flagsWithValues.has(a.slice(2))) i++; continue; } pieces.push(a);}
if (pieces.length === 0) { console.log(` rebake — Admin rebake of a KidLisp piece's oven media (artifact + thumbnail)
Usage: node kidlisp-cli/rebake.mjs \\$2un node kidlisp-cli/rebake.mjs \\$2un --api https://localhost:8888
Auth: requires an admin AC login (~/.ac-token, refreshed by ac-login). `); process.exit(0);}
if (API_BASE.includes("localhost")) { process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";}
// ── Auth ──const authPath = path.join(os.homedir(), ".ac-token");function readToken() { if (!fs.existsSync(authPath)) return null; try { const data = JSON.parse(fs.readFileSync(authPath, "utf8")); if (data.expires_at && Date.now() > data.expires_at) return null; return data; } catch { return null; }}
let auth = readToken();if (!auth) { console.log("\n🔐 Not logged in — launching ac-login...\n"); try { execFileSync("node", [AC_LOGIN], { stdio: "inherit" }); } catch { console.error("❌ Login failed."); process.exit(1); } auth = readToken(); if (!auth) { console.error("❌ Still no valid token."); process.exit(1); }}
const TOKEN = auth.access_token;const WHO = auth.user?.handle ? `@${auth.user.handle}` : auth.user?.email || "unknown";
const POLL_INTERVAL_MS = 2500;const POLL_TIMEOUT_MS = 5 * 60 * 1000;
async function rebakeOne(pieceArg) { const piece = pieceArg.replace(/^\$/, ""); console.log(`\n${"─".repeat(60)}\n🎨 $${piece} (admin: ${WHO})`);
const startRes = await fetch(`${API_BASE}/api/admin-rebake`, { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${TOKEN}`, }, body: JSON.stringify({ piece: `$${piece}` }), });
const text = await startRes.text(); let started; try { started = JSON.parse(text); } catch { started = { error: text.slice(0, 300) }; }
if (!startRes.ok) { console.error(`❌ admin-rebake HTTP ${startRes.status}: ${started.error || text}`); return { piece, success: false }; }
const jobId = started.jobId; if (!jobId) { console.error(`❌ no jobId in response: ${JSON.stringify(started).slice(0, 200)}`); return { piece, success: false }; } console.log(` job ${jobId} → polling ${API_BASE}/api/keep-status`);
const start = Date.now(); let lastLine = ""; while (Date.now() - start < POLL_TIMEOUT_MS) { await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS)); let job; try { const r = await fetch(`${API_BASE}/api/keep-status?jobId=${encodeURIComponent(jobId)}`); if (!r.ok) continue; job = await r.json(); } catch { continue; }
const elapsed = ((Date.now() - start) / 1000).toFixed(0); const line = `[${elapsed}s] ${job.status}: ${job.stage || "?"} — ${job.message || job.stageMessage || ""}`; if (line !== lastLine) { process.stdout.write(`\r ${line.padEnd(78)}`); lastLine = line; }
if (job.status === "ready") { process.stdout.write("\n"); console.log(` ✅ ready`); if (job.artifactUri) console.log(` artifact: ${job.artifactUri}`); if (job.thumbnailUri) console.log(` thumbnail: ${job.thumbnailUri}`); return { piece, success: true, ...job }; } if (job.status === "failed") { process.stdout.write("\n"); console.error(` ❌ failed at ${job.errorStage || job.stage}: ${job.error || "unknown"}`); return { piece, success: false }; } }
process.stdout.write("\n"); console.error(` ⏱ timed out after ${POLL_TIMEOUT_MS / 1000}s (job may still be running)`); return { piece, success: false };}
let failed = 0;for (const p of pieces) { const r = await rebakeOne(p); if (!r.success) failed++;}process.exit(failed > 0 ? 1 : 0);