Skip to content

Commit f435cd1

Browse files
committed
chore: Add github action using an agent to detect changes in vercel upstream package and adopt changes
1 parent c744e87 commit f435cd1

2 files changed

Lines changed: 228 additions & 0 deletions

File tree

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
# Sync Python port with upstream Vercel `packages/detect-agent` when it changes.
2+
#
3+
# Setup:
4+
# - Repository secret: CURSOR_API_KEY (https://github.com/marketplace/actions/cursor-action)
5+
# - Uses actions/cache to remember the last synced upstream commit for that path.
6+
# - First successful run bootstraps cache only (no agent); the next drift triggers the agent.
7+
#
8+
# Manual run: Actions -> "Sync Vercel detect-agent upstream" -> Run workflow
9+
10+
name: Sync Vercel detect-agent upstream
11+
12+
on:
13+
schedule:
14+
# Daily 14:05 UTC — adjust as you like
15+
- cron: "5 14 * * *"
16+
workflow_dispatch:
17+
18+
concurrency:
19+
group: sync-vercel-detect-agent-upstream
20+
cancel-in-progress: false
21+
22+
jobs:
23+
sync:
24+
runs-on: ubuntu-latest
25+
permissions:
26+
contents: write
27+
pull-requests: write
28+
env:
29+
GH_TOKEN: ${{ github.token }}
30+
GITHUB_TOKEN: ${{ github.token }}
31+
UPSTREAM_STATE_DIR: ${{ runner.temp }}/upstream-sync-state
32+
UPSTREAM_VERCEL_MIRROR: ${{ runner.temp }}/vercel.git
33+
UPSTREAM_DIFF_FILE: ${{ runner.temp }}/vercel-detect-agent.diff
34+
steps:
35+
- uses: actions/checkout@v4
36+
with:
37+
fetch-depth: 0
38+
39+
- uses: actions/cache@v4
40+
id: vercel_mirror_cache
41+
with:
42+
path: ${{ runner.temp }}/vercel.git
43+
key: vercel-github-bare-mirror-v1-${{ runner.os }}
44+
45+
- uses: actions/cache@v4
46+
id: upstream_state_cache
47+
with:
48+
path: ${{ runner.temp }}/upstream-sync-state
49+
key: vercel-detect-agent-upstream-state-v1-${{ runner.os }}
50+
51+
- name: Compute upstream drift
52+
id: drift
53+
shell: bash
54+
run: ./scripts/vercel_upstream_drift.sh
55+
56+
- name: Configure git for pushes
57+
if: steps.drift.outputs.should_sync == 'true'
58+
run: |
59+
set -euo pipefail
60+
git config user.name "github-actions[bot]"
61+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
62+
git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git"
63+
64+
- uses: astral-sh/setup-uv@v5
65+
if: steps.drift.outputs.should_sync == 'true'
66+
with:
67+
enable-cache: true
68+
python-version: "3.9"
69+
70+
- name: Install Python deps (uv)
71+
if: steps.drift.outputs.should_sync == 'true'
72+
run: uv sync --extra dev
73+
74+
- name: Run Cursor agent (port + push + PR)
75+
id: cursor
76+
if: steps.drift.outputs.should_sync == 'true'
77+
uses: PunGrumpy/cursor-action@v1.0.0
78+
with:
79+
api-key: ${{ secrets.CURSOR_API_KEY }}
80+
model: composer-2
81+
working-directory: .
82+
permissions: read-write
83+
timeout: "3600"
84+
prompt: |
85+
You are syncing this repository's Python port of Vercel's `packages/detect-agent` with upstream changes.
86+
87+
Upstream facts:
88+
- merge_base: ${{ steps.drift.outputs.merge_base }}
89+
- last_recorded_upstream_tip: ${{ steps.drift.outputs.last_recorded }}
90+
- new_upstream_tip: ${{ steps.drift.outputs.new_sha }}
91+
- compare URL: ${{ steps.drift.outputs.compare_url }}
92+
93+
Read the unified diff from this absolute path. Do not `git add` or commit that path; it is CI scratch only:
94+
${{ steps.drift.outputs.diff_path }}
95+
96+
Your job, in order:
97+
1. `git fetch origin && git checkout main && git pull --ff-only origin main`
98+
2. Create branch `sync/vercel-detect-agent-${{ steps.drift.outputs.branch_slug }}` (delete/recreate the local branch if it already exists).
99+
3. Port upstream logic into `detect_agent/__init__.py` and `tests/` as needed. Mirror detection order and semantics from the TypeScript sources; keep typing style consistent with the existing Python file.
100+
4. Update `README.md` / `CHANGELOG.md` here if upstream docs/changelog warrant it.
101+
5. Run: `uv sync --extra dev && uv run pytest && uv run ruff check . && uv run ruff format --check .`
102+
6. Commit with message like: `sync: port vercel detect-agent ${{ steps.drift.outputs.merge_base }}..${{ steps.drift.outputs.new_sha }}` (shorten SHAs in the subject if you prefer).
103+
7. `git push -u origin HEAD`
104+
8. Open a PR with `gh pr create` (title/body should link ${{ steps.drift.outputs.compare_url }} and mention https://github.com/vercel/vercel/tree/main/packages/detect-agent ). `GITHUB_TOKEN` is already available to `gh` in this environment.
105+
106+
If the diff file is empty but the SHAs differ, use the compare URL and current upstream `main` tree for that package to align the port.
107+
108+
- name: Record synced upstream SHA
109+
if: success() && steps.drift.outputs.should_sync == 'true'
110+
shell: bash
111+
env:
112+
NEW_SHA: ${{ steps.drift.outputs.new_sha }}
113+
run: |
114+
set -euo pipefail
115+
mkdir -p "${UPSTREAM_STATE_DIR}"
116+
printf '%s\n' "${NEW_SHA}" >"${UPSTREAM_STATE_DIR}/last_packages_detect_agent_commit.txt"
117+
118+
- name: Job summary
119+
if: always()
120+
shell: bash
121+
run: |
122+
set -euo pipefail
123+
{
124+
echo "## Upstream sync"
125+
echo "- should_sync: ${{ steps.drift.outputs.should_sync }}"
126+
echo "- new_sha: ${{ steps.drift.outputs.new_sha }}"
127+
echo "- compare: ${{ steps.drift.outputs.compare_url }}"
128+
} >>"${GITHUB_STEP_SUMMARY}"
129+
if [[ "${{ steps.drift.outputs.should_sync }}" == "true" ]]; then
130+
echo "- cursor exit: ${{ steps.cursor.outputs.exit-code }}" >>"${GITHUB_STEP_SUMMARY}"
131+
fi

scripts/vercel_upstream_drift.sh

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
#!/usr/bin/env bash
2+
# Compute whether vercel/vercel changed packages/detect-agent since the last run.
3+
# Intended for GitHub Actions; optionally writes key=value lines to GITHUB_OUTPUT.
4+
#
5+
# Required env:
6+
# UPSTREAM_STATE_DIR - directory holding last_packages_detect_agent_commit.txt
7+
# UPSTREAM_VERCEL_MIRROR - path to (or parent of) bare clone of github.com/vercel/vercel.git
8+
# UPSTREAM_DIFF_FILE - where to write unified diff when drift is detected
9+
#
10+
# Optional:
11+
# GITHUB_OUTPUT - when set, writes should_sync, new_sha, merge_base, last_recorded,
12+
# diff_path, compare_url (booleans as true/false strings)
13+
14+
set -euo pipefail
15+
16+
UPSTREAM_STATE_DIR="${UPSTREAM_STATE_DIR:?UPSTREAM_STATE_DIR is required}"
17+
UPSTREAM_VERCEL_MIRROR="${UPSTREAM_VERCEL_MIRROR:?UPSTREAM_VERCEL_MIRROR is required}"
18+
UPSTREAM_DIFF_FILE="${UPSTREAM_DIFF_FILE:?UPSTREAM_DIFF_FILE is required}"
19+
20+
path='packages/detect-agent'
21+
LAST_SHA_FILE="${UPSTREAM_STATE_DIR}/last_packages_detect_agent_commit.txt"
22+
23+
append_kv() {
24+
if [[ -z "${GITHUB_OUTPUT:-}" ]]; then
25+
return 0
26+
fi
27+
printf '%s=%s\n' "$1" "$2" >>"$GITHUB_OUTPUT"
28+
}
29+
30+
mkdir -p "$UPSTREAM_STATE_DIR"
31+
32+
if [[ ! -d "$UPSTREAM_VERCEL_MIRROR" ]]; then
33+
git clone --mirror https://github.com/vercel/vercel.git "$UPSTREAM_VERCEL_MIRROR"
34+
fi
35+
36+
git -C "$UPSTREAM_VERCEL_MIRROR" fetch --prune origin
37+
38+
NEW_SHA="$(git -C "$UPSTREAM_VERCEL_MIRROR" log -1 --format=%H "origin/main" -- "$path")"
39+
if [[ -z "$NEW_SHA" ]]; then
40+
echo "failed to resolve latest commit for $path on origin/main" >&2
41+
exit 1
42+
fi
43+
44+
if [[ ! -f "$LAST_SHA_FILE" ]]; then
45+
printf '%s\n' "$NEW_SHA" >"$LAST_SHA_FILE"
46+
echo "bootstrapped upstream state (no sync): $NEW_SHA" >&2
47+
append_kv should_sync false
48+
append_kv new_sha "$NEW_SHA"
49+
exit 0
50+
fi
51+
52+
LAST_SHA="$(tr -d '[:space:]' <"$LAST_SHA_FILE" || true)"
53+
if [[ -z "$LAST_SHA" ]]; then
54+
printf '%s\n' "$NEW_SHA" >"$LAST_SHA_FILE"
55+
echo "repaired empty state file -> $NEW_SHA" >&2
56+
append_kv should_sync false
57+
append_kv new_sha "$NEW_SHA"
58+
exit 0
59+
fi
60+
61+
if [[ "$LAST_SHA" == "$NEW_SHA" ]]; then
62+
echo "no upstream drift: $NEW_SHA" >&2
63+
append_kv should_sync false
64+
append_kv new_sha "$NEW_SHA"
65+
exit 0
66+
fi
67+
68+
echo "upstream drift: $LAST_SHA -> $NEW_SHA" >&2
69+
70+
if ! git -C "$UPSTREAM_VERCEL_MIRROR" cat-file -e "${LAST_SHA}^{commit}" 2>/dev/null; then
71+
echo "widening fetch for stored sha" >&2
72+
git -C "$UPSTREAM_VERCEL_MIRROR" fetch --prune origin "${LAST_SHA}" || true
73+
fi
74+
75+
if ! git -C "$UPSTREAM_VERCEL_MIRROR" cat-file -e "${LAST_SHA}^{commit}" 2>/dev/null; then
76+
echo "stored sha $LAST_SHA not found; delete ${LAST_SHA_FILE} to re-bootstrap" >&2
77+
exit 1
78+
fi
79+
80+
MB="$(git -C "$UPSTREAM_VERCEL_MIRROR" merge-base "$LAST_SHA" "$NEW_SHA" 2>/dev/null || true)"
81+
if [[ -z "$MB" ]]; then
82+
MB="$LAST_SHA"
83+
fi
84+
85+
git -C "$UPSTREAM_VERCEL_MIRROR" diff "${MB}..${NEW_SHA}" -- "$path" >"$UPSTREAM_DIFF_FILE" || true
86+
87+
COMPARE_URL="https://github.com/vercel/vercel/compare/${MB}...${NEW_SHA}"
88+
89+
append_kv should_sync true
90+
append_kv new_sha "$NEW_SHA"
91+
append_kv branch_slug "${NEW_SHA:0:7}"
92+
append_kv merge_base "$MB"
93+
append_kv last_recorded "$LAST_SHA"
94+
append_kv diff_path "$UPSTREAM_DIFF_FILE"
95+
append_kv compare_url "$COMPARE_URL"
96+
97+
exit 0

0 commit comments

Comments
 (0)