From 3b57fcea757bddbc2bf64b187fc0f9efbabab03e Mon Sep 17 00:00:00 2001 From: drelich Date: Wed, 15 Apr 2026 16:37:41 +0200 Subject: [PATCH] Add Windows ARM64 packaging support --- README.md | 6 ++++- package.json | 3 +++ scripts/generate-win-icon.mjs | 46 ++++++++++++++++++++++++++++++++++ src-tauri/icons/icon.ico | Bin 69694 -> 9307 bytes 4 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 scripts/generate-win-icon.mjs diff --git a/README.md b/README.md index fff41ed..0e8f1ea 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,8 @@ npm run build # TypeScript + Vite production build npm run desktop # Run Electron against the built dist/ 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: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 @@ -129,10 +131,12 @@ Current packaging commands: - `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: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. -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 diff --git a/package.json b/package.json index 8daa2d4..5cc99ae 100644 --- a/package.json +++ b/package.json @@ -15,11 +15,14 @@ "dev:renderer": "vite", "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\"", + "gen:win-icon": "node scripts/generate-win-icon.mjs", "build": "tsc && vite build", "desktop": "electron .", "dist:dir": "npm run build && electron-builder --dir", "dist:mac": "npm run build && electron-builder --mac dmg zip", "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", "tauri": "tauri" }, diff --git a/scripts/generate-win-icon.mjs b/scripts/generate-win-icon.mjs new file mode 100644 index 0000000..1435494 --- /dev/null +++ b/scripts/generate-win-icon.mjs @@ -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)}`); diff --git a/src-tauri/icons/icon.ico b/src-tauri/icons/icon.ico index c5394a132ef3fd57fd092fe9ebabc99c51b4ad6f..2d5e7f3909baadb233981d57203374a4a8087b2e 100644 GIT binary patch literal 9307 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 literal 69694 zcmeI4y>App7>5@L8=YiDu6!*8lC9*ANM~c91i3<#2olIYkOVh&6$00x%JQXMq#hlPDTw&la?M>ib<8lV5t@NMqw~} zxRBkuX=xVTmGRywZ;3;*2Yk5?wfhXg-3jJtKFo+uZn(RgXxARMly+bCxfPd*T~PM) zwlN>DGzWXH+!wEbo4; zF89V$!P`#buD+diU)Zi^JW~6JjJ^7gU|#4C~WFV9HD zJ|n$+{!g5DdHVNaj045;sM_y-_{$p{2%A3+z#j+vb)et!U+j~Gy1+jVisUa3aKJAI zisp|4@W%mv94I6Ii@z4?0{1;AlRpl?9|zoapzQoF{#vLD-1ML-{x|@C9B|Kpg>$3A zQt3in;FbqJPk&V@f87iRZ~zBz00(dY2XFufZ~zBz00(dY2XFufZ~zBz00(dY2XFuf zZ~zBz00(dY2XFufZ~zBz00(dY2XFufZ~zBz00(dY2XFufZ~zCwc3^Y!UA%nt7{5{K z(C_M2^<6Wue>{Hz_71U6e%rKc2Vn13xu0-vUzg>;ZtUkz?WIg`q)aHp(B;alkG1Nt+P!;qMTC zJs&Qd8;gItcDySW_3IU}yj@?5A6-7u^*+h7DrYh;Mwe5B`qj|8DuM zNcQ4?|EI5EA1Hgd)Xg*YyHnna#mzGfW!J(M*t^9yOMc5cUeGBG?EPb(_|dNeu=kgJ zzYf6OfA)DDfPH<~_v!%b>&HH;1F)|z`;QKM8Nd4f-_B-!{Og5-Jl}iLx0A5UyH+3W z@3+5=U;OuHyt4R1eCxX}U>^wkyv}f5{p$u8!x#r}z)=U*9sOLiovLX&J%fMHWz#bJ zgXVu8{z3Dff`8EbN8ukd|3UbN%RjQk_&>mbARSn0@t=f$ko+fG{2TBOl7B<|wHW_d z90P{D&w5f^c9cjepC5aU7_%0~6iYC;N^6 z91hgdfw_e7PHSU7VCe}C)XITXaove~`+IimuqEb(R5J&o?71!*;@QqO%eI{}VoB$g z{rR|lc`;TV9?8Nx?HD^(<$%f#82J|i)!{wo`%Vw+8fp*FCiIKHcxA5dJDI2bZ`Yf! zL^-z=tBjoc!aZ~&hN5xZd mReT4t@ZcCCKm>>Y5g-CYfCvx)B0vO)01+SpM1Tk=0`CEeUwTRa