👷 Add Gitea release page to CI

The publish job now opens a Gitea release for the tag, using the
matching Keep-a-Changelog section as the body via scripts/release-notes.sh.
It runs before npm publish so a broken page fails CI without burning an
npm version; npm publish stays the last step.

Uses the first-party gitea-release-action (the older actions/release-action
is deprecated and requires asset files) and contents: write for the run's
automatic token.
This commit is contained in:
tmu committed 2026-09-11 22:06:05 +00:00
1 parent 7216c418d0
commit a865b466bd
4 files changed
+85 -3

No files matched your search

+17
View File
@@ -52,6 +52,10 @@ jobs:
if: startsWith(gitea.ref, 'refs/tags/') if: startsWith(gitea.ref, 'refs/tags/')
needs: build needs: build
runs-on: ubuntu-latest runs-on: ubuntu-latest
# The release page is created with the run's automatic Gitea token
# (`github.token`), not `NPM_TOKEN`, so it needs `contents: write`.
permissions:
contents: write
steps: steps:
# Double-gate: publish only runs on a tag *and* aborts here if NPM_TOKEN # Double-gate: publish only runs on a tag *and* aborts here if NPM_TOKEN
# is unset, so a tag push never silently no-ops (or half-publishes). Set # is unset, so a tag push never silently no-ops (or half-publishes). Set
@@ -76,6 +80,19 @@ jobs:
path: dist/ path: dist/
- run: npm run publish:publint - run: npm run publish:publint
- run: npm run publish:attw - run: npm run publish:attw
# The Gitea release page is created *before* `npm publish` on
# purpose: a broken page then fails CI without burning an npm
# version. The page is cheap to retry, a published version is not.
# The body is the matching Keep-a-Changelog section; an unknown tag
# makes the extractor exit non-zero, so the page can never go up
# empty.
- name: Extract release notes from CHANGELOG.md
env:
TAG_REF: ${{ gitea.ref }}
run: ./scripts/release-notes.sh "${TAG_REF#refs/tags/}" > release-notes.md
- uses: https://gitea.com/actions/gitea-release-action@v1
with:
body_path: release-notes.md
- run: npm publish --access public - run: npm publish --access public
env: env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
+2 -2
View File
@@ -55,7 +55,7 @@ The tools are organized into a feedback ladder. Each tier catches different thin
| `npm run maintain` | manual / CI (advisory) | `maintain:knip` + `maintain:outdated` (whole-project + network scans) | ~10s | | `npm run maintain` | manual / CI (advisory) | `maintain:knip` + `maintain:outdated` (whole-project + network scans) | ~10s |
| CI build (auto) | on push to `main` | `build` job (build + correctness + packaging) — see [.gitea/workflows/ci.yml](./.gitea/workflows/ci.yml) | ~30s+ | | CI build (auto) | on push to `main` | `build` job (build + correctness + packaging) — see [.gitea/workflows/ci.yml](./.gitea/workflows/ci.yml) | ~30s+ |
| CI maintain (auto, non-blocking) | on push to `main` | `npm run maintain` — reports, never fails the build | ~10s | | CI maintain (auto, non-blocking) | on push to `main` | `npm run maintain` — reports, never fails the build | ~10s |
| CI publish (auto) | on tag | `publish:publint` + `publish:attw`, then `npm publish` | ~10s | | CI publish (auto) | on tag | Gitea release page (body from CHANGELOG) + `publish:publint` + `publish:attw`, then `npm publish` | ~15s |
### Why these splits? ### Why these splits?
@@ -96,4 +96,4 @@ Publishing is CI-only by policy. Local `npm publish` is not supported. The maint
1. All intended changes are merged to `main` and passing CI. 1. All intended changes are merged to `main` and passing CI.
2. The maintainer runs `npm run create:release` — an interactive prompt suggests a version (based on the latest CHANGELOG entry); the maintainer confirms or edits it. 2. The maintainer runs `npm run create:release` — an interactive prompt suggests a version (based on the latest CHANGELOG entry); the maintainer confirms or edits it.
3. `scripts/release.sh` creates a single release commit (changelog + package.json bump, amended into one commit), tags it, and pushes everything to Gitea. 3. `scripts/release.sh` creates a single release commit (changelog + package.json bump, amended into one commit), tags it, and pushes everything to Gitea.
4. CI runs on the push (the `build` and `maintain` jobs); the `publish` job then fires on the tag, consuming the `dist/` artifact the `build` job produced. The job graph lives in [.gitea/workflows/ci.yml](./.gitea/workflows/ci.yml) — keep that file, not this list, as the source of truth. The publish-tier checks must pass before the artifact is published. 4. CI runs on the push (the `build` and `maintain` jobs); the `publish` job then fires on the tag, consuming the `dist/` artifact the `build` job produced. The job graph lives in [.gitea/workflows/ci.yml](./.gitea/workflows/ci.yml) — keep that file, not this list, as the source of truth. The publish-tier checks must pass before the artifact is published. The `publish` job also creates the Gitea release page from the matching Keep-a-Changelog section (`scripts/release-notes.sh`); it runs _before_ `npm publish` so a broken page fails CI without consuming a version, and `npm publish` stays the last step.
+2 -1
View File
@@ -5,7 +5,8 @@ Backlog and tracking for tiny-pattern-ts. Managed in vscode-todotasks format.
--- ---
Setup: Setup:
☐ Add gitea release page in CI @high ✔ Add gitea release page in CI @high @done
☐ Manually verify the Gitea release page on a real tag push (needs main) @high
☐ Split off template into separate package => pi --session 01a07dde-7050-7054-bb36-1606d7eb2bc3 @high ☐ Split off template into separate package => pi --session 01a07dde-7050-7054-bb36-1606d7eb2bc3 @high
v1.0: v1.0:
+64
View File
@@ -0,0 +1,64 @@
#!/bin/sh
set -eu
# Print the Keep-a-Changelog section for a release tag, so CI can use it as the
# body of the Gitea release page without re-implementing CHANGELOG parsing.
#
# Run as `scripts/release-notes.sh <tag>`. A leading `v` is tolerated so both
# `v1.2.3` and `1.2.3` match the `## [1.2.3]` heading. Prints to stdout and
# exits non-zero when the tag has no section, so a release can never publish
# with an empty body.
TAG="${1:-}"
CHANGELOG="${CHANGELOG:-CHANGELOG.md}"
if [ -z "${TAG}" ]; then
echo "Error: usage: $0 <tag>" >&2
exit 1
fi
if [ ! -f "${CHANGELOG}" ]; then
echo "Error: ${CHANGELOG} not found." >&2
exit 1
fi
# `v1.2.3` and `1.2.3` are the same release; the heading only ever uses the
# bare version.
VERSION="${TAG#v}"
awk -v version="${VERSION}" '
# A new section ends the one we are printing (or is the one we want).
/^## \[/ {
if (found) exit
heading = $0
sub(/^## \[/, "", heading)
sub(/\].*$/, "", heading)
if (heading == version) found = 1
next
}
# Link-reference definitions live at the bottom of the file and are never
# part of the section body. Stopping here keeps the last release notes
# from picking them up (there is no following heading to stop at).
/^\[[^]]+\]:/ { exit }
found {
if ($0 ~ /^[[:space:]]*$/) {
# Buffer blank lines so trailing ones at the end of the section
# are dropped instead of leaking into the release body.
if (started) pending = pending $0 "\n"
} else {
printf "%s%s\n", pending, $0
pending = ""
started = 1
}
}
END {
if (!found) {
printf "Error: no CHANGELOG section for [%s].\n", version > "/dev/stderr"
exit 1
}
}
' "${CHANGELOG}"