fix(anime): restore bridge install after validation failure

- Preserve the previous install when post-swap validation fails
- Cover rollback behavior with an installer regression test
This commit is contained in:
2026-09-02 01:06:08 -07:00
parent 835a09fa67
commit 9a1b6e650f
2 changed files with 43 additions and 3 deletions
@@ -1,6 +1,6 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { access, mkdir, mkdtemp, readdir, rename, writeFile } from 'node:fs/promises';
import { access, mkdir, mkdtemp, readdir, rename, rm, writeFile } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import path from 'node:path';
import {
@@ -288,3 +288,28 @@ test('stageBridgeUpdate commit restores the existing install when activation fai
[],
);
});
test('stageBridgeUpdate commit restores the existing install when post-swap validation fails', async () => {
const root = await tempRoot();
const managed = path.join(root, 'managed');
await writeBundle(managed, 'MExtensionServer-v1.0.5.0.jar');
await writeBundleMarker(managed, 'v1.0.5.0');
const { options } = fakeUpstream();
const staged = await stageBridgeUpdate({ ...options, installDir: managed, systemDirs: [] });
const stagedJar = path.join(`${managed}.next`, `MExtensionServer-${LATEST}.jar`);
await rm(stagedJar);
await assert.rejects(
staged.commit(),
/The updated anime bridge is missing its java runtime or jar/,
);
assert.ok(await exists(path.join(managed, 'MExtensionServer-v1.0.5.0.jar')));
assert.equal(await readBundleMarker(managed), 'v1.0.5.0');
assert.ok(await exists(`${managed}.next`));
assert.ok(!(await exists(stagedJar)));
assert.deepEqual(
(await readdir(root)).filter((entry) => entry.startsWith('managed.backup-')),
[],
);
});
+17 -2
View File
@@ -324,8 +324,23 @@ export async function stageBridgeUpdate(options: EnsureBridgeOptions): Promise<S
}
const binaries = await findBundleBinaries(options.installDir);
if (!binaries)
throw new Error('The updated anime bridge is missing its java runtime or jar.');
if (!binaries) {
const validationError = new Error(
'The updated anime bridge is missing its java runtime or jar.',
);
try {
await renameImpl(options.installDir, stagingDir);
await renameImpl(backupDir, options.installDir);
backupHoldsInstall = false;
} catch (restoreError) {
throw new AggregateError(
[validationError, restoreError],
`The updated anime bridge failed validation and the previous install could not be ` +
`restored. The previous install remains at ${backupDir}.`,
);
}
throw validationError;
}
await rm(backupRoot, { recursive: true, force: true });
backupHoldsInstall = false;
return describeInstall(binaries, options.installDir, 'managed');