diff --git a/.github/workflows/notify-docs-sdk-release.yml b/.github/workflows/notify-docs-sdk-release.yml deleted file mode 100644 index 64ec7a7b34..0000000000 --- a/.github/workflows/notify-docs-sdk-release.yml +++ /dev/null @@ -1,34 +0,0 @@ -name: Notify Docs of Unity SDK Release - -on: - push: - tags: - - 'unity-sdk-[0-9]*' - - '!unity-sdk-*-PREVIEW*' - - '!unity-sdk-*-RC*' - -jobs: - dispatch: - name: Dispatch unity-sdk-released to beamable/docs - runs-on: ubuntu-latest - steps: - - name: Extract SDK version from tag - id: version - run: | - TAG="${{ github.ref_name }}" - echo "sdk_version=${TAG#unity-sdk-}" >> $GITHUB_OUTPUT - - - name: Send repository_dispatch to beamable/docs - run: | - curl --fail-with-body -sS -X POST \ - -H "Authorization: Bearer ${{ secrets.DOCS_DISPATCH_PAT }}" \ - -H "Accept: application/vnd.github+json" \ - -H "X-GitHub-Api-Version: 2022-11-28" \ - https://api.github.com/repos/beamable/docs/dispatches \ - -d '{ - "event_type": "unity-sdk-released", - "client_payload": { - "tag": "${{ github.ref_name }}", - "sdk_version": "${{ steps.version.outputs.sdk_version }}" - } - }' diff --git a/.github/workflows/release-nuget.yml b/.github/workflows/release-nuget.yml index 7e8f6a754b..e9b5211f39 100644 --- a/.github/workflows/release-nuget.yml +++ b/.github/workflows/release-nuget.yml @@ -207,13 +207,37 @@ jobs: "production": "production" }')[github.event.inputs.releaseType] }} - - name: Create Release + # Mints the release tag. Created via the API rather than git tag/push so the tag + # lands on exactly ${{ github.sha }} and stays lightweight, matching every prior + # tag - release-unity.yml checks out inputs.commit, which need not be github.sha. + # Every downstream consumer (LightBeam aliases, the docs version table) keys off + # this tag, not off the GitHub Release created below. + - name: Create release tag if: ${{ inputs.dryRun == false }} env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - gh release create "cli-${{ steps.version.outputs.VERSION }}" \ - --target "${{ github.sha }}" \ + gh api "repos/${{ github.repository }}/git/refs" \ + -f ref="refs/tags/cli-${{ steps.version.outputs.VERSION }}" \ + -f sha="${{ github.sha }}" + + # A GitHub Release is created for production only. Nightly and RC tags stay bare: + # a Release per preview build is what made the previous 999 of them noise. The + # notes are a machine-generated verbatim slice of this version's CHANGELOG.md + # section, for the lanes this workflow publishes - the same COPY_* lanes declared + # for Publish Changelog above. Warns rather than fails on a missing section, so a + # changelog nobody updated cannot red-X a release whose packages already shipped. + - name: Create GitHub Release + if: ${{ inputs.dryRun == false && inputs.releaseType == 'production' }} + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + VERSION: ${{ steps.version.outputs.VERSION }} + TAG: cli-${{ steps.version.outputs.VERSION }} + COPY_UNITY_SDK: 'false' + COPY_CLI: 'true' + COPY_WEB_SDK: 'false' + run: | + bash ./build/bin/release-notes.sh + gh release create "$TAG" --verify-tag \ --title "CLI ${{ steps.version.outputs.VERSION }}" \ - --notes "CLI Release ${{ steps.version.outputs.VERSION }}" \ - ${{ inputs.releaseType != 'production' && '--prerelease' || '' }} + --notes-file "$RUNNER_TEMP/release-notes.md" diff --git a/.github/workflows/release-unity.yml b/.github/workflows/release-unity.yml index 71c06dac8b..ce1e564cc6 100644 --- a/.github/workflows/release-unity.yml +++ b/.github/workflows/release-unity.yml @@ -365,20 +365,46 @@ jobs: "production": "production" }')[github.event.inputs.releaseType] }} - - name: Create Release + # Mints the release tag. Created via the API rather than git tag/push so the tag + # lands on exactly ${{ github.sha }} and stays lightweight, matching every prior + # tag - release-unity.yml checks out inputs.commit, which need not be github.sha. + # Every downstream consumer (LightBeam aliases, the docs version table) keys off + # this tag, not off the GitHub Release created below. + - name: Create release tag if: ${{ inputs.dryRun == false }} env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - gh release create "unity-sdk-${{ steps.version.outputs.VERSION }}" \ - --target "${{ github.sha }}" \ + gh api "repos/${{ github.repository }}/git/refs" \ + -f ref="refs/tags/unity-sdk-${{ steps.version.outputs.VERSION }}" \ + -f sha="${{ github.sha }}" + + # A GitHub Release is created for production only. Nightly and RC tags stay bare: + # a Release per preview build is what made the previous 999 of them noise. The + # notes are a machine-generated verbatim slice of this version's CHANGELOG.md + # section, for the lanes this workflow publishes - the same COPY_* lanes declared + # for Publish Changelog above. Warns rather than fails on a missing section, so a + # changelog nobody updated cannot red-X a release whose packages already shipped. + - name: Create GitHub Release + if: ${{ inputs.dryRun == false && inputs.releaseType == 'production' }} + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + VERSION: ${{ steps.version.outputs.VERSION }} + TAG: unity-sdk-${{ steps.version.outputs.VERSION }} + COPY_UNITY_SDK: 'true' + COPY_CLI: 'false' + COPY_WEB_SDK: 'false' + run: | + bash ./build/bin/release-notes.sh + gh release create "$TAG" --verify-tag \ --title "Unity SDK ${{ steps.version.outputs.VERSION }}" \ - --notes "Unity SDK Release ${{ steps.version.outputs.VERSION }}" \ - ${{ inputs.releaseType != 'production' && '--prerelease' || '' }} + --notes-file "$RUNNER_TEMP/release-notes.md" - # Tell beamable/docs to regenerate the SDK/CLI version table. Dispatched from - # here rather than from a tag-push trigger: the tag above is created with - # GITHUB_TOKEN, and events raised by GITHUB_TOKEN never start a workflow run. + # Tell beamable/docs to regenerate the SDK/CLI version table. This is the + # only path that does so: the tag above is created with GITHUB_TOKEN, and + # events raised by GITHUB_TOKEN never start a workflow run, so an + # on-push-tags trigger cannot work here. Cross-repo dispatch needs a real + # PAT; GITHUB_TOKEN is scoped to this repository and cannot reach docs. # --fail-with-body so an expired DOCS_DISPATCH_PAT fails the step loudly. - name: Notify docs of SDK release if: ${{ inputs.dryRun == false && inputs.releaseType == 'production' }} diff --git a/.github/workflows/release-web.yml b/.github/workflows/release-web.yml index b5483735c5..ba873aefc7 100644 --- a/.github/workflows/release-web.yml +++ b/.github/workflows/release-web.yml @@ -105,16 +105,40 @@ jobs: if: ${{ inputs.dryRun == false }} run: npm publish --no-git-checks --provenance --access public --tag ${{ steps.version.outputs.NPM_TAG }} - - name: Create Release + # Mints the release tag. Created via the API rather than git tag/push so the tag + # lands on exactly ${{ github.sha }} and stays lightweight, matching every prior + # tag - release-unity.yml checks out inputs.commit, which need not be github.sha. + # Every downstream consumer (LightBeam aliases, the docs version table) keys off + # this tag, not off the GitHub Release created below. + - name: Create release tag if: ${{ inputs.dryRun == false }} env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - gh release create "web-sdk-${{ steps.version.outputs.VERSION }}" \ - --target "${{ github.sha }}" \ + gh api "repos/${{ github.repository }}/git/refs" \ + -f ref="refs/tags/web-sdk-${{ steps.version.outputs.VERSION }}" \ + -f sha="${{ github.sha }}" + + # A GitHub Release is created for production only. Nightly and RC tags stay bare: + # a Release per preview build is what made the previous 999 of them noise. The + # notes are a machine-generated verbatim slice of this version's CHANGELOG.md + # section, for the lanes this workflow publishes - the same COPY_* lanes declared + # for Publish Changelog above. Warns rather than fails on a missing section, so a + # changelog nobody updated cannot red-X a release whose packages already shipped. + - name: Create GitHub Release + if: ${{ inputs.dryRun == false && inputs.releaseType == 'production' }} + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + VERSION: ${{ steps.version.outputs.VERSION }} + TAG: web-sdk-${{ steps.version.outputs.VERSION }} + COPY_UNITY_SDK: 'false' + COPY_CLI: 'false' + COPY_WEB_SDK: 'true' + run: | + bash ./../build/bin/release-notes.sh + gh release create "$TAG" --verify-tag \ --title "Web SDK ${{ steps.version.outputs.VERSION }}" \ - --notes "Web SDK Release ${{ steps.version.outputs.VERSION }}" \ - ${{ inputs.releaseType != 'production' && '--prerelease' || '' }} + --notes-file "$RUNNER_TEMP/release-notes.md" - name: Publish Changelog if: ${{ inputs.dryRun == false }} diff --git a/build/bin/release-notes.sh b/build/bin/release-notes.sh new file mode 100755 index 0000000000..e1db51cc16 --- /dev/null +++ b/build/bin/release-notes.sh @@ -0,0 +1,94 @@ +#!/bin/bash + +# Emit GitHub Release notes for one release: the verbatim CHANGELOG.md section for +# $VERSION, taken from each product lane this release actually touches. The lane +# flags are the same COPY_* names upload-changelogs.sh takes, so a release workflow +# declares its lanes identically for both scripts. +# +# Paths resolve from $GITHUB_WORKSPACE rather than the working directory: +# release-web.yml sets defaults.run.working-directory, so a relative path here +# would mean something different in each of the three release workflows. +# +# Optional $TAG adds a per-package link to the full changelog at that tag. + +set -eu + +: "${VERSION:?VERSION is required}" + +ROOT="${GITHUB_WORKSPACE:-$(cd "$(dirname "$0")/../.." && pwd)}" +OUT="${OUT:-${RUNNER_TEMP:-.}/release-notes.md}" +REPO="${GITHUB_REPOSITORY:-beamable/BeamableProduct}" + +# Each entry is "|", in the order it should appear. +lanes=() +if [ "${COPY_UNITY_SDK:-false}" = "true" ]; then + lanes+=("com.beamable|client/Packages/com.beamable/CHANGELOG.md") + lanes+=("com.beamable.server|client/Packages/com.beamable.server/CHANGELOG.md") +fi +if [ "${COPY_CLI:-false}" = "true" ]; then + lanes+=("Beamable.Tools|cli/cli/CHANGELOG.md") + lanes+=("Beamable.Server|microservice/microservice/CHANGELOG.md") +fi +if [ "${COPY_WEB_SDK:-false}" = "true" ]; then + lanes+=("Beamable Web SDK|web/CHANGELOG.md") +fi + +if [ ${#lanes[@]} -eq 0 ]; then + echo "No lane selected; set COPY_UNITY_SDK, COPY_CLI, or COPY_WEB_SDK to 'true'." >&2 + exit 1 +fi + +# Print the body of the "## [$1] - " section of changelog $2, stopping at the +# next "## " heading, with leading and trailing blank lines dropped. Matching the +# bracketed version at exactly column 4 keeps [6.1.0] from matching [16.1.0]. +slice_section() { + awk -v want="[$1]" ' + /^## / { if (inside) exit; inside = (index($0, want) == 4); next } + !inside { next } + /^[[:space:]]*$/ { if (started) blanks++; next } + { while (blanks-- > 0) print ""; blanks = 0; started = 1; print } + ' "$2" +} + +: > "$OUT" +found_any=false + +for lane in "${lanes[@]}"; do + name="${lane%%|*}" + path="${lane#*|}" + + if [ ! -f "$ROOT/$path" ]; then + echo "::warning::No changelog at $path; omitting $name from the release notes." + continue + fi + + section="$(slice_section "$VERSION" "$ROOT/$path")" + + # A production release whose changelog was never updated is worth surfacing, but + # not worth failing a release that has already published its packages. + if [ -z "$section" ]; then + echo "::warning::$path has no '## [$VERSION]' section; omitting $name from the release notes." + continue + fi + + { + echo "## $name" + echo + printf '%s\n' "$section" + if [ -n "${TAG:-}" ]; then + echo + echo "[Full $name changelog](https://github.com/$REPO/blob/$TAG/$path)" + fi + echo + } >> "$OUT" + + found_any=true +done + +if [ "$found_any" != "true" ]; then + echo "::warning::No changelog section for $VERSION in any selected lane." + printf 'No changelog entries were recorded for %s.\n' "$VERSION" > "$OUT" +fi + +echo "--- $OUT ---" +cat "$OUT"