Add Windows ARM64 packaging support

This commit is contained in:
drelich
2026-04-15 16:37:41 +02:00
parent e7fbbf710d
commit 3b57fcea75
4 changed files with 54 additions and 1 deletions

View File

@@ -60,6 +60,8 @@ npm run build # TypeScript + Vite production build
npm run desktop # Run Electron against the built dist/ npm run desktop # Run Electron against the built dist/
npm run dist:dir # Build an unpacked Electron app in release/ npm run dist:dir # Build an unpacked Electron app in release/
npm run dist:mac # Build macOS .dmg and .zip packages in release/ npm run dist:mac # Build macOS .dmg and .zip packages in release/
npm run dist:win # Build Windows installer + zip for the current Windows architecture
npm run dist:win:arm64 # Build Windows ARM64 installer + zip in release/
``` ```
## Production-Like Local Run ## Production-Like Local Run
@@ -129,10 +131,12 @@ Current packaging commands:
- `npm run dist:dir` creates an unpacked app bundle in `release/` - `npm run dist:dir` creates an unpacked app bundle in `release/`
- `npm run dist:mac` creates macOS `.dmg` and `.zip` artifacts in `release/` - `npm run dist:mac` creates macOS `.dmg` and `.zip` artifacts in `release/`
- `npm run dist:win` creates Windows `.exe` and `.zip` artifacts for the current Windows architecture
- `npm run dist:win:arm64` creates Windows ARM64 `.exe` and `.zip` artifacts in `release/`
The current mac build is unsigned and not notarized, which is fine for local use and testing but not enough for friction-free public distribution through Gatekeeper. The current mac build is unsigned and not notarized, which is fine for local use and testing but not enough for friction-free public distribution through Gatekeeper.
Windows and Linux targets are also configured in `package.json`, but they have not been validated in this repository yet. Windows ARM64 packaging has been validated in this repository with Electron Builder on Windows 11 ARM running under Parallels. Linux targets are still configured in `package.json`, but they have not been validated here yet.
## Legacy Tauri Script ## Legacy Tauri Script

View File

@@ -15,11 +15,14 @@
"dev:renderer": "vite", "dev:renderer": "vite",
"dev:electron": "wait-on tcp:1420 && cross-env ELECTRON_RENDERER_URL=http://localhost:1420 electron .", "dev:electron": "wait-on tcp:1420 && cross-env ELECTRON_RENDERER_URL=http://localhost:1420 electron .",
"dev:desktop": "concurrently -k \"npm:dev:renderer\" \"npm:dev:electron\"", "dev:desktop": "concurrently -k \"npm:dev:renderer\" \"npm:dev:electron\"",
"gen:win-icon": "node scripts/generate-win-icon.mjs",
"build": "tsc && vite build", "build": "tsc && vite build",
"desktop": "electron .", "desktop": "electron .",
"dist:dir": "npm run build && electron-builder --dir", "dist:dir": "npm run build && electron-builder --dir",
"dist:mac": "npm run build && electron-builder --mac dmg zip", "dist:mac": "npm run build && electron-builder --mac dmg zip",
"dist:linux": "npm run build && electron-builder --linux AppImage deb", "dist:linux": "npm run build && electron-builder --linux AppImage deb",
"dist:win": "npm run gen:win-icon && npm run build && electron-builder --win nsis zip",
"dist:win:arm64": "npm run gen:win-icon && npm run build && electron-builder --win nsis zip --arm64",
"preview": "vite preview", "preview": "vite preview",
"tauri": "tauri" "tauri": "tauri"
}, },

View File

@@ -0,0 +1,46 @@
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const projectRoot = path.resolve(__dirname, "..");
const sourcePngPath = path.join(projectRoot, "src-tauri", "icons", "128x128@2x.png");
const targetIcoPath = path.join(projectRoot, "src-tauri", "icons", "icon.ico");
const pngBytes = fs.readFileSync(sourcePngPath);
if (pngBytes.length < 24 || pngBytes.toString("ascii", 1, 4) !== "PNG") {
throw new Error(`Expected a PNG file at ${sourcePngPath}`);
}
const width = pngBytes.readUInt32BE(16);
const height = pngBytes.readUInt32BE(20);
if (width !== 256 || height !== 256) {
throw new Error(`Expected a 256x256 PNG, received ${width}x${height}`);
}
const headerSize = 6;
const directoryEntrySize = 16;
const imageOffset = headerSize + directoryEntrySize;
const icoBytes = Buffer.alloc(imageOffset + pngBytes.length);
icoBytes.writeUInt16LE(0, 0);
icoBytes.writeUInt16LE(1, 2);
icoBytes.writeUInt16LE(1, 4);
icoBytes.writeUInt8(0, 6);
icoBytes.writeUInt8(0, 7);
icoBytes.writeUInt8(0, 8);
icoBytes.writeUInt8(0, 9);
icoBytes.writeUInt16LE(1, 10);
icoBytes.writeUInt16LE(32, 12);
icoBytes.writeUInt32LE(pngBytes.length, 14);
icoBytes.writeUInt32LE(imageOffset, 18);
pngBytes.copy(icoBytes, imageOffset);
fs.writeFileSync(targetIcoPath, icoBytes);
console.log(`Generated ${path.relative(projectRoot, targetIcoPath)} from ${path.relative(projectRoot, sourcePngPath)}`);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 9.1 KiB