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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .copilot/skills/release-process/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,8 @@ gh run watch
The release workflow rejects stable versions on manual dispatch, creates a
GitHub prerelease, publishes npm `preview`, and uploads standalone bundles.
It also updates the `squad-preview` Homebrew cask and
`bradygaster.Squad.Preview` WinGet package. The activation pin and insider-tag
promotion remain stable-only.
`bradygaster.Squad.Preview` WinGet package. Activation installs the latest stable
GitHub Release; insider-tag promotion also remains stable-only.

## Insider

Expand Down
163 changes: 0 additions & 163 deletions .github/workflows/squad-cli-pin-drift.yml

This file was deleted.

112 changes: 0 additions & 112 deletions .github/workflows/squad-npm-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -367,8 +367,6 @@ jobs:
contents: read
id-token: write
outputs:
# Consumed by `bump-activation-pin` so the pin moves to the version this run
# actually published, rather than to whatever npm happens to report later.
dist_tag: ${{ steps.version.outputs.dist_tag }}
stable_release: ${{ steps.version.outputs.stable_release }}
version: ${{ steps.version.outputs.version }}
Expand Down Expand Up @@ -540,116 +538,6 @@ jobs:
echo "::error::Could not verify @bradygaster/squad-cli@${PACKAGE_VERSION} on npm dist-tag '${NPM_DIST_TAG}' after 5 attempts."
exit 1

# --- Issue #1825 ------------------------------------------------------
# Release-time half of the activation-pin guard.
#
# The literal in `workflows/shared/squad.md` decides which Squad CLI a brand-new
# repository installs during activation. Nothing about publishing used to touch it,
# so every release silently made it stale — it once ran 8 days behind and was caught
# only because someone happened to cold-start a throwaway repo (PR #1818).
#
# `.github/workflows/squad-cli-pin-drift.yml` is the daily backstop that *detects*
# that state. This job is the half that prevents it, by moving the pin in the same
# run that made the new version installable. Both halves are load-bearing: detection
# alone leaves a red build with no fix path, and prevention alone re-breaks the
# moment someone publishes out-of-band.
bump-activation-pin:
name: Bump activation pin to the published release
needs: publish-cli
if: needs.publish-cli.outputs.stable_release == 'true'
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: write
pull-requests: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 #v7
with:
# The pin lives on the default branch, but releases are cut from `main`.
# Bumping a `main` checkout would edit a file `dev` never sees, and the next
# merge would quietly restore the stale value.
ref: dev
fetch-depth: 0

- name: Confirm the version is published and current
id: target
env:
TARGET: ${{ needs.publish-cli.outputs.version }}
NPM_PACKAGE: '@bradygaster/squad-cli'
run: |
set -euo pipefail

if [ -z "$TARGET" ]; then
echo "::error::publish-cli reported no version — refusing to guess which version to pin."
exit 1
fi

# Two independent sources, which is the whole point of #1825: the version this
# run published, and what npm now serves as `latest`. Checking only the repo
# would compare the pin against itself, and the repo's own manifest holds the
# next *unreleased* version — the E404 that PR #1818 had to fix by hand.
latest=""
for attempt in 1 2 3 4 5; do
latest="$(npm view "$NPM_PACKAGE" dist-tags.latest 2>/dev/null || true)"
if [ "$latest" = "$TARGET" ]; then
break
fi
echo "dist-tags.latest is '${latest:-<unset>}', waiting for '$TARGET' (attempt $attempt/5)..."
sleep 15
done

if [ "$latest" != "$TARGET" ]; then
echo "::error::npm dist-tags.latest is '${latest:-<unset>}', not the published '$TARGET'. Not moving the pin to a version new repositories would not receive."
exit 1
fi

echo "version=$TARGET" >> "$GITHUB_OUTPUT"
echo "Confirmed $NPM_PACKAGE@$TARGET is the published latest."

- name: Rewrite the pin
id: bump
env:
TARGET_VERSION: ${{ steps.target.outputs.version }}
PR_BODY_FILE: ${{ runner.temp }}/activation-pin-pr.md
run: node scripts/bump-activation-pin.mjs

- name: Open pull request
if: steps.bump.outputs.changed == 'true'
env:
GH_TOKEN: ${{ github.token }}
TARGET: ${{ steps.target.outputs.version }}
PR_BODY_FILE: ${{ runner.temp }}/activation-pin-pr.md
run: |
set -euo pipefail

# A pull request rather than a push: `dev` does not take direct pushes, and a
# version bump that lands unreviewed is exactly the kind of change that should
# be visible in history.
branch="chore/activation-pin-$TARGET"

if git ls-remote --exit-code --heads origin "$branch" >/dev/null 2>&1; then
echo "Branch $branch already exists — an earlier run already opened this bump."
exit 0
fi

git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git checkout -b "$branch"

# Named explicitly: the checkout is a full release tree, and `git add -A` here
# would sweep in anything the publish jobs left behind.
git add workflows/shared/squad.md docs/src/content/docs/guide/gh-aw.md
git commit -m "chore: pin Squad CLI activation to $TARGET

Refs #1825"
git push origin "$branch"

gh pr create \
--base dev \
--head "$branch" \
--title "chore: pin Squad CLI activation to $TARGET" \
--body-file "$PR_BODY_FILE"

# --- Issues #1491 / #1497 ---------------------------------------------
# After a stable release publishes to `latest`, ensure each package's
# `insider` dist-tag is not left semver-lower than `latest`. Promotions run
Expand Down
52 changes: 51 additions & 1 deletion .github/workflows/squad-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ jobs:
gh release create "${{ steps.version.outputs.tag }}" \
--title "${{ steps.version.outputs.tag }}" \
--generate-notes \
--latest
--latest=false
else
gh release create "${{ steps.version.outputs.tag }}" \
--title "${{ steps.version.outputs.tag }}" \
Expand Down Expand Up @@ -226,3 +226,53 @@ jobs:
upload: true
release_tag: ${{ needs.release.outputs.tag }}
source_ref: ${{ needs.release.outputs.tag }}

promote-latest:
name: Promote complete stable release to latest
needs: [release, standalone]
if: needs.release.outputs.created == 'true' && needs.release.outputs.stable_release == 'true'
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Verify standalone assets and promote latest
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: ${{ needs.release.outputs.tag }}
run: |
set -euo pipefail
required_assets=(
squad-linux-x64.tar.gz
squad-linux-arm64.tar.gz
squad-darwin-x64.tar.gz
squad-darwin-arm64.tar.gz
squad-win32-x64.zip
squad-win32-arm64.zip
SHA256SUMS.txt
)
mapfile -t published_assets < <(
gh release view "${TAG}" \
--repo "${GITHUB_REPOSITORY}" \
--json assets \
--jq '.assets[].name'
)

missing_assets=()
for asset in "${required_assets[@]}"; do
if ! printf '%s\n' "${published_assets[@]}" | grep -Fxq "${asset}"; then
missing_assets+=("${asset}")
fi
done
if [ "${#missing_assets[@]}" -gt 0 ]; then
printf '::error::Release %s is incomplete; refusing latest promotion. Missing: %s\n' \
"${TAG}" "${missing_assets[*]}"
exit 1
fi

gh release edit "${TAG}" --latest --repo "${GITHUB_REPOSITORY}"
latest_tag="$(gh api "repos/${GITHUB_REPOSITORY}/releases/latest" --jq '.tag_name')"
if [ "${latest_tag}" != "${TAG}" ]; then
echo "::error::Latest release is ${latest_tag}; expected ${TAG}."
exit 1
fi
echo "✅ Complete stable release ${TAG} promoted to latest."
Loading
Loading