@molecule/api-ai-tools 1.9.18 → 1.10.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 +170 -19
- package/dist/command-position.d.ts +3 -1
- package/dist/command-position.d.ts.map +1 -1
- package/dist/command-position.js +95 -6
- package/dist/output-filter.d.ts +13 -6
- package/dist/output-filter.d.ts.map +1 -1
- package/dist/output-filter.js +35 -16
- package/dist/suite-overlap.d.ts +30 -1
- package/dist/suite-overlap.d.ts.map +1 -1
- package/dist/suite-overlap.js +171 -34
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +214 -24
- package/dist/trace-artifacts.d.ts +71 -0
- package/dist/trace-artifacts.d.ts.map +1 -1
- package/dist/trace-artifacts.js +398 -12
- package/dist/types.d.ts +19 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/utilities.d.ts +32 -12
- package/dist/utilities.d.ts.map +1 -1
- package/dist/utilities.js +101 -33
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@ AUTO-GENERATED — DO NOT EDIT THIS FILE.
|
|
|
3
3
|
Generated by `mlcl sync-docs` from the package's src/index.ts JSDoc + mlcl/registry.json.
|
|
4
4
|
Edits here are overwritten on the next commit (molecule's pre-commit hook regenerates).
|
|
5
5
|
To change this document, edit the module-level JSDoc in src/index.ts.
|
|
6
|
-
Generated: 2026-10-
|
|
6
|
+
Generated: 2026-10-18T22:07:04.913Z
|
|
7
7
|
-->
|
|
8
8
|
|
|
9
9
|
# @molecule/api-ai-tools
|
|
@@ -66,7 +66,9 @@ One statement or pipeline stage of a shell command: what it runs, and with what.
|
|
|
66
66
|
interface CommandSegment {
|
|
67
67
|
/**
|
|
68
68
|
* The word in command position, its directory removed (`/usr/bin/hurl` → `hurl`): the program
|
|
69
|
-
* the shell would run once whatever wrappers stood in front of it are taken away.
|
|
69
|
+
* the shell would run once whatever wrappers stood in front of it are taken away. When a
|
|
70
|
+
* wrapper runs a tool's own CLI by file, this is the tool's name — `node
|
|
71
|
+
* <path>/node_modules/.bin/playwright test` is a `playwright` invocation, not a `node` one.
|
|
70
72
|
*/
|
|
71
73
|
program: string
|
|
72
74
|
/** The words after it, as written — quotes kept, so a quoted `"a|b"` is one word. */
|
|
@@ -284,9 +286,13 @@ A cutting stage that follows the runner, and the command with the whole pipe rem
|
|
|
284
286
|
|
|
285
287
|
```typescript
|
|
286
288
|
interface RunnerOutputFilter {
|
|
287
|
-
/**
|
|
289
|
+
/**
|
|
290
|
+
* The stage exactly as the runner's command wrote it — `tail -6`. When several cutting filters
|
|
291
|
+
* follow the runner, this is the whole trailing chain as written, pipes included:
|
|
292
|
+
* `grep -E "Failure|actual" | head -40`.
|
|
293
|
+
*/
|
|
288
294
|
stage: string
|
|
289
|
-
/** The pipe as a sentence names it — `| tail -6`. */
|
|
295
|
+
/** The pipe as a sentence names it — `| tail -6`, or the whole chain `| grep … | head -40`. */
|
|
290
296
|
pipe: string
|
|
291
297
|
/**
|
|
292
298
|
* The command with the pipe and the stage removed — `hurl --variable uid=… --test /tmp/t.hurl
|
|
@@ -333,6 +339,17 @@ interface LeadingSleep {
|
|
|
333
339
|
}
|
|
334
340
|
```
|
|
335
341
|
|
|
342
|
+
#### `RunEnvSetting`
|
|
343
|
+
|
|
344
|
+
One `NAME=value` the request requires of a suite run.
|
|
345
|
+
|
|
346
|
+
```typescript
|
|
347
|
+
interface RunEnvSetting {
|
|
348
|
+
name: string
|
|
349
|
+
value: string
|
|
350
|
+
}
|
|
351
|
+
```
|
|
352
|
+
|
|
336
353
|
#### `SuiteRunInFlight`
|
|
337
354
|
|
|
338
355
|
One suite run, as the rule that decides whether a second may start beside it reads it.
|
|
@@ -511,6 +528,17 @@ interface TraceAction {
|
|
|
511
528
|
pageId?: string
|
|
512
529
|
/** The frame the action ran in, from the same snapshot. */
|
|
513
530
|
frameId?: string
|
|
531
|
+
/**
|
|
532
|
+
* The selector a `fill`/`type`/`selectOption` acted on (`textarea[name="bio"]`), from the
|
|
533
|
+
* trace's own params. Kept so the note can compare what the test typed with what the page
|
|
534
|
+
* submitted (m173: the field it typed into was sent empty).
|
|
535
|
+
*/
|
|
536
|
+
selector?: string
|
|
537
|
+
/**
|
|
538
|
+
* The value a `fill`/`type`/`selectOption` typed or chose, from the trace's own params —
|
|
539
|
+
* absent when the action carried none (a `selectOption` by label only).
|
|
540
|
+
*/
|
|
541
|
+
value?: string
|
|
514
542
|
}
|
|
515
543
|
```
|
|
516
544
|
|
|
@@ -561,6 +589,33 @@ interface TraceMissingText {
|
|
|
561
589
|
}
|
|
562
590
|
```
|
|
563
591
|
|
|
592
|
+
#### `TraceReadContradiction`
|
|
593
|
+
|
|
594
|
+
A write the trace recorded answering a boolean field, and a LATER GET of the same resource
|
|
595
|
+
answering the opposite for that field.
|
|
596
|
+
|
|
597
|
+
This is the pair that reads as a frontend race and is not one: xr-molecule-flash-m181's follow
|
|
598
|
+
test sent `POST /api/profiles/:username/follow` and the API answered
|
|
599
|
+
`{"profile":{…,"following":true}}`, then a `GET /api/profiles/:username` answered
|
|
600
|
+
`{"profile":{…,"following":false}}` — the READ route never saw the viewer, so it hydrated
|
|
601
|
+
`following` from nobody. The run rewrote the profile component twice, the API client once, and
|
|
602
|
+
added optimistic state before anyone read the route's middleware; the write itself was fine.
|
|
603
|
+
|
|
604
|
+
```typescript
|
|
605
|
+
interface TraceReadContradiction {
|
|
606
|
+
/** The boolean field both answered (`following`, `favorited`). */
|
|
607
|
+
field: string
|
|
608
|
+
/** What the write's response said it is. */
|
|
609
|
+
writeValue: boolean
|
|
610
|
+
/** What the later read's response said it is. */
|
|
611
|
+
readValue: boolean
|
|
612
|
+
/** The write that answered it (`POST /api/profiles/x/follow`). */
|
|
613
|
+
write: TraceRequest
|
|
614
|
+
/** The later GET of the same resource (`GET /api/profiles/x`). */
|
|
615
|
+
read: TraceRequest
|
|
616
|
+
}
|
|
617
|
+
```
|
|
618
|
+
|
|
564
619
|
#### `TraceArtifact`
|
|
565
620
|
|
|
566
621
|
What one trace says: the test it belongs to, the requests it made, and its console lines.
|
|
@@ -641,6 +696,15 @@ interface TraceArtifact {
|
|
|
641
696
|
* read before this field existed.
|
|
642
697
|
*/
|
|
643
698
|
missingText?: TraceMissingText
|
|
699
|
+
/**
|
|
700
|
+
* A write's answer a later GET of the same resource contradicted — the read route does not see
|
|
701
|
+
* the viewer (xr-molecule-flash-m181's `following`). The note leads with it and sends the run at
|
|
702
|
+
* the route's middleware, not the page's state handling.
|
|
703
|
+
*
|
|
704
|
+
* Absent when no write and later read in the trace answered the same boolean field differently,
|
|
705
|
+
* and on traces read before this field existed.
|
|
706
|
+
*/
|
|
707
|
+
readContradiction?: TraceReadContradiction
|
|
644
708
|
}
|
|
645
709
|
```
|
|
646
710
|
|
|
@@ -897,6 +961,26 @@ interface ToolBuildConfig {
|
|
|
897
961
|
*/
|
|
898
962
|
excludedTestTitles?: () => readonly string[]
|
|
899
963
|
|
|
964
|
+
/**
|
|
965
|
+
* The `NAME=value` settings the person's request REQUIRES of a suite run (molecule-dev's
|
|
966
|
+
* `requiredRunSettings` — its mode, say), asked when the cause note composes a narrowed re-run
|
|
967
|
+
* of a finished run's failures. A re-run composed from the executor's own command inherits what
|
|
968
|
+
* that command set — and nothing it left out, so a run that measured the suite in `spa` handed
|
|
969
|
+
* back a re-run that would measure `spa` again (xr-molecule-flash-m182 +12:06: the request names
|
|
970
|
+
* `TEST_MODE=fullstack`, the run and its re-run both omitted it).
|
|
971
|
+
*
|
|
972
|
+
* A getter, not an array: the request is read per turn while the tool set is built once, and
|
|
973
|
+
* what the suite's own files read is the consumer's reading. Unset answers "nothing required".
|
|
974
|
+
*
|
|
975
|
+
* @param command - The command the finished run was started with.
|
|
976
|
+
* @param cwd - The resolved working directory it ran in.
|
|
977
|
+
* @returns The settings to put on a re-run of that command.
|
|
978
|
+
*/
|
|
979
|
+
requiredSuiteEnv?: (
|
|
980
|
+
command: string,
|
|
981
|
+
cwd: string,
|
|
982
|
+
) => Promise<readonly { name: string; value: string }[] | null | undefined>
|
|
983
|
+
|
|
900
984
|
/**
|
|
901
985
|
* The supplied suite's own budget, when the Playwright config a command would load sets
|
|
902
986
|
* different settings, asked BEFORE the run executes. The consumer knows where the suite and its
|
|
@@ -1699,10 +1783,13 @@ function commandRunsNoTests(command: string): boolean
|
|
|
1699
1783
|
|
|
1700
1784
|
The output filter that follows the runner in `command`, or null when there is none.
|
|
1701
1785
|
|
|
1702
|
-
Only
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1786
|
+
Only stages of the runner's OWN pipeline count (their separator is `|`), and they are read as
|
|
1787
|
+
one trailing CHAIN: `| grep … | head -40` is two cuts, and removing only the first would leave
|
|
1788
|
+
the run piped into the second. Every stage after the runner must be a cutting filter and the
|
|
1789
|
+
chain must run to the pipeline's end — a pipeline that continues into `wc -l` is not answered
|
|
1790
|
+
here, because removing the cutting stages alone would still leave the failure hidden. A filter
|
|
1791
|
+
before the runner (`cat run.log | tail -20 && hurl …`) reads something else's output and is not
|
|
1792
|
+
this.
|
|
1706
1793
|
|
|
1707
1794
|
```typescript
|
|
1708
1795
|
function outputFilterAfterRunner(
|
|
@@ -1881,13 +1968,17 @@ function withSuiteConfig(command: string, config: string | null | undefined): st
|
|
|
1881
1968
|
|
|
1882
1969
|
**Returns:** The command with the flag, or the command unchanged.
|
|
1883
1970
|
|
|
1884
|
-
#### `scopedRerunCommand(command, locations, excluded, config, cwd)`
|
|
1971
|
+
#### `scopedRerunCommand(command, locations, excluded, config, cwd, env)`
|
|
1885
1972
|
|
|
1886
1973
|
The whole-suite command made scoped to the tests it should re-run instead: the same
|
|
1887
1974
|
invocation — same directory, same settings — with the named `file:line`s in place of the whole
|
|
1888
1975
|
suite, `--last-failed` dropped (it re-runs the tests that never ran too, X1 flash-m60) and
|
|
1889
1976
|
`--retries=0` set (a re-run of a known failure does not need to pay for retries).
|
|
1890
1977
|
|
|
1978
|
+
The re-run keeps the run's `> <file>` redirect only in the sense of keeping its OUTPUT there:
|
|
1979
|
+
the target is rewritten to a fresh sibling ({@link redirectToFreshFile}), so a re-run never
|
|
1980
|
+
overwrites the log the run itself is the evidence for (xr-molecule-flash-m173).
|
|
1981
|
+
|
|
1891
1982
|
`config` is the config the run was measured under ({@link withSuiteConfig}): the narrowed re-run
|
|
1892
1983
|
carries it whenever the command itself does not, so a run that dropped `--config` cannot be
|
|
1893
1984
|
handed back a command with the same defect (xr-molecule-flash-m113: every failing test was
|
|
@@ -1900,6 +1991,7 @@ function scopedRerunCommand(
|
|
|
1900
1991
|
excluded?: readonly string[],
|
|
1901
1992
|
config?: string | null,
|
|
1902
1993
|
cwd?: string | null,
|
|
1994
|
+
env?: readonly RunEnvSetting[],
|
|
1903
1995
|
): string
|
|
1904
1996
|
```
|
|
1905
1997
|
|
|
@@ -1908,9 +2000,32 @@ function scopedRerunCommand(
|
|
|
1908
2000
|
- `excluded` — The test titles the request carves out. They are inverted out (`--grep-invert`) so a re-run of the failing locations cannot report one of them again — xr-molecule-flash-m101 re-ran the whole 139-test suite and spent ~4 minutes to be told about a test the request had already said does not count.
|
|
1909
2001
|
- `config` — The config the run loaded, when the caller knows it; added when the command does not name one.
|
|
1910
2002
|
- `cwd` — The directory `exec_command` was given, when it was; the base a location is resolved against when the command does not `cd` anywhere.
|
|
2003
|
+
- `env` — The settings the REQUEST requires of the suite and this run left out ({@link withRunEnv}): the re-run carries them, so a re-run of a run measured in the wrong mode is not measured in that mode again (xr-molecule-flash-m182 +12:06).
|
|
1911
2004
|
|
|
1912
2005
|
**Returns:** The scoped command, or the command unchanged when it runs no Playwright suite.
|
|
1913
2006
|
|
|
2007
|
+
#### `withRunEnv(command, settings)`
|
|
2008
|
+
|
|
2009
|
+
The command with the request's required settings put in front of its RUN statement — the ONE
|
|
2010
|
+
place every composed re-run gets them (xr-molecule-flash-m182).
|
|
2011
|
+
|
|
2012
|
+
The settings are written at the statement's own assignment offset, never in front of the whole
|
|
2013
|
+
command: a shell binds an assignment there to the statement's program, while one in front of the
|
|
2014
|
+
whole command is the environment of its FIRST statement alone (`APP_BASE=… cd app && npx
|
|
2015
|
+
playwright test` sets the variable for `cd`; xr-molecule-flash-m103 +28:08). A name the command
|
|
2016
|
+
gives a different value is replaced, and one it already gives the required value is left as
|
|
2017
|
+
written. When the parse cannot place the statement (a run inside a nested `sh -c '…'`), the
|
|
2018
|
+
settings become `export`s, which no statement boundary can swallow.
|
|
2019
|
+
|
|
2020
|
+
```typescript
|
|
2021
|
+
function withRunEnv(command: string, settings?: readonly RunEnvSetting[]): string
|
|
2022
|
+
```
|
|
2023
|
+
|
|
2024
|
+
- `command` — The command to carry them.
|
|
2025
|
+
- `settings` — The `NAME`/`value` pairs the request requires.
|
|
2026
|
+
|
|
2027
|
+
**Returns:** The command, or the command unchanged when it already carries every setting.
|
|
2028
|
+
|
|
1914
2029
|
#### `suiteRunInTheWay(candidate, inFlight)`
|
|
1915
2030
|
|
|
1916
2031
|
The run in flight that makes a second run a hazard, or null when nothing is in the way.
|
|
@@ -2226,6 +2341,25 @@ function stalledDocumentSubresource(request: TraceRequest): boolean
|
|
|
2226
2341
|
|
|
2227
2342
|
**Returns:** True when the request is a document subresource that never finished.
|
|
2228
2343
|
|
|
2344
|
+
#### `isCredentialExchangePath(path, authEndpoints)`
|
|
2345
|
+
|
|
2346
|
+
Whether a request PATH asks the app to mint a credential — a login, a registration, a token
|
|
2347
|
+
endpoint — as opposed to a protected resource that presents one.
|
|
2348
|
+
|
|
2349
|
+
This is the path half of {@link isCredentialExchange}, exported so a caller holding only a
|
|
2350
|
+
`METHOD path` reads the same classification: a Hurl failing entry says
|
|
2351
|
+
`POST /api/users/login` and nothing about the body, and the whole-suite note must not explain
|
|
2352
|
+
a wrong-password login that answered 200 as a cookie-jar effect (xr-molecule-flash-m176).
|
|
2353
|
+
|
|
2354
|
+
```typescript
|
|
2355
|
+
function isCredentialExchangePath(path: string, authEndpoints?: readonly string[]): boolean
|
|
2356
|
+
```
|
|
2357
|
+
|
|
2358
|
+
- `path` — The request path (`/api/users/login`); a query or fragment is ignored.
|
|
2359
|
+
- `authEndpoints` — The endpoints the app's auth config names (`/users/login`).
|
|
2360
|
+
|
|
2361
|
+
**Returns:** True when the path names a credential exchange.
|
|
2362
|
+
|
|
2229
2363
|
#### `failureTraceNote(items, target, earlierFailures, apiPrefixes, report)`
|
|
2230
2364
|
|
|
2231
2365
|
The note a failed suite run's result carries: what each failing test's page actually sent, and
|
|
@@ -3372,14 +3506,20 @@ reads as ten defects in the app.
|
|
|
3372
3506
|
function failureCausesNote(
|
|
3373
3507
|
log: string,
|
|
3374
3508
|
excluded?: readonly string[],
|
|
3375
|
-
run?: {
|
|
3509
|
+
run?: {
|
|
3510
|
+
command: string
|
|
3511
|
+
cwd?: string
|
|
3512
|
+
log?: string
|
|
3513
|
+
config?: string | null
|
|
3514
|
+
env?: readonly RunEnvSetting[]
|
|
3515
|
+
},
|
|
3376
3516
|
pageErrors?: readonly CausePageErrors[],
|
|
3377
3517
|
): string | null
|
|
3378
3518
|
```
|
|
3379
3519
|
|
|
3380
3520
|
- `log` — The finished run's output.
|
|
3381
3521
|
- `excluded` — The test titles the request carves out, if the caller knows them.
|
|
3382
|
-
- `run` — How the run was started, when the caller knows it: the narrowed re-run is built from `command`/`cwd`/`config`, and `log` (the path the output was written to) is what the "left out" sentence greps when more causes exist than the result has room for.
|
|
3522
|
+
- `run` — How the run was started, when the caller knows it: the narrowed re-run is built from `command`/`cwd`/`config`/`env`, and `log` (the path the output was written to) is what the "left out" sentence greps when more causes exist than the result has room for.
|
|
3383
3523
|
|
|
3384
3524
|
**Returns:** The note, or null.
|
|
3385
3525
|
|
|
@@ -3398,13 +3538,13 @@ refused while the run walks ({@link suiteRunInTheWay}).
|
|
|
3398
3538
|
|
|
3399
3539
|
```typescript
|
|
3400
3540
|
function rerunOfRunningSuite(
|
|
3401
|
-
run: { command: string; cwd?: string; config?: string | null },
|
|
3541
|
+
run: { command: string; cwd?: string; config?: string | null; env?: readonly RunEnvSetting[] },
|
|
3402
3542
|
locations: readonly string[],
|
|
3403
3543
|
excluded?: readonly string[],
|
|
3404
3544
|
): string | null
|
|
3405
3545
|
```
|
|
3406
3546
|
|
|
3407
|
-
- `run` — The run in flight: the command it was started with, as it was run, the directory that command ran in,
|
|
3547
|
+
- `run` — The run in flight: the command it was started with, as it was run, the directory that command ran in, the config it loaded when the caller knows it, and the settings the request required and the run left out (`env`) — the re-run carries them, so it never reproduces the configuration the run was measured under by mistake (m182).
|
|
3408
3548
|
- `locations` — The failing `file:line`s to re-run, as the run's log printed them.
|
|
3409
3549
|
- `excluded` — The test titles the request carves out: the re-run inverts them out (`--grep-invert`) so it cannot report them again.
|
|
3410
3550
|
|
|
@@ -3443,7 +3583,7 @@ function repeatingFailureNote(
|
|
|
3443
3583
|
log: string
|
|
3444
3584
|
stop: string
|
|
3445
3585
|
universal?: UniversalCause | null
|
|
3446
|
-
run?: { command: string; cwd?: string }
|
|
3586
|
+
run?: { command: string; cwd?: string; env?: readonly RunEnvSetting[] }
|
|
3447
3587
|
editsInvalidateRun?: boolean
|
|
3448
3588
|
},
|
|
3449
3589
|
): string
|
|
@@ -4026,11 +4166,22 @@ const UNIVERSAL_CAUSE_FILES: 3
|
|
|
4026
4166
|
#### `EMPTY_PAGE_CAUSE_TESTS`
|
|
4027
4167
|
|
|
4028
4168
|
How many tests a run must have walked — with nothing passing since its first failure — before
|
|
4029
|
-
"the page renders nothing" can be its one cause.
|
|
4030
|
-
|
|
4031
|
-
|
|
4032
|
-
|
|
4033
|
-
|
|
4169
|
+
"the page renders nothing" can be its one cause.
|
|
4170
|
+
|
|
4171
|
+
THREE, and the number is the cost of the alternative. The old floor of 15 was chosen when the
|
|
4172
|
+
reading was only offered at a completed run; once it STOPS the walk, every test waited for is
|
|
4173
|
+
a test whose result is thrown away, and a blank page costs its per-test budget each time
|
|
4174
|
+
(m177: 18 tests, ~4.2 minutes, on a page whose thrown error was visible in the first
|
|
4175
|
+
`pageerror`). Three is the smallest count that is not one page's own bug: the cause additionally
|
|
4176
|
+
needs three DISTINCT spec files (`UNIVERSAL_CAUSE_FILES`, or the files a narrowed run selected),
|
|
4177
|
+
each spec waiting for its OWN first element, and no test passing after the first failure. A page
|
|
4178
|
+
that renders misses elements, but three unrelated specs in three files failing at three
|
|
4179
|
+
different first locators with nothing passing in between is the page, not three bugs. At the
|
|
4180
|
+
suite's own 5 s action timeout that verdict lands ~20 s into the walk, so the early stop costs
|
|
4181
|
+
about one test's worth of time instead of fifteen.
|
|
4182
|
+
|
|
4183
|
+
```typescript
|
|
4184
|
+
const EMPTY_PAGE_CAUSE_TESTS: 3
|
|
4034
4185
|
```
|
|
4035
4186
|
|
|
4036
4187
|
#### `CONNECTION_CAUSE_TESTS`
|
|
@@ -36,7 +36,9 @@
|
|
|
36
36
|
export interface CommandSegment {
|
|
37
37
|
/**
|
|
38
38
|
* The word in command position, its directory removed (`/usr/bin/hurl` → `hurl`): the program
|
|
39
|
-
* the shell would run once whatever wrappers stood in front of it are taken away.
|
|
39
|
+
* the shell would run once whatever wrappers stood in front of it are taken away. When a
|
|
40
|
+
* wrapper runs a tool's own CLI by file, this is the tool's name — `node
|
|
41
|
+
* <path>/node_modules/.bin/playwright test` is a `playwright` invocation, not a `node` one.
|
|
40
42
|
*/
|
|
41
43
|
program: string;
|
|
42
44
|
/** The words after it, as written — quotes kept, so a quoted `"a|b"` is one word. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"command-position.d.ts","sourceRoot":"","sources":["../src/command-position.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,uFAAuF;AACvF,MAAM,WAAW,cAAc;IAC7B
|
|
1
|
+
{"version":3,"file":"command-position.d.ts","sourceRoot":"","sources":["../src/command-position.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,uFAAuF;AACvF,MAAM,WAAW,cAAc;IAC7B;;;;;OAKG;IACH,OAAO,EAAE,MAAM,CAAA;IACf,qFAAqF;IACrF,IAAI,EAAE,SAAS,MAAM,EAAE,CAAA;IACvB,4FAA4F;IAC5F,IAAI,EAAE,MAAM,CAAA;IACZ;;;OAGG;IACH,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;CAC9B;AA8RD;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAM3D;AAwkBD;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,cAAc,EAAE,CAIjE;AAmCD;;;;;;;;;GASG;AACH,MAAM,WAAW,qBAAqB;IACpC;;;;OAIG;IACH,OAAO,EAAE,cAAc,GAAG,IAAI,CAAA;IAC9B,0FAA0F;IAC1F,WAAW,EAAE,SAAS,MAAM,EAAE,CAAA;IAC9B;;;;OAIG;IACH,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB;;;;;OAKG;IACH,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAA;IAC/B;;;OAGG;IACH,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;IAClB;;;;OAIG;IACH,aAAa,EAAE,MAAM,GAAG,IAAI,CAAA;IAC5B;;;;;OAKG;IACH,SAAS,EAAE,MAAM,CAAA;IACjB;;;OAGG;IACH,UAAU,EAAE,MAAM,CAAA;IAClB;;;;;OAKG;IACH,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;CAC9B;AAED;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,GAAG,qBAAqB,EAAE,CAE/E;AAmDD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,GAAG,IAAI,EACpB,GAAG,EAAE,MAAM,GAAG,IAAI,GACjB,MAAM,GAAG,IAAI,CAyDf;AA2FD;;;;;;;;;;;;GAYG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAOpE;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,CAAC,OAAO,EAAE,cAAc,KAAK,OAAO,GAC1C;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAoD7C;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAK/E"}
|
package/dist/command-position.js
CHANGED
|
@@ -68,6 +68,75 @@ const LAUNCHERS = new Set([
|
|
|
68
68
|
]);
|
|
69
69
|
/** The shells whose `-c '<command>'` argument is a command of its own. */
|
|
70
70
|
const SHELLS = new Set(['ash', 'bash', 'dash', 'ksh', 'sh', 'zsh']);
|
|
71
|
+
/**
|
|
72
|
+
* Node, which is a wrapper only when the script it runs IS a tool's own CLI — never in general:
|
|
73
|
+
* `node script.mjs` RUNS that script, so the parser's answer for it stays `node`.
|
|
74
|
+
*
|
|
75
|
+
* The Playwright runner is spelled this way whenever a project reaches it by file instead of by
|
|
76
|
+
* the `.bin` shim: xr-molecule-flash-m179 (+17:51) ran
|
|
77
|
+
* `node /workspace/node_modules/.bin/playwright test` after `npx playwright test` in every
|
|
78
|
+
* earlier call, and no reader recognised it — the run got no `--trace`, no suite-budget pin and
|
|
79
|
+
* no proof credit, and the platform spent the rest of the turn re-walking a suite it had already
|
|
80
|
+
* walked green.
|
|
81
|
+
*/
|
|
82
|
+
const NODE = new Set(['node']);
|
|
83
|
+
/**
|
|
84
|
+
* The Playwright CLI, as the path operand of a `node` invocation: the package's `.bin` shim, or
|
|
85
|
+
* the `cli.js` a package installs (`playwright/cli.js`, `@playwright/test/cli.js`).
|
|
86
|
+
*/
|
|
87
|
+
const PLAYWRIGHT_CLI = /(?:^|\/)node_modules\/(?:\.bin\/playwright|(?:playwright|@playwright\/test)\/cli\.(?:js|mjs|cjs))$/;
|
|
88
|
+
/**
|
|
89
|
+
* The runner a `node` script operand IS, or null when the operand is any other script.
|
|
90
|
+
*
|
|
91
|
+
* @param word - The script operand, as written.
|
|
92
|
+
* @returns The runner's name (`playwright`), or null.
|
|
93
|
+
*/
|
|
94
|
+
function nodeScriptRunner(word) {
|
|
95
|
+
return PLAYWRIGHT_CLI.test(unquoted(word)) ? 'playwright' : null;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Node's own value-taking options: the ones that consume the word after them, so the script
|
|
99
|
+
* operand is not mistaken for a value (`node -r tsx/cli.js …`). Node also accepts `--flag=value`.
|
|
100
|
+
*/
|
|
101
|
+
const NODE_VALUE_FLAGS = new Set([
|
|
102
|
+
'-e',
|
|
103
|
+
'--eval',
|
|
104
|
+
'-p',
|
|
105
|
+
'--print',
|
|
106
|
+
'-r',
|
|
107
|
+
'--require',
|
|
108
|
+
'--import',
|
|
109
|
+
'--loader',
|
|
110
|
+
'--experimental-loader',
|
|
111
|
+
'--input-type',
|
|
112
|
+
'--conditions',
|
|
113
|
+
'--title',
|
|
114
|
+
]);
|
|
115
|
+
/**
|
|
116
|
+
* The word a `node` invocation runs as its script: the first operand after node's own options,
|
|
117
|
+
* with `--` ending the options. Null when node runs no file (`node --test`, `node -e '<code>'`).
|
|
118
|
+
*
|
|
119
|
+
* A wrong guess here is harmless: the operand is then checked against {@link PLAYWRIGHT_CLI}, so
|
|
120
|
+
* an ordinary script (`node probe.mjs`) is never read as a runner.
|
|
121
|
+
*
|
|
122
|
+
* @param words - The segment's words.
|
|
123
|
+
* @param from - The index just after `node`.
|
|
124
|
+
* @returns The script operand's index, or null.
|
|
125
|
+
*/
|
|
126
|
+
function nodeScriptIndex(words, from) {
|
|
127
|
+
let at = from;
|
|
128
|
+
for (let guard = 0; guard < MAX_WRAPPERS && at < words.length; guard++) {
|
|
129
|
+
const word = words[at] ?? '';
|
|
130
|
+
if (!word.startsWith('-'))
|
|
131
|
+
return at;
|
|
132
|
+
const flag = unquoted(word);
|
|
133
|
+
// `--` ends node's options; the script is the next word.
|
|
134
|
+
if (flag === '--')
|
|
135
|
+
return at + 1 < words.length ? at + 1 : null;
|
|
136
|
+
at += flag.includes('=') || !NODE_VALUE_FLAGS.has(flag) ? 1 : 2;
|
|
137
|
+
}
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
71
140
|
/** Package runners that run the program named after them (`npx playwright test`). */
|
|
72
141
|
const PACKAGE_RUNNERS = new Set(['bunx', 'npx', 'pnpx']);
|
|
73
142
|
/** Package managers whose sub-command runs a package's binary (`bun x`, `pnpm dlx`, `npm exec`). */
|
|
@@ -345,6 +414,8 @@ function nestedCommand(words, from) {
|
|
|
345
414
|
* @returns The index of the word in command position (one past the end when the segment runs
|
|
346
415
|
* nothing), the command string of a `sh -c '<command>'` this segment turns out to be, and the
|
|
347
416
|
* seconds its own `timeout` wrapper allows that command (`timeout 280 hurl …` → 280) or null.
|
|
417
|
+
* `program` names what runs when that is not the word itself's name — `node <path>/playwright`
|
|
418
|
+
* is a `playwright` invocation, not a `node` one ({@link nodeScriptRunner}).
|
|
348
419
|
*/
|
|
349
420
|
function commandStart(words) {
|
|
350
421
|
let at = 0;
|
|
@@ -364,14 +435,14 @@ function commandStart(words) {
|
|
|
364
435
|
if (SHELLS.has(name)) {
|
|
365
436
|
const nested = nestedCommand(words, at + 1);
|
|
366
437
|
if (nested !== null)
|
|
367
|
-
return { at, nested, timeoutSeconds };
|
|
438
|
+
return { at, nested, timeoutSeconds, program: null };
|
|
368
439
|
at += 1 + flagWords(words, at + 1, name);
|
|
369
440
|
continue;
|
|
370
441
|
}
|
|
371
442
|
if (PACKAGE_RUNNERS.has(name)) {
|
|
372
443
|
const runner = packageRunnerOptions(words, at + 1, name);
|
|
373
444
|
if (runner.nested !== null)
|
|
374
|
-
return { at, nested: runner.nested, timeoutSeconds };
|
|
445
|
+
return { at, nested: runner.nested, timeoutSeconds, program: null };
|
|
375
446
|
at += 1 + runner.count;
|
|
376
447
|
continue;
|
|
377
448
|
}
|
|
@@ -380,19 +451,35 @@ function commandStart(words) {
|
|
|
380
451
|
const subcommand = unquoted(words[at + 1] ?? '');
|
|
381
452
|
const runner = packageRunnerOptions(words, at + 2, `${name} ${subcommand}`);
|
|
382
453
|
if (runner.nested !== null)
|
|
383
|
-
return { at, nested: runner.nested, timeoutSeconds };
|
|
454
|
+
return { at, nested: runner.nested, timeoutSeconds, program: null };
|
|
384
455
|
at += 2 + runner.count;
|
|
385
456
|
continue;
|
|
386
457
|
}
|
|
458
|
+
if (name === 'yarn' && unquoted(words[at + 1] ?? '') === 'playwright') {
|
|
459
|
+
// Yarn classic runs the binary named after it (`yarn playwright test`); Yarn 2+ spells the
|
|
460
|
+
// same thing `yarn exec`/`yarn dlx` ({@link PACKAGE_RUNNER_SUBCOMMANDS}). Only the runner's
|
|
461
|
+
// own name is read through it — `yarn test` is the project's own script, not a binary.
|
|
462
|
+
at += 1;
|
|
463
|
+
continue;
|
|
464
|
+
}
|
|
465
|
+
if (NODE.has(name)) {
|
|
466
|
+
// `node <path>/playwright test` is a Playwright invocation; `node probe.mjs` runs the
|
|
467
|
+
// project's own script and stays a `node` statement (`program: null`).
|
|
468
|
+
const script = nodeScriptIndex(words, at + 1);
|
|
469
|
+
const runner = script === null ? null : nodeScriptRunner(words[script] ?? '');
|
|
470
|
+
return runner === null
|
|
471
|
+
? { at, nested: null, timeoutSeconds, program: null }
|
|
472
|
+
: { at: script ?? at, nested: null, timeoutSeconds, program: runner };
|
|
473
|
+
}
|
|
387
474
|
if (!LAUNCHERS.has(name))
|
|
388
|
-
return { at, nested: null, timeoutSeconds };
|
|
475
|
+
return { at, nested: null, timeoutSeconds, program: null };
|
|
389
476
|
at += 1 + flagWords(words, at + 1, name);
|
|
390
477
|
if (name === 'timeout' && DURATION.test(words[at] ?? '')) {
|
|
391
478
|
timeoutSeconds = durationSeconds(words[at] ?? '');
|
|
392
479
|
at += 1;
|
|
393
480
|
}
|
|
394
481
|
}
|
|
395
|
-
return { at, nested: null, timeoutSeconds };
|
|
482
|
+
return { at, nested: null, timeoutSeconds, program: null };
|
|
396
483
|
}
|
|
397
484
|
/**
|
|
398
485
|
* A command with every heredoc BODY blanked to spaces, so its length and every offset in it are
|
|
@@ -993,7 +1080,9 @@ function locatedSegmentsOf(command, depth) {
|
|
|
993
1080
|
assignmentOffset: part.start + leading.end,
|
|
994
1081
|
programOffset: words[start.at] === undefined ? null : part.start + (wordOffset(part.text, start.at) ?? 0),
|
|
995
1082
|
};
|
|
996
|
-
|
|
1083
|
+
// The runner the word RUNS names, when that is not the word's own name (`node <path>/playwright`
|
|
1084
|
+
// is a `playwright` invocation, xr-molecule-flash-m179).
|
|
1085
|
+
const program = words[start.at] === undefined ? '' : (start.program ?? programName(words[start.at] ?? ''));
|
|
997
1086
|
const args = words.slice(start.at + 1);
|
|
998
1087
|
const redirectTarget = redirectTargetOf(part.text, part.depths, program, args);
|
|
999
1088
|
if (start.nested !== null) {
|
package/dist/output-filter.d.ts
CHANGED
|
@@ -19,9 +19,13 @@
|
|
|
19
19
|
import { type CommandSegment } from './command-position.js';
|
|
20
20
|
/** A cutting stage that follows the runner, and the command with the whole pipe removed. */
|
|
21
21
|
export interface RunnerOutputFilter {
|
|
22
|
-
/**
|
|
22
|
+
/**
|
|
23
|
+
* The stage exactly as the runner's command wrote it — `tail -6`. When several cutting filters
|
|
24
|
+
* follow the runner, this is the whole trailing chain as written, pipes included:
|
|
25
|
+
* `grep -E "Failure|actual" | head -40`.
|
|
26
|
+
*/
|
|
23
27
|
stage: string;
|
|
24
|
-
/** The pipe as a sentence names it — `| tail -6`. */
|
|
28
|
+
/** The pipe as a sentence names it — `| tail -6`, or the whole chain `| grep … | head -40`. */
|
|
25
29
|
pipe: string;
|
|
26
30
|
/**
|
|
27
31
|
* The command with the pipe and the stage removed — `hurl --variable uid=… --test /tmp/t.hurl
|
|
@@ -33,10 +37,13 @@ export interface RunnerOutputFilter {
|
|
|
33
37
|
/**
|
|
34
38
|
* The output filter that follows the runner in `command`, or null when there is none.
|
|
35
39
|
*
|
|
36
|
-
* Only
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
+
* Only stages of the runner's OWN pipeline count (their separator is `|`), and they are read as
|
|
41
|
+
* one trailing CHAIN: `| grep … | head -40` is two cuts, and removing only the first would leave
|
|
42
|
+
* the run piped into the second. Every stage after the runner must be a cutting filter and the
|
|
43
|
+
* chain must run to the pipeline's end — a pipeline that continues into `wc -l` is not answered
|
|
44
|
+
* here, because removing the cutting stages alone would still leave the failure hidden. A filter
|
|
45
|
+
* before the runner (`cat run.log | tail -20 && hurl …`) reads something else's output and is not
|
|
46
|
+
* this.
|
|
40
47
|
*
|
|
41
48
|
* @param command - The command as the executor sent it.
|
|
42
49
|
* @param runs - Whether a segment is the runner this run is about (the caller knows its own kind:
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"output-filter.d.ts","sourceRoot":"","sources":["../src/output-filter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,
|
|
1
|
+
{"version":3,"file":"output-filter.d.ts","sourceRoot":"","sources":["../src/output-filter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EACL,KAAK,cAAc,EAGpB,MAAM,uBAAuB,CAAA;AAS9B,4FAA4F;AAC5F,MAAM,WAAW,kBAAkB;IACjC;;;;OAIG;IACH,KAAK,EAAE,MAAM,CAAA;IACb,+FAA+F;IAC/F,IAAI,EAAE,MAAM,CAAA;IACZ;;;;OAIG;IACH,OAAO,EAAE,MAAM,CAAA;CAChB;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,CAAC,OAAO,EAAE,cAAc,KAAK,OAAO,EAC1C,UAAU,SAAI,GACb,kBAAkB,GAAG,IAAI,CAmC3B"}
|
package/dist/output-filter.js
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
*
|
|
17
17
|
* @module
|
|
18
18
|
*/
|
|
19
|
-
import { locatedCommandSegments } from './command-position.js';
|
|
19
|
+
import { locatedCommandSegments, } from './command-position.js';
|
|
20
20
|
/**
|
|
21
21
|
* The programs whose job is to re-shape what an earlier stage printed and that can DROP lines —
|
|
22
22
|
* the reader's own cut, never part of the run. `tee`, `sort` and `wc` are output plumbing too, but
|
|
@@ -26,10 +26,13 @@ const CUTTING_FILTERS = new Set(['awk', 'cut', 'grep', 'head', 'sed', 'tail']);
|
|
|
26
26
|
/**
|
|
27
27
|
* The output filter that follows the runner in `command`, or null when there is none.
|
|
28
28
|
*
|
|
29
|
-
* Only
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
29
|
+
* Only stages of the runner's OWN pipeline count (their separator is `|`), and they are read as
|
|
30
|
+
* one trailing CHAIN: `| grep … | head -40` is two cuts, and removing only the first would leave
|
|
31
|
+
* the run piped into the second. Every stage after the runner must be a cutting filter and the
|
|
32
|
+
* chain must run to the pipeline's end — a pipeline that continues into `wc -l` is not answered
|
|
33
|
+
* here, because removing the cutting stages alone would still leave the failure hidden. A filter
|
|
34
|
+
* before the runner (`cat run.log | tail -20 && hurl …`) reads something else's output and is not
|
|
35
|
+
* this.
|
|
33
36
|
*
|
|
34
37
|
* @param command - The command as the executor sent it.
|
|
35
38
|
* @param runs - Whether a segment is the runner this run is about (the caller knows its own kind:
|
|
@@ -51,23 +54,39 @@ export function outputFilterAfterRunner(command, runs, occurrence = 1) {
|
|
|
51
54
|
runners.push(index);
|
|
52
55
|
});
|
|
53
56
|
const runnerAt = runners[occurrence - 1];
|
|
54
|
-
if (runnerAt === undefined
|
|
57
|
+
if (runnerAt === undefined)
|
|
55
58
|
return null;
|
|
56
59
|
const runner = entries[runnerAt];
|
|
57
|
-
|
|
58
|
-
// The stage must be fed by the runner's own pipe, and nothing may be piped out of the filter:
|
|
59
|
-
// those are the two positions where cutting it out leaves a command that still runs the run.
|
|
60
|
-
if (!runner || !filter || filter.separator.trim() !== '|')
|
|
60
|
+
if (!runner || runner.end === null)
|
|
61
61
|
return null;
|
|
62
|
-
|
|
62
|
+
// The chain is every stage fed by the runner's own pipe, until the pipeline ends. Each one has
|
|
63
|
+
// to be a cutting filter: a stage that does not drop lines ends the chain, and the chain then
|
|
64
|
+
// stops before the pipeline's end — which is not answerable (`| grep … | wc -l` would still
|
|
65
|
+
// hide the assertion in a count).
|
|
66
|
+
const chain = [];
|
|
67
|
+
for (let index = runnerAt + 1; index < entries.length; index++) {
|
|
68
|
+
const entry = entries[index];
|
|
69
|
+
if (!entry || entry.separator.trim() !== '|')
|
|
70
|
+
break;
|
|
71
|
+
if (!entry.segment || !CUTTING_FILTERS.has(entry.segment.program))
|
|
72
|
+
break;
|
|
73
|
+
chain.push(entry);
|
|
74
|
+
if (entry.terminator.trim() !== '|')
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
const first = chain[0];
|
|
78
|
+
const last = chain[chain.length - 1];
|
|
79
|
+
if (!first || !last)
|
|
63
80
|
return null;
|
|
64
|
-
|
|
81
|
+
// A pipeline that continues past the chain means a non-cutting stage sits after it.
|
|
82
|
+
if (last.terminator.trim() === '|')
|
|
65
83
|
return null;
|
|
66
|
-
if (
|
|
84
|
+
if (first.start === null || last.end === null)
|
|
67
85
|
return null;
|
|
86
|
+
const stage = command.slice(first.start, last.end);
|
|
68
87
|
return {
|
|
69
|
-
stage
|
|
70
|
-
pipe: `| ${
|
|
71
|
-
command: `${command.slice(0, runner.end)}${command.slice(
|
|
88
|
+
stage,
|
|
89
|
+
pipe: `| ${stage}`,
|
|
90
|
+
command: `${command.slice(0, runner.end)}${command.slice(last.end)}`,
|
|
72
91
|
};
|
|
73
92
|
}
|