UNPKG

3 kBJavaScriptView Raw
1import {Buffer} from 'node:buffer';
2import {ChildProcess} from 'node:child_process';
3
4const normalizeArgs = (file, args = []) => {
5 if (!Array.isArray(args)) {
6 return [file];
7 }
8
9 return [file, ...args];
10};
11
12const NO_ESCAPE_REGEXP = /^[\w.-]+$/;
13
14const escapeArg = arg => {
15 if (typeof arg !== 'string' || NO_ESCAPE_REGEXP.test(arg)) {
16 return arg;
17 }
18
19 return `"${arg.replaceAll('"', '\\"')}"`;
20};
21
22export const joinCommand = (file, args) => normalizeArgs(file, args).join(' ');
23
24export const getEscapedCommand = (file, args) => normalizeArgs(file, args).map(arg => escapeArg(arg)).join(' ');
25
26const SPACES_REGEXP = / +/g;
27
28// Handle `execaCommand()`
29export const parseCommand = command => {
30 const tokens = [];
31 for (const token of command.trim().split(SPACES_REGEXP)) {
32 // Allow spaces to be escaped by a backslash if not meant as a delimiter
33 const previousToken = tokens.at(-1);
34 if (previousToken && previousToken.endsWith('\\')) {
35 // Merge previous token with current one
36 tokens[tokens.length - 1] = `${previousToken.slice(0, -1)} ${token}`;
37 } else {
38 tokens.push(token);
39 }
40 }
41
42 return tokens;
43};
44
45const parseExpression = expression => {
46 const typeOfExpression = typeof expression;
47
48 if (typeOfExpression === 'string') {
49 return expression;
50 }
51
52 if (typeOfExpression === 'number') {
53 return String(expression);
54 }
55
56 if (
57 typeOfExpression === 'object'
58 && expression !== null
59 && !(expression instanceof ChildProcess)
60 && 'stdout' in expression
61 ) {
62 const typeOfStdout = typeof expression.stdout;
63
64 if (typeOfStdout === 'string') {
65 return expression.stdout;
66 }
67
68 if (Buffer.isBuffer(expression.stdout)) {
69 return expression.stdout.toString();
70 }
71
72 throw new TypeError(`Unexpected "${typeOfStdout}" stdout in template expression`);
73 }
74
75 throw new TypeError(`Unexpected "${typeOfExpression}" in template expression`);
76};
77
78const concatTokens = (tokens, nextTokens, isNew) => isNew || tokens.length === 0 || nextTokens.length === 0
79 ? [...tokens, ...nextTokens]
80 : [
81 ...tokens.slice(0, -1),
82 `${tokens.at(-1)}${nextTokens[0]}`,
83 ...nextTokens.slice(1),
84 ];
85
86const parseTemplate = ({templates, expressions, tokens, index, template}) => {
87 const templateString = template ?? templates.raw[index];
88 const templateTokens = templateString.split(SPACES_REGEXP).filter(Boolean);
89 const newTokens = concatTokens(
90 tokens,
91 templateTokens,
92 templateString.startsWith(' '),
93 );
94
95 if (index === expressions.length) {
96 return newTokens;
97 }
98
99 const expression = expressions[index];
100 const expressionTokens = Array.isArray(expression)
101 ? expression.map(expression => parseExpression(expression))
102 : [parseExpression(expression)];
103 return concatTokens(
104 newTokens,
105 expressionTokens,
106 templateString.endsWith(' '),
107 );
108};
109
110export const parseTemplates = (templates, expressions) => {
111 let tokens = [];
112
113 for (const [index, template] of templates.entries()) {
114 tokens = parseTemplate({templates, expressions, tokens, index, template});
115 }
116
117 return tokens;
118};
119