const crypto = require('crypto'); const fs = require('fs'); const os = require('os'); const path = require('path'); const {execFileSync} = require('child_process'); const esbuild = require('esbuild'); const {version: packageVersion} = require('../package.json'); function normalizeBuildVersion(value) { const version = (value || packageVersion || '0.0.0').toString().trim(); return version.startsWith('v') ? version : `v${version}`; } async function download(url) { const response = await fetch(url); if (!response.ok) { throw new Error(`Failed to download ${url}: ${response.status} ${response.statusText}`); } return Buffer.from(await response.arrayBuffer()); } // SEA blobs must be injected into a node binary of the same version, and non-Windows hosts need a Windows base binary. async function getWindowsNodeBinary(tempDir) { if (process.platform === 'win32') { return process.execPath; } const baseUrl = `https://nodejs.org/dist/${process.version}`; const [binary, shasums] = await Promise.all([ download(`${baseUrl}/win-x64/node.exe`), download(`${baseUrl}/SHASUMS256.txt`), ]); const expected = shasums.toString('utf8').split('\n') .map(line => line.trim().split(/\s+/)) .find(([, file]) => file === 'win-x64/node.exe'); const actual = crypto.createHash('sha256').update(binary).digest('hex'); if (!expected || expected[0] !== actual) { throw new Error('Checksum verification failed for downloaded win-x64/node.exe'); } const binaryPath = path.join(tempDir, 'node.exe'); fs.writeFileSync(binaryPath, binary); return binaryPath; } async function main() { const distDir = path.resolve(__dirname, '..', 'dist'); if (!fs.existsSync(distDir)) { fs.mkdirSync(distDir, {recursive: true}); } const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gsa-sea-')); const bundlePath = path.join(tempDir, 'index.cjs'); const seaBlobPath = path.join(tempDir, 'sea-prep.blob'); const seaConfigPath = path.join(tempDir, 'sea-config.json'); const buildVersion = normalizeBuildVersion(process.env.BUILD_VERSION); const outputFile = path.join(distDir, `gSheets-Rate-Assistant-${buildVersion}.exe`); await esbuild.build({ entryPoints: [path.resolve(__dirname, '..', 'index.js')], bundle: true, platform: 'node', format: 'cjs', target: 'node26', outfile: bundlePath, logLevel: 'info', }); const executable = await getWindowsNodeBinary(tempDir); fs.writeFileSync(seaConfigPath, JSON.stringify({ main: bundlePath, executable, output: outputFile, mainFormat: 'commonjs', disableExperimentalSEAWarning: true, useCodeCache: false, useSnapshot: false, }, null, 2)); execFileSync(process.execPath, ['--build-sea', seaConfigPath], {stdio: 'inherit'}); const header = Buffer.alloc(2); const fd = fs.openSync(outputFile, 'r'); fs.readSync(fd, header, 0, 2, 0); fs.closeSync(fd); if (header.toString('latin1') !== 'MZ') { throw new Error(`${outputFile} is not a Windows PE executable`); } } main().catch(error => { console.error(error && error.stack ? error.stack : error); process.exit(1); });