@molecule/api-ai-tools 1.9.2 → 1.9.4

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 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-17T09:38:26.898Z
6
+ Generated: 2026-10-17T20:38:26.582Z
7
7
  -->
8
8
 
9
9
  # @molecule/api-ai-tools
@@ -352,6 +352,12 @@ interface TraceRequest {
352
352
  sent?: string
353
353
  /** What came back for that 4xx, bounded the same way — the API's own error body. */
354
354
  got?: string
355
+ /**
356
+ * The browser's own resource type (`document`, `fetch`, `xhr`, `font`, `image`, `stylesheet`,
357
+ * `script`, …). `document` when the request was the page load itself; when it ended in a
358
+ * `net::ERR_*`, nothing rendered and no selector or app request was measured.
359
+ */
360
+ type?: string
355
361
  }
356
362
  ```
357
363
 
@@ -757,6 +763,28 @@ interface FailureCause {
757
763
  * (xr-molecule-flash-m110 +18:42 rewrote a CORS middleware for a 20 s connection timeout).
758
764
  */
759
765
  kind: RunFailureKind
766
+ /**
767
+ * What the page itself showed at the moment of the failure, when the caller read it from
768
+ * Playwright's `error-context.md` (the alert / error list). The failure's own error line names
769
+ * the symptom — `waitForURL` timed out — and never what the page was saying: xr-molecule-flash-
770
+ * m122 +23:39 grouped eight `waitForURL` timeouts under one line while the page snapshots named
771
+ * the cause (`email can't be blank`, `username can't be blank` — the settings form submitted
772
+ * before its values had loaded). Attached to the cause so the fix needs no `cat` of the file.
773
+ */
774
+ pageShowed?: string[]
775
+ }
776
+ ```
777
+
778
+ #### `CausePageErrors`
779
+
780
+ One failing test's page errors, as read from its `error-context.md`.
781
+
782
+ ```typescript
783
+ interface CausePageErrors {
784
+ /** The test's location as the log prints it (`settings.spec.ts:107`). */
785
+ where: string
786
+ /** The alert / error items the page showed at the moment it failed. */
787
+ items: readonly string[]
760
788
  }
761
789
  ```
762
790
 
@@ -905,6 +933,37 @@ function locatedCommandSegments(command: string): LocatedCommandSegment[]
905
933
 
906
934
  **Returns:** One entry per statement or pipeline stage, in order.
907
935
 
936
+ #### `statementArgumentEnd(command, start, end)`
937
+
938
+ Where an option of a statement's program belongs: one past the statement's last ARGUMENT.
939
+
940
+ A flag written "at the end of the command" is not the runner's: a statement can end on a
941
+ redirection (`… 2>&1`), and a statement can sit inside a group whose closer belongs to the
942
+ shell, not to the words (`( … )`, `{ …; }`). Appending after that closer is a different shell
943
+ word that the group has already closed — `… --config=x 2>&1) --reporter=list` is
944
+ `sh: Syntax error: word unexpected`, which killed a backgrounded walk at once and pushed the
945
+ model off the platform's runner shape (xr-molecule-flash-m129 +34:29). Write the option where
946
+ the program's own arguments end and the rest of the statement still parses:
947
+ `… --config=x --reporter=list 2>&1)`.
948
+
949
+ The statement must be one {@link locatedCommandSegments} placed in THIS command: a statement
950
+ inside a nested `sh -c '<command>'` string has no offsets here (its positions are positions in
951
+ that string), so null is returned rather than a splice into the outer text.
952
+
953
+ ```typescript
954
+ function statementArgumentEnd(
955
+ command: string,
956
+ start: number | null,
957
+ end: number | null,
958
+ ): number | null
959
+ ```
960
+
961
+ - `command` — The shell command.
962
+ - `start` — The statement's first character ({@link LocatedCommandSegment.start}).
963
+ - `end` — One past the statement's last character ({@link LocatedCommandSegment.end}).
964
+
965
+ **Returns:** The offset to write the option at, or null when the statement cannot be placed here.
966
+
908
967
  #### `commandTimeoutSeconds(command)`
909
968
 
910
969
  The bound a command puts on its own run: the largest `timeout <N>` any of its segments runs
@@ -1340,7 +1399,7 @@ function withSuiteConfig(command: string, config: string | null | undefined): st
1340
1399
 
1341
1400
  **Returns:** The command with the flag, or the command unchanged.
1342
1401
 
1343
- #### `scopedRerunCommand(command, locations, excluded, config)`
1402
+ #### `scopedRerunCommand(command, locations, excluded, config, cwd)`
1344
1403
 
1345
1404
  The whole-suite command made scoped to the tests it should re-run instead: the same
1346
1405
  invocation — same directory, same settings — with the named `file:line`s in place of the whole
@@ -1358,13 +1417,15 @@ function scopedRerunCommand(
1358
1417
  locations: readonly string[],
1359
1418
  excluded?: readonly string[],
1360
1419
  config?: string | null,
1420
+ cwd?: string | null,
1361
1421
  ): string
1362
1422
  ```
1363
1423
 
1364
1424
  - `command` — The whole-suite run's command.
1365
- - `locations` — The failing `file:line`s to re-run, as the run's own log printed them — or, for a run that started outside the suite, the absolute form Playwright matches each file argument against.
1425
+ - `locations` — The failing `file:line`s to re-run, as the run's own log printed them. A location the reporter printed relative to the runner's own directory (`../../fixtures/…`, which no absolute path contains) is resolved here ({@link playwrightLocations}), so the command this returns is one Playwright can match.
1366
1426
  - `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.
1367
1427
  - `config` — The config the run loaded, when the caller knows it; added when the command does not name one.
1428
+ - `cwd` — The directory `exec_command` was given, when it was; the base a location is resolved against when the command does not `cd` anywhere.
1368
1429
 
1369
1430
  **Returns:** The scoped command, or the command unchanged when it runs no Playwright suite.
1370
1431
 
@@ -1615,6 +1676,51 @@ function parseTraceArtifacts(stdout: string): TraceArtifact[]
1615
1676
 
1616
1677
  **Returns:** One entry per trace it printed.
1617
1678
 
1679
+ #### `isStaticAssetRequest(request)`
1680
+
1681
+ Whether a request is one of the page shell's, not the test's (`STATIC_ASSET_PATH`).
1682
+
1683
+ The document itself is never incidental — a page that never loaded IS the failure — and a
1684
+ request whose type is the app's own API (`fetch`/`xhr`) is judged by its path alone.
1685
+
1686
+ ```typescript
1687
+ function isStaticAssetRequest(request: TraceRequest): boolean
1688
+ ```
1689
+
1690
+ - `request` — One request a trace recorded.
1691
+
1692
+ **Returns:** True when the request is incidental to the step under test.
1693
+
1694
+ #### `failedRequest(request)`
1695
+
1696
+ Whether a trace recorded a request the server actually failed: a 4xx/5xx, or a network error.
1697
+
1698
+ A 404 on a static asset is excepted: `/sw.js` and `/fonts/*` 404 on an app that never shipped
1699
+ them, and naming one as a failure sent m125 into writing the caching worker it had asked about.
1700
+ A 5xx on an asset, or a network error, still counts — the server did fail.
1701
+
1702
+ ```typescript
1703
+ function failedRequest(request: TraceRequest): boolean
1704
+ ```
1705
+
1706
+ - `request` — One request a trace recorded.
1707
+
1708
+ **Returns:** True when the request failed in a way the note should name.
1709
+
1710
+ #### `unansweredRequest(request)`
1711
+
1712
+ Whether a trace recorded NO response and no reason for it — a request that was aborted or still
1713
+ in flight when the test ended. It is not evidence that the server failed (m112 +30:45), and one
1714
+ the page shell made is not evidence about the step at all (m125 +29:33).
1715
+
1716
+ ```typescript
1717
+ function unansweredRequest(request: TraceRequest): boolean
1718
+ ```
1719
+
1720
+ - `request` — One request a trace recorded.
1721
+
1722
+ **Returns:** True when the request is still in flight and worth naming.
1723
+
1618
1724
  #### `failureTraceNote(items, target, earlierFailures)`
1619
1725
 
1620
1726
  The note a failed suite run's result carries: what each failing test's page actually sent, and
@@ -1935,6 +2041,12 @@ are output-only ({@link isOutputOnlyFilter}): a `| tee` or `| xargs` keeps its p
1935
2041
  caller that reports the run's exit code must then treat it as unknown
1936
2042
  ({@link suiteRunPipedAfterRunner}).
1937
2043
 
2044
+ A statement after the run's own pipeline (`; echo done`, `&& echo ok`) does not protect the
2045
+ filters before it: the filter is thrown away and the trailing statement is kept, so the log the
2046
+ run writes still holds every failure (xr-molecule-flash-m121 — a `| tail -5; echo done` kept its
2047
+ tail, and a 95-second walk's log held only the tally while the platform's own hold had handed
2048
+ that command back).
2049
+
1938
2050
  ```typescript
1939
2051
  function stripTrailingSuiteFilters(command: string): SuiteFilterStrip
1940
2052
  ```
@@ -2461,28 +2573,65 @@ function repeatingFailure(log: string): RepeatingFailure | null
2461
2573
 
2462
2574
  **Returns:** The repeating cause, or null when no signature has {@link REPEATING_FAILURE_MIN} distinct tests behind it.
2463
2575
 
2464
- #### `universalCause(log, found)`
2576
+ #### `specFilesSelected(command)`
2465
2577
 
2466
- Whether a repeating cause is failing the whole run — the one case where stopping it is the
2467
- right call.
2578
+ How many test FILES a Playwright run's own command narrows it to, or null when it selects the
2579
+ whole suite (or its selection cannot be read).
2468
2580
 
2469
- The test is: the cause accounts for every failure the log names, it has hit
2470
- {@link UNIVERSAL_CAUSE_FILES} spec files or more, and nothing has passed since its first
2471
- failure. An app that is not up, a 5xx on every request, or a selector that is missing on every
2472
- page all look like that, and every remaining test will fail the same way — so the run's
2473
- remaining minutes measure nothing. Anything less is NOT this: a cause that stops at one file,
2474
- or that leaves other tests passing, or that shares the run with failures of another shape,
2475
- leaves the run with something to say, and the run is the only thing that will say it. X1
2476
- flash-m83 (2026-10-16) stopped eight local runs at their first repeating cause over 14.5
2477
- minutes and never finished one; three of its deploys each fixed a single cause that a finished
2478
- run would have listed alongside the others.
2581
+ {@link universalCause}'s file threshold is written for a whole-suite run, which a narrowed run
2582
+ can never reach. xr-molecule-flash-m119 (+42:08) re-ran ONE spec file against a hand-started
2583
+ `vite preview` the browser could not reach, and every test in it failed `page.goto: Timeout` —
2584
+ but one file is below {@link UNIVERSAL_CAUSE_FILES}, so the wait sat out its entire 280 s
2585
+ budget on a run that had already measured all it could.
2586
+
2587
+ The command is read through {@link commandSegments}, so a `playwright test` named as an
2588
+ argument is not a run. A {@link PLAYWRIGHT_VALUE_FLAGS} flag written without `=` takes the next
2589
+ word as its value, so a `--config app/e2e-suite.config.ts` is never counted as a spec file; a
2590
+ directory or a bare substring filter names files the command does not list, which answers null
2591
+ (the whole-suite threshold) rather than a count of the wrong files.
2479
2592
 
2480
2593
  ```typescript
2481
- function universalCause(log: string, found: RepeatingFailure): UniversalCause | null
2594
+ function specFilesSelected(command: string): number | null
2595
+ ```
2596
+
2597
+ - `command` — The run's command.
2598
+
2599
+ **Returns:** The number of distinct test files it names, or null when it names none (the whole suite) or the selection cannot be told.
2600
+
2601
+ #### `universalCause(log, found, command)`
2602
+
2603
+ Whether a repeating cause is failing the whole run — the one case where stopping it is the
2604
+ right call.
2605
+
2606
+ The test is: the cause accounts for every failure the log names, it has hit the spec-file
2607
+ threshold, and nothing has passed since its first failure. The threshold is
2608
+ {@link UNIVERSAL_CAUSE_FILES} for a whole-suite run but the number of files the command
2609
+ SELECTED for a narrowed one ({@link specFilesSelected}): a one-file run can never span three,
2610
+ and xr-molecule-flash-m119 (+42:08) waited out its whole budget on exactly that shape. An app
2611
+ that is not up, a 5xx on every request, or a selector that is missing on every page all look
2612
+ like that, and every remaining test will fail the same way — so the run's remaining minutes
2613
+ measure nothing. A CONNECTION-class cause ({@link CONNECTION_CAUSE}) needs no file span at all:
2614
+ three tests that could not reach the app, with nothing passing since, is the app-unreachable
2615
+ case however few files the run walks.
2616
+
2617
+ Anything less is NOT this: a cause that stops at one file of a multi-file run, or that leaves
2618
+ other tests passing, or that shares the run with failures of another shape, leaves the run with
2619
+ something to say, and the run is the only thing that will say it. X1 flash-m83 (2026-10-16)
2620
+ stopped eight local runs at their first repeating cause over 14.5 minutes and never finished
2621
+ one; three of its deploys each fixed a single cause that a finished run would have listed
2622
+ alongside the others.
2623
+
2624
+ ```typescript
2625
+ function universalCause(
2626
+ log: string,
2627
+ found: RepeatingFailure,
2628
+ command?: string | null,
2629
+ ): UniversalCause | null
2482
2630
  ```
2483
2631
 
2484
2632
  - `log` — The log so far.
2485
2633
  - `found` — The repeating cause {@link repeatingFailure} found in it.
2634
+ - `command` — How the run was started, when the caller knows it: its spec-file selection scales the file threshold ({@link specFilesSelected}).
2486
2635
 
2487
2636
  **Returns:** The cause's count and files, or null when the run is not failing whole.
2488
2637
 
@@ -2493,8 +2642,10 @@ for: a PROVEN cause that is failing the whole run.
2493
2642
 
2494
2643
  Both halves are load-bearing, each of them learned from a run that lost minutes to the other.
2495
2644
 
2496
- - **Failing the WHOLE run** is {@link universalCause}: every failure the log names, across
2497
- {@link UNIVERSAL_CAUSE_FILES} spec files or more, with nothing passing since the first — the
2645
+ - **Failing the WHOLE run** is {@link universalCause}: every failure the log names, across the
2646
+ spec-file threshold — {@link UNIVERSAL_CAUSE_FILES}, or the files a narrowed run's own
2647
+ command selects, or {@link CONNECTION_CAUSE_TESTS} tests of an app that never answered — with
2648
+ nothing passing since the first. That is the
2498
2649
  dont-stop-runs rule. X1 flash-m83 (2026-10-16) stopped eight local runs at their first
2499
2650
  repeating cause in 14.5 minutes and never finished one, and a run that still has other causes
2500
2651
  left to name is the only thing that will name them.
@@ -2505,9 +2656,6 @@ Both halves are load-bearing, each of them learned from a run that lost minutes
2505
2656
  xr-molecule-flash-m99 (2026-10-16) was told `status: 'repeating_failure'` twice on that
2506
2657
  shape — three `url-navigation.spec.ts` tests at ~16.2 s, one `timeout: 15_000` plus teardown
2507
2658
  — read the early return as "stop waiting, edit now", and voided a run that was still walking;
2508
- the second cost a 224 s wait that ended with no result at all.
2509
-
2510
- Neither half alone is enough, and {@link repeatingFailure} finding a signature is not either:
2511
2659
 
2512
2660
  _(description truncated — read the full text in `node_modules/@molecule/api-ai-tools/dist/utilities.d.ts`.)_
2513
2661
 
@@ -2605,6 +2753,7 @@ function failureCausesNote(
2605
2753
  log: string,
2606
2754
  excluded?: readonly string[],
2607
2755
  run?: { command: string; cwd?: string; log?: string; config?: string | null },
2756
+ pageErrors?: readonly CausePageErrors[],
2608
2757
  ): string | null
2609
2758
  ```
2610
2759
 
@@ -2621,11 +2770,11 @@ invocation, in the same directory, narrowed to those tests — or null when the
2621
2770
  is not a Playwright run this can narrow.
2622
2771
 
2623
2772
  The `locations` are the paths the run's own log printed, which are relative to the directory
2624
- that run started in, so a re-run has to resolve them there: the command names that directory
2625
- unless it already changes into one of its own, because the executor's next command starts
2626
- wherever it starts. The returned command is for AFTER the run ends — it is a second run against
2627
- the same app and database, and that is exactly what is refused while the run walks
2628
- ({@link suiteRunInTheWay}).
2773
+ that run started in, so the composer resolves them there ({@link scopedRerunCommand}): the
2774
+ command names that directory unless it already changes into one of its own, because the
2775
+ executor's next command starts wherever it starts. The returned command is for AFTER the run
2776
+ ends — it is a second run against the same app and database, and that is exactly what is
2777
+ refused while the run walks ({@link suiteRunInTheWay}).
2629
2778
 
2630
2779
  ```typescript
2631
2780
  function rerunOfRunningSuite(
@@ -2675,12 +2824,13 @@ function repeatingFailureNote(
2675
2824
  stop: string
2676
2825
  universal?: UniversalCause | null
2677
2826
  run?: { command: string; cwd?: string }
2827
+ editsInvalidateRun?: boolean
2678
2828
  },
2679
2829
  ): string
2680
2830
  ```
2681
2831
 
2682
2832
  - `found` — What {@link repeatingFailure} found.
2683
- - `o` — The run's facts: its `taskId`, its `log` path, `stop` — the shell command that stops it, printed only for an {@link earlyReturnCause} — `universal`, what {@link universalCause} found (absent means the run is not failing whole), and `run`, how the run was started ({@link rerunOfRunningSuite}) when the caller knows it.
2833
+ - `o` — The run's facts: its `taskId`, its `log` path, `stop` — the shell command that stops it, printed only for an {@link earlyReturnCause} — `universal`, what {@link universalCause} found (absent means the run is not failing whole), `run`, how the run was started ({@link rerunOfRunningSuite}) when the caller knows it, and `editsInvalidateRun`, true when the run measures the sandbox's own app (a local run, where an edit now changes what the suite is testing and its result stops counting).
2684
2834
 
2685
2835
  **Returns:** The note.
2686
2836
 
@@ -3213,6 +3363,15 @@ property of the run rather than of a page.
3213
3363
  const UNIVERSAL_CAUSE_FILES: 3
3214
3364
  ```
3215
3365
 
3366
+ #### `CONNECTION_CAUSE_TESTS`
3367
+
3368
+ How many tests a connection-class cause must cover before the run is stopped even when they
3369
+ are all in one file — see {@link universalCause}.
3370
+
3371
+ ```typescript
3372
+ const CONNECTION_CAUSE_TESTS: 3
3373
+ ```
3374
+
3216
3375
  #### `PARSE_CHECKABLE`
3217
3376
 
3218
3377
  Files a parse check can vouch for cheaply right after a write.
@@ -124,6 +124,28 @@ export interface LocatedCommandSegment {
124
124
  * @returns One entry per statement or pipeline stage, in order.
125
125
  */
126
126
  export declare function locatedCommandSegments(command: string): LocatedCommandSegment[];
127
+ /**
128
+ * Where an option of a statement's program belongs: one past the statement's last ARGUMENT.
129
+ *
130
+ * A flag written "at the end of the command" is not the runner's: a statement can end on a
131
+ * redirection (`… 2>&1`), and a statement can sit inside a group whose closer belongs to the
132
+ * shell, not to the words (`( … )`, `{ …; }`). Appending after that closer is a different shell
133
+ * word that the group has already closed — `… --config=x 2>&1) --reporter=list` is
134
+ * `sh: Syntax error: word unexpected`, which killed a backgrounded walk at once and pushed the
135
+ * model off the platform's runner shape (xr-molecule-flash-m129 +34:29). Write the option where
136
+ * the program's own arguments end and the rest of the statement still parses:
137
+ * `… --config=x --reporter=list 2>&1)`.
138
+ *
139
+ * The statement must be one {@link locatedCommandSegments} placed in THIS command: a statement
140
+ * inside a nested `sh -c '<command>'` string has no offsets here (its positions are positions in
141
+ * that string), so null is returned rather than a splice into the outer text.
142
+ *
143
+ * @param command - The shell command.
144
+ * @param start - The statement's first character ({@link LocatedCommandSegment.start}).
145
+ * @param end - One past the statement's last character ({@link LocatedCommandSegment.end}).
146
+ * @returns The offset to write the option at, or null when the statement cannot be placed here.
147
+ */
148
+ export declare function statementArgumentEnd(command: string, start: number | null, end: number | null): number | null;
127
149
  /**
128
150
  * The bound a command puts on its own run: the largest `timeout <N>` any of its segments runs
129
151
  * under (`timeout 280 hurl --test …` → 280), or null when it carries none.
@@ -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;;;OAGG;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;AA2MD;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAM3D;AA+cD;;;;;;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;CAClB;AAED;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,GAAG,qBAAqB,EAAE,CAE/E;AA4ED;;;;;;;;;;;;GAYG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAOpE;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAK/E"}
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;;;OAGG;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;AAqND;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAM3D;AA+cD;;;;;;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;CAClB;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;AA4ED;;;;;;;;;;;;GAYG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAOpE;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAK/E"}
@@ -36,6 +36,14 @@
36
36
  const WORD = /"(?:\\.|[^"\\])*"|'[^']*'|\S+/g;
37
37
  /** Shell words that stand alone: grouping, never an argument. */
38
38
  const GROUPING = new Set(['(', ')', '{', '}']);
39
+ /**
40
+ * A redirection written as ONE word, its target attached: `2>&1`, `>out.log`, `&>/dev/null`,
41
+ * `<<'EOF'`. Matched against a word of the statement, so a redirect's target written as its own
42
+ * word is {@link REDIRECT_OPERATOR}'s case instead.
43
+ */
44
+ const REDIRECT_WORD = /^(?:\d*|&)(?:<<<|<<-?|>>?\|?|<>|>&|<&)(?:&?\d+|-|[^\s;&|()<>]*)?$/;
45
+ /** A redirection operator on its own, whose target is the word after it (`> out.log`). */
46
+ const REDIRECT_OPERATOR = /^(?:\d*|&)(?:<<<|<<-?|>>?\|?|<>|>&|<&)$/;
39
47
  /** How deep a `sh -c '<command>'` is opened before the parser leaves it alone. */
40
48
  const MAX_NESTING = 3;
41
49
  /** How many wrapper words one segment may carry before the parser gives up on it. */
@@ -744,6 +752,142 @@ function wordOffset(text, index) {
744
752
  export function locatedCommandSegments(command) {
745
753
  return locatedSegmentsOf(command, 0);
746
754
  }
755
+ /**
756
+ * How many `$( … )` and `${ … }` expansions a piece of shell text has open at its end.
757
+ *
758
+ * Quotes are honoured (a `'$('` expands nothing, and `"$("` opens one), and a backslash escapes
759
+ * the next character. Only `$(`/`${` are counted: a bare `(` or `{` opens a group whose closer is
760
+ * the statement's own punctuation and may be stepped over, while an expansion opened inside the
761
+ * statement is an argument in progress, and its closer must not be crossed.
762
+ *
763
+ * @param text - The statement's text, up to (not including) the position being asked about.
764
+ * @returns The open parentheses and braces.
765
+ */
766
+ function substitutionDepth(text) {
767
+ let parens = 0;
768
+ let braces = 0;
769
+ let quote = null;
770
+ for (let i = 0; i < text.length; i++) {
771
+ const char = text[i] ?? '';
772
+ if (quote === "'") {
773
+ if (char === "'")
774
+ quote = null;
775
+ continue;
776
+ }
777
+ if (char === '\\') {
778
+ i += 1;
779
+ continue;
780
+ }
781
+ if (char === "'" && quote === null) {
782
+ quote = "'";
783
+ continue;
784
+ }
785
+ if (char === '"') {
786
+ quote = quote === '"' ? null : '"';
787
+ continue;
788
+ }
789
+ if (char === '$' && text[i + 1] === '(') {
790
+ parens += 1;
791
+ i += 1;
792
+ continue;
793
+ }
794
+ if (char === '$' && text[i + 1] === '{') {
795
+ braces += 1;
796
+ i += 1;
797
+ continue;
798
+ }
799
+ if (char === ')' && parens > 0)
800
+ parens -= 1;
801
+ else if (char === '}' && braces > 0)
802
+ braces -= 1;
803
+ }
804
+ return { parens, braces };
805
+ }
806
+ /**
807
+ * Where an option of a statement's program belongs: one past the statement's last ARGUMENT.
808
+ *
809
+ * A flag written "at the end of the command" is not the runner's: a statement can end on a
810
+ * redirection (`… 2>&1`), and a statement can sit inside a group whose closer belongs to the
811
+ * shell, not to the words (`( … )`, `{ …; }`). Appending after that closer is a different shell
812
+ * word that the group has already closed — `… --config=x 2>&1) --reporter=list` is
813
+ * `sh: Syntax error: word unexpected`, which killed a backgrounded walk at once and pushed the
814
+ * model off the platform's runner shape (xr-molecule-flash-m129 +34:29). Write the option where
815
+ * the program's own arguments end and the rest of the statement still parses:
816
+ * `… --config=x --reporter=list 2>&1)`.
817
+ *
818
+ * The statement must be one {@link locatedCommandSegments} placed in THIS command: a statement
819
+ * inside a nested `sh -c '<command>'` string has no offsets here (its positions are positions in
820
+ * that string), so null is returned rather than a splice into the outer text.
821
+ *
822
+ * @param command - The shell command.
823
+ * @param start - The statement's first character ({@link LocatedCommandSegment.start}).
824
+ * @param end - One past the statement's last character ({@link LocatedCommandSegment.end}).
825
+ * @returns The offset to write the option at, or null when the statement cannot be placed here.
826
+ */
827
+ export function statementArgumentEnd(command, start, end) {
828
+ if (start === null || end === null)
829
+ return null;
830
+ if (!Number.isInteger(start) || !Number.isInteger(end))
831
+ return null;
832
+ if (start < 0 || end > command.length || end < start)
833
+ return null;
834
+ let at = end;
835
+ // Group closers are the shell's words, not the program's: `… 2>&1)` and `… ; }` end the
836
+ // statement with punctuation no option can follow. A `)` that closes a `$(` opened INSIDE the
837
+ // statement is not one of those — it is the last character of an argument
838
+ // (`… $(cat e2e/args.txt)`), and crossing it would write the option inside the substitution.
839
+ for (;;) {
840
+ let cursor = at;
841
+ while (cursor > start && /\s/.test(command[cursor - 1] ?? ''))
842
+ cursor -= 1;
843
+ const closer = command[cursor - 1];
844
+ if (closer !== ')' && closer !== '}') {
845
+ at = cursor;
846
+ break;
847
+ }
848
+ const open = substitutionDepth(command.slice(start, cursor - 1));
849
+ if ((closer === ')' && open.parens > 0) || (closer === '}' && open.braces > 0)) {
850
+ at = cursor;
851
+ break;
852
+ }
853
+ at = cursor - 1;
854
+ }
855
+ const words = [...command.slice(start, at).matchAll(new RegExp(WORD.source, 'g'))].map((m) => ({
856
+ text: m[0],
857
+ index: start + (m.index ?? 0),
858
+ }));
859
+ // A trailing redirection belongs to the statement but is not an argument of its program. An
860
+ // option written after it would still parse, but the option belongs with the program's own
861
+ // words (`… --flag 2>&1`, not `… 2>&1 --flag`), so every redirect suffix is stepped over.
862
+ while (words.length > 0) {
863
+ const last = words[words.length - 1];
864
+ if (last === undefined)
865
+ break;
866
+ if (GROUPING.has(last.text) || REDIRECT_WORD.test(last.text)) {
867
+ at = last.index;
868
+ words.pop();
869
+ continue;
870
+ }
871
+ const operator = words[words.length - 2];
872
+ if (operator !== undefined && REDIRECT_OPERATOR.test(operator.text)) {
873
+ at = operator.index;
874
+ words.pop();
875
+ words.pop();
876
+ // `2 > out.log` spells the same redirect as `2>out.log`: the descriptor is the word before
877
+ // the operator, and an option written between them would turn it into an argument.
878
+ const descriptor = words[words.length - 1];
879
+ if (descriptor !== undefined && /^\d+$/.test(descriptor.text)) {
880
+ at = descriptor.index;
881
+ words.pop();
882
+ }
883
+ continue;
884
+ }
885
+ break;
886
+ }
887
+ while (at > start && /\s/.test(command[at - 1] ?? ''))
888
+ at -= 1;
889
+ return at;
890
+ }
747
891
  /**
748
892
  * {@link locatedCommandSegments}'s recursion, so a `sh -c '<command>'` is opened as far as
749
893
  * {@link MAX_NESTING} allows. A nested statement's offsets are cleared: they are positions in
@@ -89,18 +89,21 @@ export declare function withSuiteConfig(command: string, config: string | null |
89
89
  * `Cannot navigate to invalid URL` because the re-run loaded no base URL).
90
90
  *
91
91
  * @param command - The whole-suite run's command.
92
- * @param locations - The failing `file:line`s to re-run, as the run's own log printed them — or,
93
- * for a run that started outside the suite, the absolute form Playwright matches each file
94
- * argument against.
92
+ * @param locations - The failing `file:line`s to re-run, as the run's own log printed them. A
93
+ * location the reporter printed relative to the runner's own directory (`../../fixtures/…`,
94
+ * which no absolute path contains) is resolved here ({@link playwrightLocations}), so the
95
+ * command this returns is one Playwright can match.
95
96
  * @param excluded - The test titles the request carves out. They are inverted out
96
97
  * (`--grep-invert`) so a re-run of the failing locations cannot report one of them again —
97
98
  * xr-molecule-flash-m101 re-ran the whole 139-test suite and spent ~4 minutes to be told about
98
99
  * a test the request had already said does not count.
99
100
  * @param config - The config the run loaded, when the caller knows it; added when the command
100
101
  * does not name one.
102
+ * @param cwd - The directory `exec_command` was given, when it was; the base a location is
103
+ * resolved against when the command does not `cd` anywhere.
101
104
  * @returns The scoped command, or the command unchanged when it runs no Playwright suite.
102
105
  */
103
- export declare function scopedRerunCommand(command: string, locations: readonly string[], excluded?: readonly string[], config?: string | null): string;
106
+ export declare function scopedRerunCommand(command: string, locations: readonly string[], excluded?: readonly string[], config?: string | null, cwd?: string | null): string;
104
107
  /**
105
108
  * One suite run, as the rule that decides whether a second may start beside it reads it.
106
109
  *
@@ -1 +1 @@
1
- {"version":3,"file":"suite-overlap.d.ts","sourceRoot":"","sources":["../src/suite-overlap.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAMH;;;;;;;;;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,CAc1F;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;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,GACrB,MAAM,CA8BR;AAED;;;;;;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;AAqDD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAQvD"}
1
+ {"version":3,"file":"suite-overlap.d.ts","sourceRoot":"","sources":["../src/suite-overlap.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAQH;;;;;;;;;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,CAc1F;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;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,GAClB,MAAM,CAqCR;AA8ID;;;;;;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;AAqDD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAQvD"}