Skip to content

Commit c361aa3

Browse files
authored
Merge branch 'main' into remove-non-evm-aggregated-percentage-temp-component
2 parents 2de489d + 3751d9a commit c361aa3

360 files changed

Lines changed: 13239 additions & 3598 deletions

File tree

Some content is hidden

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

.eslintrc.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@
99
const utilNumberImportBurndownFiles = [
1010
'app/component-library/components-temp/CustomSpendCap/CustomInput/CustomInput.tsx',
1111
'app/component-library/components-temp/CustomSpendCap/CustomSpendCap.tsx',
12-
'app/component-library/components-temp/Price/AggregatedPercentage/AggregatedPercentage.tsx',
13-
'app/component-library/components-temp/Price/AggregatedPercentage/utils.ts',
1412
'app/components/UI/AccountInfoCard/index.js',
1513
'app/components/UI/AssetOverview/Price/Price.advanced.tsx',
1614
'app/components/UI/AssetOverview/Price/Price.legacy.tsx',
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
name: 'Check force-builds override'
2+
description: >-
3+
Detects whether the current workflow run should bypass native build reuse
4+
(both the GHA cache and the cross-run artifact lookup) and always compile
5+
fresh. The override is honored on `pull_request` events via a `force-builds`
6+
label OR a `[force-builds]` token in the head commit message. It is
7+
intentionally ignored on `merge_group` and `push` events so the merge queue
8+
always uses hash-verified reuse.
9+
10+
inputs:
11+
github-token:
12+
description: >-
13+
GitHub token with `pull-requests: read` (for label lookup) and
14+
`contents: read` (to fetch the head commit message via the REST API).
15+
required: true
16+
label-name:
17+
description: 'PR label that, when present, forces fresh builds'
18+
required: false
19+
default: 'force-builds'
20+
commit-tag:
21+
description: 'Case-sensitive substring in the head commit message that forces fresh builds'
22+
required: false
23+
default: '[force-builds]'
24+
25+
outputs:
26+
force:
27+
description: "'true' when fresh builds should be forced, otherwise 'false'"
28+
value: ${{ steps.compute.outputs.force }}
29+
30+
runs:
31+
using: 'composite'
32+
steps:
33+
- name: Compute force-builds flag
34+
id: compute
35+
shell: bash
36+
env:
37+
GH_TOKEN: ${{ inputs.github-token }}
38+
LABEL_NAME: ${{ inputs.label-name }}
39+
COMMIT_TAG: ${{ inputs.commit-tag }}
40+
EVENT_NAME: ${{ github.event_name }}
41+
HEAD_COMMIT_HASH: ${{ github.event.pull_request.head.sha }}
42+
PR_NUMBER: ${{ github.event.pull_request.number }}
43+
REPOSITORY: ${{ github.repository }}
44+
run: |
45+
FORCE="false"
46+
47+
if [[ "$EVENT_NAME" != "pull_request" ]]; then
48+
echo "Event is $EVENT_NAME; force-builds override is ignored outside pull_request events."
49+
echo "force=$FORCE" >> "$GITHUB_OUTPUT"
50+
exit 0
51+
fi
52+
53+
# Commit-message tag.
54+
COMMIT_MESSAGE=""
55+
if COMMIT_MESSAGE=$(gh api \
56+
"repos/$REPOSITORY/commits/$HEAD_COMMIT_HASH" \
57+
--jq '.commit.message' 2>/dev/null); then
58+
if printf '%s' "$COMMIT_MESSAGE" \
59+
| grep --fixed-strings --quiet "$COMMIT_TAG"; then
60+
echo "-> force=true because '$COMMIT_TAG' was found in commit message of $HEAD_COMMIT_HASH"
61+
FORCE="true"
62+
fi
63+
else
64+
echo "::warning::Failed to fetch commit message for $HEAD_COMMIT_HASH via GitHub API; commit-tag force-builds check skipped for this run (the '$LABEL_NAME' label path still works)."
65+
fi
66+
67+
# PR label
68+
if [[ -n "$PR_NUMBER" ]]; then
69+
if gh pr view "$PR_NUMBER" --repo "$REPOSITORY" \
70+
--json labels --jq '.labels[].name' \
71+
| grep --fixed-strings --line-regexp --quiet "$LABEL_NAME"; then
72+
echo "-> force=true because '$LABEL_NAME' label is applied to PR #$PR_NUMBER"
73+
FORCE="true"
74+
fi
75+
fi
76+
77+
if [[ "$FORCE" == "false" ]]; then
78+
echo "No force-builds override active."
79+
fi
80+
81+
echo "force=$FORCE" >> "$GITHUB_OUTPUT"
Lines changed: 255 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
1+
name: 'Find reusable build from prior run'
2+
description: >-
3+
Searches recent workflow runs across three tiers (same branch, base branch,
4+
then any open PR branch) for a run whose `build-source-hash` commit status
5+
matches the current fingerprint AND whose required build artifacts are still
6+
available. If a match is found, outputs the run id so a subsequent
7+
`actions/download-artifact` step can pull the artifacts directly instead of
8+
triggering a fresh native build.
9+
10+
The third (cross-PR) tier is required because GitHub's `listWorkflowRuns`
11+
`branch` parameter filters against `head_branch` — the PR source branch for
12+
`pull_request` events — so branch-scoped lookups can never discover other
13+
PRs' runs. The cross-PR tier drops the branch filter and instead uses
14+
`event: pull_request` to let the fingerprint itself act as the cross-PR
15+
deduplication key.
16+
17+
inputs:
18+
fingerprint:
19+
description: 'The @expo/fingerprint hash the candidate must match'
20+
required: true
21+
artifact-names:
22+
description: 'JSON array of artifact names that must all be present on the candidate run'
23+
required: true
24+
github-token:
25+
description: 'GitHub token with `actions: read` and `statuses: read` permissions'
26+
required: true
27+
workflow-file:
28+
description: 'Workflow filename whose runs will be searched'
29+
required: false
30+
default: 'ci.yml'
31+
base-branch:
32+
description: 'Fallback branch when no same-branch match is found'
33+
required: false
34+
default: 'main'
35+
status-context:
36+
description: 'Commit status context that carries the fingerprint'
37+
required: false
38+
default: 'build-source-hash'
39+
max-candidates-per-branch:
40+
description: 'How many recent runs to inspect per branch-scoped tier (same-branch, base-branch)'
41+
required: false
42+
default: '10'
43+
max-candidates-cross-pr:
44+
description: >-
45+
How many recent `pull_request`-event runs (across all branches) to inspect
46+
in the cross-PR tier. The fingerprint filter is highly discriminating, so
47+
the practical cost is one `getCombinedStatusForRef` call per candidate
48+
until a match is found.
49+
required: false
50+
default: '30'
51+
52+
outputs:
53+
found:
54+
description: "'true' when a reusable run was found"
55+
value: ${{ steps.lookup.outputs.found }}
56+
run-id:
57+
description: 'Workflow run id that produced the reusable artifacts'
58+
value: ${{ steps.lookup.outputs.run-id }}
59+
source-sha:
60+
description: 'Commit SHA of the reusable run'
61+
value: ${{ steps.lookup.outputs.source-sha }}
62+
source-branch:
63+
description: 'Branch of the reusable run (same-branch or base-branch)'
64+
value: ${{ steps.lookup.outputs.source-branch }}
65+
66+
runs:
67+
using: 'composite'
68+
steps:
69+
- name: Search prior runs for matching fingerprint
70+
id: lookup
71+
uses: actions/github-script@v7
72+
continue-on-error: true
73+
env:
74+
TARGET_FINGERPRINT: ${{ inputs.fingerprint }}
75+
ARTIFACT_NAMES_JSON: ${{ inputs.artifact-names }}
76+
WORKFLOW_FILE: ${{ inputs.workflow-file }}
77+
BASE_BRANCH: ${{ inputs.base-branch }}
78+
STATUS_CONTEXT: ${{ inputs.status-context }}
79+
MAX_CANDIDATES: ${{ inputs.max-candidates-per-branch }}
80+
MAX_CANDIDATES_CROSS_PR: ${{ inputs.max-candidates-cross-pr }}
81+
HEAD_BRANCH: ${{ github.head_ref || github.ref_name }}
82+
HEAD_SHA: ${{ github.event.pull_request.head.sha || github.sha }}
83+
CURRENT_RUN_ID: ${{ github.run_id }}
84+
with:
85+
github-token: ${{ inputs.github-token }}
86+
script: |
87+
const {
88+
TARGET_FINGERPRINT,
89+
ARTIFACT_NAMES_JSON,
90+
WORKFLOW_FILE,
91+
BASE_BRANCH,
92+
STATUS_CONTEXT,
93+
MAX_CANDIDATES,
94+
MAX_CANDIDATES_CROSS_PR,
95+
HEAD_BRANCH,
96+
HEAD_SHA,
97+
CURRENT_RUN_ID,
98+
} = process.env;
99+
100+
const setNotFound = () => {
101+
core.setOutput('found', 'false');
102+
core.setOutput('run-id', '');
103+
core.setOutput('source-sha', '');
104+
core.setOutput('source-branch', '');
105+
};
106+
107+
if (!TARGET_FINGERPRINT) {
108+
core.warning('No fingerprint provided; skipping lookup');
109+
setNotFound();
110+
return;
111+
}
112+
113+
let requiredArtifacts;
114+
try {
115+
requiredArtifacts = JSON.parse(ARTIFACT_NAMES_JSON);
116+
} catch (err) {
117+
core.warning(`Could not parse artifact-names input: ${err.message}`);
118+
setNotFound();
119+
return;
120+
}
121+
if (!Array.isArray(requiredArtifacts) || requiredArtifacts.length === 0) {
122+
core.warning('artifact-names must be a non-empty JSON array');
123+
setNotFound();
124+
return;
125+
}
126+
127+
const maxCandidates = Number(MAX_CANDIDATES) || 10;
128+
const maxCandidatesCrossPr = Number(MAX_CANDIDATES_CROSS_PR) || 30;
129+
const currentRunId = String(CURRENT_RUN_ID);
130+
131+
// Three-tier discovery:
132+
// 1. same-branch — fastest path, catches retries and new commits
133+
// on the current PR.
134+
// 2. base-branch — catches post-merge CI runs on `main`. Only
135+
// matches `push`-event runs (pull_request runs
136+
// have head_branch=<source branch>, not main).
137+
// 3. cross-pr — searches recent `pull_request` runs across
138+
// ALL source branches so two unrelated PRs with
139+
// the same fingerprint can reuse each other's
140+
// artifacts. This tier deliberately drops the
141+
// `branch` filter; without it, branch-scoped
142+
// lookups can never discover another PR's run
143+
// (GitHub filters `branch` against head_branch,
144+
// which is the PR source branch).
145+
const tiers = [
146+
{
147+
label: `same-branch (branch=${HEAD_BRANCH})`,
148+
params: { branch: HEAD_BRANCH, per_page: maxCandidates },
149+
},
150+
];
151+
if (BASE_BRANCH && BASE_BRANCH !== HEAD_BRANCH) {
152+
tiers.push({
153+
label: `base-branch (branch=${BASE_BRANCH})`,
154+
params: { branch: BASE_BRANCH, per_page: maxCandidates },
155+
});
156+
}
157+
tiers.push({
158+
label: `cross-pr (event=pull_request, any branch, last ${maxCandidatesCrossPr} runs)`,
159+
params: { event: 'pull_request', per_page: maxCandidatesCrossPr },
160+
// Skip runs already visited by the same-branch tier to avoid
161+
// wasting API calls on duplicates.
162+
skipHeadBranch: HEAD_BRANCH,
163+
});
164+
165+
async function getFingerprintForSha(sha) {
166+
try {
167+
const { data } = await github.rest.repos.getCombinedStatusForRef({
168+
owner: context.repo.owner,
169+
repo: context.repo.repo,
170+
ref: sha,
171+
per_page: 100,
172+
});
173+
const status = data.statuses.find((s) => s.context === STATUS_CONTEXT);
174+
return status ? status.description : null;
175+
} catch (err) {
176+
core.info(`getCombinedStatusForRef failed for ${sha}: ${err.message}`);
177+
return null;
178+
}
179+
}
180+
181+
async function hasAllArtifacts(runId) {
182+
try {
183+
const artifacts = await github.paginate(
184+
github.rest.actions.listWorkflowRunArtifacts,
185+
{
186+
owner: context.repo.owner,
187+
repo: context.repo.repo,
188+
run_id: runId,
189+
per_page: 100,
190+
},
191+
);
192+
const available = new Set(
193+
artifacts
194+
.filter((a) => !a.expired)
195+
.map((a) => a.name),
196+
);
197+
const missing = requiredArtifacts.filter((n) => !available.has(n));
198+
if (missing.length > 0) {
199+
core.info(`Run ${runId} missing artifacts: ${missing.join(', ')}`);
200+
return false;
201+
}
202+
return true;
203+
} catch (err) {
204+
core.info(`listWorkflowRunArtifacts failed for ${runId}: ${err.message}`);
205+
return false;
206+
}
207+
}
208+
209+
const seenRunIds = new Set();
210+
seenRunIds.add(currentRunId);
211+
212+
for (const tier of tiers) {
213+
core.info(`Searching tier: ${tier.label}`);
214+
let runs;
215+
try {
216+
const { data } = await github.rest.actions.listWorkflowRuns({
217+
owner: context.repo.owner,
218+
repo: context.repo.repo,
219+
workflow_id: WORKFLOW_FILE,
220+
...tier.params,
221+
});
222+
runs = data.workflow_runs || [];
223+
} catch (err) {
224+
core.warning(`listWorkflowRuns failed for tier "${tier.label}": ${err.message}`);
225+
continue;
226+
}
227+
228+
for (const run of runs) {
229+
const runIdStr = String(run.id);
230+
if (seenRunIds.has(runIdStr)) continue;
231+
seenRunIds.add(runIdStr);
232+
233+
if (tier.skipHeadBranch && run.head_branch === tier.skipHeadBranch) continue;
234+
235+
if (run.status !== 'completed' && run.status !== 'in_progress') continue;
236+
237+
const fingerprint = await getFingerprintForSha(run.head_sha);
238+
if (!fingerprint) continue;
239+
if (fingerprint !== TARGET_FINGERPRINT) continue;
240+
241+
if (!(await hasAllArtifacts(run.id))) continue;
242+
243+
core.info(
244+
`Match: tier="${tier.label}" run=${run.id} sha=${run.head_sha} branch=${run.head_branch} url=${run.html_url}`,
245+
);
246+
core.setOutput('found', 'true');
247+
core.setOutput('run-id', runIdStr);
248+
core.setOutput('source-sha', run.head_sha);
249+
core.setOutput('source-branch', run.head_branch || '');
250+
return;
251+
}
252+
}
253+
254+
core.info('No reusable build found across any tier');
255+
setNotFound();

0 commit comments

Comments
 (0)