UNPKG

@lenne.tech/cli

Version:

lenne.Tech CLI: lt

130 lines (129 loc) 5.65 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.TRYCLOUDFLARE_URL_PATTERN = void 0; exports.buildQuickTunnelArgs = buildQuickTunnelArgs; exports.cloudflaredAvailable = cloudflaredAvailable; exports.extractTrycloudflareUrl = extractTrycloudflareUrl; exports.spawnQuickTunnel = spawnQuickTunnel; /** * Cloudflare Tunnel integration for `lt dev tunnel`. * * Quick tunnels (no Cloudflare account, ephemeral URL) are the only * mode supported here. Named tunnels would require multi-step Cloudflare * setup (auth, DNS routing) which belongs in a separate command and is * intentionally out of scope for this lib. * * The Caddy upstream stays unchanged: cloudflared connects to Caddy's * HTTPS endpoint and rewrites the `Host` header to the configured * `*.localhost` so Caddy's per-project block matches. Without that * rewrite Cloudflare's edge would forward the random `*.trycloudflare.com` * hostname which Caddy doesn't know. */ const child_process_1 = require("child_process"); /** * Match the trycloudflare URL anywhere in cloudflared's log output. * * cloudflared prints it in an ASCII-box on stderr (Linux/macOS) — exported * here so tests can assert the exact pattern without spawning the binary. */ exports.TRYCLOUDFLARE_URL_PATTERN = /https:\/\/[a-z0-9][a-z0-9-]*\.trycloudflare\.com/i; /** * Build the argv list for `cloudflared tunnel --url ...`. Pure helper. * * Why each flag: * --url : the local upstream (Caddy's HTTPS endpoint) * --http-host-header: tells cloudflared to rewrite Host before forwarding, * so Caddy's vhost match works for the public URL * --no-tls-verify : Caddy serves a locally-signed cert that cloudflared * cannot validate from outside the local trust store; * disabling the check is safe because the upstream * hop never leaves localhost */ function buildQuickTunnelArgs(opts) { return [ 'tunnel', '--no-autoupdate', '--no-tls-verify', '--http-host-header', opts.hostHeader, '--url', opts.upstreamUrl, ]; } /** Detect whether `cloudflared` is on PATH. */ function cloudflaredAvailable() { return __awaiter(this, void 0, void 0, function* () { return new Promise((resolve) => { var _a; const child = (0, child_process_1.spawn)('cloudflared', ['--version'], { stdio: ['ignore', 'pipe', 'pipe'] }); let stdout = ''; (_a = child.stdout) === null || _a === void 0 ? void 0 : _a.on('data', (b) => (stdout += String(b))); child.on('error', () => resolve({ installed: false })); child.on('close', (code) => { if (code !== 0) return resolve({ installed: false }); const match = stdout.match(/version\s+(\S+)/i); resolve({ binary: 'cloudflared', installed: true, version: match === null || match === void 0 ? void 0 : match[1] }); }); }); }); } /** * Extract the trycloudflare URL from a chunk of cloudflared output. * Returns the first match (cloudflared logs the URL exactly once). */ function extractTrycloudflareUrl(output) { const match = output.match(exports.TRYCLOUDFLARE_URL_PATTERN); return match ? match[0] : null; } /** * Spawn a quick tunnel and resolve the public URL once cloudflared logs it. * * The returned `publicUrl` promise rejects if the child exits before * surfacing a URL (timeout: ~30s, then cloudflared usually emits an * error message and exits). The caller is expected to keep the * process alive (foreground command) and `child.kill()` on Ctrl-C. */ function spawnQuickTunnel(opts) { const args = buildQuickTunnelArgs(opts); const child = (0, child_process_1.spawn)('cloudflared', args, { stdio: ['ignore', 'pipe', 'pipe'] }); const publicUrl = new Promise((resolve, reject) => { var _a, _b; let buffer = ''; let settled = false; const onChunk = (chunk) => { if (settled) return; buffer += String(chunk); const url = extractTrycloudflareUrl(buffer); if (url) { settled = true; resolve(url); } }; (_a = child.stdout) === null || _a === void 0 ? void 0 : _a.on('data', onChunk); (_b = child.stderr) === null || _b === void 0 ? void 0 : _b.on('data', onChunk); child.on('error', (err) => { if (settled) return; settled = true; reject(err); }); child.on('close', (code) => { if (settled) return; settled = true; reject(new Error(`cloudflared exited (code ${code}) before publishing a tunnel URL.\n${buffer.slice(-500)}`)); }); }); return { child, publicUrl }; }