feat: mark patch releases as prereleases, org-wide - #27
Merged
Conversation
The release workflow invoked `softprops/action-gh-release@v3` with no `prerelease:` input, so every release every Foundry package cut published as an ordinary release and was promoted to "Latest". The semver digits in this family carry a release CHANNEL, not a severity: `minor`/`major` goes to users, `patch` is beta and is never meant to be consumed — 0.9.1, 0.9.2 … accumulate toward the next user release, which one `minor` then cuts as 0.10.0. The channel lives in the digits because Foundry has to sort these and `isNewerVersion` is a naive dot-split with a string fallback, with no notion of `-beta.0`. Numeric-only sorts correctly; the GitHub label is what tells a human which channel a version is on. `X.Y.Z` with `Z` non-zero now publishes as a prerelease and does not take "Latest"; `X.Y.0` publishes as a release and does. `prerelease` and `make_latest` are set together and always explicitly — both defaults are wrong here, and labelling the prerelease while leaving Latest to the default would fix only half of it. Keyed on the `version` the decide step already reads from package.json, not on the tag string. The parse is strict (three numeric components, no leading zeros) and fails closed like every other guard in the file: an unclassifiable version stops the run rather than falling through to "release", which is the outcome with consequences.
7 tasks
toastygm
added a commit
to HeroicLands/Song-of-Heroic-Lands-FoundryVTT
that referenced
this pull request
Sep 4, 2026
…patches (#1823) Retypes **61** pending changesets from `minor` to `patch`. No prose is touched; only the bump line. **The bump type is now the release channel, not the severity** - `minor` / `major` — **ships to users.** A real release, marked as a release on GitHub. - `patch` — **beta only, never meant to be consumed.** `0.9.1`, `0.9.2` … accumulate toward the next user release, which one `minor` then cuts as `0.10.0`. Those 61 were written under the older reading, where a breaking change on 0.x was a `minor`. **None has reached a user** — they describe work sitting unreleased on `main`. Under the meaning above they are patches, so this corrects them rather than downgrading them. Severity still reads in each entry's own `**Bold label**`, which is where this repository has always stated it. **What it produces** The next release computes as **`0.8.3`** (verified locally with `changeset version`), a beta carrying everything currently on `main`. `package.json` is untouched here — the version only moves when a release actually runs. **Why it matters now.** Two satellites are blocked on content that exists on `main` and in no released version: - **`sohl-thalorna` cannot build its Foundry packs at all.** Its beings cite eight combat techniques — `bflkbite`, `bflkgrab`, `bflkheadbutt`, `bflkkick`, `bflkpunch`, `limbblock`, `press`, `trip` — present here under `assets/content/Skills/Combat_Techniques/` and absent from `v0.8.2`. The actors pass reports 620 errors and stops: _"refusing to compile packs from incomplete output."_ - **`sohl-kethira-basic` ships a field Foundry discards.** All 28 affiliation notes author `subType`; `v0.8.2` does not define `affiliation.subType` (HeroicLands/sohl-kethira-basic#30). A satellite resolves those items against the **released** pack, so neither can fix it locally. `0.8.3` unblocks both without declaring a user release. **Why the channel is in the digits and not a `-beta.0` tag** Foundry's `isNewerVersion` is a dot-split with a string fallback and no notion of a prerelease component: `"0-beta" > "0"` makes it read `0.9.0-beta.0` as **newer** than `0.9.0`, so a final release would never register as an upgrade over its own beta. A numeric-only scheme always sorts correctly. **Merge order.** `HeroicLands/.github#27` must land first — it marks patch releases as GitHub prereleases and keeps the Latest badge on the last real release. Without it, `0.8.3` publishes as an ordinary release, takes Latest, and every installed copy is offered it as an update, since `releases/latest/download/system.json` is what the manifest points at. Co-authored-by: Tom Rodriguez <tmrodrig@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The release workflow invoked
softprops/action-gh-release@v3withtag_name,name,body_pathandfilesand noprerelease:input, so every releaseevery Foundry package cut published as an ordinary release and GitHub promoted
it to "Latest".
The rule
Patch releases are all marked prerelease, period. No exceptions. Only major
and minor releases are marked release.
X.Y.ZwithZnon-zero publishes asa GitHub prerelease;
X.Y.0publishes as a normal release.Org-wide, not a per-package opt-in, and it applies to future releases only —
nothing already published is relabelled.
Why
The semver digits carry a release channel, not a severity:
minor/majorgoes to users,
patchis beta and is never meant to be consumed.0.9.1,0.9.2, … accumulate toward the next user release, which oneminorthen cutsas
0.10.0.The channel lives in the digits rather than in a semver prerelease component
because Foundry has to sort these: its versions are
<major>.<build>andisNewerVersionis a naive dot-split with a string fallback, so0.9.0-beta.0would sort wrongly against
0.9.0. Numeric-only sorts correctly; what it cannotdo on its own is say which channel a version is on. The GitHub label is what
says that to a human — and today it says the opposite.
What the decision is keyed on
steps.decide.outputs.version— the value that step already reads frompackage.json— not the tag string. The tag is that same value with avgluedon; re-parsing it would mean stripping a prefix the step added itself, and would
make the rule depend on the tag spelling rather than on the version.
Inputs, verified
Both names checked against
softprops/action-gh-release's ownaction.ymlatthe
v3tag (5113cdc), per the standing rule this workflow already carries:prerelease— "Identify the release as a prerelease. Defaults to false".Parsed as
INPUT_PRERELEASE == 'true'; anything else is false.make_latest— "Can betrue,false, orlegacy… Drafts and prereleasescannot be set as latest. Uses GitHub api default if not provided." Parsed by a
literal match on those three strings and ignored otherwise.
Both are set together and always explicitly, because both defaults are wrong
here: the action defaults
prereleaseto false and GitHub defaultsmake_latestto true, so an unlabelled patch would be published as the current release and
would take the Latest badge off the last real one. Labelling the prerelease while
leaving Latest to the default fixes only half of it. The decide step emits the
literals rather than a
${{ … && … || … }}ternary overprerelease.The decision table
Driven over the logic extracted verbatim from the committed workflow:
prereleasemake_latest0.9.0falsetrue0.9.1truefalse0.10.0falsetrue1.0.0falsetrue0.0.1truefalse0.5.3truefalse0.9.10truefalse10.0.0falsetrue1.2.3.41.0.0-beta.10.901.2.3/1.02.0v0.9.10.9.0+build.51.2.x, emptyA malformed version fails the run
The parse is strict — exactly three dot-separated numeric components, no leading
zeros — and it fails closed, like every other guard in this file. A version
it cannot classify stops the run rather than falling through to "release",
because "release" is the outcome with consequences: it is published the instant
it exists, it takes the Latest badge, and every installed copy updates from it.
An unclassifiable shape must not be resolved by guessing the irreversible answer.
It also catches the specific mistake this scheme invites — a
1.0.0-beta.1written by someone reaching for changesets' prerelease mode, which is exactly
the spelling Foundry mis-sorts.
Blast radius — stated plainly
Other packages have already shipped patch releases as ordinary releases:
sohl-kethira-basic— v0.5.1, v0.5.2, v0.5.3harn-adventures— v0.0.1After this change, their next patch release publishes as a prerelease. That
is the intended consequence of an org-wide rule, not a regression. No existing
release is relabelled — published releases are history.
Verification
the table above.
prereleaseandmake_latestconfirmed from the action's ownaction.ymlatthe
v3tag, not from memory.actionlint(1.7.12, with shellcheck integration) clean on this file and onevery workflow in the repository.
Closes #26