feat(KAN-65): add signed macOS updates and DMG distribution
CI/CD / preconditions (pull_request) Successful in 2s
Native JavaScript checks / Native launcher and release tests (pull_request) Successful in 1m0s
CI/CD / test_and_check_app (pull_request) Successful in 7m2s
CI/CD / Publish immutable image (pull_request) Skipped
macOS checks / Native Rust tests and unsigned build (pull_request) Successful in 1m35s

This commit is contained in:
2026-09-27 21:40:23 +02:00
parent 3e5c316b8f
commit 20f9314659
28 changed files with 2454 additions and 130 deletions
+35 -7
View File
@@ -4,12 +4,14 @@ import {
mkdirSync,
mkdtempSync,
readFileSync,
symlinkSync,
rmSync,
writeFileSync,
} from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import test from 'node:test';
import { updateSource } from '../update-manifest.mjs';
import {
packageNotarizedApp,
releaseMetadata,
@@ -22,6 +24,8 @@ function fixture(t) {
mkdirSync(join(directory, 'src-tauri'));
const config = {
version: '1.2.3',
plugins: { updater: { pubkey: updateSource.publicKey } },
bundle: { macOS: { bundleVersion: '123' } },
productName: 'KanbanGit',
identifier: 'de.kanbangit.desktop',
};
@@ -31,7 +35,7 @@ function fixture(t) {
);
writeFileSync(
join(directory, 'package.json'),
JSON.stringify({ version: '1.2.3' }),
JSON.stringify({ version: '1.2.3', releaseNotes: 'Release notes' }),
);
writeFileSync(
join(directory, 'src-tauri/Cargo.toml'),
@@ -42,6 +46,21 @@ function fixture(t) {
APPLE_SIGNING_IDENTITY:
'Developer ID Application: Example (ZZX2KZGTHU)',
};
const bundle = join(
directory,
'src-tauri/target/aarch64-apple-darwin/release/bundle',
);
mkdirSync(join(bundle, 'dmg'), { recursive: true });
mkdirSync(join(bundle, 'macos'), { recursive: true });
writeFileSync(
join(bundle, 'dmg/KanbanGit_1.2.3_aarch64.dmg'),
'notarized archive',
);
writeFileSync(
join(bundle, 'macos/KanbanGit.app.tar.gz'),
'notarized updater',
);
writeFileSync(join(bundle, 'macos/KanbanGit.app.tar.gz.sig'), 'signature');
const calls = [];
const run = (command, args) => {
calls.push({ command, args });
@@ -54,8 +73,8 @@ function fixture(t) {
if (command === 'lipo') {
return 'arm64';
}
if (command === 'ditto' && args.includes('-c')) {
writeFileSync(args.at(-1), 'notarized archive');
if (command === 'hdiutil' && args[0] === 'attach') {
symlinkSync('/Applications', join(args.at(-1), 'Applications'));
}
return '';
};
@@ -66,9 +85,11 @@ test('release metadata binds tag, all app versions, identity and architecture',
const f = fixture(t);
assert.deepEqual(releaseMetadata(f.env, f.directory), {
version: '1.2.3',
notes: 'Release notes',
target: 'aarch64-apple-darwin',
arch: 'arm64',
filename: 'KanbanGit-1.2.3-aarch64-apple-darwin.zip',
filename: 'KanbanGit-1.2.3-aarch64-apple-darwin.dmg',
updaterFilename: 'KanbanGit-1.2.3-aarch64-apple-darwin.app.tar.gz',
});
assert.equal(
releaseMetadata(
@@ -127,8 +148,8 @@ test('packages only a verified app and checks the extracted download again', (t)
),
`${output.checksum} ${output.filename}\n`,
);
assert.equal(f.calls.filter((call) => call.command === 'spctl').length, 2);
assert.equal(f.calls.filter((call) => call.command === 'xcrun').length, 2);
assert.equal(f.calls.filter((call) => call.command === 'spctl').length, 3);
assert.equal(f.calls.filter((call) => call.command === 'xcrun').length, 3);
assert.match(
f.calls.findLast((call) => call.command === 'spctl').args.at(-1),
/extracted\/KanbanGit\.app$/,
@@ -136,7 +157,14 @@ test('packages only a verified app and checks the extracted download again', (t)
assert.throws(() => packageNotarizedApp(f), /already exists/);
});
for (const failure of ['codesign', 'lipo', 'xcrun', 'spctl', 'ditto']) {
for (const failure of [
'codesign',
'lipo',
'xcrun',
'spctl',
'hdiutil',
'tar',
]) {
test(`does not publish local output when ${failure} verification fails`, (t) => {
const f = fixture(t);
const run = f.run;