Skip to content

Commit 8c9111d

Browse files
Merge remote-tracking branch 'origin/main' into claude-hideWizardFromAssistiveTech
Co-authored-by: Rushat Gabhane <rushatgabhane@users.noreply.github.com>
2 parents 21c4a19 + 5a809c1 commit 8c9111d

904 files changed

Lines changed: 48261 additions & 5225 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.

.github/workflows/authorChecklist.yml

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,22 @@ on:
99
paths-ignore: ['docs/articles/**/*.md', 'docs/redirects.csv', 'docs/assets/images/**']
1010

1111
jobs:
12+
validate:
13+
uses: ./.github/workflows/contributorValidationGate.yml
14+
with:
15+
PR_NUMBER: ${{ github.event.pull_request.number }}
16+
PR_AUTHOR: ${{ github.event.pull_request.user.login }}
17+
AUTHOR_ASSOCIATION: ${{ github.event.pull_request.author_association }}
18+
1219
# Note: PHP specifically looks for the name of this job, "checklist", so if the name of the job is changed,
1320
# then you also need to go into PHP and update the name of this job in the GH_JOB_NAME_CHECKLIST constant
1421
checklist:
22+
needs: [validate]
1523
runs-on: blacksmith-2vcpu-ubuntu-2404
16-
if: github.actor != 'OSBotify' && github.actor != 'imgbot[bot]'
24+
if: |
25+
needs.validate.outputs.IS_AUTHORIZED == 'true'
26+
&& github.actor != 'OSBotify'
27+
&& github.actor != 'imgbot[bot]'
1728
steps:
1829
- name: Checkout
1930
# v4

.github/workflows/cla.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,18 @@ on:
88
branches: [main]
99

1010
jobs:
11+
validate:
12+
if: ${{ github.event_name == 'pull_request_target' }}
13+
uses: ./.github/workflows/contributorValidationGate.yml
14+
with:
15+
PR_NUMBER: ${{ github.event.pull_request.number }}
16+
PR_AUTHOR: ${{ github.event.pull_request.user.login }}
17+
AUTHOR_ASSOCIATION: ${{ github.event.pull_request.author_association }}
18+
1119
CLA:
20+
needs: [validate]
21+
if: |
22+
always()
23+
&& (github.event_name == 'issue_comment' || needs.validate.outputs.IS_AUTHORIZED == 'true')
1224
uses: Expensify/GitHub-Actions/.github/workflows/cla.yml@main
1325
secrets: inherit

.github/workflows/claude-review.yml

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,19 @@ concurrency:
1313
cancel-in-progress: true
1414

1515
jobs:
16+
validate:
17+
uses: ./.github/workflows/contributorValidationGate.yml
18+
with:
19+
PR_NUMBER: ${{ github.event.pull_request.number }}
20+
PR_AUTHOR: ${{ github.event.pull_request.user.login }}
21+
AUTHOR_ASSOCIATION: ${{ github.event.pull_request.author_association }}
22+
1623
review:
17-
if: github.event.pull_request.draft != true && !contains(github.event.pull_request.title, 'Revert')
24+
needs: [validate]
25+
if: |
26+
needs.validate.outputs.IS_AUTHORIZED == 'true'
27+
&& github.event.pull_request.draft != true
28+
&& !contains(github.event.pull_request.title, 'Revert')
1829
runs-on: blacksmith-2vcpu-ubuntu-2404
1930
env:
2031
PR_NUMBER: ${{ github.event.pull_request.number }}
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
# Reusable workflow that checks whether a PR author is authorized to contribute.
2+
# Used by expensive PR workflows to skip CI for unauthorized contributors.
3+
# The actual close/lock/comment logic lives in validateContributorPR.yml.
4+
5+
name: Contributor Validation Gate
6+
7+
on:
8+
workflow_call:
9+
inputs:
10+
PR_NUMBER:
11+
description: Pull request number
12+
required: true
13+
type: number
14+
PR_AUTHOR:
15+
description: Pull request author login
16+
required: true
17+
type: string
18+
AUTHOR_ASSOCIATION:
19+
description: "Author's association with the repository (MEMBER, OWNER, CONTRIBUTOR, etc.)"
20+
required: true
21+
type: string
22+
outputs:
23+
IS_AUTHORIZED:
24+
description: "'true' if the contributor is authorized, 'false' otherwise"
25+
value: ${{ jobs.check.outputs.IS_AUTHORIZED }}
26+
27+
jobs:
28+
check:
29+
runs-on: blacksmith-2vcpu-ubuntu-2404
30+
outputs:
31+
IS_AUTHORIZED: ${{ steps.gate.outputs.IS_AUTHORIZED }}
32+
steps:
33+
- name: Check contributor authorization
34+
id: gate
35+
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd
36+
with:
37+
script: |
38+
const prNumber = ${{ inputs.PR_NUMBER }};
39+
const prAuthor = '${{ inputs.PR_AUTHOR }}';
40+
const authorAssociation = '${{ inputs.AUTHOR_ASSOCIATION }}';
41+
42+
if (['MEMBER', 'OWNER', 'CONTRIBUTOR'].includes(authorAssociation)) {
43+
console.log(`${prAuthor} is ${authorAssociation}. Authorized.`);
44+
core.setOutput('IS_AUTHORIZED', 'true');
45+
return;
46+
}
47+
48+
console.log(`${prAuthor} has association "${authorAssociation}". Checking linked issues/PRs...`);
49+
50+
const {data: pr} = await github.rest.pulls.get({
51+
owner: context.repo.owner,
52+
repo: context.repo.repo,
53+
pull_number: prNumber,
54+
});
55+
56+
const prBody = pr.body || '';
57+
const cleanBody = prBody.replace(/<!--[\s\S]*?-->/g, '');
58+
59+
const issuePattern = /https:\/\/github\.com\/(Expensify\/[^/]+)\/issues\/(\d+)/g;
60+
let match;
61+
while ((match = issuePattern.exec(cleanBody)) !== null) {
62+
const [, repo, num] = match;
63+
const issueNumber = parseInt(num);
64+
try {
65+
const [owner, repoName] = repo.split('/');
66+
const {data: issue} = await github.rest.issues.get({
67+
owner,
68+
repo: repoName,
69+
issue_number: issueNumber,
70+
});
71+
if (issue.assignees.some(a => a.login.toLowerCase() === prAuthor.toLowerCase())) {
72+
console.log(`${prAuthor} is assigned to ${repo}#${issueNumber}. Authorized.`);
73+
core.setOutput('IS_AUTHORIZED', 'true');
74+
return;
75+
}
76+
console.log(`${prAuthor} is NOT assigned to ${repo}#${issueNumber}.`);
77+
} catch (e) {
78+
console.log(`Could not verify ${repo}#${issueNumber}: ${e.message}`);
79+
}
80+
}
81+
82+
const prUrlPattern = /https:\/\/github\.com\/(Expensify\/[^/]+)\/pull\/(\d+)/g;
83+
while ((match = prUrlPattern.exec(cleanBody)) !== null) {
84+
const [, repo, num] = match;
85+
const linkedPRNumber = parseInt(num);
86+
try {
87+
const [owner, repoName] = repo.split('/');
88+
89+
const {data: linkedPR} = await github.rest.pulls.get({
90+
owner,
91+
repo: repoName,
92+
pull_number: linkedPRNumber,
93+
});
94+
if (linkedPR.user.login.toLowerCase() === prAuthor.toLowerCase()) {
95+
console.log(`${prAuthor} is the author of ${repo}#${linkedPRNumber}. Authorized.`);
96+
core.setOutput('IS_AUTHORIZED', 'true');
97+
return;
98+
}
99+
100+
const {data: reviews} = await github.rest.pulls.listReviews({
101+
owner,
102+
repo: repoName,
103+
pull_number: linkedPRNumber,
104+
});
105+
if (reviews.some(r => r.user?.login?.toLowerCase() === prAuthor.toLowerCase())) {
106+
console.log(`${prAuthor} is a reviewer of ${repo}#${linkedPRNumber}. Authorized.`);
107+
core.setOutput('IS_AUTHORIZED', 'true');
108+
return;
109+
}
110+
111+
const {data: requestedReviewers} = await github.rest.pulls.listRequestedReviewers({
112+
owner,
113+
repo: repoName,
114+
pull_number: linkedPRNumber,
115+
});
116+
if (requestedReviewers.users.some(u => u.login.toLowerCase() === prAuthor.toLowerCase())) {
117+
console.log(`${prAuthor} is a requested reviewer of ${repo}#${linkedPRNumber}. Authorized.`);
118+
core.setOutput('IS_AUTHORIZED', 'true');
119+
return;
120+
}
121+
122+
console.log(`${prAuthor} is not author or reviewer of ${repo}#${linkedPRNumber}.`);
123+
} catch (e) {
124+
console.log(`Could not verify ${repo}#${linkedPRNumber}: ${e.message}`);
125+
}
126+
}
127+
128+
console.log(`No valid authorization found for ${prAuthor}.`);
129+
core.setOutput('IS_AUTHORIZED', 'false');
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# Validates that external contributors are authorized to submit PRs.
2+
# Calls the reusable gate workflow, then closes + locks unauthorized PRs.
3+
4+
name: Validate Contributor PRs
5+
6+
on:
7+
pull_request_target:
8+
types: [opened, ready_for_review, reopened]
9+
branches: [main]
10+
11+
permissions:
12+
pull-requests: write
13+
issues: write
14+
15+
jobs:
16+
validate:
17+
if: ${{ !github.event.pull_request.draft && github.event.pull_request.user.type != 'Bot' }}
18+
uses: ./.github/workflows/contributorValidationGate.yml
19+
with:
20+
PR_NUMBER: ${{ github.event.pull_request.number }}
21+
PR_AUTHOR: ${{ github.event.pull_request.user.login }}
22+
AUTHOR_ASSOCIATION: ${{ github.event.pull_request.author_association }}
23+
24+
close-if-unauthorized:
25+
needs: [validate]
26+
if: needs.validate.outputs.IS_AUTHORIZED == 'false'
27+
runs-on: blacksmith-2vcpu-ubuntu-2404
28+
steps:
29+
- name: Close PR and leave comment
30+
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd
31+
with:
32+
script: |
33+
const prNumber = ${{ github.event.pull_request.number }};
34+
const prAuthor = '${{ github.event.pull_request.user.login }}';
35+
36+
await github.rest.issues.createComment({
37+
owner: context.repo.owner,
38+
repo: context.repo.repo,
39+
issue_number: prNumber,
40+
body: [
41+
`👋 Hi @${prAuthor}, thanks for your interest in contributing to Expensify!`,
42+
'',
43+
"This PR has been automatically closed because it doesn't appear to meet our contribution requirements:",
44+
'',
45+
'- You are not a member of the Expensify GitHub organization',
46+
'- No linked GitHub issue was found in the PR description where you are listed as an assignee',
47+
'- No linked GitHub PR was found in the PR description where you are the author or a reviewer',
48+
'',
49+
'If you\'d like to contribute, please make sure to:',
50+
'1. Find an open issue you\'d like to work on',
51+
'2. Get assigned to the issue by following our contribution process',
52+
'3. Link the issue in your PR description using the format: `$ https://github.com/Expensify/App/issues/<issueID>`',
53+
'',
54+
'Please review our [contributing guidelines](https://github.com/Expensify/App/blob/main/contributingGuides/CONTRIBUTING.md) for more details.',
55+
'',
56+
'If you believe this was closed in error, please reach out in the **#expensify-open-source** Slack channel.',
57+
].join('\n'),
58+
});
59+
60+
await github.rest.pulls.update({
61+
owner: context.repo.owner,
62+
repo: context.repo.repo,
63+
pull_number: prNumber,
64+
state: 'closed',
65+
});
66+
67+
await github.rest.issues.lock({
68+
owner: context.repo.owner,
69+
repo: context.repo.repo,
70+
issue_number: prNumber,
71+
lock_reason: 'spam',
72+
});

Mobile-Expensify

android/app/build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,8 +111,8 @@ android {
111111
minSdkVersion rootProject.ext.minSdkVersion
112112
targetSdkVersion rootProject.ext.targetSdkVersion
113113
multiDexEnabled rootProject.ext.multiDexEnabled
114-
versionCode 1009033500
115-
versionName "9.3.35-0"
114+
versionCode 1009033609
115+
versionName "9.3.36-9"
116116
// Supported language variants must be declared here to avoid from being removed during the compilation.
117117
// This also helps us to not include unnecessary language variants in the APK.
118118
resConfigs "en", "es"

babel.config.js

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ const webpack = {
6363
};
6464

6565
const metro = {
66-
presets: [[require('@react-native/babel-preset'), {disableImportExportTransform: true}]],
66+
presets: [require('@react-native/babel-preset')],
6767
plugins: [
6868
['babel-plugin-react-compiler', ReactCompilerConfig], // must run first!
6969

@@ -174,14 +174,5 @@ module.exports = (api) => {
174174
const runningIn = api.caller((args = {}) => args.name);
175175
console.debug(' - running in: ', runningIn);
176176

177-
// Jest runs in Node.js without Metro's experimentalImportSupport transform,
178-
// so Babel must handle import/export transforms for tests.
179-
if (runningIn === 'babel-jest') {
180-
return {
181-
...metro,
182-
presets: [[require('@react-native/babel-preset'), {disableImportExportTransform: false}]],
183-
};
184-
}
185-
186-
return runningIn === 'metro' ? metro : webpack;
177+
return ['metro', 'babel-jest'].includes(runningIn) ? metro : webpack;
187178
};

0 commit comments

Comments
 (0)