-
Notifications
You must be signed in to change notification settings - Fork 1
feat: add poetry-lock-update action #48
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
026b482
feat: add poetry.lock diff script for lock-update action
blink1073 0803d35
feat: add poetry-lock-update composite action
blink1073 46ed7d1
test: wire poetry-lock-update into CI
blink1073 96d74cc
docs: document poetry-lock-update action
blink1073 5c902fd
docs: generalize PR guidance instead of hardcoding a specific fork
blink1073 a7116da
fix: scope GitHub App token permissions; support Python <3.11 in diff…
blink1073 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| # CLAUDE.md | ||
|
|
||
| This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. | ||
|
|
||
| ## Commands | ||
|
|
||
| ```bash | ||
| just install # Install dependencies via Poetry | ||
| just test # Run all tests | ||
| just test tests/test_foo.py::test_bar # Run a single test | ||
| just pre-commit # Run pre-commit hooks on all files | ||
| just pre-commit --hook-stage=manual # Also run actionlint on workflow files | ||
| ``` | ||
|
|
||
| ## Architecture | ||
|
|
||
| This repo provides reusable GitHub Actions for Calysto Python packages, consumed via the `v1` floating tag: | ||
|
|
||
| ```yaml | ||
| uses: calysto/maintainer_tools/actions/<name>@v1 | ||
| ``` | ||
|
|
||
| ### Actions | ||
|
|
||
| Each action lives in `actions/<name>/action.yml`. The actions are: | ||
|
|
||
| - **`base-setup`** — Sets up Python (auto-detects minimum version from `pyproject.toml` if unspecified), Poetry, and `just` with OS-keyed cache. Must be called before `release`, `test-minimum-versions`, and `test-sdist`. | ||
| - **`poetry-lock-update`** — Runs `poetry update` under a minimum-release-age cooldown (`POETRY_SOLVER_MIN_RELEASE_AGE`, default 7 days) and opens a pull request with the `poetry.lock` diff. Requires a GitHub App (`APP_ID` / `APP_PRIVATE_KEY`) for authenticated pushes. Must be called after `base-setup`. | ||
| - **`enforce-label`** — Wraps `yogevbd/enforce-label-action`; requires one of: `bug`, `enhancement`, `dependencies`, `maintenance`, `documentation`. | ||
| - **`release`** — Full release pipeline: bumps version via Poetry, generates and writes CHANGELOG.md, commits and pushes, creates GitHub release, then bumps to next `.dev` version using `actions/release/bump_dev.py`. Supports dry-run. Requires a GitHub App (`APP_ID` / `APP_PRIVATE_KEY`) for authenticated pushes. | ||
| - **`test-minimum-versions`** — Rewrites `pyproject.toml` to pin all deps to their minimum declared versions, then runs the test suite. | ||
| - **`test-sdist`** — Downloads the `Packages` artifact from `hynek/build-and-inspect-python-package`, unpacks the sdist, and runs the test suite from within it. | ||
|
|
||
| ### Release workflow | ||
|
|
||
| `.github/workflows/release.yml` orchestrates: | ||
|
|
||
| 1. **`release`** job — runs `./actions/release`, outputs the new tag | ||
| 1. **`build-package`** job — checks out the release tag and builds via `hynek/build-and-inspect-python-package` as a release-time sanity check (this package is not published to PyPI) | ||
| 1. **`update-v1-tag`** job — moves the `v1` floating tag to the new release commit; skipped for pre-releases (tags containing `a`, `b`, `rc`, or `dev`) | ||
|
|
||
| `workflow_dispatch` runs use `dry_run: false` by default; scheduled runs always use `dry_run: true`. | ||
|
|
||
| ### PRs | ||
|
|
||
| Always target the upstream repo: `--repo Calysto/maintainer_tools --head <your-github-username>:<branch>`. |
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,101 @@ | ||
| name: Poetry Lock Update | ||
| description: Runs `poetry update` under a minimum-release-age cooldown and opens a pull request with the lock file changes | ||
|
|
||
| inputs: | ||
| app-id: | ||
| required: false | ||
| default: "" | ||
| description: "GitHub App ID for authenticated pushes (optional; falls back to github.token)" | ||
| app-private-key: | ||
| required: false | ||
| default: "" | ||
| description: "GitHub App private key for authenticated pushes" | ||
| min-release-age-days: | ||
| required: false | ||
| default: "7" | ||
| description: "Minimum release age in days before Poetry's resolver will consider a version (sets POETRY_SOLVER_MIN_RELEASE_AGE)" | ||
| branch: | ||
| required: false | ||
| default: "poetry-lock-update" | ||
| description: "Branch name for the update pull request" | ||
| labels: | ||
| required: false | ||
| default: "maintenance" | ||
| description: "Labels to apply to the pull request" | ||
| dry-run: | ||
| required: false | ||
| default: "false" | ||
| description: "If 'true', pass --dry-run to gh pr create (skips actual PR creation)" | ||
|
|
||
| runs: | ||
| using: composite | ||
| steps: | ||
| - name: Generate app token | ||
| id: app-token | ||
| if: inputs.app-id != '' && inputs.app-private-key != '' | ||
| uses: actions/create-github-app-token@v3 | ||
| with: | ||
| app-id: ${{ inputs.app-id }} | ||
| private-key: ${{ inputs.app-private-key }} | ||
| permission-contents: write | ||
| permission-pull-requests: write | ||
|
|
||
| - name: Save current lock file | ||
| shell: bash | ||
| run: cp poetry.lock "${{ runner.temp }}/poetry.lock.before" | ||
|
|
||
| - name: Run poetry update | ||
| shell: bash | ||
| env: | ||
| POETRY_SOLVER_MIN_RELEASE_AGE: ${{ inputs.min-release-age-days }} | ||
| run: poetry update --no-interaction --lock | ||
|
|
||
| - name: Create pull request | ||
| shell: bash | ||
| env: | ||
| GH_TOKEN: ${{ steps.app-token.outputs.token || github.token }} | ||
| BRANCH: ${{ inputs.branch }} | ||
| LABELS: ${{ inputs.labels }} | ||
| DRY_RUN: ${{ inputs.dry-run }} | ||
| OLD_LOCK: ${{ runner.temp }}/poetry.lock.before | ||
| run: | | ||
| if git diff --quiet poetry.lock; then | ||
| echo "No changes detected, skipping PR creation" | ||
| exit 0 | ||
| fi | ||
|
|
||
| # Generate random suffix for branch name | ||
| SUFFIX=$(openssl rand -hex 4) | ||
| FULL_BRANCH="${BRANCH}-${SUFFIX}" | ||
|
|
||
| # diff_lock.py needs tomllib (Python 3.11+) or its tomli fallback; | ||
| # install tomli on older interpreters where neither is present. | ||
| python3 -c "import tomllib" 2>/dev/null || pip install --quiet tomli | ||
|
|
||
| UPDATES=$(python3 "${{ github.action_path }}/diff_lock.py" "$OLD_LOCK" poetry.lock) | ||
|
|
||
| BODY="" | ||
| if [ -n "$UPDATES" ]; then | ||
| BODY="## Updated packages"$'\n\n'"${UPDATES}" | ||
| fi | ||
|
|
||
| git config user.name "github-actions[bot]" | ||
| git config user.email "github-actions[bot]@users.noreply.github.com" | ||
| git remote set-url origin "https://x-access-token:${GH_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" | ||
| git checkout -B "$FULL_BRANCH" | ||
| git add poetry.lock | ||
| git commit -m "chore: update poetry.lock" | ||
| if [ "$DRY_RUN" != "true" ]; then | ||
| git push origin "$FULL_BRANCH" | ||
| fi | ||
| DRY_RUN_FLAG="" | ||
| if [ "$DRY_RUN" = "true" ]; then | ||
| DRY_RUN_FLAG="--dry-run" | ||
| fi | ||
| # shellcheck disable=SC2086 | ||
| gh pr create \ | ||
| --title "chore: update poetry.lock" \ | ||
| --body "$BODY" \ | ||
| --label "$LABELS" \ | ||
| --head "$FULL_BRANCH" \ | ||
| $DRY_RUN_FLAG | ||
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| """Diff two poetry.lock files and print a markdown list of package version changes.""" | ||
|
|
||
| import sys | ||
|
|
||
| try: | ||
| import tomllib | ||
| except ImportError: | ||
| import tomli as tomllib # type: ignore[no-redef] | ||
|
|
||
|
|
||
| def load_versions(path: str) -> dict[str, str]: | ||
| with open(path, "rb") as f: | ||
| data = tomllib.load(f) | ||
| return {pkg["name"]: pkg["version"] for pkg in data.get("package", [])} | ||
|
|
||
|
|
||
| def diff_versions(old: dict[str, str], new: dict[str, str]) -> list[str]: | ||
| lines = [] | ||
| for name in sorted(set(old) | set(new)): | ||
| old_version = old.get(name) | ||
| new_version = new.get(name) | ||
| if old_version == new_version: | ||
| continue | ||
| if old_version is None: | ||
| lines.append(f"- {name}: added `{new_version}`") | ||
| elif new_version is None: | ||
| lines.append(f"- {name}: removed `{old_version}`") | ||
| else: | ||
| lines.append(f"- {name}: `{old_version}` → `{new_version}`") | ||
| return lines | ||
|
|
||
|
|
||
| def main() -> None: | ||
| if len(sys.argv) != 3: | ||
| print("Usage: diff_lock.py <old.lock> <new.lock>", file=sys.stderr) | ||
| sys.exit(2) | ||
| old = load_versions(sys.argv[1]) | ||
| new = load_versions(sys.argv[2]) | ||
| for line in diff_versions(old, new): | ||
| print(line) | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| main() |
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,96 @@ | ||
| #!/usr/bin/env bash | ||
| set -euo pipefail | ||
|
|
||
| SCRIPT="$(cd "$(dirname "$0")" && pwd)/diff_lock.py" | ||
| FAIL=0 | ||
| TMPDIR=$(mktemp -d) | ||
| trap 'rm -rf "$TMPDIR"' EXIT | ||
|
|
||
| check() { | ||
| local desc="$1" | ||
| local expected="$2" | ||
| local actual="$3" | ||
| if [ "$actual" = "$expected" ]; then | ||
| echo "OK: $desc" | ||
| else | ||
| echo "FAIL: $desc" | ||
| echo " expected: $expected" | ||
| echo " actual: $actual" | ||
| FAIL=1 | ||
| fi | ||
| } | ||
|
|
||
| cat > "$TMPDIR/old.lock" <<'EOF' | ||
| [[package]] | ||
| name = "black" | ||
| version = "23.1.0" | ||
|
|
||
| [[package]] | ||
| name = "flake8" | ||
| version = "6.0.0" | ||
|
|
||
| [[package]] | ||
| name = "requests" | ||
| version = "2.31.0" | ||
| EOF | ||
|
|
||
| cat > "$TMPDIR/new.lock" <<'EOF' | ||
| [[package]] | ||
| name = "black" | ||
| version = "23.3.0" | ||
|
|
||
| [[package]] | ||
| name = "flake8" | ||
| version = "6.0.0" | ||
|
|
||
| [[package]] | ||
| name = "requests" | ||
| version = "2.31.0" | ||
|
|
||
| [[package]] | ||
| name = "click" | ||
| version = "8.1.0" | ||
| EOF | ||
|
|
||
| ACTUAL=$(python3 "$SCRIPT" "$TMPDIR/old.lock" "$TMPDIR/new.lock") | ||
|
|
||
| check "changed version" \ | ||
| "- black: \`23.1.0\` → \`23.3.0\`" \ | ||
| "$(echo "$ACTUAL" | grep '^- black:')" | ||
|
|
||
| check "added package" \ | ||
| "- click: added \`8.1.0\`" \ | ||
| "$(echo "$ACTUAL" | grep '^- click:')" | ||
|
|
||
| check "unchanged package omitted" \ | ||
| "" \ | ||
| "$(echo "$ACTUAL" | grep '^- flake8:' || true)" | ||
|
|
||
| check "line count" \ | ||
| "2" \ | ||
| "$(echo "$ACTUAL" | grep -c '^-')" | ||
|
|
||
| cat > "$TMPDIR/old2.lock" <<'EOF' | ||
| [[package]] | ||
| name = "urllib3" | ||
| version = "2.0.0" | ||
| EOF | ||
|
|
||
| cat > "$TMPDIR/new2.lock" <<'EOF' | ||
| EOF | ||
|
|
||
| ACTUAL2=$(python3 "$SCRIPT" "$TMPDIR/old2.lock" "$TMPDIR/new2.lock") | ||
| check "removed package" \ | ||
| "- urllib3: removed \`2.0.0\`" \ | ||
| "$ACTUAL2" | ||
|
|
||
| cat > "$TMPDIR/same.lock" <<'EOF' | ||
| [[package]] | ||
| name = "idna" | ||
| version = "3.4" | ||
| EOF | ||
|
|
||
| ACTUAL3=$(python3 "$SCRIPT" "$TMPDIR/same.lock" "$TMPDIR/same.lock") | ||
| check "no changes -> empty output" "" "$ACTUAL3" | ||
|
|
||
| exit $FAIL |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.