diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 65b4c50..1225b77 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -3,6 +3,14 @@ name: Semantic Release on: push: branches: [main] + # Run manually on an older branch to cut a patch release for that line. + workflow_dispatch: + inputs: + force: + description: 'Force a version bump (auto = derive from commits)' + type: choice + options: [auto, patch, minor, major] + default: auto jobs: # The same aggregate a pull request has to pass, so the release runs exactly @@ -18,7 +26,6 @@ jobs: # release half-finished. needs: [run_ci] runs-on: ubuntu-latest - if: github.ref_name == 'main' concurrency: group: ${{ github.workflow }}-release-${{ github.ref_name }} cancel-in-progress: false @@ -56,6 +63,32 @@ jobs: # We create it ourselves in the next step so that the distributions # are attached before the release is published. See that step for why. vcs_release: "false" + force: ${{ inputs.force != 'auto' && inputs.force || '' }} + + # python-semantic-release exits successfully without releasing when the + # branch is not in a release group or there is nothing to release. A + # manual run is always meant to release, so fail it rather than show it + # as green. + - name: Check | Fail a manual run that released nothing + if: github.event_name == 'workflow_dispatch' && steps.release.outputs.released != 'true' + run: | + echo "::error::No release was made. Run this on main or release/, with releasable commits." + exit 1 + + # python-semantic-release also skips the release, and exits successfully, + # when the next version is already tagged on another branch, eg. main's + # next minor taken by a release branch. Fail so the skip doesn't look green. + - name: Check | Fail when the next version is tagged on another branch + if: steps.release.outputs.released != 'true' + env: + TAG: ${{ steps.release.outputs.tag }} + run: | + git fetch --tags --quiet + if git rev-parse -q --verify "refs/tags/$TAG" >/dev/null \ + && ! git merge-base --is-ancestor "$TAG" HEAD; then + echo "::error::$TAG is already tagged on another branch, so this release was skipped." + exit 1 + fi # Built before the release is created so that its distributions can be # attached as release assets too. See the release step for why that @@ -89,6 +122,7 @@ jobs: # publishes it -- all within this one command. gh release create "$TAG" \ --verify-tag \ + --latest=${{ github.ref_name == 'main' }} \ --title "$TAG" \ --notes-file "$RUNNER_TEMP/release_notes.md" \ backend-plugin-sample/dist/* \ @@ -121,7 +155,7 @@ jobs: # and it won't require reversing the release. runs-on: ubuntu-latest needs: release - if: github.ref_name == 'main' && needs.release.outputs.released == 'true' + if: needs.release.outputs.released == 'true' permissions: contents: read @@ -143,7 +177,7 @@ jobs: publish_tutor_plugin_to_pypi: runs-on: ubuntu-latest needs: release - if: github.ref_name == 'main' && needs.release.outputs.released == 'true' + if: needs.release.outputs.released == 'true' permissions: contents: read @@ -164,7 +198,7 @@ jobs: publish_to_npm: runs-on: ubuntu-latest needs: release - if: github.ref_name == 'main' && needs.release.outputs.released == 'true' + if: needs.release.outputs.released == 'true' permissions: contents: read id-token: write @@ -181,9 +215,13 @@ jobs: node-version-file: './frontend-plugin-sample/.nvmrc' - name: Update the package version and publish + # Only releases from main move the `latest` dist-tag; an older line + # (release/ulmo) is published under its own name (ulmo). + env: + DIST_TAG: ${{ github.ref_name == 'main' && 'latest' || github.ref_name }} run: | npm ci npm version --no-git-tag-version ${{ needs.release.outputs.version }} npm run build - npm publish + npm publish --tag "${DIST_TAG#release/}" working-directory: './frontend-plugin-sample' diff --git a/README.md b/README.md index 36929ec..41c9fd0 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,21 @@ This path assumes you already have edx-platform running locally (bare-metal or d > TODO: a fully local brand-development flow without Tutor (recompile + serve from disk) is not yet documented. +## Releasing from an older release branch + +1. Create a `release/` branch from the release tag, e.g. `git switch -c release/ulmo v3.8.7`, and merge the fix into it as a `fix:` commit. + Only branches matching `release/` can be released. +2. Go to **Actions → Semantic Release → Run workflow** and select that branch. +3. Optionally set `force` to `patch`, `minor` or `major`. The default `auto` derives the bump from the commits. + +The version comes from the tags on that branch only, so a `fix:` on `release/ulmo` releases `3.8.8` even if `main` is on `3.9.x`. +Backports should land as `fix:` commits, so the line gets a patch and not the next minor that `main` will want. For the same reason, make `main`'s next release after cutting the branch at least a minor bump (a `feat:` commit, or `force: minor`). +The GitHub Release is not marked as latest, and the npm package is published under the `` dist-tag instead of `latest`. + +This works only for branches cut from a release that includes this workflow. Older release branches have to be released manually. + +See [Multibranch Releases](https://python-semantic-release.readthedocs.io/en/latest/concepts/multibranch_releases.html) for more details. + ## Getting help - Open edX [community Slack](https://openedx.org/slack) and [discussion forums](https://discuss.openedx.org) diff --git a/backend-plugin-sample/pyproject.toml b/backend-plugin-sample/pyproject.toml index ab3ba3b..9f504e2 100644 --- a/backend-plugin-sample/pyproject.toml +++ b/backend-plugin-sample/pyproject.toml @@ -126,6 +126,14 @@ where = ["src"] # code and files we need in this repo. build_command = "pip install build && SETUPTOOLS_SCM_PRETEND_VERSION=$NEW_VERSION python -m build" +[tool.semantic_release.branches.main] +match = "main" + +# Release branches (e.g. release/ulmo) can be released by running release.yml +# manually to patch an older release line. The version comes from that branch's tags. +[tool.semantic_release.branches.maintenance] +match = "^release/[a-z]+$" + [tool.semantic_release.commit_parser_options] # Because this repo is meant to be an example, docs changes are relevant # feature changes and so should produces new releases.