Skip to content

[PM-33578] Mac OS arm builds#1077

Merged
BTreston merged 199 commits into
mainfrom
ac/pm-33578-macos-arm-builds
Apr 16, 2026
Merged

[PM-33578] Mac OS arm builds#1077
BTreston merged 199 commits into
mainfrom
ac/pm-33578-macos-arm-builds

Conversation

@BTreston

@BTreston BTreston commented Apr 7, 2026

Copy link
Copy Markdown
Contributor

🎟️ Tracking

https://bitwarden.atlassian.net/browse/PM-33578

📔 Objective

Summary

This PR adds native macOS ARM64 (Apple Silicon) support for both the CLI and GUI builds. It also replaces pkg with Node.js SEA (Single Executable Applications).

Key Changes

macOS ARM64 Support

  • Add new macos-arm64-cli and macos-arm64-gui CI jobs running on macos-15 (Apple Silicon runners)
  • Add pack:cli:mac:arm64, dist:cli:mac:arm64, dist:mac:arm64, and pack:mac:arm64 npm scripts
  • Update electron-builder.json to use ${arch} in artifact names so x64 and ARM64 artifacts are distinguishable (e.g. Bitwarden-Connector-X.Y.Z-arm64.dmg)
  • Add JIT entitlement to resources/entitlements.mac.plist required for Apple Silicon code signing

CLI Packaging: pkg → Node.js SEA

  • Replace pkg with a custom scripts/pack-sea.mjs script using Node.js SEA (Single Executable Applications)
  • Downloads official Node.js binaries (version-matched, cached in .node-sea-base/) as SEA base to avoid issues with Homebrew/nvm-installed binaries
  • macOS x64 exception: Due to a known upstream Node.js issue (nodejs/node#59553) where Mach-O injection corrupts TLS metadata or causes codesign failures on Intel Macs, macOS x64 ships a shell-wrapper bundle instead of a single binary: bwdc (launcher script), node, bwdc.js, and *.node native addon
  • macOS ARM64, Linux, and Windows use true single-executable binaries
  • Update CI zip steps to use directory-based zipping (dist-cli/<platform>/*) to include all bundle files

📸 Screenshots

BTreston and others added 30 commits January 29, 2026 14:36
…rate from keytar. Update .gitignore for Rust artifacts, adjust package.json for new build scripts, and modify workflows for native module compilation. Enhance state versioning to support migration of credentials from keytar to desktop_core.
…Script configurations and package dependencies. Refactor native binding logic and enhance error handling. Remove legacy keytar references and adjust package-lock.json for new native module structure.
…removing unnecessary semicolons. Update build scripts in package.json to exclude JavaScript during native module compilation.
…ule directly in the renderer process, removing the need for the credential storage listener. Update TypeScript definitions and enhance error handling in password management functions. Adjust Cargo dependencies and versions for improved compatibility.
… into ac/pm-12436-replace-node-keytar-sonnet-attempt
… desktop_core for consistency in error messaging.
Base automatically changed from ac/pm-31850-enable-esm to main April 14, 2026 14:17
@BTreston BTreston marked this pull request as ready for review April 14, 2026 14:29
@BTreston BTreston requested a review from a team as a code owner April 14, 2026 14:29
@BTreston BTreston requested a review from eliykat April 14, 2026 14:30

@eliykat eliykat left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is awesome, great work.

Comment thread .github/workflows/build.yml Outdated
Comment thread scripts/pack-sea.mjs Outdated
Comment thread scripts/pack-sea.mjs Outdated
Comment thread scripts/signal-exit-shim.cjs Outdated
Comment on lines +1 to +3
// Shim: proper-lockfile expects require('signal-exit') to return the onExit function
// directly (signal-exit v3 behavior). signal-exit v4 uses named exports only.
// This shim re-exports the function as the module default.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you help me understand this? Presumably proper-lockfile depends on signal-exit v3, not v4; why are we using v4 with a shim instead of v3?

I asked Claude and it said that webpack flattens dependencies, but that would be pretty drastic behavior so I'm not sure about that answer - but happy to be wrong if this is what you've verified. If that is the case - is there any way to override this and package both?

It would be better to use the correct dependency version, as we don't know what other breaking changes there may be.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

afaik its purely a transitive dependency b/c proper-lockfile ships their own v3 bundled into their app, so we can't control it. Other deps bundle v4 so we can't blanket change over to v3. But I think the better alternative is aliasing directly to the version in proper-lockfiles node_modules maybe. I'll see if I can get it working without the this "shim" as I was already not a fan of this.

Comment thread package.json
Comment thread webpack.cli.mjs Outdated
// ships its own nested v3. Point webpack directly at that copy.
"signal-exit": path.resolve(
__dirname,
"node_modules/proper-lockfile/node_modules/signal-exit/index.js",

@BTreston BTreston Apr 15, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@eliykat I think this ends up being a global alias (including for deps that expect v4) but the build pass in CI and the app seems fine. I didn't think this would work but apparantly it does, but I'm not sure why. I'll follow up if i figure it out.

edit: this breaks login functionality (b/c thats where we use inquirer, which uses v4 I believe)

Comment thread scripts/pack-sea.mjs
execFileSync("codesign", ["--sign", "-", "--force", outputBinary], {
stdio: "inherit",
});
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do the native modules need to be code signed? The bwdc binary gets signed (line 76), but the native .node modules that get copied to the output directory (lines 64-69) don't appear to be signed.
On macOS, unsigned binaries can potentially trigger Gatekeeper warnings, something like "cannot be opened because the developer cannot be verified" etc.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some of this was going to be handled in #1051, but I've decided to roll it into this PR because they fit nicely together.

The native module did indeed need to be signed, the latest changes should have that set now. The build artifacts in https://github.com/bitwarden/directory-connector/actions/runs/24476942691/job/71531511465?pr=1078 show this working

@BTreston BTreston requested a review from eliykat April 15, 2026 20:55
eliykat
eliykat previously approved these changes Apr 16, 2026
@sonarqubecloud

Copy link
Copy Markdown

@BTreston BTreston requested a review from eliykat April 16, 2026 01:33
@BTreston BTreston merged commit 859fd1d into main Apr 16, 2026
23 of 26 checks passed
@BTreston BTreston deleted the ac/pm-33578-macos-arm-builds branch April 16, 2026 01:41
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.

macOS Rosetta Support Ending

4 participants