build-sea.cjs•4.42 kB
#!/usr/bin/env node
const { execSync } = require('child_process');
const fs = require('fs');
/**
 * Build Single Executable Application (SEA) components
 */
function buildSEA() {
  console.log('🔨 Building SEA components...');
  try {
    // 1. Build TypeScript
    console.log('📦 Building TypeScript...');
    execSync('tsc --project tsconfig.build.json', { stdio: 'inherit' });
    // 2. Set execute permissions on main file
    const mainFile = 'build/index.js';
    if (fs.existsSync(mainFile)) {
      fs.chmodSync(mainFile, '755');
    }
    // 3. Bundle with esbuild (optimized)
    console.log('📦 Bundling with esbuild...');
    execSync(
      [
        'esbuild build/index.js',
        '--bundle',
        '--platform=node',
        '--format=cjs',
        '--outfile=build/bundled.cjs',
        '--external:fsevents',
        '--external:@esbuild/*',
        '--keep-names',
        '--legal-comments=none',
        '--tree-shaking',
      ].join(' '),
      { stdio: 'inherit' },
    );
    // 3.5. Prepare tiktoken WASM files for inlining
    console.log('📦 Preparing tiktoken WASM files for inlining...');
    const tiktokenPath = require.resolve('tiktoken');
    const tiktokenDir = require('path').dirname(tiktokenPath);
    const wasmFiles = {
      'tiktoken_bg.wasm': require('path').join(tiktokenDir, 'tiktoken_bg.wasm'),
      'lite/tiktoken_bg.wasm': require('path').join(tiktokenDir, 'lite/tiktoken_bg.wasm'),
    };
    const wasmData = {};
    for (const [wasmKey, srcPath] of Object.entries(wasmFiles)) {
      try {
        // Read WASM file as base64
        const wasmBuffer = fs.readFileSync(srcPath);
        wasmData[wasmKey] = wasmBuffer.toString('base64');
        console.log(`✅ Prepared ${wasmKey} (${Math.round(wasmBuffer.length / 1024)}KB)`);
      } catch (error) {
        console.warn(`⚠️  Failed to prepare ${wasmKey}:`, error.message);
      }
    }
    // Post-process: Fix import_meta.url and tiktoken WASM loading for SEA compatibility
    console.log('🔧 Fixing import_meta.url and inlining tiktoken WASM...');
    let bundledCode = fs.readFileSync('build/bundled.cjs', 'utf8');
    // Replace import_meta variations with proper fallback for SEA
    bundledCode = bundledCode.replace(
      /import_meta\d*\.url/g,
      '(typeof __filename !== "undefined" ? "file://" + __filename : "file:///sea")',
    );
    // Read package.json for version info
    const packageJson = JSON.parse(fs.readFileSync('package.json', 'utf8'));
    // Inject inline WASM data and version after the shebang line
    const wasmInjection = `
// Inlined WASM data for SEA compatibility
const __TIKTOKEN_WASM_DATA__ = ${JSON.stringify(wasmData)};
// Inlined version data for SEA compatibility
const __PACKAGE_VERSION__ = ${JSON.stringify(packageJson.version)};
`;
    // Find the shebang line and insert WASM data after it
    const lines = bundledCode.split('\n');
    if (lines[0].startsWith('#!')) {
      lines.splice(1, 0, wasmInjection);
      bundledCode = lines.join('\n');
    } else {
      bundledCode = wasmInjection + bundledCode;
    }
    // Replace tiktoken WASM file reading with inline base64 data
    bundledCode = bundledCode.replace(
      /var bytes = null;\s*for \(const candidate of candidates\) \{\s*try \{\s*bytes = fs\d*\.readFileSync\(candidate\);\s*break;\s*\} catch \{\s*\}\s*\}/g,
      `var bytes = null;
    // Use inlined WASM data for SEA compatibility
    const wasmPath = candidates[0] || '';
    const wasmKey = wasmPath.includes('lite') ? 'lite/tiktoken_bg.wasm' : 'tiktoken_bg.wasm';
    if (typeof __TIKTOKEN_WASM_DATA__ !== 'undefined' && __TIKTOKEN_WASM_DATA__[wasmKey]) {
      bytes = Buffer.from(__TIKTOKEN_WASM_DATA__[wasmKey], 'base64');
    } else {
      // Fallback to file system
      for (const candidate of candidates) {
        try {
          bytes = fs9.readFileSync(candidate);
          break;
        } catch {
        }
      }
    }`,
    );
    fs.writeFileSync('build/bundled.cjs', bundledCode);
    // 4. Create SEA preparation blob
    console.log('🔧 Creating SEA blob...');
    execSync('node --experimental-sea-config sea-config.json', { stdio: 'inherit' });
    console.log('✅ SEA build complete!');
  } catch (error) {
    console.error('❌ SEA build failed:', error.message);
    process.exit(1);
  }
}
if (require.main === module) {
  buildSEA();
}
module.exports = { buildSEA };