From f572ae4652937efb25ab66defcfa55ddf65ac484 Mon Sep 17 00:00:00 2001 From: vin <49336820+Draculabo@users.noreply.github.com> Date: Thu, 5 Feb 2026 22:25:00 +0800 Subject: [PATCH] feat: add multi-arch release artifacts and MSI packaging (#65) --- .github/workflows/publish.yaml | 33 ++++- forge.config.ts | 238 +++++++++++++++++++++++++++++---- package-lock.json | 204 ++++++++++++++++++++++++++++ package.json | 1 + 4 files changed, 445 insertions(+), 31 deletions(-) diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index 06f4f38..a616d75 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -10,7 +10,28 @@ jobs: strategy: fail-fast: false matrix: - os: [windows-latest, windows-11-arm, macos-latest, macos-14, ubuntu-latest] + include: + - os: windows-latest + platform: win32 + arch: x64 + - os: windows-11-arm + platform: win32 + arch: arm64 + - os: macos-15-intel + platform: darwin + arch: x64 + - os: macos-14 + platform: darwin + arch: arm64 + - os: macos-14 + platform: darwin + arch: universal + - os: ubuntu-latest + platform: linux + arch: x64 + - os: ubuntu-24.04-arm + platform: linux + arch: arm64 runs-on: ${{ matrix.os }} @@ -28,10 +49,14 @@ jobs: cache: 'npm' - name: Install Linux dependencies - if: matrix.os == 'ubuntu-latest' + if: matrix.platform == 'linux' run: | sudo apt-get update - sudo apt-get install -y rpm libsecret-1-dev pkg-config + sudo apt-get install -y fakeroot rpm libsecret-1-dev pkg-config + + - name: Install WiX Toolset + if: matrix.platform == 'win32' && matrix.arch == 'x64' + run: choco install wixtoolset --version=3.14.0 -y - name: Install dependencies run: npm ci @@ -43,4 +68,4 @@ jobs: SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} SENTRY_ORG: ${{ secrets.SENTRY_ORG }} SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }} - run: npm run publish + run: npm run publish -- --platform=${{ matrix.platform }} --arch=${{ matrix.arch }} diff --git a/forge.config.ts b/forge.config.ts index bb37c98..e22c826 100644 --- a/forge.config.ts +++ b/forge.config.ts @@ -10,6 +10,7 @@ import { MakerDeb } from '@electron-forge/maker-deb'; import { MakerDMG } from '@electron-forge/maker-dmg'; import { MakerRpm } from '@electron-forge/maker-rpm'; import { MakerSquirrel } from '@electron-forge/maker-squirrel'; +import { MakerWix } from '@electron-forge/maker-wix'; import { MakerZIP } from '@electron-forge/maker-zip'; import { AutoUnpackNativesPlugin } from '@electron-forge/plugin-auto-unpack-natives'; import { FusesPlugin } from '@electron-forge/plugin-fuses'; @@ -24,19 +25,20 @@ import { stringify as yamlStringify } from 'yaml'; const nativeModules = ['better-sqlite3', 'keytar', 'bindings', 'file-uri-to-path']; const ResolvedMakerAppImage = MakerAppImage; const keepLanguages = new Set(['en', 'en-US', 'zh-CN', 'ru']); +const windowsExecutableName = 'antigravity-manager'; const isStartCommand = process.argv.some((arg) => arg.includes('start')); -const artifactRegex = /.*\.(?:exe|dmg|AppImage|zip|deb|rpm)$/; +const artifactRegex = /.*\.(?:exe|dmg|AppImage|zip|deb|rpm|msi)$/; const platformNamesMap: Record = { darwin: 'macos', linux: 'linux', win32: 'windows', }; -const ymlMapsMap: Record = { - darwin: 'latest-mac.yml', - linux: 'latest-linux.yml', - win32: 'latest.yml', +const ymlBaseNameMap: Record = { + darwin: 'latest-mac', + linux: 'latest-linux', + win32: 'latest', }; const ignorePatterns = [ /^\/\.git/, @@ -74,7 +76,11 @@ function normalizeArtifactName(value?: string) { return 'app'; } - return value.trim().replace(/\s+/g, '-'); + return value + .trim() + .replace(/\s+/g, '.') + .replace(/[^a-zA-Z0-9.]/g, '') + .replace(/\.+/g, '.'); } function isSquirrelArtifact(artifactPath: string) { @@ -86,6 +92,89 @@ function isSquirrelArtifact(artifactPath: string) { return artifactPath.endsWith('.nupkg'); } +function mapArchName(arch: string, mapping: Record) { + return mapping[arch] || arch; +} + +function getArtifactFileName({ + baseName, + version, + arch, + extension, +}: { + baseName: string; + version: string; + arch: string; + extension: string; +}) { + if (extension === '.rpm') { + return `${baseName}-${version}-1.${arch}${extension}`; + } + + if (extension === '.deb') { + return `${baseName}_${version}_${arch}${extension}`; + } + + if (extension === '.AppImage') { + return `${baseName}_${version}_${arch}${extension}`; + } + + if (extension === '.dmg') { + return `${baseName}_${version}_${arch}${extension}`; + } + + if (extension === '.exe') { + return `${baseName}_${version}_${arch}-setup${extension}`; + } + + if (extension === '.msi') { + return `${baseName}_${version}_${arch}_en-US${extension}`; + } + + if (extension === '.zip') { + return `${baseName}_${version}_${arch}${extension}`; + } + + return `${baseName}_${version}_${arch}${extension}`; +} + +function getUpdateYmlFileName(platform: string, arch: string) { + const baseName = ymlBaseNameMap[platform]; + if (!baseName) { + return null; + } + + if (platform === 'darwin') { + return arch === 'universal' ? `${baseName}.yml` : `${baseName}-${arch}.yml`; + } + + if (platform === 'linux') { + return arch === 'x64' ? `${baseName}.yml` : `${baseName}-${arch}.yml`; + } + + if (platform === 'win32') { + return arch === 'x64' ? `${baseName}.yml` : `${baseName}-${arch}.yml`; + } + + return null; +} + +function getChecksumArchLabel(platform: string, arch: string) { + if (platform === 'linux') { + return mapArchName(arch, { x64: 'amd64', arm64: 'aarch64' }); + } + + if (platform === 'darwin') { + return mapArchName(arch, { x64: 'x64', arm64: 'arm64', universal: 'universal' }); + } + + if (platform === 'win32') { + return mapArchName(arch, { x64: 'x64', arm64: 'arm64' }); + } + + return arch; +} + const appImageMaker = new ResolvedMakerAppImage({ config: { icons: [ @@ -116,7 +205,7 @@ const config: ForgeConfig = { unpack: '**/{better-sqlite3,keytar}/**/*', }, name: 'Antigravity Manager', - executableName: 'antigravity-manager', + executableName: windowsExecutableName, icon: 'images/icon', // Electron Forge automatically adds .icns/.ico extraResource: ['src/assets'], // Copy assets folder to resources/assets afterCopy: packagerAfterCopy, @@ -167,10 +256,11 @@ const config: ForgeConfig = { return makeResults; } - const ymlByPlatform = new Map< + const ymlByTarget = new Map< string, { basePath: string; + fileName: string; yml: { version?: string; files: { @@ -182,16 +272,41 @@ const config: ForgeConfig = { }; } >(); + const checksumByTarget = new Map< + string, + { + basePath: string; + fileName: string; + lines: string[]; + } + >(); makeResults = makeResults.map((result) => { - const productName = normalizeArtifactName(result.packageJSON.productName); + const productName = normalizeArtifactName( + result.packageJSON.productName || result.packageJSON.name, + ); const platformName = platformNamesMap[result.platform] || result.platform; const version = result.packageJSON.version; const platformKey = result.platform; + const archKey = result.arch; + const updateFileName = getUpdateYmlFileName(platformKey, archKey); + const updateKey = updateFileName ? `${platformKey}-${archKey}` : null; + const checksumKey = `${platformKey}-${archKey}`; + const checksumArchLabel = getChecksumArchLabel(platformKey, archKey); + const checksumFileName = `sha256sums-${platformName}-${checksumArchLabel}.txt`; - if (!ymlByPlatform.has(platformKey)) { - ymlByPlatform.set(platformKey, { + if (!checksumByTarget.has(checksumKey)) { + checksumByTarget.set(checksumKey, { basePath: '', + fileName: checksumFileName, + lines: [], + }); + } + + if (updateFileName && updateKey && !ymlByTarget.has(updateKey)) { + ymlByTarget.set(updateKey, { + basePath: '', + fileName: updateFileName, yml: { version, files: [], @@ -199,7 +314,8 @@ const config: ForgeConfig = { }); } - const platformState = ymlByPlatform.get(platformKey)!; + const updateState = updateKey ? ymlByTarget.get(updateKey)! : null; + const checksumState = checksumByTarget.get(checksumKey)!; result.artifacts = result.artifacts .map((artifact) => { @@ -215,11 +331,38 @@ const config: ForgeConfig = { return artifact; } - if (!platformState.basePath) { - platformState.basePath = path.dirname(artifact); + if (!checksumState.basePath) { + checksumState.basePath = path.dirname(artifact); } - const newArtifact = `${path.dirname(artifact)}/${productName}-${version}-${platformName}-${result.arch}${path.extname(artifact)}`; + if (updateState && !updateState.basePath) { + updateState.basePath = path.dirname(artifact); + } + + const extension = path.extname(artifact); + let archLabel = archKey; + if (platformKey === 'linux' && extension === '.rpm') { + archLabel = mapArchName(archKey, { x64: 'x86_64', arm64: 'aarch64' }); + } else if (platformKey === 'linux' && extension === '.deb') { + archLabel = mapArchName(archKey, { x64: 'amd64', arm64: 'arm64' }); + } else if (platformKey === 'linux' && extension === '.AppImage') { + archLabel = mapArchName(archKey, { x64: 'amd64', arm64: 'aarch64' }); + } else if (platformKey === 'darwin') { + archLabel = mapArchName(archKey, { + x64: 'x64', + arm64: 'arm64', + universal: 'universal', + }); + } else if (platformKey === 'win32') { + archLabel = mapArchName(archKey, { x64: 'x64', arm64: 'arm64' }); + } + + const newArtifact = `${path.dirname(artifact)}/${getArtifactFileName({ + baseName: productName, + version, + arch: archLabel, + extension, + })}`; if (newArtifact !== artifact) { fs.renameSync(artifact, newArtifact); } @@ -227,13 +370,18 @@ const config: ForgeConfig = { try { const fileData = fs.readFileSync(newArtifact); const hash = crypto.createHash('sha512').update(fileData).digest('base64'); + const sha256 = crypto.createHash('sha256').update(fileData).digest('hex'); const { size } = fs.statSync(newArtifact); - platformState.yml.files.push({ - url: path.basename(newArtifact), - sha512: hash, - size, - }); + if (updateState) { + updateState.yml.files.push({ + url: path.basename(newArtifact), + sha512: hash, + size, + }); + } + + checksumState.lines.push(`${sha256} ${path.basename(newArtifact)}`); } catch { console.error(`Failed to hash ${newArtifact}`); } @@ -246,17 +394,19 @@ const config: ForgeConfig = { }); const releaseDate = new Date().toISOString(); - for (const [platform, platformState] of ymlByPlatform.entries()) { - const ymlFileName = ymlMapsMap[platform]; - if (!ymlFileName || !platformState.basePath) { + for (const [updateKey, updateState] of ymlByTarget.entries()) { + if (!updateState.basePath) { continue; } - platformState.yml.releaseDate = releaseDate; - const ymlPath = path.join(platformState.basePath, ymlFileName); - fs.writeFileSync(ymlPath, yamlStringify(platformState.yml)); + updateState.yml.releaseDate = releaseDate; + const ymlPath = path.join(updateState.basePath, updateState.fileName); + fs.writeFileSync(ymlPath, yamlStringify(updateState.yml)); - const sampleResult = makeResults.find((result) => result.platform === platform); + const [platform, arch] = updateKey.split('-'); + const sampleResult = makeResults.find( + (result) => result.platform === platform && result.arch === arch, + ); if (!sampleResult) { continue; } @@ -269,6 +419,30 @@ const config: ForgeConfig = { }); } + for (const [checksumKey, checksumState] of checksumByTarget.entries()) { + if (!checksumState.basePath || checksumState.lines.length === 0) { + continue; + } + + const checksumPath = path.join(checksumState.basePath, checksumState.fileName); + fs.writeFileSync(checksumPath, `${checksumState.lines.join('\n')}\n`); + + const [platform, arch] = checksumKey.split('-'); + const sampleResult = makeResults.find( + (result) => result.platform === platform && result.arch === arch, + ); + if (!sampleResult) { + continue; + } + + makeResults.push({ + artifacts: [checksumPath], + platform: sampleResult.platform, + arch: sampleResult.arch, + packageJSON: sampleResult.packageJSON, + }); + } + return makeResults; }, }, @@ -278,6 +452,16 @@ const config: ForgeConfig = { iconUrl: 'https://raw.githubusercontent.com/Draculabo/AntigravityManager/main/images/icon.ico', }), + ...(process.platform === 'win32' && process.arch === 'x64' + ? [ + new MakerWix({ + language: 1033, + icon: path.join(process.cwd(), 'images', 'icon.ico'), + exe: `${windowsExecutableName}.exe`, + ui: { chooseDirectory: true }, + }), + ] + : []), new MakerDMG( { overwrite: true, diff --git a/package-lock.json b/package-lock.json index 96210e0..02b2365 100644 --- a/package-lock.json +++ b/package-lock.json @@ -76,6 +76,7 @@ "@electron-forge/maker-dmg": "^7.10.2", "@electron-forge/maker-rpm": "^7.10.2", "@electron-forge/maker-squirrel": "^7.10.2", + "@electron-forge/maker-wix": "^7.10.2", "@electron-forge/maker-zip": "^7.10.2", "@electron-forge/plugin-auto-unpack-natives": "^7.10.2", "@electron-forge/plugin-fuses": "^7.10.2", @@ -790,6 +791,15 @@ "node": ">=6.9.0" } }, + "node_modules/@bitdisaster/exe-icon-extractor": { + "version": "1.0.10", + "resolved": "https://registry.npmmirror.com/@bitdisaster/exe-icon-extractor/-/exe-icon-extractor-1.0.10.tgz", + "integrity": "sha512-iTZ8cVGZ5dglNRyFdSj8U60mHIrC8XNIuOHN/NkM5/dQP4nsmpyqeQTAADLLQgoFCNJD+DiwQCv8dR2cCeWP4g==", + "dev": true, + "hasInstallScript": true, + "license": "ISC", + "optional": true + }, "node_modules/@borewit/text-codec": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/@borewit/text-codec/-/text-codec-0.1.1.tgz", @@ -1446,6 +1456,70 @@ "electron-winstaller": "^5.3.0" } }, + "node_modules/@electron-forge/maker-wix": { + "version": "7.11.1", + "resolved": "https://registry.npmmirror.com/@electron-forge/maker-wix/-/maker-wix-7.11.1.tgz", + "integrity": "sha512-yKsnfPQVBrjyjWndmSB0rqQSXAiIukwz5fuVyoCTnYpfJQojrEEMUN/eXSHA08d+rOPbB/hkMAR+QJcjJ8vO2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@electron-forge/core-utils": "7.11.1", + "@electron-forge/maker-base": "7.11.1", + "@electron-forge/shared-types": "7.11.1", + "chalk": "^4.0.0", + "electron-wix-msi": "^5.1.3", + "log-symbols": "^4.0.0", + "parse-author": "^2.0.0", + "semver": "^7.2.1" + }, + "engines": { + "node": ">= 16.4.0" + } + }, + "node_modules/@electron-forge/maker-wix/node_modules/@electron-forge/maker-base": { + "version": "7.11.1", + "resolved": "https://registry.npmmirror.com/@electron-forge/maker-base/-/maker-base-7.11.1.tgz", + "integrity": "sha512-yhZrCGoN6bDeiB5DHFaueZ1h84AReElEj+f0hl2Ph4UbZnO0cnLpbx+Bs+XfMLAiA+beC8muB5UDK5ysfuT9BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@electron-forge/shared-types": "7.11.1", + "fs-extra": "^10.0.0", + "which": "^2.0.2" + }, + "engines": { + "node": ">= 16.4.0" + } + }, + "node_modules/@electron-forge/maker-wix/node_modules/@electron-forge/shared-types": { + "version": "7.11.1", + "resolved": "https://registry.npmmirror.com/@electron-forge/shared-types/-/shared-types-7.11.1.tgz", + "integrity": "sha512-vvBWdAEh53UJlDGUevpaJk1+sqDMQibfrbHR+0IPA4MPyQex7/Uhv3vYH9oGHujBVAChQahjAuJt0fG6IJBLZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@electron-forge/tracer": "7.11.1", + "@electron/packager": "^18.3.5", + "@electron/rebuild": "^3.7.0", + "listr2": "^7.0.2" + }, + "engines": { + "node": ">= 16.4.0" + } + }, + "node_modules/@electron-forge/maker-wix/node_modules/@electron-forge/tracer": { + "version": "7.11.1", + "resolved": "https://registry.npmmirror.com/@electron-forge/tracer/-/tracer-7.11.1.tgz", + "integrity": "sha512-tiB6cglVQFcSw9N8GRwVwZUeB9u0DOx2Mj7aFXBUsFLUYQapvVGv51tUSy/UAW5lvmubGscYIILuVko+II3+NA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chrome-trace-event": "^1.0.3" + }, + "engines": { + "node": ">= 14.17.5" + } + }, "node_modules/@electron-forge/maker-zip": { "version": "7.10.2", "resolved": "https://registry.npmjs.org/@electron-forge/maker-zip/-/maker-zip-7.10.2.tgz", @@ -14472,6 +14546,54 @@ "node": ">= 8" } }, + "node_modules/cross-spawn-windows-exe": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/cross-spawn-windows-exe/-/cross-spawn-windows-exe-1.2.0.tgz", + "integrity": "sha512-mkLtJJcYbDCxEG7Js6eUnUNndWjyUZwJ3H7bErmmtOYU/Zb99DyUkpamuIZE0b3bhmJyZ7D90uS6f+CGxRRjOw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/malept" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/subscription/pkg/npm-cross-spawn-windows-exe?utm_medium=referral&utm_source=npm_fund" + } + ], + "license": "Apache-2.0", + "dependencies": { + "@malept/cross-spawn-promise": "^1.1.0", + "is-wsl": "^2.2.0", + "which": "^2.0.2" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/cross-spawn-windows-exe/node_modules/@malept/cross-spawn-promise": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/@malept/cross-spawn-promise/-/cross-spawn-promise-1.1.1.tgz", + "integrity": "sha512-RTBGWL5FWQcg9orDOCcp4LvItNzUPcyEU9bwaeJX0rJ1IQxzucC48Y0/sQLp/g6t99IQgAlGIaesJS+gTn7tVQ==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/malept" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/subscription/pkg/npm-.malept-cross-spawn-promise?utm_medium=referral&utm_source=npm_fund" + } + ], + "license": "Apache-2.0", + "dependencies": { + "cross-spawn": "^7.0.1" + }, + "engines": { + "node": ">= 10" + } + }, "node_modules/cross-zip": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/cross-zip/-/cross-zip-4.0.1.tgz", @@ -15727,6 +15849,29 @@ "node": ">= 4.0.0" } }, + "node_modules/electron-wix-msi": { + "version": "5.1.3", + "resolved": "https://registry.npmmirror.com/electron-wix-msi/-/electron-wix-msi-5.1.3.tgz", + "integrity": "sha512-EYj1cm1nZoVHmIIx3o0aKt784lxdEpJnXbEnyypklUCnglqSb7ni+1xi1Vp/gtrGS/mzIxnWBT+x5fIfuDjhvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@electron/windows-sign": "^1.1.2", + "debug": "^4.3.4", + "fs-extra": "^10.1.0", + "klaw": "^4.1.0", + "lodash": "^4.17.21", + "rcedit": "^4.0.1", + "rcinfo": "^0.1.3", + "semver": "^7.6.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "optionalDependencies": { + "@bitdisaster/exe-icon-extractor": "^1.0.10" + } + }, "node_modules/electron/node_modules/@electron/get": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@electron/get/-/get-2.0.3.tgz", @@ -19549,6 +19694,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmmirror.com/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -19932,6 +20093,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/isarray": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", @@ -20384,6 +20558,16 @@ "json-buffer": "3.0.1" } }, + "node_modules/klaw": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/klaw/-/klaw-4.1.0.tgz", + "integrity": "sha512-1zGZ9MF9H22UnkpVeuaGKOjfA2t6WrfdrJmGjy16ykcjnKQDmHVX+KI477rpbGevz/5FD4MC3xf1oxylBgcaQw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.14.0" + } + }, "node_modules/launch-ide": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/launch-ide/-/launch-ide-1.3.0.tgz", @@ -25890,6 +26074,26 @@ "node": ">=0.10.0" } }, + "node_modules/rcedit": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/rcedit/-/rcedit-4.0.1.tgz", + "integrity": "sha512-bZdaQi34krFWhrDn+O53ccBDw0MkAT2Vhu75SqhtvhQu4OPyFM4RoVheyYiVQYdjhUi6EJMVWQ0tR6bCIYVkUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn-windows-exe": "^1.1.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/rcinfo": { + "version": "0.1.3", + "resolved": "https://registry.npmmirror.com/rcinfo/-/rcinfo-0.1.3.tgz", + "integrity": "sha512-c2XV2aYgY7x3BscO+/B/nCTtMvnclZ8w5D7R6zgK4sGOQnE0MjlXhOPynno7yp6Iw1RPNSXBwXwB1svZVRfcSw==", + "dev": true, + "license": "BSD-2-Clause" + }, "node_modules/react": { "version": "19.2.0", "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz", diff --git a/package.json b/package.json index dd1b366..30403ce 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "@electron-forge/maker-dmg": "^7.10.2", "@electron-forge/maker-rpm": "^7.10.2", "@electron-forge/maker-squirrel": "^7.10.2", + "@electron-forge/maker-wix": "^7.10.2", "@electron-forge/maker-zip": "^7.10.2", "@electron-forge/plugin-auto-unpack-natives": "^7.10.2", "@electron-forge/plugin-fuses": "^7.10.2",