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