UNPKG

@shopify/cli-kit

Version:

A set of utilities, interfaces, and models that are common across all the platform features

178 lines 6.33 kB
import { AbortError, ExternalError } from './error.js'; import { cwd, dirname } from './path.js'; import { treeKill } from './tree-kill.js'; import { isTruthy } from './context/utilities.js'; import { renderWarning } from './ui.js'; import { platformAndArch } from './os.js'; import { shouldDisplayColors, outputDebug } from '../../public/node/output.js'; import { execa } from 'execa'; import which from 'which'; import { delimiter } from 'pathe'; /** * Opens a URL in the user's default browser. * * @param url - URL to open. * @returns A promise that resolves true if the URL was opened successfully, false otherwise. */ export async function openURL(url) { const externalOpen = await import('open'); try { await externalOpen.default(url); return true; // eslint-disable-next-line no-catch-all/no-catch-all } catch (error) { return false; } } /** * Runs a command asynchronously, aggregates the stdout data, and returns it. * * @param command - Command to be executed. * @param args - Arguments to pass to the command. * @param options - Optional settings for how to run the command. * @returns A promise that resolves with the aggregatted stdout of the command. */ export async function captureOutput(command, args, options) { const result = await buildExec(command, args, options); return result.stdout; } /** * Runs a command asynchronously. * * @param command - Command to be executed. * @param args - Arguments to pass to the command. * @param options - Optional settings for how to run the command. */ export async function exec(command, args, options) { if (options) { // Windows opens a new console window when running a command in the background, so we disable it. const runningOnWindows = platformAndArch().platform === 'windows'; options.background = runningOnWindows ? false : options.background; } const commandProcess = buildExec(command, args, options); if (options?.background) { commandProcess.unref(); } if (options?.stderr && options.stderr !== 'inherit') { commandProcess.stderr?.pipe(options.stderr, { end: false }); } if (options?.stdout && options.stdout !== 'inherit') { commandProcess.stdout?.pipe(options.stdout, { end: false }); } let aborted = false; options?.signal?.addEventListener('abort', () => { const pid = commandProcess.pid; if (pid) { outputDebug(`Killing process ${pid}: ${command} ${args.join(' ')}`); aborted = true; treeKill(pid, 'SIGTERM'); } }); try { await commandProcess; // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (processError) { // Windows will throw an error whenever the process is killed, no matter the reason. // The aborted flag tell use that we killed it, so we can ignore the error. if (aborted) return; if (options?.externalErrorHandler) { await options.externalErrorHandler(processError); } else { const abortError = new ExternalError(processError.message, command, args); abortError.stack = processError.stack; throw abortError; } } } /** * Runs a command asynchronously. * * @param command - Command to be executed. * @param args - Arguments to pass to the command. * @param options - Optional settings for how to run the command. * @returns A promise for a result with stdout and stderr properties. */ function buildExec(command, args, options) { const env = options?.env ?? process.env; if (shouldDisplayColors()) { env.FORCE_COLOR = '1'; } const executionCwd = options?.cwd ?? cwd(); checkCommandSafety(command, { cwd: executionCwd }); const commandProcess = execa(command, args, { env, cwd: executionCwd, input: options?.input, stdio: options?.background ? 'ignore' : options?.stdio, stdin: options?.stdin, stdout: options?.stdout === 'inherit' ? 'inherit' : undefined, stderr: options?.stderr === 'inherit' ? 'inherit' : undefined, // Setting this to false makes it possible to kill the main process // and all its sub-processes with Ctrl+C on Windows windowsHide: false, detached: options?.background, cleanup: !options?.background, }); outputDebug(`Running system process${options?.background ? ' in background' : ''}: · Command: ${command} ${args.join(' ')} · Working directory: ${executionCwd} `); return commandProcess; } function checkCommandSafety(command, _options) { const pathIncludingLocal = `${_options.cwd}${delimiter}${process.env.PATH}`; const commandPath = which.sync(command, { nothrow: true, path: pathIncludingLocal, }); if (commandPath && dirname(commandPath) === _options.cwd) { const headline = ['Skipped run of unsecure binary', { command }, 'found in the current directory.']; const body = 'Please remove that file or review your current PATH.'; renderWarning({ headline, body }); throw new AbortError(headline, body); } } /** * Waits for a given number of seconds. * * @param seconds - Number of seconds to wait. * @returns A Promise resolving after the number of seconds. */ export async function sleep(seconds) { return new Promise((resolve) => { setTimeout(resolve, 1000 * seconds); }); } /** * Check if the standard input and output streams support prompting. * * @returns True if the standard input and output streams support prompting. */ export function terminalSupportsPrompting() { if (isTruthy(process.env.CI)) { return false; } return Boolean(process.stdin.isTTY && process.stdout.isTTY); } /** * Check if the current environment is a CI environment. * * @returns True if the current environment is a CI environment. */ export function isCI() { return isTruthy(process.env.CI); } /** * Check if the current environment is a WSL environment. * * @returns True if the current environment is a WSL environment. */ export async function isWsl() { const wsl = await import('is-wsl'); return wsl.default; } //# sourceMappingURL=system.js.map