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
24 changes: 18 additions & 6 deletions .github/workflows/watch.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,18 @@
# parity script normalizes sitemap entries: URL pathname, trailing slash
# stripped, "" → "/").
#
# On any failure the run files an issue on this repo (labeled
# production-watch) with the captured output, and the run itself fails.
# On any failure the run reports on this repo and the run itself fails. If an
# issue labeled production-watch is already open, the run comments on it
# instead of filing a duplicate (#118-#179 were one stale fixture entry
# re-filed every 6 hours); otherwise it opens a new one.
#
# NOTE: downgrade the cron to weekly after 2026-08-29 (14 days
# post-cutover) — every 6 hours is only warranted while the fiestaboard.app
# cutover settles.
# Runs weekly: the 6-hourly cadence was only for the fiestaboard.app cutover,
# which settled after 2026-08-29. Use workflow_dispatch for an ad-hoc check.
name: Production watch

on:
schedule:
- cron: "41 */6 * * *" # every 6 hours, off the top of the hour
- cron: "41 14 * * 1" # weekly, Monday 14:41 UTC, off the top of the hour
workflow_dispatch: {}

permissions:
Expand Down Expand Up @@ -131,6 +132,17 @@ jobs:
--description "Filed automatically by the production watch workflow" \
--color D93F0B 2>/dev/null || true

# De-duplicate: report on the oldest open production-watch issue
# if there is one, rather than filing a new issue every run.
EXISTING="$(gh issue list --repo "$GITHUB_REPOSITORY" \
--label production-watch --state open --limit 100 \
--json number --jq 'map(.number) | min // empty' 2>/dev/null || true)"
if [ -n "$EXISTING" ]; then
echo "Open production-watch issue #$EXISTING exists; commenting instead of filing a new one."
gh issue comment "$EXISTING" --repo "$GITHUB_REPOSITORY" --body-file "$BODY"
exit 0
fi

if ! gh issue create --repo "$GITHUB_REPOSITORY" \
--title "$TITLE" --label production-watch --body-file "$BODY"; then
echo "::warning::issue creation with label failed; retrying without label"
Expand Down
48 changes: 48 additions & 0 deletions tests/routes-fixture-versions.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Keeps tests/routes.fixture.json consistent with which docs versions a
// deploy actually builds. Deploys only build versions.slice(0,
// DEPLOY_VERSION_CAP) (docusaurus.config.ts); every older snapshot is served
// as a redirect stub to the current docs. Each release snapshot pushes one
// version off the cap, so a fixture entry that pins an old version as a
// "page" silently goes stale — that is what filed #118–#179 (8.28 fell off
// the cap with the 9.10 snapshot on 2026-10-03). This test fails the
// snapshot PR instead of the production watch.

import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { test } from "node:test";

const read = (p) => readFileSync(new URL(`../${p}`, import.meta.url), "utf8");
const versions = JSON.parse(read("versions.json"));
const fixture = JSON.parse(read("tests/routes.fixture.json"));
const capMatch = read("docusaurus.config.ts").match(/const DEPLOY_VERSION_CAP = (\d+);/);

test("DEPLOY_VERSION_CAP is readable from docusaurus.config.ts", () => {
assert.ok(capMatch, "could not find `const DEPLOY_VERSION_CAP = <n>;`");
});

const built = new Set(versions.slice(0, Number(capMatch?.[1] ?? 0)));
const known = new Set(versions);
const versioned = fixture
.map((e) => ({ e, v: e.path.match(/^\/docs\/(\d+\.\d+)(?:\/|$)/)?.[1] }))
.filter(({ v }) => v && known.has(v));

for (const { e, v } of versioned) {
test(`fixture ${e.path} (${e.expect}) agrees with the version cap`, () => {
if (built.has(v)) {
// Built version: either a real page or the version root → its intro.
if (e.expect === "redirect")
assert.ok(
e.target.startsWith(`/docs/${v}/`),
`${v} is built, so ${e.path} should not redirect out of /docs/${v}/ (target ${e.target})`,
);
} else {
assert.equal(
e.expect,
"redirect",
`${v} is past DEPLOY_VERSION_CAP and is no longer built — ${e.path} is a redirect stub, not a page`,
);
const rest = e.path.slice(`/docs/${v}`.length) || "/intro";
assert.equal(e.target, `/docs${rest}`, `${e.path} should redirect to the current docs at the same path`);
}
});
}
2 changes: 1 addition & 1 deletion tests/routes.fixture.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
{ "path": "/docs/troubleshooting", "expect": "page" },

{ "path": "/docs/8.30/intro", "expect": "page", "fullBuildOnly": true },
{ "path": "/docs/8.28/setup/quick-start", "expect": "page", "fullBuildOnly": true },
{ "path": "/docs/8.28/setup/quick-start", "expect": "redirect", "target": "/docs/setup/quick-start" },
{ "path": "/docs/8.15/intro", "expect": "redirect", "target": "/docs/intro", "fullBuildOnly": true },
{ "path": "/docs/8.30", "expect": "redirect", "target": "/docs/8.30/intro", "fullBuildOnly": true },

Expand Down
Loading