Skip to content

Commit 990a758

Browse files
authored
feat(gh-action): create workflow for automatize stable-main creation (#69)
* feat(gh-action): create workflow for automatize stable-main creation This PR is intended to add as functionality a workflow call that will: - Create a `stable-main-{release}` branch - Run the script that will ensure the branch will from a known good state and will take specific files from the stable branch - Create a PR against `main` * chore: remove unused input * chore: update stable sync To accept as input the semver from the repo * chore: added workflow call * chore: pin pr action from v7 to v5 * chore: comment the Create PR step, to test previous one before * chore: set user and email * chore: added global as well * chore: enable create PR on GH toolkit * chore: commented peter evans PR create step * chore: added CREATE_BRANCH env for the sync step * chore: improve the template * chore: update adding env var to create branch * chore: add gh command to push the branch * chore: test create with gh command * chore: gh auth login * chore: test without using gh * chore: improve body PR * chore: update sync script * chore: execute chantes even if the PR exists * chore: check for push outside of the box * chore: added pull before push * chore: tes new script changes * chore: push * chore: test * chore: improve script for mobile and extension * chore: clean up body message * chore: lint yaml * chore: linter passed
1 parent 566da33 commit 990a758

2 files changed

Lines changed: 289 additions & 0 deletions

File tree

.github/scripts/stable-sync.js

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
#!/usr/bin/env node
2+
3+
// USAGE:
4+
// This will create/update a local stable-sync branch
5+
// and get it in the state needed for a stable-sync PR
6+
// Once the script successfully completes, you just
7+
// need to push the branch to the remote repo. This will
8+
// likely require a `git push --force`
9+
//
10+
// Usage: node stable-sync.js [branch-name]
11+
// If no branch name is provided, defaults to 'stable-sync'
12+
//
13+
// Environment variables:
14+
// CREATE_BRANCH - if set to 'true', will push the branch at the end
15+
16+
const { promisify } = require('util');
17+
const exec = promisify(require('child_process').exec);
18+
19+
async function runGitCommands() {
20+
// Get branch name from command line arguments or use default
21+
const branchName = process.argv[2] || 'stable-main';
22+
23+
// Check if CREATE_BRANCH environment variable exists and is set to true
24+
const shouldPushBranch = (process.env.CREATE_BRANCH || 'false').toLowerCase() === 'true';
25+
26+
try {
27+
try {
28+
// Check if the branch already exists
29+
const { stdout: branchExists } = await exec(
30+
//`git rev-parse --quiet --verify ${branchName}`,
31+
`git ls-remote origin ${branchName}`,
32+
);
33+
if (branchExists.trim()) {
34+
// Branch exists, so simply check it out
35+
await exec(`git checkout ${branchName}`);
36+
await exec(`git pull origin ${branchName}`);
37+
console.log(`Checked out branch: ${branchName}`);
38+
} else {
39+
throw new Error(
40+
'git rev-parse --quiet --verify failed. Branch hash empty',
41+
);
42+
}
43+
} catch (error) {
44+
if (error.stdout === '') {
45+
console.warn(
46+
`Branch does not exist, creating new ${branchName} branch.`,
47+
);
48+
49+
// Branch does not exist, create and check it out
50+
await exec(`git checkout -b ${branchName}`);
51+
console.log(`Created and checked out branch: ${branchName}`);
52+
} else {
53+
console.error(`Error: ${error.message}`);
54+
process.exit(1);
55+
}
56+
}
57+
58+
await exec('git fetch');
59+
console.log('Executed: git fetch');
60+
61+
await exec('git reset --hard origin/stable');
62+
console.log('Executed: git reset --hard origin/stable');
63+
64+
try {
65+
await exec('git merge origin/main');
66+
console.log('Executed: git merge origin/main');
67+
} catch (error) {
68+
// Handle the error but continue script execution
69+
if (
70+
error.stdout.includes(
71+
'Automatic merge failed; fix conflicts and then commit the result.',
72+
)
73+
) {
74+
console.warn(
75+
'Merge conflict encountered. Continuing script execution.',
76+
);
77+
} else {
78+
console.error(`Error: ${error.message}`);
79+
process.exit(1);
80+
}
81+
}
82+
83+
await exec('git add .');
84+
await exec('git restore --source origin/main .');
85+
console.log('Executed: it restore --source origin/main .');
86+
87+
await exec('git checkout origin/main -- .');
88+
console.log('Executed: git checkout origin/main -- .');
89+
90+
await exec('git checkout origin/stable -- CHANGELOG.md');
91+
console.log('Executed: git checkout origin/stable -- CHANGELOG.md');
92+
93+
// Execute mobile-specific commands if REPO is 'mobile'
94+
if (process.env.REPO === 'mobile') {
95+
console.log('Executing mobile-specific commands...');
96+
97+
await exec('git checkout origin/stable -- bitrise.yml');
98+
console.log('Executed: git checkout origin/stable -- bitrise.yml');
99+
100+
await exec('git checkout origin/stable -- android/app/build.gradle');
101+
console.log('Executed: git checkout origin/stable -- android/app/build.gradle');
102+
103+
await exec('git checkout origin/stable -- ios/MetaMask.xcodeproj/project.pbxproj');
104+
console.log('Executed: git checkout origin/stable -- ios/MetaMask.xcodeproj/project.pbxproj');
105+
106+
await exec('git checkout origin/stable -- package.json');
107+
console.log('Executed: git checkout origin/stable -- package.json');
108+
}
109+
// Execute extension-specific commands if REPO is 'extension'
110+
else if (process.env.REPO === 'extension') {
111+
console.log('Executing extension-specific commands...');
112+
113+
const { stdout: packageJsonContent } = await exec(
114+
'git show origin/master:package.json',
115+
);
116+
const packageJson = JSON.parse(packageJsonContent);
117+
const packageVersion = packageJson.version;
118+
119+
await exec(`yarn version "${packageVersion}"`);
120+
console.log('Executed: yarn version');
121+
}
122+
// If REPO is not set or has an invalid value, skip both
123+
else {
124+
console.log('REPO environment variable not set or invalid. Skipping mobile/extension specific commands.');
125+
}
126+
127+
await exec('git add .');
128+
console.log('Executed: git add .');
129+
130+
try {
131+
// Check if there are any changes to commit
132+
const { stdout: status } = await exec('git status --porcelain');
133+
if (!status.trim()) {
134+
console.log('No changes to commit, skipping commit step');
135+
return;
136+
}
137+
138+
await exec(`git commit -m "Merge origin/main into ${branchName}" --no-verify`);
139+
console.log('Executed: git commit');
140+
} catch (error) {
141+
console.error(`Error: ${error.message}`);
142+
process.exit(1);
143+
}
144+
145+
console.log(`Your local ${branchName} branch is now ready to become a PR.`);
146+
147+
// Push the branch if CREATE_BRANCH is true
148+
if (shouldPushBranch) {
149+
try {
150+
console.log(`Checking if branch ${branchName} exists remotely...`);
151+
const { stdout: remoteBranches } = await exec('git ls-remote --heads origin');
152+
const branchExists = remoteBranches.includes(`refs/heads/${branchName}`);
153+
154+
if (branchExists) {
155+
console.log(`Branch ${branchName} exists remotely, updating...`);
156+
await exec(`git push origin ${branchName}`);
157+
} else {
158+
console.log(`Branch ${branchName} does not exist remotely, creating...`);
159+
await exec(`git push --set-upstream origin ${branchName}`);
160+
}
161+
console.log(`Successfully pushed branch ${branchName} to remote`);
162+
} catch (error) {
163+
console.error(`Error pushing branch: ${error.message}`);
164+
process.exit(1);
165+
}
166+
} else {
167+
console.log('You likely now need to do `git push --force`');
168+
}
169+
} catch (error) {
170+
console.error(`Error: ${error.message}`);
171+
process.exit(1);
172+
}
173+
}
174+
175+
runGitCommands();

.github/workflows/stable-sync.yml

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
name: Stable Sync
2+
3+
on:
4+
workflow_dispatch:
5+
inputs:
6+
semver-version:
7+
required: true
8+
type: string
9+
description: 'The semantic version to use for the sync (e.g., x.x.x)'
10+
repo-type:
11+
required: false
12+
type: choice
13+
description: 'Type of repository (mobile or extension)'
14+
options:
15+
- mobile
16+
- extension
17+
default: 'mobile'
18+
workflow_call:
19+
inputs:
20+
semver-version:
21+
required: true
22+
type: string
23+
description: 'The semantic version to use for the sync (e.g., x.x.x)'
24+
repo-type:
25+
required: false
26+
type: string
27+
description: 'Type of repository (mobile or extension)'
28+
default: 'mobile'
29+
30+
jobs:
31+
stable-sync:
32+
runs-on: ubuntu-latest
33+
steps:
34+
- uses: actions/checkout@v4
35+
with:
36+
fetch-depth: 0
37+
38+
- name: Setup Node.js
39+
uses: actions/setup-node@v4
40+
with:
41+
node-version: '18'
42+
43+
- name: Check if PR exists
44+
id: check-pr
45+
uses: actions/github-script@v7
46+
with:
47+
script: |
48+
const { data: prs } = await github.rest.pulls.list({
49+
owner: context.repo.owner,
50+
repo: context.repo.repo,
51+
head: `${context.repo.owner}:stable-main-${process.env.SEMVER_VERSION}`,
52+
base: 'main'
53+
});
54+
return prs.length > 0;
55+
env:
56+
SEMVER_VERSION: ${{ inputs.semver-version }}
57+
58+
- name: Set Git user and email
59+
run: |
60+
git config --global user.name "metamaskbot"
61+
git config --global user.email "metamaskbot@users.noreply.github.com"
62+
63+
- name: Run stable sync
64+
id: run-stable-sync
65+
# if: steps.check-pr.outputs.result != 'true'
66+
env:
67+
CREATE_BRANCH: 'false' # let the script handle the branch creation
68+
REPO: ${{ inputs.repo-type }} # Default to 'mobile' if not specified
69+
run: |
70+
node .github/scripts/stable-sync.js "stable-main-${{ inputs.semver-version }}"
71+
# Check if branch exists remotely
72+
BRANCH_NAME="stable-main-${{ inputs.semver-version }}"
73+
if git ls-remote --heads origin "$BRANCH_NAME" | grep -q "$BRANCH_NAME"; then
74+
git pull --rebase
75+
echo "Branch $BRANCH_NAME exists remotely, pushing normally"
76+
git push origin "$BRANCH_NAME" --force
77+
else
78+
echo "Branch $BRANCH_NAME doesn't exist remotely, pushing with --set-upstream"
79+
git push --set-upstream origin "$BRANCH_NAME"
80+
fi
81+
82+
- name: Create Pull Request
83+
if: steps.check-pr.outputs.result != 'true'
84+
env:
85+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
86+
BRANCH_NAME: stable-main-${{ inputs.semver-version }}
87+
VERSION: ${{ inputs.semver-version }}
88+
run: |
89+
# Create PR using GitHub CLI
90+
gh pr create \
91+
--title "chore: sync stable to main for version $VERSION" \
92+
--body "This PR syncs the stable branch to main for version $VERSION.
93+
94+
*Synchronization Process:*
95+
96+
- Fetches the latest changes from the remote repository
97+
- Resets the branch to match the stable branch
98+
- Attempts to merge changes from main into the branch
99+
- Handles merge conflicts if they occur
100+
101+
*File Preservation:*
102+
103+
Preserves specific files from the stable branch:
104+
- CHANGELOG.md
105+
- bitrise.yml
106+
- android/app/build.gradle
107+
- ios/MetaMask.xcodeproj/project.pbxproj
108+
- package.json
109+
110+
Indicates the next version candidate of main to $VERSION" \
111+
--base main \
112+
--head "$BRANCH_NAME"
113+
#--label "sync" \
114+
#--label "stable"

0 commit comments

Comments
 (0)