Files
atom/src/update-process-env.js
Xavier Delaruelle 1cc2f2d8e2 Skip shell functions when parsing env
`getEnvFromShell` function calls `env` command through shell to get all
defined environment variable. However `env` also returns the shell
function defined with their whole code written on multiple lines.

Such shell function definitions were not properly handled by
`getEnvFromShell` which led to the following kind of error messages
(seen for instance when running a terminal package in Atom):

  bash: module: line 1: syntax error: unexpected end of file
  bash: error importing function definition for `BASH_FUNC_module'

With this change `getEnvFromShell` now skips shell function definition
to guarantee only environment variables are recorded and a sane `result`
array is returned.

Fixes #20389
Fixes #17369
Fixes #13451
Fixes blueimp/atom-open-terminal-here#27
Fixes blueimp/atom-open-terminal-here#18
Fixes bus-stop/Termination#101
Fixes bus-stop/terminus#24
Fixes platformio/platformio-atom-ide-terminal#120
Fixes platformio/platformio-atom-ide-terminal#293
Fixes AtomLinter/linter-pylint#243
Fixes AtomLinter/linter-flake8#643
Fixes AtomLinter/linter-flake8#165
Fixes AtomLinter/linter-flake8#422
Fixes AtomLinter/linter-puppet-lint#68
Fixes autocomplete-python/autocomplete-python#347
2020-02-08 23:50:58 +01:00

145 lines
3.3 KiB
JavaScript

const fs = require('fs');
const childProcess = require('child_process');
const ENVIRONMENT_VARIABLES_TO_PRESERVE = new Set([
'NODE_ENV',
'NODE_PATH',
'ATOM_HOME',
'ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT'
]);
const PLATFORMS_KNOWN_TO_WORK = new Set(['darwin', 'linux']);
async function updateProcessEnv(launchEnv) {
let envToAssign;
if (launchEnv) {
if (shouldGetEnvFromShell(launchEnv)) {
envToAssign = await getEnvFromShell(launchEnv);
} else if (launchEnv.PWD || launchEnv.PROMPT || launchEnv.PSModulePath) {
envToAssign = launchEnv;
}
}
if (envToAssign) {
for (let key in process.env) {
if (!ENVIRONMENT_VARIABLES_TO_PRESERVE.has(key)) {
delete process.env[key];
}
}
for (let key in envToAssign) {
if (
!ENVIRONMENT_VARIABLES_TO_PRESERVE.has(key) ||
(!process.env[key] && envToAssign[key])
) {
process.env[key] = envToAssign[key];
}
}
if (envToAssign.ATOM_HOME && fs.existsSync(envToAssign.ATOM_HOME)) {
process.env.ATOM_HOME = envToAssign.ATOM_HOME;
}
}
}
function shouldGetEnvFromShell(env) {
if (!PLATFORMS_KNOWN_TO_WORK.has(process.platform)) {
return false;
}
if (!env || !env.SHELL || env.SHELL.trim() === '') {
return false;
}
const disableSellingOut =
env.ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT ||
process.env.ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT;
if (disableSellingOut === 'true') {
return false;
}
return true;
}
async function getEnvFromShell(env) {
let { stdout, error } = await new Promise(resolve => {
let child;
let error;
let stdout = '';
let done = false;
const cleanup = () => {
if (!done && child) {
child.kill();
done = true;
}
};
process.once('exit', cleanup);
setTimeout(() => {
cleanup();
}, 5000);
child = childProcess.spawn(env.SHELL, ['-ilc', 'command env'], {
encoding: 'utf8',
detached: true,
stdio: ['ignore', 'pipe', process.stderr]
});
const buffers = [];
child.on('error', e => {
done = true;
error = e;
});
child.stdout.on('data', data => {
buffers.push(data);
});
child.on('close', (code, signal) => {
done = true;
process.removeListener('exit', cleanup);
if (buffers.length) {
stdout = Buffer.concat(buffers).toString('utf8');
}
resolve({ stdout, error });
});
});
if (error) {
if (error.handle) {
error.handle();
}
console.log(
'warning: ' +
env.SHELL +
' -ilc "command env" failed with signal (' +
error.signal +
')'
);
console.log(error);
}
if (!stdout || stdout.trim() === '') {
return null;
}
let result = {};
let skip = false;
for (let line of stdout.split('\n')) {
// start of shell function definition: skip full definition
if (line.includes('=() {')) {
skip = true;
}
if (!skip && line.includes('=')) {
let components = line.split('=');
let key = components.shift();
let value = components.join('=');
result[key] = value;
}
// end of shell function definition
if (line === '}') {
skip = false;
}
}
return result;
}
module.exports = { updateProcessEnv, shouldGetEnvFromShell };