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
4 changes: 4 additions & 0 deletions .github/actions/test/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ runs:
shell: bash
run: ./tests/runner_self_test.sh

- name: Release recovery negative controls
shell: bash
run: ./tests/release_recovery_test.sh

# No `if: hashFiles(...)` guard. This step used to skip itself when
# tests/test.sh was absent, which is indistinguishable from the script being
# renamed or deleted -- a skipped test is a green tick. This addon has a
Expand Down
115 changes: 73 additions & 42 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,21 @@ on:
workflow_dispatch:
inputs:
bump:
description: Version bump
description: Version bump. Ignored when retry-tag and retry-sha256 are both set.
required: true
type: choice
options:
- patch
- minor
- major
retry-tag:
description: Existing immutable vX.Y.Z tag to republish. Empty for a normal bump.
required: false
type: string
retry-sha256:
description: Required SHA-256 of the existing release ZIP when retry-tag is set.
required: false
type: string

permissions:
contents: read
Expand All @@ -23,6 +31,9 @@ jobs:
outputs:
version: ${{ steps.release.outputs.version }}
tag: ${{ steps.release.outputs.tag }}
retry: ${{ steps.release.outputs.retry }}
target: ${{ steps.release.outputs.target }}
sha256: ${{ steps.release.outputs.sha256 }}
# Bounded, so a step that hangs fails here rather than sitting until the
# runner's own timeout hours later.
timeout-minutes: 20
Expand All @@ -35,49 +46,30 @@ jobs:
id: release
env:
BUMP: ${{ inputs.bump }}
RETRY_TAG: ${{ inputs['retry-tag'] }}
RETRY_SHA256: ${{ inputs['retry-sha256'] }}
run: |
set -euo pipefail
if [ "$GITHUB_REF" != "refs/heads/main" ]; then
echo 'Run releases from the main branch.' >&2
exit 1
fi
git fetch --tags --force
latest="$(git tag --list 'v[0-9]*' | sed -E 's/^v//' | grep -E '^[0-9]+\.[0-9]+\.[0-9]+$' | sort -V | tail -n 1 || true)"
if [ -z "$latest" ]; then
version="0.0.1"
else
IFS=. read -r major minor patch <<< "$latest"
case "$BUMP" in
major) major=$((major + 1)); minor=0; patch=0 ;;
minor) minor=$((minor + 1)); patch=0 ;;
patch) patch=$((patch + 1)) ;;
*) echo "Unsupported bump: $BUMP" >&2; exit 1 ;;
esac
version="${major}.${minor}.${patch}"
fi
tag="v${version}"
if git rev-parse -q --verify "refs/tags/$tag" >/dev/null; then
echo "Tag already exists: $tag" >&2
exit 1
fi
plugin_version="$(sed -n -E 's/^version="([^"]+)"/\1/p' addon/plugin.cfg | head -n 1)"
if [ "$plugin_version" != "$version" ]; then
echo "addon/plugin.cfg version is $plugin_version, but the next $BUMP release is $version." >&2
echo "Update addon/plugin.cfg to version=\"$version\", commit it, then rerun this workflow." >&2
exit 1
fi
echo "version=$version" >> "$GITHUB_OUTPUT"
echo "tag=$tag" >> "$GITHUB_OUTPUT"

# The same checks CI runs, from the same definition. This workflow used
# to package and publish without running any of them -- the only thing
# between a broken commit and the GDAM registry was whether somebody had
# looked at CI. Running them here against this exact commit is the point:
# CI passing on this SHA earlier is a claim about that run.
- name: Test
./scripts/release_recovery.sh plan

# The same checks CI runs, from the same definition. Skipped on retry:
# retry republishes the existing ZIP and must not package a new one.
- name: Test exact release commit and package
if: steps.release.outputs.retry != 'true'
uses: ./.github/actions/test

- name: Recover existing immutable release package
if: steps.release.outputs.retry == 'true'
env:
GH_TOKEN: ${{ github.token }}
TAG: ${{ steps.release.outputs.tag }}
EXPECTED_SHA256: ${{ steps.release.outputs.sha256 }}
TARGET: ${{ steps.release.outputs.target }}
run: ./scripts/release_recovery.sh recover

- name: Upload exact tested package
if: steps.release.outputs.retry != 'true'
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: release-package-${{ github.sha }}
Expand All @@ -88,6 +80,16 @@ jobs:
dist/web-acceptance.png
if-no-files-found: error

- name: Upload recovered release package
if: steps.release.outputs.retry == 'true'
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: release-package-${{ github.sha }}
path: |
dist/@aviorstudio_gd-network.zip
dist/@aviorstudio_gd-network.zip.sha256
if-no-files-found: error

publish:
needs: test
runs-on: ubuntu-latest
Expand All @@ -108,17 +110,46 @@ jobs:
run: bash ./scripts/verify_package_checksum.sh

- name: Create GitHub Release
if: needs.test.outputs.retry != 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: ${{ needs.test.outputs.tag }}
run: gh release create "$TAG" dist/*.zip --target "$GITHUB_SHA" --title "$TAG" --notes "Release $TAG"
RETRY: ${{ needs.test.outputs.retry }}
RETRY_TAG: ${{ inputs['retry-tag'] }}
RETRY_SHA256: ${{ inputs['retry-sha256'] }}
run: |
set -euo pipefail
./scripts/release_recovery.sh assert-bump
gh release create "$TAG" dist/*.zip --target "$GITHUB_SHA" --title "$TAG" --notes "Release $TAG"

- name: Verify existing GitHub Release
if: needs.test.outputs.retry == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: ${{ needs.test.outputs.tag }}
TARGET: ${{ needs.test.outputs.target }}
EXPECTED_SHA256: ${{ needs.test.outputs.sha256 }}
run: ./scripts/release_recovery.sh recheck

- name: Install GDAM
- name: Install checksum-verified GDAM v0.0.8
id: install-gdam
uses: aviorstudio/gdam-actions/install@d735444eb470194585def44521d5d91df2260e63 # v0.0.2
with:
version: 'v0.0.8'

- name: Verify GDAM version
env:
INSTALLED_VERSION: ${{ steps.install-gdam.outputs.version }}
run: |
case "$INSTALLED_VERSION" in
*0.0.8*) printf '%s\n' "$INSTALLED_VERSION" ;;
*) echo "Unexpected GDAM version: $INSTALLED_VERSION" >&2; exit 1 ;;
esac

- name: Publish to GDAM
- name: Publish exact GitHub asset to GDAM
uses: aviorstudio/gdam-actions/publish@d735444eb470194585def44521d5d91df2260e63 # v0.0.2
with:
version: ${{ needs.test.outputs.version }}
tag: ${{ needs.test.outputs.tag }}
addon: '@aviorstudio/gd-network'
asset: '@aviorstudio_gd-network.zip'
secret-key: ${{ secrets.GDAM_SECRET_KEY }}
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,18 +104,21 @@ packets, and rejects outbound UTF-8 text larger than 1 MiB.
- `addon/src/`: reusable GDScript modules.
- `tests/`: Godot test project/scripts for addon behavior.
- `.github/workflows/ci.yml`: validates package shape and runs tests.
- `.github/workflows/release.yml`: creates GitHub release ZIPs and publishes to GDAM.
- `.github/workflows/release.yml`: creates GitHub release ZIPs and publishes to GDAM. A verified retry can republish an existing immutable release without creating another tag or ZIP.

## Versioning And Releases

The version in `addon/plugin.cfg` is the addon package version. `0.0.4` adds `post_zero_body`. Releases are created from `main` with the manual release workflow and plain semver tags like `v0.0.4`; the workflow verifies `plugin.cfg`, builds `@aviorstudio_gd-network.zip`, and publishes `@aviorstudio/gd-network` to GDAM.
The version in `addon/plugin.cfg` is the addon package version. `0.0.4` adds `post_zero_body`. Releases are created from protected `main` with the manual release workflow and plain semver tags like `v0.0.4`; the workflow verifies `plugin.cfg`, builds `@aviorstudio_gd-network.zip`, and publishes `@aviorstudio/gd-network` to GDAM.

To republish an existing immutable tag after a failed registry publish, dispatch that same workflow from protected `main` with `retry-tag` and `retry-sha256` set together. Retry requires the tag commit to be the current main commit or an ancestor of it, `plugin.cfg` at that tag and on main to equal the tag version, the GitHub release target to match the tag, and the existing `@aviorstudio_gd-network.zip` digest to match `retry-sha256`. Retry downloads and checksums that ZIP. It does not build a package, create a tag, or create a GitHub release. Leave both retry inputs empty for a normal bump.

## Testing

Run locally with:

```sh
./tests/test.sh
./tests/release_recovery_test.sh
```

**Correction ([fieldsofrevik#145](https://github.com/aviorstudio/fieldsofrevik/issues/145)):** the prior text said CI ran the test script
Expand Down
Loading
Loading