-
-
Notifications
You must be signed in to change notification settings - Fork 2
/
index.ts
162 lines (149 loc) · 4.56 KB
/
index.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
// based on https://github.com/facebook/create-react-app/blob/master/packages/react-dev-utils/openBrowser.js
import { execSync } from 'node:child_process'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
import spawn from 'cross-spawn'
import picocolors from 'picocolors'
// https://github.com/sindresorhus/open#app
const OSX_CHROME = 'google chrome'
enum Action {
NONE,
BROWSER,
SCRIPT,
}
function getBrowserEnv() {
// Attempt to honor this environment variable.
// It is specific to the operating system.
// See https://github.com/sindresorhus/open#app for documentation.
const value = process.env.BROWSER
const args = process.env.BROWSER_ARGS
? process.env.BROWSER_ARGS.split(' ')
: []
let action: Action
if (!value) {
// Default.
action = Action.BROWSER
} else if (value.toLowerCase().endsWith('.js')) {
action = Action.SCRIPT
} else if (value.toLowerCase() === 'none') {
action = Action.NONE
} else {
action = Action.BROWSER
}
return { action, value, args }
}
function executeNodeScript(scriptPath: string, url: string) {
const extraArgs = process.argv.slice(2)
const child = spawn(process.execPath, [scriptPath, ...extraArgs, url], {
stdio: 'inherit',
})
child.on('close', code => {
if (code !== 0) {
console.log()
console.log(
picocolors.red(
'The script specified as BROWSER environment variable failed.',
),
)
console.log(`${picocolors.cyan(scriptPath)} exited with code ${code!}`)
console.log()
}
})
return true
}
async function startBrowserProcess(
browser: string[] | string | undefined,
url: string,
args: string[],
) {
// If we're on OS X, the user hasn't specifically
// requested a different browser, we can try opening
// Chrome with AppleScript. This lets us reuse an
// existing tab when possible instead of creating a new one.
const shouldTryOpenChromiumWithAppleScript =
process.platform === 'darwin' &&
(typeof browser !== 'string' || browser === OSX_CHROME)
if (shouldTryOpenChromiumWithAppleScript) {
// Will use the first open browser found from list
const supportedChromiumBrowsers = [
'Google Chrome Canary',
'Google Chrome',
'Microsoft Edge',
'Brave Browser',
'Vivaldi',
'Chromium',
]
const _dirname =
typeof __dirname === 'undefined'
? path.dirname(fileURLToPath(import.meta.url))
: __dirname
for (const chromiumBrowser of supportedChromiumBrowsers) {
try {
// Try our best to reuse existing tab
// on OSX Chromium-based browser with AppleScript
execSync('ps cax | grep "' + chromiumBrowser + '"')
execSync(
'osascript ../openChrome.applescript "' + // lgtm [js/shell-command-constructed-from-input]
encodeURI(url) +
'" "' +
chromiumBrowser +
'"',
{
cwd: _dirname,
stdio: 'ignore',
},
)
return true
} catch {
// Ignore errors.
}
}
}
// Another special case: on OS X, check if BROWSER has been set to "open".
// In this case, instead of passing `open` to `opn` (which won't work),
// just ignore it (thus ensuring the intended behavior, i.e. opening the system browser):
// https://github.com/facebook/create-react-app/pull/1690#issuecomment-283518768
if (process.platform === 'darwin' && browser === 'open') {
browser = undefined
}
// Fallback to open
// (It will always open new tab)
try {
// eslint-disable-next-line unicorn/no-await-expression-member
const open = (await import('open')).default
open(url, {
app: browser
? {
name: browser,
arguments: args,
}
: undefined,
wait: false,
}).catch(() => {}) // eslint-disable-line @typescript-eslint/no-empty-function -- Prevent `unhandledRejection` error.
return true
} catch {
return false
}
}
/**
* Reads the BROWSER environment variable and decides what to do with it. Returns
* true if it opened a browser or ran a node.js script, otherwise false.
*/
export async function openBrowser(url: string) {
const { action, value, args } = getBrowserEnv()
switch (action) {
case Action.NONE: {
// Special case: BROWSER="none" will prevent opening completely.
return false
}
case Action.SCRIPT: {
return executeNodeScript(value!, url)
}
case Action.BROWSER: {
return startBrowserProcess(value, url, args)
}
default: {
throw new Error('Not implemented.')
}
}
}