diff --git a/.github/workflows/auto-tag.yml b/.github/workflows/auto-tag.yml index acee6ee..6e14e3b 100644 --- a/.github/workflows/auto-tag.yml +++ b/.github/workflows/auto-tag.yml @@ -1,16 +1,29 @@ # Auto-tag on version bump. # -# Watches `main` and, whenever a commit lands that changes -# `[package] version` in Cargo.toml, creates a matching -# `vX.Y.Z` tag and pushes it. The tag push then triggers the -# tag-driven release workflow (cross-compile + GitHub Release + -# cargo publish for CLIs, lib publish for libs). +# Watches `main` and, whenever a commit lands that changes the +# top-level `version = "..."` in Cargo.toml (`[package]` for +# single-crate repos or `[workspace.package]` for workspace roots), +# creates a matching `vX.Y.Z` tag and pushes it. The tag push then +# triggers the tag-driven release workflow (cross-compile + GitHub +# Release + cargo publish for CLIs, lib publish for libs). # # This file is kata-managed via `pj-rust`'s template.toml # (rendered out of `auto-tag.yml.tera`, the suffix stripped on # the consumer side). The `.tera` suffix keeps GitHub Actions # from running the source inside `pj-rust` itself, the same # protection `ci.yml.tera` and `release.yml.template` use. +# +# Token: this workflow pushes the tag with `KATA_APPLY_TOKEN` +# (a PAT) rather than the default `GITHUB_TOKEN`. The default +# token would create the tag fine, but GitHub deliberately +# refuses to fire downstream workflows (release.yml) from refs +# pushed by `GITHUB_TOKEN` to prevent recursive workflow runs. +# Using the PAT — the same one `kata-apply.yml` already relies +# on for the exact same downstream-trigger reason — restores +# the tag → release.yml chain. Each consumer repo needs a +# `KATA_APPLY_TOKEN` secret set; this is documented in +# pj-base's apply-workflow notes alongside the kata-apply +# bootstrap step. name: Auto-tag on version bump @@ -28,28 +41,43 @@ jobs: # `fetch-depth: 2` so HEAD~1 (the parent commit) is in scope # for the Cargo.toml diff. fetch-depth: 0 would also work but # is wasteful when we only need one parent. + # + # `token: KATA_APPLY_TOKEN` makes actions/checkout persist + # the PAT as the credential the later `git push` call will + # use. Without this the tag push goes out under the default + # `GITHUB_TOKEN` and downstream workflows stay silent. - name: Checkout uses: actions/checkout@v6.0.2 with: fetch-depth: 2 + token: ${{ secrets.KATA_APPLY_TOKEN }} - # Extract `version = "..."` from Cargo.toml under [package]. - # Plain grep is enough because yukimemi/* crates are - # single-package (no [workspace] virtual manifest), so the - # first `^version =` line is always the package version. + # Extract the top-level `version = "..."` from Cargo.toml. + # Plain grep is enough: the first line matching `^version + # = "` is the package version for both + # `[package]` (single crate) and `[workspace.package]` + # (workspace root). The whitespace tolerance is what lets + # alignment-style formatting (`version = "x"`) work + # alongside the more common `version = "x"`. + # + # `|| true` on the OLD extraction is what keeps the pipeline + # alive when HEAD~1 has no Cargo.toml (first-ever commit) OR + # the grep finds no match — without it, pipefail + set -e would + # kill the step before we get a chance to classify "no match" + # as "no previous version". - name: Detect version bump id: ver shell: bash run: | set -euo pipefail extract() { - grep -E '^version = "' "$1" | head -n1 | sed -E 's/.*"([^"]+)".*/\1/' + grep -E '^version[[:space:]]*=[[:space:]]*"' "$1" | head -n1 | sed -E 's/.*"([^"]+)".*/\1/' || true } NEW=$(extract Cargo.toml) # HEAD~1 might not have Cargo.toml (very first commit on a # new repo) — fall back to empty string so the diff below # still classifies it as a bump. - OLD=$(git show HEAD~1:Cargo.toml 2>/dev/null | grep -E '^version = "' | head -n1 | sed -E 's/.*"([^"]+)".*/\1/' || true) + OLD=$(git show HEAD~1:Cargo.toml 2>/dev/null | grep -E '^version[[:space:]]*=[[:space:]]*"' | head -n1 | sed -E 's/.*"([^"]+)".*/\1/' || true) OLD="${OLD:-}" echo "new=$NEW" >> "$GITHUB_OUTPUT" echo "old=$OLD" >> "$GITHUB_OUTPUT" @@ -65,9 +93,13 @@ jobs: if: steps.ver.outputs.bumped == 'true' shell: bash env: - # `gh` reads this; `git push` uses the default token from - # actions/checkout's persisted credentials. - GH_TOKEN: ${{ github.token }} + # `gh` reads this; the matching `git push` uses the PAT + # that actions/checkout persisted above. Using + # `KATA_APPLY_TOKEN` for both keeps "the tag was pushed + # by a user, not a workflow" — which is the bit GitHub + # checks before firing downstream workflows like + # release.yml. + GH_TOKEN: ${{ secrets.KATA_APPLY_TOKEN }} run: | set -euo pipefail TAG="v${{ steps.ver.outputs.new }}" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dd219a2..02403fc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -60,8 +60,17 @@ jobs: - run: cargo fmt --all -- --check clippy: - name: clippy - runs-on: ubuntu-latest + # Matrix across all three OSes so OS-specific dead code (e.g. helpers + # gated under `cfg(target_os = "windows")`) is caught everywhere, not + # just on the maintainer's machine. Without this, a Windows-only + # helper looks fine on a Windows pre-push hook but blows up the Linux + # clippy run. + name: clippy (${{ matrix.os }}) + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] steps: - uses: actions/checkout@v6.0.2 - uses: dtolnay/rust-toolchain@stable @@ -81,3 +90,28 @@ jobs: - uses: dtolnay/rust-toolchain@stable - uses: Swatinem/rust-cache@v2.9.1 - run: cargo check --locked --all-targets + + coverage: + # llvm-cov on Linux + Codecov upload. taiki-e/install-action grabs the + # cargo-llvm-cov binary. codecov-action@v5 nominally accepts tokenless + # uploads from public repos, but in practice Codecov rejects them with + # 'Token required - not valid tokenless upload', so plumb CODECOV_TOKEN + # through as a repo secret. `fail_ci_if_error: false` still keeps a + # flaky upload from gating merges. + name: coverage + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6.0.2 + - uses: dtolnay/rust-toolchain@stable + with: + components: llvm-tools-preview + - uses: Swatinem/rust-cache@v2.9.1 + - uses: taiki-e/install-action@v2 + with: + tool: cargo-llvm-cov + - run: cargo llvm-cov --workspace --lcov --output-path lcov.info + - uses: codecov/codecov-action@v5 + with: + token: ${{ secrets.CODECOV_TOKEN }} + files: lcov.info + fail_ci_if_error: false diff --git a/.kata/applied.toml b/.kata/applied.toml index 9143209..adbda37 100644 --- a/.kata/applied.toml +++ b/.kata/applied.toml @@ -1,5 +1,5 @@ preset = "github.com/yukimemi/pj-presets:rust-cli" -applied_at = "2026-05-15T04:39:12.00633478Z" +applied_at = "2026-05-16T04:29:47.102980655Z" [[templates]] source = "github.com/yukimemi/pj-base" @@ -8,7 +8,7 @@ version = "0.10.0" [[templates]] source = "github.com/yukimemi/pj-rust" -rev = "8f059bb3dc6e14792915db70fff4b16f9af92080" +rev = "247125a65701070f6bc9e124c4283bae9e902017" version = "0.5.0" [[templates]] @@ -33,10 +33,10 @@ content_hash = "486c974de88903113ed99b4e643432b7050e88f1031276f5263e8da7b43fe11e content_hash = "1a4b579b1b643a41dbf97d8834ff1f3f1fe532ff863d0f861431cf3ca47d31e2" [files.".github/workflows/auto-tag.yml"] -content_hash = "31ba3d93dfa16bfa95d81618f613a02d3975b60ecd4eda36abc0d6e18ea69972" +content_hash = "0796dfb281c7447610035360622ddada137a8e2d89efa574aea89374676d768b" [files.".github/workflows/ci.yml"] -content_hash = "3a35eeef0bd68f37a34f7168d82133305954283d9bc724208aeafce55da20d91" +content_hash = "d4c0dd6f0e7c9a565a7d8f45763069386b19d38f88f878e0e4288fd60665a10d" [files.".github/workflows/kata-apply.yml"] content_hash = "bc0e3def04b634949297d60322999a27f53bffc71b6cb662ae58ac8087e78bed"