Skip to content

Commit 9facebe

Browse files
committed
Merge branch 'main' into feat/disable-metametrics-basic-functionality
2 parents c51c1f4 + 0eb7d57 commit 9facebe

File tree

314 files changed

+3664
-4407
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

314 files changed

+3664
-4407
lines changed

.circleci/config.yml

+3
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,9 @@ jobs:
369369
root: .
370370
paths:
371371
- changed-files
372+
- store_artifacts:
373+
path: changed-files
374+
destination: changed-files
372375

373376
validate-locales-only:
374377
executor: node-browsers-small

.circleci/scripts/git-diff-default-branch.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -91,13 +91,13 @@ async function fetchUntilMergeBaseFound() {
9191
* Performs a git diff command to get the list of files changed between the current branch and the origin.
9292
* It first ensures that the necessary commits are fetched until the merge base is found.
9393
*
94-
* @returns The output of the git diff command, listing the changed files.
94+
* @returns The output of the git diff command, listing the file paths with status (A, M, D).
9595
* @throws If unable to get the diff after fetching the merge base or if an unexpected error occurs.
9696
*/
9797
async function gitDiff(): Promise<string> {
9898
await fetchUntilMergeBaseFound();
9999
const { stdout: diffResult } = await exec(
100-
`git diff --name-only "origin/HEAD...${SOURCE_BRANCH}"`,
100+
`git diff --name-status "origin/HEAD...${SOURCE_BRANCH}"`,
101101
);
102102
if (!diffResult) {
103103
throw new Error('Unable to get diff after full checkout.');

.circleci/scripts/test-run-e2e-timeout-minutes.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
import { fetchManifestFlagsFromPRAndGit } from '../../development/lib/get-manifest-flag';
2-
import { filterE2eChangedFiles } from '../../test/e2e/changedFilesUtil';
2+
import { filterE2eChangedFiles, readChangedAndNewFilesWithStatus, getChangedAndNewFiles } from '../../test/e2e/changedFilesUtil';
33

44
fetchManifestFlagsFromPRAndGit().then((manifestFlags) => {
55
let timeout;
66

77
if (manifestFlags.circleci?.timeoutMinutes) {
88
timeout = manifestFlags.circleci?.timeoutMinutes;
99
} else {
10-
const changedOrNewTests = filterE2eChangedFiles();
10+
const changedAndNewFilesWithStatus = readChangedAndNewFilesWithStatus();
11+
const changedAndNewFiles = getChangedAndNewFiles(changedAndNewFilesWithStatus);
12+
const changedOrNewTests = filterE2eChangedFiles(changedAndNewFiles);
1113

1214
// 20 minutes, plus 3 minutes for every changed file, up to a maximum of 30 minutes
1315
timeout = Math.min(20 + changedOrNewTests.length * 3, 30);

.circleci/scripts/validate-locales-only.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
1-
const { readChangedFiles } = require('../../test/e2e/changedFilesUtil.js');
1+
const { readChangedAndNewFilesWithStatus, getChangedAndNewFiles } = require('../../test/e2e/changedFilesUtil.js');
22

33
/**
44
* Verifies that all changed files are in the /_locales/ directory.
55
* Fails the build if any changed files are outside of the /_locales/ directory.
66
* Fails if no changed files are detected.
77
*/
88
function validateLocalesOnlyChangedFiles() {
9-
const changedFiles = readChangedFiles();
9+
const changedAndNewFilesWithStatus = readChangedAndNewFilesWithStatus();
10+
const changedFiles = getChangedAndNewFiles(changedAndNewFilesWithStatus);
1011
if (!changedFiles || changedFiles.length === 0) {
1112
console.error('Failure: No changed files detected.');
1213
process.exit(1);

.devcontainer/download-builds.ts

+9-29
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import { execSync } from 'child_process';
22
import util from 'util';
3-
3+
import {
4+
getJobsByWorkflowId,
5+
getPipelineId,
6+
getWorkflowId,
7+
} from '../.github/scripts/shared/circle-artifacts';
48
const exec = util.promisify(require('node:child_process').exec);
59

610
function getGitBranch() {
@@ -10,34 +14,10 @@ function getGitBranch() {
1014
return gitOutput.match(branchRegex)?.groups?.branch || 'main';
1115
}
1216

13-
async function getCircleJobs(branch: string) {
14-
let response = await fetch(
15-
`https://circleci.com/api/v2/project/gh/MetaMask/metamask-extension/pipeline?branch=${branch}`,
16-
);
17-
18-
const pipelineId = (await response.json()).items[0].id;
19-
20-
console.log('pipelineId:', pipelineId);
21-
22-
response = await fetch(
23-
`https://circleci.com/api/v2/pipeline/${pipelineId}/workflow`,
24-
);
25-
26-
const workflowId = (await response.json()).items[0].id;
27-
28-
console.log('workflowId:', workflowId);
29-
30-
response = await fetch(
31-
`https://circleci.com/api/v2/workflow/${workflowId}/job`,
32-
);
33-
34-
const jobs = (await response.json()).items;
35-
36-
return jobs;
37-
}
38-
3917
async function getBuilds(branch: string, jobNames: string[]) {
40-
const jobs = await getCircleJobs(branch);
18+
const pipelineId = await getPipelineId(branch);
19+
const workflowId = await getWorkflowId(pipelineId);
20+
const jobs = await getJobsByWorkflowId(workflowId);
4121
let builds = [] as any[];
4222

4323
for (const jobName of jobNames) {
@@ -137,7 +117,7 @@ function unzipBuilds(folder: 'builds' | 'builds-test', versionNumber: string) {
137117
}
138118

139119
async function main(jobNames: string[]) {
140-
const branch = getGitBranch();
120+
const branch = process.env.CIRCLE_BRANCH || getGitBranch();
141121

142122
const builds = await getBuilds(branch, jobNames);
143123

+185
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
import fs from 'fs';
2+
3+
// Set OWNER and REPOSITORY based on the environment
4+
const OWNER =
5+
process.env.CIRCLE_PROJECT_USERNAME || process.env.OWNER || 'MetaMask';
6+
const REPOSITORY =
7+
process.env.CIRCLE_PROJECT_REPONAME ||
8+
process.env.REPOSITORY ||
9+
'metamask-extension';
10+
const CIRCLE_BASE_URL = 'https://circleci.com/api/v2';
11+
12+
/**
13+
* Retrieves the pipeline ID for a given branch and optional commit hash.
14+
*
15+
* @param {string} branch - The branch name to fetch the pipeline for.
16+
* @param {string} [headCommitHash] - Optional commit hash to match a specific pipeline.
17+
* @returns {Promise<string>} A promise that resolves to the pipeline ID.
18+
* @throws Will throw an error if no pipeline is found or if the HTTP request fails.
19+
*/
20+
export async function getPipelineId(branch: string, headCommitHash?: string): Promise<string> {
21+
const url = `${CIRCLE_BASE_URL}/project/gh/${OWNER}/${REPOSITORY}/pipeline?branch=${branch}`;
22+
const response = await fetch(url);
23+
if (!response.ok) {
24+
throw new Error(`Failed to fetch pipeline data: ${response.statusText}`);
25+
}
26+
27+
const pipelineData = await response.json();
28+
const pipelineItem = headCommitHash
29+
? pipelineData.items.find((item: any) => item.vcs.revision === headCommitHash)
30+
: pipelineData.items[0];
31+
if (!pipelineItem) {
32+
throw new Error('Pipeline ID not found');
33+
}
34+
console.log('pipelineId:', pipelineItem.id);
35+
36+
return pipelineItem.id;
37+
}
38+
39+
/**
40+
* Retrieves the workflow ID for a given pipeline ID.
41+
*
42+
* @param {string} pipelineId - The ID of the pipeline to fetch the workflow for.
43+
* @returns {Promise<string>} A promise that resolves to the workflow ID.
44+
* @throws Will throw an error if no workflow is found or if the HTTP request fails.
45+
*/
46+
export async function getWorkflowId(pipelineId: string): Promise<string> {
47+
const url = `${CIRCLE_BASE_URL}/pipeline/${pipelineId}/workflow`;
48+
const response = await fetch(url);
49+
if (!response.ok) {
50+
throw new Error(`Failed to fetch workflow data: ${response.statusText}`);
51+
}
52+
const workflowData = await response.json();
53+
const workflowId = workflowData.items[0]?.id;
54+
if (!workflowId) {
55+
throw new Error('Workflow ID not found');
56+
}
57+
console.log('workflowId:', workflowId);
58+
return workflowId;
59+
}
60+
61+
/**
62+
* Retrieves a list of jobs for a given workflow ID.
63+
*
64+
* @param {string} workflowId - The ID of the workflow to fetch jobs for.
65+
* @returns {Promise<any[]>} A promise that resolves to an array of jobs.
66+
* @throws Will throw an error if no jobs are found or if the HTTP request fails.
67+
*/
68+
export async function getJobsByWorkflowId(workflowId: string): Promise<any[]> {
69+
const url = `${CIRCLE_BASE_URL}/workflow/${workflowId}/job`;
70+
const response = await fetch(url);
71+
if (!response.ok) {
72+
throw new Error(`Failed to fetch jobs: ${response.statusText}`);
73+
}
74+
const jobs = (await response.json()).items;
75+
return jobs;
76+
}
77+
78+
/**
79+
* Retrieves job details for a given workflow ID and optional job name.
80+
*
81+
* @param {string} workflowId - The ID of the workflow to fetch job details for.
82+
* @param {string} [jobName] - Optional job name to match a specific job.
83+
* @returns {Promise<any>} A promise that resolves to the job details.
84+
* @throws Will throw an error if no job details are found or if the HTTP request fails.
85+
*/
86+
export async function getJobDetails(workflowId: string, jobName?: string): Promise<any> {
87+
const jobs = await getJobsByWorkflowId(workflowId);
88+
const jobDetails = jobName
89+
? jobs.find((item: any) => item.name === jobName)
90+
: jobs[0];
91+
if (!jobDetails) {
92+
throw new Error('Job details not found');
93+
}
94+
return jobDetails;
95+
}
96+
97+
/**
98+
* Retrieves the artifact URL for a given branch, commit hash, job name, and artifact name.
99+
* @param {string} branch - The branch name.
100+
* @param {string} headCommitHash - The commit hash of the branch.
101+
* @param {string} jobName - The name of the job that produced the artifact.
102+
* @param {string} artifactName - The name of the artifact to retrieve.
103+
* @returns {Promise<string>} A promise that resolves to the artifact URL.
104+
* @throws Will throw an error if the artifact is not found or if any HTTP request fails.
105+
*/
106+
export async function getArtifactUrl(branch: string, headCommitHash: string, jobName: string, artifactName: string): Promise<string> {
107+
const pipelineId = await getPipelineId(branch, headCommitHash);
108+
const workflowId = await getWorkflowId(pipelineId);
109+
const jobDetails = await getJobDetails(workflowId, jobName);
110+
111+
const jobNumber = jobDetails.job_number;
112+
console.log('Job number', jobNumber);
113+
114+
const artifactResponse = await fetch(`${CIRCLE_BASE_URL}/project/gh/${OWNER}/${REPOSITORY}/${jobNumber}/artifacts`);
115+
const artifactData = await artifactResponse.json();
116+
const artifact = artifactData.items.find((item: any) => item.path.includes(artifactName));
117+
118+
if (!artifact) {
119+
throw new Error(`Artifact ${artifactName} not found`);
120+
}
121+
122+
const artifactUrl = artifact.url;
123+
console.log('Artifact URL:', artifactUrl);;
124+
125+
return artifactUrl;
126+
}
127+
128+
/**
129+
* Downloads an artifact from a given URL and saves it to the specified output path.
130+
* @param {string} artifactUrl - The URL of the artifact to download.
131+
* @param {string} outputFilePath - The path where the artifact should be saved.
132+
* @returns {Promise<void>} A promise that resolves when the download is complete.
133+
* @throws Will throw an error if the download fails or if the file cannot be written.
134+
*/
135+
export async function downloadArtifact(artifactUrl: string, outputFilePath: string): Promise<void> {
136+
try {
137+
// Ensure the directory exists
138+
const dir = require('path').dirname(outputFilePath);
139+
if (!fs.existsSync(dir)) {
140+
fs.mkdirSync(dir, { recursive: true });
141+
}
142+
143+
console.log(`Downloading artifact from URL: ${artifactUrl} to ${outputFilePath}`);
144+
145+
// Download the artifact
146+
const artifactDownloadResponse = await fetch(artifactUrl);
147+
if (!artifactDownloadResponse.ok) {
148+
throw new Error(`Failed to download artifact: ${artifactDownloadResponse.statusText}`);
149+
}
150+
const artifactArrayBuffer = await artifactDownloadResponse.arrayBuffer();
151+
const artifactBuffer = Buffer.from(artifactArrayBuffer);
152+
fs.writeFileSync(outputFilePath, artifactBuffer);
153+
154+
if (!fs.existsSync(outputFilePath)) {
155+
throw new Error(`Failed to download artifact to ${outputFilePath}`);
156+
}
157+
158+
console.log(`Artifact downloaded successfully to ${outputFilePath}`);
159+
} catch (error) {
160+
console.error(`Error during artifact download: ${error.message}`);
161+
throw error;
162+
}
163+
}
164+
165+
/**
166+
* Reads the content of a file.
167+
* @param filePath - The path to the file.
168+
* @returns The content of the file.
169+
*/
170+
export function readFileContent(filePath: string): string {
171+
if (!fs.existsSync(filePath)) {
172+
throw new Error(`File not found: ${filePath}`);
173+
}
174+
175+
const content = fs.readFileSync(filePath, 'utf-8').trim();
176+
return content;
177+
}
178+
179+
/**
180+
* Sleep function to pause execution for a specified number of seconds.
181+
* @param seconds - The number of seconds to sleep.
182+
*/
183+
export function sleep(seconds: number) {
184+
return new Promise(resolve => setTimeout(resolve, seconds * 1000));
185+
}

0 commit comments

Comments
 (0)