bitwrench 2.0.17 → 2.0.18
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 +127 -38
- package/dist/bitwrench-bccl.cjs.js +8 -8
- package/dist/bitwrench-bccl.cjs.min.js +3 -3
- package/dist/bitwrench-bccl.esm.js +8 -8
- package/dist/bitwrench-bccl.esm.min.js +3 -3
- package/dist/bitwrench-bccl.umd.js +8 -8
- package/dist/bitwrench-bccl.umd.min.js +2 -2
- package/dist/bitwrench-code-edit.cjs.js +1 -1
- package/dist/bitwrench-code-edit.cjs.min.js +1 -1
- package/dist/bitwrench-code-edit.es5.js +1 -1
- package/dist/bitwrench-code-edit.es5.min.js +1 -1
- package/dist/bitwrench-code-edit.esm.js +1 -1
- package/dist/bitwrench-code-edit.esm.min.js +1 -1
- package/dist/bitwrench-code-edit.umd.js +1 -1
- package/dist/bitwrench-code-edit.umd.min.js +1 -1
- package/dist/bitwrench-lean.cjs.js +941 -775
- package/dist/bitwrench-lean.cjs.min.js +20 -20
- package/dist/bitwrench-lean.es5.js +1012 -961
- package/dist/bitwrench-lean.es5.min.js +18 -18
- package/dist/bitwrench-lean.esm.js +941 -775
- package/dist/bitwrench-lean.esm.min.js +20 -20
- package/dist/bitwrench-lean.umd.js +941 -775
- package/dist/bitwrench-lean.umd.min.js +20 -20
- package/dist/bitwrench-util-css.cjs.js +236 -0
- package/dist/bitwrench-util-css.cjs.min.js +22 -0
- package/dist/bitwrench-util-css.es5.js +414 -0
- package/dist/bitwrench-util-css.es5.min.js +21 -0
- package/dist/bitwrench-util-css.esm.js +230 -0
- package/dist/bitwrench-util-css.esm.min.js +21 -0
- package/dist/bitwrench-util-css.umd.js +242 -0
- package/dist/bitwrench-util-css.umd.min.js +21 -0
- package/dist/bitwrench.cjs.js +948 -782
- package/dist/bitwrench.cjs.min.js +21 -21
- package/dist/bitwrench.css +456 -132
- package/dist/bitwrench.es5.js +1024 -970
- package/dist/bitwrench.es5.min.js +19 -19
- package/dist/bitwrench.esm.js +949 -783
- package/dist/bitwrench.esm.min.js +21 -21
- package/dist/bitwrench.min.css +1 -1
- package/dist/bitwrench.umd.js +948 -782
- package/dist/bitwrench.umd.min.js +21 -21
- package/dist/builds.json +178 -90
- package/dist/bwserve.cjs.js +514 -68
- package/dist/bwserve.esm.js +513 -69
- package/dist/sri.json +44 -36
- package/package.json +3 -2
- package/readme.html +136 -49
- package/src/bitwrench-bccl.js +7 -7
- package/src/bitwrench-color-utils.js +31 -9
- package/src/bitwrench-esm-entry.js +11 -0
- package/src/bitwrench-styles.js +439 -232
- package/src/bitwrench-util-css.js +229 -0
- package/src/bitwrench.js +483 -485
- package/src/bwserve/attach.js +57 -0
- package/src/bwserve/bwclient.js +141 -0
- package/src/bwserve/bwshell.js +102 -0
- package/src/bwserve/client.js +151 -1
- package/src/bwserve/index.js +127 -28
- package/src/cli/attach.js +555 -0
- package/src/cli/convert.js +2 -5
- package/src/cli/index.js +7 -0
- package/src/cli/inject.js +1 -1
- package/src/cli/serve.js +6 -2
- package/src/generate-css.js +11 -4
- package/src/vendor/html2canvas.min.js +20 -0
- package/src/version.js +3 -3
- package/src/bwserve/shell.js +0 -106
|
@@ -0,0 +1,555 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* bwcli attach — Interactive remote debugging for bitwrench pages.
|
|
3
|
+
*
|
|
4
|
+
* Starts a bwserve instance and waits for a browser page to connect
|
|
5
|
+
* via the drop-in attach script. Once connected, provides a REPL
|
|
6
|
+
* for evaluating JS, inspecting the DOM tree, taking screenshots,
|
|
7
|
+
* and listening to events in real time.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* bwcli attach [options]
|
|
11
|
+
*
|
|
12
|
+
* This is bitwrench's answer to Playwright/Chrome DevTools — a
|
|
13
|
+
* built-in inspector that speaks the bwserve protocol.
|
|
14
|
+
*
|
|
15
|
+
* @module cli/attach
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { parseArgs } from 'node:util';
|
|
19
|
+
import { createInterface } from 'node:readline';
|
|
20
|
+
import { writeFileSync } from 'node:fs';
|
|
21
|
+
import { VERSION } from '../version.js';
|
|
22
|
+
|
|
23
|
+
var ATTACH_USAGE = `
|
|
24
|
+
bwcli attach v${VERSION} — Remote debugging REPL for bitwrench pages
|
|
25
|
+
|
|
26
|
+
Usage:
|
|
27
|
+
bwcli attach [options]
|
|
28
|
+
|
|
29
|
+
Description:
|
|
30
|
+
Starts a bwserve instance and waits for a browser to connect via the
|
|
31
|
+
drop-in attach script. Once connected, you get an interactive REPL
|
|
32
|
+
where you can evaluate JS expressions, inspect the DOM, take
|
|
33
|
+
screenshots, and listen to events — all from your terminal.
|
|
34
|
+
|
|
35
|
+
To connect a page, either:
|
|
36
|
+
1. Add <script src="http://localhost:<port>/bw/attach.js"></script>
|
|
37
|
+
2. Paste the script URL into devtools console:
|
|
38
|
+
var s=document.createElement('script');s.src='http://localhost:<port>/bw/attach.js';document.head.appendChild(s);
|
|
39
|
+
|
|
40
|
+
Options:
|
|
41
|
+
-p, --port <number> Server port (default: 7902)
|
|
42
|
+
--allow-screenshot Enable /screenshot command (loads html2canvas in browser)
|
|
43
|
+
-v, --verbose Verbose output (show protocol details)
|
|
44
|
+
-h, --help Print this help
|
|
45
|
+
|
|
46
|
+
REPL Commands:
|
|
47
|
+
<expression> Evaluate JS in the connected browser (e.g., document.title)
|
|
48
|
+
/help Show command reference
|
|
49
|
+
/quit, /q Exit
|
|
50
|
+
/tree [selector] [depth] Show DOM tree summary (default: body, depth 3)
|
|
51
|
+
/screenshot [sel] [file] Capture screenshot (requires --allow-screenshot)
|
|
52
|
+
/mount <sel> <comp> [json] Mount a BCCL component on the client
|
|
53
|
+
/render <sel> <taco-json> Render a TACO object at selector
|
|
54
|
+
/patch <id> <content> Patch an element's text content
|
|
55
|
+
/listen <sel> <event> Start listening for DOM events (e.g., /listen button click)
|
|
56
|
+
/unlisten <sel> <event> Stop listening for a previously added listener
|
|
57
|
+
/clients List connected clients
|
|
58
|
+
/exec <code> Execute JS without capturing return value
|
|
59
|
+
|
|
60
|
+
Examples:
|
|
61
|
+
bwcli attach Start on default port 7902
|
|
62
|
+
bwcli attach --port 3000 Use custom port
|
|
63
|
+
bwcli attach --allow-screenshot Enable screenshot support
|
|
64
|
+
bwcli attach -v Verbose mode (shows protocol)
|
|
65
|
+
|
|
66
|
+
# In the REPL:
|
|
67
|
+
bw> document.title
|
|
68
|
+
bw> bw.$('.bw-card').length
|
|
69
|
+
bw> /tree #app 2
|
|
70
|
+
bw> /screenshot body page.png
|
|
71
|
+
bw> /listen .bw-btn click
|
|
72
|
+
bw> /mount #app card {"title":"Hello","content":"World"}
|
|
73
|
+
`.trim();
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Wrap a JS expression for client.query().
|
|
77
|
+
* Statements (var, let, const, if, for, etc.) are sent as-is.
|
|
78
|
+
* Expressions are wrapped in return() so the result comes back.
|
|
79
|
+
*
|
|
80
|
+
* @param {string} code - User input
|
|
81
|
+
* @returns {string} Code suitable for new Function()
|
|
82
|
+
*/
|
|
83
|
+
export function wrapExpression(code) {
|
|
84
|
+
code = code.trim();
|
|
85
|
+
if (/^(var |let |const |if |for |while |function |try |switch |throw |class |\{)/.test(code)) {
|
|
86
|
+
return code;
|
|
87
|
+
}
|
|
88
|
+
return 'return (' + code + ')';
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Run the attach subcommand.
|
|
93
|
+
* @param {string[]} argv - arguments after "attach"
|
|
94
|
+
*/
|
|
95
|
+
export function runAttach(argv) {
|
|
96
|
+
var values;
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
var result = parseArgs({
|
|
100
|
+
args: argv,
|
|
101
|
+
strict: true,
|
|
102
|
+
allowPositionals: false,
|
|
103
|
+
options: {
|
|
104
|
+
port: { type: 'string', short: 'p' },
|
|
105
|
+
'allow-screenshot': { type: 'boolean' },
|
|
106
|
+
verbose: { type: 'boolean', short: 'v' },
|
|
107
|
+
help: { type: 'boolean', short: 'h' }
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
values = result.values;
|
|
111
|
+
} catch (err) {
|
|
112
|
+
console.error('Error: ' + err.message);
|
|
113
|
+
console.error('Run "bwcli attach --help" for usage.');
|
|
114
|
+
process.exit(1);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (values.help) {
|
|
118
|
+
console.log(ATTACH_USAGE);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
var port = values.port ? parseInt(values.port, 10) : 7902;
|
|
123
|
+
var allowScreenshot = !!values['allow-screenshot'];
|
|
124
|
+
var verbose = !!values.verbose;
|
|
125
|
+
|
|
126
|
+
if (isNaN(port) || port < 1 || port > 65535) {
|
|
127
|
+
console.error('Error: --port must be a number between 1 and 65535.');
|
|
128
|
+
process.exit(1);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Dynamic import of bwserve
|
|
132
|
+
import('../bwserve/index.js').then(function(bwserve) {
|
|
133
|
+
startAttach(bwserve, { port, allowScreenshot, verbose });
|
|
134
|
+
}).catch(function(err) {
|
|
135
|
+
console.error('Failed to load bwserve: ' + err.message);
|
|
136
|
+
process.exit(1);
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Start the attach server and REPL.
|
|
142
|
+
* @private
|
|
143
|
+
*/
|
|
144
|
+
function startAttach(bwserve, opts) {
|
|
145
|
+
var app = bwserve.create({
|
|
146
|
+
port: opts.port,
|
|
147
|
+
title: 'bwcli attach',
|
|
148
|
+
allowExec: true,
|
|
149
|
+
allowScreenshot: opts.allowScreenshot
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// Track the active client (most recent connection)
|
|
153
|
+
var activeClient = null;
|
|
154
|
+
var clients = new Map();
|
|
155
|
+
|
|
156
|
+
// Register a catch-all page handler for attach connections.
|
|
157
|
+
// Attach clients don't hit a registered page — they connect via SSE directly.
|
|
158
|
+
// We need to handle SSE connections from attach clients by intercepting _handleSSE.
|
|
159
|
+
// Since attach clients generate their own clientId (att_...), we store them when they connect.
|
|
160
|
+
|
|
161
|
+
// Monkey-patch _handleSSE to accept attach clients (those without a pending page record)
|
|
162
|
+
var origHandleSSE = app._handleSSE.bind(app);
|
|
163
|
+
app._handleSSE = function(req, res, clientId) {
|
|
164
|
+
// If no pending record exists, this is an attach client
|
|
165
|
+
if (!app._clients.has(clientId)) {
|
|
166
|
+
app._clients.set(clientId, { pagePath: '/_attach', client: null });
|
|
167
|
+
}
|
|
168
|
+
origHandleSSE(req, res, clientId);
|
|
169
|
+
|
|
170
|
+
// After original handler runs, grab the client
|
|
171
|
+
var record = app._clients.get(clientId);
|
|
172
|
+
if (record && record.client) {
|
|
173
|
+
var client = record.client;
|
|
174
|
+
client._allowScreenshot = opts.allowScreenshot;
|
|
175
|
+
clients.set(clientId, client);
|
|
176
|
+
|
|
177
|
+
// Set as active client
|
|
178
|
+
activeClient = client;
|
|
179
|
+
|
|
180
|
+
// Print connection message
|
|
181
|
+
process.stdout.write('\r\x1b[K');
|
|
182
|
+
console.log('[connected] client ' + clientId);
|
|
183
|
+
rl.prompt(true);
|
|
184
|
+
|
|
185
|
+
// Listen for events from _bw_listen
|
|
186
|
+
client.on('_bw_event', function(data) {
|
|
187
|
+
process.stdout.write('\r\x1b[K');
|
|
188
|
+
console.log('[event] ' + data.event + ' on ' + data.selector +
|
|
189
|
+
' \u2192 ' + data.tagName + (data.id ? '#' + data.id : '') +
|
|
190
|
+
(data.text ? ' "' + data.text.slice(0, 50) + '"' : ''));
|
|
191
|
+
rl.prompt(true);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
// Handle disconnect
|
|
195
|
+
req.on('close', function() {
|
|
196
|
+
clients.delete(clientId);
|
|
197
|
+
if (activeClient === client) {
|
|
198
|
+
// Switch to another client or null
|
|
199
|
+
activeClient = clients.size > 0 ? clients.values().next().value : null;
|
|
200
|
+
}
|
|
201
|
+
process.stdout.write('\r\x1b[K');
|
|
202
|
+
console.log('[disconnected] client ' + clientId);
|
|
203
|
+
if (activeClient) {
|
|
204
|
+
console.log('[active] client ' + activeClient.id);
|
|
205
|
+
} else {
|
|
206
|
+
console.log('Waiting for connection...');
|
|
207
|
+
}
|
|
208
|
+
rl.prompt(true);
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
// Start the server
|
|
214
|
+
app.listen(function() {
|
|
215
|
+
var url = 'http://localhost:' + opts.port;
|
|
216
|
+
console.log('bwcli attach v' + VERSION);
|
|
217
|
+
console.log(' Server: ' + url);
|
|
218
|
+
console.log(' Drop-in: <script src="' + url + '/bw/attach.js"></script>');
|
|
219
|
+
if (opts.allowScreenshot) {
|
|
220
|
+
console.log(' Screenshot: enabled');
|
|
221
|
+
}
|
|
222
|
+
console.log('');
|
|
223
|
+
console.log('Waiting for connection...');
|
|
224
|
+
console.log('Type /help for commands, /quit to exit.');
|
|
225
|
+
console.log('');
|
|
226
|
+
rl.prompt();
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
// Create readline REPL
|
|
230
|
+
var rl = createInterface({
|
|
231
|
+
input: process.stdin,
|
|
232
|
+
output: process.stdout,
|
|
233
|
+
prompt: 'bw> '
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
rl.on('line', function(line) {
|
|
237
|
+
line = line.trim();
|
|
238
|
+
if (!line) {
|
|
239
|
+
rl.prompt();
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Slash commands
|
|
244
|
+
if (line.charAt(0) === '/') {
|
|
245
|
+
handleSlashCommand(line, activeClient, clients, opts, rl);
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// JS expression — requires active client
|
|
250
|
+
if (!activeClient) {
|
|
251
|
+
console.log('No client connected. Add the attach script to a page first.');
|
|
252
|
+
rl.prompt();
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
var code = wrapExpression(line);
|
|
257
|
+
if (opts.verbose) {
|
|
258
|
+
console.log('[query] ' + code);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
activeClient.query(code, { timeout: 10000 }).then(function(result) {
|
|
262
|
+
if (result !== undefined && result !== null) {
|
|
263
|
+
try {
|
|
264
|
+
console.log(typeof result === 'string' ? result : JSON.stringify(result, null, 2));
|
|
265
|
+
} catch (e) {
|
|
266
|
+
console.log(String(result));
|
|
267
|
+
}
|
|
268
|
+
} else {
|
|
269
|
+
console.log('undefined');
|
|
270
|
+
}
|
|
271
|
+
rl.prompt();
|
|
272
|
+
}).catch(function(err) {
|
|
273
|
+
console.error('[error] ' + err.message);
|
|
274
|
+
rl.prompt();
|
|
275
|
+
});
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
rl.on('close', function() {
|
|
279
|
+
console.log('\nExiting.');
|
|
280
|
+
app.close().then(function() {
|
|
281
|
+
process.exit(0);
|
|
282
|
+
});
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Handle slash commands in the REPL.
|
|
288
|
+
* @private
|
|
289
|
+
*/
|
|
290
|
+
function handleSlashCommand(line, activeClient, clients, opts, rl) {
|
|
291
|
+
var parts = line.split(/\s+/);
|
|
292
|
+
var cmd = parts[0].toLowerCase();
|
|
293
|
+
|
|
294
|
+
switch (cmd) {
|
|
295
|
+
case '/help':
|
|
296
|
+
case '/h':
|
|
297
|
+
printHelp();
|
|
298
|
+
rl.prompt();
|
|
299
|
+
break;
|
|
300
|
+
|
|
301
|
+
case '/quit':
|
|
302
|
+
case '/q':
|
|
303
|
+
rl.close();
|
|
304
|
+
break;
|
|
305
|
+
|
|
306
|
+
case '/clients':
|
|
307
|
+
if (clients.size === 0) {
|
|
308
|
+
console.log('No clients connected.');
|
|
309
|
+
} else {
|
|
310
|
+
for (var [id, c] of clients) {
|
|
311
|
+
var marker = c === activeClient ? ' (active)' : '';
|
|
312
|
+
console.log(' ' + id + marker + (c._closed ? ' [closed]' : ''));
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
rl.prompt();
|
|
316
|
+
break;
|
|
317
|
+
|
|
318
|
+
case '/tree':
|
|
319
|
+
if (!activeClient) {
|
|
320
|
+
console.log('No client connected.');
|
|
321
|
+
rl.prompt();
|
|
322
|
+
break;
|
|
323
|
+
}
|
|
324
|
+
var treeSel = parts[1] || 'body';
|
|
325
|
+
var treeDepth = parts[2] ? parseInt(parts[2], 10) : 3;
|
|
326
|
+
var treePend = activeClient._pend(10000);
|
|
327
|
+
activeClient.call('_bw_tree', {
|
|
328
|
+
selector: treeSel,
|
|
329
|
+
depth: treeDepth,
|
|
330
|
+
requestId: treePend.requestId
|
|
331
|
+
});
|
|
332
|
+
treePend.promise.then(function(result) {
|
|
333
|
+
if (!result) {
|
|
334
|
+
console.log('(no element found for "' + treeSel + '")');
|
|
335
|
+
} else {
|
|
336
|
+
printTree(result, 0);
|
|
337
|
+
}
|
|
338
|
+
rl.prompt();
|
|
339
|
+
}).catch(function(err) {
|
|
340
|
+
console.error('[error] ' + err.message);
|
|
341
|
+
rl.prompt();
|
|
342
|
+
});
|
|
343
|
+
break;
|
|
344
|
+
|
|
345
|
+
case '/screenshot':
|
|
346
|
+
if (!activeClient) {
|
|
347
|
+
console.log('No client connected.');
|
|
348
|
+
rl.prompt();
|
|
349
|
+
break;
|
|
350
|
+
}
|
|
351
|
+
if (!opts.allowScreenshot) {
|
|
352
|
+
console.log('Screenshot not enabled. Restart with --allow-screenshot.');
|
|
353
|
+
rl.prompt();
|
|
354
|
+
break;
|
|
355
|
+
}
|
|
356
|
+
var ssSel = parts[1] || 'body';
|
|
357
|
+
var ssFile = parts[2] || 'screenshot-' + Date.now() + '.png';
|
|
358
|
+
console.log('Capturing ' + ssSel + ' ...');
|
|
359
|
+
activeClient.screenshot(ssSel, { timeout: 15000 }).then(function(result) {
|
|
360
|
+
writeFileSync(ssFile, result.data);
|
|
361
|
+
console.log('Saved: ' + ssFile + ' (' + result.width + 'x' + result.height + ', ' + result.data.length + ' bytes)');
|
|
362
|
+
rl.prompt();
|
|
363
|
+
}).catch(function(err) {
|
|
364
|
+
console.error('[error] ' + err.message);
|
|
365
|
+
rl.prompt();
|
|
366
|
+
});
|
|
367
|
+
break;
|
|
368
|
+
|
|
369
|
+
case '/mount':
|
|
370
|
+
if (!activeClient) {
|
|
371
|
+
console.log('No client connected.');
|
|
372
|
+
rl.prompt();
|
|
373
|
+
break;
|
|
374
|
+
}
|
|
375
|
+
if (parts.length < 3) {
|
|
376
|
+
console.log('Usage: /mount <selector> <component> [json-props]');
|
|
377
|
+
rl.prompt();
|
|
378
|
+
break;
|
|
379
|
+
}
|
|
380
|
+
var mountSel = parts[1];
|
|
381
|
+
var mountComp = parts[2];
|
|
382
|
+
var mountProps = {};
|
|
383
|
+
if (parts[3]) {
|
|
384
|
+
try {
|
|
385
|
+
mountProps = JSON.parse(parts.slice(3).join(' '));
|
|
386
|
+
} catch (e) {
|
|
387
|
+
console.error('Invalid JSON props: ' + e.message);
|
|
388
|
+
rl.prompt();
|
|
389
|
+
break;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
activeClient.mount(mountSel, mountComp, mountProps, { timeout: 10000 }).then(function() {
|
|
393
|
+
console.log('Mounted ' + mountComp + ' at ' + mountSel);
|
|
394
|
+
rl.prompt();
|
|
395
|
+
}).catch(function(err) {
|
|
396
|
+
console.error('[error] ' + err.message);
|
|
397
|
+
rl.prompt();
|
|
398
|
+
});
|
|
399
|
+
break;
|
|
400
|
+
|
|
401
|
+
case '/render':
|
|
402
|
+
if (!activeClient) {
|
|
403
|
+
console.log('No client connected.');
|
|
404
|
+
rl.prompt();
|
|
405
|
+
break;
|
|
406
|
+
}
|
|
407
|
+
if (parts.length < 3) {
|
|
408
|
+
console.log('Usage: /render <selector> <taco-json>');
|
|
409
|
+
rl.prompt();
|
|
410
|
+
break;
|
|
411
|
+
}
|
|
412
|
+
var renderSel = parts[1];
|
|
413
|
+
var renderTaco;
|
|
414
|
+
try {
|
|
415
|
+
renderTaco = JSON.parse(parts.slice(2).join(' '));
|
|
416
|
+
} catch (e) {
|
|
417
|
+
console.error('Invalid TACO JSON: ' + e.message);
|
|
418
|
+
rl.prompt();
|
|
419
|
+
break;
|
|
420
|
+
}
|
|
421
|
+
activeClient.render(renderSel, renderTaco);
|
|
422
|
+
console.log('Rendered at ' + renderSel);
|
|
423
|
+
rl.prompt();
|
|
424
|
+
break;
|
|
425
|
+
|
|
426
|
+
case '/patch':
|
|
427
|
+
if (!activeClient) {
|
|
428
|
+
console.log('No client connected.');
|
|
429
|
+
rl.prompt();
|
|
430
|
+
break;
|
|
431
|
+
}
|
|
432
|
+
if (parts.length < 3) {
|
|
433
|
+
console.log('Usage: /patch <id> <content>');
|
|
434
|
+
rl.prompt();
|
|
435
|
+
break;
|
|
436
|
+
}
|
|
437
|
+
var patchId = parts[1];
|
|
438
|
+
var patchContent = parts.slice(2).join(' ');
|
|
439
|
+
activeClient.patch(patchId, patchContent);
|
|
440
|
+
console.log('Patched ' + patchId);
|
|
441
|
+
rl.prompt();
|
|
442
|
+
break;
|
|
443
|
+
|
|
444
|
+
case '/listen':
|
|
445
|
+
if (!activeClient) {
|
|
446
|
+
console.log('No client connected.');
|
|
447
|
+
rl.prompt();
|
|
448
|
+
break;
|
|
449
|
+
}
|
|
450
|
+
if (parts.length < 3) {
|
|
451
|
+
console.log('Usage: /listen <selector> <event>');
|
|
452
|
+
console.log('Examples: /listen button click, /listen .card mouseover');
|
|
453
|
+
rl.prompt();
|
|
454
|
+
break;
|
|
455
|
+
}
|
|
456
|
+
activeClient.call('_bw_listen', {
|
|
457
|
+
selector: parts[1],
|
|
458
|
+
event: parts[2]
|
|
459
|
+
});
|
|
460
|
+
console.log('Listening for ' + parts[2] + ' on ' + parts[1]);
|
|
461
|
+
rl.prompt();
|
|
462
|
+
break;
|
|
463
|
+
|
|
464
|
+
case '/unlisten':
|
|
465
|
+
if (!activeClient) {
|
|
466
|
+
console.log('No client connected.');
|
|
467
|
+
rl.prompt();
|
|
468
|
+
break;
|
|
469
|
+
}
|
|
470
|
+
if (parts.length < 3) {
|
|
471
|
+
console.log('Usage: /unlisten <selector> <event>');
|
|
472
|
+
rl.prompt();
|
|
473
|
+
break;
|
|
474
|
+
}
|
|
475
|
+
activeClient.call('_bw_unlisten', {
|
|
476
|
+
selector: parts[1],
|
|
477
|
+
event: parts[2]
|
|
478
|
+
});
|
|
479
|
+
console.log('Stopped listening for ' + parts[2] + ' on ' + parts[1]);
|
|
480
|
+
rl.prompt();
|
|
481
|
+
break;
|
|
482
|
+
|
|
483
|
+
case '/exec':
|
|
484
|
+
if (!activeClient) {
|
|
485
|
+
console.log('No client connected.');
|
|
486
|
+
rl.prompt();
|
|
487
|
+
break;
|
|
488
|
+
}
|
|
489
|
+
if (parts.length < 2) {
|
|
490
|
+
console.log('Usage: /exec <code>');
|
|
491
|
+
rl.prompt();
|
|
492
|
+
break;
|
|
493
|
+
}
|
|
494
|
+
activeClient.exec(parts.slice(1).join(' '));
|
|
495
|
+
console.log('Executed.');
|
|
496
|
+
rl.prompt();
|
|
497
|
+
break;
|
|
498
|
+
|
|
499
|
+
default:
|
|
500
|
+
console.log('Unknown command: ' + cmd + '. Type /help for available commands.');
|
|
501
|
+
rl.prompt();
|
|
502
|
+
break;
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
/**
|
|
507
|
+
* Pretty-print a DOM tree from _bw_tree.
|
|
508
|
+
* @private
|
|
509
|
+
*/
|
|
510
|
+
function printTree(node, indent) {
|
|
511
|
+
if (!node) return;
|
|
512
|
+
var prefix = ' '.repeat(indent);
|
|
513
|
+
var label = node.tag || '?';
|
|
514
|
+
if (node.id) label += '#' + node.id;
|
|
515
|
+
if (node.cls) label += '.' + node.cls.split(' ').join('.');
|
|
516
|
+
console.log(prefix + label);
|
|
517
|
+
if (node.children) {
|
|
518
|
+
for (var i = 0; i < node.children.length; i++) {
|
|
519
|
+
printTree(node.children[i], indent + 1);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
/**
|
|
525
|
+
* Print the REPL help reference.
|
|
526
|
+
* @private
|
|
527
|
+
*/
|
|
528
|
+
function printHelp() {
|
|
529
|
+
console.log([
|
|
530
|
+
'',
|
|
531
|
+
'bwcli attach — REPL Commands',
|
|
532
|
+
'',
|
|
533
|
+
' <expression> Evaluate JS in the browser and print result',
|
|
534
|
+
' Examples: document.title, bw.$(".card").length',
|
|
535
|
+
'',
|
|
536
|
+
' /help, /h Show this help',
|
|
537
|
+
' /quit, /q Exit',
|
|
538
|
+
'',
|
|
539
|
+
' /tree [sel] [depth] Show DOM tree (default: body, depth 3)',
|
|
540
|
+
' /screenshot [sel] [file] Capture screenshot to PNG file',
|
|
541
|
+
' Requires --allow-screenshot flag',
|
|
542
|
+
' /mount <sel> <comp> [json] Mount a BCCL component',
|
|
543
|
+
' Example: /mount #app card {"title":"Hello"}',
|
|
544
|
+
' /render <sel> <taco> Render TACO JSON at selector',
|
|
545
|
+
' Example: /render #app {"t":"h1","c":"Hi"}',
|
|
546
|
+
' /patch <id> <content> Update element text content',
|
|
547
|
+
' Example: /patch counter 42',
|
|
548
|
+
' /listen <sel> <event> Listen for DOM events (prints inline)',
|
|
549
|
+
' Example: /listen button click',
|
|
550
|
+
' /unlisten <sel> <event> Remove a listener',
|
|
551
|
+
' /exec <code> Execute JS without capturing return value',
|
|
552
|
+
' /clients List connected clients',
|
|
553
|
+
''
|
|
554
|
+
].join('\n'));
|
|
555
|
+
}
|
package/src/cli/convert.js
CHANGED
|
@@ -67,7 +67,7 @@ function resolveInjectionMode(flags) {
|
|
|
67
67
|
/**
|
|
68
68
|
* Resolve theme config from --theme flag value
|
|
69
69
|
* @param {string} themeValue - Preset name or hex colors ("primary,secondary" or "primary,secondary,tertiary")
|
|
70
|
-
* @returns {Object} Config for bw.
|
|
70
|
+
* @returns {Object} Config for bw.makeStyles
|
|
71
71
|
*/
|
|
72
72
|
function resolveTheme(themeValue) {
|
|
73
73
|
if (!themeValue) return null;
|
|
@@ -165,10 +165,7 @@ export function convertFile(inputPath, flags = {}) {
|
|
|
165
165
|
if (flags.theme) {
|
|
166
166
|
const themeConfig = resolveTheme(flags.theme);
|
|
167
167
|
if (themeConfig) {
|
|
168
|
-
const result = bw.
|
|
169
|
-
...themeConfig,
|
|
170
|
-
inject: false
|
|
171
|
-
});
|
|
168
|
+
const result = bw.makeStyles(themeConfig);
|
|
172
169
|
css += '\n' + result.css;
|
|
173
170
|
}
|
|
174
171
|
}
|
package/src/cli/index.js
CHANGED
|
@@ -11,6 +11,7 @@ import { parseArgs } from 'node:util';
|
|
|
11
11
|
import { VERSION } from '../version.js';
|
|
12
12
|
import { convertFile } from './convert.js';
|
|
13
13
|
import { runServe } from './serve.js';
|
|
14
|
+
import { runAttach } from './attach.js';
|
|
14
15
|
|
|
15
16
|
const USAGE = `
|
|
16
17
|
bwcli v${VERSION} — bitwrench command-line tool
|
|
@@ -18,6 +19,7 @@ bwcli v${VERSION} — bitwrench command-line tool
|
|
|
18
19
|
Usage:
|
|
19
20
|
bwcli <file> [options] Convert a file to styled HTML
|
|
20
21
|
bwcli serve [dir] [options] Start bwserve development server
|
|
22
|
+
bwcli attach [options] Start attach server for remote debugging
|
|
21
23
|
bwcli --version Print version
|
|
22
24
|
bwcli --help Print this help
|
|
23
25
|
|
|
@@ -52,6 +54,8 @@ Examples:
|
|
|
52
54
|
bwcli doc.md --theme "#336699,#cc6633" Custom theme colors
|
|
53
55
|
bwcli serve Serve current directory on port 7902
|
|
54
56
|
bwcli serve ./site --port 8080 Serve ./site on port 8080
|
|
57
|
+
bwcli attach Start remote debugging REPL on port 7902
|
|
58
|
+
bwcli attach --port 3000 Attach on custom port
|
|
55
59
|
`.trim();
|
|
56
60
|
|
|
57
61
|
/**
|
|
@@ -60,6 +64,9 @@ Examples:
|
|
|
60
64
|
*/
|
|
61
65
|
export function run(argv) {
|
|
62
66
|
// Check for subcommand before parseArgs (subcommands have different options)
|
|
67
|
+
if (argv.length > 0 && argv[0] === 'attach') {
|
|
68
|
+
return runAttach(argv.slice(1));
|
|
69
|
+
}
|
|
63
70
|
if (argv.length > 0 && argv[0] === 'serve') {
|
|
64
71
|
return runServe(argv.slice(1));
|
|
65
72
|
}
|
package/src/cli/inject.js
CHANGED
|
@@ -48,7 +48,7 @@ export function getInjectionHead(mode) {
|
|
|
48
48
|
*/
|
|
49
49
|
export function getInjectionBodyEnd(mode) {
|
|
50
50
|
if (mode === 'standalone' || mode === 'cdn') {
|
|
51
|
-
return `<script>if(typeof bw!=='undefined'){bw.
|
|
51
|
+
return `<script>if(typeof bw!=='undefined'){bw.loadStyles();}</script>`;
|
|
52
52
|
}
|
|
53
53
|
// mode === 'none'
|
|
54
54
|
return '';
|
package/src/cli/serve.js
CHANGED
|
@@ -29,6 +29,7 @@ Options:
|
|
|
29
29
|
--stdin Read protocol messages from stdin (newline-delimited JSON)
|
|
30
30
|
-t, --theme <name> Theme preset or hex colors ("#pri,#sec")
|
|
31
31
|
--title <string> Page title (default: "bwcli serve")
|
|
32
|
+
--allow-exec Enable exec messages (runs JS in browser, use for dev only)
|
|
32
33
|
--open Open browser on start
|
|
33
34
|
-v, --verbose Verbose output
|
|
34
35
|
-h, --help Print this help
|
|
@@ -148,6 +149,7 @@ export function runServe(argv) {
|
|
|
148
149
|
stdin: { type: 'boolean' },
|
|
149
150
|
theme: { type: 'string', short: 't' },
|
|
150
151
|
title: { type: 'string' },
|
|
152
|
+
'allow-exec': { type: 'boolean' },
|
|
151
153
|
open: { type: 'boolean' },
|
|
152
154
|
verbose: { type: 'boolean', short: 'v' },
|
|
153
155
|
help: { type: 'boolean', short: 'h' }
|
|
@@ -193,7 +195,8 @@ export function runServe(argv) {
|
|
|
193
195
|
theme: theme,
|
|
194
196
|
title: title,
|
|
195
197
|
verbose: verbose,
|
|
196
|
-
open: !!values.open
|
|
198
|
+
open: !!values.open,
|
|
199
|
+
allowExec: !!values['allow-exec']
|
|
197
200
|
});
|
|
198
201
|
}).catch(function(err) {
|
|
199
202
|
console.error('Failed to load bwserve: ' + err.message);
|
|
@@ -209,7 +212,8 @@ function startServer(bwserve, opts) {
|
|
|
209
212
|
port: opts.webPort,
|
|
210
213
|
title: opts.title,
|
|
211
214
|
static: opts.dir,
|
|
212
|
-
theme: opts.theme
|
|
215
|
+
theme: opts.theme,
|
|
216
|
+
allowExec: opts.allowExec
|
|
213
217
|
});
|
|
214
218
|
|
|
215
219
|
// Register a passthrough page handler — just keeps clients alive
|
package/src/generate-css.js
CHANGED
|
@@ -8,16 +8,23 @@ import { getAllStyles, getStructuralStyles, generateThemedCSS,
|
|
|
8
8
|
import { derivePalette } from './bitwrench-color-utils.js';
|
|
9
9
|
import fs from 'fs';
|
|
10
10
|
import path from 'path';
|
|
11
|
+
import { createRequire } from 'module';
|
|
12
|
+
const require = createRequire(import.meta.url);
|
|
13
|
+
const pkg = require('../package.json');
|
|
11
14
|
|
|
12
15
|
// Convert styles object to CSS string
|
|
13
16
|
function stylesToCSS(styles) {
|
|
14
17
|
let css = `/**
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* Generated from bitwrench-styles.js
|
|
18
|
+
* bitwrench.css v${pkg.version} — AUTO-GENERATED, DO NOT EDIT
|
|
19
|
+
*
|
|
20
|
+
* Generated by src/generate-css.js from bitwrench-styles.js
|
|
21
|
+
* This is a static snapshot of what bw.loadStyles() produces
|
|
22
|
+
* at runtime. Use one or the other, not both.
|
|
23
|
+
*
|
|
24
|
+
* To regenerate: npm run build:css
|
|
25
|
+
* ${pkg.homepage}
|
|
18
26
|
*/
|
|
19
27
|
|
|
20
|
-
/* Base styles with .bw namespace */
|
|
21
28
|
`;
|
|
22
29
|
|
|
23
30
|
// Process each style rule
|