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
63 changes: 16 additions & 47 deletions .github/workflows/upstream-sync.yml
Original file line number Diff line number Diff line change
@@ -1,73 +1,42 @@
name: Upstream Sync Tracker

on:
schedule:
- cron: "17 6 * * *"
workflow_dispatch:
inputs:
instagrapi_tag:
description: "instagrapi tag to sync through, for example 2.5.18"
description: "instagrapi tag to sync through, for example 2.18.19"
required: true
type: string
repository_dispatch:
types:
- instagrapi_release

concurrency:
group: upstream-sync-tracker
queue: max
cancel-in-progress: false

permissions:
contents: read
issues: write

jobs:
create-sync-issue:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Check out code
- name: Check out canonical baseline
uses: actions/checkout@v7
with:
ref: ${{ github.event.repository.default_branch }}
persist-credentials: false

- name: Create upstream sync issue
- name: Create upstream sync issue when needed
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
EVENT_NAME: ${{ github.event_name }}
WORKFLOW_TAG: ${{ github.event.inputs.instagrapi_tag }}
DISPATCH_TAG: ${{ github.event.client_payload.tag }}
run: |
set -euo pipefail

TARGET_TAG="${WORKFLOW_TAG:-${DISPATCH_TAG:-}}"
if [[ -z "${TARGET_TAG}" ]]; then
echo "::error::No instagrapi tag was provided"
exit 1
fi

CURRENT_TAG="$(python - <<'PY'
import re
from pathlib import Path

text = Path("aiograpi/__init__.py").read_text()
match = re.search(r'__upstream_instagrapi_version__\s*=\s*"([^"]+)"', text)
print(match.group(1) if match else "")
PY
)"

if [[ "${CURRENT_TAG}" == "${TARGET_TAG}" ]]; then
echo "Already synced through instagrapi ${TARGET_TAG}"
exit 0
fi

BODY="$(mktemp)"
cat > "${BODY}" <<EOF
A new instagrapi release needs aiograpi triage.

Current aiograpi baseline: ${CURRENT_TAG:-unknown}
Target instagrapi tag: ${TARGET_TAG}
Compare: https://github.com/subzeroid/instagrapi/compare/${CURRENT_TAG:-${TARGET_TAG}}...${TARGET_TAG}

Checklist:
- [ ] Review instagrapi release notes and changed files.
- [ ] Decide which changes are async-portable.
- [ ] Port features/fixes with regression tests.
- [ ] Update docs and aiograpi upstream baseline.
- [ ] Run local checks and CI.
- [ ] Publish an aiograpi release or record why no release is needed.
EOF

gh issue create \
--title "Sync aiograpi with instagrapi ${TARGET_TAG}" \
--body-file "${BODY}"
run: scripts/upstream_sync_tracker.sh
10 changes: 6 additions & 4 deletions docs/upstream-sync.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,15 @@ For the 2026-05 sync, the public releases are `aiograpi 0.9.0` and newer.
- Add or port regression tests before behavior changes when practical.
- Run Ruff, regression tests, docs build, and package build before tagging.

## Future automation
## Automated release tracking

When `instagrapi` publishes a release, `aiograpi` should get a visible follow-up
task even when no async changes are needed. The preferred workflow is a GitHub
Actions `workflow_dispatch` or `repository_dispatch` job that records:
The `Upstream Sync Tracker` workflow checks GitHub's latest stable `instagrapi` release every day at 06:17 UTC. It compares that tag with `__upstream_instagrapi_version__` and creates one durable sync issue when the upstream version is newer. Existing issues are matched by exact title across open and closed states, so a closed or superseded tracker is not recreated daily.

Maintainers can also run the workflow with an explicit tag through `workflow_dispatch` or send the existing `repository_dispatch` event with `event_type` `instagrapi_release` and `client_payload.tag` set to a stable three-component `X.Y.Z` tag, such as `2.18.19`. All modes record:

- previous `instagrapi` baseline;
- new `instagrapi` tag;
- compare URL;
- checklist of changed files and release-note items.

GitHub schedules are best-effort. They run from the default branch, can be delayed or dropped during high Actions load, and are disabled for public repositories after 60 days without repository activity. Manual dispatch can replace delayed or dropped scheduled runs. If GitHub disables the workflow after 60 days of inactivity, maintainers must first re-enable it in Actions or with `gh workflow enable upstream-sync.yml`, then dispatch it.
180 changes: 180 additions & 0 deletions scripts/upstream_sync_tracker.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
#!/usr/bin/env bash

set -euo pipefail

ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
BASELINE_FILE="${BASELINE_FILE:-$ROOT/aiograpi/__init__.py}"
EVENT_NAME="${EVENT_NAME:-}"
WORKFLOW_TAG="${WORKFLOW_TAG:-}"
DISPATCH_TAG="${DISPATCH_TAG:-}"
GITHUB_REPOSITORY="${GITHUB_REPOSITORY:-}"
UPSTREAM_REPOSITORY="subzeroid/instagrapi"
VERSION_PATTERN='^[0-9]+\.[0-9]+\.[0-9]+$'

case "$EVENT_NAME" in
schedule)
TARGET_TAG="$(gh api "repos/$UPSTREAM_REPOSITORY/releases/latest" --jq .tag_name)"
;;
workflow_dispatch)
TARGET_TAG="$WORKFLOW_TAG"
;;
repository_dispatch)
TARGET_TAG="$DISPATCH_TAG"
;;
*)
echo "Unsupported or missing event: $EVENT_NAME" >&2
exit 1
;;
esac

if ! [[ "$TARGET_TAG" =~ $VERSION_PATTERN ]]; then
echo "Invalid instagrapi target tag: $TARGET_TAG" >&2
exit 1
fi

read_baseline() {
python3 - "$BASELINE_FILE" <<'PY'
import re
import sys
from pathlib import Path

match = re.search(
r'^__upstream_instagrapi_version__\s*=\s*"([^"]+)"\s*$',
Path(sys.argv[1]).read_text(),
re.MULTILINE,
)
if match:
print(match.group(1))
PY
}

BASELINE="$(read_baseline)"

if ! [[ "$BASELINE" =~ $VERSION_PATTERN ]]; then
echo "Invalid aiograpi upstream baseline: $BASELINE" >&2
exit 1
fi

compare_versions() {
python3 - "$TARGET_TAG" "$BASELINE" <<'PY'
import sys

target = tuple(map(int, sys.argv[1].split(".")))
baseline = tuple(map(int, sys.argv[2].split(".")))
print("not-newer" if target <= baseline else "newer")
PY
}

VERSION_ORDER="$(compare_versions)"

case "$VERSION_ORDER" in
not-newer)
echo "No upstream sync needed: $TARGET_TAG <= $BASELINE"
;;
newer)
echo "New upstream release detected: $BASELINE -> $TARGET_TAG"
;;
*)
echo "Invalid version comparison result: $VERSION_ORDER" >&2
exit 1
;;
esac

if [[ "$VERSION_ORDER" != "newer" ]]; then
exit 0
fi

if [[ -z "$GITHUB_REPOSITORY" ]]; then
echo "::error::GITHUB_REPOSITORY is required" >&2
exit 1
fi

TARGET_REPOSITORY="$GITHUB_REPOSITORY"
ISSUE_TITLE="Sync aiograpi with instagrapi $TARGET_TAG"
ISSUES_FILE="$(mktemp)"
BODY_FILE=""

cleanup() {
rm -f "$ISSUES_FILE"
if [[ -n "$BODY_FILE" ]]; then
rm -f "$BODY_FILE"
fi
}
trap cleanup EXIT

if ! gh issue list \
--repo "$TARGET_REPOSITORY" \
--state all \
--limit 1000 \
--json number,title,url,state > "$ISSUES_FILE"; then
echo "::error::Failed to list existing upstream sync issues" >&2
exit 1
fi

find_existing_issue() {
python3 - "$ISSUES_FILE" "$ISSUE_TITLE" <<'PY'
import json
import sys
from pathlib import Path

issues = json.loads(Path(sys.argv[1]).read_text())
title = sys.argv[2]
for issue in issues:
if issue.get("title") == title:
print(f"match\t{issue.get('state', '')}\t{issue.get('url', '')}")
sys.exit(0)

if len(issues) == 1000:
print("saturated")
PY
}

if ! EXISTING_ISSUE="$(find_existing_issue)"; then
echo "::error::Failed to inspect existing upstream sync issues" >&2
exit 1
fi

if [[ -n "$EXISTING_ISSUE" ]]; then
IFS=$'\t' read -r ISSUE_RESULT EXISTING_STATE EXISTING_URL <<< "$EXISTING_ISSUE"
case "$ISSUE_RESULT" in
saturated)
echo "::error::Issue list reached its limit without an exact title match" >&2
exit 1
;;
match)
if [[ "$EXISTING_STATE" == "CLOSED" ]]; then
echo "::warning::Upstream sync issue is already closed while the baseline is behind: $EXISTING_URL" >&2
else
echo "Upstream sync issue already exists: $EXISTING_URL"
fi
exit 0
;;
*)
echo "::error::Invalid existing upstream sync issue result" >&2
exit 1
;;
esac
fi

BODY_FILE="$(mktemp)"
{
printf '%s\n\n' "A new instagrapi release needs aiograpi triage."
printf 'Current aiograpi baseline: %s\n' "$BASELINE"
printf 'Target instagrapi tag: %s\n' "$TARGET_TAG"
printf 'Compare: https://github.com/subzeroid/instagrapi/compare/%s...%s\n\n' "$BASELINE" "$TARGET_TAG"
printf '%s\n' "Checklist:"
printf '%s\n' "- [ ] Review instagrapi release notes and changed files."
printf '%s\n' "- [ ] Decide which changes are async-portable."
printf '%s\n' "- [ ] Port features/fixes with regression tests."
printf '%s\n' "- [ ] Update docs and aiograpi upstream baseline."
printf '%s\n' "- [ ] Run local checks and CI."
printf '%s\n' "- [ ] Publish an aiograpi release or record why no release is needed."
} > "$BODY_FILE"

if ! gh issue create \
--repo "$TARGET_REPOSITORY" \
--title "$ISSUE_TITLE" \
--body-file "$BODY_FILE"; then
echo "::error::Failed to create upstream sync issue" >&2
exit 1
fi
Loading
Loading