diff --git a/forge.config.ts b/forge.config.ts index b2991166..155323c8 100644 --- a/forge.config.ts +++ b/forge.config.ts @@ -160,8 +160,20 @@ const mimeTypeScheme = blueskyOAuthSchemeHandlerMimeTypeForMode( let osxSign: OsxSignOptions | undefined; let osxNotarize: NotaryToolCredentials | undefined; if (process.env.MACOS_RELEASE === "true") { + // Set by scripts/make.js, which also preflights that this identity actually + // exists in the keychain. Signing must not fall back to a hardcoded string + // here, or the preflight could pass while signing uses a different identity. + const identity = process.env.MACOS_SIGNING_IDENTITY; + if (!identity) { + throw new Error( + "MACOS_SIGNING_IDENTITY is not set. Build macOS releases via " + + "`npm run publish-dev` / `npm run publish-prod` so scripts/make.js can " + + "set it and verify the identity is in the keychain.", + ); + } + osxSign = { - identity: "Developer ID Application: Lockdown Systems LLC (G762K6CH36)", + identity, optionsForFile: (filePath) => { const entitlementDefault = path.join( assetsPath, @@ -254,7 +266,13 @@ const config: ForgeConfig = { // eslint-disable-next-line @typescript-eslint/no-explicit-any osxNotarize: osxNotarize as any, }, - rebuildConfig: {}, + // Only better-sqlite3 is loaded inside Electron and needs rebuilding against + // Electron's headers. appdmg's native deps (macos-alias, fs-xattr) are + // build-time tools that run under Node, and macos-alias's nan does not + // compile against Electron 44's V8, which fails the whole package step. + rebuildConfig: { + onlyModules: ["better-sqlite3"], + }, makers: [ // Windows new MakerSquirrel({ diff --git a/package-lock.json b/package-lock.json index dff89c95..16d34fa5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6024,6 +6024,22 @@ "node": ">=8.5" } }, + "node_modules/appdmg/node_modules/image-size": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-1.2.1.tgz", + "integrity": "sha512-rH+46sQJ2dlwfjfhCyNx5thzrv+dtmBIhPHk0zgRUukHzZ/kRueTJXoYYsclBaKcSMBWuGbOFXtioLpzTb5euw==", + "license": "MIT", + "optional": true, + "dependencies": { + "queue": "6.0.2" + }, + "bin": { + "image-size": "bin/image-size.js" + }, + "engines": { + "node": ">=16.x" + } + }, "node_modules/archiver": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/archiver/-/archiver-8.0.0.tgz", @@ -8919,15 +8935,6 @@ "reusify": "^1.0.4" } }, - "node_modules/fd-slicer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", - "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", - "license": "MIT", - "dependencies": { - "pend": "~1.2.0" - } - }, "node_modules/fdir": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", @@ -9862,19 +9869,6 @@ "node": ">= 4" } }, - "node_modules/image-size": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/image-size/-/image-size-2.0.4.tgz", - "integrity": "sha512-QRUkFFsRV/6fuESxb9Vkq+a0LkSrgKXuc2NEqfikiXxxN/G3tjWt5EVUlMaImRBZRZK/jRBEbYvpPYZL8t08Zw==", - "license": "MIT", - "optional": true, - "bin": { - "image-size": "bin/image-size.js" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/imul": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/imul/-/imul-1.0.1.tgz", @@ -12617,6 +12611,16 @@ "dev": true, "license": "MIT" }, + "node_modules/queue": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz", + "integrity": "sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==", + "license": "MIT", + "optional": true, + "dependencies": { + "inherits": "~2.0.3" + } + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -15219,22 +15223,15 @@ } }, "node_modules/yauzl": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", - "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-3.4.0.tgz", + "integrity": "sha512-jIH9yLR9wqr0wOS0TpBvo/g/2UgZH5qePVbjgRliiF0BYvOZyaBknKsF+x9Iht0O6sqgnB93rCICdOZFecJuDw==", "license": "MIT", "dependencies": { - "buffer-crc32": "~0.2.3", - "fd-slicer": "~1.1.0" - } - }, - "node_modules/yauzl/node_modules/buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", - "license": "MIT", + "pend": "~1.2.0" + }, "engines": { - "node": "*" + "node": ">=12" } }, "node_modules/yn": { diff --git a/package.json b/package.json index 266ce7a4..59fd9d5a 100644 --- a/package.json +++ b/package.json @@ -8,8 +8,8 @@ "authors": "Lockdown Systems LLC", "scripts": { "clean": "node ./scripts/clean.mjs", - "rebuild": "npx electron-rebuild --force", - "rebuild-clean": "rm -rf node_modules/better-sqlite3/build && npx electron-rebuild --force", + "rebuild": "npx electron-rebuild --force --only better-sqlite3", + "rebuild-clean": "rm -rf node_modules/better-sqlite3/build && npx electron-rebuild --force --only better-sqlite3", "postinstall": "node scripts/postinstall.js", "start": "electron-forge start", "dev": "electron-forge start", @@ -130,9 +130,13 @@ "macos-alias@0.2.12": true }, "overrides": { + "yauzl": "^3.4.0", "tar": "^7.5.22", "tmp": "^0.2.7", "image-size": "^2.0.4", + "appdmg": { + "image-size": "^1.2.1" + }, "mhtml2html": { "jsdom": "^29.1.1" }, diff --git a/scripts/make.js b/scripts/make.js index 63316ee0..39632546 100644 --- a/scripts/make.js +++ b/scripts/make.js @@ -37,6 +37,78 @@ if (platform == "win32") { process.env.MACOS_RELEASE = command === "publish" ? "true" : "false"; } +// The Developer ID identity used to sign macOS releases. forge.config.ts reads +// this from the environment so the preflight check below and the actual signing +// can never drift apart. +const macosSigningIdentity = + "Developer ID Application: Lockdown Systems LLC (G762K6CH36)"; +process.env.MACOS_SIGNING_IDENTITY = macosSigningIdentity; + +// Preflight checks +// +// @electron/packager defaults to `continueOnError: true` when signing, so a +// missing identity does not fail the build. It leaves the stock ad-hoc Electron +// signature in place and the build dies minutes later inside notarization with +// "code object is not signed at all", which does not name the real cause. Fail +// up front instead. + +function fail(message, hint) { + console.error(`\nāŒ ${message}\n`); + console.error(` ${hint}\n`); + process.exit(1); +} + +function requireEnv(name, hint) { + if (!process.env[name]) { + fail(`${name} is not set, which ${command} requires.`, hint); + } +} + +if (command === "publish") { + requireEnv( + "DO_SPACES_KEY", + "Needed to upload the release to DigitalOcean Spaces.", + ); + requireEnv( + "DO_SPACES_SECRET", + "Needed to upload the release to DigitalOcean Spaces.", + ); + + if (platform === "darwin") { + let identities = ""; + try { + identities = execSync("security find-identity -v -p codesigning", { + encoding: "utf8", + }); + } catch { + fail( + "Could not read code signing identities from the keychain.", + "Try running: security find-identity -v -p codesigning", + ); + } + + if (!identities.includes(macosSigningIdentity)) { + fail( + `No code signing identity matching "${macosSigningIdentity}" in your keychain.`, + "Import the Developer ID .p12, or create a new certificate at " + + "https://developer.apple.com/account/resources/certificates (Account " + + "Holder only), then confirm with: security find-identity -v -p codesigning", + ); + } + + console.log(`āœ… Signing identity found: ${macosSigningIdentity}`); + + requireEnv( + "APPLE_ID", + "The Apple ID used to notarize. Needed by notarytool.", + ); + requireEnv( + "APPLE_PASSWORD", + "An app-specific password from https://appleid.apple.com, not your Apple ID password.", + ); + } +} + try { // Clean up previous builds and install dependencies execSync(`node ./scripts/clean.mjs`, { stdio: "inherit" }); diff --git a/scripts/postinstall.js b/scripts/postinstall.js index c13e07fc..23617eb3 100755 --- a/scripts/postinstall.js +++ b/scripts/postinstall.js @@ -12,7 +12,7 @@ if (process.env.CI === "true" || process.env.CI === "1") { console.log("Rebuilding native modules for Electron..."); console.log("- Searching dependency tree"); try { - execSync("npx electron-rebuild --force", { + execSync("npx electron-rebuild --force --only better-sqlite3", { stdio: "inherit", cwd: process.cwd(), });