Skip to content

Commit 96ec96c

Browse files
feat: 添加 ccb update 命令,支持 npm/bun 自动更新
从 package.json 读取当前版本,查询 npm registry 最新版本, 自动检测安装方式(bun 或 npm)执行全局更新。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 13a0bfc commit 96ec96c

3 files changed

Lines changed: 176 additions & 0 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ npm i -g claude-code-best
4949

5050
ccb # 以 nodejs 打开 claude code
5151
ccb-bun # 以 bun 形态打开
52+
ccb update # 更新到最新版本
5253
CLAUDE_BRIDGE_BASE_URL=https://remote-control.claude-code-best.win/ CLAUDE_BRIDGE_OAUTH_TOKEN=test-my-key ccb --remote-control # 我们有自部署的远程控制
5354
```
5455

src/cli/updateCCB.ts

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
/**
2+
* `ccb update` — Check and install the latest version of claude-code-best.
3+
*
4+
* Detection strategy:
5+
* 1. If `bun` is available and the current installation was done via bun → use `bun update -g`
6+
* 2. Otherwise → use `npm install -g`
7+
*/
8+
import chalk from 'chalk'
9+
import { execSync } from 'node:child_process'
10+
import { existsSync, readFileSync } from 'node:fs'
11+
import { homedir } from 'node:os'
12+
import { join, dirname } from 'node:path'
13+
import { fileURLToPath } from 'node:url'
14+
import { logForDebugging } from '../utils/debug.js'
15+
import { execFileNoThrowWithCwd } from '../utils/execFileNoThrow.js'
16+
import { gracefulShutdown } from '../utils/gracefulShutdown.js'
17+
import { writeToStdout } from '../utils/process.js'
18+
19+
const PACKAGE_NAME = 'claude-code-best'
20+
21+
function getCurrentVersion(): string {
22+
// Read version from the nearest package.json (walks up from this file)
23+
try {
24+
const __dirname = dirname(fileURLToPath(import.meta.url))
25+
// In dev: src/cli/updateCCB.ts → ../../package.json
26+
// In build: dist/chunks/xxx.js → ../../package.json (may not exist)
27+
const pkgPath = join(__dirname, '..', '..', 'package.json')
28+
if (existsSync(pkgPath)) {
29+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'))
30+
if (pkg.version) return pkg.version
31+
}
32+
} catch {
33+
// fallback
34+
}
35+
return MACRO.VERSION
36+
}
37+
38+
function isCommandAvailable(cmd: string): boolean {
39+
try {
40+
execSync(`which ${cmd} 2>/dev/null`, { stdio: 'pipe' })
41+
return true
42+
} catch {
43+
return false
44+
}
45+
}
46+
47+
/**
48+
* Detect whether the current installation was done via bun.
49+
* Checks if the binary path contains "bun" or if bun's global install dir has our package.
50+
*/
51+
function isBunInstallation(): boolean {
52+
// Check if the running binary is under bun's global install path
53+
const execPath = process.execPath
54+
if (execPath.includes('bun')) {
55+
return true
56+
}
57+
58+
// Check bun's global install directory
59+
const bunGlobalDir = join(homedir(), '.bun', 'install', 'global')
60+
if (existsSync(join(bunGlobalDir, 'node_modules', PACKAGE_NAME))) {
61+
return true
62+
}
63+
64+
return false
65+
}
66+
67+
/**
68+
* Get the latest version from npm registry.
69+
*/
70+
async function getLatestVersion(): Promise<string | null> {
71+
const result = await execFileNoThrowWithCwd(
72+
'npm',
73+
['view', `${PACKAGE_NAME}@latest`, 'version', '--prefer-online'],
74+
{ abortSignal: AbortSignal.timeout(10_000), cwd: homedir() },
75+
)
76+
if (result.code !== 0) {
77+
logForDebugging(`npm view failed: ${result.stderr}`)
78+
return null
79+
}
80+
return result.stdout.trim()
81+
}
82+
83+
/**
84+
* Compare two semver strings. Returns true if a >= b.
85+
*/
86+
function gte(a: string, b: string): boolean {
87+
const parseVer = (v: string) => v.replace(/^\D/, '').split('.').map(Number)
88+
const pa = parseVer(a)
89+
const pb = parseVer(b)
90+
for (let i = 0; i < 3; i++) {
91+
if ((pa[i] ?? 0) > (pb[i] ?? 0)) return true
92+
if ((pa[i] ?? 0) < (pb[i] ?? 0)) return false
93+
}
94+
return true
95+
}
96+
97+
export async function updateCCB(): Promise<void> {
98+
const currentVersion = getCurrentVersion()
99+
writeToStdout(`Current version: ${currentVersion}\n`)
100+
101+
// Determine package manager
102+
const hasBun = isCommandAvailable('bun')
103+
const useBun = isBunInstallation()
104+
const pkgManager = useBun && hasBun ? 'bun' : 'npm'
105+
106+
writeToStdout(`Package manager: ${pkgManager}\n`)
107+
writeToStdout('Checking for updates...\n')
108+
109+
// Get latest version
110+
const latestVersion = await getLatestVersion()
111+
if (!latestVersion) {
112+
process.stderr.write(chalk.red('Failed to check for updates') + '\n')
113+
process.stderr.write('Unable to fetch latest version from npm registry.\n')
114+
await gracefulShutdown(1)
115+
return
116+
}
117+
118+
// Already up to date?
119+
if (latestVersion === currentVersion || gte(currentVersion, latestVersion)) {
120+
writeToStdout(chalk.green(`ccb is up to date (${currentVersion})`) + '\n')
121+
await gracefulShutdown(0)
122+
return
123+
}
124+
125+
writeToStdout(
126+
`New version available: ${latestVersion} (current: ${currentVersion})\n`,
127+
)
128+
writeToStdout(`Installing update via ${pkgManager}...\n`)
129+
130+
try {
131+
if (pkgManager === 'bun') {
132+
execSync(`bun update -g ${PACKAGE_NAME}`, {
133+
stdio: 'inherit',
134+
cwd: homedir(),
135+
timeout: 120_000,
136+
})
137+
} else {
138+
execSync(`npm install -g ${PACKAGE_NAME}@latest`, {
139+
stdio: 'inherit',
140+
cwd: homedir(),
141+
timeout: 120_000,
142+
})
143+
}
144+
145+
writeToStdout(
146+
chalk.green(
147+
`Successfully updated from ${currentVersion} to ${latestVersion}`,
148+
) + '\n',
149+
)
150+
} catch (error) {
151+
process.stderr.write(chalk.red('Update failed') + '\n')
152+
process.stderr.write(`${error}\n`)
153+
process.stderr.write('\n')
154+
process.stderr.write('Try manually updating with:\n')
155+
if (pkgManager === 'bun') {
156+
process.stderr.write(chalk.bold(` bun update -g ${PACKAGE_NAME}`) + '\n')
157+
} else {
158+
process.stderr.write(
159+
chalk.bold(` npm install -g ${PACKAGE_NAME}@latest`) + '\n',
160+
)
161+
}
162+
await gracefulShutdown(1)
163+
}
164+
165+
await gracefulShutdown(0)
166+
}

src/main.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6551,6 +6551,15 @@ async function run(): Promise<CommanderCommand> {
65516551
},
65526552
);
65536553

6554+
// claude update — update ccb to the latest version via npm or bun
6555+
program
6556+
.command("update")
6557+
.description("Update claude-code-best (ccb) to the latest version")
6558+
.action(async () => {
6559+
const { updateCCB } = await import("./cli/updateCCB.js");
6560+
await updateCCB();
6561+
});
6562+
65546563
// ant-only commands
65556564
if (process.env.USER_TYPE === "ant") {
65566565
const validateLogId = (value: string) => {

0 commit comments

Comments
 (0)