From 6cac3bc3cb6706219b356333254f576f7e55af9a Mon Sep 17 00:00:00 2001 From: Casey Labs <4674433+CaseyLabs@users.noreply.github.com> Date: Wed, 10 Jun 2026 15:10:04 -0700 Subject: [PATCH 1/5] Avoid moving prerelease tag --- .github/RELEASING.md | 16 ++++--- .github/scripts/release-notes.sh | 10 +---- .github/workflows/prerelease.yml | 66 +++++++++++++++++------------ _datafiles/guides/running/README.md | 6 +-- 4 files changed, 53 insertions(+), 45 deletions(-) diff --git a/.github/RELEASING.md b/.github/RELEASING.md index 57cf14fd5..cdbb3927e 100644 --- a/.github/RELEASING.md +++ b/.github/RELEASING.md @@ -6,7 +6,8 @@ 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. +- Let merge-driven builds update a rolling `prerelease` entry using generated + immutable tags. - Reserve stable semver tags like `v0.10.0` for protected stable releases. ### Prerelease @@ -14,9 +15,11 @@ Recommended versioning for the next release line: 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. + attestations, and publishes a new GitHub prerelease named `prerelease`. +3. The workflow uses a generated tag such as + `prerelease-1234-1-` for the new prerelease, then removes older + rolling prerelease releases and tags. Prerelease tags are not force-moved, so + normal developer `git pull` commands do not fail on tag clobber conflicts. 4. The prerelease is marked as a prerelease and is not marked as `Latest`. Pull requests do not publish release binaries. The generic `CI` workflow runs @@ -39,8 +42,9 @@ workflow to avoid duplicate full race-test runs on merge. Registry image. 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. +existing releases. The rolling prerelease workflow also avoids moving tags, but +it still replaces the visible prerelease entry by creating a new generated tag +and deleting older rolling prerelease releases. ### Assets diff --git a/.github/scripts/release-notes.sh b/.github/scripts/release-notes.sh index 02c1cc736..c4fff925c 100755 --- a/.github/scripts/release-notes.sh +++ b/.github/scripts/release-notes.sh @@ -49,14 +49,8 @@ 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 - # 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}" + -f "tag_name=${release_tag}" -f "target_commitish=${commit_sha}" ) if [ -n "$previous_tag" ] && [ "$previous_tag" != "$release_tag" ]; then @@ -75,7 +69,7 @@ published_at="$(date -u +"%Y-%m-%dT%H:%M:%SZ")" 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\`." + summary="The rolling prerelease entry is replaced on each successful merge to \`master\`, using a generated tag for this build." else overview="Stable release \`${release_tag}\`." summary="This stable release is immutable. Tags and assets are not replaced by workflow policy." diff --git a/.github/workflows/prerelease.yml b/.github/workflows/prerelease.yml index cda22b97b..580b6e40b 100644 --- a/.github/workflows/prerelease.yml +++ b/.github/workflows/prerelease.yml @@ -6,8 +6,9 @@ name: Prerelease branches: - master -# Prerelease is intentionally mutable: each successful merge to master replaces -# the existing prerelease assets and moves the prerelease tag forward. +# The prerelease entry is intentionally rolling: each successful merge to master +# replaces the existing prerelease assets. The Git tags are unique per workflow +# attempt so normal developer fetches do not fail on a force-moved tag. concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: false @@ -110,7 +111,8 @@ jobs: id-token: write attestations: write env: - RELEASE_TAG: prerelease + RELEASE_TAG: prerelease-${{ github.run_number }}-${{ github.run_attempt }}-${{ github.sha }} + RELEASE_TAG_PREFIX: prerelease RELEASE_TITLE: prerelease BINARY_VERSION: ${{ needs.metadata.outputs.binary_version }} GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -162,32 +164,40 @@ jobs: .github/scripts/release-assets.sh upload-paths ) - # Move the lightweight prerelease tag when it already exists. If it - # does not exist, gh release create will create it at this commit. + mapfile -t old_release_tags < <( + gh release list \ + --limit 100 \ + --json isPrerelease,tagName \ + --jq '.[] | select( + .isPrerelease + and ( + .tagName == "prerelease" + or (.tagName | startswith("prerelease-")) + ) + ) | .tagName' + ) + + gh release create "$RELEASE_TAG" \ + "${assets[@]}" \ + --title "$RELEASE_TITLE" \ + --target "$GITHUB_SHA" \ + --prerelease \ + --latest=false \ + --notes-file release-notes.md + + for old_tag in "${old_release_tags[@]}"; do + if [ "$old_tag" = "$RELEASE_TAG" ]; then + continue + fi + gh release delete "$old_tag" --yes --cleanup-tag + done + + # Remove a retired mutable prerelease tag even if it is not attached + # to a release anymore. if gh api \ - "repos/${GITHUB_REPOSITORY}/git/ref/tags/${RELEASE_TAG}" \ + "repos/${GITHUB_REPOSITORY}/git/ref/tags/${RELEASE_TAG_PREFIX}" \ >/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 - gh release create "$RELEASE_TAG" \ - "${assets[@]}" \ - --title "$RELEASE_TITLE" \ - --target "$GITHUB_SHA" \ - --prerelease \ - --latest=false \ - --notes-file release-notes.md + -X DELETE \ + "repos/${GITHUB_REPOSITORY}/git/refs/tags/${RELEASE_TAG_PREFIX}" fi diff --git a/_datafiles/guides/running/README.md b/_datafiles/guides/running/README.md index d9e715960..24c9c6c2c 100644 --- a/_datafiles/guides/running/README.md +++ b/_datafiles/guides/running/README.md @@ -14,9 +14,9 @@ or choose a numbered release for a permanent versioned build. You can download a build from the [releases page](https://github.com/GoMudEngine/GoMud/releases), unzip it and run the binary to get started, or if you prefer to build it -yourself, follow the instructions below. Use `prerelease` for the newest -`master` build, or a numbered release if you want a stable version you can -return to later. +yourself, follow the instructions below. Use the rolling `prerelease` entry for +the newest `master` build, or a numbered release if you want a stable version +you can return to later. A youtube playlist to getting started has been set up here: From e2753346d81d97ed18fcdb38b69734f8e939a95c Mon Sep 17 00:00:00 2001 From: Casey Labs <4674433+CaseyLabs@users.noreply.github.com> Date: Wed, 10 Jun 2026 15:15:11 -0700 Subject: [PATCH 2/5] Skip irrelevant CodeQL language scans --- .github/workflows/codeql.yml | 128 +++++++++++++++++++++++++++++++---- 1 file changed, 115 insertions(+), 13 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 007c859ab..2e77b62a4 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -28,18 +28,87 @@ permissions: security-events: write jobs: - analyze: - name: Analyze (${{ matrix.language }}) + changes: + name: Detect CodeQL Inputs + runs-on: ubuntu-24.04 + timeout-minutes: 5 + outputs: + go: ${{ steps.detect.outputs.go }} + javascript_typescript: ${{ steps.detect.outputs.javascript_typescript }} + steps: + # actions/checkout v6.0.3 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 + with: + fetch-depth: 0 + persist-credentials: false + + - name: Detect changed CodeQL inputs + id: detect + env: + BEFORE_SHA: ${{ github.event.before }} + EVENT_NAME: ${{ github.event_name }} + run: | + set -euo pipefail + + go_changed=false + js_changed=false + + if [ "$EVENT_NAME" = "schedule" ]; then + go_changed=true + js_changed=true + else + if git rev-parse --verify --quiet HEAD^2 >/dev/null; then + diff_base=HEAD^1 + diff_head=HEAD^2 + elif [ -n "${BEFORE_SHA}" ] && + [ "${BEFORE_SHA}" != "0000000000000000000000000000000000000000" ]; then + diff_base="${BEFORE_SHA}" + diff_head=HEAD + else + diff_base=HEAD^ + diff_head=HEAD + fi + + mapfile -t changed_files < <( + git diff --name-only "$diff_base" "$diff_head" + ) + + for file in "${changed_files[@]}"; do + case "$file" in + .github/workflows/codeql.yml | .github/codeql/*) + go_changed=true + js_changed=true + ;; + *.go | go.mod | go.sum | Makefile | \ + .github/actions/setup-go/* | .github/actions/go-checks/*) + go_changed=true + ;; + _datafiles/html/admin/static/js/monaco/* | \ + _datafiles/html/admin/static/js/highlight.js | \ + _datafiles/html/admin/static/css/monaco-editor.css | \ + _datafiles/html/public/static/js/xterm/*) + ;; + *.js | *.jsx | *.ts | *.tsx | *.mjs | *.cjs | \ + package.json | package-lock.json | npm-shrinkwrap.json | \ + yarn.lock | pnpm-lock.yaml) + js_changed=true + ;; + esac + done + fi + + { + echo "go=${go_changed}" + echo "javascript_typescript=${js_changed}" + } >> "$GITHUB_OUTPUT" + + analyze-go: + name: Analyze (go) + needs: changes + if: ${{ github.event_name == 'schedule' || needs.changes.outputs.go == 'true' }} runs-on: ubuntu-24.04 # Keep analyzer regressions from tying up a runner indefinitely. timeout-minutes: 30 - strategy: - # Run both languages so one analyzer failure does not hide the other. - fail-fast: false - matrix: - language: - - go - - javascript-typescript steps: # actions/checkout v6.0.3 @@ -52,13 +121,11 @@ jobs: # yamllint disable-line rule:line-length uses: github/codeql-action/init@8aad20d150bbac5944a9f9d289da16a4b0d87c1e with: - languages: ${{ matrix.language }} + languages: go # Path filters live in CodeQL config, not workflow event filters. config-file: ./.github/codeql/codeql-config.yml - name: Autobuild - # JavaScript/TypeScript is interpreted here; only Go needs autobuild. - if: matrix.language == 'go' # github/codeql-action v4.36.2 # yamllint disable-line rule:line-length uses: github/codeql-action/autobuild@8aad20d150bbac5944a9f9d289da16a4b0d87c1e @@ -69,4 +136,39 @@ jobs: uses: github/codeql-action/analyze@8aad20d150bbac5944a9f9d289da16a4b0d87c1e with: # Keep separate code scanning result categories for each language. - category: "/language:${{ matrix.language }}" + category: "/language:go" + + analyze-javascript-typescript: + name: Analyze (javascript-typescript) + needs: changes + if: >- + ${{ + github.event_name == 'schedule' || + needs.changes.outputs.javascript_typescript == 'true' + }} + runs-on: ubuntu-24.04 + # Keep analyzer regressions from tying up a runner indefinitely. + timeout-minutes: 30 + + steps: + # actions/checkout v6.0.3 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 + with: + persist-credentials: false + + - name: Initialize CodeQL + # github/codeql-action v4.36.2 + # yamllint disable-line rule:line-length + uses: github/codeql-action/init@8aad20d150bbac5944a9f9d289da16a4b0d87c1e + with: + languages: javascript-typescript + # Path filters live in CodeQL config, not workflow event filters. + config-file: ./.github/codeql/codeql-config.yml + + - name: Perform CodeQL Analysis + # github/codeql-action v4.36.2 + # yamllint disable-line rule:line-length + uses: github/codeql-action/analyze@8aad20d150bbac5944a9f9d289da16a4b0d87c1e + with: + # Keep separate code scanning result categories for each language. + category: "/language:javascript-typescript" From 85f0b51e65adbd80b038b24d03092191060ffe66 Mon Sep 17 00:00:00 2001 From: Casey Labs <4674433+CaseyLabs@users.noreply.github.com> Date: Wed, 10 Jun 2026 15:19:32 -0700 Subject: [PATCH 3/5] Run JavaScript CodeQL for HTML changes --- .github/workflows/codeql.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 2e77b62a4..773ded4ca 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -88,7 +88,7 @@ jobs: _datafiles/html/admin/static/css/monaco-editor.css | \ _datafiles/html/public/static/js/xterm/*) ;; - *.js | *.jsx | *.ts | *.tsx | *.mjs | *.cjs | \ + *.html | *.htm | *.js | *.jsx | *.ts | *.tsx | *.mjs | *.cjs | \ package.json | package-lock.json | npm-shrinkwrap.json | \ yarn.lock | pnpm-lock.yaml) js_changed=true From b6582baf8cd5e8f74df7a6311097d1d8645b4aba Mon Sep 17 00:00:00 2001 From: Casey Labs <4674433+CaseyLabs@users.noreply.github.com> Date: Wed, 10 Jun 2026 15:20:42 -0700 Subject: [PATCH 4/5] Exclude vendored HTML from CodeQL detection --- .github/workflows/codeql.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 773ded4ca..e254c7037 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -83,7 +83,7 @@ jobs: .github/actions/setup-go/* | .github/actions/go-checks/*) go_changed=true ;; - _datafiles/html/admin/static/js/monaco/* | \ + _datafiles/html/admin/static/js/monaco* | \ _datafiles/html/admin/static/js/highlight.js | \ _datafiles/html/admin/static/css/monaco-editor.css | \ _datafiles/html/public/static/js/xterm/*) From 976be52174acd63f79f0774e144ef75e3a0bb7fd Mon Sep 17 00:00:00 2001 From: Casey Labs <4674433+CaseyLabs@users.noreply.github.com> Date: Wed, 10 Jun 2026 15:32:56 -0700 Subject: [PATCH 5/5] Satisfy local CI workflow lint --- .github/RELEASING.md | 2 +- .github/workflows/codeql.yml | 9 +++++++-- .github/workflows/prerelease.yml | 2 +- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/.github/RELEASING.md b/.github/RELEASING.md index cdbb3927e..e950e90da 100644 --- a/.github/RELEASING.md +++ b/.github/RELEASING.md @@ -17,7 +17,7 @@ Recommended versioning for the next release line: assembles `gomud-ALL-datafiles.zip` and `SHA256SUMS.txt`, generates attestations, and publishes a new GitHub prerelease named `prerelease`. 3. The workflow uses a generated tag such as - `prerelease-1234-1-` for the new prerelease, then removes older + `prerelease-123456789-1` for the new prerelease, then removes older rolling prerelease releases and tags. Prerelease tags are not force-moved, so normal developer `git pull` commands do not fail on tag clobber conflicts. 4. The prerelease is marked as a prerelease and is not marked as `Latest`. diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index e254c7037..94afe506c 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -57,11 +57,12 @@ jobs: go_changed=true js_changed=true else + zero_sha=0000000000000000000000000000000000000000 if git rev-parse --verify --quiet HEAD^2 >/dev/null; then diff_base=HEAD^1 diff_head=HEAD^2 elif [ -n "${BEFORE_SHA}" ] && - [ "${BEFORE_SHA}" != "0000000000000000000000000000000000000000" ]; then + [ "${BEFORE_SHA}" != "${zero_sha}" ]; then diff_base="${BEFORE_SHA}" diff_head=HEAD else @@ -105,7 +106,11 @@ jobs: analyze-go: name: Analyze (go) needs: changes - if: ${{ github.event_name == 'schedule' || needs.changes.outputs.go == 'true' }} + if: >- + ${{ + github.event_name == 'schedule' || + needs.changes.outputs.go == 'true' + }} runs-on: ubuntu-24.04 # Keep analyzer regressions from tying up a runner indefinitely. timeout-minutes: 30 diff --git a/.github/workflows/prerelease.yml b/.github/workflows/prerelease.yml index 580b6e40b..908929fb0 100644 --- a/.github/workflows/prerelease.yml +++ b/.github/workflows/prerelease.yml @@ -111,7 +111,7 @@ jobs: id-token: write attestations: write env: - RELEASE_TAG: prerelease-${{ github.run_number }}-${{ github.run_attempt }}-${{ github.sha }} + RELEASE_TAG: prerelease-${{ github.run_id }}-${{ github.run_attempt }} RELEASE_TAG_PREFIX: prerelease RELEASE_TITLE: prerelease BINARY_VERSION: ${{ needs.metadata.outputs.binary_version }}