Automerge backed Markdown editor for the Atmosphere
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960import { createHash } from 'node:crypto'import { readFile, readdir, rm, writeFile } from 'node:fs/promises'import path from 'node:path'
const edition = process.argv[2]if (edition !== 'classic' && edition !== 'atproto') throw new Error('Expected classic or atproto edition')
// Run only after Vite has successfully written all bundles. A closeBundle hook// also runs when chunk generation fails, masking that error with missing assets.const output = process.env.REAMS_BUILD_OUT_DIR ? path.resolve(process.env.REAMS_BUILD_OUT_DIR) : path.resolve('dist', edition)const serviceWorkerPath = path.join(output, 'service-worker.js')const indexPath = path.join(output, 'index.html')const serviceWorker = await readFile(serviceWorkerPath, 'utf8')const html = await readFile(indexPath, 'utf8')const assets = ['index.html', 'manifest.json', 'icon-192.png', 'icon-512.png', 'icon.svg', 'icon-maskable.svg']if (edition === 'atproto') assets.push('oauth-login-scope.json')async function visit(relative) { for (const entry of await readdir(path.join(output, relative), { withFileTypes: true })) { const child = `${relative}/${entry.name}` if (entry.isDirectory()) await visit(child) else if (entry.isFile()) assets.push(child) }}await visit('assets')await visit('fonts')assets.sort()// Digest the ORIGINAL completed html (before the cache-identity meta is// injected) so the release identity never depends on itself — no circular hash.const digest = createHash('sha256')digest.update(serviceWorker)for (const asset of assets) digest.update(asset).update(await readFile(path.join(output, asset)))const cacheName = `reams-${edition}-${digest.digest('hex').slice(0, 16)}`// Inject the running page's cache identity into the finalized html. The page// reads this meta to compare its own release against worker-reported releases.const meta = `<meta name="reams-cache" content="${cacheName}" />`if (html.split('</head>').length !== 2) throw new Error(`Expected one </head> in ${indexPath}`)const finalHtml = html.replace('</head>', `${meta}</head>`)// Per-asset integrity is computed from the FINAL files — index.html includes// the injected meta — so the worker's expected digests match the bytes the// browser actually fetches and caches at install time.const integrity = {}for (const asset of assets) { const content = asset === 'index.html' ? finalHtml : await readFile(path.join(output, asset)) integrity[`/${asset}`] = createHash('sha256').update(content).digest('base64')}const replacements = { '%REAMS_CACHE_NAME%': cacheName, '%REAMS_ASSETS%': JSON.stringify(assets.map(asset => `/${asset}`)), '%REAMS_INTEGRITY%': JSON.stringify(integrity), '%REAMS_EDITION%': edition,}let generated = serviceWorkerfor (const [token, value] of Object.entries(replacements)) { if (!generated.includes(token)) throw new Error(`Expected ${token} in ${serviceWorkerPath}`) generated = generated.replaceAll(token, value)}await writeFile(serviceWorkerPath, generated)await writeFile(indexPath, finalHtml)if (edition === 'classic') await rm(path.join(output, 'oauth-callback.html'), { force: true })