bitwrench 2.0.17 → 2.0.19
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 +169 -75
- package/dist/bitwrench-bccl.cjs.js +228 -55
- package/dist/bitwrench-bccl.cjs.min.js +3 -3
- package/dist/bitwrench-bccl.esm.js +228 -55
- package/dist/bitwrench-bccl.esm.min.js +3 -3
- package/dist/bitwrench-bccl.umd.js +228 -55
- package/dist/bitwrench-bccl.umd.min.js +3 -3
- package/dist/bitwrench-code-edit.cjs.js +7 -9
- package/dist/bitwrench-code-edit.cjs.min.js +5 -7
- package/dist/bitwrench-code-edit.es5.js +6 -8
- package/dist/bitwrench-code-edit.es5.min.js +5 -7
- package/dist/bitwrench-code-edit.esm.js +7 -9
- package/dist/bitwrench-code-edit.esm.min.js +5 -7
- package/dist/bitwrench-code-edit.umd.js +7 -9
- package/dist/bitwrench-code-edit.umd.min.js +5 -7
- package/dist/bitwrench-debug.js +268 -0
- package/dist/bitwrench-debug.min.js +3 -0
- package/dist/bitwrench-lean.cjs.js +1190 -2348
- package/dist/bitwrench-lean.cjs.min.js +20 -20
- package/dist/bitwrench-lean.es5.js +1285 -2551
- package/dist/bitwrench-lean.es5.min.js +18 -18
- package/dist/bitwrench-lean.esm.js +1190 -2348
- package/dist/bitwrench-lean.esm.min.js +20 -20
- package/dist/bitwrench-lean.umd.js +1190 -2348
- 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 +1404 -2388
- package/dist/bitwrench.cjs.min.js +21 -21
- package/dist/bitwrench.css +503 -132
- package/dist/bitwrench.es5.js +1588 -2659
- package/dist/bitwrench.es5.min.js +19 -19
- package/dist/bitwrench.esm.js +1405 -2389
- package/dist/bitwrench.esm.min.js +21 -21
- package/dist/bitwrench.min.css +1 -1
- package/dist/bitwrench.umd.js +1404 -2388
- package/dist/bitwrench.umd.min.js +21 -21
- package/dist/builds.json +214 -104
- package/dist/bwserve.cjs.js +514 -68
- package/dist/bwserve.esm.js +513 -69
- package/dist/sri.json +46 -36
- package/package.json +6 -3
- package/readme.html +183 -85
- package/src/bitwrench-bccl-entry.js +3 -4
- package/src/bitwrench-bccl.js +224 -50
- package/src/bitwrench-code-edit.js +6 -8
- package/src/bitwrench-color-utils.js +31 -9
- package/src/bitwrench-debug.js +245 -0
- package/src/bitwrench-esm-entry.js +11 -0
- package/src/bitwrench-styles.js +474 -240
- package/src/bitwrench-util-css.js +229 -0
- package/src/bitwrench.js +689 -2042
- 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 +587 -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 +185 -5
- 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
package/src/cli/serve.js
CHANGED
|
@@ -5,6 +5,10 @@
|
|
|
5
5
|
* via an input HTTP port or stdin. Broadcasts messages to all connected
|
|
6
6
|
* browser tabs.
|
|
7
7
|
*
|
|
8
|
+
* The input HTTP port also supports interactive commands: POST a JSON
|
|
9
|
+
* object with a `command` field and receive the result as the HTTP
|
|
10
|
+
* response. This is the programmatic equivalent of bwcli attach's REPL.
|
|
11
|
+
*
|
|
8
12
|
* Usage:
|
|
9
13
|
* bwcli serve [dir] [--port N] [--listen N] [--stdin] [--theme name]
|
|
10
14
|
*/
|
|
@@ -29,6 +33,7 @@ Options:
|
|
|
29
33
|
--stdin Read protocol messages from stdin (newline-delimited JSON)
|
|
30
34
|
-t, --theme <name> Theme preset or hex colors ("#pri,#sec")
|
|
31
35
|
--title <string> Page title (default: "bwcli serve")
|
|
36
|
+
--allow-exec Enable exec messages (runs JS in browser, use for dev only)
|
|
32
37
|
--open Open browser on start
|
|
33
38
|
-v, --verbose Verbose output
|
|
34
39
|
-h, --help Print this help
|
|
@@ -39,6 +44,8 @@ Examples:
|
|
|
39
44
|
bwcli serve --stdin Read from pipe instead of input port
|
|
40
45
|
sensor-reader | bwcli serve --stdin Pipe sensor data to browser
|
|
41
46
|
curl -X POST :9000 -d '{"type":"replace","target":"#app","node":{"t":"h1","c":"Hi"}}'
|
|
47
|
+
curl -X POST :9000 -d '{"command":"query","code":"document.title"}'
|
|
48
|
+
curl -X POST :9000 -d '{"command":"clients"}'
|
|
42
49
|
`.trim();
|
|
43
50
|
|
|
44
51
|
/**
|
|
@@ -130,11 +137,151 @@ function parseRelaxedJSON(str) {
|
|
|
130
137
|
return JSON.parse(out.join(''));
|
|
131
138
|
}
|
|
132
139
|
|
|
140
|
+
// Required fields per command (async commands also listed)
|
|
141
|
+
var _COMMAND_REQUIRED = {
|
|
142
|
+
query: ['code'],
|
|
143
|
+
screenshot: [],
|
|
144
|
+
tree: [],
|
|
145
|
+
mount: ['selector', 'factory'],
|
|
146
|
+
exec: ['code'],
|
|
147
|
+
render: ['selector', 'taco'],
|
|
148
|
+
patch: ['id'],
|
|
149
|
+
listen: ['selector', 'event'],
|
|
150
|
+
unlisten: ['selector', 'event'],
|
|
151
|
+
clients: []
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
// Commands that return a result via promise
|
|
155
|
+
var _ASYNC_COMMANDS = { query: 1, screenshot: 1, tree: 1, mount: 1 };
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Handle an interactive command from the listen port.
|
|
159
|
+
*
|
|
160
|
+
* @param {Object} msg - Parsed message with `command` field
|
|
161
|
+
* @param {Object} app - BwServeApp instance (has _clients Map)
|
|
162
|
+
* @param {boolean} verbose - Log details to stderr
|
|
163
|
+
* @returns {Promise<Object>} Response object ({ok, result, clientId} or {error})
|
|
164
|
+
*/
|
|
165
|
+
function handleCommand(msg, app, verbose) {
|
|
166
|
+
var cmd = msg.command;
|
|
167
|
+
|
|
168
|
+
// clients: no client needed
|
|
169
|
+
if (cmd === 'clients') {
|
|
170
|
+
var ids = [];
|
|
171
|
+
for (var entry of app._clients) {
|
|
172
|
+
if (entry[1] && entry[1].client) ids.push(entry[0]);
|
|
173
|
+
}
|
|
174
|
+
return Promise.resolve({ ok: true, clients: ids });
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Validate command name
|
|
178
|
+
if (!_COMMAND_REQUIRED.hasOwnProperty(cmd)) {
|
|
179
|
+
return Promise.resolve({ error: 'Unknown command: ' + cmd });
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Validate required fields
|
|
183
|
+
var required = _COMMAND_REQUIRED[cmd];
|
|
184
|
+
for (var k = 0; k < required.length; k++) {
|
|
185
|
+
if (msg[required[k]] === undefined) {
|
|
186
|
+
return Promise.resolve({ error: 'Missing required field: ' + required[k] });
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Select client
|
|
191
|
+
var client = null;
|
|
192
|
+
var clientId = null;
|
|
193
|
+
|
|
194
|
+
if (msg.clientId) {
|
|
195
|
+
var record = app._clients.get(msg.clientId);
|
|
196
|
+
if (record && record.client) {
|
|
197
|
+
client = record.client;
|
|
198
|
+
clientId = msg.clientId;
|
|
199
|
+
} else {
|
|
200
|
+
return Promise.resolve({ error: 'Client not found: ' + msg.clientId });
|
|
201
|
+
}
|
|
202
|
+
} else {
|
|
203
|
+
// Pick first connected client
|
|
204
|
+
for (var pair of app._clients) {
|
|
205
|
+
if (pair[1] && pair[1].client && !pair[1].client._closed) {
|
|
206
|
+
client = pair[1].client;
|
|
207
|
+
clientId = pair[0];
|
|
208
|
+
break;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (!client) {
|
|
214
|
+
return Promise.resolve({ error: 'No clients connected' });
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
var timeout = msg.timeout;
|
|
218
|
+
|
|
219
|
+
if (verbose) {
|
|
220
|
+
console.error('[command] ' + cmd + ' -> client ' + clientId);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Dispatch
|
|
224
|
+
try {
|
|
225
|
+
switch (cmd) {
|
|
226
|
+
case 'query':
|
|
227
|
+
return client.query(msg.code, { timeout: timeout || 5000 }).then(function(result) {
|
|
228
|
+
return { ok: true, result: result, clientId: clientId };
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
case 'screenshot':
|
|
232
|
+
return client.screenshot(msg.selector || 'body', { timeout: timeout || 10000 }).then(function(result) {
|
|
233
|
+
// Convert Buffer to base64 for JSON response
|
|
234
|
+
var data = result && result.data ? result.data.toString('base64') : null;
|
|
235
|
+
return { ok: true, result: { data: data, width: result.width, height: result.height, format: result.format }, clientId: clientId };
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
case 'tree':
|
|
239
|
+
var pend = client._pend(timeout || 10000);
|
|
240
|
+
client.call('_bw_tree', {
|
|
241
|
+
selector: msg.selector || 'body',
|
|
242
|
+
depth: msg.depth || 3,
|
|
243
|
+
requestId: pend.requestId
|
|
244
|
+
});
|
|
245
|
+
return pend.promise.then(function(result) {
|
|
246
|
+
return { ok: true, result: result, clientId: clientId };
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
case 'mount':
|
|
250
|
+
return client.mount(msg.selector, msg.factory, msg.props || {}, { timeout: timeout || 10000 }).then(function(result) {
|
|
251
|
+
return { ok: true, result: result, clientId: clientId };
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
case 'exec':
|
|
255
|
+
client.exec(msg.code);
|
|
256
|
+
return Promise.resolve({ ok: true, clientId: clientId });
|
|
257
|
+
|
|
258
|
+
case 'render':
|
|
259
|
+
client.render(msg.selector, msg.taco);
|
|
260
|
+
return Promise.resolve({ ok: true, clientId: clientId });
|
|
261
|
+
|
|
262
|
+
case 'patch':
|
|
263
|
+
client.patch(msg.id, msg.content, msg.attr);
|
|
264
|
+
return Promise.resolve({ ok: true, clientId: clientId });
|
|
265
|
+
|
|
266
|
+
case 'listen':
|
|
267
|
+
client.call('_bw_listen', { selector: msg.selector, event: msg.event });
|
|
268
|
+
return Promise.resolve({ ok: true, clientId: clientId });
|
|
269
|
+
|
|
270
|
+
case 'unlisten':
|
|
271
|
+
client.call('_bw_unlisten', { selector: msg.selector, event: msg.event });
|
|
272
|
+
return Promise.resolve({ ok: true, clientId: clientId });
|
|
273
|
+
}
|
|
274
|
+
} catch (err) {
|
|
275
|
+
return Promise.resolve({ error: err.message });
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
133
279
|
/**
|
|
134
280
|
* Run the serve subcommand.
|
|
135
281
|
* @param {string[]} argv - arguments after "serve"
|
|
282
|
+
* @param {object} [ioOpts] - optional overrides for testing
|
|
136
283
|
*/
|
|
137
|
-
export function runServe(argv) {
|
|
284
|
+
export function runServe(argv, ioOpts) {
|
|
138
285
|
var values, positionals;
|
|
139
286
|
|
|
140
287
|
try {
|
|
@@ -148,6 +295,7 @@ export function runServe(argv) {
|
|
|
148
295
|
stdin: { type: 'boolean' },
|
|
149
296
|
theme: { type: 'string', short: 't' },
|
|
150
297
|
title: { type: 'string' },
|
|
298
|
+
'allow-exec': { type: 'boolean' },
|
|
151
299
|
open: { type: 'boolean' },
|
|
152
300
|
verbose: { type: 'boolean', short: 'v' },
|
|
153
301
|
help: { type: 'boolean', short: 'h' }
|
|
@@ -184,7 +332,9 @@ export function runServe(argv) {
|
|
|
184
332
|
}
|
|
185
333
|
|
|
186
334
|
// Dynamic import of bwserve to avoid loading it at parse time
|
|
187
|
-
|
|
335
|
+
var io = ioOpts || {};
|
|
336
|
+
var importPath = io._importPath || '../../src/bwserve/index.js';
|
|
337
|
+
var importPromise = import(importPath).then(function(bwserve) {
|
|
188
338
|
startServer(bwserve, {
|
|
189
339
|
dir: dir,
|
|
190
340
|
webPort: webPort,
|
|
@@ -193,12 +343,15 @@ export function runServe(argv) {
|
|
|
193
343
|
theme: theme,
|
|
194
344
|
title: title,
|
|
195
345
|
verbose: verbose,
|
|
196
|
-
open: !!values.open
|
|
346
|
+
open: !!values.open,
|
|
347
|
+
allowExec: !!values['allow-exec']
|
|
197
348
|
});
|
|
198
349
|
}).catch(function(err) {
|
|
199
350
|
console.error('Failed to load bwserve: ' + err.message);
|
|
200
351
|
process.exit(1);
|
|
201
352
|
});
|
|
353
|
+
|
|
354
|
+
return importPromise;
|
|
202
355
|
}
|
|
203
356
|
|
|
204
357
|
/**
|
|
@@ -209,7 +362,8 @@ function startServer(bwserve, opts) {
|
|
|
209
362
|
port: opts.webPort,
|
|
210
363
|
title: opts.title,
|
|
211
364
|
static: opts.dir,
|
|
212
|
-
theme: opts.theme
|
|
365
|
+
theme: opts.theme,
|
|
366
|
+
allowExec: opts.allowExec
|
|
213
367
|
});
|
|
214
368
|
|
|
215
369
|
// Register a passthrough page handler — just keeps clients alive
|
|
@@ -253,7 +407,11 @@ function startServer(bwserve, opts) {
|
|
|
253
407
|
}
|
|
254
408
|
|
|
255
409
|
/**
|
|
256
|
-
* Start the input HTTP server for receiving protocol messages.
|
|
410
|
+
* Start the input HTTP server for receiving protocol messages and interactive commands.
|
|
411
|
+
*
|
|
412
|
+
* Messages with a `command` field are treated as interactive commands --
|
|
413
|
+
* routed to a specific client and the result returned in the HTTP response.
|
|
414
|
+
* Messages with a `type` field (no `command`) are broadcast to all clients.
|
|
257
415
|
*/
|
|
258
416
|
function startInputServer(app, listenPort, verbose) {
|
|
259
417
|
var inputServer = createServer(function(req, res) {
|
|
@@ -273,6 +431,25 @@ function startInputServer(app, listenPort, verbose) {
|
|
|
273
431
|
return;
|
|
274
432
|
}
|
|
275
433
|
|
|
434
|
+
// Interactive command path
|
|
435
|
+
if (msg.command) {
|
|
436
|
+
handleCommand(msg, app, verbose).then(function(result) {
|
|
437
|
+
var status = result.error ? 400 : 200;
|
|
438
|
+
// Unknown command and client-not-found get 400; timeout also 400
|
|
439
|
+
res.writeHead(status, { 'Content-Type': 'application/json' });
|
|
440
|
+
res.end(JSON.stringify(result));
|
|
441
|
+
}).catch(function(err) {
|
|
442
|
+
var errMsg = err && err.message ? err.message : String(err);
|
|
443
|
+
if (verbose) {
|
|
444
|
+
console.error('[command] error: ' + errMsg);
|
|
445
|
+
}
|
|
446
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
447
|
+
res.end(JSON.stringify({ error: errMsg }));
|
|
448
|
+
});
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// Broadcast path (existing behavior)
|
|
276
453
|
var count = app.broadcast(msg);
|
|
277
454
|
if (verbose) {
|
|
278
455
|
console.error('[input] ' + msg.type + ' -> ' + count + ' client(s)');
|
|
@@ -323,3 +500,6 @@ function startStdinReader(app, verbose) {
|
|
|
323
500
|
if (verbose) console.error('[stdin] Input stream closed. Server stays running.');
|
|
324
501
|
});
|
|
325
502
|
}
|
|
503
|
+
|
|
504
|
+
// Export private functions for testability
|
|
505
|
+
export { parseMessage, parseRelaxedJSON, startServer, startInputServer, startStdinReader, handleCommand };
|
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
|