UNPKG

@swell/cli

Version:

Swell's command line interface/utility

89 lines (88 loc) 3.24 kB
import { bin, install, Tunnel } from 'cloudflared'; import localtunnel from 'localtunnel'; import * as fs from 'node:fs/promises'; import ora from 'ora'; import { LOCAL_PROXY_PROVIDER } from './constants.js'; let activeTunnel = null; let cleanupRegistered = false; async function startCloudflaredTunnel(port) { // Stop existing tunnel if called again if (activeTunnel) { activeTunnel.stop(); activeTunnel = null; } // Ensure cloudflared binary is installed const exists = await fs.access(bin, fs.constants.X_OK).then(() => true, (_error) => false); if (!exists) { const spinner = ora('Installing cloudflared tunnel (first time only)...').start(); await install(bin); spinner.stop(); } // Use Tunnel.quick() for quick tunnels without a Cloudflare account const tunnel = Tunnel.quick(`http://localhost:${port}`); activeTunnel = tunnel; // Wait for the URL to be available const url = await new Promise((resolve, reject) => { const timeout = setTimeout(() => { tunnel.stop(); activeTunnel = null; reject(new Error('Timeout waiting for cloudflared tunnel URL. Check if cloudflared can connect to Cloudflare.')); }, 60000); tunnel.on('url', (tunnelUrl) => { clearTimeout(timeout); resolve(tunnelUrl); }); tunnel.on('error', (error) => { clearTimeout(timeout); activeTunnel = null; reject(error); }); }); // Register cleanup handlers only once, referencing activeTunnel for current tunnel if (!cleanupRegistered) { cleanupRegistered = true; process.on('exit', () => activeTunnel?.stop()); process.on('SIGINT', () => { activeTunnel?.stop(); // eslint-disable-next-line no-process-exit process.exit(); }); process.on('SIGTERM', () => { activeTunnel?.stop(); // eslint-disable-next-line no-process-exit process.exit(); }); } return url; } export async function getProxyUrl(port, context) { const provider = LOCAL_PROXY_PROVIDER || 'cloudflared'; // URL template mode: resolve placeholders and return directly if (provider.includes('://')) { return provider .replace('{port}', String(port)) .replace('{appid}', context?.appId || '') .replace('{storeid}', context?.storeId || '') .replace('{storefrontid}', context?.storefrontId || ''); } try { switch (provider) { case 'local': { return `http://localhost:${port}`; } case 'localtunnel': { const tunnel = await localtunnel({ port }); return tunnel.url; } // eslint-disable-next-line unicorn/no-useless-switch-case case 'cloudflared': default: { return await startCloudflaredTunnel(port); } } } catch (error) { console.error(error); throw new Error(`Unable to start tunnel on port ${port} (${provider}): ${error.message}`, { cause: error }); } }