Skip to content

feat(build): cross-Mac packaged build — multi-arch dmg, pty unpack, unsigned-dev fallback, .icns (#495) - #506

Merged
Juliusolsson05 merged 4 commits into
mainfrom
fix/cross-mac-packaged-build
Jul 9, 2026
Merged

Juliusolsson05 merged 4 commits into
mainfrom
fix/cross-mac-packaged-build

Conversation

@Juliusolsson05

Copy link
Copy Markdown
Owner

What

Part 2 of the #495 Tier-A portability plan — the "packaged artifact works on a Mac that isn't this one" batch:

  • A2 — mac.target builds dmg+zip for [arm64, x64] (two thin artifacts, deliberately not universal: lipo-merge chokes on asymmetric native deps and thin dmgs halve the download; artifactName already templated ${arch}).
  • A3 — explicit **/*.node in asarUnpack so 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 default npmRebuild.
  • A6 — scripts/package-mac.mjs is now the single home of the unsigned-build fallback (no CSC_LINK/CSC_NAME → auto-discovery off + APPLE_* stripped). release.yml drops its inline copy and calls the same wrapper.
  • A16 — .nvmrc (20, matches CI) + engines.node >=20 floor (no upper bound: the dev machine runs Node 25 and works).
  • A18 — committed build/icon.icns + mac.icon key; packaging no longer regenerates the icon per build host.
  • A17 — README: submodules are load-bearing for npm run dev (Vite compiles packages/*/src directly), 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.node is arm64 in the arm64 app and x86_64 in the x64 app (file output checked) — the A2×A3 interaction works.
  • out/main/runtime/{mitmproxy,tmux,cloudflared} present and unpacked in both apps.
  • Signing skipped with the wrapper's logged reason (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-code with 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 (branch plan/portability-495-496).

🤖 Generated with Claude Code

Juliusolsson05 and others added 3 commits July 9, 2026 10:41
… 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>
@Juliusolsson05
Juliusolsson05 merged commit 386c5e1 into main Jul 9, 2026
@Juliusolsson05
Juliusolsson05 deleted the fix/cross-mac-packaged-build branch July 9, 2026 09:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant