mnfst-publish 0.1.6 → 0.1.7

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.
@@ -285,6 +285,44 @@ export function collectFiles(root) {
285
285
  return rels.filter((r) => !isExcludedPath(r) && !publishIgnored(r));
286
286
  }
287
287
 
288
+ // --- Component version stamp ------------------------------------------------
289
+
290
+ // Stamp each shipped manifest.json (project root, and the prerender output copy)
291
+ // with `deployment`: a content hash of its component HTML files. The components
292
+ // plugin appends it as ?v= to component fetches, so browser caches bust exactly
293
+ // when component markup changes and persist when it doesn't. Purely publish-time:
294
+ // the on-disk manifest.json is never modified.
295
+ export function stampManifests(root, rels) {
296
+ const overrides = new Map();
297
+ const relSet = new Set(rels);
298
+ const outDir = prerenderOutputDir(root);
299
+ for (const mfRel of ['manifest.json', outDir + '/manifest.json']) {
300
+ if (!relSet.has(mfRel)) continue;
301
+ let mf;
302
+ try {
303
+ mf = JSON.parse(readFileSync(join(root, mfRel), 'utf8'));
304
+ } catch {
305
+ continue; // invalid JSON — ship as-is
306
+ }
307
+ const dir = mfRel.includes('/') ? mfRel.slice(0, mfRel.lastIndexOf('/') + 1) : '';
308
+ const paths = [...(mf.preloadedComponents || []), ...(mf.components || [])]
309
+ .filter((p) => typeof p === 'string' && !p.startsWith('http'))
310
+ .map((p) => dir + p.replace(/^\/+/, ''))
311
+ .filter((p) => relSet.has(p))
312
+ .sort();
313
+ if (!paths.length) continue;
314
+ const hash = createHash('sha256');
315
+ for (const p of paths) {
316
+ hash.update(p + '\0');
317
+ hash.update(readFileSync(join(root, p)));
318
+ hash.update('\0');
319
+ }
320
+ mf.deployment = hash.digest('hex').slice(0, 12);
321
+ overrides.set(mfRel, Buffer.from(JSON.stringify(mf, null, 2) + '\n', 'utf8'));
322
+ }
323
+ return overrides;
324
+ }
325
+
288
326
  // --- Minimal ZIP writer (DEFLATE), pure Node, no deps ----------------------
289
327
 
290
328
  function crc32(buf) {
@@ -296,7 +334,7 @@ function crc32(buf) {
296
334
  return (~c) >>> 0;
297
335
  }
298
336
 
299
- function buildZip(root, rels) {
337
+ function buildZip(root, rels, overrides = new Map()) {
300
338
  // This packer writes classic (non-Zip64) ZIP records: file count is a uint16
301
339
  // and offsets are uint32. Fail loudly rather than emit a silently-corrupt
302
340
  // archive past those limits.
@@ -310,7 +348,7 @@ function buildZip(root, rels) {
310
348
  if (offset > 0xffffffff) {
311
349
  fail('project is too large to package (>4 GB). Contact support.');
312
350
  }
313
- const data = readFileSync(join(root, rel));
351
+ const data = overrides.get(rel) ?? readFileSync(join(root, rel));
314
352
  const nameBuf = Buffer.from(rel, 'utf8');
315
353
  const crc = crc32(data);
316
354
  const deflated = deflateRawSync(data);
@@ -448,7 +486,7 @@ export async function main() {
448
486
 
449
487
  const rels = collectFiles(root);
450
488
  if (!rels.length) fail('nothing to publish (no files found).');
451
- const zip = buildZip(root, rels);
489
+ const zip = buildZip(root, rels, stampManifests(root, rels));
452
490
  log(`Uploading ${rels.length} files (${(zip.length / 1048576).toFixed(1)} MB)…`);
453
491
 
454
492
  const up = await fetchRetry(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mnfst-publish",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
4
4
  "description": "One-command managed publishing for Manifest projects — render, zip, upload, live URL.",
5
5
  "type": "module",
6
6
  "bin": {