Skip to content

Commit 6c52316

Browse files
committed
Merge branch 'main' into @kosmydel/lottie-react-native-web-support
2 parents 01c4add + dee9b31 commit 6c52316

File tree

326 files changed

+4423
-9479
lines changed

Some content is hidden

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

326 files changed

+4423
-9479
lines changed

.github/CODEOWNERS

+3
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,5 @@
11
# Every PR gets a review from an internal Expensify engineer
22
* @Expensify/pullerbear
3+
4+
# Every PR that touches redirects gets reviewed by ring0
5+
docs/redirects.csv @Expensify/infra

.github/actions/javascript/getDeployPullRequestList/getDeployPullRequestList.js

+34-50
Original file line numberDiff line numberDiff line change
@@ -5,58 +5,42 @@ const ActionUtils = require('../../../libs/ActionUtils');
55
const GitUtils = require('../../../libs/GitUtils');
66
const GithubUtils = require('../../../libs/GithubUtils');
77

8-
const inputTag = core.getInput('TAG', {required: true});
9-
10-
const isProductionDeploy = ActionUtils.getJSONInput('IS_PRODUCTION_DEPLOY', {required: false}, false);
11-
const itemToFetch = isProductionDeploy ? 'release' : 'tag';
8+
async function run() {
9+
try {
10+
const inputTag = core.getInput('TAG', {required: true});
11+
const isProductionDeploy = ActionUtils.getJSONInput('IS_PRODUCTION_DEPLOY', {required: false}, false);
12+
const deployEnv = isProductionDeploy ? 'production' : 'staging';
13+
14+
console.log(`Looking for PRs deployed to ${deployEnv} in ${inputTag}...`);
15+
16+
const completedDeploys = (
17+
await GithubUtils.octokit.actions.listWorkflowRuns({
18+
owner: github.context.repo.owner,
19+
repo: github.context.repo.repo,
20+
workflow_id: 'platformDeploy.yml',
21+
status: 'completed',
22+
event: isProductionDeploy ? 'release' : 'push',
23+
})
24+
).data.workflow_runs;
25+
26+
const inputTagIndex = _.findIndex(completedDeploys, (workflowRun) => workflowRun.head_branch === inputTag);
27+
if (inputTagIndex < 0) {
28+
throw new Error(`No completed deploy found for input tag ${inputTag}`);
29+
}
1230

13-
/**
14-
* Gets either releases or tags for a GitHub repo
15-
*
16-
* @param {boolean} fetchReleases
17-
* @returns {*}
18-
*/
19-
function getTagsOrReleases(fetchReleases) {
20-
if (fetchReleases) {
21-
return GithubUtils.octokit.repos.listReleases({
22-
owner: github.context.repo.owner,
23-
repo: github.context.repo.repo,
24-
});
31+
const priorTag = completedDeploys[inputTagIndex + 1].head_branch;
32+
console.log(`Looking for PRs deployed to ${deployEnv} between ${priorTag} and ${inputTag}`);
33+
const prList = await GitUtils.getPullRequestsMergedBetween(priorTag, inputTag);
34+
console.log(`Found the pull request list: ${prList}`);
35+
core.setOutput('PR_LIST', prList);
36+
} catch (err) {
37+
console.error(err.message);
38+
core.setFailed(err);
2539
}
26-
27-
return GithubUtils.octokit.repos.listTags({
28-
owner: github.context.repo.owner,
29-
repo: github.context.repo.repo,
30-
});
3140
}
3241

33-
console.log(`Fetching ${itemToFetch} list from github...`);
34-
getTagsOrReleases(isProductionDeploy)
35-
.catch((githubError) => core.setFailed(githubError))
36-
.then(({data}) => {
37-
const keyToPluck = isProductionDeploy ? 'tag_name' : 'name';
38-
const tags = _.pluck(data, keyToPluck);
39-
const priorTagIndex = _.indexOf(tags, inputTag) + 1;
40-
41-
if (priorTagIndex === 0) {
42-
console.log(`No ${itemToFetch} was found for input tag ${inputTag}. Comparing it to latest ${itemToFetch} ${tags[0]}`);
43-
}
44-
45-
if (priorTagIndex === tags.length) {
46-
const err = new Error("Somehow, the input tag was at the end of the paginated result, so we don't have the prior tag");
47-
console.error(err.message);
48-
core.setFailed(err);
49-
return;
50-
}
51-
52-
const priorTag = tags[priorTagIndex];
53-
console.log(`Given ${itemToFetch}: ${inputTag}`);
54-
console.log(`Prior ${itemToFetch}: ${priorTag}`);
42+
if (require.main === module) {
43+
run();
44+
}
5545

56-
return GitUtils.getPullRequestsMergedBetween(priorTag, inputTag);
57-
})
58-
.then((pullRequestList) => {
59-
console.log(`Found the pull request list: ${pullRequestList}`);
60-
return core.setOutput('PR_LIST', pullRequestList);
61-
})
62-
.catch((error) => core.setFailed(error));
46+
module.exports = run;

.github/actions/javascript/getDeployPullRequestList/index.js

+60-69
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,59 @@
44
/******/ (() => { // webpackBootstrap
55
/******/ var __webpack_modules__ = ({
66

7+
/***/ 5847:
8+
/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => {
9+
10+
const _ = __nccwpck_require__(5067);
11+
const core = __nccwpck_require__(2186);
12+
const github = __nccwpck_require__(5438);
13+
const ActionUtils = __nccwpck_require__(970);
14+
const GitUtils = __nccwpck_require__(669);
15+
const GithubUtils = __nccwpck_require__(7999);
16+
17+
async function run() {
18+
try {
19+
const inputTag = core.getInput('TAG', {required: true});
20+
const isProductionDeploy = ActionUtils.getJSONInput('IS_PRODUCTION_DEPLOY', {required: false}, false);
21+
const deployEnv = isProductionDeploy ? 'production' : 'staging';
22+
23+
console.log(`Looking for PRs deployed to ${deployEnv} in ${inputTag}...`);
24+
25+
const completedDeploys = (
26+
await GithubUtils.octokit.actions.listWorkflowRuns({
27+
owner: github.context.repo.owner,
28+
repo: github.context.repo.repo,
29+
workflow_id: 'platformDeploy.yml',
30+
status: 'completed',
31+
event: isProductionDeploy ? 'release' : 'push',
32+
})
33+
).data.workflow_runs;
34+
35+
const inputTagIndex = _.findIndex(completedDeploys, (workflowRun) => workflowRun.head_branch === inputTag);
36+
if (inputTagIndex < 0) {
37+
throw new Error(`No completed deploy found for input tag ${inputTag}`);
38+
}
39+
40+
const priorTag = completedDeploys[inputTagIndex + 1].head_branch;
41+
console.log(`Looking for PRs deployed to ${deployEnv} between ${priorTag} and ${inputTag}`);
42+
const prList = await GitUtils.getPullRequestsMergedBetween(priorTag, inputTag);
43+
console.log(`Found the pull request list: ${prList}`);
44+
core.setOutput('PR_LIST', prList);
45+
} catch (err) {
46+
console.error(err.message);
47+
core.setFailed(err);
48+
}
49+
}
50+
51+
if (require.main === require.cache[eval('__filename')]) {
52+
run();
53+
}
54+
55+
module.exports = run;
56+
57+
58+
/***/ }),
59+
760
/***/ 970:
861
/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => {
962

@@ -19556,74 +19609,12 @@ module.exports = JSON.parse('[[[0,44],"disallowed_STD3_valid"],[[45,46],"valid"]
1955619609
/******/ if (typeof __nccwpck_require__ !== 'undefined') __nccwpck_require__.ab = __dirname + "/";
1955719610
/******/
1955819611
/************************************************************************/
19559-
var __webpack_exports__ = {};
19560-
// This entry need to be wrapped in an IIFE because it need to be isolated against other modules in the chunk.
19561-
(() => {
19562-
const _ = __nccwpck_require__(5067);
19563-
const core = __nccwpck_require__(2186);
19564-
const github = __nccwpck_require__(5438);
19565-
const ActionUtils = __nccwpck_require__(970);
19566-
const GitUtils = __nccwpck_require__(669);
19567-
const GithubUtils = __nccwpck_require__(7999);
19568-
19569-
const inputTag = core.getInput('TAG', {required: true});
19570-
19571-
const isProductionDeploy = ActionUtils.getJSONInput('IS_PRODUCTION_DEPLOY', {required: false}, false);
19572-
const itemToFetch = isProductionDeploy ? 'release' : 'tag';
19573-
19574-
/**
19575-
* Gets either releases or tags for a GitHub repo
19576-
*
19577-
* @param {boolean} fetchReleases
19578-
* @returns {*}
19579-
*/
19580-
function getTagsOrReleases(fetchReleases) {
19581-
if (fetchReleases) {
19582-
return GithubUtils.octokit.repos.listReleases({
19583-
owner: github.context.repo.owner,
19584-
repo: github.context.repo.repo,
19585-
});
19586-
}
19587-
19588-
return GithubUtils.octokit.repos.listTags({
19589-
owner: github.context.repo.owner,
19590-
repo: github.context.repo.repo,
19591-
});
19592-
}
19593-
19594-
console.log(`Fetching ${itemToFetch} list from github...`);
19595-
getTagsOrReleases(isProductionDeploy)
19596-
.catch((githubError) => core.setFailed(githubError))
19597-
.then(({data}) => {
19598-
const keyToPluck = isProductionDeploy ? 'tag_name' : 'name';
19599-
const tags = _.pluck(data, keyToPluck);
19600-
const priorTagIndex = _.indexOf(tags, inputTag) + 1;
19601-
19602-
if (priorTagIndex === 0) {
19603-
console.log(`No ${itemToFetch} was found for input tag ${inputTag}. Comparing it to latest ${itemToFetch} ${tags[0]}`);
19604-
}
19605-
19606-
if (priorTagIndex === tags.length) {
19607-
const err = new Error("Somehow, the input tag was at the end of the paginated result, so we don't have the prior tag");
19608-
console.error(err.message);
19609-
core.setFailed(err);
19610-
return;
19611-
}
19612-
19613-
const priorTag = tags[priorTagIndex];
19614-
console.log(`Given ${itemToFetch}: ${inputTag}`);
19615-
console.log(`Prior ${itemToFetch}: ${priorTag}`);
19616-
19617-
return GitUtils.getPullRequestsMergedBetween(priorTag, inputTag);
19618-
})
19619-
.then((pullRequestList) => {
19620-
console.log(`Found the pull request list: ${pullRequestList}`);
19621-
return core.setOutput('PR_LIST', pullRequestList);
19622-
})
19623-
.catch((error) => core.setFailed(error));
19624-
19625-
})();
19626-
19627-
module.exports = __webpack_exports__;
19612+
/******/
19613+
/******/ // startup
19614+
/******/ // Load entry module and return exports
19615+
/******/ // This entry module is referenced by other modules so it can't be inlined
19616+
/******/ var __webpack_exports__ = __nccwpck_require__(5847);
19617+
/******/ module.exports = __webpack_exports__;
19618+
/******/
1962819619
/******/ })()
1962919620
;
+125
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
#!/bin/bash
2+
#
3+
# Adds new routes to the Cloudflare Bulk Redirects list for communityDot to helpDot
4+
# pages. Does some basic sanity checking.
5+
6+
set -e
7+
8+
source scripts/shellUtils.sh
9+
10+
info "Adding any new redirects from communityDot to helpDot"
11+
12+
declare -r LIST_ID="20eb13215038446a98fd69ccf6d1026d"
13+
declare -r ZONE_ID="$CLOUDFLARE_ACCOUNT_ID"
14+
declare -r REDIRECTS_FILE="docs/redirects.csv"
15+
16+
function checkCloudflareResult {
17+
RESULTS=$1
18+
RESULT_MESSAGE=$(echo "$RESULTS" | jq .success)
19+
20+
if ! [[ "$RESULT_MESSAGE" == "true" ]]; then
21+
ERROR_MESSAGE=$(echo "$RESULTS" | jq .errors)
22+
error "Error calling Cloudfalre API: $ERROR_MESSAGE"
23+
exit 1
24+
fi
25+
}
26+
27+
declare -a ITEMS_TO_ADD
28+
29+
while read -r line; do
30+
# Split each line of the file into a source and destination so we can sanity check
31+
# and compare against the current list.
32+
read -r -a LINE_PARTS < <(echo "$line" | tr ',' ' ')
33+
SOURCE_URL=${LINE_PARTS[0]}
34+
DEST_URL=${LINE_PARTS[1]}
35+
36+
# Make sure the format of the line is as execpted.
37+
if [[ "${#LINE_PARTS[@]}" -gt 2 ]]; then
38+
error "Found a line with more than one comma: $line"
39+
exit 1
40+
fi
41+
42+
# Basic sanity checking to make sure that the source and destination are in expected
43+
# subdomains.
44+
if ! [[ $SOURCE_URL =~ ^https://community\.expensify\.com ]]; then
45+
error "Found source URL that is not a community URL: $SOURCE_URL"
46+
exit 1
47+
fi
48+
49+
if ! [[ $DEST_URL =~ ^https://help\.expensify\.com ]]; then
50+
error "Found destination URL that is not a help URL: $DEST_URL"
51+
exit 1
52+
fi
53+
54+
info "Source: $SOURCE_URL and destination: $DEST_URL appear to be formatted correctly."
55+
56+
ITEMS_TO_ADD+=("$line")
57+
58+
# This line skips the first line in the csv because the first line is a header row.
59+
done <<< "$(tail +2 $REDIRECTS_FILE)"
60+
61+
# Sanity check that we should actually be running this and we aren't about to delete
62+
# every single redirect.
63+
if [[ "${#ITEMS_TO_ADD[@]}" -lt 1 ]]; then
64+
error "No items found to add, why are we running?"
65+
exit 1
66+
fi
67+
68+
# This block builds a single JSON object with all of our updates so we can
69+
# reduce the number of API calls we make. You cannot add any logging or anything
70+
# that prints to std out to this block or it will break. We capture all of the std out
71+
# from this loop and pass it to jq to build the json object. Any non-json will break the
72+
# jq call at the end.
73+
PUT_JSON=$(for new in "${ITEMS_TO_ADD[@]}"; do
74+
read -r -a LINE_PARTS < <(echo "$new" | tr ',' ' ')
75+
SOURCE_URL=${LINE_PARTS[0]}
76+
DEST_URL=${LINE_PARTS[1]}
77+
78+
# We strip the prefix here so that the rule will match both http and https. Since vanilla will eventially be removed,
79+
# we need to catch both because we will not have the http > https redirect done by vanilla anymore.
80+
NO_PREFIX_SOURCE_URL=${SOURCE_URL/https:\/\//}
81+
jq -n --arg source "$NO_PREFIX_SOURCE_URL" --arg dest "$DEST_URL" '{"redirect": {source_url: $source, target_url: $dest}}'
82+
done | jq -n '. |= [inputs]')
83+
84+
info "Adding redirects for $PUT_JSON"
85+
86+
# We use PUT here instead of POST so that we replace the entire list in place. This has many benefits:
87+
# 1. We don't have to check if items are already in the list, allowing this script to run faster
88+
# 2. We can support deleting redirects this way by simply removing them from the list
89+
# 3. We can support updating redirects this way, in the case of typos or moved destinations.
90+
#
91+
# Additionally this API call is async, so after we finish it, we must poll to wait for it to finish to
92+
# to know that it was actually completed.
93+
PUT_RESULT=$(curl -s --request PUT --url "https://api.cloudflare.com/client/v4/accounts/$ZONE_ID/rules/lists/$LIST_ID/items" \
94+
--header 'Content-Type: application/json' \
95+
--header "Authorization: Bearer $CLOUDFLARE_LIST_TOKEN" \
96+
--data "$PUT_JSON")
97+
98+
checkCloudflareResult "$PUT_RESULT"
99+
OPERATION_ID=$(echo "$PUT_RESULT" | jq -r .result.operation_id)
100+
101+
DONE=false
102+
103+
# Poll for completition
104+
while [[ $DONE == false ]]; do
105+
CHECK_RESULT=$(curl -s --request GET --url "https://api.cloudflare.com/client/v4/accounts/$ZONE_ID/rules/lists/bulk_operations/$OPERATION_ID" \
106+
--header 'Content-Type: application/json' \
107+
--header "Authorization: Bearer $CLOUDFLARE_LIST_TOKEN")
108+
checkCloudflareResult "$CHECK_RESULT"
109+
110+
STATUS=$(echo "$CHECK_RESULT" | jq -r .result.status)
111+
112+
# Exit on completed or failed, other options are pending or running, in both cases
113+
# we want to keep polling.
114+
if [[ $STATUS == "completed" ]]; then
115+
DONE=true
116+
fi
117+
118+
if [[ $STATUS == "failed" ]]; then
119+
ERROR_MESSAGE=$(echo "$CHECK_RESULT" | jq .result.error)
120+
error "List update failed with error: $ERROR_MESSAGE"
121+
exit 1
122+
fi
123+
done
124+
125+
success "Updated lists successfully"

.github/workflows/platformDeploy.yml

+5-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ env:
1212
SHOULD_DEPLOY_PRODUCTION: ${{ github.event_name == 'release' }}
1313
DEVELOPER_DIR: /Applications/Xcode_14.1.app/Contents/Developer
1414

15+
concurrency:
16+
group: ${{ github.workflow }}-${{ github.event_name }}
17+
cancel-in-progress: true
18+
1519
jobs:
1620
validateActor:
1721
runs-on: ubuntu-latest
@@ -420,7 +424,7 @@ jobs:
420424
postGithubComment:
421425
name: Post a GitHub comment when platforms are done building and deploying
422426
runs-on: ubuntu-latest
423-
if: ${{ always() }}
427+
if: ${{ !cancelled() }}
424428
needs: [android, desktop, iOS, web]
425429
steps:
426430
- name: Checkout

0 commit comments

Comments
 (0)