Files
hermes-agent/apps/desktop/scripts/mac-icon.cjs
ethernet 26960b1afb build(desktop): compile the layered icon to Assets.car on macOS 26 hosts
electron-builder (app-builder-lib 27.0.0-alpha.6) compiles a `.icon` given as
`mac.icon` into Contents/Resources/Assets.car and sets CFBundleIconName, but
only with actool >= 26 (Xcode 26); on any other host it throws and the build
fails. scripts/mac-icon.cjs picks the Icon Composer package when that actool
is present and the legacy .icns otherwise, so a dev Mac on Xcode 16 keeps
building the app it always built.

The release runners keep Xcode 16.4 selected although 26.x is installed, so
the darwin job now selects the newest Xcode 26 via DEVELOPER_DIR and asserts
the actool version before packaging; a silent fallback cannot ship from CI.

electron-builder's `.icon` path also replaces the bundled icon.icns with
actool's 256px fallback. macOS <= 15 shows that file, so after-pack restores
our 16..1024 assets/icon.icns.
2026-10-02 10:33:43 -04:00

87 lines
3.2 KiB
JavaScript

// Which macOS icon resource electron-builder packages, decided per build host.
//
// `assets/icon.icon` is the Icon Composer package for macOS 26 (the system
// masks its layers itself, so the border ring follows the real outline).
// electron-builder compiles it to `Contents/Resources/Assets.car` with
// actool, which exists only in Xcode >= 26; on any other host it throws and
// the whole build fails. Choosing at config time keeps a dev Mac on Xcode
// 16 building the .icns-only app it always built, while release CI selects
// Xcode 26 (`DEVELOPER_DIR`) and asserts the version before packaging so a
// silent fallback can never ship from there.
//
// Whichever resource is chosen, after-pack.mjs restores `assets/icon.icns`
// as the bundle's legacy icon: electron-builder's own `.icon` path replaces
// it with actool's 256px fallback, and macOS <= 15 shows the .icns.
// @ts-check
'use strict'
const path = require('node:path')
const { spawnSync } = require('node:child_process')
const ICON_COMPOSER = 'assets/icon.icon'
const LEGACY_ICNS = 'assets/icon.icns'
/**
* The actool short version from `actool --version` plist output, or null
* when actool is missing or answers with something else.
* @param {string} output
* @returns {string | null}
*/
function parseActoolVersion(output) {
const match = /<key>short-bundle-version<\/key>\s*<string>([^<]+)<\/string>/.exec(output)
return match ? match[1].trim() : null
}
/**
* @param {string | null} version
* @returns {boolean}
*/
function actoolSupportsIconComposer(version) {
if (!version) return false
const major = Number.parseInt(version.split('.')[0], 10)
return Number.isInteger(major) && major >= 26
}
/**
* @param {(command: string, args: string[]) => { status: number | null, stdout?: string | Buffer | null, stderr?: string | Buffer | null }} run
* @returns {string | null}
*/
function installedActoolVersion(run = (command, args) => spawnSync(command, args, { encoding: 'utf8' })) {
try {
const result = run('actool', ['--version'])
if (result.status !== 0) return null
return parseActoolVersion(`${result.stdout ?? ''}${result.stderr ?? ''}`)
} catch {
return null
}
}
/**
* The `mac.icon` value for this host: the Icon Composer package when actool
* can compile it, otherwise the legacy .icns alone.
* @param {string} appDir the apps/desktop directory
* @param {{ platform?: string, actoolVersion?: string | null, log?: (message: string) => void }} [options]
* @returns {string}
*/
function macIconResource(appDir, options = {}) {
const platform = options.platform ?? process.platform
if (platform !== 'darwin') return LEGACY_ICNS
const version = options.actoolVersion === undefined ? installedActoolVersion() : options.actoolVersion
if (actoolSupportsIconComposer(version)) return ICON_COMPOSER
const log = options.log ?? (message => console.warn(message))
log(
`[mac-icon] actool ${version ?? 'not found'}: packaging ${LEGACY_ICNS} only; ` +
`the macOS 26 layered icon (${path.join(appDir, ICON_COMPOSER)}) needs Xcode 26 or newer`
)
return LEGACY_ICNS
}
module.exports = {
ICON_COMPOSER,
LEGACY_ICNS,
actoolSupportsIconComposer,
installedActoolVersion,
macIconResource,
parseActoolVersion
}