@broberg/pwa 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -8,6 +8,22 @@ as one small, framework- and bundler-agnostic package instead of a fifth copy.
8
8
  npm i @broberg/pwa
9
9
  ```
10
10
 
11
+ ## 0.9.0 — a stale page reloads itself fresh, once: `staleShellGuardScript()` (F021.13)
12
+
13
+ After a deploy, an installed app can start on its stored copy of the page. That copy points at `/assets/` files the deploy removed, they 404, and the window stays **white**. A plain reload can get the same stored page back. This guard is helpdesk's (F047.5/F047.6, in production), ported as is:
14
+
15
+ ```ts
16
+ import { staleShellGuardScript } from "@broberg/pwa/manifest";
17
+
18
+ // Vite: first thing in <head>, before your app script
19
+ transformIndexHtml: () => [{ tag: "script", children: staleShellGuardScript(), injectTo: "head-prepend" }],
20
+ // Next: <Script id="stale-shell" strategy="beforeInteractive">{staleShellGuardScript()}</Script>
21
+ ```
22
+
23
+ When one of the page's OWN same-origin `/assets/` scripts or stylesheets fails to load, it navigates to the same URL with `?_v=<time>` (an address the browser store has never seen), at most once per 30 s, and removes `_v` from the address bar on the fresh page. Images, foreign scripts and files outside `/assets/` never trigger it. `staleShellGuardScript({ storageKey })` sets your own sessionStorage key.
24
+
25
+ On Fly Live, `@broberg/deploy-core` ≥ 0.8.2 also serves old hashed assets from the kept deploys (F033.20), so the 404 does not happen in the first place. This guard covers every other host.
26
+
11
27
  ## 0.8.0 — native icon sets from the same SVG (F021.12)
12
28
 
13
29
  For Capacitor (iOS/Android) and Tauri apps, so none of them keeps its own icon script:
package/dist/bin.cjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/manifest.ts","../src/native.ts","../src/raster.ts","../src/cli.ts","../src/bin.ts"],"names":["deflateSync","readFileSync","mkdirSync","dirname","join","writeFileSync","basename"],"mappings":"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","file":"bin.cjs","sourcesContent":["/**\n * @broberg/pwa/manifest — the PWA *install* half (the update half lives in the\n * root export). A dependency-free manifest/meta factory + an SVG-based icon\n * generator, so a repo stops hand-rolling `app/manifest.ts`, a\n * `gen-pwa-icons.cjs` script, and a pile of apple-touch `<meta>` tags.\n *\n * Everything here is PURE string/object generation — no filesystem, no\n * rasteriser, no runtime deps — so it runs in Node, Bun, edge, a build script,\n * or the browser, and every piece is offline-unit-testable. Icons are emitted\n * as self-contained SVG documents. iOS does NOT use an SVG apple-touch-icon —\n * rasterise with `@broberg/pwa/raster` / the `pwa-icons` bin (F021.9).\n */\n\n// ---------------------------------------------------------------------------\n// Manifest\n// ---------------------------------------------------------------------------\n\nexport type Display = \"standalone\" | \"fullscreen\" | \"minimal-ui\" | \"browser\";\n\nexport interface ManifestIcon {\n src: string;\n sizes: string;\n type: string;\n /** \"any\" | \"maskable\" | \"monochrome\" (space-separated allowed). */\n purpose?: string;\n}\n\nexport interface WebAppManifest {\n name: string;\n short_name: string;\n start_url: string;\n scope: string;\n display: Display;\n theme_color: string;\n background_color: string;\n icons: ManifestIcon[];\n description?: string;\n lang?: string;\n orientation?: string;\n categories?: string[];\n id?: string;\n [key: string]: unknown;\n}\n\nexport interface DefineManifestOptions {\n /** App name (required). */\n name: string;\n /** Home-screen label. Default: `name`. */\n shortName?: string;\n /** Default `/`. */\n startUrl?: string;\n /** Default `/`. */\n scope?: string;\n /** Default `standalone`. */\n display?: Display;\n /** Address-bar / task-switcher colour. Default `#ffffff`. */\n themeColor?: string;\n /** Splash background. Default `#ffffff`. */\n backgroundColor?: string;\n description?: string;\n lang?: string;\n orientation?: string;\n categories?: string[];\n id?: string;\n /** Manifest `icons[]` — typically `buildIconSet(...).icons`. */\n icons?: ManifestIcon[];\n /** Escape hatch: extra top-level manifest members, merged last. */\n extra?: Record<string, unknown>;\n}\n\n/**\n * Build a spec-valid Web App Manifest object. Required members are defaulted so\n * a one-liner produces an installable manifest; every default is overridable\n * and `extra` merges last for anything not modelled (shortcuts, screenshots…).\n */\nexport function defineManifest(opts: DefineManifestOptions): WebAppManifest {\n if (!opts?.name) throw new Error(\"defineManifest: `name` is required\");\n const manifest: WebAppManifest = {\n name: opts.name,\n short_name: opts.shortName ?? opts.name,\n start_url: opts.startUrl ?? \"/\",\n scope: opts.scope ?? \"/\",\n display: opts.display ?? \"standalone\",\n theme_color: opts.themeColor ?? \"#ffffff\",\n background_color: opts.backgroundColor ?? \"#ffffff\",\n icons: opts.icons ?? [],\n };\n if (opts.description) manifest.description = opts.description;\n if (opts.lang) manifest.lang = opts.lang;\n if (opts.orientation) manifest.orientation = opts.orientation;\n if (opts.categories) manifest.categories = opts.categories;\n if (opts.id) manifest.id = opts.id;\n return { ...manifest, ...(opts.extra ?? {}) };\n}\n\n/** JSON-stringify a manifest for a `manifest.webmanifest` file. */\nexport function serializeManifest(\n manifest: WebAppManifest,\n opts: { pretty?: boolean } = {},\n): string {\n return JSON.stringify(manifest, null, opts.pretty === false ? undefined : 2);\n}\n\n// ---------------------------------------------------------------------------\n// Icon generation (SVG, zero-dep)\n// ---------------------------------------------------------------------------\n\nexport interface GeneratedIcon {\n /** URL path, e.g. `/icons/icon-192.svg`. */\n path: string;\n /** The SVG document. */\n content: string;\n type: \"image/svg+xml\";\n size: number;\n purpose: \"any\" | \"maskable\";\n}\n\nexport interface IconSet {\n files: GeneratedIcon[];\n /** Ready to drop into `defineManifest({ icons })`. */\n icons: ManifestIcon[];\n}\n\nexport interface IconSetOptions {\n /**\n * Source artwork as a full SVG document (`<svg …>…</svg>`). Embedded via a\n * data-URI `<image>` so it scales cleanly at any size regardless of its\n * internal coordinate system. Mutually exclusive with `monogram`.\n */\n svg?: string;\n /** Fallback artwork: 1–3 letters rendered centred (e.g. \"AK\"). */\n monogram?: string;\n /** Foreground (monogram text) colour. Default `#ffffff`. */\n color?: string;\n /** Background fill. Default `#111827`. */\n background?: string;\n /** Icon sizes to emit. Default `[180, 192, 512]` (180 = apple-touch). */\n sizes?: number[];\n /** Also emit a maskable 512 with a padded safe-zone. Default `true`. */\n maskable?: boolean;\n /** URL prefix for every icon `src`. Default `/icons`. */\n basePath?: string;\n}\n\nconst DEFAULT_SIZES = [180, 192, 512];\n\nfunction escapeXml(s: string): string {\n return s.replace(/[<>&\"']/g, (c) =>\n c === \"<\" ? \"&lt;\" : c === \">\" ? \"&gt;\" : c === \"&\" ? \"&amp;\" : c === '\"' ? \"&quot;\" : \"&apos;\",\n );\n}\n\n/** UTF-8-safe base64 for the data-URI embed (works in Node/Bun/browser). */\nfunction toBase64(s: string): string {\n // Reached via globalThis so the package needs no @types/node (browser-safe).\n const g = globalThis as unknown as {\n Buffer?: { from(input: string, enc: string): { toString(enc: string): string } };\n btoa?: (data: string) => string;\n };\n if (typeof g.Buffer !== \"undefined\") return g.Buffer.from(s, \"utf-8\").toString(\"base64\");\n const bytes = new TextEncoder().encode(s);\n let bin = \"\";\n for (const b of bytes) bin += String.fromCharCode(b);\n return g.btoa!(bin);\n}\n\n/** Render one square SVG icon document at `size`. `inset` = safe-zone fraction (maskable). */\nfunction renderIcon(size: number, inset: number, opts: IconSetOptions): string {\n const bg = opts.background ?? \"#111827\";\n const fg = opts.color ?? \"#ffffff\";\n const pad = Math.round(size * inset);\n const inner = size - pad * 2;\n let art: string;\n if (opts.svg) {\n const href = `data:image/svg+xml;base64,${toBase64(opts.svg)}`;\n art = `<image x=\"${pad}\" y=\"${pad}\" width=\"${inner}\" height=\"${inner}\" href=\"${href}\" preserveAspectRatio=\"xMidYMid meet\"/>`;\n } else {\n const text = escapeXml((opts.monogram ?? \"\").slice(0, 3).toUpperCase());\n // Font sized to the safe inner box; a touch smaller for breathing room.\n const fontSize = Math.round(inner * (text.length >= 3 ? 0.42 : 0.52));\n art =\n `<text x=\"50%\" y=\"50%\" fill=\"${fg}\" font-family=\"system-ui, -apple-system, Segoe UI, Roboto, sans-serif\" ` +\n `font-weight=\"700\" font-size=\"${fontSize}\" text-anchor=\"middle\" dominant-baseline=\"central\">${text}</text>`;\n }\n return (\n `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"${size}\" height=\"${size}\" viewBox=\"0 0 ${size} ${size}\">` +\n `<rect width=\"${size}\" height=\"${size}\" fill=\"${bg}\"/>` +\n art +\n `</svg>`\n );\n}\n\n/**\n * Generate a full icon set (SVG) + the matching manifest `icons[]`. Deterministic,\n * zero-dep. Give it either a source `svg` or a `monogram`; it emits apple-touch\n * (180), 192, 512 by default plus a padded maskable-512.\n */\nexport function buildIconSet(opts: IconSetOptions): IconSet {\n if (!opts?.svg && !opts?.monogram) {\n throw new Error(\"buildIconSet: provide either `svg` or `monogram`\");\n }\n const base = (opts.basePath ?? \"/icons\").replace(/\\/$/, \"\");\n const sizes = opts.sizes ?? DEFAULT_SIZES;\n const files: GeneratedIcon[] = [];\n const icons: ManifestIcon[] = [];\n\n for (const size of sizes) {\n const path = `${base}/icon-${size}.svg`;\n files.push({ path, content: renderIcon(size, 0, opts), type: \"image/svg+xml\", size, purpose: \"any\" });\n // apple-touch-icon is referenced via a <link> (see pwaMetaTags), not the\n // manifest, so only real manifest sizes go into icons[].\n if (size !== 180) {\n icons.push({ src: path, sizes: `${size}x${size}`, type: \"image/svg+xml\", purpose: \"any\" });\n }\n }\n\n if (opts.maskable !== false) {\n // Maskable safe-zone: keep content within the central 80% (10% inset).\n const path = `${base}/icon-maskable-512.svg`;\n files.push({ path, content: renderIcon(512, 0.1, opts), type: \"image/svg+xml\", size: 512, purpose: \"maskable\" });\n icons.push({ src: path, sizes: \"512x512\", type: \"image/svg+xml\", purpose: \"maskable\" });\n }\n\n return { files, icons };\n}\n\n// ---------------------------------------------------------------------------\n// Head meta tags\n// ---------------------------------------------------------------------------\n\nexport interface MetaTag {\n tag: \"meta\" | \"link\";\n attrs: Record<string, string>;\n}\n\nexport interface MetaTagsOptions {\n /** `<meta name=\"theme-color\">`. Default `#ffffff`. */\n themeColor?: string;\n /** apple-touch-icon href. Default `/icons/icon-180.svg`. */\n appleTouchIcon?: string;\n /** Manifest href for the `<link rel=\"manifest\">`. Default `/manifest.webmanifest`. */\n manifest?: string;\n /** apple-mobile-web-app-title. Default: omitted. */\n title?: string;\n /** apple-mobile-web-app-status-bar-style. Default `default`. */\n statusBarStyle?: \"default\" | \"black\" | \"black-translucent\";\n /** Emit the `*-web-app-capable` tags. Default `true`. */\n capable?: boolean;\n}\n\n/**\n * The head tags a PWA needs beyond the manifest link — returned as typed\n * descriptors so a consumer renders them in Next.js `metadata`, a Hono/JSX\n * head, or plain HTML. No framework assumption.\n */\nexport function pwaMetaTags(opts: MetaTagsOptions = {}): MetaTag[] {\n const tags: MetaTag[] = [];\n tags.push({ tag: \"link\", attrs: { rel: \"manifest\", href: opts.manifest ?? \"/manifest.webmanifest\" } });\n tags.push({ tag: \"meta\", attrs: { name: \"theme-color\", content: opts.themeColor ?? \"#ffffff\" } });\n // F021.11 — sizes: the icon is 180; iOS picks by it when several are linked.\n tags.push({ tag: \"link\", attrs: { rel: \"apple-touch-icon\", sizes: \"180x180\", href: opts.appleTouchIcon ?? \"/icons/icon-180.svg\" } });\n if (opts.capable !== false) {\n tags.push({ tag: \"meta\", attrs: { name: \"mobile-web-app-capable\", content: \"yes\" } });\n tags.push({ tag: \"meta\", attrs: { name: \"apple-mobile-web-app-capable\", content: \"yes\" } });\n tags.push({\n tag: \"meta\",\n attrs: { name: \"apple-mobile-web-app-status-bar-style\", content: opts.statusBarStyle ?? \"default\" },\n });\n }\n if (opts.title) {\n tags.push({ tag: \"meta\", attrs: { name: \"apple-mobile-web-app-title\", content: opts.title } });\n }\n return tags;\n}\n","/**\n * @broberg/pwa/raster — F021.12: the NATIVE icon sets, from the same SVG the web\n * icons come from, so no Capacitor or Tauri app keeps its own icon script.\n *\n * ios/AppIcon.appiconset/ AppIcon-1024.png + Contents.json (Xcode 14+ single size)\n * android/mipmap-anydpi-v26/ ic_launcher.xml, ic_launcher_round.xml (adaptive)\n * android/mipmap-<dpi>/ ic_launcher_foreground.png, ic_launcher.png, ic_launcher_round.png\n * android/values/ ic_launcher_background.xml\n * desktop/ icon.ico, icon.icns\n *\n * Two platform rules decide the code, and both fail silently if ignored:\n * - App Store rejects an app icon with an alpha channel. resvg always renders\n * RGBA, so the iOS PNG is RE-ENCODED as RGB (colour type 2) — flattening onto\n * the background is not enough on its own, the channel must be gone.\n * - Android crops an adaptive icon's 108dp layer to whatever mask the launcher\n * uses; only the inner 66dp is guaranteed visible. The motif is drawn inside it.\n *\n * `background` is REQUIRED: a white symbol on a transparent canvas renders as an\n * empty icon (measured by buzzalert with `tauri icon`, 25/10).\n *\n * ICO and ICNS are written as plain byte containers holding PNGs — no dependency.\n */\nimport { deflateSync } from \"node:zlib\";\n\nexport interface NativeIconFile {\n /** Path relative to the output directory, e.g. `ios/AppIcon.appiconset/AppIcon-1024.png`. */\n path: string;\n content: Uint8Array;\n}\n\nexport interface NativeIconOptions {\n /** Hex colour behind the motif (`#RRGGBB`). Required — see the module comment. */\n background: string;\n /** Default: all three. */\n platforms?: Array<\"ios\" | \"android\" | \"desktop\">;\n}\n\nexport const NATIVE_BACKGROUND_REQUIRED =\n \"nativeIconSet needs `background` (#RRGGBB): a white symbol on a transparent canvas renders as an empty icon\";\n\nconst HEX = /^#[0-9a-f]{6}$/i;\n\n/** Android density → adaptive foreground px (108dp) and legacy launcher px (48dp). */\nexport const ANDROID_DENSITIES = [\n { dpi: \"mdpi\", foreground: 108, legacy: 48 },\n { dpi: \"hdpi\", foreground: 162, legacy: 72 },\n { dpi: \"xhdpi\", foreground: 216, legacy: 96 },\n { dpi: \"xxhdpi\", foreground: 324, legacy: 144 },\n { dpi: \"xxxhdpi\", foreground: 432, legacy: 192 },\n] as const;\n\n/** Share of the 108dp adaptive canvas that every launcher mask keeps visible. */\nexport const ANDROID_SAFE_ZONE = 66 / 108;\n\nexport const ICO_SIZES = [16, 32, 48, 256] as const;\n/** ICNS chunk type → pixel size (PNG payloads). */\nexport const ICNS_CHUNKS = [\n [\"ic11\", 32],\n [\"ic12\", 64],\n [\"ic07\", 128],\n [\"ic08\", 256],\n [\"ic13\", 256],\n [\"ic09\", 512],\n [\"ic14\", 512],\n [\"ic10\", 1024],\n] as const;\n\ntype Shape = \"square\" | \"circle\" | \"none\";\n\nfunction composeSvg(svg: string, size: number, opts: { background?: string; shape: Shape; scale: number }): string {\n const d = size * opts.scale;\n const o = (size - d) / 2;\n const bg =\n opts.shape === \"square\" && opts.background\n ? `<rect width=\"${size}\" height=\"${size}\" fill=\"${opts.background}\"/>`\n : opts.shape === \"circle\" && opts.background\n ? `<circle cx=\"${size / 2}\" cy=\"${size / 2}\" r=\"${size / 2}\" fill=\"${opts.background}\"/>`\n : \"\";\n const href = `data:image/svg+xml;base64,${Buffer.from(svg).toString(\"base64\")}`;\n return (\n `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"${size}\" height=\"${size}\" viewBox=\"0 0 ${size} ${size}\">` +\n `${bg}<image href=\"${href}\" x=\"${o}\" y=\"${o}\" width=\"${d}\" height=\"${d}\"/></svg>`\n );\n}\n\n// ---- PNG encoding (RGB, no alpha) -------------------------------------------\n\nconst CRC_TABLE = (() => {\n const t = new Uint32Array(256);\n for (let n = 0; n < 256; n++) {\n let c = n;\n for (let k = 0; k < 8; k++) c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;\n t[n] = c >>> 0;\n }\n return t;\n})();\n\nfunction crc32(bytes: Uint8Array): number {\n let c = 0xffffffff;\n for (const b of bytes) c = CRC_TABLE[(c ^ b) & 0xff]! ^ (c >>> 8);\n return (c ^ 0xffffffff) >>> 0;\n}\n\nfunction chunk(type: string, data: Uint8Array): Uint8Array {\n const out = new Uint8Array(12 + data.length);\n const v = new DataView(out.buffer);\n v.setUint32(0, data.length);\n for (let i = 0; i < 4; i++) out[4 + i] = type.charCodeAt(i);\n out.set(data, 8);\n v.setUint32(8 + data.length, crc32(out.subarray(4, 8 + data.length)));\n return out;\n}\n\n/** Encode RGBA pixels as an RGB PNG (colour type 2) — the alpha channel is dropped, not just made opaque. */\nexport function encodeRgbPng(rgba: Uint8Array, width: number, height: number): Uint8Array {\n const raw = new Uint8Array(height * (1 + width * 3));\n for (let y = 0; y < height; y++) {\n const row = y * (1 + width * 3);\n raw[row] = 0; // filter: none\n for (let x = 0; x < width; x++) {\n const s = (y * width + x) * 4;\n const t = row + 1 + x * 3;\n raw[t] = rgba[s]!;\n raw[t + 1] = rgba[s + 1]!;\n raw[t + 2] = rgba[s + 2]!;\n }\n }\n const ihdr = new Uint8Array(13);\n const v = new DataView(ihdr.buffer);\n v.setUint32(0, width);\n v.setUint32(4, height);\n ihdr[8] = 8; // bit depth\n ihdr[9] = 2; // colour type: truecolour, NO alpha\n const sig = Uint8Array.from([137, 80, 78, 71, 13, 10, 26, 10]);\n const parts = [sig, chunk(\"IHDR\", ihdr), chunk(\"IDAT\", new Uint8Array(deflateSync(raw))), chunk(\"IEND\", new Uint8Array())];\n const out = new Uint8Array(parts.reduce((n, p) => n + p.length, 0));\n let off = 0;\n for (const p of parts) {\n out.set(p, off);\n off += p.length;\n }\n return out;\n}\n\n// ---- containers --------------------------------------------------------------\n\n/** An ICO holding PNG images (Vista+ format). */\nexport function encodeIco(pngs: Array<{ size: number; png: Uint8Array }>): Uint8Array {\n const header = 6 + 16 * pngs.length;\n const total = header + pngs.reduce((n, p) => n + p.png.length, 0);\n const out = new Uint8Array(total);\n const v = new DataView(out.buffer);\n v.setUint16(0, 0, true); // reserved\n v.setUint16(2, 1, true); // type: icon\n v.setUint16(4, pngs.length, true);\n let off = header;\n pngs.forEach((p, i) => {\n const e = 6 + i * 16;\n out[e] = p.size >= 256 ? 0 : p.size; // 0 means 256\n out[e + 1] = p.size >= 256 ? 0 : p.size;\n v.setUint16(e + 4, 1, true); // colour planes\n v.setUint16(e + 6, 32, true); // bits per pixel\n v.setUint32(e + 8, p.png.length, true);\n v.setUint32(e + 12, off, true);\n out.set(p.png, off);\n off += p.png.length;\n });\n return out;\n}\n\n/** An ICNS holding PNG chunks. */\nexport function encodeIcns(chunks: Array<{ type: string; png: Uint8Array }>): Uint8Array {\n const total = 8 + chunks.reduce((n, c) => n + 8 + c.png.length, 0);\n const out = new Uint8Array(total);\n const v = new DataView(out.buffer);\n const tag = (at: number, s: string) => {\n for (let i = 0; i < 4; i++) out[at + i] = s.charCodeAt(i);\n };\n tag(0, \"icns\");\n v.setUint32(4, total);\n let off = 8;\n for (const c of chunks) {\n tag(off, c.type);\n v.setUint32(off + 4, 8 + c.png.length);\n out.set(c.png, off + 8);\n off += 8 + c.png.length;\n }\n return out;\n}\n\n// ---- the set -------------------------------------------------------------------\n\nconst enc = (s: string) => new TextEncoder().encode(s);\n\nconst ADAPTIVE_XML =\n `<?xml version=\"1.0\" encoding=\"utf-8\"?>\\n` +\n `<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\\n` +\n ` <background android:drawable=\"@color/ic_launcher_background\"/>\\n` +\n ` <foreground android:drawable=\"@mipmap/ic_launcher_foreground\"/>\\n` +\n `</adaptive-icon>\\n`;\n\nconst IOS_CONTENTS = JSON.stringify(\n {\n images: [{ filename: \"AppIcon-1024.png\", idiom: \"universal\", platform: \"ios\", size: \"1024x1024\" }],\n info: { author: \"@broberg/pwa\", version: 1 },\n },\n null,\n 2,\n);\n\n/**\n * Every native icon file for `svg`. Requires the optional peer @resvg/resvg-js,\n * like `rasterizeIconSet`.\n */\nexport async function nativeIconSet(svg: string, opts: NativeIconOptions): Promise<NativeIconFile[]> {\n if (!opts?.background || !HEX.test(opts.background)) throw new Error(NATIVE_BACKGROUND_REQUIRED);\n let Resvg: typeof import(\"@resvg/resvg-js\").Resvg;\n try {\n ({ Resvg } = await import(\"@resvg/resvg-js\"));\n } catch {\n throw new Error(\"nativeIconSet needs @resvg/resvg-js — install it as a dev dependency: npm i -D @resvg/resvg-js\");\n }\n const bg = opts.background;\n const want = new Set(opts.platforms ?? [\"ios\", \"android\", \"desktop\"]);\n const render = (size: number, shape: Shape, scale: number) =>\n new Resvg(composeSvg(svg, size, { background: bg, shape, scale }), { font: { loadSystemFonts: false } }).render();\n const png = (size: number, shape: Shape, scale: number) => new Uint8Array(render(size, shape, scale).asPng());\n\n const files: NativeIconFile[] = [];\n\n if (want.has(\"ios\")) {\n const r = render(1024, \"square\", 0.8);\n files.push(\n { path: \"ios/AppIcon.appiconset/AppIcon-1024.png\", content: encodeRgbPng(new Uint8Array(r.pixels), r.width, r.height) },\n { path: \"ios/AppIcon.appiconset/Contents.json\", content: enc(IOS_CONTENTS + \"\\n\") },\n );\n }\n\n if (want.has(\"android\")) {\n files.push(\n { path: \"android/mipmap-anydpi-v26/ic_launcher.xml\", content: enc(ADAPTIVE_XML) },\n { path: \"android/mipmap-anydpi-v26/ic_launcher_round.xml\", content: enc(ADAPTIVE_XML) },\n {\n path: \"android/values/ic_launcher_background.xml\",\n content: enc(\n `<?xml version=\"1.0\" encoding=\"utf-8\"?>\\n<resources>\\n <color name=\"ic_launcher_background\">${bg}</color>\\n</resources>\\n`,\n ),\n },\n );\n for (const d of ANDROID_DENSITIES) {\n files.push(\n { path: `android/mipmap-${d.dpi}/ic_launcher_foreground.png`, content: png(d.foreground, \"none\", ANDROID_SAFE_ZONE) },\n { path: `android/mipmap-${d.dpi}/ic_launcher.png`, content: png(d.legacy, \"square\", 0.7) },\n { path: `android/mipmap-${d.dpi}/ic_launcher_round.png`, content: png(d.legacy, \"circle\", 0.62) },\n );\n }\n }\n\n if (want.has(\"desktop\")) {\n files.push(\n { path: \"desktop/icon.ico\", content: encodeIco(ICO_SIZES.map((size) => ({ size, png: png(size, \"square\", 0.8) }))) },\n { path: \"desktop/icon.icns\", content: encodeIcns(ICNS_CHUNKS.map(([type, size]) => ({ type, png: png(size, \"square\", 0.8) }))) },\n );\n }\n\n return files;\n}\n","/**\n * @broberg/pwa/raster — F021.9: PNG icons, because iOS does not use an SVG\n * apple-touch-icon. «Add to Home Screen» on an iPhone with only SVG icons shows\n * a screenshot of the page instead of the logo (measured by sales, 10/10).\n *\n * Kept OUT of the core entries so `@broberg/pwa` and `/manifest` stay\n * dependency-free: the renderer, @resvg/resvg-js (prebuilt binaries, no system\n * libraries), is an optional peer loaded only when this runs.\n */\nimport type { IconSet, ManifestIcon } from \"./manifest.js\";\n\nexport interface RasterIcon {\n /** URL path, e.g. `/icons/icon-180.png`. */\n path: string;\n content: Uint8Array;\n type: \"image/png\";\n size: number;\n purpose: \"any\" | \"maskable\";\n}\n\nexport interface RasterIconSet {\n files: RasterIcon[];\n /** Manifest `icons[]` pointing at the PNGs. */\n icons: ManifestIcon[];\n /** Href for `<link rel=\"apple-touch-icon\">` — pass it to `pwaMetaTags({ appleTouchIcon })`. Undefined when 180 was not generated. */\n appleTouchIcon?: string;\n /**\n * F021.11 — the same 180 PNG at the two ROOT paths iOS asks for on its own\n * (`/apple-touch-icon.png`, `/apple-touch-icon-precomposed.png`). Write them to\n * your web root, NOT next to the other icons. Without them iOS fell back to a\n * letter («J») on a real home screen (jam, measured). Empty when 180 was not generated.\n */\n rootFiles: RasterIcon[];\n}\n\nexport interface RasterizeOptions {\n /** Extra font files for monogram text (system fonts are loaded too). */\n fontFiles?: string[];\n}\n\nexport const RESVG_MISSING =\n \"rasterizeIconSet needs @resvg/resvg-js — install it as a dev dependency: npm i -D @resvg/resvg-js\";\n\nconst toPng = (p: string) => p.replace(/\\.svg$/, \".png\");\n\n/** Rasterise every SVG from `buildIconSet` to a PNG of the same size. */\nexport async function rasterizeIconSet(set: IconSet, opts: RasterizeOptions = {}): Promise<RasterIconSet> {\n let Resvg: typeof import(\"@resvg/resvg-js\").Resvg;\n try {\n ({ Resvg } = await import(\"@resvg/resvg-js\"));\n } catch {\n throw new Error(RESVG_MISSING);\n }\n const files: RasterIcon[] = set.files.map((f) => {\n const png = new Resvg(f.content, {\n fitTo: { mode: \"width\", value: f.size },\n font: { loadSystemFonts: true, fontFiles: opts.fontFiles ?? [] },\n })\n .render()\n .asPng();\n return { path: toPng(f.path), content: new Uint8Array(png), type: \"image/png\", size: f.size, purpose: f.purpose };\n });\n const icons = set.icons.map((i) => ({ ...i, src: toPng(i.src), type: \"image/png\" }));\n const apple = files.find((f) => f.size === 180 && f.purpose === \"any\");\n const rootFiles: RasterIcon[] = apple\n ? [\"/apple-touch-icon.png\", \"/apple-touch-icon-precomposed.png\"].map((path) => ({ ...apple, path }))\n : [];\n return { files, icons, appleTouchIcon: apple?.path, rootFiles };\n}\n\n// F021.12 — native icon sets (iOS AppIcon, Android adaptive, .ico/.icns) from the same SVG.\nexport { nativeIconSet, encodeRgbPng, encodeIco, encodeIcns, NATIVE_BACKGROUND_REQUIRED, ANDROID_DENSITIES, ANDROID_SAFE_ZONE, ICO_SIZES, ICNS_CHUNKS } from \"./native.js\";\nexport type { NativeIconFile, NativeIconOptions } from \"./native.js\";\n","/// <reference types=\"node\" />\n// F021.9 — `pwa-icons`: SVG + PNG icon set in one command, so no app keeps its\n// own sharp script. Prints the manifest icons[] and the apple-touch href.\nimport { mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { basename, dirname, join } from \"node:path\";\nimport { buildIconSet } from \"./manifest.js\";\nimport { rasterizeIconSet, nativeIconSet, NATIVE_BACKGROUND_REQUIRED } from \"./raster.js\";\n\nconst USAGE =\n \"usage: pwa-icons (--svg <file> | --monogram <text>) [--background <hex>] [--color <hex>] [--out public/icons] [--base-path /icons] [--root <web root, default: the parent of --out>]\\n pwa-icons --svg <file> --native <out dir> --background <#RRGGBB> (iOS AppIcon, Android adaptive, .ico, .icns)\";\n\nexport async function main(argv: string[]): Promise<number> {\n const arg = (name: string) => {\n const i = argv.indexOf(`--${name}`);\n return i >= 0 ? argv[i + 1] : undefined;\n };\n const svgFile = arg(\"svg\");\n const monogram = arg(\"monogram\");\n if (!svgFile && !monogram) {\n console.error(`pwa-icons: give --svg <file> or --monogram <text>\\n${USAGE}`);\n return 1;\n }\n // F021.12 — native sets. Background is required: a white symbol on a transparent\n // canvas renders as an empty icon (buzzalert, 25/10).\n const native = arg(\"native\");\n if (native !== undefined) {\n if (!svgFile) {\n console.error(`pwa-icons: --native needs --svg <file>\\n${USAGE}`);\n return 1;\n }\n const background = arg(\"background\");\n if (!background) {\n console.error(`pwa-icons: --native needs --background <#RRGGBB> — ${NATIVE_BACKGROUND_REQUIRED}`);\n return 1;\n }\n let files;\n try {\n files = await nativeIconSet(readFileSync(svgFile, \"utf8\"), { background });\n } catch (e) {\n console.error(`pwa-icons: ${(e as Error).message}`);\n return 1;\n }\n for (const f of files) {\n mkdirSync(dirname(join(native, f.path)), { recursive: true });\n writeFileSync(join(native, f.path), f.content);\n }\n console.log(JSON.stringify({ native: files.map((f) => f.path) }, null, 2));\n return 0;\n }\n const out = arg(\"out\") ?? \"public/icons\";\n const set = buildIconSet({\n svg: svgFile ? readFileSync(svgFile, \"utf8\") : undefined,\n monogram,\n background: arg(\"background\"),\n color: arg(\"color\"),\n basePath: arg(\"base-path\") ?? \"/icons\",\n });\n let raster;\n try {\n raster = await rasterizeIconSet(set);\n } catch (e) {\n console.error(`pwa-icons: ${(e as Error).message}`);\n return 1;\n }\n mkdirSync(out, { recursive: true });\n for (const f of set.files) writeFileSync(join(out, basename(f.path)), f.content);\n for (const f of raster.files) writeFileSync(join(out, basename(f.path)), f.content);\n // F021.11 — iOS asks the web ROOT for these on its own.\n const root = arg(\"root\") ?? dirname(out);\n for (const f of raster.rootFiles) writeFileSync(join(root, basename(f.path)), f.content);\n console.log(JSON.stringify({ icons: raster.icons, appleTouchIcon: raster.appleTouchIcon }, null, 2));\n return 0;\n}\n\n","#!/usr/bin/env node\n/// <reference types=\"node\" />\n// F021.9 — the `pwa-icons` entry point; the logic lives in cli.ts so tests can call it.\nimport { main } from \"./cli.js\";\n\nmain(process.argv.slice(2)).then((code) => process.exit(code));\n"]}
1
+ {"version":3,"sources":["../src/manifest.ts","../src/native.ts","../src/raster.ts","../src/cli.ts","../src/bin.ts"],"names":["deflateSync","readFileSync","mkdirSync","dirname","join","writeFileSync","basename"],"mappings":"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","file":"bin.cjs","sourcesContent":["/**\n * @broberg/pwa/manifest — the PWA *install* half (the update half lives in the\n * root export). A dependency-free manifest/meta factory + an SVG-based icon\n * generator, so a repo stops hand-rolling `app/manifest.ts`, a\n * `gen-pwa-icons.cjs` script, and a pile of apple-touch `<meta>` tags.\n *\n * Everything here is PURE string/object generation — no filesystem, no\n * rasteriser, no runtime deps — so it runs in Node, Bun, edge, a build script,\n * or the browser, and every piece is offline-unit-testable. Icons are emitted\n * as self-contained SVG documents. iOS does NOT use an SVG apple-touch-icon —\n * rasterise with `@broberg/pwa/raster` / the `pwa-icons` bin (F021.9).\n */\n\n// ---------------------------------------------------------------------------\n// Manifest\n// ---------------------------------------------------------------------------\n\nexport type Display = \"standalone\" | \"fullscreen\" | \"minimal-ui\" | \"browser\";\n\nexport interface ManifestIcon {\n src: string;\n sizes: string;\n type: string;\n /** \"any\" | \"maskable\" | \"monochrome\" (space-separated allowed). */\n purpose?: string;\n}\n\nexport interface WebAppManifest {\n name: string;\n short_name: string;\n start_url: string;\n scope: string;\n display: Display;\n theme_color: string;\n background_color: string;\n icons: ManifestIcon[];\n description?: string;\n lang?: string;\n orientation?: string;\n categories?: string[];\n id?: string;\n [key: string]: unknown;\n}\n\nexport interface DefineManifestOptions {\n /** App name (required). */\n name: string;\n /** Home-screen label. Default: `name`. */\n shortName?: string;\n /** Default `/`. */\n startUrl?: string;\n /** Default `/`. */\n scope?: string;\n /** Default `standalone`. */\n display?: Display;\n /** Address-bar / task-switcher colour. Default `#ffffff`. */\n themeColor?: string;\n /** Splash background. Default `#ffffff`. */\n backgroundColor?: string;\n description?: string;\n lang?: string;\n orientation?: string;\n categories?: string[];\n id?: string;\n /** Manifest `icons[]` — typically `buildIconSet(...).icons`. */\n icons?: ManifestIcon[];\n /** Escape hatch: extra top-level manifest members, merged last. */\n extra?: Record<string, unknown>;\n}\n\n/**\n * Build a spec-valid Web App Manifest object. Required members are defaulted so\n * a one-liner produces an installable manifest; every default is overridable\n * and `extra` merges last for anything not modelled (shortcuts, screenshots…).\n */\nexport function defineManifest(opts: DefineManifestOptions): WebAppManifest {\n if (!opts?.name) throw new Error(\"defineManifest: `name` is required\");\n const manifest: WebAppManifest = {\n name: opts.name,\n short_name: opts.shortName ?? opts.name,\n start_url: opts.startUrl ?? \"/\",\n scope: opts.scope ?? \"/\",\n display: opts.display ?? \"standalone\",\n theme_color: opts.themeColor ?? \"#ffffff\",\n background_color: opts.backgroundColor ?? \"#ffffff\",\n icons: opts.icons ?? [],\n };\n if (opts.description) manifest.description = opts.description;\n if (opts.lang) manifest.lang = opts.lang;\n if (opts.orientation) manifest.orientation = opts.orientation;\n if (opts.categories) manifest.categories = opts.categories;\n if (opts.id) manifest.id = opts.id;\n return { ...manifest, ...(opts.extra ?? {}) };\n}\n\n/** JSON-stringify a manifest for a `manifest.webmanifest` file. */\nexport function serializeManifest(\n manifest: WebAppManifest,\n opts: { pretty?: boolean } = {},\n): string {\n return JSON.stringify(manifest, null, opts.pretty === false ? undefined : 2);\n}\n\n// ---------------------------------------------------------------------------\n// Icon generation (SVG, zero-dep)\n// ---------------------------------------------------------------------------\n\nexport interface GeneratedIcon {\n /** URL path, e.g. `/icons/icon-192.svg`. */\n path: string;\n /** The SVG document. */\n content: string;\n type: \"image/svg+xml\";\n size: number;\n purpose: \"any\" | \"maskable\";\n}\n\nexport interface IconSet {\n files: GeneratedIcon[];\n /** Ready to drop into `defineManifest({ icons })`. */\n icons: ManifestIcon[];\n}\n\nexport interface IconSetOptions {\n /**\n * Source artwork as a full SVG document (`<svg …>…</svg>`). Embedded via a\n * data-URI `<image>` so it scales cleanly at any size regardless of its\n * internal coordinate system. Mutually exclusive with `monogram`.\n */\n svg?: string;\n /** Fallback artwork: 1–3 letters rendered centred (e.g. \"AK\"). */\n monogram?: string;\n /** Foreground (monogram text) colour. Default `#ffffff`. */\n color?: string;\n /** Background fill. Default `#111827`. */\n background?: string;\n /** Icon sizes to emit. Default `[180, 192, 512]` (180 = apple-touch). */\n sizes?: number[];\n /** Also emit a maskable 512 with a padded safe-zone. Default `true`. */\n maskable?: boolean;\n /** URL prefix for every icon `src`. Default `/icons`. */\n basePath?: string;\n}\n\nconst DEFAULT_SIZES = [180, 192, 512];\n\nfunction escapeXml(s: string): string {\n return s.replace(/[<>&\"']/g, (c) =>\n c === \"<\" ? \"&lt;\" : c === \">\" ? \"&gt;\" : c === \"&\" ? \"&amp;\" : c === '\"' ? \"&quot;\" : \"&apos;\",\n );\n}\n\n/** UTF-8-safe base64 for the data-URI embed (works in Node/Bun/browser). */\nfunction toBase64(s: string): string {\n // Reached via globalThis so the package needs no @types/node (browser-safe).\n const g = globalThis as unknown as {\n Buffer?: { from(input: string, enc: string): { toString(enc: string): string } };\n btoa?: (data: string) => string;\n };\n if (typeof g.Buffer !== \"undefined\") return g.Buffer.from(s, \"utf-8\").toString(\"base64\");\n const bytes = new TextEncoder().encode(s);\n let bin = \"\";\n for (const b of bytes) bin += String.fromCharCode(b);\n return g.btoa!(bin);\n}\n\n/** Render one square SVG icon document at `size`. `inset` = safe-zone fraction (maskable). */\nfunction renderIcon(size: number, inset: number, opts: IconSetOptions): string {\n const bg = opts.background ?? \"#111827\";\n const fg = opts.color ?? \"#ffffff\";\n const pad = Math.round(size * inset);\n const inner = size - pad * 2;\n let art: string;\n if (opts.svg) {\n const href = `data:image/svg+xml;base64,${toBase64(opts.svg)}`;\n art = `<image x=\"${pad}\" y=\"${pad}\" width=\"${inner}\" height=\"${inner}\" href=\"${href}\" preserveAspectRatio=\"xMidYMid meet\"/>`;\n } else {\n const text = escapeXml((opts.monogram ?? \"\").slice(0, 3).toUpperCase());\n // Font sized to the safe inner box; a touch smaller for breathing room.\n const fontSize = Math.round(inner * (text.length >= 3 ? 0.42 : 0.52));\n art =\n `<text x=\"50%\" y=\"50%\" fill=\"${fg}\" font-family=\"system-ui, -apple-system, Segoe UI, Roboto, sans-serif\" ` +\n `font-weight=\"700\" font-size=\"${fontSize}\" text-anchor=\"middle\" dominant-baseline=\"central\">${text}</text>`;\n }\n return (\n `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"${size}\" height=\"${size}\" viewBox=\"0 0 ${size} ${size}\">` +\n `<rect width=\"${size}\" height=\"${size}\" fill=\"${bg}\"/>` +\n art +\n `</svg>`\n );\n}\n\n/**\n * Generate a full icon set (SVG) + the matching manifest `icons[]`. Deterministic,\n * zero-dep. Give it either a source `svg` or a `monogram`; it emits apple-touch\n * (180), 192, 512 by default plus a padded maskable-512.\n */\nexport function buildIconSet(opts: IconSetOptions): IconSet {\n if (!opts?.svg && !opts?.monogram) {\n throw new Error(\"buildIconSet: provide either `svg` or `monogram`\");\n }\n const base = (opts.basePath ?? \"/icons\").replace(/\\/$/, \"\");\n const sizes = opts.sizes ?? DEFAULT_SIZES;\n const files: GeneratedIcon[] = [];\n const icons: ManifestIcon[] = [];\n\n for (const size of sizes) {\n const path = `${base}/icon-${size}.svg`;\n files.push({ path, content: renderIcon(size, 0, opts), type: \"image/svg+xml\", size, purpose: \"any\" });\n // apple-touch-icon is referenced via a <link> (see pwaMetaTags), not the\n // manifest, so only real manifest sizes go into icons[].\n if (size !== 180) {\n icons.push({ src: path, sizes: `${size}x${size}`, type: \"image/svg+xml\", purpose: \"any\" });\n }\n }\n\n if (opts.maskable !== false) {\n // Maskable safe-zone: keep content within the central 80% (10% inset).\n const path = `${base}/icon-maskable-512.svg`;\n files.push({ path, content: renderIcon(512, 0.1, opts), type: \"image/svg+xml\", size: 512, purpose: \"maskable\" });\n icons.push({ src: path, sizes: \"512x512\", type: \"image/svg+xml\", purpose: \"maskable\" });\n }\n\n return { files, icons };\n}\n\n// ---------------------------------------------------------------------------\n// Head meta tags\n// ---------------------------------------------------------------------------\n\nexport interface MetaTag {\n tag: \"meta\" | \"link\";\n attrs: Record<string, string>;\n}\n\nexport interface MetaTagsOptions {\n /** `<meta name=\"theme-color\">`. Default `#ffffff`. */\n themeColor?: string;\n /** apple-touch-icon href. Default `/icons/icon-180.svg`. */\n appleTouchIcon?: string;\n /** Manifest href for the `<link rel=\"manifest\">`. Default `/manifest.webmanifest`. */\n manifest?: string;\n /** apple-mobile-web-app-title. Default: omitted. */\n title?: string;\n /** apple-mobile-web-app-status-bar-style. Default `default`. */\n statusBarStyle?: \"default\" | \"black\" | \"black-translucent\";\n /** Emit the `*-web-app-capable` tags. Default `true`. */\n capable?: boolean;\n}\n\n/**\n * The head tags a PWA needs beyond the manifest link — returned as typed\n * descriptors so a consumer renders them in Next.js `metadata`, a Hono/JSX\n * head, or plain HTML. No framework assumption.\n */\nexport function pwaMetaTags(opts: MetaTagsOptions = {}): MetaTag[] {\n const tags: MetaTag[] = [];\n tags.push({ tag: \"link\", attrs: { rel: \"manifest\", href: opts.manifest ?? \"/manifest.webmanifest\" } });\n tags.push({ tag: \"meta\", attrs: { name: \"theme-color\", content: opts.themeColor ?? \"#ffffff\" } });\n // F021.11 — sizes: the icon is 180; iOS picks by it when several are linked.\n tags.push({ tag: \"link\", attrs: { rel: \"apple-touch-icon\", sizes: \"180x180\", href: opts.appleTouchIcon ?? \"/icons/icon-180.svg\" } });\n if (opts.capable !== false) {\n tags.push({ tag: \"meta\", attrs: { name: \"mobile-web-app-capable\", content: \"yes\" } });\n tags.push({ tag: \"meta\", attrs: { name: \"apple-mobile-web-app-capable\", content: \"yes\" } });\n tags.push({\n tag: \"meta\",\n attrs: { name: \"apple-mobile-web-app-status-bar-style\", content: opts.statusBarStyle ?? \"default\" },\n });\n }\n if (opts.title) {\n tags.push({ tag: \"meta\", attrs: { name: \"apple-mobile-web-app-title\", content: opts.title } });\n }\n return tags;\n}\n\n// F021.13 — the stale-page guard belongs with the other <head> helpers.\nexport { staleShellGuardScript, STALE_SHELL_STORAGE_KEY, STALE_SHELL_WINDOW_MS, STALE_SHELL_PARAM } from \"./stale-shell.js\";\n","/**\n * @broberg/pwa/raster — F021.12: the NATIVE icon sets, from the same SVG the web\n * icons come from, so no Capacitor or Tauri app keeps its own icon script.\n *\n * ios/AppIcon.appiconset/ AppIcon-1024.png + Contents.json (Xcode 14+ single size)\n * android/mipmap-anydpi-v26/ ic_launcher.xml, ic_launcher_round.xml (adaptive)\n * android/mipmap-<dpi>/ ic_launcher_foreground.png, ic_launcher.png, ic_launcher_round.png\n * android/values/ ic_launcher_background.xml\n * desktop/ icon.ico, icon.icns\n *\n * Two platform rules decide the code, and both fail silently if ignored:\n * - App Store rejects an app icon with an alpha channel. resvg always renders\n * RGBA, so the iOS PNG is RE-ENCODED as RGB (colour type 2) — flattening onto\n * the background is not enough on its own, the channel must be gone.\n * - Android crops an adaptive icon's 108dp layer to whatever mask the launcher\n * uses; only the inner 66dp is guaranteed visible. The motif is drawn inside it.\n *\n * `background` is REQUIRED: a white symbol on a transparent canvas renders as an\n * empty icon (measured by buzzalert with `tauri icon`, 25/10).\n *\n * ICO and ICNS are written as plain byte containers holding PNGs — no dependency.\n */\nimport { deflateSync } from \"node:zlib\";\n\nexport interface NativeIconFile {\n /** Path relative to the output directory, e.g. `ios/AppIcon.appiconset/AppIcon-1024.png`. */\n path: string;\n content: Uint8Array;\n}\n\nexport interface NativeIconOptions {\n /** Hex colour behind the motif (`#RRGGBB`). Required — see the module comment. */\n background: string;\n /** Default: all three. */\n platforms?: Array<\"ios\" | \"android\" | \"desktop\">;\n}\n\nexport const NATIVE_BACKGROUND_REQUIRED =\n \"nativeIconSet needs `background` (#RRGGBB): a white symbol on a transparent canvas renders as an empty icon\";\n\nconst HEX = /^#[0-9a-f]{6}$/i;\n\n/** Android density → adaptive foreground px (108dp) and legacy launcher px (48dp). */\nexport const ANDROID_DENSITIES = [\n { dpi: \"mdpi\", foreground: 108, legacy: 48 },\n { dpi: \"hdpi\", foreground: 162, legacy: 72 },\n { dpi: \"xhdpi\", foreground: 216, legacy: 96 },\n { dpi: \"xxhdpi\", foreground: 324, legacy: 144 },\n { dpi: \"xxxhdpi\", foreground: 432, legacy: 192 },\n] as const;\n\n/** Share of the 108dp adaptive canvas that every launcher mask keeps visible. */\nexport const ANDROID_SAFE_ZONE = 66 / 108;\n\nexport const ICO_SIZES = [16, 32, 48, 256] as const;\n/** ICNS chunk type → pixel size (PNG payloads). */\nexport const ICNS_CHUNKS = [\n [\"ic11\", 32],\n [\"ic12\", 64],\n [\"ic07\", 128],\n [\"ic08\", 256],\n [\"ic13\", 256],\n [\"ic09\", 512],\n [\"ic14\", 512],\n [\"ic10\", 1024],\n] as const;\n\ntype Shape = \"square\" | \"circle\" | \"none\";\n\nfunction composeSvg(svg: string, size: number, opts: { background?: string; shape: Shape; scale: number }): string {\n const d = size * opts.scale;\n const o = (size - d) / 2;\n const bg =\n opts.shape === \"square\" && opts.background\n ? `<rect width=\"${size}\" height=\"${size}\" fill=\"${opts.background}\"/>`\n : opts.shape === \"circle\" && opts.background\n ? `<circle cx=\"${size / 2}\" cy=\"${size / 2}\" r=\"${size / 2}\" fill=\"${opts.background}\"/>`\n : \"\";\n const href = `data:image/svg+xml;base64,${Buffer.from(svg).toString(\"base64\")}`;\n return (\n `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"${size}\" height=\"${size}\" viewBox=\"0 0 ${size} ${size}\">` +\n `${bg}<image href=\"${href}\" x=\"${o}\" y=\"${o}\" width=\"${d}\" height=\"${d}\"/></svg>`\n );\n}\n\n// ---- PNG encoding (RGB, no alpha) -------------------------------------------\n\nconst CRC_TABLE = (() => {\n const t = new Uint32Array(256);\n for (let n = 0; n < 256; n++) {\n let c = n;\n for (let k = 0; k < 8; k++) c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;\n t[n] = c >>> 0;\n }\n return t;\n})();\n\nfunction crc32(bytes: Uint8Array): number {\n let c = 0xffffffff;\n for (const b of bytes) c = CRC_TABLE[(c ^ b) & 0xff]! ^ (c >>> 8);\n return (c ^ 0xffffffff) >>> 0;\n}\n\nfunction chunk(type: string, data: Uint8Array): Uint8Array {\n const out = new Uint8Array(12 + data.length);\n const v = new DataView(out.buffer);\n v.setUint32(0, data.length);\n for (let i = 0; i < 4; i++) out[4 + i] = type.charCodeAt(i);\n out.set(data, 8);\n v.setUint32(8 + data.length, crc32(out.subarray(4, 8 + data.length)));\n return out;\n}\n\n/** Encode RGBA pixels as an RGB PNG (colour type 2) — the alpha channel is dropped, not just made opaque. */\nexport function encodeRgbPng(rgba: Uint8Array, width: number, height: number): Uint8Array {\n const raw = new Uint8Array(height * (1 + width * 3));\n for (let y = 0; y < height; y++) {\n const row = y * (1 + width * 3);\n raw[row] = 0; // filter: none\n for (let x = 0; x < width; x++) {\n const s = (y * width + x) * 4;\n const t = row + 1 + x * 3;\n raw[t] = rgba[s]!;\n raw[t + 1] = rgba[s + 1]!;\n raw[t + 2] = rgba[s + 2]!;\n }\n }\n const ihdr = new Uint8Array(13);\n const v = new DataView(ihdr.buffer);\n v.setUint32(0, width);\n v.setUint32(4, height);\n ihdr[8] = 8; // bit depth\n ihdr[9] = 2; // colour type: truecolour, NO alpha\n const sig = Uint8Array.from([137, 80, 78, 71, 13, 10, 26, 10]);\n const parts = [sig, chunk(\"IHDR\", ihdr), chunk(\"IDAT\", new Uint8Array(deflateSync(raw))), chunk(\"IEND\", new Uint8Array())];\n const out = new Uint8Array(parts.reduce((n, p) => n + p.length, 0));\n let off = 0;\n for (const p of parts) {\n out.set(p, off);\n off += p.length;\n }\n return out;\n}\n\n// ---- containers --------------------------------------------------------------\n\n/** An ICO holding PNG images (Vista+ format). */\nexport function encodeIco(pngs: Array<{ size: number; png: Uint8Array }>): Uint8Array {\n const header = 6 + 16 * pngs.length;\n const total = header + pngs.reduce((n, p) => n + p.png.length, 0);\n const out = new Uint8Array(total);\n const v = new DataView(out.buffer);\n v.setUint16(0, 0, true); // reserved\n v.setUint16(2, 1, true); // type: icon\n v.setUint16(4, pngs.length, true);\n let off = header;\n pngs.forEach((p, i) => {\n const e = 6 + i * 16;\n out[e] = p.size >= 256 ? 0 : p.size; // 0 means 256\n out[e + 1] = p.size >= 256 ? 0 : p.size;\n v.setUint16(e + 4, 1, true); // colour planes\n v.setUint16(e + 6, 32, true); // bits per pixel\n v.setUint32(e + 8, p.png.length, true);\n v.setUint32(e + 12, off, true);\n out.set(p.png, off);\n off += p.png.length;\n });\n return out;\n}\n\n/** An ICNS holding PNG chunks. */\nexport function encodeIcns(chunks: Array<{ type: string; png: Uint8Array }>): Uint8Array {\n const total = 8 + chunks.reduce((n, c) => n + 8 + c.png.length, 0);\n const out = new Uint8Array(total);\n const v = new DataView(out.buffer);\n const tag = (at: number, s: string) => {\n for (let i = 0; i < 4; i++) out[at + i] = s.charCodeAt(i);\n };\n tag(0, \"icns\");\n v.setUint32(4, total);\n let off = 8;\n for (const c of chunks) {\n tag(off, c.type);\n v.setUint32(off + 4, 8 + c.png.length);\n out.set(c.png, off + 8);\n off += 8 + c.png.length;\n }\n return out;\n}\n\n// ---- the set -------------------------------------------------------------------\n\nconst enc = (s: string) => new TextEncoder().encode(s);\n\nconst ADAPTIVE_XML =\n `<?xml version=\"1.0\" encoding=\"utf-8\"?>\\n` +\n `<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\\n` +\n ` <background android:drawable=\"@color/ic_launcher_background\"/>\\n` +\n ` <foreground android:drawable=\"@mipmap/ic_launcher_foreground\"/>\\n` +\n `</adaptive-icon>\\n`;\n\nconst IOS_CONTENTS = JSON.stringify(\n {\n images: [{ filename: \"AppIcon-1024.png\", idiom: \"universal\", platform: \"ios\", size: \"1024x1024\" }],\n info: { author: \"@broberg/pwa\", version: 1 },\n },\n null,\n 2,\n);\n\n/**\n * Every native icon file for `svg`. Requires the optional peer @resvg/resvg-js,\n * like `rasterizeIconSet`.\n */\nexport async function nativeIconSet(svg: string, opts: NativeIconOptions): Promise<NativeIconFile[]> {\n if (!opts?.background || !HEX.test(opts.background)) throw new Error(NATIVE_BACKGROUND_REQUIRED);\n let Resvg: typeof import(\"@resvg/resvg-js\").Resvg;\n try {\n ({ Resvg } = await import(\"@resvg/resvg-js\"));\n } catch {\n throw new Error(\"nativeIconSet needs @resvg/resvg-js — install it as a dev dependency: npm i -D @resvg/resvg-js\");\n }\n const bg = opts.background;\n const want = new Set(opts.platforms ?? [\"ios\", \"android\", \"desktop\"]);\n const render = (size: number, shape: Shape, scale: number) =>\n new Resvg(composeSvg(svg, size, { background: bg, shape, scale }), { font: { loadSystemFonts: false } }).render();\n const png = (size: number, shape: Shape, scale: number) => new Uint8Array(render(size, shape, scale).asPng());\n\n const files: NativeIconFile[] = [];\n\n if (want.has(\"ios\")) {\n const r = render(1024, \"square\", 0.8);\n files.push(\n { path: \"ios/AppIcon.appiconset/AppIcon-1024.png\", content: encodeRgbPng(new Uint8Array(r.pixels), r.width, r.height) },\n { path: \"ios/AppIcon.appiconset/Contents.json\", content: enc(IOS_CONTENTS + \"\\n\") },\n );\n }\n\n if (want.has(\"android\")) {\n files.push(\n { path: \"android/mipmap-anydpi-v26/ic_launcher.xml\", content: enc(ADAPTIVE_XML) },\n { path: \"android/mipmap-anydpi-v26/ic_launcher_round.xml\", content: enc(ADAPTIVE_XML) },\n {\n path: \"android/values/ic_launcher_background.xml\",\n content: enc(\n `<?xml version=\"1.0\" encoding=\"utf-8\"?>\\n<resources>\\n <color name=\"ic_launcher_background\">${bg}</color>\\n</resources>\\n`,\n ),\n },\n );\n for (const d of ANDROID_DENSITIES) {\n files.push(\n { path: `android/mipmap-${d.dpi}/ic_launcher_foreground.png`, content: png(d.foreground, \"none\", ANDROID_SAFE_ZONE) },\n { path: `android/mipmap-${d.dpi}/ic_launcher.png`, content: png(d.legacy, \"square\", 0.7) },\n { path: `android/mipmap-${d.dpi}/ic_launcher_round.png`, content: png(d.legacy, \"circle\", 0.62) },\n );\n }\n }\n\n if (want.has(\"desktop\")) {\n files.push(\n { path: \"desktop/icon.ico\", content: encodeIco(ICO_SIZES.map((size) => ({ size, png: png(size, \"square\", 0.8) }))) },\n { path: \"desktop/icon.icns\", content: encodeIcns(ICNS_CHUNKS.map(([type, size]) => ({ type, png: png(size, \"square\", 0.8) }))) },\n );\n }\n\n return files;\n}\n","/**\n * @broberg/pwa/raster — F021.9: PNG icons, because iOS does not use an SVG\n * apple-touch-icon. «Add to Home Screen» on an iPhone with only SVG icons shows\n * a screenshot of the page instead of the logo (measured by sales, 10/10).\n *\n * Kept OUT of the core entries so `@broberg/pwa` and `/manifest` stay\n * dependency-free: the renderer, @resvg/resvg-js (prebuilt binaries, no system\n * libraries), is an optional peer loaded only when this runs.\n */\nimport type { IconSet, ManifestIcon } from \"./manifest.js\";\n\nexport interface RasterIcon {\n /** URL path, e.g. `/icons/icon-180.png`. */\n path: string;\n content: Uint8Array;\n type: \"image/png\";\n size: number;\n purpose: \"any\" | \"maskable\";\n}\n\nexport interface RasterIconSet {\n files: RasterIcon[];\n /** Manifest `icons[]` pointing at the PNGs. */\n icons: ManifestIcon[];\n /** Href for `<link rel=\"apple-touch-icon\">` — pass it to `pwaMetaTags({ appleTouchIcon })`. Undefined when 180 was not generated. */\n appleTouchIcon?: string;\n /**\n * F021.11 — the same 180 PNG at the two ROOT paths iOS asks for on its own\n * (`/apple-touch-icon.png`, `/apple-touch-icon-precomposed.png`). Write them to\n * your web root, NOT next to the other icons. Without them iOS fell back to a\n * letter («J») on a real home screen (jam, measured). Empty when 180 was not generated.\n */\n rootFiles: RasterIcon[];\n}\n\nexport interface RasterizeOptions {\n /** Extra font files for monogram text (system fonts are loaded too). */\n fontFiles?: string[];\n}\n\nexport const RESVG_MISSING =\n \"rasterizeIconSet needs @resvg/resvg-js — install it as a dev dependency: npm i -D @resvg/resvg-js\";\n\nconst toPng = (p: string) => p.replace(/\\.svg$/, \".png\");\n\n/** Rasterise every SVG from `buildIconSet` to a PNG of the same size. */\nexport async function rasterizeIconSet(set: IconSet, opts: RasterizeOptions = {}): Promise<RasterIconSet> {\n let Resvg: typeof import(\"@resvg/resvg-js\").Resvg;\n try {\n ({ Resvg } = await import(\"@resvg/resvg-js\"));\n } catch {\n throw new Error(RESVG_MISSING);\n }\n const files: RasterIcon[] = set.files.map((f) => {\n const png = new Resvg(f.content, {\n fitTo: { mode: \"width\", value: f.size },\n font: { loadSystemFonts: true, fontFiles: opts.fontFiles ?? [] },\n })\n .render()\n .asPng();\n return { path: toPng(f.path), content: new Uint8Array(png), type: \"image/png\", size: f.size, purpose: f.purpose };\n });\n const icons = set.icons.map((i) => ({ ...i, src: toPng(i.src), type: \"image/png\" }));\n const apple = files.find((f) => f.size === 180 && f.purpose === \"any\");\n const rootFiles: RasterIcon[] = apple\n ? [\"/apple-touch-icon.png\", \"/apple-touch-icon-precomposed.png\"].map((path) => ({ ...apple, path }))\n : [];\n return { files, icons, appleTouchIcon: apple?.path, rootFiles };\n}\n\n// F021.12 — native icon sets (iOS AppIcon, Android adaptive, .ico/.icns) from the same SVG.\nexport { nativeIconSet, encodeRgbPng, encodeIco, encodeIcns, NATIVE_BACKGROUND_REQUIRED, ANDROID_DENSITIES, ANDROID_SAFE_ZONE, ICO_SIZES, ICNS_CHUNKS } from \"./native.js\";\nexport type { NativeIconFile, NativeIconOptions } from \"./native.js\";\n","/// <reference types=\"node\" />\n// F021.9 — `pwa-icons`: SVG + PNG icon set in one command, so no app keeps its\n// own sharp script. Prints the manifest icons[] and the apple-touch href.\nimport { mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { basename, dirname, join } from \"node:path\";\nimport { buildIconSet } from \"./manifest.js\";\nimport { rasterizeIconSet, nativeIconSet, NATIVE_BACKGROUND_REQUIRED } from \"./raster.js\";\n\nconst USAGE =\n \"usage: pwa-icons (--svg <file> | --monogram <text>) [--background <hex>] [--color <hex>] [--out public/icons] [--base-path /icons] [--root <web root, default: the parent of --out>]\\n pwa-icons --svg <file> --native <out dir> --background <#RRGGBB> (iOS AppIcon, Android adaptive, .ico, .icns)\";\n\nexport async function main(argv: string[]): Promise<number> {\n const arg = (name: string) => {\n const i = argv.indexOf(`--${name}`);\n return i >= 0 ? argv[i + 1] : undefined;\n };\n const svgFile = arg(\"svg\");\n const monogram = arg(\"monogram\");\n if (!svgFile && !monogram) {\n console.error(`pwa-icons: give --svg <file> or --monogram <text>\\n${USAGE}`);\n return 1;\n }\n // F021.12 — native sets. Background is required: a white symbol on a transparent\n // canvas renders as an empty icon (buzzalert, 25/10).\n const native = arg(\"native\");\n if (native !== undefined) {\n if (!svgFile) {\n console.error(`pwa-icons: --native needs --svg <file>\\n${USAGE}`);\n return 1;\n }\n const background = arg(\"background\");\n if (!background) {\n console.error(`pwa-icons: --native needs --background <#RRGGBB> — ${NATIVE_BACKGROUND_REQUIRED}`);\n return 1;\n }\n let files;\n try {\n files = await nativeIconSet(readFileSync(svgFile, \"utf8\"), { background });\n } catch (e) {\n console.error(`pwa-icons: ${(e as Error).message}`);\n return 1;\n }\n for (const f of files) {\n mkdirSync(dirname(join(native, f.path)), { recursive: true });\n writeFileSync(join(native, f.path), f.content);\n }\n console.log(JSON.stringify({ native: files.map((f) => f.path) }, null, 2));\n return 0;\n }\n const out = arg(\"out\") ?? \"public/icons\";\n const set = buildIconSet({\n svg: svgFile ? readFileSync(svgFile, \"utf8\") : undefined,\n monogram,\n background: arg(\"background\"),\n color: arg(\"color\"),\n basePath: arg(\"base-path\") ?? \"/icons\",\n });\n let raster;\n try {\n raster = await rasterizeIconSet(set);\n } catch (e) {\n console.error(`pwa-icons: ${(e as Error).message}`);\n return 1;\n }\n mkdirSync(out, { recursive: true });\n for (const f of set.files) writeFileSync(join(out, basename(f.path)), f.content);\n for (const f of raster.files) writeFileSync(join(out, basename(f.path)), f.content);\n // F021.11 — iOS asks the web ROOT for these on its own.\n const root = arg(\"root\") ?? dirname(out);\n for (const f of raster.rootFiles) writeFileSync(join(root, basename(f.path)), f.content);\n console.log(JSON.stringify({ icons: raster.icons, appleTouchIcon: raster.appleTouchIcon }, null, 2));\n return 0;\n}\n\n","#!/usr/bin/env node\n/// <reference types=\"node\" />\n// F021.9 — the `pwa-icons` entry point; the logic lives in cli.ts so tests can call it.\nimport { main } from \"./cli.js\";\n\nmain(process.argv.slice(2)).then((code) => process.exit(code));\n"]}
package/dist/bin.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/manifest.ts","../src/cli.ts","../src/bin.ts"],"names":[],"mappings":"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","file":"bin.js","sourcesContent":["/**\n * @broberg/pwa/manifest — the PWA *install* half (the update half lives in the\n * root export). A dependency-free manifest/meta factory + an SVG-based icon\n * generator, so a repo stops hand-rolling `app/manifest.ts`, a\n * `gen-pwa-icons.cjs` script, and a pile of apple-touch `<meta>` tags.\n *\n * Everything here is PURE string/object generation — no filesystem, no\n * rasteriser, no runtime deps — so it runs in Node, Bun, edge, a build script,\n * or the browser, and every piece is offline-unit-testable. Icons are emitted\n * as self-contained SVG documents. iOS does NOT use an SVG apple-touch-icon —\n * rasterise with `@broberg/pwa/raster` / the `pwa-icons` bin (F021.9).\n */\n\n// ---------------------------------------------------------------------------\n// Manifest\n// ---------------------------------------------------------------------------\n\nexport type Display = \"standalone\" | \"fullscreen\" | \"minimal-ui\" | \"browser\";\n\nexport interface ManifestIcon {\n src: string;\n sizes: string;\n type: string;\n /** \"any\" | \"maskable\" | \"monochrome\" (space-separated allowed). */\n purpose?: string;\n}\n\nexport interface WebAppManifest {\n name: string;\n short_name: string;\n start_url: string;\n scope: string;\n display: Display;\n theme_color: string;\n background_color: string;\n icons: ManifestIcon[];\n description?: string;\n lang?: string;\n orientation?: string;\n categories?: string[];\n id?: string;\n [key: string]: unknown;\n}\n\nexport interface DefineManifestOptions {\n /** App name (required). */\n name: string;\n /** Home-screen label. Default: `name`. */\n shortName?: string;\n /** Default `/`. */\n startUrl?: string;\n /** Default `/`. */\n scope?: string;\n /** Default `standalone`. */\n display?: Display;\n /** Address-bar / task-switcher colour. Default `#ffffff`. */\n themeColor?: string;\n /** Splash background. Default `#ffffff`. */\n backgroundColor?: string;\n description?: string;\n lang?: string;\n orientation?: string;\n categories?: string[];\n id?: string;\n /** Manifest `icons[]` — typically `buildIconSet(...).icons`. */\n icons?: ManifestIcon[];\n /** Escape hatch: extra top-level manifest members, merged last. */\n extra?: Record<string, unknown>;\n}\n\n/**\n * Build a spec-valid Web App Manifest object. Required members are defaulted so\n * a one-liner produces an installable manifest; every default is overridable\n * and `extra` merges last for anything not modelled (shortcuts, screenshots…).\n */\nexport function defineManifest(opts: DefineManifestOptions): WebAppManifest {\n if (!opts?.name) throw new Error(\"defineManifest: `name` is required\");\n const manifest: WebAppManifest = {\n name: opts.name,\n short_name: opts.shortName ?? opts.name,\n start_url: opts.startUrl ?? \"/\",\n scope: opts.scope ?? \"/\",\n display: opts.display ?? \"standalone\",\n theme_color: opts.themeColor ?? \"#ffffff\",\n background_color: opts.backgroundColor ?? \"#ffffff\",\n icons: opts.icons ?? [],\n };\n if (opts.description) manifest.description = opts.description;\n if (opts.lang) manifest.lang = opts.lang;\n if (opts.orientation) manifest.orientation = opts.orientation;\n if (opts.categories) manifest.categories = opts.categories;\n if (opts.id) manifest.id = opts.id;\n return { ...manifest, ...(opts.extra ?? {}) };\n}\n\n/** JSON-stringify a manifest for a `manifest.webmanifest` file. */\nexport function serializeManifest(\n manifest: WebAppManifest,\n opts: { pretty?: boolean } = {},\n): string {\n return JSON.stringify(manifest, null, opts.pretty === false ? undefined : 2);\n}\n\n// ---------------------------------------------------------------------------\n// Icon generation (SVG, zero-dep)\n// ---------------------------------------------------------------------------\n\nexport interface GeneratedIcon {\n /** URL path, e.g. `/icons/icon-192.svg`. */\n path: string;\n /** The SVG document. */\n content: string;\n type: \"image/svg+xml\";\n size: number;\n purpose: \"any\" | \"maskable\";\n}\n\nexport interface IconSet {\n files: GeneratedIcon[];\n /** Ready to drop into `defineManifest({ icons })`. */\n icons: ManifestIcon[];\n}\n\nexport interface IconSetOptions {\n /**\n * Source artwork as a full SVG document (`<svg …>…</svg>`). Embedded via a\n * data-URI `<image>` so it scales cleanly at any size regardless of its\n * internal coordinate system. Mutually exclusive with `monogram`.\n */\n svg?: string;\n /** Fallback artwork: 1–3 letters rendered centred (e.g. \"AK\"). */\n monogram?: string;\n /** Foreground (monogram text) colour. Default `#ffffff`. */\n color?: string;\n /** Background fill. Default `#111827`. */\n background?: string;\n /** Icon sizes to emit. Default `[180, 192, 512]` (180 = apple-touch). */\n sizes?: number[];\n /** Also emit a maskable 512 with a padded safe-zone. Default `true`. */\n maskable?: boolean;\n /** URL prefix for every icon `src`. Default `/icons`. */\n basePath?: string;\n}\n\nconst DEFAULT_SIZES = [180, 192, 512];\n\nfunction escapeXml(s: string): string {\n return s.replace(/[<>&\"']/g, (c) =>\n c === \"<\" ? \"&lt;\" : c === \">\" ? \"&gt;\" : c === \"&\" ? \"&amp;\" : c === '\"' ? \"&quot;\" : \"&apos;\",\n );\n}\n\n/** UTF-8-safe base64 for the data-URI embed (works in Node/Bun/browser). */\nfunction toBase64(s: string): string {\n // Reached via globalThis so the package needs no @types/node (browser-safe).\n const g = globalThis as unknown as {\n Buffer?: { from(input: string, enc: string): { toString(enc: string): string } };\n btoa?: (data: string) => string;\n };\n if (typeof g.Buffer !== \"undefined\") return g.Buffer.from(s, \"utf-8\").toString(\"base64\");\n const bytes = new TextEncoder().encode(s);\n let bin = \"\";\n for (const b of bytes) bin += String.fromCharCode(b);\n return g.btoa!(bin);\n}\n\n/** Render one square SVG icon document at `size`. `inset` = safe-zone fraction (maskable). */\nfunction renderIcon(size: number, inset: number, opts: IconSetOptions): string {\n const bg = opts.background ?? \"#111827\";\n const fg = opts.color ?? \"#ffffff\";\n const pad = Math.round(size * inset);\n const inner = size - pad * 2;\n let art: string;\n if (opts.svg) {\n const href = `data:image/svg+xml;base64,${toBase64(opts.svg)}`;\n art = `<image x=\"${pad}\" y=\"${pad}\" width=\"${inner}\" height=\"${inner}\" href=\"${href}\" preserveAspectRatio=\"xMidYMid meet\"/>`;\n } else {\n const text = escapeXml((opts.monogram ?? \"\").slice(0, 3).toUpperCase());\n // Font sized to the safe inner box; a touch smaller for breathing room.\n const fontSize = Math.round(inner * (text.length >= 3 ? 0.42 : 0.52));\n art =\n `<text x=\"50%\" y=\"50%\" fill=\"${fg}\" font-family=\"system-ui, -apple-system, Segoe UI, Roboto, sans-serif\" ` +\n `font-weight=\"700\" font-size=\"${fontSize}\" text-anchor=\"middle\" dominant-baseline=\"central\">${text}</text>`;\n }\n return (\n `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"${size}\" height=\"${size}\" viewBox=\"0 0 ${size} ${size}\">` +\n `<rect width=\"${size}\" height=\"${size}\" fill=\"${bg}\"/>` +\n art +\n `</svg>`\n );\n}\n\n/**\n * Generate a full icon set (SVG) + the matching manifest `icons[]`. Deterministic,\n * zero-dep. Give it either a source `svg` or a `monogram`; it emits apple-touch\n * (180), 192, 512 by default plus a padded maskable-512.\n */\nexport function buildIconSet(opts: IconSetOptions): IconSet {\n if (!opts?.svg && !opts?.monogram) {\n throw new Error(\"buildIconSet: provide either `svg` or `monogram`\");\n }\n const base = (opts.basePath ?? \"/icons\").replace(/\\/$/, \"\");\n const sizes = opts.sizes ?? DEFAULT_SIZES;\n const files: GeneratedIcon[] = [];\n const icons: ManifestIcon[] = [];\n\n for (const size of sizes) {\n const path = `${base}/icon-${size}.svg`;\n files.push({ path, content: renderIcon(size, 0, opts), type: \"image/svg+xml\", size, purpose: \"any\" });\n // apple-touch-icon is referenced via a <link> (see pwaMetaTags), not the\n // manifest, so only real manifest sizes go into icons[].\n if (size !== 180) {\n icons.push({ src: path, sizes: `${size}x${size}`, type: \"image/svg+xml\", purpose: \"any\" });\n }\n }\n\n if (opts.maskable !== false) {\n // Maskable safe-zone: keep content within the central 80% (10% inset).\n const path = `${base}/icon-maskable-512.svg`;\n files.push({ path, content: renderIcon(512, 0.1, opts), type: \"image/svg+xml\", size: 512, purpose: \"maskable\" });\n icons.push({ src: path, sizes: \"512x512\", type: \"image/svg+xml\", purpose: \"maskable\" });\n }\n\n return { files, icons };\n}\n\n// ---------------------------------------------------------------------------\n// Head meta tags\n// ---------------------------------------------------------------------------\n\nexport interface MetaTag {\n tag: \"meta\" | \"link\";\n attrs: Record<string, string>;\n}\n\nexport interface MetaTagsOptions {\n /** `<meta name=\"theme-color\">`. Default `#ffffff`. */\n themeColor?: string;\n /** apple-touch-icon href. Default `/icons/icon-180.svg`. */\n appleTouchIcon?: string;\n /** Manifest href for the `<link rel=\"manifest\">`. Default `/manifest.webmanifest`. */\n manifest?: string;\n /** apple-mobile-web-app-title. Default: omitted. */\n title?: string;\n /** apple-mobile-web-app-status-bar-style. Default `default`. */\n statusBarStyle?: \"default\" | \"black\" | \"black-translucent\";\n /** Emit the `*-web-app-capable` tags. Default `true`. */\n capable?: boolean;\n}\n\n/**\n * The head tags a PWA needs beyond the manifest link — returned as typed\n * descriptors so a consumer renders them in Next.js `metadata`, a Hono/JSX\n * head, or plain HTML. No framework assumption.\n */\nexport function pwaMetaTags(opts: MetaTagsOptions = {}): MetaTag[] {\n const tags: MetaTag[] = [];\n tags.push({ tag: \"link\", attrs: { rel: \"manifest\", href: opts.manifest ?? \"/manifest.webmanifest\" } });\n tags.push({ tag: \"meta\", attrs: { name: \"theme-color\", content: opts.themeColor ?? \"#ffffff\" } });\n // F021.11 — sizes: the icon is 180; iOS picks by it when several are linked.\n tags.push({ tag: \"link\", attrs: { rel: \"apple-touch-icon\", sizes: \"180x180\", href: opts.appleTouchIcon ?? \"/icons/icon-180.svg\" } });\n if (opts.capable !== false) {\n tags.push({ tag: \"meta\", attrs: { name: \"mobile-web-app-capable\", content: \"yes\" } });\n tags.push({ tag: \"meta\", attrs: { name: \"apple-mobile-web-app-capable\", content: \"yes\" } });\n tags.push({\n tag: \"meta\",\n attrs: { name: \"apple-mobile-web-app-status-bar-style\", content: opts.statusBarStyle ?? \"default\" },\n });\n }\n if (opts.title) {\n tags.push({ tag: \"meta\", attrs: { name: \"apple-mobile-web-app-title\", content: opts.title } });\n }\n return tags;\n}\n","/// <reference types=\"node\" />\n// F021.9 — `pwa-icons`: SVG + PNG icon set in one command, so no app keeps its\n// own sharp script. Prints the manifest icons[] and the apple-touch href.\nimport { mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { basename, dirname, join } from \"node:path\";\nimport { buildIconSet } from \"./manifest.js\";\nimport { rasterizeIconSet, nativeIconSet, NATIVE_BACKGROUND_REQUIRED } from \"./raster.js\";\n\nconst USAGE =\n \"usage: pwa-icons (--svg <file> | --monogram <text>) [--background <hex>] [--color <hex>] [--out public/icons] [--base-path /icons] [--root <web root, default: the parent of --out>]\\n pwa-icons --svg <file> --native <out dir> --background <#RRGGBB> (iOS AppIcon, Android adaptive, .ico, .icns)\";\n\nexport async function main(argv: string[]): Promise<number> {\n const arg = (name: string) => {\n const i = argv.indexOf(`--${name}`);\n return i >= 0 ? argv[i + 1] : undefined;\n };\n const svgFile = arg(\"svg\");\n const monogram = arg(\"monogram\");\n if (!svgFile && !monogram) {\n console.error(`pwa-icons: give --svg <file> or --monogram <text>\\n${USAGE}`);\n return 1;\n }\n // F021.12 — native sets. Background is required: a white symbol on a transparent\n // canvas renders as an empty icon (buzzalert, 25/10).\n const native = arg(\"native\");\n if (native !== undefined) {\n if (!svgFile) {\n console.error(`pwa-icons: --native needs --svg <file>\\n${USAGE}`);\n return 1;\n }\n const background = arg(\"background\");\n if (!background) {\n console.error(`pwa-icons: --native needs --background <#RRGGBB> — ${NATIVE_BACKGROUND_REQUIRED}`);\n return 1;\n }\n let files;\n try {\n files = await nativeIconSet(readFileSync(svgFile, \"utf8\"), { background });\n } catch (e) {\n console.error(`pwa-icons: ${(e as Error).message}`);\n return 1;\n }\n for (const f of files) {\n mkdirSync(dirname(join(native, f.path)), { recursive: true });\n writeFileSync(join(native, f.path), f.content);\n }\n console.log(JSON.stringify({ native: files.map((f) => f.path) }, null, 2));\n return 0;\n }\n const out = arg(\"out\") ?? \"public/icons\";\n const set = buildIconSet({\n svg: svgFile ? readFileSync(svgFile, \"utf8\") : undefined,\n monogram,\n background: arg(\"background\"),\n color: arg(\"color\"),\n basePath: arg(\"base-path\") ?? \"/icons\",\n });\n let raster;\n try {\n raster = await rasterizeIconSet(set);\n } catch (e) {\n console.error(`pwa-icons: ${(e as Error).message}`);\n return 1;\n }\n mkdirSync(out, { recursive: true });\n for (const f of set.files) writeFileSync(join(out, basename(f.path)), f.content);\n for (const f of raster.files) writeFileSync(join(out, basename(f.path)), f.content);\n // F021.11 — iOS asks the web ROOT for these on its own.\n const root = arg(\"root\") ?? dirname(out);\n for (const f of raster.rootFiles) writeFileSync(join(root, basename(f.path)), f.content);\n console.log(JSON.stringify({ icons: raster.icons, appleTouchIcon: raster.appleTouchIcon }, null, 2));\n return 0;\n}\n\n","#!/usr/bin/env node\n/// <reference types=\"node\" />\n// F021.9 — the `pwa-icons` entry point; the logic lives in cli.ts so tests can call it.\nimport { main } from \"./cli.js\";\n\nmain(process.argv.slice(2)).then((code) => process.exit(code));\n"]}
1
+ {"version":3,"sources":["../src/manifest.ts","../src/cli.ts","../src/bin.ts"],"names":[],"mappings":"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","file":"bin.js","sourcesContent":["/**\n * @broberg/pwa/manifest — the PWA *install* half (the update half lives in the\n * root export). A dependency-free manifest/meta factory + an SVG-based icon\n * generator, so a repo stops hand-rolling `app/manifest.ts`, a\n * `gen-pwa-icons.cjs` script, and a pile of apple-touch `<meta>` tags.\n *\n * Everything here is PURE string/object generation — no filesystem, no\n * rasteriser, no runtime deps — so it runs in Node, Bun, edge, a build script,\n * or the browser, and every piece is offline-unit-testable. Icons are emitted\n * as self-contained SVG documents. iOS does NOT use an SVG apple-touch-icon —\n * rasterise with `@broberg/pwa/raster` / the `pwa-icons` bin (F021.9).\n */\n\n// ---------------------------------------------------------------------------\n// Manifest\n// ---------------------------------------------------------------------------\n\nexport type Display = \"standalone\" | \"fullscreen\" | \"minimal-ui\" | \"browser\";\n\nexport interface ManifestIcon {\n src: string;\n sizes: string;\n type: string;\n /** \"any\" | \"maskable\" | \"monochrome\" (space-separated allowed). */\n purpose?: string;\n}\n\nexport interface WebAppManifest {\n name: string;\n short_name: string;\n start_url: string;\n scope: string;\n display: Display;\n theme_color: string;\n background_color: string;\n icons: ManifestIcon[];\n description?: string;\n lang?: string;\n orientation?: string;\n categories?: string[];\n id?: string;\n [key: string]: unknown;\n}\n\nexport interface DefineManifestOptions {\n /** App name (required). */\n name: string;\n /** Home-screen label. Default: `name`. */\n shortName?: string;\n /** Default `/`. */\n startUrl?: string;\n /** Default `/`. */\n scope?: string;\n /** Default `standalone`. */\n display?: Display;\n /** Address-bar / task-switcher colour. Default `#ffffff`. */\n themeColor?: string;\n /** Splash background. Default `#ffffff`. */\n backgroundColor?: string;\n description?: string;\n lang?: string;\n orientation?: string;\n categories?: string[];\n id?: string;\n /** Manifest `icons[]` — typically `buildIconSet(...).icons`. */\n icons?: ManifestIcon[];\n /** Escape hatch: extra top-level manifest members, merged last. */\n extra?: Record<string, unknown>;\n}\n\n/**\n * Build a spec-valid Web App Manifest object. Required members are defaulted so\n * a one-liner produces an installable manifest; every default is overridable\n * and `extra` merges last for anything not modelled (shortcuts, screenshots…).\n */\nexport function defineManifest(opts: DefineManifestOptions): WebAppManifest {\n if (!opts?.name) throw new Error(\"defineManifest: `name` is required\");\n const manifest: WebAppManifest = {\n name: opts.name,\n short_name: opts.shortName ?? opts.name,\n start_url: opts.startUrl ?? \"/\",\n scope: opts.scope ?? \"/\",\n display: opts.display ?? \"standalone\",\n theme_color: opts.themeColor ?? \"#ffffff\",\n background_color: opts.backgroundColor ?? \"#ffffff\",\n icons: opts.icons ?? [],\n };\n if (opts.description) manifest.description = opts.description;\n if (opts.lang) manifest.lang = opts.lang;\n if (opts.orientation) manifest.orientation = opts.orientation;\n if (opts.categories) manifest.categories = opts.categories;\n if (opts.id) manifest.id = opts.id;\n return { ...manifest, ...(opts.extra ?? {}) };\n}\n\n/** JSON-stringify a manifest for a `manifest.webmanifest` file. */\nexport function serializeManifest(\n manifest: WebAppManifest,\n opts: { pretty?: boolean } = {},\n): string {\n return JSON.stringify(manifest, null, opts.pretty === false ? undefined : 2);\n}\n\n// ---------------------------------------------------------------------------\n// Icon generation (SVG, zero-dep)\n// ---------------------------------------------------------------------------\n\nexport interface GeneratedIcon {\n /** URL path, e.g. `/icons/icon-192.svg`. */\n path: string;\n /** The SVG document. */\n content: string;\n type: \"image/svg+xml\";\n size: number;\n purpose: \"any\" | \"maskable\";\n}\n\nexport interface IconSet {\n files: GeneratedIcon[];\n /** Ready to drop into `defineManifest({ icons })`. */\n icons: ManifestIcon[];\n}\n\nexport interface IconSetOptions {\n /**\n * Source artwork as a full SVG document (`<svg …>…</svg>`). Embedded via a\n * data-URI `<image>` so it scales cleanly at any size regardless of its\n * internal coordinate system. Mutually exclusive with `monogram`.\n */\n svg?: string;\n /** Fallback artwork: 1–3 letters rendered centred (e.g. \"AK\"). */\n monogram?: string;\n /** Foreground (monogram text) colour. Default `#ffffff`. */\n color?: string;\n /** Background fill. Default `#111827`. */\n background?: string;\n /** Icon sizes to emit. Default `[180, 192, 512]` (180 = apple-touch). */\n sizes?: number[];\n /** Also emit a maskable 512 with a padded safe-zone. Default `true`. */\n maskable?: boolean;\n /** URL prefix for every icon `src`. Default `/icons`. */\n basePath?: string;\n}\n\nconst DEFAULT_SIZES = [180, 192, 512];\n\nfunction escapeXml(s: string): string {\n return s.replace(/[<>&\"']/g, (c) =>\n c === \"<\" ? \"&lt;\" : c === \">\" ? \"&gt;\" : c === \"&\" ? \"&amp;\" : c === '\"' ? \"&quot;\" : \"&apos;\",\n );\n}\n\n/** UTF-8-safe base64 for the data-URI embed (works in Node/Bun/browser). */\nfunction toBase64(s: string): string {\n // Reached via globalThis so the package needs no @types/node (browser-safe).\n const g = globalThis as unknown as {\n Buffer?: { from(input: string, enc: string): { toString(enc: string): string } };\n btoa?: (data: string) => string;\n };\n if (typeof g.Buffer !== \"undefined\") return g.Buffer.from(s, \"utf-8\").toString(\"base64\");\n const bytes = new TextEncoder().encode(s);\n let bin = \"\";\n for (const b of bytes) bin += String.fromCharCode(b);\n return g.btoa!(bin);\n}\n\n/** Render one square SVG icon document at `size`. `inset` = safe-zone fraction (maskable). */\nfunction renderIcon(size: number, inset: number, opts: IconSetOptions): string {\n const bg = opts.background ?? \"#111827\";\n const fg = opts.color ?? \"#ffffff\";\n const pad = Math.round(size * inset);\n const inner = size - pad * 2;\n let art: string;\n if (opts.svg) {\n const href = `data:image/svg+xml;base64,${toBase64(opts.svg)}`;\n art = `<image x=\"${pad}\" y=\"${pad}\" width=\"${inner}\" height=\"${inner}\" href=\"${href}\" preserveAspectRatio=\"xMidYMid meet\"/>`;\n } else {\n const text = escapeXml((opts.monogram ?? \"\").slice(0, 3).toUpperCase());\n // Font sized to the safe inner box; a touch smaller for breathing room.\n const fontSize = Math.round(inner * (text.length >= 3 ? 0.42 : 0.52));\n art =\n `<text x=\"50%\" y=\"50%\" fill=\"${fg}\" font-family=\"system-ui, -apple-system, Segoe UI, Roboto, sans-serif\" ` +\n `font-weight=\"700\" font-size=\"${fontSize}\" text-anchor=\"middle\" dominant-baseline=\"central\">${text}</text>`;\n }\n return (\n `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"${size}\" height=\"${size}\" viewBox=\"0 0 ${size} ${size}\">` +\n `<rect width=\"${size}\" height=\"${size}\" fill=\"${bg}\"/>` +\n art +\n `</svg>`\n );\n}\n\n/**\n * Generate a full icon set (SVG) + the matching manifest `icons[]`. Deterministic,\n * zero-dep. Give it either a source `svg` or a `monogram`; it emits apple-touch\n * (180), 192, 512 by default plus a padded maskable-512.\n */\nexport function buildIconSet(opts: IconSetOptions): IconSet {\n if (!opts?.svg && !opts?.monogram) {\n throw new Error(\"buildIconSet: provide either `svg` or `monogram`\");\n }\n const base = (opts.basePath ?? \"/icons\").replace(/\\/$/, \"\");\n const sizes = opts.sizes ?? DEFAULT_SIZES;\n const files: GeneratedIcon[] = [];\n const icons: ManifestIcon[] = [];\n\n for (const size of sizes) {\n const path = `${base}/icon-${size}.svg`;\n files.push({ path, content: renderIcon(size, 0, opts), type: \"image/svg+xml\", size, purpose: \"any\" });\n // apple-touch-icon is referenced via a <link> (see pwaMetaTags), not the\n // manifest, so only real manifest sizes go into icons[].\n if (size !== 180) {\n icons.push({ src: path, sizes: `${size}x${size}`, type: \"image/svg+xml\", purpose: \"any\" });\n }\n }\n\n if (opts.maskable !== false) {\n // Maskable safe-zone: keep content within the central 80% (10% inset).\n const path = `${base}/icon-maskable-512.svg`;\n files.push({ path, content: renderIcon(512, 0.1, opts), type: \"image/svg+xml\", size: 512, purpose: \"maskable\" });\n icons.push({ src: path, sizes: \"512x512\", type: \"image/svg+xml\", purpose: \"maskable\" });\n }\n\n return { files, icons };\n}\n\n// ---------------------------------------------------------------------------\n// Head meta tags\n// ---------------------------------------------------------------------------\n\nexport interface MetaTag {\n tag: \"meta\" | \"link\";\n attrs: Record<string, string>;\n}\n\nexport interface MetaTagsOptions {\n /** `<meta name=\"theme-color\">`. Default `#ffffff`. */\n themeColor?: string;\n /** apple-touch-icon href. Default `/icons/icon-180.svg`. */\n appleTouchIcon?: string;\n /** Manifest href for the `<link rel=\"manifest\">`. Default `/manifest.webmanifest`. */\n manifest?: string;\n /** apple-mobile-web-app-title. Default: omitted. */\n title?: string;\n /** apple-mobile-web-app-status-bar-style. Default `default`. */\n statusBarStyle?: \"default\" | \"black\" | \"black-translucent\";\n /** Emit the `*-web-app-capable` tags. Default `true`. */\n capable?: boolean;\n}\n\n/**\n * The head tags a PWA needs beyond the manifest link — returned as typed\n * descriptors so a consumer renders them in Next.js `metadata`, a Hono/JSX\n * head, or plain HTML. No framework assumption.\n */\nexport function pwaMetaTags(opts: MetaTagsOptions = {}): MetaTag[] {\n const tags: MetaTag[] = [];\n tags.push({ tag: \"link\", attrs: { rel: \"manifest\", href: opts.manifest ?? \"/manifest.webmanifest\" } });\n tags.push({ tag: \"meta\", attrs: { name: \"theme-color\", content: opts.themeColor ?? \"#ffffff\" } });\n // F021.11 — sizes: the icon is 180; iOS picks by it when several are linked.\n tags.push({ tag: \"link\", attrs: { rel: \"apple-touch-icon\", sizes: \"180x180\", href: opts.appleTouchIcon ?? \"/icons/icon-180.svg\" } });\n if (opts.capable !== false) {\n tags.push({ tag: \"meta\", attrs: { name: \"mobile-web-app-capable\", content: \"yes\" } });\n tags.push({ tag: \"meta\", attrs: { name: \"apple-mobile-web-app-capable\", content: \"yes\" } });\n tags.push({\n tag: \"meta\",\n attrs: { name: \"apple-mobile-web-app-status-bar-style\", content: opts.statusBarStyle ?? \"default\" },\n });\n }\n if (opts.title) {\n tags.push({ tag: \"meta\", attrs: { name: \"apple-mobile-web-app-title\", content: opts.title } });\n }\n return tags;\n}\n\n// F021.13 — the stale-page guard belongs with the other <head> helpers.\nexport { staleShellGuardScript, STALE_SHELL_STORAGE_KEY, STALE_SHELL_WINDOW_MS, STALE_SHELL_PARAM } from \"./stale-shell.js\";\n","/// <reference types=\"node\" />\n// F021.9 — `pwa-icons`: SVG + PNG icon set in one command, so no app keeps its\n// own sharp script. Prints the manifest icons[] and the apple-touch href.\nimport { mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { basename, dirname, join } from \"node:path\";\nimport { buildIconSet } from \"./manifest.js\";\nimport { rasterizeIconSet, nativeIconSet, NATIVE_BACKGROUND_REQUIRED } from \"./raster.js\";\n\nconst USAGE =\n \"usage: pwa-icons (--svg <file> | --monogram <text>) [--background <hex>] [--color <hex>] [--out public/icons] [--base-path /icons] [--root <web root, default: the parent of --out>]\\n pwa-icons --svg <file> --native <out dir> --background <#RRGGBB> (iOS AppIcon, Android adaptive, .ico, .icns)\";\n\nexport async function main(argv: string[]): Promise<number> {\n const arg = (name: string) => {\n const i = argv.indexOf(`--${name}`);\n return i >= 0 ? argv[i + 1] : undefined;\n };\n const svgFile = arg(\"svg\");\n const monogram = arg(\"monogram\");\n if (!svgFile && !monogram) {\n console.error(`pwa-icons: give --svg <file> or --monogram <text>\\n${USAGE}`);\n return 1;\n }\n // F021.12 — native sets. Background is required: a white symbol on a transparent\n // canvas renders as an empty icon (buzzalert, 25/10).\n const native = arg(\"native\");\n if (native !== undefined) {\n if (!svgFile) {\n console.error(`pwa-icons: --native needs --svg <file>\\n${USAGE}`);\n return 1;\n }\n const background = arg(\"background\");\n if (!background) {\n console.error(`pwa-icons: --native needs --background <#RRGGBB> — ${NATIVE_BACKGROUND_REQUIRED}`);\n return 1;\n }\n let files;\n try {\n files = await nativeIconSet(readFileSync(svgFile, \"utf8\"), { background });\n } catch (e) {\n console.error(`pwa-icons: ${(e as Error).message}`);\n return 1;\n }\n for (const f of files) {\n mkdirSync(dirname(join(native, f.path)), { recursive: true });\n writeFileSync(join(native, f.path), f.content);\n }\n console.log(JSON.stringify({ native: files.map((f) => f.path) }, null, 2));\n return 0;\n }\n const out = arg(\"out\") ?? \"public/icons\";\n const set = buildIconSet({\n svg: svgFile ? readFileSync(svgFile, \"utf8\") : undefined,\n monogram,\n background: arg(\"background\"),\n color: arg(\"color\"),\n basePath: arg(\"base-path\") ?? \"/icons\",\n });\n let raster;\n try {\n raster = await rasterizeIconSet(set);\n } catch (e) {\n console.error(`pwa-icons: ${(e as Error).message}`);\n return 1;\n }\n mkdirSync(out, { recursive: true });\n for (const f of set.files) writeFileSync(join(out, basename(f.path)), f.content);\n for (const f of raster.files) writeFileSync(join(out, basename(f.path)), f.content);\n // F021.11 — iOS asks the web ROOT for these on its own.\n const root = arg(\"root\") ?? dirname(out);\n for (const f of raster.rootFiles) writeFileSync(join(root, basename(f.path)), f.content);\n console.log(JSON.stringify({ icons: raster.icons, appleTouchIcon: raster.appleTouchIcon }, null, 2));\n return 0;\n}\n\n","#!/usr/bin/env node\n/// <reference types=\"node\" />\n// F021.9 — the `pwa-icons` entry point; the logic lives in cli.ts so tests can call it.\nimport { main } from \"./cli.js\";\n\nmain(process.argv.slice(2)).then((code) => process.exit(code));\n"]}
package/dist/manifest.cjs CHANGED
@@ -1,5 +1,39 @@
1
1
  'use strict';
2
2
 
3
+ // src/stale-shell.ts
4
+ var STALE_SHELL_STORAGE_KEY = "broberg.pwa.stale-reload";
5
+ var STALE_SHELL_WINDOW_MS = 3e4;
6
+ var STALE_SHELL_PARAM = "_v";
7
+ var staleShellGuardScript = (opts = {}) => {
8
+ const key = opts.storageKey ?? STALE_SHELL_STORAGE_KEY;
9
+ return `(function (w) {
10
+ try {
11
+ var her = new URL(w.location.href);
12
+ if (her.searchParams.has(${JSON.stringify(STALE_SHELL_PARAM)})) {
13
+ her.searchParams.delete(${JSON.stringify(STALE_SHELL_PARAM)});
14
+ w.history.replaceState(w.history.state, "", her.pathname + her.search + her.hash);
15
+ }
16
+ } catch (_) {}
17
+ w.addEventListener("error", function (e) {
18
+ var t = e.target;
19
+ if (!t || (t.tagName !== "SCRIPT" && t.tagName !== "LINK")) return;
20
+ var url;
21
+ try { url = new URL(t.src || t.href, w.location.href); } catch (_) { return; }
22
+ if (url.origin !== w.location.origin || url.pathname.indexOf("/assets/") !== 0) return;
23
+ try {
24
+ var sidst = Number(w.sessionStorage.getItem(${JSON.stringify(key)}) || 0);
25
+ if (Date.now() - sidst < ${STALE_SHELL_WINDOW_MS}) return;
26
+ w.sessionStorage.setItem(${JSON.stringify(key)}, String(Date.now()));
27
+ } catch (_) { return; }
28
+ try {
29
+ var frisk = new URL(w.location.href);
30
+ frisk.searchParams.set(${JSON.stringify(STALE_SHELL_PARAM)}, String(Date.now()));
31
+ w.location.replace(frisk.toString());
32
+ } catch (_) { w.location.reload(); }
33
+ }, true);
34
+ })(window);`;
35
+ };
36
+
3
37
  // src/manifest.ts
4
38
  function defineManifest(opts) {
5
39
  if (!opts?.name) throw new Error("defineManifest: `name` is required");
@@ -95,9 +129,13 @@ function pwaMetaTags(opts = {}) {
95
129
  return tags;
96
130
  }
97
131
 
132
+ exports.STALE_SHELL_PARAM = STALE_SHELL_PARAM;
133
+ exports.STALE_SHELL_STORAGE_KEY = STALE_SHELL_STORAGE_KEY;
134
+ exports.STALE_SHELL_WINDOW_MS = STALE_SHELL_WINDOW_MS;
98
135
  exports.buildIconSet = buildIconSet;
99
136
  exports.defineManifest = defineManifest;
100
137
  exports.pwaMetaTags = pwaMetaTags;
101
138
  exports.serializeManifest = serializeManifest;
139
+ exports.staleShellGuardScript = staleShellGuardScript;
102
140
  //# sourceMappingURL=manifest.cjs.map
103
141
  //# sourceMappingURL=manifest.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/manifest.ts"],"names":[],"mappings":"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","file":"manifest.cjs","sourcesContent":["/**\n * @broberg/pwa/manifest — the PWA *install* half (the update half lives in the\n * root export). A dependency-free manifest/meta factory + an SVG-based icon\n * generator, so a repo stops hand-rolling `app/manifest.ts`, a\n * `gen-pwa-icons.cjs` script, and a pile of apple-touch `<meta>` tags.\n *\n * Everything here is PURE string/object generation — no filesystem, no\n * rasteriser, no runtime deps — so it runs in Node, Bun, edge, a build script,\n * or the browser, and every piece is offline-unit-testable. Icons are emitted\n * as self-contained SVG documents. iOS does NOT use an SVG apple-touch-icon —\n * rasterise with `@broberg/pwa/raster` / the `pwa-icons` bin (F021.9).\n */\n\n// ---------------------------------------------------------------------------\n// Manifest\n// ---------------------------------------------------------------------------\n\nexport type Display = \"standalone\" | \"fullscreen\" | \"minimal-ui\" | \"browser\";\n\nexport interface ManifestIcon {\n src: string;\n sizes: string;\n type: string;\n /** \"any\" | \"maskable\" | \"monochrome\" (space-separated allowed). */\n purpose?: string;\n}\n\nexport interface WebAppManifest {\n name: string;\n short_name: string;\n start_url: string;\n scope: string;\n display: Display;\n theme_color: string;\n background_color: string;\n icons: ManifestIcon[];\n description?: string;\n lang?: string;\n orientation?: string;\n categories?: string[];\n id?: string;\n [key: string]: unknown;\n}\n\nexport interface DefineManifestOptions {\n /** App name (required). */\n name: string;\n /** Home-screen label. Default: `name`. */\n shortName?: string;\n /** Default `/`. */\n startUrl?: string;\n /** Default `/`. */\n scope?: string;\n /** Default `standalone`. */\n display?: Display;\n /** Address-bar / task-switcher colour. Default `#ffffff`. */\n themeColor?: string;\n /** Splash background. Default `#ffffff`. */\n backgroundColor?: string;\n description?: string;\n lang?: string;\n orientation?: string;\n categories?: string[];\n id?: string;\n /** Manifest `icons[]` — typically `buildIconSet(...).icons`. */\n icons?: ManifestIcon[];\n /** Escape hatch: extra top-level manifest members, merged last. */\n extra?: Record<string, unknown>;\n}\n\n/**\n * Build a spec-valid Web App Manifest object. Required members are defaulted so\n * a one-liner produces an installable manifest; every default is overridable\n * and `extra` merges last for anything not modelled (shortcuts, screenshots…).\n */\nexport function defineManifest(opts: DefineManifestOptions): WebAppManifest {\n if (!opts?.name) throw new Error(\"defineManifest: `name` is required\");\n const manifest: WebAppManifest = {\n name: opts.name,\n short_name: opts.shortName ?? opts.name,\n start_url: opts.startUrl ?? \"/\",\n scope: opts.scope ?? \"/\",\n display: opts.display ?? \"standalone\",\n theme_color: opts.themeColor ?? \"#ffffff\",\n background_color: opts.backgroundColor ?? \"#ffffff\",\n icons: opts.icons ?? [],\n };\n if (opts.description) manifest.description = opts.description;\n if (opts.lang) manifest.lang = opts.lang;\n if (opts.orientation) manifest.orientation = opts.orientation;\n if (opts.categories) manifest.categories = opts.categories;\n if (opts.id) manifest.id = opts.id;\n return { ...manifest, ...(opts.extra ?? {}) };\n}\n\n/** JSON-stringify a manifest for a `manifest.webmanifest` file. */\nexport function serializeManifest(\n manifest: WebAppManifest,\n opts: { pretty?: boolean } = {},\n): string {\n return JSON.stringify(manifest, null, opts.pretty === false ? undefined : 2);\n}\n\n// ---------------------------------------------------------------------------\n// Icon generation (SVG, zero-dep)\n// ---------------------------------------------------------------------------\n\nexport interface GeneratedIcon {\n /** URL path, e.g. `/icons/icon-192.svg`. */\n path: string;\n /** The SVG document. */\n content: string;\n type: \"image/svg+xml\";\n size: number;\n purpose: \"any\" | \"maskable\";\n}\n\nexport interface IconSet {\n files: GeneratedIcon[];\n /** Ready to drop into `defineManifest({ icons })`. */\n icons: ManifestIcon[];\n}\n\nexport interface IconSetOptions {\n /**\n * Source artwork as a full SVG document (`<svg …>…</svg>`). Embedded via a\n * data-URI `<image>` so it scales cleanly at any size regardless of its\n * internal coordinate system. Mutually exclusive with `monogram`.\n */\n svg?: string;\n /** Fallback artwork: 1–3 letters rendered centred (e.g. \"AK\"). */\n monogram?: string;\n /** Foreground (monogram text) colour. Default `#ffffff`. */\n color?: string;\n /** Background fill. Default `#111827`. */\n background?: string;\n /** Icon sizes to emit. Default `[180, 192, 512]` (180 = apple-touch). */\n sizes?: number[];\n /** Also emit a maskable 512 with a padded safe-zone. Default `true`. */\n maskable?: boolean;\n /** URL prefix for every icon `src`. Default `/icons`. */\n basePath?: string;\n}\n\nconst DEFAULT_SIZES = [180, 192, 512];\n\nfunction escapeXml(s: string): string {\n return s.replace(/[<>&\"']/g, (c) =>\n c === \"<\" ? \"&lt;\" : c === \">\" ? \"&gt;\" : c === \"&\" ? \"&amp;\" : c === '\"' ? \"&quot;\" : \"&apos;\",\n );\n}\n\n/** UTF-8-safe base64 for the data-URI embed (works in Node/Bun/browser). */\nfunction toBase64(s: string): string {\n // Reached via globalThis so the package needs no @types/node (browser-safe).\n const g = globalThis as unknown as {\n Buffer?: { from(input: string, enc: string): { toString(enc: string): string } };\n btoa?: (data: string) => string;\n };\n if (typeof g.Buffer !== \"undefined\") return g.Buffer.from(s, \"utf-8\").toString(\"base64\");\n const bytes = new TextEncoder().encode(s);\n let bin = \"\";\n for (const b of bytes) bin += String.fromCharCode(b);\n return g.btoa!(bin);\n}\n\n/** Render one square SVG icon document at `size`. `inset` = safe-zone fraction (maskable). */\nfunction renderIcon(size: number, inset: number, opts: IconSetOptions): string {\n const bg = opts.background ?? \"#111827\";\n const fg = opts.color ?? \"#ffffff\";\n const pad = Math.round(size * inset);\n const inner = size - pad * 2;\n let art: string;\n if (opts.svg) {\n const href = `data:image/svg+xml;base64,${toBase64(opts.svg)}`;\n art = `<image x=\"${pad}\" y=\"${pad}\" width=\"${inner}\" height=\"${inner}\" href=\"${href}\" preserveAspectRatio=\"xMidYMid meet\"/>`;\n } else {\n const text = escapeXml((opts.monogram ?? \"\").slice(0, 3).toUpperCase());\n // Font sized to the safe inner box; a touch smaller for breathing room.\n const fontSize = Math.round(inner * (text.length >= 3 ? 0.42 : 0.52));\n art =\n `<text x=\"50%\" y=\"50%\" fill=\"${fg}\" font-family=\"system-ui, -apple-system, Segoe UI, Roboto, sans-serif\" ` +\n `font-weight=\"700\" font-size=\"${fontSize}\" text-anchor=\"middle\" dominant-baseline=\"central\">${text}</text>`;\n }\n return (\n `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"${size}\" height=\"${size}\" viewBox=\"0 0 ${size} ${size}\">` +\n `<rect width=\"${size}\" height=\"${size}\" fill=\"${bg}\"/>` +\n art +\n `</svg>`\n );\n}\n\n/**\n * Generate a full icon set (SVG) + the matching manifest `icons[]`. Deterministic,\n * zero-dep. Give it either a source `svg` or a `monogram`; it emits apple-touch\n * (180), 192, 512 by default plus a padded maskable-512.\n */\nexport function buildIconSet(opts: IconSetOptions): IconSet {\n if (!opts?.svg && !opts?.monogram) {\n throw new Error(\"buildIconSet: provide either `svg` or `monogram`\");\n }\n const base = (opts.basePath ?? \"/icons\").replace(/\\/$/, \"\");\n const sizes = opts.sizes ?? DEFAULT_SIZES;\n const files: GeneratedIcon[] = [];\n const icons: ManifestIcon[] = [];\n\n for (const size of sizes) {\n const path = `${base}/icon-${size}.svg`;\n files.push({ path, content: renderIcon(size, 0, opts), type: \"image/svg+xml\", size, purpose: \"any\" });\n // apple-touch-icon is referenced via a <link> (see pwaMetaTags), not the\n // manifest, so only real manifest sizes go into icons[].\n if (size !== 180) {\n icons.push({ src: path, sizes: `${size}x${size}`, type: \"image/svg+xml\", purpose: \"any\" });\n }\n }\n\n if (opts.maskable !== false) {\n // Maskable safe-zone: keep content within the central 80% (10% inset).\n const path = `${base}/icon-maskable-512.svg`;\n files.push({ path, content: renderIcon(512, 0.1, opts), type: \"image/svg+xml\", size: 512, purpose: \"maskable\" });\n icons.push({ src: path, sizes: \"512x512\", type: \"image/svg+xml\", purpose: \"maskable\" });\n }\n\n return { files, icons };\n}\n\n// ---------------------------------------------------------------------------\n// Head meta tags\n// ---------------------------------------------------------------------------\n\nexport interface MetaTag {\n tag: \"meta\" | \"link\";\n attrs: Record<string, string>;\n}\n\nexport interface MetaTagsOptions {\n /** `<meta name=\"theme-color\">`. Default `#ffffff`. */\n themeColor?: string;\n /** apple-touch-icon href. Default `/icons/icon-180.svg`. */\n appleTouchIcon?: string;\n /** Manifest href for the `<link rel=\"manifest\">`. Default `/manifest.webmanifest`. */\n manifest?: string;\n /** apple-mobile-web-app-title. Default: omitted. */\n title?: string;\n /** apple-mobile-web-app-status-bar-style. Default `default`. */\n statusBarStyle?: \"default\" | \"black\" | \"black-translucent\";\n /** Emit the `*-web-app-capable` tags. Default `true`. */\n capable?: boolean;\n}\n\n/**\n * The head tags a PWA needs beyond the manifest link — returned as typed\n * descriptors so a consumer renders them in Next.js `metadata`, a Hono/JSX\n * head, or plain HTML. No framework assumption.\n */\nexport function pwaMetaTags(opts: MetaTagsOptions = {}): MetaTag[] {\n const tags: MetaTag[] = [];\n tags.push({ tag: \"link\", attrs: { rel: \"manifest\", href: opts.manifest ?? \"/manifest.webmanifest\" } });\n tags.push({ tag: \"meta\", attrs: { name: \"theme-color\", content: opts.themeColor ?? \"#ffffff\" } });\n // F021.11 — sizes: the icon is 180; iOS picks by it when several are linked.\n tags.push({ tag: \"link\", attrs: { rel: \"apple-touch-icon\", sizes: \"180x180\", href: opts.appleTouchIcon ?? \"/icons/icon-180.svg\" } });\n if (opts.capable !== false) {\n tags.push({ tag: \"meta\", attrs: { name: \"mobile-web-app-capable\", content: \"yes\" } });\n tags.push({ tag: \"meta\", attrs: { name: \"apple-mobile-web-app-capable\", content: \"yes\" } });\n tags.push({\n tag: \"meta\",\n attrs: { name: \"apple-mobile-web-app-status-bar-style\", content: opts.statusBarStyle ?? \"default\" },\n });\n }\n if (opts.title) {\n tags.push({ tag: \"meta\", attrs: { name: \"apple-mobile-web-app-title\", content: opts.title } });\n }\n return tags;\n}\n"]}
1
+ {"version":3,"sources":["../src/stale-shell.ts","../src/manifest.ts"],"names":[],"mappings":"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","file":"manifest.cjs","sourcesContent":["/**\n * A STALE PAGE RELOADS ITSELF — ONCE (F021.13).\n *\n * PORTED, NOT REWRITTEN: helpdesk's apps/console/src/pwa/foraeldet-side.ts\n * (helpdesk F047.5/F047.6, commit 5ca4df0, verified in production in chromium\n * and webkit). The script below is theirs line for line; the only change is\n * that the sessionStorage key is a parameter (default generic, so helpdesk can\n * keep its own \"helpdesk.genindlaest\").\n *\n * Christian 26/10: «Hver gang jeg starter pwa og der måske er kommet en\n * opdatering så får jeg bare en kæmpe hvid skærm».\n *\n * On start the browser shows its stored copy of the page. After a deploy that\n * copy points at /assets/ files that no longer exist (404), and nothing is drawn\n * — not even the theme's background. This guard sits in <head> BEFORE the app\n * script, notices that one of OUR OWN files failed to load, and reloads.\n *\n * THE RELOAD GOES TO A NEW ADDRESS. A plain reload() can get the SAME stored\n * page from the browser's store, so it navigates to `?_v=<time>` — an address\n * the store has never seen — and clears the parameter again on the fresh page.\n *\n * Only once per 30 s (sessionStorage), so a real outage does not become a loop.\n * Only our own same-origin /assets/ scripts and stylesheets: a failing image or a\n * foreign script is not a stale page.\n *\n * The string IS the guard: put it first in <head> (Vite: transformIndexHtml with\n * injectTo \"head-prepend\"; Next: a beforeInteractive inline script), and the\n * tests run the same string.\n *\n * Complements @broberg/deploy-core F033.20, which serves old hashed assets from\n * kept deploys on Fly Live; this covers every other host.\n */\nexport const STALE_SHELL_STORAGE_KEY = \"broberg.pwa.stale-reload\";\nexport const STALE_SHELL_WINDOW_MS = 30_000;\nexport const STALE_SHELL_PARAM = \"_v\";\n\nexport const staleShellGuardScript = (opts: { storageKey?: string } = {}): string => {\n const key = opts.storageKey ?? STALE_SHELL_STORAGE_KEY;\n return `(function (w) {\n try {\n var her = new URL(w.location.href);\n if (her.searchParams.has(${JSON.stringify(STALE_SHELL_PARAM)})) {\n her.searchParams.delete(${JSON.stringify(STALE_SHELL_PARAM)});\n w.history.replaceState(w.history.state, \"\", her.pathname + her.search + her.hash);\n }\n } catch (_) {}\n w.addEventListener(\"error\", function (e) {\n var t = e.target;\n if (!t || (t.tagName !== \"SCRIPT\" && t.tagName !== \"LINK\")) return;\n var url;\n try { url = new URL(t.src || t.href, w.location.href); } catch (_) { return; }\n if (url.origin !== w.location.origin || url.pathname.indexOf(\"/assets/\") !== 0) return;\n try {\n var sidst = Number(w.sessionStorage.getItem(${JSON.stringify(key)}) || 0);\n if (Date.now() - sidst < ${STALE_SHELL_WINDOW_MS}) return;\n w.sessionStorage.setItem(${JSON.stringify(key)}, String(Date.now()));\n } catch (_) { return; }\n try {\n var frisk = new URL(w.location.href);\n frisk.searchParams.set(${JSON.stringify(STALE_SHELL_PARAM)}, String(Date.now()));\n w.location.replace(frisk.toString());\n } catch (_) { w.location.reload(); }\n }, true);\n})(window);`;\n};\n","/**\n * @broberg/pwa/manifest — the PWA *install* half (the update half lives in the\n * root export). A dependency-free manifest/meta factory + an SVG-based icon\n * generator, so a repo stops hand-rolling `app/manifest.ts`, a\n * `gen-pwa-icons.cjs` script, and a pile of apple-touch `<meta>` tags.\n *\n * Everything here is PURE string/object generation — no filesystem, no\n * rasteriser, no runtime deps — so it runs in Node, Bun, edge, a build script,\n * or the browser, and every piece is offline-unit-testable. Icons are emitted\n * as self-contained SVG documents. iOS does NOT use an SVG apple-touch-icon —\n * rasterise with `@broberg/pwa/raster` / the `pwa-icons` bin (F021.9).\n */\n\n// ---------------------------------------------------------------------------\n// Manifest\n// ---------------------------------------------------------------------------\n\nexport type Display = \"standalone\" | \"fullscreen\" | \"minimal-ui\" | \"browser\";\n\nexport interface ManifestIcon {\n src: string;\n sizes: string;\n type: string;\n /** \"any\" | \"maskable\" | \"monochrome\" (space-separated allowed). */\n purpose?: string;\n}\n\nexport interface WebAppManifest {\n name: string;\n short_name: string;\n start_url: string;\n scope: string;\n display: Display;\n theme_color: string;\n background_color: string;\n icons: ManifestIcon[];\n description?: string;\n lang?: string;\n orientation?: string;\n categories?: string[];\n id?: string;\n [key: string]: unknown;\n}\n\nexport interface DefineManifestOptions {\n /** App name (required). */\n name: string;\n /** Home-screen label. Default: `name`. */\n shortName?: string;\n /** Default `/`. */\n startUrl?: string;\n /** Default `/`. */\n scope?: string;\n /** Default `standalone`. */\n display?: Display;\n /** Address-bar / task-switcher colour. Default `#ffffff`. */\n themeColor?: string;\n /** Splash background. Default `#ffffff`. */\n backgroundColor?: string;\n description?: string;\n lang?: string;\n orientation?: string;\n categories?: string[];\n id?: string;\n /** Manifest `icons[]` — typically `buildIconSet(...).icons`. */\n icons?: ManifestIcon[];\n /** Escape hatch: extra top-level manifest members, merged last. */\n extra?: Record<string, unknown>;\n}\n\n/**\n * Build a spec-valid Web App Manifest object. Required members are defaulted so\n * a one-liner produces an installable manifest; every default is overridable\n * and `extra` merges last for anything not modelled (shortcuts, screenshots…).\n */\nexport function defineManifest(opts: DefineManifestOptions): WebAppManifest {\n if (!opts?.name) throw new Error(\"defineManifest: `name` is required\");\n const manifest: WebAppManifest = {\n name: opts.name,\n short_name: opts.shortName ?? opts.name,\n start_url: opts.startUrl ?? \"/\",\n scope: opts.scope ?? \"/\",\n display: opts.display ?? \"standalone\",\n theme_color: opts.themeColor ?? \"#ffffff\",\n background_color: opts.backgroundColor ?? \"#ffffff\",\n icons: opts.icons ?? [],\n };\n if (opts.description) manifest.description = opts.description;\n if (opts.lang) manifest.lang = opts.lang;\n if (opts.orientation) manifest.orientation = opts.orientation;\n if (opts.categories) manifest.categories = opts.categories;\n if (opts.id) manifest.id = opts.id;\n return { ...manifest, ...(opts.extra ?? {}) };\n}\n\n/** JSON-stringify a manifest for a `manifest.webmanifest` file. */\nexport function serializeManifest(\n manifest: WebAppManifest,\n opts: { pretty?: boolean } = {},\n): string {\n return JSON.stringify(manifest, null, opts.pretty === false ? undefined : 2);\n}\n\n// ---------------------------------------------------------------------------\n// Icon generation (SVG, zero-dep)\n// ---------------------------------------------------------------------------\n\nexport interface GeneratedIcon {\n /** URL path, e.g. `/icons/icon-192.svg`. */\n path: string;\n /** The SVG document. */\n content: string;\n type: \"image/svg+xml\";\n size: number;\n purpose: \"any\" | \"maskable\";\n}\n\nexport interface IconSet {\n files: GeneratedIcon[];\n /** Ready to drop into `defineManifest({ icons })`. */\n icons: ManifestIcon[];\n}\n\nexport interface IconSetOptions {\n /**\n * Source artwork as a full SVG document (`<svg …>…</svg>`). Embedded via a\n * data-URI `<image>` so it scales cleanly at any size regardless of its\n * internal coordinate system. Mutually exclusive with `monogram`.\n */\n svg?: string;\n /** Fallback artwork: 1–3 letters rendered centred (e.g. \"AK\"). */\n monogram?: string;\n /** Foreground (monogram text) colour. Default `#ffffff`. */\n color?: string;\n /** Background fill. Default `#111827`. */\n background?: string;\n /** Icon sizes to emit. Default `[180, 192, 512]` (180 = apple-touch). */\n sizes?: number[];\n /** Also emit a maskable 512 with a padded safe-zone. Default `true`. */\n maskable?: boolean;\n /** URL prefix for every icon `src`. Default `/icons`. */\n basePath?: string;\n}\n\nconst DEFAULT_SIZES = [180, 192, 512];\n\nfunction escapeXml(s: string): string {\n return s.replace(/[<>&\"']/g, (c) =>\n c === \"<\" ? \"&lt;\" : c === \">\" ? \"&gt;\" : c === \"&\" ? \"&amp;\" : c === '\"' ? \"&quot;\" : \"&apos;\",\n );\n}\n\n/** UTF-8-safe base64 for the data-URI embed (works in Node/Bun/browser). */\nfunction toBase64(s: string): string {\n // Reached via globalThis so the package needs no @types/node (browser-safe).\n const g = globalThis as unknown as {\n Buffer?: { from(input: string, enc: string): { toString(enc: string): string } };\n btoa?: (data: string) => string;\n };\n if (typeof g.Buffer !== \"undefined\") return g.Buffer.from(s, \"utf-8\").toString(\"base64\");\n const bytes = new TextEncoder().encode(s);\n let bin = \"\";\n for (const b of bytes) bin += String.fromCharCode(b);\n return g.btoa!(bin);\n}\n\n/** Render one square SVG icon document at `size`. `inset` = safe-zone fraction (maskable). */\nfunction renderIcon(size: number, inset: number, opts: IconSetOptions): string {\n const bg = opts.background ?? \"#111827\";\n const fg = opts.color ?? \"#ffffff\";\n const pad = Math.round(size * inset);\n const inner = size - pad * 2;\n let art: string;\n if (opts.svg) {\n const href = `data:image/svg+xml;base64,${toBase64(opts.svg)}`;\n art = `<image x=\"${pad}\" y=\"${pad}\" width=\"${inner}\" height=\"${inner}\" href=\"${href}\" preserveAspectRatio=\"xMidYMid meet\"/>`;\n } else {\n const text = escapeXml((opts.monogram ?? \"\").slice(0, 3).toUpperCase());\n // Font sized to the safe inner box; a touch smaller for breathing room.\n const fontSize = Math.round(inner * (text.length >= 3 ? 0.42 : 0.52));\n art =\n `<text x=\"50%\" y=\"50%\" fill=\"${fg}\" font-family=\"system-ui, -apple-system, Segoe UI, Roboto, sans-serif\" ` +\n `font-weight=\"700\" font-size=\"${fontSize}\" text-anchor=\"middle\" dominant-baseline=\"central\">${text}</text>`;\n }\n return (\n `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"${size}\" height=\"${size}\" viewBox=\"0 0 ${size} ${size}\">` +\n `<rect width=\"${size}\" height=\"${size}\" fill=\"${bg}\"/>` +\n art +\n `</svg>`\n );\n}\n\n/**\n * Generate a full icon set (SVG) + the matching manifest `icons[]`. Deterministic,\n * zero-dep. Give it either a source `svg` or a `monogram`; it emits apple-touch\n * (180), 192, 512 by default plus a padded maskable-512.\n */\nexport function buildIconSet(opts: IconSetOptions): IconSet {\n if (!opts?.svg && !opts?.monogram) {\n throw new Error(\"buildIconSet: provide either `svg` or `monogram`\");\n }\n const base = (opts.basePath ?? \"/icons\").replace(/\\/$/, \"\");\n const sizes = opts.sizes ?? DEFAULT_SIZES;\n const files: GeneratedIcon[] = [];\n const icons: ManifestIcon[] = [];\n\n for (const size of sizes) {\n const path = `${base}/icon-${size}.svg`;\n files.push({ path, content: renderIcon(size, 0, opts), type: \"image/svg+xml\", size, purpose: \"any\" });\n // apple-touch-icon is referenced via a <link> (see pwaMetaTags), not the\n // manifest, so only real manifest sizes go into icons[].\n if (size !== 180) {\n icons.push({ src: path, sizes: `${size}x${size}`, type: \"image/svg+xml\", purpose: \"any\" });\n }\n }\n\n if (opts.maskable !== false) {\n // Maskable safe-zone: keep content within the central 80% (10% inset).\n const path = `${base}/icon-maskable-512.svg`;\n files.push({ path, content: renderIcon(512, 0.1, opts), type: \"image/svg+xml\", size: 512, purpose: \"maskable\" });\n icons.push({ src: path, sizes: \"512x512\", type: \"image/svg+xml\", purpose: \"maskable\" });\n }\n\n return { files, icons };\n}\n\n// ---------------------------------------------------------------------------\n// Head meta tags\n// ---------------------------------------------------------------------------\n\nexport interface MetaTag {\n tag: \"meta\" | \"link\";\n attrs: Record<string, string>;\n}\n\nexport interface MetaTagsOptions {\n /** `<meta name=\"theme-color\">`. Default `#ffffff`. */\n themeColor?: string;\n /** apple-touch-icon href. Default `/icons/icon-180.svg`. */\n appleTouchIcon?: string;\n /** Manifest href for the `<link rel=\"manifest\">`. Default `/manifest.webmanifest`. */\n manifest?: string;\n /** apple-mobile-web-app-title. Default: omitted. */\n title?: string;\n /** apple-mobile-web-app-status-bar-style. Default `default`. */\n statusBarStyle?: \"default\" | \"black\" | \"black-translucent\";\n /** Emit the `*-web-app-capable` tags. Default `true`. */\n capable?: boolean;\n}\n\n/**\n * The head tags a PWA needs beyond the manifest link — returned as typed\n * descriptors so a consumer renders them in Next.js `metadata`, a Hono/JSX\n * head, or plain HTML. No framework assumption.\n */\nexport function pwaMetaTags(opts: MetaTagsOptions = {}): MetaTag[] {\n const tags: MetaTag[] = [];\n tags.push({ tag: \"link\", attrs: { rel: \"manifest\", href: opts.manifest ?? \"/manifest.webmanifest\" } });\n tags.push({ tag: \"meta\", attrs: { name: \"theme-color\", content: opts.themeColor ?? \"#ffffff\" } });\n // F021.11 — sizes: the icon is 180; iOS picks by it when several are linked.\n tags.push({ tag: \"link\", attrs: { rel: \"apple-touch-icon\", sizes: \"180x180\", href: opts.appleTouchIcon ?? \"/icons/icon-180.svg\" } });\n if (opts.capable !== false) {\n tags.push({ tag: \"meta\", attrs: { name: \"mobile-web-app-capable\", content: \"yes\" } });\n tags.push({ tag: \"meta\", attrs: { name: \"apple-mobile-web-app-capable\", content: \"yes\" } });\n tags.push({\n tag: \"meta\",\n attrs: { name: \"apple-mobile-web-app-status-bar-style\", content: opts.statusBarStyle ?? \"default\" },\n });\n }\n if (opts.title) {\n tags.push({ tag: \"meta\", attrs: { name: \"apple-mobile-web-app-title\", content: opts.title } });\n }\n return tags;\n}\n\n// F021.13 — the stale-page guard belongs with the other <head> helpers.\nexport { staleShellGuardScript, STALE_SHELL_STORAGE_KEY, STALE_SHELL_WINDOW_MS, STALE_SHELL_PARAM } from \"./stale-shell.js\";\n"]}
@@ -1,3 +1,42 @@
1
+ /**
2
+ * A STALE PAGE RELOADS ITSELF — ONCE (F021.13).
3
+ *
4
+ * PORTED, NOT REWRITTEN: helpdesk's apps/console/src/pwa/foraeldet-side.ts
5
+ * (helpdesk F047.5/F047.6, commit 5ca4df0, verified in production in chromium
6
+ * and webkit). The script below is theirs line for line; the only change is
7
+ * that the sessionStorage key is a parameter (default generic, so helpdesk can
8
+ * keep its own "helpdesk.genindlaest").
9
+ *
10
+ * Christian 26/10: «Hver gang jeg starter pwa og der måske er kommet en
11
+ * opdatering så får jeg bare en kæmpe hvid skærm».
12
+ *
13
+ * On start the browser shows its stored copy of the page. After a deploy that
14
+ * copy points at /assets/ files that no longer exist (404), and nothing is drawn
15
+ * — not even the theme's background. This guard sits in <head> BEFORE the app
16
+ * script, notices that one of OUR OWN files failed to load, and reloads.
17
+ *
18
+ * THE RELOAD GOES TO A NEW ADDRESS. A plain reload() can get the SAME stored
19
+ * page from the browser's store, so it navigates to `?_v=<time>` — an address
20
+ * the store has never seen — and clears the parameter again on the fresh page.
21
+ *
22
+ * Only once per 30 s (sessionStorage), so a real outage does not become a loop.
23
+ * Only our own same-origin /assets/ scripts and stylesheets: a failing image or a
24
+ * foreign script is not a stale page.
25
+ *
26
+ * The string IS the guard: put it first in <head> (Vite: transformIndexHtml with
27
+ * injectTo "head-prepend"; Next: a beforeInteractive inline script), and the
28
+ * tests run the same string.
29
+ *
30
+ * Complements @broberg/deploy-core F033.20, which serves old hashed assets from
31
+ * kept deploys on Fly Live; this covers every other host.
32
+ */
33
+ declare const STALE_SHELL_STORAGE_KEY = "broberg.pwa.stale-reload";
34
+ declare const STALE_SHELL_WINDOW_MS = 30000;
35
+ declare const STALE_SHELL_PARAM = "_v";
36
+ declare const staleShellGuardScript: (opts?: {
37
+ storageKey?: string;
38
+ }) => string;
39
+
1
40
  /**
2
41
  * @broberg/pwa/manifest — the PWA *install* half (the update half lives in the
3
42
  * root export). A dependency-free manifest/meta factory + an SVG-based icon
@@ -134,4 +173,4 @@ interface MetaTagsOptions {
134
173
  */
135
174
  declare function pwaMetaTags(opts?: MetaTagsOptions): MetaTag[];
136
175
 
137
- export { type DefineManifestOptions, type Display, type GeneratedIcon, type IconSet, type IconSetOptions, type ManifestIcon, type MetaTag, type MetaTagsOptions, type WebAppManifest, buildIconSet, defineManifest, pwaMetaTags, serializeManifest };
176
+ export { type DefineManifestOptions, type Display, type GeneratedIcon, type IconSet, type IconSetOptions, type ManifestIcon, type MetaTag, type MetaTagsOptions, STALE_SHELL_PARAM, STALE_SHELL_STORAGE_KEY, STALE_SHELL_WINDOW_MS, type WebAppManifest, buildIconSet, defineManifest, pwaMetaTags, serializeManifest, staleShellGuardScript };
@@ -1,3 +1,42 @@
1
+ /**
2
+ * A STALE PAGE RELOADS ITSELF — ONCE (F021.13).
3
+ *
4
+ * PORTED, NOT REWRITTEN: helpdesk's apps/console/src/pwa/foraeldet-side.ts
5
+ * (helpdesk F047.5/F047.6, commit 5ca4df0, verified in production in chromium
6
+ * and webkit). The script below is theirs line for line; the only change is
7
+ * that the sessionStorage key is a parameter (default generic, so helpdesk can
8
+ * keep its own "helpdesk.genindlaest").
9
+ *
10
+ * Christian 26/10: «Hver gang jeg starter pwa og der måske er kommet en
11
+ * opdatering så får jeg bare en kæmpe hvid skærm».
12
+ *
13
+ * On start the browser shows its stored copy of the page. After a deploy that
14
+ * copy points at /assets/ files that no longer exist (404), and nothing is drawn
15
+ * — not even the theme's background. This guard sits in <head> BEFORE the app
16
+ * script, notices that one of OUR OWN files failed to load, and reloads.
17
+ *
18
+ * THE RELOAD GOES TO A NEW ADDRESS. A plain reload() can get the SAME stored
19
+ * page from the browser's store, so it navigates to `?_v=<time>` — an address
20
+ * the store has never seen — and clears the parameter again on the fresh page.
21
+ *
22
+ * Only once per 30 s (sessionStorage), so a real outage does not become a loop.
23
+ * Only our own same-origin /assets/ scripts and stylesheets: a failing image or a
24
+ * foreign script is not a stale page.
25
+ *
26
+ * The string IS the guard: put it first in <head> (Vite: transformIndexHtml with
27
+ * injectTo "head-prepend"; Next: a beforeInteractive inline script), and the
28
+ * tests run the same string.
29
+ *
30
+ * Complements @broberg/deploy-core F033.20, which serves old hashed assets from
31
+ * kept deploys on Fly Live; this covers every other host.
32
+ */
33
+ declare const STALE_SHELL_STORAGE_KEY = "broberg.pwa.stale-reload";
34
+ declare const STALE_SHELL_WINDOW_MS = 30000;
35
+ declare const STALE_SHELL_PARAM = "_v";
36
+ declare const staleShellGuardScript: (opts?: {
37
+ storageKey?: string;
38
+ }) => string;
39
+
1
40
  /**
2
41
  * @broberg/pwa/manifest — the PWA *install* half (the update half lives in the
3
42
  * root export). A dependency-free manifest/meta factory + an SVG-based icon
@@ -134,4 +173,4 @@ interface MetaTagsOptions {
134
173
  */
135
174
  declare function pwaMetaTags(opts?: MetaTagsOptions): MetaTag[];
136
175
 
137
- export { type DefineManifestOptions, type Display, type GeneratedIcon, type IconSet, type IconSetOptions, type ManifestIcon, type MetaTag, type MetaTagsOptions, type WebAppManifest, buildIconSet, defineManifest, pwaMetaTags, serializeManifest };
176
+ export { type DefineManifestOptions, type Display, type GeneratedIcon, type IconSet, type IconSetOptions, type ManifestIcon, type MetaTag, type MetaTagsOptions, STALE_SHELL_PARAM, STALE_SHELL_STORAGE_KEY, STALE_SHELL_WINDOW_MS, type WebAppManifest, buildIconSet, defineManifest, pwaMetaTags, serializeManifest, staleShellGuardScript };
package/dist/manifest.js CHANGED
@@ -1,3 +1,37 @@
1
+ // src/stale-shell.ts
2
+ var STALE_SHELL_STORAGE_KEY = "broberg.pwa.stale-reload";
3
+ var STALE_SHELL_WINDOW_MS = 3e4;
4
+ var STALE_SHELL_PARAM = "_v";
5
+ var staleShellGuardScript = (opts = {}) => {
6
+ const key = opts.storageKey ?? STALE_SHELL_STORAGE_KEY;
7
+ return `(function (w) {
8
+ try {
9
+ var her = new URL(w.location.href);
10
+ if (her.searchParams.has(${JSON.stringify(STALE_SHELL_PARAM)})) {
11
+ her.searchParams.delete(${JSON.stringify(STALE_SHELL_PARAM)});
12
+ w.history.replaceState(w.history.state, "", her.pathname + her.search + her.hash);
13
+ }
14
+ } catch (_) {}
15
+ w.addEventListener("error", function (e) {
16
+ var t = e.target;
17
+ if (!t || (t.tagName !== "SCRIPT" && t.tagName !== "LINK")) return;
18
+ var url;
19
+ try { url = new URL(t.src || t.href, w.location.href); } catch (_) { return; }
20
+ if (url.origin !== w.location.origin || url.pathname.indexOf("/assets/") !== 0) return;
21
+ try {
22
+ var sidst = Number(w.sessionStorage.getItem(${JSON.stringify(key)}) || 0);
23
+ if (Date.now() - sidst < ${STALE_SHELL_WINDOW_MS}) return;
24
+ w.sessionStorage.setItem(${JSON.stringify(key)}, String(Date.now()));
25
+ } catch (_) { return; }
26
+ try {
27
+ var frisk = new URL(w.location.href);
28
+ frisk.searchParams.set(${JSON.stringify(STALE_SHELL_PARAM)}, String(Date.now()));
29
+ w.location.replace(frisk.toString());
30
+ } catch (_) { w.location.reload(); }
31
+ }, true);
32
+ })(window);`;
33
+ };
34
+
1
35
  // src/manifest.ts
2
36
  function defineManifest(opts) {
3
37
  if (!opts?.name) throw new Error("defineManifest: `name` is required");
@@ -93,6 +127,6 @@ function pwaMetaTags(opts = {}) {
93
127
  return tags;
94
128
  }
95
129
 
96
- export { buildIconSet, defineManifest, pwaMetaTags, serializeManifest };
130
+ export { STALE_SHELL_PARAM, STALE_SHELL_STORAGE_KEY, STALE_SHELL_WINDOW_MS, buildIconSet, defineManifest, pwaMetaTags, serializeManifest, staleShellGuardScript };
97
131
  //# sourceMappingURL=manifest.js.map
98
132
  //# sourceMappingURL=manifest.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/manifest.ts"],"names":[],"mappings":"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","file":"manifest.js","sourcesContent":["/**\n * @broberg/pwa/manifest — the PWA *install* half (the update half lives in the\n * root export). A dependency-free manifest/meta factory + an SVG-based icon\n * generator, so a repo stops hand-rolling `app/manifest.ts`, a\n * `gen-pwa-icons.cjs` script, and a pile of apple-touch `<meta>` tags.\n *\n * Everything here is PURE string/object generation — no filesystem, no\n * rasteriser, no runtime deps — so it runs in Node, Bun, edge, a build script,\n * or the browser, and every piece is offline-unit-testable. Icons are emitted\n * as self-contained SVG documents. iOS does NOT use an SVG apple-touch-icon —\n * rasterise with `@broberg/pwa/raster` / the `pwa-icons` bin (F021.9).\n */\n\n// ---------------------------------------------------------------------------\n// Manifest\n// ---------------------------------------------------------------------------\n\nexport type Display = \"standalone\" | \"fullscreen\" | \"minimal-ui\" | \"browser\";\n\nexport interface ManifestIcon {\n src: string;\n sizes: string;\n type: string;\n /** \"any\" | \"maskable\" | \"monochrome\" (space-separated allowed). */\n purpose?: string;\n}\n\nexport interface WebAppManifest {\n name: string;\n short_name: string;\n start_url: string;\n scope: string;\n display: Display;\n theme_color: string;\n background_color: string;\n icons: ManifestIcon[];\n description?: string;\n lang?: string;\n orientation?: string;\n categories?: string[];\n id?: string;\n [key: string]: unknown;\n}\n\nexport interface DefineManifestOptions {\n /** App name (required). */\n name: string;\n /** Home-screen label. Default: `name`. */\n shortName?: string;\n /** Default `/`. */\n startUrl?: string;\n /** Default `/`. */\n scope?: string;\n /** Default `standalone`. */\n display?: Display;\n /** Address-bar / task-switcher colour. Default `#ffffff`. */\n themeColor?: string;\n /** Splash background. Default `#ffffff`. */\n backgroundColor?: string;\n description?: string;\n lang?: string;\n orientation?: string;\n categories?: string[];\n id?: string;\n /** Manifest `icons[]` — typically `buildIconSet(...).icons`. */\n icons?: ManifestIcon[];\n /** Escape hatch: extra top-level manifest members, merged last. */\n extra?: Record<string, unknown>;\n}\n\n/**\n * Build a spec-valid Web App Manifest object. Required members are defaulted so\n * a one-liner produces an installable manifest; every default is overridable\n * and `extra` merges last for anything not modelled (shortcuts, screenshots…).\n */\nexport function defineManifest(opts: DefineManifestOptions): WebAppManifest {\n if (!opts?.name) throw new Error(\"defineManifest: `name` is required\");\n const manifest: WebAppManifest = {\n name: opts.name,\n short_name: opts.shortName ?? opts.name,\n start_url: opts.startUrl ?? \"/\",\n scope: opts.scope ?? \"/\",\n display: opts.display ?? \"standalone\",\n theme_color: opts.themeColor ?? \"#ffffff\",\n background_color: opts.backgroundColor ?? \"#ffffff\",\n icons: opts.icons ?? [],\n };\n if (opts.description) manifest.description = opts.description;\n if (opts.lang) manifest.lang = opts.lang;\n if (opts.orientation) manifest.orientation = opts.orientation;\n if (opts.categories) manifest.categories = opts.categories;\n if (opts.id) manifest.id = opts.id;\n return { ...manifest, ...(opts.extra ?? {}) };\n}\n\n/** JSON-stringify a manifest for a `manifest.webmanifest` file. */\nexport function serializeManifest(\n manifest: WebAppManifest,\n opts: { pretty?: boolean } = {},\n): string {\n return JSON.stringify(manifest, null, opts.pretty === false ? undefined : 2);\n}\n\n// ---------------------------------------------------------------------------\n// Icon generation (SVG, zero-dep)\n// ---------------------------------------------------------------------------\n\nexport interface GeneratedIcon {\n /** URL path, e.g. `/icons/icon-192.svg`. */\n path: string;\n /** The SVG document. */\n content: string;\n type: \"image/svg+xml\";\n size: number;\n purpose: \"any\" | \"maskable\";\n}\n\nexport interface IconSet {\n files: GeneratedIcon[];\n /** Ready to drop into `defineManifest({ icons })`. */\n icons: ManifestIcon[];\n}\n\nexport interface IconSetOptions {\n /**\n * Source artwork as a full SVG document (`<svg …>…</svg>`). Embedded via a\n * data-URI `<image>` so it scales cleanly at any size regardless of its\n * internal coordinate system. Mutually exclusive with `monogram`.\n */\n svg?: string;\n /** Fallback artwork: 1–3 letters rendered centred (e.g. \"AK\"). */\n monogram?: string;\n /** Foreground (monogram text) colour. Default `#ffffff`. */\n color?: string;\n /** Background fill. Default `#111827`. */\n background?: string;\n /** Icon sizes to emit. Default `[180, 192, 512]` (180 = apple-touch). */\n sizes?: number[];\n /** Also emit a maskable 512 with a padded safe-zone. Default `true`. */\n maskable?: boolean;\n /** URL prefix for every icon `src`. Default `/icons`. */\n basePath?: string;\n}\n\nconst DEFAULT_SIZES = [180, 192, 512];\n\nfunction escapeXml(s: string): string {\n return s.replace(/[<>&\"']/g, (c) =>\n c === \"<\" ? \"&lt;\" : c === \">\" ? \"&gt;\" : c === \"&\" ? \"&amp;\" : c === '\"' ? \"&quot;\" : \"&apos;\",\n );\n}\n\n/** UTF-8-safe base64 for the data-URI embed (works in Node/Bun/browser). */\nfunction toBase64(s: string): string {\n // Reached via globalThis so the package needs no @types/node (browser-safe).\n const g = globalThis as unknown as {\n Buffer?: { from(input: string, enc: string): { toString(enc: string): string } };\n btoa?: (data: string) => string;\n };\n if (typeof g.Buffer !== \"undefined\") return g.Buffer.from(s, \"utf-8\").toString(\"base64\");\n const bytes = new TextEncoder().encode(s);\n let bin = \"\";\n for (const b of bytes) bin += String.fromCharCode(b);\n return g.btoa!(bin);\n}\n\n/** Render one square SVG icon document at `size`. `inset` = safe-zone fraction (maskable). */\nfunction renderIcon(size: number, inset: number, opts: IconSetOptions): string {\n const bg = opts.background ?? \"#111827\";\n const fg = opts.color ?? \"#ffffff\";\n const pad = Math.round(size * inset);\n const inner = size - pad * 2;\n let art: string;\n if (opts.svg) {\n const href = `data:image/svg+xml;base64,${toBase64(opts.svg)}`;\n art = `<image x=\"${pad}\" y=\"${pad}\" width=\"${inner}\" height=\"${inner}\" href=\"${href}\" preserveAspectRatio=\"xMidYMid meet\"/>`;\n } else {\n const text = escapeXml((opts.monogram ?? \"\").slice(0, 3).toUpperCase());\n // Font sized to the safe inner box; a touch smaller for breathing room.\n const fontSize = Math.round(inner * (text.length >= 3 ? 0.42 : 0.52));\n art =\n `<text x=\"50%\" y=\"50%\" fill=\"${fg}\" font-family=\"system-ui, -apple-system, Segoe UI, Roboto, sans-serif\" ` +\n `font-weight=\"700\" font-size=\"${fontSize}\" text-anchor=\"middle\" dominant-baseline=\"central\">${text}</text>`;\n }\n return (\n `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"${size}\" height=\"${size}\" viewBox=\"0 0 ${size} ${size}\">` +\n `<rect width=\"${size}\" height=\"${size}\" fill=\"${bg}\"/>` +\n art +\n `</svg>`\n );\n}\n\n/**\n * Generate a full icon set (SVG) + the matching manifest `icons[]`. Deterministic,\n * zero-dep. Give it either a source `svg` or a `monogram`; it emits apple-touch\n * (180), 192, 512 by default plus a padded maskable-512.\n */\nexport function buildIconSet(opts: IconSetOptions): IconSet {\n if (!opts?.svg && !opts?.monogram) {\n throw new Error(\"buildIconSet: provide either `svg` or `monogram`\");\n }\n const base = (opts.basePath ?? \"/icons\").replace(/\\/$/, \"\");\n const sizes = opts.sizes ?? DEFAULT_SIZES;\n const files: GeneratedIcon[] = [];\n const icons: ManifestIcon[] = [];\n\n for (const size of sizes) {\n const path = `${base}/icon-${size}.svg`;\n files.push({ path, content: renderIcon(size, 0, opts), type: \"image/svg+xml\", size, purpose: \"any\" });\n // apple-touch-icon is referenced via a <link> (see pwaMetaTags), not the\n // manifest, so only real manifest sizes go into icons[].\n if (size !== 180) {\n icons.push({ src: path, sizes: `${size}x${size}`, type: \"image/svg+xml\", purpose: \"any\" });\n }\n }\n\n if (opts.maskable !== false) {\n // Maskable safe-zone: keep content within the central 80% (10% inset).\n const path = `${base}/icon-maskable-512.svg`;\n files.push({ path, content: renderIcon(512, 0.1, opts), type: \"image/svg+xml\", size: 512, purpose: \"maskable\" });\n icons.push({ src: path, sizes: \"512x512\", type: \"image/svg+xml\", purpose: \"maskable\" });\n }\n\n return { files, icons };\n}\n\n// ---------------------------------------------------------------------------\n// Head meta tags\n// ---------------------------------------------------------------------------\n\nexport interface MetaTag {\n tag: \"meta\" | \"link\";\n attrs: Record<string, string>;\n}\n\nexport interface MetaTagsOptions {\n /** `<meta name=\"theme-color\">`. Default `#ffffff`. */\n themeColor?: string;\n /** apple-touch-icon href. Default `/icons/icon-180.svg`. */\n appleTouchIcon?: string;\n /** Manifest href for the `<link rel=\"manifest\">`. Default `/manifest.webmanifest`. */\n manifest?: string;\n /** apple-mobile-web-app-title. Default: omitted. */\n title?: string;\n /** apple-mobile-web-app-status-bar-style. Default `default`. */\n statusBarStyle?: \"default\" | \"black\" | \"black-translucent\";\n /** Emit the `*-web-app-capable` tags. Default `true`. */\n capable?: boolean;\n}\n\n/**\n * The head tags a PWA needs beyond the manifest link — returned as typed\n * descriptors so a consumer renders them in Next.js `metadata`, a Hono/JSX\n * head, or plain HTML. No framework assumption.\n */\nexport function pwaMetaTags(opts: MetaTagsOptions = {}): MetaTag[] {\n const tags: MetaTag[] = [];\n tags.push({ tag: \"link\", attrs: { rel: \"manifest\", href: opts.manifest ?? \"/manifest.webmanifest\" } });\n tags.push({ tag: \"meta\", attrs: { name: \"theme-color\", content: opts.themeColor ?? \"#ffffff\" } });\n // F021.11 — sizes: the icon is 180; iOS picks by it when several are linked.\n tags.push({ tag: \"link\", attrs: { rel: \"apple-touch-icon\", sizes: \"180x180\", href: opts.appleTouchIcon ?? \"/icons/icon-180.svg\" } });\n if (opts.capable !== false) {\n tags.push({ tag: \"meta\", attrs: { name: \"mobile-web-app-capable\", content: \"yes\" } });\n tags.push({ tag: \"meta\", attrs: { name: \"apple-mobile-web-app-capable\", content: \"yes\" } });\n tags.push({\n tag: \"meta\",\n attrs: { name: \"apple-mobile-web-app-status-bar-style\", content: opts.statusBarStyle ?? \"default\" },\n });\n }\n if (opts.title) {\n tags.push({ tag: \"meta\", attrs: { name: \"apple-mobile-web-app-title\", content: opts.title } });\n }\n return tags;\n}\n"]}
1
+ {"version":3,"sources":["../src/stale-shell.ts","../src/manifest.ts"],"names":[],"mappings":"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","file":"manifest.js","sourcesContent":["/**\n * A STALE PAGE RELOADS ITSELF — ONCE (F021.13).\n *\n * PORTED, NOT REWRITTEN: helpdesk's apps/console/src/pwa/foraeldet-side.ts\n * (helpdesk F047.5/F047.6, commit 5ca4df0, verified in production in chromium\n * and webkit). The script below is theirs line for line; the only change is\n * that the sessionStorage key is a parameter (default generic, so helpdesk can\n * keep its own \"helpdesk.genindlaest\").\n *\n * Christian 26/10: «Hver gang jeg starter pwa og der måske er kommet en\n * opdatering så får jeg bare en kæmpe hvid skærm».\n *\n * On start the browser shows its stored copy of the page. After a deploy that\n * copy points at /assets/ files that no longer exist (404), and nothing is drawn\n * — not even the theme's background. This guard sits in <head> BEFORE the app\n * script, notices that one of OUR OWN files failed to load, and reloads.\n *\n * THE RELOAD GOES TO A NEW ADDRESS. A plain reload() can get the SAME stored\n * page from the browser's store, so it navigates to `?_v=<time>` — an address\n * the store has never seen — and clears the parameter again on the fresh page.\n *\n * Only once per 30 s (sessionStorage), so a real outage does not become a loop.\n * Only our own same-origin /assets/ scripts and stylesheets: a failing image or a\n * foreign script is not a stale page.\n *\n * The string IS the guard: put it first in <head> (Vite: transformIndexHtml with\n * injectTo \"head-prepend\"; Next: a beforeInteractive inline script), and the\n * tests run the same string.\n *\n * Complements @broberg/deploy-core F033.20, which serves old hashed assets from\n * kept deploys on Fly Live; this covers every other host.\n */\nexport const STALE_SHELL_STORAGE_KEY = \"broberg.pwa.stale-reload\";\nexport const STALE_SHELL_WINDOW_MS = 30_000;\nexport const STALE_SHELL_PARAM = \"_v\";\n\nexport const staleShellGuardScript = (opts: { storageKey?: string } = {}): string => {\n const key = opts.storageKey ?? STALE_SHELL_STORAGE_KEY;\n return `(function (w) {\n try {\n var her = new URL(w.location.href);\n if (her.searchParams.has(${JSON.stringify(STALE_SHELL_PARAM)})) {\n her.searchParams.delete(${JSON.stringify(STALE_SHELL_PARAM)});\n w.history.replaceState(w.history.state, \"\", her.pathname + her.search + her.hash);\n }\n } catch (_) {}\n w.addEventListener(\"error\", function (e) {\n var t = e.target;\n if (!t || (t.tagName !== \"SCRIPT\" && t.tagName !== \"LINK\")) return;\n var url;\n try { url = new URL(t.src || t.href, w.location.href); } catch (_) { return; }\n if (url.origin !== w.location.origin || url.pathname.indexOf(\"/assets/\") !== 0) return;\n try {\n var sidst = Number(w.sessionStorage.getItem(${JSON.stringify(key)}) || 0);\n if (Date.now() - sidst < ${STALE_SHELL_WINDOW_MS}) return;\n w.sessionStorage.setItem(${JSON.stringify(key)}, String(Date.now()));\n } catch (_) { return; }\n try {\n var frisk = new URL(w.location.href);\n frisk.searchParams.set(${JSON.stringify(STALE_SHELL_PARAM)}, String(Date.now()));\n w.location.replace(frisk.toString());\n } catch (_) { w.location.reload(); }\n }, true);\n})(window);`;\n};\n","/**\n * @broberg/pwa/manifest — the PWA *install* half (the update half lives in the\n * root export). A dependency-free manifest/meta factory + an SVG-based icon\n * generator, so a repo stops hand-rolling `app/manifest.ts`, a\n * `gen-pwa-icons.cjs` script, and a pile of apple-touch `<meta>` tags.\n *\n * Everything here is PURE string/object generation — no filesystem, no\n * rasteriser, no runtime deps — so it runs in Node, Bun, edge, a build script,\n * or the browser, and every piece is offline-unit-testable. Icons are emitted\n * as self-contained SVG documents. iOS does NOT use an SVG apple-touch-icon —\n * rasterise with `@broberg/pwa/raster` / the `pwa-icons` bin (F021.9).\n */\n\n// ---------------------------------------------------------------------------\n// Manifest\n// ---------------------------------------------------------------------------\n\nexport type Display = \"standalone\" | \"fullscreen\" | \"minimal-ui\" | \"browser\";\n\nexport interface ManifestIcon {\n src: string;\n sizes: string;\n type: string;\n /** \"any\" | \"maskable\" | \"monochrome\" (space-separated allowed). */\n purpose?: string;\n}\n\nexport interface WebAppManifest {\n name: string;\n short_name: string;\n start_url: string;\n scope: string;\n display: Display;\n theme_color: string;\n background_color: string;\n icons: ManifestIcon[];\n description?: string;\n lang?: string;\n orientation?: string;\n categories?: string[];\n id?: string;\n [key: string]: unknown;\n}\n\nexport interface DefineManifestOptions {\n /** App name (required). */\n name: string;\n /** Home-screen label. Default: `name`. */\n shortName?: string;\n /** Default `/`. */\n startUrl?: string;\n /** Default `/`. */\n scope?: string;\n /** Default `standalone`. */\n display?: Display;\n /** Address-bar / task-switcher colour. Default `#ffffff`. */\n themeColor?: string;\n /** Splash background. Default `#ffffff`. */\n backgroundColor?: string;\n description?: string;\n lang?: string;\n orientation?: string;\n categories?: string[];\n id?: string;\n /** Manifest `icons[]` — typically `buildIconSet(...).icons`. */\n icons?: ManifestIcon[];\n /** Escape hatch: extra top-level manifest members, merged last. */\n extra?: Record<string, unknown>;\n}\n\n/**\n * Build a spec-valid Web App Manifest object. Required members are defaulted so\n * a one-liner produces an installable manifest; every default is overridable\n * and `extra` merges last for anything not modelled (shortcuts, screenshots…).\n */\nexport function defineManifest(opts: DefineManifestOptions): WebAppManifest {\n if (!opts?.name) throw new Error(\"defineManifest: `name` is required\");\n const manifest: WebAppManifest = {\n name: opts.name,\n short_name: opts.shortName ?? opts.name,\n start_url: opts.startUrl ?? \"/\",\n scope: opts.scope ?? \"/\",\n display: opts.display ?? \"standalone\",\n theme_color: opts.themeColor ?? \"#ffffff\",\n background_color: opts.backgroundColor ?? \"#ffffff\",\n icons: opts.icons ?? [],\n };\n if (opts.description) manifest.description = opts.description;\n if (opts.lang) manifest.lang = opts.lang;\n if (opts.orientation) manifest.orientation = opts.orientation;\n if (opts.categories) manifest.categories = opts.categories;\n if (opts.id) manifest.id = opts.id;\n return { ...manifest, ...(opts.extra ?? {}) };\n}\n\n/** JSON-stringify a manifest for a `manifest.webmanifest` file. */\nexport function serializeManifest(\n manifest: WebAppManifest,\n opts: { pretty?: boolean } = {},\n): string {\n return JSON.stringify(manifest, null, opts.pretty === false ? undefined : 2);\n}\n\n// ---------------------------------------------------------------------------\n// Icon generation (SVG, zero-dep)\n// ---------------------------------------------------------------------------\n\nexport interface GeneratedIcon {\n /** URL path, e.g. `/icons/icon-192.svg`. */\n path: string;\n /** The SVG document. */\n content: string;\n type: \"image/svg+xml\";\n size: number;\n purpose: \"any\" | \"maskable\";\n}\n\nexport interface IconSet {\n files: GeneratedIcon[];\n /** Ready to drop into `defineManifest({ icons })`. */\n icons: ManifestIcon[];\n}\n\nexport interface IconSetOptions {\n /**\n * Source artwork as a full SVG document (`<svg …>…</svg>`). Embedded via a\n * data-URI `<image>` so it scales cleanly at any size regardless of its\n * internal coordinate system. Mutually exclusive with `monogram`.\n */\n svg?: string;\n /** Fallback artwork: 1–3 letters rendered centred (e.g. \"AK\"). */\n monogram?: string;\n /** Foreground (monogram text) colour. Default `#ffffff`. */\n color?: string;\n /** Background fill. Default `#111827`. */\n background?: string;\n /** Icon sizes to emit. Default `[180, 192, 512]` (180 = apple-touch). */\n sizes?: number[];\n /** Also emit a maskable 512 with a padded safe-zone. Default `true`. */\n maskable?: boolean;\n /** URL prefix for every icon `src`. Default `/icons`. */\n basePath?: string;\n}\n\nconst DEFAULT_SIZES = [180, 192, 512];\n\nfunction escapeXml(s: string): string {\n return s.replace(/[<>&\"']/g, (c) =>\n c === \"<\" ? \"&lt;\" : c === \">\" ? \"&gt;\" : c === \"&\" ? \"&amp;\" : c === '\"' ? \"&quot;\" : \"&apos;\",\n );\n}\n\n/** UTF-8-safe base64 for the data-URI embed (works in Node/Bun/browser). */\nfunction toBase64(s: string): string {\n // Reached via globalThis so the package needs no @types/node (browser-safe).\n const g = globalThis as unknown as {\n Buffer?: { from(input: string, enc: string): { toString(enc: string): string } };\n btoa?: (data: string) => string;\n };\n if (typeof g.Buffer !== \"undefined\") return g.Buffer.from(s, \"utf-8\").toString(\"base64\");\n const bytes = new TextEncoder().encode(s);\n let bin = \"\";\n for (const b of bytes) bin += String.fromCharCode(b);\n return g.btoa!(bin);\n}\n\n/** Render one square SVG icon document at `size`. `inset` = safe-zone fraction (maskable). */\nfunction renderIcon(size: number, inset: number, opts: IconSetOptions): string {\n const bg = opts.background ?? \"#111827\";\n const fg = opts.color ?? \"#ffffff\";\n const pad = Math.round(size * inset);\n const inner = size - pad * 2;\n let art: string;\n if (opts.svg) {\n const href = `data:image/svg+xml;base64,${toBase64(opts.svg)}`;\n art = `<image x=\"${pad}\" y=\"${pad}\" width=\"${inner}\" height=\"${inner}\" href=\"${href}\" preserveAspectRatio=\"xMidYMid meet\"/>`;\n } else {\n const text = escapeXml((opts.monogram ?? \"\").slice(0, 3).toUpperCase());\n // Font sized to the safe inner box; a touch smaller for breathing room.\n const fontSize = Math.round(inner * (text.length >= 3 ? 0.42 : 0.52));\n art =\n `<text x=\"50%\" y=\"50%\" fill=\"${fg}\" font-family=\"system-ui, -apple-system, Segoe UI, Roboto, sans-serif\" ` +\n `font-weight=\"700\" font-size=\"${fontSize}\" text-anchor=\"middle\" dominant-baseline=\"central\">${text}</text>`;\n }\n return (\n `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"${size}\" height=\"${size}\" viewBox=\"0 0 ${size} ${size}\">` +\n `<rect width=\"${size}\" height=\"${size}\" fill=\"${bg}\"/>` +\n art +\n `</svg>`\n );\n}\n\n/**\n * Generate a full icon set (SVG) + the matching manifest `icons[]`. Deterministic,\n * zero-dep. Give it either a source `svg` or a `monogram`; it emits apple-touch\n * (180), 192, 512 by default plus a padded maskable-512.\n */\nexport function buildIconSet(opts: IconSetOptions): IconSet {\n if (!opts?.svg && !opts?.monogram) {\n throw new Error(\"buildIconSet: provide either `svg` or `monogram`\");\n }\n const base = (opts.basePath ?? \"/icons\").replace(/\\/$/, \"\");\n const sizes = opts.sizes ?? DEFAULT_SIZES;\n const files: GeneratedIcon[] = [];\n const icons: ManifestIcon[] = [];\n\n for (const size of sizes) {\n const path = `${base}/icon-${size}.svg`;\n files.push({ path, content: renderIcon(size, 0, opts), type: \"image/svg+xml\", size, purpose: \"any\" });\n // apple-touch-icon is referenced via a <link> (see pwaMetaTags), not the\n // manifest, so only real manifest sizes go into icons[].\n if (size !== 180) {\n icons.push({ src: path, sizes: `${size}x${size}`, type: \"image/svg+xml\", purpose: \"any\" });\n }\n }\n\n if (opts.maskable !== false) {\n // Maskable safe-zone: keep content within the central 80% (10% inset).\n const path = `${base}/icon-maskable-512.svg`;\n files.push({ path, content: renderIcon(512, 0.1, opts), type: \"image/svg+xml\", size: 512, purpose: \"maskable\" });\n icons.push({ src: path, sizes: \"512x512\", type: \"image/svg+xml\", purpose: \"maskable\" });\n }\n\n return { files, icons };\n}\n\n// ---------------------------------------------------------------------------\n// Head meta tags\n// ---------------------------------------------------------------------------\n\nexport interface MetaTag {\n tag: \"meta\" | \"link\";\n attrs: Record<string, string>;\n}\n\nexport interface MetaTagsOptions {\n /** `<meta name=\"theme-color\">`. Default `#ffffff`. */\n themeColor?: string;\n /** apple-touch-icon href. Default `/icons/icon-180.svg`. */\n appleTouchIcon?: string;\n /** Manifest href for the `<link rel=\"manifest\">`. Default `/manifest.webmanifest`. */\n manifest?: string;\n /** apple-mobile-web-app-title. Default: omitted. */\n title?: string;\n /** apple-mobile-web-app-status-bar-style. Default `default`. */\n statusBarStyle?: \"default\" | \"black\" | \"black-translucent\";\n /** Emit the `*-web-app-capable` tags. Default `true`. */\n capable?: boolean;\n}\n\n/**\n * The head tags a PWA needs beyond the manifest link — returned as typed\n * descriptors so a consumer renders them in Next.js `metadata`, a Hono/JSX\n * head, or plain HTML. No framework assumption.\n */\nexport function pwaMetaTags(opts: MetaTagsOptions = {}): MetaTag[] {\n const tags: MetaTag[] = [];\n tags.push({ tag: \"link\", attrs: { rel: \"manifest\", href: opts.manifest ?? \"/manifest.webmanifest\" } });\n tags.push({ tag: \"meta\", attrs: { name: \"theme-color\", content: opts.themeColor ?? \"#ffffff\" } });\n // F021.11 — sizes: the icon is 180; iOS picks by it when several are linked.\n tags.push({ tag: \"link\", attrs: { rel: \"apple-touch-icon\", sizes: \"180x180\", href: opts.appleTouchIcon ?? \"/icons/icon-180.svg\" } });\n if (opts.capable !== false) {\n tags.push({ tag: \"meta\", attrs: { name: \"mobile-web-app-capable\", content: \"yes\" } });\n tags.push({ tag: \"meta\", attrs: { name: \"apple-mobile-web-app-capable\", content: \"yes\" } });\n tags.push({\n tag: \"meta\",\n attrs: { name: \"apple-mobile-web-app-status-bar-style\", content: opts.statusBarStyle ?? \"default\" },\n });\n }\n if (opts.title) {\n tags.push({ tag: \"meta\", attrs: { name: \"apple-mobile-web-app-title\", content: opts.title } });\n }\n return tags;\n}\n\n// F021.13 — the stale-page guard belongs with the other <head> helpers.\nexport { staleShellGuardScript, STALE_SHELL_STORAGE_KEY, STALE_SHELL_WINDOW_MS, STALE_SHELL_PARAM } from \"./stale-shell.js\";\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@broberg/pwa",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "description": "Fleet PWA primitive: a framework-agnostic service-worker update-detection controller (new-version-available lifecycle) + a React hook and unstyled banner skeleton + a service-worker skip-waiting helper. Zero runtime deps; ship-dark.",
5
5
  "type": "module",
6
6
  "license": "MIT",