mikser-io 9.40.1 → 9.40.3
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/docs/diagnostics.md +12 -0
- package/package.json +1 -1
- package/src/explain.js +26 -0
- package/src/manifest.js +41 -0
package/docs/diagnostics.md
CHANGED
|
@@ -563,6 +563,18 @@ cannot model is how the entity's own frontmatter would change — that is
|
|
|
563
563
|
parsed during import, so an edit that moves `meta.layout` moves the
|
|
564
564
|
destination too, and this does not see it.
|
|
565
565
|
|
|
566
|
+
**Layout sidecars are the one entity answered by a different route.** A
|
|
567
|
+
sidecar never renders, nothing points at it by ref, and no query matches
|
|
568
|
+
it, so all three candidate walks come back empty — yet editing one
|
|
569
|
+
re-renders the whole site. Its dependency is the layout's *input digest*,
|
|
570
|
+
not an edge: `mikser-io-layouts` folds every sidecar script under the
|
|
571
|
+
folder into a single `sharedDigest` carried by every layout's checksum, so
|
|
572
|
+
one sidecar edit moves every layout. `affectedBy` therefore reports every
|
|
573
|
+
destination that rendered through any layout, each attributed to the
|
|
574
|
+
layout it went through. Entities with no layout — a copied asset, a
|
|
575
|
+
`files()` passthrough — stay out, or the answer would be "everything,
|
|
576
|
+
always", which is the same non-answer as "nothing" with the sign flipped.
|
|
577
|
+
|
|
566
578
|
### `runtime.provenance`
|
|
567
579
|
|
|
568
580
|
Where a value was **written** — source file, field path, line and column.
|
package/package.json
CHANGED
package/src/explain.js
CHANGED
|
@@ -228,6 +228,16 @@ export async function explain(reference) {
|
|
|
228
228
|
: 'unknown',
|
|
229
229
|
outputHash: snap.outputHash ?? null,
|
|
230
230
|
parent: snap.parent ?? null,
|
|
231
|
+
// Which keys of its OWN meta the render read, and which keys it
|
|
232
|
+
// read off OTHER entities.
|
|
233
|
+
//
|
|
234
|
+
// refClosure answers "what would re-render this". These answer
|
|
235
|
+
// "what does it actually use", which is the question behind a
|
|
236
|
+
// document that renders to a hole: a key the layout never touches
|
|
237
|
+
// is either a typo or dead weight, and nothing else on this report
|
|
238
|
+
// distinguishes them. Empty when the catalog predates the record.
|
|
239
|
+
metaReads: snap.metaReads ?? [],
|
|
240
|
+
consumedReads: (snap.consumedReads ?? []).map(([id, keys]) => ({ entity: id, keys })),
|
|
231
241
|
refClosure: (snap.refClosure ?? []).map(entry =>
|
|
232
242
|
entry.kind === 'query'
|
|
233
243
|
? {
|
|
@@ -383,6 +393,22 @@ export function formatExplain(report) {
|
|
|
383
393
|
const gone = e.gone?.length ? ' [TARGET DELETED SINCE]' : ''
|
|
384
394
|
out.push(` ${e.kind.padEnd(10)} ${e.target}${bound}${e.hash ? ` ${e.hash}` : ''}${gone}`)
|
|
385
395
|
}
|
|
396
|
+
|
|
397
|
+
// What the render actually READ, as against what would re-render it.
|
|
398
|
+
// Capped, because a large page reads a lot and a wall of keys buries
|
|
399
|
+
// the rest of the report — the full list is in --json.
|
|
400
|
+
const SHOWN = 12
|
|
401
|
+
const shown = (keys) => keys.length > SHOWN
|
|
402
|
+
? `${keys.slice(0, SHOWN).join(', ')} … +${keys.length - SHOWN} more`
|
|
403
|
+
: keys.join(', ')
|
|
404
|
+
if (r.metaReads?.length) {
|
|
405
|
+
row('metaReads', `${r.metaReads.length} key${r.metaReads.length === 1 ? '' : 's'} of its own meta`)
|
|
406
|
+
out.push(` ${shown(r.metaReads)}`)
|
|
407
|
+
}
|
|
408
|
+
for (const c of r.consumedReads ?? []) {
|
|
409
|
+
row('consumed', `${c.entity} (${c.keys.length})`)
|
|
410
|
+
out.push(` ${shown(c.keys)}`)
|
|
411
|
+
}
|
|
386
412
|
}
|
|
387
413
|
|
|
388
414
|
out.push('')
|
package/src/manifest.js
CHANGED
|
@@ -484,6 +484,25 @@ export function createManifest(db) {
|
|
|
484
484
|
WHERE refClosure LIKE '%"kind":"query"%'
|
|
485
485
|
`)
|
|
486
486
|
|
|
487
|
+
// Everything that rendered through a layout — the blast radius of a change
|
|
488
|
+
// to a layout SIDECAR.
|
|
489
|
+
//
|
|
490
|
+
// A sidecar is reachable by none of the three routes affectedBy otherwise
|
|
491
|
+
// walks: it never renders, so it has no snapshots; nothing points at it by
|
|
492
|
+
// ref; and no query matches it. Its dependency is the layout's input
|
|
493
|
+
// digest, which is not an edge. So the preview answered "nothing would be
|
|
494
|
+
// affected" for a change that re-renders the entire site — the one
|
|
495
|
+
// direction of wrong that matters, since it says a site-wide edit is safe.
|
|
496
|
+
//
|
|
497
|
+
// The blast radius really is everything: the layouts plugin folds every
|
|
498
|
+
// OTHER sidecar under the folder into each layout's `shared` digest, so any
|
|
499
|
+
// sidecar edit moves every layout's checksum, and every page renders
|
|
500
|
+
// through some layout.
|
|
501
|
+
const stmtSnapshotsWithLayout = db.prepare(`
|
|
502
|
+
SELECT id, destination, refClosure FROM mikser_snapshots
|
|
503
|
+
WHERE refClosure LIKE '%"kind":"layout"%'
|
|
504
|
+
`)
|
|
505
|
+
|
|
487
506
|
// Snapshots holding a non-query edge that names any of the given keys,
|
|
488
507
|
// by the name asked for OR by the entity it bound to. Both, for the same
|
|
489
508
|
// reason skipDecision reads both — a name survives a rename only through
|
|
@@ -572,6 +591,28 @@ export function createManifest(db) {
|
|
|
572
591
|
}
|
|
573
592
|
|
|
574
593
|
const affected = []
|
|
594
|
+
|
|
595
|
+
// Sidecars take the route above rather than the candidate walk:
|
|
596
|
+
// skipDecision compares against the edges a snapshot recorded, and
|
|
597
|
+
// no snapshot records an edge to a sidecar — it would answer "skip"
|
|
598
|
+
// for every page and hide the whole radius.
|
|
599
|
+
if (entity.type === 'sidecar') {
|
|
600
|
+
for (const row of stmtSnapshotsWithLayout.iterate()) {
|
|
601
|
+
let layoutEdge = null
|
|
602
|
+
try {
|
|
603
|
+
layoutEdge = JSON.parse(row.refClosure ?? '[]').find(e => e.kind === 'layout') ?? null
|
|
604
|
+
} catch { /* unreadable closure — still affected, just unattributed */ }
|
|
605
|
+
affected.push({
|
|
606
|
+
id: row.id,
|
|
607
|
+
destination: row.destination,
|
|
608
|
+
reason: 'ref-changed',
|
|
609
|
+
...(layoutEdge?.target ? { dependency: layoutEdge.target } : {}),
|
|
610
|
+
why: 'a layout sidecar feeds every layout through its shared digest',
|
|
611
|
+
})
|
|
612
|
+
}
|
|
613
|
+
return affected
|
|
614
|
+
}
|
|
615
|
+
|
|
575
616
|
for (const { id, destination } of candidates.values()) {
|
|
576
617
|
// Its own renders: the premise of the question is that this
|
|
577
618
|
// entity changed, so asking skipDecision — which compares the
|