@@ -236,20 +236,50 @@ function centerBlock(block: string): string {
236236 return lines . map ( line => indent + line ) . join ( '\n' )
237237}
238238
239- function openBrowser ( url : string ) : void {
239+ /**
240+ * Resolve the command that opens `url`, honouring the de facto `BROWSER` and
241+ * `BROWSER_ARGS` environment variables (`BROWSER=none` disables opening).
242+ */
243+ export function resolveOpenCommand (
244+ url : string ,
245+ platform : NodeJS . Platform = process . platform ,
246+ env : NodeJS . ProcessEnv = process . env ,
247+ ) : [ command : string , args : string [ ] ] | undefined {
248+ const browser = env . BROWSER ?. trim ( )
249+ if ( browser === 'none' ) {
250+ return
251+ }
252+
253+ if ( browser ) {
254+ const browserArgs = env . BROWSER_ARGS ?. trim ( ) . split ( / \s + / ) . filter ( Boolean ) ?? [ ]
255+ return platform === 'darwin' && ! browser . includes ( '/' )
256+ ? [ 'open' , [ '-a' , browser , url , ...( browserArgs . length > 0 ? [ '--args' , ...browserArgs ] : [ ] ) ] ]
257+ : [ browser , [ ...browserArgs , url ] ]
258+ }
259+
240260 // WSL reports itself as Linux but has no X server, so `xdg-open` fails there;
241261 // `cmd.exe` opens the browser on the Windows side instead. WSL1 spells the
242262 // release `Microsoft`, WSL2 `microsoft-standard-WSL2`.
243- const isWSL = process . platform === 'linux'
244- && ( ! ! process . env . WSL_DISTRO_NAME || release ( ) . toLowerCase ( ) . includes ( 'microsoft' ) )
245-
246- const [ command , args ] = process . platform === 'darwin'
247- ? [ 'open' , [ url ] ]
248- // `cmd /c start` owns the arcane quoting rules; `""` is a dummy window title
249- // (otherwise `start` treats a quoted URL as one), and `&`/`^` need escaping.
250- : process . platform === 'win32' || isWSL
251- ? [ 'cmd.exe' , [ '/c' , 'start' , '""' , url . replace ( / [ & ^ ] / g, '^$&' ) ] ]
252- : [ 'xdg-open' , [ url ] ] satisfies [ string , string [ ] ]
263+ const isWSL = platform === 'linux'
264+ && ( ! ! env . WSL_DISTRO_NAME || release ( ) . toLowerCase ( ) . includes ( 'microsoft' ) )
265+
266+ if ( platform === 'darwin' ) {
267+ return [ 'open' , [ url ] ]
268+ }
269+ // `cmd /c start` owns the arcane quoting rules; `""` is a dummy window title
270+ // (otherwise `start` treats a quoted URL as one), and `&`/`^` need escaping.
271+ if ( platform === 'win32' || isWSL ) {
272+ return [ 'cmd.exe' , [ '/c' , 'start' , '""' , url . replace ( / [ & ^ ] / g, '^$&' ) ] ]
273+ }
274+ return [ 'xdg-open' , [ url ] ]
275+ }
276+
277+ function openBrowser ( url : string ) : void {
278+ const resolved = resolveOpenCommand ( url )
279+ if ( ! resolved ) {
280+ return
281+ }
282+ const [ command , args ] = resolved
253283 try {
254284 spawn ( command , args , { stdio : 'ignore' , detached : true } ) . on ( 'error' , error => debug ( 'Failed to open browser:' , error ) ) . unref ( )
255285 }
0 commit comments