From b0e85a3c9452433cdb7d298451c687a8b1005c05 Mon Sep 17 00:00:00 2001 From: ManuelStaggl <17688572+ManuelStaggl@users.noreply.github.com> Date: Thu, 12 Nov 2026 18:51:33 +0100 Subject: [PATCH 1/5] feat(desktop): a first macOS test build comes as a universal dmg from the installer workflow The installer workflow gets a macOS job that builds an ad hoc signed universal dmg, keeps it as an artifact and checks architectures, signature, both credential stores and that the app survives its start. On macOS PartBuddy finds SolidCraft in its app bundle and Claude Code in the Homebrew folders, because an app started from the Finder does not get the shell's PATH. Co-Authored-By: Claude Fable 5.2 --- .github/workflows/installer.yml | 61 +++++++++++++++++- CONTRIBUTING.md | 1 + apps/desktop/electron-builder.yml | 19 ++++++ apps/desktop/package.json | 3 +- apps/desktop/src/main/agents/claudeCode.ts | 11 +++- apps/desktop/src/main/index.ts | 7 +- apps/desktop/src/main/solidcraft/discovery.ts | 14 ++++ apps/desktop/test/agent.test.ts | 6 ++ apps/desktop/test/discovery.test.ts | 21 +++++- assets/brand/icon/png/partbuddy-1024.png | Bin 0 -> 19951 bytes tools/brand/build.mjs | 3 +- 11 files changed, 136 insertions(+), 10 deletions(-) create mode 100644 assets/brand/icon/png/partbuddy-1024.png diff --git a/.github/workflows/installer.yml b/.github/workflows/installer.yml index 7b6f17dc..246c0d14 100644 --- a/.github/workflows/installer.yml +++ b/.github/workflows/installer.yml @@ -1,7 +1,7 @@ name: Installer -# Builds the Windows installer from a tag or branch on a GitHub-hosted runner and keeps it as a -# workflow artifact. Started by hand for now; it publishes nothing. Code signing through the +# Builds the Windows installer and the macOS dmg from a tag or branch on GitHub-hosted runners and +# keeps them as workflow artifacts. Started by hand for now; it publishes nothing. Code signing through the # SignPath Foundation needs exactly this: every job up to the signing request on GitHub-hosted # runners, and the unsigned file stored as a workflow artifact first (docs/code-signing-policy.md). # The self-hosted runners of ADR 0022 are therefore not used here. @@ -58,3 +58,60 @@ jobs: apps/desktop/dist/latest.yml if-no-files-found: error retention-days: 14 + + macos: + name: macOS dmg + runs-on: macos-15 + timeout-minutes: 30 + steps: + - name: Check out + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + ref: ${{ inputs.ref }} + lfs: true + persist-credentials: false + + - name: Set up pnpm + uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0 + + - name: Set up Node.js + uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 + with: + node-version-file: .node-version + + # The universal app needs the native credential store of both architectures. + - name: Install dependencies + run: pnpm install --frozen-lockfile --cpu=arm64,x64 + + - name: Build the dmg + run: pnpm --filter @partbuddy/desktop dist:mac + + - name: Keep the dmg + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 + with: + name: partbuddy-dmg-macos + path: apps/desktop/dist/PartBuddy-*-mac-universal.dmg + if-no-files-found: error + retention-days: 14 + + # Nobody on the team has a Mac: the runner is the only place that shows whether the app is + # universal, signed ad hoc, carries both credential stores and survives its start. + - name: Check the app + working-directory: apps/desktop/dist/mac-universal + run: | + set -euo pipefail + app=PartBuddy.app + lipo -archs "$app/Contents/MacOS/PartBuddy" | tee /dev/stderr | grep -q 'x86_64 arm64' + codesign --verify --deep --strict --verbose=2 "$app" + codesign -dv "$app" 2>&1 | grep 'Signature=adhoc' + test "$(find "$app" -name 'keyring.darwin-*.node' | tee /dev/stderr | wc -l)" -eq 2 + "$app/Contents/MacOS/PartBuddy" > start.log 2>&1 & + pid=$! + sleep 25 + if ! kill -0 "$pid" 2>/dev/null; then + cat start.log + echo "PartBuddy did not stay running" + exit 1 + fi + kill "$pid" + tail -n 40 start.log diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index aa9d66dc..7632646e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -51,6 +51,7 @@ pnpm --filter @partbuddy/desktop dev # Electron with hot reload pnpm --filter @partbuddy/desktop build # bundle into apps/desktop/out pnpm --filter @partbuddy/desktop test:e2e # Electron flows against a fake SolidCraft pnpm --filter @partbuddy/desktop dist # Windows installer in apps/desktop/dist (B10) +pnpm --filter @partbuddy/desktop dist:mac # macOS dmg (universal, signed ad hoc); runs on a Mac only, see installer.yml ``` The app finds a running SolidCraft through its control folder, or starts `solidcraft --control 7878` diff --git a/apps/desktop/electron-builder.yml b/apps/desktop/electron-builder.yml index 68dcfb30..cbfc5b62 100644 --- a/apps/desktop/electron-builder.yml +++ b/apps/desktop/electron-builder.yml @@ -1,5 +1,7 @@ # Windows installer of PartBuddy (plan step B10): NSIS, per user, with Start menu and desktop # shortcuts. Unsigned for now, so SmartScreen warns; signing comes before the public release. +# macOS: a universal dmg as a test build, signed ad hoc only (no Apple developer account yet), so +# Gatekeeper asks once and there are no automatic updates on the Mac. # Updates come from the GitHub releases of the repository (electron-updater). appId: io.github.manuelstaggl.partbuddy productName: PartBuddy @@ -17,6 +19,8 @@ files: - "!node_modules/**" - node_modules/@napi-rs/keyring/** - node_modules/@napi-rs/keyring-win32-x64-msvc/** + - node_modules/@napi-rs/keyring-darwin-arm64/** + - node_modules/@napi-rs/keyring-darwin-x64/** # Licence texts of every bundled library (ADR 0007), written before packing, next to the asar. beforePack: ../../tools/licenses/notices.mjs extraResources: @@ -32,6 +36,21 @@ win: icon: ../../assets/brand/icon/png/partbuddy-256.png # No certificate yet, so nothing is signed; the version and icon are still written into the exe. signAndEditExecutable: true +mac: + target: + - target: dmg + arch: [universal] + # electron-builder makes the icns from this PNG (tools/brand/build.mjs renders it). + icon: ../../assets/brand/icon/png/partbuddy-1024.png + category: public.app-category.graphics-design + # Ad hoc: without any signature Apple Silicon refuses to start the app at all. The hardened + # runtime would need a real certificate to load the credential store. + identity: "-" + hardenedRuntime: false + # Both builds carry both single-architecture credential stores; they are kept, not merged. + x64ArchFiles: "**/keyring.darwin-*.node" +dmg: + artifactName: PartBuddy-${version}-mac-universal.${ext} nsis: oneClick: false perMachine: false diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 1de1d6b5..9e48104d 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -15,7 +15,8 @@ "test": "vitest run", "test:e2e": "playwright test", "dist": "electron-vite build && electron-builder --win --publish never", - "dist:dir": "electron-vite build && electron-builder --win --dir --publish never" + "dist:dir": "electron-vite build && electron-builder --win --dir --publish never", + "dist:mac": "electron-vite build && electron-builder --mac --publish never" }, "dependencies": { "@ai-sdk/openai": "4.0.106", diff --git a/apps/desktop/src/main/agents/claudeCode.ts b/apps/desktop/src/main/agents/claudeCode.ts index 61b18dc0..65f890aa 100644 --- a/apps/desktop/src/main/agents/claudeCode.ts +++ b/apps/desktop/src/main/agents/claudeCode.ts @@ -32,15 +32,22 @@ export interface ClaudeCommand { prefix: string[]; } -export function findClaudeCode(env: NodeJS.ProcessEnv, home: string, exists: (path: string) => boolean = existsSync): ClaudeCommand | null { +export function findClaudeCode( + env: NodeJS.ProcessEnv, + home: string, + exists: (path: string) => boolean = existsSync, + platform: NodeJS.Platform = process.platform, +): ClaudeCommand | null { if (env.PARTBUDDY_CLAUDE_COMMAND) { // Test hook: a JSON array, for example ["node", "test/fakeClaude.mjs"]. const parts = JSON.parse(env.PARTBUDDY_CLAUDE_COMMAND) as string[]; const [command, ...prefix] = parts; return command ? { command, prefix } : null; } - const name = process.platform === 'win32' ? 'claude.exe' : 'claude'; + const name = platform === 'win32' ? 'claude.exe' : 'claude'; const candidates = [join(home, '.local', 'bin', name)]; + // An app started from the Finder does not get the shell's PATH; Homebrew and npm install here. + if (platform === 'darwin') candidates.push(join('/opt/homebrew/bin', name), join('/usr/local/bin', name)); for (const dir of (env.PATH ?? env.Path ?? '').split(delimiter)) { if (dir) candidates.push(join(dir, name)); } diff --git a/apps/desktop/src/main/index.ts b/apps/desktop/src/main/index.ts index e889a796..5c7fd942 100644 --- a/apps/desktop/src/main/index.ts +++ b/apps/desktop/src/main/index.ts @@ -40,7 +40,7 @@ import { SecretStore } from './credentials/secretStore'; import { loadNoteKey } from './notes/noteRecord'; import { NotesService } from './notes/notesService'; import { Bridge, trustedSender } from './bridge'; -import { findExecutable, systemEnvironment } from './solidcraft/discovery'; +import { findExecutable, programInBundle, systemEnvironment } from './solidcraft/discovery'; import { EngineChoice } from './solidcraft/engineChoice'; import { AssistantSettings } from './agents/assistantSettings'; import { checkImportFile, IMPORT_EXTENSIONS, importName, type ImportKind } from './projects/importFile'; @@ -338,13 +338,14 @@ if (!app.requestSingleInstanceLock()) { const options: Electron.OpenDialogOptions = { title: 'SolidCraft', properties: ['openFile'], - filters: process.platform === 'win32' ? [{ name: 'SolidCraft', extensions: ['exe'] }] : [], + filters: process.platform === 'win32' ? [{ name: 'SolidCraft', extensions: ['exe'] }] : process.platform === 'darwin' ? [{ name: 'SolidCraft', extensions: ['app'] }] : [], ...(current ? { defaultPath: current } : {}), }; const picked = mainWindow ? await dialog.showOpenDialog(mainWindow, options) : await dialog.showOpenDialog(options); const [file] = picked.filePaths; if (picked.canceled || !file) return { info: await engineChoice.info(), error: null, cancelled: true }; - return { ...(await engineChoice.choose(file)), cancelled: false }; + // On macOS the dialog hands over the bundle "SolidCraft.app"; the program lies inside it. + return { ...(await engineChoice.choose(programInBundle(file))), cancelled: false }; }); bridge.handle(invokeChannels.appRelaunch, () => { app.relaunch(); diff --git a/apps/desktop/src/main/solidcraft/discovery.ts b/apps/desktop/src/main/solidcraft/discovery.ts index 4e2f80e7..78304391 100644 --- a/apps/desktop/src/main/solidcraft/discovery.ts +++ b/apps/desktop/src/main/solidcraft/discovery.ts @@ -109,12 +109,26 @@ export function locateExecutable( if (root) candidates.push(join(root, 'SolidCraft', name)); } } + if (platform === 'darwin') { + // An app started from the Finder does not get the shell's PATH, so the bundle is looked up directly. + for (const root of ['/Applications', join(environment.home, 'Applications')]) { + candidates.push(programInBundle(join(root, 'SolidCraft.app'))); + } + } for (const dir of (env.PATH ?? env.Path ?? '').split(delimiter)) { if (dir) candidates.push(join(dir, name)); } return { path: candidates.find((path) => exists(path)) ?? null, source: 'automatic' }; } +/** + * The program inside a macOS app bundle (seen in solidcraft-0.40.1-macos-universal.dmg: + * `Contents/MacOS/SolidCraft` with `solidcraft-cli` beside it); any other path stays as it is. + */ +export function programInBundle(path: string): string { + return /\.app[\\/]?$/.test(path) ? join(path, 'Contents', 'MacOS', 'SolidCraft') : path; +} + /** Path of the SolidCraft desktop app, or null when it is not installed. */ export function findExecutable(environment: Environment, exists: (path: string) => boolean = existsSync): string | null { return locateExecutable(environment, exists).path; diff --git a/apps/desktop/test/agent.test.ts b/apps/desktop/test/agent.test.ts index 35d5e612..8d06604b 100644 --- a/apps/desktop/test/agent.test.ts +++ b/apps/desktop/test/agent.test.ts @@ -354,6 +354,12 @@ describe('Claude Code adapter', () => { expect(findClaudeCode({}, home, (path) => path === expected)).toEqual({ command: expected, prefix: [] }); expect(findClaudeCode({}, home, () => false)).toBeNull(); }); + + it('finds a Homebrew install on macOS without the shell PATH', () => { + const expected = join('/opt/homebrew/bin', 'claude'); + expect(findClaudeCode({}, tmpdir(), (path) => path === expected, 'darwin')).toEqual({ command: expected, prefix: [] }); + expect(findClaudeCode({}, tmpdir(), (path) => path === expected, 'linux')).toBeNull(); + }); }); describe('agent service with a fake Claude Code', () => { diff --git a/apps/desktop/test/discovery.test.ts b/apps/desktop/test/discovery.test.ts index ebb0704b..c025afaa 100644 --- a/apps/desktop/test/discovery.test.ts +++ b/apps/desktop/test/discovery.test.ts @@ -4,7 +4,7 @@ import { join } from 'node:path'; import { afterEach, describe, expect, it } from 'vitest'; -import { controlDirectory, findExecutable, findRunning, type Environment } from '../src/main/solidcraft/discovery'; +import { cliBeside, controlDirectory, findExecutable, findRunning, programInBundle, type Environment } from '../src/main/solidcraft/discovery'; const token = 'ab'.repeat(32); const dirs: string[] = []; @@ -82,4 +82,23 @@ describe('SolidCraft discovery', () => { expect(findExecutable({ ...base, env: {} }, exists)).toBeNull(); expect(findExecutable({ ...base, env: { PARTBUDDY_SOLIDCRAFT_EXE: override } }, (p) => p === override)).toBe(override); }); + + it('finds the app bundle on macOS, for all users or in the user folder, with the CLI beside it', () => { + const home = join(tmpdir(), 'partbuddy-home'); + const base = { platform: 'darwin' as const, home, env: {}, isAlive: () => true }; + const system = join('/Applications', 'SolidCraft.app', 'Contents', 'MacOS', 'SolidCraft'); + const own = join(home, 'Applications', 'SolidCraft.app', 'Contents', 'MacOS', 'SolidCraft'); + expect(findExecutable(base, (p) => p === system)).toBe(system); + expect(findExecutable(base, (p) => p === own)).toBe(own); + expect(findExecutable(base, () => false)).toBeNull(); + expect(findExecutable({ ...base, platform: 'linux' }, (p) => p === system)).toBeNull(); + expect(cliBeside(system, 'darwin')).toBe(join('/Applications', 'SolidCraft.app', 'Contents', 'MacOS', 'solidcraft-cli')); + }); + + it('takes a chosen app bundle as the program inside it', () => { + const bundle = join('/Applications', 'SolidCraft.app'); + expect(programInBundle(bundle)).toBe(join(bundle, 'Contents', 'MacOS', 'SolidCraft')); + expect(programInBundle(`${bundle}/`)).toBe(join(bundle, 'Contents', 'MacOS', 'SolidCraft')); + expect(programInBundle(join(bundle, 'Contents', 'MacOS', 'SolidCraft'))).toBe(join(bundle, 'Contents', 'MacOS', 'SolidCraft')); + }); }); diff --git a/assets/brand/icon/png/partbuddy-1024.png b/assets/brand/icon/png/partbuddy-1024.png new file mode 100644 index 0000000000000000000000000000000000000000..673dcaa2f60ef9a3b2c71b1007e61fac3783fa65 GIT binary patch literal 19951 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 Date: Thu, 12 Nov 2026 18:55:51 +0100 Subject: [PATCH 2/5] ci: the macOS start check prints exit code, log and crash report when the app ends Co-Authored-By: Claude Fable 5.2 --- .github/workflows/installer.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/installer.yml b/.github/workflows/installer.yml index 246c0d14..2ebf1663 100644 --- a/.github/workflows/installer.yml +++ b/.github/workflows/installer.yml @@ -109,8 +109,11 @@ jobs: pid=$! sleep 25 if ! kill -0 "$pid" 2>/dev/null; then + wait "$pid" && code=0 || code=$? + echo "PartBuddy did not stay running (exit code $code)" cat start.log - echo "PartBuddy did not stay running" + cat "$HOME/Library/Application Support/PartBuddy/logs/partbuddy.log" || true + for report in "$HOME"/Library/Logs/DiagnosticReports/PartBuddy*; do head -n 80 "$report" || true; done exit 1 fi kill "$pid" From dede2c58f6f0ecdcbf002dfa3421d8e009a657d5 Mon Sep 17 00:00:00 2001 From: ManuelStaggl <17688572+ManuelStaggl@users.noreply.github.com> Date: Thu, 12 Nov 2026 19:04:47 +0100 Subject: [PATCH 3/5] ci: the macOS start check turns on Electron's own logging Co-Authored-By: Claude Fable 5.2 --- .github/workflows/installer.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/installer.yml b/.github/workflows/installer.yml index 2ebf1663..3b2a5467 100644 --- a/.github/workflows/installer.yml +++ b/.github/workflows/installer.yml @@ -105,13 +105,15 @@ jobs: codesign --verify --deep --strict --verbose=2 "$app" codesign -dv "$app" 2>&1 | grep 'Signature=adhoc' test "$(find "$app" -name 'keyring.darwin-*.node' | tee /dev/stderr | wc -l)" -eq 2 - "$app/Contents/MacOS/PartBuddy" > start.log 2>&1 & + ELECTRON_ENABLE_LOGGING=1 "$app/Contents/MacOS/PartBuddy" > start.log 2>&1 & pid=$! sleep 25 if ! kill -0 "$pid" 2>/dev/null; then wait "$pid" && code=0 || code=$? echo "PartBuddy did not stay running (exit code $code)" cat start.log + ls -la "$app/Contents/Resources" + find "$HOME/Library" -newer start.log -type f -not -path '*/Caches/*' 2>/dev/null | head -n 40 cat "$HOME/Library/Application Support/PartBuddy/logs/partbuddy.log" || true for report in "$HOME"/Library/Logs/DiagnosticReports/PartBuddy*; do head -n 80 "$report" || true; done exit 1 From d2203bc41bffbbee035f7f90d72658d0d5a3dec1 Mon Sep 17 00:00:00 2001 From: ManuelStaggl <17688572+ManuelStaggl@users.noreply.github.com> Date: Thu, 12 Nov 2026 19:09:20 +0100 Subject: [PATCH 4/5] fix(desktop): the universal macOS app keeps its app.asar instead of an empty merged one Co-Authored-By: Claude Fable 5.2 --- .github/workflows/installer.yml | 3 ++- apps/desktop/electron-builder.yml | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/installer.yml b/.github/workflows/installer.yml index 3b2a5467..1199a84c 100644 --- a/.github/workflows/installer.yml +++ b/.github/workflows/installer.yml @@ -105,6 +105,8 @@ jobs: codesign --verify --deep --strict --verbose=2 "$app" codesign -dv "$app" 2>&1 | grep 'Signature=adhoc' test "$(find "$app" -name 'keyring.darwin-*.node' | tee /dev/stderr | wc -l)" -eq 2 + ls -la "$app/Contents/Resources" | grep -v lproj + test "$(stat -f%z "$app/Contents/Resources/app.asar")" -gt 1000000 ELECTRON_ENABLE_LOGGING=1 "$app/Contents/MacOS/PartBuddy" > start.log 2>&1 & pid=$! sleep 25 @@ -112,7 +114,6 @@ jobs: wait "$pid" && code=0 || code=$? echo "PartBuddy did not stay running (exit code $code)" cat start.log - ls -la "$app/Contents/Resources" find "$HOME/Library" -newer start.log -type f -not -path '*/Caches/*' 2>/dev/null | head -n 40 cat "$HOME/Library/Application Support/PartBuddy/logs/partbuddy.log" || true for report in "$HOME"/Library/Logs/DiagnosticReports/PartBuddy*; do head -n 80 "$report" || true; done diff --git a/apps/desktop/electron-builder.yml b/apps/desktop/electron-builder.yml index cbfc5b62..302917f1 100644 --- a/apps/desktop/electron-builder.yml +++ b/apps/desktop/electron-builder.yml @@ -49,6 +49,9 @@ mac: hardenedRuntime: false # Both builds carry both single-architecture credential stores; they are kept, not merged. x64ArchFiles: "**/keyring.darwin-*.node" + # Merging the two archives left an empty app.asar (installer run 43276112067); they are the same + # JavaScript anyway, so one of them is taken as it is. + mergeASARs: false dmg: artifactName: PartBuddy-${version}-mac-universal.${ext} nsis: From b99d87fe092b176678aa3bb277242df566d7de80 Mon Sep 17 00:00:00 2001 From: ManuelStaggl <17688572+ManuelStaggl@users.noreply.github.com> Date: Thu, 12 Nov 2026 19:29:55 +0100 Subject: [PATCH 5/5] fix(desktop): the macOS test build no longer looks for updates it cannot install Co-Authored-By: Claude Fable 5.2 --- apps/desktop/src/main/updates.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/desktop/src/main/updates.ts b/apps/desktop/src/main/updates.ts index e947d5f4..79c9ed05 100644 --- a/apps/desktop/src/main/updates.ts +++ b/apps/desktop/src/main/updates.ts @@ -28,7 +28,8 @@ export class Updates { /** Whether this build can update itself: the installed app, unless the environment says no. */ get enabled(): boolean { - return app.isPackaged && process.env.PARTBUDDY_UPDATES !== '0'; + // macOS: the test build is signed ad hoc and no release carries a latest-mac.yml, so it cannot update itself. + return app.isPackaged && process.platform !== 'darwin' && process.env.PARTBUDDY_UPDATES !== '0'; } current(): UpdateState {