diff --git a/docs/releasing.md b/docs/releasing.md index d747eab8..22dd1c36 100644 --- a/docs/releasing.md +++ b/docs/releasing.md @@ -4,102 +4,40 @@ Describes how we do releases of Mithril.js # Mithril.js Release Processes -**Note** These steps all assume that `MithrilJS/mithril.js` is a git remote named `mithriljs`, adjust accordingly if that doesn't match your setup. +Mithril's release process is automated by [pr-release]. pr-release is maintained by a long time mithril.js community member [@JAForbes](https://github.com/JAForbes). -- [Releasing a new Mithril.js version](#releasing-a-new-mithriljs-version) -- [Updating mithril.js.org](#updating-mithriljsorg) +pr-release handles the following: -## Releasing a new Mithril.js version +- Generating changelog entries +- Automating the semver version +- Publishing releases and pre-releases to npm +- Creating github releases +- Rollbacks -### Prepare the release +## For contributors -1. Ensure your local branch is up to date +Contributors should create their feature branch targetting the default branch `next`. When this branch is merged `pr-release` will either generate or update a release PR from `next` to `main`. -```bash -$ git checkout next -$ git pull --rebase mithriljs next -``` +The description and title will be managed by [pr-release], including the semver version. -2. Determine patch level of the change -3. Update information in `docs/changelog.md` to match reality of the new version being prepared for release. - - Don't forget to add today's date under the version heading! -4. Replace all existing references to `mithril@next` to `mithril` if moving from a release candidate to stable. - - Note: if making an initial release candidate, don't forget to move all the playground snippets to pull from `mithril@next`! -5. Commit changes to `next` +Contributors who have permissions should add the correct semver label to their PR (`major` | `minor` | `patch`). If no label is set, `patch` is assumed. -``` -$ git add . -$ git commit -m "Preparing for release" +If you do not have permissions, the maintainer will set the label on your behalf. -# Push to your branch -$ git push +## Changelog -# Push to MithrilJS/mithril.js -$ git push mithriljs next -``` +There are two changelogs in the mithril project -### Merge from `next` to `master` +- `docs/changelog.md` a hand written curated reflection of changes to the codebase +- `docs/release.md` an automatically prepended log of changes, managed by pr-release -6. Switch to `master` and make sure it's up to date +In future we may collapse these into a single file, the separation is due to the fact the `changelog.md` predates the `release.md` file. -```bash -$ git checkout master -$ git pull --rebase mithriljs master -``` +## For maintainers -7. merge `next` on top of it +Whenever a new feature branch is opened, a reviewing maintainer should add the correct semver label to their PR (`major` | `minor` | `patch`). If no label is set, `patch` is assumed. -```bash -$ git merge next -``` - -8. Clean & update npm dependencies and ensure the tests are passing. - -```bash -$ npm prune -$ npm i -$ npm test -``` - -### Publish the release - -9. `npm run release `, see the docs for [`npm version`](https://docs.npmjs.com/cli/version) -10. The changes will be automatically pushed to your fork -11. Push the changes to `MithrilJS/mithril.js` - -```bash -$ git push mithriljs master -``` - -12. Travis will push the new release to npm & create a GitHub release - -### Merge `master` back into `next` - -This helps to ensure that the `version` field of `package.json` doesn't get out of date. - -13. Switch to `next` and make sure it's up to date - -```bash -$ git checkout next -$ git pull --rebase mithriljs next -``` - -14. Merge `master` back onto `next` - -```bash -$ git merge master -``` - -15. Push the changes to your fork & `MithrilJS/mithril.js` - -```bash -$ git push -$ git push mithriljs next -``` - -### Update the GitHub release - -16. The GitHub Release will require a manual description & title to be added. I suggest coming up with a fun title & then copying the `docs/changelog.md` entry for the build. +If a `major` or `minor` feature branch is merge, but no labels were set, you can go back and apply the labels and then re-run the `pr` workflow in github actions, this will recalculate the semver version. ## Updating mithril.js.org @@ -124,3 +62,5 @@ $ node scripts/update-docs After the docs build completes, the updated docs should appear on https://mithril.js.org in a few minutes. **Note:** When updating the stable version with a release candidate out, ***make sure to update the index + navigation to point to the new stable version!!!*** + +[pr-release]: https://pr-release.org/ \ No newline at end of file diff --git a/scripts/release.js b/scripts/release.js deleted file mode 100644 index 5f8ae15d..00000000 --- a/scripts/release.js +++ /dev/null @@ -1,282 +0,0 @@ -#!/usr/bin/env node -"use strict" - -// This is my temporary hack to simplify deployment until I fix the underlying -// problems in these bugs: -// - https://github.com/MithrilJS/mithril.js/issues/2417 -// - https://github.com/MithrilJS/mithril.js/pull/2422 -// -// Depending on the complexity, it might become permanent. It really isn't that -// helpful to create a release on Travis vs locally, aside from a couple extra -// potential 2FA prompts by npm during login and publish. - -const path = require("path") -const {promises: fsp} = require("fs") -const readline = require("readline") -const {execFileSync} = require("child_process") -const {promisify} = require("util") -const rimraf = promisify(require("rimraf")) -const semver = require("semver") -const upstream = require("./_upstream") -const updateDocs = require("./update-docs") - -// Fake it until it works with this. -upstream.fetch.remote = "origin" - -function showHelp() { - console.error(` -node scripts/release increment [ --preid id ] [ --publish ] - -Invoke as 'scripts/release.sh' to invoke the release sequence, specifying the -version increment via 'increment' (required). Pass '--publish' to push the -change and publish it, instead of just logging the commands used to push the -release. - -Here's how each increment type works: - -- 'major' increments from 1.0.0 or 2.0.0-beta.0 to 2.0.0 -- 'minor' increments from 1.0.0 to 1.1.0 -- 'patch' increments from 1.0.0 to 1.0.1 -- 'premajor' increments from 1.0.0 to 2.0.0-beta.0 -- 'preminor' increments from 1.0.0 to 1.1.0-beta.0 -- 'prepatch' increments from 1.0.0 to 1.0.1-beta.0 -- 'prerelease' increments from 2.0.0-beta.0 to 2.0.0-beta.1 - -'--preid beta' specifies the 'beta' part above (default). It's required for all -'pre*' increment types except 'prerelease'. - -See the docs for 'npm version' for details -on the 'increment' parameter. -`) -} - -const rootDir = path.dirname(__dirname) -const p = (...args) => path.resolve(rootDir, ...args) - -function fail(...args) { - console.error(...args) - return 1 -} - -function execCommand(cmd, args, opts) { - console.error() - console.error(["executing:", cmd, ...args].join(" ")) - return execFileSync(cmd, args, { - windowsHide: true, - stdio: "inherit", - encoding: "utf-8", - ...opts, - }) -} - -function git(...cmd) { return execCommand("git", cmd) } -function npm(...cmd) { return execCommand("npm", cmd) } -function npmConfig(key) { return npm("config", "get", key).trim() } - -function getChanges() { - return execCommand("git", ["status", "-z"], { - stdio: ["inherit", "pipe", "inherit"], - }) - .split(/\0/g) - .filter((l) => (/\S/).test(l)) -} - -async function release({increment, preid, publish}) { - if (!(/^prerelease$|^(pre)?(major|minor|patch)$/).test(increment)) { - return fail(`Invalid increment: ${increment}`) - } - - if ((/^pre(major|minor|patch)/).test(increment) && preid == null) { - return fail(`'${increment}' must include a '--preid'`) - } - - if (getChanges().length) { - return fail("Tree must be clean to start!") - } - - if (upstream.push == null) { - return fail("You must have an upstream to push to!") - } - - const rl = readline.createInterface({ - input: process.stdin, - output: process.stdout, - }) - - // Update local `master` and `next`. - git("fetch", upstream.fetch.remote, "master", "next") - - // Make sure we're on the current `next` and merge any docs fixes and - // similar that have landed in upstream `master`. - git("checkout", "next") - git("pull", "--rebase", upstream.fetch.remote, "next") - git( - "pull", "--allow-unrelated-histories", - upstream.fetch.remote, "master" - ) - - // Note: we're doing our own semver incrementing. - const packageJson = JSON.parse( - await fsp.readFile(p("package.json"), "utf-8") - ) - const version = semver.inc(packageJson.version, increment, preid) - - console.error(` -Copy the parts listed in "Upcoming" to a new section "### v${version}" in -docs/changelog.md and clear that section out. Also, add today's date under the -new section's heading to match the others and don't forget to update the table -of contents accordingly. -`) - - for (;;) { - await new Promise((resolve) => rl.question( - "Press once ready to continue or Ctrl+C to abort.", - // Ignore any input. - () => resolve(), - )) - - // Verify the changelog was updated, and give a chance to retry if it's - // prematurely continued. - const changes = getChanges() - const isChangelog = /^[ M][ M] docs\/changelog\.md$/ - const errors = [] - - console.log("changes", changes) - - if (!changes.some((l) => isChangelog.test(l))) { - errors.push("Changelog must be updated!") - } - - if (changes.some((l) => !isChangelog.test(l))) { - errors.push("Tree must not be otherwise dirty!") - } - - if (!errors.length) break - console.error(errors.join("\n")) - } - - await rimraf(p("node_modules")) - npm("install-test") - npm("run", "build") - console.log("*** Build done ***") - - // Update the package file. - packageJson.version = version - await fsp.writeFile(p("package.json"), "utf-8", - JSON.stringify(packageJson, null, 2) - ) - // Commit and tag the new release, with the appropriate CLI flag if the - // commit needs signed. - git("add", ".") - git( - "commit", - ...npmConfig("sign-git-tag") === "true" ? ["--gpg-sign"] : [], - "--message", `v${version}`, - ) - git("tag", `v${version}`) - - // Update `master` to reflect the current state of `next`. - git("checkout", "master") - git("reset", "--hard", "next") - git("checkout", "next") - - if (publish) { - // TODO: switch this to just do the push, and use the following Travis - // config. This also conveniently keeps private stuff out of the build - // scripts and just in build config, avoiding the grief that led to this - // file's existence. - // - // ```yml - // # See https://docs.travis-ci.com/user/deployment/npm/ for details on - // # `api_key:` for the npm provider. - // # See https://docs.travis-ci.com/user/deployment/pages/ for details - // # on `github_token:` for the pages provider. - // after_success: > - // [ "$TRAVIS_BRANCH" == "master" ] && node scripts/generate-docs - // - // deploy: - // - provider: npm - // skip_cleanup: true - // email: 'contact@claudiameadows.dev' - // api_key: - // secure: 'output of `travis encrypt NPM_AUTH_TOKEN`' - // on: - // tags: true - // condition: "$TRAVIS_TAG != *-*" - // - provider: npm - // skip_cleanup: true - // tag: next - // email: 'contact@claudiameadows.dev' - // api_key: - // secure: 'output of `travis encrypt NPM_AUTH_TOKEN`' - // on: - // tags: true - // condition: "$TRAVIS_TAG == *-*" - // - provider: pages - // skip_cleanup: true - // github_token: - // secure: 'output of `travis encrypt GITHUB_AUTH_TOKEN`' - // local_dir: dist - // fqdn: mithril.js.org - // committer_from_gh: true - // on: - // tags: false - // branch: master - // ``` - npm("login") - if (increment.startsWith("pre")) { - npm("publish", "--tag", "next") - } else { - npm("publish") - } - npm("logout") - - // Only push after successful publish - git( - "push", "--atomic", "origin", - "+next:master", "next:next", `next:refs/tags/v${version}`, - ) - git( - "push", "--atomic", upstream.push.remote, - "+next:master", "next:next", `next:refs/tags/v${version}`, - ) - await updateDocs() - } else { - const remote = upstream.push.remote - console.error(` -npm login -npm publish${increment.startsWith("pre") ? " --tag next" : ""} -npm logout -git push --atomic origin +next:master next:next next:refs/tags/v${version} -git push --atomic ${remote} +next:master next:next next:refs/tags/v${version} -npm run release:docs -`) - } - - console.error(` -Don't forget to update the latest release! You can find it here: -https://github.com/MithrilJS/mithril.js/releases/tag/v${version} -`) - - return 0 -} - -/* eslint-disable global-require */ -if (require.main === module) { - require("./_command")({async exec() { - const parsed = require("minimist")(process.argv.slice(2), { - boolean: ["help", "publish"], - alias: {help: ["h", "?"]}, - string: ["preid"], - }) - - if (parsed.help || !parsed._.length) showHelp() - else { - await release({ - increment: parsed._[0], - preid: parsed.preid, - publish: parsed.publish, - }) - } - }}) -}