Skip to content

Commit 4ec0f68

Browse files
committed
fix(update): sync skills after binary upgrades
1 parent 9ae5dc9 commit 4ec0f68

4 files changed

Lines changed: 138 additions & 9 deletions

File tree

packages/commands/src/commands/update.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ export default defineCommand({
142142
`\n${color.green(`\u2713 Update complete: ${currentVersion} \u2192 ${newVer}`)}\n`,
143143
);
144144
writeUpdateState(newVer);
145+
updateAgentSkill(color);
145146
} catch (error) {
146147
const message = error instanceof Error ? error.message : String(error);
147148
const reinstall =
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import { mkdtempSync, rmSync } from "node:fs";
2+
import { tmpdir } from "node:os";
3+
import { join } from "node:path";
4+
import { afterEach, beforeEach, expect, test, vi } from "vite-plus/test";
5+
6+
const runtimeMocks = vi.hoisted(() => ({
7+
performBinaryUpdate: vi.fn(),
8+
}));
9+
10+
const childProcessMocks = vi.hoisted(() => ({
11+
execSync: vi.fn(),
12+
}));
13+
14+
vi.mock("bailian-cli-runtime", async (importOriginal) => {
15+
const actual = await importOriginal<typeof import("bailian-cli-runtime")>();
16+
return { ...actual, performBinaryUpdate: runtimeMocks.performBinaryUpdate };
17+
});
18+
19+
vi.mock("child_process", async (importOriginal) => {
20+
const actual = await importOriginal<typeof import("child_process")>();
21+
return { ...actual, execSync: childProcessMocks.execSync };
22+
});
23+
24+
import updateCommand from "../src/commands/update.ts";
25+
26+
let configDir: string;
27+
let previousConfigDir: string | undefined;
28+
let previousInstallMethod: string | undefined;
29+
30+
beforeEach(() => {
31+
configDir = mkdtempSync(join(tmpdir(), "bl-update-binary-"));
32+
previousConfigDir = process.env.BAILIAN_CONFIG_DIR;
33+
previousInstallMethod = process.env.BAILIAN_INSTALL_METHOD;
34+
process.env.BAILIAN_CONFIG_DIR = configDir;
35+
process.env.BAILIAN_INSTALL_METHOD = "binary";
36+
runtimeMocks.performBinaryUpdate.mockResolvedValue("1.15.0");
37+
});
38+
39+
afterEach(() => {
40+
if (previousConfigDir === undefined) delete process.env.BAILIAN_CONFIG_DIR;
41+
else process.env.BAILIAN_CONFIG_DIR = previousConfigDir;
42+
if (previousInstallMethod === undefined) delete process.env.BAILIAN_INSTALL_METHOD;
43+
else process.env.BAILIAN_INSTALL_METHOD = previousInstallMethod;
44+
rmSync(configDir, { recursive: true, force: true });
45+
vi.clearAllMocks();
46+
});
47+
48+
test("binary bl update syncs bailian skills after the CLI update succeeds", async () => {
49+
await updateCommand.run({
50+
identity: {
51+
binName: "bl",
52+
clientName: "bailian-cli",
53+
npmPackage: "bailian-cli",
54+
version: "1.14.3",
55+
},
56+
flags: { to: "1.15.0" },
57+
settings: {},
58+
} as never);
59+
60+
expect(runtimeMocks.performBinaryUpdate).toHaveBeenCalledWith("1.15.0");
61+
expect(childProcessMocks.execSync).toHaveBeenCalledWith("bl skill init", { stdio: "inherit" });
62+
});

packages/runtime/src/utils/update-checker.ts

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,25 @@ function errorMessage(err: unknown): string {
209209
return String(err);
210210
}
211211

212+
async function syncAgentSkillsAfterUpdate(
213+
dim: string,
214+
green: string,
215+
yellow: string,
216+
reset: string,
217+
): Promise<void> {
218+
try {
219+
process.stderr.write(` ${dim}Syncing agent skill...${reset}\n`);
220+
const { execSync } = await import("child_process");
221+
execSync("bl skill init", { stdio: "inherit" });
222+
process.stderr.write(` ${green}\u2713 Agent skill updated.${reset}\n\n`);
223+
} catch (error) {
224+
process.stderr.write(
225+
` ${yellow}\u26a0 Agent skill sync failed: ${errorMessage(error)}${reset}\n`,
226+
);
227+
process.stderr.write(` ${yellow} Run manually: bl skill init${reset}\n\n`);
228+
}
229+
}
230+
212231
/**
213232
* Perform auto-update for npm or binary installs.
214233
* Returns true if update succeeded, false otherwise.
@@ -256,6 +275,7 @@ export async function performAutoUpdate(
256275
writeState({ lastChecked: Date.now(), latestVersion: newVer });
257276
process.stderr.write(` ${green}✓ Update complete: ${currentVersion}${newVer}${reset}\n`);
258277
process.stderr.write(` ${dim}Run ${cyan}bl --version${reset}${dim} to verify.${reset}\n\n`);
278+
await syncAgentSkillsAfterUpdate(dim, green, yellow, reset);
259279
pendingNotification = null;
260280
return true;
261281
} catch (err) {
@@ -297,14 +317,7 @@ export async function performAutoUpdate(
297317
);
298318
process.stderr.write(` ${dim}Run ${cyan}bl --version${reset}${dim} to verify.${reset}\n\n`);
299319

300-
try {
301-
process.stderr.write(` ${dim}Syncing agent skill...${reset}\n`);
302-
execSync(`bl skill init`, { stdio: "inherit" });
303-
process.stderr.write(` ${green}✓ Agent skill updated.${reset}\n\n`);
304-
} catch (err) {
305-
process.stderr.write(` ${yellow}⚠ Agent skill sync failed: ${errorMessage(err)}${reset}\n`);
306-
process.stderr.write(` ${yellow} Run manually: bl skill init${reset}\n\n`);
307-
}
320+
await syncAgentSkillsAfterUpdate(dim, green, yellow, reset);
308321

309322
pendingNotification = null;
310323
return true;

packages/runtime/tests/update-checker.test.ts

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,58 @@
1-
import { expect, test } from "vite-plus/test";
1+
import { mkdtempSync, rmSync } from "node:fs";
2+
import { tmpdir } from "node:os";
3+
import { join } from "node:path";
4+
import { afterEach, beforeEach, expect, test, vi } from "vite-plus/test";
5+
6+
const binaryUpdateMocks = vi.hoisted(() => ({
7+
performBinaryUpdate: vi.fn(),
8+
}));
9+
10+
const childProcessMocks = vi.hoisted(() => ({
11+
execSync: vi.fn(),
12+
}));
13+
14+
vi.mock("../src/utils/binary-update.ts", async (importOriginal) => {
15+
const actual = await importOriginal<typeof import("../src/utils/binary-update.ts")>();
16+
return { ...actual, performBinaryUpdate: binaryUpdateMocks.performBinaryUpdate };
17+
});
18+
19+
vi.mock("child_process", async (importOriginal) => {
20+
const actual = await importOriginal<typeof import("child_process")>();
21+
return { ...actual, execSync: childProcessMocks.execSync };
22+
});
23+
224
import {
325
compareVersion,
426
isMajorUpgrade,
527
isNewerVersion,
628
isPrerelease,
729
parseVersion,
30+
performAutoUpdate,
831
shouldAutoUpdate,
932
} from "../src/utils/update-checker.ts";
1033

34+
let configDir: string;
35+
let previousConfigDir: string | undefined;
36+
let previousInstallMethod: string | undefined;
37+
38+
beforeEach(() => {
39+
configDir = mkdtempSync(join(tmpdir(), "bl-auto-update-binary-"));
40+
previousConfigDir = process.env.BAILIAN_CONFIG_DIR;
41+
previousInstallMethod = process.env.BAILIAN_INSTALL_METHOD;
42+
process.env.BAILIAN_CONFIG_DIR = configDir;
43+
process.env.BAILIAN_INSTALL_METHOD = "binary";
44+
binaryUpdateMocks.performBinaryUpdate.mockResolvedValue("2.0.0");
45+
});
46+
47+
afterEach(() => {
48+
if (previousConfigDir === undefined) delete process.env.BAILIAN_CONFIG_DIR;
49+
else process.env.BAILIAN_CONFIG_DIR = previousConfigDir;
50+
if (previousInstallMethod === undefined) delete process.env.BAILIAN_INSTALL_METHOD;
51+
else process.env.BAILIAN_INSTALL_METHOD = previousInstallMethod;
52+
rmSync(configDir, { recursive: true, force: true });
53+
vi.clearAllMocks();
54+
});
55+
1156
test("parseVersion strips pre-release and build metadata", () => {
1257
expect(parseVersion("1.4.2")).toEqual([1, 4, 2]);
1358
expect(parseVersion("2.0.0-beta.1")).toEqual([2, 0, 0]);
@@ -124,3 +169,11 @@ test("shouldAutoUpdate only targets stable releases with a significant gap", ()
124169
// Same core, release over its pre-release: notify only (no major gap).
125170
expect(shouldAutoUpdate("1.4.2", "1.4.2-beta.1")).toBe(false);
126171
});
172+
173+
test("binary auto-update syncs bailian skills after the CLI update succeeds", async () => {
174+
const updated = await performAutoUpdate("1.14.3", "2.0.0");
175+
176+
expect(updated).toBe(true);
177+
expect(binaryUpdateMocks.performBinaryUpdate).toHaveBeenCalledWith("2.0.0");
178+
expect(childProcessMocks.execSync).toHaveBeenCalledWith("bl skill init", { stdio: "inherit" });
179+
});

0 commit comments

Comments
 (0)