fix(launcher): address review feedback on setup and runtime cleanup

This commit is contained in:
2026-09-10 13:32:12 -07:00
parent 8c7ad75033
commit 962442ccd5
7 changed files with 126 additions and 35 deletions
+19 -15
View File
@@ -207,7 +207,7 @@ async function run(command, args, options = {}) {
});
}
async function downloadArchive(source, cacheDir, fetchImpl) {
export async function downloadArchive(source, cacheDir, fetchImpl) {
const archivePath = path.join(cacheDir, `${source.name}-${source.revision}.tar.gz`);
try {
if ((await sha256File(archivePath)) === source.sha256) return archivePath;
@@ -218,22 +218,26 @@ async function downloadArchive(source, cacheDir, fetchImpl) {
const url = `https://codeload.github.com/${source.repository}/tar.gz/${source.revision}`;
const temporaryPath = `${archivePath}.download-${randomUUID()}`;
const response = await fetchImpl(url);
if (!response.ok || !response.body)
throw new Error(`Unable to download ${url}: HTTP ${response.status}`);
await pipeline(
Readable.fromWeb(response.body),
createWriteStream(temporaryPath, { flags: 'wx' }),
);
const actualSha256 = await sha256File(temporaryPath);
if (actualSha256 !== source.sha256) {
await fs.rm(temporaryPath, { force: true });
throw new Error(
`Source checksum mismatch for ${source.name}: expected ${source.sha256}, received ${actualSha256}.`,
try {
const response = await fetchImpl(url);
if (!response.ok || !response.body)
throw new Error(`Unable to download ${url}: HTTP ${response.status}`);
await pipeline(
Readable.fromWeb(response.body),
createWriteStream(temporaryPath, { flags: 'wx' }),
);
const actualSha256 = await sha256File(temporaryPath);
if (actualSha256 !== source.sha256) {
throw new Error(
`Source checksum mismatch for ${source.name}: expected ${source.sha256}, received ${actualSha256}.`,
);
}
await fs.rename(temporaryPath, archivePath);
return archivePath;
} finally {
// Cleanup must not replace the original download, validation, or rename error.
await fs.rm(temporaryPath, { force: true }).catch(() => {});
}
await fs.rename(temporaryPath, archivePath);
return archivePath;
}
async function materializeArchive(source, root, cacheDir, fetchImpl) {
+28
View File
@@ -1,7 +1,10 @@
import { describe, expect, test } from 'bun:test';
import { createHash } from 'node:crypto';
import fs from 'node:fs/promises';
import os from 'node:os';
import path from 'node:path';
import {
downloadArchive,
parseRegisteredRepositories,
parseSourceManifest,
validateBunPins,
@@ -11,6 +14,31 @@ import {
const projectRoot = path.resolve(import.meta.dir, '..');
describe('Bun corresponding-source manifest', () => {
test('removes partial downloads when placing a valid archive fails', async () => {
const cacheDir = await fs.mkdtemp(path.join(os.tmpdir(), 'subminer-bun-source-test-'));
try {
const payload = new Uint8Array([1, 2, 3]);
const source = {
name: 'fixture',
repository: 'example/fixture',
revision: 'a'.repeat(40),
sha256: createHash('sha256').update(payload).digest('hex'),
};
const archivePath = path.join(cacheDir, `${source.name}-${source.revision}.tar.gz`);
await expect(
downloadArchive(source, cacheDir, async () => {
await fs.mkdir(archivePath);
return { ok: true, status: 200, body: new Response(payload).body };
}),
).rejects.toMatchObject({ syscall: 'rename' });
const entries = await fs.readdir(cacheDir);
expect(entries.filter((entry) => entry.includes('.download-'))).toEqual([]);
} finally {
await fs.rm(cacheDir, { recursive: true, force: true });
}
});
test('pins the source to the revision reported by the distributed binary', async () => {
const manifest = parseSourceManifest(
JSON.parse(