@molecule/api-ai-tools 1.16.2 → 1.16.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.
@@ -36,6 +36,7 @@
36
36
  *
37
37
  * @module
38
38
  */
39
+ import { codeWithModuleSpecifiers as codeOnly } from './source-code.js';
39
40
  /** A `const|let|var <name> = await [qualifier.]chromium|firefox|webkit.launch(` binding. */
40
41
  const LAUNCH = /(?<![\w$.])(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*(?::[^=\n]+)?=\s*await\s+(?:([A-Za-z_$][\w$]*)\s*\.\s*)?(chromium|firefox|webkit)\s*\.\s*launch\s*\(/g;
41
42
  /** A browser method that does not close the browser, so calling it leaves the handle alive. */
@@ -57,217 +58,6 @@ const ANY_CLEANUP = /\.\s*(?:close|disconnect|dispose)\s*\(|[A-Za-z_$][\w$]*\s*\
57
58
  const CLEANUP_HINT = /cleanup|teardown|close|dispose|shutdown|quit/i;
58
59
  /** The packages whose `chromium`/`firefox`/`webkit` export really launches a browser. */
59
60
  const PLAYWRIGHT_PACKAGE = '(?:playwright-core|playwright|@playwright/test)';
60
- /** The characters after which a `/` starts a regex literal rather than a division. */
61
- const REGEX_AFTER = new Set([
62
- '',
63
- '(',
64
- '[',
65
- '{',
66
- ',',
67
- ';',
68
- ':',
69
- '=',
70
- '!',
71
- '&',
72
- '|',
73
- '?',
74
- '+',
75
- '-',
76
- '*',
77
- '%',
78
- '^',
79
- '~',
80
- '<',
81
- '>',
82
- '}',
83
- ]);
84
- /**
85
- * A source with every comment and every string, template and regex literal blanked to spaces,
86
- * preserving offsets and newlines so a match's position still indexes the original source.
87
- *
88
- * A launch mentioned in prose is not a launch: a review proved scripts that only PRINT
89
- * `const browser = await chromium.launch()` — in a string, a comment and a template literal — and
90
- * a raw substring match refused all of them. Template `${…}` substitutions stay code (a `close()`
91
- * written there is a real cleanup path), nested braces are tracked so the substitution's end is
92
- * found, and regex literals are recognised by the token before them so a quote inside a character
93
- * class cannot blank the rest of the file.
94
- *
95
- * @param source - The script's source.
96
- * @returns The same text with literal/comment spans replaced by spaces.
97
- */
98
- function codeOnly(source) {
99
- const out = source.split('');
100
- const blank = (from, to) => {
101
- for (let i = from; i < to; i++)
102
- if (out[i] !== '\n')
103
- out[i] = ' ';
104
- };
105
- const stack = [{ mode: 'code', braces: 0 }];
106
- const top = () => stack[stack.length - 1];
107
- /** The last non-whitespace CODE character: decides whether a `/` opens a regex. */
108
- let lastCode = '';
109
- /** The last identifier word and the word being read — a module specifier follows `from`. */
110
- let lastWord = '';
111
- let word = '';
112
- /** A specifier (`from 'playwright'`, `require('playwright')`) stays readable to provenance. */
113
- const specifierAhead = () => lastWord === 'from' || lastWord === 'require' || lastWord === 'import';
114
- let i = 0;
115
- while (i < source.length) {
116
- const frame = top();
117
- const char = source[i] ?? '';
118
- const next = source[i + 1];
119
- if (frame.mode === 'code') {
120
- if (char === '/' && next === '/') {
121
- stack.push({ mode: 'line' });
122
- blank(i, i + 2);
123
- i += 2;
124
- continue;
125
- }
126
- if (char === '/' && next === '*') {
127
- stack.push({ mode: 'block' });
128
- blank(i, i + 2);
129
- i += 2;
130
- continue;
131
- }
132
- if (char === "'" || char === '"') {
133
- stack.push({ mode: 'string', quote: char, keep: specifierAhead() });
134
- if (!specifierAhead())
135
- out[i] = ' ';
136
- i += 1;
137
- continue;
138
- }
139
- if (char === '`') {
140
- stack.push({ mode: 'template' });
141
- out[i] = ' ';
142
- i += 1;
143
- continue;
144
- }
145
- if (char === '/' && REGEX_AFTER.has(lastCode)) {
146
- stack.push({ mode: 'regex', inClass: false });
147
- out[i] = ' ';
148
- i += 1;
149
- continue;
150
- }
151
- if (frame.braces > 0) {
152
- if (char === '{')
153
- frame.braces += 1;
154
- else if (char === '}') {
155
- frame.braces -= 1;
156
- if (frame.braces === 0) {
157
- stack.pop();
158
- out[i] = ' ';
159
- i += 1;
160
- continue;
161
- }
162
- }
163
- }
164
- if (/[\w$]/.test(char))
165
- word += char;
166
- else if (word !== '') {
167
- lastWord = word;
168
- word = '';
169
- }
170
- if (!/\s/.test(char))
171
- lastCode = char;
172
- i += 1;
173
- continue;
174
- }
175
- if (frame.mode === 'line') {
176
- if (char === '\n') {
177
- stack.pop();
178
- i += 1;
179
- continue;
180
- }
181
- out[i] = ' ';
182
- i += 1;
183
- continue;
184
- }
185
- if (frame.mode === 'block') {
186
- if (char === '*' && next === '/') {
187
- blank(i, i + 2);
188
- stack.pop();
189
- i += 2;
190
- continue;
191
- }
192
- if (char !== '\n')
193
- out[i] = ' ';
194
- i += 1;
195
- continue;
196
- }
197
- if (frame.mode === 'string') {
198
- if (char === '\\') {
199
- if (!frame.keep)
200
- blank(i, i + 2);
201
- i += 2;
202
- continue;
203
- }
204
- if (char === frame.quote) {
205
- if (!frame.keep)
206
- out[i] = ' ';
207
- stack.pop();
208
- // A string is an operand: the next `/` is division, not a regex.
209
- lastCode = 'x';
210
- i += 1;
211
- continue;
212
- }
213
- if (!frame.keep && char !== '\n')
214
- out[i] = ' ';
215
- i += 1;
216
- continue;
217
- }
218
- if (frame.mode === 'template') {
219
- if (char === '\\') {
220
- blank(i, i + 2);
221
- i += 2;
222
- continue;
223
- }
224
- if (char === '`') {
225
- out[i] = ' ';
226
- stack.pop();
227
- lastCode = 'x';
228
- i += 1;
229
- continue;
230
- }
231
- if (char === '$' && next === '{') {
232
- // The substitution is code: keep it visible and track its closing brace.
233
- blank(i, i + 2);
234
- stack.push({ mode: 'code', braces: 1 });
235
- lastCode = '{';
236
- i += 2;
237
- continue;
238
- }
239
- if (char !== '\n')
240
- out[i] = ' ';
241
- i += 1;
242
- continue;
243
- }
244
- // Regex literal: blank it, honour escapes and character classes, then its flags.
245
- if (char === '\\') {
246
- blank(i, i + 2);
247
- i += 2;
248
- continue;
249
- }
250
- if (char === '[')
251
- frame.inClass = true;
252
- else if (char === ']')
253
- frame.inClass = false;
254
- else if (char === '/' && !frame.inClass) {
255
- out[i] = ' ';
256
- stack.pop();
257
- lastCode = 'x';
258
- i += 1;
259
- while (i < source.length && /[a-z]/i.test(source[i] ?? '')) {
260
- out[i] = ' ';
261
- i += 1;
262
- }
263
- continue;
264
- }
265
- if (char !== '\n')
266
- out[i] = ' ';
267
- i += 1;
268
- }
269
- return out.join('');
270
- }
271
61
  /**
272
62
  * How many `{ … }` blocks are open at one offset of the code-only text.
273
63
  *
@@ -0,0 +1,17 @@
1
+ /** Shared lexical masking for source inspection without executing a module. */
2
+ /**
3
+ * A source with every comment and every string, template and regex literal blanked to spaces,
4
+ * preserving offsets and newlines so a match's position still indexes the original source.
5
+ *
6
+ * A launch mentioned in prose is not a launch: a review proved scripts that only PRINT
7
+ * `const browser = await chromium.launch()` — in a string, a comment and a template literal — and
8
+ * a raw substring match refused all of them. Template `${…}` substitutions stay code (a `close()`
9
+ * written there is a real cleanup path), nested braces are tracked so the substitution's end is
10
+ * found, and regex literals are recognised by the token before them so a quote inside a character
11
+ * class cannot blank the rest of the file.
12
+ *
13
+ * @param source - The script's source.
14
+ * @returns The same text with literal/comment spans replaced by spaces.
15
+ */
16
+ export declare function codeWithModuleSpecifiers(source: string): string;
17
+ //# sourceMappingURL=source-code.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"source-code.d.ts","sourceRoot":"","sources":["../src/source-code.ts"],"names":[],"mappings":"AAAA,+EAA+E;AA2B/E;;;;;;;;;;;;;GAaG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAwK/D"}
@@ -0,0 +1,214 @@
1
+ /** Shared lexical masking for source inspection without executing a module. */
2
+ /** The characters after which a `/` starts a regex literal rather than a division. */
3
+ const REGEX_AFTER = new Set([
4
+ '',
5
+ '(',
6
+ '[',
7
+ '{',
8
+ ',',
9
+ ';',
10
+ ':',
11
+ '=',
12
+ '!',
13
+ '&',
14
+ '|',
15
+ '?',
16
+ '+',
17
+ '-',
18
+ '*',
19
+ '%',
20
+ '^',
21
+ '~',
22
+ '<',
23
+ '>',
24
+ '}',
25
+ ]);
26
+ /**
27
+ * A source with every comment and every string, template and regex literal blanked to spaces,
28
+ * preserving offsets and newlines so a match's position still indexes the original source.
29
+ *
30
+ * A launch mentioned in prose is not a launch: a review proved scripts that only PRINT
31
+ * `const browser = await chromium.launch()` — in a string, a comment and a template literal — and
32
+ * a raw substring match refused all of them. Template `${…}` substitutions stay code (a `close()`
33
+ * written there is a real cleanup path), nested braces are tracked so the substitution's end is
34
+ * found, and regex literals are recognised by the token before them so a quote inside a character
35
+ * class cannot blank the rest of the file.
36
+ *
37
+ * @param source - The script's source.
38
+ * @returns The same text with literal/comment spans replaced by spaces.
39
+ */
40
+ export function codeWithModuleSpecifiers(source) {
41
+ const out = source.split('');
42
+ const blank = (from, to) => {
43
+ for (let i = from; i < to; i++)
44
+ if (out[i] !== '\n')
45
+ out[i] = ' ';
46
+ };
47
+ const stack = [{ mode: 'code', braces: 0 }];
48
+ const top = () => stack[stack.length - 1];
49
+ /** The last non-whitespace CODE character: decides whether a `/` opens a regex. */
50
+ let lastCode = '';
51
+ /** The last identifier word and the word being read — a module specifier follows `from`. */
52
+ let lastWord = '';
53
+ let word = '';
54
+ /** A specifier (`from 'playwright'`, `require('playwright')`) stays readable to provenance. */
55
+ const specifierAhead = () => (lastWord === 'from' && lastCode === 'm') ||
56
+ (lastWord === 'import' && lastCode === 't') ||
57
+ ((lastWord === 'require' || lastWord === 'import') && lastCode === '(');
58
+ let i = 0;
59
+ while (i < source.length) {
60
+ const frame = top();
61
+ const char = source[i] ?? '';
62
+ const next = source[i + 1];
63
+ if (frame.mode === 'code') {
64
+ if (char === '/' && next === '/') {
65
+ stack.push({ mode: 'line' });
66
+ blank(i, i + 2);
67
+ i += 2;
68
+ continue;
69
+ }
70
+ if (char === '/' && next === '*') {
71
+ stack.push({ mode: 'block' });
72
+ blank(i, i + 2);
73
+ i += 2;
74
+ continue;
75
+ }
76
+ if (char === "'" || char === '"') {
77
+ stack.push({ mode: 'string', quote: char, keep: specifierAhead() });
78
+ if (!specifierAhead())
79
+ out[i] = ' ';
80
+ i += 1;
81
+ continue;
82
+ }
83
+ if (char === '`') {
84
+ stack.push({ mode: 'template' });
85
+ out[i] = ' ';
86
+ i += 1;
87
+ continue;
88
+ }
89
+ if (char === '/' && REGEX_AFTER.has(lastCode)) {
90
+ stack.push({ mode: 'regex', inClass: false });
91
+ out[i] = ' ';
92
+ i += 1;
93
+ continue;
94
+ }
95
+ if (frame.braces > 0) {
96
+ if (char === '{')
97
+ frame.braces += 1;
98
+ else if (char === '}') {
99
+ frame.braces -= 1;
100
+ if (frame.braces === 0) {
101
+ stack.pop();
102
+ out[i] = ' ';
103
+ i += 1;
104
+ continue;
105
+ }
106
+ }
107
+ }
108
+ if (/[\w$]/.test(char))
109
+ word += char;
110
+ else if (word !== '') {
111
+ lastWord = word;
112
+ word = '';
113
+ }
114
+ if (!/\s/.test(char))
115
+ lastCode = char;
116
+ i += 1;
117
+ continue;
118
+ }
119
+ if (frame.mode === 'line') {
120
+ if (char === '\n') {
121
+ stack.pop();
122
+ i += 1;
123
+ continue;
124
+ }
125
+ out[i] = ' ';
126
+ i += 1;
127
+ continue;
128
+ }
129
+ if (frame.mode === 'block') {
130
+ if (char === '*' && next === '/') {
131
+ blank(i, i + 2);
132
+ stack.pop();
133
+ i += 2;
134
+ continue;
135
+ }
136
+ if (char !== '\n')
137
+ out[i] = ' ';
138
+ i += 1;
139
+ continue;
140
+ }
141
+ if (frame.mode === 'string') {
142
+ if (char === '\\') {
143
+ if (!frame.keep)
144
+ blank(i, i + 2);
145
+ i += 2;
146
+ continue;
147
+ }
148
+ if (char === frame.quote) {
149
+ if (!frame.keep)
150
+ out[i] = ' ';
151
+ stack.pop();
152
+ // A string is an operand: the next `/` is division, not a regex.
153
+ lastCode = 'x';
154
+ i += 1;
155
+ continue;
156
+ }
157
+ if (!frame.keep && char !== '\n')
158
+ out[i] = ' ';
159
+ i += 1;
160
+ continue;
161
+ }
162
+ if (frame.mode === 'template') {
163
+ if (char === '\\') {
164
+ blank(i, i + 2);
165
+ i += 2;
166
+ continue;
167
+ }
168
+ if (char === '`') {
169
+ out[i] = ' ';
170
+ stack.pop();
171
+ lastCode = 'x';
172
+ i += 1;
173
+ continue;
174
+ }
175
+ if (char === '$' && next === '{') {
176
+ // The substitution is code: keep it visible and track its closing brace.
177
+ blank(i, i + 2);
178
+ stack.push({ mode: 'code', braces: 1 });
179
+ lastCode = '{';
180
+ i += 2;
181
+ continue;
182
+ }
183
+ if (char !== '\n')
184
+ out[i] = ' ';
185
+ i += 1;
186
+ continue;
187
+ }
188
+ // Regex literal: blank it, honour escapes and character classes, then its flags.
189
+ if (char === '\\') {
190
+ blank(i, i + 2);
191
+ i += 2;
192
+ continue;
193
+ }
194
+ if (char === '[')
195
+ frame.inClass = true;
196
+ else if (char === ']')
197
+ frame.inClass = false;
198
+ else if (char === '/' && !frame.inClass) {
199
+ out[i] = ' ';
200
+ stack.pop();
201
+ lastCode = 'x';
202
+ i += 1;
203
+ while (i < source.length && /[a-z]/i.test(source[i] ?? '')) {
204
+ out[i] = ' ';
205
+ i += 1;
206
+ }
207
+ continue;
208
+ }
209
+ if (char !== '\n')
210
+ out[i] = ' ';
211
+ i += 1;
212
+ }
213
+ return out.join('');
214
+ }
@@ -50,6 +50,14 @@ export declare function withoutHeredocs(command: string): string;
50
50
  * @returns The target key.
51
51
  */
52
52
  export declare function suiteCommandTarget(command: string): string;
53
+ /**
54
+ * The target of the actual Playwright step, including inherited exports. URLs and option names
55
+ * in another program or a quoted title cannot decide whether this step is deployed.
56
+ * @param command - The shell command.
57
+ * @param last - Read the last Playwright invocation, for the background launch rewrite.
58
+ * @returns The comparable target key, or local when no target setting is present.
59
+ */
60
+ export declare function playwrightCommandTarget(command: string, last?: boolean): string;
53
61
  /**
54
62
  * Whether a Playwright statement names its own config — the reading {@link withSuiteConfig} and
55
63
  * {@link scopedRerunCommand} share, so neither adds a second `--config` to a command that has one.
@@ -147,6 +155,22 @@ export interface RunEnvSetting {
147
155
  * @returns The command, or the command unchanged when it already carries every setting.
148
156
  */
149
157
  export declare function withRunEnv(command: string, settings?: readonly RunEnvSetting[]): string;
158
+ /**
159
+ * The absolute directory the Playwright statement in a command runs in, or null when the command
160
+ * does not say.
161
+ *
162
+ * The reporter prints a failing test's file relative to the process directory the runner started
163
+ * in (`../fixtures/…` from a run whose suite lives outside its own tree), so the directory has to
164
+ * come from the command itself: `exec_command`'s `cwd` when it was given, then each `cd` before
165
+ * the runner. Only a literal `cd` is followed — a target holding a variable, a glob or `~`
166
+ * resolves nowhere useful, and a guessed absolute path is worse than the relative one the
167
+ * reporter printed.
168
+ *
169
+ * @param command - The shell command.
170
+ * @param cwd - The `cwd` the tool was given, when it was.
171
+ * @returns The absolute directory, or null.
172
+ */
173
+ export declare function suiteRunDirectory(command: string, cwd?: string | null): string | null;
150
174
  /**
151
175
  * Resolve literal `./`/`../` Playwright file selections against the runner's own directory.
152
176
  *
@@ -1 +1 @@
1
- {"version":3,"file":"suite-overlap.d.ts","sourceRoot":"","sources":["../src/suite-overlap.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAaH;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEvD;AAED;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAoB1D;AAKD;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAEzD;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAS1F;AA2VD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;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,EAClC,OAAO,GAAE;IAAE,aAAa,CAAC,EAAE,OAAO,CAAA;CAAO,GACxC,MAAM,CA2DR;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;AA2PD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,+BAA+B,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAa5F;AAED;;;;;;GAMG;AACH,MAAM,WAAW,gBAAgB;IAC/B,+FAA+F;IAC/F,SAAS,EAAE,OAAO,CAAA;IAClB,oFAAoF;IACpF,MAAM,EAAE,MAAM,CAAA;IACd;;;OAGG;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;AAqHD;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC;;;OAGG;IACH,kBAAkB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACtC;;;;;;;;;;;;;OAaG;IACH,aAAa,CAAC,EAAE,OAAO,CAAA;CACxB;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,cAAc,EAAE,OAAO,MAA4D,CAAA;AAEhG;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAM7D;AAmCD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkDG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,mBAAmB,GAAG,OAAO,CA2BtF"}
1
+ {"version":3,"file":"suite-overlap.d.ts","sourceRoot":"","sources":["../src/suite-overlap.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAaH;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEvD;AAED;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAoB1D;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,UAAQ,GAAG,MAAM,CAwB7E;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;AA2VD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;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,EAClC,OAAO,GAAE;IAAE,aAAa,CAAC,EAAE,OAAO,CAAA;CAAO,GACxC,MAAM,CA2DR;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;AAQD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,GAAG,IAAI,CAiCrF;AAkND;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,+BAA+B,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAa5F;AAED;;;;;;GAMG;AACH,MAAM,WAAW,gBAAgB;IAC/B,+FAA+F;IAC/F,SAAS,EAAE,OAAO,CAAA;IAClB,oFAAoF;IACpF,MAAM,EAAE,MAAM,CAAA;IACd;;;OAGG;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;AAqHD;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC;;;OAGG;IACH,kBAAkB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACtC;;;;;;;;;;;;;OAaG;IACH,aAAa,CAAC,EAAE,OAAO,CAAA;CACxB;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,cAAc,EAAE,OAAO,MAA4D,CAAA;AAEhG;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAM7D;AAmCD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkDG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,mBAAmB,GAAG,OAAO,CA2BtF"}
@@ -75,6 +75,31 @@ export function suiteCommandTarget(command) {
75
75
  }
76
76
  return 'local';
77
77
  }
78
+ /**
79
+ * The target of the actual Playwright step, including inherited exports. URLs and option names
80
+ * in another program or a quoted title cannot decide whether this step is deployed.
81
+ * @param command - The shell command.
82
+ * @param last - Read the last Playwright invocation, for the background launch rewrite.
83
+ * @returns The comparable target key, or local when no target setting is present.
84
+ */
85
+ export function playwrightCommandTarget(command, last = false) {
86
+ const steps = locatedCommandSegments(command);
87
+ const runners = steps.filter((step) => step.segment?.program === 'playwright' && step.segment.args[0] === 'test');
88
+ const runner = runners.at(last ? -1 : 0);
89
+ if (!runner)
90
+ return suiteCommandTarget(command);
91
+ const inherited = steps
92
+ .slice(0, steps.indexOf(runner))
93
+ .filter((step) => (step.shellScope ?? []).every((scope, i) => scope === runner.shellScope?.[i]) &&
94
+ (step.segment === null || step.segment.program === 'export'));
95
+ const prefix = runner.start !== null && runner.programOffset !== null
96
+ ? command.slice(runner.start, runner.programOffset)
97
+ : runner.assignments.join(' ');
98
+ return suiteCommandTarget([
99
+ prefix,
100
+ ...inherited.reverse().map((step) => step.segment?.text ?? step.assignments.join(' ')),
101
+ ].join('; '));
102
+ }
78
103
  /** Whether a Playwright statement already names its own config (`-c`, `--config`, `--config=`). */
79
104
  const NAMES_CONFIG = /(?:^|\s)(?:-c|--config)(?:\s+|=)/;
80
105
  /**
@@ -619,29 +644,40 @@ const unquoted = (word) => word.replace(/^['"]|['"]$/g, '');
619
644
  * @param cwd - The `cwd` the tool was given, when it was.
620
645
  * @returns The absolute directory, or null.
621
646
  */
622
- function suiteRunDirectory(command, cwd) {
623
- let dir = typeof cwd === 'string' && cwd.startsWith('/') ? posix.normalize(cwd) : null;
624
- for (const { segment } of locatedCommandSegments(command)) {
647
+ export function suiteRunDirectory(command, cwd) {
648
+ const dirs = new Map([
649
+ ['', typeof cwd === 'string' && cwd.startsWith('/') ? posix.normalize(cwd) : null],
650
+ ]);
651
+ for (const located of locatedCommandSegments(command)) {
652
+ const scope = located.shellScope ?? [];
653
+ const key = scope.join('\0');
654
+ if (!dirs.has(key)) {
655
+ let parent = scope.slice(0, -1);
656
+ while (!dirs.has(parent.join('\0')))
657
+ parent = parent.slice(0, -1);
658
+ dirs.set(key, dirs.get(parent.join('\0')) ?? null);
659
+ }
660
+ const { segment } = located;
625
661
  if (segment === null)
626
662
  continue;
627
663
  if (segment.program === 'cd') {
628
- const target = unquoted(segment.args[0] ?? '').replace(/\/+$/, '');
629
- if (!target || target.startsWith('-') || target.startsWith('~') || !LITERAL_DIR.test(target))
630
- return null;
631
- // A shell `cd /abs` DISCARDS the directory before it; only a relative target resolves
632
- // against the one the run started in.
633
- if (target.startsWith('/'))
634
- dir = posix.normalize(target);
635
- else if (dir === null)
636
- return null;
637
- else
638
- dir = posix.normalize(posix.join(dir, target));
639
- continue;
664
+ const words = segment.args.map(unquoted);
665
+ while (words[0] === '--' || words[0] === '-L' || words[0] === '-P')
666
+ words.shift();
667
+ const target = words[0] ?? '';
668
+ const dir = dirs.get(key) ?? null;
669
+ dirs.set(key, !target || target.startsWith('-') || target.startsWith('~') || !LITERAL_DIR.test(target)
670
+ ? null
671
+ : target.startsWith('/')
672
+ ? posix.normalize(target)
673
+ : dir === null
674
+ ? null
675
+ : posix.normalize(posix.join(dir, target)));
640
676
  }
641
677
  if (segment.program === 'playwright' && segment.args[0] === 'test')
642
- break;
678
+ return dirs.get(key) ?? null;
643
679
  }
644
- return dir;
680
+ return null;
645
681
  }
646
682
  /** A failure location's path and its `:line[:column]` suffix, split the way Playwright does. */
647
683
  const LOCATION_LINE = /^(.*?)(:\d+(?::\d+)?)$/;
@@ -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;AA2B9C,OAAO,EAIL,KAAK,mBAAmB,EAEzB,MAAM,wBAAwB,CAAA;AAsC/B;;;;;;;;;;GAUG;AACH,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,IAAI,CAAC,gBAAgB,EAAE,UAAU,GAAG,KAAK,CAAC,EACnD,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GACjC,OAAO,CAAC,mBAAmB,CAAC,CAmB9B;AAuLD;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,eAAe,CAAC,OAAO,EAAE,gBAAgB,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAahG;AAwdD,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAkInE;;;GAGG;AACH,eAAO,MAAM,iBAAiB,QAEA,CAAA;AAE9B;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,wBAAwB,QAI6C,CAAA;AA0DlF;;;;;;;;;;;GAWG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAUhE;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAGtE;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,CA6pGxF"}
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;AA2B9C,OAAO,EAIL,KAAK,mBAAmB,EAEzB,MAAM,wBAAwB,CAAA;AAsC/B;;;;;;;;;;GAUG;AACH,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,IAAI,CAAC,gBAAgB,EAAE,UAAU,GAAG,KAAK,CAAC,EACnD,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GACjC,OAAO,CAAC,mBAAmB,CAAC,CAmB9B;AAuLD;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,eAAe,CAAC,OAAO,EAAE,gBAAgB,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAahG;AAwdD,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAkInE;;;GAGG;AACH,eAAO,MAAM,iBAAiB,QAEA,CAAA;AAE9B;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,wBAAwB,QAI6C,CAAA;AA0DlF;;;;;;;;;;;GAWG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAUhE;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAGtE;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,CAypGxF"}
package/dist/tools.js CHANGED
@@ -2819,17 +2819,13 @@ export function buildTools(backend, config) {
2819
2819
  };
2820
2820
  }
2821
2821
  }
2822
- // A Playwright run of a suite whose config records no trace gets one recorded anyway
2823
- // (`diagnosisTraceCommand`, trace-artifacts.ts): the failing tests' own network log is what
2824
- // tells a swallowed request error from a selector, and X1 xr-molecule-flash-m100 spent the
2825
- // last minutes of its run hand-writing Chromium probes for what the trace it never wrote
2826
- // down would have said. The whole rule is that function's; the whole-suite proof run gets it
2827
- // too, since a trace is retained only for a failing test (m163's proof run left none and the
2828
- // executor hunted one that did not exist).
2822
+ // Diagnostic runs need readable failure traces. A proof-capable deployed run retains its
2823
+ // own settings; diagnosisTraceCommand owns both decisions and uses the backend's readers.
2829
2824
  const traced = await diagnosisTraceCommand({
2830
2825
  command: rawCommand,
2831
2826
  cwd,
2832
2827
  readFile: (path) => backend.readFile(path),
2828
+ readDir: (path) => backend.readDir(path),
2833
2829
  });
2834
2830
  let command = traced.command;
2835
2831
  // A leading `sleep N; <read of a background task's log>` is a hand-rolled poll, and the
@@ -853,6 +853,11 @@ export declare function suiteRecordsTraces(configText: string): boolean;
853
853
  export declare function namesNoRetries(command: string): boolean;
854
854
  /** The `--trace` flag this platform adds, and the only value it adds it with. */
855
855
  export declare const DIAGNOSIS_TRACE_FLAG = "--trace=retain-on-failure";
856
+ /** The backend's existing directory capability, when available. */
857
+ type ReadConfigDirectory = (path: string) => Promise<Array<{
858
+ name: string;
859
+ type: 'file' | 'directory';
860
+ }>>;
856
861
  /** A command with the trace flag this platform decided to add, and the sentence to report it in. */
857
862
  export interface DiagnosisTraceRewrite {
858
863
  /** The command to run. */
@@ -904,6 +909,7 @@ export declare function diagnosisTraceCommand(args: {
904
909
  command: string;
905
910
  cwd: string;
906
911
  readFile: (path: string) => Promise<string>;
912
+ readDir?: ReadConfigDirectory;
907
913
  }): Promise<DiagnosisTraceRewrite>;
908
914
  export {};
909
915
  //# sourceMappingURL=trace-artifacts.d.ts.map