mirror of
https://github.com/hanzo-apps/gallery.git
synced 2026-10-30 14:48:38 +00:00
Every capture in public/screenshots is .webp. gen-templates-json looked for .png, matched none of the 211 of them, and wrote hasScreenshot:false for every template in the catalog. The consumer draws a schematic when that is false, so the whole gallery rendered as coloured mockups while the real screenshots sat on disk beside it. Nothing failed, which is why it lasted: a catalog with no pictures is still a valid catalog, and the count it printed — "0 with screenshots" — was the only symptom, in a build log nobody reads. So the count is now a gate. Zero screenshots across a non-empty catalog exits non-zero, because that is what a wrong extension, a moved directory and an empty checkout all look like, and none of them announced themselves before. A partial miss warns and names the slugs. 42 templates, 42 with screenshots. Also removed, on request: quantum, blocks, matrix, mosaic, matrix-react, mosaic-react, prism, canvas, kalli, drive-html, prism-html, cipher-html, beta, beta-variant, hygge-bootstrap, soar-html, loop-html, cipher-cards-html, cipher-cards-react, and six folio pages. A slug lives in three places — the Template entry, its TEMPLATE_PORTS row, and its capture — so all three go, and a capture is deleted only once nothing still references it. That last rule earned itself immediately: mosaic-react carried screenshot:'mosaic', so removing mosaic first would have left a card pointing at a file that was gone.
73 lines
3.1 KiB
JavaScript
73 lines
3.1 KiB
JavaScript
#!/usr/bin/env node
|
|
// One source of truth -> canonical catalog API.
|
|
// Reads app/templates-data.ts (the SoT) and emits public/templates.json so
|
|
// gallery.hanzo.ai/templates.json is the machine-readable template catalog
|
|
// consumed by hanzo.app. Run as a prebuild step (npm run build).
|
|
//
|
|
// Each emitted record keeps the raw SoT fields and adds absolute URLs so
|
|
// consumers never have to know the gallery's internal path scheme:
|
|
// screenshotUrl -> https://gallery.hanzo.ai/screenshots/<screenshot>.webp
|
|
// templateUrl -> https://gallery.hanzo.ai/templates/<slug>
|
|
// repo -> https://github.com/hanzo-apps/<slug> (fork source)
|
|
|
|
import { readFileSync, writeFileSync, existsSync } from 'node:fs';
|
|
import { fileURLToPath } from 'node:url';
|
|
import { dirname, join } from 'node:path';
|
|
import { templates } from '../app/templates-data.ts';
|
|
|
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
const root = join(here, '..');
|
|
const GALLERY_ORIGIN = process.env.GALLERY_ORIGIN || 'https://gallery.hanzo.ai';
|
|
const screenshotsDir = join(root, 'public', 'screenshots');
|
|
|
|
// WEBP, which is what every capture in public/screenshots actually is. This
|
|
// line spelled .png, matched nothing, and wrote hasScreenshot:false for all of
|
|
// them — and the consumer draws a schematic when that is false, so the whole
|
|
// gallery rendered as coloured mockups while 211 real screenshots sat on disk
|
|
// beside it. Nothing failed, because a catalog with no pictures is still a
|
|
// catalog. See the count check below, which is the half that was missing.
|
|
const enriched = templates.map((t) => {
|
|
const shotFile = `${t.screenshot}.webp`;
|
|
const hasShot = existsSync(join(screenshotsDir, shotFile));
|
|
return {
|
|
...t,
|
|
screenshotUrl: `${GALLERY_ORIGIN}/screenshots/${shotFile}`,
|
|
hasScreenshot: hasShot,
|
|
templateUrl: `${GALLERY_ORIGIN}/templates/${t.slug}`,
|
|
repo: `https://github.com/hanzo-apps/${t.slug}`,
|
|
};
|
|
});
|
|
|
|
const withShots = enriched.filter((t) => t.hasScreenshot).length;
|
|
|
|
// A catalog where NOTHING has a picture is a broken build, not a thin one. It is
|
|
// also exactly what a wrong extension, a moved directory or an empty checkout
|
|
// all look like, and none of them announced themselves before.
|
|
if (enriched.length > 0 && withShots === 0) {
|
|
console.error(
|
|
`gen-templates-json: ${enriched.length} templates and not one screenshot in ${screenshotsDir}.\n` +
|
|
`Every card would render as a drawn placeholder. Check the file extension and the directory.`,
|
|
);
|
|
process.exit(1);
|
|
}
|
|
|
|
const missing = enriched.filter((t) => !t.hasScreenshot).map((t) => t.slug);
|
|
if (missing.length) {
|
|
console.warn(`gen-templates-json: no screenshot for ${missing.length}: ${missing.join(', ')}`);
|
|
}
|
|
|
|
const payload = {
|
|
version: 1,
|
|
origin: GALLERY_ORIGIN,
|
|
count: enriched.length,
|
|
screenshots: withShots,
|
|
generatedAt: new Date().toISOString(),
|
|
templates: enriched,
|
|
};
|
|
|
|
const out = join(root, 'public', 'templates.json');
|
|
writeFileSync(out, JSON.stringify(payload, null, 2));
|
|
console.log(
|
|
`gen-templates-json: wrote ${enriched.length} templates (${withShots} with screenshots) -> public/templates.json`,
|
|
);
|