@hncode/hncode 0.5.2 → 0.5.4

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/src/update.js CHANGED
@@ -166,6 +166,28 @@ export function readUpdateStamp() {
166
166
  try { return JSON.parse(fs.readFileSync(updateStampFile(), 'utf8')); } catch { return null; }
167
167
  }
168
168
 
169
+ // npm's own stderr from the background install, beside the stamp. A FILE because
170
+ // the install outlives this process: a pipe belonged to hncode, and quitting
171
+ // mid-install closed it under npm — the install died on EPIPE partway through.
172
+ function updateLogFile() {
173
+ const dir = process.env.HNCODE_HOME || path.join(os.homedir(), '.hncode');
174
+ return path.join(dir, 'update-log.txt');
175
+ }
176
+
177
+ /** Last `n` bytes of a file, or ''. Never throws. */
178
+ function tailOfFile(p, n = 4000) {
179
+ try {
180
+ const st = fs.statSync(p);
181
+ const fh = fs.openSync(p, 'r');
182
+ try {
183
+ const start = Math.max(0, st.size - n);
184
+ const buf = Buffer.alloc(st.size - start);
185
+ fs.readSync(fh, buf, 0, buf.length, start);
186
+ return buf.toString('utf8');
187
+ } finally { fs.closeSync(fh); }
188
+ } catch { return ''; }
189
+ }
190
+
169
191
  /** Forget it, once it has been reported. Never throws. */
170
192
  export function clearUpdateStamp() {
171
193
  try { fs.unlinkSync(updateStampFile()); return true; } catch { return false; }
@@ -202,13 +224,23 @@ export function updateBackground(version = '') {
202
224
  return;
203
225
  }
204
226
 
227
+ // stderr lands in a file (survives this process); when the file cannot be
228
+ // opened, fall back to a pipe and the in-memory capture below.
229
+ const logFile = updateLogFile();
230
+ let logFd = null;
231
+ try {
232
+ fs.mkdirSync(path.dirname(logFile), { recursive: true });
233
+ logFd = fs.openSync(logFile, 'w');
234
+ } catch { logFd = null; }
235
+
205
236
  let child;
206
237
  try {
207
238
  child = spawnNpm(['install', '-g', `${PKG_NAME}@latest`], {
208
239
  detached: true,
209
- stdio: ['ignore', 'ignore', 'pipe'],
240
+ stdio: ['ignore', 'ignore', logFd == null ? 'pipe' : logFd],
210
241
  });
211
242
  } catch (e) {
243
+ if (logFd != null) { try { fs.closeSync(logFd); } catch { /* already gone */ } }
212
244
  releaseInstallLock();
213
245
  const error = (e && e.message) || String(e);
214
246
  writeUpdateStamp({ status: 'error', version, error });
@@ -217,7 +249,7 @@ export function updateBackground(version = '') {
217
249
  }
218
250
 
219
251
  let stderr = '';
220
- if (child.stderr) {
252
+ if (logFd == null && child.stderr) {
221
253
  child.stderr.setEncoding('utf8');
222
254
  child.stderr.on('data', (d) => {
223
255
  stderr += d;
@@ -230,17 +262,20 @@ export function updateBackground(version = '') {
230
262
  // it prevents.
231
263
  child.on('error', (e) => {
232
264
  releaseInstallLock();
265
+ if (logFd != null) { try { fs.closeSync(logFd); } catch { /* already gone */ } }
233
266
  writeUpdateStamp({ status: 'error', version, error: (e && e.message) || String(e) });
234
267
  });
235
268
  child.on('close', (code) => {
236
269
  releaseInstallLock();
270
+ if (logFd != null) { try { fs.closeSync(logFd); } catch { /* already gone */ } }
237
271
  // Exit code 0 is the only thing that means the files were replaced. Anything else —
238
272
  // no permission, an unreachable registry, a killed process — is recorded with npm's
239
273
  // own last words, which is what makes the next launch's message actionable.
240
274
  if (code === 0) {
241
275
  writeUpdateStamp({ status: 'installed', version });
242
276
  } else {
243
- const tail = stderr.trim().split(/\r?\n/).slice(-6).join('\n');
277
+ const raw = logFd != null ? tailOfFile(logFile) : stderr;
278
+ const tail = raw.trim().split(/\r?\n/).slice(-6).join('\n');
244
279
  writeUpdateStamp({ status: 'error', version, code, error: tail || `npm exited ${code}` });
245
280
  }
246
281
  });