Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 7
Release Platform shared workflows#37
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
327375a61ff265e03c56b222a5363dbd6e70126f51f0f120e8a4d624d005e5770485249bd62d89b140dd88f5803e845a6c3ac84a3c414eeb900139cb319fa5dadaf233fe46d81b4419cc19362fc0f33cc20c1ec4901d026d042a580344317892581a302bd331028dc7538008e07b5e114116f51de715669147606ad05d92f6aa2ac8062cda144be3b7585476cf3abe09c177235d9db131c6601b12aedfcb58d21c14cb195f8aaa681535b8b76cc35e52e254445df60fd1809ac4798775f6d6cf2a5f7206c91cfc018d0db6415f3ac9714ce6e22a7f555cb1da41440f085f6d3fa633302d5a4d1fFile filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,179 @@ | ||
| #!/usr/bin/env bash | ||
| set -e | ||
| set -u | ||
| set -o pipefail | ||
| PLATFORM="${1}" | ||
| PREVIOUS_VERSION="${2}" | ||
| NEW_VERSION="${3}" | ||
| NEW_VERSION_NUMBER="${4:-}" | ||
| if [[ -z $PLATFORM ]]; then | ||
| echo "Error: No platform specified." | ||
| exit 1 | ||
| fi | ||
| if [[ -z $NEW_VERSION ]]; then | ||
| echo "Error: No new version specified." | ||
| exit 1 | ||
| fi | ||
| if [[ -z $NEW_VERSION_NUMBER && $PLATFORM == "mobile" ]]; then | ||
| echo "Error: No new version number specified for mobile platform." | ||
| exit 1 | ||
| fi | ||
| get_expected_changed_files() { | ||
| local platform="$1" | ||
| local expected_changed_files="" | ||
| if [[ "$platform" == "mobile" ]]; then | ||
| expected_changed_files="package.json android/app/build.gradle ios/MetaMask.xcodeproj/project.pbxproj bitrise.yml" | ||
| elif [[ "$platform" == "extension" ]]; then | ||
| expected_changed_files="package.json" | ||
| else | ||
| echo "Error: Unknown platform '$platform'. Must be 'mobile' or 'extension'." | ||
| exit 1 | ||
| fi | ||
| echo "$expected_changed_files" | ||
| } | ||
| # Returns the release branch name to use for the given platform | ||
| get_release_branch_name() { | ||
| # Input arguments | ||
| local platform="$1" # Platform can be 'mobile' or 'extension' | ||
| local new_version="$2" # Semantic version, e.g., '12.9.2' | ||
| #RELEASE_BRANCH_NAME="release/${new_version}" | ||
| RELEASE_BRANCH_NAME="release-testing/rls-mgmt" | ||
| # Output the release branch name | ||
| echo "${RELEASE_BRANCH_NAME}" | ||
| } | ||
| RELEASE_BRANCH_NAME=$(get_release_branch_name $PLATFORM $NEW_VERSION) | ||
| CHANGELOG_BRANCH_NAME="chore/${NEW_VERSION}-Changelog" | ||
| RELEASE_BODY="This is the release candidate for version ${NEW_VERSION}. The changelog will be found in another PR ${CHANGELOG_BRANCH_NAME}. | ||
| # Team sign-off checklist | ||
| - [ ] team-accounts | ||
| - [ ] team-assets | ||
| - [ ] team-confirmations | ||
| - [ ] team-design-system | ||
| - [ ] team-notifications | ||
| - [ ] team-platform | ||
| - [ ] team-security | ||
| - [ ] team-snaps-platform | ||
| - [ ] team-sdk | ||
| - [ ] team-stake | ||
| - [ ] team-tiger | ||
| - [ ] team-wallet-framework | ||
| # Reference | ||
| - Testing plan sheet - https://docs.google.com/spreadsheets/d/1tsoodlAlyvEUpkkcNcbZ4PM9HuC9cEM80RZeoVv5OCQ/edit?gid=404070372#gid=404070372" | ||
| echo "Configuring git.." | ||
| git config user.name metamaskbot | ||
| git config user.email metamaskbot@users.noreply.github.com | ||
| echo "Fetching from remote..." | ||
| git fetch | ||
| # Check out the existing release branch from the remote | ||
| echo "Checking out the release branch: ${RELEASE_BRANCH_NAME}" | ||
| git checkout "${RELEASE_BRANCH_NAME}" | ||
| echo "Release Branch Checked Out" | ||
| echo "version : ${NEW_VERSION}" | ||
| echo "platform : ${PLATFORM}" | ||
| echo "Running version update scripts.." | ||
| # Bump versions for the release | ||
| ./github-tools/.github/scripts/set-semvar-version.sh "${NEW_VERSION}" ${PLATFORM} | ||
| if [[ "$PLATFORM" == "mobile" ]]; then | ||
| ./github-tools/.github/scripts/set-mobile-build-version.sh "${NEW_VERSION_NUMBER}" | ||
| fi | ||
| changed_files=$(get_expected_changed_files "$PLATFORM") | ||
| # Echo the files to be added | ||
| echo "Files to be staged for commit: $changed_files" | ||
| echo "Adding and committing changes.." | ||
| # Track our changes | ||
| git add $changed_files | ||
| # Generate a commit based on PLATFORM | ||
| if [ "$PLATFORM" = "mobile" ]; then | ||
| git commit -m "bump semvar version to ${NEW_VERSION} && build version to ${NEW_VERSION_NUMBER}" | ||
| elif [ "$PLATFORM" = "extension" ]; then | ||
| git commit -m "bump semvar version to ${NEW_VERSION}" | ||
| fi | ||
| echo "Pushing changes to the remote.." | ||
| git push --set-upstream origin "${RELEASE_BRANCH_NAME}" | ||
| echo Creating release PR.. | ||
| gh pr create \ | ||
| --draft \ | ||
| --title "feat: ${NEW_VERSION}" \ | ||
| --body "${RELEASE_BODY}" \ | ||
| --head "${RELEASE_BRANCH_NAME}"; | ||
Contributor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If two PRs are created for the same tag (arguably an edge-case, but it could still theoretically somehow happen), it would be nice to update the PR instead of crashing. Mentioning this, as it happened during testing on extension. Example code: | ||
| echo "Release PR Created" | ||
| echo "Checking out ${CHANGELOG_BRANCH_NAME}" | ||
| git checkout -b "${CHANGELOG_BRANCH_NAME}" | ||
| echo "Changelog Branch Created" | ||
| echo "Generating changelog via auto-changelog.." | ||
| npx @metamask/auto-changelog@4.1.0 update --rc --repo "${GITHUB_REPOSITORY_URL}" --currentVersion "${NEW_VERSION}" --autoCategorize | ||
| # Need to run from .github-tools context to inherit it's dependencies/environment | ||
| echo "Current Directory: $(pwd)" | ||
| PROJECT_GIT_DIR=$(pwd) | ||
| ls -ltra | ||
| cd ./github-tools/ | ||
| ls -ltra | ||
| corepack prepare yarn@4.5.1 --activate | ||
| # This can't be done from the actions context layer due to the upstream repository having it's own context set with yarn | ||
| yarn --cwd install | ||
| echo "Generating test plan csv.." | ||
| yarn run gen:commits "${PLATFORM}" "${PREVIOUS_VERSION}" "${RELEASE_BRANCH_NAME}" "${PROJECT_GIT_DIR}" | ||
| cd ../ | ||
| echo "Adding and committing changes.." | ||
| git add ./commits.csv | ||
| if ! (git commit -am "updated changelog and generated feature test plan"); | ||
| then | ||
| echo "Error: No changes detected." | ||
| exit 1 | ||
| fi | ||
| PR_BODY="This PR updates the change log for ${NEW_VERSION} and generates the test plan here [commit.csv](${GITHUB_REPOSITORY_URL}/blob/${CHANGELOG_BRANCH_NAME}/commits.csv)" | ||
| echo "Pushing changes to the remote.." | ||
| git push --set-upstream origin "${CHANGELOG_BRANCH_NAME}" | ||
| echo Creating release PR.. | ||
| gh pr create \ | ||
| --draft \ | ||
| --title "chore: ${CHANGELOG_BRANCH_NAME}" \ | ||
| --body "${PR_BODY}" \ | ||
| --base "${RELEASE_BRANCH_NAME}" \ | ||
| --head "${CHANGELOG_BRANCH_NAME}"; | ||
| echo "Changelog PR Created" | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,201 @@ | ||
| // eslint-disable-next-line import/no-nodejs-modules | ||
| import fs from 'fs'; | ||
| // eslint-disable-next-line import/no-extraneous-dependencies | ||
| import simpleGit from 'simple-git'; | ||
| // eslint-disable-next-line import/no-extraneous-dependencies | ||
| import { Octokit } from '@octokit/rest'; | ||
| import axios from 'axios'; | ||
| // "GITHUB_TOKEN" is an automatically generated, repository-specific access token provided by GitHub Actions. | ||
| const githubToken = process.env.GITHUB_TOKEN; | ||
| if (!githubToken) { | ||
| console.log('GITHUB_TOKEN not found'); | ||
| process.exit(1); | ||
| } | ||
| // Initialize Octokit with your GitHub token | ||
| const octokit = new Octokit({ auth: githubToken}); | ||
| // https://github.com/MetaMask/MetaMask-planning/blob/main/teams.json lookup from here | ||
| async function getTeam(repository, prNumber) { | ||
| try { | ||
| const { data: prData } = await octokit.pulls.get({ | ||
| owner: 'MetaMask', | ||
| repo: repository, | ||
| pull_number: prNumber[1], | ||
| }); | ||
| const author = prData.user.login; // PR author's GitHub username | ||
| const teamsJsonUrl = 'https://raw.githubusercontent.com/MetaMask/MetaMask-planning/refs/heads/main/teams.json'; | ||
| const githubToken = process.env.GITHUB_TOKEN; | ||
| const response = await axios.get(teamsJsonUrl, { | ||
| headers: { 'Authorization': `token ${githubToken}` } | ||
| }); | ||
| // Check if the response is successful and contains data | ||
| if (response.status !== 200 || !response.data ) { | ||
| console.error(`Invalid response when fetching teams.json: ${response.status}`); | ||
| return ['Unknown']; | ||
| } | ||
| const teamsJson = response.data; | ||
| // Step 3: Match the PR author's username to a team | ||
| const team = teamsJson[author]; | ||
| // Step 4: Return the team name or 'Unknown' if not found | ||
| return team || 'Unknown'; | ||
| } catch (error) { | ||
| console.error(`Error fetching team for PR #${prNumber}:`, error.message || error); | ||
| return 'Unknown'; | ||
| } | ||
| } | ||
| // Function to filter commits based on unique commit messages and group by teams | ||
| async function filterCommitsByTeam(platform, branchA, branchB) { | ||
| console.log('Filtering commits by team...'); | ||
| var repository = ''; | ||
| switch (platform) { | ||
| case 'mobile': | ||
| repository = 'metamask-mobile'; | ||
| break; | ||
| case 'extension': | ||
| repository = 'metamask-extension'; | ||
| break; | ||
| default: | ||
| repository = 'metamask-mobile'; | ||
| } | ||
| try { | ||
| const git = simpleGit(); | ||
| const logOptions = { | ||
| from: branchB, | ||
| to: branchA, | ||
| format: { | ||
| hash: '%H', | ||
| author: '%an', | ||
| message: '%s', | ||
| }, | ||
| }; | ||
| const log = await git.log(logOptions); | ||
| const commitsByTeam = {}; | ||
| const MAX_COMMITS = 500; // Limit the number of commits to process | ||
| for (const commit of log.all) { | ||
| const { author, message, hash } = commit; | ||
| if (Object.keys(commitsByTeam).length >= MAX_COMMITS) { | ||
| console.error('Too many commits for script to work') | ||
| break; | ||
| } | ||
| // Extract PR number from the commit message using regex | ||
| const prMatch = message.match(/\(#(\d{4,5})\)$/u); | ||
| if(prMatch){ | ||
| const prLink = prMatch ? `https://github.com/MetaMask/${repository}/pull/${prMatch[1]}` : ''; | ||
| const team = await getTeam(repository, prMatch); | ||
| // Initialize the team's commits array if it doesn't exist | ||
| if (!commitsByTeam[team]) { | ||
| commitsByTeam[team] = []; | ||
| } | ||
| commitsByTeam[team].push({ | ||
| message, | ||
| author, | ||
| hash: hash.substring(0, 7), | ||
| prLink, | ||
| }); | ||
| } | ||
| } | ||
| return commitsByTeam; | ||
| } catch (error) { | ||
| console.error(error); | ||
| return {}; | ||
| } | ||
| } | ||
| function formatAsCSV(commitsByTeam) { | ||
| const csvContent = []; | ||
| for (const [team, commits] of Object.entries(commitsByTeam)) { | ||
| commits.forEach((commit) => { | ||
| const row = [ | ||
| escapeCSV(commit.message), | ||
| escapeCSV(commit.author), | ||
| commit.prLink, | ||
| escapeCSV(team), | ||
| assignChangeType(commit.message) | ||
| ]; | ||
| csvContent.push(row.join(',')); | ||
| }); | ||
| } | ||
| csvContent.unshift('Commit Message,Author,PR Link,Team,Change Type'); | ||
| return csvContent; | ||
| } | ||
| // Helper function to escape CSV fields | ||
| function escapeCSV(field) { | ||
| if (field.includes(',') || field.includes('"') || field.includes('\n')) { | ||
| return `"${field.replace(/"/g, '""')}"`; // Encapsulate in double quotes and escape existing quotes | ||
| } | ||
| return field; | ||
| } | ||
| // Helper function to create change type | ||
| function assignChangeType(field) { | ||
| if (field.includes('feat')) | ||
| return 'Added'; | ||
| else if (field.includes('cherry') || field.includes('bump')) | ||
| return 'Ops'; | ||
| else if (field.includes('chore') || field.includes('test') || field.includes('ci') || field.includes('docs') || field.includes('refactor')) | ||
| return 'Changed'; | ||
| else if (field.includes('fix')) | ||
| return 'Fixed'; | ||
| return 'Unknown'; | ||
| } | ||
| async function main() { | ||
| const args = process.argv.slice(2); | ||
| const fileTitle = 'commits.csv'; | ||
| if (args.length !== 4) { | ||
| console.error('Usage: node generate-rc-commits.mjs platform branchA branchB'); | ||
| console.error('Received:', args, ' with length:', args.length); | ||
| process.exit(1); | ||
| } | ||
| const platform = args[0]; | ||
| const branchA = args[1]; | ||
| const branchB = args[2]; | ||
| const gitDir = args[3]; | ||
| // Change the working directory to the git repository path | ||
| // Since this is invoked by a shared workflow, the working directory is not guaranteed to be the repository root | ||
| process.chdir(gitDir); | ||
| console.log(`Generating CSV file for commits between ${branchA} and ${branchB} on ${platform} platform...`); | ||
| const commitsByTeam = await filterCommitsByTeam(platform, branchA, branchB); | ||
| if (Object.keys(commitsByTeam).length === 0) { | ||
| console.log('No commits found.'); | ||
| } else { | ||
| const csvContent = formatAsCSV(commitsByTeam); | ||
| fs.writeFileSync(fileTitle, csvContent.join('\n')); | ||
| console.log('CSV file ', fileTitle, ' created successfully.'); | ||
| } | ||
| } | ||
| main(); |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.