123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157 |
- '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();
- };
-
- 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())) {
- command = 'powershell' + (isWsl ? '.exe' : '');
- cliArguments.push(
- '-NoProfile',
- '-NonInteractive',
- '–ExecutionPolicy',
- 'Bypass',
- '-EncodedCommand'
- );
-
- if (!isWsl) {
- childProcessOptions.windowsVerbatimArguments = true;
- }
-
- const encodedArguments = ['Start'];
-
- if (options.wait) {
- encodedArguments.push('-Wait');
- }
-
- if (app) {
- if (isWsl && app.startsWith('/mnt/')) {
- 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;
- };
|