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

Simplify release process #4030

Merged
merged 5 commits into from
May 24, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions RELEASE.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ In the same day:
3. The selected commit is tagged as `v0.34.0-beta.0`, released, and published for testing as a Pre-Release

```
export TAG=v0.34.0-beta.0 && git tag -a $TAG 9fceb02 -m "$TAG" && git push origin $TAG
yarn release -t v0.34.0-beta.0
```

4. The team creates a PR to bump `master` to the next version (in the example: `v0.35.0`) and continues releasing nightly builds.
Expand All @@ -25,7 +25,7 @@ After 3-5 days of testing:
5. Tag final stable commit as `v0.34.0`, release and publish the stable release. This commit will be in `v0.34.x` branch and may note be on `master` if beta candidate required bug fixes.

```
export TAG=v0.34.0 && git tag -a $TAG 9fceb02 -m "$TAG" && git push origin $TAG
yarn release -t v0.34.0
```

## Pre-Releases
Expand Down
3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,7 @@
"benchmark": "yarn benchmark:files 'packages/*/test/perf/**/*.test.ts'",
"benchmark:files": "LODESTAR_PRESET=mainnet NODE_OPTIONS=--max-old-space-size=4096 benchmark --config .benchrc.yaml",
"publish:release": "lerna publish from-package --yes --no-verify-access",
"release": "lerna version --no-push --sign-git-commit",
"postrelease": "git tag -d $(git describe --abbrev=0)",
"release": "node scripts/release.mjs",
"check-readme": "lerna run check-readme"
},
"devDependencies": {
Expand Down
167 changes: 167 additions & 0 deletions scripts/release.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
import {execSync} from "node:child_process";
import {readFileSync} from "node:fs";
import yargs from "yargs";
import {hideBin} from "yargs/helpers";
import inquirer from "inquirer";
import _lernaVersion from "@lerna/version";

// Script to make releasing easier
// Run with --help to see usage

const cmd = (cmd, opts={}) => execSync(cmd, {encoding: "utf8", stdio: ["pipe", "pipe", "ignore"], ...opts}).trim();
const exit = (...msg) => { console.log(...msg), process.exit(1) };

const argv = yargs(hideBin(process.argv))
.usage("Release lodestar")
.example([
["$0 -t v0.36.0", "Release version 0.36.0 using the current commit"]
])
.options({
tag: {
alias: "t",
demandOption: true,
type: "string",
describe: "The tag to release",
},
commit: {
alias: "c",
type: "string",
describe: "The commit to tag",
},
yes: {
alias: "y",
type: "boolean",
describe: "Automatic yes to prompts"
}
})
.version("v0.1.0")
.alias("v", "version")
.alias("h", "help")
.strict()
.help()
.argv;

const yes = argv.yes;
const {
tag,
currentCommit,
commit,
commitMessage,
branch,
} = getInfo(argv);

console.log("Tag", tag);
console.log("Checked-out commit", currentCommit);
console.log("Commit", commit, commitMessage);
console.log("Branch", branch);

ensureCommitExistsInBranch(commit, branch);

if (!lernaVersionMatchesTag(tag)) {
console.log("Lerna-controlled version does not match tag");

if (commit !== currentCommit) {
exit("Cannot continue because the checked-out commit doesn't match the selected commit");
}
console.log("Deferring to lerna");
await lernaVersion(tag, yes);
} else {
await tagAndPush(commit, tag, yes);
}

console.log("Success!");

/////////////////////////////

function getInfo(argv) {
// Validate tag version (must be semver-ish)
const versionCaptureRegex=/^(v[0-9]+\.[0-9]+)\.[0-9]+(-beta\.[0-9]+)?$/
const versionMatch = versionCaptureRegex.exec(argv.tag);
if (versionMatch == null) {
exit(`Tag must match ${versionCaptureRegex}`);
}

const tag = argv.tag;
const currentCommit = cmd("git rev-parse --short HEAD");
const commit = argv.commit ?? currentCommit;
const commitMessage = cmd(`git show-branch --no-name ${commit}`);
// The branch is assumed from the tag
const branch = `${versionMatch[1]}.x`;

return {
tag,
currentCommit,
commit,
commitMessage,
branch,
};
}

function ensureCommitExistsInBranch(commit, branch) {
// Ensure the branch exists
try {
cmd(`git show-branch --no-name ${branch}`);
} catch (e) {
exit(`Branch ${branch} does not exist`);
}

// Ensure the commit exists in the branch (last 10 commits)
const last10Commits = cmd(`git log --oneline -n 10 ${branch}`);
const commitMatch = last10Commits.match(commit);
if (commitMatch == null) {
exit(`Commit ${commit} does not belong to branch ${branch}`);
}
}

function lernaVersionMatchesTag(tag) {
// Ensure the lerna.json is at the right version
let lernaVersion;
try {
lernaVersion = JSON.parse(readFileSync("./lerna.json")).version;
} catch (e) {
exit(`Error fetching/parsing lerna.json: ${e.message}`);
}
return lernaVersion === tag;
}

async function lernaVersion(tag, yes) {
try {
await _lernaVersion({
cwd: process.cwd(),
bump: tag,
"force-publish": true,
yes: yes,
wemeetagain marked this conversation as resolved.
Show resolved Hide resolved
})
} catch (e) {
exit((e.message));
}
}

Copy link
Contributor

Choose a reason for hiding this comment

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

how do we prevent this error TAG v0.36.1-beta.0 does not include LOCAL_VERSION 0.36.0 from https://github.com/ChainSafe/lodestar/runs/6476719731?check_suite_focus=true ?

if yes, we should do a check here before we create a tag.

Copy link
Contributor

Choose a reason for hiding this comment

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

That's a good idea! This condition should be checked here too

Copy link
Member Author

@wemeetagain wemeetagain May 20, 2022

Choose a reason for hiding this comment

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

The script now calls lerna version $TAG if the tag does not match lerna.json (which bumps the versions in package.jsons, commits, tags and pushes).
Otherwise, if the tag matches lerna.json, it tags and pushes manually with git.

This makes it very handy to just checkout the version branch (eg: v0.36.x), cherry pick commits on top, and run the script, in the case of a beta release.
Or just checkout the version branch and run the script, in the case of a non-beta release.

async function tagAndPush(commit, tag, yes) {
// Last chance to exit
if (!yes) {
const input = await inquirer.prompt([
{
name: "yes",
type: "confirm",
message: "Do you want to proceed? Continuing will tag and push.",
},
]);
if (!input.yes) {
process.exit(1);
}
}

// Perform release actions
try {
const tagCmd = `git tag -a ${tag} ${commit} -m "${tag}"`;
console.log(tagCmd);
cmd(tagCmd, {stdio: "pipe"});

const pushCmd = `git push origin ${tag}`;
console.log(pushCmd);
cmd(pushCmd, {stdio: "pipe"});
} catch (e) {
exit(e.message);
}
}