-
Notifications
You must be signed in to change notification settings - Fork 9.2k
/
shell.ts
169 lines (142 loc) · 3.99 KB
/
shell.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
163
164
165
166
167
168
169
/* eslint-disable no-sync */
import * as ChildProcess from 'child_process'
import * as os from 'os'
type IndexLookup = {
[propName: string]: string
}
/**
* The names of any env vars that we shouldn't copy from the shell environment.
*/
const ExcludedEnvironmentVars = new Set(['LOCAL_GIT_DIRECTORY'])
/**
* Inspect whether the current process needs to be patched to get important
* environment variables for Desktop to work and integrate with other tools
* the user may invoke as part of their workflow.
*
* This is only applied to macOS installations due to how the application
* is launched.
*
* @param process The process to inspect.
*/
export function shellNeedsPatching(process: NodeJS.Process): boolean {
return __DARWIN__ && !process.env.PWD
}
type ShellResult = {
stdout: string
error: Error | null
}
/**
* Gets a dump of the user's configured shell environment.
*
* @returns the output of the `env` command or `null` if there was an error.
*/
async function getRawShellEnv(): Promise<string | null> {
const shell = getUserShell()
const promise = new Promise<ShellResult>(resolve => {
let child: ChildProcess.ChildProcess | null = null
let error: Error | null = null
let stdout = ''
let done = false
// ensure we clean up eventually, in case things go bad
const cleanup = () => {
if (!done && child) {
child.kill()
done = true
}
}
process.once('exit', cleanup)
setTimeout(() => {
cleanup()
}, 5000)
child = ChildProcess.spawn(shell, ['-ilc', 'command env'], {
detached: true,
stdio: ['ignore', 'pipe', process.stderr],
})
const buffers: Array<Buffer> = []
child.on('error', (e: Error) => {
done = true
error = e
})
// If Node.js encounters a synchronous runtime error while spawning
// `stdout` will be undefined and the error will be emitted asynchronously
if (child.stdout) {
child.stdout.on('data', (data: Buffer) => {
buffers.push(data)
})
}
child.on('close', (code: number, signal) => {
done = true
process.removeListener('exit', cleanup)
if (buffers.length) {
stdout = Buffer.concat(buffers).toString('utf8')
}
resolve({ stdout, error })
})
})
const { stdout, error } = await promise
if (error) {
// just swallow the error and move on with everything
return null
}
return stdout
}
function getUserShell() {
if (process.env.SHELL) {
return process.env.SHELL
}
return '/bin/bash'
}
/**
* Get the environment variables from the user's current shell and update the
* current environment.
*
* @param updateEnvironment a callback to fire if a valid environment is found
*/
async function getEnvironmentFromShell(
updateEnvironment: (env: IndexLookup) => void
): Promise<void> {
if (__WIN32__) {
return
}
const shellEnvText = await getRawShellEnv()
if (!shellEnvText) {
return
}
const env: IndexLookup = {}
for (const line of shellEnvText.split(os.EOL)) {
if (line.includes('=')) {
const components = line.split('=')
if (components.length === 2) {
env[components[0]] = components[1]
} else {
const k = components.shift()
const v = components.join('=')
if (k) {
env[k] = v
}
}
}
}
updateEnvironment(env)
}
/**
* Apply new environment variables to the current process, ignoring
* Node-specific environment variables which need to be preserved.
*
* @param env The new environment variables from the user's shell.
*/
function mergeEnvironmentVariables(env: IndexLookup) {
for (const key in env) {
if (ExcludedEnvironmentVars.has(key)) {
continue
}
process.env[key] = env[key]
}
}
/**
* Update the current process's environment variables using environment
* variables from the user's shell, if they can be retrieved successfully.
*/
export function updateEnvironmentForProcess(): Promise<void> {
return getEnvironmentFromShell(mergeEnvironmentVariables)
}