Skip to content

Commit 2e2d65e

Browse files
cipolleschifacebook-github-bot
authored andcommitted
Refactor the prebuild script to use fetch instead of curl
Summary: 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 3d8943c commit 2e2d65e

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
@@ -12,6 +12,9 @@
1212
const {execSync} = require('child_process');
1313
const fs = require('fs');
1414
const path = require('path');
15+
const {promisify} = require('util');
16+
const stream = require('stream');
17+
const pipeline = promisify(stream.pipeline);
1518

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

62-
const sourceType = hermesSourceType(resolvedVersion, buildType);
63-
localPath = resolveSourceFromSourceType(
65+
const sourceType = await hermesSourceType(resolvedVersion, buildType);
66+
localPath = await resolveSourceFromSourceType(
6467
sourceType,
6568
resolvedVersion,
6669
buildType,
@@ -161,68 +164,84 @@ function getNightlyTarballUrl(
161164
buildType /*: 'debug' | 'release' */,
162165
) /*: string */ {
163166
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}`;
167168
}
168169

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

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

194-
function hermesSourceType(
206+
/**
207+
* Determines the source type for Hermes based on availability
208+
*/
209+
async function hermesSourceType(
195210
version /*: string */,
196211
buildType /*: 'debug' | 'release' */,
197-
) /*: HermesEngineSourceType */ {
212+
) /*: Promise<HermesEngineSourceType> */ {
198213
if (hermesEngineTarballEnvvarDefined()) {
199214
hermesLog('Using local prebuild tarball');
200215
return HermesEngineSourceTypes.LOCAL_PREBUILT_TARBALL;
201216
}
202-
if (hermesArtifactExists(getTarballUrl(version, buildType))) {
217+
218+
const tarballUrl = getTarballUrl(version, buildType);
219+
if (await hermesArtifactExists(tarballUrl)) {
203220
hermesLog(`Using download prebuild ${buildType} tarball`);
204221
return HermesEngineSourceTypes.DOWNLOAD_PREBUILD_TARBALL;
205222
}
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)) {
211229
hermesLog('Using download prebuild nightly tarball');
212230
return HermesEngineSourceTypes.DOWNLOAD_PREBUILT_NIGHTLY_TARBALL;
213231
}
232+
214233
hermesLog(
215234
'Using download prebuild nightly tarball - this is a fallback and might not work.',
216235
);
217236
return HermesEngineSourceTypes.DOWNLOAD_PREBUILT_NIGHTLY_TARBALL;
218237
}
219238

220-
function resolveSourceFromSourceType(
239+
async function resolveSourceFromSourceType(
221240
sourceType /*: HermesEngineSourceType */,
222241
version /*: string */,
223242
buildType /*: 'debug' | 'release' */,
224243
artifactsPath /*: string*/,
225-
) /*: string */ {
244+
) /*: Promise<string> */ {
226245
switch (sourceType) {
227246
case HermesEngineSourceTypes.LOCAL_PREBUILT_TARBALL:
228247
return localPrebuiltTarball();
@@ -239,67 +258,101 @@ function resolveSourceFromSourceType(
239258
}
240259

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

255-
function downloadPrebuildTarball(
275+
async function downloadPrebuildTarball(
256276
version /*: string */,
257277
buildType /*: 'debug' | 'release' */,
258278
artifactsPath /*: string*/,
259-
) /*: string */ {
279+
) /*: Promise<string> */ {
260280
const url = getTarballUrl(version, buildType);
261281
hermesLog(`Using release tarball from URL: ${url}`);
262282
return downloadStableHermes(version, buildType, artifactsPath);
263283
}
264284

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

275-
function downloadStableHermes(
297+
async function downloadStableHermes(
276298
version /*: string */,
277299
buildType /*: 'debug' | 'release' */,
278300
artifactsPath /*: string */,
279-
) /*: string */ {
301+
) /*: Promise<string> */ {
280302
const tarballUrl = getTarballUrl(version, buildType);
281303
return downloadHermesTarball(tarballUrl, version, buildType, artifactsPath);
282304
}
283305

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

0 commit comments

Comments
 (0)