#!/usr/bin/env node /** * generate.mjs — turn the cast definitions in `casts.mjs` into asciinema * `.cast` recordings under `public/casts/`, using autocast (ADR-website-001 * §2; driver chosen by the 2026-06-10 spike — autocast drives the full-screen * TUI correctly, asciinema-automation does not). * * Usage: * pnpm casts # regenerate every cast * pnpm casts quickstart # regenerate just one * * Requires `autocast` on PATH (cargo install autocast) and a built * `rdbms-playground` binary at ../target/debug (run `cargo build` at the repo * root first). The binary's dir is added to PATH for the autocast child so the * recording shows a clean `$ rdbms-playground` launch line. */ import { spawnSync } from 'node:child_process'; import { mkdirSync, writeFileSync, rmSync } from 'node:fs'; import { dirname, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; import os from 'node:os'; import { casts } from './casts.mjs'; const here = dirname(fileURLToPath(import.meta.url)); const websiteRoot = resolve(here, '..'); const repoRoot = resolve(websiteRoot, '..'); const binDir = resolve(repoRoot, 'target', 'debug'); const outDir = resolve(websiteRoot, 'public', 'casts'); const cargoBin = resolve(os.homedir(), '.cargo', 'bin'); /** YAML-escape a single character for a double-quoted scalar. */ function charKey(ch) { if (ch === '\\') return '"\\\\"'; if (ch === '"') return '"\\""'; return `"${ch}"`; } /** Named single keys → autocast control codes. */ const NAMED_KEYS = { Enter: '^M', Tab: '^I', }; /** Build the autocast `keys:` list (one entry per line) for a cast's steps. */ function keysFor(steps) { const keys = []; for (const step of steps) { if (step.wait != null) { keys.push(`${step.wait}ms`); continue; } if (step.key != null) { const code = NAMED_KEYS[step.key]; if (!code) throw new Error(`unknown key: ${step.key}`); keys.push(code); if (step.after != null) keys.push(`${step.after}ms`); continue; } if (step.type != null) { for (const ch of step.type) keys.push(charKey(ch)); if (step.enter !== false) keys.push('^M'); // Enter = CR (the TUI submits on \r) if (step.after != null) keys.push(`${step.after}ms`); continue; } throw new Error(`unrecognised step: ${JSON.stringify(step)}`); } return keys; } function yamlFor(cast) { const keys = keysFor(cast.steps) .map((k) => ` - ${k}`) .join('\n'); return [ 'settings:', ` width: ${cast.width ?? 90}`, ` height: ${cast.height ?? 26}`, ` title: ${JSON.stringify(cast.title ?? cast.name)}`, ` type_speed: ${cast.typeSpeed ?? '45ms'}`, ' timeout: 90s', ' prompt: "$ "', 'instructions:', ' - !Interactive', ' command: rdbms-playground', ' keys:', keys, '', ].join('\n'); } const only = process.argv[2]; const selected = only ? casts.filter((c) => c.name === only) : casts; if (only && selected.length === 0) { console.error(`no cast named "${only}". Known: ${casts.map((c) => c.name).join(', ')}`); process.exit(1); } mkdirSync(outDir, { recursive: true }); const env = { ...process.env, PATH: `${binDir}:${cargoBin}:${process.env.PATH}` }; let failures = 0; for (const cast of selected) { const yamlPath = resolve(os.tmpdir(), `autocast-${cast.name}.yaml`); const outPath = resolve(outDir, `${cast.name}.cast`); writeFileSync(yamlPath, yamlFor(cast)); console.log(`▶ recording ${cast.name} → public/casts/${cast.name}.cast`); const res = spawnSync('autocast', ['--overwrite', yamlPath, outPath], { env, stdio: 'inherit', }); rmSync(yamlPath, { force: true }); if (res.status !== 0) { console.error(`✗ ${cast.name} failed (autocast exit ${res.status})`); failures += 1; } else { console.log(`✓ ${cast.name}`); } } if (failures) process.exit(1); console.log(`Done — ${selected.length} cast(s).`);