site: hero auto-discovery, pick.json hero selection, amazonLink + shortlist fields

- src/lib/hero.ts: review heroes are now auto-discovered via import.meta.glob
  over reviews/*/hero.<ext>; only legacy non-hero-named assets remain as
  explicit overrides. New reviews need zero registration.
- Pick of the day moves from a hardcoded HERO_SLUG in index.astro to
  src/data/pick.json (content edit, not code edit); dev set-pick API and
  the review-page button now write pick.json. Fallback: latest review.
- Schemas: finds gain shortlist (bool) + amazonLink; reviews gain amazonLink.
- Review + find pages render an 'Also on Amazon' affiliate link when
  amazonLink is set, with shared disclosure handling.
- Both hero globs now include .avif (two June finds ship avif heroes).
This commit is contained in:
2026-07-03 15:16:25 -04:00
parent 5a4de71ad4
commit 80b3920715
8 changed files with 86 additions and 53 deletions
+2 -6
View File
@@ -12,7 +12,7 @@ const EXCLUDED_PATHS = new Set([
const FIND_ROOT = path.resolve('src/content/find');
const REVIEW_ROOT = path.resolve('src/content/reviews');
const HOME_PAGE = path.resolve('src/pages/index.astro');
const PICK_FILE = path.resolve('src/data/pick.json');
const VALID_SLUG = /^[a-z0-9][a-z0-9-]*$/;
const VALID_TAG = /^[a-z0-9][a-z0-9-]{0,40}$/i;
@@ -118,11 +118,7 @@ const setPickApi = {
if (!reviewPath.startsWith(REVIEW_ROOT + path.sep)) return send(res, 400, { error: 'path escape' });
try { await stat(reviewPath); } catch { return send(res, 404, { error: 'review not found' }); }
const text = await readFile(HOME_PAGE, 'utf8');
const re = /^(const HERO_SLUG = ')([^']+)(';)/m;
if (!re.test(text)) return send(res, 422, { error: 'HERO_SLUG line not found in src/pages/index.astro' });
const out = text.replace(re, (_, a, _b, c) => `${a}${slug}${c}`);
await writeFile(HOME_PAGE, out, 'utf8');
await writeFile(PICK_FILE, JSON.stringify({ hero: slug }, null, 2) + '\n', 'utf8');
return send(res, 200, { slug });
} catch (err) {