Files
gh-jbabin91-super-claude-pl…/hooks/utils/runtime-check.ts
2025-11-29 18:50:12 +08:00

147 lines
3.8 KiB
TypeScript

/**
* Runtime Check Utilities
*
* Validates runtime requirements for hooks (e.g., Bun installation).
* Provides clear, actionable error messages when requirements aren't met.
*/
import { execSync } from 'node:child_process';
/**
* Check if Bun is installed and available in PATH
*
* @throws Error with installation instructions if Bun is not found
*/
export function ensureBunInstalled(): void {
try {
// Use 'bun --version' for cross-platform compatibility (works on Windows, macOS, Linux)
execSync('bun --version', { stdio: 'pipe' });
} catch {
const errorMessage = [
'',
'═'.repeat(70),
'❌ BUN RUNTIME NOT FOUND',
'═'.repeat(70),
'',
'This workflow hook requires Bun to be installed.',
'',
'📦 Install Bun:',
'',
' macOS/Linux:',
' curl -fsSL https://bun.sh/install | bash',
'',
' Windows:',
' powershell -c "irm bun.sh/install.ps1|iex"',
'',
' Or via npm:',
' npm install -g bun',
'',
' Or via Homebrew (macOS):',
' brew install oven-sh/bun/bun',
'',
'🔗 More info: https://bun.sh',
'',
'⚠️ After installing, restart your terminal and try again.',
'',
'═'.repeat(70),
'',
].join('\n');
console.error(errorMessage);
process.exit(1);
}
}
/**
* Check Bun version meets minimum requirement
*
* @param minVersion Minimum required version (e.g., "1.0.0")
* @returns true if version is sufficient, false otherwise
*/
export function checkBunVersion(minVersion: string): boolean {
try {
const version = execSync('bun --version', {
encoding: 'utf8',
stdio: 'pipe',
}).trim();
return compareVersions(version, minVersion) >= 0;
} catch {
return false;
}
}
/**
* Strip pre-release and build metadata from semver string
*
* @param version Version string (e.g., "1.0.0-beta", "1.0.0+build")
* @returns Clean version (e.g., "1.0.0")
*/
function stripSemverMetadata(version: string): string {
return version.split(/[-+]/)[0] || version;
}
/**
* Compare two semantic versions
*
* Strips pre-release and build metadata before comparison.
* Examples: "1.0.0-beta" → "1.0.0", "1.0.0+build" → "1.0.0"
*
* @param a Version string (e.g., "1.2.3", "1.2.3-beta")
* @param b Version string (e.g., "1.0.0", "1.0.0+build")
* @returns -1 if a < b, 0 if equal, 1 if a > b
*/
function compareVersions(a: string, b: string): number {
const aParts = stripSemverMetadata(a).split('.').map(Number);
const bParts = stripSemverMetadata(b).split('.').map(Number);
for (let i = 0; i < Math.max(aParts.length, bParts.length); i++) {
const aPart = aParts[i] || 0;
const bPart = bParts[i] || 0;
if (aPart > bPart) return 1;
if (aPart < bPart) return -1;
}
return 0;
}
/**
* Ensure required command-line tools are installed
*
* @param tools Array of required tools (e.g., ['git', 'tsc'])
* @throws Error with installation instructions if any tool is missing
*/
export function ensureToolsInstalled(tools: string[]): void {
const missing: string[] = [];
// Use platform-specific command: 'where' on Windows, 'which' on Unix-like systems
const checkCommand = process.platform === 'win32' ? 'where' : 'which';
for (const tool of tools) {
try {
execSync(`${checkCommand} ${tool}`, { stdio: 'pipe' });
} catch {
missing.push(tool);
}
}
if (missing.length > 0) {
const errorMessage = [
'',
'═'.repeat(70),
'❌ MISSING REQUIRED TOOLS',
'═'.repeat(70),
'',
`The following tools are required but not found: ${missing.join(', ')}`,
'',
'Please install the missing tools and try again.',
'',
'═'.repeat(70),
'',
].join('\n');
console.error(errorMessage);
process.exit(1);
}
}