Skip to content

Commit 1825d3f

Browse files
cipolleschifacebook-github-bot
authored andcommitted
Refactor the prebuild script to use fetch instead of curl (#51398)
Summary: Pull Request resolved: #51398 As a followup after the comment of D74565936 (https://www.internalfb.com/diff/D74565936?dst_version_fbid=727294986523938&transaction_fbid=1121452289999956), I refactored the code to use `fetch` instead of `curl`. ## Changelog: [Internal] - Refactor the code to use Fetch instead of curl Differential Revision: D74886699
1 parent 6a8e9f1 commit 1825d3f

File tree

1 file changed

+95
-41
lines changed
  • packages/react-native/scripts/ios-prebuild

1 file changed

+95
-41
lines changed

packages/react-native/scripts/ios-prebuild/hermes.js

+95-41
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@
1111
const {execSync} = require('child_process');
1212
const fs = require('fs');
1313
const path = require('path');
14+
const {promisify} = require('util');
15+
const stream = require('stream');
16+
const pipeline = promisify(stream.pipeline);
1417

1518
/**
1619
* Downloads hermes artifacts from the specified version and build type. If you want to specify a specific
@@ -58,8 +61,8 @@ async function prepareHermesArtifactsAsync(
5861
return artifactsPath;
5962
}
6063

61-
const sourceType = hermesSourceType(resolvedVersion, buildType);
62-
localPath = resolveSourceFromSourceType(
64+
const sourceType = await hermesSourceType(resolvedVersion, buildType);
65+
localPath = await resolveSourceFromSourceType(
6366
sourceType,
6467
resolvedVersion,
6568
buildType,
@@ -160,68 +163,84 @@ function getNightlyTarballUrl(
160163
buildType /*: 'debug' | 'release' */,
161164
) /*: string */ {
162165
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}`;
166167
}
167168

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> */ {
170173
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;
174181
} catch (e) {
175182
hermesLog(`Failed to resolve URL redirects\n${e}`, 'error');
176183
return url;
177184
}
178185
}
179186

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> */ {
181193
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;
188200
} catch (e) {
189201
return false;
190202
}
191203
}
192204

193-
function hermesSourceType(
205+
/**
206+
* Determines the source type for Hermes based on availability
207+
*/
208+
async function hermesSourceType(
194209
version /*: string */,
195210
buildType /*: 'debug' | 'release' */,
196-
) /*: HermesEngineSourceType */ {
211+
) /*: Promise<HermesEngineSourceType> */ {
197212
if (hermesEngineTarballEnvvarDefined()) {
198213
hermesLog('Using local prebuild tarball');
199214
return HermesEngineSourceTypes.LOCAL_PREBUILT_TARBALL;
200215
}
201-
if (hermesArtifactExists(getTarballUrl(version, buildType))) {
216+
217+
const tarballUrl = getTarballUrl(version, buildType);
218+
if (await hermesArtifactExists(tarballUrl)) {
202219
hermesLog(`Using download prebuild ${buildType} tarball`);
203220
return HermesEngineSourceTypes.DOWNLOAD_PREBUILD_TARBALL;
204221
}
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)) {
210228
hermesLog('Using download prebuild nightly tarball');
211229
return HermesEngineSourceTypes.DOWNLOAD_PREBUILT_NIGHTLY_TARBALL;
212230
}
231+
213232
hermesLog(
214233
'Using download prebuild nightly tarball - this is a fallback and might not work.',
215234
);
216235
return HermesEngineSourceTypes.DOWNLOAD_PREBUILT_NIGHTLY_TARBALL;
217236
}
218237

219-
function resolveSourceFromSourceType(
238+
async function resolveSourceFromSourceType(
220239
sourceType /*: HermesEngineSourceType */,
221240
version /*: string */,
222241
buildType /*: 'debug' | 'release' */,
223242
artifactsPath /*: string*/,
224-
) /*: string */ {
243+
) /*: Promise<string> */ {
225244
switch (sourceType) {
226245
case HermesEngineSourceTypes.LOCAL_PREBUILT_TARBALL:
227246
return localPrebuiltTarball();
@@ -238,67 +257,101 @@ function resolveSourceFromSourceType(
238257
}
239258

240259
function localPrebuiltTarball() /*: string */ {
260+
// $FlowFixMe - process is a global object
241261
const tarballPath = process.env.HERMES_ENGINE_TARBALL_PATH;
242262
if (tarballPath && fs.existsSync(tarballPath)) {
243263
hermesLog(
244264
`Using pre-built binary from local path defined by HERMES_ENGINE_TARBALL_PATH envvar: ${tarballPath}`,
245265
);
246-
return `file://${tarballPath}`;
266+
return tarballPath;
247267
}
248268
abort(
249269
`[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'`,
250270
);
251271
return '';
252272
}
253273

254-
function downloadPrebuildTarball(
274+
async function downloadPrebuildTarball(
255275
version /*: string */,
256276
buildType /*: 'debug' | 'release' */,
257277
artifactsPath /*: string*/,
258-
) /*: string */ {
278+
) /*: Promise<string> */ {
259279
const url = getTarballUrl(version, buildType);
260280
hermesLog(`Using release tarball from URL: ${url}`);
261281
return downloadStableHermes(version, buildType, artifactsPath);
262282
}
263283

264-
function downloadPrebuiltNightlyTarball(
284+
async function downloadPrebuiltNightlyTarball(
265285
version /*: string */,
266286
buildType /*: 'debug' | 'release' */,
267287
artifactsPath /*: string*/,
268-
) /*: string */ {
269-
const url = getNightlyTarballUrl(version, buildType);
288+
) /*: Promise<string> */ {
289+
const url = await resolveUrlRedirects(
290+
getNightlyTarballUrl(version, buildType),
291+
);
270292
hermesLog(`Using nightly tarball from URL: ${url}`);
271293
return downloadHermesTarball(url, version, buildType, artifactsPath);
272294
}
273295

274-
function downloadStableHermes(
296+
async function downloadStableHermes(
275297
version /*: string */,
276298
buildType /*: 'debug' | 'release' */,
277299
artifactsPath /*: string */,
278-
) /*: string */ {
300+
) /*: Promise<string> */ {
279301
const tarballUrl = getTarballUrl(version, buildType);
280302
return downloadHermesTarball(tarballUrl, version, buildType, artifactsPath);
281303
}
282304

283-
function downloadHermesTarball(
305+
/**
306+
* Downloads a Hermes tarball using fetch instead of curl
307+
*/
308+
async function downloadHermesTarball(
284309
tarballUrl /*: string */,
285310
version /*: string */,
286311
buildType /*: 'debug' | 'release' */,
287312
artifactsPath /*: string */,
288-
) /*: string */ {
313+
) /*: Promise<string> */ {
289314
const destPath = buildType
290315
? `${artifactsPath}/hermes-ios-${version}-${buildType}.tar.gz`
291316
: `${artifactsPath}/hermes-ios-${version}.tar.gz`;
317+
292318
if (!fs.existsSync(destPath)) {
293319
const tmpFile = `${artifactsPath}/hermes-ios.download`;
294320
try {
295321
fs.mkdirSync(artifactsPath, {recursive: true});
296322
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);
300347
} 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+
);
302355
}
303356
}
304357
return destPath;
@@ -316,6 +369,7 @@ function hermesLog(
316369
// Simple log coloring for terminal output
317370
const prefix = '[Hermes] ';
318371
let colorFn = (x /*:string*/) => x;
372+
// $FlowFixMe - process is a global object
319373
if (process.stdout.isTTY) {
320374
if (level === 'info') colorFn = x => `\x1b[32m${x}\x1b[0m`;
321375
else if (level === 'error') colorFn = x => `\x1b[31m${x}\x1b[0m`;

0 commit comments

Comments
 (0)