import {Buffer} from 'node:buffer'; import {ChildProcess} from 'node:child_process'; const normalizeArgs = (file, args = []) => { if (!Array.isArray(args)) { return [file]; } return [file, ...args]; }; const NO_ESCAPE_REGEXP = /^[\w.-]+$/; const escapeArg = arg => { if (typeof arg !== 'string' || NO_ESCAPE_REGEXP.test(arg)) { return arg; } return `"${arg.replaceAll('"', '\\"')}"`; }; export const joinCommand = (file, args) => normalizeArgs(file, args).join(' '); export const getEscapedCommand = (file, args) => normalizeArgs(file, args).map(arg => escapeArg(arg)).join(' '); const SPACES_REGEXP = / +/g; // Handle `execaCommand()` export const parseCommand = command => { const tokens = []; for (const token of command.trim().split(SPACES_REGEXP)) { // Allow spaces to be escaped by a backslash if not meant as a delimiter const previousToken = tokens.at(-1); if (previousToken && previousToken.endsWith('\\')) { // Merge previous token with current one tokens[tokens.length - 1] = `${previousToken.slice(0, -1)} ${token}`; } else { tokens.push(token); } } return tokens; }; const parseExpression = expression => { const typeOfExpression = typeof expression; if (typeOfExpression === 'string') { return expression; } if (typeOfExpression === 'number') { return String(expression); } if ( typeOfExpression === 'object' && expression !== null && !(expression instanceof ChildProcess) && 'stdout' in expression ) { const typeOfStdout = typeof expression.stdout; if (typeOfStdout === 'string') { return expression.stdout; } if (Buffer.isBuffer(expression.stdout)) { return expression.stdout.toString(); } throw new TypeError(`Unexpected "${typeOfStdout}" stdout in template expression`); } throw new TypeError(`Unexpected "${typeOfExpression}" in template expression`); }; const concatTokens = (tokens, nextTokens, isNew) => isNew || tokens.length === 0 || nextTokens.length === 0 ? [...tokens, ...nextTokens] : [ ...tokens.slice(0, -1), `${tokens.at(-1)}${nextTokens[0]}`, ...nextTokens.slice(1), ]; const parseTemplate = ({templates, expressions, tokens, index, template}) => { const templateString = template ?? templates.raw[index]; const templateTokens = templateString.split(SPACES_REGEXP).filter(Boolean); const newTokens = concatTokens( tokens, templateTokens, templateString.startsWith(' '), ); if (index === expressions.length) { return newTokens; } const expression = expressions[index]; const expressionTokens = Array.isArray(expression) ? expression.map(expression => parseExpression(expression)) : [parseExpression(expression)]; return concatTokens( newTokens, expressionTokens, templateString.endsWith(' '), ); }; export const parseTemplates = (templates, expressions) => { let tokens = []; for (const [index, template] of templates.entries()) { tokens = parseTemplate({templates, expressions, tokens, index, template}); } return tokens; };