# electron-builder config — LearnHarness desktop (macOS arm64 first). # # Shape (fact-checked 2026-09-17): # - the app's real server (.next/standalone, curated by # scripts/build-server.mjs) ships via extraResources — OUTSIDE the asar, # because native .node/dylib can't dlopen from a read-only archive and # server.js process.chdir()s into its own dir; # - the asar holds only the shell (electron/main.cjs + package.json); # - signed + notarized since 2026-10-08 (Developer ID Application cert in # the login keychain; notary credentials in the LH_NOTARY keychain # profile; the profile + team env vars are baked into desktop:dist). appId: com.learnharness.LearnHarness productName: LearnHarness directories: output: dist files: # Only the shell goes into the asar. ORDER IS LOAD-BEARING (payload # wave, 2026-09-24): electron-builder's dependency collector packs the # whole production dep tree into node_modules/ REGARDLESS of this # allow-list — normalizeFiles turns this array into one {filter: [...]} # fileSet, getNodeModuleFileMatcher feeds EVERY filter entry (positives # included) to the per-dep copier, and without a negation its implicit # '**/*' passes everything (the measured result: a 502MB asar holding # next/@ai-sdk/pdfjs/mermaid/puppeteer… while the launcher requires # nothing but electron + node builtins — the server ships via # extraResources). # # The fix is gitignore-style ordering (verified against the installed # app-builder-lib 26.15.3 + a DEBUG-mode rebuild): '!**/*' first excludes # EVERYTHING, the two positives after it re-include only the shell. # The per-dep collector then contributes ZERO files — app-builder-lib's # filter evaluates each dep file's DESTINATION path (the # stat.moduleFullFilePath quirk in util/filter.js getRelativePath), so # '!**/*' matches every 'node_modules//...' path and the bare # positives can never re-include one (DEBUG logs confirm filesCount=0 # for all 566 modules). If a future electron-builder fixes that quirk, # ~1MB of package.json stubs would start landing — harmless. The # two-package.json layout avoids the collector entirely but # restructures the repo. app.asar.unpacked dies with it: the natives # were only unpacked because their packages were packed. - "!**/*" - electron/**/* - package.json extraResources: # Copy from the PARENT dir (.next), not .next/standalone itself: # electron-builder hard-excludes a copy's root node_modules and the # default no-filter pattern is a literal path match (empirically both # node_modules/ and .next/ were silently dropped → server died on # "Cannot find module 'next'"). With `from: .next` the filter's # relative paths start with standalone/, dodging the root-node_modules # rule, and the explicit wildcard (minimatch dot:true) reaches # standalone/.next and standalone/node_modules. - from: .next to: server filter: - "standalone/**/*" mac: # zip rides next to dmg (flipped 2026-10-08): Squirrel.Mac — Electron's # macOS auto-updater — installs from ZIP and cannot read DMGs. DMG is # for humans; ZIP + blockmap are the updater's payload. target: - target: dmg arch: - arm64 - target: zip arch: - arm64 # Locale trim (payload wave, 2026-09-24): English-only app — cuts # every non-en .lproj from the frameworks (the STT/mic entitlements # don't depend on locales). Revisit only if localization ever lands. electronLanguages: - en category: public.app-category.education # Laid for the STT milestone (researcher-verified): the TCC mic prompt # reads this string; macOS asks regardless of Electron's own handlers. extendInfo: NSMicrophoneUsageDescription: "LearnHarness uses the microphone only for speech-to-text input into your sessions." hardenedRuntime: true entitlements: build/entitlements.mac.plist # Signing + notarization (flipped 2026-10-08): identity auto-detects the # Developer ID Application certificate from the login keychain; # notarize: true makes electron-builder sign → submit to Apple's notary # service → staple the ticket, all inside this one build (credentials # via the LH_NOTARY keychain profile, env baked into desktop:dist). # Adds minutes per build (Apple's scan queue). Verification battery: # codesign --verify --deep / spctl --assess / stapler validate. notarize: true dmg: title: "${productName} ${version}" publish: # Update feed = GitHub Releases (decided 2026-10-08): latest-mac.yml + # zip/blockmap live on releases; electron-updater reads them anonymously. # REQUIRES THE REPO TO BE PUBLIC before 0.3.0 ships — anonymous downloads # from a private repo are impossible, and a token baked into the app # would be a leak. desktop:dist passes no --publish flag, so builds never # upload; publishing happens at release time (GH_TOKEN + --publish). - provider: github owner: amosblomqvist repo: learnharness