From 2af31a09a95317b9e9edfa46788c76df417d3624 Mon Sep 17 00:00:00 2001 From: Mark Rapson Date: Sun, 18 Oct 2026 16:47:14 +0100 Subject: [PATCH] fix: build Windows exe from Windows node binary on non-Windows CI --- package-lock.json | 4 ++-- package.json | 2 +- scripts/build-win.js | 45 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 48 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8567f4c..c2eb4a4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "google-sheets-rate-assistant", - "version": "2.1.11", + "version": "2.1.12", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "google-sheets-rate-assistant", - "version": "2.1.11", + "version": "2.1.12", "dependencies": { "csv-stringify": "^6.0.0", "googleapis": "^173.0.0", diff --git a/package.json b/package.json index 0720e9f..7e3e709 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "google-sheets-rate-assistant", - "version": "2.1.11", + "version": "2.1.12", "description": "Pull Google Sheets data and export to CSV with configurable schedules and rate limiting.", "main": "index.js", "bin": "index.js", diff --git a/scripts/build-win.js b/scripts/build-win.js index 919bc16..5873bce 100644 --- a/scripts/build-win.js +++ b/scripts/build-win.js @@ -1,3 +1,4 @@ +const crypto = require('crypto'); const fs = require('fs'); const os = require('os'); const path = require('path'); @@ -10,6 +11,39 @@ function normalizeBuildVersion(value) { 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)) { @@ -33,8 +67,11 @@ async function main() { logLevel: 'info', }); + const executable = await getWindowsNodeBinary(tempDir); + fs.writeFileSync(seaConfigPath, JSON.stringify({ main: bundlePath, + executable, output: outputFile, mainFormat: 'commonjs', disableExperimentalSEAWarning: true, @@ -43,6 +80,14 @@ async function main() { }, 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 => {