feedback1 integrate: fetch-assets refuses to shrink data/assets.json without --allow-shrink or --prune (pointed out in PR #7)

A rebuilt manifest leaves out every entry whose files are missing on that machine, so a run
with failed downloads silently dropped entries from the tracked file (PR #7 carried one 42
audio entries short; all 42 still resolve upstream). The run now keeps the current manifest,
prints the entries it would drop (also in the report) and exits 1. The tool runs main only as
a script so the guard and parseArgs can be tested; ASSETS.md documents the flag.
This commit is contained in:
sganggs committed 2026-10-03 13:48:13 +08:00
1 parent cb01bb3fcf
commit 36d6766d76
4 files changed
+179 -16

No files matched your search

+32
View File
@@ -146,3 +146,35 @@ export function totalBytes(root, rels) {
export function contentHash(value) {
return createHash('sha1').update(JSON.stringify(value)).digest('hex').slice(0, 12);
}
/** Top-level manifest fields that describe the build, not assets: never counted as dropped entries. */
const BUILD_FIELDS = new Set(['version', 'hash', 'generator', 'stats']);
/**
* Entries of the manifest `prev` that `next` no longer has, as sorted dotted paths. An entry is a leaf value (a URL
* string, a number, a flag, null or an array — an array is one entry); an object entry missing from `next` contributes
* all its leaves. A path that changed shape (a leaf became an object or the reverse) is still there, not dropped.
* The build fields (version, hash, generator, stats) are left out.
* @param {object} prev the current data/assets.json
* @param {object} next the manifest about to replace it
* @returns {string[]}
*/
export function droppedEntries(prev, next) {
const out = [];
const isObj = (v) => !!v && typeof v === 'object' && !Array.isArray(v);
const leaves = (v, path) => {
if (!isObj(v)) { out.push(path); return; }
for (const [k, x] of Object.entries(v)) leaves(x, `${path}.${k}`);
};
const walk = (p, n, path) => {
if (n === undefined) { leaves(p, path); return; }
if (!isObj(p) || !isObj(n)) return;
for (const [k, v] of Object.entries(p)) walk(v, Object.hasOwn(n, k) ? n[k] : undefined, `${path}.${k}`);
};
if (!isObj(prev)) return out;
const nx = isObj(next) ? next : {};
for (const [k, v] of Object.entries(prev)) {
if (!BUILD_FIELDS.has(k)) walk(v, Object.hasOwn(nx, k) ? nx[k] : undefined, k);
}
return out.sort();
}