Repository navigation
feat(build): cross-Mac packaged build — multi-arch dmg, pty unpack, unsigned-dev fallback, .icns (#495) - #506
Merged
Conversation
… committed .icns electron-builder defaulted to the HOST arch, so CI on an arm64 runner shipped arm64-only dmgs and an Intel Mac had no runnable artifact (#495 A2). mac.target now builds dmg+zip for [arm64, x64] — two thin per-arch artifacts rather than universal, because lipo-merging has a history of choking on asymmetric native deps (node-pty) and thin dmgs halve the download; artifactName already templated ${arch}. asarUnpack gains an explicit **/*.node: electron-builder's implicit native-module detection is a documented footgun once a custom asarUnpack list exists, and if pty.node stays inside the asar every terminal is dead in the packaged app while npm run dev looks fine (#495 A3). Verified on a real two-arch dist: build/Release/pty.node is arm64 in the arm64 app and x86_64 in the x64 app, unpacked in both. build/icon.icns is committed (generated once via sips+iconutil from the 1024px build/icon.png) and referenced by mac.icon, so packaging no longer depends on the build host being able to regenerate it (#495 A18). This is a stable first-party asset, not a fetched runtime tool — the third_party no-binaries rule does not apply. Refs #495 (A2, A3, A18) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The 'build unsigned when no cert' guard lived ONLY as inline shell in release.yml, so npm run dist:mac worked in CI and failed on any Mac without a Developer ID identity — electron-builder auto-discovery either errors or produces a broken half-signature (#495 A6). package:mac / pack:mac now route through scripts/package-mac.mjs, which disables identity auto-discovery AND strips the APPLE_* notarization vars when no CSC_LINK/CSC_NAME is present (leaving them set makes electron-builder try to notarize an unsigned app, which hard-fails at the end of a long build). The CI step drops its inline copy and calls the same wrapper, so the policy can't drift between local and CI again. Verified: local dist:mac with no cert env completes and logs the skipped-signing reason. .nvmrc (20) matches the CI setup-node pin; engines.node >=20 is a floor only — the dev machine currently runs Node 25 and works, so an upper bound would warn on the exact machine that builds releases (#495 A16). npm engines is a warning by default, which is the intended strength: signal, not enforcement. Refs #495 (A6, A16) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The Getting-started section claimed Node 22 (CI pins 20, dev runs 25 — now says 20+ pointing at .nvmrc) and didn't say what happens if you skip --recurse-submodules: the dev build compiles the five package submodules straight from src/ via Vite aliases, so npm run dev cannot start without them (#495 A17). Also corrects the audit-era claim that the package repos are private — all five are public now, verified via the GitHub API, so no PAT/SSH story is needed for a plain clone (CI's SUBMODULE_PAT plumbing is kept for private forks). Refs #495 (A17) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ds, unsigned artifact naming, anonymous submodule fallback 1. scripts/package-mac.mjs stripped only the APPLE_ID-path notarization vars; app-builder-lib's getNotarizeOptions() has two more independent credential paths (APPLE_API_KEY/APPLE_API_KEY_ID/APPLE_API_ISSUER and APPLE_KEYCHAIN_PROFILE/APPLE_KEYCHAIN), and on arm64 the no-identity build falls back to ad-hoc signing and still calls notarize — so a dev with only those exported got a late notarytool failure after a full "UNSIGNED" build. Strip the whole set and log which names were ignored. 2. release.yml uploaded unsigned dev builds under the artifact name macos-release. Name now tracks what the build actually is: CSC_LINK present → macos-release, absent → macos-unsigned-dev (publish_release already implies CSC_LINK via validate-release). 3. release.yml hard-failed without SUBMODULE_PAT/SUBMODULE_SSH_KEY while README says no special access is needed. Verified all six submodule repos (five Juliusolsson05 packages + openai/codex) return 200 to anonymous API calls — the README is right; the workflow now falls back to anonymous HTTPS when neither secret is configured, keeping the auth plumbing for private forks. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Part 2 of the #495 Tier-A portability plan — the "packaged artifact works on a Mac that isn't this one" batch:
mac.targetbuilds dmg+zip for [arm64, x64] (two thin artifacts, deliberately not universal: lipo-merge chokes on asymmetric native deps and thin dmgs halve the download;artifactNamealready templated${arch}).**/*.nodeinasarUnpackso pty.node can never be stranded inside the asar by implicit-detection failure (dead terminals in the dmg, invisible in dev). Native deps rebuild per target arch via electron-builder's defaultnpmRebuild.scripts/package-mac.mjsis now the single home of the unsigned-build fallback (noCSC_LINK/CSC_NAME→ auto-discovery off +APPLE_*stripped).release.ymldrops its inline copy and calls the same wrapper..nvmrc(20, matches CI) +engines.node >=20floor (no upper bound: the dev machine runs Node 25 and works).build/icon.icns+mac.iconkey; packaging no longer regenerates the icon per build host.npm run dev(Vite compilespackages/*/srcdirectly), and corrected to note all five package repos are public now (verified via API — the audit's "4 private" is stale).Verified on a real build
npm run dist:mac(no signing env) produced all four artifacts:Agent Code-0.0.2-beta.1-{arm64,x64}.{dmg,zip}app.asar.unpacked/.../build/Release/pty.nodeis arm64 in the arm64 app and x86_64 in the x64 app (fileoutput checked) — the A2×A3 interaction works.out/main/runtime/{mitmproxy,tmux,cloudflared}present and unpacked in both apps.CSC_IDENTITY_AUTO_DISCOVERY=false).Remaining manual step: launch the x64 dmg on an Intel Mac (or Rosetta) — not automated here because the packaged app shares
~/.config/agent-codewith the live dev install.Refs #495 (A2, A3, A6, A16, A17, A18). Part of the plan in
docs/superpowers/plans/2026-07-09-portability-tier-a-any-mac-hardening.md(branchplan/portability-495-496).🤖 Generated with Claude Code