diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 50032f21..73b552ae 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1 +1,5 @@ -* @Andarist @emmatown +/.github/workflows/** @Andarist @emmatown +/action.yml @Andarist @emmatown +/scripts/bump.ts @Andarist @emmatown +/scripts/release.ts @Andarist @emmatown +/scripts/release-pr.ts @Andarist @emmatown diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 00000000..46edd6c7 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,62 @@ +name: Publish + +on: + push: + branches: + - main + +concurrency: ${{ github.workflow }}-${{ github.ref }} + +permissions: {} # each job should define its own permission explicitly + +jobs: + version: + name: Version + runs-on: ubuntu-latest + timeout-minutes: 20 + outputs: + hasChangesets: ${{ steps.changesets.outputs.hasChangesets }} + permissions: + contents: write # to create release (changesets/action) + issues: write # to post issue comments (changesets/action) + pull-requests: write # to create pull request (changesets/action) + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: ./.github/actions/ci-setup + + - name: Build + run: yarn build + + - name: Create or update release pull request + id: changesets + uses: ./ + with: + version: yarn bump + + publish: + name: Publish + if: needs.version.outputs.hasChangesets == 'false' + needs: version + runs-on: ubuntu-latest + environment: marketplace + timeout-minutes: 20 + steps: + - uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1 + id: app-token + with: + client-id: ${{ vars.APP_CLIENT_ID }} + private-key: ${{ secrets.APP_PRIVATE_KEY }} + + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + token: ${{ steps.app-token.outputs.token }} + + - uses: ./.github/actions/ci-setup + + - name: Build + run: yarn build + + - name: Publish to marketplace + uses: ./ + with: + publish: yarn release diff --git a/.github/workflows/release-pr.yml b/.github/workflows/release-pr.yml index 31d21afc..35f2f5d1 100644 --- a/.github/workflows/release-pr.yml +++ b/.github/workflows/release-pr.yml @@ -4,10 +4,16 @@ on: issue_comment: types: [created] +permissions: {} + jobs: release_check: if: github.repository == 'changesets/action' && github.event.issue.pull_request && startsWith(github.event.comment.body, '/release-pr') runs-on: ubuntu-latest + permissions: + contents: read + issues: write + pull-requests: read steps: - id: report_in_progress run: | @@ -15,6 +21,18 @@ jobs: env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - id: parse_command + run: | + if [[ "$COMMENT_BODY" =~ ^/release-pr[[:space:]]+([0-9a-fA-F]{7,40})[[:space:]]*$ ]] + then + echo "requested_sha=${BASH_REMATCH[1]}" >> "$GITHUB_OUTPUT" + else + echo "Expected '/release-pr ' where is a 7-40 character commit SHA." + exit 1 + fi + env: + COMMENT_BODY: ${{ github.event.comment.body }} + - id: check_authorization run: | if [[ $AUTHOR_ASSOCIATION == 'MEMBER' || $AUTHOR_ASSOCIATION == 'OWNER' || $AUTHOR_ASSOCIATION == 'COLLABORATOR' ]] @@ -27,22 +45,80 @@ jobs: env: AUTHOR_ASSOCIATION: ${{ github.event.comment.author_association }} + - id: resolve_requested_sha + run: | + requested_sha=$(echo "$REQUESTED_SHA" | tr '[:upper:]' '[:lower:]') + + mapfile -t matches < <( + gh api /repos/${{ github.repository }}/pulls/${{ github.event.issue.number }}/commits --paginate --jq '.[].sha' | + awk -v sha="$requested_sha" 'index(tolower($0), sha) == 1 { print $0 }' + ) + + if [[ ${#matches[@]} -eq 0 ]] + then + echo "Requested SHA $REQUESTED_SHA is not part of this pull request." + exit 1 + fi + + if [[ ${#matches[@]} -gt 1 ]] + then + echo "Requested SHA $REQUESTED_SHA is ambiguous for this pull request." + exit 1 + fi + + resolved_sha="${matches[0]}" + pr_head_sha=$(gh pr view ${{ github.event.issue.number }} --json headRefOid --jq '.headRefOid') + + if [[ "$resolved_sha" != "$pr_head_sha" ]] + then + echo "Requested SHA $resolved_sha is not the current PR head $pr_head_sha." + exit 1 + fi + + echo "resolved_sha=$resolved_sha" >> "$GITHUB_OUTPUT" + echo "pr_head_sha=$pr_head_sha" >> "$GITHUB_OUTPUT" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + REQUESTED_SHA: ${{ steps.parse_command.outputs.requested_sha }} + + - id: get_pr_head_repository + run: | + echo "head_repository=$(gh pr view ${{ github.event.issue.number }} --json headRepositoryOwner,headRepository --jq '.headRepositoryOwner.login + "/" + .headRepository.name')" >> "$GITHUB_OUTPUT" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + outputs: in_progress_reaction_id: ${{ steps.report_in_progress.outputs.in_progress_reaction_id }} + requested_sha: ${{ steps.parse_command.outputs.requested_sha }} + resolved_sha: ${{ steps.resolve_requested_sha.outputs.resolved_sha }} + pr_head_sha: ${{ steps.resolve_requested_sha.outputs.pr_head_sha }} + head_repository: ${{ steps.get_pr_head_repository.outputs.head_repository }} release: if: github.repository == 'changesets/action' timeout-minutes: 20 runs-on: ubuntu-latest needs: release_check + permissions: + contents: write + issues: write + pull-requests: write steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: ./.github/actions/ci-setup - - name: Checkout pull request - run: gh pr checkout ${{ github.event.issue.number }} + - name: Fetch validated commit from pull request head repository + run: | + git remote add pr-head https://github.com/$HEAD_REPOSITORY.git + git fetch --no-tags pr-head $RESOLVED_SHA env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + HEAD_REPOSITORY: ${{ needs.release_check.outputs.head_repository }} + RESOLVED_SHA: ${{ needs.release_check.outputs.resolved_sha }} + + - name: Checkout validated commit + run: git checkout --detach $RESOLVED_SHA + env: + RESOLVED_SHA: ${{ needs.release_check.outputs.resolved_sha }} - name: Check if Version Packages PR id: check_version_packages @@ -78,7 +154,8 @@ jobs: env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - run: gh pr comment ${{ github.event.issue.number }} --body "The [${{ github.repository }}@$(git rev-parse HEAD)](https://github.com/${{ github.repository }}/commit/$(git rev-parse HEAD)) release triggered by [this comment](${{ github.event.comment.url }}) has [succeeded](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})." + - run: | + gh pr comment ${{ github.event.issue.number }} --body "The release for \`${{ needs.release_check.outputs.resolved_sha }}\` triggered by [this comment](${{ github.event.comment.url }}) has [succeeded](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}). Published commit: [${{ github.repository }}@$(git rev-parse HEAD)](https://github.com/${{ github.repository }}/commit/$(git rev-parse HEAD))." env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -87,6 +164,9 @@ jobs: timeout-minutes: 2 runs-on: ubuntu-latest if: failure() && github.repository == 'changesets/action' && (needs.release_check.result == 'failure' || needs.release.result == 'failure') + permissions: + issues: write + pull-requests: write steps: - run: gh api /repos/${{ github.repository }}/issues/comments/${{ github.event.comment.id }}/reactions -f content='-1' env: @@ -96,7 +176,8 @@ jobs: env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - run: gh pr comment ${{ github.event.issue.number }} --body "The release triggered by [this comment](${{ github.event.comment.url }}) has [failed](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})." + - run: | + gh pr comment ${{ github.event.issue.number }} --body "The release for \`${{ needs.release_check.outputs.requested_sha }}\` triggered by [this comment](${{ github.event.comment.url }}) has [failed](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})." env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GH_REPO: ${{ github.repository }} diff --git a/.github/workflows/version-or-publish.yml b/.github/workflows/version-or-publish.yml deleted file mode 100644 index 4e7f070c..00000000 --- a/.github/workflows/version-or-publish.yml +++ /dev/null @@ -1,27 +0,0 @@ -name: Version or Publish - -on: - push: - branches: - - main - -concurrency: ${{ github.workflow }}-${{ github.ref }} - -jobs: - changesets: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: ./.github/actions/ci-setup - - - name: Build - run: yarn build - - - name: Create Release Pull Request or Publish - id: changesets - uses: ./ - with: - version: yarn bump - publish: yarn release - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}