89 lines
3.4 KiB
JavaScript
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);
|
|
});
|