@bobfrankston/msger 0.1.198 → 0.1.200

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.
Binary file
@@ -1,34 +1,76 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * msger-native postinstall — set binary permissions on Linux/Mac.
4
- * Uses @bobfrankston/rust-builder if available, falls back to direct chmod.
3
+ * msger-native postinstall:
4
+ * 1. Copy binary from target/release/ to bin/ (if newer after cargo build)
5
+ * 2. Create timestamped copy (so new installs don't conflict with running old version)
6
+ * 3. Clean up old timestamped copies (skip if locked by running process)
7
+ * 4. Set binary permissions on Linux/Mac
5
8
  */
9
+ import fs from "fs";
6
10
  import path from "path";
7
11
  import { fileURLToPath } from "url";
8
12
 
9
13
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
10
- const binDir = path.join(__dirname, "..", "bin");
14
+ const nativeDir = path.join(__dirname, "..");
15
+ const binDir = path.join(nativeDir, "bin");
11
16
 
12
- const binaries = {
13
- win32: "msgernative.exe",
14
- darwin: "msgernative",
15
- darwinArm64: "msgernative-arm64",
16
- linux: "msgernative",
17
- linuxArm64: "msgernative-linux-aarch64",
18
- };
17
+ const isWindows = process.platform === "win32";
18
+ const arch = process.arch;
19
19
 
20
+ let baseName, ext;
21
+ if (isWindows) {
22
+ baseName = "msgernative";
23
+ ext = ".exe";
24
+ } else if (process.platform === "darwin") {
25
+ baseName = arch === "arm64" ? "msgernative-arm64" : "msgernative";
26
+ ext = "";
27
+ } else {
28
+ baseName = arch === "arm64" ? "msgernative-linux-aarch64" : "msgernative";
29
+ ext = "";
30
+ }
31
+
32
+ const srcBinary = path.join(binDir, `${baseName}${ext}`);
33
+ const buildBinary = path.join(nativeDir, "target", "release", `${baseName}${ext}`);
34
+
35
+ // Step 1: Copy from target/release/ → bin/ if build output is newer
36
+ if (fs.existsSync(buildBinary)) {
37
+ const buildTime = fs.statSync(buildBinary).mtimeMs;
38
+ const srcTime = fs.existsSync(srcBinary) ? fs.statSync(srcBinary).mtimeMs : 0;
39
+ if (buildTime > srcTime) {
40
+ try {
41
+ fs.copyFileSync(buildBinary, srcBinary);
42
+ console.log(` msger: updated ${baseName}${ext} from build`);
43
+ } catch (e) {
44
+ // Source might be locked by running process — that's OK, timestamped copy will use what we have
45
+ console.log(` msger: could not update ${baseName}${ext} (locked?) — using existing`);
46
+ }
47
+ }
48
+ }
49
+
50
+ // Step 2: Create timestamped copy
51
+ const stamp = Date.now();
52
+ const versionedBinary = path.join(binDir, `${baseName}-${stamp}${ext}`);
53
+ if (fs.existsSync(srcBinary)) {
54
+ try {
55
+ fs.copyFileSync(srcBinary, versionedBinary);
56
+ } catch (e) {
57
+ console.error(` msger: failed to create versioned binary: ${e.message}`);
58
+ }
59
+ }
60
+
61
+ // Step 3: Clean up old timestamped copies (keep the one we just created)
20
62
  try {
21
- const { runPostinstall } = await import("@bobfrankston/rust-builder/postinstall");
22
- runPostinstall({ binaryName: "msgernative", binDir, binaries });
23
- } catch {
24
- // rust-builder not available (npm consumer install)do chmod directly
25
- if (process.platform !== "win32") {
26
- const fs = await import("fs");
27
- const arch = process.arch;
28
- const name = process.platform === "darwin"
29
- ? (arch === "arm64" ? binaries.darwinArm64 : binaries.darwin)
30
- : (arch === "arm64" ? binaries.linuxArm64 : binaries.linux);
31
- const bin = path.join(binDir, name);
32
- try { fs.chmodSync(bin, 0o755); } catch { /* binary may not exist yet */ }
63
+ const pattern = new RegExp(`^${baseName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}-(\\d+)${ext.replace('.', '\\.')}$`);
64
+ for (const f of fs.readdirSync(binDir)) {
65
+ if (f.match(pattern) && f !== path.basename(versionedBinary)) {
66
+ try { fs.unlinkSync(path.join(binDir, f)); } catch { /* locked cleaned up next time */ }
67
+ }
68
+ }
69
+ } catch { /* ignore */ }
70
+
71
+ // Step 4: Set permissions on Linux/Mac
72
+ if (!isWindows) {
73
+ for (const bin of [srcBinary, versionedBinary]) {
74
+ try { fs.chmodSync(bin, 0o755); } catch { /* */ }
33
75
  }
34
76
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/msger",
3
- "version": "0.1.198",
3
+ "version": "0.1.200",
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
@@ -41,8 +41,10 @@ export interface MessageBoxOptions {
41
41
  debug?: boolean; /** Return debug information (HTML, size) in result (default: false) */
42
42
  showVersion?: boolean; /** Show version in window title (default: false) */
43
43
  appUserModelId?: string; /** Windows AppUserModelID for taskbar pinning (internal use) */
44
- initScript?: string; /** Custom JS injected into WebView alongside msger-api.js */
44
+ initScript?: string; /** Custom JS injected into WebView alongside msger-api.js (inline) */
45
+ initScriptPath?: string; /** Path to JS file injected into WebView (avoids large JSON) */
45
46
  service?: boolean; /** Service mode: bidirectional IPC with parent. Stdin/stdout stay open. */
47
+ contentDir?: string; /** Base directory for custom protocol file serving (avoids file:// URLs) */
46
48
  }
47
49
  export interface MessageBoxResult {
48
50
  button: string;
package/shower.js CHANGED
@@ -47,21 +47,41 @@ export function closeMessageBox(pid) {
47
47
  * @param options Message box configuration
48
48
  * @returns MessageBoxHandle with result promise
49
49
  */
50
- function createMessageBoxHandle(options) {
50
+ /** Resolve the native binary path — uses timestamped name (e.g., msgernative-1712345678901.exe)
51
+ * so a new version can be installed while the old one is still running. */
52
+ function resolveBinaryPath() {
51
53
  const isWindows = platform() === 'win32';
52
54
  const arch = process.arch;
53
- // Determine the binary name based on platform and architecture
54
- let binaryName;
55
+ const binDir = path.join(import.meta.dirname, 'msger-native', 'bin');
56
+ let baseName;
57
+ let ext;
55
58
  if (isWindows) {
56
- binaryName = 'msgernative.exe';
59
+ baseName = 'msgernative';
60
+ ext = '.exe';
57
61
  }
58
62
  else if (arch === 'arm64') {
59
- binaryName = 'msgernative-linux-aarch64';
63
+ baseName = 'msgernative-linux-aarch64';
64
+ ext = '';
60
65
  }
61
66
  else {
62
- binaryName = 'msgernative';
67
+ baseName = 'msgernative';
68
+ ext = '';
69
+ }
70
+ // Find the latest timestamped binary, fall back to unversioned
71
+ try {
72
+ const pattern = new RegExp(`^${baseName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}-(\\d+)${ext.replace('.', '\\.')}$`);
73
+ const matches = fs.readdirSync(binDir)
74
+ .filter(f => pattern.test(f))
75
+ .sort()
76
+ .reverse();
77
+ if (matches.length > 0)
78
+ return path.join(binDir, matches[0]);
63
79
  }
64
- const binaryPath = path.join(import.meta.dirname, 'msger-native', 'bin', binaryName);
80
+ catch { /* fall through */ }
81
+ return path.join(binDir, `${baseName}${ext}`);
82
+ }
83
+ function createMessageBoxHandle(options) {
84
+ const binaryPath = resolveBinaryPath();
65
85
  // Declare variables that will be used outside the Promise
66
86
  const childEnv = { ...process.env };
67
87
  if ('NODE_OPTIONS' in childEnv) {
@@ -77,7 +97,7 @@ function createMessageBoxHandle(options) {
77
97
  return;
78
98
  }
79
99
  // On Unix systems, check if binary is executable
80
- if (!isWindows) {
100
+ if (platform() !== 'win32') {
81
101
  try {
82
102
  fs.accessSync(binaryPath, fs.constants.X_OK);
83
103
  }
@@ -316,16 +336,7 @@ export class ServiceHandle {
316
336
  */
317
337
  export function showService(options) {
318
338
  const serviceOptions = { ...options, service: true, detach: false };
319
- const isWindows = platform() === "win32";
320
- const arch = process.arch;
321
- let binaryName;
322
- if (isWindows)
323
- binaryName = "msgernative.exe";
324
- else if (arch === "arm64")
325
- binaryName = "msgernative-linux-aarch64";
326
- else
327
- binaryName = "msgernative";
328
- const binaryPath = path.join(import.meta.dirname, "msger-native", "bin", binaryName);
339
+ const binaryPath = resolveBinaryPath();
329
340
  const childEnv = { ...process.env };
330
341
  if ("NODE_OPTIONS" in childEnv)
331
342
  delete childEnv.NODE_OPTIONS;
@@ -345,14 +356,20 @@ export function showService(options) {
345
356
  rustOptions.height = rustOptions.size.height;
346
357
  delete rustOptions.size;
347
358
  }
348
- // Convert local paths to file:// URLs (same as createMessageBoxHandle)
349
- if (rustOptions.url && !rustOptions.url.startsWith("http://") && !rustOptions.url.startsWith("https://") && !rustOptions.url.startsWith("file://")) {
359
+ // Convert local paths to file:// URLs when no custom protocol (contentDir) is used
360
+ if (!rustOptions.contentDir && rustOptions.url && !rustOptions.url.startsWith("http://") && !rustOptions.url.startsWith("https://") && !rustOptions.url.startsWith("file://")) {
350
361
  const absolutePath = path.resolve(rustOptions.url);
351
362
  if (!fs.existsSync(absolutePath)) {
352
363
  throw new Error(`File not found: ${absolutePath}`);
353
364
  }
354
365
  rustOptions.url = pathToFileURL(absolutePath).href;
355
366
  }
356
- child.stdin?.write(JSON.stringify(rustOptions) + "\n");
367
+ // Resolve contentDir to absolute path
368
+ if (rustOptions.contentDir) {
369
+ rustOptions.contentDir = path.resolve(rustOptions.contentDir);
370
+ }
371
+ const jsonToSend = JSON.stringify(rustOptions);
372
+ console.error(`[service] Sending to Rust: ${jsonToSend.slice(0, 200)}`);
373
+ child.stdin?.write(jsonToSend + "\n");
357
374
  return new ServiceHandle(child);
358
375
  }