@@ -10,6 +10,29 @@ import * as out from "../utils/output";
1010import { handleSSEStream } from "../utils/sse" ;
1111import { resolveToken } from "../utils/token.js" ;
1212
13+ async function fetchWithRetry (
14+ url : string ,
15+ options : RequestInit ,
16+ maxRetries = 3
17+ ) : Promise < Response > {
18+ for ( let attempt = 0 ; attempt <= maxRetries ; attempt ++ ) {
19+ try {
20+ const response = await fetch ( url , options ) ;
21+ if ( response . status < 500 || attempt === maxRetries ) {
22+ return response ;
23+ }
24+ // 5xx — retry with backoff
25+ const delay = 1000 * Math . pow ( 2 , attempt ) ;
26+ await new Promise ( ( r ) => setTimeout ( r , delay ) ) ;
27+ } catch ( error ) {
28+ if ( attempt === maxRetries ) throw error ;
29+ const delay = 1000 * Math . pow ( 2 , attempt ) ;
30+ await new Promise ( ( r ) => setTimeout ( r , delay ) ) ;
31+ }
32+ }
33+ throw new Error ( "Retry logic error" ) ;
34+ }
35+
1336// Publisher types for API interaction
1437interface Publisher {
1538 id : number ;
@@ -65,7 +88,7 @@ async function createNewPublisher(
6588 // Try to get user's name for default
6689 let defaultName = "" ;
6790 try {
68- const userResponse = await fetch ( `${ apiUrl } /v1/twist/user` , {
91+ const userResponse = await fetchWithRetry ( `${ apiUrl } /v1/twist/user` , {
6992 method : "GET" ,
7093 headers : {
7194 Authorization : `Bearer ${ deployToken } ` ,
@@ -83,6 +106,12 @@ async function createNewPublisher(
83106 "Your login token is invalid or has expired. Please run 'plot login' to authenticate."
84107 ) ;
85108 process . exit ( 1 ) ;
109+ } else if ( userResponse . status >= 500 ) {
110+ out . error (
111+ "Server error" ,
112+ "The Plot API is temporarily unavailable. Please try again."
113+ ) ;
114+ process . exit ( 1 ) ;
86115 }
87116 } catch ( error ) {
88117 // Ignore error, just won't have default
@@ -130,17 +159,20 @@ async function createNewPublisher(
130159 try {
131160 out . progress ( `Creating publisher "${ publisherName } "...` ) ;
132161
133- const createResponse = await fetch ( `${ apiUrl } /v1/twist/publishers` , {
134- method : "POST" ,
135- headers : {
136- "Content-Type" : "application/json" ,
137- Authorization : `Bearer ${ deployToken } ` ,
138- } ,
139- body : JSON . stringify ( {
140- name : publisherName ,
141- url : response . url || null ,
142- } as NewPublisher ) ,
143- } ) ;
162+ const createResponse = await fetchWithRetry (
163+ `${ apiUrl } /v1/twist/publishers` ,
164+ {
165+ method : "POST" ,
166+ headers : {
167+ "Content-Type" : "application/json" ,
168+ Authorization : `Bearer ${ deployToken } ` ,
169+ } ,
170+ body : JSON . stringify ( {
171+ name : publisherName ,
172+ url : response . url || null ,
173+ } as NewPublisher ) ,
174+ }
175+ ) ;
144176
145177 if ( ! createResponse . ok ) {
146178 if ( createResponse . status === 401 ) {
@@ -150,6 +182,13 @@ async function createNewPublisher(
150182 ) ;
151183 process . exit ( 1 ) ;
152184 }
185+ if ( createResponse . status >= 500 ) {
186+ out . error (
187+ "Server error" ,
188+ "The Plot API is temporarily unavailable. Please try again."
189+ ) ;
190+ process . exit ( 1 ) ;
191+ }
153192 const errorText = await createResponse . text ( ) ;
154193 out . error ( "Failed to create publisher" , errorText ) ;
155194 process . exit ( 1 ) ;
@@ -312,7 +351,7 @@ export async function deployCommand(options: DeployOptions) {
312351 const urlPath = deploymentId || "personal" ;
313352
314353 try {
315- const twistInfoResponse = await fetch (
354+ const twistInfoResponse = await fetchWithRetry (
316355 `${ options . apiUrl } /v1/twist/${ urlPath } ` ,
317356 {
318357 method : "GET" ,
@@ -336,6 +375,12 @@ export async function deployCommand(options: DeployOptions) {
336375 "Your login token is invalid or has expired. Please run 'plot login' to authenticate."
337376 ) ;
338377 process . exit ( 1 ) ;
378+ } else if ( twistInfoResponse . status >= 500 ) {
379+ out . error (
380+ "Server error" ,
381+ "The Plot API is temporarily unavailable. Please try again."
382+ ) ;
383+ process . exit ( 1 ) ;
339384 } else if ( twistInfoResponse . status !== 404 ) {
340385 // Log non-404 errors, but continue with publisher setup
341386 const errorText = await twistInfoResponse . text ( ) ;
@@ -359,7 +404,7 @@ export async function deployCommand(options: DeployOptions) {
359404
360405 try {
361406 // Fetch accessible publishers
362- const publishersResponse = await fetch (
407+ const publishersResponse = await fetchWithRetry (
363408 `${ options . apiUrl } /v1/twist/publishers` ,
364409 {
365410 method : "GET" ,
@@ -378,6 +423,13 @@ export async function deployCommand(options: DeployOptions) {
378423 ) ;
379424 process . exit ( 1 ) ;
380425 }
426+ if ( publishersResponse . status >= 500 ) {
427+ out . error (
428+ "Server error" ,
429+ "The Plot API is temporarily unavailable. Please try again."
430+ ) ;
431+ process . exit ( 1 ) ;
432+ }
381433 const errorText = await publishersResponse . text ( ) ;
382434 out . warning ( "Failed to fetch publishers" , [ errorText ] ) ;
383435 } else {
@@ -520,15 +572,18 @@ export async function deployCommand(options: DeployOptions) {
520572 const urlPath = deploymentId || "personal" ;
521573
522574 try {
523- const response = await fetch ( `${ options . apiUrl } /v1/twist/${ urlPath } ` , {
524- method : "POST" ,
525- headers : {
526- "Content-Type" : "application/json" ,
527- Accept : "text/event-stream" ,
528- Authorization : `Bearer ${ deployToken } ` ,
529- } ,
530- body : JSON . stringify ( requestBody ) ,
531- } ) ;
575+ const response = await fetchWithRetry (
576+ `${ options . apiUrl } /v1/twist/${ urlPath } ` ,
577+ {
578+ method : "POST" ,
579+ headers : {
580+ "Content-Type" : "application/json" ,
581+ Accept : "text/event-stream" ,
582+ Authorization : `Bearer ${ deployToken } ` ,
583+ } ,
584+ body : JSON . stringify ( requestBody ) ,
585+ }
586+ ) ;
532587
533588 if ( ! response . ok ) {
534589 if ( response . status === 401 ) {
@@ -538,6 +593,13 @@ export async function deployCommand(options: DeployOptions) {
538593 ) ;
539594 process . exit ( 1 ) ;
540595 }
596+ if ( response . status >= 500 ) {
597+ out . error (
598+ "Server error" ,
599+ "The Plot API is temporarily unavailable. Please try again."
600+ ) ;
601+ process . exit ( 1 ) ;
602+ }
541603 const errorText = await response . text ( ) ;
542604 out . error ( "Upload failed" , errorText ) ;
543605 process . exit ( 1 ) ;
0 commit comments