12
12
const { execSync} = require ( 'child_process' ) ;
13
13
const fs = require ( 'fs' ) ;
14
14
const path = require ( 'path' ) ;
15
+ const { promisify} = require ( 'util' ) ;
16
+ const stream = require ( 'stream' ) ;
17
+ const pipeline = promisify ( stream . pipeline ) ;
15
18
16
19
/**
17
20
* Downloads hermes artifacts from the specified version and build type. If you want to specify a specific
@@ -59,8 +62,8 @@ async function prepareHermesArtifactsAsync(
59
62
return artifactsPath ;
60
63
}
61
64
62
- const sourceType = hermesSourceType ( resolvedVersion , buildType ) ;
63
- localPath = resolveSourceFromSourceType (
65
+ const sourceType = await hermesSourceType ( resolvedVersion , buildType ) ;
66
+ localPath = await resolveSourceFromSourceType (
64
67
sourceType ,
65
68
resolvedVersion ,
66
69
buildType ,
@@ -161,68 +164,84 @@ function getNightlyTarballUrl(
161
164
buildType /*: 'debug' | 'release' */ ,
162
165
) /*: string */ {
163
166
const params = `r=snapshots&g=com.facebook.react&a=react-native-artifacts&c=hermes-ios-${ buildType } &e=tar.gz&v=${ version } -SNAPSHOT` ;
164
- return resolveUrlRedirects (
165
- `https://oss.sonatype.org/service/local/artifact/maven/redirect?${ params } ` ,
166
- ) ;
167
+ return `https://oss.sonatype.org/service/local/artifact/maven/redirect?${ params } ` ;
167
168
}
168
169
169
- function resolveUrlRedirects ( url /*: string */ ) /*: string */ {
170
- // Synchronously resolve the final URL after redirects using curl
170
+ /**
171
+ * Resolves URL redirects using fetch instead of curl
172
+ */
173
+ async function resolveUrlRedirects ( url /*: string */ ) /*: Promise<string> */ {
171
174
try {
172
- return execSync ( `curl -Ls -o /dev/null -w '%{url_effective}' "${ url } "` )
173
- . toString ( )
174
- . trim ( ) ;
175
+ // $FlowFixMe - fetch is dynamically imported
176
+ const response /*: FetchResponse */ = await fetch ( url , {
177
+ method : 'HEAD' ,
178
+ redirect : 'follow' ,
179
+ } ) ;
180
+
181
+ return response . url ;
175
182
} catch ( e ) {
176
183
hermesLog ( `Failed to resolve URL redirects\n${ e } ` , 'error' ) ;
177
184
return url ;
178
185
}
179
186
}
180
187
181
- function hermesArtifactExists ( tarballUrl /*: string */ ) /*: boolean */ {
188
+ /**
189
+ * Checks if a Hermes artifact exists at the given URL using fetch instead of curl
190
+ */
191
+ async function hermesArtifactExists (
192
+ tarballUrl /*: string */ ,
193
+ ) /*: Promise<boolean> */ {
182
194
try {
183
- const code = execSync (
184
- `curl -o /dev/null --silent -Iw '%{http_code}' -L " ${ tarballUrl } "` ,
185
- )
186
- . toString ( )
187
- . trim ( ) ;
188
- return code === ' 200' ;
195
+ // $FlowFixMe - fetch is dynamically imported
196
+ const response /*: FetchResponse */ = await fetch ( tarballUrl , {
197
+ method : 'HEAD' ,
198
+ } ) ;
199
+
200
+ return response . status === 200 ;
189
201
} catch ( e ) {
190
202
return false ;
191
203
}
192
204
}
193
205
194
- function hermesSourceType (
206
+ /**
207
+ * Determines the source type for Hermes based on availability
208
+ */
209
+ async function hermesSourceType (
195
210
version /*: string */ ,
196
211
buildType /*: 'debug' | 'release' */ ,
197
- ) /*: HermesEngineSourceType */ {
212
+ ) /*: Promise< HermesEngineSourceType> */ {
198
213
if ( hermesEngineTarballEnvvarDefined ( ) ) {
199
214
hermesLog ( 'Using local prebuild tarball' ) ;
200
215
return HermesEngineSourceTypes . LOCAL_PREBUILT_TARBALL ;
201
216
}
202
- if ( hermesArtifactExists ( getTarballUrl ( version , buildType ) ) ) {
217
+
218
+ const tarballUrl = getTarballUrl ( version , buildType ) ;
219
+ if ( await hermesArtifactExists ( tarballUrl ) ) {
203
220
hermesLog ( `Using download prebuild ${ buildType } tarball` ) ;
204
221
return HermesEngineSourceTypes . DOWNLOAD_PREBUILD_TARBALL ;
205
222
}
206
- if (
207
- hermesArtifactExists (
208
- getNightlyTarballUrl ( version , buildType ) . replace ( / \\ / g, '' ) ,
209
- )
210
- ) {
223
+
224
+ // For nightly tarball, we need to resolve redirects first
225
+ const nightlyUrl = await resolveUrlRedirects (
226
+ getNightlyTarballUrl ( version , buildType ) ,
227
+ ) ;
228
+ if ( await hermesArtifactExists ( nightlyUrl ) ) {
211
229
hermesLog ( 'Using download prebuild nightly tarball' ) ;
212
230
return HermesEngineSourceTypes . DOWNLOAD_PREBUILT_NIGHTLY_TARBALL ;
213
231
}
232
+
214
233
hermesLog (
215
234
'Using download prebuild nightly tarball - this is a fallback and might not work.' ,
216
235
) ;
217
236
return HermesEngineSourceTypes . DOWNLOAD_PREBUILT_NIGHTLY_TARBALL ;
218
237
}
219
238
220
- function resolveSourceFromSourceType (
239
+ async function resolveSourceFromSourceType (
221
240
sourceType /*: HermesEngineSourceType */ ,
222
241
version /*: string */ ,
223
242
buildType /*: 'debug' | 'release' */ ,
224
243
artifactsPath /*: string*/ ,
225
- ) /*: string */ {
244
+ ) /*: Promise< string> */ {
226
245
switch ( sourceType ) {
227
246
case HermesEngineSourceTypes . LOCAL_PREBUILT_TARBALL :
228
247
return localPrebuiltTarball ( ) ;
@@ -239,67 +258,101 @@ function resolveSourceFromSourceType(
239
258
}
240
259
241
260
function localPrebuiltTarball ( ) /*: string */ {
261
+ // $FlowFixMe - process is a global object
242
262
const tarballPath = process . env . HERMES_ENGINE_TARBALL_PATH ;
243
263
if ( tarballPath && fs . existsSync ( tarballPath ) ) {
244
264
hermesLog (
245
265
`Using pre-built binary from local path defined by HERMES_ENGINE_TARBALL_PATH envvar: ${ tarballPath } ` ,
246
266
) ;
247
- return `file:// ${ tarballPath } ` ;
267
+ return tarballPath ;
248
268
}
249
269
abort (
250
270
`[Hermes] HERMES_ENGINE_TARBALL_PATH is set, but points to a non-existing file: "${ tarballPath ?? 'unknown' } "\nIf you don't want to use tarball, run 'unset HERMES_ENGINE_TARBALL_PATH'` ,
251
271
) ;
252
272
return '' ;
253
273
}
254
274
255
- function downloadPrebuildTarball (
275
+ async function downloadPrebuildTarball (
256
276
version /*: string */ ,
257
277
buildType /*: 'debug' | 'release' */ ,
258
278
artifactsPath /*: string*/ ,
259
- ) /*: string */ {
279
+ ) /*: Promise< string> */ {
260
280
const url = getTarballUrl ( version , buildType ) ;
261
281
hermesLog ( `Using release tarball from URL: ${ url } ` ) ;
262
282
return downloadStableHermes ( version , buildType , artifactsPath ) ;
263
283
}
264
284
265
- function downloadPrebuiltNightlyTarball (
285
+ async function downloadPrebuiltNightlyTarball (
266
286
version /*: string */ ,
267
287
buildType /*: 'debug' | 'release' */ ,
268
288
artifactsPath /*: string*/ ,
269
- ) /*: string */ {
270
- const url = getNightlyTarballUrl ( version , buildType ) ;
289
+ ) /*: Promise<string> */ {
290
+ const url = await resolveUrlRedirects (
291
+ getNightlyTarballUrl ( version , buildType ) ,
292
+ ) ;
271
293
hermesLog ( `Using nightly tarball from URL: ${ url } ` ) ;
272
294
return downloadHermesTarball ( url , version , buildType , artifactsPath ) ;
273
295
}
274
296
275
- function downloadStableHermes (
297
+ async function downloadStableHermes (
276
298
version /*: string */ ,
277
299
buildType /*: 'debug' | 'release' */ ,
278
300
artifactsPath /*: string */ ,
279
- ) /*: string */ {
301
+ ) /*: Promise< string> */ {
280
302
const tarballUrl = getTarballUrl ( version , buildType ) ;
281
303
return downloadHermesTarball ( tarballUrl , version , buildType , artifactsPath ) ;
282
304
}
283
305
284
- function downloadHermesTarball (
306
+ /**
307
+ * Downloads a Hermes tarball using fetch instead of curl
308
+ */
309
+ async function downloadHermesTarball (
285
310
tarballUrl /*: string */ ,
286
311
version /*: string */ ,
287
312
buildType /*: 'debug' | 'release' */ ,
288
313
artifactsPath /*: string */ ,
289
- ) /*: string */ {
314
+ ) /*: Promise< string> */ {
290
315
const destPath = buildType
291
316
? `${ artifactsPath } /hermes-ios-${ version } -${ buildType } .tar.gz`
292
317
: `${ artifactsPath } /hermes-ios-${ version } .tar.gz` ;
318
+
293
319
if ( ! fs . existsSync ( destPath ) ) {
294
320
const tmpFile = `${ artifactsPath } /hermes-ios.download` ;
295
321
try {
296
322
fs . mkdirSync ( artifactsPath , { recursive : true } ) ;
297
323
hermesLog ( `Downloading Hermes tarball from ${ tarballUrl } ` ) ;
298
- execSync (
299
- `curl "${ tarballUrl } " -Lo "${ tmpFile } " && mv "${ tmpFile } " "${ destPath } "` ,
300
- ) ;
324
+
325
+ // $FlowFixMe - fetch is dynamically imported
326
+ const response /*: FetchResponse */ = await fetch ( tarballUrl ) ;
327
+
328
+ if ( ! response . ok ) {
329
+ throw new Error (
330
+ `Failed to download: ${ response . status } ${ response . statusText } ` ,
331
+ ) ;
332
+ }
333
+
334
+ // Create a write stream to the temporary file
335
+ const fileStream = fs . createWriteStream ( tmpFile ) ;
336
+
337
+ // Use Node.js stream pipeline to safely pipe the response body to the file
338
+ if ( response . body ) {
339
+ await pipeline ( response . body , fileStream ) ;
340
+ } else {
341
+ // For older fetch implementations that don't support response.body as a stream
342
+ const buffer = await response . buffer ( ) ;
343
+ fs . writeFileSync ( tmpFile , buffer ) ;
344
+ }
345
+
346
+ // Move the temporary file to the destination path
347
+ fs . renameSync ( tmpFile , destPath ) ;
301
348
} catch ( e ) {
302
- abort ( `Failed to download Hermes tarball from ${ tarballUrl } ` ) ;
349
+ // Clean up the temporary file if it exists
350
+ if ( fs . existsSync ( tmpFile ) ) {
351
+ fs . unlinkSync ( tmpFile ) ;
352
+ }
353
+ abort (
354
+ `Failed to download Hermes tarball from ${ tarballUrl } : ${ e . message } ` ,
355
+ ) ;
303
356
}
304
357
}
305
358
return destPath ;
@@ -317,6 +370,7 @@ function hermesLog(
317
370
// Simple log coloring for terminal output
318
371
const prefix = '[Hermes] ' ;
319
372
let colorFn = ( x /*:string*/ ) => x ;
373
+ // $FlowFixMe - process is a global object
320
374
if ( process . stdout . isTTY ) {
321
375
if ( level === 'info' ) colorFn = x => `\x1b[32m${ x } \x1b[0m` ;
322
376
else if ( level === 'error' ) colorFn = x => `\x1b[31m${ x } \x1b[0m` ;
0 commit comments