-
Notifications
You must be signed in to change notification settings - Fork 0
241 lines (209 loc) · 9.1 KB
/
contract-check.yml
File metadata and controls
241 lines (209 loc) · 9.1 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
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
name: API Contract Check
on:
pull_request:
branches: [ main ]
paths:
- '**.go'
- 'go.mod'
- 'go.sum'
- 'modules/**/go.mod'
- 'modules/**/go.sum'
permissions:
contents: read
pull-requests: write
actions: read
env:
GO_VERSION: '^1.26'
jobs:
contract-check:
name: API Contract Check
runs-on: ubuntu-latest
steps:
- name: Checkout PR code
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v6
with:
go-version: ${{ env.GO_VERSION }}
check-latest: true
cache: true
- name: Build modcli
run: |
cd cmd/modcli
go build -o modcli
- name: Extract contracts from main branch
run: |
git checkout origin/main
mkdir -p artifacts/contracts/main
# Extract core framework contract
./cmd/modcli/modcli contract extract . -o artifacts/contracts/main/core.json
# Extract contracts for all modules
for module_dir in modules/*/; do
module_name=$(basename "$module_dir")
if [ -f "$module_dir/go.mod" ]; then
echo "Extracting contract for module: $module_name"
./cmd/modcli/modcli contract extract "./$module_dir" -o "artifacts/contracts/main/${module_name}.json" || echo "Failed to extract $module_name"
fi
done
- name: Checkout PR branch
run: |
git checkout ${{ github.event.pull_request.head.sha }}
- name: Extract contracts from PR branch
run: |
mkdir -p artifacts/contracts/pr
# Extract core framework contract
./cmd/modcli/modcli contract extract . -o artifacts/contracts/pr/core.json
# Extract contracts for all modules
for module_dir in modules/*/; do
module_name=$(basename "$module_dir")
if [ -f "$module_dir/go.mod" ]; then
echo "Extracting contract for module: $module_name"
./cmd/modcli/modcli contract extract "./$module_dir" -o "artifacts/contracts/pr/${module_name}.json" || echo "Failed to extract $module_name"
fi
done
- name: Compare contracts and generate diffs
id: contract-diff
run: |
mkdir -p artifacts/diffs
breaking_changes=false
has_changes=false
# Compare core framework
if [ -f "artifacts/contracts/main/core.json" ] && [ -f "artifacts/contracts/pr/core.json" ]; then
echo "Comparing core framework contract..."
if ./cmd/modcli/modcli contract compare artifacts/contracts/main/core.json artifacts/contracts/pr/core.json -o artifacts/diffs/core.json --format=markdown > artifacts/diffs/core.md 2>&1; then
if [ -s "artifacts/diffs/core.md" ]; then
echo "Core framework: Changes detected (no breaking changes)"
has_changes=true
else
echo "Core framework: No changes"
fi
else
echo "Core framework: Breaking changes detected!"
breaking_changes=true
has_changes=true
fi
fi
# Compare all modules
for module_dir in modules/*/; do
module_name=$(basename "$module_dir")
if [ -f "artifacts/contracts/main/${module_name}.json" ] && [ -f "artifacts/contracts/pr/${module_name}.json" ]; then
echo "Comparing module: $module_name"
if ./cmd/modcli/modcli contract compare "artifacts/contracts/main/${module_name}.json" "artifacts/contracts/pr/${module_name}.json" -o "artifacts/diffs/${module_name}.json" --format=markdown > "artifacts/diffs/${module_name}.md" 2>&1; then
if [ -s "artifacts/diffs/${module_name}.md" ]; then
echo "Module $module_name: Changes detected (no breaking changes)"
has_changes=true
else
echo "Module $module_name: No changes"
fi
else
echo "Module $module_name: Breaking changes detected!"
breaking_changes=true
has_changes=true
fi
fi
done
echo "breaking_changes=$breaking_changes" >> $GITHUB_OUTPUT
echo "has_changes=$has_changes" >> $GITHUB_OUTPUT
- name: Upload contract artifacts
uses: actions/upload-artifact@v7
if: always()
with:
name: api-contracts-${{ github.run_number }}
path: artifacts/
retention-days: 30
- name: Generate contract summary
id: summary
if: steps.contract-diff.outputs.has_changes == 'true'
run: |
echo "## 📋 API Contract Changes Summary" > contract-summary.md
echo "" >> contract-summary.md
if [ "${{ steps.contract-diff.outputs.breaking_changes }}" == "true" ]; then
echo "⚠️ **WARNING: This PR contains breaking API changes!**" >> contract-summary.md
echo "" >> contract-summary.md
else
echo "✅ **No breaking changes detected - only additions and non-breaking modifications**" >> contract-summary.md
echo "" >> contract-summary.md
fi
echo "### Changed Components:" >> contract-summary.md
echo "" >> contract-summary.md
# Add core framework diff if it exists
if [ -f "artifacts/diffs/core.md" ] && [ -s "artifacts/diffs/core.md" ]; then
echo "#### Core Framework" >> contract-summary.md
echo "" >> contract-summary.md
cat artifacts/diffs/core.md >> contract-summary.md
echo "" >> contract-summary.md
fi
# Add module diffs
for diff_file in artifacts/diffs/*.md; do
if [ -f "$diff_file" ] && [ -s "$diff_file" ]; then
module_name=$(basename "$diff_file" .md)
if [ "$module_name" != "core" ]; then
echo "#### Module: $module_name" >> contract-summary.md
echo "" >> contract-summary.md
cat "$diff_file" >> contract-summary.md
echo "" >> contract-summary.md
fi
fi
done
echo "### Artifacts" >> contract-summary.md
echo "" >> contract-summary.md
echo "📁 Full contract diffs and JSON artifacts are available in the [workflow artifacts](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})." >> contract-summary.md
- name: Comment PR with contract changes
if: steps.contract-diff.outputs.has_changes == 'true'
uses: actions/github-script@v9
with:
script: |
const fs = require('fs');
const path = 'contract-summary.md';
if (fs.existsSync(path)) {
const summary = fs.readFileSync(path, 'utf8');
// Find existing contract comment
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.pull_request.number,
});
const botComment = comments.find(comment =>
comment.user.type === 'Bot' &&
comment.body.includes('📋 API Contract Changes Summary')
);
if (botComment) {
// Update existing comment
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: botComment.id,
body: summary
});
} else {
// Create new comment
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.pull_request.number,
body: summary
});
}
}
- name: Fail if breaking changes
if: steps.contract-diff.outputs.breaking_changes == 'true'
run: |
echo "::error::Breaking API changes detected! Please review the contract diff and ensure this is intentional."
echo "If this is a major version change, consider:"
echo "1. Updating version numbers appropriately"
echo "2. Adding migration guides"
echo "3. Updating documentation"
echo "4. Communicating breaking changes to users"
exit 1
# Success job that only runs if contract check passes or no changes
contract-passed:
name: API Contract Passed
runs-on: ubuntu-latest
needs: contract-check
if: always() && (needs.contract-check.result == 'success' || needs.contract-check.outputs.has_changes != 'true')
steps:
- name: Contract check passed
run: |
echo "✅ API contract check passed - no breaking changes detected"