node-ejs-renderer/node_modules/open/index.js
2024-06-09 13:55:01 -04:00

362 lines
8.7 KiB
JavaScript

import process from 'node:process';
import {Buffer} from 'node:buffer';
import path from 'node:path';
import {fileURLToPath} from 'node:url';
import childProcess from 'node:child_process';
import fs, {constants as fsConstants} from 'node:fs/promises';
import isWsl from 'is-wsl';
import defineLazyProperty from 'define-lazy-prop';
import defaultBrowser from 'default-browser';
import isInsideContainer from 'is-inside-container';
// Path to included `xdg-open`.
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const localXdgOpenPath = path.join(__dirname, 'xdg-open');
const {platform, arch} = process;
/**
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,
},
}));
}
if (app === 'browser' || app === 'browserPrivate') {
// IDs from default-browser for macOS and windows are the same
const ids = {
'com.google.chrome': 'chrome',
'google-chrome.desktop': 'chrome',
'org.mozilla.firefox': 'firefox',
'firefox.desktop': 'firefox',
'com.microsoft.msedge': 'edge',
'com.microsoft.edge': 'edge',
'microsoft-edge.desktop': 'edge',
};
// Incognito flags for each browser in `apps`.
const flags = {
chrome: '--incognito',
firefox: '--private-window',
edge: '--inPrivate',
};
const browser = await defaultBrowser();
if (browser.id in ids) {
const browserName = ids[browser.id];
if (app === 'browserPrivate') {
appArguments.push(flags[browserName]);
}
return baseOpen({
...options,
app: {
name: apps[browserName],
arguments: appArguments,
},
});
}
throw new Error(`${browser.name} is not supported as a default browser`);
}
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 || process.env.windir || 'C:\\Windows'}\\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}\`""`);
if (options.target) {
appArguments.push(options.target);
}
} else if (options.target) {
encodedArguments.push(`"${options.target}"`);
}
if (appArguments.length > 0) {
appArguments = appArguments.map(argument => `"\`"${argument}\`""`);
encodedArguments.push('-ArgumentList', 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 (platform === 'darwin' && appArguments.length > 0) {
cliArguments.push('--args', ...appArguments);
}
// This has to come after `--args`.
if (options.target) {
cliArguments.push(options.target);
}
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,
});
};
export const openApp = (name, options) => {
if (typeof name !== 'string' && !Array.isArray(name)) {
throw new TypeError('Expected a valid `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);
}
export 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',
}));
defineLazyProperty(apps, 'browser', () => 'browser');
defineLazyProperty(apps, 'browserPrivate', () => 'browserPrivate');
export default open;