@@ -11,7 +11,7 @@ const DIST_DIR = path.resolve(process.env.DIST_DIR || 'dist')
1111const RELEASE_METADATA_PATH = path . resolve ( process . env . GITHUB_RELEASE_JSON || 'github-release.json' )
1212const MAX_ATTEMPTS = 3
1313const RETRYABLE_STATUS_CODES = new Set ( [ 408 , 429 , 500 , 502 , 503 , 504 ] )
14- const UPLOAD_CONCURRENCY = Number . parseInt ( process . env . ATOM_UPLOAD_CONCURRENCY || '3 ' , 10 )
14+ const UPLOAD_TIMEOUT_MINUTES = Number . parseInt ( process . env . ATOM_UPLOAD_TIMEOUT_MINUTES || '10 ' , 10 )
1515
1616function requireEnvironmentVariable ( name , value ) {
1717 if ( ! value ) {
@@ -192,41 +192,49 @@ async function getUploadRequest(fileName) {
192192
193193async function uploadAsset ( filePath ) {
194194 const fileName = path . basename ( filePath )
195- const uploadRequest = await getUploadRequest ( fileName )
196- const fileSize = fs . statSync ( filePath ) . size
197- const uploadHeaders = new Headers ( uploadRequest . headers )
198- uploadHeaders . set ( 'Content-Length' , String ( fileSize ) )
195+ const fileBuffer = await fs . promises . readFile ( filePath )
196+ const fileSize = fileBuffer . byteLength
199197 const startedAt = Date . now ( )
200198
201199 console . log ( `[AtomGit] Uploading ${ fileName } (${ formatFileSize ( fileSize ) } )` )
202- const response = await requestWithRetry (
203- uploadRequest . url ,
204- ( ) => ( {
205- method : 'PUT' ,
206- headers : uploadHeaders ,
207- body : fs . createReadStream ( filePath ) ,
208- duplex : 'half'
209- } ) ,
210- `upload ${ fileName } `
211- )
212200
213- await assertSuccessfulResponse ( response , `Upload ${ fileName } ` )
214- console . log ( `[AtomGit] Uploaded ${ fileName } in ${ formatDuration ( Date . now ( ) - startedAt ) } ` )
215- }
201+ for ( let attempt = 1 ; attempt <= MAX_ATTEMPTS ; attempt += 1 ) {
202+ const uploadRequest = await getUploadRequest ( fileName )
203+ const uploadHeaders = new Headers ( uploadRequest . headers )
204+ uploadHeaders . set ( 'Content-Length' , String ( fileSize ) )
205+ let response
206+
207+ try {
208+ response = await fetch ( uploadRequest . url , {
209+ method : 'PUT' ,
210+ headers : uploadHeaders ,
211+ body : fileBuffer ,
212+ signal : AbortSignal . timeout ( UPLOAD_TIMEOUT_MINUTES * 60 * 1000 )
213+ } )
214+ } catch ( error ) {
215+ if ( attempt === MAX_ATTEMPTS ) {
216+ throw error
217+ }
216218
217- async function uploadAssets ( assetPaths ) {
218- let nextAssetIndex = 0
219- const workerCount = Math . min ( UPLOAD_CONCURRENCY , assetPaths . length )
219+ console . warn ( `[AtomGit] Upload ${ fileName } failed; requesting a new upload URL (${ attempt } /${ MAX_ATTEMPTS } )` )
220+ await wait ( 1000 * attempt )
221+ continue
222+ }
220223
221- const runWorker = async ( ) => {
222- while ( nextAssetIndex < assetPaths . length ) {
223- const assetIndex = nextAssetIndex
224- nextAssetIndex += 1
225- await uploadAsset ( assetPaths [ assetIndex ] )
224+ if ( response . ok ) {
225+ await response . text ( )
226+ console . log ( `[AtomGit] Uploaded ${ fileName } in ${ formatDuration ( Date . now ( ) - startedAt ) } ` )
227+ return
226228 }
227- }
228229
229- await Promise . all ( Array . from ( { length : workerCount } , runWorker ) )
230+ const responseBody = await readResponseBody ( response )
231+ if ( ! RETRYABLE_STATUS_CODES . has ( response . status ) || attempt === MAX_ATTEMPTS ) {
232+ throw new Error ( `Upload ${ fileName } failed with HTTP ${ response . status } : ${ formatResponseBody ( responseBody ) } ` )
233+ }
234+
235+ console . warn ( `[AtomGit] Upload ${ fileName } returned ${ response . status } ; requesting a new upload URL (${ attempt } /${ MAX_ATTEMPTS } )` )
236+ await wait ( 1000 * attempt )
237+ }
230238}
231239
232240function readReleaseMetadata ( ) {
@@ -264,8 +272,8 @@ async function publishRelease() {
264272 requireEnvironmentVariable ( 'ATOM_OWNER' , ATOM_OWNER )
265273 requireEnvironmentVariable ( 'ATOM_REPO' , ATOM_REPO )
266274 requireEnvironmentVariable ( 'RELEASE_TAG' , RELEASE_TAG )
267- if ( ! Number . isInteger ( UPLOAD_CONCURRENCY ) || UPLOAD_CONCURRENCY < 1 || UPLOAD_CONCURRENCY > 10 ) {
268- throw new Error ( 'ATOM_UPLOAD_CONCURRENCY must be an integer between 1 and 10 ' )
275+ if ( ! Number . isInteger ( UPLOAD_TIMEOUT_MINUTES ) || UPLOAD_TIMEOUT_MINUTES < 1 || UPLOAD_TIMEOUT_MINUTES > 120 ) {
276+ throw new Error ( 'ATOM_UPLOAD_TIMEOUT_MINUTES must be an integer between 1 and 120 ' )
269277 }
270278
271279 const metadata = readReleaseMetadata ( )
@@ -278,7 +286,6 @@ async function publishRelease() {
278286 await verifyCredentials ( )
279287 const release = await getOrCreateRelease ( metadata )
280288 const existingAssetNames = getExistingAssetNames ( release )
281- const pendingAssetPaths = [ ]
282289
283290 for ( const assetPath of assetPaths ) {
284291 const fileName = path . basename ( assetPath )
@@ -287,12 +294,7 @@ async function publishRelease() {
287294 continue
288295 }
289296
290- pendingAssetPaths . push ( assetPath )
291- }
292-
293- if ( pendingAssetPaths . length > 0 ) {
294- console . log ( `[AtomGit] Uploading ${ pendingAssetPaths . length } assets with concurrency ${ UPLOAD_CONCURRENCY } ` )
295- await uploadAssets ( pendingAssetPaths )
297+ await uploadAsset ( assetPath )
296298 }
297299
298300 console . log ( `[AtomGit] Release ${ RELEASE_TAG } mirror completed` )
0 commit comments