Count both paths of a rename in the docs-only CI skip; pause auto-release #55
@@ -168,6 +168,23 @@ them. Revisit only if the stated reason stops holding.
|
|||||||
workspace dir, so ci.sh's web-image build races another run's container creation on the
|
workspace dir, so ci.sh's web-image build races another run's container creation on the
|
||||||
`<project>-web` tag. Accepted for a single-maintainer cadence; serialize with a workflow
|
`<project>-web` tag. Accepted for a single-maintainer cadence; serialize with a workflow
|
||||||
`concurrency` group if it ever bites.
|
`concurrency` group if it ever bites.
|
||||||
|
- **The docs-only CI skip is `*.md` anywhere in the tree, not just the root.** No test, build step or
|
||||||
|
workflow reads a markdown file (`README-dockerhub.md` is pasted into Docker Hub by hand), so a
|
||||||
|
nested `examples/plugins/admin/README.md` edit is as safe to skip as `README.md`, and narrowing it
|
||||||
|
would spend the full gate on one. Both git channels in `ci.sh`'s `docs_only()` pass `--no-renames`:
|
||||||
|
rename detection names only the destination, so `git mv src/app.ts notes.md` otherwise read as docs
|
||||||
|
and skipped the gate over a source file that was gone. `src/ci-gate.test.ts` locks the flags as a
|
||||||
|
*text* guard — the test image (`node:24.19.0-alpine3.24`) ships neither `git` nor `bash`, so it
|
||||||
|
cannot exercise the function; behaviour was verified against a scratch repo across ten scenarios.
|
||||||
|
Revisit if a `.md` ever becomes load-bearing. Decided 2026-08-05.
|
||||||
|
- **Plainpages is pre-announcement: no tags, no releases.** The repo carried tags up to `v0.2.2` from
|
||||||
|
the `auto-release` job; all of them — and the semver container tags — were deleted 2026-08-05, and
|
||||||
|
the job is gated behind the `AUTO_RELEASE` Actions variable (unset ⇒ skipped, the fail-safe
|
||||||
|
direction on every unknown-`vars` path). A version only communicates to consumers, and there are
|
||||||
|
none; same reasoning that freezes `HOST_API_VERSION` at 1.0.0. Note the coupling:
|
||||||
|
`registry-cleanup` keeps a hash image only while its commit is a branch head *or* release-tagged,
|
||||||
|
so with zero tags only branch heads survive the nightly prune — a hand-cut tag must sit on `main`'s
|
||||||
|
tip. Valid until the maintainer says Plainpages is ready to show people.
|
||||||
- **A dropdown is a `<button popovertarget>` + `[popover]`, never a `<details>`.** The browser then
|
- **A dropdown is a `<button popovertarget>` + `[popover]`, never a `<details>`.** The browser then
|
||||||
owns open/close, which is the only zero-JS way to dismiss a menu by clicking outside it (the whole
|
owns open/close, which is the only zero-JS way to dismiss a menu by clicking outside it (the whole
|
||||||
point), and the panel sits in the top layer so a row kebab is no longer clipped by `.table-wrap`'s
|
point), and the panel sits in the top layer so a row kebab is no longer clipped by `.table-wrap`'s
|
||||||
|
|||||||
@@ -1526,7 +1526,7 @@ Gitea Actions (`.gitea/workflows/`) runs the pipeline; the test job runs
|
|||||||
| `release.yml` | push of a `vX.Y.Z` tag | re-tag that commit's image as `X.Y.Z`, `X.Y`, `X`, `latest`; sync those tags to Docker Hub |
|
| `release.yml` | push of a `vX.Y.Z` tag | re-tag that commit's image as `X.Y.Z`, `X.Y`, `X`, `latest`; sync those tags to Docker Hub |
|
||||||
| `mirror.yml` | push to `main` or any tag, or manual | force-push `main` + tags to the [GitHub mirror](https://github.com/larvit/plainpages) |
|
| `mirror.yml` | push to `main` or any tag, or manual | force-push `main` + tags to the [GitHub mirror](https://github.com/larvit/plainpages) |
|
||||||
| `registry-cleanup.yml` | nightly cron, or manual | delete registry images that are neither release-tagged nor a branch head |
|
| `registry-cleanup.yml` | nightly cron, or manual | delete registry images that are neither release-tagged nor a branch head |
|
||||||
| `renovate.yml` | nightly cron, or manual | open dependency-update PRs, automerge them once the gate is green; the release-tag job only runs when `AUTO_RELEASE` is set |
|
| `renovate.yml` | nightly cron, or manual | open dependency-update PRs, automerge them once the gate is green; the release-tag job only runs when `AUTO_RELEASE` is `true` |
|
||||||
|
|
||||||
`main` is not re-tested on push — its commits are meant to arrive already green from a
|
`main` is not re-tested on push — its commits are meant to arrive already green from a
|
||||||
gated branch, so the status check to gate a merge on is `CI / full-gate (push)`.
|
gated branch, so the status check to gate a merge on is `CI / full-gate (push)`.
|
||||||
@@ -1607,11 +1607,14 @@ instead of tripping the anonymous 60-requests/hour limit.
|
|||||||
**Releases are paused.** Plainpages is pre-announcement: the repository carries **no tags**, and
|
**Releases are paused.** Plainpages is pre-announcement: the repository carries **no tags**, and
|
||||||
neither `release.yml` nor Docker Hub has a version to promote. Turn releasing back on by setting the
|
neither `release.yml` nor Docker Hub has a version to promote. Turn releasing back on by setting the
|
||||||
Actions **variable** `AUTO_RELEASE` to `true` (that alone re-enables the job below), or cut a
|
Actions **variable** `AUTO_RELEASE` to `true` (that alone re-enables the job below), or cut a
|
||||||
`vX.Y.Z` tag by hand.
|
`vX.Y.Z` tag by hand **on `main`'s tip** — with nothing tagged, the nightly registry cleanup keeps
|
||||||
|
only branch-head images, so an older commit's image is already gone and `release.yml` would fail
|
||||||
|
loud with nothing to promote.
|
||||||
|
|
||||||
**Auto-release on dependency updates** — a second job in `renovate.yml` (`auto-release`, `needs:
|
**Auto-release on dependency updates** — a second job in `renovate.yml` (`auto-release`, `needs:
|
||||||
renovate`, gated on `AUTO_RELEASE` above) cuts **one** `vX.Y.Z` tag per run covering the renovate-bot commits merged to `main`
|
renovate`, gated on `AUTO_RELEASE` above) cuts **one** `vX.Y.Z` tag per run covering the
|
||||||
since the last tag (it targets `origin/main`, and **skips** when the tip isn't a Renovate commit —
|
renovate-bot commits merged to `main` since the last tag (it targets `origin/main`, and
|
||||||
|
**skips** when the tip isn't a Renovate commit —
|
||||||
a human owns that release — or when nothing new merged). Renovate stamps every commit with a
|
a human owns that release — or when nothing new merged). Renovate stamps every commit with a
|
||||||
`Release-Bump: <updateType>` trailer (`commitBody` in `renovate.json`), and
|
`Release-Bump: <updateType>` trailer (`commitBody` in `renovate.json`), and
|
||||||
[`auto-release/next-version.ts`](auto-release/next-version.ts) (unit-tested) turns the highest
|
[`auto-release/next-version.ts`](auto-release/next-version.ts) (unit-tested) turns the highest
|
||||||
|
|||||||
@@ -16,7 +16,8 @@ step() { printf '\n\033[1;34m==> %s\033[0m\n' "$1"; }
|
|||||||
# The working tree counts too — a dirty tree carrying real code must never skip. Anything
|
# The working tree counts too — a dirty tree carrying real code must never skip. Anything
|
||||||
# undeterminable (no git, no reachable main, no merge-base) falls through to the gate, never a skip.
|
# undeterminable (no git, no reachable main, no merge-base) falls through to the gate, never a skip.
|
||||||
# --no-renames on both channels: rename detection names only the destination, so `git mv src/app.ts
|
# --no-renames on both channels: rename detection names only the destination, so `git mv src/app.ts
|
||||||
# notes.md` reads as a lone *.md and would skip the gate over a source file that is gone.
|
# notes.md` reads as a lone *.md — under --porcelain as one `R src/app.ts -> notes.md` line still
|
||||||
|
# ending in .md after cut -c4- — and the gate would skip over a source file that is gone.
|
||||||
docs_only() {
|
docs_only() {
|
||||||
local base changed
|
local base changed
|
||||||
git rev-parse --git-dir >/dev/null 2>&1 || return 1
|
git rev-parse --git-dir >/dev/null 2>&1 || return 1
|
||||||
|
|||||||
+2
-4
@@ -8,7 +8,8 @@ import { readFileSync } from "node:fs";
|
|||||||
|
|
||||||
const read = (p: string) => readFileSync(new URL(`../${p}`, import.meta.url), "utf8");
|
const read = (p: string) => readFileSync(new URL(`../${p}`, import.meta.url), "utf8");
|
||||||
const workflow = read(".gitea/workflows/ci.yml");
|
const workflow = read(".gitea/workflows/ci.yml");
|
||||||
const gate = read("ci.sh");
|
// Comments stripped: the flags below must be asserted against the code, not against prose naming them.
|
||||||
|
const gate = read("ci.sh").split("\n").filter((l) => !l.trimStart().startsWith("#")).join("\n");
|
||||||
const step = (needle: string) => {
|
const step = (needle: string) => {
|
||||||
const found = workflow.split("\n - ").slice(1).filter((s) => s.includes(needle));
|
const found = workflow.split("\n - ").slice(1).filter((s) => s.includes(needle));
|
||||||
assert.equal(found.length, 1, `exactly one workflow step contains ${needle}`);
|
assert.equal(found.length, 1, `exactly one workflow step contains ${needle}`);
|
||||||
@@ -31,8 +32,5 @@ test("the commit-hash image is pushed even when the gate no-ops", () => {
|
|||||||
test("only *.md counts as docs; a dirty tree and a rename both count as changed", () => {
|
test("only *.md counts as docs; a dirty tree and a rename both count as changed", () => {
|
||||||
assert.ok(gate.includes("\\.md$"), "the non-docs match is a *.md suffix test");
|
assert.ok(gate.includes("\\.md$"), "the non-docs match is a *.md suffix test");
|
||||||
assert.match(gate, /git status --porcelain --no-renames/, "uncommitted code can never be skipped over");
|
assert.match(gate, /git status --porcelain --no-renames/, "uncommitted code can never be skipped over");
|
||||||
// Rename detection hides the source path: `git mv src/app.ts notes.md` reads as the .md alone
|
|
||||||
// under --name-only, and as a single `R src/app.ts -> notes.md` line under --porcelain — so
|
|
||||||
// without --no-renames a deleted source file looks like docs and skips the gate.
|
|
||||||
assert.match(gate, /git diff --name-only --no-renames/, "a rename must list both of its paths");
|
assert.match(gate, /git diff --name-only --no-renames/, "a rename must list both of its paths");
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user