Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 50 additions & 24 deletions packages/cli/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ const main = defineCommand({

let checkResponse;
try {
checkResponse = await fetch(new URL("/check", apiUrl), {
checkResponse = await fetchWithRetry(new URL("/check", apiUrl), {
method: "POST",
body: JSON.stringify({
owner,
Expand Down Expand Up @@ -652,14 +652,17 @@ const main = defineCommand({
continue;
}
const totalChunks = Math.ceil(file.size / chunkSize);
const createMultipartRes = await fetch(createMultipart, {
method: "POST",
headers: {
"sb-key": key,
"sb-name": name.slice("package:".length),
"sb-sha": sha,
const createMultipartRes = await fetchWithRetry(
createMultipart,
{
method: "POST",
headers: {
"sb-key": key,
"sb-name": name.slice("package:".length),
"sb-sha": sha,
},
},
});
);
if (!createMultipartRes.ok) {
console.error(await createMultipartRes.text());
continue;
Expand All @@ -678,15 +681,18 @@ const main = defineCommand({
const end = Math.min(file.size, start + chunkSize);
const chunk = file.slice(start, end);

const uploadMultipartRes = await fetch(uploadMultipart, {
method: "PUT",
headers: {
key: uploadKey,
id: uploadId,
"part-number": `${i + 1}`,
const uploadMultipartRes = await fetchWithRetry(
uploadMultipart,
{
method: "PUT",
headers: {
key: uploadKey,
id: uploadId,
"part-number": `${i + 1}`,
},
body: chunk,
},
body: chunk,
});
);

if (!uploadMultipartRes.ok) {
console.error(
Expand All @@ -697,14 +703,17 @@ const main = defineCommand({
const { part } = await uploadMultipartRes.json();
uploadedParts.push(part);
}
const completeMultipartRes = await fetch(completeMultipart, {
method: "POST",
headers: {
key: uploadKey,
id: uploadId,
"uploaded-parts": JSON.stringify(uploadedParts),
const completeMultipartRes = await fetchWithRetry(
completeMultipart,
{
method: "POST",
headers: {
key: uploadKey,
id: uploadId,
"uploaded-parts": JSON.stringify(uploadedParts),
},
},
});
);
if (!completeMultipartRes.ok) {
console.error(
`Error completing ${key}: ${await completeMultipartRes.text()}`,
Expand All @@ -719,7 +728,7 @@ const main = defineCommand({
}
}

const res = await fetch(publishUrl, {
const res = await fetchWithRetry(publishUrl, {
method: "POST",
headers: {
"sb-sha": sha,
Expand Down Expand Up @@ -906,6 +915,23 @@ function hijackDeps(
}
}

async function fetchWithRetry(
input: URL | string,
init: RequestInit,
): Promise<Response> {
const maxRetries = 3;
let res = await fetch(input, init);
for (let attempt = 1; attempt <= maxRetries && res.status >= 500; attempt++) {
const delay = 1000 * 2 ** (attempt - 1); // 1s, 2s, 4s
console.warn(
`Server error (${res.status}). Retrying in ${delay / 1000}s (attempt ${attempt}/${maxRetries})...`,
);
await new Promise((resolve) => setTimeout(resolve, delay));
res = await fetch(input, init);
}
return res;
}

function getFormEntrySize(entry: FormDataEntryValue) {
if (typeof entry === "string") {
return entry.length;
Expand Down
Loading