147 lines
3.8 KiB
TypeScript
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);
|
|
}
|
|
}
|