1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
name: Publish
on:
push:
tags:
- "v*"
permissions:
contents: write
id-token: write
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: dtolnay/rust-toolchain@stable
- name: Install cargo-set-version
run: cargo install cargo-set-version
- name: Set version from tag
run: |
TAG_VERSION="${GITHUB_REF#refs/tags/v}"
CURRENT=$(grep '^version' Cargo.toml | head -1 | sed 's/.*"\(.*\)"/\1/')
if [ "$TAG_VERSION" != "$CURRENT" ]; then
cargo set-version "$TAG_VERSION"
fi
# Cache is split into restore+save so the save key is content-addressable
# via the post-fetch hash of `.xx-hocon-version`. On a fresh checkout,
# `.xx-hocon-version` is gitignored and absent at restore time, so the
# restore step relies on `restore-keys` to match the most recent entry.
# Mirrors the test.yml pattern so the publish job doesn't re-fetch on
# every tag push (avoids GitHub API rate-limit + transient network
# failures during release). (closes #101)
- name: Restore expected JSON cache
uses: actions/cache/restore@v5
with:
path: |
tests/testdata/expected
.xx-hocon-version
key: xx-hocon-expected-pending
restore-keys: xx-hocon-expected-
- name: Fetch expected JSON sidecars from xx.hocon
# tests/concat_errors_test.rs, tests/s8_unquoted_starts.rs, and
# other sidecar-driven conformance tests need `tests/testdata/expected/`
# populated. That dir is gitignored (repo convention — sidecars are
# fetched, not vendored), so without this step the publish-time
# `cargo test` fails with "no expected sidecar" panics on ce01-ce15
# / us02-us30 etc. Mirrors ts.hocon release.yml fix from v1.2.0
# publish regression. The Makefile's early-exit on matching pin SHA
# makes this a no-op when the cache hit above is fresh.
run: make testdata
# Default `if: success()` — skip the save if `make testdata` failed so
# we don't write a partial/missing-pin cache under the empty-hash key
# (which would collapse to the constant `xx-hocon-expected-` and
# recreate the bug this PR is fixing). Copilot review thread.
- name: Save expected JSON cache
uses: actions/cache/save@v5
with:
path: |
tests/testdata/expected
.xx-hocon-version
key: xx-hocon-expected-${{ hashFiles('.xx-hocon-version') }}
- run: cargo test
- run: cargo test --features serde
- name: Extract CHANGELOG section for this tag
# Pulls the section between `## [VERSION]` and the next `## [`
# from CHANGELOG.md so the GitHub Release body matches what's
# already committed and reviewed.
#
# Uses `index($0, str) == 1` (prefix match) rather than awk regex
# so SemVer build metadata (`+...`) and other regex metacharacters
# in version strings can't break or overmatch the section header.
#
# Runs BEFORE `cargo publish` (and the GitHub Release step below)
# so that a missing/incorrect CHANGELOG heading fails the run
# without first publishing to crates.io — crates.io doesn't allow
# republishing the same version, so an irreversible publish
# followed by a failed release step would leave a half-shipped tag.
run: |
VERSION="${GITHUB_REF#refs/tags/v}"
awk -v ver="$VERSION" '
index($0, "## [" ver "]") == 1 { flag=1; next }
flag && index($0, "## [") == 1 { flag=0 }
flag
' CHANGELOG.md > /tmp/release-notes.md
if [ ! -s /tmp/release-notes.md ]; then
echo "::error::No CHANGELOG section found for v${VERSION}; aborting release so an empty body doesn't ship"
exit 1
fi
- name: Create GitHub Release
# Runs before `cargo publish` so a Release-creation failure doesn't
# leave us with a crate published but no GitHub Release entry
# (crates.io forbids republishing the same version, so a retry
# would fail at `cargo publish` and never reach this step again).
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ github.ref_name }}
name: ${{ github.ref_name }}
body_path: /tmp/release-notes.md
fail_on_unmatched_files: false
make_latest: true
- name: Authenticate with crates.io
id: auth
uses: rust-lang/crates-io-auth-action@v1
- name: Publish
run: cargo publish --allow-dirty
env:
CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }}