Skip to content

fix: require the redirect that makes a landing reachable - #25

Merged
toastygm merged 1 commit into
mainfrom
bug/24_require-package-root-redirect
Sep 4, 2026
Merged

fix: require the redirect that makes a landing reachable#25
toastygm merged 1 commit into
mainfrom
bug/24_require-package-root-redirect

Conversation

@toastygm

@toastygm toastygm commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Since package-build 15 (package-build#182) a package's landing is published at /<package>/homepage-<shortcode>/, and /<package>/ — the canonical address, the one every link points at and the one the site router proxies — is Hugo's site root: chrome around an empty <main>. What connects them is a _redirects entry the package authors, specified in MIGRATING.md §3 of that release.

Nothing checked for it. The guard requires _headers at the deployment root and never _redirects, so a build that emits a perfectly good landing and no redirect passes every check in this file, deploys, and serves a blank page at the package's canonical URL. sohl-kethira-basic shipped exactly that; it was noticed by eye and fixed by hand (sohl-kethira-basic#83), not caught by anything here. Same visitor-facing outcome as #21, down a different path: #21 was a build that produced no landing, this is a build that produced one nothing routes to.

Require it.

Keyed on the landing, which by that point in the step is the new-shape signal. A post-#182 tree carrying no landing has already been refused by #23, so an empty landing at this line is a pre-#182 caller — and harn-adventures, harn-ensemble and HarnMaster-3-FoundryVTT serve their landing at /<package>/ itself and correctly author no _redirects. So no second version read is needed: the one #23 added already stands between the two shapes.

Both path forms. Cloudflare Pages matches a rule against the raw url.pathname, before any trailing-slash handling, so /<package> and /<package>/ are distinct keys and a rule for only one of them leaves the other on the blank root. Each is required separately, with its own message.

The destination is derived, never assumed. It comes from the landing that was actually found, so a package whose homepage note carries a different shortcode is measured against its own address. The first matching rule is the one read, as Pages reads it, so a wrong rule cannot be rescued by a correct one placed after it.

The status column is not read. Every value a package would plausibly write there arrives at the landing — 301, 302, or a 200 rewrite — and what this guard exists to catch is a rule that is absent, not one that is misnumbered.

A guard, not a fallback, unlike _headers above. The destination is the landing's own address, which is package-specific, so there is no generic file this workflow could write instead.

Verification

The guard step's run: body and the default-_headers step's were extracted verbatim with yq and driven against the real built trees of all four callers — kethira at sohl-kethira-basic#84, and harnadventures, hm3 and harnensemble as their repositories build today — each with the @heroiclands/package-build its checkout actually installs (15.0.0 / 9.0.0 / 9.0.0 / 10.0.0). The three pre-#182 trees emit no _headers, so the workflow's own default step was run against them first, as it is in the job.

tree installed case expected result
kethira 15.0.0 as built, real _redirects pass pass
kethira 15.0.0 _redirects deleted reject rejected — missing or empty
kethira 15.0.0 _redirects empty reject rejected — missing or empty
kethira 15.0.0 only the /kethira/ rule reject rejected — no rule for /kethira
kethira 15.0.0 only the /kethira rule reject rejected — no rule for /kethira/
kethira 15.0.0 both rules commented out reject rejected — no rule for /kethira/
kethira 15.0.0 destination is not the landing reject rejected — names both addresses
kethira 15.0.0 a wrong rule placed before the right one reject rejected — first match wins
kethira 15.0.0 trailing # comment on a rule pass pass
kethira 15.0.0 tab-separated columns pass pass
kethira 15.0.0 destination without trailing slash pass pass
kethira 15.0.0 302 instead of 301 pass pass
kethira 15.0.0 landing deleted (#21 regression) reject rejected — emitted no landing
kethira 15.0.0 a genuine second content page reject rejected — licensing bound, 2 page(s)
harnadventures 9.0.0 as built, no _redirects pass pass — 1 page(s)
hm3 9.0.0 as built, no _redirects pass pass — 1 page(s)
harnensemble 10.0.0 as built, no _redirects pass pass — 1 page(s)
each of the three 9/9/10 root index.html removed reject rejected by the root check

shellcheck -s bash is clean on the extracted body, actionlint is clean on the file, and it parses as YAML.

What this does not do

It does not check the pinned Cache-Control that MIGRATING.md §3 asks for alongside the redirect. That is a caching decision, not a blank page, and it is the package's _headers to write.

#21 is untouched and stays open: its remaining criteria are that every caller reaches package-build >= 15 and that #20's transitional branch then goes, which is three content migrations in three other repositories.

Closes #24

Since package-build 15 (HeroicLands/package-build#182) a package's landing is
published at `/<package>/homepage-<shortcode>/`, and `/<package>/` — the
canonical address, the one every link points at and the one the site router
proxies — is Hugo's site root: chrome around an empty `<main>`. What connects
them is a `_redirects` entry the package authors, and nothing checked for it.

The guard requires `_headers` at the deployment root and never `_redirects`, so
a build that emits a perfect landing and no redirect passes every check,
deploys, and serves a blank page at the package's canonical URL.
`sohl-kethira-basic` shipped exactly that; it was noticed by eye and fixed by
hand (HeroicLands/sohl-kethira-basic#83) with every check green.

Require it. Keyed on the landing, which by that point in the step IS the
new-shape signal: a post-#182 tree with no landing has already been refused
(#23), so an empty `landing` is a pre-#182 caller — and those three serve their
landing at `/<package>/` itself and correctly author no `_redirects`.

Both path forms are required, because Pages matches a rule against the raw
`url.pathname` before any trailing-slash handling, so `/<package>` and
`/<package>/` are distinct keys and a rule for one leaves the other blank. The
destination is derived from the landing that was actually found, never assumed:
the shortcode is the author's. The first matching rule is the one read, as
Pages reads it, so a wrong rule cannot be rescued by a right one after it. The
status column is not read — 301, 302 and a 200 rewrite all arrive at the
landing, and what this catches is a rule that is absent, not one misnumbered.

A guard, not a fallback, unlike `_headers`: the destination is the landing's own
address, which is the package's to state and not this workflow's to guess.

Closes #24
@toastygm
toastygm merged commit cb99877 into main Sep 4, 2026
1 check passed
@toastygm
toastygm deleted the bug/24_require-package-root-redirect branch September 4, 2026 18:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

A build with a landing but no redirect from the package root passes the guard and serves a blank page

1 participant