Skip to content

Commit 4beecd4

Browse files
committed
Docs changes workflow
1 parent 5b2d163 commit 4beecd4

5 files changed

Lines changed: 620 additions & 37 deletions

File tree

.github/workflows/docs-preview.yml

Lines changed: 328 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,328 @@
1+
# yamllint disable rule:line-length
2+
---
3+
name: Docs preview
4+
5+
# Renders the generated-docs diff (config docs, OAS/x-tyk-gateway, swagger)
6+
# for a PR in a source repo, posts it as a sticky PR comment, and requests
7+
# an ADVISORY review from a PM team. Never a required check.
8+
#
9+
# Generation steps mirror the tyk-docs sync:
10+
# https://github.com/TykTechnologies/exp/blob/main/.github/workflows/tyk-docs.yml
11+
# Keep them in sync until they are factored into shared composite actions.
12+
13+
on:
14+
workflow_call:
15+
inputs:
16+
component:
17+
description: 'Generator set to run: gateway, dashboard, pump, mdcb or portal'
18+
required: true
19+
type: string
20+
docs_paths:
21+
description: 'Newline-separated globs of doc-bearing paths; the workflow no-ops unless the PR touches one'
22+
required: true
23+
type: string
24+
pm_team:
25+
description: 'GitHub team slug to request as advisory reviewer (empty = comment only)'
26+
required: false
27+
default: ''
28+
type: string
29+
comment_marker:
30+
description: 'Hidden marker identifying the sticky PR comment'
31+
required: false
32+
default: '<!-- docs-preview -->'
33+
type: string
34+
secrets:
35+
PROBE_APP_ID:
36+
required: true
37+
PROBE_APP_PRIVATE_KEY:
38+
required: true
39+
40+
permissions:
41+
contents: read
42+
pull-requests: write
43+
44+
jobs:
45+
detect:
46+
name: Detect docs-bearing changes
47+
# Fork PRs: secrets are unavailable and the config generator needs remote branch names.
48+
if: github.event.pull_request.head.repo.full_name == github.repository
49+
runs-on: ubuntu-latest
50+
outputs:
51+
docs_affected: ${{ steps.match.outputs.docs_affected }}
52+
steps:
53+
- name: Checkout
54+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
55+
with:
56+
fetch-depth: 0
57+
58+
- name: Match changed files against docs paths
59+
id: match
60+
env:
61+
DOCS_PATHS: ${{ inputs.docs_paths }}
62+
BASE_REF: ${{ github.event.pull_request.base.ref }}
63+
run: |
64+
affected=false
65+
changed=$(git diff --name-only "origin/${BASE_REF}...HEAD")
66+
while IFS= read -r file; do
67+
[ -z "$file" ] && continue
68+
while IFS= read -r pat; do
69+
[ -z "$pat" ] && continue
70+
# bash [[ == ]] pattern matching: * matches across '/', so ** and * behave the same here
71+
if [[ "$file" == $pat ]]; then
72+
echo "docs-bearing change: $file (pattern: $pat)"
73+
affected=true
74+
break 2
75+
fi
76+
done <<< "$DOCS_PATHS"
77+
done <<< "$changed"
78+
echo "docs_affected=$affected" >> "$GITHUB_OUTPUT"
79+
80+
preview:
81+
name: Generate docs preview
82+
needs: [detect]
83+
if: needs.detect.outputs.docs_affected == 'true'
84+
runs-on: ubuntu-latest
85+
env:
86+
BASE_REF: ${{ github.event.pull_request.base.ref }}
87+
HEAD_REF: ${{ github.event.pull_request.head.ref }}
88+
steps:
89+
- name: Generate GitHub App token
90+
id: app-token
91+
uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1
92+
with:
93+
app-id: ${{ secrets.PROBE_APP_ID }}
94+
private-key: ${{ secrets.PROBE_APP_PRIVATE_KEY }}
95+
owner: TykTechnologies
96+
97+
- name: Checkout head
98+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
99+
with:
100+
ref: ${{ github.event.pull_request.head.sha }}
101+
path: head
102+
fetch-depth: 1
103+
104+
- name: Checkout base
105+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
106+
with:
107+
ref: ${{ github.event.pull_request.base.ref }}
108+
path: base
109+
fetch-depth: 1
110+
111+
- name: Prepare output directories
112+
run: mkdir -p out-base out-head
113+
114+
- name: Set up Go
115+
if: inputs.component == 'gateway'
116+
uses: actions/setup-go@41dfa10bad2bb2ae585af6ee5bb4d7d973ad74ed # v5.1.0
117+
with:
118+
go-version: '1.21'
119+
120+
- name: Checkout exp (schema-gen + cleanup script)
121+
if: inputs.component == 'gateway'
122+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
123+
with:
124+
repository: TykTechnologies/exp
125+
path: exp
126+
fetch-depth: 1
127+
128+
- name: Install schema-gen
129+
if: inputs.component == 'gateway'
130+
run: cd exp/cmd/schema-gen && GOBIN=/usr/local/bin go install .
131+
132+
- name: Generate x-tyk-gateway docs (base and head)
133+
if: inputs.component == 'gateway'
134+
run: |
135+
for side in base head; do
136+
(
137+
cd "$side/apidef/oas"
138+
schema-gen extract -o - | schema-gen markdown -i - \
139+
--root XTykAPIGateway \
140+
--skip "OldOAS,APIDef,OAS,TykExtensionConfigParams,ServerRegenerationConfig" \
141+
--title "## Tyk vendor extension reference" \
142+
--replace "tyktime.ReadableDuration=string,model.ObjectID=string,apidef.MiddlewareDriver=string,apidef.IdExtractorSource=string,apidef.IdExtractorType=string,apidef.RequestInputType=string,apidef.AuthTypeEnum=string,[]osin.AuthorizeRequestType=[]string" \
143+
--heading-format "### **%s**" \
144+
--trim "Tyk classic API definition" \
145+
-o "$GITHUB_WORKSPACE/out-$side/x-tyk-gateway.mdx"
146+
)
147+
(
148+
cd exp
149+
python3 scripts/remove-oas-dublicates/index.py --input-file "$GITHUB_WORKSPACE/out-$side/x-tyk-gateway.mdx"
150+
)
151+
done
152+
153+
- name: Generate gateway swagger (base and head)
154+
if: inputs.component == 'gateway'
155+
run: |
156+
massage() {
157+
cp "$1" ./swagger-modified.yml
158+
sed -i "s|'https://raw.githubusercontent.com/TykTechnologies/tyk/refs/heads/master/apidef/oas/schema/3.0.json'|'#/components/schemas/OpenAPI3Schema'|g" ./swagger-modified.yml
159+
sed -i "s|https://raw.githubusercontent.com/TykTechnologies/tyk/refs/heads/master/apidef/oas/schema/3.0.json|'#/components/schemas/OpenAPI3Schema'|g" ./swagger-modified.yml
160+
awk '
161+
{print}
162+
/schemas:/ && !done {
163+
print " OpenAPI3Schema:"
164+
print " type: object"
165+
print " additionalProperties: true"
166+
done=1
167+
}
168+
' ./swagger-modified.yml > "$2"
169+
}
170+
for side in base head; do
171+
massage "$side/swagger.yml" "out-$side/gateway-swagger.yml"
172+
done
173+
174+
- name: Generate dashboard docs (base and head)
175+
if: inputs.component == 'dashboard'
176+
run: |
177+
massage() {
178+
cp "$1" ./swagger-modified.yml
179+
sed -i "s|'https://raw.githubusercontent.com/TykTechnologies/tyk/refs/heads/master/apidef/oas/schema/3.0.json'|'#/components/schemas/OpenAPI3Schema'|g" ./swagger-modified.yml
180+
sed -i "s|https://raw.githubusercontent.com/TykTechnologies/tyk/refs/heads/master/apidef/oas/schema/3.0.json|'#/components/schemas/OpenAPI3Schema'|g" ./swagger-modified.yml
181+
awk '
182+
{print}
183+
/schemas:/ && !done {
184+
print " OpenAPI3Schema:"
185+
print " type: object"
186+
print " additionalProperties: true"
187+
done=1
188+
}
189+
' ./swagger-modified.yml > "$2"
190+
}
191+
for side in base head; do
192+
massage "$side/swagger.yml" "out-$side/dashboard-swagger.yml"
193+
cp "$side/swagger-admin.yml" "out-$side/dashboard-admin-swagger.yml"
194+
cp "$side/docs/opa/opa-rules.md" "out-$side/opa-rules.mdx"
195+
done
196+
197+
- name: Generate MDCB swagger (base and head)
198+
if: inputs.component == 'mdcb'
199+
run: |
200+
for side in base head; do
201+
cp "$side/swagger.yml" "out-$side/mdcb-swagger.yml"
202+
done
203+
204+
- name: Generate portal swagger (base and head)
205+
if: inputs.component == 'portal'
206+
run: |
207+
for side in base head; do
208+
cp "$side/docs/api/enterprise-developer-portal-swagger.yaml" "out-$side/enterprise-developer-portal-swagger.yaml"
209+
done
210+
211+
- name: Checkout config info generator
212+
if: contains(fromJSON('["gateway","dashboard","pump","mdcb"]'), inputs.component)
213+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
214+
with:
215+
repository: TykTechnologies/tyk-config-info-generator
216+
token: ${{ steps.app-token.outputs.token }}
217+
path: tyk-config-info-generator
218+
ref: main
219+
220+
- name: Generate config docs (base and head)
221+
if: contains(fromJSON('["gateway","dashboard","pump","mdcb"]'), inputs.component)
222+
working-directory: ./tyk-config-info-generator/src
223+
run: |
224+
repo="${{ inputs.component }}"
225+
for side in base head; do
226+
if [ "$side" = "base" ]; then branch="$BASE_REF"; else branch="$HEAD_REF"; fi
227+
sudo TOKEN=${{ steps.app-token.outputs.token }} node app.js "$repo:$branch"
228+
cp "/node/home/tyk-config-info-generator/info/$branch/$repo.md" "$GITHUB_WORKSPACE/out-$side/$repo-config.mdx"
229+
done
230+
231+
- name: Diff rendered docs
232+
id: diff
233+
run: |
234+
set +e
235+
diff -ruN out-base out-head > docs.diff
236+
rc=$?
237+
set -e
238+
if [ "$rc" -gt 1 ]; then
239+
echo "diff failed with exit code $rc" >&2
240+
exit "$rc"
241+
fi
242+
if [ "$rc" -eq 1 ]; then
243+
echo "changed=true" >> "$GITHUB_OUTPUT"
244+
else
245+
echo "changed=false" >> "$GITHUB_OUTPUT"
246+
fi
247+
wc -c docs.diff
248+
249+
- name: Upload full diff as artifact
250+
if: steps.diff.outputs.changed == 'true'
251+
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
252+
with:
253+
name: docs-preview-diff
254+
path: docs.diff
255+
256+
- name: Build comment body
257+
env:
258+
MARKER: ${{ inputs.comment_marker }}
259+
PM_TEAM: ${{ inputs.pm_team }}
260+
COMPONENT: ${{ inputs.component }}
261+
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
262+
CHANGED: ${{ steps.diff.outputs.changed }}
263+
run: |
264+
if [ "$CHANGED" != "true" ]; then
265+
{
266+
echo "$MARKER"
267+
echo "## 📚 Documentation preview"
268+
echo
269+
echo "No documentation impact — the latest changes do not alter generated docs."
270+
} > comment-body.md
271+
exit 0
272+
fi
273+
limit=60000
274+
size=$(wc -c < docs.diff)
275+
{
276+
echo "$MARKER"
277+
echo "## 📚 Documentation preview"
278+
echo
279+
echo "This PR changes generated documentation (component: \`$COMPONENT\`)."
280+
if [ -n "$PM_TEAM" ]; then
281+
echo
282+
echo "cc @TykTechnologies/$PM_TEAM — advisory review requested; this does **not** block merging."
283+
fi
284+
echo
285+
echo '<details><summary>Rendered docs diff</summary>'
286+
echo
287+
echo '````diff'
288+
head -c "$limit" docs.diff
289+
echo
290+
echo '````'
291+
echo
292+
echo '</details>'
293+
if [ "$size" -gt "$limit" ]; then
294+
echo
295+
echo "_Diff truncated (${size} bytes total). Full diff: \`docs-preview-diff\` artifact on the [workflow run]($RUN_URL)._"
296+
fi
297+
} > comment-body.md
298+
299+
- name: Find existing preview comment
300+
id: fc
301+
uses: peter-evans/find-comment@a54c31d7fa095754bfef525c0c8e5e5674c4b4b1 # v2
302+
with:
303+
issue-number: ${{ github.event.pull_request.number }}
304+
body-includes: ${{ inputs.comment_marker }}
305+
306+
# Post when there is a docs diff; when there is none, only update an
307+
# existing comment (a PR that never had docs impact stays silent).
308+
- name: Create or update preview comment
309+
if: steps.diff.outputs.changed == 'true' || steps.fc.outputs.comment-id != ''
310+
uses: peter-evans/create-or-update-comment@23ff15729ef2fc348714a3bb66d2f655ca9066f2 # v3
311+
with:
312+
comment-id: ${{ steps.fc.outputs.comment-id }}
313+
issue-number: ${{ github.event.pull_request.number }}
314+
body-path: comment-body.md
315+
edit-mode: replace
316+
317+
# Advisory only, requested once per PR: on the first push that produces
318+
# a docs diff (no pre-existing sticky comment). Uses the App token —
319+
# GITHUB_TOKEN cannot request org team reviews.
320+
- name: Request PM team review (advisory)
321+
if: steps.diff.outputs.changed == 'true' && steps.fc.outputs.comment-id == '' && inputs.pm_team != ''
322+
env:
323+
GH_TOKEN: ${{ steps.app-token.outputs.token }}
324+
PM_TEAM: ${{ inputs.pm_team }}
325+
run: |
326+
gh api "repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/requested_reviewers" \
327+
-f "team_reviewers[]=$PM_TEAM" \
328+
|| echo "::warning::Could not request review from team '$PM_TEAM' — check the slug and that the PROBE App can write PRs in this repo"

0 commit comments

Comments
 (0)