Skip to content

Commit dddff87

Browse files
leex279Archonclaude
authored
feat(cli): embed git commit hash in version output (#1035)
* feat(cli): embed git commit hash in version output - Add BUNDLED_GIT_COMMIT constant to bundled-version.ts for binary builds - Read commit at runtime via git rev-parse (dev) or from bundled constant (binary) - Display Git commit: line in archon version output; falls back to "unknown" - Update build-binaries.sh to capture and embed short SHA at compile time - Update version tests to assert new Git commit: output line * fix: use @archon/git execFileAsync and add error logging in version command - Replace local child_process/promisify wrapper with execFileAsync from @archon/git (fixes CLAUDE.md violation; enables test mockability via spyOn) - Add 5s timeout to git rev-parse subprocess call to prevent indefinite hang - Log debug trace in catch block with comment explaining intentional fallback - Import createLogger from @archon/paths for structured logging - Update version.test.ts to spy on @archon/git execFileAsync for deterministic SHA - Add test case covering the git-unavailable fallback path (returns 'unknown') - Tighten git commit assertion to match exact value instead of label presence only Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Archon <archon@dynamous.ai> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent e7e42c1 commit dddff87

4 files changed

Lines changed: 54 additions & 8 deletions

File tree

packages/cli/src/commands/bundled-version.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@
88
*/
99

1010
export const BUNDLED_VERSION = '0.2.0';
11+
export const BUNDLED_GIT_COMMIT = 'unknown';

packages/cli/src/commands/version.test.ts

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,28 @@
22
* Tests for version command
33
*/
44
import { describe, it, expect, beforeEach, afterEach, spyOn } from 'bun:test';
5+
import * as git from '@archon/git';
56
import { versionCommand } from './version';
67

78
describe('versionCommand', () => {
89
let consoleSpy: ReturnType<typeof spyOn>;
10+
let execSpy: ReturnType<typeof spyOn>;
911

1012
beforeEach(() => {
1113
consoleSpy = spyOn(console, 'log').mockImplementation(() => {});
14+
execSpy = spyOn(git, 'execFileAsync').mockResolvedValue({ stdout: 'abc1234\n', stderr: '' });
1215
});
1316

1417
afterEach(() => {
1518
consoleSpy.mockRestore();
19+
execSpy.mockRestore();
1620
});
1721

1822
it('should output version and system info', async () => {
1923
await versionCommand();
2024

21-
// Should have called console.log 4 times (version, platform, build, database)
22-
expect(consoleSpy).toHaveBeenCalledTimes(4);
25+
// Should have called console.log 5 times (version, platform, build, database, git commit)
26+
expect(consoleSpy).toHaveBeenCalledTimes(5);
2327

2428
// First call should contain "Archon CLI" and version
2529
const firstCall = consoleSpy.mock.calls[0][0] as string;
@@ -37,6 +41,20 @@ describe('versionCommand', () => {
3741
// Fourth call should contain database type
3842
const fourthCall = consoleSpy.mock.calls[3][0] as string;
3943
expect(fourthCall).toContain('Database:');
44+
45+
// Fifth call should contain git commit with the mocked SHA
46+
const fifthCall = consoleSpy.mock.calls[4][0] as string;
47+
expect(fifthCall).toMatch(/Git commit: ([0-9a-f]{7,}|unknown)/);
48+
expect(fifthCall).toBe(' Git commit: abc1234');
49+
});
50+
51+
it('should return unknown git commit when git is unavailable', async () => {
52+
execSpy.mockRejectedValueOnce(new Error('not a git repository'));
53+
54+
await versionCommand();
55+
56+
const fifthCall = consoleSpy.mock.calls[4][0] as string;
57+
expect(fifthCall).toBe(' Git commit: unknown');
4058
});
4159

4260
it('should output correct format for version line', async () => {

packages/cli/src/commands/version.ts

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
11
/**
22
* Version command - displays version info
33
*
4-
* For compiled binaries, version is embedded via bundled-version.ts
5-
* For development (Bun), reads from package.json
4+
* For compiled binaries, version and git commit are embedded via bundled-version.ts
5+
* For development (Bun), reads from package.json and retrieves git commit at runtime
66
*/
77
import { readFile } from 'fs/promises';
88
import { join, dirname } from 'path';
99
import { fileURLToPath } from 'url';
10+
import { execFileAsync } from '@archon/git';
11+
import { createLogger } from '@archon/paths';
1012
import { getDatabaseType } from '@archon/core';
1113
import { isBinaryBuild } from '@archon/workflows/defaults';
12-
import { BUNDLED_VERSION } from './bundled-version';
14+
import { BUNDLED_VERSION, BUNDLED_GIT_COMMIT } from './bundled-version';
15+
16+
const log = createLogger('cli:version');
1317

1418
const SCRIPT_DIR = dirname(fileURLToPath(import.meta.url));
1519

@@ -47,16 +51,36 @@ async function getDevVersion(): Promise<{ name: string; version: string }> {
4751
return { name: pkg.name, version: pkg.version };
4852
}
4953

54+
/**
55+
* Get the git commit hash at runtime (dev mode).
56+
* Returns 'unknown' if git is unavailable or the command fails.
57+
*/
58+
async function getDevGitCommit(): Promise<string> {
59+
try {
60+
const { stdout } = await execFileAsync('git', ['rev-parse', '--short', 'HEAD'], {
61+
timeout: 5000,
62+
});
63+
return stdout.trim();
64+
} catch (err) {
65+
// Non-blocking: git may not be installed or cwd may not be a git repo
66+
log.debug({ err }, 'version.git_commit_lookup_failed');
67+
return 'unknown';
68+
}
69+
}
70+
5071
export async function versionCommand(): Promise<void> {
5172
let version: string;
73+
let gitCommit: string;
5274

5375
if (isBinaryBuild()) {
54-
// Compiled binary: use embedded version
76+
// Compiled binary: use embedded version and commit
5577
version = BUNDLED_VERSION;
78+
gitCommit = BUNDLED_GIT_COMMIT;
5679
} else {
57-
// Development mode: read from package.json
80+
// Development mode: read from package.json and git
5881
const devInfo = await getDevVersion();
5982
version = devInfo.version;
83+
gitCommit = await getDevGitCommit();
6084
}
6185

6286
const platform = process.platform;
@@ -68,4 +92,5 @@ export async function versionCommand(): Promise<void> {
6892
console.log(` Platform: ${platform}-${arch}`);
6993
console.log(` Build: ${buildType}`);
7094
console.log(` Database: ${dbType}`);
95+
console.log(` Git commit: ${gitCommit}`);
7196
}

scripts/build-binaries.sh

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ set -euo pipefail
66

77
# Get version from package.json or git tag
88
VERSION="${VERSION:-$(grep '"version"' package.json | head -1 | cut -d'"' -f4)}"
9-
echo "Building Archon CLI v${VERSION}"
9+
GIT_COMMIT="${GIT_COMMIT:-$(git rev-parse --short HEAD 2>/dev/null || echo 'unknown')}"
10+
echo "Building Archon CLI v${VERSION} (commit: ${GIT_COMMIT})"
1011

1112
# Update bundled version in source before compiling
1213
BUNDLED_VERSION_FILE="packages/cli/src/commands/bundled-version.ts"
@@ -22,6 +23,7 @@ cat > "$BUNDLED_VERSION_FILE" << EOF
2223
*/
2324
2425
export const BUNDLED_VERSION = '${VERSION}';
26+
export const BUNDLED_GIT_COMMIT = '${GIT_COMMIT}';
2527
EOF
2628

2729
# Output directory

0 commit comments

Comments
 (0)