mirror of
https://github.com/trustedsec/hate_crack.git
synced 2026-07-28 14:47:22 -07:00
fix(ci): add test CI, repair auto-tag releases, harden action pins (2.11.4)
The repo had no CI: ruff/ty/pytest ran only in local prek pre-push hooks, so a commit pushed without hooks installed reached main unvalidated -- and auto-tag would then cut a release from it. Adds ci.yml (ruff, ty, pytest on 3.13) and gates tagging on it. Auto-tagged versions also never produced a release. The tag is pushed with the default GITHUB_TOKEN, and GitHub suppresses workflow triggers for GITHUB_TOKEN-created events, so the tag-triggered release.yml never fired -- v2.11.3 was tagged with no release. auto-tag now creates the release itself, idempotently; release.yml stays as the manual-tag path. Also fixes version logic that matched the `!` breaking marker but only ever bumped minor (and never saw BREAKING CHANGE: footers, since only subjects were inspected), serializes concurrent merges that both computed the same tag, and repins softprops/action-gh-release from an arbitrary master commit to v2.6.2 with all workflows on one actions/checkout version. Clears the pre-existing ty error in notify/tailer.py that would have made the new type-check step red: _read_new_lines read self._file_pos (int | None) while its only None-guard lived in the caller, so the position is passed in explicitly as an int. Behavior unchanged. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude
parent
a0dc56ce08
commit
665c8ab47c
@@ -0,0 +1,29 @@
|
||||
version: 2
|
||||
|
||||
updates:
|
||||
# Python dependencies (PEP 621 pyproject.toml + uv.lock).
|
||||
# The native "uv" ecosystem is used instead of "pip" because it reads
|
||||
# pyproject.toml and uv.lock directly (GA since 2025-03; security updates
|
||||
# since 2025-12).
|
||||
- package-ecosystem: "uv"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
open-pull-requests-limit: 5
|
||||
# "chore" deliberately does NOT match the feat|fix|perf patterns in
|
||||
# .github/workflows/auto-tag.yml, so dependency PRs never auto-cut a tag.
|
||||
commit-message:
|
||||
prefix: "chore(deps)"
|
||||
prefix-development: "chore(deps-dev)"
|
||||
|
||||
# GitHub Actions are SHA-pinned in .github/workflows; Dependabot keeps the
|
||||
# pins current (and refreshes the trailing "# vN" comment).
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
open-pull-requests-limit: 5
|
||||
# "chore(ci)" deliberately does NOT match the feat|fix|perf patterns in
|
||||
# .github/workflows/auto-tag.yml, so dependency PRs never auto-cut a tag.
|
||||
commit-message:
|
||||
prefix: "chore(ci)"
|
||||
@@ -1,26 +1,50 @@
|
||||
name: Auto Tag
|
||||
|
||||
# Runs only after the CI workflow finishes successfully on main, so a broken
|
||||
# commit never gets tagged or released.
|
||||
on:
|
||||
push:
|
||||
workflow_run:
|
||||
workflows: ["CI"]
|
||||
types:
|
||||
- completed
|
||||
branches:
|
||||
- main
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
# Two merges landing back-to-back would otherwise both compute the same new tag
|
||||
# and the second push would fail. Serialize instead of cancelling so no merge
|
||||
# gets skipped.
|
||||
concurrency:
|
||||
group: auto-tag
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
tag:
|
||||
runs-on: ubuntu-latest
|
||||
if: >-
|
||||
github.event.workflow_run.conclusion == 'success' &&
|
||||
github.event.workflow_run.event == 'push'
|
||||
steps:
|
||||
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
# workflow_run defaults to the tip of the default branch, which is not
|
||||
# necessarily the commit CI validated.
|
||||
ref: ${{ github.event.workflow_run.head_sha }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Configure git identity
|
||||
run: |
|
||||
git config user.name 'github-actions[bot]'
|
||||
git config user.email '41898282+github-actions[bot]@users.noreply.github.com'
|
||||
|
||||
- name: Determine version bump
|
||||
id: bump
|
||||
run: |
|
||||
# Get the latest semver tag
|
||||
latest_tag=$(git tag --sort=-v:refname | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | head -1)
|
||||
# Get the latest semver tag. The `|| true` keeps `set -o pipefail`
|
||||
# from aborting the step when there are no matching tags yet.
|
||||
latest_tag=$(git tag --sort=-v:refname | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | head -1 || true)
|
||||
if [ -z "$latest_tag" ]; then
|
||||
echo "No semver tag found, starting at v0.1.0"
|
||||
echo "new_tag=v0.1.0" >> "$GITHUB_OUTPUT"
|
||||
@@ -29,10 +53,12 @@ jobs:
|
||||
|
||||
echo "Latest tag: $latest_tag"
|
||||
|
||||
# Get commits since last tag
|
||||
commits=$(git log "$latest_tag"..HEAD --pretty=format:"%s")
|
||||
# Commit SHAs since the last tag. We iterate SHAs rather than reading
|
||||
# subjects line-by-line so that commit *bodies* (and therefore
|
||||
# BREAKING CHANGE footers) are visible.
|
||||
shas=$(git log "$latest_tag"..HEAD --format=%H)
|
||||
|
||||
if [ -z "$commits" ]; then
|
||||
if [ -z "$shas" ]; then
|
||||
echo "No new commits since $latest_tag"
|
||||
echo "skip=true" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
@@ -44,23 +70,42 @@ jobs:
|
||||
minor=$(echo "$version" | cut -d. -f2)
|
||||
patch=$(echo "$version" | cut -d. -f3)
|
||||
|
||||
# Check commit prefixes for bump type
|
||||
# Determine bump type. Precedence: major > minor > patch.
|
||||
bump="none"
|
||||
while IFS= read -r msg; do
|
||||
if echo "$msg" | grep -qE '^feat(\(.+\))?!?:'; then
|
||||
while IFS= read -r sha; do
|
||||
[ -n "$sha" ] || continue
|
||||
body=$(git log -1 --format=%B "$sha")
|
||||
subject=${body%%$'\n'*}
|
||||
|
||||
# Note: matches are done with bash [[ =~ ]] / here-strings rather
|
||||
# than pipelines, so `set -o pipefail` cannot turn an early-exiting
|
||||
# `grep -q` (SIGPIPE, status 141) into a false negative.
|
||||
if [[ "$subject" =~ ^[a-zA-Z]+(\(.+\))?!: ]]; then
|
||||
# e.g. "feat!: ..." / "refactor(api)!: ..."
|
||||
bump="major"
|
||||
elif grep -qE '^(BREAKING CHANGE|BREAKING-CHANGE):' <<<"$body"; then
|
||||
# Conventional Commits breaking-change footer in the body.
|
||||
bump="major"
|
||||
elif [[ "$subject" =~ ^feat(\(.+\))?: ]] && [ "$bump" != "major" ]; then
|
||||
bump="minor"
|
||||
elif echo "$msg" | grep -qE '^(fix|perf)(\(.+\))?:' && [ "$bump" != "minor" ]; then
|
||||
elif [[ "$subject" =~ ^(fix|perf)(\(.+\))?: ]] && [ "$bump" = "none" ]; then
|
||||
bump="patch"
|
||||
fi
|
||||
done <<< "$commits"
|
||||
done <<EOF
|
||||
$shas
|
||||
EOF
|
||||
|
||||
if [ "$bump" = "none" ]; then
|
||||
echo "No feat/fix/perf commits, skipping tag"
|
||||
echo "No feat/fix/perf/breaking commits, skipping tag"
|
||||
echo "skip=true" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ "$bump" = "minor" ]; then
|
||||
if [ "$bump" = "major" ]; then
|
||||
major=$((major + 1))
|
||||
minor=0
|
||||
patch=0
|
||||
elif [ "$bump" = "minor" ]; then
|
||||
minor=$((minor + 1))
|
||||
patch=0
|
||||
else
|
||||
@@ -68,7 +113,7 @@ jobs:
|
||||
fi
|
||||
|
||||
new_tag="v${major}.${minor}.${patch}"
|
||||
echo "Bump: $bump → $new_tag"
|
||||
echo "Bump: $bump -> $new_tag"
|
||||
echo "new_tag=$new_tag" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Create tag
|
||||
@@ -76,5 +121,25 @@ jobs:
|
||||
env:
|
||||
NEW_TAG: ${{ steps.bump.outputs.new_tag }}
|
||||
run: |
|
||||
git tag "$NEW_TAG"
|
||||
git push origin "$NEW_TAG"
|
||||
if git rev-parse -q --verify "refs/tags/$NEW_TAG" >/dev/null; then
|
||||
echo "Tag $NEW_TAG already exists, nothing to push"
|
||||
else
|
||||
git tag "$NEW_TAG"
|
||||
git push origin "refs/tags/$NEW_TAG"
|
||||
fi
|
||||
|
||||
# GitHub never dispatches workflow events for refs pushed with
|
||||
# GITHUB_TOKEN, so release.yml will not fire for the tag above. Create the
|
||||
# release here instead. release.yml remains the path for tags pushed
|
||||
# manually by a human.
|
||||
- name: Create GitHub release
|
||||
if: steps.bump.outputs.skip != 'true'
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
NEW_TAG: ${{ steps.bump.outputs.new_tag }}
|
||||
run: |
|
||||
if gh release view "$NEW_TAG" >/dev/null 2>&1; then
|
||||
echo "Release $NEW_TAG already exists, nothing to do"
|
||||
exit 0
|
||||
fi
|
||||
gh release create "$NEW_TAG" --generate-notes
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ci-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
checks:
|
||||
name: Lint, type-check, and tests
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
# tests/test_main_pcfg.py needs pcfg_cracker/pcfg_guesser.py on disk
|
||||
submodules: true
|
||||
|
||||
- uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
|
||||
with:
|
||||
python-version: "3.13"
|
||||
enable-cache: true
|
||||
cache-dependency-glob: "**/uv.lock"
|
||||
|
||||
- name: Install dependencies
|
||||
run: uv sync --dev
|
||||
|
||||
- name: Ruff
|
||||
run: uv run ruff check hate_crack
|
||||
|
||||
- name: Ty
|
||||
run: uv run ty check hate_crack
|
||||
|
||||
- name: Pytest
|
||||
env:
|
||||
HATE_CRACK_SKIP_INIT: "1"
|
||||
run: uv run pytest -q
|
||||
@@ -12,10 +12,10 @@ jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: softprops/action-gh-release@153bb8e04406b158c6c84fc1615b65b24149a1fe # master
|
||||
- uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2.6.2
|
||||
with:
|
||||
generate_release_notes: true
|
||||
|
||||
Reference in New Issue
Block a user