|
|
const path = require('path'); const childProcess = require('child_process'); const {promises: fs, constants: fsConstants} = require('fs'); const isWsl = require('is-wsl'); const isDocker = require('is-docker'); const defineLazyProperty = require('define-lazy-prop');
// Path to included `xdg-open`.
const localXdgOpenPath = path.join(__dirname, 'xdg-open');
const {platform, arch} = process;
// Podman detection
const hasContainerEnv = () => { try { fs.statSync('/run/.containerenv'); return true; } catch { return false; } };
let cachedResult; function isInsideContainer() { if (cachedResult === undefined) { cachedResult = hasContainerEnv() || isDocker(); }
return cachedResult; }
/** 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 fs.access(configFilePath, fsConstants.F_OK); isConfigFileExists = true; } catch {}
if (!isConfigFileExists) { return defaultMountPoint; }
const configContent = await fs.readFile(configFilePath, {encoding: 'utf8'}); const configMountPoint = /(?<!#.*)root\s*=\s*(?<mountPoint>.*)/g.exec(configContent);
if (!configMountPoint) { return defaultMountPoint; }
mountPoint = configMountPoint.groups.mountPoint.trim(); mountPoint = mountPoint.endsWith('/') ? mountPoint : `${mountPoint}/`;
return mountPoint; }; })();
const pTryEach = async (array, mapper) => { let latestError;
for (const item of array) { try { return await mapper(item); // eslint-disable-line no-await-in-loop
} catch (error) { latestError = error; } }
throw latestError; };
const baseOpen = async options => { options = { wait: false, background: false, newInstance: false, allowNonzeroExitCode: false, ...options };
if (Array.isArray(options.app)) { return pTryEach(options.app, singleApp => baseOpen({ ...options, app: singleApp })); }
let {name: app, arguments: appArguments = []} = options.app || {}; appArguments = [...appArguments];
if (Array.isArray(app)) { return pTryEach(app, appName => baseOpen({ ...options, app: { name: appName, arguments: appArguments } })); }
let command; const cliArguments = []; const childProcessOptions = {};
if (platform === 'darwin') { command = 'open';
if (options.wait) { cliArguments.push('--wait-apps'); }
if (options.background) { cliArguments.push('--background'); }
if (options.newInstance) { cliArguments.push('--new'); }
if (app) { cliArguments.push('-a', app); } } else if (platform === 'win32' || (isWsl && !isInsideContainer() && !app)) { 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'); if (options.target) { appArguments.unshift(options.target); } } else if (options.target) { encodedArguments.push(`"${options.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.
options.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 fs.access(localXdgOpenPath, fsConstants.X_OK); exeLocalXdgOpen = true; } catch {}
const useSystemXdgOpen = process.versions.electron || 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; } }
if (options.target) { cliArguments.push(options.target); }
if (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; };
const open = (target, options) => { if (typeof target !== 'string') { throw new TypeError('Expected a `target`'); }
return baseOpen({ ...options, target }); };
const openApp = (name, options) => { if (typeof name !== 'string') { throw new TypeError('Expected a `name`'); }
const {arguments: appArguments = []} = options || {}; if (appArguments !== undefined && appArguments !== null && !Array.isArray(appArguments)) { throw new TypeError('Expected `appArguments` as Array type'); }
return baseOpen({ ...options, app: { name, arguments: appArguments } }); };
function detectArchBinary(binary) { if (typeof binary === 'string' || Array.isArray(binary)) { return binary; }
const {[arch]: archBinary} = binary;
if (!archBinary) { throw new Error(`${arch} is not supported`); }
return archBinary; }
function detectPlatformBinary({[platform]: platformBinary}, {wsl}) { if (wsl && isWsl) { return detectArchBinary(wsl); }
if (!platformBinary) { throw new Error(`${platform} is not supported`); }
return detectArchBinary(platformBinary); }
const apps = {};
defineLazyProperty(apps, 'chrome', () => detectPlatformBinary({ darwin: 'google chrome', win32: 'chrome', linux: ['google-chrome', 'google-chrome-stable', 'chromium'] }, { wsl: { ia32: '/mnt/c/Program Files (x86)/Google/Chrome/Application/chrome.exe', x64: ['/mnt/c/Program Files/Google/Chrome/Application/chrome.exe', '/mnt/c/Program Files (x86)/Google/Chrome/Application/chrome.exe'] } }));
defineLazyProperty(apps, 'firefox', () => detectPlatformBinary({ darwin: 'firefox', win32: 'C:\\Program Files\\Mozilla Firefox\\firefox.exe', linux: 'firefox' }, { wsl: '/mnt/c/Program Files/Mozilla Firefox/firefox.exe' }));
defineLazyProperty(apps, 'edge', () => detectPlatformBinary({ darwin: 'microsoft edge', win32: 'msedge', linux: ['microsoft-edge', 'microsoft-edge-dev'] }, { wsl: '/mnt/c/Program Files (x86)/Microsoft/Edge/Application/msedge.exe' }));
open.apps = apps; open.openApp = openApp;
module.exports = open;
|