Updater.update() now returns what happened: per-phase timings (fetch,
localize, process, publish, images), which languages had to be fetched,
which locale documents changed, and how many images were downloaded.
Subclasses add their own details (stages seeded and new stages, festival
rankings fetched). updateAll() carries each summary in its results, so the
Worker's "Updaters finished" log entry contains the whole picture per
updater. FilesystemStorage.writeJson now skips unchanged content like
BucketStorage does, so "locale documents written" means the same locally.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
One skill image is no longer served by SplatNet's CDN. The updater's
image step falls back to a bundled copy: under Node, cdn.node.js reads
the PNG from the repo; in the Worker, cdn.worker.js imports it as bytes
through a wrangler "Data" module rule. A "#cdn-images" import condition
in package.json selects the right one, so the PNG in src/common/cdn stays
the only artifact and nothing is generated or copied by hand.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
utilities.js is the filesystem helper module the posters still use, and
the schedules updater only needed the time helper from it. With the
helper in its own module the updater import graph no longer pulls in
node:fs, which is what the Worker bundle needs. Also applies the
remaining ESLint fixes outside src/web.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
ESLint moves to a flat config (eslint.config.mjs) with the same rules,
run directly by npm run lint; @vue/cli-plugin-eslint is dropped since the
Vue build does not need it. With a parser that understands import
attributes, the bundled data files (brands, skills, gear, finalFest,
shiftyStations) are imported with `with { type: 'json' }`, which Node and
the Workers bundler both support, instead of being read through node:fs.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Updaters take a storage object instead of touching the filesystem: a
publicStorage (what the site serves: data/, assets/) and a privateStorage
(updater state). Two implementations share one small interface
(exists / readJson / writeJson / readBytes / writeText / writeBytes):
- FilesystemStorage over a directory, used by npm run splatnet, so dist/
and storage/ end up exactly as they always have and the S3 sync is
untouched;
- BucketStorage over an R2 bucket binding (MemoryBucket in tests), with a
thin per-run cache: exists() lists a directory once and answers from
memory, readJson() parses a document once, and writeJson() skips the put
when the content is unchanged.
The updater base class and localization processor keep their original
structure with awaits added; the processor reads its document lazily and
writes once per update, as splatoon3.ink's does. Objects written to a
bucket carry the content type and cache-control the S3 sync used to apply.
Tests cover the storage contract (run against both implementations), the
BucketStorage cache behaviour, MemoryBucket's R2-style listing, the
localization processor, the updater base, and each concrete updater.
scripts/compare-data.mjs compares two data directories ignoring SplatNet's
random ordering and ICS timestamps.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>