From 384ffe597b4f685df41008f603bd7a61c8e30815 Mon Sep 17 00:00:00 2001 From: Casey Labs <4674433+CaseyLabs@users.noreply.github.com> Date: Mon, 8 Jun 2026 15:52:55 -0700 Subject: [PATCH 01/19] ci: redesign release publishing --- .github/AGENTS.md | 2 +- .github/RELEASING.md | 167 +++----- .github/act/stable_release.json | 6 + .github/workflows/build-and-release.yml | 448 +++++++++----------- .github/workflows/release-latest-assets.yml | 265 ------------ .github/workflows/run-test.yml | 3 - .github/workflows/stable-release.yml | 359 ++++++++++++++++ Makefile | 3 + scripts/release-notes.sh | 129 ++++++ 9 files changed, 755 insertions(+), 627 deletions(-) create mode 100644 .github/act/stable_release.json delete mode 100644 .github/workflows/release-latest-assets.yml create mode 100644 .github/workflows/stable-release.yml create mode 100755 scripts/release-notes.sh diff --git a/.github/AGENTS.md b/.github/AGENTS.md index b8dde0f5a..12e64e51e 100644 --- a/.github/AGENTS.md +++ b/.github/AGENTS.md @@ -11,7 +11,7 @@ - Keep trigger shapes intentional. This repo currently uses pull request triggers such as `opened`, `synchronize`, `reopened`, and `ready_for_review` where needed. - Preserve `persist-credentials: false` on checkout steps unless there is a concrete reason to change authentication behavior. - Reuse local composite actions in `.github/actions/` when they already capture setup shared across workflows. -- Treat release behavior as merge-driven: pushes to `master` and manual `workflow_dispatch` runs feed the current release flow documented in `.github/RELEASING.md`. +- Treat release behavior as split by stability: pushes to `master` update the rolling prerelease, and manual `workflow_dispatch` runs create protected stable releases as documented in `.github/RELEASING.md`. ## Verification diff --git a/.github/RELEASING.md b/.github/RELEASING.md index 1bd4aa384..a1e007714 100644 --- a/.github/RELEASING.md +++ b/.github/RELEASING.md @@ -7,132 +7,81 @@ Recommended versioning for the next release line: - Start the next stable release at `v0.10.0`. - Keep the binary's embedded version in source as a normal semver such as `0.10.0`. - Let merge-driven builds update a single rolling `prerelease` tag. -- Reserve semver tags like `v0.10.x` for stable releases. +- Reserve stable semver tags like `v0.10.0` for protected stable releases. -### 1. New Feature or Breaking‑Change Release (Minor/Major) +### Prerelease -1. **Merge & Verify** +1. Merge to `master`. +2. The `Prerelease` workflow runs tests, builds the seven supported binaries, + assembles `gomud-ALL-datafiles.zip` and `SHA256SUMS.txt`, generates + attestations, and updates the mutable `prerelease` GitHub prerelease. +3. The workflow may move the `prerelease` tag and clobber existing prerelease + assets. This is intentional so the rolling prerelease remains mutable. +4. The prerelease is marked as a prerelease and is not marked as `Latest`. -- Merge all feature or breaking‑change PRs into `master`. -- Ensure CI (tests, linter, codegen) all pass on `master`. +Pull requests do not publish release binaries. The generic `Run Tests` workflow +is PR-focused; `master` release testing happens inside the `Prerelease` workflow +to avoid duplicate full race-test runs on merge. -1. **Determine Version Bump** +### Stable Release -- **Major** (`X.0.0`) when you make incompatible changes -- **Minor** (`0.Y.0`) when you add functionality in a backward compatible manner -- **Patch** (`0.0.Z`) when you make backward compatible bug fixes +1. Confirm the source version in `main.go` is correct. +2. Run the `Stable Release` workflow manually with a new stable semver + `release_tag`, such as `v0.10.0`. +3. The workflow fails before publishing if the requested tag or GitHub release + already exists. +4. The `publish` job uses the `stable-release` GitHub Environment. Configure + required reviewers on that environment in repository settings. +5. After environment approval, the workflow creates a draft release, uploads + assets, attaches release notes, then publishes the stable release. -1. **Merge to `master`** - - Merging to `master` triggers the `Release` workflow automatically. +Stable release workflow policy does not move tags, use `--clobber`, or replace +existing releases. Keep repository-wide immutable releases disabled so the +rolling `prerelease` release can remain mutable. - Or, for a manual test without merging: - - Run the `Release` workflow with `workflow_dispatch`. - - Optionally set `release_tag` if you want the run to upload assets to a - specific existing or new release tag instead of the default prerelease tag. - - Run `Release Latest Assets` when you want the Actions UI path that updates - the repo's current latest GitHub release tag without any manual input. +### Assets -2. **Monitor Release** - - GitHub Actions will: - - Run `go generate ./...` - - Build per-platform binaries with `main.version` set from `main.go` - - Replace the rolling `prerelease` tag and GitHub prerelease - - Archive `_datafiles` as `gomud-ALL-datafiles-prerelease.zip` - - Generate `gomud-prerelease-SHA256SUMS.txt` - - Leave the release unmarked as `Latest` +Each prerelease and stable release includes: -3. **Announce** - - After review, a repo owner can edit the release in GitHub and promote it to - `Latest`. - - Share the release link with the team or via configured notifications. +- `gomud-linux_x64` +- `gomud-linux_arm64` +- `gomud-linux_armv7` +- `gomud-windows_x64.exe` +- `gomud-windows_arm64.exe` +- `gomud-darwin_x64` +- `gomud-darwin_arm64` +- `gomud-ALL-datafiles.zip` +- `SHA256SUMS.txt` ---- +Release notes are generated by `scripts/release-notes.sh` and include `Overview`, +`Downloads`, `Install From Source`, `Manual Binary Install`, `Verify Provenance`, +and `Changes`. The `Changes` section appends GitHub auto-generated notes from +`releases/generate-notes`. -### 2. Merge-Driven Prerelease Policy +### Verification -1. **Pull requests do not publish release binaries** - - PRs should run normal CI only. +After a release workflow succeeds: -2. **Merges to `master` do publish release binaries** - - A push to `master` runs the `Release` workflow and replaces the rolling - `prerelease`. +- Confirm the expected assets are attached. +- Confirm `SHA256SUMS.txt` verifies downloaded assets with `sha256sum -c`. +- Confirm binaries and `SHA256SUMS.txt` have artifact attestations. +- For stable releases, confirm the release tag points at the intended commit and + the release is no longer a draft. -3. **Manual test runs can also publish prereleases** - - `workflow_dispatch` can be used to refresh the rolling `prerelease` - without merging. - - If you provide `release_tag`, the run publishes to that tag instead. - - `Release Latest Assets` is the no-input manual workflow that resolves the - current latest GitHub release tag and publishes to it. - -4. **Rolling release naming** - - The release tag is always `prerelease`. - - The release notes record the commit SHA and publish time for the current build. - - Numbered releases such as `v0.10.0` remain the permanent download history. - ---- - -### 3. Manual Test Release Flow - -1. **Run the release workflow manually** - - Use `workflow_dispatch` when you want a test release - without merging to `master`. - - Leave `release_tag` blank to use the default manual prerelease naming. - - Set `release_tag` when you want the run to upload assets to a specific - existing or new release tag. - - Run `Release Latest Assets` when you want the no-input Actions UI path - that uploads assets to whichever tag GitHub currently considers the latest - release. - -2. **Verify the GitHub release** - - Confirm the workflow succeeds. - - Confirm the targeted release now points at the expected commit. - - Confirm the per-platform binaries are attached. - - Confirm the `_datafiles` zip asset is attached. - - Confirm the checksum manifest asset is attached. - - For default manual prerelease runs, confirm GitHub marks the release as a - prerelease and does not mark it as `Latest`. - -3. **Clean up if needed** - - No cleanup is normally required for the rolling `prerelease` path because - the next successful run replaces it. - - Tag-targeted manual runs use the tag you requested, so cleanup is only - needed if you created a temporary release tag for testing. - ---- - -### FAQ / Guidelines +### FAQ - **Does every merge to `master` trigger a release?** - Yes - every push to `master` runs the release workflow and publishes a prerelease. - That prerelease is the rolling `prerelease` entry, not a newly named release. - -- **Is auto-tagging enabled?** - Stable semver tags are not generated automatically. The merge-driven workflow - updates the rolling `prerelease` tag instead. - -- **Can I create a test release without merging to `master`?** - Yes - run the `Release` workflow manually with `workflow_dispatch`. That keeps PR - submissions clean while still allowing an on-demand refresh of `prerelease`. - Set `release_tag` when you want the run to publish assets to a specific tag. - Run `Release Latest Assets` when you want the no-input path that targets the - current latest release. - -- **Are workflow-created releases stable releases?** - No - the workflow creates prereleases. A repo owner must manually promote a release - to `Latest` in GitHub when it is approved. - -- **What assets should a release include?** - Each release should include separate per-platform binaries, a `_datafiles` zip, - and a checksum manifest so testers can download only what they need and still - verify the assets. + Yes. Every push to `master` runs the `Prerelease` workflow and updates the + rolling `prerelease`. -- **What tag format should we use going forward?** - Keep the source/binary version on the `0.10.x` line. Use `prerelease` for the - rolling master build and semver tags for stable releases. +- **Can the workflow publish a stable release from an existing tag?** + No. Stable releases must use a new semver tag. Existing tags and releases fail + the workflow. -- **When should I bump minor vs. patch?** - - **Minor** for new, backward‑compatible features. - - **Patch** for bug fixes or documentation tweaks. +- **Can stable assets be replaced?** + No. The stable workflow does not clobber assets. Publish a new patch release + tag instead. - **What about `go generate` directives?** - The workflow runs `go generate ./...` automatically before each build. + Release workflows run `go generate ./...` before tests and before each + cross-compiled build. diff --git a/.github/act/stable_release.json b/.github/act/stable_release.json new file mode 100644 index 000000000..510a66cca --- /dev/null +++ b/.github/act/stable_release.json @@ -0,0 +1,6 @@ +{ + "ref": "refs/heads/master", + "inputs": { + "release_tag": "v0.0.0" + } +} diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml index c31c24b18..f0a9aa2f1 100644 --- a/.github/workflows/build-and-release.yml +++ b/.github/workflows/build-and-release.yml @@ -1,16 +1,10 @@ --- -name: Release +name: Prerelease "on": push: branches: - master - workflow_dispatch: - inputs: - release_tag: - description: Existing/new release tag, or latest, to publish assets to - required: false - type: string concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -19,20 +13,18 @@ concurrency: permissions: contents: read +env: + DATAFILES_ARCHIVE: gomud-ALL-datafiles.zip + CHECKSUMS_FILE: SHA256SUMS.txt + jobs: - prep: + metadata: runs-on: ubuntu-24.04 timeout-minutes: 5 - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + permissions: + contents: read outputs: binary_version: ${{ steps.meta.outputs.binary_version }} - release_tag: ${{ steps.meta.outputs.release_tag }} - release_title: ${{ steps.meta.outputs.release_title }} - release_notes_intro: ${{ steps.meta.outputs.release_notes_intro }} - release_notes_summary: ${{ steps.meta.outputs.release_notes_summary }} - datafiles_archive: ${{ steps.meta.outputs.datafiles_archive }} - checksums_file: ${{ steps.meta.outputs.checksums_file }} steps: # actions/checkout v6.0.2 - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd @@ -41,12 +33,9 @@ jobs: - name: Compute release metadata id: meta - env: - REQUESTED_RELEASE_TAG: ${{ github.event.inputs.release_tag || '' }} run: | - short_sha="$(printf '%s' "$GITHUB_SHA" | cut -c1-7)" - timestamp="$(date -u +'%Y%m%d%H%M%S')" - requested_release_tag="$REQUESTED_RELEASE_TAG" + set -euo pipefail + binary_version="$( awk -F'"' '/^const VERSION = "/ { print $2; exit }' main.go )" @@ -55,80 +44,19 @@ jobs: exit 1 fi - if [ -n "$requested_release_tag" ]; then - if [ "$requested_release_tag" = "latest" ]; then - if [ "$GITHUB_REF" != "refs/heads/master" ]; then - echo \ - "release_tag=latest may only be used when " \ - "workflow_dispatch runs from master" \ - >&2 - exit 1 - fi - release_tag="$( - gh api "repos/${GITHUB_REPOSITORY}/releases/latest" \ - --jq '.tag_name' - )" - if [ -z "$release_tag" ]; then - echo "Could not determine latest release tag" >&2 - exit 1 - fi - release_title="$release_tag" - release_notes_intro="Manual release build for current latest \ - release \`${release_tag}\` from \`${GITHUB_REF_NAME}\`." - else - case "$requested_release_tag" in - *[!A-Za-z0-9._-]*) - echo \ - "release_tag may contain only letters, digits, ., _, and \ - -" \ - >&2 - exit 1 - ;; - esac - - release_tag="$requested_release_tag" - release_title="$requested_release_tag" - release_notes_intro="Manual release build for \ - \`${requested_release_tag}\` from \`${GITHUB_REF_NAME}\`." - fi - release_notes_summary="This run publishes assets to the \ - requested release tag." - elif [ "$GITHUB_REF" = "refs/heads/master" ]; then - release_tag="prerelease" - release_title="prerelease" - release_notes_intro="Rolling prerelease build from \`master\`." - release_notes_summary="This release is replaced on each successful \ - merge to \`master\`." - else - release_tag="pre-${timestamp}-${short_sha}" - release_title="$release_tag" - release_notes_intro="Manual prerelease build from \ - \`${GITHUB_REF_NAME}\`." - release_notes_summary="This is an ad hoc prerelease for the \ - selected ref and does not replace the rolling \`master\` prerelease." - fi - - { - echo "binary_version=${binary_version}" - echo "release_tag=${release_tag}" - echo "release_title=${release_title}" - echo "release_notes_intro=${release_notes_intro}" - echo "release_notes_summary=${release_notes_summary}" - echo "datafiles_archive=gomud-ALL-datafiles.zip" - echo "checksums_file=SHA256SUMS.txt" - } >> "$GITHUB_OUTPUT" + echo "binary_version=${binary_version}" >> "$GITHUB_OUTPUT" - build: + test: runs-on: ubuntu-24.04 timeout-minutes: 30 - needs: prep - env: - BINARY_VERSION: ${{ needs.prep.outputs.binary_version }} + permissions: + contents: read steps: # actions/checkout v6.0.2 - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd with: persist-credentials: false + - uses: ./.github/actions/setup-go - name: Generate code @@ -137,209 +65,231 @@ jobs: - name: Run tests run: go test -race ./... - - name: Create bin directory - run: mkdir -p bin/ - - - name: Copy _datafiles to bin/ - run: cp -r _datafiles bin/ - - - name: Build windows amd64 - run: >- - env GOOS=windows GOARCH=amd64 go build -v - -ldflags "-X main.version=${BINARY_VERSION}" - -o bin/gomud-windows_x64.exe . - - - name: Build windows arm64 - run: >- - env GOOS=windows GOARCH=arm64 go build -v - -ldflags "-X main.version=${BINARY_VERSION}" - -o bin/gomud-windows_arm64.exe . - - - name: Build darwin/arm64 - run: >- - env GOOS=darwin GOARCH=arm64 go build -v - -ldflags "-X main.version=${BINARY_VERSION}" - -o bin/gomud-darwin_arm64 . - - - name: Build darwin/amd64 - run: >- - env GOOS=darwin GOARCH=amd64 go build -v - -ldflags "-X main.version=${BINARY_VERSION}" - -o bin/gomud-darwin_x64 . - - - name: Build linux/amd64 - run: >- - env GOOS=linux GOARCH=amd64 go build -v - -ldflags "-X main.version=${BINARY_VERSION}" - -o bin/gomud-linux_x64 . - - - name: Build linux/arm64 - run: >- - env GOOS=linux GOARCH=arm64 go build -v - -ldflags "-X main.version=${BINARY_VERSION}" - -o bin/gomud-linux_arm64 . - - - name: Build linux/armv7 - run: >- - env GOOS=linux GOARCH=arm GOARM=7 go build -v - -ldflags "-X main.version=${BINARY_VERSION}" - -o bin/gomud-linux_armv7 . - - - name: Upload bin + build: + runs-on: ubuntu-24.04 + timeout-minutes: 20 + needs: + - metadata + - test + permissions: + contents: read + strategy: + fail-fast: false + matrix: + target: + - goos: linux + goarch: amd64 + goarm: '' + asset: gomud-linux_x64 + - goos: linux + goarch: arm64 + goarm: '' + asset: gomud-linux_arm64 + - goos: linux + goarch: arm + goarm: '7' + asset: gomud-linux_armv7 + - goos: windows + goarch: amd64 + goarm: '' + asset: gomud-windows_x64.exe + - goos: windows + goarch: arm64 + goarm: '' + asset: gomud-windows_arm64.exe + - goos: darwin + goarch: amd64 + goarm: '' + asset: gomud-darwin_x64 + - goos: darwin + goarch: arm64 + goarm: '' + asset: gomud-darwin_arm64 + env: + BINARY_VERSION: ${{ needs.metadata.outputs.binary_version }} + steps: + # actions/checkout v6.0.2 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd + with: + persist-credentials: false + + - uses: ./.github/actions/setup-go + + - name: Generate code + run: go generate ./... + + - name: Build release binary + env: + GOOS: ${{ matrix.target.goos }} + GOARCH: ${{ matrix.target.goarch }} + GOARM: ${{ matrix.target.goarm }} + ASSET_NAME: ${{ matrix.target.asset }} + run: | + set -euo pipefail + + mkdir -p dist + go build \ + -ldflags "-X main.version=${BINARY_VERSION}" \ + -o "dist/${ASSET_NAME}" \ + . + + - name: Upload release binary # actions/upload-artifact v7.0.1 uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a with: - name: bin-artifact - path: bin/ + name: release-binary-${{ matrix.target.asset }} + path: dist/${{ matrix.target.asset }} + if-no-files-found: error - release: + publish: runs-on: ubuntu-24.04 timeout-minutes: 30 - permissions: - contents: write needs: - - prep + - metadata - build + permissions: + contents: write + id-token: write + attestations: write env: - RELEASE_TAG: ${{ needs.prep.outputs.release_tag }} - RELEASE_TITLE: ${{ needs.prep.outputs.release_title }} - BINARY_VERSION: ${{ needs.prep.outputs.binary_version }} - RELEASE_NOTES_INTRO: ${{ needs.prep.outputs.release_notes_intro }} - RELEASE_NOTES_SUMMARY: ${{ needs.prep.outputs.release_notes_summary }} - DATAFILES_ARCHIVE: ${{ needs.prep.outputs.datafiles_archive }} - CHECKSUMS_FILE: ${{ needs.prep.outputs.checksums_file }} + RELEASE_TAG: prerelease + RELEASE_TITLE: prerelease + BINARY_VERSION: ${{ needs.metadata.outputs.binary_version }} GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - REQUESTED_RELEASE_TAG: ${{ github.event.inputs.release_tag || '' }} steps: # actions/checkout v6.0.2 - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd with: persist-credentials: false - - name: Download builds + - name: Download release binaries # actions/download-artifact v8.0.1 uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c with: - name: bin-artifact + pattern: release-binary-* path: bin/ + merge-multiple: true - - name: Archive datafiles - run: >- - zip -r - "bin/${DATAFILES_ARCHIVE}" - bin/_datafiles - - - name: Generate release checksums + - name: Assemble release assets run: | + set -euo pipefail + + zip -r "bin/${DATAFILES_ARCHIVE}" _datafiles + cd bin sha256sum \ - gomud-windows_x64.exe \ - gomud-windows_arm64.exe \ - gomud-darwin_arm64 \ - gomud-darwin_x64 \ gomud-linux_x64 \ gomud-linux_arm64 \ gomud-linux_armv7 \ + gomud-windows_x64.exe \ + gomud-windows_arm64.exe \ + gomud-darwin_x64 \ + gomud-darwin_arm64 \ "${DATAFILES_ARCHIVE}" \ > "${CHECKSUMS_FILE}" - name: Write release notes - run: | - latest_release_tag="$( - gh api "repos/${GITHUB_REPOSITORY}/releases/latest" \ - --jq '.tag_name' \ - 2>/dev/null || true - )" - generate_notes_args=( - -f "tag_name=release-notes-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}" - -f "target_commitish=${GITHUB_SHA}" - ) - if [ -n "$latest_release_tag" ]; then - generate_notes_args+=(-f "previous_tag_name=${latest_release_tag}") - changes_since_line="- Changes since: \`${latest_release_tag}\`" - else - changes_since_line="- Changes since: initial release history" - fi - generated_release_notes="$( - gh api \ - -X POST \ - "repos/${GITHUB_REPOSITORY}/releases/generate-notes" \ - "${generate_notes_args[@]}" \ - --jq '.body' - )" - published_at="$(date -u +"%Y-%m-%dT%H:%M:%SZ")" - cat > release-notes.md </dev/null 2>&1; then - gh api \ - -X PATCH \ - "repos/${GITHUB_REPOSITORY}/git/refs/tags/${RELEASE_TAG}" \ - -f "sha=${GITHUB_SHA}" \ - -F force=true - gh release upload "$RELEASE_TAG" "${assets[@]}" --clobber - gh release edit "$RELEASE_TAG" \ - --title "$RELEASE_TITLE" \ - --target "$GITHUB_SHA" \ - --notes-file release-notes.md - else - if gh api \ - "repos/${GITHUB_REPOSITORY}/git/ref/tags/${RELEASE_TAG}" \ - >/dev/null 2>&1; then - gh api \ - -X PATCH \ - "repos/${GITHUB_REPOSITORY}/git/refs/tags/${RELEASE_TAG}" \ - -f "sha=${GITHUB_SHA}" \ - -F force=true - fi - gh release create "$RELEASE_TAG" \ - "${assets[@]}" \ - --title "$RELEASE_TITLE" \ - --target "$GITHUB_SHA" \ - --notes-file release-notes.md - fi + if gh api \ + "repos/${GITHUB_REPOSITORY}/git/ref/tags/${RELEASE_TAG}" \ + >/dev/null 2>&1; then + gh api \ + -X PATCH \ + "repos/${GITHUB_REPOSITORY}/git/refs/tags/${RELEASE_TAG}" \ + -f "sha=${GITHUB_SHA}" \ + -F force=true + fi + + if gh release view "$RELEASE_TAG" >/dev/null 2>&1; then + gh release upload "$RELEASE_TAG" "${assets[@]}" --clobber + gh release edit "$RELEASE_TAG" \ + --title "$RELEASE_TITLE" \ + --target "$GITHUB_SHA" \ + --prerelease \ + --latest=false \ + --notes-file release-notes.md else - if gh release view "$RELEASE_TAG" >/dev/null 2>&1; then - gh release upload "$RELEASE_TAG" "${assets[@]}" --clobber - gh release edit "$RELEASE_TAG" \ - --title "$RELEASE_TITLE" \ - --target "$GITHUB_SHA" \ - --prerelease \ - --latest=false \ - --notes-file release-notes.md - else - gh release create "$RELEASE_TAG" \ - "${assets[@]}" \ - --title "$RELEASE_TITLE" \ - --target "$GITHUB_SHA" \ - --prerelease \ - --latest=false \ - --notes-file release-notes.md - fi + gh release create "$RELEASE_TAG" \ + "${assets[@]}" \ + --title "$RELEASE_TITLE" \ + --target "$GITHUB_SHA" \ + --prerelease \ + --latest=false \ + --notes-file release-notes.md fi diff --git a/.github/workflows/release-latest-assets.yml b/.github/workflows/release-latest-assets.yml deleted file mode 100644 index 8e6a9afb4..000000000 --- a/.github/workflows/release-latest-assets.yml +++ /dev/null @@ -1,265 +0,0 @@ ---- -name: Release Latest Assets - -"on": - workflow_dispatch: - -concurrency: - group: Release-${{ github.ref }} - cancel-in-progress: false - -permissions: - contents: read - -jobs: - prep: - runs-on: ubuntu-24.04 - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - outputs: - binary_version: ${{ steps.meta.outputs.binary_version }} - release_tag: ${{ steps.meta.outputs.release_tag }} - release_title: ${{ steps.meta.outputs.release_title }} - release_notes_intro: ${{ steps.meta.outputs.release_notes_intro }} - release_notes_summary: ${{ steps.meta.outputs.release_notes_summary }} - datafiles_archive: ${{ steps.meta.outputs.datafiles_archive }} - checksums_file: ${{ steps.meta.outputs.checksums_file }} - steps: - # actions/checkout v6.0.2 - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - with: - ref: ${{ github.sha }} - persist-credentials: false - - - name: Compute release metadata - id: meta - run: | - if [ "$GITHUB_REF" != "refs/heads/master" ]; then - echo \ - "Release Latest Assets may only be run from master" \ - >&2 - exit 1 - fi - release_tag="$( - gh api "repos/${GITHUB_REPOSITORY}/releases/latest" \ - --jq '.tag_name' - )" - binary_version="$( - awk -F'"' '/^const VERSION = "/ { print $2; exit }' main.go - )" - if [ -z "$binary_version" ]; then - echo "Could not determine binary version from main.go" >&2 - exit 1 - fi - if [ -z "$release_tag" ]; then - echo "Could not determine latest release tag" >&2 - exit 1 - fi - - { - echo "binary_version=${binary_version}" - echo "release_tag=${release_tag}" - echo "release_title=${release_tag}" - echo "release_notes_intro=Manual release build for current latest\ - release \`${release_tag}\` from \`${GITHUB_REF_NAME}\`." - echo "release_notes_summary=This run publishes assets to the repo's\ - current latest release tag." - echo "datafiles_archive=gomud-ALL-datafiles.zip" - echo "checksums_file=SHA256SUMS.txt" - } >> "$GITHUB_OUTPUT" - - build: - runs-on: ubuntu-24.04 - needs: prep - env: - BINARY_VERSION: ${{ needs.prep.outputs.binary_version }} - steps: - # actions/checkout v6.0.2 - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - with: - ref: ${{ github.sha }} - persist-credentials: false - - uses: ./.github/actions/setup-go - - - name: Generate code - run: go generate ./... - - - name: Run tests - run: go test -race ./... - - - name: Create bin directory - run: mkdir -p bin/ - - - name: Copy _datafiles to bin/ - run: cp -r _datafiles bin/ - - - name: Build windows amd64 - run: >- - env GOOS=windows GOARCH=amd64 go build -v - -ldflags "-X main.version=${{ env.BINARY_VERSION }}" - -o bin/gomud-windows_x64.exe . - - - name: Build windows arm64 - run: >- - env GOOS=windows GOARCH=arm64 go build -v - -ldflags "-X main.version=${{ env.BINARY_VERSION }}" - -o bin/gomud-windows_arm64.exe . - - - name: Build darwin/arm64 - run: >- - env GOOS=darwin GOARCH=arm64 go build -v - -ldflags "-X main.version=${{ env.BINARY_VERSION }}" - -o bin/gomud-darwin_arm64 . - - - name: Build darwin/amd64 - run: >- - env GOOS=darwin GOARCH=amd64 go build -v - -ldflags "-X main.version=${{ env.BINARY_VERSION }}" - -o bin/gomud-darwin_x64 . - - - name: Build linux/amd64 - run: >- - env GOOS=linux GOARCH=amd64 go build -v - -ldflags "-X main.version=${{ env.BINARY_VERSION }}" - -o bin/gomud-linux_x64 . - - - name: Build linux/arm64 - run: >- - env GOOS=linux GOARCH=arm64 go build -v - -ldflags "-X main.version=${{ env.BINARY_VERSION }}" - -o bin/gomud-linux_arm64 . - - - name: Build linux/armv7 - run: >- - env GOOS=linux GOARCH=arm GOARM=7 go build -v - -ldflags "-X main.version=${{ env.BINARY_VERSION }}" - -o bin/gomud-linux_armv7 . - - - name: Upload bin - # actions/upload-artifact v7.0.1 - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a - with: - name: bin-artifact - path: bin/ - - release: - runs-on: ubuntu-24.04 - permissions: - contents: write - needs: - - prep - - build - env: - RELEASE_TAG: ${{ needs.prep.outputs.release_tag }} - RELEASE_TITLE: ${{ needs.prep.outputs.release_title }} - BINARY_VERSION: ${{ needs.prep.outputs.binary_version }} - RELEASE_NOTES_INTRO: ${{ needs.prep.outputs.release_notes_intro }} - RELEASE_NOTES_SUMMARY: ${{ needs.prep.outputs.release_notes_summary }} - DATAFILES_ARCHIVE: ${{ needs.prep.outputs.datafiles_archive }} - CHECKSUMS_FILE: ${{ needs.prep.outputs.checksums_file }} - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - steps: - # actions/checkout v6.0.2 - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - with: - ref: ${{ github.sha }} - persist-credentials: false - - - name: Download builds - # actions/download-artifact v8.0.1 - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c - with: - name: bin-artifact - path: bin/ - - - name: Archive datafiles - run: >- - zip -r - bin/${{ env.DATAFILES_ARCHIVE }} - bin/_datafiles - - - name: Generate release checksums - run: | - cd bin - sha256sum \ - gomud-windows_x64.exe \ - gomud-windows_arm64.exe \ - gomud-darwin_arm64 \ - gomud-darwin_x64 \ - gomud-linux_x64 \ - gomud-linux_arm64 \ - gomud-linux_armv7 \ - "${{ env.DATAFILES_ARCHIVE }}" \ - > "${{ env.CHECKSUMS_FILE }}" - - - name: Write release notes - run: | - latest_release_tag="$( - gh api "repos/${GITHUB_REPOSITORY}/releases/latest" \ - --jq '.tag_name' \ - 2>/dev/null || true - )" - generate_notes_args=( - -f "tag_name=release-notes-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}" - -f "target_commitish=${GITHUB_SHA}" - ) - if [ -n "$latest_release_tag" ]; then - generate_notes_args+=(-f "previous_tag_name=${latest_release_tag}") - changes_since_line="- Changes since: \`${latest_release_tag}\`" - else - changes_since_line="- Changes since: initial release history" - fi - generated_release_notes="$( - gh api \ - -X POST \ - "repos/${GITHUB_REPOSITORY}/releases/generate-notes" \ - "${generate_notes_args[@]}" \ - --jq '.body' - )" - published_at="$(date -u +"%Y-%m-%dT%H:%M:%SZ")" - cat > release-notes.md </dev/null 2>&1; then - gh api \ - -X PATCH \ - "repos/${GITHUB_REPOSITORY}/git/refs/tags/${RELEASE_TAG}" \ - -f "sha=${GITHUB_SHA}" \ - -F force=true - gh release upload "$RELEASE_TAG" "${assets[@]}" --clobber - gh release edit "$RELEASE_TAG" \ - --title "$RELEASE_TITLE" \ - --target "$GITHUB_SHA" \ - --notes-file release-notes.md - else - gh release create "$RELEASE_TAG" \ - "${assets[@]}" \ - --title "$RELEASE_TITLE" \ - --target "$GITHUB_SHA" \ - --notes-file release-notes.md - fi diff --git a/.github/workflows/run-test.yml b/.github/workflows/run-test.yml index acc00e937..d9f3cd130 100644 --- a/.github/workflows/run-test.yml +++ b/.github/workflows/run-test.yml @@ -2,9 +2,6 @@ name: Run Tests "on": - push: - branches: - - master pull_request: types: - opened diff --git a/.github/workflows/stable-release.yml b/.github/workflows/stable-release.yml new file mode 100644 index 000000000..37f2910b3 --- /dev/null +++ b/.github/workflows/stable-release.yml @@ -0,0 +1,359 @@ +--- +name: Stable Release + +"on": + workflow_dispatch: + inputs: + release_tag: + description: Stable semver tag to create, such as v0.10.0 + required: true + type: string + +concurrency: + group: ${{ github.workflow }}-${{ inputs.release_tag }} + cancel-in-progress: false + +permissions: + contents: read + +env: + DATAFILES_ARCHIVE: gomud-ALL-datafiles.zip + CHECKSUMS_FILE: SHA256SUMS.txt + +jobs: + validate-release: + runs-on: ubuntu-24.04 + timeout-minutes: 5 + permissions: + contents: read + env: + RELEASE_TAG: ${{ inputs.release_tag }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + outputs: + binary_version: ${{ steps.meta.outputs.binary_version }} + steps: + # actions/checkout v6.0.2 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd + with: + persist-credentials: false + + - name: Validate stable release request + id: meta + run: | + set -euo pipefail + + semver_re='^v(0|[1-9][0-9]*)\.' + semver_re="${semver_re}(0|[1-9][0-9]*)\." + semver_re="${semver_re}(0|[1-9][0-9]*)$" + if ! printf '%s\n' "$RELEASE_TAG" | grep -Eq "$semver_re"; then + echo "release_tag must be a stable semver tag like v0.10.0" >&2 + exit 1 + fi + + if gh api \ + "repos/${GITHUB_REPOSITORY}/git/ref/tags/${RELEASE_TAG}" \ + >/dev/null 2>&1; then + echo "Tag ${RELEASE_TAG} already exists; stable releases are" \ + "immutable." >&2 + exit 1 + fi + + if gh release view "$RELEASE_TAG" >/dev/null 2>&1; then + echo "Release ${RELEASE_TAG} already exists; stable releases are" \ + "immutable." >&2 + exit 1 + fi + + binary_version="$( + awk -F'"' '/^const VERSION = "/ { print $2; exit }' main.go + )" + if [ -z "$binary_version" ]; then + echo "Could not determine binary version from main.go" >&2 + exit 1 + fi + + echo "binary_version=${binary_version}" >> "$GITHUB_OUTPUT" + + test: + runs-on: ubuntu-24.04 + timeout-minutes: 30 + needs: validate-release + permissions: + contents: read + steps: + # actions/checkout v6.0.2 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd + with: + persist-credentials: false + + - uses: ./.github/actions/setup-go + + - name: Generate code + run: go generate ./... + + - name: Run tests + run: go test -race ./... + + build: + runs-on: ubuntu-24.04 + timeout-minutes: 20 + needs: + - validate-release + - test + permissions: + contents: read + strategy: + fail-fast: false + matrix: + target: + - goos: linux + goarch: amd64 + goarm: '' + asset: gomud-linux_x64 + - goos: linux + goarch: arm64 + goarm: '' + asset: gomud-linux_arm64 + - goos: linux + goarch: arm + goarm: '7' + asset: gomud-linux_armv7 + - goos: windows + goarch: amd64 + goarm: '' + asset: gomud-windows_x64.exe + - goos: windows + goarch: arm64 + goarm: '' + asset: gomud-windows_arm64.exe + - goos: darwin + goarch: amd64 + goarm: '' + asset: gomud-darwin_x64 + - goos: darwin + goarch: arm64 + goarm: '' + asset: gomud-darwin_arm64 + env: + BINARY_VERSION: ${{ needs.validate-release.outputs.binary_version }} + steps: + # actions/checkout v6.0.2 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd + with: + persist-credentials: false + + - uses: ./.github/actions/setup-go + + - name: Generate code + run: go generate ./... + + - name: Build release binary + env: + GOOS: ${{ matrix.target.goos }} + GOARCH: ${{ matrix.target.goarch }} + GOARM: ${{ matrix.target.goarm }} + ASSET_NAME: ${{ matrix.target.asset }} + run: | + set -euo pipefail + + mkdir -p dist + go build \ + -ldflags "-X main.version=${BINARY_VERSION}" \ + -o "dist/${ASSET_NAME}" \ + . + + - name: Upload release binary + # actions/upload-artifact v7.0.1 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a + with: + name: release-binary-${{ matrix.target.asset }} + path: dist/${{ matrix.target.asset }} + if-no-files-found: error + + publish: + runs-on: ubuntu-24.04 + timeout-minutes: 30 + needs: + - validate-release + - build + environment: stable-release + permissions: + contents: write + id-token: write + attestations: write + env: + RELEASE_TAG: ${{ inputs.release_tag }} + RELEASE_TITLE: ${{ inputs.release_tag }} + BINARY_VERSION: ${{ needs.validate-release.outputs.binary_version }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + steps: + # actions/checkout v6.0.2 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd + with: + persist-credentials: false + + - name: Recheck stable release immutability + run: | + set -euo pipefail + + if gh api \ + "repos/${GITHUB_REPOSITORY}/git/ref/tags/${RELEASE_TAG}" \ + >/dev/null 2>&1; then + echo "Tag ${RELEASE_TAG} already exists; stable releases are" \ + "immutable." >&2 + exit 1 + fi + + if gh release view "$RELEASE_TAG" >/dev/null 2>&1; then + echo "Release ${RELEASE_TAG} already exists; stable releases are" \ + "immutable." >&2 + exit 1 + fi + + - name: Download release binaries + # actions/download-artifact v8.0.1 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c + with: + pattern: release-binary-* + path: bin/ + merge-multiple: true + + - name: Assemble release assets + run: | + set -euo pipefail + + zip -r "bin/${DATAFILES_ARCHIVE}" _datafiles + + cd bin + sha256sum \ + gomud-linux_x64 \ + gomud-linux_arm64 \ + gomud-linux_armv7 \ + gomud-windows_x64.exe \ + gomud-windows_arm64.exe \ + gomud-darwin_x64 \ + gomud-darwin_arm64 \ + "${DATAFILES_ARCHIVE}" \ + > "${CHECKSUMS_FILE}" + + - name: Write release notes + env: + RELEASE_KIND: stable + RELEASE_NOTES_FILE: release-notes.md + run: scripts/release-notes.sh + + - name: Attest linux x64 binary + # actions/attest-build-provenance v4.1.0 + # yamllint disable-line rule:line-length + uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 + with: + subject-path: bin/gomud-linux_x64 + + - name: Attest linux arm64 binary + # actions/attest-build-provenance v4.1.0 + # yamllint disable-line rule:line-length + uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 + with: + subject-path: bin/gomud-linux_arm64 + + - name: Attest linux armv7 binary + # actions/attest-build-provenance v4.1.0 + # yamllint disable-line rule:line-length + uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 + with: + subject-path: bin/gomud-linux_armv7 + + - name: Attest windows x64 binary + # actions/attest-build-provenance v4.1.0 + # yamllint disable-line rule:line-length + uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 + with: + subject-path: bin/gomud-windows_x64.exe + + - name: Attest windows arm64 binary + # actions/attest-build-provenance v4.1.0 + # yamllint disable-line rule:line-length + uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 + with: + subject-path: bin/gomud-windows_arm64.exe + + - name: Attest darwin x64 binary + # actions/attest-build-provenance v4.1.0 + # yamllint disable-line rule:line-length + uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 + with: + subject-path: bin/gomud-darwin_x64 + + - name: Attest darwin arm64 binary + # actions/attest-build-provenance v4.1.0 + # yamllint disable-line rule:line-length + uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 + with: + subject-path: bin/gomud-darwin_arm64 + + - name: Attest checksum manifest + # actions/attest-build-provenance v4.1.0 + # yamllint disable-line rule:line-length + uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 + with: + subject-path: bin/SHA256SUMS.txt + + - name: Create stable draft release + id: draft + run: | + set -euo pipefail + + release_id="$( + gh api \ + -X POST \ + "repos/${GITHUB_REPOSITORY}/releases" \ + -f "tag_name=${RELEASE_TAG}" \ + -f "target_commitish=${GITHUB_SHA}" \ + -f "name=${RELEASE_TITLE}" \ + -f "body=Stable release assets are uploading." \ + -f "make_latest=true" \ + -F draft=true \ + -F prerelease=false \ + --jq '.id' + )" + + echo "release_id=${release_id}" >> "$GITHUB_OUTPUT" + + - name: Upload stable release assets + run: | + set -euo pipefail + + assets=( + bin/gomud-linux_x64 + bin/gomud-linux_arm64 + bin/gomud-linux_armv7 + bin/gomud-windows_x64.exe + bin/gomud-windows_arm64.exe + bin/gomud-darwin_x64 + bin/gomud-darwin_arm64 + "bin/${DATAFILES_ARCHIVE}" + "bin/${CHECKSUMS_FILE}" + ) + + gh release upload "$RELEASE_TAG" "${assets[@]}" + + - name: Attach stable release notes + run: | + set -euo pipefail + + gh release edit "$RELEASE_TAG" \ + --notes-file release-notes.md \ + --title "$RELEASE_TITLE" \ + --latest + + - name: Publish stable release + env: + RELEASE_ID: ${{ steps.draft.outputs.release_id }} + run: | + set -euo pipefail + + gh api \ + -X PATCH \ + "repos/${GITHUB_REPOSITORY}/releases/${RELEASE_ID}" \ + -F draft=false \ + -F prerelease=false diff --git a/Makefile b/Makefile index b41e012df..23cffef73 100644 --- a/Makefile +++ b/Makefile @@ -74,6 +74,9 @@ ci-local-inner: ### Run the local CI checks inside the CI tool image. act $(ACT_FLAGS) --dryrun push \ -e .github/act/push_master.json \ -W .github/workflows/build-and-release.yml + act $(ACT_FLAGS) --dryrun workflow_dispatch \ + -e .github/act/stable_release.json \ + -W .github/workflows/stable-release.yml act $(ACT_FLAGS) --dryrun push $(ACT_DRYRUN_SECRETS) \ -e .github/act/push_master.json \ -W .github/workflows/docker-package.yml diff --git a/scripts/release-notes.sh b/scripts/release-notes.sh new file mode 100755 index 000000000..caa6c808b --- /dev/null +++ b/scripts/release-notes.sh @@ -0,0 +1,129 @@ +#!/usr/bin/env bash +set -euo pipefail + +notes_file="${RELEASE_NOTES_FILE:-release-notes.md}" +generated_notes_file="${GENERATED_NOTES_FILE:-generated-release-notes.md}" +release_kind="${RELEASE_KIND:-}" +release_tag="${RELEASE_TAG:-}" +binary_version="${BINARY_VERSION:-}" +repository="${GITHUB_REPOSITORY:-}" +commit_sha="${GITHUB_SHA:-}" +ref_name="${GITHUB_REF_NAME:-}" +datafiles_archive="${DATAFILES_ARCHIVE:-gomud-ALL-datafiles.zip}" +checksums_file="${CHECKSUMS_FILE:-SHA256SUMS.txt}" + +require_env() { + local name="$1" + local value="$2" + + if [ -z "$value" ]; then + printf '%s is required\n' "$name" >&2 + exit 1 + fi +} + +require_env RELEASE_KIND "$release_kind" +require_env RELEASE_TAG "$release_tag" +require_env BINARY_VERSION "$binary_version" +require_env GITHUB_REPOSITORY "$repository" +require_env GITHUB_SHA "$commit_sha" + +case "$release_kind" in +prerelease | stable) + ;; +*) + printf 'RELEASE_KIND must be prerelease or stable\n' >&2 + exit 1 + ;; +esac + +previous_tag="${PREVIOUS_TAG_NAME:-}" +if [ -z "$previous_tag" ] && [ "${RELEASE_NOTES_SKIP_GH:-}" != "true" ]; then + previous_tag="$( + gh api "repos/${repository}/releases/latest" \ + --jq '.tag_name' \ + 2>/dev/null || true + )" +fi + +if [ "${RELEASE_NOTES_SKIP_GH:-}" = "true" ]; then + printf 'Generated release notes skipped for local dry run.\n' \ + >"$generated_notes_file" +else + generate_notes_args=( + -f "tag_name=${release_tag}" + -f "target_commitish=${commit_sha}" + ) + if [ -n "$previous_tag" ] && [ "$previous_tag" != "$release_tag" ]; then + generate_notes_args+=(-f "previous_tag_name=${previous_tag}") + fi + + gh api \ + -X POST \ + "repos/${repository}/releases/generate-notes" \ + "${generate_notes_args[@]}" \ + --jq '.body' \ + >"$generated_notes_file" +fi + +published_at="$(date -u +"%Y-%m-%dT%H:%M:%SZ")" +if [ -n "$previous_tag" ] && [ "$previous_tag" != "$release_tag" ]; then + changes_since="Changes since: \`${previous_tag}\`" +else + changes_since="Changes since: initial release history" +fi + +if [ "$release_kind" = "prerelease" ]; then + overview="Rolling prerelease build from \`${ref_name:-master}\`." + summary="This mutable prerelease is replaced on each successful merge to \`master\`." +else + overview="Stable release \`${release_tag}\`." + summary="This stable release is immutable. Tags and assets are not replaced by workflow policy." +fi + +cat >"$notes_file" < --repo ${repository}\`. + +## Changes + +EOF + +cat "$generated_notes_file" >>"$notes_file" From bf02d961ccf62a59958967deeab5dc598e42bff7 Mon Sep 17 00:00:00 2001 From: Casey Labs <4674433+CaseyLabs@users.noreply.github.com> Date: Mon, 8 Jun 2026 16:04:50 -0700 Subject: [PATCH 02/19] Fix rolling prerelease note generation --- .github/RELEASING.md | 2 +- {scripts => .github/scripts}/release-notes.sh | 7 ++++++- .github/workflows/build-and-release.yml | 2 +- .github/workflows/stable-release.yml | 2 +- 4 files changed, 9 insertions(+), 4 deletions(-) rename {scripts => .github/scripts}/release-notes.sh (95%) diff --git a/.github/RELEASING.md b/.github/RELEASING.md index a1e007714..37cc555e9 100644 --- a/.github/RELEASING.md +++ b/.github/RELEASING.md @@ -53,7 +53,7 @@ Each prerelease and stable release includes: - `gomud-ALL-datafiles.zip` - `SHA256SUMS.txt` -Release notes are generated by `scripts/release-notes.sh` and include `Overview`, +Release notes are generated by `.github/scripts/release-notes.sh` and include `Overview`, `Downloads`, `Install From Source`, `Manual Binary Install`, `Verify Provenance`, and `Changes`. The `Changes` section appends GitHub auto-generated notes from `releases/generate-notes`. diff --git a/scripts/release-notes.sh b/.github/scripts/release-notes.sh similarity index 95% rename from scripts/release-notes.sh rename to .github/scripts/release-notes.sh index caa6c808b..24f241c3b 100755 --- a/scripts/release-notes.sh +++ b/.github/scripts/release-notes.sh @@ -50,8 +50,13 @@ if [ "${RELEASE_NOTES_SKIP_GH:-}" = "true" ]; then printf 'Generated release notes skipped for local dry run.\n' \ >"$generated_notes_file" else + notes_tag="$release_tag" + if [ "$release_kind" = "prerelease" ]; then + notes_tag="${release_tag}-notes-${commit_sha}" + fi + generate_notes_args=( - -f "tag_name=${release_tag}" + -f "tag_name=${notes_tag}" -f "target_commitish=${commit_sha}" ) if [ -n "$previous_tag" ] && [ "$previous_tag" != "$release_tag" ]; then diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml index f0a9aa2f1..abef9fb1e 100644 --- a/.github/workflows/build-and-release.yml +++ b/.github/workflows/build-and-release.yml @@ -192,7 +192,7 @@ jobs: env: RELEASE_KIND: prerelease RELEASE_NOTES_FILE: release-notes.md - run: scripts/release-notes.sh + run: .github/scripts/release-notes.sh - name: Attest linux x64 binary # actions/attest-build-provenance v4.1.0 diff --git a/.github/workflows/stable-release.yml b/.github/workflows/stable-release.yml index 37f2910b3..b89295d3b 100644 --- a/.github/workflows/stable-release.yml +++ b/.github/workflows/stable-release.yml @@ -240,7 +240,7 @@ jobs: env: RELEASE_KIND: stable RELEASE_NOTES_FILE: release-notes.md - run: scripts/release-notes.sh + run: .github/scripts/release-notes.sh - name: Attest linux x64 binary # actions/attest-build-provenance v4.1.0 From a751c5d715f04db186ce18048546f73cdb552960 Mon Sep 17 00:00:00 2001 From: Casey Labs <4674433+CaseyLabs@users.noreply.github.com> Date: Mon, 8 Jun 2026 16:17:49 -0700 Subject: [PATCH 03/19] ci: reduce release workflow duplication --- .github/RELEASING.md | 14 +- .github/actions/codegen-and-test/action.yml | 10 +- .github/scripts/release-assets.sh | 90 ++++++++++++ .github/scripts/release-notes.sh | 15 +- .github/workflows/build-and-release.yml | 154 +++++--------------- .github/workflows/stable-release.yml | 154 +++++--------------- Makefile | 24 ++- PLAN.md | 108 ++++++++++++++ 8 files changed, 301 insertions(+), 268 deletions(-) create mode 100755 .github/scripts/release-assets.sh create mode 100644 PLAN.md diff --git a/.github/RELEASING.md b/.github/RELEASING.md index 37cc555e9..46893e39b 100644 --- a/.github/RELEASING.md +++ b/.github/RELEASING.md @@ -41,17 +41,9 @@ rolling `prerelease` release can remain mutable. ### Assets -Each prerelease and stable release includes: - -- `gomud-linux_x64` -- `gomud-linux_arm64` -- `gomud-linux_armv7` -- `gomud-windows_x64.exe` -- `gomud-windows_arm64.exe` -- `gomud-darwin_x64` -- `gomud-darwin_arm64` -- `gomud-ALL-datafiles.zip` -- `SHA256SUMS.txt` +Prerelease and stable release assets are defined by +`.github/scripts/release-assets.sh`. Each release includes the supported +OS/architecture binaries, `gomud-ALL-datafiles.zip`, and `SHA256SUMS.txt`. Release notes are generated by `.github/scripts/release-notes.sh` and include `Overview`, `Downloads`, `Install From Source`, `Manual Binary Install`, `Verify Provenance`, diff --git a/.github/actions/codegen-and-test/action.yml b/.github/actions/codegen-and-test/action.yml index ffdb92413..8622531a9 100644 --- a/.github/actions/codegen-and-test/action.yml +++ b/.github/actions/codegen-and-test/action.yml @@ -30,11 +30,7 @@ runs: echo "::endgroup::" } - build_target linux amd64 "" gomud-linux_x64 - build_target linux arm64 "" gomud-linux_arm64 - build_target linux arm 7 gomud-linux_armv7 - build_target windows amd64 "" gomud-windows_x64.exe - build_target windows arm64 "" gomud-windows_arm64.exe - build_target darwin amd64 "" gomud-darwin_x64 - build_target darwin arm64 "" gomud-darwin_arm64 + while IFS='|' read -r _label goos goarch goarm output; do + build_target "$goos" "$goarch" "$goarm" "$output" + done < <(.github/scripts/release-assets.sh targets) shell: bash diff --git a/.github/scripts/release-assets.sh b/.github/scripts/release-assets.sh new file mode 100755 index 000000000..9e81e5763 --- /dev/null +++ b/.github/scripts/release-assets.sh @@ -0,0 +1,90 @@ +#!/usr/bin/env bash +set -euo pipefail + +datafiles_archive="${DATAFILES_ARCHIVE:-gomud-ALL-datafiles.zip}" +checksums_file="${CHECKSUMS_FILE:-SHA256SUMS.txt}" + +release_targets() { + cat <<'EOF' +Linux amd64|linux|amd64||gomud-linux_x64 +Linux arm64|linux|arm64||gomud-linux_arm64 +Linux arm/v7|linux|arm|7|gomud-linux_armv7 +Windows amd64|windows|amd64||gomud-windows_x64.exe +Windows arm64|windows|arm64||gomud-windows_arm64.exe +macOS amd64|darwin|amd64||gomud-darwin_x64 +macOS arm64|darwin|arm64||gomud-darwin_arm64 +EOF +} + +binary_names() { + local label goos goarch goarm asset + + while IFS='|' read -r label goos goarch goarm asset; do + printf '%s\n' "$asset" + done < <(release_targets) +} + +binary_paths() { + local asset + + while IFS= read -r asset; do + printf 'bin/%s\n' "$asset" + done < <(binary_names) +} + +checksum_names() { + binary_names + printf '%s\n' "$datafiles_archive" +} + +upload_paths() { + binary_paths + printf 'bin/%s\n' "$datafiles_archive" + printf 'bin/%s\n' "$checksums_file" +} + +attestation_paths() { + binary_paths + printf 'bin/%s\n' "$checksums_file" +} + +downloads_markdown() { + local label goos goarch goarm asset + + while IFS='|' read -r label goos goarch goarm asset; do + printf -- '- %s: `%s`\n' "$label" "$asset" + done < <(release_targets) + printf -- '- Datafiles: `%s`\n' "$datafiles_archive" + printf -- '- Checksums: `%s`\n' "$checksums_file" +} + +case "${1:-}" in +targets) + release_targets + ;; +binary-names) + binary_names + ;; +binary-paths) + binary_paths + ;; +checksum-names) + checksum_names + ;; +upload-paths) + upload_paths + ;; +attestation-paths) + attestation_paths + ;; +downloads-markdown) + downloads_markdown + ;; +*) + printf 'Usage: %s COMMAND\n' "$0" >&2 + printf 'Commands: targets, binary-names, binary-paths, ' >&2 + printf 'checksum-names, upload-paths, attestation-paths, ' >&2 + printf 'downloads-markdown\n' >&2 + exit 2 + ;; +esac diff --git a/.github/scripts/release-notes.sh b/.github/scripts/release-notes.sh index 24f241c3b..2b3374885 100755 --- a/.github/scripts/release-notes.sh +++ b/.github/scripts/release-notes.sh @@ -11,6 +11,11 @@ commit_sha="${GITHUB_SHA:-}" ref_name="${GITHUB_REF_NAME:-}" datafiles_archive="${DATAFILES_ARCHIVE:-gomud-ALL-datafiles.zip}" checksums_file="${CHECKSUMS_FILE:-SHA256SUMS.txt}" +downloads="$( + DATAFILES_ARCHIVE="$datafiles_archive" \ + CHECKSUMS_FILE="$checksums_file" \ + .github/scripts/release-assets.sh downloads-markdown +)" require_env() { local name="$1" @@ -100,15 +105,7 @@ ${summary} ## Downloads -- Linux amd64: \`gomud-linux_x64\` -- Linux arm64: \`gomud-linux_arm64\` -- Linux arm/v7: \`gomud-linux_armv7\` -- Windows amd64: \`gomud-windows_x64.exe\` -- Windows arm64: \`gomud-windows_arm64.exe\` -- macOS amd64: \`gomud-darwin_x64\` -- macOS arm64: \`gomud-darwin_arm64\` -- Datafiles: \`${datafiles_archive}\` -- Checksums: \`${checksums_file}\` +${downloads} ## Install From Source diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml index abef9fb1e..9e9269eba 100644 --- a/.github/workflows/build-and-release.yml +++ b/.github/workflows/build-and-release.yml @@ -73,38 +73,6 @@ jobs: - test permissions: contents: read - strategy: - fail-fast: false - matrix: - target: - - goos: linux - goarch: amd64 - goarm: '' - asset: gomud-linux_x64 - - goos: linux - goarch: arm64 - goarm: '' - asset: gomud-linux_arm64 - - goos: linux - goarch: arm - goarm: '7' - asset: gomud-linux_armv7 - - goos: windows - goarch: amd64 - goarm: '' - asset: gomud-windows_x64.exe - - goos: windows - goarch: arm64 - goarm: '' - asset: gomud-windows_arm64.exe - - goos: darwin - goarch: amd64 - goarm: '' - asset: gomud-darwin_x64 - - goos: darwin - goarch: arm64 - goarm: '' - asset: gomud-darwin_arm64 env: BINARY_VERSION: ${{ needs.metadata.outputs.binary_version }} steps: @@ -118,27 +86,33 @@ jobs: - name: Generate code run: go generate ./... - - name: Build release binary - env: - GOOS: ${{ matrix.target.goos }} - GOARCH: ${{ matrix.target.goarch }} - GOARM: ${{ matrix.target.goarm }} - ASSET_NAME: ${{ matrix.target.asset }} + - name: Build release binaries run: | set -euo pipefail mkdir -p dist - go build \ - -ldflags "-X main.version=${BINARY_VERSION}" \ - -o "dist/${ASSET_NAME}" \ - . - - name: Upload release binary + while IFS='|' read -r _label goos goarch goarm asset; do + target="${goos}/${goarch}" + if [ -n "$goarm" ]; then + target="${target} GOARM=${goarm}" + fi + + echo "::group::Build ${asset} (${target})" + env GOOS="$goos" GOARCH="$goarch" GOARM="$goarm" \ + go build \ + -ldflags "-X main.version=${BINARY_VERSION}" \ + -o "dist/${asset}" \ + . + echo "::endgroup::" + done < <(.github/scripts/release-assets.sh targets) + + - name: Upload release binaries # actions/upload-artifact v7.0.1 uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a with: - name: release-binary-${{ matrix.target.asset }} - path: dist/${{ matrix.target.asset }} + name: release-binaries + path: dist/* if-no-files-found: error publish: @@ -166,27 +140,21 @@ jobs: # actions/download-artifact v8.0.1 uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c with: - pattern: release-binary-* + name: release-binaries path: bin/ - merge-multiple: true - name: Assemble release assets run: | set -euo pipefail + mapfile -t checksum_assets < <( + .github/scripts/release-assets.sh checksum-names + ) + zip -r "bin/${DATAFILES_ARCHIVE}" _datafiles cd bin - sha256sum \ - gomud-linux_x64 \ - gomud-linux_arm64 \ - gomud-linux_armv7 \ - gomud-windows_x64.exe \ - gomud-windows_arm64.exe \ - gomud-darwin_x64 \ - gomud-darwin_arm64 \ - "${DATAFILES_ARCHIVE}" \ - > "${CHECKSUMS_FILE}" + sha256sum "${checksum_assets[@]}" > "${CHECKSUMS_FILE}" - name: Write release notes env: @@ -194,76 +162,28 @@ jobs: RELEASE_NOTES_FILE: release-notes.md run: .github/scripts/release-notes.sh - - name: Attest linux x64 binary - # actions/attest-build-provenance v4.1.0 - # yamllint disable-line rule:line-length - uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 - with: - subject-path: bin/gomud-linux_x64 - - - name: Attest linux arm64 binary - # actions/attest-build-provenance v4.1.0 - # yamllint disable-line rule:line-length - uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 - with: - subject-path: bin/gomud-linux_arm64 - - - name: Attest linux armv7 binary - # actions/attest-build-provenance v4.1.0 - # yamllint disable-line rule:line-length - uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 - with: - subject-path: bin/gomud-linux_armv7 - - - name: Attest windows x64 binary - # actions/attest-build-provenance v4.1.0 - # yamllint disable-line rule:line-length - uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 - with: - subject-path: bin/gomud-windows_x64.exe - - - name: Attest windows arm64 binary - # actions/attest-build-provenance v4.1.0 - # yamllint disable-line rule:line-length - uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 - with: - subject-path: bin/gomud-windows_arm64.exe - - - name: Attest darwin x64 binary - # actions/attest-build-provenance v4.1.0 - # yamllint disable-line rule:line-length - uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 - with: - subject-path: bin/gomud-darwin_x64 - - - name: Attest darwin arm64 binary - # actions/attest-build-provenance v4.1.0 - # yamllint disable-line rule:line-length - uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 - with: - subject-path: bin/gomud-darwin_arm64 + - name: Resolve attestation paths + id: release-assets + run: | + { + echo 'attestation_paths<> "$GITHUB_OUTPUT" - - name: Attest checksum manifest + - name: Attest release assets # actions/attest-build-provenance v4.1.0 # yamllint disable-line rule:line-length uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 with: - subject-path: bin/SHA256SUMS.txt + subject-path: ${{ steps.release-assets.outputs.attestation_paths }} - name: Publish rolling prerelease run: | set -euo pipefail - assets=( - bin/gomud-linux_x64 - bin/gomud-linux_arm64 - bin/gomud-linux_armv7 - bin/gomud-windows_x64.exe - bin/gomud-windows_arm64.exe - bin/gomud-darwin_x64 - bin/gomud-darwin_arm64 - "bin/${DATAFILES_ARCHIVE}" - "bin/${CHECKSUMS_FILE}" + mapfile -t assets < <( + .github/scripts/release-assets.sh upload-paths ) if gh api \ diff --git a/.github/workflows/stable-release.yml b/.github/workflows/stable-release.yml index b89295d3b..1e94c724a 100644 --- a/.github/workflows/stable-release.yml +++ b/.github/workflows/stable-release.yml @@ -102,38 +102,6 @@ jobs: - test permissions: contents: read - strategy: - fail-fast: false - matrix: - target: - - goos: linux - goarch: amd64 - goarm: '' - asset: gomud-linux_x64 - - goos: linux - goarch: arm64 - goarm: '' - asset: gomud-linux_arm64 - - goos: linux - goarch: arm - goarm: '7' - asset: gomud-linux_armv7 - - goos: windows - goarch: amd64 - goarm: '' - asset: gomud-windows_x64.exe - - goos: windows - goarch: arm64 - goarm: '' - asset: gomud-windows_arm64.exe - - goos: darwin - goarch: amd64 - goarm: '' - asset: gomud-darwin_x64 - - goos: darwin - goarch: arm64 - goarm: '' - asset: gomud-darwin_arm64 env: BINARY_VERSION: ${{ needs.validate-release.outputs.binary_version }} steps: @@ -147,27 +115,33 @@ jobs: - name: Generate code run: go generate ./... - - name: Build release binary - env: - GOOS: ${{ matrix.target.goos }} - GOARCH: ${{ matrix.target.goarch }} - GOARM: ${{ matrix.target.goarm }} - ASSET_NAME: ${{ matrix.target.asset }} + - name: Build release binaries run: | set -euo pipefail mkdir -p dist - go build \ - -ldflags "-X main.version=${BINARY_VERSION}" \ - -o "dist/${ASSET_NAME}" \ - . - - name: Upload release binary + while IFS='|' read -r _label goos goarch goarm asset; do + target="${goos}/${goarch}" + if [ -n "$goarm" ]; then + target="${target} GOARM=${goarm}" + fi + + echo "::group::Build ${asset} (${target})" + env GOOS="$goos" GOARCH="$goarch" GOARM="$goarm" \ + go build \ + -ldflags "-X main.version=${BINARY_VERSION}" \ + -o "dist/${asset}" \ + . + echo "::endgroup::" + done < <(.github/scripts/release-assets.sh targets) + + - name: Upload release binaries # actions/upload-artifact v7.0.1 uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a with: - name: release-binary-${{ matrix.target.asset }} - path: dist/${{ matrix.target.asset }} + name: release-binaries + path: dist/* if-no-files-found: error publish: @@ -214,27 +188,21 @@ jobs: # actions/download-artifact v8.0.1 uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c with: - pattern: release-binary-* + name: release-binaries path: bin/ - merge-multiple: true - name: Assemble release assets run: | set -euo pipefail + mapfile -t checksum_assets < <( + .github/scripts/release-assets.sh checksum-names + ) + zip -r "bin/${DATAFILES_ARCHIVE}" _datafiles cd bin - sha256sum \ - gomud-linux_x64 \ - gomud-linux_arm64 \ - gomud-linux_armv7 \ - gomud-windows_x64.exe \ - gomud-windows_arm64.exe \ - gomud-darwin_x64 \ - gomud-darwin_arm64 \ - "${DATAFILES_ARCHIVE}" \ - > "${CHECKSUMS_FILE}" + sha256sum "${checksum_assets[@]}" > "${CHECKSUMS_FILE}" - name: Write release notes env: @@ -242,61 +210,21 @@ jobs: RELEASE_NOTES_FILE: release-notes.md run: .github/scripts/release-notes.sh - - name: Attest linux x64 binary - # actions/attest-build-provenance v4.1.0 - # yamllint disable-line rule:line-length - uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 - with: - subject-path: bin/gomud-linux_x64 - - - name: Attest linux arm64 binary - # actions/attest-build-provenance v4.1.0 - # yamllint disable-line rule:line-length - uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 - with: - subject-path: bin/gomud-linux_arm64 - - - name: Attest linux armv7 binary - # actions/attest-build-provenance v4.1.0 - # yamllint disable-line rule:line-length - uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 - with: - subject-path: bin/gomud-linux_armv7 - - - name: Attest windows x64 binary - # actions/attest-build-provenance v4.1.0 - # yamllint disable-line rule:line-length - uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 - with: - subject-path: bin/gomud-windows_x64.exe - - - name: Attest windows arm64 binary - # actions/attest-build-provenance v4.1.0 - # yamllint disable-line rule:line-length - uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 - with: - subject-path: bin/gomud-windows_arm64.exe - - - name: Attest darwin x64 binary - # actions/attest-build-provenance v4.1.0 - # yamllint disable-line rule:line-length - uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 - with: - subject-path: bin/gomud-darwin_x64 - - - name: Attest darwin arm64 binary - # actions/attest-build-provenance v4.1.0 - # yamllint disable-line rule:line-length - uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 - with: - subject-path: bin/gomud-darwin_arm64 + - name: Resolve attestation paths + id: release-assets + run: | + { + echo 'attestation_paths<> "$GITHUB_OUTPUT" - - name: Attest checksum manifest + - name: Attest release assets # actions/attest-build-provenance v4.1.0 # yamllint disable-line rule:line-length uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 with: - subject-path: bin/SHA256SUMS.txt + subject-path: ${{ steps.release-assets.outputs.attestation_paths }} - name: Create stable draft release id: draft @@ -323,16 +251,8 @@ jobs: run: | set -euo pipefail - assets=( - bin/gomud-linux_x64 - bin/gomud-linux_arm64 - bin/gomud-linux_armv7 - bin/gomud-windows_x64.exe - bin/gomud-windows_arm64.exe - bin/gomud-darwin_x64 - bin/gomud-darwin_arm64 - "bin/${DATAFILES_ARCHIVE}" - "bin/${CHECKSUMS_FILE}" + mapfile -t assets < <( + .github/scripts/release-assets.sh upload-paths ) gh release upload "$RELEASE_TAG" "${assets[@]}" diff --git a/Makefile b/Makefile index 23cffef73..b54e9eb7e 100644 --- a/Makefile +++ b/Makefile @@ -205,13 +205,23 @@ coverage: .PHONY: js-lint js-lint: ### Run Javascript linter -# Grep filtering it to remove errors reported by docker image around npm packages -# if "### errors" is found in the output, exits with an error code of 1 -# This should allow us to use it in CI/CD. - @docker run --rm -v "$(PWD)":/app -w /app node:22 sh -lc "\ - npx --yes jshint@$(JSHINT_VERSION) $(WEBCLIENT_BASE_JS) && \ - npx --yes jshint@$(JSHINT_VERSION) --config .jshintrc.webclient-windows $(WEBCLIENT_WINDOW_JS)" \ - 2>&1 | grep -v "^npm " | tee /dev/stderr | grep -Eq "^[0-9]+ errors" && exit 1 || true + @if command -v npx >/dev/null 2>&1; then \ + npx --yes --loglevel=error jshint@$(JSHINT_VERSION) \ + $(WEBCLIENT_BASE_JS); \ + npx --yes --loglevel=error jshint@$(JSHINT_VERSION) \ + --config .jshintrc.webclient-windows \ + $(WEBCLIENT_WINDOW_JS); \ + elif command -v docker >/dev/null 2>&1; then \ + docker run --rm -v "$(PWD)":/app -w /app node:22 sh -lc "\ + npx --yes --loglevel=error jshint@$(JSHINT_VERSION) \ + $(WEBCLIENT_BASE_JS) && \ + npx --yes --loglevel=error jshint@$(JSHINT_VERSION) \ + --config .jshintrc.webclient-windows \ + $(WEBCLIENT_WINDOW_JS)"; \ + else \ + echo "js-lint requires npx or docker" >&2; \ + exit 127; \ + fi # # diff --git a/PLAN.md b/PLAN.md new file mode 100644 index 000000000..6f78a641f --- /dev/null +++ b/PLAN.md @@ -0,0 +1,108 @@ +# Modernize GitHub Actions and Release Builds + +## Summary + +- Implement the work across four focused PRs to reduce release risk. +- Split the current draft branch into separate runtime and release-automation PRs because the XP clamp and GitHub Actions changes have different reviewers, risks, and rollback paths. +- Support official release binaries for `linux/amd64`, `linux/arm64`, `linux/arm/v7`, `windows/amd64`, `windows/arm64`, `darwin/amd64`, and `darwin/arm64`. +- Keep XP values as the current `int` domain and clamp XP formulas with `math.MaxInt`. +- Split mutable prereleases from protected stable releases, then add targeted CI speedups. + +## PR 1: Clamp XP Progression Values + +- Replace `Character.XPTL`'s `math.MaxInt64` clamp with `math.MaxInt`. +- Add the same `math.MaxInt` clamp to `internal/web/api_v1_progression.go`'s `xpTLWithCfg`. +- Add high-XP progression tests proving engine and admin preview clamp consistently. + +## PR 2: Update Current Release Asset Matrix + +- Replace `linux/arm GOARM=5` with `linux/arm GOARM=7`. +- Add `linux/arm64`. +- Preserve existing `x64` asset names and add ARM assets: + - `gomud-linux_x64` + - `gomud-linux_arm64` + - `gomud-linux_armv7` + - `gomud-windows_x64.exe` + - `gomud-windows_arm64.exe` + - `gomud-darwin_x64` + - `gomud-darwin_arm64` +- Update current release workflows' build steps, checksum generation, and upload lists for the seven binaries. +- Add CI cross-compile checks for all seven release targets + +## PR 3: Redesign Release Publishing + +- If possible: reviewers should not be notified for draft PRs, only when the PR is set to ready for review. +- Split the current mixed release flow into: + - `Prerelease`: push to `master`, mutable rolling `prerelease`, no protected environment. + - `Stable Release`: manual `workflow_dispatch` with required semver `release_tag`, protected GitHub Environment, no tag moves or asset clobbering. +- Keep repo-wide immutable releases disabled while rolling `prerelease` stays mutable. +- Enforce stable-release immutability by workflow policy: fail if the semver tag or release already exists. +- Publish stable releases draft-first: create draft, upload assets, attach notes, then publish. +- Define release targets once in `.github/scripts/release-assets.sh`. +- Build release binaries from that target table and upload one + `release-binaries` artifact. +- Assemble all binaries, `gomud-ALL-datafiles.zip`, and `SHA256SUMS.txt` in the publish job. +- Add artifact attestations for each binary and `SHA256SUMS.txt` with a single + multi-subject attestation step. +- Use job-level permissions: + - build/test jobs: `contents: read`; + - publish jobs: `contents: write`, `id-token: write`, `attestations: write`. +- Centralize release note generation in `scripts/release-notes.sh` or a composite action. +- Release notes include `Overview`, `Downloads`, `Install From Source`, `Manual Binary Install`, `Verify Provenance`, and `Changes`. +- Insert GitHub auto-generated notes from `releases/generate-notes`. +- Avoid shell-injection risk by writing generated notes to files and not evaluating generated release text as shell input. +- Drop `go build -v` from release builds unless debugging. +- Avoid duplicate full test runs on `master` by making prerelease publishing depend on the normal test workflow success or by combining test/build/release with a single test job. + +## PR 4: Installers and CI Optimization + +- Keep current installers as source-build installers. +- Add release ref support: + - `scripts/install.sh`: support `GOMUD_VER=v0.9.9` or `GOMUD_VER=prerelease` before build. + - `scripts/install.ps1`: support `GOMUD_VER` environment variable and `-version` parameter. +- Update `scripts/install.sh` architecture detection to recognize `armv7l` as ARMv7. +- Do not claim install scripts download release binaries. +- Release notes state official Windows binary assets are `windows/amd64` and `windows/arm64`. +- Add a docs-only change detector so Go tests, release cross-compiles, and Docker builds can be skipped when changes are limited to docs or non-runtime metadata. +- Keep checks enabled for runtime/build inputs: `*.go`, `go.mod`, `go.sum`, `Makefile`, `_datafiles/**`, `modules/**`, `cmd/**`, `internal/**`, `scripts/**`, `provisioning/**`, `compose.yml`, and relevant workflow files. +- Make Docker PR builds conditional on Docker/runtime/build input changes. +- Keep `go test -race ./...` on PRs and `master` initially; revisit PR race tests only if CI time remains a problem. +- Keep `cancel-in-progress: true` for PR test/lint runs. +- Consider `cancel-in-progress: true` for Docker `master` image builds so only the newest image publishes. +- Keep stable release publishing `cancel-in-progress: false`. +- Use separate Docker cache scopes for PR and master if cache contention appears. +- Fix `fmtcheck` so it checks formatting without first mutating files. +- Fix `make js-lint` so missing Docker does not get masked as success; prefer + local `npx`, fall back to Docker, and fail if neither is available. +- Correct Dependabot Docker paths to `/provisioning` and `/provisioning/terminal`. +- Keep `go generate` behavior explicit: run once in CI and verify generated output is clean before tests/builds depend on it. + +## Test Plan + +- PR 1: + - `go test ./internal/characters ./internal/web` + - `make validate` +- PR 2: + - `make validate` + - `go test -race ./...` + - all seven supported cross-compiles + - verify current release workflow dry-run/checksums reference all seven binaries +- PR 3: + - workflow lint / `make ci-local` if Docker is available + - dry-run release note generation + - prerelease run on `master` + - stable release dispatch against a test semver tag, including existing tag/release failure behavior + - verify binaries, datafiles archive, checksums, and attestations +- PR 4: + - installer source-build paths with and without `GOMUD_VER` / `-Ref` + - Linux `armv7l` detection logic + - docs-only and Docker-conditional skip behavior with representative changed-file sets + - `make ci-local` if Docker is available + +## Assumptions + +- XP remains an `int` domain; no `int64` persistence/API migration is in scope. +- 32-bit official binary support means Linux ARMv7 only, not ARMv5 or `linux/386`. +- Existing `x64` artifact names are preserved for compatibility. +- Release asset renames apply to new releases; stale asset cleanup is not required for this branch's release path. +- Repo-wide immutable releases remain disabled while rolling `prerelease` stays mutable. From 9b9ccb69375c411a4c2244340e3b24d560d700d7 Mon Sep 17 00:00:00 2001 From: Casey Labs <4674433+CaseyLabs@users.noreply.github.com> Date: Mon, 8 Jun 2026 16:23:22 -0700 Subject: [PATCH 04/19] Fix js-lint failure propagation --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index b54e9eb7e..8c2acb862 100644 --- a/Makefile +++ b/Makefile @@ -207,7 +207,7 @@ coverage: js-lint: ### Run Javascript linter @if command -v npx >/dev/null 2>&1; then \ npx --yes --loglevel=error jshint@$(JSHINT_VERSION) \ - $(WEBCLIENT_BASE_JS); \ + $(WEBCLIENT_BASE_JS) && \ npx --yes --loglevel=error jshint@$(JSHINT_VERSION) \ --config .jshintrc.webclient-windows \ $(WEBCLIENT_WINDOW_JS); \ From b344db045d52913e8e3ef030113b7a79d45d7ef5 Mon Sep 17 00:00:00 2001 From: Casey Labs <4674433+CaseyLabs@users.noreply.github.com> Date: Mon, 8 Jun 2026 16:32:03 -0700 Subject: [PATCH 05/19] Fix prerelease generated notes tag --- .github/scripts/release-notes.sh | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/scripts/release-notes.sh b/.github/scripts/release-notes.sh index 2b3374885..3fbb22930 100755 --- a/.github/scripts/release-notes.sh +++ b/.github/scripts/release-notes.sh @@ -56,9 +56,6 @@ if [ "${RELEASE_NOTES_SKIP_GH:-}" = "true" ]; then >"$generated_notes_file" else notes_tag="$release_tag" - if [ "$release_kind" = "prerelease" ]; then - notes_tag="${release_tag}-notes-${commit_sha}" - fi generate_notes_args=( -f "tag_name=${notes_tag}" From 1716c5240c329a3ec6ddcce650d02ccac26d30ac Mon Sep 17 00:00:00 2001 From: Casey Labs <4674433+CaseyLabs@users.noreply.github.com> Date: Mon, 8 Jun 2026 16:32:26 -0700 Subject: [PATCH 06/19] ci: redesign release publishing --- .github/workflows/build-and-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml index 9e9269eba..55c91ab4a 100644 --- a/.github/workflows/build-and-release.yml +++ b/.github/workflows/build-and-release.yml @@ -108,7 +108,7 @@ jobs: done < <(.github/scripts/release-assets.sh targets) - name: Upload release binaries - # actions/upload-artifact v7.0.1 + # actions/uploadf-artifact v7.0.1 uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a with: name: release-binaries From 505f413a84a1dfee2cd3f7dfba54cb261a3950b5 Mon Sep 17 00:00:00 2001 From: Casey Labs <4674433+CaseyLabs@users.noreply.github.com> Date: Mon, 8 Jun 2026 16:52:50 -0700 Subject: [PATCH 07/19] ci: simplify workflows and make targets --- .github/act/actrc | 1 + .github/act/push_tag.json | 3 - .github/actions/codegen-and-test/action.yml | 36 --------- .github/actions/go-checks/action.yml | 10 +++ .github/dependabot.yml | 4 +- .github/scripts/assemble-release-assets.sh | 21 +++++ .github/scripts/build-release-binaries.sh | 32 ++++++++ .github/scripts/ci-local-act.sh | 37 +++++++++ .../{build-and-release.yml => prerelease.yml} | 38 +-------- .github/workflows/run-test.yml | 6 +- .github/workflows/stable-release.yml | 38 +-------- Makefile | 77 ++++++++----------- 12 files changed, 145 insertions(+), 158 deletions(-) create mode 100644 .github/act/actrc delete mode 100644 .github/act/push_tag.json delete mode 100644 .github/actions/codegen-and-test/action.yml create mode 100644 .github/actions/go-checks/action.yml create mode 100755 .github/scripts/assemble-release-assets.sh create mode 100755 .github/scripts/build-release-binaries.sh create mode 100755 .github/scripts/ci-local-act.sh rename .github/workflows/{build-and-release.yml => prerelease.yml} (82%) diff --git a/.github/act/actrc b/.github/act/actrc new file mode 100644 index 000000000..9f92c62bf --- /dev/null +++ b/.github/act/actrc @@ -0,0 +1 @@ +# Repo-local act config placeholder. diff --git a/.github/act/push_tag.json b/.github/act/push_tag.json deleted file mode 100644 index 4001b8349..000000000 --- a/.github/act/push_tag.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "ref": "refs/tags/v0.0.0" -} diff --git a/.github/actions/codegen-and-test/action.yml b/.github/actions/codegen-and-test/action.yml deleted file mode 100644 index 8622531a9..000000000 --- a/.github/actions/codegen-and-test/action.yml +++ /dev/null @@ -1,36 +0,0 @@ ---- -name: "Codegen and Test" -description: "Run go generate and go test" -runs: - using: "composite" - steps: - - run: go generate ./... - shell: bash - - run: go test -race ./... - shell: bash - - name: Cross-compile release targets - run: | - tmp_dir="$(mktemp -d)" - trap 'rm -rf "$tmp_dir"' EXIT - - build_target() { - local goos="$1" - local goarch="$2" - local goarm="$3" - local output="$4" - - echo "::group::Build $output ($goos/$goarch${goarm:+ GOARM=$goarm})" - if [ -n "$goarm" ]; then - env GOOS="$goos" GOARCH="$goarch" GOARM="$goarm" \ - go build -v -o "$tmp_dir/$output" . - else - env GOOS="$goos" GOARCH="$goarch" \ - go build -v -o "$tmp_dir/$output" . - fi - echo "::endgroup::" - } - - while IFS='|' read -r _label goos goarch goarm output; do - build_target "$goos" "$goarch" "$goarm" "$output" - done < <(.github/scripts/release-assets.sh targets) - shell: bash diff --git a/.github/actions/go-checks/action.yml b/.github/actions/go-checks/action.yml new file mode 100644 index 000000000..cfb21209a --- /dev/null +++ b/.github/actions/go-checks/action.yml @@ -0,0 +1,10 @@ +--- +name: "Go Checks" +description: "Run go generate and go test" +runs: + using: "composite" + steps: + - run: go generate ./... + shell: bash + - run: go test -race ./... + shell: bash diff --git a/.github/dependabot.yml b/.github/dependabot.yml index e82ec7a2b..450723651 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -45,7 +45,7 @@ updates: - "*" - package-ecosystem: "docker" - directory: "/docker/provisioning" + directory: "/provisioning" schedule: interval: "monthly" time: "06:00" @@ -62,7 +62,7 @@ updates: - "patch" - package-ecosystem: "docker" - directory: "/docker/terminal" + directory: "/provisioning/terminal" schedule: interval: "monthly" time: "06:00" diff --git a/.github/scripts/assemble-release-assets.sh b/.github/scripts/assemble-release-assets.sh new file mode 100755 index 000000000..b40094dc0 --- /dev/null +++ b/.github/scripts/assemble-release-assets.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +set -euo pipefail + +script_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +repo_root="$(cd -- "${script_dir}/../.." && pwd)" +bin_dir="${RELEASE_BIN_DIR:-bin}" +datafiles_archive="${DATAFILES_ARCHIVE:-gomud-ALL-datafiles.zip}" +checksums_file="${CHECKSUMS_FILE:-SHA256SUMS.txt}" + +cd "$repo_root" + +mapfile -t checksum_assets < <( + DATAFILES_ARCHIVE="$datafiles_archive" \ + CHECKSUMS_FILE="$checksums_file" \ + "${script_dir}/release-assets.sh" checksum-names +) + +zip -qr "${bin_dir}/${datafiles_archive}" _datafiles + +cd "$bin_dir" +sha256sum "${checksum_assets[@]}" >"$checksums_file" diff --git a/.github/scripts/build-release-binaries.sh b/.github/scripts/build-release-binaries.sh new file mode 100755 index 000000000..6690f5283 --- /dev/null +++ b/.github/scripts/build-release-binaries.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash +set -euo pipefail + +script_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +repo_root="$(cd -- "${script_dir}/../.." && pwd)" +dist_dir="${RELEASE_DIST_DIR:-dist}" +binary_version="${BINARY_VERSION:-}" + +cd "$repo_root" +mkdir -p "$dist_dir" + +while IFS='|' read -r _label goos goarch goarm asset; do + target="${goos}/${goarch}" + if [ -n "$goarm" ]; then + target="${target} GOARM=${goarm}" + fi + + build_args=() + if [ -n "$binary_version" ]; then + build_args+=(-ldflags "-X main.version=${binary_version}") + fi + + echo "::group::Build ${asset} (${target})" + if [ -n "$goarm" ]; then + env GOOS="$goos" GOARCH="$goarch" GOARM="$goarm" \ + go build "${build_args[@]}" -o "${dist_dir}/${asset}" . + else + env GOOS="$goos" GOARCH="$goarch" \ + go build "${build_args[@]}" -o "${dist_dir}/${asset}" . + fi + echo "::endgroup::" +done < <("${script_dir}/release-assets.sh" targets) diff --git a/.github/scripts/ci-local-act.sh b/.github/scripts/ci-local-act.sh new file mode 100755 index 000000000..502d97439 --- /dev/null +++ b/.github/scripts/ci-local-act.sh @@ -0,0 +1,37 @@ +#!/usr/bin/env bash +set -euo pipefail + +script_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +repo_root="$(cd -- "${script_dir}/../.." && pwd)" + +ACT_FLAGS="${ACT_FLAGS:---pull=false -P ubuntu-24.04=catthehacker/ubuntu:act-latest}" +ACT_DRYRUN_SECRETS="${ACT_DRYRUN_SECRETS:--s DISCORD_WEBHOOK_URL=https://example.invalid/webhook}" +XDG_CONFIG_HOME="${ACT_CONFIG_HOME:-${repo_root}/.github}" +export XDG_CONFIG_HOME + +mkdir -p "${XDG_CONFIG_HOME}/act" +touch "${XDG_CONFIG_HOME}/act/actrc" + +run_act() { + local event="$1" + local event_file="$2" + local workflow="$3" + shift 3 + + act ${ACT_FLAGS:-} --dryrun "$event" "$@" \ + -e "$event_file" \ + -W "$workflow" +} + +run_act push .github/act/push_master.json .github/workflows/lint.yml +run_act pull_request .github/act/pull_request.json .github/workflows/lint.yml +run_act pull_request .github/act/pull_request.json .github/workflows/run-test.yml +run_act pull_request .github/act/pull_request.json \ + .github/workflows/discord-notify.yml ${ACT_DRYRUN_SECRETS:-} +run_act push .github/act/push_master.json .github/workflows/prerelease.yml +run_act workflow_dispatch .github/act/stable_release.json \ + .github/workflows/stable-release.yml +run_act push .github/act/push_master.json \ + .github/workflows/docker-package.yml ${ACT_DRYRUN_SECRETS:-} +run_act pull_request .github/act/pull_request.json \ + .github/workflows/docker-package.yml ${ACT_DRYRUN_SECRETS:-} diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/prerelease.yml similarity index 82% rename from .github/workflows/build-and-release.yml rename to .github/workflows/prerelease.yml index 55c91ab4a..f77c958f5 100644 --- a/.github/workflows/build-and-release.yml +++ b/.github/workflows/prerelease.yml @@ -59,11 +59,7 @@ jobs: - uses: ./.github/actions/setup-go - - name: Generate code - run: go generate ./... - - - name: Run tests - run: go test -race ./... + - uses: ./.github/actions/go-checks build: runs-on: ubuntu-24.04 @@ -87,25 +83,7 @@ jobs: run: go generate ./... - name: Build release binaries - run: | - set -euo pipefail - - mkdir -p dist - - while IFS='|' read -r _label goos goarch goarm asset; do - target="${goos}/${goarch}" - if [ -n "$goarm" ]; then - target="${target} GOARM=${goarm}" - fi - - echo "::group::Build ${asset} (${target})" - env GOOS="$goos" GOARCH="$goarch" GOARM="$goarm" \ - go build \ - -ldflags "-X main.version=${BINARY_VERSION}" \ - -o "dist/${asset}" \ - . - echo "::endgroup::" - done < <(.github/scripts/release-assets.sh targets) + run: .github/scripts/build-release-binaries.sh - name: Upload release binaries # actions/uploadf-artifact v7.0.1 @@ -144,17 +122,7 @@ jobs: path: bin/ - name: Assemble release assets - run: | - set -euo pipefail - - mapfile -t checksum_assets < <( - .github/scripts/release-assets.sh checksum-names - ) - - zip -r "bin/${DATAFILES_ARCHIVE}" _datafiles - - cd bin - sha256sum "${checksum_assets[@]}" > "${CHECKSUMS_FILE}" + run: .github/scripts/assemble-release-assets.sh - name: Write release notes env: diff --git a/.github/workflows/run-test.yml b/.github/workflows/run-test.yml index d9f3cd130..46682e660 100644 --- a/.github/workflows/run-test.yml +++ b/.github/workflows/run-test.yml @@ -27,4 +27,8 @@ jobs: with: persist-credentials: false - uses: ./.github/actions/setup-go - - uses: ./.github/actions/codegen-and-test + - uses: ./.github/actions/go-checks + - name: Cross-compile release targets + env: + RELEASE_DIST_DIR: ${{ runner.temp }}/release-cross-compile + run: .github/scripts/build-release-binaries.sh diff --git a/.github/workflows/stable-release.yml b/.github/workflows/stable-release.yml index 1e94c724a..38117c9e2 100644 --- a/.github/workflows/stable-release.yml +++ b/.github/workflows/stable-release.yml @@ -88,11 +88,7 @@ jobs: - uses: ./.github/actions/setup-go - - name: Generate code - run: go generate ./... - - - name: Run tests - run: go test -race ./... + - uses: ./.github/actions/go-checks build: runs-on: ubuntu-24.04 @@ -116,25 +112,7 @@ jobs: run: go generate ./... - name: Build release binaries - run: | - set -euo pipefail - - mkdir -p dist - - while IFS='|' read -r _label goos goarch goarm asset; do - target="${goos}/${goarch}" - if [ -n "$goarm" ]; then - target="${target} GOARM=${goarm}" - fi - - echo "::group::Build ${asset} (${target})" - env GOOS="$goos" GOARCH="$goarch" GOARM="$goarm" \ - go build \ - -ldflags "-X main.version=${BINARY_VERSION}" \ - -o "dist/${asset}" \ - . - echo "::endgroup::" - done < <(.github/scripts/release-assets.sh targets) + run: .github/scripts/build-release-binaries.sh - name: Upload release binaries # actions/upload-artifact v7.0.1 @@ -192,17 +170,7 @@ jobs: path: bin/ - name: Assemble release assets - run: | - set -euo pipefail - - mapfile -t checksum_assets < <( - .github/scripts/release-assets.sh checksum-names - ) - - zip -r "bin/${DATAFILES_ARCHIVE}" _datafiles - - cd bin - sha256sum "${checksum_assets[@]}" > "${CHECKSUMS_FILE}" + run: .github/scripts/assemble-release-assets.sh - name: Write release notes env: diff --git a/Makefile b/Makefile index 8c2acb862..1159ae7b3 100644 --- a/Makefile +++ b/Makefile @@ -17,6 +17,9 @@ JSHINT_VERSION ?= 2.13.6 JS_LINT_PATHS := $(shell find _datafiles -name '*.js' -print) WEBCLIENT_WINDOW_JS := $(shell find _datafiles/html/public/static/js/windows -name '*.js' -print) WEBCLIENT_BASE_JS := $(filter-out $(WEBCLIENT_WINDOW_JS),$(JS_LINT_PATHS)) +JSHINT_BASE_CMD := npx --yes --loglevel=error jshint@$(JSHINT_VERSION) $(WEBCLIENT_BASE_JS) +JSHINT_WINDOWS_CMD := npx --yes --loglevel=error jshint@$(JSHINT_VERSION) --config .jshintrc.webclient-windows $(WEBCLIENT_WINDOW_JS) +CROSS_BUILD_CMD = env $(strip GOOS=$(CROSS_GOOS) GOARCH=$(CROSS_GOARCH) $(if $(CROSS_GOARM),GOARM=$(CROSS_GOARM))) go build -o $(CROSS_OUTPUT) CI_LOCAL_RUN := docker run --rm \ --user "$(CI_LOCAL_UID):$(CI_LOCAL_GID)" \ --group-add "$(CI_LOCAL_DOCKER_SOCK_GID)" \ @@ -37,8 +40,8 @@ export GOFLAGS := -mod=mod go-version: ### Print the Go version pinned in go.mod. @printf '%s\n' "$(GO_VERSION)" -.PHONY: docker_build -docker_build: +.PHONY: docker_build +docker_build: GO_VERSION=$(GO_VERSION) TAG=$(VERSION) $(DOCKER_COMPOSE) build server .PHONY: ci-local-image @@ -59,30 +62,9 @@ ci-local-inner: ### Run the local CI checks inside the CI tool image. yamllint .github $(MAKE) validate $(MAKE) js-lint - act $(ACT_FLAGS) --dryrun push \ - -e .github/act/push_master.json \ - -W .github/workflows/lint.yml - act $(ACT_FLAGS) --dryrun pull_request \ - -e .github/act/pull_request.json \ - -W .github/workflows/lint.yml - act $(ACT_FLAGS) --dryrun pull_request \ - -e .github/act/pull_request.json \ - -W .github/workflows/run-test.yml - act $(ACT_FLAGS) --dryrun pull_request $(ACT_DRYRUN_SECRETS) \ - -e .github/act/pull_request.json \ - -W .github/workflows/discord-notify.yml - act $(ACT_FLAGS) --dryrun push \ - -e .github/act/push_master.json \ - -W .github/workflows/build-and-release.yml - act $(ACT_FLAGS) --dryrun workflow_dispatch \ - -e .github/act/stable_release.json \ - -W .github/workflows/stable-release.yml - act $(ACT_FLAGS) --dryrun push $(ACT_DRYRUN_SECRETS) \ - -e .github/act/push_master.json \ - -W .github/workflows/docker-package.yml - act $(ACT_FLAGS) --dryrun pull_request $(ACT_DRYRUN_SECRETS) \ - -e .github/act/pull_request.json \ - -W .github/workflows/docker-package.yml + ACT_FLAGS="$(ACT_FLAGS)" \ + ACT_DRYRUN_SECRETS="$(ACT_DRYRUN_SECRETS)" \ + .github/scripts/ci-local-act.sh DOCKER_CMD ?= bash @@ -105,16 +87,25 @@ docker-%: # .PHONY: build_rpi_zero2w +build_rpi_zero2w: CROSS_GOOS := linux +build_rpi_zero2w: CROSS_GOARCH := arm64 +build_rpi_zero2w: CROSS_OUTPUT := $(BIN)-rpi build_rpi_zero2w: generate ### Build a binary for a raspberry pi zero 2w - env GOOS=linux GOARCH=arm64 go build -o $(BIN)-rpi + $(CROSS_BUILD_CMD) .PHONY: build_win64 +build_win64: CROSS_GOOS := windows +build_win64: CROSS_GOARCH := amd64 +build_win64: CROSS_OUTPUT := $(BIN)-win64.exe build_win64: generate ### Build a binary for 64bit windows - env GOOS=windows GOARCH=amd64 go build -o $(BIN)-win64.exe + $(CROSS_BUILD_CMD) .PHONY: build_linux64 +build_linux64: CROSS_GOOS := linux +build_linux64: CROSS_GOARCH := amd64 +build_linux64: CROSS_OUTPUT := $(BIN)-linux64 build_linux64: generate ### Build a binary for linux - env GOOS=linux GOARCH=amd64 go build -o $(BIN)-linux64 + $(CROSS_BUILD_CMD) .PHONY: build build: validate build_local ### Validate the code and build the binary. @@ -146,7 +137,7 @@ clean-instances: ### Deletes all room instance data. Starts the world fresh. ## Run Targets -.PHONY: run +.PHONY: run run: generate ### Build and run server. @go run . @@ -197,7 +188,7 @@ test: generate js-lint ### Run code generation, lint, and Go tests. @go test -race ./... .PHONY: coverage -coverage: +coverage: @mkdir -p bin/covdatafiles && \ go test ./... -coverprofile=bin/covdatafiles/cover.out && \ go tool cover -html=bin/covdatafiles/cover.out && \ @@ -206,18 +197,12 @@ coverage: .PHONY: js-lint js-lint: ### Run Javascript linter @if command -v npx >/dev/null 2>&1; then \ - npx --yes --loglevel=error jshint@$(JSHINT_VERSION) \ - $(WEBCLIENT_BASE_JS) && \ - npx --yes --loglevel=error jshint@$(JSHINT_VERSION) \ - --config .jshintrc.webclient-windows \ - $(WEBCLIENT_WINDOW_JS); \ + $(JSHINT_BASE_CMD) && \ + $(JSHINT_WINDOWS_CMD); \ elif command -v docker >/dev/null 2>&1; then \ docker run --rm -v "$(PWD)":/app -w /app node:22 sh -lc "\ - npx --yes --loglevel=error jshint@$(JSHINT_VERSION) \ - $(WEBCLIENT_BASE_JS) && \ - npx --yes --loglevel=error jshint@$(JSHINT_VERSION) \ - --config .jshintrc.webclient-windows \ - $(WEBCLIENT_WINDOW_JS)"; \ + $(JSHINT_BASE_CMD) && \ + $(JSHINT_WINDOWS_CMD)"; \ else \ echo "js-lint requires npx or docker" >&2; \ exit 127; \ @@ -250,12 +235,12 @@ fmt: @go fmt ./... .PHONY: fmtcheck -fmtcheck: fmt +fmtcheck: @set -e; \ - unformatted=$$(go fmt ./...); \ + unformatted=$$(gofmt -l $$(find . -path './vendor' -prune -o -name '*.go' -print)); \ if [ ! -z "$$unformatted" ]; then \ - echo Fixed inconsistent format in some files.; \ - echo $$unformatted; \ + echo "Go files need formatting:"; \ + printf '%s\n' "$$unformatted"; \ exit 1; \ fi @@ -268,7 +253,7 @@ mod: .PHONY: vet vet: - @go vet -composites=false ./... + @go vet -composites=false ./... .PHONY: set_gopath set_gopath: From ccb13d69bb6023c74eca06992db2bbd560afb8f8 Mon Sep 17 00:00:00 2001 From: Casey Labs <4674433+CaseyLabs@users.noreply.github.com> Date: Mon, 8 Jun 2026 16:58:23 -0700 Subject: [PATCH 08/19] Tidy Makefile structure and help text --- Makefile | 353 ++++++++++++++++++++++++------------------------------- 1 file changed, 156 insertions(+), 197 deletions(-) diff --git a/Makefile b/Makefile index 1159ae7b3..fbcb4f7e4 100644 --- a/Makefile +++ b/Makefile @@ -1,25 +1,37 @@ - - .DEFAULT_GOAL := build +# Makefile overview: +# - Target comments marked with "##" appear in `make help`. +# - Most values are overridable: `make build BIN=gomud VERSION=v1.2.3`. +# - The Go version is read from go.mod so Docker workflows use the same toolchain. + VERSION ?= $(shell git rev-parse HEAD) BIN ?= go-mud-server -DOCKER_COMPOSE := docker-compose -f compose.yml GO_VERSION ?= $(shell awk '/^toolchain go/ { sub(/^toolchain go/, ""); print; found=1; exit } /^go / && !gover { gover=$$2 } END { if (!found && gover) print gover }' go.mod) + +DOCKER_COMPOSE := docker-compose -f compose.yml GO_CONSOLE_IMAGE ?= golang:$(GO_VERSION)-bookworm +DOCKER_CMD ?= bash + CI_LOCAL_IMAGE ?= gomud-ci-local CI_LOCAL_UID ?= $(shell id -u) CI_LOCAL_GID ?= $(shell id -g) CI_LOCAL_DOCKER_SOCK_GID ?= $(shell stat -c '%g' /var/run/docker.sock 2>/dev/null || id -g) CI_LOCAL_HOME ?= /home/gomud CI_LOCAL_ACT_CACHE_DIR ?= $(PWD)/.git/.cache/act +ACT_FLAGS ?= --pull=false -P ubuntu-24.04=catthehacker/ubuntu:act-latest +ACT_DRYRUN_SECRETS ?= -s DISCORD_WEBHOOK_URL=https://example.invalid/webhook + JSHINT_VERSION ?= 2.13.6 JS_LINT_PATHS := $(shell find _datafiles -name '*.js' -print) WEBCLIENT_WINDOW_JS := $(shell find _datafiles/html/public/static/js/windows -name '*.js' -print) WEBCLIENT_BASE_JS := $(filter-out $(WEBCLIENT_WINDOW_JS),$(JS_LINT_PATHS)) -JSHINT_BASE_CMD := npx --yes --loglevel=error jshint@$(JSHINT_VERSION) $(WEBCLIENT_BASE_JS) -JSHINT_WINDOWS_CMD := npx --yes --loglevel=error jshint@$(JSHINT_VERSION) --config .jshintrc.webclient-windows $(WEBCLIENT_WINDOW_JS) +JSHINT := npx --yes --loglevel=error jshint@$(JSHINT_VERSION) +JSHINT_BASE_CMD := $(JSHINT) $(WEBCLIENT_BASE_JS) +JSHINT_WINDOWS_CMD := $(JSHINT) --config .jshintrc.webclient-windows $(WEBCLIENT_WINDOW_JS) + CROSS_BUILD_CMD = env $(strip GOOS=$(CROSS_GOOS) GOARCH=$(CROSS_GOARCH) $(if $(CROSS_GOARM),GOARM=$(CROSS_GOARM))) go build -o $(CROSS_OUTPUT) + CI_LOCAL_RUN := docker run --rm \ --user "$(CI_LOCAL_UID):$(CI_LOCAL_GID)" \ --group-add "$(CI_LOCAL_DOCKER_SOCK_GID)" \ @@ -29,35 +41,129 @@ CI_LOCAL_RUN := docker run --rm \ -v "$(CI_LOCAL_ACT_CACHE_DIR)":"$(CI_LOCAL_HOME)/.cache/act" \ -w /work \ $(CI_LOCAL_IMAGE) -ACT_FLAGS ?= --pull=false -P ubuntu-24.04=catthehacker/ubuntu:act-latest -ACT_DRYRUN_SECRETS ?= -s DISCORD_WEBHOOK_URL=https://example.invalid/webhook export GOFLAGS := -mod=mod -## Build Targets +## Developer Workflow +.PHONY: build build_local generate module validate test coverage fmt fmtcheck vet mod js-lint -.PHONY: go-version -go-version: ### Print the Go version pinned in go.mod. - @printf '%s\n' "$(GO_VERSION)" +build: validate build_local ## Validate the code and build ./$(BIN). + +build_local: generate ## Generate module imports and compile the local server binary. + @go mod tidy + CGO_ENABLED=0 go build -trimpath -a -o $(BIN) + +generate: ## Refresh generated module import wiring. + go generate + +# Pass module-manager arguments after the target: +# make module list +# make module install all-official +MODULE_ARGS := $(filter-out module,$(MAKECMDGOALS)) +module: ## Run the community module manager. + @go run . module $(MODULE_ARGS) + +ifneq ($(filter module,$(MAKECMDGOALS)),) +.PHONY: $(MODULE_ARGS) +$(MODULE_ARGS): + @: +endif + +validate: fmtcheck vet ## Run the standard Go formatting and vet checks. + +test: generate js-lint ## Run code generation, JavaScript linting, and Go tests. + @go test -race ./... + +coverage: ## Generate and open an HTML Go coverage report. + @mkdir -p bin/covdatafiles && \ + go test ./... -coverprofile=bin/covdatafiles/cover.out && \ + go tool cover -html=bin/covdatafiles/cover.out && \ + rm -rf bin + +fmt: ## Format all Go files. + @go fmt ./... + +fmtcheck: ## Fail if any Go file is not gofmt-formatted. + @set -e; \ + unformatted=$$(gofmt -l $$(find . -path './vendor' -prune -o -name '*.go' -print)); \ + if [ -n "$$unformatted" ]; then \ + echo "Go files need formatting:"; \ + printf '%s\n' "$$unformatted"; \ + exit 1; \ + fi + +vet: ## Run go vet with repo-specific composite literal settings. + @go vet -composites=false ./... + +mod: ## Refresh vendored modules, tidy go.mod, and verify dependencies. + @go mod vendor + @go mod tidy + @go mod verify + +js-lint: ## Run JSHint using npx when available, otherwise Docker. + @if command -v npx >/dev/null 2>&1; then \ + $(JSHINT_BASE_CMD) && \ + $(JSHINT_WINDOWS_CMD); \ + elif command -v docker >/dev/null 2>&1; then \ + docker run --rm -v "$(PWD)":/app -w /app node:22 sh -lc "\ + $(JSHINT_BASE_CMD) && \ + $(JSHINT_WINDOWS_CMD)"; \ + else \ + echo "js-lint requires npx or docker" >&2; \ + exit 127; \ + fi + +## Running Locally +.PHONY: run run-new clean-instances https-setup reset-admin-pw client + +run: generate ## Start the server with `go run .`. + @go run . + +run-new: clean-instances generate run ## Delete room instance data and start a fresh world. + +clean-instances: ## Delete generated room instance data for bundled worlds. + rm -Rf _datafiles/world/default/rooms.instances + rm -Rf _datafiles/world/empty/rooms.instances + +https-setup: ## Run the interactive HTTPS certificate setup helper. + @sh ./scripts/https-setup.sh -.PHONY: docker_build -docker_build: +reset-admin-pw: ## Interactively reset the admin user's password. + @go run ./cmd/reset-admin-pw + +client: ## Open a telnet client connected to the Docker server. + $(DOCKER_COMPOSE) run --rm terminal telnet go-mud-server 33333 + +## Docker And CI +.PHONY: docker_build run-docker console ci-local-image ci-local ci-local-inner clean + +docker_build: ## Build the server image with compose.yml. GO_VERSION=$(GO_VERSION) TAG=$(VERSION) $(DOCKER_COMPOSE) build server -.PHONY: ci-local-image -ci-local-image: ### Build the local CI tool image. +run-docker: ## Build and start the server container from compose.yml. + GO_VERSION=$(GO_VERSION) $(DOCKER_COMPOSE) up --build --remove-orphans server + +console: ## Open a shell in a Go toolchain container mounted on this repo. + @docker run --rm -it --init \ + -v "$(PWD)":/src \ + -w /src \ + $(GO_CONSOLE_IMAGE) \ + $(DOCKER_CMD) + +docker-%: ## Run a make target inside the Go toolchain container, for example `make docker-test`. + @$(MAKE) console DOCKER_CMD="make $(patsubst docker-%,%,$@)" + +ci-local-image: ## Build the local CI tool image used by `make ci-local`. docker build \ --build-arg GO_VERSION=$(GO_VERSION) \ -f .github/Dockerfile.act \ -t $(CI_LOCAL_IMAGE) . -.PHONY: ci-local -ci-local: ci-local-image ### Run local CI validation in a container. +ci-local: ci-local-image ## Run local CI validation in the CI tool container. mkdir -p "$(CI_LOCAL_ACT_CACHE_DIR)" $(CI_LOCAL_RUN) make ci-local-inner -.PHONY: ci-local-inner -ci-local-inner: ### Run the local CI checks inside the CI tool image. +ci-local-inner: ## Run CI checks from inside the local CI tool container. actionlint .github/workflows/*.yml yamllint .github $(MAKE) validate @@ -66,221 +172,74 @@ ci-local-inner: ### Run the local CI checks inside the CI tool image. ACT_DRYRUN_SECRETS="$(ACT_DRYRUN_SECRETS)" \ .github/scripts/ci-local-act.sh -DOCKER_CMD ?= bash - -.PHONY: console -console: ### Open a shell in the project Go toolchain container. - @docker run --rm -it --init \ - -v "$(PWD)":/src \ - -w /src \ - $(GO_CONSOLE_IMAGE) \ - $(DOCKER_CMD) - -docker-%: - @$(MAKE) console DOCKER_CMD="make $(patsubst docker-%,%,$@)" +clean: ## Stop compose services, remove their volumes, and prune Docker images. + $(DOCKER_COMPOSE) down --volumes --remove-orphans + docker system prune -a -# -# -# For a complete list of GOOS/GOARCH combinations: -# Run: go tool dist list -# -# +## Cross Builds +.PHONY: build_rpi_zero2w build_win64 build_linux64 -.PHONY: build_rpi_zero2w +# For supported GOOS/GOARCH values, run: go tool dist list build_rpi_zero2w: CROSS_GOOS := linux build_rpi_zero2w: CROSS_GOARCH := arm64 build_rpi_zero2w: CROSS_OUTPUT := $(BIN)-rpi -build_rpi_zero2w: generate ### Build a binary for a raspberry pi zero 2w - $(CROSS_BUILD_CMD) +build_rpi_zero2w: generate ## Build a Raspberry Pi Zero 2 W binary. -.PHONY: build_win64 build_win64: CROSS_GOOS := windows build_win64: CROSS_GOARCH := amd64 build_win64: CROSS_OUTPUT := $(BIN)-win64.exe -build_win64: generate ### Build a binary for 64bit windows - $(CROSS_BUILD_CMD) +build_win64: generate ## Build a 64-bit Windows binary. -.PHONY: build_linux64 build_linux64: CROSS_GOOS := linux build_linux64: CROSS_GOARCH := amd64 build_linux64: CROSS_OUTPUT := $(BIN)-linux64 -build_linux64: generate ### Build a binary for linux - $(CROSS_BUILD_CMD) - -.PHONY: build -build: validate build_local ### Validate the code and build the binary. - -.PHONY: build_local -build_local: generate - @go mod tidy - CGO_ENABLED=0 go build -trimpath -a -o $(BIN) - -.PHONY: generate -generate: ### Generates include directives for modules - go generate - -.PHONY: module -module: ### Manage community modules (usage: go run . module ) - @go run . module $(filter-out module,$(MAKECMDGOALS)) +build_linux64: generate ## Build a 64-bit Linux binary. +build_rpi_zero2w build_win64 build_linux64: + $(CROSS_BUILD_CMD) -# Clean both development and production containers -.PHONY: clean -clean: - $(DOCKER_COMPOSE) down --volumes --remove-orphans - docker system prune -a - -.PHONY: clean-instances -clean-instances: ### Deletes all room instance data. Starts the world fresh. - rm -Rf _datafiles/world/default/rooms.instances - rm -Rf _datafiles/world/empty/rooms.instances - -## Run Targets - -.PHONY: run -run: generate ### Build and run server. - @go run . - -.PHONY: run-new -run-new: clean-instances generate run ### Deletes instance data and runs server - -.PHONY: run-docker -run-docker: ### Build and run server in docker. - GO_VERSION=$(GO_VERSION) $(DOCKER_COMPOSE) up --build --remove-orphans server - -.PHONY: https-setup -https-setup: ### Interactive HTTPS certificate setup helper. - @sh ./scripts/https-setup.sh - -.PHONY: reset-admin-pw -reset-admin-pw: ### Interactively reset the admin user's password. - @go run ./cmd/reset-admin-pw - +## Utility +.PHONY: go-version image_tag port shell cert-clean cert set_gopath view_pprof_mem help -.PHONY: client -client: ### Build and run client terminal client - $(DOCKER_COMPOSE) run --rm terminal telnet go-mud-server 33333 +go-version: ## Print the Go version pinned in go.mod. + @printf '%s\n' "$(GO_VERSION)" -.PHONY: image_tag -image_tag: +image_tag: ## Print the current Docker image tag value. @echo $(VERSION) -.PHONY: port -port: +port: ## Print the host port mapped to server port 8080. @$(eval PORT := $(shell $(DOCKER_COMPOSE) port server 8080)) @echo $(PORT) -.PHONY: shell -shell: +shell: ## Open /bin/sh inside the running server container. @$(eval CONTAINER_NAME := $(shell docker ps --filter="name=mud" --format '{{.Names}}' )) - docker exec -it $(CONTAINER_NAME) /bin/sh - -# -# -# Local code run/test -# -# -.PHONY: validate -validate: fmtcheck vet - -.PHONY: test -test: generate js-lint ### Run code generation, lint, and Go tests. - @go test -race ./... - -.PHONY: coverage -coverage: - @mkdir -p bin/covdatafiles && \ - go test ./... -coverprofile=bin/covdatafiles/cover.out && \ - go tool cover -html=bin/covdatafiles/cover.out && \ - rm -rf bin + docker exec -it $(CONTAINER_NAME) /bin/sh -.PHONY: js-lint -js-lint: ### Run Javascript linter - @if command -v npx >/dev/null 2>&1; then \ - $(JSHINT_BASE_CMD) && \ - $(JSHINT_WINDOWS_CMD); \ - elif command -v docker >/dev/null 2>&1; then \ - docker run --rm -v "$(PWD)":/app -w /app node:22 sh -lc "\ - $(JSHINT_BASE_CMD) && \ - $(JSHINT_WINDOWS_CMD)"; \ - else \ - echo "js-lint requires npx or docker" >&2; \ - exit 127; \ - fi - -# -# -# Cert generation for testing -# -# -.PHONY: cert-clean -cert-clean: +cert-clean: ## Remove local development TLS certificate files. rm -f server.crt server.key -.PHONY: cert -cert: server.crt server.key +cert: server.crt server.key ## Generate local self-signed TLS certificate files. -# This rule generates both files in one go using OpenSSL. server.crt server.key: openssl req -x509 -nodes -newkey rsa:4096 \ -keyout server.key -out server.crt \ -days 365 -subj "/CN=localhost" - - -# Go targets - -.PHONY: fmt -fmt: - @go fmt ./... - -.PHONY: fmtcheck -fmtcheck: - @set -e; \ - unformatted=$$(gofmt -l $$(find . -path './vendor' -prune -o -name '*.go' -print)); \ - if [ ! -z "$$unformatted" ]; then \ - echo "Go files need formatting:"; \ - printf '%s\n' "$$unformatted"; \ - exit 1; \ - fi - -.PHONY: mod -mod: - @go mod vendor - @go mod tidy - @go mod verify - - -.PHONY: vet -vet: - @go vet -composites=false ./... - -.PHONY: set_gopath -set_gopath: +set_gopath: ## Print a command for adding this repo to GOPATH in legacy shells. ifeq ($(OS),Windows_NT) - setx PATH "$(PATH);mytest" -m + @echo 'PowerShell: $$env:GOPATH = "$$env:GOPATH;$(CURDIR)"' else - export GOPATH=$GOPATH:$(pwd) + @printf 'export GOPATH="$${GOPATH:+$$GOPATH:}%s"\n' "$(CURDIR)" endif -.PHONY: view_pprof_mem -view_pprof_mem: +view_pprof_mem: ## Open the saved memory profile in the Go pprof web UI. go tool pprof -http=:8989 source/_datafiles/profiles/mem.pprof -# -# Help target - greps and formats special comments to form a "help" command for makefiles -# ## Help -.PHONY: help -help: ### List makefile targets. -# 1. grep for any lines starting with "##" or containing "\s###\s" -# 2. Align targets/comments with string padding -# 3. Wrap lines starting with "##" in ANSI escape codes (color) as headers -# 4. Wrap lines containing "\s###\s" in ANSI escape codes (color) as commands -# 5. Add new lines before any that aren't prefixed with space (Headers) - @grep -hE "^##\s|\s###\s" $(MAKEFILE_LIST) \ - | awk -F'[[:space:]]###[[:space:]]' '{printf "%-36s### %s\n", substr($$1,1,35), $$2}' \ - | sed -E "s/^## ([^#]*)#*/`printf "\033[90;3m"`\1`printf "\033[0m"`/" \ - | sed "s/\(.*\):\(.*\)###\(.*\)$$/ `printf "\033[93m"`\1:`printf "\033[36m"`\2`printf "\033[97m"`-\3`printf "\033[0m"`/" \ - | sed "/^[^[:blank:]]/{x;p;x;}" +help: ## List documented Makefile targets. + @awk ' \ + BEGIN { FS = ":.*##"; printf "\nUsage:\n make \n" } \ + /^## / { printf "\n\033[90;3m%s\033[0m\n", substr($$0, 4); next } \ + /^[[:alnum:]_.%/-]+:.*## / { printf " \033[93m%-24s\033[0m %s\n", $$1, $$2 } \ + ' $(MAKEFILE_LIST) @printf "\n" From da4c874d9df4f12807cb828696258f635a16d58a Mon Sep 17 00:00:00 2001 From: Casey Labs <4674433+CaseyLabs@users.noreply.github.com> Date: Mon, 8 Jun 2026 17:08:35 -0700 Subject: [PATCH 09/19] update makefile --- Makefile | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/Makefile b/Makefile index fbcb4f7e4..9089374d8 100644 --- a/Makefile +++ b/Makefile @@ -1,9 +1,12 @@ -.DEFAULT_GOAL := build +# --- Makefile Overview --- -# Makefile overview: -# - Target comments marked with "##" appear in `make help`. -# - Most values are overridable: `make build BIN=gomud VERSION=v1.2.3`. -# - The Go version is read from go.mod so Docker workflows use the same toolchain. +# - Run "make help" to show a full list of commands. +# - Comments marked with double hash signs ("##") will appear in `make help` output. +# - Most command values are overridable: `make build BIN=gomud VERSION=v1.2.3`. + +# --- Makefile Variables --- + +.DEFAULT_GOAL := help VERSION ?= $(shell git rev-parse HEAD) BIN ?= go-mud-server @@ -44,6 +47,17 @@ CI_LOCAL_RUN := docker run --rm \ export GOFLAGS := -mod=mod +# --- Makefile Commands --- + +## Help +help: ## List documented Makefile targets. + @awk ' \ + BEGIN { FS = ":.*##"; printf "\nUsage:\n make \n" } \ + /^## / { printf "\n\033[90;3m%s\033[0m\n", substr($$0, 4); next } \ + /^[[:alnum:]_.%/-]+:.*## / { printf " \033[93m%-24s\033[0m %s\n", $$1, $$2 } \ + ' $(MAKEFILE_LIST) + @printf "\n" + ## Developer Workflow .PHONY: build build_local generate module validate test coverage fmt fmtcheck vet mod js-lint @@ -235,11 +249,3 @@ endif view_pprof_mem: ## Open the saved memory profile in the Go pprof web UI. go tool pprof -http=:8989 source/_datafiles/profiles/mem.pprof -## Help -help: ## List documented Makefile targets. - @awk ' \ - BEGIN { FS = ":.*##"; printf "\nUsage:\n make \n" } \ - /^## / { printf "\n\033[90;3m%s\033[0m\n", substr($$0, 4); next } \ - /^[[:alnum:]_.%/-]+:.*## / { printf " \033[93m%-24s\033[0m %s\n", $$1, $$2 } \ - ' $(MAKEFILE_LIST) - @printf "\n" From d0d9c8c0ccedc8fb5a59d3722b93d92eea728aa7 Mon Sep 17 00:00:00 2001 From: Casey Labs <4674433+CaseyLabs@users.noreply.github.com> Date: Mon, 8 Jun 2026 17:11:08 -0700 Subject: [PATCH 10/19] update makefile --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 9089374d8..d116f85a4 100644 --- a/Makefile +++ b/Makefile @@ -52,7 +52,7 @@ export GOFLAGS := -mod=mod ## Help help: ## List documented Makefile targets. @awk ' \ - BEGIN { FS = ":.*##"; printf "\nUsage:\n make \n" } \ + BEGIN { FS = ":.*##"; printf "\nUsage: make \nExample: make build\n" } \ /^## / { printf "\n\033[90;3m%s\033[0m\n", substr($$0, 4); next } \ /^[[:alnum:]_.%/-]+:.*## / { printf " \033[93m%-24s\033[0m %s\n", $$1, $$2 } \ ' $(MAKEFILE_LIST) From 6a4b1cf76244ddd56932ba26f5608b460ed4b44a Mon Sep 17 00:00:00 2001 From: Casey Labs <4674433+CaseyLabs@users.noreply.github.com> Date: Mon, 8 Jun 2026 17:15:26 -0700 Subject: [PATCH 11/19] update gomud ver number --- main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.go b/main.go index 98b326f3a..609a4fdb4 100644 --- a/main.go +++ b/main.go @@ -64,7 +64,7 @@ import ( // When updating this version: // 1. Expect to update the github release version // 2. Consider whether any migration code is needed for breaking changes, particularly in datafiles (see internal/migration) -const VERSION = "0.9.2" +const VERSION = "0.9.10" var ( sigChan = make(chan os.Signal, 1) From d8e28a393dd3bcda704525a2a58b3924c45aed0b Mon Sep 17 00:00:00 2001 From: Casey Labs <4674433+CaseyLabs@users.noreply.github.com> Date: Mon, 8 Jun 2026 17:16:31 -0700 Subject: [PATCH 12/19] Fix prerelease notes target commit --- .github/scripts/release-notes.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/scripts/release-notes.sh b/.github/scripts/release-notes.sh index 3fbb22930..d5cfc789e 100755 --- a/.github/scripts/release-notes.sh +++ b/.github/scripts/release-notes.sh @@ -56,6 +56,10 @@ if [ "${RELEASE_NOTES_SKIP_GH:-}" = "true" ]; then >"$generated_notes_file" else notes_tag="$release_tag" + if [ "$release_kind" = "prerelease" ]; then + # GitHub ignores target_commitish when tag_name already exists. + notes_tag="${release_tag}-notes-${commit_sha}" + fi generate_notes_args=( -f "tag_name=${notes_tag}" From dfe3db2bcd05d4c90e9fc9c2a204396211c5c001 Mon Sep 17 00:00:00 2001 From: Casey Labs <4674433+CaseyLabs@users.noreply.github.com> Date: Mon, 8 Jun 2026 17:26:45 -0700 Subject: [PATCH 13/19] Fix fmtcheck source selection --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index d116f85a4..0f05ad859 100644 --- a/Makefile +++ b/Makefile @@ -99,7 +99,7 @@ fmt: ## Format all Go files. fmtcheck: ## Fail if any Go file is not gofmt-formatted. @set -e; \ - unformatted=$$(gofmt -l $$(find . -path './vendor' -prune -o -name '*.go' -print)); \ + unformatted=$$(gofmt -l $$(git ls-files '*.go')); \ if [ -n "$$unformatted" ]; then \ echo "Go files need formatting:"; \ printf '%s\n' "$$unformatted"; \ From d4b90b317b0a575652b021594fb294cdc19d5135 Mon Sep 17 00:00:00 2001 From: Casey Labs <4674433+CaseyLabs@users.noreply.github.com> Date: Mon, 8 Jun 2026 17:40:19 -0700 Subject: [PATCH 14/19] update gha workflow files --- .github/RELEASING.md | 6 ++-- .github/actions/go-checks/action.yml | 8 +++-- .github/actions/setup-go/action.yml | 2 +- .github/scripts/ci-local-act.sh | 7 +++-- .github/workflows/{lint.yml => ci.yml} | 37 ++++++++++++++++++++++- .github/workflows/discord-notify.yml | 3 ++ .github/workflows/docker-package.yml | 4 +++ .github/workflows/prerelease.yml | 10 ++++++ .github/workflows/run-test.yml | 34 --------------------- .github/workflows/stable-release.yml | 12 ++++++++ .github/workflows/update-go-toolchain.yml | 8 +++++ 11 files changed, 86 insertions(+), 45 deletions(-) rename .github/workflows/{lint.yml => ci.yml} (63%) delete mode 100644 .github/workflows/run-test.yml diff --git a/.github/RELEASING.md b/.github/RELEASING.md index 46893e39b..6e5a4df75 100644 --- a/.github/RELEASING.md +++ b/.github/RELEASING.md @@ -19,9 +19,9 @@ Recommended versioning for the next release line: assets. This is intentional so the rolling prerelease remains mutable. 4. The prerelease is marked as a prerelease and is not marked as `Latest`. -Pull requests do not publish release binaries. The generic `Run Tests` workflow -is PR-focused; `master` release testing happens inside the `Prerelease` workflow -to avoid duplicate full race-test runs on merge. +Pull requests do not publish release binaries. The generic `CI` workflow runs +the PR test gate; `master` release testing happens inside the `Prerelease` +workflow to avoid duplicate full race-test runs on merge. ### Stable Release diff --git a/.github/actions/go-checks/action.yml b/.github/actions/go-checks/action.yml index cfb21209a..7f68824b6 100644 --- a/.github/actions/go-checks/action.yml +++ b/.github/actions/go-checks/action.yml @@ -1,10 +1,12 @@ --- name: "Go Checks" -description: "Run go generate and go test" +description: "Refresh generated code and run the race-enabled Go test suite" runs: using: "composite" steps: - - run: go generate ./... + - name: Generate code + run: go generate ./... shell: bash - - run: go test -race ./... + - name: Run Go tests + run: go test -race ./... shell: bash diff --git a/.github/actions/setup-go/action.yml b/.github/actions/setup-go/action.yml index be61fde78..1460b7e8a 100644 --- a/.github/actions/setup-go/action.yml +++ b/.github/actions/setup-go/action.yml @@ -1,6 +1,6 @@ --- name: "Setup Go" -description: "Setup Go using go.mod" +description: "Install the Go toolchain pinned by go.mod and enable module cache" runs: using: "composite" steps: diff --git a/.github/scripts/ci-local-act.sh b/.github/scripts/ci-local-act.sh index 502d97439..e113150c3 100755 --- a/.github/scripts/ci-local-act.sh +++ b/.github/scripts/ci-local-act.sh @@ -23,9 +23,10 @@ run_act() { -W "$workflow" } -run_act push .github/act/push_master.json .github/workflows/lint.yml -run_act pull_request .github/act/pull_request.json .github/workflows/lint.yml -run_act pull_request .github/act/pull_request.json .github/workflows/run-test.yml +# CI combines the old lint and PR test workflows. Dry-run both event shapes +# because pull requests cancel superseded runs while pushes to master do not. +run_act push .github/act/push_master.json .github/workflows/ci.yml +run_act pull_request .github/act/pull_request.json .github/workflows/ci.yml run_act pull_request .github/act/pull_request.json \ .github/workflows/discord-notify.yml ${ACT_DRYRUN_SECRETS:-} run_act push .github/act/push_master.json .github/workflows/prerelease.yml diff --git a/.github/workflows/lint.yml b/.github/workflows/ci.yml similarity index 63% rename from .github/workflows/lint.yml rename to .github/workflows/ci.yml index c3732e58e..673138b90 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/ci.yml @@ -1,5 +1,11 @@ --- -name: Lint +name: CI + +# ci.yml performs the Continuous Integration (CI) workflow and performs the following actions: +# - Go format/vet +# - conditional JS lint +# - Go race tests +# - Release cross-compile check "on": push: @@ -12,6 +18,7 @@ name: Lint - reopened - ready_for_review +# Superseded CI runs cancel just like the former Lint and Run Tests workflows. concurrency: group: >- ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} @@ -22,12 +29,15 @@ permissions: jobs: detect-changes: + name: Detect JavaScript Changes runs-on: ubuntu-24.04 timeout-minutes: 5 outputs: js_lint: >- ${{ steps.filter.outputs.js_lint }} steps: + # Full history is needed so push and pull request events can diff against + # the actual base commit instead of whatever shallow checkout provides. # actions/checkout v6.0.2 - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd with: @@ -59,6 +69,7 @@ jobs: fi go-lint: + name: Go Format And Vet runs-on: ubuntu-24.04 timeout-minutes: 20 steps: @@ -73,9 +84,11 @@ jobs: run: make validate js-lint: + name: JavaScript Lint runs-on: ubuntu-24.04 timeout-minutes: 10 needs: detect-changes + # The web client JavaScript is linted only when related files change. if: >- needs.detect-changes.outputs.js_lint == 'true' steps: @@ -92,3 +105,25 @@ jobs: - name: Run JavaScript lint checks run: make js-lint + + test: + name: Go Tests And Release Cross-Compile + # PRs run the full test/cross-compile gate. Pushes to master get the same + # release validation in the Prerelease workflow, so avoid doing it twice. + if: ${{ github.event_name == 'pull_request' }} + runs-on: ubuntu-24.04 + timeout-minutes: 30 + steps: + # actions/checkout v6.0.2 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd + with: + persist-credentials: false + + - uses: ./.github/actions/setup-go + + - uses: ./.github/actions/go-checks + + - name: Cross-compile release targets + env: + RELEASE_DIST_DIR: ${{ runner.temp }}/release-cross-compile + run: .github/scripts/build-release-binaries.sh diff --git a/.github/workflows/discord-notify.yml b/.github/workflows/discord-notify.yml index 6b597669f..293d185c9 100644 --- a/.github/workflows/discord-notify.yml +++ b/.github/workflows/discord-notify.yml @@ -12,6 +12,8 @@ permissions: jobs: notify-discord: + # Draft pull requests wait until ready_for_review so Discord only receives + # notifications for PRs that are ready for broader attention. if: ${{ !github.event.pull_request.draft }} runs-on: ubuntu-24.04 timeout-minutes: 5 @@ -25,6 +27,7 @@ jobs: run: | webhook_url='${{ secrets.DISCORD_WEBHOOK_URL }}' if [ -z "$webhook_url" ]; then + # Local act dry runs can pass this as an environment variable. webhook_url="${DISCORD_WEBHOOK_URL:-}" fi echo "url=$webhook_url" >> "$GITHUB_OUTPUT" diff --git a/.github/workflows/docker-package.yml b/.github/workflows/docker-package.yml index be4285315..ba47af57c 100644 --- a/.github/workflows/docker-package.yml +++ b/.github/workflows/docker-package.yml @@ -2,6 +2,7 @@ name: Docker Image "on": + # Manual dispatch lets maintainers rebuild the image without changing source. workflow_dispatch: push: branches: @@ -35,6 +36,7 @@ jobs: - name: Detect Go version id: go-version + # The Dockerfile build arg follows go.mod through the Makefile helper. run: echo "version=$(make go-version)" >> "$GITHUB_OUTPUT" - name: Set up Docker Buildx @@ -69,6 +71,8 @@ jobs: uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f with: context: . + # Pull requests build and cache the image, but only trusted events + # publish to GitHub Packages. push: ${{ github.event_name != 'pull_request' }} file: ./provisioning/Dockerfile build-args: | diff --git a/.github/workflows/prerelease.yml b/.github/workflows/prerelease.yml index f77c958f5..412adfe9e 100644 --- a/.github/workflows/prerelease.yml +++ b/.github/workflows/prerelease.yml @@ -6,6 +6,8 @@ name: Prerelease branches: - master +# Prerelease is intentionally mutable: each successful merge to master replaces +# the existing prerelease assets and moves the prerelease tag forward. concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: false @@ -19,6 +21,7 @@ env: jobs: metadata: + name: Read Binary Version runs-on: ubuntu-24.04 timeout-minutes: 5 permissions: @@ -47,6 +50,7 @@ jobs: echo "binary_version=${binary_version}" >> "$GITHUB_OUTPUT" test: + name: Validate Release Source runs-on: ubuntu-24.04 timeout-minutes: 30 permissions: @@ -62,6 +66,7 @@ jobs: - uses: ./.github/actions/go-checks build: + name: Build Release Binaries runs-on: ubuntu-24.04 timeout-minutes: 20 needs: @@ -94,6 +99,7 @@ jobs: if-no-files-found: error publish: + name: Publish Rolling Prerelease runs-on: ubuntu-24.04 timeout-minutes: 30 needs: @@ -132,6 +138,8 @@ jobs: - name: Resolve attestation paths id: release-assets + # Attest binaries and checksums. The datafiles zip is covered by the + # checksum file because it is assembled inside this workflow. run: | { echo 'attestation_paths</dev/null 2>&1; then diff --git a/.github/workflows/run-test.yml b/.github/workflows/run-test.yml deleted file mode 100644 index 46682e660..000000000 --- a/.github/workflows/run-test.yml +++ /dev/null @@ -1,34 +0,0 @@ ---- -name: Run Tests - -"on": - pull_request: - types: - - opened - - synchronize - - reopened - - ready_for_review - -concurrency: - group: >- - ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true - -permissions: - contents: read - -jobs: - test: - runs-on: ubuntu-24.04 - timeout-minutes: 30 - steps: - # actions/checkout v6.0.2 - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - with: - persist-credentials: false - - uses: ./.github/actions/setup-go - - uses: ./.github/actions/go-checks - - name: Cross-compile release targets - env: - RELEASE_DIST_DIR: ${{ runner.temp }}/release-cross-compile - run: .github/scripts/build-release-binaries.sh diff --git a/.github/workflows/stable-release.yml b/.github/workflows/stable-release.yml index 38117c9e2..bd05988f4 100644 --- a/.github/workflows/stable-release.yml +++ b/.github/workflows/stable-release.yml @@ -9,6 +9,8 @@ name: Stable Release required: true type: string +# Stable releases are intentionally immutable. Re-running with an existing tag +# or release fails instead of replacing published assets. concurrency: group: ${{ github.workflow }}-${{ inputs.release_tag }} cancel-in-progress: false @@ -22,6 +24,7 @@ env: jobs: validate-release: + name: Validate Stable Release Request runs-on: ubuntu-24.04 timeout-minutes: 5 permissions: @@ -75,6 +78,7 @@ jobs: echo "binary_version=${binary_version}" >> "$GITHUB_OUTPUT" test: + name: Validate Release Source runs-on: ubuntu-24.04 timeout-minutes: 30 needs: validate-release @@ -91,6 +95,7 @@ jobs: - uses: ./.github/actions/go-checks build: + name: Build Release Binaries runs-on: ubuntu-24.04 timeout-minutes: 20 needs: @@ -123,6 +128,7 @@ jobs: if-no-files-found: error publish: + name: Publish Stable Release runs-on: ubuntu-24.04 timeout-minutes: 30 needs: @@ -145,6 +151,9 @@ jobs: persist-credentials: false - name: Recheck stable release immutability + # The first check catches bad manual inputs early. This second check + # runs after environment approval in case someone created the tag or + # release while the workflow was waiting. run: | set -euo pipefail @@ -180,6 +189,8 @@ jobs: - name: Resolve attestation paths id: release-assets + # Attest binaries and checksums. The datafiles zip is covered by the + # checksum file because it is assembled inside this workflow. run: | { echo 'attestation_paths< Date: Mon, 8 Jun 2026 17:41:52 -0700 Subject: [PATCH 15/19] update gha workflow files --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 673138b90..2312ccb13 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,7 +18,7 @@ name: CI - reopened - ready_for_review -# Superseded CI runs cancel just like the former Lint and Run Tests workflows. +# Superseded CI runs cancel previous runs concurrency: group: >- ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} From 5a72b9d110c08f1a60e994b8037b6b198040af14 Mon Sep 17 00:00:00 2001 From: Casey Labs <4674433+CaseyLabs@users.noreply.github.com> Date: Mon, 8 Jun 2026 17:50:15 -0700 Subject: [PATCH 16/19] Fix CI workflow comment lint --- .github/workflows/ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2312ccb13..1b04262c6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,7 +1,8 @@ --- name: CI -# ci.yml performs the Continuous Integration (CI) workflow and performs the following actions: +# ci.yml performs the Continuous Integration (CI) workflow and performs the +# following actions: # - Go format/vet # - conditional JS lint # - Go race tests From b0748c25b3b0ba0822d6e784c1c35f904e48f646 Mon Sep 17 00:00:00 2001 From: Casey Labs <4674433+CaseyLabs@users.noreply.github.com> Date: Mon, 8 Jun 2026 17:50:50 -0700 Subject: [PATCH 17/19] Remove local plan from branch --- PLAN.md | 108 -------------------------------------------------------- 1 file changed, 108 deletions(-) delete mode 100644 PLAN.md diff --git a/PLAN.md b/PLAN.md deleted file mode 100644 index 6f78a641f..000000000 --- a/PLAN.md +++ /dev/null @@ -1,108 +0,0 @@ -# Modernize GitHub Actions and Release Builds - -## Summary - -- Implement the work across four focused PRs to reduce release risk. -- Split the current draft branch into separate runtime and release-automation PRs because the XP clamp and GitHub Actions changes have different reviewers, risks, and rollback paths. -- Support official release binaries for `linux/amd64`, `linux/arm64`, `linux/arm/v7`, `windows/amd64`, `windows/arm64`, `darwin/amd64`, and `darwin/arm64`. -- Keep XP values as the current `int` domain and clamp XP formulas with `math.MaxInt`. -- Split mutable prereleases from protected stable releases, then add targeted CI speedups. - -## PR 1: Clamp XP Progression Values - -- Replace `Character.XPTL`'s `math.MaxInt64` clamp with `math.MaxInt`. -- Add the same `math.MaxInt` clamp to `internal/web/api_v1_progression.go`'s `xpTLWithCfg`. -- Add high-XP progression tests proving engine and admin preview clamp consistently. - -## PR 2: Update Current Release Asset Matrix - -- Replace `linux/arm GOARM=5` with `linux/arm GOARM=7`. -- Add `linux/arm64`. -- Preserve existing `x64` asset names and add ARM assets: - - `gomud-linux_x64` - - `gomud-linux_arm64` - - `gomud-linux_armv7` - - `gomud-windows_x64.exe` - - `gomud-windows_arm64.exe` - - `gomud-darwin_x64` - - `gomud-darwin_arm64` -- Update current release workflows' build steps, checksum generation, and upload lists for the seven binaries. -- Add CI cross-compile checks for all seven release targets - -## PR 3: Redesign Release Publishing - -- If possible: reviewers should not be notified for draft PRs, only when the PR is set to ready for review. -- Split the current mixed release flow into: - - `Prerelease`: push to `master`, mutable rolling `prerelease`, no protected environment. - - `Stable Release`: manual `workflow_dispatch` with required semver `release_tag`, protected GitHub Environment, no tag moves or asset clobbering. -- Keep repo-wide immutable releases disabled while rolling `prerelease` stays mutable. -- Enforce stable-release immutability by workflow policy: fail if the semver tag or release already exists. -- Publish stable releases draft-first: create draft, upload assets, attach notes, then publish. -- Define release targets once in `.github/scripts/release-assets.sh`. -- Build release binaries from that target table and upload one - `release-binaries` artifact. -- Assemble all binaries, `gomud-ALL-datafiles.zip`, and `SHA256SUMS.txt` in the publish job. -- Add artifact attestations for each binary and `SHA256SUMS.txt` with a single - multi-subject attestation step. -- Use job-level permissions: - - build/test jobs: `contents: read`; - - publish jobs: `contents: write`, `id-token: write`, `attestations: write`. -- Centralize release note generation in `scripts/release-notes.sh` or a composite action. -- Release notes include `Overview`, `Downloads`, `Install From Source`, `Manual Binary Install`, `Verify Provenance`, and `Changes`. -- Insert GitHub auto-generated notes from `releases/generate-notes`. -- Avoid shell-injection risk by writing generated notes to files and not evaluating generated release text as shell input. -- Drop `go build -v` from release builds unless debugging. -- Avoid duplicate full test runs on `master` by making prerelease publishing depend on the normal test workflow success or by combining test/build/release with a single test job. - -## PR 4: Installers and CI Optimization - -- Keep current installers as source-build installers. -- Add release ref support: - - `scripts/install.sh`: support `GOMUD_VER=v0.9.9` or `GOMUD_VER=prerelease` before build. - - `scripts/install.ps1`: support `GOMUD_VER` environment variable and `-version` parameter. -- Update `scripts/install.sh` architecture detection to recognize `armv7l` as ARMv7. -- Do not claim install scripts download release binaries. -- Release notes state official Windows binary assets are `windows/amd64` and `windows/arm64`. -- Add a docs-only change detector so Go tests, release cross-compiles, and Docker builds can be skipped when changes are limited to docs or non-runtime metadata. -- Keep checks enabled for runtime/build inputs: `*.go`, `go.mod`, `go.sum`, `Makefile`, `_datafiles/**`, `modules/**`, `cmd/**`, `internal/**`, `scripts/**`, `provisioning/**`, `compose.yml`, and relevant workflow files. -- Make Docker PR builds conditional on Docker/runtime/build input changes. -- Keep `go test -race ./...` on PRs and `master` initially; revisit PR race tests only if CI time remains a problem. -- Keep `cancel-in-progress: true` for PR test/lint runs. -- Consider `cancel-in-progress: true` for Docker `master` image builds so only the newest image publishes. -- Keep stable release publishing `cancel-in-progress: false`. -- Use separate Docker cache scopes for PR and master if cache contention appears. -- Fix `fmtcheck` so it checks formatting without first mutating files. -- Fix `make js-lint` so missing Docker does not get masked as success; prefer - local `npx`, fall back to Docker, and fail if neither is available. -- Correct Dependabot Docker paths to `/provisioning` and `/provisioning/terminal`. -- Keep `go generate` behavior explicit: run once in CI and verify generated output is clean before tests/builds depend on it. - -## Test Plan - -- PR 1: - - `go test ./internal/characters ./internal/web` - - `make validate` -- PR 2: - - `make validate` - - `go test -race ./...` - - all seven supported cross-compiles - - verify current release workflow dry-run/checksums reference all seven binaries -- PR 3: - - workflow lint / `make ci-local` if Docker is available - - dry-run release note generation - - prerelease run on `master` - - stable release dispatch against a test semver tag, including existing tag/release failure behavior - - verify binaries, datafiles archive, checksums, and attestations -- PR 4: - - installer source-build paths with and without `GOMUD_VER` / `-Ref` - - Linux `armv7l` detection logic - - docs-only and Docker-conditional skip behavior with representative changed-file sets - - `make ci-local` if Docker is available - -## Assumptions - -- XP remains an `int` domain; no `int64` persistence/API migration is in scope. -- 32-bit official binary support means Linux ARMv7 only, not ARMv5 or `linux/386`. -- Existing `x64` artifact names are preserved for compatibility. -- Release asset renames apply to new releases; stale asset cleanup is not required for this branch's release path. -- Repo-wide immutable releases remain disabled while rolling `prerelease` stays mutable. From f20753216fee200558b5b389026905d95192a015 Mon Sep 17 00:00:00 2001 From: Casey Labs <4674433+CaseyLabs@users.noreply.github.com> Date: Mon, 8 Jun 2026 18:04:21 -0700 Subject: [PATCH 18/19] Exclude vendored Monaco bundle from JS lint --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 0f05ad859..69a89178f 100644 --- a/Makefile +++ b/Makefile @@ -26,7 +26,8 @@ ACT_FLAGS ?= --pull=false -P ubuntu-24.04=catthehacker/ubuntu:act-latest ACT_DRYRUN_SECRETS ?= -s DISCORD_WEBHOOK_URL=https://example.invalid/webhook JSHINT_VERSION ?= 2.13.6 -JS_LINT_PATHS := $(shell find _datafiles -name '*.js' -print) +VENDORED_JS_LINT_PATHS := _datafiles/html/admin/static/js/monaco/% +JS_LINT_PATHS := $(filter-out $(VENDORED_JS_LINT_PATHS),$(shell find _datafiles -name '*.js' -print)) WEBCLIENT_WINDOW_JS := $(shell find _datafiles/html/public/static/js/windows -name '*.js' -print) WEBCLIENT_BASE_JS := $(filter-out $(WEBCLIENT_WINDOW_JS),$(JS_LINT_PATHS)) JSHINT := npx --yes --loglevel=error jshint@$(JSHINT_VERSION) @@ -248,4 +249,3 @@ endif view_pprof_mem: ## Open the saved memory profile in the Go pprof web UI. go tool pprof -http=:8989 source/_datafiles/profiles/mem.pprof - From 43fd3bd8330513fe44874b9aca608ca9377d99d0 Mon Sep 17 00:00:00 2001 From: Casey Labs <4674433+CaseyLabs@users.noreply.github.com> Date: Mon, 8 Jun 2026 18:16:33 -0700 Subject: [PATCH 19/19] Run release cross-compiles only on master --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1b04262c6..0da952dd8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -109,9 +109,6 @@ jobs: test: name: Go Tests And Release Cross-Compile - # PRs run the full test/cross-compile gate. Pushes to master get the same - # release validation in the Prerelease workflow, so avoid doing it twice. - if: ${{ github.event_name == 'pull_request' }} runs-on: ubuntu-24.04 timeout-minutes: 30 steps: @@ -125,6 +122,9 @@ jobs: - uses: ./.github/actions/go-checks - name: Cross-compile release targets + if: >- + github.event_name == 'push' && + github.ref == 'refs/heads/master' env: RELEASE_DIST_DIR: ${{ runner.temp }}/release-cross-compile run: .github/scripts/build-release-binaries.sh