diff --git a/public/sw.js b/public/sw.js index c85334d78..2e4cbf415 100644 --- a/public/sw.js +++ b/public/sw.js @@ -27,8 +27,16 @@ try { } catch { // Fall through to the literal fallback below. } +// The `|| "..."` fallback literal is the value used only when the +// `importScripts('/sw-version.js')` above is absent (dev with no `prebuild` +// step, or a 404 on the generated file). `scripts/generate-sw-version.mjs` +// rewrites it to the current `package.json` version on every prebuild — the +// marker below is the rewrite anchor — so in any shipped image the literal +// matches the active release and can never drift stale the way it did across +// v1.4.38.4 → v1.4.42. Do not hand-edit; bump `package.json` and rebuild. const CACHE_VERSION = - (typeof self !== "undefined" && self.__APP_VERSION__) || "v1.4.43"; + (typeof self !== "undefined" && self.__APP_VERSION__) || + /* @sw-version-fallback */ "v1.16.12"; const STATIC_CACHE = `healthlog-static-${CACHE_VERSION}`; const PAGE_CACHE = `healthlog-pages-${CACHE_VERSION}`; const MAX_STATIC_ENTRIES = 150; diff --git a/scripts/generate-sw-version.mjs b/scripts/generate-sw-version.mjs index 5157f051b..b206edad8 100644 --- a/scripts/generate-sw-version.mjs +++ b/scripts/generate-sw-version.mjs @@ -23,6 +23,7 @@ const __dirname = dirname(fileURLToPath(import.meta.url)); const repoRoot = join(__dirname, ".."); const pkgPath = join(repoRoot, "package.json"); const outPath = join(repoRoot, "public", "sw-version.js"); +const swPath = join(repoRoot, "public", "sw.js"); const pkg = JSON.parse(readFileSync(pkgPath, "utf8")); const version = `v${pkg.version}`; @@ -33,5 +34,23 @@ const body = `self.__APP_VERSION__ = ${JSON.stringify(version)};\n`; writeFileSync(outPath, banner + body, "utf8"); +// Also re-anchor the in-`sw.js` fallback literal to the current version, so +// the literal used when `/sw-version.js` is missing (dev, or a 404 on the +// generated file) can never drift stale — it bit v1.4.38.4 → v1.4.42. The +// `/* @sw-version-fallback */` marker is the rewrite anchor; the literal that +// follows it is replaced in place. If the marker is gone the SW source has +// been restructured and the build must surface that rather than silently skip +// the re-anchor. +const sw = readFileSync(swPath, "utf8"); +const fallbackPattern = /(\/\* @sw-version-fallback \*\/\s*)"v[^"]*"/; +if (!fallbackPattern.test(sw)) { + process.stderr.write( + "generate-sw-version: @sw-version-fallback marker not found in public/sw.js\n", + ); + process.exit(1); +} +const rewritten = sw.replace(fallbackPattern, `$1${JSON.stringify(version)}`); +if (rewritten !== sw) writeFileSync(swPath, rewritten, "utf8"); + // One-line log so the build output shows the version we baked in. -process.stdout.write(`sw-version.js → ${version}\n`); +process.stdout.write(`sw-version.js → ${version} (sw.js fallback re-anchored)\n`); diff --git a/src/__tests__/service-worker.test.ts b/src/__tests__/service-worker.test.ts index 8601c0a41..cde471c89 100644 --- a/src/__tests__/service-worker.test.ts +++ b/src/__tests__/service-worker.test.ts @@ -24,10 +24,16 @@ const SW_SOURCE = readFileSync( ); const ORIGIN = "https://app.example"; -// `importScripts` throws in the sandbox, so the literal fallback version -// is active and the cache names are deterministic. -const CURRENT_PAGE_CACHE = "healthlog-pages-v1.4.43"; -const CURRENT_STATIC_CACHE = "healthlog-static-v1.4.43"; +// `importScripts` throws in the sandbox, so the in-`sw.js` fallback literal +// is active and the cache names are deterministic. The fallback is +// re-anchored to `package.json` on every prebuild by +// `scripts/generate-sw-version.mjs`; read it straight from the SW source so +// this test never drifts from the literal it is exercising. +const FALLBACK_VERSION = /\/\* @sw-version-fallback \*\/\s*"(v[^"]*)"/.exec( + SW_SOURCE, +)![1]; +const CURRENT_PAGE_CACHE = `healthlog-pages-${FALLBACK_VERSION}`; +const CURRENT_STATIC_CACHE = `healthlog-static-${FALLBACK_VERSION}`; class FakeCache { map = new Map();