/ scripts / build-electron.mjs
build-electron.mjs
  1  #!/usr/bin/env node
  2  
  3  import fs from 'node:fs'
  4  import path from 'node:path'
  5  import { spawnSync } from 'node:child_process'
  6  import { fileURLToPath } from 'node:url'
  7  
  8  const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
  9  const nativeModules = [
 10    'better-sqlite3',
 11    '@mongodb-js/zstd',
 12    'node-liblzma',
 13    'utf-8-validate',
 14  ]
 15  
 16  const args = new Set(process.argv.slice(2))
 17  const skipNext = args.has('--skip-next')
 18  const publishAlways = args.has('--publish')
 19  // --skip-rebuild accepted for backwards compat; electron-builder + the
 20  // afterPack hook (scripts/electron-after-pack.cjs) now handle native module
 21  // ABI per-architecture so there is no pre-package rebuild step to skip.
 22  if (args.has('--skip-rebuild')) {
 23    // no-op
 24  }
 25  const platformFlag = args.has('--mac') ? '--mac'
 26    : args.has('--win') ? '--win'
 27    : args.has('--linux') ? '--linux'
 28    : null
 29  
 30  function run(cmd, cmdArgs, env = {}) {
 31    const status = runWithStatus(cmd, cmdArgs, env)
 32    if (status !== 0) process.exit(status)
 33  }
 34  
 35  function runWithStatus(cmd, cmdArgs, env = {}) {
 36    const result = spawnSync(cmd, cmdArgs, {
 37      cwd: repoRoot,
 38      stdio: 'inherit',
 39      env: { ...process.env, ...env },
 40      shell: process.platform === 'win32',
 41    })
 42    if (result.status !== 0) {
 43      console.error(`[build-electron] ${cmd} ${cmdArgs.join(' ')} failed with status ${result.status}`)
 44      return result.status ?? 1
 45    }
 46    return 0
 47  }
 48  
 49  function restoreHostNativeModules() {
 50    console.log('[build-electron] restoring host native modules…')
 51    run('npm', ['rebuild', ...nativeModules, '--silent'])
 52  }
 53  
 54  function copyDir(src, dest) {
 55    fs.mkdirSync(dest, { recursive: true })
 56    for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
 57      const from = path.join(src, entry.name)
 58      const to = path.join(dest, entry.name)
 59      if (entry.isDirectory()) copyDir(from, to)
 60      else fs.copyFileSync(from, to)
 61    }
 62  }
 63  
 64  console.log('[build-electron] compiling electron main process…')
 65  run('npx', ['--no-install', 'tsc', '-p', 'electron/tsconfig.json'])
 66  
 67  if (!skipNext) {
 68    console.log('[build-electron] running next build…')
 69    run('npm', ['run', 'build'])
 70  }
 71  
 72  const standaloneDir = path.join(repoRoot, '.next', 'standalone')
 73  if (!fs.existsSync(standaloneDir)) {
 74    console.error(`[build-electron] missing ${standaloneDir}. Did next build fail?`)
 75    process.exit(1)
 76  }
 77  
 78  console.log('[build-electron] copying static + public into standalone…')
 79  const nextStatic = path.join(repoRoot, '.next', 'static')
 80  const standaloneNextStatic = path.join(standaloneDir, '.next', 'static')
 81  if (fs.existsSync(nextStatic)) {
 82    fs.rmSync(standaloneNextStatic, { recursive: true, force: true })
 83    copyDir(nextStatic, standaloneNextStatic)
 84  }
 85  const publicDir = path.join(repoRoot, 'public')
 86  const standalonePublic = path.join(standaloneDir, 'public')
 87  if (fs.existsSync(publicDir)) {
 88    fs.rmSync(standalonePublic, { recursive: true, force: true })
 89    copyDir(publicDir, standalonePublic)
 90  }
 91  
 92  // Native modules inside .next/standalone/node_modules are rebuilt per-arch by
 93  // the electron-builder afterPack hook (scripts/electron-after-pack.cjs), which
 94  // runs electron-rebuild against the packaged .app's copy of standalone/. Doing
 95  // it here would only rebuild once for the host arch and be overwritten during
 96  // packaging anyway.
 97  
 98  console.log('[build-electron] running electron-builder…')
 99  const builderArgs = []
100  if (platformFlag) builderArgs.push(platformFlag)
101  if (publishAlways) {
102    builderArgs.push('--publish', 'always')
103  } else {
104    builderArgs.push('--publish', 'never')
105  }
106  const builderStatus = runWithStatus('npx', ['--no-install', 'electron-builder', ...builderArgs])
107  restoreHostNativeModules()
108  if (builderStatus !== 0) process.exit(builderStatus)
109  
110  console.log('[build-electron] done. Artifacts in release/')