@molecule/api-ai-tools 1.7.0 → 1.8.2
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 +324 -25
- package/dist/command-position.d.ts +42 -0
- package/dist/command-position.d.ts.map +1 -1
- package/dist/command-position.js +271 -41
- package/dist/cut-run.d.ts +98 -8
- package/dist/cut-run.d.ts.map +1 -1
- package/dist/cut-run.js +182 -29
- package/dist/suite-overlap.d.ts +5 -1
- package/dist/suite-overlap.d.ts.map +1 -1
- package/dist/suite-overlap.js +16 -2
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +78 -13
- package/dist/trace-artifacts.d.ts.map +1 -1
- package/dist/trace-artifacts.js +45 -1
- package/dist/types.d.ts +13 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/utilities.d.ts +121 -8
- package/dist/utilities.d.ts.map +1 -1
- package/dist/utilities.js +482 -41
- package/package.json +1 -1
package/dist/command-position.js
CHANGED
|
@@ -64,6 +64,122 @@ const PACKAGE_RUNNER_HEADS = new Set(['bun', 'npm', 'pnpm', 'yarn']);
|
|
|
64
64
|
* a script of the project's, and nothing is stripped from them.
|
|
65
65
|
*/
|
|
66
66
|
const PACKAGE_RUNNER_SUBCOMMANDS = new Set(['dlx', 'exec', 'x']);
|
|
67
|
+
/**
|
|
68
|
+
* npm's value-taking options: `npm exec`'s own (`-p/--package`, `-c/--call`, `-w/--workspace`,
|
|
69
|
+
* `--prefix`) and the string-valued configs `npm config ls -l` lists in the sandbox image, which
|
|
70
|
+
* npm accepts on the command line of any invocation.
|
|
71
|
+
*/
|
|
72
|
+
const NPM_VALUE_FLAGS = [
|
|
73
|
+
'-c',
|
|
74
|
+
'--call',
|
|
75
|
+
'-p',
|
|
76
|
+
'--package',
|
|
77
|
+
'-w',
|
|
78
|
+
'--workspace',
|
|
79
|
+
'--prefix',
|
|
80
|
+
'--registry',
|
|
81
|
+
'--cache',
|
|
82
|
+
'--userconfig',
|
|
83
|
+
'--globalconfig',
|
|
84
|
+
'--loglevel',
|
|
85
|
+
'--logs-dir',
|
|
86
|
+
'--script-shell',
|
|
87
|
+
'--shell',
|
|
88
|
+
'--tag',
|
|
89
|
+
'--otp',
|
|
90
|
+
'--scope',
|
|
91
|
+
'--location',
|
|
92
|
+
'--user-agent',
|
|
93
|
+
'--which',
|
|
94
|
+
'--viewer',
|
|
95
|
+
'--proxy',
|
|
96
|
+
'--https-proxy',
|
|
97
|
+
'--cert',
|
|
98
|
+
'--key',
|
|
99
|
+
'--ca',
|
|
100
|
+
'--cafile',
|
|
101
|
+
'--editor',
|
|
102
|
+
'--message',
|
|
103
|
+
'--node-options',
|
|
104
|
+
'--save-prefix',
|
|
105
|
+
'--preid',
|
|
106
|
+
'--access',
|
|
107
|
+
'--also',
|
|
108
|
+
'--audit-level',
|
|
109
|
+
'--auth-type',
|
|
110
|
+
'--before',
|
|
111
|
+
'--browser',
|
|
112
|
+
'--cidr',
|
|
113
|
+
'--cpu',
|
|
114
|
+
'--depth',
|
|
115
|
+
'--expect-result-count',
|
|
116
|
+
'--expect-results',
|
|
117
|
+
'--install-strategy',
|
|
118
|
+
'--libc',
|
|
119
|
+
'--local-address',
|
|
120
|
+
'--lockfile-version',
|
|
121
|
+
'--only',
|
|
122
|
+
'--os',
|
|
123
|
+
'--pack-destination',
|
|
124
|
+
'--provenance-file',
|
|
125
|
+
'--replace-registry-host',
|
|
126
|
+
'--sbom-format',
|
|
127
|
+
'--sbom-type',
|
|
128
|
+
'--searchexclude',
|
|
129
|
+
'--searchopts',
|
|
130
|
+
'--tag-version-prefix',
|
|
131
|
+
];
|
|
132
|
+
/** pnpm dlx's value-taking options (`pnpm dlx --help`); its `-c` is the boolean shell-mode. */
|
|
133
|
+
const PNPM_DLX_VALUE_FLAGS = ['--allow-build', '--package', '--reporter'];
|
|
134
|
+
/**
|
|
135
|
+
* The package manager's OWN options that take their value from the next word — read from the
|
|
136
|
+
* versions the sandbox image (`node:22-trixie-slim`) ships: npm 10.9.9 (`npm exec --help`, and
|
|
137
|
+
* every string-valued key of `npm config ls -l`), pnpm 10.34.7 (`pnpm help exec`,
|
|
138
|
+
* `pnpm dlx --help`) and Yarn 4.18.1 (`yarn dlx --help`). A flag absent here is skipped as a
|
|
139
|
+
* boolean, which is what every documented switch is.
|
|
140
|
+
*
|
|
141
|
+
* The table is keyed per manager because the same short flag means different things in two of
|
|
142
|
+
* them: `-c` is npm's `--call <command>` (a value, and a command of its own —
|
|
143
|
+
* {@link PACKAGE_RUNNER_CALL_FLAGS}) but pnpm's `--shell-mode` (a switch), and `-w` is npm's
|
|
144
|
+
* `--workspace <name>` (a value) but pnpm's `--workspace-root` (a switch).
|
|
145
|
+
*/
|
|
146
|
+
const PACKAGE_RUNNER_VALUE_FLAGS = {
|
|
147
|
+
// npm exec's documented options, plus the config flags npm accepts on any invocation.
|
|
148
|
+
npx: NPM_VALUE_FLAGS,
|
|
149
|
+
'npm exec': NPM_VALUE_FLAGS,
|
|
150
|
+
'npm x': NPM_VALUE_FLAGS,
|
|
151
|
+
// `npx --prefix /app playwright test` is how flash-m101's run reached the app's own Playwright.
|
|
152
|
+
bunx: ['-p', '--package'],
|
|
153
|
+
'bun x': ['-p', '--package'],
|
|
154
|
+
pnpx: PNPM_DLX_VALUE_FLAGS,
|
|
155
|
+
'pnpm dlx': PNPM_DLX_VALUE_FLAGS,
|
|
156
|
+
'pnpm x': PNPM_DLX_VALUE_FLAGS,
|
|
157
|
+
'pnpm exec': [
|
|
158
|
+
'-C',
|
|
159
|
+
'--dir',
|
|
160
|
+
'--loglevel',
|
|
161
|
+
'--resume-from',
|
|
162
|
+
'--reporter',
|
|
163
|
+
'--filter',
|
|
164
|
+
'--filter-prod',
|
|
165
|
+
'--changed-files-ignore-pattern',
|
|
166
|
+
'--workspace-concurrency',
|
|
167
|
+
'--prefix',
|
|
168
|
+
],
|
|
169
|
+
'yarn dlx': ['-p', '--package'],
|
|
170
|
+
'yarn exec': [],
|
|
171
|
+
'yarn x': [],
|
|
172
|
+
};
|
|
173
|
+
/**
|
|
174
|
+
* The package-runner options whose VALUE is itself a command (`npm exec -c '<command>'`), so it is
|
|
175
|
+
* parsed as one: `npx -c 'playwright test'` runs Playwright, and its program is not the word after
|
|
176
|
+
* the flag.
|
|
177
|
+
*/
|
|
178
|
+
const PACKAGE_RUNNER_CALL_FLAGS = {
|
|
179
|
+
npx: ['-c', '--call'],
|
|
180
|
+
'npm exec': ['-c', '--call'],
|
|
181
|
+
'npm x': ['-c', '--call'],
|
|
182
|
+
};
|
|
67
183
|
/** Wrapper flags that are followed by their own value (`sudo -u root`, `timeout -k 5`). */
|
|
68
184
|
const WRAPPER_VALUE_FLAGS = {
|
|
69
185
|
env: ['-S', '--split-string', '-u', '--unset', '-C', '--chdir'],
|
|
@@ -149,6 +265,44 @@ function flagWords(words, from, wrapper) {
|
|
|
149
265
|
}
|
|
150
266
|
return count;
|
|
151
267
|
}
|
|
268
|
+
/**
|
|
269
|
+
* How many words a package runner (`npx`, `npm exec`, `pnpm dlx`, `yarn dlx`) owns before the
|
|
270
|
+
* program it runs — its own options, and each value flag's value — plus the command string of a
|
|
271
|
+
* `-c '<command>'`, which is a command of its own rather than the program word.
|
|
272
|
+
*
|
|
273
|
+
* A `--` ends the options: `npm exec --prefix x -- playwright test` runs `playwright` with `test`,
|
|
274
|
+
* so the terminator is consumed and the walk stops there.
|
|
275
|
+
*
|
|
276
|
+
* @param words - The segment's words.
|
|
277
|
+
* @param from - The index just after the runner (or after `npm exec`).
|
|
278
|
+
* @param key - The runner's table key (`npm`, `npx`, `pnpm exec`, `yarn dlx`, …).
|
|
279
|
+
* @returns The count of words the runner owns, and the `-c` command string when it carries one.
|
|
280
|
+
*/
|
|
281
|
+
function packageRunnerOptions(words, from, key) {
|
|
282
|
+
const withValue = PACKAGE_RUNNER_VALUE_FLAGS[key] ?? [];
|
|
283
|
+
const callFlags = PACKAGE_RUNNER_CALL_FLAGS[key] ?? [];
|
|
284
|
+
let count = 0;
|
|
285
|
+
while (from + count < words.length) {
|
|
286
|
+
const word = words[from + count] ?? '';
|
|
287
|
+
if (!word.startsWith('-'))
|
|
288
|
+
break;
|
|
289
|
+
// `--` ends the runner's options; the program is the next word.
|
|
290
|
+
if (word === '--')
|
|
291
|
+
return { count: count + 1, nested: null };
|
|
292
|
+
const flag = unquoted(word);
|
|
293
|
+
// `--flag=value` carries its value inside the same word.
|
|
294
|
+
if (word.includes('=')) {
|
|
295
|
+
count += 1;
|
|
296
|
+
continue;
|
|
297
|
+
}
|
|
298
|
+
if (callFlags.includes(flag)) {
|
|
299
|
+
const value = words[from + count + 1];
|
|
300
|
+
return { count: count + 2, nested: value === undefined ? null : unquoted(value) };
|
|
301
|
+
}
|
|
302
|
+
count += withValue.includes(flag) ? 2 : 1;
|
|
303
|
+
}
|
|
304
|
+
return { count, nested: null };
|
|
305
|
+
}
|
|
152
306
|
/**
|
|
153
307
|
* The `-c '<command>'` string a shell was handed, or null when the shell was given a FILE
|
|
154
308
|
* (`bash run.sh`) — the wrapper walk then steps past its flags instead.
|
|
@@ -201,12 +355,19 @@ function commandStart(words) {
|
|
|
201
355
|
continue;
|
|
202
356
|
}
|
|
203
357
|
if (PACKAGE_RUNNERS.has(name)) {
|
|
204
|
-
|
|
358
|
+
const runner = packageRunnerOptions(words, at + 1, name);
|
|
359
|
+
if (runner.nested !== null)
|
|
360
|
+
return { at, nested: runner.nested, timeoutSeconds };
|
|
361
|
+
at += 1 + runner.count;
|
|
205
362
|
continue;
|
|
206
363
|
}
|
|
207
364
|
if (PACKAGE_RUNNER_HEADS.has(name) &&
|
|
208
365
|
PACKAGE_RUNNER_SUBCOMMANDS.has(unquoted(words[at + 1] ?? ''))) {
|
|
209
|
-
|
|
366
|
+
const subcommand = unquoted(words[at + 1] ?? '');
|
|
367
|
+
const runner = packageRunnerOptions(words, at + 2, `${name} ${subcommand}`);
|
|
368
|
+
if (runner.nested !== null)
|
|
369
|
+
return { at, nested: runner.nested, timeoutSeconds };
|
|
370
|
+
at += 2 + runner.count;
|
|
210
371
|
continue;
|
|
211
372
|
}
|
|
212
373
|
if (!LAUNCHERS.has(name))
|
|
@@ -239,11 +400,14 @@ function commandStart(words) {
|
|
|
239
400
|
* no runner, and every run of it reads as nothing).
|
|
240
401
|
*
|
|
241
402
|
* @param command - The shell command.
|
|
242
|
-
* @returns Each segment's text
|
|
403
|
+
* @returns Each segment's text and the offset in the command its first character sits at, in
|
|
404
|
+
* order, with empty ones dropped.
|
|
243
405
|
*/
|
|
244
406
|
function splitSegments(command) {
|
|
245
407
|
const parts = [];
|
|
246
408
|
let buffer = '';
|
|
409
|
+
/** The offset in the command of the current buffer's first character. */
|
|
410
|
+
let bufferStart = 0;
|
|
247
411
|
/** The nestings this position sits inside, outermost first. */
|
|
248
412
|
const stack = [];
|
|
249
413
|
for (let i = 0; i < command.length; i++) {
|
|
@@ -305,25 +469,33 @@ function splitSegments(command) {
|
|
|
305
469
|
continue;
|
|
306
470
|
}
|
|
307
471
|
if ((char === '&' && next === '&') || (char === '|' && next === '|')) {
|
|
308
|
-
parts.push(buffer);
|
|
472
|
+
parts.push({ text: buffer, start: bufferStart });
|
|
309
473
|
buffer = '';
|
|
310
474
|
i += 1;
|
|
475
|
+
bufferStart = i + 1;
|
|
311
476
|
continue;
|
|
312
477
|
}
|
|
313
478
|
if (char === ';' || char === '|' || char === '\n') {
|
|
314
|
-
parts.push(buffer);
|
|
479
|
+
parts.push({ text: buffer, start: bufferStart });
|
|
315
480
|
buffer = '';
|
|
481
|
+
bufferStart = i + 1;
|
|
316
482
|
continue;
|
|
317
483
|
}
|
|
318
484
|
if (char === '&' && command[i - 1] !== '>' && next !== '>') {
|
|
319
|
-
parts.push(buffer);
|
|
485
|
+
parts.push({ text: buffer, start: bufferStart });
|
|
320
486
|
buffer = '';
|
|
487
|
+
bufferStart = i + 1;
|
|
321
488
|
continue;
|
|
322
489
|
}
|
|
323
490
|
buffer += char;
|
|
324
491
|
}
|
|
325
|
-
parts.push(buffer);
|
|
326
|
-
return parts
|
|
492
|
+
parts.push({ text: buffer, start: bufferStart });
|
|
493
|
+
return parts
|
|
494
|
+
.map((part) => ({
|
|
495
|
+
text: part.text.trim(),
|
|
496
|
+
start: part.start + (part.text.length - part.text.trimStart().length),
|
|
497
|
+
}))
|
|
498
|
+
.filter((part) => part.text !== '');
|
|
327
499
|
}
|
|
328
500
|
/**
|
|
329
501
|
* One segment's words: quotes kept whole, grouping parentheses dropped, and a `(` written
|
|
@@ -345,6 +517,38 @@ function segmentWords(text) {
|
|
|
345
517
|
}
|
|
346
518
|
return cleaned;
|
|
347
519
|
}
|
|
520
|
+
/** {@link WORD} anchored at an offset, for walking a statement's raw words. */
|
|
521
|
+
const WORD_AT = new RegExp(WORD.source, 'y');
|
|
522
|
+
/**
|
|
523
|
+
* A statement's own leading `NAME=value` words, and where they end.
|
|
524
|
+
*
|
|
525
|
+
* The shell binds these to the command the statement runs, so they are the environment THAT
|
|
526
|
+
* program sees — which is why a caller writing a variable for a run has to write it here and not
|
|
527
|
+
* at the start of the whole command.
|
|
528
|
+
*
|
|
529
|
+
* @param text - One statement's text.
|
|
530
|
+
* @returns The assignments as written, and the offset in `text` just before the word that runs.
|
|
531
|
+
*/
|
|
532
|
+
function leadingAssignments(text) {
|
|
533
|
+
const words = [];
|
|
534
|
+
let at = 0;
|
|
535
|
+
for (let guard = 0; guard < MAX_WRAPPERS; guard++) {
|
|
536
|
+
while (at < text.length && /\s/.test(text[at] ?? ''))
|
|
537
|
+
at += 1;
|
|
538
|
+
// Grouping in front of the statement (`(npx …)`, `{ …`): not a word the shell assigns with.
|
|
539
|
+
if (text[at] === '(' || text[at] === '{') {
|
|
540
|
+
at += 1;
|
|
541
|
+
continue;
|
|
542
|
+
}
|
|
543
|
+
WORD_AT.lastIndex = at;
|
|
544
|
+
const match = WORD_AT.exec(text);
|
|
545
|
+
if (!match || !ENV_ASSIGNMENT.test(match[0]))
|
|
546
|
+
return { words, end: at };
|
|
547
|
+
words.push(match[0]);
|
|
548
|
+
at += match[0].length;
|
|
549
|
+
}
|
|
550
|
+
return { words, end: at };
|
|
551
|
+
}
|
|
348
552
|
/**
|
|
349
553
|
* The segments of a command, each with its program and arguments — the answer every classifier
|
|
350
554
|
* that reads a command as "this runs the suite" is asking for.
|
|
@@ -353,66 +557,92 @@ function segmentWords(text) {
|
|
|
353
557
|
* @returns One entry per statement or pipeline stage that runs something, in order.
|
|
354
558
|
*/
|
|
355
559
|
export function commandSegments(command) {
|
|
356
|
-
return
|
|
560
|
+
return locatedSegmentsOf(command, 0).flatMap((located) => located.segment === null ? [] : [located.segment]);
|
|
357
561
|
}
|
|
358
562
|
/**
|
|
359
|
-
*
|
|
360
|
-
*
|
|
361
|
-
*
|
|
362
|
-
* This is the bound `man timeout` enforces by KILLING the command — so a run that used it did not
|
|
363
|
-
* finish, whatever the pipeline's own exit status says. It is read here, from the same parse every
|
|
364
|
-
* other command reader uses, rather than matched as text: `timeout` in an argument
|
|
365
|
-
* (`grep -n 'timeout 280' notes.md`) bounds nothing, and `-k 5` is the grace before the kill, not
|
|
366
|
-
* the bound ({@link WRAPPER_VALUE_FLAGS}).
|
|
563
|
+
* Every statement of a command, with its own assignments and where it sits — assignment-only
|
|
564
|
+
* statements included, because the variables those persist are exactly what a later statement of
|
|
565
|
+
* the same command reads.
|
|
367
566
|
*
|
|
368
567
|
* @param command - The shell command.
|
|
369
|
-
* @returns
|
|
568
|
+
* @returns One entry per statement or pipeline stage, in order.
|
|
370
569
|
*/
|
|
371
|
-
export function
|
|
372
|
-
|
|
373
|
-
for (const segment of segmentsOf(command, 0)) {
|
|
374
|
-
if (segment.timeoutSeconds === null)
|
|
375
|
-
continue;
|
|
376
|
-
bound = bound === null ? segment.timeoutSeconds : Math.max(bound, segment.timeoutSeconds);
|
|
377
|
-
}
|
|
378
|
-
return bound;
|
|
570
|
+
export function locatedCommandSegments(command) {
|
|
571
|
+
return locatedSegmentsOf(command, 0);
|
|
379
572
|
}
|
|
380
573
|
/**
|
|
381
|
-
* {@link
|
|
382
|
-
* {@link MAX_NESTING} allows.
|
|
574
|
+
* {@link locatedCommandSegments}'s recursion, so a `sh -c '<command>'` is opened as far as
|
|
575
|
+
* {@link MAX_NESTING} allows. A nested statement's offsets are cleared: they are positions in
|
|
576
|
+
* the nested string, not in the command this walk started from.
|
|
383
577
|
*
|
|
384
578
|
* @param command - The shell command.
|
|
385
579
|
* @param depth - How many `sh -c` strings this command already sits inside.
|
|
386
|
-
* @returns The
|
|
580
|
+
* @returns The located statements.
|
|
387
581
|
*/
|
|
388
|
-
function
|
|
582
|
+
function locatedSegmentsOf(command, depth) {
|
|
389
583
|
if (typeof command !== 'string' || depth > MAX_NESTING)
|
|
390
584
|
return [];
|
|
391
585
|
const out = [];
|
|
392
|
-
for (const
|
|
393
|
-
const words = segmentWords(text);
|
|
394
|
-
if (words.length === 0)
|
|
395
|
-
continue;
|
|
586
|
+
for (const part of splitSegments(command)) {
|
|
587
|
+
const words = segmentWords(part.text);
|
|
396
588
|
const start = commandStart(words);
|
|
589
|
+
const leading = leadingAssignments(part.text);
|
|
397
590
|
if (start.nested !== null) {
|
|
398
|
-
|
|
591
|
+
for (const inner of locatedSegmentsOf(start.nested, depth + 1))
|
|
592
|
+
out.push({ ...inner, start: null, assignmentOffset: null });
|
|
399
593
|
continue;
|
|
400
594
|
}
|
|
401
595
|
const word = words[start.at];
|
|
402
|
-
|
|
403
|
-
|
|
596
|
+
if (word === undefined) {
|
|
597
|
+
// Only wrappers and assignments: nothing this statement runs, but the variables it sets
|
|
598
|
+
// persist in the shell the rest of the command runs in.
|
|
599
|
+
if (leading.words.length > 0)
|
|
600
|
+
out.push({
|
|
601
|
+
segment: null,
|
|
602
|
+
assignments: leading.words,
|
|
603
|
+
start: part.start,
|
|
604
|
+
assignmentOffset: part.start + leading.end,
|
|
605
|
+
});
|
|
404
606
|
continue;
|
|
607
|
+
}
|
|
405
608
|
const program = programName(word);
|
|
406
609
|
const args = words.slice(start.at + 1);
|
|
407
610
|
out.push({
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
611
|
+
segment: {
|
|
612
|
+
program,
|
|
613
|
+
args,
|
|
614
|
+
text: [program, ...args].join(' '),
|
|
615
|
+
timeoutSeconds: start.timeoutSeconds,
|
|
616
|
+
},
|
|
617
|
+
assignments: leading.words,
|
|
618
|
+
start: part.start,
|
|
619
|
+
assignmentOffset: part.start + leading.end,
|
|
412
620
|
});
|
|
413
621
|
}
|
|
414
622
|
return out;
|
|
415
623
|
}
|
|
624
|
+
/**
|
|
625
|
+
* The bound a command puts on its own run: the largest `timeout <N>` any of its segments runs
|
|
626
|
+
* under (`timeout 280 hurl --test …` → 280), or null when it carries none.
|
|
627
|
+
*
|
|
628
|
+
* This is the bound `man timeout` enforces by KILLING the command — so a run that used it did not
|
|
629
|
+
* finish, whatever the pipeline's own exit status says. It is read here, from the same parse every
|
|
630
|
+
* other command reader uses, rather than matched as text: `timeout` in an argument
|
|
631
|
+
* (`grep -n 'timeout 280' notes.md`) bounds nothing, and `-k 5` is the grace before the kill, not
|
|
632
|
+
* the bound ({@link WRAPPER_VALUE_FLAGS}).
|
|
633
|
+
*
|
|
634
|
+
* @param command - The shell command.
|
|
635
|
+
* @returns The seconds the command allows itself, or null.
|
|
636
|
+
*/
|
|
637
|
+
export function commandTimeoutSeconds(command) {
|
|
638
|
+
let bound = null;
|
|
639
|
+
for (const segment of commandSegments(command)) {
|
|
640
|
+
if (segment.timeoutSeconds === null)
|
|
641
|
+
continue;
|
|
642
|
+
bound = bound === null ? segment.timeoutSeconds : Math.max(bound, segment.timeoutSeconds);
|
|
643
|
+
}
|
|
644
|
+
return bound;
|
|
645
|
+
}
|
|
416
646
|
/**
|
|
417
647
|
* Whether any segment of the command runs a program whose command-position text matches
|
|
418
648
|
* `pattern` — the one question a runner-kind table asks.
|
package/dist/cut-run.d.ts
CHANGED
|
@@ -24,6 +24,12 @@
|
|
|
24
24
|
* `backends/sandbox.ts`), which reports 124 whatever the pipeline did, and returns everything
|
|
25
25
|
* printed until then. The budget is the tool's own, so its number belongs to the tool.
|
|
26
26
|
*
|
|
27
|
+
* A runner's closing summary is what separates "used the bound and finished" from "stopped at the
|
|
28
|
+
* bound", but it is only half the evidence: Playwright prints one when its process is stopped too,
|
|
29
|
+
* listing just the tests it reached. So a summary is a finish only when it accounts for the total
|
|
30
|
+
* the runner announced before it started ({@link suiteSummary}) — a summary that does not
|
|
31
|
+
* reconcile is a stopped run like any other, and the result says so.
|
|
32
|
+
*
|
|
27
33
|
* @module
|
|
28
34
|
*/
|
|
29
35
|
/**
|
|
@@ -46,14 +52,82 @@ export type CommandRunState = 'ran' | typeof CUT_AT_BUDGET;
|
|
|
46
52
|
*/
|
|
47
53
|
export declare function commandRunState(result: unknown): CommandRunState;
|
|
48
54
|
/**
|
|
49
|
-
*
|
|
55
|
+
* What a runner's own closing summary reports, and whether it accounts for the whole run.
|
|
56
|
+
*
|
|
57
|
+
* A summary is not by itself proof that the run finished. Playwright prints one when its process
|
|
58
|
+
* is stopped too (SIGTERM/SIGINT), listing only the tests it reached — so a run killed at 36 of
|
|
59
|
+
* 139 tests ends with the same `36 passed (2.5m)` line as a run that walked all 139. The only
|
|
60
|
+
* thing that separates them is the total the runner announced before it started
|
|
61
|
+
* (`Running 139 tests using 1 worker`), which {@link suiteSummary} reconciles the summary against.
|
|
62
|
+
*/
|
|
63
|
+
export interface SuiteSummary {
|
|
64
|
+
/**
|
|
65
|
+
* The total the runner announced before its first test, or null when it announced none (Hurl
|
|
66
|
+
* announces nothing; Vitest's own summary line carries its total in parentheses).
|
|
67
|
+
*/
|
|
68
|
+
announced: number | null;
|
|
69
|
+
/**
|
|
70
|
+
* Every test the summary accounts for — the ones it passed, failed, marked flaky, timed out,
|
|
71
|
+
* interrupted, skipped or left "did not run". This is how far the walk got, which is what a
|
|
72
|
+
* stopped run's sentence says; a summary that accounts for fewer than {@link announced} was
|
|
73
|
+
* printed by a process that stopped early.
|
|
74
|
+
*/
|
|
75
|
+
accounted: number;
|
|
76
|
+
/** Whether the summary accounts for every test the runner announced. */
|
|
77
|
+
finished: boolean;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* What the runner's closing summary accounts for, or null when it printed none.
|
|
81
|
+
*
|
|
82
|
+
* Three runners' summaries are read, each by its own wording: Playwright's per-status lines
|
|
83
|
+
* (reconciled against its `Running N tests` announcement), Vitest's single `Tests …` line (its own
|
|
84
|
+
* parenthesised total), and Hurl's `Executed files:` block (which it prints only at the end, and
|
|
85
|
+
* which announces no total — so it is a finish by construction).
|
|
86
|
+
*
|
|
87
|
+
* @param text - The run's stdout and stderr, RAW (a truncated copy can lose the announcement).
|
|
88
|
+
* @returns The summary, or null when the text carries none.
|
|
89
|
+
*/
|
|
90
|
+
export declare function suiteSummary(text: string): SuiteSummary | null;
|
|
91
|
+
/**
|
|
92
|
+
* Whether text carries a runner's closing summary.
|
|
93
|
+
*
|
|
94
|
+
* A summary is NOT a finish on its own — a stopped Playwright run prints one too. Use
|
|
95
|
+
* {@link suiteSummary} and its `finished` for that.
|
|
50
96
|
*
|
|
51
97
|
* @param text - The run's stdout and stderr.
|
|
52
98
|
* @returns True when the runner's own end-of-run summary is there.
|
|
53
99
|
*/
|
|
54
100
|
export declare function suiteTally(text: string): boolean;
|
|
101
|
+
/**
|
|
102
|
+
* The exit code a finished suite run may report.
|
|
103
|
+
*
|
|
104
|
+
* The code in a background result is the code of the command's LAST pipeline stage. When a stage
|
|
105
|
+
* after the suite runner still consumes its output (`| tee run.log`, or a filter the platform could
|
|
106
|
+
* not prove output-only), that stage produced the code, not the run — and `… | grep … | tail -20`
|
|
107
|
+
* exited 0 for a run with a failure in it (xr-molecule-flash-m101 +25:54). No code is reported
|
|
108
|
+
* then: the log's own tally decides.
|
|
109
|
+
*
|
|
110
|
+
* The same reading covers a code of 0 beside a tally that names a failure: whatever ended the
|
|
111
|
+
* pipeline, a run whose log counts a failure did not pass.
|
|
112
|
+
*
|
|
113
|
+
* @param exitCode - The code the pipeline reported (null when it has none).
|
|
114
|
+
* @param output - The run's full log.
|
|
115
|
+
* @param piped - Whether a pipeline stage after the suite runner still consumes its output.
|
|
116
|
+
* @returns The code the run may report, or null when the code is the filter's or contradicts the tally.
|
|
117
|
+
*/
|
|
118
|
+
export declare function suiteRunExitCode(exitCode: number | null, output: string, piped: boolean): number | null;
|
|
55
119
|
/** What stopped a run. */
|
|
56
|
-
export type CutBy =
|
|
120
|
+
export type CutBy =
|
|
121
|
+
/** This tool's own budget (the backend's `timeout`, just past it). */
|
|
122
|
+
'tool-budget'
|
|
123
|
+
/** The `timeout <N>` the command itself carried. */
|
|
124
|
+
| 'command-timeout'
|
|
125
|
+
/**
|
|
126
|
+
* Something outside the run: the runner's summary does not account for the total it announced,
|
|
127
|
+
* and neither of the two bounds above had fired — the process was killed by something this
|
|
128
|
+
* platform does not own (the sandbox's own process supervisor, the box going away, a signal).
|
|
129
|
+
*/
|
|
130
|
+
| 'process-stop';
|
|
57
131
|
/** A run a budget stopped, as its result reports it. */
|
|
58
132
|
export interface CutRun {
|
|
59
133
|
/** The bound the run was allowed, in seconds. */
|
|
@@ -65,9 +139,15 @@ export interface CutRun {
|
|
|
65
139
|
* the pipeline's filter (`… | tail`) rather than to the run — the case that read as success.
|
|
66
140
|
*/
|
|
67
141
|
exitCode: number | null;
|
|
68
|
-
/**
|
|
142
|
+
/**
|
|
143
|
+
* How far the run got, as its own output counts it — the files that finished, or the tests its
|
|
144
|
+
* summary accounts for — or null when the output says nothing.
|
|
145
|
+
*/
|
|
69
146
|
ran: number | null;
|
|
70
|
-
/**
|
|
147
|
+
/**
|
|
148
|
+
* How many the run was walking: the total its runner announced (`Running 139 tests`), or
|
|
149
|
+
* Playwright's `[n/K]`, whichever the output carries; null when neither does.
|
|
150
|
+
*/
|
|
71
151
|
total: number | null;
|
|
72
152
|
/** The noun the runner counts in. */
|
|
73
153
|
noun: 'files' | 'tests';
|
|
@@ -80,6 +160,9 @@ export interface CutRun {
|
|
|
80
160
|
*
|
|
81
161
|
* Evidence, in order — never a guess:
|
|
82
162
|
*
|
|
163
|
+
* - the runner's own summary, when it printed one: it is a finish only if it accounts for the total
|
|
164
|
+
* the runner announced. A summary that accounts for fewer is a walk a process stopped early, and
|
|
165
|
+
* falls through to the branches below with its own counts ({@link suiteSummary}).
|
|
83
166
|
* - `timeout`'s own 124 (the code the backend's wrapper returns whenever the TOOL's budget fired,
|
|
84
167
|
* whatever the pipeline ended with) — and, when the clock says the command's own smaller bound
|
|
85
168
|
* ran out first and less than the budget, that bound instead.
|
|
@@ -88,14 +171,19 @@ export interface CutRun {
|
|
|
88
171
|
* killed at its own bound. Its exit code, when the pipeline handed one back, is the filter's.
|
|
89
172
|
* - the run used this tool's whole budget behind a filter that withheld its output, and printed no
|
|
90
173
|
* closing summary: the budget stopped it, and no code in the result is the run's.
|
|
174
|
+
* - a summary that does not account for its announced total, with neither bound fired: something
|
|
175
|
+
* outside this platform stopped the process ({@link CutBy} `process-stop`).
|
|
91
176
|
*
|
|
92
177
|
* @param command - The command the executor sent.
|
|
93
178
|
* @param result - Its result.
|
|
94
179
|
* @param elapsedMs - How long the command ran, in ms.
|
|
95
180
|
* @param budgetSeconds - This tool's foreground budget, in seconds (0 when it has none).
|
|
181
|
+
* @param rawOutput - The run's output BEFORE truncation, when the caller has it: the announcement
|
|
182
|
+
* a summary is reconciled against is the first line a long run prints, which is exactly what a
|
|
183
|
+
* size cap drops. Detection reads this; the sentence's last line still comes from `result`.
|
|
96
184
|
* @returns The stopped run, or null.
|
|
97
185
|
*/
|
|
98
|
-
export declare function commandCutRun(command: string, result: unknown, elapsedMs: number, budgetSeconds?: number): CutRun | null;
|
|
186
|
+
export declare function commandCutRun(command: string, result: unknown, elapsedMs: number, budgetSeconds?: number, rawOutput?: string): CutRun | null;
|
|
99
187
|
/**
|
|
100
188
|
* The facts a result records about the run a budget stopped it in — what a reader needs to say
|
|
101
189
|
* how far the run got WITHOUT re-reading its log or re-deriving the counts.
|
|
@@ -110,12 +198,14 @@ export declare function commandCutRun(command: string, result: unknown, elapsedM
|
|
|
110
198
|
*/
|
|
111
199
|
export declare function cutRunFacts(result: unknown): CutRun | null;
|
|
112
200
|
/**
|
|
113
|
-
* The first line of a stopped run's result: how
|
|
114
|
-
*
|
|
201
|
+
* The first line of a stopped run's result: how far it got, what stopped it, the last thing it
|
|
202
|
+
* printed, and what the reader must do next.
|
|
115
203
|
*
|
|
116
204
|
* Written to be quotable by the model in one sentence and to leave no room for "nearly passing":
|
|
117
205
|
* the counts come from the runner's own output ({@link runProgress}), and a `total` the caller
|
|
118
|
-
* read elsewhere (the suite's own file count) fills in the "
|
|
206
|
+
* read elsewhere (the suite's own file count) fills in the "of N" it can then state. The sentence
|
|
207
|
+
* says how many tests the stop left unrun — a number a reader cannot round up to "the suite
|
|
208
|
+
* passed" — and hands over the one move that survives a long run: run it in the background.
|
|
119
209
|
*
|
|
120
210
|
* @param run - The stopped run.
|
|
121
211
|
* @returns The sentence.
|
package/dist/cut-run.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cut-run.d.ts","sourceRoot":"","sources":["../src/cut-run.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"cut-run.d.ts","sourceRoot":"","sources":["../src/cut-run.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAKH;;;GAGG;AACH,eAAO,MAAM,aAAa,kBAAkB,CAAA;AAE5C,kFAAkF;AAClF,MAAM,MAAM,eAAe,GAAG,KAAK,GAAG,OAAO,aAAa,CAAA;AAE1D;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,OAAO,GAAG,eAAe,CAGhE;AAiBD;;;;;;;;GAQG;AACH,MAAM,WAAW,YAAY;IAC3B;;;OAGG;IACH,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB;;;;;OAKG;IACH,SAAS,EAAE,MAAM,CAAA;IACjB,wEAAwE;IACxE,QAAQ,EAAE,OAAO,CAAA;CAClB;AAkDD;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CA4B9D;AAED;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEhD;AAKD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,MAAM,GAAG,IAAI,EACvB,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,OAAO,GACb,MAAM,GAAG,IAAI,CAIf;AAED,0BAA0B;AAC1B,MAAM,MAAM,KAAK;AACf,sEAAsE;AACpE,aAAa;AACf,oDAAoD;GAClD,iBAAiB;AACnB;;;;GAIG;GACD,cAAc,CAAA;AAElB,wDAAwD;AACxD,MAAM,WAAW,MAAM;IACrB,iDAAiD;IACjD,OAAO,EAAE,MAAM,CAAA;IACf,kFAAkF;IAClF,SAAS,EAAE,KAAK,CAAA;IAChB;;;OAGG;IACH,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB;;;OAGG;IACH,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;IAClB;;;OAGG;IACH,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,qCAAqC;IACrC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAA;IACvB,yEAAyE;IACzE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;CACrB;AAsDD;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,OAAO,EACf,SAAS,EAAE,MAAM,EACjB,aAAa,SAAI,EACjB,SAAS,CAAC,EAAE,MAAM,GACjB,MAAM,GAAG,IAAI,CAgFf;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAgB1D;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAuBlD"}
|