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
2 changes: 2 additions & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/.github/ @ZenRows/prod-approvers
/release-please-config.json @ZenRows/prod-approvers
7 changes: 7 additions & 0 deletions .github/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# GitHub's generated notes: "What's Changed", one line per PR. The release PR
# itself is left out.
changelog:
exclude:
labels:
- "autorelease: pending"
- "autorelease: tagged"
73 changes: 73 additions & 0 deletions .github/workflows/commit-hygiene.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
name: Commit hygiene

on:
pull_request:
types: [opened, edited, synchronize, reopened]

permissions:
contents: read
pull-requests: read

jobs:
check:
name: Title, body, branch and commits
if: >-
github.event.pull_request.user.login != 'dependabot[bot]' &&
github.event.pull_request.user.login != 'github-actions[bot]'
runs-on: ubuntu-latest
steps:
# Untrusted text reaches the shell only through environment variables.
- name: Check the PR title, body, branch name and commit messages
env:
GH_TOKEN: ${{ github.token }}
PR_NUMBER: ${{ github.event.pull_request.number }}
PR_TITLE: ${{ github.event.pull_request.title }}
PR_BODY: ${{ github.event.pull_request.body }}
PR_BRANCH: ${{ github.head_ref }}
run: |
set -euo pipefail
trailer='(?i)^(Co-authored-by|Signed-off-by|Claude-Session|Generated-by|Assisted-by|Reviewed-by):'
# Standard identifiers such as CVE-2024-1234, SHA-256, UTF-8, ISO-8601, PEP-8, RFC-9110 and HTTP-2 are not tracker keys.
tracker='\b(?!(?:CVE|GHSA|SHA|UTF|ISO|PEP|RFC|HTTP)-)[A-Z]{2,10}-[0-9]+\b'
# Attribution phrases only: product names such as Claude or OpenAI stay allowed.
attribution='(?i)(\bgenerated (with|by) (claude|chatgpt|copilot|openai|gemini|an? ai)\b|\bai-generated\b|noreply@anthropic\.com)'
conventional='^((fix|feat|perf)\([a-z][a-z0-9-]*\)|(build|chore|ci|docs|refactor|revert|style|test)(\([a-z][a-z0-9-]*\))?)!?: \S'
failed=0

# Every printed line starts with a label, so text from the PR cannot start a workflow command.
check() { # $1: where, $2: pattern, $3: rule; the text is on stdin
local hits
if hits=$(grep -nP -- "$2" | sed 's/^/ line /'); then
echo "$1 has $3:"
echo "$hits"
failed=1
fi
}
scan() { # $1: where; the text is on stdin
local text
text=$(cat)
check "$1" "$trailer" "a trailer line" <<<"$text"
check "$1" "$tracker" "an issue tracker key" <<<"$text"
check "$1" "$attribution" "an AI attribution" <<<"$text"
}

if ! grep -qP -- "$conventional" <<<"$PR_TITLE"; then
echo "PR title is not a Conventional Commit: type(scope)!: subject, with type one of build, chore, ci, docs, feat, fix, perf, refactor, revert, style, test. fix, feat and perf need a lowercase scope, for example fix(crawl): subject."
failed=1
fi
scan "PR title" <<<"$PR_TITLE"
scan "PR body" <<<"$PR_BODY"
scan "Branch name" <<<"$PR_BRANCH"
# Read the commits first, so a failed API call fails the check instead of scanning nothing.
commits=$(gh api --paginate "repos/$GITHUB_REPOSITORY/pulls/$PR_NUMBER/commits" \
--jq '.[] | "\(.sha[0:12]) \(.commit.message | @base64)"')
[ -n "$commits" ] || { echo "::error::The PR has no commits to check."; exit 1; }
while read -r sha message; do
scan "Commit $sha" < <(base64 -d <<<"$message")
done <<<"$commits"

if [ "$failed" -ne 0 ]; then
echo "::error::Fix the PR title, body, branch name or commit messages listed above."
exit 1
fi
echo "The PR title, body, branch name and commit messages are clean."
144 changes: 144 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
name: Release

on:
push:
branches: [main]

permissions: {}

jobs:
release-pr:
name: Open or update the release PR
# After the tag job: on the push that merges a release PR, release-please
# must see the new release, or it reads the whole history as unreleased.
needs: release
if: ${{ !cancelled() && needs.release.result != 'failure' }}
runs-on: ubuntu-latest
concurrency:
group: ${{ github.workflow }}-release-pr
cancel-in-progress: false
permissions:
contents: write
pull-requests: write
steps:
- uses: googleapis/release-please-action@5c625bfb5d1ff62eadeeb3772007f7f66fdcf071 # v4.4.1
with:
token: ${{ secrets.GITHUB_TOKEN }}
config-file: release-please-config.json
manifest-file: .release-please-manifest.json
skip-github-release: true

detect:
name: Detect a release
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
outputs:
release: ${{ steps.detect.outputs.release }}
version: ${{ steps.detect.outputs.version }}
tag: ${{ steps.detect.outputs.tag }}
previous_tag: ${{ steps.detect.outputs.previous_tag }}
sha: ${{ steps.detect.outputs.sha }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
persist-credentials: false

- id: detect
env:
GH_TOKEN: ${{ github.token }}
BEFORE: ${{ github.event.before }}
run: |
set -euo pipefail
manifest=.release-please-manifest.json
no_release() { echo "release=false" >>"$GITHUB_OUTPUT"; echo "No release: $1"; exit 0; }

# This push must merge a release PR that release-please opened and
# change the manifest. Branch name alone is not enough: anyone with
# write access can name a branch. "tagged" keeps a full re-run working.
pr=$(gh api "repos/$GITHUB_REPOSITORY/commits/$GITHUB_SHA/pulls" --jq '
[.[] | select(.merged_at != null
and (.head.ref | startswith("release-please--branches--main"))
and .user.login == "github-actions[bot]"
and ([.labels[].name] | any(. == "autorelease: pending" or . == "autorelease: tagged")))][0].number // empty')
[ -n "$pr" ] || no_release "this push does not merge a release PR"
if git diff --quiet "$BEFORE" "$GITHUB_SHA" -- "$manifest"; then no_release "the manifest did not change"; fi

version=$(jq -r '."."' "$manifest")
previous=$(git show "$BEFORE:$manifest" | jq -r '."."')
echo "Release PR #$pr: v$previous -> v$version"
{
echo "release=true"
echo "version=$version"
echo "tag=v$version"
echo "previous_tag=v$previous"
echo "sha=$GITHUB_SHA"
} >>"$GITHUB_OUTPUT"

release:
name: Tag and release
needs: detect
if: needs.detect.outputs.release == 'true'
runs-on: ubuntu-latest
# The release environment deploys only from main.
environment: release
concurrency:
group: ${{ github.workflow }}-release
cancel-in-progress: false
permissions:
contents: write
pull-requests: write
env:
VERSION: ${{ needs.detect.outputs.version }}
TAG: ${{ needs.detect.outputs.tag }}
PREVIOUS_TAG: ${{ needs.detect.outputs.previous_tag }}
COMMIT: ${{ needs.detect.outputs.sha }}
steps:
# A re-run after a partial release finds its own tag and goes on to the notes.
- name: Check the tag
id: tag
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
ref=$(gh api "repos/$GITHUB_REPOSITORY/git/ref/tags/$TAG" 2>/dev/null) || exit 0
sha=$(jq -r .object.sha <<<"$ref")
if [ "$(jq -r .object.type <<<"$ref")" = tag ]; then
sha=$(gh api "repos/$GITHUB_REPOSITORY/git/tags/$sha" --jq .object.sha)
fi
if [ "$sha" != "$COMMIT" ]; then
echo "::error::The tag $TAG already exists on another commit ($sha)."
exit 1
fi
echo "::notice::The tag $TAG already exists on this commit. Creating it is skipped."
echo "exists=true" >>"$GITHUB_OUTPUT"

- name: Create the tag and GitHub release
if: steps.tag.outputs.exists != 'true'
uses: googleapis/release-please-action@5c625bfb5d1ff62eadeeb3772007f7f66fdcf071 # v4.4.1
with:
token: ${{ secrets.GITHUB_TOKEN }}
config-file: release-please-config.json
manifest-file: .release-please-manifest.json
skip-github-pull-request: true

- name: Set the release title and notes
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
gh api "repos/$GITHUB_REPOSITORY/releases/generate-notes" \
-f "tag_name=$TAG" -f "previous_tag_name=$PREVIOUS_TAG" --jq .body |
gh release edit "$TAG" --repo "$GITHUB_REPOSITORY" --title "$TAG" --notes-file -

- name: Write the summary
run: |
repo="$GITHUB_SERVER_URL/$GITHUB_REPOSITORY"
{
echo "## zenrows $VERSION"
echo "- Tag: [$TAG]($repo/tree/$TAG)"
echo "- Release: [$TAG]($repo/releases/tag/$TAG)"
echo "- Commit: [${COMMIT:0:7}]($repo/commit/$COMMIT)"
} >>"$GITHUB_STEP_SUMMARY"
3 changes: 3 additions & 0 deletions .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
".": "0.1.0"
}
82 changes: 82 additions & 0 deletions release-please-config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
{
"$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json",
"release-type": "simple",
"include-component-in-tag": false,
"skip-changelog": true,
"pull-request-title-pattern": "chore: release ${version}",
"changelog-sections": [
{
"type": "feat",
"section": "Features"
},
{
"type": "fix",
"section": "Bug Fixes"
},
{
"type": "perf",
"section": "Performance Improvements"
},
{
"type": "revert",
"section": "Reverts"
},
{
"type": "docs",
"section": "Other",
"hidden": true
},
{
"type": "chore",
"section": "Other",
"hidden": true
},
{
"type": "test",
"section": "Other",
"hidden": true
},
{
"type": "ci",
"section": "Other",
"hidden": true
},
{
"type": "refactor",
"section": "Other",
"hidden": true
},
{
"type": "build",
"section": "Other",
"hidden": true
},
{
"type": "style",
"section": "Other",
"hidden": true
}
],
"packages": {
".": {
"package-name": "zenrows",
"extra-files": [
{
"type": "json",
"path": "plugin.json",
"jsonpath": "$.version"
},
{
"type": "json",
"path": ".github/plugin/marketplace.json",
"jsonpath": "$.metadata.version"
},
{
"type": "json",
"path": ".github/plugin/marketplace.json",
"jsonpath": "$.plugins[0].version"
}
]
}
}
}
Loading