Sign the macOS bundle, or it arrives "damaged"

The first release could not be opened: macOS said "WarpEngine Store is damaged
and can't be opened. You should move it to the Bin."

Not a wording problem — an integrity one. electron-builder found no signing
identity and skipped signing, so the bundle kept only the linker's ad-hoc
signature on its main executable, with no resource seal. `codesign --verify` said
"code has no resources but signature indicates they must be present", and
Gatekeeper reports that as damaged and offers no way past it, unlike an
un-notarised app which can at least be approved.

`scripts/after-pack.js` now signs the bundle itself during packaging. Measured on
a copy unzipped from the artifact with the quarantine flag set by hand:

  before  code has no resources but signature indicates they must be present
  after   valid on disk; satisfies its Designated Requirement

and the identifier is ours rather than `Electron`. `syspolicy_check` is down to
its expected "adhoc signed" warning. A downloaded copy still has to be approved —
that is Gatekeeper policy for anything un-notarised, and notarisation needs a paid
Developer ID — so the README and the release notes lead with the one command that
does it.

Two smaller things the failure turned up:

- The self-test was passing silently. With a copy of the app already open, the
  second process lost the single-instance lock and exited 0 with no output, which
  reads exactly like success. It now uses its own user-data directory and skips
  the lock, and it caught a real launch failure immediately afterwards.
- The README claimed right-click ▸ Open was enough. It was not, and I had not
  checked it — replaced with what the measurements support.

v1.0.0's attachments are withdrawn rather than left downloadable.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-18 11:55:17 +02:00
co-authored by Claude Opus 5
parent f88340d63c
commit e635a032ea
4 changed files with 79 additions and 11 deletions
+8 -1
View File
@@ -60,6 +60,13 @@ async function guarded (fn) {
// would otherwise be noticed: the main process log stays empty.
const SELFTEST = process.argv.includes('--selftest')
// A test run must never be swallowed by a copy the user already has open: it gets
// its own user-data directory and skips the single-instance lock. Without this the
// second process exits silently with status 0, which reads as a passing test.
if (SELFTEST) {
app.setPath('userData', path.join(app.getPath('temp'), 'warpstore-gui-selftest'))
}
async function selftest () {
const result = await win.webContents.executeJavaScript(`(() => ({
cards: document.querySelectorAll('.card').length,
@@ -215,7 +222,7 @@ ipcMain.handle('app:openExternal', async (_event, url) => {
// --- lifecycle ------------------------------------------------------------
if (!app.requestSingleInstanceLock()) {
if (!SELFTEST && !app.requestSingleInstanceLock()) {
app.quit()
} else {
app.on('second-instance', () => {