Files
learnharness/electron-builder.yml
T
amosblomqvist cf042b25e7 icons: per-platform icon set — real Liquid Glass on macOS, flat design on Windows/Linux
The hand-packed icns was pixel-clean (verified by unpacking and scanning
every size), but macOS 26 composites legacy .icns icons into the Liquid
Glass system at render time; stacked on Icon Composer's baked PNG-export
lighting, that clips the rim highlights into the near-corner specks and
abrupt highlight ends (owner report 2026-10-13). The fix is the real
layered icon: mac.icon now points at the Icon Composer document
(build/icon.icon, copied from ~/Documents/Affinity/lh-logo) and
electron-builder compiles it via actool into Assets.car — macOS 26+
renders the true Liquid Glass design (dark/tinted variants included).

actool's backwards-compat icns caps at 256px, so scripts/after-pack.cjs
swaps in the existing full-res build/icon.icns before signing: pre-Tahoe
macOS keeps today's quality, Tahoe gets the layered icon.

Windows/Linux get the owner's flat design (rounded squircle with baked
transparency): scripts/make-icons.mjs renders build/icon-flat.svg via
resvg into build/icon.ico (16→256 PNG entries) + build/icon.png (512),
and electron-builder.yml pins win.icon/linux.icon.

desktop:dist injects DEVELOPER_DIR (actool needs Xcode; the machine
selects CommandLineTools by default, CI's setup-xcode takes precedence
via the :- default). The release workflow's mac job selects Xcode 26+
explicitly. Verified locally: mac --dir build (Assets.car + checksummed
full-res fallback + CFBundleIconName/IconFile) and the win-unpacked exe
carries our ico bytes; NSIS/AppImage assembly is CI's (cross-build
tooling can't run on arm64 macOS). Runbook updated. 1610/1610.
2026-10-13 10:51:22 +03:00

173 lines
8.1 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# electron-builder config — LearnHarness desktop (macOS arm64; Windows/Linux x64).
#
# 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
# Icons (owner ruling, 2026-10-13) — per-platform sources in build/:
# mac build/icon.icon — the Icon Composer document (layered Liquid
# Glass); actool (Xcode 26+) compiles Assets.car (CFBundleIconName,
# macOS 26+ renders the true layered icon) + an actool fallback icns
# capped at 256px, which scripts/after-pack.cjs swaps for the
# hand-packed full-res build/icon.icns (pre-Tahoe keeps 16→1024).
# Requires actool ≥ 26: desktop:dist injects DEVELOPER_DIR locally;
# the release workflow's mac job runs setup-xcode.
# win build/icon.ico — generated (16→256) from the flat design by
# scripts/make-icons.mjs.
# linux build/icon.png — 512×512, same script. Rounded squircle with
# baked transparency on both (owner ruling) — Windows/Linux don't
# mask, the shape ships in the artwork.
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:
icon: build/icon.icon
# 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}"
afterPack: scripts/after-pack.cjs
win:
icon: build/icon.ico
target:
- target: nsis
arch:
- x64
artifactName: "${productName}-${version}-${arch}-setup.${ext}"
# Unsigned Windows release (owner ruling): preserve icon/version metadata,
# skip signing, and do not require a publisher signature for updates.
signExecutable: false
verifyUpdateCodeSignature: false
nsis:
oneClick: true
perMachine: false
linux:
icon: build/icon.png
target:
- target: AppImage
arch:
- x64
- target: deb
arch:
- x64
artifactName: "${productName}-${version}-${arch}.${ext}"
category: Education
# Match Electron's app_id/WM_CLASS to the installed desktop launcher.
syncDesktopName: true
# Both targets update via electron-updater 6.8.9. AppImage replaces its
# own file; deb uses dpkg/apt and may request administrator permission.
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
# All three CI jobs contribute assets; only the owner publishes it.
releaseType: draft