mnfst-publish 0.1.9 → 0.1.11

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.
@@ -301,7 +301,15 @@ export function collectFiles(root, outputDir) {
301
301
  // .manifestignore applies ON TOP of gitignore, so a versioned file can still be
302
302
  // kept out of the published bundle.
303
303
  const publishIgnored = loadPublishIgnore(root);
304
- return rels.filter((r) => !isExcludedPath(r) && !publishIgnored(r));
304
+ return rels
305
+ .filter((r) => !isExcludedPath(r) && !publishIgnored(r))
306
+ // `git ls-files -c` reports paths from the index, not the working tree — a file
307
+ // committed once (e.g. an old build's <locale>/docs/** output) then deleted on
308
+ // disk without `git rm` still shows up here. Ship what's actually there; a
309
+ // stale tracked-but-missing path would otherwise crash buildZip with ENOENT
310
+ // (seen with mnfst-publish --no-render after a re-render dropped locale
311
+ // variants under an excluded route prefix).
312
+ .filter((r) => existsSync(join(root, r)));
305
313
  }
306
314
 
307
315
  // --- Upload + publish status ------------------------------------------------
@@ -313,8 +321,12 @@ export function collectFiles(root, outputDir) {
313
321
  // A server that predates this ignores the header and answers synchronously with
314
322
  // {ok:true}; both are handled below.
315
323
  const POLL_FIRST_MS = 1000;
316
- const POLL_MAX_MS = 5000;
324
+ const POLL_MAX_MS = 2000;
317
325
  const POLL_TOTAL_MS = 10 * 60 * 1000;
326
+ /* Ingest advances a bounded slice per status poll, so polls that observe the
327
+ `written` counter climbing must never give up: the overall cap applies only
328
+ while NO progress is being made. */
329
+ const NO_PROGRESS_MS = 10 * 60 * 1000;
318
330
  // A status read that stays "pending" this long means the bundle never actually
319
331
  // arrived. The grace matters because the server's token record is eventually
320
332
  // consistent, so a brief stale "pending" right after an upload is normal.
@@ -353,6 +365,8 @@ export async function pollPublishStatus(statusUrl, opts = {}) {
353
365
  const started = now();
354
366
  let delay = POLL_FIRST_MS;
355
367
  let lastProgress = started;
368
+ let lastWritten = -1;
369
+ let lastAdvance = started;
356
370
 
357
371
  for (;;) {
358
372
  let status = 0;
@@ -380,11 +394,22 @@ export async function pollPublishStatus(statusUrl, opts = {}) {
380
394
  if (json && json.status === 'pending' && now() - started >= pendingGraceMs) {
381
395
  return { state: 'pending', body: json };
382
396
  }
397
+ // An HTTP error body that skips the {status:'error'} tag entirely (a plain
398
+ // {error, message} shape, same as an upload-time rejection) is still a
399
+ // settled failure, not a blip — UNLESS it's 429 (rate limited) or a 5xx,
400
+ // both of which are expected to be transient and worth retrying.
401
+ if (json && json.error && !json.status && status >= 400 && status !== 429 && status < 500) {
402
+ return { state: 'error', body: json };
403
+ }
383
404
  // Anything else (ingesting, pending inside the grace, 429, 5xx, a blip) —
384
405
  // keep waiting.
385
406
 
386
407
  const elapsed = now() - started;
387
- if (elapsed >= totalMs) return { state: 'timeout' };
408
+ if (json && typeof json.written === 'number' && json.written > lastWritten) {
409
+ lastWritten = json.written;
410
+ lastAdvance = now();
411
+ }
412
+ if (now() - lastAdvance >= Math.min(totalMs, NO_PROGRESS_MS)) return { state: 'timeout' };
388
413
  if (elapsed - (lastProgress - started) >= PROGRESS_EVERY_MS) {
389
414
  lastProgress = now();
390
415
  logFn(` still publishing… (${Math.round(elapsed / 1000)}s)`);
@@ -399,7 +424,8 @@ function settledPayload(res) {
399
424
  if (res.state === 'done') return res.body;
400
425
  if (res.state === 'error') {
401
426
  const b = res.body || {};
402
- throw new Error(`publish failed${b.error ? ` (${b.error})` : ''}: ${b.message || 'the server rejected the upload.'}`);
427
+ const retrySafe = b.error === 'retry_upload' ? ' Re-running the same command is safe and cheap nothing was double-charged or double-shipped.' : '';
428
+ throw new Error(`publish failed${b.error ? ` (${b.error})` : ''}: ${b.message || 'the server rejected the upload.'}${retrySafe}`);
403
429
  }
404
430
  if (res.state === 'unknown') {
405
431
  throw new Error('this publish link expired before the upload finished. Run the publish again.');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mnfst-publish",
3
- "version": "0.1.9",
3
+ "version": "0.1.11",
4
4
  "description": "One-command managed publishing for Manifest projects — render, zip, upload, live URL.",
5
5
  "type": "module",
6
6
  "bin": {