UNPKG

@pkerschbaum/code-oss-file-service

Version:

VS Code ([microsoft/vscode](https://github.com/microsoft/vscode)) includes a rich "`FileService`" and "`DiskFileSystemProvider`" abstraction built on top of Node.js core modules (`fs`, `path`) and Electron's `shell` module. This package allows to use that

498 lines 20.8 kB
"use strict"; /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.win32 = exports.createQueuedSender = exports.LineProcess = exports.AbstractProcess = exports.getWindowsShell = exports.removeDangerousEnvVariables = exports.TerminateResponseCode = exports.Source = void 0; const cp = require("child_process"); const extpath = require("../../base/common/extpath"); const network_1 = require("../../base/common/network"); const Objects = require("../../base/common/objects"); const path = require("../../base/common/path"); const Platform = require("../../base/common/platform"); const process = require("../../base/common/process"); const processes_1 = require("../../base/common/processes"); Object.defineProperty(exports, "Source", { enumerable: true, get: function () { return processes_1.Source; } }); Object.defineProperty(exports, "TerminateResponseCode", { enumerable: true, get: function () { return processes_1.TerminateResponseCode; } }); const Types = require("../../base/common/types"); const decoder_1 = require("../../base/node/decoder"); const pfs = require("../../base/node/pfs"); const nls = require("../../nls"); function getWindowsCode(status) { switch (status) { case 0: return 0 /* Success */; case 1: return 2 /* AccessDenied */; case 128: return 3 /* ProcessNotFound */; default: return 1 /* Unknown */; } } function terminateProcess(process, cwd) { if (Platform.isWindows) { try { const options = { stdio: ['pipe', 'pipe', 'ignore'] }; if (cwd) { options.cwd = cwd; } const killProcess = cp.execFile('taskkill', ['/T', '/F', '/PID', process.pid.toString()], options); return new Promise(resolve => { killProcess.once('error', (err) => { resolve({ success: false, error: err }); }); killProcess.once('exit', (code, signal) => { if (code === 0) { resolve({ success: true }); } else { resolve({ success: false, code: code !== null ? code : 1 /* Unknown */ }); } }); }); } catch (err) { return Promise.resolve({ success: false, error: err, code: err.status ? getWindowsCode(err.status) : 1 /* Unknown */ }); } } else if (Platform.isLinux || Platform.isMacintosh) { try { const cmd = network_1.FileAccess.asFileUri('vs/base/node/terminateProcess.sh', require).fsPath; return new Promise(resolve => { cp.execFile(cmd, [process.pid.toString()], { encoding: 'utf8', shell: true }, (err, stdout, stderr) => { if (err) { resolve({ success: false, error: err }); } else { resolve({ success: true }); } }); }); } catch (err) { return Promise.resolve({ success: false, error: err }); } } else { process.kill('SIGKILL'); } return Promise.resolve({ success: true }); } /** * Remove dangerous environment variables that have caused crashes * in forked processes (i.e. in ELECTRON_RUN_AS_NODE processes) * * @param env The env object to change */ function removeDangerousEnvVariables(env) { if (!env) { return; } // Unset `DEBUG`, as an invalid value might lead to process crashes // See https://github.com/microsoft/vscode/issues/130072 delete env['DEBUG']; if (Platform.isMacintosh) { // Unset `DYLD_LIBRARY_PATH`, as it leads to process crashes // See https://github.com/microsoft/vscode/issues/104525 // See https://github.com/microsoft/vscode/issues/105848 delete env['DYLD_LIBRARY_PATH']; } if (Platform.isLinux) { // Unset `LD_PRELOAD`, as it might lead to process crashes // See https://github.com/microsoft/vscode/issues/134177 delete env['LD_PRELOAD']; } } exports.removeDangerousEnvVariables = removeDangerousEnvVariables; function getWindowsShell(env = process.env) { return env['comspec'] || 'cmd.exe'; } exports.getWindowsShell = getWindowsShell; class AbstractProcess { constructor(arg1, arg2, arg3, arg4) { if (arg2 !== undefined && arg3 !== undefined && arg4 !== undefined) { this.cmd = arg1; this.args = arg2; this.shell = arg3; this.options = arg4; } else { const executable = arg1; this.cmd = executable.command; this.shell = executable.isShellCommand; this.args = executable.args.slice(0); this.options = executable.options || {}; } this.childProcess = null; this.childProcessPromise = null; this.terminateRequested = false; if (this.options.env) { const newEnv = Object.create(null); Object.keys(process.env).forEach((key) => { newEnv[key] = process.env[key]; }); Object.keys(this.options.env).forEach((key) => { newEnv[key] = this.options.env[key]; }); this.options.env = newEnv; } } getSanitizedCommand() { let result = this.cmd.toLowerCase(); const index = result.lastIndexOf(path.sep); if (index !== -1) { result = result.substring(index + 1); } if (AbstractProcess.WellKnowCommands[result]) { return result; } return 'other'; } start(pp) { if (Platform.isWindows && ((this.options && this.options.cwd && extpath.isUNC(this.options.cwd)) || !this.options && extpath.isUNC(process.cwd()))) { return Promise.reject(new Error(nls.localize('TaskRunner.UNC', 'Can\'t execute a shell command on a UNC drive.'))); } return this.useExec().then((useExec) => { let cc; let ee; const result = new Promise((c, e) => { cc = c; ee = e; }); if (useExec) { let cmd = this.cmd; if (this.args) { cmd = cmd + ' ' + this.args.join(' '); } this.childProcess = cp.exec(cmd, this.options, (error, stdout, stderr) => { this.childProcess = null; const err = error; // This is tricky since executing a command shell reports error back in case the executed command return an // error or the command didn't exist at all. So we can't blindly treat an error as a failed command. So we // always parse the output and report success unless the job got killed. if (err && err.killed) { ee({ killed: this.terminateRequested, stdout: stdout.toString(), stderr: stderr.toString() }); } else { this.handleExec(cc, pp, error, stdout, stderr); } }); } else { let childProcess = null; const closeHandler = (data) => { this.childProcess = null; this.childProcessPromise = null; this.handleClose(data, cc, pp, ee); const result = { terminated: this.terminateRequested }; if (Types.isNumber(data)) { result.cmdCode = data; } cc(result); }; if (this.shell && Platform.isWindows) { const options = Objects.deepClone(this.options); options.windowsVerbatimArguments = true; options.detached = false; let quotedCommand = false; let quotedArg = false; const commandLine = []; let quoted = this.ensureQuotes(this.cmd); commandLine.push(quoted.value); quotedCommand = quoted.quoted; if (this.args) { this.args.forEach((elem) => { quoted = this.ensureQuotes(elem); commandLine.push(quoted.value); quotedArg = quotedArg && quoted.quoted; }); } const args = [ '/s', '/c', ]; if (quotedCommand) { if (quotedArg) { args.push('"' + commandLine.join(' ') + '"'); } else if (commandLine.length > 1) { args.push('"' + commandLine[0] + '"' + ' ' + commandLine.slice(1).join(' ')); } else { args.push('"' + commandLine[0] + '"'); } } else { args.push(commandLine.join(' ')); } childProcess = cp.spawn(getWindowsShell(), args, options); } else { if (this.cmd) { childProcess = cp.spawn(this.cmd, this.args, this.options); } } if (childProcess) { this.childProcess = childProcess; this.childProcessPromise = Promise.resolve(childProcess); if (this.pidResolve) { this.pidResolve(Types.isNumber(childProcess.pid) ? childProcess.pid : -1); this.pidResolve = undefined; } childProcess.on('error', (error) => { this.childProcess = null; ee({ terminated: this.terminateRequested, error: error }); }); if (childProcess.pid) { this.childProcess.on('close', closeHandler); this.handleSpawn(childProcess, cc, pp, ee, true); } } } return result; }); } handleClose(data, cc, pp, ee) { // Default is to do nothing. } ensureQuotes(value) { if (AbstractProcess.regexp.test(value)) { return { value: '"' + value + '"', quoted: true }; } else { return { value: value, quoted: value.length > 0 && value[0] === '"' && value[value.length - 1] === '"' }; } } get pid() { if (this.childProcessPromise) { return this.childProcessPromise.then(childProcess => childProcess.pid, err => -1); } else { return new Promise((resolve) => { this.pidResolve = resolve; }); } } terminate() { if (!this.childProcessPromise) { return Promise.resolve({ success: true }); } return this.childProcessPromise.then((childProcess) => { this.terminateRequested = true; return terminateProcess(childProcess, this.options.cwd).then(response => { if (response.success) { this.childProcess = null; } return response; }); }, (err) => { return { success: true }; }); } useExec() { return new Promise(resolve => { if (!this.shell || !Platform.isWindows) { return resolve(false); } const cmdShell = cp.spawn(getWindowsShell(), ['/s', '/c']); cmdShell.on('error', (error) => { return resolve(true); }); cmdShell.on('exit', (data) => { return resolve(false); }); }); } } exports.AbstractProcess = AbstractProcess; AbstractProcess.WellKnowCommands = { 'ant': true, 'cmake': true, 'eslint': true, 'gradle': true, 'grunt': true, 'gulp': true, 'jake': true, 'jenkins': true, 'jshint': true, 'make': true, 'maven': true, 'msbuild': true, 'msc': true, 'nmake': true, 'npm': true, 'rake': true, 'tsc': true, 'xbuild': true }; AbstractProcess.regexp = /^[^"].* .*[^"]/; class LineProcess extends AbstractProcess { constructor(arg1, arg2, arg3, arg4) { super(arg1, arg2, arg3, arg4); this.stdoutLineDecoder = null; this.stderrLineDecoder = null; } handleExec(cc, pp, error, stdout, stderr) { [stdout, stderr].forEach((buffer, index) => { const lineDecoder = new decoder_1.LineDecoder(); const lines = lineDecoder.write(buffer); lines.forEach((line) => { pp({ line: line, source: index === 0 ? 0 /* stdout */ : 1 /* stderr */ }); }); const line = lineDecoder.end(); if (line) { pp({ line: line, source: index === 0 ? 0 /* stdout */ : 1 /* stderr */ }); } }); cc({ terminated: this.terminateRequested, error: error }); } handleSpawn(childProcess, cc, pp, ee, sync) { const stdoutLineDecoder = new decoder_1.LineDecoder(); const stderrLineDecoder = new decoder_1.LineDecoder(); childProcess.stdout.on('data', (data) => { const lines = stdoutLineDecoder.write(data); lines.forEach(line => pp({ line: line, source: 0 /* stdout */ })); }); childProcess.stderr.on('data', (data) => { const lines = stderrLineDecoder.write(data); lines.forEach(line => pp({ line: line, source: 1 /* stderr */ })); }); this.stdoutLineDecoder = stdoutLineDecoder; this.stderrLineDecoder = stderrLineDecoder; } handleClose(data, cc, pp, ee) { const stdoutLine = this.stdoutLineDecoder ? this.stdoutLineDecoder.end() : null; if (stdoutLine) { pp({ line: stdoutLine, source: 0 /* stdout */ }); } const stderrLine = this.stderrLineDecoder ? this.stderrLineDecoder.end() : null; if (stderrLine) { pp({ line: stderrLine, source: 1 /* stderr */ }); } } } exports.LineProcess = LineProcess; // Wrapper around process.send() that will queue any messages if the internal node.js // queue is filled with messages and only continue sending messages when the internal // queue is free again to consume messages. // On Windows we always wait for the send() method to return before sending the next message // to workaround https://github.com/nodejs/node/issues/7657 (IPC can freeze process) function createQueuedSender(childProcess) { let msgQueue = []; let useQueue = false; const send = function (msg) { if (useQueue) { msgQueue.push(msg); // add to the queue if the process cannot handle more messages return; } const result = childProcess.send(msg, (error) => { if (error) { console.error(error); // unlikely to happen, best we can do is log this error } useQueue = false; // we are good again to send directly without queue // now send all the messages that we have in our queue and did not send yet if (msgQueue.length > 0) { const msgQueueCopy = msgQueue.slice(0); msgQueue = []; msgQueueCopy.forEach(entry => send(entry)); } }); if (!result || Platform.isWindows /* workaround https://github.com/nodejs/node/issues/7657 */) { useQueue = true; } }; return { send }; } exports.createQueuedSender = createQueuedSender; var win32; (function (win32) { function findExecutable(command, cwd, paths) { return __awaiter(this, void 0, void 0, function* () { // If we have an absolute path then we take it. if (path.isAbsolute(command)) { return command; } if (cwd === undefined) { cwd = process.cwd(); } const dir = path.dirname(command); if (dir !== '.') { // We have a directory and the directory is relative (see above). Make the path absolute // to the current working directory. return path.join(cwd, command); } if (paths === undefined && Types.isString(process.env['PATH'])) { paths = process.env['PATH'].split(path.delimiter); } // No PATH environment. Make path absolute to the cwd. if (paths === undefined || paths.length === 0) { return path.join(cwd, command); } function fileExists(path) { return __awaiter(this, void 0, void 0, function* () { if (yield pfs.Promises.exists(path)) { let statValue; try { statValue = yield pfs.Promises.stat(path); } catch (e) { if (e.message.startsWith('EACCES')) { // it might be symlink statValue = yield pfs.Promises.lstat(path); } } return statValue ? !statValue.isDirectory() : false; } return false; }); } // We have a simple file name. We get the path variable from the env // and try to find the executable on the path. for (let pathEntry of paths) { // The path entry is absolute. let fullPath; if (path.isAbsolute(pathEntry)) { fullPath = path.join(pathEntry, command); } else { fullPath = path.join(cwd, pathEntry, command); } if (yield fileExists(fullPath)) { return fullPath; } let withExtension = fullPath + '.com'; if (yield fileExists(withExtension)) { return withExtension; } withExtension = fullPath + '.exe'; if (yield fileExists(withExtension)) { return withExtension; } } return path.join(cwd, command); }); } win32.findExecutable = findExecutable; })(win32 = exports.win32 || (exports.win32 = {})); //# sourceMappingURL=processes.js.map