Skip to content

Commit 6b1809f

Browse files
authored
feat(release): add shell scripts to automate PR creation (#5330)
* chore(release): allow using "major" or "minor" instead of exact version * fix(copy-fork): use correct package detection * chore: create release scripts that automate GitHub * fix(scripts): tidy up release scripts * fix(script): use path relative to file, not cwd * chore: minor fixes to streamline versioning script * chore: minor fixes to streamline publish script * chore(release): use commit SHA from local git * chore(release): update script to wait for approvals and auto-create GitHub release * chore(release): use $() instead of `` in shell script * chore(release): update publish script to work better with nucleus * chore(release): be more flexible in version bump input * chore(release): clean up locally before watching CI this way local dev can continue without worrying about git state * chore(scripts): ergonomic improvements to release scripts * chore(release): add release script that does all the steps * chore(release): remove outdated README the scripts all have explanatory comments in their files * chore(release): make version script watch for version PR merged
1 parent c74480b commit 6b1809f

File tree

8 files changed

+180
-20
lines changed

8 files changed

+180
-20
lines changed

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,9 @@
3131
"test:performance:best": "nx test:best @lwc/perf-benchmarks",
3232
"test:performance:best:ci": "nx test:best:ci @lwc/perf-benchmarks",
3333
"test:types": "nx test @lwc/integration-types",
34-
"release:version": "node ./scripts/release/version.js",
35-
"release:publish": "nx release publish --registry https://registry.npmjs.org"
34+
"release:version": "./scripts/release/version.sh",
35+
"release:publish": "./scripts/release/publish.sh",
36+
"release:publish:canary": "nx release publish --registry https://registry.npmjs.org"
3637
},
3738
"devDependencies": {
3839
"@commitlint/cli": "^19.8.0",

scripts/release/README.md

Lines changed: 0 additions & 12 deletions
This file was deleted.

scripts/release/publish.sh

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
#!/usr/bin/env bash
2+
3+
# Find the commit on master that last changed the root package.json version,
4+
# then submit a PR to merge that commit onto the release branch and release via nucleus
5+
# Usage: yarn release:publish [branch=release]
6+
7+
set -e
8+
9+
BRANCH="release-publish-$(date -u +'%Y-%m-%d-%H_%M')"
10+
BASE_BRANCH='master'
11+
RELEASE_BRANCH="${1:-release}"
12+
13+
# Avoid accidentally committing unrelated files
14+
if [[ -n $(git status --porcelain) ]]; then
15+
echo -e '\033[1mPlease stash your work before continuing.\n\033[0m'
16+
git status
17+
exit 1
18+
fi
19+
20+
git switch "$BASE_BRANCH"
21+
git pull
22+
# The last commit that changed the root package.json version
23+
# (in case new commits were merged onto the base branch since then that aren't ready for release)
24+
VERSION_SHA=$(git blame -- package.json | grep '"version":' | cut -d' ' -f1)
25+
VERSION=$(jq -r .version package.json)
26+
27+
# Create a new branch with the changes to release
28+
git switch -c "$BRANCH" "$VERSION_SHA"
29+
git push origin HEAD
30+
31+
if which gh >/dev/null; then
32+
# Use GitHub CLI to create a PR and wait for CI checks to pass
33+
gh pr create -t "chore: release $VERSION" -B "$RELEASE_BRANCH" -b ''
34+
# Clean up locally
35+
git switch "$BASE_BRANCH"
36+
git branch -D "$BRANCH"
37+
38+
# Wait for CI to complete
39+
./wait-for-pr.sh "$BRANCH"
40+
if ! gh pr checks --fail-fast --watch; then
41+
echo 'CI failed. Cannot continue with release.'
42+
gh pr view "$BRANCH" --web
43+
exit 1
44+
fi
45+
46+
sleep 10 # Give nucleus time to start the release job
47+
RELEASE_JOB=$(gh pr checks "$BRANCH" --json name,link -q '.[]|select(.name=="continuous-integration/nucleus/release").link')
48+
echo "Nucleus release started: $RELEASE_JOB"
49+
50+
# Wait for GitHub release to be created by Nucleus, then open it
51+
echo 'The GitHub release notes must be added manually. You can exit the script now and open GitHub on your own, or wait until the release is created and the script will open the page for you.'
52+
sleep 300 # Nucleus job usually takes ~5 minutes
53+
while ! gh release view "v$VERSION" 1>/dev/null 2>/dev/null; do
54+
sleep 15
55+
done
56+
gh release view "v$VERSION" --web
57+
else
58+
# GitHub CLI not installed - clean up and prompt for manual branch creation
59+
git switch "$BASE_BRANCH"
60+
git branch -D "$BRANCH"
61+
echo "Open a PR: https://github.com/salesforce/lwc/pull/new/$BRANCH"
62+
fi

scripts/release/release.sh

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#!/usr/bin/env bash
2+
3+
# Do the full git/GitHub dance to publish a new release. Still requires some manual steps.
4+
# Usage: yarn release <version> [branch=release]
5+
# Example: yarn release 8.16.5 summer25
6+
7+
set -e
8+
9+
if ! which gh >/dev/null; then
10+
echo 'This script requires the GitHub CLI. Please install before continuing (`brew install gh` or https://cli.github.com/).'
11+
echo 'Alternatively, run the individual steps of this script manually (`yarn release:version` and `yarn release:publish`).'
12+
exit 1
13+
fi
14+
15+
echo 'Creating version bump PR...'
16+
./version.sh "$1"
17+
echo 'Creating release PR...'
18+
./publish.sh "$2"

scripts/release/version.js

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,13 @@
77
const fs = require('node:fs');
88
const path = require('node:path');
99
const readline = require('node:readline');
10+
const semver = require('semver');
1011
const { globSync } = require('glob');
1112

13+
const rootPath = path.resolve(__dirname, '../../');
14+
const rootPackageJsonPath = `${rootPath}/package.json`;
15+
const rootPackageJson = JSON.parse(fs.readFileSync(rootPackageJsonPath, 'utf-8'));
16+
1217
(async () => {
1318
const newVersion = process.argv[2] || (await promptVersion());
1419
updatePackages(newVersion);
@@ -24,7 +29,17 @@ async function promptVersion() {
2429
const answer = await new Promise((resolve) =>
2530
rl.question('Enter a new LWC version: ', resolve)
2631
);
27-
return answer;
32+
const exact = semver.valid(answer);
33+
if (exact) {
34+
// answer is a semver version
35+
return exact;
36+
}
37+
const incremented = semver.inc(rootPackageJson.version, answer);
38+
if (incremented) {
39+
// answer is a semver release type (major/minor/etc.)
40+
return incremented;
41+
}
42+
throw new Error(`Invalid release version: ${answer}`);
2843
} catch (error) {
2944
console.error(error);
3045
process.exit(1);
@@ -71,9 +86,6 @@ function updatePackages(newVersion) {
7186
}
7287

7388
function getPackagesToUpdate() {
74-
const rootPath = path.resolve(__dirname, '../../');
75-
const rootPackageJsonPath = `${rootPath}/package.json`;
76-
const rootPackageJson = JSON.parse(fs.readFileSync(rootPackageJsonPath, 'utf-8'));
7789
const packagesToUpdate = [];
7890

7991
const workspacePkgs = rootPackageJson.workspaces.reduce(

scripts/release/version.sh

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
#!/usr/bin/env bash
2+
3+
# Bump package versions for release and submit a PR on GitHub
4+
# Usage: yarn release:version <version>
5+
6+
set -e
7+
8+
VERSION="$1"
9+
BRANCH="release-version-$(date -u +'%Y-%m-%d-%H_%M')"
10+
BASE_BRANCH='master'
11+
12+
if [ -z "$VERSION" ]; then
13+
echo 'Specify a new version.'
14+
exit 1
15+
fi
16+
17+
# Avoid accidentally committing unrelated files
18+
if [[ -n $(git status --porcelain) ]]; then
19+
echo -e '\033[1mPlease stash your work before continuing.\n\033[0m'
20+
git status
21+
exit 1
22+
fi
23+
24+
git switch "$BASE_BRANCH"
25+
git pull
26+
git switch -c "$BRANCH"
27+
node "$(dirname "$0")/version.js" "$VERSION"
28+
# Input could have been major/minor/patch; update the var to the resolved version
29+
VERSION=$(jq -r .version package.json)
30+
VERSION_BUMP_MESSAGE="chore: bump version to $VERSION"
31+
git commit -am "$VERSION_BUMP_MESSAGE"
32+
git push origin HEAD
33+
34+
if which gh 2>/dev/null 1>/dev/null; then
35+
# Use GitHub CLI to create a PR and wait for it to be merged before exiting
36+
gh pr create -t "$VERSION_BUMP_MESSAGE" -b ''
37+
gh pr merge --auto --squash --delete-branch
38+
git switch "$BASE_BRANCH"
39+
git branch -D "$BRANCH"
40+
41+
./wait-for-pr.sh "$BRANCH"
42+
while [ "$(gh pr view "$BRANCH" --json state -q .state)" != 'MERGED' ]; do
43+
sleep 3 # Wait for GitHub to auto-merge the PR
44+
done
45+
46+
else
47+
# Clean up and prompt for manual branch creation
48+
git switch "$BASE_BRANCH"
49+
echo "Open a PR: https://github.com/salesforce/lwc/pull/new/$BRANCH"
50+
fi

scripts/release/wait-for-pr.sh

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#!/usr/bin/env/bash
2+
3+
# Waits until a PR is ready to be merged
4+
# Usage: ./wait-for-pr.sh <branch name or PR number>
5+
# Example: ./wait-for-pr.sh 'my-branch-with-cool-changes'
6+
7+
set -e
8+
9+
BRANCH="$1"
10+
if [ -z "$BRANCH" ]; then
11+
echo 'Please specify a branch name or PR number.'
12+
exit 1
13+
fi
14+
15+
# Wait for CI to complete
16+
if ! gh pr checks --fail-fast --watch; then
17+
echo 'CI failed. Cannot continue with release.'
18+
gh pr view "$BRANCH" --web
19+
exit 1
20+
fi
21+
22+
# Also wait for approvals, if needed
23+
if [ "$(gh pr view "$BRANCH" --json reviewDecision -q .reviewDecision)" != 'APPROVED' ]; then
24+
echo 'Waiting for PR approval.'
25+
while [ "$(gh pr view "$BRANCH" --json reviewDecision --jq .reviewDecision)" != 'APPROVED' ]; do
26+
sleep 30
27+
done
28+
echo 'PR approved! Continuing...'
29+
fi

scripts/tasks/unsafe-external-contributor-ci-workaround.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,11 @@ if [[ -n `git status --porcelain` ]]; then
2121
fi
2222

2323
# Enforce required tooling
24-
if [[ -z `which gh` ]]; then
24+
if ! which gh 2>/dev/null 1>/dev/null; then
2525
echo 'Please install the GitHub CLI (gh): https://cli.github.com/'
2626
exit 1
2727
fi
28-
if [[ -z `which jq` ]]; then
28+
if ! which jq 2>/dev/null 1>/dev/null; then
2929
echo 'Please install jq: https://jqlang.github.io/jq'
3030
exit 1
3131
fi

0 commit comments

Comments
 (0)