import { errorNormalize } from 'jsc-utils/error'; import { objectAssign } from 'jsc-utils/object'; import execa, { ExecaChildProcess, Options as ExecaOptions } from 'execa'; import path from 'path'; import { mergeProcessEnvPath } from './process'; import { isUndefined } from 'jsc-utils/type'; /** * 解析命令字符串 * @ref https://github.com/thiagodp/split-cmd/blob/master/index.js#L13 * @param {string} command * @returns {string[]} */ export function parseCommand(command: string): string[] { if (/\n/.test(command)) { throw new Error('命令行字符串里不能包含换行符,可以使用 JSON.stringify 将其字符串化'); } return (command.match(/[^"\s]+|"(?:\\"|[^"])*"/g) || []).map((token) => /^["']/.test(token) ? token.slice(1, -1) : token ); } export interface ExecCommandOptions { stdio: ExecaOptions['stdio']; cwd: string; env: NodeJS.ProcessEnv; errorExitCode: number; } export const execCommand = async ( command: string, options?: Partial ): Promise => { const defaults: ExecCommandOptions = { errorExitCode: 197, cwd: process.cwd(), env: {}, stdio: 'inherit' }; const finalOptions = objectAssign(defaults, options); const { cwd, env } = finalOptions; const npmBinDir = path.join(cwd, 'node_modules/.bin'); finalOptions.env = mergeProcessEnvPath(npmBinDir, env); const [file, ...args] = parseCommand(command); try { return await execa(file, args, finalOptions); } catch (err) { const error = errorNormalize(err); if (isUndefined(error.exitCode)) error.exitCode = finalOptions.errorExitCode; throw error; } };