mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-18 22:35:51 +00:00
feat: 添加 ccb update 命令,支持 npm/bun 自动更新
从 package.json 读取当前版本,查询 npm registry 最新版本, 自动检测安装方式(bun 或 npm)执行全局更新。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -49,6 +49,7 @@ npm i -g claude-code-best
|
|||||||
|
|
||||||
ccb # 以 nodejs 打开 claude code
|
ccb # 以 nodejs 打开 claude code
|
||||||
ccb-bun # 以 bun 形态打开
|
ccb-bun # 以 bun 形态打开
|
||||||
|
ccb update # 更新到最新版本
|
||||||
CLAUDE_BRIDGE_BASE_URL=https://remote-control.claude-code-best.win/ CLAUDE_BRIDGE_OAUTH_TOKEN=test-my-key ccb --remote-control # 我们有自部署的远程控制
|
CLAUDE_BRIDGE_BASE_URL=https://remote-control.claude-code-best.win/ CLAUDE_BRIDGE_OAUTH_TOKEN=test-my-key ccb --remote-control # 我们有自部署的远程控制
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
166
src/cli/updateCCB.ts
Normal file
166
src/cli/updateCCB.ts
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
/**
|
||||||
|
* `ccb update` — Check and install the latest version of claude-code-best.
|
||||||
|
*
|
||||||
|
* Detection strategy:
|
||||||
|
* 1. If `bun` is available and the current installation was done via bun → use `bun update -g`
|
||||||
|
* 2. Otherwise → use `npm install -g`
|
||||||
|
*/
|
||||||
|
import chalk from 'chalk'
|
||||||
|
import { execSync } from 'node:child_process'
|
||||||
|
import { existsSync, readFileSync } from 'node:fs'
|
||||||
|
import { homedir } from 'node:os'
|
||||||
|
import { join, dirname } from 'node:path'
|
||||||
|
import { fileURLToPath } from 'node:url'
|
||||||
|
import { logForDebugging } from '../utils/debug.js'
|
||||||
|
import { execFileNoThrowWithCwd } from '../utils/execFileNoThrow.js'
|
||||||
|
import { gracefulShutdown } from '../utils/gracefulShutdown.js'
|
||||||
|
import { writeToStdout } from '../utils/process.js'
|
||||||
|
|
||||||
|
const PACKAGE_NAME = 'claude-code-best'
|
||||||
|
|
||||||
|
function getCurrentVersion(): string {
|
||||||
|
// Read version from the nearest package.json (walks up from this file)
|
||||||
|
try {
|
||||||
|
const __dirname = dirname(fileURLToPath(import.meta.url))
|
||||||
|
// In dev: src/cli/updateCCB.ts → ../../package.json
|
||||||
|
// In build: dist/chunks/xxx.js → ../../package.json (may not exist)
|
||||||
|
const pkgPath = join(__dirname, '..', '..', 'package.json')
|
||||||
|
if (existsSync(pkgPath)) {
|
||||||
|
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'))
|
||||||
|
if (pkg.version) return pkg.version
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// fallback
|
||||||
|
}
|
||||||
|
return MACRO.VERSION
|
||||||
|
}
|
||||||
|
|
||||||
|
function isCommandAvailable(cmd: string): boolean {
|
||||||
|
try {
|
||||||
|
execSync(`which ${cmd} 2>/dev/null`, { stdio: 'pipe' })
|
||||||
|
return true
|
||||||
|
} catch {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detect whether the current installation was done via bun.
|
||||||
|
* Checks if the binary path contains "bun" or if bun's global install dir has our package.
|
||||||
|
*/
|
||||||
|
function isBunInstallation(): boolean {
|
||||||
|
// Check if the running binary is under bun's global install path
|
||||||
|
const execPath = process.execPath
|
||||||
|
if (execPath.includes('bun')) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check bun's global install directory
|
||||||
|
const bunGlobalDir = join(homedir(), '.bun', 'install', 'global')
|
||||||
|
if (existsSync(join(bunGlobalDir, 'node_modules', PACKAGE_NAME))) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the latest version from npm registry.
|
||||||
|
*/
|
||||||
|
async function getLatestVersion(): Promise<string | null> {
|
||||||
|
const result = await execFileNoThrowWithCwd(
|
||||||
|
'npm',
|
||||||
|
['view', `${PACKAGE_NAME}@latest`, 'version', '--prefer-online'],
|
||||||
|
{ abortSignal: AbortSignal.timeout(10_000), cwd: homedir() },
|
||||||
|
)
|
||||||
|
if (result.code !== 0) {
|
||||||
|
logForDebugging(`npm view failed: ${result.stderr}`)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return result.stdout.trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compare two semver strings. Returns true if a >= b.
|
||||||
|
*/
|
||||||
|
function gte(a: string, b: string): boolean {
|
||||||
|
const parseVer = (v: string) => v.replace(/^\D/, '').split('.').map(Number)
|
||||||
|
const pa = parseVer(a)
|
||||||
|
const pb = parseVer(b)
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
if ((pa[i] ?? 0) > (pb[i] ?? 0)) return true
|
||||||
|
if ((pa[i] ?? 0) < (pb[i] ?? 0)) return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateCCB(): Promise<void> {
|
||||||
|
const currentVersion = getCurrentVersion()
|
||||||
|
writeToStdout(`Current version: ${currentVersion}\n`)
|
||||||
|
|
||||||
|
// Determine package manager
|
||||||
|
const hasBun = isCommandAvailable('bun')
|
||||||
|
const useBun = isBunInstallation()
|
||||||
|
const pkgManager = useBun && hasBun ? 'bun' : 'npm'
|
||||||
|
|
||||||
|
writeToStdout(`Package manager: ${pkgManager}\n`)
|
||||||
|
writeToStdout('Checking for updates...\n')
|
||||||
|
|
||||||
|
// Get latest version
|
||||||
|
const latestVersion = await getLatestVersion()
|
||||||
|
if (!latestVersion) {
|
||||||
|
process.stderr.write(chalk.red('Failed to check for updates') + '\n')
|
||||||
|
process.stderr.write('Unable to fetch latest version from npm registry.\n')
|
||||||
|
await gracefulShutdown(1)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Already up to date?
|
||||||
|
if (latestVersion === currentVersion || gte(currentVersion, latestVersion)) {
|
||||||
|
writeToStdout(chalk.green(`ccb is up to date (${currentVersion})`) + '\n')
|
||||||
|
await gracefulShutdown(0)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
writeToStdout(
|
||||||
|
`New version available: ${latestVersion} (current: ${currentVersion})\n`,
|
||||||
|
)
|
||||||
|
writeToStdout(`Installing update via ${pkgManager}...\n`)
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (pkgManager === 'bun') {
|
||||||
|
execSync(`bun update -g ${PACKAGE_NAME}`, {
|
||||||
|
stdio: 'inherit',
|
||||||
|
cwd: homedir(),
|
||||||
|
timeout: 120_000,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
execSync(`npm install -g ${PACKAGE_NAME}@latest`, {
|
||||||
|
stdio: 'inherit',
|
||||||
|
cwd: homedir(),
|
||||||
|
timeout: 120_000,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
writeToStdout(
|
||||||
|
chalk.green(
|
||||||
|
`Successfully updated from ${currentVersion} to ${latestVersion}`,
|
||||||
|
) + '\n',
|
||||||
|
)
|
||||||
|
} catch (error) {
|
||||||
|
process.stderr.write(chalk.red('Update failed') + '\n')
|
||||||
|
process.stderr.write(`${error}\n`)
|
||||||
|
process.stderr.write('\n')
|
||||||
|
process.stderr.write('Try manually updating with:\n')
|
||||||
|
if (pkgManager === 'bun') {
|
||||||
|
process.stderr.write(chalk.bold(` bun update -g ${PACKAGE_NAME}`) + '\n')
|
||||||
|
} else {
|
||||||
|
process.stderr.write(
|
||||||
|
chalk.bold(` npm install -g ${PACKAGE_NAME}@latest`) + '\n',
|
||||||
|
)
|
||||||
|
}
|
||||||
|
await gracefulShutdown(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
await gracefulShutdown(0)
|
||||||
|
}
|
||||||
@@ -6551,6 +6551,15 @@ async function run(): Promise<CommanderCommand> {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// claude update — update ccb to the latest version via npm or bun
|
||||||
|
program
|
||||||
|
.command("update")
|
||||||
|
.description("Update claude-code-best (ccb) to the latest version")
|
||||||
|
.action(async () => {
|
||||||
|
const { updateCCB } = await import("./cli/updateCCB.js");
|
||||||
|
await updateCCB();
|
||||||
|
});
|
||||||
|
|
||||||
// ant-only commands
|
// ant-only commands
|
||||||
if (process.env.USER_TYPE === "ant") {
|
if (process.env.USER_TYPE === "ant") {
|
||||||
const validateLogId = (value: string) => {
|
const validateLogId = (value: string) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user