feat: add stable macOS app download to website
CI/CD / preconditions (pull_request) Successful in 1s
Native JavaScript checks / Native launcher and release tests (pull_request) Successful in 1m3s
macOS checks / Native Rust tests and unsigned build (pull_request) Successful in 1m41s
CI/CD / test_and_check_app (pull_request) Successful in 6m53s
CI/CD / Publish immutable image (pull_request) Skipped

This commit is contained in:
2026-09-28 09:12:37 +02:00
parent aece653970
commit d18fffe665
12 changed files with 310 additions and 31 deletions
+114 -9
View File
@@ -168,9 +168,9 @@ test('uploads revalidated files and writes completion record after public verifi
);
assert.equal(
f.events.filter((event) => event.operation === 'put-object').length,
8,
9,
);
assert.equal(f.objects.size, 8);
assert.equal(f.objects.size, 9);
for (const event of f.events.filter(
(event) => event.operation === 'put-object',
)) {
@@ -184,6 +184,26 @@ test('uploads revalidated files and writes completion record after public verifi
f.events.at(-1).key,
'releases/macos/aarch64-apple-darwin/latest.json',
);
const downloadKey = 'releases/macos/aarch64-apple-darwin/KanbanGit.dmg';
assert.deepEqual(
f.objects.get(downloadKey),
readFileSync(join(f.directory, f.metadata.filename)),
);
const downloadPut = f.events.findIndex(
(event) =>
event.operation === 'put-object' && event.key === downloadKey,
);
const downloadVerified = f.events.findIndex(
(event) =>
event.operation === 'public-get' && event.key === downloadKey,
);
const feedPut = f.events.findIndex(
(event) =>
event.operation === 'put-object' &&
event.key.endsWith('/latest.json'),
);
assert(downloadPut > 0 && downloadVerified > downloadPut);
assert(feedPut > downloadVerified);
assert.deepEqual(
JSON.parse(
f.objects.get(
@@ -202,6 +222,91 @@ test('uploads revalidated files and writes completion record after public verifi
);
});
test('replaces the stable download only for a newer verified release', async (t) => {
const f = fixture(t);
const downloadKey = `releases/macos/${f.metadata.target}/KanbanGit.dmg`;
const latestKey = `releases/macos/${f.metadata.target}/latest.json`;
f.objects.set(downloadKey, Buffer.from('previous download'));
f.objects.set(latestKey, Buffer.from(JSON.stringify({ version: '1.0.0' })));
await publishMacos(f);
assert.deepEqual(
f.objects.get(downloadKey),
readFileSync(join(f.directory, f.metadata.filename)),
);
assert.equal(JSON.parse(f.objects.get(latestKey)).version, '1.2.3');
const replacement = f.events.find(
(event) =>
event.operation === 'put-object' &&
event.key === downloadKey &&
event.args.includes('--if-match'),
);
assert(replacement);
});
test('a newer published version protects the stable download even when its alias is missing', async (t) => {
const f = fixture(t);
const downloadKey = `releases/macos/${f.metadata.target}/KanbanGit.dmg`;
const latestKey = `releases/macos/${f.metadata.target}/latest.json`;
f.objects.set(latestKey, Buffer.from(JSON.stringify({ version: '2.0.0' })));
await assert.rejects(publishMacos(f), /downgrade/);
assert.equal(f.objects.has(downloadKey), false);
assert.equal(JSON.parse(f.objects.get(latestKey)).version, '2.0.0');
});
test('a published version cannot replace a different stable download', async (t) => {
const f = fixture(t);
const downloadKey = `releases/macos/${f.metadata.target}/KanbanGit.dmg`;
const latestKey = `releases/macos/${f.metadata.target}/latest.json`;
const original = Buffer.from('published download');
f.objects.set(downloadKey, original);
f.objects.set(latestKey, Buffer.from(JSON.stringify({ version: '1.2.3' })));
await assert.rejects(publishMacos(f), /bump the app version/);
assert.deepEqual(f.objects.get(downloadKey), original);
});
test('a failed public stable download blocks feed promotion', async (t) => {
const f = fixture(t);
const fetchPublic = f.fetchPublic;
f.fetchPublic = (url, options) =>
url.endsWith('/KanbanGit.dmg')
? Promise.resolve({ ok: false, status: 403 })
: fetchPublic(url, options);
await assert.rejects(publishMacos(f), /KanbanGit\.dmg: HTTP 403/);
assert.equal(
f.objects.has(`releases/macos/${f.metadata.target}/latest.json`),
false,
);
});
test('concurrent stable download replacement preserves the winner', async (t) => {
const f = fixture(t);
const downloadKey = `releases/macos/${f.metadata.target}/KanbanGit.dmg`;
const latestKey = `releases/macos/${f.metadata.target}/latest.json`;
f.objects.set(downloadKey, Buffer.from('previous download'));
f.objects.set(latestKey, Buffer.from(JSON.stringify({ version: '1.0.0' })));
const run = f.run;
f.run = (command, args) => {
if (args.includes('--if-match') && args.includes(downloadKey)) {
f.objects.set(downloadKey, Buffer.from('concurrent winner'));
throw new Error('(PreconditionFailed) concurrent replacement');
}
return run(command, args);
};
await assert.rejects(publishMacos(f), /concurrent replacement/);
assert.equal(f.objects.get(downloadKey).toString(), 'concurrent winner');
assert.equal(JSON.parse(f.objects.get(latestKey)).version, '1.0.0');
});
for (const response of ['', ' \n\t', '{}']) {
test(`unversioned bucket response ${JSON.stringify(response)} permits a verified upload`, async (t) => {
const f = fixture(t);
@@ -211,7 +316,7 @@ for (const response of ['', ' \n\t', '{}']) {
return args.includes('get-bucket-versioning') ? response : result;
};
await publishMacos(f);
assert.equal(f.objects.size, 8);
assert.equal(f.objects.size, 9);
assert.equal(f.events.at(-1).operation, 'public-get');
assert(f.events.at(-1).key.endsWith('/latest.json'));
});
@@ -237,7 +342,7 @@ test('retry accepts byte-identical objects without replacing them', async (t) =>
assert.deepEqual([...f.objects.entries()], originals);
assert.equal(
f.events.filter((event) => event.operation === 'get-object').length,
8,
11,
);
});
@@ -325,7 +430,7 @@ test('a partial upload can be retried with the same files', async (t) => {
assert.equal(f.objects.size, 3);
f.run = run;
await publishMacos(f);
assert.equal(f.objects.size, 8);
assert.equal(f.objects.size, 9);
});
function rebuild(f) {
@@ -387,7 +492,7 @@ for (const previous of [null, '1.0.0']) {
return result;
};
await publishMacos(f);
assert.equal(f.objects.size, 8);
assert.equal(f.objects.size, 9);
assert.equal(JSON.parse(f.objects.get(latestKey)).version, '1.2.3');
assert.equal(
JSON.parse(f.objects.get(latestKey)).notes,
@@ -516,7 +621,7 @@ for (const listing of [
!('Contents' in listing)
) {
await publishMacos(f);
assert.equal(f.objects.size, 8);
assert.equal(f.objects.size, 9);
} else {
await assert.rejects(publishMacos(f), /NoneType|AccessDenied/);
assert.equal(f.objects.get(key).toString(), 'unfinished old build');
@@ -549,7 +654,7 @@ for (const status of [403, 404, 408, 429, 500, 502, 503, 504, 'network']) {
await publishMacos(f);
assert.deepEqual(waits, [2000, 2000]);
assert.equal(cancelled, status === 'network' ? 0 : 2);
assert.equal(f.objects.size, 8);
assert.equal(f.objects.size, 9);
assert(f.events.at(-1).key.endsWith('/latest.json'));
});
}
@@ -593,7 +698,7 @@ test('checksum access retries finish before publishing the completion record', a
};
await publishMacos(f);
assert.equal(failures, 3);
assert.equal(f.objects.size, 8);
assert.equal(f.objects.size, 9);
});
for (const overrides of [