Repository navigation
Diff: two-stage line/word engine, ignore toggles, copy as patch (#8) #8
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 | |
| # Merging to main does not deploy directly. It derives the next version from | |
| # the [Unreleased] section of CHANGELOG.md, rewrites the changelog, commits | |
| # that back to main, and pushes a tag. The TAG is what deploys. | |
| # | |
| # A merge with an empty [Unreleased] releases nothing, so merging and releasing | |
| # stay separate decisions. | |
| on: | |
| push: | |
| branches: [main] | |
| workflow_dispatch: | |
| concurrency: | |
| group: release | |
| cancel-in-progress: false | |
| permissions: | |
| contents: write | |
| actions: write | |
| jobs: | |
| release: | |
| name: Cut a release from the changelog | |
| runs-on: ubuntu-latest | |
| # Never react to our own release commit. | |
| if: "!contains(github.event.head_commit.message, '[skip ci]')" | |
| steps: | |
| - uses: actions/checkout@v7 | |
| with: | |
| # Check out the BRANCH TIP, not the commit that triggered the run. | |
| # On a re-run the triggering sha is stale, and committing on top of it | |
| # produces a non-fast-forward push that is rejected. Combined with the | |
| # changelog logic being idempotent -- an empty [Unreleased] releases | |
| # nothing -- this makes a re-run safe rather than broken. | |
| ref: main | |
| fetch-depth: 0 | |
| token: ${{ secrets.RELEASE_TOKEN || secrets.GITHUB_TOKEN }} | |
| - uses: pnpm/action-setup@v6 | |
| - uses: actions/setup-node@v7 | |
| with: | |
| node-version: 22 | |
| cache: pnpm | |
| - run: pnpm install --frozen-lockfile | |
| - id: cut | |
| name: Derive the version from CHANGELOG.md | |
| run: pnpm exec vite-node scripts/release.ts | |
| - name: Commit the changelog and tag | |
| if: steps.cut.outputs.released == 'true' | |
| env: | |
| TAG: ${{ steps.cut.outputs.tag }} | |
| run: | | |
| set -euo pipefail | |
| git config user.name "github-actions[bot]" | |
| git config user.email "41898282+github-actions[bot]@users.noreply.github.com" | |
| # Each push is guarded, so a partially-completed earlier attempt can | |
| # be resumed rather than blocking every retry. | |
| if git diff --quiet -- CHANGELOG.md package.json; then | |
| echo "::notice::Changelog and version already committed." | |
| else | |
| git add CHANGELOG.md package.json | |
| # [skip ci] so this push cannot re-enter this workflow when a PAT | |
| # is in use -- with a PAT, pushes DO trigger workflows. | |
| git commit -m "Release ${TAG} [skip ci]" | |
| git push origin HEAD:main | |
| fi | |
| if git ls-remote --exit-code --tags origin "refs/tags/${TAG}" >/dev/null 2>&1; then | |
| echo "::notice::${TAG} already exists on the remote." | |
| else | |
| git tag -a "${TAG}" -m "${TAG}" | |
| git push origin "${TAG}" | |
| fi | |
| # The notes go through env, never through ${{ }} in the script. Expression | |
| # substitution pastes the text into the script BEFORE bash parses it, so | |
| # every backtick in the changelog (`display: flex`) was run as a command | |
| # and the step died -- after the tag was pushed, so Deploy never started. | |
| - name: Create the GitHub release | |
| if: steps.cut.outputs.released == 'true' | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| TAG: ${{ steps.cut.outputs.tag }} | |
| NOTES: ${{ steps.cut.outputs.notes }} | |
| run: | | |
| gh release create "$TAG" --title "$TAG" --notes "$NOTES" \ | |
| || echo "::notice::Release already exists." | |
| # Always dispatch, unconditionally. | |
| # | |
| # This previously only ran when no PAT was configured, on the assumption | |
| # that a PAT-pushed tag would trigger Deploy natively via `on: push: | |
| # tags`. With a PAT present that assumption silently took over and no | |
| # deploy ever happened: the tag was pushed, Release reported success, and | |
| # Deploy had zero runs. Dispatching every time removes the dependency on | |
| # how GitHub attributes the push. Deploy is idempotent -- it builds and | |
| # uploads the same tag -- so a duplicate run is harmless, whereas a | |
| # missed one is invisible. | |
| - name: Start Deploy | |
| if: steps.cut.outputs.released == 'true' | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: gh workflow run deploy.yml --ref main -f tag="${{ steps.cut.outputs.tag }}" | |
| - name: Summary | |
| run: | | |
| if [ "${{ steps.cut.outputs.released }}" = "true" ]; then | |
| echo "Released ${{ steps.cut.outputs.tag }}" >> "$GITHUB_STEP_SUMMARY" | |
| else | |
| echo "No [Unreleased] entries — nothing released." >> "$GITHUB_STEP_SUMMARY" | |
| fi |