add auto-updater

This commit is contained in:
2026-09-08 17:33:55 +00:00
parent d1793cc11b
commit 8763226ccd
12 changed files with 685 additions and 29 deletions
+96
View File
@@ -0,0 +1,96 @@
import path from "node:path";
import { describe, expect, test } from "vitest";
import {
GitUpdateService,
type CommandResult,
type CommandRunner,
} from "../../src/updater/git-update-service.js";
function result(stdout = "", code = 0, stderr = ""): CommandResult {
return { code, stdout, stderr };
}
function fakeRunner(responses: Map<string, CommandResult>): CommandRunner {
return async (command, args) =>
responses.get(`${command} ${args.join(" ")}`) ??
result("", 1, "unexpected command");
}
describe("GitUpdateService", () => {
test("checks the currently checked-out branch", async () => {
const root = path.resolve(".");
const runner = fakeRunner(
new Map([
["git branch --show-current", result("feature/demo\n")],
[
"git fetch --quiet origin +refs/heads/feature/demo:refs/remotes/origin/feature/demo",
result(),
],
["git rev-parse HEAD", result("aaa\n")],
["git rev-parse refs/remotes/origin/feature/demo", result("bbb\n")],
["git status --porcelain", result()],
["git merge-base --is-ancestor aaa bbb", result()],
["git log -1 --format=%s bbb", result("Useful update\n")],
]),
);
const update = await new GitUpdateService(root, runner).check();
expect(update).toMatchObject({
status: "available",
branch: "feature/demo",
currentCommit: "aaa",
targetCommit: "bbb",
canApply: true,
});
});
test("reports an available update but blocks applying over local changes", async () => {
const root = path.resolve(".");
const runner = fakeRunner(
new Map([
["git branch --show-current", result("main\n")],
[
"git fetch --quiet origin +refs/heads/main:refs/remotes/origin/main",
result(),
],
["git rev-parse HEAD", result("aaa\n")],
["git rev-parse refs/remotes/origin/main", result("bbb\n")],
["git status --porcelain", result(" M src/example.ts\n")],
["git merge-base --is-ancestor aaa bbb", result()],
["git log -1 --format=%s bbb", result("Update\n")],
]),
);
const update = await new GitUpdateService(root, runner).check();
expect(update).toMatchObject({ status: "available", canApply: false });
});
test("does not replace commits when the local branch is ahead", async () => {
const root = path.resolve(".");
const runner = fakeRunner(
new Map([
["git branch --show-current", result("main\n")],
[
"git fetch --quiet origin +refs/heads/main:refs/remotes/origin/main",
result(),
],
["git rev-parse HEAD", result("bbb\n")],
["git rev-parse refs/remotes/origin/main", result("aaa\n")],
["git status --porcelain", result()],
["git merge-base --is-ancestor bbb aaa", result("", 1)],
["git merge-base --is-ancestor aaa bbb", result()],
]),
);
await expect(
new GitUpdateService(root, runner).check(),
).resolves.toMatchObject({
status: "up-to-date",
branch: "main",
currentCommit: "bbb",
});
});
});