import { spawn } from 'child_process' export type OpenUrlLaunchVia = 'system' export interface OpenUrlOptions { detached?: boolean background?: boolean } export interface OpenUrlCommandOptions extends OpenUrlOptions { platform?: NodeJS.Platform } export interface OpenUrlCommand { command: string args: string[] via: OpenUrlLaunchVia target?: string } export interface OpenUrlLaunchResult extends OpenUrlCommand { pid?: number attachUrl: string } export function buildOpenUrlCommand( url: string, options: OpenUrlCommandOptions = {}, ): OpenUrlCommand { if (!url) throw new Error('openUrl requires a url') const platform = options.platform ?? process.platform if (platform === 'darwin') { return { command: 'open', args: options.background === false ? [url] : ['-g', url], via: 'system', } } if (platform === 'win32') { return { command: 'cmd', args: ['/c', 'start', '', url], via: 'system', } } return { command: 'xdg-open', args: [url], via: 'system', } } async function spawnLaunch( command: string, args: string[], detached: boolean, ): Promise { return new Promise((resolve, reject) => { const child = spawn(command, args, { detached, stdio: 'ignore', }) child.once('error', reject) child.once('spawn', () => { if (detached) child.unref() resolve(child.pid) }) }) } export async function launchUrl( url: string, options: OpenUrlOptions = {}, ): Promise { const command = buildOpenUrlCommand(url, options) const pid = await spawnLaunch(command.command, command.args, options.detached ?? true) return { ...command, pid, attachUrl: url, } } export async function openUrl(url: string, options: OpenUrlOptions = {}): Promise { await launchUrl(url, options) }