#!/usr/bin/env node /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * * @format */ 'use strict'; const asyncRequest = require('request'); const {exec} = require('shelljs'); const util = require('util'); const request = util.promisify(asyncRequest); let circleCIHeaders; let jobs; let baseTemporaryPath; async function initialize(circleCIToken, baseTempPath, branchName) { console.info('Getting CircleCI information'); circleCIHeaders = {'Circle-Token': circleCIToken}; baseTemporaryPath = baseTempPath; exec(`mkdir -p ${baseTemporaryPath}`); const pipeline = await _getLastCircleCIPipelineID(branchName); const testsWorkflow = await _getTestsWorkflow(pipeline.id); const jobsResults = await _getCircleCIJobs(testsWorkflow.id); jobs = jobsResults.flatMap(j => j); } function baseTmpPath() { return baseTemporaryPath; } async function _getLastCircleCIPipelineID(branchName) { const options = { method: 'GET', url: 'https://circleci.com/api/v2/project/gh/facebook/react-native/pipeline', qs: { branch: branchName, }, headers: circleCIHeaders, }; const response = await request(options); if (response.error) { throw new Error(error); } const items = JSON.parse(response.body).items; if (!items || items.length === 0) { throw new Error( 'No pipelines found on this branch. Make sure that the CI has run at least once, successfully', ); } const lastPipeline = items[0]; return {id: lastPipeline.id, number: lastPipeline.number}; } async function _getSpecificWorkflow(pipelineId, workflowName) { const options = { method: 'GET', url: `https://circleci.com/api/v2/pipeline/${pipelineId}/workflow`, headers: circleCIHeaders, }; const response = await request(options); if (response.error) { throw new Error(error); } const body = JSON.parse(response.body); let workflow = body.items.find(w => w.name === workflowName); _throwIfWorkflowNotFound(workflow, workflowName); return workflow; } function _throwIfWorkflowNotFound(workflow, name) { if (!workflow) { throw new Error( `Can't find a workflow named ${name}. Please check whether that workflow has started.`, ); } } async function _getTestsWorkflow(pipelineId) { return _getSpecificWorkflow(pipelineId, 'tests'); } async function _getCircleCIJobs(workflowId) { const options = { method: 'GET', url: `https://circleci.com/api/v2/workflow/${workflowId}/job`, headers: circleCIHeaders, }; const response = await request(options); if (response.error) { throw new Error(error); } const body = JSON.parse(response.body); return body.items; } async function _getJobsArtifacts(jobNumber) { const options = { method: 'GET', url: `https://circleci.com/api/v2/project/gh/facebook/react-native/${jobNumber}/artifacts`, headers: circleCIHeaders, }; const response = await request(options); if (response.error) { throw new Error(error); } const body = JSON.parse(response.body); return body.items; } async function _findUrlForJob(jobName, artifactPath) { const job = jobs.find(j => j.name === jobName); _throwIfJobIsNull(job); _throwIfJobIsUnsuccessful(job); const artifacts = await _getJobsArtifacts(job.job_number); let artifact = artifacts.find(a => a.path.indexOf(artifactPath) > -1); if (!artifact) { throw new Error(`I could not find the artifact with path ${artifactPath}`); } return artifact.url; } function _throwIfJobIsNull(job) { if (!job) { throw new Error( `Can't find a job with name ${job.name}. Please verify that it has been executed and that all its dependencies completed successfully.`, ); } } function _throwIfJobIsUnsuccessful(job) { if (job.status !== 'success') { throw new Error( `The job ${job.name} status is ${job.status}. We need a 'success' status to proceed with the testing.`, ); } } async function artifactURLHermesDebug() { return _findUrlForJob('build_hermes_macos-Debug', 'hermes-ios-debug.tar.gz'); } async function artifactURLForMavenLocal() { return _findUrlForJob('build_npm_package', 'maven-local.zip'); } async function artifactURLForReactNative() { let shortCommit = exec('git rev-parse HEAD', {silent: true}) .toString() .trim() .slice(0, 9); return _findUrlForJob( 'build_npm_package', `react-native-1000.0.0-${shortCommit}.tgz`, ); } async function artifactURLForHermesRNTesterAPK(emulatorArch) { return _findUrlForJob( 'test_android', `rntester-apk/hermes/debug/app-hermes-${emulatorArch}-debug.apk`, ); } async function artifactURLForJSCRNTesterAPK(emulatorArch) { return _findUrlForJob( 'test_android', `rntester-apk/jsc/debug/app-jsc-${emulatorArch}-debug.apk`, ); } function downloadArtifact(artifactURL, destination) { exec(`rm -rf ${destination}`); exec(`curl ${artifactURL} -Lo ${destination}`); } module.exports = { initialize, downloadArtifact, artifactURLForJSCRNTesterAPK, artifactURLForHermesRNTesterAPK, artifactURLForMavenLocal, artifactURLHermesDebug, artifactURLForReactNative, baseTmpPath, };