home-hosted 0.7.14 → 0.7.16

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/dist/cli.js CHANGED
@@ -346,14 +346,95 @@ function withoutTrailingBlank(lines) {
346
346
  *
347
347
  * The current file plus its `.1` ancestor, so `logs` shows what happened just before a
348
348
  * rotation rather than starting blank. `lines <= 0` means the whole thing.
349
+ *
350
+ * Reads only the tail it needs. The log is capped at 5 MB per file *by rotation*, so a panel
351
+ * that has been up a while keeps a 5 MB current file plus a 5 MB `.1` — and the old version
352
+ * read both in full, split every line and threw all but the last fifty away: 24 ms and 28 MB
353
+ * of heap to print fifty lines, on the *default* invocation of a diagnostic command. Most of
354
+ * a tail is not looked at, so it is not read.
349
355
  */
350
356
  function readLog(file, lines) {
357
+ if (lines <= 0) return withoutTrailingBlank(readBoth(file).join("").split("\n"));
358
+ const tail = tailText(file, lines);
359
+ if (tail === null) return withoutTrailingBlank(readBoth(file).join("").split("\n")).slice(-lines);
360
+ return withoutTrailingBlank(tail.split("\n")).slice(-lines);
361
+ }
362
+ /** Both sides of the rotation, oldest first. Missing files contribute nothing. */
363
+ function readBoth(file) {
351
364
  const parts = [];
352
365
  for (const candidate of [`${file}.1`, file]) try {
353
366
  parts.push(fs.readFileSync(candidate, "utf8"));
354
367
  } catch {}
355
- const all = withoutTrailingBlank(parts.join("").split("\n"));
356
- return lines > 0 ? all.slice(-lines) : all;
368
+ return parts;
369
+ }
370
+ /**
371
+ * The text of the last `lines` lines across the rotation, joined the way concatenating the
372
+ * two files would join it — or `null` when the tail cannot be reached within the block budget,
373
+ * in which case the caller reads everything.
374
+ *
375
+ * Concatenating `.1` and the current file makes `.1`'s unterminated last line and the current
376
+ * file's first line *one* line. Keeping the two tails as text and joining them reproduces that
377
+ * for free: no gluing to arrange, and no line to lose. (An earlier draft spliced the arrays
378
+ * instead and dropped that line.)
379
+ */
380
+ function tailText(file, lines) {
381
+ const wanted = lines + 1;
382
+ const current = readTail(file, wanted);
383
+ if (current === null) return null;
384
+ if (countNewlines(current) >= wanted) return current;
385
+ const rotated = readTail(`${file}.1`, wanted);
386
+ if (rotated === null) return null;
387
+ return `${rotated}${current}`;
388
+ }
389
+ /**
390
+ * The last `lines` lines of one file, as text — read backwards in blocks, stopping as soon as
391
+ * enough line separators have been seen. `''` when there is nothing to contribute, `null` when
392
+ * the file cannot be read at all (which the caller answers by reading everything).
393
+ *
394
+ * The first element may be a fragment: a block boundary lands mid-line. It is deliberately
395
+ * left in place. The loop only exits once `lines + 1` separators are present, so at least
396
+ * `lines` complete lines follow the fragment and the caller's `slice(-lines)` can never reach
397
+ * it — trimming it would be code no test could make fail, and a randomised comparison against
398
+ * the previous implementation confirmed removing it changes nothing.
399
+ */
400
+ function readTail(file, lines) {
401
+ let handle;
402
+ try {
403
+ handle = fs.openSync(file, "r");
404
+ } catch {
405
+ return "";
406
+ }
407
+ try {
408
+ const size = fs.fstatSync(handle).size;
409
+ if (size === 0) return "";
410
+ const block = 65536;
411
+ let end = size;
412
+ let text = "";
413
+ while (end > 0 && countNewlines(text) < lines + 1) {
414
+ const start = Math.max(0, end - block);
415
+ const length = end - start;
416
+ const buffer = Buffer$1.alloc(length);
417
+ fs.readSync(handle, buffer, 0, length, start);
418
+ text = buffer.toString("utf8") + text;
419
+ end = start;
420
+ }
421
+ return text;
422
+ } catch {
423
+ return null;
424
+ } finally {
425
+ fs.closeSync(handle);
426
+ }
427
+ }
428
+ /**
429
+ * The last `lines` lines of one file, without reading the whole of it.
430
+ *
431
+ * Reads backwards in blocks and stops as soon as it has enough newlines. A single line can
432
+ * still be long, so the block grows rather than the read being capped at the exact byte.
433
+ */
434
+ function countNewlines(text) {
435
+ let count = 0;
436
+ for (let index = 0; index < text.length; index += 1) if (text.charCodeAt(index) === 10) count += 1;
437
+ return count;
357
438
  }
358
439
  /**
359
440
  * Prints the last `lines` of the log, then keeps printing whatever is appended.
@@ -388,7 +469,7 @@ async function followLog(file, lines, write, pollMs = 250) {
388
469
  const length = size - offset;
389
470
  const buffer = Buffer$1.alloc(length);
390
471
  fs.readSync(handle, buffer, 0, length, offset);
391
- write(buffer.toString("utf8").replace(/\n+$/, "\n"));
472
+ write(buffer.toString("utf8"));
392
473
  } finally {
393
474
  fs.closeSync(handle);
394
475
  }