/** * Build a standalone app.asar + checksums.sha256 for minor (patch) updates. * * Replicates what electron-builder packs into the asar (dist/ + package.json + * required node_modules) without running the full installer build. * * Usage: * npm run build:asar # default (github) update source * UPDATE_SOURCE=gitea npm run build:asar # gitea update source */ const { execSync } = require('child_process'); const { cpSync, mkdirSync, rmSync, readFileSync, writeFileSync, createReadStream } = require('fs'); const { join } = require('path'); const { createHash } = require('crypto'); const asar = require('@electron/asar'); const ROOT = join(__dirname, '..'); const STAGING = join(ROOT, 'out', 'asar-staging'); const OUTPUT = join(ROOT, 'out', 'asar'); // Same node_modules list as electron-builder.yml const NODE_MODULES = [ 'electron-store', 'conf', 'dot-prop', 'type-fest', 'pkg-up', 'find-up', 'locate-path', 'p-locate', 'p-limit', 'yocto-queue', 'path-exists', 'env-paths', 'json-schema-typed', 'ajv', 'ajv-formats', 'atomically', 'debounce-fn', 'mimic-fn', 'semver', 'onetime', ]; function shouldExclude(src) { return src.endsWith('.ts') || src.endsWith('.map'); } async function main() { // 1. Run Vite build (pass UPDATE_SOURCE through env) const env = { ...process.env }; if (env.UPDATE_SOURCE) { console.log(`Building with UPDATE_SOURCE=${env.UPDATE_SOURCE}`); } execSync('npm run build', { cwd: ROOT, stdio: 'inherit', env }); // 2. Prepare staging directory rmSync(STAGING, { recursive: true, force: true }); mkdirSync(STAGING, { recursive: true }); // Copy dist/ cpSync(join(ROOT, 'dist'), join(STAGING, 'dist'), { recursive: true }); // Copy package.json (Electron reads "main" from it) cpSync(join(ROOT, 'package.json'), join(STAGING, 'package.json')); // Copy required node_modules (excluding .ts and .map files) for (const mod of NODE_MODULES) { const src = join(ROOT, 'node_modules', mod); const dest = join(STAGING, 'node_modules', mod); cpSync(src, dest, { recursive: true, filter: (s) => !shouldExclude(s), }); } // 3. Pack into asar mkdirSync(OUTPUT, { recursive: true }); const asarPath = join(OUTPUT, 'app.asar'); await asar.createPackage(STAGING, asarPath); console.log('Created:', asarPath); // 4. Generate checksums.sha256 const hex = await fileHash(asarPath); const checksumsPath = join(OUTPUT, 'checksums.sha256'); writeFileSync(checksumsPath, `${hex} app.asar\n`); console.log(`SHA-256: ${hex}`); console.log('Created:', checksumsPath); // 5. Cleanup staging rmSync(STAGING, { recursive: true, force: true }); // Print size const size = readFileSync(asarPath).length; console.log(`\napp.asar size: ${(size / 1024).toFixed(1)} KB`); } function fileHash(filePath) { return new Promise((resolve, reject) => { const hash = createHash('sha256'); const stream = createReadStream(filePath); stream.on('data', (chunk) => hash.update(chunk)); stream.on('end', () => resolve(hash.digest('hex'))); stream.on('error', reject); }); } main().catch((err) => { console.error('build-asar failed:', err); process.exit(1); });