Files
Codeman/src/utils/cloudflared-resolver.ts
T

65 lines
2.2 KiB
TypeScript
Raw Normal View History

/**
* @fileoverview Resolve the `cloudflared` binary across common install paths.
*
* Mirrors the CLI resolvers (gemini-cli-resolver.ts et al), for the same reason
* they exist: the welcome screen should not offer a button whose only possible
* outcome is an error toast.
*
* The search list is deliberately the SAME one `TunnelManager.resolveCloudflared()`
* has always used, and that method now delegates here so the two can never drift.
* The difference is the fallback: this module answers "is it installed?" honestly
* with null, while the tunnel manager keeps falling back to the bare name so a
* cloudflared that only exists somewhere on the tunnel process's PATH still
* starts. A stricter answer there would turn a working tunnel into a refusal.
*
* @module utils/cloudflared-resolver
*/
import { execSync } from 'node:child_process';
import { existsSync } from 'node:fs';
import { join } from 'node:path';
import { homedir } from 'node:os';
import { EXEC_TIMEOUT_MS } from '../config/exec-timeout.js';
/** Common directories where the cloudflared binary may be installed */
const CLOUDFLARED_SEARCH_DIRS = [join(homedir(), '.local', 'bin'), '/usr/local/bin'];
/** Cached path to the cloudflared binary (empty string = searched but not found) */
let _cloudflaredPath: string | null = null;
/**
* Finds the `cloudflared` binary.
*
* @returns Absolute path, or null if not found
*/
export function resolveCloudflaredPath(): string | null {
if (_cloudflaredPath !== null) return _cloudflaredPath || null;
for (const dir of CLOUDFLARED_SEARCH_DIRS) {
const candidate = join(dir, 'cloudflared');
if (existsSync(candidate)) {
_cloudflaredPath = candidate;
return candidate;
}
}
try {
const result = execSync('which cloudflared', { encoding: 'utf-8', timeout: EXEC_TIMEOUT_MS }).trim();
if (result && existsSync(result)) {
_cloudflaredPath = result;
return result;
}
} catch {
// Not on PATH either.
}
_cloudflaredPath = ''; // mark as searched, not found
return null;
}
/**
* Check if cloudflared is available on the system.
*/
export function isCloudflaredAvailable(): boolean {
return resolveCloudflaredPath() !== null;
}