Compare commits
1
Commits
0.1.5
..
376eb42bcb
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
376eb42bcb |
No files matched your search
+17
-95
@@ -12,64 +12,17 @@ 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
|
||||||
# `image` extends the runner's default job image (catthehacker/act)
|
# Bind-mount the shared pages tree so the coverage step below can write
|
||||||
# with Node 26 pre-planted in the tool cache layout, so setup-node's
|
# into it. The runner whitelists this path via `container.valid_volumes`
|
||||||
# version probe hits and never downloads (see docker/Dockerfile). The
|
# (docker-space `setup/gitea.sh`); `image` is omitted on purpose so the
|
||||||
# tag MUST equal the exact version pinned in `.node-version`; the bump
|
# runner keeps using its default job image.
|
||||||
# ritual is documented in CONTRIBUTING.md § CI runner image. The volume
|
|
||||||
# bind-mounts the shared pages tree so the
|
|
||||||
# coverage step below can write into it; the runner whitelists this
|
|
||||||
# path via `container.valid_volumes` (docker-space `setup/gitea.sh`).
|
|
||||||
container:
|
container:
|
||||||
image: gitea.e1nsnull.de/tmu/act-ci:26.8.2
|
|
||||||
volumes:
|
volumes:
|
||||||
- /data/gitea-pages:/data/gitea-pages
|
- /data/gitea-pages:/data/gitea-pages
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
# Fail fast when the job container is not the baked image: a stale
|
|
||||||
# tag on the runner (`forcePull=false` in its pull log) silently
|
|
||||||
# reintroduces the per-job download. Cheap, and it names the
|
|
||||||
# invariant.
|
|
||||||
- name: Assert the baked tool cache is present
|
|
||||||
run: |
|
|
||||||
test -f "/opt/hostedtoolcache/node/$(tr -d '[:space:]' < .node-version)/x64.complete"
|
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version-file: .node-version
|
node-version-file: .node-version
|
||||||
@@ -116,14 +69,8 @@ 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
|
||||||
# Same baked image as `build` — without it this job re-downloads Node
|
|
||||||
# per run (see docker/Dockerfile).
|
|
||||||
container:
|
|
||||||
image: gitea.e1nsnull.de/tmu/act-ci:26.8.2
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
@@ -137,30 +84,24 @@ 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
|
||||||
# Same baked image as `build` — setup-node still owns the registry-url
|
|
||||||
# `.npmrc` rewrite here; only the Node download is skipped.
|
|
||||||
container:
|
|
||||||
image: gitea.e1nsnull.de/tmu/act-ci:26.8.2
|
|
||||||
# The release page is created with the run's automatic Gitea token
|
# The release page is created with the run's automatic Gitea token
|
||||||
# (`github.token`), so it needs `contents: write`.
|
# (`github.token`), not `NPM_TOKEN`, so it needs `contents: write`.
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
# The npm token is optional: `secrets` is not an allowed context in a
|
|
||||||
# step `if` (see GitHub's context-availability table), so it is lifted
|
|
||||||
# into job-level `env`, where an unset secret arrives as the empty
|
|
||||||
# string and skips the publish rather than attempting an unauthenticated
|
|
||||||
# one. Set NPM_TOKEN in the Gitea repo: Settings → Actions → Secrets.
|
|
||||||
env:
|
|
||||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
||||||
steps:
|
steps:
|
||||||
|
# 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
|
||||||
|
# NPM_TOKEN in the Gitea repo: Settings → Actions → Secrets.
|
||||||
|
- name: Assert NPM_TOKEN is configured
|
||||||
|
run: |
|
||||||
|
if [ -z "${{ secrets.NPM_TOKEN }}" ]; then
|
||||||
|
echo "::error::NPM_TOKEN secret is not set — refusing to publish."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version-file: .node-version
|
node-version-file: .node-version
|
||||||
# Same lockfile/key as `build`, and tag runs can read caches
|
|
||||||
# saved on `main` — without this, every release pays a cold
|
|
||||||
# `npm ci` despite the warm shared npm cache.
|
|
||||||
cache: "npm"
|
|
||||||
registry-url: "https://registry.npmjs.org/"
|
registry-url: "https://registry.npmjs.org/"
|
||||||
- run: npm ci
|
- run: npm ci
|
||||||
# Consume the dist/ that `build` produced and gated, instead of
|
# Consume the dist/ that `build` produced and gated, instead of
|
||||||
@@ -181,28 +122,9 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
TAG_REF: ${{ gitea.ref }}
|
TAG_REF: ${{ gitea.ref }}
|
||||||
run: ./scripts/release-notes.sh "${TAG_REF#refs/tags/}" > release-notes.md
|
run: ./scripts/release-notes.sh "${TAG_REF#refs/tags/}" > release-notes.md
|
||||||
- name: Create the Gitea release
|
- uses: https://gitea.com/actions/gitea-release-action@v1
|
||||||
id: gitea_release
|
|
||||||
uses: https://gitea.com/actions/gitea-release-action@v1
|
|
||||||
with:
|
with:
|
||||||
body_path: release-notes.md
|
body_path: release-notes.md
|
||||||
- name: Publish to npm
|
- run: npm publish --access public
|
||||||
id: npm_publish
|
|
||||||
if: env.NPM_TOKEN != ''
|
|
||||||
run: npm publish --access public
|
|
||||||
env:
|
env:
|
||||||
NODE_AUTH_TOKEN: ${{ env.NPM_TOKEN }}
|
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||||
# All-or-nothing: the tag is only released once *both* the release
|
|
||||||
# page and the npm package are up. A skipped npm publish (NPM_TOKEN
|
|
||||||
# unset) has no `success` outcome, so `always()` reaches this check
|
|
||||||
# even after a failure and turns the skipped half into an explicit
|
|
||||||
# red job instead of a silently green one.
|
|
||||||
- name: Require both releases
|
|
||||||
if: always()
|
|
||||||
run: |
|
|
||||||
GITEA="${{ steps.gitea_release.outcome }}"
|
|
||||||
NPM="${{ steps.npm_publish.outcome }}"
|
|
||||||
if [ "${GITEA}" != success ] || [ "${NPM}" != success ]; then
|
|
||||||
echo "::error::incomplete release — gitea=${GITEA:-skipped} npm=${NPM:-skipped}"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
+1
-1
@@ -1 +1 @@
|
|||||||
26.8.2
|
26
|
||||||
+1
-32
@@ -7,35 +7,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
## [0.1.5] - 2026-09-15
|
[Unreleased]: https://gitea.e1nsnull.de/tmu/tiny-pattern-ts
|
||||||
|
|
||||||
- improve CI configuration
|
|
||||||
|
|
||||||
## [0.1.4] - 2026-09-14
|
|
||||||
|
|
||||||
- fix CI to node from custom image
|
|
||||||
- upgrade dependencies
|
|
||||||
|
|
||||||
## [0.1.3] - 2026-09-14
|
|
||||||
|
|
||||||
- change to custom image for CI
|
|
||||||
|
|
||||||
## [0.1.2] - 2026-09-14
|
|
||||||
|
|
||||||
- upgrade dependencies
|
|
||||||
|
|
||||||
## [0.1.1] - 2026-09-14
|
|
||||||
|
|
||||||
- upgrade dependencies
|
|
||||||
|
|
||||||
## [0.1.0] - 2026-09-14
|
|
||||||
|
|
||||||
- basic setup
|
|
||||||
|
|
||||||
[Unreleased]: https://gitea.e1nsnull.de/tmu/tiny-pattern-ts/compare/0.1.5...main
|
|
||||||
[0.1.5]: https://gitea.e1nsnull.de/tmu/tiny-pattern-ts/compare/0.1.4...0.1.5
|
|
||||||
[0.1.4]: https://gitea.e1nsnull.de/tmu/tiny-pattern-ts/compare/0.1.3...0.1.4
|
|
||||||
[0.1.3]: https://gitea.e1nsnull.de/tmu/tiny-pattern-ts/compare/0.1.2...0.1.3
|
|
||||||
[0.1.2]: https://gitea.e1nsnull.de/tmu/tiny-pattern-ts/compare/0.1.1...0.1.2
|
|
||||||
[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
-23
@@ -46,7 +46,7 @@ Separately, some top-level scripts are **bare** (no prefix): the entry points th
|
|||||||
The tools are organized into a feedback ladder. Each tier catches different things at different costs; the rule of thumb is "earlier tiers fire more often, faster tiers catch less, slower tiers are more thorough":
|
The tools are organized into a feedback ladder. Each tier catches different things at different costs; the rule of thumb is "earlier tiers fire more often, faster tiers catch less, slower tiers are more thorough":
|
||||||
|
|
||||||
| Tier | When | What it runs | Time |
|
| Tier | When | What it runs | Time |
|
||||||
| -------------------------------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- |
|
| -------------------------------- | ----------------------- | -------------------------------------------------------------------------------------------------------- | ----- |
|
||||||
| `npm run watch` | manual | `watch:test` — re-runs tests on file save | ~0.1s |
|
| `npm run watch` | manual | `watch:test` — re-runs tests on file save | ~0.1s |
|
||||||
| Pre-commit (auto) | on stage | tsc + oxlint + oxfmt + cspell (staged files only) | ~1.3s |
|
| Pre-commit (auto) | on stage | tsc + oxlint + oxfmt + cspell (staged files only) | ~1.3s |
|
||||||
| Pre-push (auto) | on push | `npm test` (full tsc + unit tests) | ~3.5s |
|
| Pre-push (auto) | on push | `npm test` (full tsc + unit tests) | ~3.5s |
|
||||||
@@ -56,7 +56,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` / tag | `build` job (build + correctness + packaging) — see [.gitea/workflows/ci.yml](./.gitea/workflows/ci.yml) | ~30s+ |
|
| CI build (auto) | on push to `main` / tag | `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 | packaging checks + `publish:publint` / `publish:attw`, then the Gitea release page and `npm publish` (skipped, and the job failed, without `NPM_TOKEN`) | ~15s |
|
| 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?
|
||||||
|
|
||||||
@@ -87,26 +87,9 @@ 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)).
|
||||||
|
|
||||||
## CI runner image
|
|
||||||
|
|
||||||
The `build` / `maintain` / `publish` jobs run in `gitea.e1nsnull.de/tmu/act-ci:<version>` ([docker/Dockerfile](./docker/Dockerfile)) — the runner's default act image with the Node distribution overlaid at the exact `/opt/hostedtoolcache` layout `actions/setup-node` probes before downloading, so no job pays the ~50 MB fetch. The image tag MUST equal the exact version pinned in `.node-version`; `release-gate` uses no Node and stays on the default image. The script is deliberately NOT an `npm run` script: building requires a docker daemon and registry credentials, so it belongs to no feedback tier — per [Script prefix convention](#script-prefix-convention), no existing prefix fits and that is the signal.
|
|
||||||
|
|
||||||
Bumping Node is one coordinated change, committed as a unit:
|
|
||||||
|
|
||||||
1. Edit `.node-version` to the new exact `x.y.z` — floats like `26` resolve to the latest patch at runtime and silently bust the baked entry; `scripts/runner-image.sh` refuses them.
|
|
||||||
2. `docker login gitea.e1nsnull.de` (user + package/access token), then `./scripts/runner-image.sh --push` — it reads the version from `.node-version` and builds/pushes `<IMAGE_REPO>:<version>`.
|
|
||||||
3. Repoint the three `container.image` tags in [.gitea/workflows/ci.yml](./.gitea/workflows/ci.yml) to the same version.
|
|
||||||
|
|
||||||
Skipping step 2 fails CI at image pull; skipping step 3 silently reverts to the per-job download.
|
|
||||||
|
|
||||||
Two invariants the image must satisfy for the probe to hit, both easy to break:
|
|
||||||
|
|
||||||
- **The `x64.complete` marker.** `actions/tool-cache` accepts a cached tool only when `<version>/<arch>.complete` exists next to the directory (`tc.find()` checks it); a plausible-looking `node/<version>/x64/` alone is ignored and the download happens anyway. See the comment in [docker/Dockerfile](./docker/Dockerfile).
|
|
||||||
- **Tag freshness.** The tag encodes only the Node version, so a Dockerfile change (like the marker above) produces _new content under an unchanged tag_. `act_runner` skips the pull when a tag of that name already exists locally (`forcePull=false` in the job log), so the runner must either force-pull (`force_pull` under `container:` in its `config.yaml`, if the installed version has it) or have the tag removed on the runner host (`docker rmi gitea.e1nsnull.de/tmu/act-ci:<version>`) after any image change. Symptom of getting this wrong: CI keeps running the previous image while the registry shows the new digest.
|
|
||||||
|
|
||||||
## Publishing workflow
|
## Publishing workflow
|
||||||
|
|
||||||
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`:
|
||||||
@@ -114,6 +97,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`. 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`. 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.
|
||||||
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 (graduated 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.
|
||||||
|
|
||||||
The Gitea release page uses the run's automatic token (`github.token`), so it only needs `contents: write`. `npm publish` is gated on `NPM_TOKEN`, lifted into job-level `env` because `secrets` is not an allowed context in a step `if`: an unset secret skips the publish instead of attempting an unauthenticated one. A tag is all-or-nothing, though — a final `always()` step fails the job unless both the release page and `npm publish` reported `success`, so a skipped or failed npm half turns the job red rather than silently green. Set `NPM_TOKEN` (npm publish rights) under Settings → Actions → Secrets.
|
|
||||||
+1
-18
@@ -24,7 +24,6 @@ Bugs:
|
|||||||
Enhancements:
|
Enhancements:
|
||||||
|
|
||||||
Documentation:
|
Documentation:
|
||||||
☐ Clean up CONTRIBUTING.md and README.md, create docs
|
|
||||||
☐ Add usage examples to README.md
|
☐ Add usage examples to README.md
|
||||||
☐ Create `examples/` directory with runnable snippets
|
☐ Create `examples/` directory with runnable snippets
|
||||||
☐ Add comparison section vs. other TS pattern-matching libs
|
☐ Add comparison section vs. other TS pattern-matching libs
|
||||||
@@ -36,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)
|
||||||
@@ -46,19 +45,3 @@ Maintenance:
|
|||||||
☐ Add a minimal dir-listing webserver to the gitea docker setup for serving landing page (reuse existing reverse proxy)
|
☐ Add a minimal dir-listing webserver to the gitea docker setup for serving landing page (reuse existing reverse proxy)
|
||||||
☐ CI writes landing page to a shared volume keyed by project + tag (e.g. `/landing/tiny-pattern-ts/<tag>/`)
|
☐ CI writes landing page to a shared volume keyed by project + tag (e.g. `/landing/tiny-pattern-ts/<tag>/`)
|
||||||
☐ Browse to `…/tiny-pattern-ts/index.html` in the browser
|
☐ Browse to `…/tiny-pattern-ts/index.html` in the browser
|
||||||
✔ Stop Gitea CI re-downloading Node on every job @done
|
|
||||||
✔ Share the warm npm cache with the publish job @done
|
|
||||||
✔ Bake Node into the CI job image (docker/Dockerfile, container.image in ci.yml) @done
|
|
||||||
✔ Build/push gitea.e1nsnull.de/tmu/act-ci:26.8.2 and confirm setup-node skips the download @done
|
|
||||||
✔ Write the `<version>/x64.complete` marker — actions/tool-cache ignores a bare directory, so the probe missed and the download continued @done
|
|
||||||
✔ Log tool-cache state from the job container to find it (temporary, removed once understood) @done
|
|
||||||
✔ Guard the invariant in CI (`Assert the baked tool cache is present`) @done
|
|
||||||
☐ Enable force-pull for the runner so a changed act-ci image is never missed @low
|
|
||||||
→ the tag encodes only the Node version, so a Dockerfile change yields new content under an unchanged tag; with `forcePull=false` the runner keeps the old image (see CONTRIBUTING § CI runner image)
|
|
||||||
✔ Improve CI publish @done
|
|
||||||
✔ Check whether publish job is only run on tags, if not, guard it @done
|
|
||||||
✔ Gate only single steps @done
|
|
||||||
✔ Do not publish to npm, if NPM_TOKEN is not set (e.g. PRs from forks) @done
|
|
||||||
✔ Do not publish to Gitea — uses the run's automatic `github.token`, so no secret gate is needed @done
|
|
||||||
✔ Otherwise run the steps @done
|
|
||||||
✔ Fail the job unless both the Gitea release and npm publish succeeded @done
|
|
||||||
@@ -27,14 +27,6 @@
|
|||||||
"knope",
|
"knope",
|
||||||
"runwisp",
|
"runwisp",
|
||||||
"glab",
|
"glab",
|
||||||
"hostedtoolcache",
|
|
||||||
"nodebase",
|
|
||||||
"frontends",
|
|
||||||
"catthehacker",
|
|
||||||
"nsnull",
|
|
||||||
"dedup",
|
|
||||||
"dedupe",
|
|
||||||
"repoint",
|
|
||||||
"postversion",
|
"postversion",
|
||||||
"prebuild",
|
"prebuild",
|
||||||
"Zilla",
|
"Zilla",
|
||||||
|
|||||||
@@ -1,51 +0,0 @@
|
|||||||
# CI job image for the Gitea act_runner: the runner's default job image with
|
|
||||||
# Node pre-planted where actions/setup-node looks first.
|
|
||||||
#
|
|
||||||
# Why this layout: setup-node ignores `node` on PATH; its only fast path is a
|
|
||||||
# probe of /opt/hostedtoolcache/node/<version>/<arch>. Without an entry there
|
|
||||||
# it downloads the ~50 MB distribution on EVERY job (the runner's job
|
|
||||||
# containers are ephemeral, so its tool cache never survives a job). The
|
|
||||||
# official node images keep exactly the layout setup-node expects under
|
|
||||||
# /usr/local, so this layer is a pure file overlay — no scripts, no env.
|
|
||||||
#
|
|
||||||
# Why not a host bind of /opt/hostedtoolcache: binds never self-prune. Docker
|
|
||||||
# images are content-addressed: the base layers dedupe against the act image
|
|
||||||
# the host already has, and `docker image prune` / re-pulls are the cleanup
|
|
||||||
# story.
|
|
||||||
#
|
|
||||||
# NODE_VERSION must match `.node-version` exactly. setup-node resolves a float
|
|
||||||
# like `26` to the latest known patch at runtime, so a bump silently busts the
|
|
||||||
# baked entry; `.node-version` is pinned to x.y.z and scripts/runner-image.sh
|
|
||||||
# guards the coupling. Rebuild + repoint `container.image` in
|
|
||||||
# .gitea/workflows/ci.yml on every bump.
|
|
||||||
#
|
|
||||||
# The extra `nodebase` stage is load-bearing: `COPY --from=` resolves its value
|
|
||||||
# as a *stage name* at parse time, before build args exist, so
|
|
||||||
# `COPY --from=node:${NODE_VERSION}` collapses to the invalid `node:` on
|
|
||||||
# frontends that do not expand args there. ARGs declared before the first FROM
|
|
||||||
# *are* expanded in FROM, so routing through a named stage works everywhere.
|
|
||||||
|
|
||||||
# Global scope: only visible to FROM lines, but that is exactly where we need it.
|
|
||||||
ARG NODE_VERSION=26.8.2
|
|
||||||
FROM node:${NODE_VERSION} AS nodebase
|
|
||||||
|
|
||||||
FROM catthehacker/ubuntu:act-latest
|
|
||||||
|
|
||||||
# ARGs do not cross stage boundaries; redeclare (with the same default, so a
|
|
||||||
# bare `docker build -f docker/Dockerfile .` still works) for the paths below.
|
|
||||||
# Keep this default in sync with the global one above.
|
|
||||||
ARG NODE_VERSION=26.8.2
|
|
||||||
|
|
||||||
# node image: bin/ + lib/ under /usr/local → tool cache: bin/ + lib/ under <ver>/x64.
|
|
||||||
COPY --from=nodebase /usr/local /opt/hostedtoolcache/node/${NODE_VERSION}/x64
|
|
||||||
|
|
||||||
# actions/tool-cache only accepts a cached tool when the sibling marker file
|
|
||||||
# "<version>/<arch>.complete" exists — tc.find() checks it and falls back to
|
|
||||||
# downloading otherwise, however complete the directory is. The marker is what
|
|
||||||
# tc.cacheDir() writes after *it* installs a tool, so a pre-baked entry has to
|
|
||||||
# reproduce it explicitly.
|
|
||||||
RUN touch "/opt/hostedtoolcache/node/${NODE_VERSION}/x64.complete"
|
|
||||||
|
|
||||||
# Fail the build (not CI) if the overlay or the version arg were wrong.
|
|
||||||
# Shell form on purpose: exec form (`RUN [...]`) does not expand ARG values.
|
|
||||||
RUN "/opt/hostedtoolcache/node/${NODE_VERSION}/x64/bin/node" --version
|
|
||||||
Generated
+486
-486
File diff suppressed because it is too large.
Load diff
+4
-4
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "tiny-pattern-ts",
|
"name": "tiny-pattern-ts",
|
||||||
"version": "0.1.5",
|
"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",
|
||||||
@@ -77,8 +77,8 @@
|
|||||||
"expect-type": "1.4.0",
|
"expect-type": "1.4.0",
|
||||||
"knip": "^6.34.0",
|
"knip": "^6.34.0",
|
||||||
"lefthook": "^2.1.12",
|
"lefthook": "^2.1.12",
|
||||||
"oxfmt": "^0.68.0",
|
"oxfmt": "^0.66.0",
|
||||||
"oxlint": "^1.83.0",
|
"oxlint": "^1.81.0",
|
||||||
"oxlint-tsgolint": "^7.0.2001",
|
"oxlint-tsgolint": "^7.0.2001",
|
||||||
"publint": "^0.3.24",
|
"publint": "^0.3.24",
|
||||||
"typescript": "^7.0.2"
|
"typescript": "^7.0.2"
|
||||||
@@ -87,6 +87,6 @@
|
|||||||
"node": ">=26"
|
"node": ">=26"
|
||||||
},
|
},
|
||||||
"allowScripts": {
|
"allowScripts": {
|
||||||
"lefthook@2.1.14": true
|
"lefthook@2.1.12": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+1
-5
@@ -102,11 +102,7 @@ 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}"
|
||||||
|
|||||||
@@ -1,33 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
# Build (and optionally push) the CI job image from docker/Dockerfile.
|
|
||||||
# Run wherever docker + registry credentials live (the runner host, or any
|
|
||||||
# machine that can reach the registry). The registry/repo below MUST match
|
|
||||||
# the `container.image` references in .gitea/workflows/ci.yml — the runner
|
|
||||||
# pulls the image by name.
|
|
||||||
#
|
|
||||||
# Usage: scripts/runner-image.sh [--push]
|
|
||||||
|
|
||||||
IMAGE_REPO="gitea.e1nsnull.de/tmu/act-ci"
|
|
||||||
|
|
||||||
NODE_VERSION="$(tr -d '[:space:]' < .node-version)"
|
|
||||||
if [[ ! "${NODE_VERSION}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
|
||||||
echo "error: .node-version must be pinned to an exact x.y.z, got '${NODE_VERSION}'." >&2
|
|
||||||
echo " setup-node resolves floats like '26' to the latest patch at runtime," >&2
|
|
||||||
echo " which silently busts the tool-cache entry baked into the image." >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
IMAGE="${IMAGE_REPO}:${NODE_VERSION}"
|
|
||||||
|
|
||||||
# --pull: refresh the act base layer so the derivative does not float on an
|
|
||||||
# aging default image forever (layer dedup keeps this cheap).
|
|
||||||
docker build --pull --build-arg "NODE_VERSION=${NODE_VERSION}" -t "${IMAGE}" -f docker/Dockerfile .
|
|
||||||
|
|
||||||
if [[ "${1:-}" == "--push" ]]; then
|
|
||||||
docker push "${IMAGE}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "built ${IMAGE}"
|
|
||||||
echo "reminder: bump container.image in .gitea/workflows/ci.yml to this tag"
|
|
||||||
Reference in new issue
Block a user