Skip to content

Commit ba78d13

Browse files
committed
feat: add auto update cli
1 parent 60c49ec commit ba78d13

2 files changed

Lines changed: 128 additions & 7 deletions

File tree

packages/cli/src/main.ts

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,12 @@ import {
1010
import { ensureApiKey } from "./utils/ensure-key.ts";
1111
import { setupProxyFromEnv } from "./proxy.ts";
1212
import { handleError } from "./error-handler.ts";
13-
import { checkForUpdate, getPendingUpdateNotification } from "./utils/update-checker.ts";
13+
import {
14+
checkForUpdate,
15+
getPendingUpdateNotification,
16+
isMajorUpgrade,
17+
performAutoUpdate,
18+
} from "./utils/update-checker.ts";
1419
import { maybeShowStatusBar } from "./output/status-bar.ts";
1520
import { printWelcomeBanner, printQuickStart } from "./output/banner.ts";
1621
import { CLI_VERSION } from "./version.ts";
@@ -129,12 +134,20 @@ async function main() {
129134
const isUpdateCommand = commandPath.length === 1 && commandPath[0] === "update";
130135
const newVersion = getPendingUpdateNotification();
131136
if (newVersion && !config.quiet && !isUpdateCommand) {
132-
const isTTY = process.stderr.isTTY;
133-
const yellow = isTTY ? "\x1b[33m" : "";
134-
const cyan = isTTY ? "\x1b[36m" : "";
135-
const reset = isTTY ? "\x1b[0m" : "";
136-
process.stderr.write(`\n ${yellow}Update available: ${CLI_VERSION}${newVersion}${reset}\n`);
137-
process.stderr.write(` Run ${cyan}bl update${reset} to upgrade\n\n`);
137+
if (isMajorUpgrade(newVersion, CLI_VERSION)) {
138+
// 大版本差距,自动更新
139+
await performAutoUpdate(CLI_VERSION, newVersion);
140+
} else {
141+
// 普通小版本提示
142+
const isTTY = process.stderr.isTTY;
143+
const yellow = isTTY ? "\x1b[33m" : "";
144+
const cyan = isTTY ? "\x1b[36m" : "";
145+
const reset = isTTY ? "\x1b[0m" : "";
146+
process.stderr.write(
147+
`\n ${yellow}Update available: ${CLI_VERSION}${newVersion}${reset}\n`,
148+
);
149+
process.stderr.write(` Run ${cyan}bl update${reset} to upgrade\n\n`);
150+
}
138151
}
139152

140153
// 进程退出前尽力等待在途的埋点完成。

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

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,114 @@ export function getPendingUpdateNotification(): string | null {
7171
return pendingNotification;
7272
}
7373

74+
/**
75+
* Determines if the version gap is large enough to warrant auto-update.
76+
* Conditions (either triggers auto-update):
77+
* 1. New major > current major
78+
* 2. Same major, but new minor - current minor > 3
79+
*/
80+
export function isMajorUpgrade(latest: string, current: string): boolean {
81+
const [latestMajor, latestMinor] = latest.split(".").map(Number);
82+
const [currentMajor, currentMinor] = current.split(".").map(Number);
83+
84+
// Condition 1: major version bump
85+
if (latestMajor > currentMajor) return true;
86+
87+
// Condition 2: same major, minor gap > 3
88+
if (latestMajor === currentMajor && latestMinor - currentMinor > 3) return true;
89+
90+
return false;
91+
}
92+
93+
/**
94+
* Perform auto-update: install latest version globally and update agent skill.
95+
* Returns true if update succeeded, false otherwise.
96+
*/
97+
export async function performAutoUpdate(
98+
currentVersion: string,
99+
latestVersion: string,
100+
): Promise<boolean> {
101+
const isTTY = process.stderr.isTTY;
102+
const green = isTTY ? "\x1b[32m" : "";
103+
const yellow = isTTY ? "\x1b[33m" : "";
104+
const cyan = isTTY ? "\x1b[36m" : "";
105+
const dim = isTTY ? "\x1b[2m" : "";
106+
const reset = isTTY ? "\x1b[0m" : "";
107+
108+
const [latestMajor] = latestVersion.split(".").map(Number);
109+
const [currentMajor] = currentVersion.split(".").map(Number);
110+
const isMajorBump = latestMajor > currentMajor;
111+
112+
process.stderr.write("\n");
113+
process.stderr.write(` ${yellow}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${reset}\n`);
114+
if (isMajorBump) {
115+
process.stderr.write(
116+
` ${yellow}⚡ Major update detected: ${currentVersion}${latestVersion}${reset}\n`,
117+
);
118+
} else {
119+
process.stderr.write(
120+
` ${yellow}⚡ Significant update detected: ${currentVersion}${latestVersion}${reset}\n`,
121+
);
122+
}
123+
process.stderr.write(` ${dim}Auto-updating to keep your CLI up to date...${reset}\n`);
124+
process.stderr.write(` ${yellow}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${reset}\n\n`);
125+
126+
const cmd = `npm install -g ${NPM_PACKAGE}@latest`;
127+
128+
try {
129+
const { execSync } = await import("child_process");
130+
execSync(cmd, { stdio: "inherit" });
131+
132+
// Verify installed version
133+
let newVer: string | null = null;
134+
try {
135+
const rawVer = execSync("bl --version 2>/dev/null", { encoding: "utf-8" }).trim();
136+
newVer = rawVer.replace(/^bl\s+/, "");
137+
} catch {
138+
/* ignore */
139+
}
140+
141+
// Update cached state
142+
try {
143+
const { writeFileSync } = await import("fs");
144+
const { join } = await import("path");
145+
const { getConfigDir } = await import("bailian-cli-core");
146+
const stateFile = join(getConfigDir(), "update-state.json");
147+
writeFileSync(
148+
stateFile,
149+
JSON.stringify({ lastChecked: Date.now(), latestVersion: newVer ?? latestVersion }),
150+
);
151+
} catch {
152+
/* ignore */
153+
}
154+
155+
process.stderr.write(
156+
` ${green}✓ Update complete: ${currentVersion}${newVer ?? latestVersion}${reset}\n`,
157+
);
158+
process.stderr.write(` ${dim}Run ${cyan}bl --version${reset}${dim} to verify.${reset}\n\n`);
159+
160+
// Update agent skill
161+
try {
162+
const { execSync: exec } = await import("child_process");
163+
process.stderr.write(` ${dim}Syncing agent skill...${reset}\n`);
164+
exec(`npx skills add modelstudioai/cli --all -g -y`, { stdio: "inherit" });
165+
process.stderr.write(` ${green}✓ Agent skill updated.${reset}\n\n`);
166+
} catch {
167+
process.stderr.write(
168+
` ${yellow}Agent skill sync skipped (run manually: npx skills add modelstudioai/cli --all -g -y)${reset}\n\n`,
169+
);
170+
}
171+
172+
// Clear pending notification
173+
pendingNotification = null;
174+
return true;
175+
} catch {
176+
process.stderr.write(` ${yellow}⚠ Auto-update failed. Please run manually:${reset}\n`);
177+
process.stderr.write(` ${cyan}${cmd}${reset}\n\n`);
178+
return false;
179+
}
180+
}
181+
74182
export async function checkForUpdate(currentVersion: string): Promise<void> {
75183
// Skip in CI / non-TTY environments
76184
if (process.env.CI || !process.stderr.isTTY) return;

0 commit comments

Comments
 (0)