@molecule/api-ai-tools 1.13.0 → 1.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +120 -6
- package/dist/command-position.d.ts +26 -0
- package/dist/command-position.d.ts.map +1 -1
- package/dist/command-position.js +314 -59
- package/dist/suite-overlap.d.ts +8 -4
- package/dist/suite-overlap.d.ts.map +1 -1
- package/dist/suite-overlap.js +446 -39
- package/dist/tools.d.ts +15 -0
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +20 -1
- package/dist/utilities.d.ts +39 -1
- package/dist/utilities.d.ts.map +1 -1
- package/dist/utilities.js +110 -7
- package/package.json +1 -1
package/dist/command-position.js
CHANGED
|
@@ -32,10 +32,246 @@
|
|
|
32
32
|
*
|
|
33
33
|
* @module
|
|
34
34
|
*/
|
|
35
|
-
/** A command's words, quotes kept whole — the same reading a printed-source check uses. */
|
|
36
|
-
const WORD = /"(?:\\.|[^"\\])*"|'[^']*'|\S+/g;
|
|
37
35
|
/** Shell words that stand alone: grouping, never an argument. */
|
|
38
36
|
const GROUPING = new Set(['(', ')', '{', '}']);
|
|
37
|
+
/** The index just past a single-quoted string that starts at `at` (its opening quote). */
|
|
38
|
+
function singleQuotedEnd(text, at) {
|
|
39
|
+
const close = text.indexOf("'", at + 1);
|
|
40
|
+
return close === -1 ? text.length : close + 1;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* The index just past a double-quoted string that starts at `at`. A command substitution or a
|
|
44
|
+
* backtick inside it is skipped as a unit, because a `"` inside one belongs to the nested command,
|
|
45
|
+
* not to this string (`DIR="$(cd "$(dirname "$0")" && pwd)"`).
|
|
46
|
+
*/
|
|
47
|
+
function doubleQuotedEnd(text, at) {
|
|
48
|
+
let i = at + 1;
|
|
49
|
+
while (i < text.length) {
|
|
50
|
+
const char = text[i] ?? '';
|
|
51
|
+
if (char === '\\') {
|
|
52
|
+
i += 2;
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (char === '"')
|
|
56
|
+
return i + 1;
|
|
57
|
+
if (char === '`') {
|
|
58
|
+
i = backtickEnd(text, i);
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
if (char === '$' && text[i + 1] === '(') {
|
|
62
|
+
i = substitutionEnd(text, i + 2);
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
if (char === '$' && text[i + 1] === '{') {
|
|
66
|
+
i = parameterEnd(text, i + 2);
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
i += 1;
|
|
70
|
+
}
|
|
71
|
+
return text.length;
|
|
72
|
+
}
|
|
73
|
+
/** The index just past a backtick substitution that starts at `at` (`\`` is literal, not a close). */
|
|
74
|
+
function backtickEnd(text, at) {
|
|
75
|
+
let i = at + 1;
|
|
76
|
+
while (i < text.length) {
|
|
77
|
+
if (text[i] === '\\') {
|
|
78
|
+
i += 2;
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
if (text[i] === '`')
|
|
82
|
+
return i + 1;
|
|
83
|
+
i += 1;
|
|
84
|
+
}
|
|
85
|
+
return text.length;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* The index just past a `$( … )` command substitution whose body starts at `at`. The body is NOT
|
|
89
|
+
* evaluated and its whitespace splits nothing: only its closing `)` is looked for, through quotes,
|
|
90
|
+
* escapes and nested substitutions. An unclosed substitution runs to the end of the text.
|
|
91
|
+
*/
|
|
92
|
+
function substitutionEnd(text, at) {
|
|
93
|
+
let depth = 1;
|
|
94
|
+
let i = at;
|
|
95
|
+
while (i < text.length) {
|
|
96
|
+
const char = text[i] ?? '';
|
|
97
|
+
// A `#` in the nested command starts a comment, so a `$(` or `)` inside it is data — the same
|
|
98
|
+
// reading `splitCommand` makes (`$(printf x # $(` opens nothing).
|
|
99
|
+
if (char === '#' && commentStartsAt(text, i)) {
|
|
100
|
+
const newline = text.indexOf('\n', i);
|
|
101
|
+
i = newline === -1 ? text.length : newline;
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
if (char === '\\') {
|
|
105
|
+
i += 2;
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
if (char === "'") {
|
|
109
|
+
i = singleQuotedEnd(text, i);
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
if (char === '"') {
|
|
113
|
+
i = doubleQuotedEnd(text, i);
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
if (char === '`') {
|
|
117
|
+
i = backtickEnd(text, i);
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
if (char === '$' && text[i + 1] === '(') {
|
|
121
|
+
i = substitutionEnd(text, i + 2);
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
if (char === '$' && text[i + 1] === '{') {
|
|
125
|
+
i = parameterEnd(text, i + 2);
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
if (char === '(')
|
|
129
|
+
depth += 1;
|
|
130
|
+
else if (char === ')') {
|
|
131
|
+
depth -= 1;
|
|
132
|
+
if (depth === 0)
|
|
133
|
+
return i + 1;
|
|
134
|
+
}
|
|
135
|
+
i += 1;
|
|
136
|
+
}
|
|
137
|
+
return text.length;
|
|
138
|
+
}
|
|
139
|
+
/** The index just past a `${ … }` parameter expansion whose body starts at `at`. */
|
|
140
|
+
function parameterEnd(text, at) {
|
|
141
|
+
let depth = 1;
|
|
142
|
+
let i = at;
|
|
143
|
+
while (i < text.length) {
|
|
144
|
+
const char = text[i] ?? '';
|
|
145
|
+
if (char === '\\') {
|
|
146
|
+
i += 2;
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
if (char === "'") {
|
|
150
|
+
i = singleQuotedEnd(text, i);
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
if (char === '"') {
|
|
154
|
+
i = doubleQuotedEnd(text, i);
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
if (char === '`') {
|
|
158
|
+
i = backtickEnd(text, i);
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
if (char === '$' && text[i + 1] === '(') {
|
|
162
|
+
i = substitutionEnd(text, i + 2);
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
if (char === '$' && text[i + 1] === '{') {
|
|
166
|
+
depth += 1;
|
|
167
|
+
i += 2;
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
if (char === '}') {
|
|
171
|
+
depth -= 1;
|
|
172
|
+
if (depth === 0)
|
|
173
|
+
return i + 1;
|
|
174
|
+
}
|
|
175
|
+
i += 1;
|
|
176
|
+
}
|
|
177
|
+
return text.length;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* The index just past the shell word that starts at `at` (a non-whitespace character): a word ends
|
|
181
|
+
* at unquoted whitespace, and every quoted part, command substitution (`$( … )`, backticks),
|
|
182
|
+
* parameter expansion (`${ … }`) and process substitution (`<( … )`) inside it is skipped as a
|
|
183
|
+
* unit — so the spaces in `UID_VAL=h$(date +%s)` belong to that one word.
|
|
184
|
+
*
|
|
185
|
+
* Nothing is evaluated or expanded: this finds word BOUNDARIES only, and returns the text as
|
|
186
|
+
* written. A backslash-newline is a line continuation, so it ends the word and is skipped.
|
|
187
|
+
*/
|
|
188
|
+
function shellWordEnd(text, at) {
|
|
189
|
+
let i = at;
|
|
190
|
+
while (i < text.length) {
|
|
191
|
+
const char = text[i] ?? '';
|
|
192
|
+
if (/\s/.test(char))
|
|
193
|
+
break;
|
|
194
|
+
// Grouping punctuation is a shell operator, never part of the word beside it (`tail -25)`,
|
|
195
|
+
// `(npx …`), so a caller can step over the closer the way it always has.
|
|
196
|
+
if (GROUPING.has(char))
|
|
197
|
+
break;
|
|
198
|
+
if (char === '\\') {
|
|
199
|
+
if (text[i + 1] === '\n')
|
|
200
|
+
break;
|
|
201
|
+
i += 2;
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
if (char === "'") {
|
|
205
|
+
i = singleQuotedEnd(text, i);
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
if (char === '"') {
|
|
209
|
+
i = doubleQuotedEnd(text, i);
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
if (char === '`') {
|
|
213
|
+
i = backtickEnd(text, i);
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
if (char === '$' && text[i + 1] === '(') {
|
|
217
|
+
i = substitutionEnd(text, i + 2);
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
if (char === '$' && text[i + 1] === '{') {
|
|
221
|
+
i = parameterEnd(text, i + 2);
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
if ((char === '<' || char === '>') && text[i + 1] === '(') {
|
|
225
|
+
i = substitutionEnd(text, i + 2);
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
i += 1;
|
|
229
|
+
}
|
|
230
|
+
return i;
|
|
231
|
+
}
|
|
232
|
+
/** One text's shell words, quotes kept, grouping punctuation still its own word. */
|
|
233
|
+
function shellWordSpans(text) {
|
|
234
|
+
const words = [];
|
|
235
|
+
let i = 0;
|
|
236
|
+
while (i < text.length) {
|
|
237
|
+
const char = text[i] ?? '';
|
|
238
|
+
if (char === '\\' && text[i + 1] === '\n') {
|
|
239
|
+
i += 2;
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
if (/\s/.test(char)) {
|
|
243
|
+
i += 1;
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
if (GROUPING.has(char)) {
|
|
247
|
+
words.push({ text: char, start: i });
|
|
248
|
+
i += 1;
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
const end = shellWordEnd(text, i);
|
|
252
|
+
words.push({ text: text.slice(i, end), start: i });
|
|
253
|
+
i = end;
|
|
254
|
+
}
|
|
255
|
+
return words;
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* The words {@link segmentWords} keeps, with the offset each was written at: quotes kept whole,
|
|
259
|
+
* grouping words dropped, and a leading `(`/`{` taken off the first word. One walk answers both
|
|
260
|
+
* readings, so a caller splicing text at a word's offset and a caller matching that word can
|
|
261
|
+
* never disagree.
|
|
262
|
+
*/
|
|
263
|
+
function segmentWordSpans(text) {
|
|
264
|
+
const words = shellWordSpans(text).filter((word) => !GROUPING.has(word.text));
|
|
265
|
+
const first = words[0];
|
|
266
|
+
if (first !== undefined) {
|
|
267
|
+
const bare = first.text.replace(/^[({]+/, '');
|
|
268
|
+
if (bare === '')
|
|
269
|
+
words.shift();
|
|
270
|
+
else if (bare !== first.text)
|
|
271
|
+
words[0] = { text: bare, start: first.start + (first.text.length - bare.length) };
|
|
272
|
+
}
|
|
273
|
+
return words;
|
|
274
|
+
}
|
|
39
275
|
/**
|
|
40
276
|
* A redirection written as ONE word, its target attached: `2>&1`, `>out.log`, `&>/dev/null`,
|
|
41
277
|
* `<<'EOF'`. Matched against a word of the statement, so a redirect's target written as its own
|
|
@@ -707,6 +943,8 @@ function splitCommand(command) {
|
|
|
707
943
|
const source = blankHeredocBodies(command);
|
|
708
944
|
const parts = [];
|
|
709
945
|
const substitutions = [];
|
|
946
|
+
const subshells = [];
|
|
947
|
+
const subshellOpens = [];
|
|
710
948
|
/** Where each open `$(` body starts; -1 for a bare `(` opened inside one. */
|
|
711
949
|
const subOpens = [];
|
|
712
950
|
/** Where each open backtick body starts. */
|
|
@@ -855,11 +1093,18 @@ function splitCommand(command) {
|
|
|
855
1093
|
// `{ … }` writes through that group, so the depth travels with every character a caller
|
|
856
1094
|
// reading a redirect may ask about.
|
|
857
1095
|
if (char === '(' || char === '{') {
|
|
1096
|
+
if (char === '(')
|
|
1097
|
+
subshellOpens.push(i + 1);
|
|
858
1098
|
put(char);
|
|
859
1099
|
groupDepth += 1;
|
|
860
1100
|
continue;
|
|
861
1101
|
}
|
|
862
1102
|
if (char === ')' || char === '}') {
|
|
1103
|
+
if (char === ')') {
|
|
1104
|
+
const start = subshellOpens.pop();
|
|
1105
|
+
if (start !== undefined)
|
|
1106
|
+
subshells.push({ start, end: i });
|
|
1107
|
+
}
|
|
863
1108
|
groupDepth = Math.max(0, groupDepth - 1);
|
|
864
1109
|
put(char);
|
|
865
1110
|
continue;
|
|
@@ -903,7 +1148,9 @@ function splitCommand(command) {
|
|
|
903
1148
|
});
|
|
904
1149
|
lastAfter = part.after;
|
|
905
1150
|
}
|
|
906
|
-
|
|
1151
|
+
for (const start of subshellOpens)
|
|
1152
|
+
subshells.push({ start, end: source.length });
|
|
1153
|
+
return { parts: out, substitutions, subshells };
|
|
907
1154
|
}
|
|
908
1155
|
/**
|
|
909
1156
|
* A command's statements and pipeline stages — {@link splitCommand}'s parts on their own.
|
|
@@ -980,20 +1227,8 @@ function redirectTargetOf(text, depths, program, args) {
|
|
|
980
1227
|
* @returns The words.
|
|
981
1228
|
*/
|
|
982
1229
|
function segmentWords(text) {
|
|
983
|
-
|
|
984
|
-
const cleaned = words.filter((word) => !GROUPING.has(word));
|
|
985
|
-
const first = cleaned[0];
|
|
986
|
-
if (first !== undefined) {
|
|
987
|
-
const bare = first.replace(/^[({]+/, '');
|
|
988
|
-
if (bare === '')
|
|
989
|
-
cleaned.shift();
|
|
990
|
-
else
|
|
991
|
-
cleaned[0] = bare;
|
|
992
|
-
}
|
|
993
|
-
return cleaned;
|
|
1230
|
+
return segmentWordSpans(text).map((word) => word.text);
|
|
994
1231
|
}
|
|
995
|
-
/** {@link WORD} anchored at an offset, for walking a statement's raw words. */
|
|
996
|
-
const WORD_AT = new RegExp(WORD.source, 'y');
|
|
997
1232
|
/**
|
|
998
1233
|
* A statement's own leading `NAME=value` words, and where they end.
|
|
999
1234
|
*
|
|
@@ -1008,19 +1243,25 @@ function leadingAssignments(text) {
|
|
|
1008
1243
|
const words = [];
|
|
1009
1244
|
let at = 0;
|
|
1010
1245
|
for (let guard = 0; guard < MAX_WRAPPERS; guard++) {
|
|
1011
|
-
while (at < text.length
|
|
1246
|
+
while (at < text.length) {
|
|
1247
|
+
if (text[at] === '\\' && text[at + 1] === '\n') {
|
|
1248
|
+
at += 2;
|
|
1249
|
+
continue;
|
|
1250
|
+
}
|
|
1251
|
+
if (!/\s/.test(text[at] ?? ''))
|
|
1252
|
+
break;
|
|
1012
1253
|
at += 1;
|
|
1254
|
+
}
|
|
1013
1255
|
// Grouping in front of the statement (`(npx …)`, `{ …`): not a word the shell assigns with.
|
|
1014
1256
|
if (text[at] === '(' || text[at] === '{') {
|
|
1015
1257
|
at += 1;
|
|
1016
1258
|
continue;
|
|
1017
1259
|
}
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
if (!match || !ENV_ASSIGNMENT.test(match[0]))
|
|
1260
|
+
const word = text.slice(at, shellWordEnd(text, at));
|
|
1261
|
+
if (!ENV_ASSIGNMENT.test(word))
|
|
1021
1262
|
return { words, end: at };
|
|
1022
|
-
words.push(
|
|
1023
|
-
at +=
|
|
1263
|
+
words.push(word);
|
|
1264
|
+
at += word.length;
|
|
1024
1265
|
}
|
|
1025
1266
|
return { words, end: at };
|
|
1026
1267
|
}
|
|
@@ -1117,6 +1358,31 @@ function executedCommandsOf(command, depth) {
|
|
|
1117
1358
|
}
|
|
1118
1359
|
return out;
|
|
1119
1360
|
}
|
|
1361
|
+
/**
|
|
1362
|
+
* The direct executable command-substitution bodies in shell text, in source order.
|
|
1363
|
+
*
|
|
1364
|
+
* Uses the same quote, comment and heredoc boundaries as {@link executedCommands}.
|
|
1365
|
+
* Single-quoted and escaped text is inert. Nested substitutions stay inside their
|
|
1366
|
+
* parent's body so a caller can recurse with a separate working-directory scope
|
|
1367
|
+
* for each body; a `cd` inside one must not affect its sibling or the outer shell.
|
|
1368
|
+
*
|
|
1369
|
+
* @param command - The shell text to read, including any leading assignments.
|
|
1370
|
+
* @returns Each direct `$(…)` or backtick body, without its delimiters.
|
|
1371
|
+
*/
|
|
1372
|
+
export function commandSubstitutions(command) {
|
|
1373
|
+
if (typeof command !== 'string' || !command.trim())
|
|
1374
|
+
return [];
|
|
1375
|
+
const { substitutions } = splitCommand(command);
|
|
1376
|
+
const bodies = [];
|
|
1377
|
+
let parentEnd = -1;
|
|
1378
|
+
for (const { start, end } of substitutions.sort((a, b) => a.start - b.start || b.end - a.end)) {
|
|
1379
|
+
if (start < parentEnd)
|
|
1380
|
+
continue;
|
|
1381
|
+
bodies.push(command.slice(start, end));
|
|
1382
|
+
parentEnd = end;
|
|
1383
|
+
}
|
|
1384
|
+
return bodies;
|
|
1385
|
+
}
|
|
1120
1386
|
/**
|
|
1121
1387
|
* Every command a shell command actually executes, at any depth — the statements themselves, the
|
|
1122
1388
|
* body of a `sh -c '<command>'`, and the body of every command substitution (`$(…)`, backticks).
|
|
@@ -1154,29 +1420,7 @@ export function executedCommands(command) {
|
|
|
1154
1420
|
* @returns The word's offset in `text`, or null when there is no such word.
|
|
1155
1421
|
*/
|
|
1156
1422
|
function wordOffset(text, index) {
|
|
1157
|
-
|
|
1158
|
-
let kept = 0;
|
|
1159
|
-
let first = true;
|
|
1160
|
-
for (let match = placement.exec(text); match !== null; match = placement.exec(text)) {
|
|
1161
|
-
if (GROUPING.has(match[0]))
|
|
1162
|
-
continue;
|
|
1163
|
-
if (first) {
|
|
1164
|
-
first = false;
|
|
1165
|
-
const bare = match[0].replace(/^[({]+/, '');
|
|
1166
|
-
// `segmentWords` shifts a first word that was nothing but grouping away, so the NEXT word
|
|
1167
|
-
// is not stripped and is counted from zero.
|
|
1168
|
-
if (bare === '')
|
|
1169
|
-
continue;
|
|
1170
|
-
if (kept === index)
|
|
1171
|
-
return match.index + (match[0].length - bare.length);
|
|
1172
|
-
kept += 1;
|
|
1173
|
-
continue;
|
|
1174
|
-
}
|
|
1175
|
-
if (kept === index)
|
|
1176
|
-
return match.index;
|
|
1177
|
-
kept += 1;
|
|
1178
|
-
}
|
|
1179
|
-
return null;
|
|
1423
|
+
return segmentWordSpans(text)[index]?.start ?? null;
|
|
1180
1424
|
}
|
|
1181
1425
|
/**
|
|
1182
1426
|
* Every statement of a command, with its own assignments and where it sits — assignment-only
|
|
@@ -1289,9 +1533,9 @@ export function statementArgumentEnd(command, start, end) {
|
|
|
1289
1533
|
}
|
|
1290
1534
|
at = cursor - 1;
|
|
1291
1535
|
}
|
|
1292
|
-
const words =
|
|
1293
|
-
text:
|
|
1294
|
-
index: start +
|
|
1536
|
+
const words = shellWordSpans(command.slice(start, at)).map((word) => ({
|
|
1537
|
+
text: word.text,
|
|
1538
|
+
index: start + word.start,
|
|
1295
1539
|
}));
|
|
1296
1540
|
// A trailing redirection belongs to the statement but is not an argument of its program. An
|
|
1297
1541
|
// option written after it would still parse, but the option belongs with the program's own
|
|
@@ -1404,7 +1648,8 @@ function locatedSegmentsOf(command, depth) {
|
|
|
1404
1648
|
if (typeof command !== 'string' || depth > MAX_NESTING)
|
|
1405
1649
|
return [];
|
|
1406
1650
|
const out = [];
|
|
1407
|
-
|
|
1651
|
+
const { parts, subshells } = splitCommand(command);
|
|
1652
|
+
for (const part of parts) {
|
|
1408
1653
|
const words = segmentWords(part.text);
|
|
1409
1654
|
const start = commandStart(words);
|
|
1410
1655
|
const leading = leadingAssignments(part.text);
|
|
@@ -1428,6 +1673,12 @@ function locatedSegmentsOf(command, depth) {
|
|
|
1428
1673
|
: part.start + (wordOffset(part.text, start.envInsert) ?? leading.end),
|
|
1429
1674
|
programOffset: words[start.at] === undefined ? null : part.start + (wordOffset(part.text, start.at) ?? 0),
|
|
1430
1675
|
};
|
|
1676
|
+
const scopeAt = placed.programOffset ?? part.start + (wordOffset(part.text, 0) ?? 0);
|
|
1677
|
+
const shellScope = subshells
|
|
1678
|
+
.filter(({ start, end }) => start <= scopeAt && scopeAt < end)
|
|
1679
|
+
.sort((a, b) => a.start - b.start)
|
|
1680
|
+
.map(({ start }) => `group:${start}`);
|
|
1681
|
+
const scoped = shellScope.length === 0 ? {} : { shellScope };
|
|
1431
1682
|
// The runner the word RUNS names, when that is not the word's own name (`node <path>/playwright`
|
|
1432
1683
|
// is a `playwright` invocation, xr-molecule-flash-m179).
|
|
1433
1684
|
const program = words[start.at] === undefined ? '' : (start.program ?? programName(words[start.at] ?? ''));
|
|
@@ -1441,9 +1692,14 @@ function locatedSegmentsOf(command, depth) {
|
|
|
1441
1692
|
continue;
|
|
1442
1693
|
out.push({
|
|
1443
1694
|
...entry,
|
|
1695
|
+
shellScope: [...shellScope, `shell:${part.start}`, ...(entry.shellScope ?? [])],
|
|
1444
1696
|
// The variables this statement bound before the shell reached it are the nested
|
|
1445
1697
|
// command's too: `env BASE_URL=… sh -c '… npx playwright test …'` passes them down.
|
|
1446
1698
|
assignments: [...new Set([...assignments, ...entry.assignments])],
|
|
1699
|
+
sourceAssignments: [
|
|
1700
|
+
...(i === 0 ? assignments : []),
|
|
1701
|
+
...(entry.sourceAssignments ?? entry.assignments),
|
|
1702
|
+
],
|
|
1447
1703
|
// A nested statement's positions are positions in the nested string, not in this
|
|
1448
1704
|
// command. The FIRST one keeps this statement's own separator: the shell reads the
|
|
1449
1705
|
// nested string where this statement stands.
|
|
@@ -1469,6 +1725,7 @@ function locatedSegmentsOf(command, depth) {
|
|
|
1469
1725
|
if (assignments.length > 0)
|
|
1470
1726
|
out.push({
|
|
1471
1727
|
segment: null,
|
|
1728
|
+
...scoped,
|
|
1472
1729
|
assignments,
|
|
1473
1730
|
...placed,
|
|
1474
1731
|
programOffset: null,
|
|
@@ -1479,6 +1736,7 @@ function locatedSegmentsOf(command, depth) {
|
|
|
1479
1736
|
continue;
|
|
1480
1737
|
}
|
|
1481
1738
|
out.push({
|
|
1739
|
+
...scoped,
|
|
1482
1740
|
segment: {
|
|
1483
1741
|
program,
|
|
1484
1742
|
args,
|
|
@@ -1554,16 +1812,13 @@ export function withoutTimeoutWrapper(command, which) {
|
|
|
1554
1812
|
continue;
|
|
1555
1813
|
// Every word in front of the program: the assignments and the wrappers. The duration belongs
|
|
1556
1814
|
// to the wrapper, so the walk has to see the words, not just the parse's total bound.
|
|
1557
|
-
const
|
|
1558
|
-
const
|
|
1559
|
-
const
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
end: located.start + match.index + match[0].length,
|
|
1565
|
-
});
|
|
1566
|
-
}
|
|
1815
|
+
const start = located.start;
|
|
1816
|
+
const prefix = command.slice(start, located.programOffset);
|
|
1817
|
+
const words = shellWordSpans(prefix).map((word) => ({
|
|
1818
|
+
text: word.text,
|
|
1819
|
+
start: start + word.start,
|
|
1820
|
+
end: start + word.start + word.text.length,
|
|
1821
|
+
}));
|
|
1567
1822
|
for (let i = 0; i < words.length; i += 1) {
|
|
1568
1823
|
const word = words[i];
|
|
1569
1824
|
if (word === undefined || programName(word.text) !== 'timeout')
|
package/dist/suite-overlap.d.ts
CHANGED
|
@@ -79,13 +79,17 @@ export declare function namesSuiteConfig(command: string): boolean;
|
|
|
79
79
|
export declare function withSuiteConfig(command: string, config: string | null | undefined): string;
|
|
80
80
|
/**
|
|
81
81
|
* The whole-suite command made scoped to the tests it should re-run instead: the same
|
|
82
|
-
* invocation — same
|
|
83
|
-
*
|
|
82
|
+
* invocation — same launcher, same settings — with the named `file:line`s in place of every
|
|
83
|
+
* positional selection the run carried (a spec file, a suite directory or a regex filter),
|
|
84
|
+
* `--last-failed` dropped (it re-runs the tests that never ran too, X1 flash-m60) and
|
|
84
85
|
* `--retries=0` set (a re-run of a known failure does not need to pay for retries).
|
|
85
86
|
*
|
|
86
87
|
* The re-run keeps the run's `> <file>` redirect only in the sense of keeping its OUTPUT there:
|
|
87
|
-
* the target is rewritten to a fresh sibling ({@link
|
|
88
|
-
* overwrites the log the run itself is the evidence for (xr-molecule-flash-m173)
|
|
88
|
+
* the target is rewritten to a fresh sibling ({@link redirectRunnerOutputsToFreshFiles}), so a
|
|
89
|
+
* re-run never overwrites the log the run itself is the evidence for (xr-molecule-flash-m173) —
|
|
90
|
+
* and the setup around the run keeps its own file writes untouched (xr-molecule-flash-m429). A
|
|
91
|
+
* `tail`/`head`/`cat`/`grep` chained after the run that reads that exact log is shown the fresh
|
|
92
|
+
* sibling instead, so the re-run displays the run it performed (xr-molecule-flash-o03).
|
|
89
93
|
*
|
|
90
94
|
* `config` is the config the run was measured under ({@link withSuiteConfig}): the narrowed re-run
|
|
91
95
|
* carries it whenever the command itself does not, so a run that dropped `--config` cannot be
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"suite-overlap.d.ts","sourceRoot":"","sources":["../src/suite-overlap.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;
|
|
1
|
+
{"version":3,"file":"suite-overlap.d.ts","sourceRoot":"","sources":["../src/suite-overlap.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAaH;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEvD;AAED;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAoB1D;AAKD;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAEzD;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAS1F;AA4VD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,SAAS,MAAM,EAAE,EAC5B,QAAQ,GAAE,SAAS,MAAM,EAAO,EAChC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,EACtB,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,EACnB,GAAG,GAAE,SAAS,aAAa,EAAO,GACjC,MAAM,CAgDR;AAED,4DAA4D;AAC5D,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;CACd;AAmCD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,GAAE,SAAS,aAAa,EAAO,GAAG,MAAM,CAe3F;AAwOD;;;;;;GAMG;AACH,MAAM,WAAW,gBAAgB;IAC/B,+FAA+F;IAC/F,SAAS,EAAE,OAAO,CAAA;IAClB,oFAAoF;IACpF,MAAM,EAAE,MAAM,CAAA;IACd;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAA;CAC3B;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,gBAAgB,EACzD,SAAS,EAAE;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,EAC7B,QAAQ,EAAE,SAAS,CAAC,EAAE,GACrB,CAAC,GAAG,IAAI,CAMV;AA2FD;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC;;;OAGG;IACH,kBAAkB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CACvC;AAmCD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAYtF"}
|