Skip to content

Fix macOS release build and preflight release requirements - #724

Merged
micahflee merged 3 commits into
mainfrom
fix-electron-rebuild-scope
Sep 22, 2026
Merged

micahflee merged 3 commits into
mainfrom
fix-electron-rebuild-scope

Conversation

@micahflee

@micahflee micahflee commented Sep 22, 2026 •

Copy link
Copy Markdown
Member

Four stacked failures broke npm install and npm run publish-prod on macOS after the dependency upgrade, plus a fifth problem in the release environment itself. Each one masked the next, so they only surfaced one at a time — which is also what motivated the preflight check at the end.

1. npm install failed — unscoped electron-rebuild

node-gyp failed to rebuild '.../node_modules/macos-alias'
error: too few arguments to function call, single argument 'tag' was not specified

scripts/postinstall.js ran electron-rebuild --force with no filter, rebuilding every native module against Electron 44's headers — including macos-alias and fs-xattr, transitive deps of the optional appdmg. macos-alias uses nan, which is incompatible with Electron 44's V8 (the new ExternalPointerTypeTag argument on v8::External::New / External::Value).

Added --only better-sqlite3 to scripts/postinstall.js and the rebuild / rebuild-clean scripts. better-sqlite3 is the only native module the app loads at runtime; appdmg is a build-time DMG packager that runs under Node.

2. Packaging exited 0 with no output — yauzl on Node 24

The build stopped during Electron template extraction with no error message, exit code 0, and no out/ directory.

extract-zip@2.0.1 (the latest published version) pins yauzl@2.10.0, which uses fd-slicer@1.1.0. On Node 24 it stops delivering read streams partway through an archive and never settles:

entries total: 583
process exit code 0   entries seen: 20   streams opened: 2

Run under a top-level await this surfaces as Detected unsettled top-level await. Inside electron-packager's promise chain there's nothing to report it — the event loop just drains and Node exits 0.

yauzl@3.4.0 drops fd-slicer and reads the same archive completely (583/583). extract-zip only calls yauzl.open, so the API is compatible. Added as an override.

3. DMG maker crashed — unscoped image-size override

TypeError: sizeOf is not a function
    at Object.fn (node_modules/appdmg/lib/appdmg.js:289:5)

The existing "image-size": "^2.0.4" override upgraded appdmg's copy along with everything else. appdmg declares ^0.7.4 and calls it callback-style, but v2 dropped the default-function export.

Scoped appdmg to ^1.2.1 — still the patched release the override was added for, and it keeps the callback API (verified against the actual assets/dmg-background.png: { height: 290, width: 540, type: 'png' }).

4. Would have failed next — forge.config.ts rebuildConfig

Forge passes rebuildConfig straight into @electron/rebuild during packaging (@electron-forge/core/dist/api/package.js:181). It was {}, so it would rebuild macos-alias against Electron headers and hit the same nan wall as #1. appdmg is an optional prod dependency, so it survives pruning and gets packaged.

Set to onlyModules: ["better-sqlite3"]. The build log now shows ✔ Preparing native dependencies: 1 / 1.

5. Preflight the release requirements

With the build fixed, publish-prod then failed on code signing — and the error pointed nowhere useful:

Cyd.app: code object is not signed at all
CodeDirectory ... flags=0x20002(adhoc,linker-signed)
Signature=adhoc
TeamIdentifier=not set

The real cause was that the machine had no Developer ID certificate at all. @electron/packager defaults to continueOnError: true when signing (mac.js:402-404, "Default to continueOnError: true since this was the default behavior before this option was added"), so the missing identity was swallowed. The stock ad-hoc Electron signature survived, and the first thing to complain was @electron/notarize's pre-flight check — minutes into the build, describing the symptom and never the cause.

scripts/make.js now checks up front, before clean.mjs deletes the previous build:

  • every platform: DO_SPACES_KEY, DO_SPACES_SECRET
  • macOS: the signing identity is present in the keychain, then APPLE_ID and APPLE_PASSWORD

Each failure names the missing piece and how to obtain it.

forge.config.ts now reads the identity from MACOS_SIGNING_IDENTITY instead of hardcoding it, so the preflight cannot pass while signing uses a different identity. It throws if that variable is unset while MACOS_RELEASE=true, which only happens when forge is invoked outside scripts/make.js — nothing in the repo or CI does that.

Verification

Full npm run make-local passes:

✔ Making a dmg distributable for darwin/universal
✔ Making a zip distributable for darwin/universal
› Artifacts available at: .../out/make
  • lipo -archs on the packaged binary: x86_64 arm64 — universal stitch worked
  • hdiutil verify on the DMG: checksum VALID
  • zip + RELEASES.json produced for auto-updates

Preflight behavior:

Scenario Result
Real identity string vs keychain true
Bogus identity string vs keychain false
publish + Spaces creds, no Apple creds ✅ identity found, then ❌ APPLE_ID
publish, nothing set ❌ DO_SPACES_KEY
make local (non-publish) preflight skipped entirely

Prettier and ESLint clean on both changed files.

Not covered by this verification: code signing and notarization of a real build. scripts/make.js only sets MACOS_RELEASE=true for the publish command, so no make-* script exercises them. A test-sign with the new certificate does check out (full chain to Apple Root CA, secure timestamp, hardened runtime, TeamIdentifier=G762K6CH36), but publish-dev should be run before publish-prod to exercise the real path.

🤖 Generated with Claude Code

micahflee and others added 2 commits September 22, 2026 10:15
electron-rebuild was rebuilding every native module against Electron 44's
headers, including macos-alias and fs-xattr (transitive deps of the optional
appdmg package). macos-alias uses nan, which no longer compiles against
Electron 44's V8 due to the new ExternalPointerTypeTag argument on
v8::External::New and External::Value, so npm install failed outright.

appdmg is a build-time DMG packager that runs under Node, never inside
Electron, so rebuilding it against Electron headers was incorrect regardless.
better-sqlite3 is the only native module the app loads at runtime.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Three independent failures broke `npm run publish-prod` on macOS after the
dependency upgrade. Each one masked the next.

yauzl: extract-zip@2.0.1 pins yauzl@2.10.0, which uses fd-slicer@1.1.0. On
Node 24 it stops delivering read streams partway through an archive (20 of 583
entries, 2 streams opened) and never settles. Inside electron-packager's
promise chain the event loop simply drains, so the build exited 0 with no
error and no out/ directory while extracting the Electron template zips.
yauzl@3 drops fd-slicer and handles the same archive completely; extract-zip
only calls yauzl.open, so the API is compatible.

image-size: the unscoped "image-size": "^2.0.4" override upgraded appdmg's
copy too. appdmg declares ^0.7.4 and calls it callback-style, but v2 dropped
the default-function export, so the DMG maker crashed with "sizeOf is not a
function". Scope appdmg to ^1.2.1, which keeps the callback API and is still
the patched release the override was added for.

rebuildConfig: forge passes rebuildConfig straight into @electron/rebuild
during packaging. Unfiltered, it rebuilds appdmg's macos-alias against
Electron 44's headers, where nan no longer compiles. appdmg is a build-time
DMG packager that runs under Node and survives pruning as an optional prod
dependency, so it has to be excluded; better-sqlite3 is the only native module
the app loads at runtime.

Verified with a full `npm run make-local`: universal package built
(lipo: x86_64 arm64), DMG passes hdiutil verify, zip and RELEASES.json
produced. Signing and notarization are not exercised by make-local, since
scripts/make.js only sets MACOS_RELEASE=true for publish.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@micahflee micahflee changed the title Scope electron-rebuild to better-sqlite3 Fix macOS release build (electron-rebuild, yauzl, image-size) Sep 22, 2026
@electron/packager defaults to continueOnError: true when signing (mac.js:402),
so a missing Developer ID identity does not fail the build. It leaves the stock
ad-hoc Electron signature in place, and the build dies minutes later inside
@electron/notarize's pre-flight with "code object is not signed at all" — a
message that describes the symptom and never names the cause.

Check up front, before clean.mjs deletes the previous build, that publish has
what it needs: the Spaces credentials on every platform, and on macOS that the
signing identity is actually in the keychain plus the notarization credentials
are set. Each failure names the missing piece and how to get it.

forge.config.ts now reads the identity from MACOS_SIGNING_IDENTITY rather than
hardcoding it, so the preflight cannot pass while signing uses some other
identity. It throws if the variable is unset while MACOS_RELEASE is true, which
only happens when forge is invoked outside scripts/make.js.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@micahflee micahflee changed the title Fix macOS release build (electron-rebuild, yauzl, image-size) Fix macOS release build and preflight release requirements Sep 22, 2026
@micahflee
micahflee merged commit 9cb2aae into main Sep 22, 2026
1 check passed
@micahflee
micahflee deleted the fix-electron-rebuild-scope branch September 22, 2026 18:48
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