mnfst-publish 0.1.6 → 0.1.8

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.
File without changes
@@ -30,6 +30,7 @@ function parseArgs(argv) {
30
30
  else if (a === '--production' || a === '--prod') out.env = 'production';
31
31
  else if (a === '--env') out.env = argv[++i];
32
32
  else if (a === '--source') out.source = argv[++i];
33
+ else if (a === '--output-dir') out.outputDir = argv[++i];
33
34
  else if (a === '--no-render') out.render = false;
34
35
  else if (a === '--render') out.render = true;
35
36
  else if (a === '--promote') out.promote = true;
@@ -259,11 +260,29 @@ export function loadPublishIgnore(root) {
259
260
  return makePublishIgnore(patterns);
260
261
  }
261
262
 
262
- export function collectFiles(root) {
263
+ export function collectFiles(root, outputDir) {
263
264
  const git = spawnSync('git', ['ls-files', '-co', '--exclude-standard'], { cwd: root, encoding: 'utf8' });
264
265
  let rels;
265
266
  if (git.status === 0) {
266
267
  rels = git.stdout.split('\n').map((s) => s.trim()).filter(Boolean);
268
+ // Non-Manifest builds usually write to a GITIGNORED folder (dist/, build/…)
269
+ // — force-include the declared output dir or the publish ships no site.
270
+ if (outputDir && existsSync(join(root, outputDir))) {
271
+ const seen = new Set(rels);
272
+ const walkOut = (dir) => {
273
+ for (const name of readdirSync(dir)) {
274
+ if (EXCLUDED_DIRS.has(name)) continue;
275
+ const abs = join(dir, name);
276
+ const st = statSync(abs);
277
+ if (st.isDirectory()) walkOut(abs);
278
+ else {
279
+ const rel = relative(root, abs).split(sep).join('/');
280
+ if (!seen.has(rel)) { seen.add(rel); rels.push(rel); }
281
+ }
282
+ }
283
+ };
284
+ walkOut(join(root, outputDir));
285
+ }
267
286
  } else {
268
287
  // Not a git repo — walk, skipping excluded dirs as we go.
269
288
  rels = [];
@@ -285,6 +304,44 @@ export function collectFiles(root) {
285
304
  return rels.filter((r) => !isExcludedPath(r) && !publishIgnored(r));
286
305
  }
287
306
 
307
+ // --- Component version stamp ------------------------------------------------
308
+
309
+ // Stamp each shipped manifest.json (project root, and the prerender output copy)
310
+ // with `deployment`: a content hash of its component HTML files. The components
311
+ // plugin appends it as ?v= to component fetches, so browser caches bust exactly
312
+ // when component markup changes and persist when it doesn't. Purely publish-time:
313
+ // the on-disk manifest.json is never modified.
314
+ export function stampManifests(root, rels) {
315
+ const overrides = new Map();
316
+ const relSet = new Set(rels);
317
+ const outDir = prerenderOutputDir(root);
318
+ for (const mfRel of ['manifest.json', outDir + '/manifest.json']) {
319
+ if (!relSet.has(mfRel)) continue;
320
+ let mf;
321
+ try {
322
+ mf = JSON.parse(readFileSync(join(root, mfRel), 'utf8'));
323
+ } catch {
324
+ continue; // invalid JSON — ship as-is
325
+ }
326
+ const dir = mfRel.includes('/') ? mfRel.slice(0, mfRel.lastIndexOf('/') + 1) : '';
327
+ const paths = [...(mf.preloadedComponents || []), ...(mf.components || [])]
328
+ .filter((p) => typeof p === 'string' && !p.startsWith('http'))
329
+ .map((p) => dir + p.replace(/^\/+/, ''))
330
+ .filter((p) => relSet.has(p))
331
+ .sort();
332
+ if (!paths.length) continue;
333
+ const hash = createHash('sha256');
334
+ for (const p of paths) {
335
+ hash.update(p + '\0');
336
+ hash.update(readFileSync(join(root, p)));
337
+ hash.update('\0');
338
+ }
339
+ mf.deployment = hash.digest('hex').slice(0, 12);
340
+ overrides.set(mfRel, Buffer.from(JSON.stringify(mf, null, 2) + '\n', 'utf8'));
341
+ }
342
+ return overrides;
343
+ }
344
+
288
345
  // --- Minimal ZIP writer (DEFLATE), pure Node, no deps ----------------------
289
346
 
290
347
  function crc32(buf) {
@@ -296,7 +353,7 @@ function crc32(buf) {
296
353
  return (~c) >>> 0;
297
354
  }
298
355
 
299
- function buildZip(root, rels) {
356
+ function buildZip(root, rels, overrides = new Map()) {
300
357
  // This packer writes classic (non-Zip64) ZIP records: file count is a uint16
301
358
  // and offsets are uint32. Fail loudly rather than emit a silently-corrupt
302
359
  // archive past those limits.
@@ -310,7 +367,7 @@ function buildZip(root, rels) {
310
367
  if (offset > 0xffffffff) {
311
368
  fail('project is too large to package (>4 GB). Contact support.');
312
369
  }
313
- const data = readFileSync(join(root, rel));
370
+ const data = overrides.get(rel) ?? readFileSync(join(root, rel));
314
371
  const nameBuf = Buffer.from(rel, 'utf8');
315
372
  const crc = crc32(data);
316
373
  const deflated = deflateRawSync(data);
@@ -365,7 +422,7 @@ function buildZip(root, rels) {
365
422
  export async function main() {
366
423
  const opts = parseArgs(process.argv.slice(2));
367
424
  if (opts.help) {
368
- log('Usage: npx mnfst-publish [--staging|--production] [--no-render] [--promote]');
425
+ log('Usage: npx mnfst-publish [--staging|--production] [--source spa|render] [--output-dir <dir>] [--no-render] [--promote]');
369
426
  log('Publishes the current Manifest project to managed hosting and prints the live URL.');
370
427
  return;
371
428
  }
@@ -446,9 +503,12 @@ export async function main() {
446
503
  fail('the upload URL returned by the server was malformed.');
447
504
  }
448
505
 
449
- const rels = collectFiles(root);
506
+ const rels = collectFiles(root, opts.outputDir);
450
507
  if (!rels.length) fail('nothing to publish (no files found).');
451
- const zip = buildZip(root, rels);
508
+ if (opts.outputDir && !existsSync(join(root, opts.outputDir))) {
509
+ fail(`the output folder "${opts.outputDir}" doesn't exist — run the project's build first, then publish again.`);
510
+ }
511
+ const zip = buildZip(root, rels, stampManifests(root, rels));
452
512
  log(`Uploading ${rels.length} files (${(zip.length / 1048576).toFixed(1)} MB)…`);
453
513
 
454
514
  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.8",
4
4
  "description": "One-command managed publishing for Manifest projects — render, zip, upload, live URL.",
5
5
  "type": "module",
6
6
  "bin": {