/**
* Post-build script: inline remaining external assets (favicon) into index.html
* and remove leftover files so only a single HTML file remains.
*/
import { readFileSync, writeFileSync, rmSync, existsSync } from 'fs'
import { join, dirname } from 'path'
import { fileURLToPath } from 'url'
const __dirname = dirname(fileURLToPath(import.meta.url))
const distDir = join(__dirname, '..', 'dist')
// Read favicon SVG and encode as data URI
const faviconPath = join(distDir, 'favicon.svg')
if (existsSync(faviconPath)) {
const svgContent = readFileSync(faviconPath, 'utf8')
const encoded = Buffer.from(svgContent).toString('base64')
const dataUri = `data:image/svg+xml;base64,${encoded}`
// Replace the favicon link in index.html
const indexPath = join(distDir, 'index.html')
let html = readFileSync(indexPath, 'utf8')
html = html.replace(
/]*href="[^"]*favicon\.svg"[^>]*\/?>/i,
``
)
writeFileSync(indexPath, html)
// Remove the standalone SVG
rmSync(faviconPath)
console.log('[inline-assets] Inlined favicon.svg into index.html')
}
// Remove any other leftover asset files (e.g. icons.svg from Svelte compiler,
// and the source pwa-icon.svg which is only needed to regenerate the PNGs).
for (const leftover of ['icons.svg', 'pwa-icon.svg']) {
const p = join(distDir, leftover)
if (existsSync(p)) {
rmSync(p)
console.log(`[inline-assets] Removed ${leftover}`)
}
}
// Remove assets directory if it exists
const assetsDir = join(distDir, 'assets')
if (existsSync(assetsDir)) {
rmSync(assetsDir, { recursive: true })
console.log('[inline-assets] Removed assets/ directory')
}
// ---- Production network sandbox CSP -----------------------------
// Replace the dev CSP with the strict, shipped one. The app is single-file and
// offline-first, so script/style keep 'unsafe-inline' (they are inline), but
// every channel that could exfiltrate vault data is denied at the browser level:
// connect-src 'none' (no fetch/XHR/WebSocket anywhere), form-action 'none',
// object-src 'none', base-uri 'none'. (Clickjacking via frame-ancestors /
// X-Frame-Options is a header-only directive and must be set by the serving
// layer, so it is intentionally absent here.)
const indexPath = join(distDir, 'index.html')
let html = readFileSync(indexPath, 'utf8')
const PROD_CSP = [
"default-src 'self';",
"script-src 'self' 'unsafe-inline';",
"style-src 'self' 'unsafe-inline';",
"img-src 'self' data:;",
"font-src 'self' data:;",
"media-src 'self' data:;",
"connect-src 'none';",
"form-action 'none';",
"object-src 'none';",
"base-uri 'none';",
"manifest-src 'self';",
'upgrade-insecure-requests',
].join('\n ')
html = html.replace(
//i,
``
)
writeFileSync(indexPath, html)
console.log('[inline-assets] Replaced dev CSP with strict production sandbox')
// ---- Keep the PWA companion files -------------------------------
// These MUST ship alongside index.html so the app is installable: the web
// manifest, the network-sandbox service worker, and the icon set.
for (const keep of ['manifest.webmanifest', 'sw.js', 'icons']) {
const p = join(distDir, keep)
if (existsSync(p)) console.log(`[inline-assets] Preserved PWA ${keep}: ${p}`)
}
console.log('[inline-assets] Done — dist/ contains index.html + PWA files')