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
34 changes: 34 additions & 0 deletions .github/actions/release-summary/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: Release summary
description: Writes what approving this run's gated job would ship to the job summary.
inputs:
gated-job:
description: Name of the environment-gated job as it appears on the run page.
required: true
version:
description: Version or image tag the gated job publishes.
required: true
paths:
description: Space-separated pathspecs that limit the listed changes to one package.
required: false
default: ""
published-step:
description: Step of the gated job that only succeeds when something was published. Earlier runs where it was skipped do not count as releases.
required: false
default: ""
publishes:
description: Set to false when the gated job will find nothing new to publish.
required: false
default: ""
runs:
using: composite
steps:
- name: Write the release summary
shell: bash
env:
GH_TOKEN: ${{ github.token }}
GATED_JOB: ${{ inputs.gated-job }}
VERSION: ${{ inputs.version }}
PATHS: ${{ inputs.paths }}
PUBLISHED_STEP: ${{ inputs.published-step }}
PUBLISHES: ${{ inputs.publishes }}
run: bash "$GITHUB_ACTION_PATH/summary.sh"
115 changes: 115 additions & 0 deletions .github/actions/release-summary/summary.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
#!/usr/bin/env bash
# Needs a checkout with full history and a token with contents, actions and
# pull-requests read. Never fails the job: a release must not wait on this.
set -uo pipefail

repo="$GITHUB_REPOSITORY"
sha="$GITHUB_SHA"
web="$GITHUB_SERVER_URL/$repo"
workflow_file="${GITHUB_WORKFLOW_REF%@*}"
workflow_file="${workflow_file##*/}"
out="${GITHUB_STEP_SUMMARY:-/dev/stdout}"
max_commits=100
PATHS="${PATHS:-}"
PUBLISHED_STEP="${PUBLISHED_STEP:-}"
PUBLISHES="${PUBLISHES:-}"

cell() { printf '%s' "$1" | tr '\n' ' ' | sed 's/|/\\|/g'; }

prev=""
prev_run=""
while read -r id run_sha; do
[ -n "$id" ] || continue
git merge-base --is-ancestor "$run_sha" "$sha" 2>/dev/null || continue
approved=$(gh api "repos/$repo/actions/runs/$id/jobs?filter=latest&per_page=100" |
jq --arg n "$GATED_JOB" --arg s "$PUBLISHED_STEP" '[.jobs[]
| select((.name == $n or (.name | startswith($n + " ("))) and .conclusion == "success")
| select($s == "" or any(.steps[]?; .name == $s and .conclusion == "success"))] | length')
if [ "${approved:-0}" -gt 0 ]; then
prev="$run_sha"
prev_run="$id"
break
fi
done < <(gh api "repos/$repo/actions/workflows/$workflow_file/runs?branch=$GITHUB_REF_NAME&status=completed&per_page=100" |
jq -r --argjson n "$GITHUB_RUN_NUMBER" '.workflow_runs[] | select(.run_number < $n) | "\(.id) \(.head_sha)"')

subject=$(git log -1 --format=%s "$sha" 2>/dev/null)

{
echo "## Release summary"
echo
if [ "$PUBLISHES" = false ]; then
echo "Nothing new to publish: \`$(cell "$VERSION")\` is already released. Approving **$GATED_JOB** runs it without publishing."
else
echo "Approving **$GATED_JOB** publishes the version below from this commit."
fi
echo
echo "| | |"
echo "|---|---|"
echo "| Version | \`$(cell "$VERSION")\` |"
echo "| Commit | [\`${sha:0:10}\`]($web/commit/$sha) $(cell "$subject") |"
if [ -n "$prev" ]; then
tags=""
for t in $(git tag --points-at "$prev"); do
in_scope=${PATHS:+no}
for p in $PATHS; do
case "$t" in "${p%/}"/*) in_scope="" ;; esac
done
[ -z "$in_scope" ] && tags="${tags:+$tags, }$t"
done
echo "| Previously released | [\`${prev:0:10}\`]($web/commit/$prev)${tags:+ (tag $tags)}, released in [run $prev_run]($web/actions/runs/$prev_run) |"
echo "| Compare | [${prev:0:10}...${sha:0:10}]($web/compare/$prev...$sha) |"
else
echo "| Previously released | none found in the last 100 completed runs of $workflow_file on $GITHUB_REF_NAME |"
fi
echo
} >> "$out"

[ -n "$prev" ] || exit 0

# shellcheck disable=SC2086
mapfile -t commits < <(git log --format=%H "$prev..$sha" -- $PATHS)
scope=""
[ -n "$PATHS" ] && scope=" touching \`$PATHS\`"

declare -A seen=()
pr_rows=()
direct=()
for c in "${commits[@]:0:$max_commits}"; do
prs=$(gh api "repos/$repo/commits/$c/pulls" |
jq -r '.[] | select(.merged_at != null) | [.number, .user.login, .title] | @tsv')
if [ -z "$prs" ]; then
direct+=("- [\`${c:0:10}\`]($web/commit/$c) $(git log -1 --format='%s (%an)' "$c")")
continue
fi
while IFS=$'\t' read -r number author title; do
[ -n "${seen[$number]:-}" ] && continue
seen[$number]=1
approvers=$(gh api "repos/$repo/pulls/$number/reviews?per_page=100" |
jq -r '[.[] | select(.state == "APPROVED") | .user.login] | unique | join(", ")')
pr_rows+=("| [#$number]($web/pull/$number) | $(cell "$title") | $author | ${approvers:-**none**} |")
done <<< "$prs"
done

{
echo "### Merged pull requests$scope (${#pr_rows[@]})"
echo
if [ "${#pr_rows[@]}" -gt 0 ]; then
echo "| PR | Title | Author | Approved by |"
echo "|---|---|---|---|"
printf '%s\n' "${pr_rows[@]}"
else
echo "None."
fi
echo
if [ "${#direct[@]}" -gt 0 ]; then
echo "### Commits without a pull request (${#direct[@]})"
echo
printf '%s\n' "${direct[@]}"
echo
fi
if [ "${#commits[@]}" -gt "$max_commits" ]; then
echo "Only the newest $max_commits of ${#commits[@]} commits are listed. The compare link has the rest."
fi
} >> "$out"
exit 0
10 changes: 10 additions & 0 deletions .github/actions/supersede-waiting-runs/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
name: Supersede unapproved release runs
description: Cancels earlier runs of this workflow on the same branch that are still waiting for environment approval and whose commit this run includes.
runs:
using: composite
steps:
- name: Cancel superseded runs waiting for approval
shell: bash
env:
GH_TOKEN: ${{ github.token }}
run: bash "$GITHUB_ACTION_PATH/supersede.sh"
29 changes: 29 additions & 0 deletions .github/actions/supersede-waiting-runs/supersede.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#!/usr/bin/env bash
# Needs actions write and contents read. Only a run whose status is "waiting"
# (held at an environment approval) is cancelled; a job that has started is never touched.
set -uo pipefail

repo="$GITHUB_REPOSITORY"
web="$GITHUB_SERVER_URL/$repo"
workflow_file="${GITHUB_WORKFLOW_REF%@*}"
workflow_file="${workflow_file##*/}"
out="${GITHUB_STEP_SUMMARY:-/dev/stdout}"

while read -r id sha; do
[ -n "$id" ] || continue
relation=$(gh api "repos/$repo/compare/$sha...$GITHUB_SHA" --jq .status)
case "$relation" in
ahead | identical) ;;
*) echo "Run $id on $sha is not included in $GITHUB_SHA ($relation); left waiting"; continue ;;
esac
state=$(gh api "repos/$repo/actions/runs/$id" --jq .status)
if [ "$state" != waiting ]; then
echo "Run $id is $state now; left alone"
continue
fi
if gh run cancel "$id" -R "$repo"; then
echo "- Cancelled [run $id]($web/actions/runs/$id), which was waiting for approval on \`${sha:0:10}\`. This run includes that commit." >> "$out"
fi
done < <(gh api "repos/$repo/actions/workflows/$workflow_file/runs?branch=$GITHUB_REF_NAME&status=waiting&per_page=100" |
jq -r --argjson n "$GITHUB_RUN_NUMBER" '.workflow_runs[] | select(.run_number < $n) | "\(.id) \(.head_sha)"')
exit 0
48 changes: 47 additions & 1 deletion .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ jobs:
permissions:
contents: read
outputs:
version: ${{ steps.check-version.outputs.version }}
tag: ${{ steps.check-version.outputs.tag }}
exists: ${{ steps.check-version.outputs.exists }}
tarball: ${{ steps.pack.outputs.tarball }}
Expand Down Expand Up @@ -46,6 +47,7 @@ jobs:
run: |
VERSION=$(node -p "require('./package.json').version")
PACKAGE_NAME=$(node -p "require('./package.json').name")
echo "version=$VERSION" >> $GITHUB_OUTPUT

if [[ "$VERSION" == *"-alexandria"* ]]; then
echo "tag=alexandria" >> $GITHUB_OUTPUT
Expand Down Expand Up @@ -83,11 +85,55 @@ jobs:
if-no-files-found: error
retention-days: 7

publish:
supersede:
name: Supersede unapproved runs
needs: build
if: needs.build.outputs.exists == 'false' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
permissions:
actions: write
contents: read
steps:
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
continue-on-error: true
with:
sparse-checkout: .github/actions
persist-credentials: false
- uses: ./.github/actions/supersede-waiting-runs
continue-on-error: true
timeout-minutes: 2

release-summary:
name: Release summary
needs: build
if: needs.build.outputs.exists == 'false' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
permissions:
contents: read
actions: read
pull-requests: read
steps:
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
continue-on-error: true
with:
fetch-depth: 0
filter: blob:none
persist-credentials: false
- uses: ./.github/actions/release-summary
continue-on-error: true
timeout-minutes: 3
with:
gated-job: publish
version: firecrawl-cli@${{ needs.build.outputs.version }} (npm tag ${{ needs.build.outputs.tag }})

publish:
needs: [build, release-summary]
if: needs.build.outputs.exists == 'false' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
environment: release
concurrency:
group: ${{ github.workflow }}-release-${{ github.ref }}
cancel-in-progress: false
permissions:
id-token: write
steps:
Expand Down
46 changes: 45 additions & 1 deletion .github/workflows/release-binaries.yml
Original file line number Diff line number Diff line change
Expand Up @@ -200,12 +200,56 @@ jobs:
if-no-files-found: error
retention-days: 7

supersede:
name: Supersede unapproved runs
needs: check-version
if: needs.check-version.outputs.released == 'false' && needs.check-version.outputs.dry_run != 'true' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
permissions:
actions: write
contents: read
steps:
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
continue-on-error: true
with:
sparse-checkout: .github/actions
persist-credentials: false
- uses: ./.github/actions/supersede-waiting-runs
continue-on-error: true
timeout-minutes: 2

release-summary:
name: Release summary
needs: check-version
if: needs.check-version.outputs.released == 'false' && needs.check-version.outputs.dry_run != 'true' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
permissions:
contents: read
actions: read
pull-requests: read
steps:
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
continue-on-error: true
with:
fetch-depth: 0
filter: blob:none
persist-credentials: false
- uses: ./.github/actions/release-summary
continue-on-error: true
timeout-minutes: 3
with:
gated-job: Create release and upload assets
version: v${{ needs.check-version.outputs.version }}

release:
needs: [check-version, package]
needs: [check-version, package, release-summary]
if: needs.check-version.outputs.dry_run != 'true' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
name: Create release and upload assets
environment: release
concurrency:
group: ${{ github.workflow }}-release-${{ github.ref }}
cancel-in-progress: false
permissions:
contents: write
id-token: write
Expand Down
Loading