desktop-release: architecture guard before signing (tauri build --no-sign, guard, then tauri bundle signs / notarizes): every Mach-O in the app must contain arm64, nothing Intel-only, Info.plist must not force Intel (LSRequiresNativeExecution false, LSArchitecturePriority x86_64 first); guard repeated on the signed app, the DMG and the updater bundle, recorded in evidence.json

This commit is contained in:
CRTkafa 2026-10-13 02:06:53 +08:00
parent 9ba8471002
commit 12b01f065c
4 changed files with 82 additions and 7 deletions

View file

@ -3,7 +3,7 @@ import { describe, expect, test } from 'bun:test';
import { cpSync, mkdirSync, mkdtempSync, readFileSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { artifactName, developerIdIdentity, mergeManifest, pathFreeEnv, releaseUrl, remoteSessionReason, sliceProblems, sumsText, uploadable } from '../tools/desktop-release';
import { artifactName, developerIdIdentity, mergeManifest, pathFreeEnv, releaseUrl, remoteSessionReason, sliceProblems, sumsText, uploadable, archProblems } from '../tools/desktop-release';
import { COPIES, setVersion, versions } from '../tools/desktop-version';
describe('desktop version authority', () => {
@ -99,3 +99,27 @@ describe('universal slices (never run under Rosetta)', () => {
}
});
});
describe('architecture guard (no Rosetta notice for users)', () => {
const ok = [{ file: 'Contents/MacOS/storebound-desktop', lipo: 'x86_64 arm64\n' }, { file: 'Contents/MacOS/storebound', lipo: 'x86_64 arm64' }, { file: 'Contents/Frameworks/libx.dylib', lipo: 'arm64' }];
test('every Mach-O with arm64 and a neutral Info.plist pass', () => {
expect(archProblems(ok, { CFBundleIdentifier: 'dev.example' })).toEqual([]);
expect(archProblems(ok, { LSRequiresNativeExecution: true, LSArchitecturePriority: ['arm64', 'x86_64'] })).toEqual([]);
});
test('an Intel-only helper, a file without slices or one without arm64 fail', () => {
expect(archProblems([...ok, { file: 'Contents/Resources/helper', lipo: 'x86_64' }], {})).toEqual(['Contents/Resources/helper: no arm64 slice (x86_64, Intel-only: needs Rosetta)']);
expect(archProblems([{ file: 'a', lipo: '' }], {})).toEqual(['a: no arm64 slice (no slices)']);
expect(archProblems([{ file: 'b', lipo: 'x86_64h i386' }], {})).toEqual(['b: no arm64 slice (x86_64h i386, Intel-only: needs Rosetta)']);
});
test('an Info.plist that forces Intel fails', () => {
expect(archProblems(ok, { LSRequiresNativeExecution: false })).toEqual(['Info.plist: LSRequiresNativeExecution is false']);
expect(archProblems(ok, { LSArchitecturePriority: ['x86_64', 'arm64'] })).toEqual(['Info.plist: LSArchitecturePriority puts x86_64 first']);
});
test('the release runs the guard before signing (tauri build --no-sign, then tauri bundle)', () => {
const src = readFileSync(join(import.meta.dir, '..', 'tools/desktop-release.ts'), 'utf8');
const build = src.indexOf("'--no-sign'"), guard = src.indexOf("archGuard(join(targetDir"), bundle = src.indexOf("['tauri', 'bundle'");
expect(build).toBeGreaterThan(0);
expect(guard).toBeGreaterThan(build);
expect(bundle).toBeGreaterThan(guard);
});
});