Compare commits
2
Commits
0.1.1
..
85fd37737a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
85fd37737a | ||
|
|
bd0145642c |
No files matched your search
@@ -12,42 +12,7 @@ on:
|
|||||||
workflow_dispatch: {}
|
workflow_dispatch: {}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
# Cheap gate that collapses the release double-run. `scripts/release.sh`
|
|
||||||
# pushes `main` and the tag seconds apart, and the tag points at exactly
|
|
||||||
# the HEAD commit that push delivers — so the branch run would verify the
|
|
||||||
# identical tree the tag run verifies anyway (plus `publish`). When a push
|
|
||||||
# to `main` is headed by a release commit (`:rocket: Release x.y.z`, the
|
|
||||||
# single commit release.sh creates), the full CI is skipped here and the
|
|
||||||
# tag run becomes the authoritative one for that SHA. All other pushes —
|
|
||||||
# PRs, tags, ordinary `main` merges — see `skip=false` and run as before.
|
|
||||||
#
|
|
||||||
# Coupling: the pattern below MUST stay in sync with the release commit
|
|
||||||
# message in `scripts/release.sh`. Failure mode if the tag push ever fails
|
|
||||||
# after `main` accepted the release commit: no CI fires; fix by re-running
|
|
||||||
# `git push --tags`.
|
|
||||||
release-gate:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
outputs:
|
|
||||||
skip: ${{ steps.decide.outputs.skip }}
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- id: decide
|
|
||||||
env:
|
|
||||||
REF: ${{ gitea.ref }}
|
|
||||||
run: |
|
|
||||||
# Keyed on the ref, not just the message: a tag run checks out
|
|
||||||
# the same release commit, and `publish` needs its `build`.
|
|
||||||
if [ "${REF}" = "refs/heads/main" ] &&
|
|
||||||
git log -1 --format=%s | grep -qE '^:rocket: Release [0-9]+\.[0-9]+\.[0-9]+$'; then
|
|
||||||
echo 'Release commit on main — the tag run covers this SHA; skipping full CI.'
|
|
||||||
echo 'skip=true' >>"${GITHUB_OUTPUT}"
|
|
||||||
else
|
|
||||||
echo 'skip=false' >>"${GITHUB_OUTPUT}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
build:
|
build:
|
||||||
needs: release-gate
|
|
||||||
if: needs.release-gate.outputs.skip != 'true'
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
# Bind-mount the shared pages tree so the coverage step below can write
|
# Bind-mount the shared pages tree so the coverage step below can write
|
||||||
# into it. The runner whitelists this path via `container.valid_volumes`
|
# into it. The runner whitelists this path via `container.valid_volumes`
|
||||||
@@ -104,8 +69,6 @@ jobs:
|
|||||||
# the Actions tab for visibility, but must never gate a merge — so
|
# the Actions tab for visibility, but must never gate a merge — so
|
||||||
# continue-on-error and intentionally NOT in `publish`'s `needs`.
|
# continue-on-error and intentionally NOT in `publish`'s `needs`.
|
||||||
maintain:
|
maintain:
|
||||||
needs: release-gate
|
|
||||||
if: needs.release-gate.outputs.skip != 'true'
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
steps:
|
steps:
|
||||||
|
|||||||
+1
-11
@@ -7,14 +7,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
## [0.1.1] - 2026-09-14
|
[Unreleased]: https://gitea.e1nsnull.de/tmu/tiny-pattern-ts
|
||||||
|
|
||||||
- upgrade dependencies
|
|
||||||
|
|
||||||
## [0.1.0] - 2026-09-14
|
|
||||||
|
|
||||||
- basic setup
|
|
||||||
|
|
||||||
[Unreleased]: https://gitea.e1nsnull.de/tmu/tiny-pattern-ts/compare/0.1.1...main
|
|
||||||
[0.1.1]: https://gitea.e1nsnull.de/tmu/tiny-pattern-ts/compare/0.1.0...0.1.1
|
|
||||||
[0.1.0]: https://gitea.e1nsnull.de/tmu/tiny-pattern-ts/compare/20308c5a6d8cccfb09b02ac2ebebd8055e91cd11...0.1.0
|
|
||||||
+4
-4
@@ -87,7 +87,7 @@ This is why every test in the suite pairs an `expectTypeOf(...)` with an `assert
|
|||||||
- **Branch naming:** `feature/<desc>` / `fix/<desc>` / `chore/<desc>`
|
- **Branch naming:** `feature/<desc>` / `fix/<desc>` / `chore/<desc>`
|
||||||
- **Starting work:** `npm run create:branch -- <prefix>/<desc>`. It refuses, without changing anything, unless the working tree is clean (untracked files included), no merge/rebase/cherry-pick is in progress, `main` matches its upstream, and `npm run test` is green on `main` — so a later failure is always attributable to your edits. The prefix is still _your_ call, inferred from the task; the script validates it rather than guessing it.
|
- **Starting work:** `npm run create:branch -- <prefix>/<desc>`. It refuses, without changing anything, unless the working tree is clean (untracked files included), no merge/rebase/cherry-pick is in progress, `main` matches its upstream, and `npm run test` is green on `main` — so a later failure is always attributable to your edits. The prefix is still _your_ call, inferred from the task; the script validates it rather than guessing it.
|
||||||
- **Merging:** `npm run create:finish` (on the branch). It asserts the same clean-tree / no-operation / current-`main` preconditions, fast-forwards a stale `main` (a true divergence is refused), merges the branch `--no-ff`, runs `npm run verify`, and deletes the branch only after the merge is green. The push is deliberately left to `create:release`, so the merge stays local and reviewable — read the diff yourself before finishing.
|
- **Merging:** `npm run create:finish` (on the branch). It asserts the same clean-tree / no-operation / current-`main` preconditions, fast-forwards a stale `main` (a true divergence is refused), merges the branch `--no-ff`, runs `npm run verify`, and deletes the branch only after the merge is green. The push is deliberately left to `create:release`, so the merge stays local and reviewable — read the diff yourself before finishing.
|
||||||
- CI runs `npm run check` + `npm run test:ci` on every push to `main` — this is the authoritative gate. The one exception: a push headed by a release commit (`:rocket: Release x.y.z`) skips the full `build`/`maintain` jobs, because `create:release` pushes the tag for that exact commit right after and the tag run is the authoritative one (see `release-gate` in [.gitea/workflows/ci.yml](./.gitea/workflows/ci.yml)).
|
- CI runs `npm run check` + `npm run test:ci` on every push to `main` — this is the authoritative gate.
|
||||||
- **Releases are NOT triggered by pushes.** Only the maintainer triggers a release (see [Publishing workflow](#publishing-workflow)).
|
- **Releases are NOT triggered by pushes.** Only the maintainer triggers a release (see [Publishing workflow](#publishing-workflow)).
|
||||||
|
|
||||||
## Publishing workflow
|
## Publishing workflow
|
||||||
@@ -95,6 +95,6 @@ This is why every test in the suite pairs an `expectTypeOf(...)` with an `assert
|
|||||||
Publishing is CI-only by policy. Local `npm publish` is not supported. The maintainer triggers releases from `main`:
|
Publishing is CI-only by policy. Local `npm publish` is not supported. The maintainer triggers releases from `main`:
|
||||||
|
|
||||||
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`. VS Code opens `CHANGELOG.md` to finalize the `[Unreleased]` notes; because pubv refuses a dirty tree, any edit is committed first (then folded into the release commit), and pubv's interactive prompt suggests a version from those notes — 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 (graduated 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 fires on both pushes: the `publish` job runs on the tag (`build` + publish-tier checks + release page + `npm publish`), while the branch run's `release-gate` job recognizes the release commit and skips `build`/`maintain` — the tag verifies the identical SHA, so no work is duplicated. 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.
|
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.
|
||||||
+1
-1
@@ -35,7 +35,7 @@ Maintenance:
|
|||||||
✔ Add a minimal dir-listing webserver to the gitea docker setup (e.g. caddy `file_server browse` reusing the existing reverse proxy, or any single-binary static server, lipanski/docker-static-website) @done (9/13/2026, 9:02:37 PM)
|
✔ Add a minimal dir-listing webserver to the gitea docker setup (e.g. caddy `file_server browse` reusing the existing reverse proxy, or any single-binary static server, lipanski/docker-static-website) @done (9/13/2026, 9:02:37 PM)
|
||||||
✔ drop the `actions/upload-artifact` coverage step in favour of the shared-dir layout @done (9/13/2026, 10:37:22 PM)
|
✔ drop the `actions/upload-artifact` coverage step in favour of the shared-dir layout @done (9/13/2026, 10:37:22 PM)
|
||||||
☐ Explore serving coverage for non-tag pushes (e.g. `main/coverage`, PR previews) @low
|
☐ Explore serving coverage for non-tag pushes (e.g. `main/coverage`, PR previews) @low
|
||||||
✔ Manually verify the coverage was created on a real tag push (needs main) @low @done (9/14/2026, 1:55:03 PM)
|
☐ Manually verify the coverage was created on a real tag push (needs main) @low
|
||||||
→ design: no deploy step in CI; the webserver just exposes the shared directory (decided over Gitea Pages / Codecov — neither confirmed available/ wanted)
|
→ design: no deploy step in CI; the webserver just exposes the shared directory (decided over Gitea Pages / Codecov — neither confirmed available/ wanted)
|
||||||
☐ serve docs over self hosted server @low
|
☐ serve docs over self hosted server @low
|
||||||
☐ Add a minimal dir-listing webserver to the gitea docker setup for serving docs (reuse existing reverse proxy)
|
☐ Add a minimal dir-listing webserver to the gitea docker setup for serving docs (reuse existing reverse proxy)
|
||||||
|
|||||||
Generated
+322
-322
File diff suppressed because it is too large.
Load diff
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "tiny-pattern-ts",
|
"name": "tiny-pattern-ts",
|
||||||
"version": "0.1.1",
|
"version": "0.0.0",
|
||||||
"description": "Pattern matching for TypeScript/ESM environments (F#-style, not regex)",
|
"description": "Pattern matching for TypeScript/ESM environments (F#-style, not regex)",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"adt",
|
"adt",
|
||||||
@@ -87,6 +87,6 @@
|
|||||||
"node": ">=26"
|
"node": ">=26"
|
||||||
},
|
},
|
||||||
"allowScripts": {
|
"allowScripts": {
|
||||||
"lefthook@2.1.14": true
|
"lefthook@2.1.12": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+1
-4
@@ -106,10 +106,7 @@ git show-ref --verify --quiet "refs/heads/${BASE}" || {
|
|||||||
# Derive the remote rather than hardcoding it: this repo has `origin` (ssh) and
|
# Derive the remote rather than hardcoding it: this repo has `origin` (ssh) and
|
||||||
# `origin_https`, and `main` tracks the latter — `git fetch origin main` would
|
# `origin_https`, and `main` tracks the latter — `git fetch origin main` would
|
||||||
# check currency against a ref that is never updated here.
|
# check currency against a ref that is never updated here.
|
||||||
# `--quiet` echoes the unresolved `main@{upstream}` literal to stdout on
|
UPSTREAM=$(git rev-parse --quiet --abbrev-ref --symbolic-full-name "${BASE}@{upstream}" 2>/dev/null || true)
|
||||||
# failure, so it cannot be paired with a `$(...) || fallback`; the non-quiet
|
|
||||||
# form prints nothing on failure and the fallback runs.
|
|
||||||
UPSTREAM=$(git rev-parse --abbrev-ref --symbolic-full-name "${BASE}@{upstream}" 2>/dev/null) || UPSTREAM=""
|
|
||||||
if [ -n "${UPSTREAM}" ]; then
|
if [ -n "${UPSTREAM}" ]; then
|
||||||
git fetch --quiet "${UPSTREAM%/*}" "${UPSTREAM#*/}" || {
|
git fetch --quiet "${UPSTREAM%/*}" "${UPSTREAM#*/}" || {
|
||||||
echo "error: '${UPSTREAM}' check failed: could not reach '${UPSTREAM%/*}'." >&2
|
echo "error: '${UPSTREAM}' check failed: could not reach '${UPSTREAM%/*}'." >&2
|
||||||
|
|||||||
+2
-5
@@ -85,10 +85,7 @@ git show-ref --verify --quiet "refs/heads/${BASE}" || {
|
|||||||
# Derive the remote rather than hardcoding it: this repo has `origin` (ssh) and
|
# Derive the remote rather than hardcoding it: this repo has `origin` (ssh) and
|
||||||
# `origin_https`, and `main` tracks the latter — `git fetch origin main` would
|
# `origin_https`, and `main` tracks the latter — `git fetch origin main` would
|
||||||
# check currency against a ref that is never updated here.
|
# check currency against a ref that is never updated here.
|
||||||
# `--quiet` echoes the unresolved `main@{upstream}` literal to stdout on
|
UPSTREAM=$(git rev-parse --quiet --abbrev-ref --symbolic-full-name "${BASE}@{upstream}" 2>/dev/null || true)
|
||||||
# failure, so it cannot be paired with a `$(...) || fallback`; the non-quiet
|
|
||||||
# form prints nothing on failure and the fallback runs.
|
|
||||||
UPSTREAM=$(git rev-parse --abbrev-ref --symbolic-full-name "${BASE}@{upstream}" 2>/dev/null) || UPSTREAM=""
|
|
||||||
BEHIND=0
|
BEHIND=0
|
||||||
if [ -n "${UPSTREAM}" ]; then
|
if [ -n "${UPSTREAM}" ]; then
|
||||||
git fetch --quiet "${UPSTREAM%/*}" "${UPSTREAM#*/}" || {
|
git fetch --quiet "${UPSTREAM%/*}" "${UPSTREAM#*/}" || {
|
||||||
@@ -116,7 +113,7 @@ if [ "${BEHIND}" -ne 0 ]; then
|
|||||||
git merge --quiet --ff-only "${UPSTREAM}"
|
git merge --quiet --ff-only "${UPSTREAM}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if ! git merge --quiet --no-ff -m ":twisted_rightwards_arrows: Merge ${START_REF} into ${BASE}" "${START_REF}"; then
|
if ! git merge --quiet --no-ff --no-edit "${START_REF}"; then
|
||||||
echo "error: merge of '${START_REF}' failed; aborting and returning to it." >&2
|
echo "error: merge of '${START_REF}' failed; aborting and returning to it." >&2
|
||||||
git merge --abort 2>/dev/null || true
|
git merge --abort 2>/dev/null || true
|
||||||
git switch --quiet "${START_REF}"
|
git switch --quiet "${START_REF}"
|
||||||
|
|||||||
+4
-33
@@ -13,12 +13,6 @@ set -eu
|
|||||||
# package.json + the lockfile; `--amend` folds them into pubv's single commit;
|
# package.json + the lockfile; `--amend` folds them into pubv's single commit;
|
||||||
# tag AFTER the amend (so the tag is never orphaned) and push.
|
# tag AFTER the amend (so the tag is never orphaned) and push.
|
||||||
#
|
#
|
||||||
# The notes are finalized in VS Code *before* pubv: the [Unreleased] body is
|
|
||||||
# what pubv's bump heuristic reads, so editing afterwards would inform the
|
|
||||||
# changelog only, not the version choice. pubv refuses a dirty tree, so that
|
|
||||||
# edit is committed as a staging commit and folded back into the single release
|
|
||||||
# commit below.
|
|
||||||
#
|
|
||||||
# Rejected: the conventional-commits family (our history is gitmoji, not
|
# Rejected: the conventional-commits family (our history is gitmoji, not
|
||||||
# Conventional; and we want hand-written notes); changesets/rtk (config + a
|
# Conventional; and we want hand-written notes); changesets/rtk (config + a
|
||||||
# heavier version/publish flow that fights our CI-only publish); knope/kacl/
|
# heavier version/publish flow that fights our CI-only publish); knope/kacl/
|
||||||
@@ -58,23 +52,12 @@ if ! git remote set-head origin --auto >/dev/null 2>&1; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# The [Unreleased] body drives pubv's bump heuristic, so finalize it first.
|
|
||||||
echo "Opening ${CHANGELOG} in VS Code to finalize the release notes..."
|
|
||||||
code --wait "${CHANGELOG}"
|
|
||||||
|
|
||||||
# pubv refuses a dirty tree (its "continue with a dirty tree?" prompt defaults
|
|
||||||
# to No), so a changed changelog must be committed before it runs. That commit
|
|
||||||
# is staging only — the fold below rewrites it into the single release commit.
|
|
||||||
NOTES_MSG=":memo: Finalize release notes"
|
|
||||||
if [ -n "$(git status --porcelain -- "${CHANGELOG}")" ]; then
|
|
||||||
echo "Committing finalized release notes..."
|
|
||||||
git add "${CHANGELOG}"
|
|
||||||
git commit -m "${NOTES_MSG}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Running pubv..."
|
echo "Running pubv..."
|
||||||
pubv --no-tag --no-push --tag-prefix=none
|
pubv --no-tag --no-push --tag-prefix=none
|
||||||
|
|
||||||
|
echo "Opening ${CHANGELOG} in VS Code..."
|
||||||
|
code --wait "${CHANGELOG}"
|
||||||
|
|
||||||
echo "Reading version from ${CHANGELOG}..."
|
echo "Reading version from ${CHANGELOG}..."
|
||||||
|
|
||||||
VERSION=$(
|
VERSION=$(
|
||||||
@@ -92,21 +75,9 @@ echo "Release version: ${VERSION}"
|
|||||||
echo "Updating package.json and package-lock.json..."
|
echo "Updating package.json and package-lock.json..."
|
||||||
npm version "${VERSION}" --no-git-tag-version
|
npm version "${VERSION}" --no-git-tag-version
|
||||||
|
|
||||||
# If pubv's graduation commit sits on top of our staging notes commit, drop it
|
|
||||||
# back into the index so the amend below rewrites the notes commit into the one
|
|
||||||
# release commit. A message check, not a flag, so a re-run after pubv aborted
|
|
||||||
# still folds a notes commit left behind by the earlier attempt.
|
|
||||||
if [ "$(git log -1 --format=%s HEAD~1 2>/dev/null || true)" = "${NOTES_MSG}" ]; then
|
|
||||||
git reset --soft HEAD~1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Amending release commit..."
|
echo "Amending release commit..."
|
||||||
git add package.json package-lock.json "${CHANGELOG}"
|
git add package.json package-lock.json "${CHANGELOG}"
|
||||||
# The exact message format is load-bearing: the `release-gate` job in
|
git commit --amend -m ":bookmark: Release ${VERSION}"
|
||||||
# .gitea/workflows/ci.yml recognizes `:rocket: Release x.y.z` on main and
|
|
||||||
# skips the full CI run, since the tag push immediately after verifies the
|
|
||||||
# identical SHA (and publishes). Keep the two in sync.
|
|
||||||
git commit --amend -m ":rocket: Release ${VERSION}"
|
|
||||||
|
|
||||||
echo "Creating tag ${VERSION}..."
|
echo "Creating tag ${VERSION}..."
|
||||||
git tag "${VERSION}"
|
git tag "${VERSION}"
|
||||||
|
|||||||
Reference in new issue
Block a user