Files
2026-08-20 22:41:25 +02:00

89 lines
3.4 KiB
JavaScript

/**
* Regenerate the Boneyard bone files.
*
* Boneyard measures a running page, so this needs `pnpm dev` up. It captures exactly
* two routes and does not crawl:
*
* - a real mint page, for the reviews panel's bones
* - a /mint/ URL with no prerendered page, for the prerender miss skeleton
*
* The mint host is looked up from the API rather than hardcoded, so this keeps
* working as mints come and go. Both islands render fixture content when the CLI
* sets `window.__BONEYARD_BUILD`, so neither capture depends on a relay answering.
*
* Re-run after any layout change to a review card or to the prerender miss summary.
*/
import { spawn } from 'node:child_process';
import { rmSync } from 'node:fs';
const WEB_PORT = process.env['WEB_PORT'] ?? '4321';
const WEB_URL = process.env['BONES_URL'] ?? `http://localhost:${WEB_PORT}`;
const API_URL = process.env['API_URL'] ?? 'http://127.0.0.1:8787';
/** A host that is never prerendered, so Astro serves the 404 fallback for it. */
const MISS_HOST = 'not-prerendered.boneyard.invalid';
function fail(message) {
console.error(`\n bones: ${message}\n`);
process.exit(1);
}
async function reachable(url) {
try {
const res = await fetch(url, { signal: AbortSignal.timeout(4000) });
return res.ok;
} catch {
return false;
}
}
if (!(await reachable(WEB_URL))) {
fail(`no dev server at ${WEB_URL}. Start one with "pnpm dev", then run this again.`);
}
/**
* The dev server proxies /api to the API in same origin mode, so try it first and
* fall back to the API's own address for setups that set PUBLIC_API_URL.
*/
async function firstMintHost() {
for (const base of [WEB_URL, API_URL]) {
try {
const res = await fetch(`${base}/api/mints`, { signal: AbortSignal.timeout(8000) });
if (!res.ok) continue;
const mints = await res.json();
if (Array.isArray(mints) && mints[0]?.host) return mints[0].host;
} catch {
// Try the next base.
}
}
return null;
}
const host = await firstMintHost();
if (!host) {
fail(`no mints from the API (tried ${WEB_URL} and ${API_URL}). Start it with "pnpm dev:api".`);
}
const targets = [`${WEB_URL}/mint/${host}`, `${WEB_URL}/mint/${MISS_HOST}`];
console.log(`\n bones: measuring\n ${targets.join('\n ')}\n`);
// Passing explicit non-root paths puts the CLI in single page mode: it visits only
// these URLs, with no link following and no filesystem route scan.
//
// --force because Boneyard decides a skeleton is unchanged by hashing the captured
// element's innerHTML. Bone geometry depends just as much on CSS, so a styling change
// alone would silently reuse stale bones. There are only two skeletons here, so
// always recapturing costs a few seconds and removes the trap.
const child = spawn('boneyard-js', ['build', '--force', ...targets], { stdio: 'inherit', shell: false });
child.on('error', (error) => fail(`could not run boneyard-js (${error.message}).`));
child.on('exit', (code) => {
// Boneyard also writes a registry that calls registerBones() through a framework
// adapter, defaulting to boneyard-js/react. We have no React and no <Skeleton>
// components: the islands import the .bones.json files directly. Leaving an
// unresolvable import in src/ would only break typechecking.
rmSync(new URL('../src/bones/registry.ts', import.meta.url), { force: true });
rmSync(new URL('../src/bones/registry.js', import.meta.url), { force: true });
process.exit(code ?? 1);
});