@molecule/api-ai-tools 1.10.6 → 1.10.8
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 +118 -18
- package/dist/browser-probe.d.ts +82 -0
- package/dist/browser-probe.d.ts.map +1 -0
- package/dist/browser-probe.js +526 -0
- package/dist/command-position.d.ts.map +1 -1
- package/dist/command-position.js +51 -7
- package/dist/cut-run.d.ts +7 -0
- package/dist/cut-run.d.ts.map +1 -1
- package/dist/cut-run.js +10 -5
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/inline-script.d.ts +11 -1
- package/dist/inline-script.d.ts.map +1 -1
- package/dist/inline-script.js +50 -6
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +15 -3
- package/dist/server-command.d.ts +16 -0
- package/dist/server-command.d.ts.map +1 -1
- package/dist/server-command.js +37 -0
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +52 -4
- package/dist/utilities.d.ts +7 -16
- package/dist/utilities.d.ts.map +1 -1
- package/dist/utilities.js +30 -33
- package/package.json +1 -1
|
@@ -0,0 +1,526 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A standalone Playwright probe that launches a browser and never closes it — the
|
|
3
|
+
* resource-lifecycle hang `exec_command` did not yet classify.
|
|
4
|
+
*
|
|
5
|
+
* X1 xr-molecule-flash-m257 (+10:44 → +15:34) ran
|
|
6
|
+
* `cd /workspace/my-app/app && node /workspace/.mol-scratch/fav-probe.mjs 2>&1 | tail -5`. The
|
|
7
|
+
* 30-line script binds `const browser = await chromium.launch()`, walks a real page, reaches its
|
|
8
|
+
* last `console.log`, and ends without `browser.close()`, `disconnect()`, a disposal scope or
|
|
9
|
+
* `process.exit()`. Chromium is a live handle, so Node never exits: the tool waited its whole 290 s
|
|
10
|
+
* budget, returned `status: cut_at_budget` with empty stdout (the `| tail` held it), and then
|
|
11
|
+
* advised "run it in the background" — advice for a slow workload, not for a leaked handle.
|
|
12
|
+
*
|
|
13
|
+
* That is the same lifecycle class {@link foregroundServerCommand} already refuses: a foreground
|
|
14
|
+
* process that never returns. So the same preflight seam reads it, with a deliberately narrow
|
|
15
|
+
* closed-world proof:
|
|
16
|
+
*
|
|
17
|
+
* - the script is DIRECTLY executed (`node`/`tsx`/`bun`/`deno` naming a script file, never a
|
|
18
|
+
* test-runner invocation or a test/fixture file — those are the runner's business);
|
|
19
|
+
* - the source, with comments and string/template/regex literals blanked, binds a browser by a
|
|
20
|
+
* TOP-LEVEL (brace depth 0) `const|let|var <x> = await chromium|firefox|webkit.launch(`
|
|
21
|
+
* assignment, where the engine (or its namespace) is imported/required from `playwright`,
|
|
22
|
+
* `playwright-core` or `@playwright/test` — a locally bound stub is not Playwright;
|
|
23
|
+
* - every reference to `<x>` is a known browser method that does NOT close it (`newPage`,
|
|
24
|
+
* `newContext`, `on`, `version`, …), and the binding is never aliased, passed to a helper, or
|
|
25
|
+
* read as a property;
|
|
26
|
+
* - nothing in the source is a cleanup path (any `.close(`/`.disconnect(`/`.dispose(`, a `using`
|
|
27
|
+
* scope, a cleanup-named import), an escape (`process.exit`, `eval`, `new Function`, dynamic
|
|
28
|
+
* `import`), or a persistent-process intent (`launchServer`, `connect`, a server `.listen`, a
|
|
29
|
+
* timer or signal handler).
|
|
30
|
+
*
|
|
31
|
+
* Anything ambiguous is left alone. A wrong refusal costs a valid probe — the review that found
|
|
32
|
+
* the m257 leak also proved five harmless scripts a raw substring match would refuse (a local
|
|
33
|
+
* engine stub, the launch text in a string, in a comment, in a template literal, and inside a
|
|
34
|
+
* function that is never called) — and a missed leak costs the same budget the tool already had.
|
|
35
|
+
* This is a lifecycle diagnostic, never an egress or security gate.
|
|
36
|
+
*
|
|
37
|
+
* @module
|
|
38
|
+
*/
|
|
39
|
+
/** A `const|let|var <name> = await [qualifier.]chromium|firefox|webkit.launch(` binding. */
|
|
40
|
+
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
|
+
/** A browser method that does not close the browser, so calling it leaves the handle alive. */
|
|
42
|
+
const SAFE_BROWSER_METHOD = '(?:newPage|newContext|version|isConnected|contexts|browserType|on|once|off|addListener|' +
|
|
43
|
+
'removeListener|removeAllListeners|eventNames|listenerCount|rawListeners|setMaxListeners|' +
|
|
44
|
+
'getMaxListeners|prependListener|prependOnceListener|startTracing|stopTracing)';
|
|
45
|
+
/** An explicit process exit: Node ends even with a live browser handle, so this is not a leak. */
|
|
46
|
+
const EXPLICIT_EXIT = /\bprocess\s*\.\s*exit\s*\(/;
|
|
47
|
+
/** A top-level `throw`: an uncaught exception ends Node even with a live browser handle. */
|
|
48
|
+
const THROW = /(?<![\w$.])throw\b/g;
|
|
49
|
+
/** Code a source-level reading cannot follow, so the script is left alone. */
|
|
50
|
+
const INDIRECT_CODE = /\b(?:eval|new\s+Function)\s*\(|\bimport\s*\(/;
|
|
51
|
+
/** A persistent process, not a probe that should have ended: a browser server or a listener. */
|
|
52
|
+
const PERSISTENT_INTENT = /\bsetInterval\s*\(|\bwhile\s*\(\s*true\s*\)|\bfor\s*\(\s*;\s*;\s*\)|\bprocess\s*\.\s*(?:on|once)\s*\(|\.\s*(?:connectOverCDP|launchServer|launchPersistentContext|connect)\s*\(|\.\s*listen\s*\(/;
|
|
53
|
+
/** A disposal scope (`using browser = …`) or any resource close: an ambiguity by construction. */
|
|
54
|
+
const DISPOSAL_SCOPE = /\b(?:await\s+)?using\s+[A-Za-z_$][\w$]*\s*=/;
|
|
55
|
+
const ANY_CLEANUP = /\.\s*(?:close|disconnect|dispose)\s*\(|[A-Za-z_$][\w$]*\s*\[\s*['"](?:close|disconnect|dispose)['"]\s*\]/;
|
|
56
|
+
/** An import whose name or module path suggests cleanup the source reading cannot see. */
|
|
57
|
+
const CLEANUP_HINT = /cleanup|teardown|close|dispose|shutdown|quit/i;
|
|
58
|
+
/** The packages whose `chromium`/`firefox`/`webkit` export really launches a browser. */
|
|
59
|
+
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: the m257 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
|
+
/**
|
|
272
|
+
* How many `{ … }` blocks are open at one offset of the code-only text.
|
|
273
|
+
*
|
|
274
|
+
* @param code - The {@link codeOnly} text.
|
|
275
|
+
* @param at - The offset to ask about.
|
|
276
|
+
* @returns The open-block count (`0` at the module's top level).
|
|
277
|
+
*/
|
|
278
|
+
function braceDepthAt(code, at) {
|
|
279
|
+
let depth = 0;
|
|
280
|
+
for (let i = 0; i < at; i += 1) {
|
|
281
|
+
const char = code[i] ?? '';
|
|
282
|
+
if (char === '{')
|
|
283
|
+
depth += 1;
|
|
284
|
+
else if (char === '}')
|
|
285
|
+
depth = Math.max(0, depth - 1);
|
|
286
|
+
}
|
|
287
|
+
return depth;
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Whether the script throws at its own top level — an uncaught exception that terminates Node
|
|
291
|
+
* before the leaked handle can keep it alive. A `throw` inside a function or a `try` block is not
|
|
292
|
+
* an exit path for every run, so it is left where it is.
|
|
293
|
+
*
|
|
294
|
+
* @param code - The {@link codeOnly} text.
|
|
295
|
+
* @returns True when a `throw` statement sits at brace depth 0.
|
|
296
|
+
*/
|
|
297
|
+
function topLevelThrow(code) {
|
|
298
|
+
for (const match of code.matchAll(THROW)) {
|
|
299
|
+
if (braceDepthAt(code, match.index ?? 0) === 0)
|
|
300
|
+
return true;
|
|
301
|
+
}
|
|
302
|
+
return false;
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Whether a declaration is the first thing on its line — only whitespace between it and the
|
|
306
|
+
* previous newline.
|
|
307
|
+
*
|
|
308
|
+
* This is the conservative guard for the one shape the lexer cannot read: a regex literal whose
|
|
309
|
+
* opening `/` follows a `)` (`if (x) /…/.test(s)`) reads as division, so the launch text inside it
|
|
310
|
+
* survives blanking. A mid-line top-level launch is a deliberate false negative; every positive
|
|
311
|
+
* shape (the m257 probe, `import * as pw` + `pw.chromium.launch`, a destructured require) declares
|
|
312
|
+
* at line start.
|
|
313
|
+
*
|
|
314
|
+
* @param code - The {@link codeOnly} text.
|
|
315
|
+
* @param at - The offset of the declaration.
|
|
316
|
+
* @returns True when the declaration starts its line.
|
|
317
|
+
*/
|
|
318
|
+
function startsLine(code, at) {
|
|
319
|
+
for (let i = at - 1; i >= 0; i -= 1) {
|
|
320
|
+
const char = code[i] ?? '';
|
|
321
|
+
if (char === '\n')
|
|
322
|
+
return true;
|
|
323
|
+
if (!/\s/.test(char))
|
|
324
|
+
return false;
|
|
325
|
+
}
|
|
326
|
+
return true;
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Escape one text for a literal use inside a regular expression.
|
|
330
|
+
*
|
|
331
|
+
* @param text - The text to escape.
|
|
332
|
+
* @returns The escaped text.
|
|
333
|
+
*/
|
|
334
|
+
function escapeRegExp(text) {
|
|
335
|
+
return text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
336
|
+
}
|
|
337
|
+
/** Every reference to one variable, as a whole word not reached through another object. */
|
|
338
|
+
function occurrences(variable) {
|
|
339
|
+
return new RegExp(`(?<![\\w$.])${escapeRegExp(variable)}(?![\\w$])`, 'g');
|
|
340
|
+
}
|
|
341
|
+
/** Every direct call of a non-closing browser method on one variable. */
|
|
342
|
+
function safeCalls(variable) {
|
|
343
|
+
return new RegExp(`(?<![\\w$.])${escapeRegExp(variable)}\\s*\\.\\s*${SAFE_BROWSER_METHOD}\\s*\\(`, 'g');
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Whether a name is declared locally (`const chromium = { launch… }`) — a stub, not Playwright.
|
|
347
|
+
*
|
|
348
|
+
* A local declaration of the engine or of the namespace it is reached through makes the source
|
|
349
|
+
* ambiguous, so it is left alone. The declaration in `const { chromium } = require(…)` is a
|
|
350
|
+
* destructuring pattern, not this shape, and is handled by {@link namedRequireBinding}.
|
|
351
|
+
*
|
|
352
|
+
* @param code - The {@link codeOnly} text.
|
|
353
|
+
* @param name - The binding to look for.
|
|
354
|
+
* @returns True when the name is shadowed by a local declaration.
|
|
355
|
+
*/
|
|
356
|
+
function locallyDeclared(code, name) {
|
|
357
|
+
return new RegExp(`\\b(?:const|let|var|function|class)\\s+${escapeRegExp(name)}(?![\\w$])`).test(code);
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Whether a name is bound by a named import or a destructured require from a Playwright package.
|
|
361
|
+
*
|
|
362
|
+
* @param code - The {@link codeOnly} text.
|
|
363
|
+
* @param name - The binding to look for.
|
|
364
|
+
* @returns True when the name comes from Playwright.
|
|
365
|
+
*/
|
|
366
|
+
function namedPlaywrightBinding(code, name) {
|
|
367
|
+
const importRe = new RegExp(`\\bimport\\s*\\{([^}]*)\\}\\s*from\\s*['"]${PLAYWRIGHT_PACKAGE}['"]`, 'g');
|
|
368
|
+
for (const match of code.matchAll(importRe)) {
|
|
369
|
+
if ((match[1] ?? '').split(',').some((part) => part.trim() === name))
|
|
370
|
+
return true;
|
|
371
|
+
}
|
|
372
|
+
const requireRe = new RegExp(`\\b(?:const|let|var)\\s*\\{([^}]*)\\}\\s*=\\s*require\\s*\\(\\s*['"]${PLAYWRIGHT_PACKAGE}['"]\\s*\\)`, 'g');
|
|
373
|
+
for (const match of code.matchAll(requireRe)) {
|
|
374
|
+
if ((match[1] ?? '').split(',').some((part) => part.trim() === name))
|
|
375
|
+
return true;
|
|
376
|
+
}
|
|
377
|
+
return false;
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Whether a name is bound to a Playwright package as a namespace/default import or a whole
|
|
381
|
+
* `require` (`import * as pw from 'playwright'`, `const pw = require('playwright')`).
|
|
382
|
+
*
|
|
383
|
+
* @param code - The {@link codeOnly} text.
|
|
384
|
+
* @param name - The namespace binding to look for.
|
|
385
|
+
* @returns True when the name is a Playwright namespace.
|
|
386
|
+
*/
|
|
387
|
+
function namespacePlaywrightBinding(code, name) {
|
|
388
|
+
const importRe = new RegExp(`\\bimport\\s+(?:\\*\\s+as\\s+)?([A-Za-z_$][\\w$]*)\\s+from\\s*['"]${PLAYWRIGHT_PACKAGE}['"]`, 'g');
|
|
389
|
+
for (const match of code.matchAll(importRe)) {
|
|
390
|
+
if (match[1] === name)
|
|
391
|
+
return true;
|
|
392
|
+
}
|
|
393
|
+
const requireRe = new RegExp(`\\b(?:const|let|var)\\s+([A-Za-z_$][\\w$]*)\\s*=\\s*require\\s*\\(\\s*['"]${PLAYWRIGHT_PACKAGE}['"]\\s*\\)`, 'g');
|
|
394
|
+
for (const match of code.matchAll(requireRe)) {
|
|
395
|
+
if (match[1] === name)
|
|
396
|
+
return true;
|
|
397
|
+
}
|
|
398
|
+
return false;
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* Whether the launched engine really is Playwright's: the bare `chromium`/`firefox`/`webkit` bound
|
|
402
|
+
* by a named import/require, or a namespace bound to a Playwright package with the engine read off
|
|
403
|
+
* it. A local stub with a `launch()` method is not a browser and is left alone.
|
|
404
|
+
*
|
|
405
|
+
* @param code - The {@link codeOnly} text.
|
|
406
|
+
* @param qualifier - The namespace word before the engine (`pw` in `pw.chromium.launch`), or null.
|
|
407
|
+
* @param engine - The engine word (`chromium`, `firefox`, `webkit`).
|
|
408
|
+
* @returns True when the engine comes from a Playwright package.
|
|
409
|
+
*/
|
|
410
|
+
function playwrightEngine(code, qualifier, engine) {
|
|
411
|
+
if (qualifier === null) {
|
|
412
|
+
if (locallyDeclared(code, engine))
|
|
413
|
+
return false;
|
|
414
|
+
return namedPlaywrightBinding(code, engine);
|
|
415
|
+
}
|
|
416
|
+
if (locallyDeclared(code, qualifier))
|
|
417
|
+
return false;
|
|
418
|
+
return namedPlaywrightBinding(code, qualifier) || namespacePlaywrightBinding(code, qualifier);
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* Whether the source imports a helper whose job may be to close the browser — a cleanup path this
|
|
422
|
+
* reading cannot follow, so the script must be left alone.
|
|
423
|
+
*
|
|
424
|
+
* The check covers both the imported names and the module path (`./helpers/cleanup`), because the
|
|
425
|
+
* two ways a helper is named are both evidence. It is deliberately over-inclusive: a false
|
|
426
|
+
* negative here keeps a valid probe running.
|
|
427
|
+
*
|
|
428
|
+
* @param code - The {@link codeOnly} text.
|
|
429
|
+
* @returns True when an import names a cleanup helper.
|
|
430
|
+
*/
|
|
431
|
+
function importsCleanupHelper(code) {
|
|
432
|
+
for (const match of code.matchAll(/\bimport\s+([^;]*?)\s+from\s*['"]([^'"]+)['"]/g)) {
|
|
433
|
+
if (CLEANUP_HINT.test(match[1] ?? '') || CLEANUP_HINT.test(match[2] ?? ''))
|
|
434
|
+
return true;
|
|
435
|
+
}
|
|
436
|
+
for (const match of code.matchAll(/\brequire\s*\(\s*['"]([^'"]+)['"]\s*\)/g)) {
|
|
437
|
+
if (CLEANUP_HINT.test(match[1] ?? ''))
|
|
438
|
+
return true;
|
|
439
|
+
}
|
|
440
|
+
return false;
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* The browser variable a standalone script launches and never closes, when that can be proven from
|
|
444
|
+
* the source alone.
|
|
445
|
+
*
|
|
446
|
+
* The proof is a closed world: one plain TOP-LEVEL launch binding whose engine is genuinely
|
|
447
|
+
* Playwright's, every reference to it a known non-closing method call, and no cleanup, escape or
|
|
448
|
+
* persistent-process path anywhere in the source. A source with two launches is read one binding
|
|
449
|
+
* at a time — a proven leak in either one is enough, because that handle alone keeps Node alive.
|
|
450
|
+
*
|
|
451
|
+
* @param source - The script's source, as read from disk.
|
|
452
|
+
* @returns The leaking binding, or null when the script is clean or ambiguous.
|
|
453
|
+
*/
|
|
454
|
+
export function standaloneBrowserLeak(source) {
|
|
455
|
+
if (typeof source !== 'string' || source.trim() === '')
|
|
456
|
+
return null;
|
|
457
|
+
const code = codeOnly(source);
|
|
458
|
+
if (EXPLICIT_EXIT.test(code) || topLevelThrow(code) || INDIRECT_CODE.test(code))
|
|
459
|
+
return null;
|
|
460
|
+
if (PERSISTENT_INTENT.test(code) || DISPOSAL_SCOPE.test(code))
|
|
461
|
+
return null;
|
|
462
|
+
if (ANY_CLEANUP.test(code) || importsCleanupHelper(code))
|
|
463
|
+
return null;
|
|
464
|
+
for (const match of code.matchAll(LAUNCH)) {
|
|
465
|
+
const variable = match[1];
|
|
466
|
+
const qualifier = match[2] ?? null;
|
|
467
|
+
const engine = match[3];
|
|
468
|
+
if (variable === undefined || variable === '' || engine === undefined)
|
|
469
|
+
continue;
|
|
470
|
+
// A launch inside a function/block is not proof it ever ran: leave it alone.
|
|
471
|
+
if (braceDepthAt(code, match.index ?? 0) !== 0)
|
|
472
|
+
continue;
|
|
473
|
+
// A top-level launch must start its line: a mid-line match can be text the lexer could not
|
|
474
|
+
// classify (a regex literal after `)`), so it is not a closed world.
|
|
475
|
+
if (!startsLine(code, match.index ?? 0))
|
|
476
|
+
continue;
|
|
477
|
+
if (!playwrightEngine(code, qualifier, engine))
|
|
478
|
+
continue;
|
|
479
|
+
// The declaration is the one reference the count below does not have to explain.
|
|
480
|
+
const total = [...code.matchAll(occurrences(variable))].length;
|
|
481
|
+
const direct = [...code.matchAll(safeCalls(variable))].length;
|
|
482
|
+
// Anything besides the declaration and a direct non-closing call is an unknown escape: an
|
|
483
|
+
// alias, a helper argument, a property read. Not a closed world.
|
|
484
|
+
if (total === direct + 1)
|
|
485
|
+
return { variable };
|
|
486
|
+
}
|
|
487
|
+
return null;
|
|
488
|
+
}
|
|
489
|
+
/**
|
|
490
|
+
* The refusal for a foreground command that would run a leaking browser probe: it never returns,
|
|
491
|
+
* and the one move that makes it return is closing the browser (or letting the Playwright runner
|
|
492
|
+
* own it), never moving the leak to the background.
|
|
493
|
+
*
|
|
494
|
+
* @param probe - The leaked probe, from `foregroundBrowserProbe`.
|
|
495
|
+
* @param budgetSeconds - The tool's budget, which the probe would consume whole.
|
|
496
|
+
* @param piped - Whether the command pipes through `tail`/`head`, which withholds the probe's output.
|
|
497
|
+
* @returns The refusal sentence.
|
|
498
|
+
*/
|
|
499
|
+
export function browserProbeRefusal(probe, budgetSeconds, piped = false) {
|
|
500
|
+
return (`\`${probe.script}\` binds the launched browser to \`${probe.variable}\`. ` +
|
|
501
|
+
'This probe launches a browser and never closes it, so Node will stay alive after its last ' +
|
|
502
|
+
'statement. Put the page work in try { … } finally { await ' +
|
|
503
|
+
`${probe.variable}.close() }, or use a Playwright test whose browser fixture is cleaned up by ` +
|
|
504
|
+
'the runner. ' +
|
|
505
|
+
`Run in the foreground it would hold this tool for its whole ${budgetSeconds} s budget and ` +
|
|
506
|
+
'return nothing.' +
|
|
507
|
+
(piped
|
|
508
|
+
? ' Its `| tail` pipe would also withhold its output until it exits: run it without the pipe so its progress streams.'
|
|
509
|
+
: ''));
|
|
510
|
+
}
|
|
511
|
+
/**
|
|
512
|
+
* The over-budget advice for a leak discovered at cut time (the preflight could not read the script
|
|
513
|
+
* before the run — e.g. the same command wrote it). It names the lifecycle fix and says explicitly
|
|
514
|
+
* that the background is not one: a leaked handle hangs there too.
|
|
515
|
+
*
|
|
516
|
+
* @param probe - The leaked probe.
|
|
517
|
+
* @returns The sentence the cut result carries.
|
|
518
|
+
*/
|
|
519
|
+
export function browserProbeOverBudgetAdvice(probe) {
|
|
520
|
+
return (`\`${probe.script}\` binds the launched browser to \`${probe.variable}\`. ` +
|
|
521
|
+
'This probe launches a browser and never closes it, so Node will stay alive after its last ' +
|
|
522
|
+
'statement. It is not a bigger unit to split, and starting it in the background only moves the ' +
|
|
523
|
+
'hang. Put the page work in try { … } finally { await ' +
|
|
524
|
+
`${probe.variable}.close() }, or use a Playwright test whose browser fixture is cleaned up by ` +
|
|
525
|
+
'the runner.');
|
|
526
|
+
}
|
|
@@ -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;;;;;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;AAgSD;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAM3D;
|
|
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;AAgSD;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAM3D;AA6qBD;;;;;;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;AA4GD;;;;;;;;;;;;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
|
@@ -537,6 +537,23 @@ function commandStart(words) {
|
|
|
537
537
|
}
|
|
538
538
|
return { at, nested: null, timeoutSeconds, program: null, bound, envInsert, envReset };
|
|
539
539
|
}
|
|
540
|
+
/** Whether a shell metacharacter is escaped by an odd run of preceding backslashes. */
|
|
541
|
+
function escapedAt(command, at) {
|
|
542
|
+
let slashes = 0;
|
|
543
|
+
while (at > 0 && command[--at] === '\\')
|
|
544
|
+
slashes += 1;
|
|
545
|
+
return slashes % 2 === 1;
|
|
546
|
+
}
|
|
547
|
+
/** A hash opens a comment only at the start of a shell word (outside quotes/expansions). */
|
|
548
|
+
function commentStartsAt(command, at) {
|
|
549
|
+
if (command[at] !== '#')
|
|
550
|
+
return false;
|
|
551
|
+
let before = at - 1;
|
|
552
|
+
// Backslash-newline is removed before tokenization: `word\\\n#suffix` is one word.
|
|
553
|
+
while (command[before] === '\n' && escapedAt(command, before))
|
|
554
|
+
before -= 2;
|
|
555
|
+
return before < 0 || (/[ \t\n;&|()<>]/.test(command[before] ?? '') && !escapedAt(command, before));
|
|
556
|
+
}
|
|
540
557
|
/**
|
|
541
558
|
* A command with every heredoc BODY blanked to spaces, so its length and every offset in it are
|
|
542
559
|
* unchanged.
|
|
@@ -577,6 +594,10 @@ function blankHeredocBodies(command) {
|
|
|
577
594
|
let quote = null;
|
|
578
595
|
for (let i = lineStart; i < lineEnd; i++) {
|
|
579
596
|
const char = command[i] ?? '';
|
|
597
|
+
if (char === '\\' && quote !== "'") {
|
|
598
|
+
i += 1;
|
|
599
|
+
continue;
|
|
600
|
+
}
|
|
580
601
|
if (quote !== null) {
|
|
581
602
|
if (char === quote)
|
|
582
603
|
quote = null;
|
|
@@ -587,7 +608,7 @@ function blankHeredocBodies(command) {
|
|
|
587
608
|
continue;
|
|
588
609
|
}
|
|
589
610
|
// A `#` that starts a word opens a comment: the rest of the line is not shell words.
|
|
590
|
-
if (
|
|
611
|
+
if (commentStartsAt(command, i))
|
|
591
612
|
break;
|
|
592
613
|
if (char !== '<' || command[i + 1] !== '<')
|
|
593
614
|
continue;
|
|
@@ -718,6 +739,16 @@ function splitSegments(command) {
|
|
|
718
739
|
stack.pop();
|
|
719
740
|
continue;
|
|
720
741
|
}
|
|
742
|
+
// Comments are shell syntax only in a command context, including an unquoted `$(`.
|
|
743
|
+
// Blank their bytes so quotes/operators cannot affect this walk, while every later
|
|
744
|
+
// position and depth index stays aligned with the original command. Keep the newline.
|
|
745
|
+
if ((top === undefined || top === 'sub') && commentStartsAt(source, i)) {
|
|
746
|
+
const newline = source.indexOf('\n', i);
|
|
747
|
+
const end = newline < 0 ? source.length : newline;
|
|
748
|
+
put(' '.repeat(end - i), -1);
|
|
749
|
+
i = end - 1;
|
|
750
|
+
continue;
|
|
751
|
+
}
|
|
721
752
|
// A command substitution, wherever a shell opens one — inside double quotes too, and never
|
|
722
753
|
// inside single quotes (handled above). The `(` is consumed here so the depth is counted once.
|
|
723
754
|
if (char === '$' && next === '(') {
|
|
@@ -726,6 +757,12 @@ function splitSegments(command) {
|
|
|
726
757
|
i += 1;
|
|
727
758
|
continue;
|
|
728
759
|
}
|
|
760
|
+
if (char === '$' && next === '{') {
|
|
761
|
+
stack.push('parameter');
|
|
762
|
+
put('${', -1);
|
|
763
|
+
i += 1;
|
|
764
|
+
continue;
|
|
765
|
+
}
|
|
729
766
|
// Inside double quotes: a `"` closes them, a backslash escapes the next character (so
|
|
730
767
|
// `"\$("` opens nothing), and everything else is literal — a `'`, a `|`, a newline included.
|
|
731
768
|
if (top === 'double') {
|
|
@@ -749,6 +786,19 @@ function splitSegments(command) {
|
|
|
749
786
|
put(char, -1);
|
|
750
787
|
continue;
|
|
751
788
|
}
|
|
789
|
+
if (char === '\\' && next !== undefined) {
|
|
790
|
+
// An escaped character is literal, including inside an unquoted substitution.
|
|
791
|
+
put(char + next, -1);
|
|
792
|
+
i += 1;
|
|
793
|
+
continue;
|
|
794
|
+
}
|
|
795
|
+
// A parameter's `#` is expansion syntax or literal data, never a shell comment.
|
|
796
|
+
if (top === 'parameter') {
|
|
797
|
+
if (char === '}')
|
|
798
|
+
stack.pop();
|
|
799
|
+
put(char, -1);
|
|
800
|
+
continue;
|
|
801
|
+
}
|
|
752
802
|
// Inside a substitution or a subshell of one: its own parentheses nest, its `|`/`;`/`&&`
|
|
753
803
|
// belong to the nested command, and its newlines are that command's too.
|
|
754
804
|
if (top === 'sub') {
|
|
@@ -759,12 +809,6 @@ function splitSegments(command) {
|
|
|
759
809
|
put(char, -1);
|
|
760
810
|
continue;
|
|
761
811
|
}
|
|
762
|
-
if (char === '\\' && next !== undefined) {
|
|
763
|
-
// An escaped character is a literal (`\>` writes no file).
|
|
764
|
-
put(char + next, -1);
|
|
765
|
-
i += 1;
|
|
766
|
-
continue;
|
|
767
|
-
}
|
|
768
812
|
// Grouping belongs to the shell, not to a word: a statement written inside `( … )` or
|
|
769
813
|
// `{ … }` writes through that group, so the depth travels with every character a caller
|
|
770
814
|
// reading a redirect may ask about.
|
package/dist/cut-run.d.ts
CHANGED
|
@@ -167,6 +167,13 @@ export interface CutRun {
|
|
|
167
167
|
* is slow, so the sentence must not send it to the background (xr-molecule-flash-m200 +03:45).
|
|
168
168
|
*/
|
|
169
169
|
inlineScript?: string | null;
|
|
170
|
+
/**
|
|
171
|
+
* The variable a leaked standalone browser launch is bound to, when the command ran such a probe
|
|
172
|
+
* and the cut reader could only classify it after the run (xr-molecule-flash-m257). A leaked
|
|
173
|
+
* handle is not a slow workload: the background is the one place it must not be sent, so the
|
|
174
|
+
* sentence names the close instead.
|
|
175
|
+
*/
|
|
176
|
+
browserProbe?: string | null;
|
|
170
177
|
}
|
|
171
178
|
/**
|
|
172
179
|
* The run a command's result records as stopped by a budget, or null when the result is a run that
|
package/dist/cut-run.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cut-run.d.ts","sourceRoot":"","sources":["../src/cut-run.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAMH;;;GAGG;AACH,eAAO,MAAM,aAAa,kBAAkB,CAAA;AAE5C,kFAAkF;AAClF,MAAM,MAAM,eAAe,GAAG,KAAK,GAAG,OAAO,aAAa,CAAA;AAE1D;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,OAAO,GAAG,eAAe,CAGhE;AAiBD;;;;;;;;GAQG;AACH,MAAM,WAAW,YAAY;IAC3B;;;OAGG;IACH,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB;;;;;OAKG;IACH,SAAS,EAAE,MAAM,CAAA;IACjB,wEAAwE;IACxE,QAAQ,EAAE,OAAO,CAAA;CAClB;AAkDD;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CA4B9D;AAED;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEhD;AAKD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,MAAM,GAAG,IAAI,EACvB,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,OAAO,GACb,MAAM,GAAG,IAAI,CAIf;AAED,0BAA0B;AAC1B,MAAM,MAAM,KAAK;AACf,sEAAsE;AACpE,aAAa;AACf,oDAAoD;GAClD,iBAAiB;AACnB;;;;GAIG;GACD,cAAc,CAAA;AAElB,wDAAwD;AACxD,MAAM,WAAW,MAAM;IACrB,iDAAiD;IACjD,OAAO,EAAE,MAAM,CAAA;IACf,kFAAkF;IAClF,SAAS,EAAE,KAAK,CAAA;IAChB;;;;;;OAMG;IACH,UAAU,EAAE,OAAO,CAAA;IACnB;;;OAGG;IACH,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB;;;OAGG;IACH,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;IAClB;;;OAGG;IACH,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,qCAAqC;IACrC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAA;IACvB,yEAAyE;IACzE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAC7B;AAsDD;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,OAAO,EACf,SAAS,EAAE,MAAM,EACjB,aAAa,SAAI,EACjB,SAAS,CAAC,EAAE,MAAM,GACjB,MAAM,GAAG,IAAI,CA4Ff;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"cut-run.d.ts","sourceRoot":"","sources":["../src/cut-run.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAMH;;;GAGG;AACH,eAAO,MAAM,aAAa,kBAAkB,CAAA;AAE5C,kFAAkF;AAClF,MAAM,MAAM,eAAe,GAAG,KAAK,GAAG,OAAO,aAAa,CAAA;AAE1D;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,OAAO,GAAG,eAAe,CAGhE;AAiBD;;;;;;;;GAQG;AACH,MAAM,WAAW,YAAY;IAC3B;;;OAGG;IACH,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB;;;;;OAKG;IACH,SAAS,EAAE,MAAM,CAAA;IACjB,wEAAwE;IACxE,QAAQ,EAAE,OAAO,CAAA;CAClB;AAkDD;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CA4B9D;AAED;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEhD;AAKD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,MAAM,GAAG,IAAI,EACvB,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,OAAO,GACb,MAAM,GAAG,IAAI,CAIf;AAED,0BAA0B;AAC1B,MAAM,MAAM,KAAK;AACf,sEAAsE;AACpE,aAAa;AACf,oDAAoD;GAClD,iBAAiB;AACnB;;;;GAIG;GACD,cAAc,CAAA;AAElB,wDAAwD;AACxD,MAAM,WAAW,MAAM;IACrB,iDAAiD;IACjD,OAAO,EAAE,MAAM,CAAA;IACf,kFAAkF;IAClF,SAAS,EAAE,KAAK,CAAA;IAChB;;;;;;OAMG;IACH,UAAU,EAAE,OAAO,CAAA;IACnB;;;OAGG;IACH,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB;;;OAGG;IACH,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;IAClB;;;OAGG;IACH,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,qCAAqC;IACrC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAA;IACvB,yEAAyE;IACzE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC5B;;;;;OAKG;IACH,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAC7B;AAsDD;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,OAAO,EACf,SAAS,EAAE,MAAM,EACjB,aAAa,SAAI,EACjB,SAAS,CAAC,EAAE,MAAM,GACjB,MAAM,GAAG,IAAI,CA4Ff;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAmB1D;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAyClD"}
|