'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 pReadFile = promisify(fs.readFile); // Path to included `xdg-open`. const localXdgOpenPath = path.join(__dirname, 'xdg-open'); /** Get the mount point for fixed drives in WSL. @inner @returns {string} The mount point. */ const getWslDrivesMountPoint = (() => { // Default value for "root" param // according to https://docs.microsoft.com/en-us/windows/wsl/wsl-config const defaultMountPoint = '/mnt/'; let mountPoint; return async function () { if (mountPoint) { // Return memoized mount point value return mountPoint; } const configFilePath = '/etc/wsl.conf'; let isConfigFileExists = false; try { await pAccess(configFilePath, fs.constants.F_OK); isConfigFileExists = true; } catch (_) {} if (!isConfigFileExists) { return defaultMountPoint; } const configContent = await pReadFile(configFilePath, {encoding: 'utf8'}); const configMountPoint = /root\s*=\s*(.*)/g.exec(configContent); if (!configMountPoint) { return defaultMountPoint; } mountPoint = configMountPoint[1].trim(); mountPoint = mountPoint.endsWith('/') ? mountPoint : mountPoint + '/'; return mountPoint; }; })(); 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 mountPoint = await getWslDrivesMountPoint(); command = isWsl ? `${mountPoint}c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe` : `${process.env.SYSTEMROOT}\\System32\\WindowsPowerShell\\v1.0\\powershell`; cliArguments.push( '-NoProfile', '-NonInteractive', '–ExecutionPolicy', 'Bypass', '-EncodedCommand' ); if (!isWsl) { childProcessOptions.windowsVerbatimArguments = true; } const encodedArguments = ['Start']; if (options.wait) { encodedArguments.push('-Wait'); } if (app) { // 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; };