|
9 | 9 | import crypto from 'node:crypto'; |
10 | 10 | import http from 'node:http'; |
11 | 11 | import https from 'node:https'; |
12 | | -import { spawn } from 'node:child_process'; |
| 12 | +import { spawn, type ChildProcess } from 'node:child_process'; |
13 | 13 | import { URL } from 'node:url'; |
14 | 14 |
|
15 | 15 | // All three ports must be pre-registered in the Cognito App Client. |
@@ -76,24 +76,32 @@ function isPortFree(port: number): Promise<boolean> { |
76 | 76 | * Open a URL in the system browser. The URL is passed as an argument — not |
77 | 77 | * interpolated into a shell string — to avoid command injection. |
78 | 78 | */ |
79 | | -export function openBrowser(url: string): void { |
80 | | - // detached:true + stdio:'ignore' + unref() is the standard Node.js pattern for |
81 | | - // fire-and-forget child processes — the event loop will not wait for them to exit. |
82 | | - const spawnOpts = { detached: true, stdio: 'ignore' as const }; |
83 | | - let child; |
84 | | - switch (process.platform) { |
| 79 | +/** |
| 80 | + * Return the platform-specific command and argument list for opening a URL |
| 81 | + * in the system browser. Exported so tests can assert the correct command is |
| 82 | + * chosen for each platform without actually spawning a process. |
| 83 | + */ |
| 84 | +export function getBrowserCommand(url: string, platform: NodeJS.Platform = process.platform): { cmd: string; args: string[] } { |
| 85 | + switch (platform) { |
85 | 86 | case 'darwin': |
86 | | - child = spawn('open', [url], spawnOpts); |
87 | | - break; |
| 87 | + return { cmd: 'open', args: [url] }; |
88 | 88 | case 'win32': |
89 | 89 | // Pass the URL via $args[0] so it is never interpolated into the -Command |
90 | 90 | // string — avoids quote-breaking and injection risk from special characters. |
91 | | - child = spawn('powershell.exe', ['-NoProfile', '-Command', 'Start-Process $args[0]', '-args', url], spawnOpts); |
92 | | - break; |
| 91 | + return { cmd: 'powershell.exe', args: ['-NoProfile', '-Command', 'Start-Process $args[0]', '-args', url] }; |
93 | 92 | default: |
94 | | - child = spawn('xdg-open', [url], spawnOpts); |
95 | | - break; |
| 93 | + return { cmd: 'xdg-open', args: [url] }; |
96 | 94 | } |
| 95 | +} |
| 96 | + |
| 97 | +export function openBrowser(url: string): void { |
| 98 | + // detached:true + stdio:'ignore' + unref() is the standard Node.js pattern for |
| 99 | + // fire-and-forget child processes — the event loop will not wait for them to exit. |
| 100 | + const { cmd, args } = getBrowserCommand(url); |
| 101 | + const child: ChildProcess = spawn(cmd, args, { detached: true, stdio: 'ignore' }); |
| 102 | + // Suppress unhandled-error crashes if the browser executable is not found. |
| 103 | + // The login URL is already printed to the terminal so the user can open it manually. |
| 104 | + child.on('error', () => { /* intentional no-op */ }); |
97 | 105 | child.unref(); |
98 | 106 | } |
99 | 107 |
|
|
0 commit comments