mikser-io 10.5.0 → 10.6.0
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/package.json +1 -1
- package/src/engine.js +10 -10
- package/src/instance.js +48 -6
- package/src/logger.js +77 -8
package/package.json
CHANGED
package/src/engine.js
CHANGED
|
@@ -25,8 +25,8 @@ import Queue from 'p-queue'
|
|
|
25
25
|
import packageInfo from '../package.json' with { type: 'json' }
|
|
26
26
|
import { attachServerCliOptions, setupServer } from './server.js'
|
|
27
27
|
import {
|
|
28
|
-
createMikserLogger,
|
|
29
|
-
|
|
28
|
+
createMikserLogger, rememberBaseLevel, applyLogRequest,
|
|
29
|
+
LOG_LEVELS, INSTALLED_LOG_TTL_MS,
|
|
30
30
|
} from './logger.js'
|
|
31
31
|
import { inputHashOf } from './utils.js'
|
|
32
32
|
import { createTrack, mergeTrack } from './track.js'
|
|
@@ -685,14 +685,14 @@ The full version, with what each code means: docs/diagnostics.md`)
|
|
|
685
685
|
// logger — the whole reason --debug did nothing.
|
|
686
686
|
runtime.options.info = true
|
|
687
687
|
const asked = runtime.options.log
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
688
|
+
// The same call the instance makes for a forwarded request — see
|
|
689
|
+
// applyLogRequest. Two implementations drifted within one commit.
|
|
690
|
+
const logRefusal = applyLogRequest({
|
|
691
|
+
log: asked,
|
|
692
|
+
logInstall: runtime.options.logInstall,
|
|
693
|
+
logReset: runtime.options.logReset,
|
|
694
|
+
})
|
|
695
|
+
if (logRefusal) throw new Error(logRefusal)
|
|
696
696
|
rememberBaseLevel(asked && LOG_LEVELS.includes(asked) ? asked : 'info')
|
|
697
697
|
|
|
698
698
|
// Resolve folders inside onInitialize so journal.js and
|
package/src/instance.js
CHANGED
|
@@ -38,7 +38,7 @@ import { onLoaded } from './lifecycle.js'
|
|
|
38
38
|
import { renderErrorCount } from './report.js'
|
|
39
39
|
import { runReportOnly } from './engine.js'
|
|
40
40
|
import {
|
|
41
|
-
setLogLevel,
|
|
41
|
+
setLogLevel, restingLogLevel, applyLogRequest, LOG_LEVELS,
|
|
42
42
|
} from './logger.js'
|
|
43
43
|
import { emitReport } from './report.js'
|
|
44
44
|
import { pluginOptionsFrom } from './cli.js'
|
|
@@ -240,6 +240,17 @@ function captureOutput(onChunk) {
|
|
|
240
240
|
// than by content hash — the hash would require the client to import its
|
|
241
241
|
// config, which is most of the startup forwarding exists to skip, and it is
|
|
242
242
|
// not what tells the two apart. Different configs are different files.
|
|
243
|
+
// Does this request name a level that does not exist? Pure, so the refusal
|
|
244
|
+
// can be decided before anything is applied.
|
|
245
|
+
function logRequestRefusal(request) {
|
|
246
|
+
for (const [flag, level] of [['--log', request?.log], ['--log-install', request?.logInstall]]) {
|
|
247
|
+
if (level !== undefined && level !== null && !LOG_LEVELS.includes(level)) {
|
|
248
|
+
return `${flag} ${level}: no such level. Levels: ${LOG_LEVELS.join(', ')}`
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return null
|
|
252
|
+
}
|
|
253
|
+
|
|
243
254
|
function configMismatch(theirs) {
|
|
244
255
|
if (!theirs) return null
|
|
245
256
|
const mine = path.resolve(runtime.options.config ?? 'mikser.config.js')
|
|
@@ -369,6 +380,26 @@ function refuseClear(socket, request) {
|
|
|
369
380
|
return true
|
|
370
381
|
}
|
|
371
382
|
|
|
383
|
+
// A level the instance cannot use.
|
|
384
|
+
//
|
|
385
|
+
// Validated where the request ARRIVES rather than where it is applied, because
|
|
386
|
+
// applying it is exactly what a bad level cannot do: setLogLevel returns false
|
|
387
|
+
// and, before this, nobody read it — so `--log chatty` exited 1 locally and
|
|
388
|
+
// built normally with a watcher up. The refusal frame is how the instance says
|
|
389
|
+
// no to everything else, and a client that mistypes a level deserves the same
|
|
390
|
+
// answer whether or not something happens to be listening.
|
|
391
|
+
function refuseLogLevel(socket, request) {
|
|
392
|
+
const refusal = logRequestRefusal(request)
|
|
393
|
+
if (!refusal) return false
|
|
394
|
+
frame(socket, {
|
|
395
|
+
type: 'refused',
|
|
396
|
+
reason: refusal,
|
|
397
|
+
detail: 'The instance would otherwise have built normally and said nothing, which is the '
|
|
398
|
+
+ 'forwarded-versus-local split this surface exists to remove.',
|
|
399
|
+
})
|
|
400
|
+
return true
|
|
401
|
+
}
|
|
402
|
+
|
|
372
403
|
// Which process to restart.
|
|
373
404
|
//
|
|
374
405
|
// "Restart it" is only actionable if you know which `it` — and the machine
|
|
@@ -428,15 +459,25 @@ async function withRequestOutput(request, run) {
|
|
|
428
459
|
// a watcher's own cycle before or after keeps the level it was running at.
|
|
429
460
|
// An installed level is different and deliberately not touched here — it
|
|
430
461
|
// outlives the request, which is its whole point.
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
for
|
|
462
|
+
// Captured BEFORE applyLogRequest, which writes `info`.
|
|
463
|
+
//
|
|
464
|
+
// `info` is here because --log silent turns the progress bar off through
|
|
465
|
+
// it, and without a restore one silent request left the instance with no
|
|
466
|
+
// bar for the rest of its life. Capturing after the call would have
|
|
467
|
+
// recorded the value the call just wrote and restored nothing — the same
|
|
468
|
+
// shape of mistake, one line further on.
|
|
469
|
+
for (const key of ['json', 'tool', 'tools', 'requested', 'info']) {
|
|
436
470
|
prior[key] = runtime.options[key]
|
|
437
471
|
if (request[key]) runtime.options[key] = request[key]
|
|
438
472
|
}
|
|
439
473
|
|
|
474
|
+
// The same call the argv path makes — see applyLogRequest. The level was
|
|
475
|
+
// validated at arrival by refuseLogLevel, so a refusal here is impossible;
|
|
476
|
+
// it is asserted rather than ignored, because "cannot happen" is how the
|
|
477
|
+
// first version of this silently accepted a bad level.
|
|
478
|
+
const refusal = applyLogRequest(request)
|
|
479
|
+
if (refusal) throw new Error(refusal)
|
|
480
|
+
|
|
440
481
|
// Whatever the client's argv said about a PLUGIN's options.
|
|
441
482
|
//
|
|
442
483
|
// The instance parsed its own argv and never saw the client's, so a flag a
|
|
@@ -558,6 +599,7 @@ export function serveInstance() {
|
|
|
558
599
|
// writes anything.
|
|
559
600
|
if (refuseUnknownFlags(socket, request)) return
|
|
560
601
|
if (refuseClear(socket, request)) return
|
|
602
|
+
if (refuseLogLevel(socket, request)) return
|
|
561
603
|
const wrongConfig = configMismatch(request.config)
|
|
562
604
|
if (wrongConfig) return refuseConfig(socket, request, wrongConfig)
|
|
563
605
|
const movedFile = await configStale()
|
package/src/logger.js
CHANGED
|
@@ -491,16 +491,49 @@ export function trackProgress(name, total) {
|
|
|
491
491
|
if (!name || !total) return
|
|
492
492
|
const logger = useLogger()
|
|
493
493
|
logger.debug('%s started: %d', name, total)
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
494
|
+
// The bar writes to stdout, and stdout is where --json and --tool put
|
|
495
|
+
// their DOCUMENT. Forwarded, those writes are captured and framed to the
|
|
496
|
+
// client, so the gauge landed inside the JSON:
|
|
497
|
+
// `^[[?25lDocuments import: >416/800` at byte 0, and JSON.parse threw —
|
|
498
|
+
// 4 runs in 10 at the default level on an 800-document corpus.
|
|
499
|
+
//
|
|
500
|
+
// Checked HERE rather than through runtime.options.info, because this is
|
|
501
|
+
// the actual invariant and info is a preference. A preference can be
|
|
502
|
+
// forgotten on a path; an invariant stated at the one place a bar starts
|
|
503
|
+
// cannot.
|
|
504
|
+
const carriesDocument = runtime.options?.json || runtime.options?.tool || runtime.options?.tools
|
|
505
|
+
|
|
506
|
+
// TRACKED always, DRAWN only where a bar belongs.
|
|
507
|
+
//
|
|
508
|
+
// The two used to be one decision, so anything that could not draw also
|
|
509
|
+
// stopped counting — and `stopProgress` returns early without a bar, which
|
|
510
|
+
// is where the "finished: N in Ns" line comes from. A piped build
|
|
511
|
+
// therefore reported no phase timings at all, and suppressing the bar for
|
|
512
|
+
// --json would have extended that to every machine reading the output.
|
|
513
|
+
// Losing the graphics is the point; losing the information is not.
|
|
514
|
+
const drawn = !carriesDocument && Boolean(process.stdout.isTTY) && Boolean(runtime.options.info)
|
|
515
|
+
currentBar = { name, total, value: 0, started: Date.now(), drawn, milestone: 0 }
|
|
516
|
+
if (drawn) ensureGauge().show({ section: name, subsection: `0/${total}` }, 0)
|
|
517
|
+
else logger.info({ code: 'progress', phase: name, total, value: 0 }, '%s: 0/%d', name, total)
|
|
497
518
|
}
|
|
498
519
|
|
|
499
520
|
export function updateProgress() {
|
|
500
521
|
if (!currentBar) return
|
|
501
522
|
currentBar.value++
|
|
502
|
-
const { name, total, value } = currentBar
|
|
503
|
-
|
|
523
|
+
const { name, total, value, drawn } = currentBar
|
|
524
|
+
if (drawn) {
|
|
525
|
+
gauge?.show({ section: name, subsection: `${value}/${total}` }, value / total)
|
|
526
|
+
} else {
|
|
527
|
+
// Quartiles, not every item: a bar redraws in place and costs one
|
|
528
|
+
// line, a log record does not. 800 documents is 800 lines of noise if
|
|
529
|
+
// this counts the way the bar does.
|
|
530
|
+
const reached = Math.floor((value / total) * 4)
|
|
531
|
+
if (reached > currentBar.milestone && value < total) {
|
|
532
|
+
currentBar.milestone = reached
|
|
533
|
+
useLogger()?.info({ code: 'progress', phase: name, total, value },
|
|
534
|
+
'%s: %d/%d', name, value, total)
|
|
535
|
+
}
|
|
536
|
+
}
|
|
504
537
|
if (value >= total) stopProgress()
|
|
505
538
|
}
|
|
506
539
|
|
|
@@ -509,11 +542,15 @@ export function stopProgress() {
|
|
|
509
542
|
const logger = useLogger()
|
|
510
543
|
const { name, total, value, started } = currentBar
|
|
511
544
|
gauge?.hide()
|
|
545
|
+
// Structured either way, so a machine reading --json's stderr gets the
|
|
546
|
+
// same facts a person reads off the bar.
|
|
512
547
|
if (value < total) {
|
|
513
|
-
logger.warn(
|
|
548
|
+
logger.warn({ code: 'progress-unfinished', phase: name, total, value, missing: total - value },
|
|
549
|
+
'%s unfinished: %d', name, total - value)
|
|
514
550
|
} else {
|
|
515
|
-
const
|
|
516
|
-
logger.info(
|
|
551
|
+
const ms = Date.now() - started
|
|
552
|
+
logger.info({ code: 'progress-finished', phase: name, total, ms },
|
|
553
|
+
'%s finished: %d %ds', name, total, Math.round(ms / 1000))
|
|
517
554
|
}
|
|
518
555
|
currentBar = null
|
|
519
556
|
}
|
|
@@ -528,6 +565,38 @@ export function updateProgressDetails(details) {
|
|
|
528
565
|
)
|
|
529
566
|
}
|
|
530
567
|
|
|
568
|
+
// What a caller asked for about logging, applied the same way from argv and
|
|
569
|
+
// from a forwarded request.
|
|
570
|
+
//
|
|
571
|
+
// It was written twice and the copies drifted immediately: the argv path threw
|
|
572
|
+
// on an unknown level and set `info`, the forwarded path called setLogLevel and
|
|
573
|
+
// ignored the false it returns. So `--log chatty` exited 1 locally and built
|
|
574
|
+
// normally with a watcher up, and `--log silent` left the progress bar running
|
|
575
|
+
// on an instance. The same forwarded/local split --json and --force each had,
|
|
576
|
+
// and this feature's own argument against itself: a flag that lies is worse
|
|
577
|
+
// than a flag that is missing.
|
|
578
|
+
//
|
|
579
|
+
// Returns an error STRING rather than throwing, because the two callers need
|
|
580
|
+
// different things from a failure — argv throws, the instance refuses over the
|
|
581
|
+
// socket — and a shared implementation should not decide that for them.
|
|
582
|
+
export function applyLogRequest({ log, logInstall, logReset } = {}) {
|
|
583
|
+
for (const [flag, level] of [['--log', log], ['--log-install', logInstall]]) {
|
|
584
|
+
if (level !== undefined && level !== null && !LOG_LEVELS.includes(level)) {
|
|
585
|
+
return `${flag} ${level}: no such level. Levels: ${LOG_LEVELS.join(', ')}`
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
if (logReset) resetLogLevel()
|
|
589
|
+
if (logInstall) installLogLevel(logInstall)
|
|
590
|
+
if (log) setLogLevel(log)
|
|
591
|
+
|
|
592
|
+
// A bar on top of debug output is noise, and silent means silent.
|
|
593
|
+
const level = log || logInstall
|
|
594
|
+
if (level === 'trace' || level === 'debug' || level === 'silent') {
|
|
595
|
+
runtime.options.info = false
|
|
596
|
+
}
|
|
597
|
+
return null
|
|
598
|
+
}
|
|
599
|
+
|
|
531
600
|
// An installed level, disclosed and expired, once per cycle.
|
|
532
601
|
//
|
|
533
602
|
// Disclosed for the reason an installed command is: it is state left on a live
|