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.
Files changed (72) hide show
  1. package/README.md +169 -75
  2. package/dist/bitwrench-bccl.cjs.js +228 -55
  3. package/dist/bitwrench-bccl.cjs.min.js +3 -3
  4. package/dist/bitwrench-bccl.esm.js +228 -55
  5. package/dist/bitwrench-bccl.esm.min.js +3 -3
  6. package/dist/bitwrench-bccl.umd.js +228 -55
  7. package/dist/bitwrench-bccl.umd.min.js +3 -3
  8. package/dist/bitwrench-code-edit.cjs.js +7 -9
  9. package/dist/bitwrench-code-edit.cjs.min.js +5 -7
  10. package/dist/bitwrench-code-edit.es5.js +6 -8
  11. package/dist/bitwrench-code-edit.es5.min.js +5 -7
  12. package/dist/bitwrench-code-edit.esm.js +7 -9
  13. package/dist/bitwrench-code-edit.esm.min.js +5 -7
  14. package/dist/bitwrench-code-edit.umd.js +7 -9
  15. package/dist/bitwrench-code-edit.umd.min.js +5 -7
  16. package/dist/bitwrench-debug.js +268 -0
  17. package/dist/bitwrench-debug.min.js +3 -0
  18. package/dist/bitwrench-lean.cjs.js +1190 -2348
  19. package/dist/bitwrench-lean.cjs.min.js +20 -20
  20. package/dist/bitwrench-lean.es5.js +1285 -2551
  21. package/dist/bitwrench-lean.es5.min.js +18 -18
  22. package/dist/bitwrench-lean.esm.js +1190 -2348
  23. package/dist/bitwrench-lean.esm.min.js +20 -20
  24. package/dist/bitwrench-lean.umd.js +1190 -2348
  25. package/dist/bitwrench-lean.umd.min.js +20 -20
  26. package/dist/bitwrench-util-css.cjs.js +236 -0
  27. package/dist/bitwrench-util-css.cjs.min.js +22 -0
  28. package/dist/bitwrench-util-css.es5.js +414 -0
  29. package/dist/bitwrench-util-css.es5.min.js +21 -0
  30. package/dist/bitwrench-util-css.esm.js +230 -0
  31. package/dist/bitwrench-util-css.esm.min.js +21 -0
  32. package/dist/bitwrench-util-css.umd.js +242 -0
  33. package/dist/bitwrench-util-css.umd.min.js +21 -0
  34. package/dist/bitwrench.cjs.js +1404 -2388
  35. package/dist/bitwrench.cjs.min.js +21 -21
  36. package/dist/bitwrench.css +503 -132
  37. package/dist/bitwrench.es5.js +1588 -2659
  38. package/dist/bitwrench.es5.min.js +19 -19
  39. package/dist/bitwrench.esm.js +1405 -2389
  40. package/dist/bitwrench.esm.min.js +21 -21
  41. package/dist/bitwrench.min.css +1 -1
  42. package/dist/bitwrench.umd.js +1404 -2388
  43. package/dist/bitwrench.umd.min.js +21 -21
  44. package/dist/builds.json +214 -104
  45. package/dist/bwserve.cjs.js +514 -68
  46. package/dist/bwserve.esm.js +513 -69
  47. package/dist/sri.json +46 -36
  48. package/package.json +6 -3
  49. package/readme.html +183 -85
  50. package/src/bitwrench-bccl-entry.js +3 -4
  51. package/src/bitwrench-bccl.js +224 -50
  52. package/src/bitwrench-code-edit.js +6 -8
  53. package/src/bitwrench-color-utils.js +31 -9
  54. package/src/bitwrench-debug.js +245 -0
  55. package/src/bitwrench-esm-entry.js +11 -0
  56. package/src/bitwrench-styles.js +474 -240
  57. package/src/bitwrench-util-css.js +229 -0
  58. package/src/bitwrench.js +689 -2042
  59. package/src/bwserve/attach.js +57 -0
  60. package/src/bwserve/bwclient.js +141 -0
  61. package/src/bwserve/bwshell.js +102 -0
  62. package/src/bwserve/client.js +151 -1
  63. package/src/bwserve/index.js +127 -28
  64. package/src/cli/attach.js +587 -0
  65. package/src/cli/convert.js +2 -5
  66. package/src/cli/index.js +7 -0
  67. package/src/cli/inject.js +1 -1
  68. package/src/cli/serve.js +185 -5
  69. package/src/generate-css.js +11 -4
  70. package/src/vendor/html2canvas.min.js +20 -0
  71. package/src/version.js +3 -3
  72. 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
- import('../../src/bwserve/index.js').then(function(bwserve) {
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 };
@@ -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
- * Bitwrench v2 CSS
16
- * Class-based styles to prevent framework collisions
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