feat(seeder): arch-aware macOS .pkg selection for auto-update
The seeder update checker treated a macOS .pkg as arch-universal and returned the first .pkg it saw. But build-macos-remote.sh emits BOTH `PearCalSeeder-<v>-arm64.pkg` and `-x64.pkg`, so a release carrying both would hand ~half of x64 Macs the arm64 build (or vice versa). Make selectAsset match the running arch for darwin the same way it already does for Linux: prefer an arch-matching .pkg, fall back to an arch-universal (un-suffixed) .pkg, and never return the wrong arch's suffixed build — a wrong-arch install is worse than none. This is the last correctness gap before wiring the seeder installers into the release flow so self-managed installs can actually self-update. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018hccVdQLiqJjE35qhUs3Rb
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
d655ab0a13
commit
38352bc8fb
@@ -55,14 +55,21 @@ function archTokens (arch) {
|
||||
return arch ? [String(arch).toLowerCase()] : []
|
||||
}
|
||||
|
||||
// Every arch token that may appear in a seeder installer name, across all
|
||||
// platforms. Used to tell an arch-suffixed asset from an arch-universal one.
|
||||
const ALL_ARCH_TOKENS = ['x86_64', 'amd64', 'x64', 'aarch64', 'arm64']
|
||||
|
||||
// Pick this platform+arch's installer asset from a GitHub release's `assets`
|
||||
// array. platform is process.platform ('darwin' | 'win32' | 'linux'), arch is
|
||||
// process.arch ('x64' | 'arm64'). macOS `.pkg` and Windows `.exe` ship as a
|
||||
// single (universal) asset, so arch is not required there. On Linux the
|
||||
// process.arch ('x64' | 'arm64'). Windows `.exe` ships as a single (universal)
|
||||
// asset, so arch is not required there. macOS `.pkg` and Linux `.AppImage`/`.deb`
|
||||
// are built per-arch (the .pkg name carries `-arm64`/`-x64`), so we match the
|
||||
// running arch and never hand back a wrong-arch binary — a wrong-arch install is
|
||||
// worse than none, so we fall back to null. A macOS `.pkg` with no arch token in
|
||||
// its name is treated as arch-universal (a single fat build). On Linux the
|
||||
// `installKind` hint decides which artifact a running seeder gets so the apply
|
||||
// path matches how it was installed. Only an arch-matching asset is returned - a
|
||||
// wrong-arch binary is worse than none, so we fall back to null. .sha256
|
||||
// sidecars are never returned as the primary asset.
|
||||
// path matches how it was installed. .sha256 sidecars are never returned as the
|
||||
// primary asset.
|
||||
function selectAsset (assets, platform, arch, installKind) {
|
||||
if (!Array.isArray(assets)) return null
|
||||
// Match ONLY seeder-named assets. Every seeder installer carries "seeder" in
|
||||
@@ -76,11 +83,17 @@ function selectAsset (assets, platform, arch, installKind) {
|
||||
!a.name.endsWith('.sha256') && a.name.toLowerCase().includes('seeder'))
|
||||
const lower = (a) => a.name.toLowerCase()
|
||||
const bySuffix = (suffix) => named.filter((a) => lower(a).endsWith(suffix))
|
||||
if (platform === 'darwin') return bySuffix('.pkg')[0] || null
|
||||
const toks = archTokens(arch)
|
||||
const matchArch = (list) => list.find((a) => toks.some((t) => lower(a).includes(t))) || null
|
||||
if (platform === 'darwin') {
|
||||
const pkgs = bySuffix('.pkg')
|
||||
// Prefer an arch-matching build; otherwise accept an arch-universal pkg (no
|
||||
// arch token in the name). Never return the wrong arch's suffixed pkg.
|
||||
const universal = pkgs.find((a) => !ALL_ARCH_TOKENS.some((t) => lower(a).includes(t))) || null
|
||||
return matchArch(pkgs) || universal
|
||||
}
|
||||
if (platform === 'win32') return bySuffix('.exe')[0] || null
|
||||
if (platform === 'linux') {
|
||||
const toks = archTokens(arch)
|
||||
const matchArch = (list) => list.find((a) => toks.some((t) => lower(a).includes(t))) || null
|
||||
const appimage = () => matchArch(bySuffix('.appimage'))
|
||||
const deb = () => matchArch(bySuffix('.deb'))
|
||||
return installKind === 'deb'
|
||||
|
||||
@@ -48,12 +48,32 @@ const ASSETS = [
|
||||
{ name: 'pearcal-v1.0.10.apk', browser_download_url: 'u/apk' }, // mobile, never picked
|
||||
]
|
||||
|
||||
test('selectAsset: macOS pkg + Windows exe are arch-universal', () => {
|
||||
test('selectAsset: an arch-universal macOS pkg + Windows exe serve any arch', () => {
|
||||
// An un-suffixed .pkg (a single fat build) is offered to both Mac arches.
|
||||
assert.equal(selectAsset(ASSETS, 'darwin', 'x64').browser_download_url, 'u/pkg')
|
||||
assert.equal(selectAsset(ASSETS, 'darwin', 'arm64').browser_download_url, 'u/pkg')
|
||||
assert.equal(selectAsset(ASSETS, 'win32', 'x64').browser_download_url, 'u/exe')
|
||||
})
|
||||
|
||||
test('selectAsset: arch-suffixed macOS pkgs match the running arch, never cross', () => {
|
||||
// The real build (build-macos-remote.sh) emits BOTH -arm64.pkg and -x64.pkg.
|
||||
// Each Mac must get its own arch; a wrong-arch .pkg is worse than none.
|
||||
const pkgs = [
|
||||
{ name: 'PearCalSeeder-1.0.34-arm64.pkg', browser_download_url: 'u/pkg-arm64' },
|
||||
{ name: 'PearCalSeeder-1.0.34-arm64.pkg.sha256', browser_download_url: 'u/pkg-arm64.sha' },
|
||||
{ name: 'PearCalSeeder-1.0.34-x64.pkg', browser_download_url: 'u/pkg-x64' },
|
||||
{ name: 'PearCalSeeder-1.0.34-x64.pkg.sha256', browser_download_url: 'u/pkg-x64.sha' },
|
||||
]
|
||||
assert.equal(selectAsset(pkgs, 'darwin', 'arm64').browser_download_url, 'u/pkg-arm64')
|
||||
assert.equal(selectAsset(pkgs, 'darwin', 'x64').browser_download_url, 'u/pkg-x64')
|
||||
// Only the arm64 pkg present → an x64 Mac gets nothing, not the arm64 build.
|
||||
const armOnly = pkgs.filter((a) => a.name.includes('arm64'))
|
||||
assert.equal(selectAsset(armOnly, 'darwin', 'x64'), null)
|
||||
// sha256 sidecar resolves for the arch-matched pkg.
|
||||
const chosen = selectAsset(pkgs, 'darwin', 'x64')
|
||||
assert.equal(selectSha256For(pkgs, chosen.name).browser_download_url, 'u/pkg-x64.sha')
|
||||
})
|
||||
|
||||
test('selectAsset: linux prefers the ARCH-matching AppImage, then deb', () => {
|
||||
assert.equal(selectAsset(ASSETS, 'linux', 'x64').browser_download_url, 'u/app-x64')
|
||||
assert.equal(selectAsset(ASSETS, 'linux', 'arm64').browser_download_url, 'u/app-arm64')
|
||||
|
||||
Reference in New Issue
Block a user