@bobfrankston/msger 0.1.328 → 0.1.329
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
|
|
Binary file
|
|
Binary file
|
|
@@ -65,7 +65,16 @@ if (fs.existsSync(buildBinary)) {
|
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
// Step 2: Create timestamped copy in the user bin dir (NOT node_modules)
|
|
68
|
-
try {
|
|
68
|
+
try {
|
|
69
|
+
fs.mkdirSync(userBinDir, { recursive: true });
|
|
70
|
+
} catch (e) {
|
|
71
|
+
// recursive:true normally makes this idempotent, so a real failure here
|
|
72
|
+
// means the path can't be created at all (permissions, disk full, etc.).
|
|
73
|
+
if (e.code !== "EEXIST") {
|
|
74
|
+
console.error(` msger: could not create user bin dir ${userBinDir}: ${e.message}`);
|
|
75
|
+
throw e;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
69
78
|
const stamp = Date.now();
|
|
70
79
|
const versionedBinary = path.join(userBinDir, `${baseName}-${stamp}${ext}`);
|
|
71
80
|
if (fs.existsSync(srcBinary)) {
|
|
@@ -78,44 +87,113 @@ if (fs.existsSync(srcBinary)) {
|
|
|
78
87
|
|
|
79
88
|
// Step 2b: Create/update a stable-name hardlink so the launcher always uses
|
|
80
89
|
// the same path. Taskbar pins and AUMID grouping depend on a fixed exe path.
|
|
81
|
-
//
|
|
82
|
-
//
|
|
90
|
+
//
|
|
91
|
+
// Rename-aside pattern: Windows lets you rename a running exe's path but
|
|
92
|
+
// refuses to delete it. So we rename the current stable link aside (to a
|
|
93
|
+
// timestamped `.old-<ts>` name) before creating the new hardlink. This works
|
|
94
|
+
// whether or not the old binary is currently executing — the retired name
|
|
95
|
+
// keeps the running process's path valid; the stable slot is freed for the
|
|
96
|
+
// new hardlink. Previously we tried unlink+link, which silently failed when
|
|
97
|
+
// the stable name was locked, leaving users on a stale binary indefinitely.
|
|
83
98
|
const stableLink = path.join(userBinDir, `${baseName}${ext}`);
|
|
99
|
+
if (fs.existsSync(stableLink)) {
|
|
100
|
+
const retired = path.join(userBinDir, `${baseName}${ext}.old-${stamp}`);
|
|
101
|
+
try {
|
|
102
|
+
fs.renameSync(stableLink, retired);
|
|
103
|
+
} catch (e) {
|
|
104
|
+
console.error(` msger: could not rename stable link aside: ${e.message}`);
|
|
105
|
+
console.error(` msger: update cannot proceed — stable link ${stableLink} is in an unexpected state`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
84
108
|
try {
|
|
85
|
-
// Remove old link (may point to previous version)
|
|
86
|
-
try { fs.unlinkSync(stableLink); } catch { /* didn't exist */ }
|
|
87
109
|
fs.linkSync(versionedBinary, stableLink);
|
|
88
110
|
} catch (e) {
|
|
89
|
-
// Hardlink failed (cross-device,
|
|
90
|
-
|
|
111
|
+
// Hardlink failed (cross-device, weird FS). Copy as a fallback.
|
|
112
|
+
console.error(` msger: hardlink ${stableLink} -> ${versionedBinary} failed (${e.message}); falling back to copy`);
|
|
113
|
+
try {
|
|
114
|
+
fs.copyFileSync(versionedBinary, stableLink);
|
|
115
|
+
} catch (e2) {
|
|
116
|
+
console.error(` msger: copy fallback also failed: ${e2.message}`);
|
|
117
|
+
console.error(` msger: launcher will fall back to timestamped copies; taskbar pins may not group.`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Step 2c: Clean up retired `.old-<ts>` copies from previous updates. A file
|
|
122
|
+
// that's still locked by a not-yet-exited process stays behind and gets
|
|
123
|
+
// retried on the NEXT update — that's expected, so an EBUSY/EPERM on unlink
|
|
124
|
+
// isn't an error, but anything else is.
|
|
125
|
+
const oldPattern = new RegExp(`^${baseName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}${ext.replace('.', '\\.')}\\.old-\\d+$`);
|
|
126
|
+
try {
|
|
127
|
+
for (const f of fs.readdirSync(userBinDir)) {
|
|
128
|
+
if (!oldPattern.test(f)) continue;
|
|
129
|
+
const full = path.join(userBinDir, f);
|
|
130
|
+
try {
|
|
131
|
+
fs.unlinkSync(full);
|
|
132
|
+
} catch (e) {
|
|
133
|
+
if (e.code !== "EBUSY" && e.code !== "EPERM") {
|
|
134
|
+
console.error(` msger: unexpected error cleaning up ${f}: ${e.message}`);
|
|
135
|
+
}
|
|
136
|
+
// else: expected — owning process still running, defer to next update
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
} catch (e) {
|
|
140
|
+
console.error(` msger: could not scan ${userBinDir} for retired copies: ${e.message}`);
|
|
91
141
|
}
|
|
92
142
|
|
|
93
143
|
// Step 3a: Clean up old timestamped copies in the user bin dir (keep the one
|
|
94
|
-
// we just created).
|
|
95
|
-
//
|
|
144
|
+
// we just created). EBUSY/EPERM on a locked copy means the owning process is
|
|
145
|
+
// still running — expected, retry on next update. Anything else is unexpected
|
|
146
|
+
// and gets logged.
|
|
96
147
|
const pattern = new RegExp(`^${baseName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}-(\\d+)${ext.replace('.', '\\.')}$`);
|
|
97
148
|
try {
|
|
98
149
|
for (const f of fs.readdirSync(userBinDir)) {
|
|
99
|
-
if (f.match(pattern)
|
|
100
|
-
|
|
150
|
+
if (!f.match(pattern) || f === path.basename(versionedBinary)) continue;
|
|
151
|
+
const full = path.join(userBinDir, f);
|
|
152
|
+
try {
|
|
153
|
+
fs.unlinkSync(full);
|
|
154
|
+
} catch (e) {
|
|
155
|
+
if (e.code !== "EBUSY" && e.code !== "EPERM") {
|
|
156
|
+
console.error(` msger: unexpected error cleaning up ${f}: ${e.message}`);
|
|
157
|
+
}
|
|
101
158
|
}
|
|
102
159
|
}
|
|
103
|
-
} catch {
|
|
160
|
+
} catch (e) {
|
|
161
|
+
console.error(` msger: could not scan ${userBinDir} for stale timestamped copies: ${e.message}`);
|
|
162
|
+
}
|
|
104
163
|
|
|
105
164
|
// Step 3b: Migrate-and-clean any stale timestamped copies still living inside
|
|
106
165
|
// node_modules from older installs. They serve no purpose now and may be the
|
|
107
166
|
// EPERM blocker on upgrade.
|
|
108
167
|
try {
|
|
109
168
|
for (const f of fs.readdirSync(binDir)) {
|
|
110
|
-
if (f.match(pattern))
|
|
111
|
-
|
|
169
|
+
if (!f.match(pattern)) continue;
|
|
170
|
+
const full = path.join(binDir, f);
|
|
171
|
+
try {
|
|
172
|
+
fs.unlinkSync(full);
|
|
173
|
+
} catch (e) {
|
|
174
|
+
if (e.code !== "EBUSY" && e.code !== "EPERM") {
|
|
175
|
+
console.error(` msger: unexpected error cleaning up legacy ${f}: ${e.message}`);
|
|
176
|
+
}
|
|
112
177
|
}
|
|
113
178
|
}
|
|
114
|
-
} catch {
|
|
179
|
+
} catch (e) {
|
|
180
|
+
// binDir should always exist inside the package — if it doesn't, something
|
|
181
|
+
// is very wrong, but we can still let the install complete.
|
|
182
|
+
console.error(` msger: could not scan ${binDir} for legacy timestamped copies: ${e.message}`);
|
|
183
|
+
}
|
|
115
184
|
|
|
116
185
|
// Step 4: Set permissions on Linux/Mac
|
|
117
186
|
if (!isWindows) {
|
|
118
187
|
for (const bin of [srcBinary, versionedBinary]) {
|
|
119
|
-
try {
|
|
188
|
+
try {
|
|
189
|
+
fs.chmodSync(bin, 0o755);
|
|
190
|
+
} catch (e) {
|
|
191
|
+
// ENOENT = we never created that file (bin wasn't present); any
|
|
192
|
+
// other error (EACCES, EROFS, etc.) means the binary will fail to
|
|
193
|
+
// launch later, which is user-visible — log loud.
|
|
194
|
+
if (e.code !== "ENOENT") {
|
|
195
|
+
console.error(` msger: could not chmod ${bin}: ${e.message}`);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
120
198
|
}
|
|
121
199
|
}
|