electron-updater in the shell (packaged builds only; dev never updates). Checks at startup + every 24h, downloads silently, and the renderer shows a Restart banner once the update is staged (it installs on quit either way). Manual "Check for Updates..." under the app menu answers via dialog. Every updater event routes through the shell log and is caught - the updater can never take the app down (verified live: a feed failure logs three lines and the app runs on). LH_UPDATE_FEED overrides the baked GitHub feed with a generic local server - the pre-release drill: serve latest-mac.yml + zip + blockmap from localhost and a real installed copy updates itself, no release, no public repo. The asar allow-list re-includes electron-updater + its 16-package runtime tree explicitly (gitignore last-match-wins; asar verified at 2.1 MB). The banner rides the preload bridge (onUpdateStatus/installUpdate) and renders nothing in the browser. window.lh typing centralized in sidebar.tsx's declaration.
126 lines
6.2 KiB
YAML
126 lines
6.2 KiB
YAML
# 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/<dep>/...' 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
|
|
# Auto-update (Wave C, 2026-10-10): electron-updater + its runtime tree
|
|
# re-included explicitly. The negation above excludes node_modules, and
|
|
# the shell's bare positives can't re-include them — these explicit
|
|
# node_modules positives do (gitignore order: the LAST matching pattern
|
|
# wins; verified post-build by listing the asar — see desktop:dist notes).
|
|
# Tree fact-checked via each package's declared dependencies:
|
|
# electron-updater → fs-extra, js-yaml, lazy-val, lodash.escaperegexp,
|
|
# lodash.isequal, semver, tiny-typed-emitter, builder-util-runtime;
|
|
# builder-util-runtime → debug, sax; fs-extra → graceful-fs, jsonfile,
|
|
# universalify; debug → ms; jsonfile → universalify (again); js-yaml →
|
|
# argparse. Keep in sync when electron-updater bumps (npm ls --all).
|
|
- node_modules/electron-updater/**/*
|
|
- node_modules/fs-extra/**/*
|
|
- node_modules/js-yaml/**/*
|
|
- node_modules/lazy-val/**/*
|
|
- node_modules/lodash.escaperegexp/**/*
|
|
- node_modules/lodash.isequal/**/*
|
|
- node_modules/semver/**/*
|
|
- node_modules/tiny-typed-emitter/**/*
|
|
- node_modules/builder-util-runtime/**/*
|
|
- node_modules/debug/**/*
|
|
- node_modules/sax/**/*
|
|
- node_modules/graceful-fs/**/*
|
|
- node_modules/jsonfile/**/*
|
|
- node_modules/universalify/**/*
|
|
- node_modules/ms/**/*
|
|
- node_modules/argparse/**/*
|
|
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 |