|
| 1 | +#!/usr/bin/env node |
| 2 | +// Fetch compute.toys shaders by id and generate hero ShaderEntry modules. |
| 3 | +// |
| 4 | +// Usage: |
| 5 | +// node scripts/fetch-computetoys.mjs 202 31 ... # fetch these ids |
| 6 | +// node scripts/fetch-computetoys.mjs # refetch ids in manifest |
| 7 | +// |
| 8 | +// For each id it pulls /view/<id>/wgsl (source) and /view/<id>/json (uniforms + |
| 9 | +// metadata), then writes src/components/hero/shaders/generated/ct-<id>.ts and |
| 10 | +// regenerates the generated/index.ts barrel. Generated files are committed, so |
| 11 | +// the docs build needs no network access. |
| 12 | + |
| 13 | +import { mkdir, readFile, writeFile } from "node:fs/promises"; |
| 14 | +import { dirname, join } from "node:path"; |
| 15 | +import { fileURLToPath } from "node:url"; |
| 16 | + |
| 17 | +const __dirname = dirname(fileURLToPath(import.meta.url)); |
| 18 | +const GEN_DIR = join(__dirname, "../src/components/hero/shaders/generated"); |
| 19 | +const MANIFEST = join(GEN_DIR, "manifest.json"); |
| 20 | + |
| 21 | +async function readManifest() { |
| 22 | + try { |
| 23 | + return JSON.parse(await readFile(MANIFEST, "utf8")); |
| 24 | + } catch { |
| 25 | + return []; |
| 26 | + } |
| 27 | +} |
| 28 | + |
| 29 | +async function fetchToy(id) { |
| 30 | + const [wgslRes, jsonRes] = await Promise.all([ |
| 31 | + fetch(`https://compute.toys/view/${id}/wgsl`), |
| 32 | + fetch(`https://compute.toys/view/${id}/json`), |
| 33 | + ]); |
| 34 | + if (!wgslRes.ok || !jsonRes.ok) { |
| 35 | + throw new Error(`Failed to fetch toy ${id} (${wgslRes.status}/${jsonRes.status})`); |
| 36 | + } |
| 37 | + const code = await wgslRes.text(); |
| 38 | + const meta = await jsonRes.json(); |
| 39 | + return { code, meta }; |
| 40 | +} |
| 41 | + |
| 42 | +function generateModule(id, code, meta) { |
| 43 | + const uniforms = meta.body?.uniforms ?? []; |
| 44 | + const customOrder = uniforms.map((u) => u.name); |
| 45 | + const custom = Object.fromEntries(uniforms.map((u) => [u.name, u.value])); |
| 46 | + const passF32 = Boolean(meta.body?.float32Enabled); |
| 47 | + const title = meta.name ?? `compute.toys #${id}`; |
| 48 | + const author = meta.profile?.username ?? "unknown"; |
| 49 | + |
| 50 | + return `// AUTO-GENERATED by scripts/fetch-computetoys.mjs from |
| 51 | +// https://compute.toys/view/${id} — do not edit by hand. Re-run the script to |
| 52 | +// update. Per-hero tuning (orbitPeriod, maxDimension) belongs in the registry. |
| 53 | +// |
| 54 | +// "${title}" by ${author}. compute.toys exposes no explicit license; used with |
| 55 | +// attribution. Confirm reuse terms with the author if needed. |
| 56 | +import type { ShaderEntry } from "../../types"; |
| 57 | +
|
| 58 | +export const ct${id}: ShaderEntry = { |
| 59 | + id: "ct-${id}", |
| 60 | + title: ${JSON.stringify(title)}, |
| 61 | + author: ${JSON.stringify(author)}, |
| 62 | + sourceUrl: "https://compute.toys/view/${id}", |
| 63 | + license: "compute.toys", |
| 64 | + shader: { |
| 65 | + kind: "computetoys", |
| 66 | + passF32: ${passF32}, |
| 67 | + customOrder: ${JSON.stringify(customOrder)}, |
| 68 | + custom: ${JSON.stringify(custom, null, 6).replace(/\n/g, "\n ")}, |
| 69 | + code: ${JSON.stringify(code)}, |
| 70 | + }, |
| 71 | +}; |
| 72 | +`; |
| 73 | +} |
| 74 | + |
| 75 | +function generateIndex(ids) { |
| 76 | + const sorted = [...ids].sort((a, b) => a - b); |
| 77 | + const imports = sorted.map((id) => `import { ct${id} } from "./ct-${id}";`).join("\n"); |
| 78 | + const list = sorted.map((id) => `ct${id}`).join(", "); |
| 79 | + return `// AUTO-GENERATED by scripts/fetch-computetoys.mjs — do not edit by hand. |
| 80 | +import type { ShaderEntry } from "../../types"; |
| 81 | +
|
| 82 | +${imports} |
| 83 | +
|
| 84 | +export { ${list} }; |
| 85 | +
|
| 86 | +export const GENERATED_SHADERS: ShaderEntry[] = [${list}]; |
| 87 | +`; |
| 88 | +} |
| 89 | + |
| 90 | +async function main() { |
| 91 | + const argIds = process.argv.slice(2).map((s) => parseInt(s, 10)).filter((n) => !Number.isNaN(n)); |
| 92 | + const manifestIds = await readManifest(); |
| 93 | + const ids = [...new Set([...manifestIds, ...argIds])]; |
| 94 | + |
| 95 | + if (ids.length === 0) { |
| 96 | + console.error("No ids. Pass ids as args, e.g. node scripts/fetch-computetoys.mjs 202"); |
| 97 | + process.exit(1); |
| 98 | + } |
| 99 | + |
| 100 | + await mkdir(GEN_DIR, { recursive: true }); |
| 101 | + |
| 102 | + for (const id of ids) { |
| 103 | + process.stdout.write(`Fetching compute.toys/${id} ... `); |
| 104 | + const { code, meta } = await fetchToy(id); |
| 105 | + await writeFile(join(GEN_DIR, `ct-${id}.ts`), generateModule(id, code, meta)); |
| 106 | + console.log(`ok ("${meta.name}" by ${meta.profile?.username ?? "?"})`); |
| 107 | + } |
| 108 | + |
| 109 | + await writeFile(MANIFEST, JSON.stringify([...ids].sort((a, b) => a - b), null, 2) + "\n"); |
| 110 | + await writeFile(join(GEN_DIR, "index.ts"), generateIndex(ids)); |
| 111 | + console.log(`\nGenerated ${ids.length} shader(s) in ${GEN_DIR}`); |
| 112 | +} |
| 113 | + |
| 114 | +main().catch((err) => { |
| 115 | + console.error(err); |
| 116 | + process.exit(1); |
| 117 | +}); |
0 commit comments