Skip to content

Commit 739e0c8

Browse files
yuWormclaude
andcommitted
feat: add check command to show how many git commits repos are behind remote
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 51bf6f3 commit 739e0c8

File tree

3 files changed

+98
-0
lines changed

3 files changed

+98
-0
lines changed

src/commands/check.ts

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// check.ts — 检查项目仓库落后远程多少 git 提交
2+
import chalk from 'chalk'
3+
import { existsSync } from 'fs'
4+
import { resolveProjectDir, getBackendDir, getFrontendDir } from '../lib/config.js'
5+
import { run } from '../lib/process.js'
6+
import { t } from '../lib/i18n.js'
7+
8+
interface CheckOptions {
9+
project?: string
10+
}
11+
12+
async function getRepoBehindCount(repoDir: string): Promise<{ behind: number; branch: string } | null> {
13+
if (!existsSync(repoDir)) return null
14+
15+
// fetch latest from remote
16+
const fetchResult = await run('git', ['fetch'], { cwd: repoDir, spinner: true, label: `${t('checkFetching')} ${repoDir}`, showErrorOutput: false })
17+
if (fetchResult.exitCode !== 0) return null
18+
19+
// get current branch name
20+
const branchResult = await run('git', ['rev-parse', '--abbrev-ref', 'HEAD'], { cwd: repoDir, stdio: 'pipe', showErrorOutput: false })
21+
if (branchResult.exitCode !== 0) return null
22+
const branch = branchResult.stdout.trim()
23+
24+
// check if upstream tracking branch exists
25+
const upstreamResult = await run('git', ['rev-parse', '--abbrev-ref', `${branch}@{upstream}`], { cwd: repoDir, stdio: 'pipe', showErrorOutput: false })
26+
if (upstreamResult.exitCode !== 0) return null
27+
28+
// count commits behind
29+
const countResult = await run('git', ['rev-list', '--count', `HEAD..${branch}@{upstream}`], { cwd: repoDir, stdio: 'pipe', showErrorOutput: false })
30+
if (countResult.exitCode !== 0) return null
31+
32+
return { behind: parseInt(countResult.stdout.trim(), 10), branch }
33+
}
34+
35+
export async function checkAction(options: CheckOptions = {}) {
36+
const projectDir = resolveProjectDir(options.project)
37+
if (!projectDir || !existsSync(projectDir)) {
38+
console.log(chalk.red(t('projectDirNotExist')))
39+
console.log(chalk.dim(t('hintRunCreate')))
40+
return
41+
}
42+
43+
const backendDir = getBackendDir(projectDir)
44+
const frontendDir = getFrontendDir(projectDir)
45+
46+
console.log(chalk.bold(t('checkTitle')))
47+
console.log()
48+
49+
const dirs = [
50+
{ label: t('labelBackend'), dir: backendDir },
51+
{ label: t('labelFrontend'), dir: frontendDir },
52+
]
53+
54+
for (const { label, dir } of dirs) {
55+
if (!existsSync(dir)) {
56+
console.log(` ${label}: ${chalk.dim(t('checkDirNotFound'))}`)
57+
continue
58+
}
59+
60+
const result = await getRepoBehindCount(dir)
61+
if (result === null) {
62+
console.log(` ${label}: ${chalk.yellow(t('checkFetchFailed'))}`)
63+
} else if (result.behind === 0) {
64+
console.log(` ${label} ${chalk.dim(`(${result.branch})`)}: ${chalk.green(t('checkUpToDate'))}`)
65+
} else {
66+
console.log(` ${label} ${chalk.dim(`(${result.branch})`)}: ${chalk.yellow(`${t('checkBehind')} ${result.behind} ${t('checkCommits')}`)}`)
67+
}
68+
}
69+
}

src/index.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,15 @@ program
219219
await goAction(options)
220220
})
221221

222+
// ─── check ───
223+
program
224+
.command('check')
225+
.description(t('cmdCheck'))
226+
.action(async () => {
227+
const { checkAction } = await import('./commands/check.js')
228+
await checkAction({ project: program.opts().project })
229+
})
230+
222231
// ─── infra ───
223232
const infraCmd = program
224233
.command('infra')

src/lib/i18n.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,16 @@ const messages = {
205205
'运行 "fba-cli remove" 清理,或运行 "fba-cli create" 创建新项目',
206206
hintRunUse: '运行 "fba-cli use" 设置默认项目',
207207

208+
// Check
209+
checkTitle: "检查仓库更新状态",
210+
checkFetching: "正在拉取远程更新",
211+
checkDirNotFound: "目录不存在",
212+
checkFetchFailed: "无法获取远程状态",
213+
checkUpToDate: "已是最新",
214+
checkBehind: "落后",
215+
checkCommits: "个提交",
216+
cmdCheck: "检查项目仓库落后远程多少 git 提交",
217+
208218
// Dev server
209219
devStartingBackend: "正在启动后端服务器,端口:",
210220
devStartingFrontend: "正在启动前端开发服务器...",
@@ -497,6 +507,16 @@ const messages = {
497507
'Run "fba-cli remove" to clean up, or "fba-cli create" to create a new project',
498508
hintRunUse: 'Run "fba-cli use" to set a default project',
499509

510+
// Check
511+
checkTitle: "Checking repository update status",
512+
checkFetching: "Fetching remote updates",
513+
checkDirNotFound: "directory not found",
514+
checkFetchFailed: "Failed to fetch remote status",
515+
checkUpToDate: "Up to date",
516+
checkBehind: "behind",
517+
checkCommits: "commits",
518+
cmdCheck: "Check how many git commits the project repos are behind remote",
519+
500520
devStartingBackend: "Starting backend server on port",
501521
devStartingFrontend: "Starting frontend dev server...",
502522
devStartingCelery: "Starting Celery",

0 commit comments

Comments
 (0)