playwright-repl 0.2.1 → 0.7.10
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/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -0
- package/dist/playwright-repl.d.ts +14 -0
- package/dist/playwright-repl.d.ts.map +1 -0
- package/dist/playwright-repl.js +107 -0
- package/dist/playwright-repl.js.map +1 -0
- package/dist/recorder.d.ts +95 -0
- package/dist/recorder.d.ts.map +1 -0
- package/dist/recorder.js +207 -0
- package/dist/recorder.js.map +1 -0
- package/dist/repl.d.ts +48 -0
- package/dist/repl.d.ts.map +1 -0
- package/dist/repl.js +686 -0
- package/dist/repl.js.map +1 -0
- package/package.json +10 -16
- package/CHANGELOG.md +0 -129
- package/LICENSE +0 -21
- package/README.md +0 -418
- package/bin/daemon-launcher.cjs +0 -13
- package/bin/mcp-server.cjs +0 -32
- package/bin/playwright-repl.mjs +0 -79
- package/examples/01-add-todos.pw +0 -14
- package/examples/02-complete-and-filter.pw +0 -21
- package/examples/03-record-session.pw +0 -13
- package/examples/04-replay-session.pw +0 -17
- package/examples/05-ci-pipe.pw +0 -14
- package/examples/06-edit-todo.pw +0 -11
- package/examples/ghost-completion-demo.mjs +0 -122
- package/src/colors.mjs +0 -17
- package/src/completion-data.mjs +0 -50
- package/src/connection.mjs +0 -119
- package/src/index.mjs +0 -16
- package/src/parser.mjs +0 -141
- package/src/recorder.mjs +0 -241
- package/src/repl.mjs +0 -678
- package/src/resolve.mjs +0 -82
- package/src/workspace.mjs +0 -104
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Ghost completion demo — standalone test for inline suggestions.
|
|
4
|
-
*
|
|
5
|
-
* Run: node examples/ghost-completion-demo.mjs
|
|
6
|
-
*
|
|
7
|
-
* Type a prefix (e.g. "go") and see dimmed suggestion text.
|
|
8
|
-
* Tab cycles through matches, Right Arrow accepts.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import readline from 'node:readline';
|
|
12
|
-
|
|
13
|
-
const COMMANDS = [
|
|
14
|
-
'click', 'check', 'close', 'console', 'cookie-get', 'cookie-list',
|
|
15
|
-
'dblclick', 'drag', 'eval', 'fill', 'goto', 'go-back', 'go-forward',
|
|
16
|
-
'hover', 'network', 'open', 'press', 'reload', 'screenshot', 'select',
|
|
17
|
-
'snapshot', 'type', 'uncheck', 'upload',
|
|
18
|
-
'.help', '.aliases', '.status', '.exit',
|
|
19
|
-
];
|
|
20
|
-
|
|
21
|
-
const rl = readline.createInterface({
|
|
22
|
-
input: process.stdin,
|
|
23
|
-
output: process.stdout,
|
|
24
|
-
prompt: '\x1b[36mpw>\x1b[0m ',
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
// ─── Ghost completion via _ttyWrite ─────────────────────────────────────────
|
|
28
|
-
|
|
29
|
-
let ghost = '';
|
|
30
|
-
let matches = [];
|
|
31
|
-
let matchIdx = 0;
|
|
32
|
-
|
|
33
|
-
function getMatches(input) {
|
|
34
|
-
if (input.length > 0 && !input.includes(' ')) {
|
|
35
|
-
return COMMANDS.filter(cmd => cmd.startsWith(input) && cmd !== input);
|
|
36
|
-
}
|
|
37
|
-
return [];
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function renderGhost(suffix) {
|
|
41
|
-
ghost = suffix;
|
|
42
|
-
rl.output.write(`\x1b[2m${ghost}\x1b[0m\x1b[${ghost.length}D`);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const origTtyWrite = rl._ttyWrite.bind(rl);
|
|
46
|
-
rl._ttyWrite = function (s, key) {
|
|
47
|
-
if (ghost && key) {
|
|
48
|
-
// Right-arrow-at-end accepts ghost suggestion
|
|
49
|
-
if (key.name === 'right' && rl.cursor === rl.line.length) {
|
|
50
|
-
const text = ghost;
|
|
51
|
-
rl.output.write('\x1b[K');
|
|
52
|
-
ghost = '';
|
|
53
|
-
matches = [];
|
|
54
|
-
rl._insertString(text);
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// Tab cycles through matches (or accepts if only one)
|
|
59
|
-
if (key.name === 'tab') {
|
|
60
|
-
if (matches.length > 1) {
|
|
61
|
-
rl.output.write('\x1b[K');
|
|
62
|
-
matchIdx = (matchIdx + 1) % matches.length;
|
|
63
|
-
const input = rl.line || '';
|
|
64
|
-
renderGhost(matches[matchIdx].slice(input.length));
|
|
65
|
-
return;
|
|
66
|
-
}
|
|
67
|
-
// Single match — accept it
|
|
68
|
-
const text = ghost;
|
|
69
|
-
rl.output.write('\x1b[K');
|
|
70
|
-
ghost = '';
|
|
71
|
-
matches = [];
|
|
72
|
-
rl._insertString(text);
|
|
73
|
-
return;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// Tab on empty input — show all commands as ghost suggestions
|
|
78
|
-
if (key && key.name === 'tab') {
|
|
79
|
-
if ((rl.line || '') === '') {
|
|
80
|
-
matches = COMMANDS;
|
|
81
|
-
matchIdx = 0;
|
|
82
|
-
renderGhost(matches[0]);
|
|
83
|
-
}
|
|
84
|
-
return;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// Clear existing ghost text before readline processes the key
|
|
88
|
-
if (ghost) {
|
|
89
|
-
rl.output.write('\x1b[K');
|
|
90
|
-
ghost = '';
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// Let readline handle the key normally
|
|
94
|
-
origTtyWrite(s, key);
|
|
95
|
-
|
|
96
|
-
// Render new ghost text if cursor is at end of line
|
|
97
|
-
const input = rl.line || '';
|
|
98
|
-
matches = getMatches(input);
|
|
99
|
-
matchIdx = 0;
|
|
100
|
-
if (matches.length > 0 && rl.cursor === rl.line.length) {
|
|
101
|
-
renderGhost(matches[0].slice(input.length));
|
|
102
|
-
}
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
// ─── REPL loop ──────────────────────────────────────────────────────────────
|
|
106
|
-
|
|
107
|
-
console.log('Ghost completion demo — Tab cycles matches, Right Arrow accepts\n');
|
|
108
|
-
rl.prompt();
|
|
109
|
-
|
|
110
|
-
rl.on('line', (line) => {
|
|
111
|
-
if (line.trim() === '.exit') {
|
|
112
|
-
rl.close();
|
|
113
|
-
return;
|
|
114
|
-
}
|
|
115
|
-
console.log(` → ${line.trim() || '(empty)'}`);
|
|
116
|
-
rl.prompt();
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
rl.on('close', () => {
|
|
120
|
-
console.log('\nBye!');
|
|
121
|
-
process.exit(0);
|
|
122
|
-
});
|
package/src/colors.mjs
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Terminal color helpers.
|
|
3
|
-
* No dependency — just ANSI escape codes.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
export const c = {
|
|
7
|
-
reset: '\x1b[0m',
|
|
8
|
-
bold: '\x1b[1m',
|
|
9
|
-
dim: '\x1b[2m',
|
|
10
|
-
red: '\x1b[31m',
|
|
11
|
-
green: '\x1b[32m',
|
|
12
|
-
yellow: '\x1b[33m',
|
|
13
|
-
blue: '\x1b[34m',
|
|
14
|
-
magenta: '\x1b[35m',
|
|
15
|
-
cyan: '\x1b[36m',
|
|
16
|
-
gray: '\x1b[90m',
|
|
17
|
-
};
|
package/src/completion-data.mjs
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Completion data — builds the list of items for dropdown autocomplete.
|
|
3
|
-
*
|
|
4
|
-
* Sources: COMMANDS from resolve.mjs, ALIASES from parser.mjs,
|
|
5
|
-
* plus REPL meta-commands (.help, .exit, etc.).
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { COMMANDS } from './resolve.mjs';
|
|
9
|
-
import { ALIASES } from './parser.mjs';
|
|
10
|
-
|
|
11
|
-
// ─── Meta-commands ───────────────────────────────────────────────────────────
|
|
12
|
-
|
|
13
|
-
const META_COMMANDS = [
|
|
14
|
-
{ cmd: '.help', desc: 'Show available commands' },
|
|
15
|
-
{ cmd: '.aliases', desc: 'Show command aliases' },
|
|
16
|
-
{ cmd: '.status', desc: 'Show connection status' },
|
|
17
|
-
{ cmd: '.reconnect', desc: 'Reconnect to daemon' },
|
|
18
|
-
{ cmd: '.record', desc: 'Start recording commands' },
|
|
19
|
-
{ cmd: '.save', desc: 'Stop recording and save' },
|
|
20
|
-
{ cmd: '.pause', desc: 'Pause/resume recording' },
|
|
21
|
-
{ cmd: '.discard', desc: 'Discard current recording' },
|
|
22
|
-
{ cmd: '.replay', desc: 'Replay a recorded session' },
|
|
23
|
-
{ cmd: '.exit', desc: 'Exit REPL' },
|
|
24
|
-
];
|
|
25
|
-
|
|
26
|
-
// ─── Build completion items ──────────────────────────────────────────────────
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Returns a sorted array of `{ cmd, desc }` for all completable items:
|
|
30
|
-
* commands, aliases (with "→ target" description), and meta-commands.
|
|
31
|
-
*/
|
|
32
|
-
export function buildCompletionItems() {
|
|
33
|
-
const items = [];
|
|
34
|
-
|
|
35
|
-
// Primary commands
|
|
36
|
-
for (const [name, info] of Object.entries(COMMANDS)) {
|
|
37
|
-
items.push({ cmd: name, desc: info.desc });
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// Aliases — show "→ target" as description
|
|
41
|
-
for (const [alias, target] of Object.entries(ALIASES)) {
|
|
42
|
-
items.push({ cmd: alias, desc: `→ ${target}` });
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// Meta-commands
|
|
46
|
-
items.push(...META_COMMANDS);
|
|
47
|
-
|
|
48
|
-
items.sort((a, b) => a.cmd.localeCompare(b.cmd));
|
|
49
|
-
return items;
|
|
50
|
-
}
|
package/src/connection.mjs
DELETED
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* DaemonConnection — persistent Unix socket client.
|
|
3
|
-
*
|
|
4
|
-
* Wire protocol: newline-delimited JSON.
|
|
5
|
-
*
|
|
6
|
-
* Send: {"id":1,"method":"run","params":{"args":{...},"cwd":"/"},"version":"0.1.0"}\n
|
|
7
|
-
* Receive: {"id":1,"result":{"text":"..."},"version":"0.1.0"}\n
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import net from 'node:net';
|
|
11
|
-
|
|
12
|
-
export class DaemonConnection {
|
|
13
|
-
constructor(sockPath, version) {
|
|
14
|
-
this.sockPath = sockPath;
|
|
15
|
-
this.version = version;
|
|
16
|
-
this.socket = null;
|
|
17
|
-
this.nextId = 1;
|
|
18
|
-
this.callbacks = new Map();
|
|
19
|
-
this.pendingBuffers = [];
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
async connect() {
|
|
23
|
-
return new Promise((resolve, reject) => {
|
|
24
|
-
const sock = net.createConnection(this.sockPath, () => {
|
|
25
|
-
this.socket = sock;
|
|
26
|
-
resolve(true);
|
|
27
|
-
});
|
|
28
|
-
sock.on('data', (buf) => this._onData(buf));
|
|
29
|
-
sock.on('error', (err) => {
|
|
30
|
-
if (!this.socket) reject(err);
|
|
31
|
-
else this._handleError(err);
|
|
32
|
-
});
|
|
33
|
-
sock.on('close', () => {
|
|
34
|
-
this.socket = null;
|
|
35
|
-
for (const cb of this.callbacks.values()) {
|
|
36
|
-
cb.reject(new Error('Connection closed'));
|
|
37
|
-
}
|
|
38
|
-
this.callbacks.clear();
|
|
39
|
-
});
|
|
40
|
-
});
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
get connected() {
|
|
44
|
-
return this.socket !== null && !this.socket.destroyed;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Send a raw message to the daemon.
|
|
49
|
-
* Returns the result from the daemon response.
|
|
50
|
-
*/
|
|
51
|
-
async send(method, params = {}) {
|
|
52
|
-
if (!this.connected) throw new Error('Not connected to daemon');
|
|
53
|
-
const id = this.nextId++;
|
|
54
|
-
const msg = { id, method, params, version: this.version };
|
|
55
|
-
return new Promise((resolve, reject) => {
|
|
56
|
-
this.callbacks.set(id, { resolve, reject });
|
|
57
|
-
this.socket.write(JSON.stringify(msg) + '\n', (err) => {
|
|
58
|
-
if (err) {
|
|
59
|
-
this.callbacks.delete(id);
|
|
60
|
-
reject(err);
|
|
61
|
-
}
|
|
62
|
-
});
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Send a "run" command — the standard way to execute CLI commands.
|
|
68
|
-
* `minimistArgs` is a pre-parsed minimist object, e.g. { _: ["click", "e5"] }
|
|
69
|
-
*/
|
|
70
|
-
async run(minimistArgs) {
|
|
71
|
-
return this.send('run', { args: minimistArgs, cwd: process.cwd() });
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
close() {
|
|
75
|
-
if (this.socket) {
|
|
76
|
-
this.socket.destroy();
|
|
77
|
-
this.socket = null;
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// ─── Internal: newline-delimited JSON parsing ────────────────────────
|
|
82
|
-
|
|
83
|
-
_onData(buffer) {
|
|
84
|
-
let end = buffer.indexOf('\n');
|
|
85
|
-
if (end === -1) {
|
|
86
|
-
this.pendingBuffers.push(buffer);
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
|
-
this.pendingBuffers.push(buffer.slice(0, end));
|
|
90
|
-
this._dispatch(Buffer.concat(this.pendingBuffers).toString());
|
|
91
|
-
let start = end + 1;
|
|
92
|
-
end = buffer.indexOf('\n', start);
|
|
93
|
-
while (end !== -1) {
|
|
94
|
-
this._dispatch(buffer.toString(undefined, start, end));
|
|
95
|
-
start = end + 1;
|
|
96
|
-
end = buffer.indexOf('\n', start);
|
|
97
|
-
}
|
|
98
|
-
this.pendingBuffers = [buffer.slice(start)];
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
_dispatch(message) {
|
|
102
|
-
try {
|
|
103
|
-
const obj = JSON.parse(message);
|
|
104
|
-
if (obj.id && this.callbacks.has(obj.id)) {
|
|
105
|
-
const cb = this.callbacks.get(obj.id);
|
|
106
|
-
this.callbacks.delete(obj.id);
|
|
107
|
-
if (obj.error) cb.reject(new Error(obj.error));
|
|
108
|
-
else cb.resolve(obj.result);
|
|
109
|
-
}
|
|
110
|
-
} catch {
|
|
111
|
-
// Ignore parse errors on partial messages
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
_handleError(err) {
|
|
116
|
-
if (err.code !== 'EPIPE')
|
|
117
|
-
console.error(`\n⚠️ Socket error: ${err.message}`);
|
|
118
|
-
}
|
|
119
|
-
}
|
package/src/index.mjs
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* playwright-repl — public API
|
|
3
|
-
*
|
|
4
|
-
* Usage as CLI:
|
|
5
|
-
* npx playwright-repl [options]
|
|
6
|
-
*
|
|
7
|
-
* Usage as library:
|
|
8
|
-
* import { DaemonConnection, parseInput, SessionRecorder } from 'playwright-repl';
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
export { DaemonConnection } from './connection.mjs';
|
|
12
|
-
export { parseInput, ALIASES, ALL_COMMANDS } from './parser.mjs';
|
|
13
|
-
export { SessionRecorder, SessionPlayer } from './recorder.mjs';
|
|
14
|
-
export { socketPath, isDaemonRunning, startDaemon, findWorkspaceDir } from './workspace.mjs';
|
|
15
|
-
export { startRepl } from './repl.mjs';
|
|
16
|
-
export { buildCompletionItems } from './completion-data.mjs';
|
package/src/parser.mjs
DELETED
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Input parser — transforms human input into minimist-style args.
|
|
3
|
-
*
|
|
4
|
-
* Flow: "c e5" → alias resolve → ["click", "e5"] → minimist → { _: ["click", "e5"] }
|
|
5
|
-
*
|
|
6
|
-
* The resulting object is sent to the daemon as-is. The daemon runs
|
|
7
|
-
* parseCliCommand() which maps it to a tool call.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { minimist, COMMANDS } from './resolve.mjs';
|
|
11
|
-
|
|
12
|
-
// ─── Command aliases ─────────────────────────────────────────────────────────
|
|
13
|
-
|
|
14
|
-
export const ALIASES = {
|
|
15
|
-
// Navigation
|
|
16
|
-
'o': 'open',
|
|
17
|
-
'g': 'goto',
|
|
18
|
-
'go': 'goto',
|
|
19
|
-
'back': 'go-back',
|
|
20
|
-
'fwd': 'go-forward',
|
|
21
|
-
'r': 'reload',
|
|
22
|
-
|
|
23
|
-
// Interaction
|
|
24
|
-
'c': 'click',
|
|
25
|
-
'dc': 'dblclick',
|
|
26
|
-
't': 'type',
|
|
27
|
-
'f': 'fill',
|
|
28
|
-
'h': 'hover',
|
|
29
|
-
'p': 'press',
|
|
30
|
-
'sel': 'select',
|
|
31
|
-
'chk': 'check',
|
|
32
|
-
'unchk':'uncheck',
|
|
33
|
-
|
|
34
|
-
// Inspection
|
|
35
|
-
's': 'snapshot',
|
|
36
|
-
'snap': 'snapshot',
|
|
37
|
-
'ss': 'screenshot',
|
|
38
|
-
'e': 'eval',
|
|
39
|
-
'con': 'console',
|
|
40
|
-
'net': 'network',
|
|
41
|
-
|
|
42
|
-
// Tabs
|
|
43
|
-
'tl': 'tab-list',
|
|
44
|
-
'tn': 'tab-new',
|
|
45
|
-
'tc': 'tab-close',
|
|
46
|
-
'ts': 'tab-select',
|
|
47
|
-
|
|
48
|
-
// Assertions (Phase 2 — mapped to daemon tools that exist but have no CLI keywords)
|
|
49
|
-
'vt': 'verify-text',
|
|
50
|
-
've': 'verify-element',
|
|
51
|
-
'vv': 'verify-value',
|
|
52
|
-
'vl': 'verify-list',
|
|
53
|
-
|
|
54
|
-
// Session
|
|
55
|
-
'q': 'close',
|
|
56
|
-
'ls': 'list',
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
// ─── Known boolean options ───────────────────────────────────────────────────
|
|
60
|
-
|
|
61
|
-
export const booleanOptions = new Set([
|
|
62
|
-
'headed', 'persistent', 'extension', 'submit', 'clear',
|
|
63
|
-
'fullPage', 'includeStatic',
|
|
64
|
-
]);
|
|
65
|
-
|
|
66
|
-
// ─── All known commands ──────────────────────────────────────────────────────
|
|
67
|
-
|
|
68
|
-
export const ALL_COMMANDS = Object.keys(COMMANDS);
|
|
69
|
-
|
|
70
|
-
// ─── Tokenizer ───────────────────────────────────────────────────────────────
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Tokenize input respecting quoted strings.
|
|
74
|
-
* "fill e7 'hello world'" → ["fill", "e7", "hello world"]
|
|
75
|
-
*/
|
|
76
|
-
function tokenize(line) {
|
|
77
|
-
const tokens = [];
|
|
78
|
-
let current = '';
|
|
79
|
-
let inQuote = null;
|
|
80
|
-
|
|
81
|
-
for (let i = 0; i < line.length; i++) {
|
|
82
|
-
const ch = line[i];
|
|
83
|
-
if (inQuote) {
|
|
84
|
-
if (ch === inQuote) {
|
|
85
|
-
inQuote = null;
|
|
86
|
-
} else {
|
|
87
|
-
current += ch;
|
|
88
|
-
}
|
|
89
|
-
} else if (ch === '"' || ch === "'") {
|
|
90
|
-
inQuote = ch;
|
|
91
|
-
} else if (ch === ' ' || ch === '\t') {
|
|
92
|
-
if (current) {
|
|
93
|
-
tokens.push(current);
|
|
94
|
-
current = '';
|
|
95
|
-
}
|
|
96
|
-
} else {
|
|
97
|
-
current += ch;
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
if (current) tokens.push(current);
|
|
101
|
-
return tokens;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// ─── Main parse function ─────────────────────────────────────────────────────
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* Parse a REPL input line into a minimist args object ready for the daemon.
|
|
108
|
-
* Returns null if the line is empty.
|
|
109
|
-
*/
|
|
110
|
-
export function parseInput(line) {
|
|
111
|
-
const tokens = tokenize(line);
|
|
112
|
-
if (tokens.length === 0) return null;
|
|
113
|
-
|
|
114
|
-
// Resolve alias
|
|
115
|
-
const cmd = tokens[0].toLowerCase();
|
|
116
|
-
if (ALIASES[cmd]) tokens[0] = ALIASES[cmd];
|
|
117
|
-
|
|
118
|
-
// Parse with minimist (same lib and boolean set as playwright-cli)
|
|
119
|
-
const args = minimist(tokens, { boolean: [...booleanOptions] });
|
|
120
|
-
|
|
121
|
-
// Stringify non-boolean values (playwright-cli does this)
|
|
122
|
-
for (const key of Object.keys(args)) {
|
|
123
|
-
if (key === '_') continue;
|
|
124
|
-
if (typeof args[key] !== 'boolean')
|
|
125
|
-
args[key] = String(args[key]);
|
|
126
|
-
}
|
|
127
|
-
for (let i = 0; i < args._.length; i++)
|
|
128
|
-
args._[i] = String(args._[i]);
|
|
129
|
-
|
|
130
|
-
// Remove boolean options set to false that weren't explicitly passed.
|
|
131
|
-
// minimist sets all declared booleans to false by default, but the
|
|
132
|
-
// daemon rejects unknown options like --headed false.
|
|
133
|
-
for (const opt of booleanOptions) {
|
|
134
|
-
if (args[opt] === false) {
|
|
135
|
-
const hasExplicitNo = tokens.some(t => t === `--no-${opt}`);
|
|
136
|
-
if (!hasExplicitNo) delete args[opt];
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
return args;
|
|
141
|
-
}
|