Repository navigation
187 lines (170 loc) · 7.8 KB
/
Copy pathapi-sync.yml
File metadata and controls
187 lines (170 loc) · 7.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
name: API Sync
on:
repository_dispatch:
types: [api-sync]
permissions:
contents: write
pull-requests: write
id-token: write
jobs:
sync:
name: Sync CLI with API changes
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- name: Fetch API sync data
run: |
mkdir -p /tmp/api-sync
git fetch origin api-sync-data
git show origin/api-sync-data:.api-sync/changelog.md > /tmp/api-sync/changelog.md
echo "=== Changelog ==="
cat /tmp/api-sync/changelog.md
- name: Check for existing api-sync PR
id: check-pr
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
PR_NUMBER=$(gh pr list --head api-sync --json number --jq '.[0].number // empty')
if [ -n "$PR_NUMBER" ]; then
echo "existing_pr=$PR_NUMBER" >> $GITHUB_OUTPUT
echo "Found existing api-sync PR: #$PR_NUMBER"
else
echo "existing_pr=" >> $GITHUB_OUTPUT
echo "No existing api-sync PR found"
fi
- name: Create or checkout api-sync branch
run: |
git fetch origin api-sync 2>/dev/null || true
if git rev-parse --verify origin/api-sync >/dev/null 2>&1; then
git checkout api-sync
git reset --hard origin/main
else
git checkout -b api-sync
fi
- name: Install dependencies
run: bun install --frozen-lockfile
# The entire "what changed, and can this repo express it without a
# human" decision is a pure script: scripts/api-sync/generate.ts. It
# parses the changelog, classifies every change as either mechanically
# applicable (an additive request-body field on a known resource path)
# or needs-human, applies only the former, and bumps package.json's
# version. No LLM, no best-guessing: an unrecognized changelog shape
# makes the parser throw and this step fails the run.
- name: Run deterministic generator
id: generate
run: |
set -o pipefail
bun scripts/api-sync/generate.ts \
--changelog /tmp/api-sync/changelog.md \
--repo-root . \
| tee /tmp/api-sync/generate.log
SUMMARY_JSON=$(grep '^SUMMARY_JSON:' /tmp/api-sync/generate.log | tail -1 | sed 's/^SUMMARY_JSON://')
echo "$SUMMARY_JSON" > /tmp/api-sync/summary.json
echo "summary_path=/tmp/api-sync/summary.json" >> "$GITHUB_OUTPUT"
echo "has_changes=$(jq -r '.hasChanges' /tmp/api-sync/summary.json)" >> "$GITHUB_OUTPUT"
echo "can_automerge=$(jq -r '.canAutoMerge' /tmp/api-sync/summary.json)" >> "$GITHUB_OUTPUT"
echo "needs_human_count=$(jq -r '.needsHumanCount' /tmp/api-sync/summary.json)" >> "$GITHUB_OUTPUT"
echo "bump_type=$(jq -r '.bumpType' /tmp/api-sync/summary.json)" >> "$GITHUB_OUTPUT"
# A changelog can contain ONLY changes the generator cannot express
# (new endpoint, removed field, enum-only, etc.) with nothing
# mechanically applicable. There is then no code diff to commit and no
# PR to open — but staying silent would bury a real API change a human
# needs to see, so this opens an issue instead.
- name: Open an issue when nothing was applicable but something needs a human
if: steps.generate.outputs.has_changes == 'false' && steps.generate.outputs.needs_human_count != '0'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
{
echo "The latest API changelog contains changes this repo's deterministic"
echo "api-sync generator (\`scripts/api-sync/\`) cannot express. Nothing was"
echo "applied or committed. A human needs to decide the CLI surface for:"
echo
jq -r '.needsHuman[] | "- " + .' /tmp/api-sync/summary.json
} > /tmp/api-sync/issue-body.md
gh issue create \
--title "api-sync: API changes need human review (no automatic changes applied)" \
--body-file /tmp/api-sync/issue-body.md \
--label api-sync
- name: Run CI checks against the generated changes
if: steps.generate.outputs.has_changes == 'true'
run: |
bun run typecheck
bun run lint
bun test
bun run build
- name: Commit and push
id: commit
if: steps.generate.outputs.has_changes == 'true'
run: |
git remote set-url origin "https://x-access-token:${{ secrets.SDK_SYNC_PAT }}@github.com/${{ github.repository }}.git"
git checkout -- .github/workflows/ 2>/dev/null || true
git add -A
git reset HEAD .github/workflows/ 2>/dev/null || true
if git diff --staged --quiet; then
echo "No changes to commit"
echo "committed=false" >> "$GITHUB_OUTPUT"
exit 0
fi
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git commit -m "feat: sync CLI with API changes"
git push --force-with-lease origin api-sync
echo "committed=true" >> "$GITHUB_OUTPUT"
- name: Write PR body
id: pr-body
if: steps.commit.outputs.committed == 'true'
run: |
{
echo "Automated, script-generated CLI update from API changes."
echo "No LLM was involved in producing this diff — see \`scripts/api-sync/\` in this repo."
echo
echo "### Applied automatically"
jq -r '.applied[] | "- " + .' /tmp/api-sync/summary.json
NEEDS_HUMAN_COUNT=$(jq -r '.needsHumanCount' /tmp/api-sync/summary.json)
if [ "$NEEDS_HUMAN_COUNT" != "0" ]; then
echo
echo "### Needs a human — NOT applied, NOT auto-merged"
jq -r '.needsHuman[] | "- " + .' /tmp/api-sync/summary.json
fi
echo
echo "Version bump: \`$(jq -r '.bumpType' /tmp/api-sync/summary.json)\`"
} > /tmp/api-sync/pr-body.md
- name: Create or update PR
id: pr
if: steps.commit.outputs.committed == 'true'
env:
GH_TOKEN: ${{ secrets.SDK_SYNC_PAT }}
run: |
EXISTING_PR="${{ steps.check-pr.outputs.existing_pr }}"
if [ -n "$EXISTING_PR" ]; then
echo "Updating existing PR #$EXISTING_PR"
gh pr comment "$EXISTING_PR" --body-file /tmp/api-sync/pr-body.md
PR_NUMBER="$EXISTING_PR"
else
gh pr create \
--title "feat: sync CLI with API changes" \
--body-file /tmp/api-sync/pr-body.md \
--base main \
--head api-sync \
--label api-sync
PR_NUMBER=$(gh pr list --head api-sync --json number --jq '.[0].number')
fi
echo "pr_number=$PR_NUMBER" >> "$GITHUB_OUTPUT"
# Auto-merge only when the generator reports nothing was left for a
# human to decide. A needs-human PR stays open for manual review and is
# never auto-merged — this is the honest-failure path, not an error.
- name: Auto-merge or flag for human review
if: steps.commit.outputs.committed == 'true'
env:
GH_TOKEN: ${{ secrets.SDK_SYNC_PAT }}
run: |
PR_NUMBER="${{ steps.pr.outputs.pr_number }}"
if [ "${{ steps.generate.outputs.can_automerge }}" = "true" ]; then
gh pr merge "$PR_NUMBER" --auto --squash
else
gh pr comment "$PR_NUMBER" --body "Auto-merge NOT enabled: this changelog contains ${{ steps.generate.outputs.needs_human_count }} change(s) the generator cannot express (see the needs-human section above). A human needs to review and merge this manually."
fi