// Builds the pages ipx serves: each page's TypeScript from web/src, types stripped and minified // by swc into a script of its own (app.js, login.js), and the page minified, its // `); out = out.replace(//, () => ``); // The icons are named by their contents too. They are kept a day under a fixed name, and a new // logo went unseen for that day, in browsers and at Cloudflare's edge. The server ignores the // query; /favicon.ico, which a browser asks for on its own, cannot carry one. out = out.replace(/(href|src|srcset|data-light|data-dark)="\/(favicon\.png|favicon-dark\.png|apple-touch-icon\.png|logo\.svg|logo-dark\.svg)"/g, (_, attr, file) => `${attr}="/${file}?v=${hash(fs.readFileSync(path.join(here, file)))}"`); if (!minify) return { html: out, js, script }; const r = html.minifySync(out, { minifyJs: false, minifyCss: true, removeComments: true }); const bad = (r.errors || []).filter(e => e.level === 'Error'); if (bad.length) throw new Error(`${name}: ${bad.map(e => e.message).join('; ')}`); return { html: r.code, js, script }; } if (process.argv[1] === fileURLToPath(import.meta.url)) { const out = process.argv[2] || path.join(here, 'dist'); fs.mkdirSync(out, { recursive: true }); fs.writeFileSync(path.join(out, STYLE), buildStyle()); for (const name of Object.keys(PAGES)) { const { html, js, script } = buildPage(name); fs.writeFileSync(path.join(out, name), html); fs.writeFileSync(path.join(out, script), js); } }