Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 7 additions & 28 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -186,34 +186,13 @@ jobs:

- id: assets
name: Resolve per-binary Windows archives
shell: bash
env:
CHOCOLATEY: ${{ steps.meta.outputs.chocolatey-base-matrix }}
WINGET: ${{ steps.meta.outputs.winget-base-matrix }}
VERSION: ${{ steps.meta.outputs.version }}
run: |
set -euo pipefail
# The snapshot build names its archives with the snapshot version
# (e.g. 1.2.1-SNAPSHOT-abc1234); the published release uses the final
# version, so rewrite that substring before handing names to the
# Windows channels.
snapshot_version="$(jq -r '.version' dist/metadata.json)"
[ -n "$snapshot_version" ] && [ "$snapshot_version" != null ] || { echo "::error::dist/metadata.json has no version; the snapshot goreleaser step above must write it (check GORELEASER_CURRENT_TAG and the goreleaser config)"; exit 1; }
enrich() {
printf '%s' "$1" | jq -c --slurpfile artifacts dist/artifacts.json --arg snap "$snapshot_version" --arg final "$VERSION" '
def owns($name):
(.extra.ID // "") == $name or
((.extra.Binaries // []) | index($name) != null);
def finalize: split($snap) | join($final);
def asset($name; $arch):
[$artifacts[0][] | select(.type == "Archive" and .goos == "windows" and .goarch == $arch and owns($name)) | .name | finalize]
| if length == 1 then .[0] else error("expected exactly one windows/" + $arch + " archive for " + $name + "; got " + (length|tostring)) end;
map(. + {x64_asset: asset(.name; "amd64"), arm64_asset: asset(.name; "arm64")})'
}
{
echo "chocolatey-matrix=$(enrich "$CHOCOLATEY")"
echo "winget-matrix=$(enrich "$WINGET")"
} >> "$GITHUB_OUTPUT"
uses: $/actions/release-assets
with:
artifacts-path: dist/artifacts.json
metadata-path: dist/metadata.json
chocolatey-matrix: ${{ steps.meta.outputs.chocolatey-base-matrix }}
winget-matrix: ${{ steps.meta.outputs.winget-base-matrix }}
version: ${{ steps.meta.outputs.version }}

# CGO-darwin pre-publish gate — prove the Keychain backend is present on
# the snapshot artifacts BEFORE we publish. Skipped for tools that declare
Expand Down
35 changes: 35 additions & 0 deletions .github/workflows/test-actions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,41 @@ jobs:
- run: ./actions/chocolatey-render/test_render.ps1
shell: pwsh

# Release asset resolution: exercise the real cfl GoReleaser metadata shape
# and verify missing architecture artifacts fail before release fan-out.
release-assets:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5.1.0
- run: bash actions/release-assets/test_resolve.sh
shell: bash
- id: resolve
uses: ./actions/release-assets
with:
artifacts-path: actions/release-assets/testdata/artifacts.json
metadata-path: actions/release-assets/testdata/metadata.json
chocolatey-matrix: '[{"name":"cfl","id":"confluence-cli","dir":"packaging/chocolatey"}]'
winget-matrix: '[{"name":"cfl","id":"OpenCLICollective.cfl","bootstrap":false}]'
version: 1.3.97
- name: Assert both release matrix outputs
shell: bash
env:
CHOCOLATEY: ${{ steps.resolve.outputs.chocolatey-matrix }}
WINGET: ${{ steps.resolve.outputs.winget-matrix }}
run: |
set -euo pipefail
assert_assets() {
printf '%s' "$1" | jq -e '
length == 1 and
.[0].name == "cfl" and
.[0].x64_asset == "cfl_1.3.97_windows_amd64.zip" and
.[0].arm64_asset == "cfl_1.3.97_windows_arm64.zip"
' >/dev/null
}
assert_assets "$CHOCOLATEY"
assert_assets "$WINGET"
echo "ok: both action outputs contain the cfl amd64 and arm64 assets"

# identity.py unit tests — PASS + every drift rule + missing-manifest, in tmp dirs.
identity-unit:
runs-on: ubuntu-latest
Expand Down
40 changes: 40 additions & 0 deletions actions/release-assets/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
name: Resolve release assets
description: Resolve exact Windows archive names for release channels.
inputs:
artifacts-path:
description: Path to the GoReleaser artifacts.json file.
required: false
default: dist/artifacts.json
metadata-path:
description: Path to the GoReleaser metadata.json file.
required: false
default: dist/metadata.json
chocolatey-matrix:
description: JSON array of Chocolatey binary entries from identity metadata.
required: true
winget-matrix:
description: JSON array of WinGet binary entries from identity metadata.
required: true
version:
description: Final release version used to rename snapshot asset names.
required: true
outputs:
chocolatey-matrix:
description: Chocolatey entries enriched with x64_asset and arm64_asset.
value: ${{ steps.resolve.outputs.chocolatey-matrix }}
winget-matrix:
description: WinGet entries enriched with x64_asset and arm64_asset.
value: ${{ steps.resolve.outputs.winget-matrix }}
runs:
using: composite
steps:
- id: resolve
shell: bash
env:
ACTION_PATH: ${{ github.action_path }}
ARTIFACTS_PATH: ${{ inputs.artifacts-path }}
METADATA_PATH: ${{ inputs.metadata-path }}
CHOCOLATEY: ${{ inputs.chocolatey-matrix }}
WINGET: ${{ inputs.winget-matrix }}
VERSION: ${{ inputs.version }}
run: bash "$ACTION_PATH/resolve.sh"
42 changes: 42 additions & 0 deletions actions/release-assets/resolve.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#!/usr/bin/env bash
set -euo pipefail

: "${ARTIFACTS_PATH:?ARTIFACTS_PATH is required}"
: "${METADATA_PATH:?METADATA_PATH is required}"
: "${CHOCOLATEY:?CHOCOLATEY is required}"
: "${WINGET:?WINGET is required}"
: "${VERSION:?VERSION is required}"
: "${GITHUB_OUTPUT:?GITHUB_OUTPUT is required}"

[ -f "$ARTIFACTS_PATH" ] || { echo "::error::artifacts.json not found: $ARTIFACTS_PATH" >&2; exit 1; }
[ -f "$METADATA_PATH" ] || { echo "::error::metadata.json not found: $METADATA_PATH" >&2; exit 1; }

# The snapshot build names its archives with the snapshot version (for example,
# 1.2.1-SNAPSHOT-abc1234); the published release uses the final version, so
# rewrite that substring before handing names to the Windows channels.
snapshot_version="$(jq -r '.version // empty' "$METADATA_PATH")"
[ -n "$snapshot_version" ] || {
echo "::error::$METADATA_PATH has no version; the snapshot goreleaser step must write it" >&2
exit 1
}

enrich() {
printf '%s' "$1" | jq -c --slurpfile artifacts "$ARTIFACTS_PATH" --arg snap "$snapshot_version" --arg final "$VERSION" '
# GoReleaser records Windows executables with their .exe suffix while
# identity manifests use the canonical binary name (for example cfl).
def canonical($value):
($value // "" | ascii_downcase | sub("\\.exe$"; ""));
def owns($name):
(canonical(.extra.ID) == canonical($name)) or
((.extra.Binaries // []) | map(canonical(.)) | index(canonical($name)) != null);
def finalize: split($snap) | join($final);
def asset($name; $arch):
[$artifacts[0][] | select(.type == "Archive" and .goos == "windows" and .goarch == $arch and owns($name)) | .name | finalize]
| if length == 1 then .[0] else error("expected exactly one windows/" + $arch + " archive for " + $name + "; got " + (length|tostring)) end;
map(. + {x64_asset: asset(.name; "amd64"), arm64_asset: asset(.name; "arm64")})'
}

chocolatey_matrix="$(enrich "$CHOCOLATEY")"
winget_matrix="$(enrich "$WINGET")"
printf 'chocolatey-matrix=%s\n' "$chocolatey_matrix" >> "$GITHUB_OUTPUT"
printf 'winget-matrix=%s\n' "$winget_matrix" >> "$GITHUB_OUTPUT"
47 changes: 47 additions & 0 deletions actions/release-assets/test_resolve.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#!/usr/bin/env bash
# Exercise the same release asset resolver used by release.yml.
set -uo pipefail
cd "$(dirname "$0")"
fixture_dir="$PWD/testdata"
tmp=$(mktemp -d)
trap 'rm -rf "$tmp"' EXIT
fails=0
ok() { echo "ok $1"; }
bad() { echo "FAIL $1"; fails=$((fails+1)); }

output="$tmp/output"
GITHUB_OUTPUT="$output" \
ARTIFACTS_PATH="$fixture_dir/artifacts.json" \
METADATA_PATH="$fixture_dir/metadata.json" \
CHOCOLATEY='[{"name":"cfl","id":"confluence-cli","dir":"packaging/chocolatey"}]' \
WINGET='[]' \
VERSION='1.3.97' \
bash resolve.sh >/dev/null

matrix=$(sed -n 's/^chocolatey-matrix=//p' "$output")
printf '%s' "$matrix" | jq -e '
length == 1 and
.[0].name == "cfl" and
.[0].x64_asset == "cfl_1.3.97_windows_amd64.zip" and
.[0].arm64_asset == "cfl_1.3.97_windows_arm64.zip"
' >/dev/null \
&& ok "cfl .exe artifacts resolve to exact per-arch assets" \
|| bad "cfl .exe artifacts resolve to exact per-arch assets"

jq 'map(select(.goarch != "amd64"))' "$fixture_dir/artifacts.json" > "$tmp/missing-amd64.json"
if GITHUB_OUTPUT="$tmp/missing-output" \
ARTIFACTS_PATH="$tmp/missing-amd64.json" \
METADATA_PATH="$fixture_dir/metadata.json" \
CHOCOLATEY='[{"name":"cfl","id":"confluence-cli","dir":"packaging/chocolatey"}]' \
WINGET='[]' \
VERSION='1.3.97' \
bash resolve.sh > /dev/null 2>"$tmp/missing-error"; then
bad "missing amd64 artifact fails"
elif grep -Fq 'expected exactly one windows/amd64 archive for cfl; got 0' "$tmp/missing-error"; then
ok "missing amd64 artifact fails loudly"
else
bad "missing amd64 artifact reports the expected error"
fi

echo "----"
if [ "$fails" -eq 0 ]; then echo "all release asset tests passed"; else echo "$fails failed"; exit 1; fi
4 changes: 4 additions & 0 deletions actions/release-assets/testdata/artifacts.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[
{"type":"Archive","goos":"windows","goarch":"amd64","name":"cfl_1.3.97-SNAPSHOT-abc1234_windows_amd64.zip","extra":{"Binaries":["cfl.exe"],"ID":"default"}},
{"type":"Archive","goos":"windows","goarch":"arm64","name":"cfl_1.3.97-SNAPSHOT-abc1234_windows_arm64.zip","extra":{"Binaries":["cfl.exe"],"ID":"default"}}
]
1 change: 1 addition & 0 deletions actions/release-assets/testdata/metadata.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"version":"1.3.97-SNAPSHOT-abc1234"}
Loading