173 lines
4.5 KiB
JavaScript
173 lines
4.5 KiB
JavaScript
'use strict';
|
||
const {promisify} = require('util');
|
||
const path = require('path');
|
||
const childProcess = require('child_process');
|
||
const fs = require('fs');
|
||
const isWsl = require('is-wsl');
|
||
const isDocker = require('is-docker');
|
||
|
||
const pAccess = promisify(fs.access);
|
||
const pExecFile = promisify(childProcess.execFile);
|
||
|
||
// Path to included `xdg-open`.
|
||
const localXdgOpenPath = path.join(__dirname, 'xdg-open');
|
||
|
||
// Convert a path from WSL format to Windows format:
|
||
// `/mnt/c/Program Files/Example/MyApp.exe` → `C:\Program Files\Example\MyApp.exe`
|
||
const wslToWindowsPath = async path => {
|
||
const {stdout} = await pExecFile('wslpath', ['-w', path]);
|
||
return stdout.trim();
|
||
};
|
||
|
||
// Convert a path from Windows format to WSL format
|
||
const windowsToWslPath = async path => {
|
||
const {stdout} = await pExecFile('wslpath', [path]);
|
||
return stdout.trim();
|
||
};
|
||
|
||
// Get an environment variable from Windows
|
||
const wslGetWindowsEnvVar = async envVar => {
|
||
const {stdout} = await pExecFile('wslvar', [envVar]);
|
||
return stdout.trim();
|
||
};
|
||
|
||
module.exports = async (target, options) => {
|
||
if (typeof target !== 'string') {
|
||
throw new TypeError('Expected a `target`');
|
||
}
|
||
|
||
options = {
|
||
wait: false,
|
||
background: false,
|
||
allowNonzeroExitCode: false,
|
||
...options
|
||
};
|
||
|
||
let command;
|
||
let {app} = options;
|
||
let appArguments = [];
|
||
const cliArguments = [];
|
||
const childProcessOptions = {};
|
||
|
||
if (Array.isArray(app)) {
|
||
appArguments = app.slice(1);
|
||
app = app[0];
|
||
}
|
||
|
||
if (process.platform === 'darwin') {
|
||
command = 'open';
|
||
|
||
if (options.wait) {
|
||
cliArguments.push('--wait-apps');
|
||
}
|
||
|
||
if (options.background) {
|
||
cliArguments.push('--background');
|
||
}
|
||
|
||
if (app) {
|
||
cliArguments.push('-a', app);
|
||
}
|
||
} else if (process.platform === 'win32' || (isWsl && !isDocker())) {
|
||
const windowsRoot = isWsl ? await wslGetWindowsEnvVar('systemroot') : process.env.SYSTEMROOT;
|
||
command = String.raw`${windowsRoot}\System32\WindowsPowerShell\v1.0\powershell${isWsl ? '.exe' : ''}`;
|
||
cliArguments.push(
|
||
'-NoProfile',
|
||
'-NonInteractive',
|
||
'–ExecutionPolicy',
|
||
'Bypass',
|
||
'-EncodedCommand'
|
||
);
|
||
|
||
if (isWsl) {
|
||
command = await windowsToWslPath(command);
|
||
} else {
|
||
childProcessOptions.windowsVerbatimArguments = true;
|
||
}
|
||
|
||
const encodedArguments = ['Start'];
|
||
|
||
if (options.wait) {
|
||
encodedArguments.push('-Wait');
|
||
}
|
||
|
||
if (app) {
|
||
if (isWsl && app.startsWith('/')) {
|
||
const windowsPath = await wslToWindowsPath(app);
|
||
app = windowsPath;
|
||
}
|
||
|
||
// Double quote with double quotes to ensure the inner quotes are passed through.
|
||
// Inner quotes are delimited for PowerShell interpretation with backticks.
|
||
encodedArguments.push(`"\`"${app}\`""`, '-ArgumentList');
|
||
appArguments.unshift(target);
|
||
} else {
|
||
encodedArguments.push(`"\`"${target}\`""`);
|
||
}
|
||
|
||
if (appArguments.length > 0) {
|
||
appArguments = appArguments.map(arg => `"\`"${arg}\`""`);
|
||
encodedArguments.push(appArguments.join(','));
|
||
}
|
||
|
||
// Using Base64-encoded command, accepted by PowerShell, to allow special characters.
|
||
target = Buffer.from(encodedArguments.join(' '), 'utf16le').toString('base64');
|
||
} else {
|
||
if (app) {
|
||
command = app;
|
||
} else {
|
||
// When bundled by Webpack, there's no actual package file path and no local `xdg-open`.
|
||
const isBundled = !__dirname || __dirname === '/';
|
||
|
||
// Check if local `xdg-open` exists and is executable.
|
||
let exeLocalXdgOpen = false;
|
||
try {
|
||
await pAccess(localXdgOpenPath, fs.constants.X_OK);
|
||
exeLocalXdgOpen = true;
|
||
} catch (_) {}
|
||
|
||
const useSystemXdgOpen = process.versions.electron ||
|
||
process.platform === 'android' || isBundled || !exeLocalXdgOpen;
|
||
command = useSystemXdgOpen ? 'xdg-open' : localXdgOpenPath;
|
||
}
|
||
|
||
if (appArguments.length > 0) {
|
||
cliArguments.push(...appArguments);
|
||
}
|
||
|
||
if (!options.wait) {
|
||
// `xdg-open` will block the process unless stdio is ignored
|
||
// and it's detached from the parent even if it's unref'd.
|
||
childProcessOptions.stdio = 'ignore';
|
||
childProcessOptions.detached = true;
|
||
}
|
||
}
|
||
|
||
cliArguments.push(target);
|
||
|
||
if (process.platform === 'darwin' && appArguments.length > 0) {
|
||
cliArguments.push('--args', ...appArguments);
|
||
}
|
||
|
||
const subprocess = childProcess.spawn(command, cliArguments, childProcessOptions);
|
||
|
||
if (options.wait) {
|
||
return new Promise((resolve, reject) => {
|
||
subprocess.once('error', reject);
|
||
|
||
subprocess.once('close', exitCode => {
|
||
if (options.allowNonzeroExitCode && exitCode > 0) {
|
||
reject(new Error(`Exited with code ${exitCode}`));
|
||
return;
|
||
}
|
||
|
||
resolve(subprocess);
|
||
});
|
||
});
|
||
}
|
||
|
||
subprocess.unref();
|
||
|
||
return subprocess;
|
||
};
|