Skip to content

Commit 23752c8

Browse files
committed
Merge
1 parent 41c2688 commit 23752c8

File tree

735 files changed

+18541
-28552
lines changed

Some content is hidden

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

735 files changed

+18541
-28552
lines changed

.claude/agents/code-inline-reviewer.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,6 @@ Each rule file contains:
5656
8. **DO NOT invent new rules, stylistic preferences, or commentary outside the listed rules.**
5757
9. **DO NOT describe what you are doing or add extra content.**
5858
EXCEPTION: If you believe something MIGHT be a Rule violation but are uncertain, err on the side of including it in the violations array rather than skipping it.
59-
10. **Reality check before posting**: Before creating each inline comment, re-read the specific code one more time and confirm the violation is real. If upon re-reading you realize the code is actually correct, **do NOT post the comment** — silently skip it and move on. Never post a comment that flags a violation and then concludes it is not actually a problem.
6059

6160
## Comment Format
6261

.github/ISSUE_TEMPLATE/DesignDoc.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
name: Design Doc tracking issue
33
about: A standard template to follow when leading a project
4-
labels: Daily, NewFeature
4+
labels: Daily, NewFeature, DesignDoc
55
---
66

77
## Proposal

.github/actions/javascript/authorChecklist/index.js

Lines changed: 0 additions & 247 deletions
Original file line numberDiff line numberDiff line change
@@ -15685,9 +15685,7 @@ const utils_1 = __nccwpck_require__(3030);
1568515685
const plugin_paginate_rest_1 = __nccwpck_require__(4193);
1568615686
const plugin_throttling_1 = __nccwpck_require__(9968);
1568715687
const request_error_1 = __nccwpck_require__(537);
15688-
const arrayDifference_1 = __importDefault(__nccwpck_require__(7532));
1568915688
const CONST_1 = __importDefault(__nccwpck_require__(9873));
15690-
const isEmptyObject_1 = __nccwpck_require__(6497);
1569115689
class GithubUtils {
1569215690
static internalOctokit;
1569315691
/**
@@ -15766,219 +15764,6 @@ class GithubUtils {
1576615764
// eslint-disable-next-line @typescript-eslint/non-nullable-type-assertion-style
1576715765
return this.internalOctokit.paginate;
1576815766
}
15769-
/**
15770-
* Finds one open `StagingDeployCash` issue via GitHub octokit library.
15771-
*/
15772-
static getStagingDeployCash() {
15773-
return this.octokit.issues
15774-
.listForRepo({
15775-
owner: CONST_1.default.GITHUB_OWNER,
15776-
repo: CONST_1.default.APP_REPO,
15777-
labels: CONST_1.default.LABELS.STAGING_DEPLOY,
15778-
state: 'open',
15779-
})
15780-
.then(({ data }) => {
15781-
if (!data.length) {
15782-
throw new Error(`Unable to find ${CONST_1.default.LABELS.STAGING_DEPLOY} issue.`);
15783-
}
15784-
if (data.length > 1) {
15785-
throw new Error(`Found more than one ${CONST_1.default.LABELS.STAGING_DEPLOY} issue.`);
15786-
}
15787-
const issue = data.at(0);
15788-
if (!issue) {
15789-
throw new Error(`Found an undefined ${CONST_1.default.LABELS.STAGING_DEPLOY} issue.`);
15790-
}
15791-
return this.getStagingDeployCashData(issue);
15792-
});
15793-
}
15794-
/**
15795-
* Takes in a GitHub issue object and returns the data we want.
15796-
*/
15797-
static getStagingDeployCashData(issue) {
15798-
try {
15799-
const versionRegex = new RegExp('([0-9]+)\\.([0-9]+)\\.([0-9]+)(?:-([0-9]+))?', 'g');
15800-
const version = (issue.body?.match(versionRegex)?.[0] ?? '').replaceAll('`', '');
15801-
return {
15802-
title: issue.title,
15803-
url: issue.url,
15804-
number: this.getIssueOrPullRequestNumberFromURL(issue.url),
15805-
labels: issue.labels,
15806-
PRList: this.getStagingDeployCashPRList(issue),
15807-
PRListMobileExpensify: this.getStagingDeployCashPRListMobileExpensify(issue),
15808-
deployBlockers: this.getStagingDeployCashDeployBlockers(issue),
15809-
internalQAPRList: this.getStagingDeployCashInternalQA(issue),
15810-
isSentryChecked: issue.body ? /-\s\[x]\sI checked \[Sentry]/.test(issue.body) : false,
15811-
isGHStatusChecked: issue.body ? /-\s\[x]\sI checked \[GitHub Status]/.test(issue.body) : false,
15812-
version,
15813-
tag: `${version}-staging`,
15814-
};
15815-
}
15816-
catch (exception) {
15817-
throw new Error(`Unable to find ${CONST_1.default.LABELS.STAGING_DEPLOY} issue with correct data.`);
15818-
}
15819-
}
15820-
/**
15821-
* Parse the PRList and Internal QA section of the StagingDeployCash issue body.
15822-
*
15823-
* @private
15824-
*/
15825-
static getStagingDeployCashPRList(issue) {
15826-
let PRListSection = issue.body?.match(/pull requests:\*\*\r?\n((?:-.*\r?\n)+)\r?\n\r?\n?/) ?? null;
15827-
if (PRListSection?.length !== 2) {
15828-
// No PRs, return an empty array
15829-
console.log('Hmmm...The open StagingDeployCash does not list any pull requests, continuing...');
15830-
return [];
15831-
}
15832-
PRListSection = PRListSection[1];
15833-
const PRList = [...PRListSection.matchAll(new RegExp(`- \\[([ x])] (${CONST_1.default.PULL_REQUEST_REGEX.source})`, 'g'))].map((match) => ({
15834-
url: match[2],
15835-
number: Number.parseInt(match[3], 10),
15836-
isVerified: match[1] === 'x',
15837-
}));
15838-
return PRList.sort((a, b) => a.number - b.number);
15839-
}
15840-
static getStagingDeployCashPRListMobileExpensify(issue) {
15841-
let mobileExpensifySection = issue.body?.match(/Mobile-Expensify PRs:\*\*\r?\n((?:-.*\r?\n)+)/) ?? null;
15842-
if (mobileExpensifySection?.length !== 2) {
15843-
return [];
15844-
}
15845-
mobileExpensifySection = mobileExpensifySection[1];
15846-
const mobileExpensifyPRs = [...mobileExpensifySection.matchAll(new RegExp(`- \\[([ x])]\\s(${CONST_1.default.ISSUE_OR_PULL_REQUEST_REGEX.source})`, 'g'))].map((match) => ({
15847-
url: match[2],
15848-
number: Number.parseInt(match[3], 10),
15849-
isVerified: match[1] === 'x',
15850-
}));
15851-
return mobileExpensifyPRs.sort((a, b) => a.number - b.number);
15852-
}
15853-
/**
15854-
* Parse DeployBlocker section of the StagingDeployCash issue body.
15855-
*
15856-
* @private
15857-
*/
15858-
static getStagingDeployCashDeployBlockers(issue) {
15859-
let deployBlockerSection = issue.body?.match(/Deploy Blockers:\*\*\r?\n((?:-.*\r?\n)+)/) ?? null;
15860-
if (deployBlockerSection?.length !== 2) {
15861-
return [];
15862-
}
15863-
deployBlockerSection = deployBlockerSection[1];
15864-
const deployBlockers = [...deployBlockerSection.matchAll(new RegExp(`- \\[([ x])]\\s(${CONST_1.default.ISSUE_OR_PULL_REQUEST_REGEX.source})`, 'g'))].map((match) => ({
15865-
url: match[2],
15866-
number: Number.parseInt(match[3], 10),
15867-
isResolved: match[1] === 'x',
15868-
}));
15869-
return deployBlockers.sort((a, b) => a.number - b.number);
15870-
}
15871-
/**
15872-
* Parse InternalQA section of the StagingDeployCash issue body.
15873-
*
15874-
* @private
15875-
*/
15876-
static getStagingDeployCashInternalQA(issue) {
15877-
let internalQASection = issue.body?.match(/Internal QA:\*\*\r?\n((?:- \[[ x]].*\r?\n)+)/) ?? null;
15878-
if (internalQASection?.length !== 2) {
15879-
return [];
15880-
}
15881-
internalQASection = internalQASection[1];
15882-
const internalQAPRs = [...internalQASection.matchAll(new RegExp(`- \\[([ x])]\\s(${CONST_1.default.PULL_REQUEST_REGEX.source})`, 'g'))].map((match) => ({
15883-
url: match[2].split('-').at(0)?.trim() ?? '',
15884-
number: Number.parseInt(match[3], 10),
15885-
isResolved: match[1] === 'x',
15886-
}));
15887-
return internalQAPRs.sort((a, b) => a.number - b.number);
15888-
}
15889-
/**
15890-
* Generate the issue body and assignees for a StagingDeployCash.
15891-
*/
15892-
static generateStagingDeployCashBodyAndAssignees({ tag, PRList, PRListMobileExpensify = [], verifiedPRList = [], verifiedPRListMobileExpensify = [], deployBlockers = [], resolvedDeployBlockers = [], resolvedInternalQAPRs = [], isSentryChecked = false, isGHStatusChecked = false, previousTag = '', chronologicalSection = '', }) {
15893-
return this.fetchAllPullRequests(PRList.map((pr) => this.getPullRequestNumberFromURL(pr)))
15894-
.then((data) => {
15895-
const internalQAPRs = Array.isArray(data) ? data.filter((pr) => !(0, isEmptyObject_1.isEmptyObject)(pr.labels.find((item) => item.name === CONST_1.default.LABELS.INTERNAL_QA))) : [];
15896-
return Promise.all(internalQAPRs.map((pr) => this.getPullRequestMergerLogin(pr.number).then((mergerLogin) => ({ url: pr.html_url, mergerLogin })))).then((results) => {
15897-
// The format of this map is following:
15898-
// {
15899-
// 'https://github.com/Expensify/App/pull/9641': 'PauloGasparSv',
15900-
// 'https://github.com/Expensify/App/pull/9642': 'mountiny'
15901-
// }
15902-
const internalQAPRMap = results.reduce((acc, { url, mergerLogin }) => {
15903-
acc[url] = mergerLogin;
15904-
return acc;
15905-
}, {});
15906-
console.log('Found the following Internal QA PRs:', internalQAPRMap);
15907-
const noQAPRs = Array.isArray(data) ? data.filter((PR) => /\[No\s?QA]/i.test(PR.title)).map((item) => item.html_url) : [];
15908-
console.log('Found the following NO QA PRs:', noQAPRs);
15909-
const verifiedOrNoQAPRs = new Set([...verifiedPRList, ...verifiedPRListMobileExpensify, ...noQAPRs]);
15910-
const sortedPRList = [...new Set((0, arrayDifference_1.default)(PRList, Object.keys(internalQAPRMap)))].sort((a, b) => GithubUtils.getPullRequestNumberFromURL(a) - GithubUtils.getPullRequestNumberFromURL(b));
15911-
const sortedPRListMobileExpensify = [...new Set(PRListMobileExpensify)].sort((a, b) => GithubUtils.getPullRequestNumberFromURL(a) - GithubUtils.getPullRequestNumberFromURL(b));
15912-
const sortedDeployBlockers = [...new Set(deployBlockers)].sort((a, b) => GithubUtils.getIssueOrPullRequestNumberFromURL(a) - GithubUtils.getIssueOrPullRequestNumberFromURL(b));
15913-
// Tag version and comparison URL
15914-
// eslint-disable-next-line max-len
15915-
let issueBody = `**Release Version:** \`${tag}\`\r\n**Compare Changes:** https://github.com/${process.env.GITHUB_REPOSITORY}/compare/production...staging\r\n`;
15916-
// Add Mobile-Expensify compare link if there are Mobile-Expensify PRs
15917-
if (sortedPRListMobileExpensify.length > 0) {
15918-
issueBody += `**Mobile-Expensify Changes:** https://github.com/${CONST_1.default.GITHUB_OWNER}/${CONST_1.default.MOBILE_EXPENSIFY_REPO}/compare/production...staging\r\n`;
15919-
}
15920-
issueBody += '\r\n';
15921-
// PR list
15922-
if (sortedPRList.length > 0) {
15923-
issueBody += '**This release contains changes from the following pull requests:**\r\n';
15924-
for (const URL of sortedPRList) {
15925-
issueBody += verifiedOrNoQAPRs.has(URL) ? '- [x]' : '- [ ]';
15926-
issueBody += ` ${URL}\r\n`;
15927-
}
15928-
issueBody += '\r\n\r\n';
15929-
}
15930-
// Mobile-Expensify PR list
15931-
if (sortedPRListMobileExpensify.length > 0) {
15932-
issueBody += '**Mobile-Expensify PRs:**\r\n';
15933-
for (const URL of sortedPRListMobileExpensify) {
15934-
issueBody += verifiedOrNoQAPRs.has(URL) ? '- [x]' : '- [ ]';
15935-
issueBody += ` ${URL}\r\n`;
15936-
}
15937-
issueBody += '\r\n\r\n';
15938-
}
15939-
// Internal QA PR list
15940-
if (!(0, isEmptyObject_1.isEmptyObject)(internalQAPRMap)) {
15941-
console.log('Found the following verified Internal QA PRs:', resolvedInternalQAPRs);
15942-
issueBody += '**Internal QA:**\r\n';
15943-
for (const URL of Object.keys(internalQAPRMap)) {
15944-
const merger = internalQAPRMap[URL];
15945-
const mergerMention = `@${merger}`;
15946-
issueBody += `${resolvedInternalQAPRs.includes(URL) ? '- [x]' : '- [ ]'} `;
15947-
issueBody += `${URL}`;
15948-
issueBody += ` - ${mergerMention}`;
15949-
issueBody += '\r\n';
15950-
}
15951-
issueBody += '\r\n\r\n';
15952-
}
15953-
// Deploy blockers
15954-
if (deployBlockers.length > 0) {
15955-
issueBody += '**Deploy Blockers:**\r\n';
15956-
for (const URL of sortedDeployBlockers) {
15957-
issueBody += resolvedDeployBlockers.includes(URL) ? '- [x] ' : '- [ ] ';
15958-
issueBody += URL;
15959-
issueBody += '\r\n';
15960-
}
15961-
issueBody += '\r\n\r\n';
15962-
}
15963-
if (chronologicalSection) {
15964-
issueBody += chronologicalSection;
15965-
issueBody += '\r\n\r\n';
15966-
}
15967-
issueBody += '**Deployer verifications:**';
15968-
// eslint-disable-next-line max-len
15969-
issueBody += `\r\n- [${isSentryChecked ? 'x' : ' '}] I checked [Sentry](https://expensify.sentry.io/releases/new.expensify%40${tag}/?project=app&environment=staging) for **this release version** and verified that this release does not introduce any new crashes. More detailed instructions on this verification can be found [here](https://stackoverflowteams.com/c/expensify/questions/15095/15096).`;
15970-
// eslint-disable-next-line max-len
15971-
issueBody += `\r\n- [${isSentryChecked ? 'x' : ' '}] I checked [Sentry](https://expensify.sentry.io/releases/new.expensify%40${previousTag}/?project=app&environment=production) for **the previous release version** and verified that the release did not introduce any new crashes. Because mobile deploys use a phased rollout, completing this checklist will deploy the previous release version to 100% of users. More detailed instructions on this verification can be found [here](https://stackoverflowteams.com/c/expensify/questions/15095/15096).`;
15972-
// eslint-disable-next-line max-len
15973-
issueBody += `\r\n- [${isGHStatusChecked ? 'x' : ' '}] I checked [GitHub Status](https://www.githubstatus.com/) and verified there is no reported incident with Actions.`;
15974-
issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n';
15975-
const issueAssignees = [...new Set(Object.values(internalQAPRMap))];
15976-
const issue = { issueBody, issueAssignees };
15977-
return issue;
15978-
});
15979-
})
15980-
.catch((err) => console.warn('Error generating StagingDeployCash issue body! Continuing...', err));
15981-
}
1598215767
/**
1598315768
* Fetch all pull requests given a list of PR numbers.
1598415769
*/
@@ -16301,38 +16086,6 @@ class GithubUtils {
1630116086
exports["default"] = GithubUtils;
1630216087

1630316088

16304-
/***/ }),
16305-
16306-
/***/ 7532:
16307-
/***/ ((__unused_webpack_module, exports) => {
16308-
16309-
"use strict";
16310-
16311-
Object.defineProperty(exports, "__esModule", ({ value: true }));
16312-
/**
16313-
* This function is an equivalent of _.difference, it takes two arrays and returns the difference between them.
16314-
* It returns an array of items that are in the first array but not in the second array.
16315-
*/
16316-
function arrayDifference(array1, array2) {
16317-
return [array1, array2].reduce((a, b) => a.filter((c) => !b.includes(c)));
16318-
}
16319-
exports["default"] = arrayDifference;
16320-
16321-
16322-
/***/ }),
16323-
16324-
/***/ 6497:
16325-
/***/ ((__unused_webpack_module, exports) => {
16326-
16327-
"use strict";
16328-
16329-
Object.defineProperty(exports, "__esModule", ({ value: true }));
16330-
exports.isEmptyObject = isEmptyObject;
16331-
function isEmptyObject(obj) {
16332-
return Object.keys(obj ?? {}).length === 0;
16333-
}
16334-
16335-
1633616089
/***/ }),
1633716090

1633816091
/***/ 8534:

0 commit comments

Comments
 (0)