Variants no longer live in the source tree. The Vite plugin's buildStart
hook generates them into node_modules/.cache/@koehler8/cms/image-variants/
{siteKey}/assets/img/..., and a third import.meta.glob in the asset-resolver
virtual module surfaces them through Vite's normal pipeline (hashed,
emitted to dist/assets/). The cache mirrors the site's `assets/` layout
so normalizeAssetKey strips the same `assets/` prefix and registers
variants under canonical `img/...` keys without resolver changes.
End state per site: site/assets/img/logo.png and friends are the only
thing in source control. No _source/ directory, no committed variants,
no preBuild script step, no amplify.yml change beyond removal.
Breaking changes:
- bin/cms-generate-image-variants.js removed. Sites that still reference
it in package.json scripts or amplify.yml will fail at command lookup
until they remove the references.
- _source/ is no longer read by the pipeline. Sites with files only in
_source/ get broken image URLs (with a loud warning + migration
instructions on every build).
Migration: `npx @koehler8/cms migrate-image-variants` from each site
directory. Idempotent. Promotes _source/ files, deletes beta.26 auto-copy
duplicates, untracks committed variants, drops scripts/amplify lines,
bumps cms range, runs npm install.
Architecture:
- scripts/image-variants/ split into config, classifier, planner,
renderer, manifest, cache, reconcile, index modules.
- buildStart calls reconcileVariantCache: mtime-skip cache, orphan
eviction, manifest-tracked. Wrapped in try/catch so a pipeline
failure warns but doesn't kill the build.
- Sharp is lazy-imported inside reconcile — only loads when there's
actual rendering work, so cold dev start with a warm cache is fast.
- configureServer adds a debounced watcher on site/assets/img/** that
re-runs the pipeline + invalidates the asset virtual module + sends
full-reload on source-image change.
- The classifier distinguishes originals from variant-shaped files by
checking whether the trailing width number is in the configured
widths set. Edge case escape: rename source files that conflict.
Tests: 584 passing (45 new for the lifted pipeline modules + classifier;
19 obsolete tests for the old _source/-based pipeline removed).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>