release: use macOS 26 for Icon Composer packaging

This commit is contained in:
amosblomqvist
2026-10-13 18:41:34 +03:00
parent 91672160c2
commit 5da0d407ab
3 changed files with 34 additions and 9 deletions
+4 -4
View File
@@ -92,7 +92,7 @@ jobs:
fail-fast: false
matrix:
include:
- os: macos-15
- os: macos-26
platform: mac-arm64
builder_args: --mac --arm64
- os: windows-2025
@@ -114,9 +114,9 @@ jobs:
node-version: "26"
cache: npm
# The mac icon is the Icon Composer document (build/icon.icon);
# compiling it needs actool ≥ 26 (Xcode 26+). macos-15 images carry
# several Xcodes with an older default — select the latest stable
# explicitly. No-op for the windows/linux matrix legs.
# compiling it needs both macOS 26+ and actool ≥ 26 (Xcode 26+).
# Xcode 26's asset compiler crashes on macOS 15's older frameworks.
# Select the latest stable Xcode; no-op for Windows/Linux.
- name: Select Xcode 26+ for actool (mac icon compile)
if: runner.os == 'macOS'
uses: maxim-lobanov/setup-xcode@v1
+3 -3
View File
@@ -4,7 +4,7 @@
| Platform | Build runner | Packages | In-app update |
|---|---|---|---|
| macOS, Apple Silicon | `macos-15` (arm64) | signed/notarized DMG + ZIP | ZIP via Squirrel.Mac |
| macOS, Apple Silicon | `macos-26` (arm64) | signed/notarized DMG + ZIP | ZIP via Squirrel.Mac |
| Windows, x64 | `windows-2025` | unsigned per-user NSIS installer | NSIS |
| Linux, x64 | `ubuntu-24.04` | AppImage + `.deb` | AppImage replacement; `.deb` through dpkg/apt with elevation if needed |
@@ -12,7 +12,7 @@ Windows signing is deliberately disabled for this first release. SmartScreen may
The custom app bar stays on all three platforms. macOS keeps its left-side traffic lights; Windows/Linux use Electron's native right-side Window Controls Overlay, with CSS space reserved from the overlay's actual geometry. Windows/Linux hide the native menu until Alt is pressed. macOS keeps its system menu.
Icons are per-platform (2026-10-13): macOS compiles the Icon Composer document `build/icon.icon` via actool into `Assets.car` (macOS 26 Liquid Glass) plus a fallback icns that `scripts/after-pack.cjs` replaces with the hand-packed full-res `build/icon.icns` (actool's caps at 256px); Windows embeds `build/icon.ico` and Linux uses `build/icon.png`, both generated from the flat design (`build/icon-flat.svg`) by `node scripts/make-icons.mjs`. Building the mac icon needs Xcode 26+ (`actool`) — `desktop:dist` injects `DEVELOPER_DIR` locally and the release workflow selects Xcode via setup-xcode. Linux's desktop identity is `learnharness.desktop`. Public package-maintainer metadata uses the owner's GitHub no-reply address; support goes through [GitHub Issues](https://github.com/amosblomqvist/learnharness/issues).
Icons are per-platform (2026-10-13): macOS compiles the Icon Composer document `build/icon.icon` via actool into `Assets.car` (macOS 26 Liquid Glass) plus a fallback icns that `scripts/after-pack.cjs` replaces with the hand-packed full-res `build/icon.icns` (actool's caps at 256px); Windows embeds `build/icon.ico` and Linux uses `build/icon.png`, both generated from the flat design (`build/icon-flat.svg`) by `node scripts/make-icons.mjs`. Building the mac icon needs **macOS 26+ and Xcode 26+** (`actool`): Xcode 26's asset compiler crashes on macOS 15 because its host frameworks lack required symbols. `desktop:dist` injects `DEVELOPER_DIR` locally; release packaging uses `macos-26` and selects Xcode via setup-xcode. The older-OS fallback icon remains included. Linux's desktop identity is `learnharness.desktop`. Public package-maintainer metadata uses the owner's GitHub no-reply address; support goes through [GitHub Issues](https://github.com/amosblomqvist/learnharness/issues).
## Build pipeline
@@ -42,7 +42,7 @@ npx electron-builder --linux --x64 --publish never
`.github/workflows/ci.yml` runs on main pushes and pull requests:
- Typecheck, full deterministic test suite, and lint on Ubuntu.
- Native renderer tests plus standalone build/boot-smoke on macOS, Windows, and Linux.
- Native renderer tests plus standalone build/boot-smoke on macOS (`macos-15`), Windows, and Linux. This macOS job does not compile the Icon Composer asset; only release packaging needs the newer runner.
`.github/workflows/release.yml` has two deliberately separate modes:
+27 -2
View File
@@ -115,10 +115,16 @@ describe("desktop packaging policy", () => {
});
describe("desktop release workflow", () => {
type Step = { if?: string; run?: string; env?: Record<string, string>; with?: { script: string } };
type Step = {
if?: string; run?: string; uses?: string; env?: Record<string, string>;
with?: { script?: string; "xcode-version"?: string };
};
type Workflow = {
concurrency: { group: string; "cancel-in-progress": boolean };
jobs: Record<string, { needs?: string | string[]; steps: Step[] }>;
jobs: Record<string, {
needs?: string | string[]; steps: Step[]; "runs-on"?: string;
strategy?: { matrix: { include: { os: string; platform: string; builder_args?: string }[] } };
}>;
};
const yaml = require("js-yaml") as { load(source: string): Workflow };
const workflow = yaml.load(readFileSync(new URL("../.github/workflows/release.yml", import.meta.url), "utf8"));
@@ -128,6 +134,7 @@ describe("desktop release workflow", () => {
const prepare = draft.steps[0];
assert.ok(prepare?.with);
const script = prepare.with.script;
assert.ok(script);
type Release = { id: number; tag_name: string; draft: boolean };
/** Execute the REAL action script with an in-memory GitHub API. */
@@ -176,6 +183,24 @@ describe("desktop release workflow", () => {
}
});
it("pairs Icon Composer packaging with macOS 26 and Xcode selection, retaining macOS 15 CI", () => {
const mac = packaging.strategy?.matrix.include.find(entry => entry.platform === "mac-arm64");
assert.equal(mac?.os, "macos-26", "actool's .icon compiler needs macOS 26 host frameworks, not just newer Xcode");
assert.equal(mac?.builder_args, "--mac --arm64");
assert.equal(packaging["runs-on"], "${{ matrix.os }}");
const xcodeIndex = packaging.steps.findIndex(step => step.uses === "maxim-lobanov/setup-xcode@v1");
assert.ok(xcodeIndex >= 0);
const xcode = packaging.steps[xcodeIndex];
assert.equal(xcode?.if, "runner.os == 'macOS'");
assert.equal(xcode?.with?.["xcode-version"], "latest-stable");
const buildIndex = packaging.steps.findIndex(step => step.run?.includes("electron-builder"));
assert.ok(buildIndex > xcodeIndex, "select Xcode before packaging compiles the icon");
const ci = yaml.load(readFileSync(new URL("../.github/workflows/ci.yml", import.meta.url), "utf8"));
const ciMac = ci.jobs.standalone?.strategy?.matrix.include.find(entry => entry.platform === "mac-arm64");
assert.equal(ciMac?.os, "macos-15", "ordinary CI keeps older macOS build/boot coverage");
});
it("creates exactly one unpublished draft when none matches", async () => {
assert.deepEqual(await prepareDraft([{ id: 9, tag_name: "v0.2.0", draft: false }]), [{
owner: "test-owner", repo: "test-repo", tag_name: "v0.3.0", name: "0.3.0", draft: true,