docs: surface docs/site links + add command-reference link in README … #23
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Release | |
| on: | |
| push: | |
| tags: ['v*'] | |
| # Release-PR-merge entry point: a release-labeled PR merging to main is | |
| # detected by the `tag-on-release-merge` job. That job creates+pushes a v* | |
| # tag (the durable version anchor the Homebrew tap and history depend on) | |
| # AND exposes an `is_release` output so the `release` job runs in THIS SAME | |
| # workflow run. We do NOT rely on the tag push re-triggering `release`: | |
| # GitHub deliberately suppresses workflow runs from pushes authenticated | |
| # with the default GITHUB_TOKEN (documented loop-prevention), so a | |
| # GITHUB_TOKEN-authored tag push would never fire a second `release` run. | |
| # Running via job dependency (needs/outputs) is what makes the release-merge | |
| # path actually execute the pipeline. | |
| branches: [main] | |
| workflow_dispatch: | |
| inputs: | |
| bump: | |
| description: 'Version bump' | |
| type: choice | |
| options: | |
| - patch | |
| - minor | |
| - major | |
| default: patch | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| concurrency: | |
| group: release | |
| cancel-in-progress: false | |
| jobs: | |
| # ── Entry point: tag on release-PR merge ───────────────────────────── | |
| # Runs only on a push to main. Detects whether the merge corresponds to a | |
| # release-labeled PR; if so, tags the (already-bumped) package.json version | |
| # and pushes the tag, then exposes is_release/tag/version as outputs so the | |
| # `release` job below runs in the same workflow run. A non-release push to | |
| # main resolves is_release=false and the `release` job no-ops (skips its | |
| # heavy steps). On a tag-push or workflow_dispatch event this job's `if` is | |
| # false, so it is SKIPPED — the `release` job tolerates that via always(). | |
| tag-on-release-merge: | |
| if: github.event_name == 'push' && github.ref == 'refs/heads/main' | |
| runs-on: ubuntu-latest | |
| outputs: | |
| is_release: ${{ steps.detect.outputs.is_release }} | |
| tag: ${{ steps.detect.outputs.tag }} | |
| version: ${{ steps.detect.outputs.version }} | |
| steps: | |
| - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | |
| with: | |
| fetch-depth: 0 | |
| - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 | |
| with: | |
| node-version: 20 | |
| # Detect a release: the PR associated with this merge commit must carry | |
| # the `release` label. (Decoupled from commit-message parsing — explicit | |
| # and low-false-positive. The release PR is expected to have bumped | |
| # package.json already, so we tag that version rather than bumping again.) | |
| # Emits is_release plus the resolved tag/version so the `release` job can | |
| # run off these outputs without re-parsing github.ref (which is `main`). | |
| - name: Detect release PR | |
| id: detect | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| # Resolve the PR(s) for this merge commit via the commit→PR association | |
| # API, which is the reliable source — `gh pr list --search <sha>` relies | |
| # on the search index, which does not dependably index commits by SHA | |
| # (and lags), so it can spuriously yield is_release=false. | |
| is_release=$(gh api "repos/${{ github.repository }}/commits/${{ github.sha }}/pulls" \ | |
| --jq 'any(.[]; any(.labels[]; .name == "release"))' || echo "false") | |
| version=$(node -p 'require("./package.json").version') | |
| echo "is_release=$is_release" >> "$GITHUB_OUTPUT" | |
| echo "version=$version" >> "$GITHUB_OUTPUT" | |
| echo "tag=v$version" >> "$GITHUB_OUTPUT" | |
| - name: Create and push tag | |
| if: steps.detect.outputs.is_release == 'true' | |
| run: | | |
| tag="${{ steps.detect.outputs.tag }}" | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| # If the tag already exists, do nothing (idempotent re-runs). The tag | |
| # is the durable version anchor; the release pipeline still runs in | |
| # this workflow run via the `release` job's needs/outputs (NOT via a | |
| # re-trigger from this GITHUB_TOKEN-authored push, which is suppressed). | |
| if git rev-parse "$tag" >/dev/null 2>&1; then | |
| echo "Tag $tag already exists — skipping push." | |
| else | |
| git tag "$tag" | |
| git push origin "$tag" | |
| fi | |
| # ── Release pipeline (tag-push + manual dispatch + release-merge) ───── | |
| # Runs in three cases, each exactly once: | |
| # 1. a real v* tag push → startsWith(github.ref, 'refs/tags/v') | |
| # 2. workflow_dispatch → manual bump-and-tag in-place | |
| # 3. a release-merge to main → tag-on-release-merge resolved is_release=true | |
| # `needs: tag-on-release-merge` lets us read that job's outputs, but a needed | |
| # job that is SKIPPED would normally skip this job too — so we gate with | |
| # always() and tolerate `needs.tag-on-release-merge.result == 'skipped'` | |
| # (which is exactly what happens on the tag-push and dispatch paths). The | |
| # explicit per-case conditions ensure a non-release push to main, or a failed/ | |
| # cancelled upstream, does NOT run the heavy pipeline. | |
| release: | |
| needs: tag-on-release-merge | |
| if: | | |
| always() && ( | |
| startsWith(github.ref, 'refs/tags/v') || | |
| github.event_name == 'workflow_dispatch' || | |
| (needs.tag-on-release-merge.result == 'success' && | |
| needs.tag-on-release-merge.outputs.is_release == 'true') | |
| ) | |
| runs-on: ubuntu-latest | |
| steps: | |
| # Check out the right ref for each path: | |
| # - tag push → the tag (github.ref) — the canonical version anchor | |
| # - dispatch → main (release.sh bumps + tags from here) | |
| # - merge → the just-created tag from tag-on-release-merge, which | |
| # points at the merge commit carrying the bumped package.json | |
| - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | |
| with: | |
| fetch-depth: 0 | |
| ref: ${{ github.event_name == 'workflow_dispatch' && 'main' || (startsWith(github.ref, 'refs/tags/v') && github.ref || needs.tag-on-release-merge.outputs.tag) }} | |
| - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 | |
| with: | |
| node-version: 20 | |
| # On manual dispatch, run scripts/release.sh — it bumps package.json | |
| # via `npm version`, commits, tags, and pushes both the commit and tag. | |
| - name: Create tag (manual dispatch) | |
| if: github.event_name == 'workflow_dispatch' | |
| id: create_tag | |
| run: | | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| ./scripts/release.sh "${{ inputs.bump }}" | |
| echo "tag=$(git tag --sort=-v:refname | head -1)" >> "$GITHUB_OUTPUT" | |
| # Resolve the release tag/version for all three event shapes: | |
| # - tag push → github.ref_name (e.g. v0.4.15) | |
| # - dispatch → the tag scripts/release.sh just created | |
| # - merge → tag-on-release-merge's output (falls back to package.json) | |
| - name: Resolve release tag | |
| id: version | |
| run: | | |
| if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then | |
| tag="${{ steps.create_tag.outputs.tag }}" | |
| elif [ "${{ startsWith(github.ref, 'refs/tags/v') }}" = "true" ]; then | |
| tag="${{ github.ref_name }}" | |
| else | |
| tag="${{ needs.tag-on-release-merge.outputs.tag }}" | |
| [ -n "$tag" ] || tag="v$(node -p 'require("./package.json").version')" | |
| fi | |
| echo "tag=$tag" >> "$GITHUB_OUTPUT" | |
| echo "version=${tag#v}" >> "$GITHUB_OUTPUT" | |
| - uses: extractions/setup-just@dd310ad5a97d8e7b41793f8ef055398d51ad4de6 # v2 | |
| # ── Fail-loud steps FIRST (before any external side effect) ────────── | |
| # Install and build up front. Running these before the GitHub Release and | |
| # Homebrew tap update means a build failure aborts the run BEFORE those | |
| # external side effects are applied — avoiding a partially-applied release | |
| # (release/tap published but the bundle failed to build). | |
| - name: Install dependencies | |
| run: npm ci | |
| - name: Build bundle | |
| run: npm run build | |
| - name: Generate release notes | |
| run: just release-notes "${{ steps.version.outputs.tag }}" | |
| - name: Create GitHub Release | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| gh release create "${{ steps.version.outputs.tag }}" \ | |
| --title "tu ${{ steps.version.outputs.tag }}" \ | |
| --notes-file dist/release-notes.md | |
| - name: Update Homebrew tap | |
| env: | |
| TAP_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }} | |
| run: | | |
| version="${{ steps.version.outputs.version }}" | |
| git clone "https://x-access-token:${TAP_TOKEN}@github.com/sahil87/homebrew-tap.git" /tmp/tap | |
| sed -i "s|tag: \"v.*\"|tag: \"v${version}\"|" /tmp/tap/Formula/tu.rb | |
| cd /tmp/tap | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| git add Formula/tu.rb | |
| git commit -m "tu ${version}" | |
| git push |