@bobfrankston/msger 0.1.320 → 0.1.322

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
@@ -2,9 +2,14 @@
2
2
  /**
3
3
  * msger-native postinstall:
4
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)
5
+ * 2. Create timestamped copy in the PER-USER bin dir (not node_modules)
6
+ * so the locked previous-version exe doesn't block npm's aside-dir cleanup
7
+ * on upgrade — npm only manages the stable msgernative.exe in bin/.
8
+ * 3. Clean up old timestamped copies in the user dir (skip if locked)
7
9
  * 4. Set binary permissions on Linux/Mac
10
+ *
11
+ * NOTE: keep this in sync with `getUserBinDir()` in shower.ts — same path
12
+ * convention so the launcher finds what postinstall just wrote.
8
13
  */
9
14
  import fs from "fs";
10
15
  import path from "path";
@@ -32,6 +37,18 @@ if (isWindows) {
32
37
  const srcBinary = path.join(binDir, `${baseName}${ext}`);
33
38
  const buildBinary = path.join(nativeDir, "target", "release", `${baseName}${ext}`);
34
39
 
40
+ function getUserBinDir() {
41
+ if (isWindows) {
42
+ const base = process.env.LOCALAPPDATA || path.join(process.env.USERPROFILE || ".", "AppData", "Local");
43
+ return path.join(base, "msger", "bin");
44
+ }
45
+ const xdg = process.env.XDG_DATA_HOME;
46
+ const base = xdg || path.join(process.env.HOME || ".", ".local", "share");
47
+ return path.join(base, "msger", "bin");
48
+ }
49
+
50
+ const userBinDir = getUserBinDir();
51
+
35
52
  // Step 1: Copy from target/release/ → bin/ if build output is newer
36
53
  if (fs.existsSync(buildBinary)) {
37
54
  const buildTime = fs.statSync(buildBinary).mtimeMs;
@@ -47,9 +64,10 @@ if (fs.existsSync(buildBinary)) {
47
64
  }
48
65
  }
49
66
 
50
- // Step 2: Create timestamped copy
67
+ // Step 2: Create timestamped copy in the user bin dir (NOT node_modules)
68
+ try { fs.mkdirSync(userBinDir, { recursive: true }); } catch { /* exists */ }
51
69
  const stamp = Date.now();
52
- const versionedBinary = path.join(binDir, `${baseName}-${stamp}${ext}`);
70
+ const versionedBinary = path.join(userBinDir, `${baseName}-${stamp}${ext}`);
53
71
  if (fs.existsSync(srcBinary)) {
54
72
  try {
55
73
  fs.copyFileSync(srcBinary, versionedBinary);
@@ -58,12 +76,25 @@ if (fs.existsSync(srcBinary)) {
58
76
  }
59
77
  }
60
78
 
61
- // Step 3: Clean up old timestamped copies (keep the one we just created)
79
+ // Step 3a: Clean up old timestamped copies in the user bin dir (keep the one
80
+ // we just created). Locked copies are silently skipped — they get cleaned up
81
+ // on the next install once their owning process has exited.
82
+ const pattern = new RegExp(`^${baseName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}-(\\d+)${ext.replace('.', '\\.')}$`);
62
83
  try {
63
- const pattern = new RegExp(`^${baseName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}-(\\d+)${ext.replace('.', '\\.')}$`);
64
- for (const f of fs.readdirSync(binDir)) {
84
+ for (const f of fs.readdirSync(userBinDir)) {
65
85
  if (f.match(pattern) && f !== path.basename(versionedBinary)) {
66
- try { fs.unlinkSync(path.join(binDir, f)); } catch { /* locked — cleaned up next time */ }
86
+ try { fs.unlinkSync(path.join(userBinDir, f)); } catch { /* locked */ }
87
+ }
88
+ }
89
+ } catch { /* user dir didn't exist or unreadable */ }
90
+
91
+ // Step 3b: Migrate-and-clean any stale timestamped copies still living inside
92
+ // node_modules from older installs. They serve no purpose now and may be the
93
+ // EPERM blocker on upgrade.
94
+ try {
95
+ for (const f of fs.readdirSync(binDir)) {
96
+ if (f.match(pattern)) {
97
+ try { fs.unlinkSync(path.join(binDir, f)); } catch { /* locked */ }
67
98
  }
68
99
  }
69
100
  } catch { /* ignore */ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/msger",
3
- "version": "0.1.320",
3
+ "version": "0.1.322",
4
4
  "description": "Fast, lightweight, cross-platform message box - Rust-powered alternative to msgview",
5
5
  "type": "module",
6
6
  "main": "./index.js",
@@ -25,8 +25,7 @@
25
25
  "prerelease:local": "git add -A && (git diff-index --quiet HEAD || git commit -m \"Pre-release commit\")",
26
26
  "preversion": "npm run build:ts && npm run build:native && git add -A",
27
27
  "release": "npm run prerelease:local && npm version patch && npm publish --quiet",
28
- "installer": "npm run release && node utils/install-latest.ts",
29
- "postversion": "git push && git push --tags"
28
+ "installer": "npm run release && node utils/install-latest.ts"
30
29
  },
31
30
  "keywords": [
32
31
  "message-box",
package/shower.js CHANGED
@@ -47,12 +47,31 @@ export function closeMessageBox(pid) {
47
47
  * @param options Message box configuration
48
48
  * @returns MessageBoxHandle with result promise
49
49
  */
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. */
50
+ /** Per-user bin dir for timestamped exe copies. Storing them OUTSIDE
51
+ * node_modules avoids npm's EPERM cleanup warnings on upgrade npm tries to
52
+ * delete the previous package's `.msger-<rand>/` aside-dir but the running
53
+ * exe inside it is locked. Keeping timestamped copies here means npm only
54
+ * manages the stable `msgernative.exe` (which isn't the one being executed),
55
+ * so it's never locked. */
56
+ function getUserBinDir() {
57
+ const isWindows = platform() === 'win32';
58
+ if (isWindows) {
59
+ const base = process.env.LOCALAPPDATA || path.join(process.env.USERPROFILE || '.', 'AppData', 'Local');
60
+ return path.join(base, 'msger', 'bin');
61
+ }
62
+ const xdg = process.env.XDG_DATA_HOME;
63
+ const base = xdg || path.join(process.env.HOME || '.', '.local', 'share');
64
+ return path.join(base, 'msger', 'bin');
65
+ }
66
+ /** Resolve the native binary path. Prefers a timestamped copy in the per-user
67
+ * bin dir (immune to npm-upgrade locks). Falls back to the package's bundled
68
+ * unversioned binary when no per-user copy exists yet — first launch after a
69
+ * fresh install before postinstall has copied one out. */
52
70
  function resolveBinaryPath() {
53
71
  const isWindows = platform() === 'win32';
54
72
  const arch = process.arch;
55
- const binDir = path.join(import.meta.dirname, 'msger-native', 'bin');
73
+ const pkgBinDir = path.join(import.meta.dirname, 'msger-native', 'bin');
74
+ const userBinDir = getUserBinDir();
56
75
  let baseName;
57
76
  let ext;
58
77
  if (isWindows) {
@@ -67,18 +86,29 @@ function resolveBinaryPath() {
67
86
  baseName = 'msgernative';
68
87
  ext = '';
69
88
  }
70
- // Find the latest timestamped binary, fall back to unversioned
89
+ const pattern = new RegExp(`^${baseName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}-(\\d+)${ext.replace('.', '\\.')}$`);
90
+ // Prefer per-user timestamped copy
91
+ try {
92
+ const matches = fs.readdirSync(userBinDir)
93
+ .filter(f => pattern.test(f))
94
+ .sort()
95
+ .reverse();
96
+ if (matches.length > 0)
97
+ return path.join(userBinDir, matches[0]);
98
+ }
99
+ catch { /* user dir doesn't exist yet — fall through */ }
100
+ // Legacy: timestamped copies inside the package (older installs that ran
101
+ // the old postinstall). Still honored so a half-migrated tree keeps working.
71
102
  try {
72
- const pattern = new RegExp(`^${baseName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}-(\\d+)${ext.replace('.', '\\.')}$`);
73
- const matches = fs.readdirSync(binDir)
103
+ const matches = fs.readdirSync(pkgBinDir)
74
104
  .filter(f => pattern.test(f))
75
105
  .sort()
76
106
  .reverse();
77
107
  if (matches.length > 0)
78
- return path.join(binDir, matches[0]);
108
+ return path.join(pkgBinDir, matches[0]);
79
109
  }
80
110
  catch { /* fall through */ }
81
- return path.join(binDir, `${baseName}${ext}`);
111
+ return path.join(pkgBinDir, `${baseName}${ext}`);
82
112
  }
83
113
  function createMessageBoxHandle(options) {
84
114
  const binaryPath = resolveBinaryPath();