diff --git a/.github/workflows/auto-merge.yml b/.github/workflows/auto-merge.yml index 4eeaab7f4..900a5e58e 100644 --- a/.github/workflows/auto-merge.yml +++ b/.github/workflows/auto-merge.yml @@ -8,7 +8,7 @@ jobs: auto-merge: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: ahmadnassri/action-dependabot-auto-merge@v2 with: target: patch diff --git a/.github/workflows/merge.yml b/.github/workflows/merge.yml new file mode 100644 index 000000000..fcf3988c1 --- /dev/null +++ b/.github/workflows/merge.yml @@ -0,0 +1,43 @@ +name: merge + +on: + push: + branches: [ main ] + + workflow_dispatch: + +concurrency: prr:deploy + +jobs: + merge: + + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [16.x] + + steps: + - uses: actions/checkout@v3 + with: + ref: 'next' + fetch-depth: 0 + + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node-version }} + cache: 'npm' + - run: npm ci + - run: npm run build + - run: npx pr-release merge --target main --source next --commit --force --clean --changelog ./docs/recent-changes.md --compact + env: + GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} + + # The following will publish the release to npm + - run: echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > ~/.npmrc + name: Setup NPM Auth + env: + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + - run: npm publish + name: Publish diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml new file mode 100644 index 000000000..3d9d88c4b --- /dev/null +++ b/.github/workflows/pr.yml @@ -0,0 +1,41 @@ +name: pr + +on: + push: + branches: [ next ] + + workflow_dispatch: + + pull_request: + types: [labeled, unlabeled, edited] + +concurrency: prr:pre-release + +jobs: + pr: + + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [16.x] + + steps: + - uses: actions/checkout@v3 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node-version }} + cache: 'npm' + - run: npm ci + - run: npm run build + - run: npx pr-release pr --verbose --target main --source next --compact --verbose + env: + GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} + # The following will publish a prerelease to npm + - run: echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > ~/.npmrc + name: Setup NPM Auth + env: + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + - run: npx pr-release infer-prerelease --preid=next --verbose --publish + name: Publish diff --git a/.github/workflows/rollback.yml b/.github/workflows/rollback.yml new file mode 100644 index 000000000..953d97fa3 --- /dev/null +++ b/.github/workflows/rollback.yml @@ -0,0 +1,31 @@ +name: rollback + +on: + workflow_dispatch: + +concurrency: prr:deploy + +jobs: + pr: + + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [16.x] + + steps: + - uses: actions/checkout@v3 + with: + ref: 'next' + fetch-depth: 0 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node-version }} + cache: 'npm' + - run: npm ci + - run: npm run build + - run: npx pr-release rollback --verbose --target main --source next --verbose --ignore 'package*' --ignore docs/changelog.md --ignore docs/recent-changes.md + env: + GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index e0d498c7d..000000000 --- a/.travis.yml +++ /dev/null @@ -1,101 +0,0 @@ -sudo: false - -# Only care about running tests against latest node -language: node_js -arch: - - amd64 - - ppc64le -node_js: -- node - -# Keep node_modules around, it speeds up builds & they don't change often -cache: - directories: - - node_modules - -# Custom install step so the travis-only stuff doesn't need to be in package.json -install: -- npm install -# This is to prevent lint-staged/prettier from running on the bundles -- npm rm husky - -# Run tests, lint, and then check for perf regressions -script: -- npm test -- npm run perf - -# After a successful build commit changes back to repo -# Disable per https://github.com/MithrilJS/mithril.js/issues/2417 -# after_success: -# - | -# # Set up SSH environment -# $(npm bin)/set-up-ssh \ -# --key "$encrypted_016049456622_key" \ -# --iv "$encrypted_016049456622_iv" \ -# --path-encrypted-key "./.deploy.enc" -# -# # Commit bundle changes generated in before_script step -# # --commands is a weird no-op but required for commit-changes to run -# # --branch arg is to ensure this only runs against the `next` branch -# $(npm bin)/commit-changes \ -# --commands "echo committing" \ -# --commit-message "Bundled output for commit $TRAVIS_COMMIT [skip ci]" \ -# --branch "next" -# -# # Only want to commit docs when building pushes on master & -# # this doesn't have the built-in branch protection like commit-changes -# if [ "$TRAVIS_EVENT_TYPE" == "push" ] && \ -# [ "$TRAVIS_BRANCH" == "master" ] && \ -# [ "$TRAVIS_REPO_SLUG" == "MithrilJS/mithril.js" ] -# then -# # Generate docs -# npm run gendocs -# -# # Set up git env -# git config --global user.email "$GH_USER_EMAIL" -# git config --global user.name "$GH_USER_NAME" -# -# # Commit docs to gh-pages branch -# # Using --add to ensure that archived versions aren't lost -# # Using --repo to force it to go over SSH so the saved keys are used (tschaub/gh-pages#160) -# $(npm bin)/gh-pages \ -# --dist ./dist \ -# --add \ -# --repo "git@github.com:MithrilJS/mithril.js.git" \ -# --message "Generated docs for commit $TRAVIS_COMMIT [skip ci]" -# else -# echo "Not submitting documentation updates" -# fi - -# Environment configuration -env: - global: - # Set up GH_USER_EMAIL & GH_USER_NAME env variables used by travis-scripts package - - secure: UdSk2uKTL56iOHkIZP9Tpj/OI8w26DRTs6INRq+peZKkHq8NVC0TmbtbpRoc5kxErovN2hyqsAnoMtXSQHKK+H9mpMx0v4ck/I+o2oEny/1hwi5YQ/Q0nAebVhZkgA3eVhJY1brK0bOlr8uI07m9mcPs3Qz0ramZutJQG7FdnZs= - -# Deploy to npm and github pages on tagged commits that successfully build -deploy: - - provider: releases - api_key: - secure: BBlVwr3CRWS6Zmc8nsYMMN59P95g/lg9OzNCsP7uiFyu7mnC6VdOIgwmmJXBviUiEymbcdepgRqEdjHIoemg2YhM5IOnhMoYFrfMBoWUpMihpejFY8EVm3NrTh0prXgCHbp/3OktoKdOn/Zhc2z7cKDMeToHzaDStLtQPR8u9jU= - file: - - "mithril.js" - - "mithril.min.js" - - "stream/stream.js" - - "stream/stream.min.js" - skip_cleanup: true - draft: true - on: - tags: true - repo: MithrilJS/mithril.js - - # Skip until I can figure out what's going on with docs + version deployment - # - provider: npm - # skip_cleanup: true - # email: contact@claudiameadows.dev - # api_key: - # secure: "uPLbeJTalA/b38srb1VuWnD3eOgeKTkXf8VVUasUXIqc2xub4DSkFm1IVKSVd/rzP7EeO7+gRUs2UteNKlpZJl226IS5mFPSVtC7ViW46WSpYT0wlMsc7hrubMBGTx3/XYpPwtmMlTIGs5ICT7YkGAuju/6i79LDAB+gbnEY8Bc=" - # on: - # tags: true - # repo: MithrilJS/mithril.js - # condition: "$TRAVIS_TAG != *-*" diff --git a/docs/releasing.md b/docs/releasing.md index d747eab82..28cd52f4c 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.js' 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 -``` - -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` - -``` -$ git add . -$ git commit -m "Preparing for release" - -# Push to your branch -$ git push - -# Push to MithrilJS/mithril.js -$ git push mithriljs next -``` - -### Merge from `next` to `master` - -6. Switch to `master` and make sure it's up to date - -```bash -$ git checkout master -$ git pull --rebase mithriljs master -``` - -7. merge `next` on top of it - -```bash -$ git merge next -``` +The description and title will be managed by [pr-release], including the semver version. -8. Clean & update npm dependencies and ensure the tests are passing. +Contributors who have permissions should add the correct semver label to their PR (`major` | `minor` | `patch`). If no label is set, `patch` is assumed. -```bash -$ npm prune -$ npm i -$ npm test -``` - -### Publish the release +If you do not have permissions, the maintainer will set the label on your behalf. -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 -``` +## Changelog -12. Travis will push the new release to npm & create a GitHub release +There are two changelogs in the Mithril.js project -### Merge `master` back into `next` +- `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 -This helps to ensure that the `version` field of `package.json` doesn't get out of 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. -13. Switch to `next` and make sure it's up to date +## For maintainers -```bash -$ git checkout next -$ git pull --rebase mithriljs next -``` +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. -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 merged but no labels were set, you can still go back and edit the semver labels. On label change the release pr will automatically be regenerated and 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/ diff --git a/package.json b/package.json index 6be3cc219..54a401040 100644 --- a/package.json +++ b/package.json @@ -25,9 +25,7 @@ "pretest": "npm run lint", "test": "run-s test:js", "test:js": "ospec", - "cover": "istanbul cover --print both ospec/bin/ospec", - "release": "npm version -m 'v%s'", - "version": "npm run build && git add mithril.js mithril.min.js stream.js stream.min.js README.md" + "cover": "istanbul cover --print both ospec/bin/ospec" }, "devDependencies": { "@alrra/travis-scripts": "^3.0.1", diff --git a/scripts/release.js b/scripts/release.js deleted file mode 100644 index 5f8ae15d9..000000000 --- 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, - }) - } - }}) -}