@bobfrankston/msger 0.1.411 → 0.1.413

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/cli.js CHANGED
@@ -3,9 +3,14 @@
3
3
  * msger CLI - uses universal CLI from msgcommon
4
4
  */
5
5
  import { runCli } from '@bobfrankston/msgcommon';
6
- import { showMessageBox } from './shower.js';
6
+ import { showMessageBox, installLinuxDeps } from './shower.js';
7
7
  import packageJson from './package.json' with { type: 'json' };
8
8
  import path from 'path';
9
+ // Handled before runCli: this is a setup command, not a message box, and it
10
+ // has to work on a machine where msger cannot start at all.
11
+ if (process.argv.slice(2).some(a => a === '--install-deps' || a === '-install-deps')) {
12
+ process.exit(installLinuxDeps());
13
+ }
9
14
  const HELP_TEXT = `
10
15
  msger v${packageJson.version} - Fast, lightweight, cross-platform message box (Rust-powered)
11
16
 
@@ -23,6 +28,7 @@ Options:
23
28
  -url <url|file|dir> Load URL directly in webview (no template, no buttons)
24
29
  A local file or directory works too; a directory loads its index.html
25
30
  -hash <fragment> Hash fragment to append to URL (requires -url). Leading # optional.
31
+ --install-deps Linux: install the WebKitGTK runtime msger needs (uses sudo)
26
32
  -buttons <label...> Specify button labels (e.g., -buttons Yes No Cancel)
27
33
  -ok Add OK button
28
34
  -cancel Add Cancel button
Binary file
@@ -13,6 +13,7 @@
13
13
  */
14
14
  import fs from "fs";
15
15
  import path from "path";
16
+ import { spawnSync } from "child_process";
16
17
  import { fileURLToPath } from "url";
17
18
 
18
19
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
@@ -202,6 +203,84 @@ if (!isWindows) {
202
203
  }
203
204
  }
204
205
 
206
+ // Step 5: Linux — make sure the WebKitGTK runtime msger links against exists.
207
+ //
208
+ // msger renders with the SYSTEM webview (that's why it's 7 MB, not 300), so
209
+ // without WebKitGTK the binary cannot start at all — the loader fails with a
210
+ // bare "libwebkit2gtk-4.1.so.0: cannot open shared object file" and exit 127,
211
+ // which reads like a broken msger rather than a missing dependency (Bob
212
+ // 2026-07-31, fresh Pi 4 on a Lite image; a full desktop image already has it).
213
+ //
214
+ // Install it automatically ONLY when that can't hurt or hang:
215
+ // - we already have root (a global npm install on the Pi does) or passwordless
216
+ // sudo, so nothing escalates and nothing prompts;
217
+ // - apt-get is present (the scriptable front end);
218
+ // - MSGER_SKIP_DEPS isn't set.
219
+ // Anything else — unprivileged install, sudo that would ask for a password,
220
+ // unknown distro — falls back to telling the user. An npm script must never
221
+ // block on a password prompt: unattended installs would hang forever.
222
+ if (!isWindows && process.platform === "linux") {
223
+ const soNames = ["libwebkit2gtk-4.1.so.0", "libjavascriptcoregtk-4.1.so.0"];
224
+ const libDirs = [
225
+ "/usr/lib", "/usr/local/lib", "/lib",
226
+ `/usr/lib/${process.arch === "arm64" ? "aarch64" : "x86_64"}-linux-gnu`,
227
+ ];
228
+ const haveWebkit = () => soNames.every(so =>
229
+ libDirs.some(dir => { try { return fs.existsSync(path.join(dir, so)); } catch { return false; } })
230
+ );
231
+
232
+ if (!haveWebkit()) {
233
+ const isRoot = typeof process.getuid === "function" && process.getuid() === 0;
234
+ const hasAptGet = fs.existsSync("/usr/bin/apt-get");
235
+ // `sudo -n true` succeeds only when sudo needs no password.
236
+ const sudoNoPassword = !isRoot && hasAptGet &&
237
+ spawnSync("sudo", ["-n", "true"], { stdio: "ignore" }).status === 0;
238
+ const canInstall = hasAptGet && (isRoot || sudoNoPassword) && process.env.MSGER_SKIP_DEPS !== "1";
239
+
240
+ if (canInstall) {
241
+ const prefix = isRoot ? [] : ["sudo", "-n"];
242
+ const args = [...prefix, "apt-get", "install", "-y", "libwebkit2gtk-4.1-0"];
243
+ console.log("");
244
+ console.log(" msger: WebKitGTK runtime missing — installing it now:");
245
+ console.log(` ${args.join(" ")}`);
246
+ const run = (a) => spawnSync(a[0], a.slice(1), {
247
+ stdio: "inherit",
248
+ // -y alone isn't enough; some packages still ask without this.
249
+ env: { ...process.env, DEBIAN_FRONTEND: "noninteractive" },
250
+ });
251
+ let res = run(args);
252
+ if (res.status !== 0) {
253
+ // Usual cause is a stale package index (404 on the .deb).
254
+ console.log(" msger: retrying after apt-get update…");
255
+ run([...prefix, "apt-get", "update"]);
256
+ res = run(args);
257
+ }
258
+ if (res.status === 0 && haveWebkit()) {
259
+ console.log(" msger: WebKitGTK installed — msger is ready to run.");
260
+ console.log("");
261
+ } else {
262
+ console.log("");
263
+ console.log(" msger: WARNING — automatic install of WebKitGTK failed.");
264
+ console.log(" Run this by hand, then msger will work:");
265
+ console.log("");
266
+ console.log(" sudo apt install libwebkit2gtk-4.1-0");
267
+ console.log("");
268
+ }
269
+ } else {
270
+ console.log("");
271
+ console.log(" msger: WARNING — the WebKitGTK runtime was not found.");
272
+ console.log(" msger renders with the system webview and cannot");
273
+ console.log(" start without it. Install it with:");
274
+ console.log("");
275
+ console.log(" sudo apt install libwebkit2gtk-4.1-0");
276
+ console.log(" or: msger --install-deps");
277
+ console.log("");
278
+ console.log(" (runtime only; -dev is needed just to build msger)");
279
+ console.log("");
280
+ }
281
+ }
282
+ }
283
+
205
284
  // Friendly message about the EPERM warnings npm itself emits during a
206
285
  // subsequent global upgrade. They come from npm trying to delete its own
207
286
  // staging directory while the previously-running msgernative.exe inside
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/msger",
3
- "version": "0.1.411",
3
+ "version": "0.1.413",
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
@@ -95,6 +95,23 @@ export declare function setAppName(name: string): void;
95
95
  * provision so Rust's window icon search picks it up. Per-call `icon` in
96
96
  * `MessageBoxOptions` still takes precedence. */
97
97
  export declare function setAppIcon(iconPath: string): void;
98
+ /** The right package for this distro. Only the runtime library — building
99
+ * msger additionally needs the -dev package, which is not msger's business to
100
+ * install on a machine that only runs it. */
101
+ export declare function webkitInstallCommand(): string;
102
+ /** `msger --install-deps` — install the WebKitGTK runtime msger links against.
103
+ *
104
+ * Deliberately NOT done from postinstall: an npm script that shells out to
105
+ * `sudo apt` escalates privilege behind the user's back, blocks forever on a
106
+ * password prompt in any non-interactive install (CI, containers, unattended
107
+ * provisioning), and guesses wrong on distros it doesn't know. postinstall
108
+ * detects and tells; installing stays an explicit, interactive choice.
109
+ *
110
+ * Returns the process exit code to use. */
111
+ export declare function installLinuxDeps(): number;
112
+ /** Is the WebKitGTK runtime msger links against present? Mirrors the check in
113
+ * msger-native/builder/postinstall.js — keep the two in step. */
114
+ export declare function hasWebkitRuntime(): boolean;
98
115
  /**
99
116
  * Show a message box dialog using native Rust implementation (extended version)
100
117
  * @param options Message box configuration
package/shower.js CHANGED
@@ -1,4 +1,4 @@
1
- import { spawn, execFileSync } from 'child_process';
1
+ import { spawn, spawnSync, execFileSync } from 'child_process';
2
2
  import { platform } from 'os';
3
3
  import path from 'path';
4
4
  import { pathToFileURL } from 'url';
@@ -72,6 +72,92 @@ export function setAppName(name) { _appName = name; }
72
72
  * provision so Rust's window icon search picks it up. Per-call `icon` in
73
73
  * `MessageBoxOptions` still takes precedence. */
74
74
  export function setAppIcon(iconPath) { _appIcon = iconPath; }
75
+ /** The Linux/Pi binary dynamically links WebKitGTK, which a Lite or trimmed
76
+ * Raspberry Pi OS image doesn't ship — the loader then fails with a bare
77
+ * "libwebkit2gtk-4.1.so.0: cannot open shared object file" and exit 127, which
78
+ * reads like a broken msger rather than a missing dependency (Bob 2026-07-31,
79
+ * fresh pi4d). Turn it into the apt line that fixes it. */
80
+ function missingWebkitHint(stderr) {
81
+ if (!/libwebkit2gtk|libjavascriptcoregtk/.test(stderr))
82
+ return '';
83
+ return '\n\nmsger needs the WebKitGTK runtime, which this system does not have.\n' +
84
+ ` ${webkitInstallCommand()}\n` +
85
+ 'or let msger run it for you:\n' +
86
+ ' msger --install-deps\n' +
87
+ '(runtime only — the -dev package is needed just for building msger.)';
88
+ }
89
+ /** The right package for this distro. Only the runtime library — building
90
+ * msger additionally needs the -dev package, which is not msger's business to
91
+ * install on a machine that only runs it. */
92
+ export function webkitInstallCommand() {
93
+ const has = (cmd) => fs.existsSync(`/usr/bin/${cmd}`) || fs.existsSync(`/bin/${cmd}`);
94
+ if (has('apt'))
95
+ return 'sudo apt install libwebkit2gtk-4.1-0';
96
+ if (has('dnf'))
97
+ return 'sudo dnf install webkit2gtk4.1';
98
+ if (has('pacman'))
99
+ return 'sudo pacman -S webkit2gtk-4.1';
100
+ if (has('zypper'))
101
+ return 'sudo zypper install libwebkit2gtk-4_1-0';
102
+ return 'install the WebKitGTK 4.1 runtime for your distribution';
103
+ }
104
+ /** `msger --install-deps` — install the WebKitGTK runtime msger links against.
105
+ *
106
+ * Deliberately NOT done from postinstall: an npm script that shells out to
107
+ * `sudo apt` escalates privilege behind the user's back, blocks forever on a
108
+ * password prompt in any non-interactive install (CI, containers, unattended
109
+ * provisioning), and guesses wrong on distros it doesn't know. postinstall
110
+ * detects and tells; installing stays an explicit, interactive choice.
111
+ *
112
+ * Returns the process exit code to use. */
113
+ export function installLinuxDeps() {
114
+ if (platform() !== 'linux') {
115
+ console.error('--install-deps is for Linux; nothing to install on ' + platform());
116
+ return 1;
117
+ }
118
+ if (hasWebkitRuntime()) {
119
+ console.log('WebKitGTK runtime is already present — nothing to do.');
120
+ return 0;
121
+ }
122
+ const command = webkitInstallCommand();
123
+ if (!command.startsWith('sudo ')) {
124
+ console.error(`msger could not identify this distribution's package manager.\nPlease ${command}.`);
125
+ return 1;
126
+ }
127
+ console.log(`Running: ${command}`);
128
+ const [cmd, ...args] = command.split(' ');
129
+ // stdio inherit so sudo's password prompt actually reaches the terminal.
130
+ const res = spawnSync(cmd, args, { stdio: 'inherit' });
131
+ if (res.error) {
132
+ console.error(`Could not run ${cmd}: ${res.error.message}`);
133
+ return 1;
134
+ }
135
+ if (res.status !== 0)
136
+ return res.status ?? 1;
137
+ if (!hasWebkitRuntime()) {
138
+ console.error('Install reported success but the WebKitGTK runtime is still not visible.');
139
+ return 1;
140
+ }
141
+ console.log('WebKitGTK runtime installed — msger is ready to run.');
142
+ return 0;
143
+ }
144
+ /** Is the WebKitGTK runtime msger links against present? Mirrors the check in
145
+ * msger-native/builder/postinstall.js — keep the two in step. */
146
+ export function hasWebkitRuntime() {
147
+ if (platform() !== 'linux')
148
+ return true;
149
+ const soNames = ['libwebkit2gtk-4.1.so.0', 'libjavascriptcoregtk-4.1.so.0'];
150
+ const arch = process.arch === 'arm64' ? 'aarch64' : 'x86_64';
151
+ const libDirs = ['/usr/lib', '/usr/local/lib', '/lib', `/usr/lib/${arch}-linux-gnu`];
152
+ return soNames.every(so => libDirs.some(dir => {
153
+ try {
154
+ return fs.existsSync(path.join(dir, so));
155
+ }
156
+ catch {
157
+ return false;
158
+ }
159
+ }));
160
+ }
75
161
  /** Raspberry Pi: WebKitGTK's DMABUF rendering path is broken on the vc4/v3d
76
162
  * driver — the page comes up as interlaced shredded scanlines instead of
77
163
  * content (verified on a Pi 5 / Debian 13 / WebKitGTK 2.50.6 Wayland session,
@@ -454,7 +540,7 @@ function createMessageBoxHandle(options) {
454
540
  return;
455
541
  }
456
542
  if (code !== 0) {
457
- reject(new Error(`msger exited with code ${code}: ${stderr}`));
543
+ reject(new Error(`msger exited with code ${code}: ${stderr}${missingWebkitHint(stderr)}`));
458
544
  return;
459
545
  }
460
546
  try {
@@ -724,6 +810,11 @@ export function showService(options) {
724
810
  child.stderr?.on("data", (data) => {
725
811
  // Forward Rust stderr to parent stderr (debug messages)
726
812
  process.stderr.write(data);
813
+ // Service mode never inspects the exit code, so surface the
814
+ // missing-WebKitGTK case as it happens or it stays a bare loader error.
815
+ const hint = missingWebkitHint(data.toString());
816
+ if (hint)
817
+ process.stderr.write(hint + '\n');
727
818
  });
728
819
  child.stdin?.on("error", () => { });
729
820
  // Send options as first line of stdin (Rust reads one line for options, then keeps reading)