README is a short front page (about 120 lines) with one real visual per section; every former section moved unchanged into docs/*.md with a docs/README.md index (journey, rejections, getting-started, commands, growth, studios, store-art, listing-languages, api, skills, mcp, safety, faq, project). The site tells the same story on one page: the teaser in the hero, real CLI output as text, the desktop app as a section with light/dark captures of eight screens and the per-system downloads (desktop/latest.json); /desktop/ redirects to /#download. New capture tools: tools/site-desktop-shots.ts (fixture-mode desktop frames) and tools/site-term-shots.ts (real CLI output as text and WebP). tests/docs-links.test.ts checks README, docs and site links resolve.
66 lines
4.4 KiB
TypeScript
66 lines
4.4 KiB
TypeScript
#!/usr/bin/env bun
|
|
// Renders the diagrams in docs/diagrams/*.mmd with the Mermaid CLI (@mermaid-js/mermaid-cli, a devDependency; never
|
|
// shipped). Plain SVG text (no HTML labels), a fixed dark canvas so it reads the same on light and dark pages.
|
|
// bun tools/diagrams.ts
|
|
// journey.mmd -> docs/img/journey.svg (docs/journey.md), site/img/journey.svg (wide screens) and site/img/journey-tall.svg
|
|
// (phones: every row turned into a column). Chrome: STOREBOUND_CHROME, Playwright's Chromium, or Google Chrome.
|
|
import { copyFileSync, existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
import { tmpdir } from 'node:os';
|
|
import { join } from 'node:path';
|
|
|
|
const root = new URL('..', import.meta.url).pathname;
|
|
const BG = '#0f172a';
|
|
|
|
function chrome(): string {
|
|
const cands = [process.env.STOREBOUND_CHROME ?? ''];
|
|
try { cands.push((require('playwright') as typeof import('playwright')).chromium.executablePath()); } catch { /* not installed */ }
|
|
cands.push('/Applications/Google Chrome.app/Contents/MacOS/Google Chrome', '/usr/bin/google-chrome', '/usr/bin/chromium', 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe');
|
|
const home = process.env.HOME ?? '';
|
|
// a newer Playwright Chromium than the installed playwright expects is fine for Mermaid
|
|
for (const base of [join(home, 'Library/Caches/ms-playwright'), join(home, '.cache/ms-playwright')]) {
|
|
if (!existsSync(base)) continue;
|
|
for (const d of Array.from(new Bun.Glob('chromium-*/chrome-*/**/{Google Chrome for Testing,chrome}').scanSync({ cwd: base, onlyFiles: true }))) cands.push(join(base, d));
|
|
}
|
|
const hit = cands.find(c => c && existsSync(c));
|
|
if (!hit) throw new Error('diagrams: no Chrome found (set STOREBOUND_CHROME or run `bunx playwright install chromium`)');
|
|
return hit;
|
|
}
|
|
|
|
const mermaidConfig = {
|
|
theme: 'base',
|
|
htmlLabels: false,
|
|
flowchart: { htmlLabels: false, curve: 'basis', nodeSpacing: 28, rankSpacing: 36, padding: 14, wrappingWidth: 400 },
|
|
themeVariables: {
|
|
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif', fontSize: '15px',
|
|
background: BG, primaryColor: '#1e293b', primaryTextColor: '#e2e8f0', primaryBorderColor: '#475569',
|
|
lineColor: '#94a3b8', textColor: '#e2e8f0', titleColor: '#f8fafc', clusterBkg: '#111a2e', clusterBorder: '#334155',
|
|
},
|
|
};
|
|
|
|
function render(src: string, out: string, tmp: string, exe: string) {
|
|
const input = join(tmp, 'in.mmd');
|
|
writeFileSync(input, src);
|
|
writeFileSync(join(tmp, 'mermaid.json'), JSON.stringify(mermaidConfig));
|
|
writeFileSync(join(tmp, 'puppeteer.json'), JSON.stringify({ executablePath: exe, args: ['--no-sandbox'] }));
|
|
const r = Bun.spawnSync(['node', join(root, 'node_modules/@mermaid-js/mermaid-cli/src/cli.js'), '-q', '-i', input, '-o', out,
|
|
'-c', join(tmp, 'mermaid.json'), '-p', join(tmp, 'puppeteer.json'), '-b', BG], { stdout: 'inherit', stderr: 'inherit' });
|
|
if (r.exitCode !== 0) throw new Error(`diagrams: mmdc failed for ${out}`);
|
|
// a self-describing image: title for screen readers, the dark canvas filled in (some viewers drop the background style)
|
|
let svg = readFileSync(out, 'utf8');
|
|
// Mermaid embeds its default web font; the diagram uses the system font stack instead, so drop the font files
|
|
svg = svg.replace(/@font-face\s*\{[^}]*\}/g, '');
|
|
// a fixed intrinsic size (from the viewBox) so <img width height> and README renderers keep the aspect ratio
|
|
const [, , w, h] = (svg.match(/viewBox="([^"]+)"/)?.[1] ?? '0 0 960 560').split(/\s+/).map(Number);
|
|
svg = svg.replace(/^<svg([^>]*)>/, (_m, a: string) => `<svg${a.replace(/ role="[^"]*"/, '').replace('width="100%"', `width="${Math.ceil(w!)}" height="${Math.ceil(h!)}"`).replace(/max-width: [\d.]+px; ?/, '')} role="img"><title>storebound · the journey from zero to Submit for review</title>`);
|
|
writeFileSync(out, svg);
|
|
console.log(`diagrams: ${out.replace(root, '')} (${(svg.length / 1024).toFixed(1)} KB)`);
|
|
}
|
|
|
|
const exe = chrome();
|
|
const tmp = mkdtempSync(join(tmpdir(), 'sb-diagrams-'));
|
|
try {
|
|
const journey = readFileSync(join(root, 'docs/diagrams/journey.mmd'), 'utf8');
|
|
render(journey, join(root, 'docs/img/journey.svg'), tmp, exe);
|
|
copyFileSync(join(root, 'docs/img/journey.svg'), join(root, 'site/img/journey.svg'));
|
|
render(journey.replace(/direction LR/g, 'direction TB'), join(root, 'site/img/journey-tall.svg'), tmp, exe);
|
|
} finally { rmSync(tmp, { recursive: true, force: true }); }
|