feat(cli): include git sha in --version
This commit is contained in:
+145
-26
@@ -1,45 +1,164 @@
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { dirname, join } from 'node:path';
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
function readPackageVersionFromJsonFile(path: string): string | undefined {
|
||||
declare const __dirname: string | undefined;
|
||||
|
||||
export const FALLBACK_VERSION = 'unknown';
|
||||
|
||||
function readPackageVersionFromJsonFile(candidate: string): string | null {
|
||||
try {
|
||||
const raw = readFileSync(path, 'utf8');
|
||||
const parsed = JSON.parse(raw) as { version?: unknown };
|
||||
return typeof parsed.version === 'string' ? parsed.version : undefined;
|
||||
const raw = fs.readFileSync(candidate, 'utf8');
|
||||
const json = JSON.parse(raw) as { version?: unknown } | null;
|
||||
if (json && typeof json.version === 'string' && json.version.trim().length > 0) {
|
||||
return json.version.trim();
|
||||
}
|
||||
return null;
|
||||
} catch {
|
||||
return undefined;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function readVersionFromTextFile(path: string): string | undefined {
|
||||
function readVersionFromTextFile(candidate: string): string | null {
|
||||
try {
|
||||
const raw = readFileSync(path, 'utf8').trim();
|
||||
return raw.length > 0 ? raw : undefined;
|
||||
const raw = fs.readFileSync(candidate, 'utf8').trim();
|
||||
return raw.length > 0 ? raw : null;
|
||||
} catch {
|
||||
return undefined;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function tryReadVersionFromImportMeta(): string | undefined {
|
||||
try {
|
||||
const thisFile = fileURLToPath(import.meta.url);
|
||||
const baseDir = join(dirname(thisFile), '..', '..');
|
||||
return (
|
||||
readPackageVersionFromJsonFile(join(baseDir, 'package.json')) ?? readVersionFromTextFile(join(baseDir, 'VERSION'))
|
||||
);
|
||||
} catch {
|
||||
return undefined;
|
||||
function resolveStartDir(importMetaUrl?: string): string {
|
||||
if (typeof importMetaUrl === 'string' && importMetaUrl.trim().length > 0) {
|
||||
try {
|
||||
return path.dirname(fileURLToPath(importMetaUrl));
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof __dirname === 'string' && __dirname.length > 0) return __dirname;
|
||||
|
||||
return process.cwd();
|
||||
}
|
||||
|
||||
function tryReadVersionNextToExecutable(): string | undefined {
|
||||
const execDir = dirname(process.argv[0] ?? process.execPath);
|
||||
return (
|
||||
readPackageVersionFromJsonFile(join(execDir, 'package.json')) ?? readVersionFromTextFile(join(execDir, 'VERSION'))
|
||||
);
|
||||
export function resolvePackageVersion(importMetaUrl?: string): string {
|
||||
const injected =
|
||||
typeof process !== 'undefined' && typeof process.env.BIRD_VERSION === 'string'
|
||||
? process.env.BIRD_VERSION.trim()
|
||||
: '';
|
||||
if (injected.length > 0) return injected;
|
||||
|
||||
let dir = resolveStartDir(importMetaUrl);
|
||||
for (let i = 0; i < 10; i += 1) {
|
||||
const version =
|
||||
readPackageVersionFromJsonFile(path.join(dir, 'package.json')) ??
|
||||
readVersionFromTextFile(path.join(dir, 'VERSION'));
|
||||
if (version) return version;
|
||||
|
||||
const parent = path.dirname(dir);
|
||||
if (parent === dir) break;
|
||||
dir = parent;
|
||||
}
|
||||
|
||||
return FALLBACK_VERSION;
|
||||
}
|
||||
|
||||
function truncateSha(sha: string, length = 8): string {
|
||||
const trimmed = sha.trim();
|
||||
if (!trimmed) return '';
|
||||
if (trimmed.length <= length) return trimmed;
|
||||
return trimmed.slice(0, length);
|
||||
}
|
||||
|
||||
function resolveGitShaFromGitDir(gitDir: string): string | null {
|
||||
const headPath = path.join(gitDir, 'HEAD');
|
||||
let head = '';
|
||||
try {
|
||||
head = fs.readFileSync(headPath, 'utf8').trim();
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!head) return null;
|
||||
if (!head.startsWith('ref:')) {
|
||||
const sha = truncateSha(head);
|
||||
return sha.length > 0 ? sha : null;
|
||||
}
|
||||
|
||||
const ref = head.replace(/^ref:\s*/i, '').trim();
|
||||
if (!ref) return null;
|
||||
|
||||
const refPath = path.join(gitDir, ref);
|
||||
try {
|
||||
const sha = truncateSha(fs.readFileSync(refPath, 'utf8'));
|
||||
return sha.length > 0 ? sha : null;
|
||||
} catch {
|
||||
// fall through to packed-refs
|
||||
}
|
||||
|
||||
const packedRefsPath = path.join(gitDir, 'packed-refs');
|
||||
try {
|
||||
const packed = fs.readFileSync(packedRefsPath, 'utf8');
|
||||
const lines = packed.split(/\r?\n/);
|
||||
for (const line of lines) {
|
||||
if (!line || line.startsWith('#') || line.startsWith('^')) continue;
|
||||
const [shaRaw, refName] = line.split(' ');
|
||||
if (refName?.trim() === ref) {
|
||||
const sha = truncateSha(shaRaw ?? '');
|
||||
return sha.length > 0 ? sha : null;
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export function resolveGitSha(importMetaUrl?: string): string | null {
|
||||
const injected =
|
||||
typeof process !== 'undefined' && typeof process.env.BIRD_GIT_SHA === 'string'
|
||||
? process.env.BIRD_GIT_SHA.trim()
|
||||
: '';
|
||||
if (injected.length > 0) return truncateSha(injected);
|
||||
|
||||
let dir = resolveStartDir(importMetaUrl);
|
||||
for (let i = 0; i < 10; i += 1) {
|
||||
const dotGit = path.join(dir, '.git');
|
||||
try {
|
||||
const stat = fs.statSync(dotGit);
|
||||
if (stat.isDirectory()) {
|
||||
const sha = resolveGitShaFromGitDir(dotGit);
|
||||
if (sha) return sha;
|
||||
} else if (stat.isFile()) {
|
||||
const txt = fs.readFileSync(dotGit, 'utf8');
|
||||
const match = txt.match(/gitdir:\s*(.+)\s*$/i);
|
||||
const gitDir = match?.[1]?.trim();
|
||||
if (gitDir) {
|
||||
const resolved = path.isAbsolute(gitDir) ? gitDir : path.resolve(dir, gitDir);
|
||||
const sha = resolveGitShaFromGitDir(resolved);
|
||||
if (sha) return sha;
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
|
||||
const parent = path.dirname(dir);
|
||||
if (parent === dir) break;
|
||||
dir = parent;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export function formatVersionLine(importMetaUrl?: string): string {
|
||||
const version = resolvePackageVersion(importMetaUrl);
|
||||
const sha = resolveGitSha(importMetaUrl);
|
||||
return sha ? `${version} (${sha})` : version;
|
||||
}
|
||||
|
||||
export function getCliVersion(): string {
|
||||
return tryReadVersionFromImportMeta() ?? tryReadVersionNextToExecutable() ?? 'unknown';
|
||||
return formatVersionLine(import.meta.url);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user