mikser-io 10.10.0 → 10.10.1

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mikser-io",
3
- "version": "10.10.0",
3
+ "version": "10.10.1",
4
4
  "files": [
5
5
  "app.js",
6
6
  "index.js",
@@ -418,11 +418,12 @@ export function assets(options = {}) {
418
418
  }
419
419
 
420
420
  async function forgetPresetMarkers(entity) {
421
- for (const marker of markersByDestination.get(entity.destination) ?? []) {
421
+ const destination = path.resolve(entity.destination)
422
+ for (const marker of markersByDestination.get(destination) ?? []) {
422
423
  await rm(marker, { force: true })
423
424
  checksumMap.delete(marker)
424
425
  }
425
- markersByDestination.delete(entity.destination)
426
+ markersByDestination.delete(destination)
426
427
  }
427
428
 
428
429
  async function renderPresets(entities, { rendererChanged = new Set() } = {}) {
@@ -702,27 +703,22 @@ export function assets(options = {}) {
702
703
 
703
704
  checksumMap.clear()
704
705
  markersByDestination.clear()
705
- // Scoped to the preset folders rather than the whole assets tree: the
706
- // assets folder also carries what the files plugin symlinks into it,
707
- // and those have no markers and never will. Unscoped, every one of
708
- // them would read as an interrupted render on every cycle.
709
- const presetFolders = Object.keys(runtime.state.assets.presets).map(name => `${name}/**/*`)
710
- const files = presetFolders.length
711
- ? await globby(presetFolders, { cwd: runtime.options.assetsFolder, onlyFiles: true })
712
- : []
713
- const derivatives = new Set()
714
- for (let file of files) {
715
- const absolute = path.join(runtime.options.assetsFolder, file)
716
- if (!file.endsWith('.md5')) {
717
- derivatives.add(absolute)
718
- continue
719
- }
720
- checksumMap.add(absolute)
706
+ const checksumFiles = await globby('**/*.md5', { cwd: runtime.options.assetsFolder })
707
+ for (let checksumFile of checksumFiles) {
708
+ const marker = path.join(runtime.options.assetsFolder, checksumFile)
709
+ checksumMap.add(marker)
721
710
  // `<destination>.<revision>.md5` — the same shape the orphan
722
711
  // sweep strips to get back to the derivative.
723
- const destination = absolute.replace(/\.[^.]+\.md5$/, '')
712
+ //
713
+ // Resolved on both sides of the comparison, because the other
714
+ // side is a manifest destination and these two are built by
715
+ // different subsystems. It is a no-op while assetsFolder is
716
+ // derived from workingFolder (see onLoaded), which is why no test
717
+ // can tell it apart — normalization at the boundary, not a
718
+ // transform anything depends on.
719
+ const destination = path.resolve(marker.replace(/\.[^.]+\.md5$/, ''))
724
720
  if (!markersByDestination.has(destination)) markersByDestination.set(destination, [])
725
- markersByDestination.get(destination).push(absolute)
721
+ markersByDestination.get(destination).push(marker)
726
722
  }
727
723
 
728
724
  const entitiesToRender = new Map()
@@ -745,28 +741,63 @@ export function assets(options = {}) {
745
741
  // Empty on any healthy build, and the catalog walk is behind that
746
742
  // emptiness: this costs one Set lookup per cycle until something has
747
743
  // actually gone wrong.
748
- const unmarked = new Set()
749
- for (const derivative of derivatives) {
750
- if (!markersByDestination.has(derivative)) unmarked.add(derivative)
744
+ // Asked of the MANIFEST, not of the folder.
745
+ //
746
+ // The first version walked every file under each preset folder and
747
+ // called anything without a marker an interrupted render. But only
748
+ // the destination the engine hands the preset gets a marker, so a
749
+ // preset that legitimately writes MORE than one file — a poster frame
750
+ // beside a video — left permanent evidence of a failure that never
751
+ // happened. It warned on every build for ever, said the derivative
752
+ // was "being re-derived", and re-derived nothing, because no entity's
753
+ // destination ever matched the extra file. A warning that cries wolf
754
+ // on every build is worse than the silence it replaced, and it
755
+ // teaches people to ignore the one that matters.
756
+ //
757
+ // A file nothing claims is an ORPHAN, which is a different fault with
758
+ // its own report. This asks only about outputs the manifest recorded,
759
+ // which is also what makes the recovery possible: a snapshot carries
760
+ // the entity id, so there is nothing to reverse-map and no catalog to
761
+ // walk.
762
+ const presetRoots = Object.keys(presets)
763
+ .map(name => path.resolve(runtime.options.assetsFolder, name) + path.sep)
764
+ const unfinished = new Map()
765
+ for (const snapshot of runtime.manifest?.all?.() ?? []) {
766
+ if (!snapshot.destination) continue
767
+ const destination = path.resolve(snapshot.destination)
768
+ if (!presetRoots.some(root => destination.startsWith(root))) continue
769
+ if (markersByDestination.has(destination)) continue
770
+ // Gone entirely is a different fault, and the engine's
771
+ // missing-output path already has it. Reporting it here as well
772
+ // would name the wrong cause.
773
+ if (outputMissing(snapshot.destination)) continue
774
+ unfinished.set(snapshot.id, destination)
751
775
  }
752
- if (unmarked.size) {
753
- for await (const candidate of iterateEntities({ collection: { $ne: collection } })) {
754
- const candidatePresets = await getEntityPresets(candidate)
755
- const damaged = candidatePresets.filter(name => presets[name]
756
- && unmarked.has(presetDestination(candidate, presets[name])))
757
- if (!damaged.length) continue
758
- assetsMap[candidate.id] ??= candidatePresets
776
+ if (unfinished.size) {
777
+ const damaged = []
778
+ for (const [id, destination] of unfinished) {
779
+ const entity = await findEntity({ id })
780
+ // Source gone: the orphan sweep removes the derivative at
781
+ // finalize. Nothing to re-derive and nothing to say.
782
+ if (!entity) continue
783
+ assetsMap[entity.id] ??= await getEntityPresets(entity)
759
784
  // Forced past the reuse check as well as scheduled: the file
760
785
  // on disk is exactly what must not be trusted, and
761
- // isPresetRendered has no way to tell half-written bytes from
786
+ // isPresetRendered cannot tell half-written bytes from
762
787
  // finished ones.
763
- presetMoved.add(candidate.id)
764
- entitiesToRender.set(candidate.id, candidate)
788
+ presetMoved.add(entity.id)
789
+ entitiesToRender.set(entity.id, entity)
790
+ damaged.push(destination)
791
+ }
792
+ // Only when something is actually being re-derived. The warning
793
+ // claimed the action rather than reporting it, so a build that
794
+ // scheduled nothing still announced a recovery.
795
+ if (damaged.length) {
796
+ useLogger().warn({ code: 'preset-unfinished', derivatives: damaged },
797
+ 'Assets: %d derivative(s) have no completed-render marker and are being re-derived. '
798
+ + 'A preset render did not finish — the file left behind is not trustworthy.',
799
+ damaged.length)
765
800
  }
766
- useLogger().warn({ code: 'preset-unfinished', derivatives: [...unmarked], sources: entitiesToRender.size },
767
- 'Assets: %d derivative(s) have no completed-render marker and are being re-derived. '
768
- + 'A preset render did not finish — the file left behind is not trustworthy.',
769
- unmarked.size)
770
801
  }
771
802
 
772
803
  // --render-presets [name]: re-derive though nothing moved.