Skip to content
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

chore: changelog release automation #10172

Open
wants to merge 40 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 35 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
68b85ee
added scripts to auto generate changelogs
sethkfman Jun 28, 2024
bb5e0e2
moved create release scripts to .github and edited for testing
sethkfman Jun 28, 2024
682fb7a
updated script to take in args
sethkfman Jun 28, 2024
812661d
set the base branch to test for RC cut
sethkfman Jun 28, 2024
f4aecd6
fix path
sethkfman Jun 28, 2024
f2f046a
fix path
sethkfman Jun 28, 2024
c44be11
fix PR create msg
sethkfman Jun 28, 2024
8b0a6d0
fix deps for action
sethkfman Jun 28, 2024
65b1417
moved script files
sethkfman Jun 28, 2024
7a156d6
fix paths
sethkfman Jun 28, 2024
a37519f
fix paths and PR text
sethkfman Jun 28, 2024
8c7b2d4
update csv script
sethkfman Jun 28, 2024
4a2faf5
update csv script to use let
sethkfman Jun 28, 2024
b292665
updated path
sethkfman Jun 28, 2024
8f979f7
updated deps and move to devDeps
sethkfman Jun 28, 2024
638cf2a
updated yarn command
sethkfman Jul 1, 2024
5c92492
added release value arg
sethkfman Jul 1, 2024
c08dbf0
added trap and refactor constants
sethkfman Jul 1, 2024
24516dd
updated team variable name and logic
sethkfman Jul 1, 2024
8f2a7b4
added error log if we are processing more than 500 commits
sethkfman Jul 1, 2024
bdba6e3
removed unused array and formatting
sethkfman Jul 1, 2024
903c347
reduced hash length to standard
sethkfman Jul 1, 2024
1346f7b
updated console logs
sethkfman Jul 1, 2024
40aa588
updated console log
sethkfman Jul 1, 2024
065bcd2
updated ref
sethkfman Jul 2, 2024
abef73d
updated test plan link
sethkfman Jul 3, 2024
7043c2f
M:erge branch 'main' into chore/changelog-release-automation
sethkfman Jul 3, 2024
d81bb6a
Merge branch 'main' into chore/changelog-release-automation
sethkfman Jul 3, 2024
3dd570f
remove test code and prepared the PR for final approval
sethkfman Jul 3, 2024
d8a90e5
convert return values to an array
sethkfman Jul 3, 2024
68aa1ee
updated to check Object.keys
sethkfman Jul 3, 2024
545bda5
main sync
sethkfman Jul 5, 2024
2d9b4ce
yarn.lock dedup
sethkfman Jul 5, 2024
29ff1a4
fix yarnrc
sethkfman Jul 5, 2024
0bb8caa
update lockfile dedup issues
sethkfman Jul 5, 2024
0bc0068
fix dedupe error
sethkfman Jul 5, 2024
cabb32b
fix dedupe
sethkfman Jul 5, 2024
a92169f
fix dedup
sethkfman Jul 5, 2024
f957788
dedupe fix
sethkfman Jul 5, 2024
07960b6
dedup fix
sethkfman Jul 5, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 11 additions & 8 deletions .github/workflows/create-release-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ on:
version-number:
description: 'A natural version number. eg: 862'
required: true

previous-version-tag:
NicolasMassart marked this conversation as resolved.
Show resolved Hide resolved
description: 'Previous release version tag. eg: v7.7.0'
required: true
jobs:
create-release-pr:
runs-on: ubuntu-latest
Expand All @@ -34,20 +36,21 @@ jobs:
# The workaround is to use a personal access token (BUG_REPORT_TOKEN) instead of
# the default GITHUB_TOKEN for the checkout action.
token: ${{ secrets.BUG_REPORT_TOKEN }}
- name: Get Node.js version
id: nvm
run: echo "NODE_VERSION=$(cat .nvmrc)" >> "$GITHUB_OUTPUT"
- uses: actions/setup-node@v3
- name: Set up Node.js
uses: actions/setup-node@v3
with:
node-version: ${{ steps.nvm.outputs.NODE_VERSION }}
node-version-file: '.nvmrc'
cache: yarn
- name: Install dependencies
run: yarn --immutable
- name: Set Versions
id: set-versions
shell: bash
run: SEMVER_VERSION=${{ github.event.inputs.semver-version }} VERSION_NUMBER=${{ github.event.inputs.version-number }} yarn create-release
run: SEMVER_VERSION=${{ github.event.inputs.semver-version }} VERSION_NUMBER=${{ github.event.inputs.version-number }} yarn set-version
- name: Create Release PR
id: create-release-pr
shell: bash
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
./scripts/create-release-pr.sh ${{ github.event.inputs.semver-version }}
./scripts/create-release-pr.sh ${{ github.event.inputs.previous-version-tag }} ${{ github.event.inputs.semver-version }}
7 changes: 5 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,10 @@
"test:merge-coverage": "nyc report --temp-dir ./tests/coverage --report-dir ./tests/merged-coverage/ --reporter json --reporter text --reporter lcovonly",
"test:validate-coverage": "nyc check-coverage --nycrc-path ./coverage-thresholds.json -t ./tests/merged-coverage/",
"update-changelog": "./scripts/auto-changelog.sh",
"changeset-changelog": "wrap () { node ./scripts/generate-rc-commits.js \"$@\" && ./scripts/changelog-csv.sh }; wrap ",
"prestorybook": "rnstl",
"deduplicate": "yarn yarn-deduplicate && yarn install",
"create-release": "./scripts/set-versions.sh && yarn update-changelog",
"set-version": "./scripts/set-versions.sh",
"add-release-label-to-pr-and-linked-issues": "ts-node ./.github/scripts/add-release-label-to-pr-and-linked-issues.ts",
"add-team-label-to-pr": "ts-node ./.github/scripts/add-team-label-to-pr.ts",
"run-bitrise-e2e-check": "ts-node ./.github/scripts/bitrise/run-bitrise-e2e-check.ts",
Expand Down Expand Up @@ -371,6 +372,7 @@
"@metamask/object-multiplex": "^1.1.0",
"@metamask/providers": "^13.1.0",
"@metamask/test-dapp": "^8.9.0",
"@octokit/rest": "^21.0.0",
"@open-rpc/mock-server": "^1.7.5",
"@open-rpc/schema-utils-js": "^1.16.2",
"@open-rpc/test-coverage": "^2.2.2",
Expand Down Expand Up @@ -477,6 +479,7 @@
"regenerator-runtime": "0.13.9",
"rn-nodeify": "10.3.0",
"serve-handler": "^6.1.5",
"simple-git": "^3.22.0",
"ts-node": "^10.5.0",
"typescript": "~4.8.4",
"wdio-cucumberjs-json-reporter": "^4.4.3",
Expand Down Expand Up @@ -574,4 +577,4 @@
}
},
"packageManager": "yarn@1.22.22"
}
}
80 changes: 80 additions & 0 deletions scripts/changelog-csv.sh
sethkfman marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
#!/bin/bash

set -e
set -u
set -o pipefail

readonly CSV_FILE='commits.csv'

# Add release branch arg name
RELEASE_BRANCH_NAME="${1}"

# Temporary file for new entries
NEW_ENTRIES=$(mktemp)

# Backup file for existing CHANGELOG
CHANGELOG="CHANGELOG.md"
CHANGELOG_BACKUP="$CHANGELOG.bak"

# Backup existing CHANGELOG.md
cp "$CHANGELOG" "$CHANGELOG_BACKUP"

# Function to append entry to the correct category in the temp file
append_entry() {
local change_type="$1"
local entry="$2"
# Ensure the "Other" category is explicitly handled
case "$change_type" in
Added|Changed|Fixed) ;;
*) change_type="Other" ;; # Categorize as "Other" if not matching predefined categories
esac
echo "$entry" >> "$NEW_ENTRIES-$change_type"
}

# Read the CSV file and append entries to temp files based on change type
while IFS=, read -r commit_message author pr_link team change_type
NicolasMassart marked this conversation as resolved.
Show resolved Hide resolved
do
pr_id=$(echo "$pr_link" | grep -o '[^/]*$')
entry="- [#$pr_id]($pr_link): $commit_message"
append_entry "$change_type" "$entry"
done < <(tail -n +2 "$CSV_FILE") # Skip the header line

# Function to insert new entries into CHANGELOG.md after a specific line
insert_new_entries() {
local marker="## Current Main Branch"
local temp_changelog=$(mktemp)

# Find the line number of the marker
local line_num=$(grep -n "$marker" "$CHANGELOG_BACKUP" | cut -d ':' -f 1)

# Split the existing CHANGELOG at the marker line
head -n "$line_num" "$CHANGELOG_BACKUP" > "$temp_changelog"

# Append the release header
echo "" >> "$temp_changelog"
echo "## $RELEASE_BRANCH_NAME - <Date>" >> "$temp_changelog"
echo "" >> "$temp_changelog"

# Append new entries for each change type if they exist
for change_type in Added Changed Fixed Other; do
if [[ -s "$NEW_ENTRIES-$change_type" ]]; then
echo "### $change_type" >> "$temp_changelog"
cat "$NEW_ENTRIES-$change_type" >> "$temp_changelog"
echo "" >> "$temp_changelog" # Add a newline for spacing
fi
done

# Append the rest of the original CHANGELOG content
tail -n +$((line_num + 1)) "$CHANGELOG_BACKUP" >> "$temp_changelog"

# Replace the original CHANGELOG with the updated one
mv "$temp_changelog" "$CHANGELOG"
}

# Trap to ensure cleanup happens
trap 'rm -f "$NEW_ENTRIES-"* "$CHANGELOG_BACKUP"' EXIT

# Insert new entries into CHANGELOG.md
insert_new_entries

echo 'CHANGELOG updated'
14 changes: 11 additions & 3 deletions scripts/create-release-pr.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ set -e
set -u
set -o pipefail

NEW_VERSION="${1}"
PREVIOUS_VERSION="${1}"
NEW_VERSION="${2}"
RELEASE_BRANCH_PREFIX="release/"

if [[ -z $NEW_VERSION ]]; then
Expand All @@ -13,7 +14,7 @@ if [[ -z $NEW_VERSION ]]; then
fi

RELEASE_BRANCH_NAME="${RELEASE_BRANCH_PREFIX}${NEW_VERSION}"
RELEASE_BODY="This is the release candidate for version ${NEW_VERSION}."
RELEASE_BODY="This is the release candidate for version ${NEW_VERSION}. The test plan can be found at [commit.csv](https://github.com/MetaMask/metamask-mobile/blob/${RELEASE_BRANCH_NAME}/commits.csv)"

git config user.name metamaskbot
git config user.email metamaskbot@users.noreply.github.com
Expand All @@ -30,6 +31,13 @@ git push --set-upstream origin "${RELEASE_BRANCH_NAME}"

gh pr create \
--draft \
--title "${NEW_VERSION}" \
--title "feat: ${NEW_VERSION}" \
--body "${RELEASE_BODY}" \
--head "${RELEASE_BRANCH_NAME}";

#Generate changelog and test plan csv
node ./scripts/generate-rc-commits.mjs "${PREVIOUS_VERSION}" "${RELEASE_BRANCH_NAME}"
./scripts/changelog-csv.sh "${RELEASE_BRANCH_NAME}"
git add ./commits.csv
git commit -am "updated changelog and generated feature test plan"
git push
158 changes: 158 additions & 0 deletions scripts/generate-rc-commits.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
// 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';

// "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});

async function getPRLabels(prNumber) {
try {
const { data } = await octokit.pulls.get({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Q: what about using the GraphQL API to retrieve all the labels for PRs and filter them at once?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we can look at this in another revision.

owner: 'MetaMask',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

S: Extract values from Github action envs

repo: 'metamask-mobile',
pull_number: prNumber[1],
});

const labels = data.labels.map(label => label.name);

// Check if any label name contains "team"
let teamArray = labels.filter(label => label.toLowerCase().startsWith('team-'));

if(teamArray.length > 1 && teamArray.includes('team-mobile-platform'))
teamArray = teamArray.filter(item => item !== 'team-mobile-platform');

return teamArray || ['Unknown'];

} catch (error) {
console.error(`Error fetching labels for PR #${prNumber}:`, error);
return ['Unknown'];
}
}

// Function to filter commits based on unique commit messages and group by teams
async function filterCommitsByTeam(branchA, branchB) {
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/metamask-mobile/pull/${prMatch[1]}` : '';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

S: reuse repo constants here, available from Github actions.

const teams = await getPRLabels(prMatch);

// Initialize the team's commits array if it doesn't exist
if (!commitsByTeam[teams]) {
commitsByTeam[teams] = [];
}

commitsByTeam[teams].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 !== 2) {
console.error('Usage: node generate-rc-commits.mjs branchA branchB');
process.exit(1);
}

const branchA = args[0];
const branchB = args[1];

const commitsByTeam = await filterCommitsByTeam(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();
Loading
Loading