Skip to content

Commit c0fdf88

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

2 files changed

Lines changed: 241 additions & 0 deletions

File tree

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
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+
# - No GitHub PAT: pushes and `gh pr create` use the job's built-in GITHUB_TOKEN (see permissions below).
6+
# - Uses actions/cache to remember the last synced upstream commit for that path.
7+
# - First successful run bootstraps cache only (no agent); the next drift triggers the agent.
8+
#
9+
# Test on your PR (same repo only; fork PRs are ignored):
10+
# 1. Create a repo label named `test-upstream-sync` (Settings -> Labels) if it does not exist.
11+
# 2. Open your PR, add the `test-upstream-sync` label — this workflow runs once (labeled event).
12+
# 3. PR runs use a separate Actions cache key so they do not overwrite the mainline upstream SHA.
13+
# 4. Remove the label when done (optional).
14+
#
15+
# Manual run: Actions -> "Sync Vercel detect-agent upstream" -> Run workflow (pick your branch to test workflow_dispatch changes).
16+
17+
name: Sync Vercel detect-agent upstream
18+
19+
on:
20+
schedule:
21+
# Daily 14:05 UTC — adjust as you like
22+
- cron: "5 14 * * *"
23+
workflow_dispatch:
24+
pull_request:
25+
types: [labeled]
26+
27+
concurrency:
28+
group: sync-vercel-detect-agent-${{ github.ref }}
29+
cancel-in-progress: false
30+
31+
jobs:
32+
sync:
33+
if: >-
34+
github.event_name != 'pull_request' ||
35+
(github.event_name == 'pull_request' &&
36+
github.event.label.name == 'test-upstream-sync' &&
37+
github.event.pull_request.head.repo.full_name == github.repository)
38+
runs-on: ubuntu-latest
39+
permissions:
40+
contents: write
41+
pull-requests: write
42+
env:
43+
UPSTREAM_STATE_DIR: ${{ runner.temp }}/upstream-sync-state
44+
UPSTREAM_VERCEL_MIRROR: ${{ runner.temp }}/vercel.git
45+
UPSTREAM_DIFF_FILE: ${{ runner.temp }}/vercel-detect-agent.diff
46+
steps:
47+
- uses: actions/checkout@v4
48+
with:
49+
fetch-depth: 0
50+
persist-credentials: true
51+
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
52+
53+
- uses: actions/cache@v4
54+
id: vercel_mirror_cache
55+
with:
56+
path: ${{ runner.temp }}/vercel.git
57+
key: vercel-github-bare-mirror-v1-${{ runner.os }}
58+
59+
- uses: actions/cache@v4
60+
id: upstream_state_cache
61+
with:
62+
path: ${{ runner.temp }}/upstream-sync-state
63+
key: vercel-detect-agent-upstream-state-v1-${{ runner.os }}-${{ github.event_name == 'pull_request' && format('pr-{0}', github.event.pull_request.number) || 'default' }}
64+
65+
- name: Compute upstream drift
66+
id: drift
67+
shell: bash
68+
run: ./scripts/vercel_upstream_drift.sh
69+
70+
- name: Configure git committer (actions bot)
71+
if: steps.drift.outputs.should_sync == 'true'
72+
run: |
73+
set -euo pipefail
74+
git config user.name "github-actions[bot]"
75+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
76+
77+
- uses: astral-sh/setup-uv@v5
78+
if: steps.drift.outputs.should_sync == 'true'
79+
with:
80+
enable-cache: true
81+
python-version: "3.9"
82+
83+
- name: Install Python deps (uv)
84+
if: steps.drift.outputs.should_sync == 'true'
85+
run: uv sync --extra dev
86+
87+
- name: Run Cursor agent (port + push + PR)
88+
id: cursor
89+
if: steps.drift.outputs.should_sync == 'true'
90+
uses: PunGrumpy/cursor-action@v1.0.0
91+
with:
92+
api-key: ${{ secrets.CURSOR_API_KEY }}
93+
model: composer-2
94+
working-directory: .
95+
permissions: read-write
96+
timeout: "3600"
97+
prompt: |
98+
You are syncing this repository's Python port of Vercel's `packages/detect-agent` with upstream changes.
99+
100+
Upstream facts:
101+
- merge_base: ${{ steps.drift.outputs.merge_base }}
102+
- last_recorded_upstream_tip: ${{ steps.drift.outputs.last_recorded }}
103+
- new_upstream_tip: ${{ steps.drift.outputs.new_sha }}
104+
- compare URL: ${{ steps.drift.outputs.compare_url }}
105+
106+
Read the unified diff from this absolute path. Do not `git add` or commit that path; it is CI scratch only:
107+
${{ steps.drift.outputs.diff_path }}
108+
109+
Your job, in order:
110+
1. `git fetch origin && git checkout main && git pull --ff-only origin main`
111+
2. Create branch `sync/vercel-detect-agent-${{ steps.drift.outputs.branch_slug }}` (delete/recreate the local branch if it already exists).
112+
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.
113+
4. Update `README.md` / `CHANGELOG.md` here if upstream docs/changelog warrant it.
114+
5. Run: `uv sync --extra dev && uv run pytest && uv run ruff check . && uv run ruff format --check .`
115+
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).
116+
7. `git push -u origin HEAD`
117+
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 ). Use the default Actions token already in the environment (`GITHUB_TOKEN`); do not use a PAT or `gh auth login`.
118+
119+
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.
120+
121+
- name: Record synced upstream SHA
122+
if: success() && steps.drift.outputs.should_sync == 'true'
123+
shell: bash
124+
env:
125+
NEW_SHA: ${{ steps.drift.outputs.new_sha }}
126+
run: |
127+
set -euo pipefail
128+
mkdir -p "${UPSTREAM_STATE_DIR}"
129+
printf '%s\n' "${NEW_SHA}" >"${UPSTREAM_STATE_DIR}/last_packages_detect_agent_commit.txt"
130+
131+
- name: Job summary
132+
if: always()
133+
shell: bash
134+
run: |
135+
set -euo pipefail
136+
{
137+
echo "## Upstream sync"
138+
echo "- should_sync: ${{ steps.drift.outputs.should_sync }}"
139+
echo "- new_sha: ${{ steps.drift.outputs.new_sha }}"
140+
echo "- compare: ${{ steps.drift.outputs.compare_url }}"
141+
} >>"${GITHUB_STEP_SUMMARY}"
142+
if [[ "${{ steps.drift.outputs.should_sync }}" == "true" ]]; then
143+
echo "- cursor exit: ${{ steps.cursor.outputs.exit-code }}" >>"${GITHUB_STEP_SUMMARY}"
144+
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)