shiply-cli 0.27.1 → 0.27.2
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/dist/publish.js +12 -3
- package/package.json +1 -1
package/dist/publish.js
CHANGED
|
@@ -21,11 +21,17 @@ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
|
21
21
|
const backoffMs = (attempt) => Math.min(8000, 500 * 2 ** attempt) + Math.floor(Math.random() * 250);
|
|
22
22
|
/** fetch() with a per-attempt timeout + retry-with-backoff on transient failures
|
|
23
23
|
* (network errors, aborts/timeouts, and 5xx gateway statuses). Bodies here are
|
|
24
|
-
* strings/Buffers, so re-sending on retry is safe.
|
|
24
|
+
* strings/Buffers, so re-sending on retry is safe.
|
|
25
|
+
*
|
|
26
|
+
* `alsoRetry`: extra statuses the CALLER knows are safe to retry. The global
|
|
27
|
+
* set excludes 500 to protect non-idempotent creates — but an idempotent
|
|
28
|
+
* presigned PUT (upload) SHOULD retry a 500 (observed: R2 throws transient
|
|
29
|
+
* 500s mid-publish; without this, one blip failed the whole publish). */
|
|
25
30
|
export async function fetchWithRetry(url, init, opts = {}) {
|
|
26
31
|
const attempts = opts.attempts ?? 4;
|
|
27
32
|
const timeoutMs = opts.timeoutMs ?? 60_000;
|
|
28
33
|
const label = opts.label ?? url;
|
|
34
|
+
const alsoRetry = new Set(opts.alsoRetry ?? []);
|
|
29
35
|
let lastErr;
|
|
30
36
|
for (let i = 0; i < attempts; i++) {
|
|
31
37
|
const ac = new AbortController();
|
|
@@ -33,7 +39,7 @@ export async function fetchWithRetry(url, init, opts = {}) {
|
|
|
33
39
|
try {
|
|
34
40
|
const res = await fetch(url, { ...init, signal: ac.signal });
|
|
35
41
|
clearTimeout(timer);
|
|
36
|
-
if (TRANSIENT_STATUS.has(res.status) && i < attempts - 1) {
|
|
42
|
+
if ((TRANSIENT_STATUS.has(res.status) || alsoRetry.has(res.status)) && i < attempts - 1) {
|
|
37
43
|
process.stderr.write(` ↻ ${label} returned ${res.status} — retrying (${i + 2}/${attempts})…\n`);
|
|
38
44
|
await sleep(backoffMs(i));
|
|
39
45
|
continue;
|
|
@@ -235,7 +241,10 @@ async function uploadAll(dir, targets) {
|
|
|
235
241
|
while (next < targets.length) {
|
|
236
242
|
const t = targets[next++];
|
|
237
243
|
const body = await readFile(join(dir, t.path));
|
|
238
|
-
const res = await fetchWithRetry(t.url, { method: t.method, headers: t.headers, body },
|
|
244
|
+
const res = await fetchWithRetry(t.url, { method: t.method, headers: t.headers, body },
|
|
245
|
+
// Presigned object PUT = idempotent → a transient 500 (R2 does throw
|
|
246
|
+
// them) must be retried, not fail the whole publish.
|
|
247
|
+
{ label: `upload ${t.path}`, alsoRetry: [500] });
|
|
239
248
|
if (!res.ok) {
|
|
240
249
|
if (res.status === 403) {
|
|
241
250
|
throw new Error(`upload failed for ${t.path}: HTTP 403 — presigned URLs likely expired ` +
|
package/package.json
CHANGED