@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.
@@ -83,6 +83,10 @@ export declare function withSuiteConfig(command: string, config: string | null |
83
83
  * suite, `--last-failed` dropped (it re-runs the tests that never ran too, X1 flash-m60) and
84
84
  * `--retries=0` set (a re-run of a known failure does not need to pay for retries).
85
85
  *
86
+ * The re-run keeps the run's `> <file>` redirect only in the sense of keeping its OUTPUT there:
87
+ * the target is rewritten to a fresh sibling ({@link redirectToFreshFile}), so a re-run never
88
+ * overwrites the log the run itself is the evidence for (xr-molecule-flash-m173).
89
+ *
86
90
  * `config` is the config the run was measured under ({@link withSuiteConfig}): the narrowed re-run
87
91
  * carries it whenever the command itself does not, so a run that dropped `--config` cannot be
88
92
  * handed back a command with the same defect (xr-molecule-flash-m113: every failing test was
@@ -101,9 +105,34 @@ export declare function withSuiteConfig(command: string, config: string | null |
101
105
  * does not name one.
102
106
  * @param cwd - The directory `exec_command` was given, when it was; the base a location is
103
107
  * resolved against when the command does not `cd` anywhere.
108
+ * @param env - The settings the REQUEST requires of the suite and this run left out
109
+ * ({@link withRunEnv}): the re-run carries them, so a re-run of a run measured in the wrong
110
+ * mode is not measured in that mode again (xr-molecule-flash-m182 +12:06).
104
111
  * @returns The scoped command, or the command unchanged when it runs no Playwright suite.
105
112
  */
106
- export declare function scopedRerunCommand(command: string, locations: readonly string[], excluded?: readonly string[], config?: string | null, cwd?: string | null): string;
113
+ export declare function scopedRerunCommand(command: string, locations: readonly string[], excluded?: readonly string[], config?: string | null, cwd?: string | null, env?: readonly RunEnvSetting[]): string;
114
+ /** One `NAME=value` the request requires of a suite run. */
115
+ export interface RunEnvSetting {
116
+ name: string;
117
+ value: string;
118
+ }
119
+ /**
120
+ * The command with the request's required settings put in front of its RUN statement — the ONE
121
+ * place every composed re-run gets them (xr-molecule-flash-m182).
122
+ *
123
+ * The settings are written at the statement's own assignment offset, never in front of the whole
124
+ * command: a shell binds an assignment there to the statement's program, while one in front of the
125
+ * whole command is the environment of its FIRST statement alone (`APP_BASE=… cd app && npx
126
+ * playwright test` sets the variable for `cd`; xr-molecule-flash-m103 +28:08). A name the command
127
+ * gives a different value is replaced, and one it already gives the required value is left as
128
+ * written. When the parse cannot place the statement (a run inside a nested `sh -c '…'`), the
129
+ * settings become `export`s, which no statement boundary can swallow.
130
+ *
131
+ * @param command - The command to carry them.
132
+ * @param settings - The `NAME`/`value` pairs the request requires.
133
+ * @returns The command, or the command unchanged when it already carries every setting.
134
+ */
135
+ export declare function withRunEnv(command: string, settings?: readonly RunEnvSetting[]): string;
107
136
  /**
108
137
  * One suite run, as the rule that decides whether a second may start beside it reads it.
109
138
  *
@@ -1 +1 @@
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,CA0CR;AAgHD;;;;;;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;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC;;;OAGG;IACH,kBAAkB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CACvC;AAmCD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAYtF"}
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,CAS1F;AAqCD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,SAAS,MAAM,EAAE,EAC5B,QAAQ,GAAE,SAAS,MAAM,EAAO,EAChC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,EACtB,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,EACnB,GAAG,GAAE,SAAS,aAAa,EAAO,GACjC,MAAM,CA8CR;AAED,4DAA4D;AAC5D,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;CACd;AAmCD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,GAAE,SAAS,aAAa,EAAO,GAAG,MAAM,CAe3F;AAkHD;;;;;;GAMG;AACH,MAAM,WAAW,gBAAgB;IAC/B,+FAA+F;IAC/F,SAAS,EAAE,OAAO,CAAA;IAClB,oFAAoF;IACpF,MAAM,EAAE,MAAM,CAAA;IACd;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAA;CAC3B;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,gBAAgB,EACzD,SAAS,EAAE;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,EAC7B,QAAQ,EAAE,SAAS,CAAC,EAAE,GACrB,CAAC,GAAG,IAAI,CAMV;AA2FD;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC;;;OAGG;IACH,kBAAkB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CACvC;AAmCD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAYtF"}
@@ -108,17 +108,48 @@ export function namesSuiteConfig(command) {
108
108
  export function withSuiteConfig(command, config) {
109
109
  if (!config)
110
110
  return command;
111
- const at = command.search(/\bplaywright\s+test\b/);
112
- if (at === -1)
111
+ const run = playwrightStatement(command);
112
+ if (run === null)
113
113
  return command;
114
- const head = command.slice(0, at);
115
- const rest = command.slice(at);
116
- const end = rest.search(/&&|\|\||;|\|/);
117
- const statement = end === -1 ? rest : rest.slice(0, end);
118
- const tail = end === -1 ? '' : rest.slice(end);
119
- if (namesSuiteConfig(statement))
114
+ if (namesSuiteConfig(command.slice(run.start, run.end)))
120
115
  return command;
121
- return `${head}${statement.replace(/^(playwright\s+test)\b/, `$1 --config ${shellQuote(config)}`)}${tail}`;
116
+ // Right after the runner's own words (`playwright test <here>`), so a launcher in front of
117
+ // them — `npx --no-install`, `node <path>/playwright` — is untouched and the flag is the
118
+ // runner's own argument wherever it sits.
119
+ return `${command.slice(0, run.afterRunner)} --config ${shellQuote(config)}${command.slice(run.afterRunner)}`;
120
+ }
121
+ /**
122
+ * A redirect that names a FILE for a stream whose target a re-run would destroy: `> f`, `>> f`,
123
+ * `1> f`, `&> f`. `2>` is stderr and deliberately not matched; a descriptor duplication
124
+ * (`2>&1`, `>&2`) has no file target and does not match either, because `&` cannot start one.
125
+ */
126
+ const FILE_REDIRECT = /(^|\s)(&>>?|1?>>?)(\s*)(?:'([^']+)'|"([^"]+)"|([^\s;&|]+))/g;
127
+ /** A target that is not a log anyone could lose: rewriting it would CREATE a file. */
128
+ const DEVICE_TARGET = /^\/dev\//;
129
+ /**
130
+ * The command with every file redirect pointed at a fresh file beside its original target
131
+ * (`/tmp/pw-full.log` → `/tmp/pw-full.rerun.log`), and nothing else changed.
132
+ *
133
+ * A re-run composed from a run's own command keeps the run's redirect by construction, and the
134
+ * run wrote the walk's only report to that file: xr-molecule-flash-m173 handed over *"Send this
135
+ * once the run ends: … npx playwright test …/articles.spec.ts:32 --retries=0 … > /tmp/pw-full.log
136
+ * 2>&1"* — the ONE narrowed re-run that was about to check the fixes would have overwritten the
137
+ * full walk's log, destroying the other 11 failures' error reports. Following the platform's own
138
+ * advice must never destroy the evidence it was based on.
139
+ *
140
+ * @param command - A shell command.
141
+ * @returns The command whose file redirects write to a fresh target.
142
+ */
143
+ function redirectToFreshFile(command) {
144
+ return command.replace(FILE_REDIRECT, (whole, before, op, space, single, double, bare) => {
145
+ const target = single ?? double ?? bare ?? '';
146
+ // `/dev/null` and friends are not a report to preserve; leaving them keeps the re-run silent
147
+ // the way the run was, where rewriting them would create a file nobody asked for.
148
+ if (!target || DEVICE_TARGET.test(target))
149
+ return whole;
150
+ const quote = single !== undefined ? "'" : double !== undefined ? '"' : '';
151
+ return `${before}${op}${space}${quote}${target}.rerun.log${quote}`;
152
+ });
122
153
  }
123
154
  /**
124
155
  * The whole-suite command made scoped to the tests it should re-run instead: the same
@@ -126,6 +157,10 @@ export function withSuiteConfig(command, config) {
126
157
  * suite, `--last-failed` dropped (it re-runs the tests that never ran too, X1 flash-m60) and
127
158
  * `--retries=0` set (a re-run of a known failure does not need to pay for retries).
128
159
  *
160
+ * The re-run keeps the run's `> <file>` redirect only in the sense of keeping its OUTPUT there:
161
+ * the target is rewritten to a fresh sibling ({@link redirectToFreshFile}), so a re-run never
162
+ * overwrites the log the run itself is the evidence for (xr-molecule-flash-m173).
163
+ *
129
164
  * `config` is the config the run was measured under ({@link withSuiteConfig}): the narrowed re-run
130
165
  * carries it whenever the command itself does not, so a run that dropped `--config` cannot be
131
166
  * handed back a command with the same defect (xr-molecule-flash-m113: every failing test was
@@ -144,19 +179,23 @@ export function withSuiteConfig(command, config) {
144
179
  * does not name one.
145
180
  * @param cwd - The directory `exec_command` was given, when it was; the base a location is
146
181
  * resolved against when the command does not `cd` anywhere.
182
+ * @param env - The settings the REQUEST requires of the suite and this run left out
183
+ * ({@link withRunEnv}): the re-run carries them, so a re-run of a run measured in the wrong
184
+ * mode is not measured in that mode again (xr-molecule-flash-m182 +12:06).
147
185
  * @returns The scoped command, or the command unchanged when it runs no Playwright suite.
148
186
  */
149
- export function scopedRerunCommand(command, locations, excluded = [], config, cwd) {
150
- const at = command.search(/\bplaywright\s+test\b/);
151
- if (at === -1)
187
+ export function scopedRerunCommand(command, locations, excluded = [], config, cwd, env = []) {
188
+ const run = playwrightStatement(command);
189
+ if (run === null)
152
190
  return command;
153
- const head = command.slice(0, at);
154
- const rest = command.slice(at);
155
191
  // Only the first statement runs the suite; the rest (`| tail -50`, `&& echo done`)
156
192
  // is left exactly as it was.
157
- const end = rest.search(/&&|\|\||;|\|/);
158
- const statement = end === -1 ? rest : rest.slice(0, end);
159
- const tail = end === -1 ? '' : rest.slice(end);
193
+ const statement = command.slice(run.start, run.end);
194
+ const tail = command.slice(run.end);
195
+ // The arguments after the runner's own words (`playwright test`), where the narrowed re-run
196
+ // writes its locations — the launcher in front of them (`npx --no-install`, `node
197
+ // <path>/playwright`) is not part of the arguments.
198
+ const args = command.slice(run.afterRunner, run.end);
160
199
  // One literal alternation, regex-escaped and shell-quoted: a title carries parentheses and
161
200
  // quotes (`should only allow comment author to delete`) that are regex and shell syntax alike.
162
201
  const invert = excluded.length > 0 && !/--grep-invert\b/.test(command)
@@ -176,16 +215,79 @@ export function scopedRerunCommand(command, locations, excluded = [], config, cw
176
215
  // nothing — "No tests found" — so every location is rendered in a form Playwright can match
177
216
  // ({@link playwrightLocations}).
178
217
  const narrowed = playwrightLocations(locations, command, cwd);
179
- const scoped = dropSupersededSpecFiles(statement, narrowed)
218
+ const dropped = dropSupersededSpecFiles(args, narrowed)
180
219
  .replace(/\s*--last-failed\b/g, '')
181
- .replace(/\s*--retries(?:[= ]\d+)?/g, '')
182
- .replace(/^(playwright\s+test)\b/, `$1 ${narrowed.join(' ')} --retries=0${carry}${invert}`);
220
+ .replace(/\s*--retries(?:[= ]\d+)?/g, '');
221
+ const scoped = command.slice(0, run.afterRunner) +
222
+ ` ${narrowed.join(' ')} --retries=0${carry}${invert}` +
223
+ dropped;
183
224
  // A command that starts the suite AGAIN after this statement is a chain of walks, not a re-run:
184
225
  // everything from the next suite runner on is dropped, so the composed command carries ONE walk.
185
226
  // X1 xr-molecule-flash-m157 (+14:02) was handed a re-run that still carried the chain's second
186
227
  // whole walk — 4.5 minutes measuring code it had not changed.
187
228
  const rerunTail = suiteRunStatements(tail).length > 0 ? '' : tail;
188
- return `${head}${scoped}${rerunTail}`;
229
+ return withRunEnv(`${redirectToFreshFile(scoped)}${redirectToFreshFile(rerunTail)}`, env);
230
+ }
231
+ /** A `NAME=value` word in command position, optionally behind `export`/`env`. */
232
+ const ENV_WORD = /(^|[\s;&|(])(?:export[ \t]+|env[ \t]+)?([A-Za-z_][A-Za-z0-9_]*)=('[^']*'|"[^"]*"|[^\s;&|]+)/g;
233
+ /** The value a command gives a name, unquoted, or null when it sets none. */
234
+ function envValueOf(command, name) {
235
+ for (const m of command.matchAll(ENV_WORD)) {
236
+ if (m[2] !== name)
237
+ continue;
238
+ return (m[3] ?? '').replace(/^(['"])(.*)\1$/, '$2');
239
+ }
240
+ return null;
241
+ }
242
+ /** The command with one `NAME=value` word taken out, whitespace and all. */
243
+ function withoutEnvWord(command, name) {
244
+ const pattern = new RegExp(`(^|[\\s;&|(])(?:export[ \\t]+|env[ \\t]+)?${name}=('[^']*'|"[^"]*"|[^\\s;&|]+)`);
245
+ const m = pattern.exec(command);
246
+ if (!m)
247
+ return command;
248
+ const start = m.index + m[1].length;
249
+ const end = m.index + m[0].length;
250
+ const left = /[ \t]*$/.exec(command.slice(0, start))?.[0] ?? '';
251
+ const chain = /^[ \t]*(?:&&|\|\||;)[ \t]*/.exec(command.slice(end));
252
+ // A word between two chained statements takes the following operator with it, so the result is
253
+ // `a && b` and not `a && && b`.
254
+ if (chain && left.length > 0)
255
+ return command.slice(0, start) + command.slice(end + chain[0].length);
256
+ if (left.length > 0)
257
+ return command.slice(0, start - left.length) + command.slice(end);
258
+ const right = /^[ \t]*/.exec(command.slice(end))?.[0] ?? '';
259
+ return command.slice(0, start) + command.slice(end + right.length);
260
+ }
261
+ /**
262
+ * The command with the request's required settings put in front of its RUN statement — the ONE
263
+ * place every composed re-run gets them (xr-molecule-flash-m182).
264
+ *
265
+ * The settings are written at the statement's own assignment offset, never in front of the whole
266
+ * command: a shell binds an assignment there to the statement's program, while one in front of the
267
+ * whole command is the environment of its FIRST statement alone (`APP_BASE=… cd app && npx
268
+ * playwright test` sets the variable for `cd`; xr-molecule-flash-m103 +28:08). A name the command
269
+ * gives a different value is replaced, and one it already gives the required value is left as
270
+ * written. When the parse cannot place the statement (a run inside a nested `sh -c '…'`), the
271
+ * settings become `export`s, which no statement boundary can swallow.
272
+ *
273
+ * @param command - The command to carry them.
274
+ * @param settings - The `NAME`/`value` pairs the request requires.
275
+ * @returns The command, or the command unchanged when it already carries every setting.
276
+ */
277
+ export function withRunEnv(command, settings = []) {
278
+ const added = [];
279
+ let out = command;
280
+ for (const setting of settings) {
281
+ if (envValueOf(out, setting.name) === setting.value)
282
+ continue;
283
+ out = withoutEnvWord(out, setting.name);
284
+ added.push(`${setting.name}=${setting.value}`);
285
+ }
286
+ if (added.length === 0)
287
+ return command;
288
+ const at = locatedCommandSegments(out).find((entry) => entry.segment !== null && PLAYWRIGHT_RUN.test(entry.segment.text))?.assignmentOffset ?? null;
289
+ const words = added.join(' ');
290
+ return at === null ? `export ${words} && ${out}` : `${out.slice(0, at)}${words} ${out.slice(at)}`;
189
291
  }
190
292
  /** A directory a `cd` can name literally: no `~`, no variable, no glob. */
191
293
  const LITERAL_DIR = /^[A-Za-z0-9_./+-]+$/;
@@ -267,7 +369,8 @@ function playwrightLocations(locations, command, cwd) {
267
369
  /** A spec-file argument Playwright accepts as its positional filter — never a flag. */
268
370
  const SPEC_FILE_ARGUMENT = /^(?!-)[^\s]*\.(?:spec|test)\.[cm]?[jt]sx?(?::\d+(?::\d+)?)?$/;
269
371
  /**
270
- * The statement with every bare spec-file argument taken out once the re-run names failure locations.
372
+ * The runner's own arguments with every bare spec-file argument taken out once the re-run names
373
+ * failure locations.
271
374
  *
272
375
  * The re-run is "just these": Playwright matches each positional argument against every collected
273
376
  * file's path, so a bare file left beside the `file:line`s re-admits ALL of that file's tests.
@@ -276,23 +379,20 @@ const SPEC_FILE_ARGUMENT = /^(?!-)[^\s]*\.(?:spec|test)\.[cm]?[jt]sx?(?::\d+(?::
276
379
  * failures sat in two of them — 32 tests, ~3 min, instead of the 8 failures' ~30 s. A file with no
277
380
  * failure in it has nothing to re-run, so it goes too.
278
381
  *
279
- * @param statement - The Playwright statement, starting at its own `playwright test`.
382
+ * The launcher's words are not arguments and are not here: the caller passes what follows
383
+ * `playwright test` ({@link playwrightStatement}'s `afterRunner`), so `node <path>/playwright
384
+ * test` and `npx playwright test` lose the same spec files.
385
+ *
386
+ * @param args - The run's own arguments, after `playwright test`.
280
387
  * @param locations - The failing `file:line`s the re-run names.
281
- * @returns The statement without its bare spec-file arguments.
388
+ * @returns The arguments without their bare spec-file arguments.
282
389
  */
283
- function dropSupersededSpecFiles(statement, locations) {
390
+ function dropSupersededSpecFiles(args, locations) {
284
391
  if (locations.length === 0)
285
- return statement;
286
- const runner = /^(playwright\s+test)\b/.exec(statement);
287
- if (runner === null)
288
- return statement;
289
- const head = runner[1];
290
- const kept = statement
291
- .slice(head.length)
292
- .replace(/(^|\s+)([^\s]+)(?=\s|$)/g, (whole, _space, token) => {
392
+ return args;
393
+ return args.replace(/(^|\s+)([^\s]+)(?=\s|$)/g, (whole, _space, token) => {
293
394
  return SPEC_FILE_ARGUMENT.test(token) ? '' : whole;
294
395
  });
295
- return `${head}${kept}`;
296
396
  }
297
397
  /**
298
398
  * The run in flight that makes a second run a hazard, or null when nothing is in the way.
@@ -320,6 +420,43 @@ export function suiteRunInTheWay(candidate, inFlight) {
320
420
  * closing quote is that echo. A version-pinned `playwright@1.44.0 test` is the same run.
321
421
  */
322
422
  const PLAYWRIGHT_RUN = /^(?:@playwright\/test|playwright)(?:@\S+)?\s+test(?=\s|$)/;
423
+ /**
424
+ * The statement of a command that runs `playwright test`, and where its own arguments begin.
425
+ *
426
+ * Both rewrites that compose a command ({@link withSuiteConfig}, {@link scopedRerunCommand}) read
427
+ * the run through the shared shell parse — the same answer every classifier gets — instead of
428
+ * searching the text for `playwright test`. The search missed the runner reached by file (`node
429
+ * <path>/@playwright/test/cli.js test`, xr-molecule-flash-m179), so a narrowed re-run of that run
430
+ * silently stayed the whole suite.
431
+ *
432
+ * `afterRunner` is the offset just past the runner's own words (`playwright test`): the
433
+ * sub-command is the word after the program word, wherever the program word is (the script path
434
+ * behind `node`, the name itself otherwise), so an option written there is the run's own.
435
+ *
436
+ * @param command - The shell command.
437
+ * @returns The statement's span and its argument offset, or null when no statement runs the suite.
438
+ */
439
+ function playwrightStatement(command) {
440
+ for (const located of locatedCommandSegments(command)) {
441
+ const segment = located.segment;
442
+ if (!segment || !PLAYWRIGHT_RUN.test(segment.text))
443
+ continue;
444
+ // A statement inside a nested `sh -c '<command>'` has no offsets here: its positions are
445
+ // positions in that string, so nothing may be spliced at them.
446
+ if (located.start === null || located.end === null || located.programOffset === null)
447
+ return null;
448
+ const words = [...command.slice(located.programOffset).matchAll(/\S+/g)];
449
+ const subcommand = words[1];
450
+ if (subcommand === undefined)
451
+ return null;
452
+ return {
453
+ start: located.start,
454
+ end: located.end,
455
+ afterRunner: located.programOffset + (subcommand.index ?? 0) + subcommand[0].length,
456
+ };
457
+ }
458
+ return null;
459
+ }
323
460
  /**
324
461
  * What narrows a Playwright run to a part of the suite: a named file or line, a title filter, or
325
462
  * the tests the run before it printed (`--last-failed`, `--only-changed`).
@@ -1 +1 @@
1
- {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AAwJ9C;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,eAAe,CAAC,OAAO,EAAE,gBAAgB,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAahG;AAiSD,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAoFnE;;;GAGG;AACH,eAAO,MAAM,iBAAiB,QAEA,CAAA;AAE9B;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,wBAAwB,QAI6C,CAAA;AAmBlF;;;;;;;;;;;GAWG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAUhE;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,qBAAqB;IACpC,iFAAiF;IACjF,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;IACzB,8FAA8F;IAC9F,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAA;CAC5B;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,qBAAqB,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,qBAAqB,CAAC,CAAA;AAoBpF;;;;;;;GAOG;AACH,wBAAgB,0BAA0B,CACxC,GAAG,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,GAAG,SAAS,CAAC,GACxE,qBAAqB,CAkBvB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,wBAAsB,4BAA4B,CAChD,IAAI,EAAE,MAAM,EACZ,KAAK,CAAC,EAAE,qBAAqB,GAC5B,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAiCxB;AA2GD;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,gBAAgB,EAAE,MAAM,CAAC,EAAE,eAAe,GAAG,MAAM,EAAE,CA8pFxF"}
1
+ {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AA4K9C;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,eAAe,CAAC,OAAO,EAAE,gBAAgB,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAahG;AA6ZD,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAqFnE;;;GAGG;AACH,eAAO,MAAM,iBAAiB,QAEA,CAAA;AAE9B;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,wBAAwB,QAI6C,CAAA;AAmBlF;;;;;;;;;;;GAWG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAUhE;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,qBAAqB;IACpC,iFAAiF;IACjF,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;IACzB,8FAA8F;IAC9F,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAA;CAC5B;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,qBAAqB,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,qBAAqB,CAAC,CAAA;AAoBpF;;;;;;;GAOG;AACH,wBAAgB,0BAA0B,CACxC,GAAG,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,GAAG,SAAS,CAAC,GACxE,qBAAqB,CAkBvB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,wBAAsB,4BAA4B,CAChD,IAAI,EAAE,MAAM,EACZ,KAAK,CAAC,EAAE,qBAAqB,GAC5B,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAiCxB;AA2GD;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,gBAAgB,EAAE,MAAM,CAAC,EAAE,eAAe,GAAG,MAAM,EAAE,CAguFxF"}