diff --git a/package.json b/package.json index b6ee7177..b61c4d8d 100644 --- a/package.json +++ b/package.json @@ -32,12 +32,12 @@ "team:smoke-changes-real-data": "tsx scripts/team-changes-real-data-smoke.ts", "prebuild": "tsx scripts/fetch-pricing-data.ts && pnpm --filter agent-teams-controller build && pnpm --filter agent-teams-mcp build", "build": "node --max-old-space-size=8192 ./node_modules/electron-vite/bin/electron-vite.js build", - "dist": "electron-builder --mac --win --linux", - "dist:mac": "electron-builder --mac", - "dist:mac:arm64": "electron-builder --mac --arm64", - "dist:mac:x64": "electron-builder --mac --x64", - "dist:win": "electron-builder --win", - "dist:linux": "electron-builder --linux", + "dist": "node ./scripts/electron-builder/dist.mjs --mac --win --linux", + "dist:mac": "node ./scripts/electron-builder/dist.mjs --mac", + "dist:mac:arm64": "node ./scripts/electron-builder/dist.mjs --mac --arm64", + "dist:mac:x64": "node ./scripts/electron-builder/dist.mjs --mac --x64", + "dist:win": "node ./scripts/electron-builder/dist.mjs --win", + "dist:linux": "node ./scripts/electron-builder/dist.mjs --linux", "preview": "electron-vite preview", "typecheck": "tsc --noEmit", "typecheck:workspace": "pnpm typecheck && pnpm --filter agent-teams-mcp typecheck && pnpm --filter agent-teams-mcp typecheck:test", diff --git a/scripts/electron-builder/dist.mjs b/scripts/electron-builder/dist.mjs new file mode 100644 index 00000000..e3c72615 --- /dev/null +++ b/scripts/electron-builder/dist.mjs @@ -0,0 +1,97 @@ +#!/usr/bin/env node +import { spawn } from 'node:child_process'; +import { createRequire } from 'node:module'; +import { pathToFileURL } from 'node:url'; + +const require = createRequire(import.meta.url); + +const PLATFORM_FLAGS = new Map([ + ['--mac', 'mac'], + ['-m', 'mac'], + ['--win', 'win'], + ['-w', 'win'], + ['--linux', 'linux'], + ['-l', 'linux'], +]); + +const PLATFORM_ARGS = { + mac: '--mac', + win: '--win', + linux: '--linux', +}; + +const LINUX_PACKAGE_NAME_OVERRIDES = [ + '--config.productName=Agent-Teams-UI', + '--config.linux.desktop.entry.Name=Agent Teams UI', +]; + +export function buildElectronBuilderInvocations(argv) { + const targets = []; + const sharedArgs = []; + + for (const arg of argv) { + const target = PLATFORM_FLAGS.get(arg); + if (target) { + if (!targets.includes(target)) { + targets.push(target); + } + continue; + } + sharedArgs.push(arg); + } + + if (targets.length === 0) { + return [{ args: sharedArgs }]; + } + + return targets.map((target) => ({ + args: [ + PLATFORM_ARGS[target], + ...sharedArgs, + ...(target === 'linux' ? LINUX_PACKAGE_NAME_OVERRIDES : []), + ], + })); +} + +async function runElectronBuilder(args) { + const cliPath = require.resolve('electron-builder/cli.js'); + await new Promise((resolve, reject) => { + const child = spawn(process.execPath, [cliPath, ...args], { + stdio: 'inherit', + env: process.env, + }); + + child.on('error', reject); + child.on('exit', (code, signal) => { + if (code === 0) { + resolve(); + return; + } + reject(new Error(`electron-builder failed with ${signal ?? `exit code ${code}`}`)); + }); + }); +} + +async function main(argv) { + const invocations = buildElectronBuilderInvocations(argv); + + if (process.env.ELECTRON_BUILDER_DIST_DRY_RUN === '1') { + console.log( + JSON.stringify( + invocations.map((invocation) => invocation.args), + null, + 2 + ) + ); + return; + } + + for (const invocation of invocations) { + await runElectronBuilder(invocation.args); + } +} + +const entryPointUrl = process.argv[1] ? pathToFileURL(process.argv[1]).href : null; +if (entryPointUrl === import.meta.url) { + await main(process.argv.slice(2)); +} diff --git a/test/main/build/electronBuilderDistScript.test.ts b/test/main/build/electronBuilderDistScript.test.ts new file mode 100644 index 00000000..dcb80d48 --- /dev/null +++ b/test/main/build/electronBuilderDistScript.test.ts @@ -0,0 +1,52 @@ +// @vitest-environment node +import { pathToFileURL } from 'node:url'; + +import { describe, expect, it } from 'vitest'; + +const scriptUrl = pathToFileURL(`${process.cwd()}/scripts/electron-builder/dist.mjs`).href; + +describe('electron-builder dist wrapper', () => { + it('splits multi-platform builds so Linux-only package name overrides do not affect macOS or Windows', async () => { + const { buildElectronBuilderInvocations } = await import(scriptUrl); + + expect( + buildElectronBuilderInvocations(['--mac', '--win', '--linux', '--publish', 'never']) + ).toEqual([ + { args: ['--mac', '--publish', 'never'] }, + { args: ['--win', '--publish', 'never'] }, + { + args: [ + '--linux', + '--publish', + 'never', + '--config.productName=Agent-Teams-UI', + '--config.linux.desktop.entry.Name=Agent Teams UI', + ], + }, + ]); + }); + + it('adds the filesystem-safe package name override to Linux-only builds', async () => { + const { buildElectronBuilderInvocations } = await import(scriptUrl); + + expect(buildElectronBuilderInvocations(['--linux', '--publish', 'never'])).toEqual([ + { + args: [ + '--linux', + '--publish', + 'never', + '--config.productName=Agent-Teams-UI', + '--config.linux.desktop.entry.Name=Agent Teams UI', + ], + }, + ]); + }); + + it('leaves macOS arch-specific builds unchanged', async () => { + const { buildElectronBuilderInvocations } = await import(scriptUrl); + + expect(buildElectronBuilderInvocations(['--mac', '--arm64', '--publish', 'never'])).toEqual([ + { args: ['--mac', '--arm64', '--publish', 'never'] }, + ]); + }); +});