mikser-io 10.5.0 → 10.7.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 +40 -10
- package/src/instance.js +80 -9
- package/src/journal.js +1 -2
- package/src/logger.js +132 -14
- package/src/plugins/files.js +1 -1
- package/src/plugins/observer.js +2 -2
- package/src/plugins/resources.js +2 -2
- package/src/source.js +1 -1
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
|
|
@@ -1593,6 +1593,16 @@ The full version, with what each code means: docs/diagnostics.md`)
|
|
|
1593
1593
|
// undefined here. The logger has no such problem — it writes during the
|
|
1594
1594
|
// run, by which time options exist.
|
|
1595
1595
|
const quietStdout = ['--json', '--tool', '--tools'].some(flag => process.argv.includes(flag))
|
|
1596
|
+
// The banner is an info record, so a level above info must not print it.
|
|
1597
|
+
//
|
|
1598
|
+
// `--log silent` printed the version line and nothing else — the one line
|
|
1599
|
+
// the flag most obviously promises to remove — because this runs before
|
|
1600
|
+
// the level is applied. Only the FORWARDED path was ever silent, and that
|
|
1601
|
+
// is the path the test exercises, so the test could not see it.
|
|
1602
|
+
//
|
|
1603
|
+
// argv again, for the reason quietStdout reads it: commander parses in a
|
|
1604
|
+
// lifecycle hook, which runs after setup() returns.
|
|
1605
|
+
if (!argvWantsInfo()) return runtime
|
|
1596
1606
|
// Through the logger when nobody is watching, so it gets a timestamp.
|
|
1597
1607
|
//
|
|
1598
1608
|
// This line marks a process start, which in a supervisor's log is the
|
|
@@ -1625,6 +1635,26 @@ The full version, with what each code means: docs/diagnostics.md`)
|
|
|
1625
1635
|
return runtime
|
|
1626
1636
|
}
|
|
1627
1637
|
|
|
1638
|
+
// The level asked for on the command line, before commander has parsed.
|
|
1639
|
+
// Both spellings, both flags: `--log warn` and `--log=warn`, and
|
|
1640
|
+
// `--log-install`, which on a run with no instance to forward to sets the
|
|
1641
|
+
// level for this process.
|
|
1642
|
+
function argvLogLevel() {
|
|
1643
|
+
for (const flag of ['--log', '--log-install', '-l']) {
|
|
1644
|
+
const at = process.argv.indexOf(flag)
|
|
1645
|
+
if (at >= 0 && process.argv[at + 1]) return process.argv[at + 1]
|
|
1646
|
+
const inline = process.argv.find(arg => arg.startsWith(`${flag}=`))
|
|
1647
|
+
if (inline) return inline.slice(flag.length + 1)
|
|
1648
|
+
}
|
|
1649
|
+
return null
|
|
1650
|
+
}
|
|
1651
|
+
|
|
1652
|
+
function argvWantsInfo() {
|
|
1653
|
+
const level = argvLogLevel()
|
|
1654
|
+
if (!level || !LOG_LEVELS.includes(level)) return true
|
|
1655
|
+
return LOG_LEVELS.indexOf(level) <= LOG_LEVELS.indexOf('info')
|
|
1656
|
+
}
|
|
1657
|
+
|
|
1628
1658
|
export function useLogger() {
|
|
1629
1659
|
return runtime.engine?.logger
|
|
1630
1660
|
}
|
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'
|
|
@@ -218,10 +218,29 @@ let server = null
|
|
|
218
218
|
// both into one undifferentiated stream throws that away, and the client can
|
|
219
219
|
// only guess — it guessed stderr, so every forwarded document landed where no
|
|
220
220
|
// consumer looks while the command exited 0.
|
|
221
|
-
|
|
221
|
+
// `echoStdout: false` frames the chunk to the client and writes NOTHING
|
|
222
|
+
// locally.
|
|
223
|
+
//
|
|
224
|
+
// The instance wears the client's output so an operator watching it sees what
|
|
225
|
+
// a forwarded request did — right for log lines, wrong for a DOCUMENT. A
|
|
226
|
+
// --json request put its 364-line report into the instance's own console as
|
|
227
|
+
// well as onto the client's stdout, and under pm2 that is 364 lines in the out
|
|
228
|
+
// log for every report an agent asks for.
|
|
229
|
+
//
|
|
230
|
+
// The seam is exact rather than a guess: under --json / --tool the logger
|
|
231
|
+
// writes to stderr and stdout carries only the document (see logger.js), so
|
|
232
|
+
// stdout chunks during a document-carrying request ARE the document.
|
|
233
|
+
function captureOutput(onChunk, { echoStdout = true } = {}) {
|
|
222
234
|
const originals = [process.stdout.write, process.stderr.write]
|
|
223
235
|
const patch = (stream, original, name) => function (chunk, encoding, callback) {
|
|
224
236
|
try { onChunk(typeof chunk === 'string' ? chunk : chunk.toString(), name) } catch { /* client gone */ }
|
|
237
|
+
if (!echoStdout && name === 'stdout') {
|
|
238
|
+
// write()'s callback is the second argument when encoding is
|
|
239
|
+
// omitted. Dropped, a caller awaiting the drain never resumes.
|
|
240
|
+
const done = typeof encoding === 'function' ? encoding : callback
|
|
241
|
+
if (typeof done === 'function') process.nextTick(done)
|
|
242
|
+
return true
|
|
243
|
+
}
|
|
225
244
|
return original.call(stream, chunk, encoding, callback)
|
|
226
245
|
}
|
|
227
246
|
process.stdout.write = patch(process.stdout, originals[0], 'stdout')
|
|
@@ -240,6 +259,23 @@ function captureOutput(onChunk) {
|
|
|
240
259
|
// than by content hash — the hash would require the client to import its
|
|
241
260
|
// config, which is most of the startup forwarding exists to skip, and it is
|
|
242
261
|
// not what tells the two apart. Different configs are different files.
|
|
262
|
+
// Does this request name a level that does not exist? Pure, so the refusal
|
|
263
|
+
// can be decided before anything is applied.
|
|
264
|
+
// Does this request's answer go on stdout as a document?
|
|
265
|
+
// The same trio logger.js gates the progress bar on, for the same reason.
|
|
266
|
+
function carriesDocument(request) {
|
|
267
|
+
return Boolean(request?.json || request?.tool || request?.tools)
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function logRequestRefusal(request) {
|
|
271
|
+
for (const [flag, level] of [['--log', request?.log], ['--log-install', request?.logInstall]]) {
|
|
272
|
+
if (level !== undefined && level !== null && !LOG_LEVELS.includes(level)) {
|
|
273
|
+
return `${flag} ${level}: no such level. Levels: ${LOG_LEVELS.join(', ')}`
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
return null
|
|
277
|
+
}
|
|
278
|
+
|
|
243
279
|
function configMismatch(theirs) {
|
|
244
280
|
if (!theirs) return null
|
|
245
281
|
const mine = path.resolve(runtime.options.config ?? 'mikser.config.js')
|
|
@@ -285,7 +321,9 @@ async function configStale() {
|
|
|
285
321
|
// the only process that can answer correctly. Same guards as a build: wrong
|
|
286
322
|
// config refuses, drifted config refuses.
|
|
287
323
|
async function serveReport(socket, request, logger) {
|
|
288
|
-
const restore = captureOutput(
|
|
324
|
+
const restore = captureOutput(
|
|
325
|
+
(chunk, stream) => frame(socket, { type: 'log', chunk, stream }),
|
|
326
|
+
{ echoStdout: !carriesDocument(request) })
|
|
289
327
|
let code = 0
|
|
290
328
|
try {
|
|
291
329
|
code = await withRequestOutput(request, () => runReportOnly(request)) ?? 0
|
|
@@ -369,6 +407,26 @@ function refuseClear(socket, request) {
|
|
|
369
407
|
return true
|
|
370
408
|
}
|
|
371
409
|
|
|
410
|
+
// A level the instance cannot use.
|
|
411
|
+
//
|
|
412
|
+
// Validated where the request ARRIVES rather than where it is applied, because
|
|
413
|
+
// applying it is exactly what a bad level cannot do: setLogLevel returns false
|
|
414
|
+
// and, before this, nobody read it — so `--log chatty` exited 1 locally and
|
|
415
|
+
// built normally with a watcher up. The refusal frame is how the instance says
|
|
416
|
+
// no to everything else, and a client that mistypes a level deserves the same
|
|
417
|
+
// answer whether or not something happens to be listening.
|
|
418
|
+
function refuseLogLevel(socket, request) {
|
|
419
|
+
const refusal = logRequestRefusal(request)
|
|
420
|
+
if (!refusal) return false
|
|
421
|
+
frame(socket, {
|
|
422
|
+
type: 'refused',
|
|
423
|
+
reason: refusal,
|
|
424
|
+
detail: 'The instance would otherwise have built normally and said nothing, which is the '
|
|
425
|
+
+ 'forwarded-versus-local split this surface exists to remove.',
|
|
426
|
+
})
|
|
427
|
+
return true
|
|
428
|
+
}
|
|
429
|
+
|
|
372
430
|
// Which process to restart.
|
|
373
431
|
//
|
|
374
432
|
// "Restart it" is only actionable if you know which `it` — and the machine
|
|
@@ -428,15 +486,25 @@ async function withRequestOutput(request, run) {
|
|
|
428
486
|
// a watcher's own cycle before or after keeps the level it was running at.
|
|
429
487
|
// An installed level is different and deliberately not touched here — it
|
|
430
488
|
// outlives the request, which is its whole point.
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
for
|
|
489
|
+
// Captured BEFORE applyLogRequest, which writes `info`.
|
|
490
|
+
//
|
|
491
|
+
// `info` is here because --log silent turns the progress bar off through
|
|
492
|
+
// it, and without a restore one silent request left the instance with no
|
|
493
|
+
// bar for the rest of its life. Capturing after the call would have
|
|
494
|
+
// recorded the value the call just wrote and restored nothing — the same
|
|
495
|
+
// shape of mistake, one line further on.
|
|
496
|
+
for (const key of ['json', 'tool', 'tools', 'requested', 'info']) {
|
|
436
497
|
prior[key] = runtime.options[key]
|
|
437
498
|
if (request[key]) runtime.options[key] = request[key]
|
|
438
499
|
}
|
|
439
500
|
|
|
501
|
+
// The same call the argv path makes — see applyLogRequest. The level was
|
|
502
|
+
// validated at arrival by refuseLogLevel, so a refusal here is impossible;
|
|
503
|
+
// it is asserted rather than ignored, because "cannot happen" is how the
|
|
504
|
+
// first version of this silently accepted a bad level.
|
|
505
|
+
const refusal = applyLogRequest(request)
|
|
506
|
+
if (refusal) throw new Error(refusal)
|
|
507
|
+
|
|
440
508
|
// Whatever the client's argv said about a PLUGIN's options.
|
|
441
509
|
//
|
|
442
510
|
// The instance parsed its own argv and never saw the client's, so a flag a
|
|
@@ -460,7 +528,9 @@ async function withRequestOutput(request, run) {
|
|
|
460
528
|
}
|
|
461
529
|
|
|
462
530
|
async function serveBuild(socket, request, logger) {
|
|
463
|
-
const restore = captureOutput(
|
|
531
|
+
const restore = captureOutput(
|
|
532
|
+
(chunk, stream) => frame(socket, { type: 'log', chunk, stream }),
|
|
533
|
+
{ echoStdout: !carriesDocument(request) })
|
|
464
534
|
let code = 0
|
|
465
535
|
try {
|
|
466
536
|
// Fire the pending debounce rather than waiting it out.
|
|
@@ -558,6 +628,7 @@ export function serveInstance() {
|
|
|
558
628
|
// writes anything.
|
|
559
629
|
if (refuseUnknownFlags(socket, request)) return
|
|
560
630
|
if (refuseClear(socket, request)) return
|
|
631
|
+
if (refuseLogLevel(socket, request)) return
|
|
561
632
|
const wrongConfig = configMismatch(request.config)
|
|
562
633
|
if (wrongConfig) return refuseConfig(socket, request, wrongConfig)
|
|
563
634
|
const movedFile = await configStale()
|
package/src/journal.js
CHANGED
|
@@ -227,9 +227,8 @@ export async function* useJournal(name, operations, signal) {
|
|
|
227
227
|
stopProgress()
|
|
228
228
|
throw new AbortError()
|
|
229
229
|
}
|
|
230
|
-
updateProgress()
|
|
231
|
-
|
|
232
230
|
const entry = rowToEntry(row)
|
|
231
|
+
updateProgress(entry.entity?.id)
|
|
233
232
|
const originalEntity = row.entity // already a JSON string
|
|
234
233
|
|
|
235
234
|
yield entry
|
package/src/logger.js
CHANGED
|
@@ -28,6 +28,7 @@ import pino from 'pino'
|
|
|
28
28
|
import pretty from 'pino-pretty'
|
|
29
29
|
import Gauge from 'gauge'
|
|
30
30
|
import { Writable } from 'node:stream'
|
|
31
|
+
import path from 'node:path'
|
|
31
32
|
import runtime from './runtime.js'
|
|
32
33
|
import { useLogger } from './engine.js'
|
|
33
34
|
import { onLoad, onFinalized } from './lifecycle.js'
|
|
@@ -482,25 +483,103 @@ onLoad(() => {
|
|
|
482
483
|
runtime.engine.logger = createMikserLogger(level)
|
|
483
484
|
})
|
|
484
485
|
|
|
485
|
-
// Progress API
|
|
486
|
-
//
|
|
487
|
-
//
|
|
488
|
-
|
|
486
|
+
// Progress API: trackProgress / updateProgress / stopProgress /
|
|
487
|
+
// updateProgressDetails. A gauge where a terminal is watching, coded records
|
|
488
|
+
// where one is not, and nothing at all for a phase that took no time.
|
|
489
|
+
|
|
490
|
+
// Long phases only, and long means TIME.
|
|
491
|
+
//
|
|
492
|
+
// A record per quartile is right for 800 documents and absurd for 5. Eleven
|
|
493
|
+
// of a plain build's thirteen phases carry exactly five items, so reporting
|
|
494
|
+
// them the way a bar counts took a piped no-op build from 32 lines to 97 —
|
|
495
|
+
// sixty-five progress records, five in the same millisecond to say a phase
|
|
496
|
+
// did nothing. That is written into the log a deployment keeps, which is the
|
|
497
|
+
// log the installed-level expiry exists to protect.
|
|
498
|
+
//
|
|
499
|
+
// A minimum TOTAL would fix the count and get the other half wrong: four PDFs
|
|
500
|
+
// through Chrome is a small phase and a slow one, and it is exactly the phase
|
|
501
|
+
// worth narrating. Elapsed time is what "long" means, it needs no per-phase
|
|
502
|
+
// tuning, and it applies to the drawn path too — `finished: 5 0s` is the same
|
|
503
|
+
// non-information in a terminal, and its own `0s` says so.
|
|
504
|
+
export const PROGRESS_MIN_MS = 1000
|
|
505
|
+
|
|
506
|
+
// Which item, not just how far. A phase name alone says a build is doing
|
|
507
|
+
// something; the thing a stuck build needs to say is WHAT it is stuck on.
|
|
508
|
+
function progressDetail(detail) {
|
|
509
|
+
if (detail === null || detail === undefined || detail === '') return null
|
|
510
|
+
const text = String(detail)
|
|
511
|
+
const root = runtime.options?.workingFolder
|
|
512
|
+
if (!root || !path.isAbsolute(text)) return text
|
|
513
|
+
// An entity id LOOKS absolute and is not a filesystem path.
|
|
514
|
+
// `/documents/p3706.md` relativised against the working folder becomes
|
|
515
|
+
// `../../../documents/p3706.md`, which names nothing anyone can act on.
|
|
516
|
+
// So shorten only what is genuinely INSIDE the folder, and leave every
|
|
517
|
+
// other string exactly as the call site handed it over.
|
|
518
|
+
const relative = path.relative(root, text)
|
|
519
|
+
return relative.startsWith('..') ? text : relative
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
function emitProgress({ name, total, value, detail }) {
|
|
523
|
+
const at = progressDetail(detail)
|
|
524
|
+
const fields = { code: 'progress', phase: name, total, value }
|
|
525
|
+
if (at) useLogger()?.info({ ...fields, detail: at }, '%s: %d/%d — %s', name, value, total, at)
|
|
526
|
+
else useLogger()?.info(fields, '%s: %d/%d', name, value, total)
|
|
527
|
+
}
|
|
489
528
|
|
|
490
529
|
export function trackProgress(name, total) {
|
|
491
530
|
if (!name || !total) return
|
|
492
531
|
const logger = useLogger()
|
|
493
532
|
logger.debug('%s started: %d', name, total)
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
533
|
+
// The bar writes to stdout, and stdout is where --json and --tool put
|
|
534
|
+
// their DOCUMENT. Forwarded, those writes are captured and framed to the
|
|
535
|
+
// client, so the gauge landed inside the JSON:
|
|
536
|
+
// `^[[?25lDocuments import: >416/800` at byte 0, and JSON.parse threw —
|
|
537
|
+
// 4 runs in 10 at the default level on an 800-document corpus.
|
|
538
|
+
//
|
|
539
|
+
// Checked HERE rather than through runtime.options.info, because this is
|
|
540
|
+
// the actual invariant and info is a preference. A preference can be
|
|
541
|
+
// forgotten on a path; an invariant stated at the one place a bar starts
|
|
542
|
+
// cannot.
|
|
543
|
+
const carriesDocument = runtime.options?.json || runtime.options?.tool || runtime.options?.tools
|
|
544
|
+
|
|
545
|
+
// TRACKED always, DRAWN only where a bar belongs.
|
|
546
|
+
//
|
|
547
|
+
// The two used to be one decision, so anything that could not draw also
|
|
548
|
+
// stopped counting — and `stopProgress` returns early without a bar, which
|
|
549
|
+
// is where the "finished: N in Ns" line comes from. A piped build
|
|
550
|
+
// therefore reported no phase timings at all, and suppressing the bar for
|
|
551
|
+
// --json would have extended that to every machine reading the output.
|
|
552
|
+
// Losing the graphics is the point; losing the information is not.
|
|
553
|
+
const drawn = !carriesDocument && Boolean(process.stdout.isTTY) && Boolean(runtime.options.info)
|
|
554
|
+
currentBar = { name, total, value: 0, started: Date.now(), drawn, milestone: 0, detail: null }
|
|
555
|
+
if (drawn) ensureGauge().show({ section: name, subsection: `0/${total}` }, 0)
|
|
497
556
|
}
|
|
498
557
|
|
|
499
|
-
export function updateProgress() {
|
|
558
|
+
export function updateProgress(detail) {
|
|
500
559
|
if (!currentBar) return
|
|
501
560
|
currentBar.value++
|
|
502
|
-
|
|
503
|
-
|
|
561
|
+
// The call site hands over whatever identifies the item it is on — a
|
|
562
|
+
// path, an id — and pays one assignment for it. Formatting happens at
|
|
563
|
+
// emit time, at most four times a phase, so naming the work costs the
|
|
564
|
+
// 14k-entity import nothing.
|
|
565
|
+
if (detail !== undefined) currentBar.detail = detail
|
|
566
|
+
const { name, total, value, drawn } = currentBar
|
|
567
|
+
if (drawn) {
|
|
568
|
+
gauge?.show({ section: name, subsection: `${value}/${total}` }, value / total)
|
|
569
|
+
} else {
|
|
570
|
+
// Quartiles, not every item: a bar redraws in place and costs one
|
|
571
|
+
// line, a log record does not. 800 documents is 800 lines of noise if
|
|
572
|
+
// this counts the way the bar does.
|
|
573
|
+
//
|
|
574
|
+
// The milestone advances whether or not the record is emitted. Held
|
|
575
|
+
// back, every quartile the phase passed under the threshold would fire
|
|
576
|
+
// in a burst the moment it crossed.
|
|
577
|
+
const reached = Math.floor((value / total) * 4)
|
|
578
|
+
if (reached > currentBar.milestone && value < total) {
|
|
579
|
+
currentBar.milestone = reached
|
|
580
|
+
if (Date.now() - currentBar.started >= PROGRESS_MIN_MS) emitProgress(currentBar)
|
|
581
|
+
}
|
|
582
|
+
}
|
|
504
583
|
if (value >= total) stopProgress()
|
|
505
584
|
}
|
|
506
585
|
|
|
@@ -508,12 +587,19 @@ export function stopProgress() {
|
|
|
508
587
|
if (!currentBar) return
|
|
509
588
|
const logger = useLogger()
|
|
510
589
|
const { name, total, value, started } = currentBar
|
|
590
|
+
const ms = Date.now() - started
|
|
511
591
|
gauge?.hide()
|
|
592
|
+
// Structured either way, so a machine reading --json's stderr gets the
|
|
593
|
+
// same facts a person reads off the bar.
|
|
594
|
+
//
|
|
595
|
+
// A phase that did not finish is worth a line at any duration — it is a
|
|
596
|
+
// warning, not progress, and the whole point is that it is unexpected.
|
|
512
597
|
if (value < total) {
|
|
513
|
-
logger.warn(
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
logger.info(
|
|
598
|
+
logger.warn({ code: 'progress-unfinished', phase: name, total, value, missing: total - value },
|
|
599
|
+
'%s unfinished: %d', name, total - value)
|
|
600
|
+
} else if (ms >= PROGRESS_MIN_MS) {
|
|
601
|
+
logger.info({ code: 'progress-finished', phase: name, total, ms },
|
|
602
|
+
'%s finished: %d %ds', name, total, Math.round(ms / 1000))
|
|
517
603
|
}
|
|
518
604
|
currentBar = null
|
|
519
605
|
}
|
|
@@ -528,6 +614,38 @@ export function updateProgressDetails(details) {
|
|
|
528
614
|
)
|
|
529
615
|
}
|
|
530
616
|
|
|
617
|
+
// What a caller asked for about logging, applied the same way from argv and
|
|
618
|
+
// from a forwarded request.
|
|
619
|
+
//
|
|
620
|
+
// It was written twice and the copies drifted immediately: the argv path threw
|
|
621
|
+
// on an unknown level and set `info`, the forwarded path called setLogLevel and
|
|
622
|
+
// ignored the false it returns. So `--log chatty` exited 1 locally and built
|
|
623
|
+
// normally with a watcher up, and `--log silent` left the progress bar running
|
|
624
|
+
// on an instance. The same forwarded/local split --json and --force each had,
|
|
625
|
+
// and this feature's own argument against itself: a flag that lies is worse
|
|
626
|
+
// than a flag that is missing.
|
|
627
|
+
//
|
|
628
|
+
// Returns an error STRING rather than throwing, because the two callers need
|
|
629
|
+
// different things from a failure — argv throws, the instance refuses over the
|
|
630
|
+
// socket — and a shared implementation should not decide that for them.
|
|
631
|
+
export function applyLogRequest({ log, logInstall, logReset } = {}) {
|
|
632
|
+
for (const [flag, level] of [['--log', log], ['--log-install', logInstall]]) {
|
|
633
|
+
if (level !== undefined && level !== null && !LOG_LEVELS.includes(level)) {
|
|
634
|
+
return `${flag} ${level}: no such level. Levels: ${LOG_LEVELS.join(', ')}`
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
if (logReset) resetLogLevel()
|
|
638
|
+
if (logInstall) installLogLevel(logInstall)
|
|
639
|
+
if (log) setLogLevel(log)
|
|
640
|
+
|
|
641
|
+
// A bar on top of debug output is noise, and silent means silent.
|
|
642
|
+
const level = log || logInstall
|
|
643
|
+
if (level === 'trace' || level === 'debug' || level === 'silent') {
|
|
644
|
+
runtime.options.info = false
|
|
645
|
+
}
|
|
646
|
+
return null
|
|
647
|
+
}
|
|
648
|
+
|
|
531
649
|
// An installed level, disclosed and expired, once per cycle.
|
|
532
650
|
//
|
|
533
651
|
// Disclosed for the reason an installed command is: it is state left on a live
|
package/src/plugins/files.js
CHANGED
|
@@ -228,7 +228,7 @@ export function files(options = {}) {
|
|
|
228
228
|
// nothing to emit. Progress ticks either way — a gated file
|
|
229
229
|
// was still looked at.
|
|
230
230
|
const newChecksum = await gateChecksum(source, id, { priorChecksums })
|
|
231
|
-
updateProgress()
|
|
231
|
+
updateProgress(id)
|
|
232
232
|
if (newChecksum === null) return
|
|
233
233
|
await createEntity({
|
|
234
234
|
id,
|
package/src/plugins/observer.js
CHANGED
|
@@ -100,7 +100,7 @@ export function observer(options = {}) {
|
|
|
100
100
|
synced++
|
|
101
101
|
}
|
|
102
102
|
}
|
|
103
|
-
updateProgress()
|
|
103
|
+
updateProgress(meta.id)
|
|
104
104
|
}
|
|
105
105
|
|
|
106
106
|
const entitiesToRemove = await findEntities({
|
|
@@ -114,7 +114,7 @@ export function observer(options = {}) {
|
|
|
114
114
|
for (let entity of entitiesToRemove) {
|
|
115
115
|
deleteEntity(entity)
|
|
116
116
|
removed++
|
|
117
|
-
updateProgress()
|
|
117
|
+
updateProgress(entity.id)
|
|
118
118
|
}
|
|
119
119
|
if (synced || removed) {
|
|
120
120
|
logger.debug('Syncing api [%s] synced: %d, removed: %d', collection, synced, removed)
|
package/src/plugins/resources.js
CHANGED
|
@@ -145,7 +145,7 @@ export function resources(options = {}) {
|
|
|
145
145
|
logger.error('Resource error: %s %s %s', entity.id, resource, err.message)
|
|
146
146
|
}
|
|
147
147
|
}
|
|
148
|
-
updateProgress()
|
|
148
|
+
updateProgress(resource)
|
|
149
149
|
}
|
|
150
150
|
|
|
151
151
|
const resourceFiles = await globby('**/*', { cwd: runtime.options.resourcesFolder })
|
|
@@ -226,7 +226,7 @@ export function resources(options = {}) {
|
|
|
226
226
|
checksum: await checksum(resource)
|
|
227
227
|
})
|
|
228
228
|
}
|
|
229
|
-
updateProgress()
|
|
229
|
+
updateProgress(resource)
|
|
230
230
|
}, { concurrency: 10, signal })
|
|
231
231
|
count && logger.info('Downloaded: %d', count)
|
|
232
232
|
}
|
package/src/source.js
CHANGED
|
@@ -431,7 +431,7 @@ export function useSource(core, options) {
|
|
|
431
431
|
// is never the bottleneck.
|
|
432
432
|
await pMap(files, async (file) => {
|
|
433
433
|
await registerFile(file, { logger, scanned, stats: scanStats, priorChecksums })
|
|
434
|
-
if (phase === 'import') updateProgress()
|
|
434
|
+
if (phase === 'import') updateProgress(file)
|
|
435
435
|
}, { concurrency: SCAN_CONCURRENCY })
|
|
436
436
|
|
|
437
437
|
scanStats.deleted = await sweepDeleted(collection, scanned, async (e) => {
|