Skip to content
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
17 changes: 12 additions & 5 deletions .github/actions/release-prep/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,11 @@ any, it writes nothing and accepts any version. The action's check does not depe
GitHub refuses the dispatch itself with HTTP 422 and no run starts. When the workflow declares it
optional, the run starts and the action fails it with "No version was given". Either way the
release needs a version before the step runs again.
- **A version the repository has already tagged is refused.** A release branch cut from `main` right
after a release still carries that version, and preparing it again would put the next changes
under a version that is already tagged. Only the tag is checked, not whether a release was
published for it. It is read through the GitHub API with `token`, so the check works on the
shallow checkout the template makes. Give the release a new version.
- **A version lower than the branch's is refused.** pup reads the version the branch already has
(`pup get-version`, the first file in `paths.versions`), and preparing `4.17.0` on a branch at
`4.18.0` would bump the plugin backwards: either the version or the branch is wrong. The same
Expand Down Expand Up @@ -170,7 +175,7 @@ download.
| `branch-prefixes` | no | `["release/", "hotfix/", "bucket/"]` | The prefixes `branch` has to start with, as a JSON array. Empty keeps the default; `[]` turns the check off. See [Branches it refuses](#branches-it-refuses) |
| `version` | yes | | The version to prepare, e.g. `4.17.0`. See [Versions it refuses](#versions-it-refuses) |
| `date` | no | today, in UTC | The changelog date as `YYYY-MM-DD`. Empty or `today` writes today |
| `token` | no | `github.token` | The token the commit is created with. See [Committing](#committing) |
| `token` | no | `github.token` | The token the tag is read with and the commit is created with. See [Versions it refuses](#versions-it-refuses) and [Committing](#committing) |
| `pup-version` | no | `latest` | The pup release to run: a number such as `2.0.0`, or `latest` for the newest. See [Which pup runs](#which-pup-runs) |

## Outputs
Expand Down Expand Up @@ -210,8 +215,9 @@ branch already has — runs on the phar path only; the TS contract does not defi
## Running it again

Preparing a version the branch already holds changes nothing: the versions match, no TBDs are left
and `changelog/` is empty. The action commits nothing, calls no API, and succeeds, so a retry of a
finished preparation is safe.
and `changelog/` is empty. The action commits nothing and succeeds, so a retry of a finished
preparation is safe, as long as the version has not been tagged in between: once the tag exists,
preparing its version again is refused.

## The date

Expand Down Expand Up @@ -260,8 +266,9 @@ a commit made with `GITHUB_TOKEN`.
## How it runs

Two [actions/github-script](https://github.com/actions/github-script) steps run `release-prep.js`,
with the changelogger action between them. The first validates the inputs, the `.puprc` and the
changelogger strategy, then runs pup. The second commits through the API. github-script supplies
with the changelogger action between them. The first validates the inputs and the changelogger
strategy, reads the version's tag through the API to refuse one that exists, checks the `.puprc`,
then runs pup. The second commits through the API. github-script supplies
the Node runtime, `exec` and the `github` client, built from `token`; there is no dependency
to install and no bundle to build.

Expand Down
16 changes: 10 additions & 6 deletions .github/actions/release-prep/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ inputs:
The version to prepare. It is validated against the versioning strategy the repository's
changelogger configures: three or four numeric parts for `stellarwp`, a semver version for
`semver` or no configuration, no shape check for a custom strategy. An empty value and a
version lower than the one the branch already has are refused before anything is changed.
version lower than the one the branch already has are refused before anything is changed, and
so is a version the repository has already tagged.
required: true
date:
description: >-
Expand All @@ -37,9 +38,10 @@ inputs:
default: ''
token:
description: >-
Token the commit is created with, through the GitHub API. It needs `contents: write` on the
repository, so the job needs that permission too. Nothing is pushed with git, so the checkout
can use `persist-credentials: false`.
Token the tag is read with before pup runs, and the commit is created with afterwards, both
through the GitHub API. It needs `contents: write` on the repository, so the job needs that
permission too. Nothing is pushed with git, so the checkout can use
`persist-credentials: false`.
required: false
default: ${{ github.token }}
pup-version:
Expand Down Expand Up @@ -89,12 +91,14 @@ runs:
INPUT_DATE: ${{ inputs.date }}
INPUT_PUP_VERSION: ${{ inputs.pup-version }}
# Authenticates the 'latest' lookup against the GitHub API; a pinned pup-version
# never calls the API.
# never calls it for pup.
INPUT_TOKEN: ${{ inputs.token }}
with:
# The tag check reads the repository through github-script's own client, built from this token.
github-token: ${{ inputs.token }}
Comment thread
d4mation marked this conversation as resolved.
Resolved
script: |
const { runPrepare } = require(`${process.env.ACTION_PATH}/release-prep.js`)
await runPrepare({ core, exec, env: process.env })
await runPrepare({ github, core, exec, env: process.env })

# The resolved date, never inputs.date: this action writes the date it is given as it is, so
# `today` would land in the changelog as the word and an empty value as an empty date.
Expand Down
45 changes: 39 additions & 6 deletions .github/actions/release-prep/release-prep.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const { execFileSync } = require('node:child_process')

// The three-or-four-parts pattern lives with the other version rules, shared with release-merge.
const { compareVersions, STELLARWP_VERSION_PATTERN } = require('../../lib/versions.js')
const { readRepository } = require('../../lib/github.js')
const { readRepository, tagExists } = require('../../lib/github.js')
const { runOrFail } = require('../../lib/exec.js')

/**
Expand Down Expand Up @@ -619,15 +619,44 @@ function readCheckoutFile(cwd, name) {
}
}

/**
* Refuses a version the repository has already tagged.
*
* A release branch cut from `main` right after a release still carries that release's version, and
* preparing it again would put the next changes under a version that is already tagged. The tag is
* read through the API rather than from the checkout, which is shallow and fetches no tags.
*
* @param {object} options
* @param {object} options.github The Octokit github-script supplies.
* @param {string} options.owner The repository owner.
* @param {string} options.repo The repository name.
* @param {string} options.version The version to prepare, validated.
*
* @throws {Error} When the tag exists, or reading it fails for any reason but 404.
*
* @returns {Promise<void>} Resolves when the tag does not exist.
*/
async function refuseTaggedVersion({ github, owner, repo, version }) {
if (!(await tagExists({ github, owner, repo, version, what: 'Nothing was changed.' }))) {
return
}

throw new Error(
`The tag ${version} already exists in ${owner}/${repo}. Preparing the branch again would put new ` +
'changes under a version that is already tagged. Give the release a new version. Nothing was changed.'
)
}

/**
* Validates the inputs, then bumps the version and replaces the TBDs with pup.
*
* Every check runs before anything is written, so a wrong version, date or ref, a checkout that is
* not on the release branch, a repository without a usable .puprc or with a changelogger versioning
* the changelogger does not know, or a version lower than the one the branch already has, fails
* with the branch untouched. The version is checked the way the changelog step will check it: by
* the versioning strategy package.json configures.
* not on the release branch, a version the repository has already tagged, a repository without a
* usable .puprc or with a changelogger versioning the changelogger does not know, or a version lower
* than the one the branch already has, fails with the branch untouched. The version is checked the
* way the changelog step will check it: by the versioning strategy package.json configures.
*
* @param {object} github An Octokit, as actions/github-script supplies it, for the tag read.
* @param {object} core The @actions/core toolkit, for the log and the outputs.
* @param {object} exec The @actions/exec toolkit.
* @param {object} env The environment the action manifest put its inputs in.
Expand All @@ -639,7 +668,7 @@ function readCheckoutFile(cwd, name) {
*
* @returns {Promise<void>} Resolves once pup has run and the outputs are set.
*/
async function runPrepare({ core, exec, env, cwd = process.cwd(), fetchImpl = globalThis.fetch }) {
async function runPrepare({ github, core, exec, env, cwd = process.cwd(), fetchImpl = globalThis.fetch }) {
const changelogger = readChangeloggerStrategy(readCheckoutFile(cwd, 'package.json'))
const version = validateVersion(env.INPUT_VERSION, changelogger.strategy)
const date = resolveDate(env.INPUT_DATE)
Expand Down Expand Up @@ -672,6 +701,9 @@ async function runPrepare({ core, exec, env, cwd = process.cwd(), fetchImpl = gl
)
}

const { owner, repo } = readRepository(env, 'Nothing was changed.')
await refuseTaggedVersion({ github, owner, repo, version })

const { tbdDirs } = readPuprc(readCheckoutFile(cwd, '.puprc'))

if (!tbdDirs) {
Expand Down Expand Up @@ -917,6 +949,7 @@ async function runCommit({ github, core, exec, env, cwd = process.cwd() }) {

module.exports = {
runPrepare,
refuseTaggedVersion,
runCommit,
readVersion,
validateVersion,
Expand Down
Loading
Loading