Skip to content
Open
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
10 changes: 8 additions & 2 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 @@ -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
9 changes: 6 additions & 3 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 Down Expand Up @@ -89,12 +90,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 }}
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