Compare commits
25 Commits
v0.0.3
..
8808430808
| Author | SHA1 | Date | |
|---|---|---|---|
| 8808430808 | |||
| 1ea8209afd | |||
| ffeec70f8f | |||
| 62d4c8b7cd | |||
| c64156a9d5 | |||
| de2ad42f5a | |||
| 9213e5a0de | |||
| 45054db5e6 | |||
| 23bafd247d | |||
| 7c66599f35 | |||
| 6db0f57bf4 | |||
| 175717f04d | |||
| 6c850b8923 | |||
| af4a70d904 | |||
| a0244a32cd | |||
| 6559f40142 | |||
| d3154819f8 | |||
| 1cba6d470c | |||
| 194c090bd1 | |||
| ff5094f7e9 | |||
| 419ee1750b | |||
| cedac950be | |||
| ff455f1ef2 | |||
| 5bd26d773d | |||
| 4f60bad119 |
@@ -8,6 +8,8 @@ jobs:
|
||||
runs-on: docker-host
|
||||
steps:
|
||||
- uses: actions/checkout@v4.2.2
|
||||
with:
|
||||
fetch-depth: 0 # ci.sh's docs-only check needs history; checkout defaults to depth 1
|
||||
- run: bash ci.sh
|
||||
- name: Push app image tagged with the commit hash
|
||||
env:
|
||||
|
||||
@@ -19,4 +19,4 @@ jobs:
|
||||
run: |
|
||||
docker run --rm -v "$PWD:/repo" -w /repo \
|
||||
-e REGISTRY_TOKEN -e REGISTRY_USER -e REPO_TOKEN -e REPOSITORY -e SERVER_URL \
|
||||
node:24.18.0-alpine3.24 node registry-cleanup/cleanup.ts
|
||||
node:24.18.1-alpine3.24 node registry-cleanup/cleanup.ts
|
||||
|
||||
@@ -19,7 +19,7 @@ jobs:
|
||||
-e RENOVATE_PLATFORM=gitea \
|
||||
-e RENOVATE_REPOSITORIES=${{ github.repository }} \
|
||||
-e RENOVATE_TOKEN \
|
||||
renovate/renovate:43.252.1
|
||||
renovate/renovate:44.6.0
|
||||
|
||||
# After the renovate job, cut ONE tag covering the renovate-bot commits merged to main since the
|
||||
# last tag (batch per run). Targets origin/main — the real post-merge tip; the checkout SHA is the
|
||||
@@ -52,7 +52,7 @@ jobs:
|
||||
fi
|
||||
BUMPS=$(git log "${LATEST}..origin/main" --author='renovate@larvit.se' \
|
||||
--format='%(trailers:key=Release-Bump,valueonly)' | { grep -vx '' || true; })
|
||||
NEXT=$(docker run --rm -v "$PWD:/repo" -w /repo node:24.16.0-alpine3.24 \
|
||||
NEXT=$(docker run --rm -v "$PWD:/repo" -w /repo node:24.18.1-alpine3.24 \
|
||||
node auto-release/next-version.ts "$LATEST" $BUMPS)
|
||||
echo "Releasing $LATEST -> $NEXT"
|
||||
git tag "$NEXT" origin/main
|
||||
|
||||
@@ -89,8 +89,10 @@ them. Revisit only if the stated reason stops holding.
|
||||
- **CI docker logins share the runner host's Docker config.** The act_runner is host-mode, so
|
||||
`docker login`/`logout` in the workflows mutate one shared `~/.docker/config.json`:
|
||||
concurrent jobs can race (one job's logout can 401 another's push — recover by re-running),
|
||||
and tokens sit in that file between login and logout. Accepted for a single-maintainer
|
||||
cadence; serialize with a workflow `concurrency` group if it ever bites.
|
||||
and tokens sit in that file between login and logout. Same class: concurrent runs share 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
|
||||
`concurrency` group if it ever bites.
|
||||
|
||||
## Docker only — no host tooling
|
||||
|
||||
@@ -127,6 +129,14 @@ When editing: put content in the section it belongs to (don't prepend rationale
|
||||
start); keep the ToC in sync when you add/rename/remove an `H2`/`H3`; and state each fact in
|
||||
one home, linking to it rather than restating (credentials, env vars, rotation steps).
|
||||
|
||||
**Don't document internals here.** How a script reaches a decision, why one run behaved
|
||||
differently from another, what a function guards — a developer doesn't need it day to day and
|
||||
can read it off the code or a run's log in seconds. Prose like that only makes the README
|
||||
longer and harder to consume, for humans and machines alike. It belongs in the code it
|
||||
describes, or nowhere. The README earns its length on what you cannot dig out: how to use and
|
||||
operate Plainpages, the external contracts, and one-time setup (secrets, accounts, tokens).
|
||||
Same test before adding a row to a table or the file map — a clause, not a paragraph.
|
||||
|
||||
## Rules
|
||||
|
||||
- Node 24 runs `.ts` directly (type stripping). Keep all TypeScript **erasable**
|
||||
@@ -142,6 +152,10 @@ one home, linking to it rather than restating (credentials, env vars, rotation s
|
||||
- Tests use the built-in `node --test` runner — no test framework dependency.
|
||||
- English everywhere. Keep code comments short and information-dense. Self explained code
|
||||
without any comment at all is the preferred solution.
|
||||
- Do not comment about history in the code or README. Like "This function included X before,
|
||||
but it moved to Y".
|
||||
- Do not comment about the absence of things, if it is not very unexpected. Banned is things
|
||||
like "This function does not calculate pi, that is done in function Z".
|
||||
- Pin all dependencies and Docker images to exact, human-readable **semantic
|
||||
versions** — never ranges (`^`, `~`) and never digests/hashes. npm deps are kept
|
||||
exact by `.npmrc` (`save-exact=true`) + `npm ci`; the base image by tag (e.g.
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
# Node 24 runs TypeScript directly (type stripping) — no build step. Pinned exact tag.
|
||||
FROM node:24.18.0-alpine3.24
|
||||
FROM node:24.18.1-alpine3.24
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
|
||||
@@ -1173,7 +1173,7 @@ Gitea Actions (`.gitea/workflows/`) runs the pipeline; the test job runs
|
||||
|
||||
| Workflow | Trigger | Does |
|
||||
| --- | --- | --- |
|
||||
| `ci.yml` | push, any branch except `main` | the full gate (`bash ci.sh`), then build + push the app image |
|
||||
| `ci.yml` | push, any branch except `main` | the full gate (`bash ci.sh`, a no-op on a docs-only branch), then build + push the app image |
|
||||
| `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) |
|
||||
| `registry-cleanup.yml` | nightly cron, or manual | delete registry images that are neither release-tagged nor a branch head |
|
||||
@@ -1203,12 +1203,9 @@ this step runs
|
||||
inside the required gate, a missing/expired token (or registry outage) fails every branch's
|
||||
gate and blocks **all** merges until restored — set the secrets before this lands, and use a
|
||||
non-expiring token or track its expiry. Retention: hash tags accumulate one image per gated
|
||||
push, so the nightly `registry-cleanup.yml` prunes them precisely
|
||||
([`registry-cleanup/cleanup.ts`](registry-cleanup/cleanup.ts), run in a `node:24` container):
|
||||
a hash tag survives only while its commit is a **branch head** or carries a **`vX.Y.Z`
|
||||
release tag**; deleted alongside are the untagged `sha256:…` child manifests (arch image +
|
||||
provenance) that no surviving tag references. Named tags (`1.2.3`, `latest`, …) are never
|
||||
touched. It reuses `DOCKER_REGISTRY_USER`/`DOCKER_REGISTRY_TOKEN` — no extra setup. Don't
|
||||
push, so the nightly `registry-cleanup.yml` prunes them
|
||||
([`registry-cleanup/cleanup.ts`](registry-cleanup/cleanup.ts) defines what survives).
|
||||
It reuses `DOCKER_REGISTRY_USER`/`DOCKER_REGISTRY_TOKEN` — no extra setup. Don't
|
||||
add a pattern-based org cleanup rule for this package (and remove it if one exists): its
|
||||
age/count heuristics can't see branch heads or release tags and would delete images the
|
||||
workflow protects.
|
||||
@@ -1259,12 +1256,10 @@ rejected).
|
||||
renovate`) cuts **one** `vX.Y.Z` tag per run covering the 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
|
||||
`Release-Bump: <updateType>` trailer (`commitBody` in `renovate.json`); the job takes the highest
|
||||
trailer on those commits — any dependency's `major`/`minor`/`patch` maps straight through,
|
||||
defaulting to `patch`.
|
||||
**Pre-1.0 the level shifts down** — a dep major bumps the `0.x` minor, dep minor/patch bump the
|
||||
`0.x` patch (see [`auto-release/next-version.ts`](auto-release/next-version.ts), unit-tested) — so
|
||||
routine bumps never auto-cross into `1.0.0`; `1.0.0` stays a deliberate hand-cut tag. It's
|
||||
`Release-Bump: <updateType>` trailer (`commitBody` in `renovate.json`), and
|
||||
[`auto-release/next-version.ts`](auto-release/next-version.ts) (unit-tested) turns the highest
|
||||
trailer on those commits into the next version — pre-1.0 it never auto-crosses into `1.0.0`,
|
||||
which stays a deliberate hand-cut tag. It's
|
||||
**tag-only** (no source commits): the tag hands off to `release.yml`, which promotes the
|
||||
already-built image, and is pushed with renovate-bot's PAT so `release.yml` actually fires (a tag
|
||||
pushed by the built-in Actions token wouldn't trigger it). The plugin-contract version
|
||||
|
||||
@@ -12,6 +12,26 @@ cd "$(dirname "$0")"
|
||||
|
||||
step() { printf '\n\033[1;34m==> %s\033[0m\n' "$1"; }
|
||||
|
||||
# Docs-only fast path: nothing but *.md changed since main, so there is nothing here to break.
|
||||
# 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.
|
||||
docs_only() {
|
||||
local base changed
|
||||
git rev-parse --git-dir >/dev/null 2>&1 || return 1
|
||||
git fetch --no-tags --quiet origin +refs/heads/main:refs/remotes/origin/main 2>/dev/null || true
|
||||
base=$(git merge-base refs/remotes/origin/main HEAD 2>/dev/null) || return 1
|
||||
changed=$(
|
||||
{ git diff --name-only "$base" HEAD && git status --porcelain --untracked-files=all | cut -c4-; } 2>/dev/null
|
||||
) || return 1
|
||||
[ -n "$changed" ] || return 1
|
||||
! printf '%s\n' "$changed" | grep -qvE '\.md$'
|
||||
}
|
||||
|
||||
if docs_only; then
|
||||
step "Only *.md changed since main — nothing to test, skipping the gate"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Pins that MUST move in lockstep: a browser/runner mismatch yields confusing E2E failures.
|
||||
step "Playwright pin lockstep (e2e-tests/Dockerfile image == e2e-tests/package.json @playwright/test)"
|
||||
# `|| true` so a no-match doesn't trip `set -e`/`pipefail` before the explicit check below can report.
|
||||
@@ -20,6 +40,12 @@ pkg=$(grep -oE '"@playwright/test": "[0-9.]+"' e2e-tests/package.json | grep -oE
|
||||
[ -n "$img" ] && [ "$img" = "$pkg" ] || { echo "Playwright pin mismatch/unreadable: image v$img vs @playwright/test $pkg"; exit 1; }
|
||||
echo "ok ($img)"
|
||||
|
||||
# Explicit rebuild: without it a stale web image from a previous branch supplies node_modules
|
||||
# (the source is bind-mounted but deps are baked in), so a dep bump gets typechecked/tested
|
||||
# against the OLD packages. Cheap when deps are unchanged (npm ci layer is cache-keyed).
|
||||
step "Build web image"
|
||||
docker compose build web
|
||||
|
||||
step "Typecheck"
|
||||
docker compose run --rm --no-deps web npm run typecheck
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ services:
|
||||
# backs it (SCHEDULING_UPSTREAM above points here). Stand-in for the customer's real service —
|
||||
# stdlib-only, in-memory, no auth. Prod points SCHEDULING_UPSTREAM at the real backend instead.
|
||||
shifts-upstream:
|
||||
image: node:24.18.0-alpine3.24
|
||||
image: node:24.18.1-alpine3.24
|
||||
command: node /srv/server.ts
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
@@ -36,7 +36,7 @@ services:
|
||||
# Dev mail catcher — Kratos recovery/verification emails land here (web UI on 8025).
|
||||
# kratos.yml points the courier at smtp://mailpit:1025; prod uses a real SMTP via env.
|
||||
mailpit:
|
||||
image: axllent/mailpit:v1.30.3
|
||||
image: axllent/mailpit:v1.30.6
|
||||
ports:
|
||||
- "8025:8025"
|
||||
restart: unless-stopped
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Playwright runner — browsers preinstalled, pinned to match @playwright/test in e2e-tests/.
|
||||
# Built/run via e2e-tests/compose.visual.yml; targets the `web` service over the network.
|
||||
FROM mcr.microsoft.com/playwright:v1.49.1-noble
|
||||
FROM mcr.microsoft.com/playwright:v1.62.1-noble
|
||||
|
||||
WORKDIR /e2e-tests
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ services:
|
||||
|
||||
# The reference plugin's upstream (examples/shifts-upstream) so /scheduling/shifts shows real rows.
|
||||
shifts-upstream:
|
||||
image: node:24.18.0-alpine3.24
|
||||
image: node:24.18.1-alpine3.24
|
||||
command: ["node", "/server.ts"]
|
||||
volumes:
|
||||
- ./examples/shifts-upstream/server.ts:/server.ts:ro
|
||||
@@ -67,7 +67,7 @@ services:
|
||||
# Mock OIDC provider for the SSO login test — stdlib Node, auto-approves, signs an id_token Kratos
|
||||
# verifies via its jwks. Reachable as the same host (mock-oidc:9000) by both the browser and Kratos.
|
||||
mock-oidc:
|
||||
image: node:24.18.0-alpine3.24
|
||||
image: node:24.18.1-alpine3.24
|
||||
command: ["node", "/mock-oidc.ts"]
|
||||
environment:
|
||||
ISSUER: http://mock-oidc:9000
|
||||
@@ -82,7 +82,7 @@ services:
|
||||
|
||||
# Same-origin gateway: Kratos-owned paths → kratos, everything else → web (e2e-tests/proxy.ts).
|
||||
proxy:
|
||||
image: node:24.18.0-alpine3.24
|
||||
image: node:24.18.1-alpine3.24
|
||||
command: ["node", "/proxy.ts"]
|
||||
depends_on:
|
||||
web:
|
||||
|
||||
Generated
+15
-15
@@ -8,23 +8,23 @@
|
||||
"name": "plainpages-e2e",
|
||||
"version": "0.1.0",
|
||||
"devDependencies": {
|
||||
"@playwright/test": "1.49.1"
|
||||
"@playwright/test": "1.62.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@playwright/test": {
|
||||
"version": "1.49.1",
|
||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.49.1.tgz",
|
||||
"integrity": "sha512-Ky+BVzPz8pL6PQxHqNRW1k3mIyv933LML7HktS8uik0bUXNCdPhoS/kLihiO1tMf/egaJb4IutXd7UywvXEW+g==",
|
||||
"version": "1.62.1",
|
||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.62.1.tgz",
|
||||
"integrity": "sha512-DTcUc8qii+cpHvtOwggMtBRMjKZHXYWdw8syRYu2vtzuq4Wxphqq4NfCs5Zt44L6mA8rfDfj+PHnxFc/FeK6mQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright": "1.49.1"
|
||||
"playwright": "1.62.1"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
"node": ">=20"
|
||||
}
|
||||
},
|
||||
"node_modules/fsevents": {
|
||||
@@ -43,35 +43,35 @@
|
||||
}
|
||||
},
|
||||
"node_modules/playwright": {
|
||||
"version": "1.49.1",
|
||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.49.1.tgz",
|
||||
"integrity": "sha512-VYL8zLoNTBxVOrJBbDuRgDWa3i+mfQgDTrL8Ah9QXZ7ax4Dsj0MSq5bYgytRnDVVe+njoKnfsYkH3HzqVj5UZA==",
|
||||
"version": "1.62.1",
|
||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.62.1.tgz",
|
||||
"integrity": "sha512-0M+L3LAD8/nm554LOla9Ayx0j0tmFZ0FBcoQ7F1VuVHpM/XpiC8RcDzBQB8W5+hA8L22THxELzeF+2WcUzvcLg==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright-core": "1.49.1"
|
||||
"playwright-core": "1.62.1"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
"node": ">=20"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/playwright-core": {
|
||||
"version": "1.49.1",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.49.1.tgz",
|
||||
"integrity": "sha512-BzmpVcs4kE2CH15rWfzpjzVGhWERJfmnXmniSyKeRZUs9Ws65m+RGIi7mjJK/euCegfn3i7jvqWeWyHe9y3Vgg==",
|
||||
"version": "1.62.1",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.62.1.tgz",
|
||||
"integrity": "sha512-wPYSwEBJY9GHraISXqyqtx0na0LpO3XEX7jNDhntbex7tzUS7kLnZsOlFruFJB4Hi/rhDMjXGqHewDZ68nYZVw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"playwright-core": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
"node": ">=20"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,6 @@
|
||||
"test": "playwright test"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "1.49.1"
|
||||
"@playwright/test": "1.62.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{"errors":null,"message":"not found","url":"https://gitea.larvit.se/api/swagger"}
|
||||
Generated
+8
-8
@@ -10,11 +10,11 @@
|
||||
"dependencies": {
|
||||
"@larvit/log": "2.3.0",
|
||||
"ejs": "3.1.10",
|
||||
"lucide-static": "1.18.0"
|
||||
"lucide-static": "1.28.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/ejs": "3.1.5",
|
||||
"@types/node": "24.13.2",
|
||||
"@types/node": "24.13.3",
|
||||
"typescript": "5.9.3"
|
||||
},
|
||||
"engines": {
|
||||
@@ -38,9 +38,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "24.13.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.2.tgz",
|
||||
"integrity": "sha512-fRa09kZTgu8o71KFcDjUFuc7F+dEbZYZmkI0mg5YBTRs0yMKjYHsq/c0urDKeDb+D5qVgXOdFcuu+DZPKOITwA==",
|
||||
"version": "24.13.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.3.tgz",
|
||||
"integrity": "sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -110,9 +110,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/lucide-static": {
|
||||
"version": "1.18.0",
|
||||
"resolved": "https://registry.npmjs.org/lucide-static/-/lucide-static-1.18.0.tgz",
|
||||
"integrity": "sha512-0WRXLQnjbte5SXuzom6yfeGlVSFsEsC9rzxn66DZN0pXows3+N34CQHy3BHI1qA3uH7u/SUzx8LQhjeAnxd8JQ==",
|
||||
"version": "1.28.0",
|
||||
"resolved": "https://registry.npmjs.org/lucide-static/-/lucide-static-1.28.0.tgz",
|
||||
"integrity": "sha512-dC3VJwRFsjEVX7Iaq4rY88pm7Fi2OmOb8P0WRzXsUMgbt7sCmFX8bLhaDBeNW6JdRjuele+jKqqFaam4yr+Ygg==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/minimatch": {
|
||||
|
||||
+2
-2
@@ -20,11 +20,11 @@
|
||||
"dependencies": {
|
||||
"@larvit/log": "2.3.0",
|
||||
"ejs": "3.1.10",
|
||||
"lucide-static": "1.18.0"
|
||||
"lucide-static": "1.28.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/ejs": "3.1.5",
|
||||
"@types/node": "24.13.2",
|
||||
"@types/node": "24.13.3",
|
||||
"typescript": "5.9.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
// Guards the docs-only fast path: `ci.sh` no-ops when nothing but *.md changed since main. The
|
||||
// decision lives in ci.sh alone so `bash ci.sh` reproduces CI locally, and the workflow must still
|
||||
// push the commit-hash image when it no-ops — release.yml re-tags that exact image, and
|
||||
// fast-forward-only merges make every branch head a main commit.
|
||||
import { test } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { readFileSync } from "node:fs";
|
||||
|
||||
const read = (p: string) => readFileSync(new URL(`../${p}`, import.meta.url), "utf8");
|
||||
const workflow = read(".gitea/workflows/ci.yml");
|
||||
const gate = read("ci.sh");
|
||||
const step = (needle: string) => {
|
||||
const found = workflow.split("\n - ").slice(1).filter((s) => s.includes(needle));
|
||||
assert.equal(found.length, 1, `exactly one workflow step contains ${needle}`);
|
||||
return found[0]!;
|
||||
};
|
||||
|
||||
test("the skip decision lives in ci.sh, so the workflow only runs it", () => {
|
||||
assert.match(gate, /docs_only\(\)/);
|
||||
assert.doesNotMatch(workflow, /docs_only|merge-base|GITHUB_OUTPUT/);
|
||||
});
|
||||
|
||||
test("checkout is unshallow — the docs-only check needs the branch's history", () => {
|
||||
assert.match(step("actions/checkout"), /fetch-depth: 0/);
|
||||
});
|
||||
|
||||
test("the commit-hash image is pushed even when the gate no-ops", () => {
|
||||
assert.doesNotMatch(step("docker push"), /^\s*if:/m);
|
||||
});
|
||||
|
||||
test("only *.md counts as docs, and a dirty working tree counts as changed", () => {
|
||||
assert.ok(gate.includes("\\.md$"), "the non-docs match is a *.md suffix test");
|
||||
assert.match(gate, /git status --porcelain/, "uncommitted code can never be skipped over");
|
||||
});
|
||||
@@ -15,6 +15,7 @@
|
||||
- [x] CI/CD - When renovate updates a dependency - also release a new version of plainpages based on what got updated with Renovate. Major typescript? New apiVersion + new major. A tiny patch to ejs? Only patch release etc. Before implementing, explain in detail how you will solve this. (`renovate.yml` gains an `auto-release` job (`needs: renovate`) that cuts one `vX.Y.Z` tag per run for what Renovate merged; level = highest `Release-Bump:` trailer Renovate stamps via `commitBody`, any dep's major/minor/patch mapped straight through (default patch). Decoupled from `apiVersion` (tag-only, `HOST_API_VERSION` untouched — a "major" is just a bigger image tag, never a plugin break); pre-1.0 shifts down so nothing auto-crosses into 1.0.0. Pure `auto-release/next-version.ts` + unit tests; tag pushed with renovate-bot's PAT so `release.yml` fires; documented in README → CI/CD.)
|
||||
- [ ] Add an e2e test for the admin plugin's OAuth2-clients (Hydra) screen. The full-flow e2e suite runs without Hydra (compose.full.yml), so /admin/clients register/detail/delete is only unit-covered (src/http/app.test.ts); wire Hydra into an e2e stack and drive the screen in the browser.
|
||||
- [ ] Build and publish docker image as CI/CD.
|
||||
- [ ] The human developer understands the security model in the auth in this project.
|
||||
- [ ] Add i18n support.
|
||||
|
||||
## Architectural review findings (2026-07-02)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<symbol id="i-arrow-left" viewBox="0 0 24 24"><path d="m12 19-7-7 7-7" /><path d="M19 12H5" /></symbol>
|
||||
<symbol id="i-bell" viewBox="0 0 24 24"><path d="M10.268 21a2 2 0 0 0 3.464 0" /><path d="M3.262 15.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673C19.41 13.956 18 12.499 18 8A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326" /></symbol>
|
||||
<symbol id="i-box" viewBox="0 0 24 24"><path d="M21 8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16Z" /><path d="m3.3 7 8.7 5 8.7-5" /><path d="M12 22V12" /></symbol>
|
||||
<symbol id="i-cal" viewBox="0 0 24 24"><path d="M8 2v4" /><path d="M16 2v4" /><rect width="18" height="18" x="3" y="4" rx="2" /><path d="M3 10h18" /></symbol>
|
||||
<symbol id="i-cal" viewBox="0 0 24 24"><path d="M8 2v3" /><path d="M16 2v3" /><rect x="3" y="3" width="18" height="18" rx="2" /><path d="M3 9h18" /></symbol>
|
||||
<symbol id="i-chart" viewBox="0 0 24 24"><path d="M5 21v-6" /><path d="M12 21V3" /><path d="M19 21V9" /></symbol>
|
||||
<symbol id="i-check-circle" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10" /><path d="m9 12 2 2 4-4" /></symbol>
|
||||
<symbol id="i-chev" viewBox="0 0 24 24"><path d="m9 18 6-6-6-6" /></symbol>
|
||||
|
||||
Reference in New Issue
Block a user