import fs from 'node:fs'; import path from 'node:path'; import zlib from 'node:zlib'; import { execFileSync } from 'node:child_process'; import { fileURLToPath } from 'node:url'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const rootDir = path.resolve(__dirname, '..'); const distDir = path.join(rootDir, 'dist'); const srcDir = path.join(rootDir, 'src'); const configPath = path.join(rootDir, 'sea-config.json'); const payloadPath = path.join(distDir, 'app-payload.bin'); const dbschemaDir = path.resolve(rootDir, '../dashdbschema'); const llmorbiterDir = path.resolve(rootDir, '../llmorbiter'); if (!fs.existsSync(distDir)) { fs.mkdirSync(distDir, { recursive: true }); } console.log('Packaging application, siblings, and JavaScript dependencies into SEA payload (excluding native binaries)...'); const nativeNodes = {}; function findNearestPackageJson(startDir) { let dir = startDir; while (dir && dir !== path.dirname(dir)) { const pkgFile = path.join(dir, 'package.json'); if (fs.existsSync(pkgFile)) { try { return JSON.parse(fs.readFileSync(pkgFile, 'utf8')); } catch {} } dir = path.dirname(dir); } return null; } function getVersionedNodeFilename(fullPath) { const base = path.basename(fullPath, '.node'); const pkg = findNearestPackageJson(path.dirname(fullPath)); if (!pkg || !pkg.name || !pkg.version) { return `${base}.node`; } const shortName = pkg.name.replace(/^@[^/]+\//, ''); if (base.includes(pkg.version)) { return `${base}.node`; } if (base === shortName) { return `${shortName}-${process.platform}-${process.arch}-${pkg.version}.node`; } return `${shortName}-${base}-${pkg.version}.node`; } function isNativeBinary(name) { return ( name.endsWith('.node') || name.endsWith('.so') || name.includes('.so.') || name.endsWith('.dylib') || name.endsWith('.dll') || name.endsWith('.a') || name.endsWith('.o') || name.endsWith('.cc') || name.endsWith('.cpp') || name.endsWith('.c') || name.endsWith('.h') ); } function packDirectory(dir, prefix, result) { if (!fs.existsSync(dir)) return; try { for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { const full = path.join(dir, entry.name); const rel = path.join(prefix, entry.name); if (entry.isDirectory()) { if (entry.name === 'test' || entry.name === 'tests' || entry.name === 'docs' || entry.name === '.github' || entry.name === '.git' || entry.name === 'obj.target') continue; packDirectory(full, rel, result); } else if (entry.isFile()) { if (entry.name.endsWith('.node')) { nativeNodes[rel] = getVersionedNodeFilename(full); continue; } if (isNativeBinary(entry.name)) continue; result[rel] = fs.readFileSync(full).toString('base64'); } } } catch {} } const payload = {}; // 1. Pack dashback src and package.json packDirectory(srcDir, 'src', payload); const pkgPath = path.join(rootDir, 'package.json'); if (fs.existsSync(pkgPath)) { payload['package.json'] = fs.readFileSync(pkgPath).toString('base64'); } // 2. Pack sibling dependencies (libdashstore and libllmorbiter) directly into node_modules if (fs.existsSync(path.join(dbschemaDir, 'src'))) { packDirectory(path.join(dbschemaDir, 'src'), 'node_modules/libdashstore/src', payload); const dbschemaPkg = path.join(dbschemaDir, 'package.json'); if (fs.existsSync(dbschemaPkg)) { payload['node_modules/libdashstore/package.json'] = fs.readFileSync(dbschemaPkg).toString('base64'); } } if (fs.existsSync(path.join(llmorbiterDir, 'src'))) { packDirectory(path.join(llmorbiterDir, 'src'), 'node_modules/libllmorbiter/src', payload); const llmorbiterPkg = path.join(llmorbiterDir, 'package.json'); if (fs.existsSync(llmorbiterPkg)) { payload['node_modules/libllmorbiter/package.json'] = fs.readFileSync(llmorbiterPkg).toString('base64'); } } // 3. Pack all node_modules dependencies function packNodeModules(nmPath) { if (!fs.existsSync(nmPath)) return; for (const name of fs.readdirSync(nmPath)) { if (name.startsWith('.') || name === 'libdashstore' || name === 'libllmorbiter') continue; const pkgDir = path.join(nmPath, name); if (name.startsWith('@')) { for (const sub of fs.readdirSync(pkgDir)) { packDirectory(path.join(pkgDir, sub), `node_modules/${name}/${sub}`, payload); } } else { packDirectory(pkgDir, `node_modules/${name}`, payload); } } } packNodeModules(path.join(rootDir, 'node_modules')); packNodeModules(path.join(dbschemaDir, 'node_modules')); payload['.native-nodes.json'] = Buffer.from(JSON.stringify(nativeNodes)).toString('base64'); const compressedPayload = zlib.deflateSync(Buffer.from(JSON.stringify(payload))); fs.writeFileSync(payloadPath, compressedPayload); console.log(`Packed ${Object.keys(payload).length} files (${(compressedPayload.length / 1024 / 1024).toFixed(2)} MB compressed)`); // Prepare sea-config.json const config = { main: 'src/sea-entry.cjs', output: 'dist/dashback', assets: { 'app-payload.bin': 'dist/app-payload.bin', }, disableExperimentalSEAWarning: true, }; fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n'); console.log('Generating Single Executable Application (SEA)...'); try { execFileSync(process.execPath, ['--build-sea', 'sea-config.json'], { cwd: rootDir, stdio: 'inherit', }); const outputPath = path.join(distDir, 'dashback'); if (process.platform !== 'win32' && fs.existsSync(outputPath)) { fs.chmodSync(outputPath, 0o755); } console.log(`\nSuccessfully built standalone SEA: ${path.relative(rootDir, outputPath)}`); } catch (err) { console.error('\nFailed to build SEA:', err.message); process.exit(1); }