@bobfrankston/msger 0.1.387 → 0.1.388

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 CHANGED
@@ -118,6 +118,40 @@ Notes:
118
118
  - The two `requestAnimationFrame` calls let layout settle before reading computed style — needed for `contrast-color`, gradients, etc.
119
119
  - The original `-noshow -save` behavior is unchanged: it still exits after saving without ever creating a WebView.
120
120
 
121
+ ### Render mode (`-render`) — capture the page to an image
122
+
123
+ `-render` turns msger into a headless page→bitmap renderer: no window ever appears; the page loads, settles, gets screenshotted, and msger exits. Windows (WebView2) only for now.
124
+
125
+ ```bash
126
+ # Write a PNG of a web page (format follows the extension: .png/.jpg/.bmp)
127
+ msger -url "https://example.com" -size 1024,768 -render page.png
128
+
129
+ # Render inline HTML (charts, badges, generated markup)
130
+ msger -html "<h1 style='color:steelblue'>Build OK</h1>" -size 400,150 -render status.png
131
+
132
+ # No file → the image comes back base64 in the JSON result
133
+ msger -message "hello" -render
134
+ # → { "button": "render", "render": { "format": "png", "data": "iVBOR...", "width": 500, "height": 375 } }
135
+ ```
136
+
137
+ The Node API mirrors this:
138
+
139
+ ```typescript
140
+ // Write a file; result.render = { path, width, height, format }
141
+ await showMessageBox({ url: "https://example.com", size: { width: 1024, height: 768 }, render: "page.png" });
142
+
143
+ // Or get the image as an object; result.render = { data (base64), width, height, format }
144
+ const result = await showMessageBox({ html: "<h1>Chart</h1>", render: true });
145
+ fs.writeFileSync("out.png", Buffer.from(result.render!.data!, "base64"));
146
+ ```
147
+
148
+ Notes:
149
+ - The capture fires after the page's `load` event + two `requestAnimationFrame`s + a settle delay (`renderDelay` option, default 100ms). If the page never fires `load`, msger captures best-effort at the timeout (default 30s in render mode).
150
+ - Width/height are physical pixels — a 400×300 window on a 125% DPI display captures at 500×375.
151
+ - `renderFormat` (`png`/`jpeg`/`bmp`) can be set explicitly in the API; the CLI derives it from the file extension.
152
+ - A failed capture rejects the promise (CLI: error on stderr, nonzero exit).
153
+ - msgview supports the same `-render` option via Electron's `capturePage()` on all its platforms.
154
+
121
155
  ## JSON Configuration Files
122
156
 
123
157
  You can store message box configuration in a JSON file for reuse. JSON files support comments (JSON5 format).
@@ -290,6 +324,9 @@ Options:
290
324
  -fullscreen, --fullscreen Start window in fullscreen mode (F11 to toggle, Escape to exit)
291
325
  -raw, --raw With -html: load HTML as-is, no msger template/buttons (alias: -full)
292
326
  -debug, --debug Return debug info (HTML, size, autoSize) in result
327
+ -render, --render [file] Render the page to a bitmap instead of displaying it.
328
+ With <file>, writes the image (.png/.jpg/.bmp by extension);
329
+ without, returns it base64 in result.render. [Windows only]
293
330
  -noshow, --noshow Don't display the window. With -save, exits after saving.
294
331
  Otherwise the WebView still loads + runs scripts so a page
295
332
  can post a result via window.ipc.postMessage; only the
@@ -432,6 +469,10 @@ interface MessageBoxOptions {
432
469
  rawHtml?: boolean; // With html: load HTML as-is, no msger template/buttons (used by mdview). CLI: -raw
433
470
  debug?: boolean; // Return debug info (HTML, size, autoSize) in result
434
471
  noResult?: boolean; // Suppress JSON result on stdout (CLI: -noresult, -result re-enables)
472
+ render?: boolean | string; // Render to a bitmap instead of displaying. String = file to write
473
+ // (format from extension), true = base64 object in result.render.
474
+ renderDelay?: number; // Render mode: ms between page load and capture (default 100)
475
+ renderFormat?: string; // Render mode: png (default) | jpeg | bmp
435
476
  }
436
477
  ```
437
478
 
@@ -460,6 +501,14 @@ interface MessageBoxResult {
460
501
  height: number; // Window height
461
502
  autoSize: boolean; // Whether auto-sizing is enabled
462
503
  };
504
+ render?: { // Captured screenshot (render mode)
505
+ format: string; // png | jpeg | bmp
506
+ width: number; // Physical pixels (includes DPI scale)
507
+ height: number;
508
+ data?: string; // Base64 image bytes (render: true)
509
+ path?: string; // Absolute path of written file (render: "file")
510
+ };
511
+ renderError?: string; // Why render produced no image (the promise rejects with this)
463
512
  }
464
513
  ```
465
514
 
package/cli.js CHANGED
@@ -40,6 +40,12 @@ Options:
40
40
  -ontop Keep window always on top of other windows
41
41
  -load <file> Load options from JSON file (supports comments)
42
42
  -save <file> Save current options to JSON file
43
+ -render [file] Render to a bitmap instead of displaying a window. Waits for
44
+ the page to load, captures a screenshot, and exits — nothing
45
+ appears on screen. With <file>, writes the image there (format
46
+ from extension: .png/.jpg/.bmp) and the JSON result carries
47
+ {render:{path,width,height,format}}. Without <file>, the image
48
+ is returned base64 in {render:{data,...}}. [Windows only]
43
49
  -noshow Don't display the window. With -save, also exits after saving.
44
50
  Otherwise the WebView still loads and runs scripts (so a page can
45
51
  query the DOM/CSS and post a result via window.ipc.postMessage)
@@ -70,6 +76,10 @@ Examples:
70
76
  echo -e "\\x1b[31mError:\\x1b[0m Something failed" | msger
71
77
  echo '{"message":"Test","buttons":["OK"]}' | msger
72
78
 
79
+ # Render a page to an image without showing a window
80
+ msger -url "https://example.com" -size 1024,768 -render page.png
81
+ msger -html "<h1>Chart</h1>" -render chart.png
82
+
73
83
  # Headless evaluation — use the WebView as a CSS/JS engine, no UI
74
84
  msger -noshow -html "<div id=x style='color:contrast-color(steelblue)'></div><script>requestAnimationFrame(()=>requestAnimationFrame(()=>window.ipc.postMessage(JSON.stringify({button:'x',value:getComputedStyle(document.getElementById('x')).color}))))</script>" -result value
75
85
 
package/index.d.ts CHANGED
@@ -1 +1 @@
1
- export { showMessageBox, showMessageBoxEx, MessageBoxHandle, closeMessageBox, showService, ServiceHandle, MessageBoxOptions, MessageBoxResult, setAppName, setAppIcon } from "./shower.js";
1
+ export { showMessageBox, showMessageBoxEx, MessageBoxHandle, closeMessageBox, showService, ServiceHandle, MessageBoxOptions, MessageBoxResult, RenderResult, setAppName, setAppIcon } from "./shower.js";
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/msger",
3
- "version": "0.1.387",
3
+ "version": "0.1.388",
4
4
  "description": "Fast, lightweight, cross-platform message box - Rust-powered alternative to msgview",
5
5
  "type": "module",
6
6
  "main": "./index.js",
package/shower.d.ts CHANGED
@@ -51,6 +51,19 @@ export interface MessageBoxOptions {
51
51
  service?: boolean; /** Service mode: bidirectional IPC with parent. Stdin/stdout stay open. */
52
52
  contentDir?: string; /** Base directory for custom protocol file serving (avoids file:// URLs) */
53
53
  hidden?: boolean; /** Create the window invisible. The WebView still renders so JS / CSS / layout queries (e.g. `getComputedStyle`, `contrast-color()`) resolve; the window just isn't mapped on screen. Set by the CLI's `-noshow` flag. */
54
+ render?: boolean | string; /** Render to a bitmap instead of displaying. The window stays hidden; after the page loads (plus renderDelay) a screenshot is captured and msger exits. A string is a file path to write — format from extension (.png/.jpg/.jpeg/.bmp), result.render = {format,width,height,path}. `true` returns the image in result.render = {format,width,height,data(base64)}. Windows (WebView2) only for now. */
55
+ renderDelay?: number; /** Render mode: settle delay in ms between the page's load event and the capture (fonts/images/async paint). Default 100. */
56
+ renderFormat?: string; /** Render mode image format: png (default), jpeg, or bmp. Derived from the file extension when `render` is a path; settable directly when `render: true`. */
57
+ }
58
+ /** Screenshot returned in MessageBoxResult.render (render mode). Width/height
59
+ * are physical pixels — the capture includes the monitor's DPI scale, so a
60
+ * 400x300 logical window on a 125% display comes back 500x375. */
61
+ export interface RenderResult {
62
+ format: string; /** png | jpeg | bmp */
63
+ width: number; /** Physical pixel width */
64
+ height: number; /** Physical pixel height */
65
+ data?: string; /** Base64 image bytes (render: true) */
66
+ path?: string; /** Absolute path of the written file (render: "file") */
54
67
  }
55
68
  export interface MessageBoxResult {
56
69
  button: string;
@@ -65,6 +78,8 @@ export interface MessageBoxResult {
65
78
  height: number;
66
79
  autoSize: boolean;
67
80
  };
81
+ render?: RenderResult; /** Captured screenshot (render mode) */
82
+ renderError?: string; /** Why render mode produced no image — the result promise rejects with this */
68
83
  }
69
84
  /** Set the app name used for the per-user bin dir AND the per-app exe filename.
70
85
  * Call before showService/showMessageBox — e.g. `setAppName("mailx")` makes
package/shower.js CHANGED
@@ -362,6 +362,13 @@ function resolveBinaryPath() {
362
362
  }
363
363
  function createMessageBoxHandle(options) {
364
364
  const binaryPath = resolveBinaryPath();
365
+ // Render mode: string = write the image to this file, true = return it
366
+ // base64 in result.render. Either way the image travels back as base64
367
+ // JSON on stdout, which detach mode throws away — so the combo is an error.
368
+ const renderTarget = typeof options.render === 'string' ? path.resolve(options.render) : undefined;
369
+ if (options.render && options.detach) {
370
+ throw new Error('render cannot be combined with detach — the image comes back on stdout, which detach discards');
371
+ }
365
372
  // Validate the binary up front and synchronously. Doing this inside the
366
373
  // Promise executor used to leave `child` undefined when the binary was
367
374
  // missing, then `new MessageBoxHandle(child, ...)` blew up on `child.pid`
@@ -421,6 +428,23 @@ function createMessageBoxHandle(options) {
421
428
  }
422
429
  try {
423
430
  const result = JSON.parse(stdout.trim());
431
+ if (options.render) {
432
+ if (result.renderError || !result.render?.data) {
433
+ reject(new Error(`render failed: ${result.renderError || 'no image in result'}`));
434
+ return;
435
+ }
436
+ if (renderTarget) {
437
+ // Write the file and hand back a path instead of the
438
+ // (large) base64 payload.
439
+ fs.writeFileSync(renderTarget, Buffer.from(result.render.data, 'base64'));
440
+ result.render = {
441
+ format: result.render.format,
442
+ width: result.render.width,
443
+ height: result.render.height,
444
+ path: renderTarget
445
+ };
446
+ }
447
+ }
424
448
  resolve(result);
425
449
  }
426
450
  catch (error) {
@@ -443,6 +467,19 @@ function createMessageBoxHandle(options) {
443
467
  if (optionsToSend.autoSize === undefined && !optionsToSend.size) {
444
468
  optionsToSend.autoSize = true;
445
469
  }
470
+ // Render mode: Rust wants a bare boolean plus a format string; the
471
+ // file path (if any) stays on this side. Force hidden so no window
472
+ // ever flashes on screen.
473
+ if (optionsToSend.render) {
474
+ if (renderTarget && !optionsToSend.renderFormat) {
475
+ const ext = path.extname(renderTarget).toLowerCase();
476
+ optionsToSend.renderFormat =
477
+ ext === '.bmp' ? 'bmp' :
478
+ (ext === '.jpg' || ext === '.jpeg') ? 'jpeg' : 'png';
479
+ }
480
+ optionsToSend.render = true;
481
+ optionsToSend.hidden = true;
482
+ }
446
483
  // Convert ANSI escape sequences to HTML if message is provided
447
484
  // ansi-to-html will escape HTML characters and convert ANSI codes to colored spans
448
485
  // Only do this if the message contains actual ANSI codes to preserve plain text as-is