const fs = require('fs'); const path = require('path'); const { execFileSync } = require('child_process'); const rootDir = path.resolve(__dirname, '..'); const frontendDir = path.join(rootDir, 'frontend'); const distDir = path.join(frontendDir, 'dist'); const workerDir = path.join(rootDir, 'src', 'worker'); const htmlTsFile = path.join(workerDir, 'html.ts'); function inlineRequiredAsset(html, patterns, replacement, assetType) { for (const pattern of patterns) { if (pattern.test(html)) { return html.replace(pattern, replacement); } } throw new Error(`Unable to find the ${assetType} reference in the generated frontend HTML.`); } console.log('Building frontend...'); try { execFileSync(process.execPath, ['scripts/sync-theme-editor.js'], { cwd: rootDir, stdio: 'inherit' }); // Dependencies are installed explicitly by developers/CI. Keeping installs out // of the build makes production artifacts deterministic and offline-buildable. const executableSuffix = process.platform === 'win32' ? '.cmd' : ''; execFileSync(path.join(frontendDir, 'node_modules', '.bin', `tsc${executableSuffix}`), [], { cwd: frontendDir, stdio: 'inherit', }); execFileSync(path.join(frontendDir, 'node_modules', '.bin', `vite${executableSuffix}`), ['build'], { cwd: frontendDir, stdio: 'inherit', }); console.log('Inlining assets...'); // 2. Read dist/index.html let html = fs.readFileSync(path.join(distDir, 'index.html'), 'utf8'); // 3. Find assets in dist/assets const assetsDir = path.join(distDir, 'assets'); const files = fs.readdirSync(assetsDir).sort(); const jsFiles = files.filter((file) => file.endsWith('.js')); const cssFiles = files.filter((file) => file.endsWith('.css')); if (jsFiles.length !== 1 || cssFiles.length !== 1) { throw new Error( `Expected exactly one JS and one CSS bundle for Worker inlining, got ${jsFiles.length} JS and ${cssFiles.length} CSS.` ); } const jsContent = fs.readFileSync(path.join(assetsDir, jsFiles[0]), 'utf8'); const cssContent = fs.readFileSync(path.join(assetsDir, cssFiles[0]), 'utf8'); // 4. Inline favicon.svg as base64 data URL const faviconPath = path.join(frontendDir, 'public', 'favicon.svg'); if (fs.existsSync(faviconPath)) { const faviconContent = fs.readFileSync(faviconPath, 'utf8'); const faviconBase64 = Buffer.from(faviconContent).toString('base64'); html = html.replace( /href=["']\/favicon\.svg["']/i, `href="data:image/svg+xml;base64,${faviconBase64}"` ); } // 5. Inline CSS (replace ) html = inlineRequiredAsset(html, [ /]*rel=["']stylesheet["'][^>]*href=["']\/assets\/[^"']+["'][^>]*>/i, /]*rel=["']stylesheet["'][^>]*href=["']\/src\/[^"']+["'][^>]*>/i, ], () => ``, 'stylesheet'); // 6. Inline JS (replace or similar) html = inlineRequiredAsset(html, [ /]*type=["']module["'][^>]*src=["']\/assets\/[^"']+["'][^>]*><\/script>/i, /]*type=["']module["'][^>]*src=["']\/src\/[^"']+["'][^>]*><\/script>/i, ], () => ``, 'module script'); // 7. Write to src/worker/html.ts const escapedHtml = html .replace(/\\/g, '\\\\') .replace(/`/g, '\\`') .replace(/\${/g, '\\${'); const htmlTsContent = `// Automatically generated by scripts/build-html.js. Do not edit directly. export const HTML = \`${escapedHtml}\`; `; if (!fs.existsSync(workerDir)) { fs.mkdirSync(workerDir, { recursive: true }); } fs.writeFileSync(htmlTsFile, htmlTsContent, 'utf8'); console.log('Frontend built and inlined successfully to src/worker/html.ts!'); } catch (error) { console.error('Failed to build and inline frontend:', error); process.exit(1); }