microsoft/TypeAgent

Public

mirrored from https://github.com/microsoft/TypeAgentAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
cf6e13e178badf4218ffc6802de65e2d00ec88f0

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

.github/workflows/docs-generate.yml

309lines · modecode

1# Copyright (c) Microsoft Corporation.
2# Licensed under the MIT License.
3
4# Daily regeneration of package-level READMEs under ts/packages/**.
5#
6# Detects packages whose source has changed since the last successful
7# scheduled run (tracked by the `docs-bot/last-run` lightweight tag),
8# rebuilds the AUTOGEN region in each affected README, runs link
9# validation + Trademarks-block guard before writing, and opens a
10# single batched PR. Prior open bot PRs are closed with --delete-branch
11# so only one is ever live at a time.
12#
13# Authoring of the Overview section uses Azure OpenAI via
14# packages/aiclient. Everything else is deterministic.
15#
16# Required repository configuration:
17# variables:
18# DOCS_BOT_APP_ID GitHub App that opens the PR
19# secrets:
20# DOCS_BOT_APP_PRIVATE_KEY Private key for the GitHub App
21# AZURE_OPENAI_ENDPOINT Azure OpenAI endpoint URL
22# AZURE_OPENAI_API_KEY Azure OpenAI key (or wire OIDC instead)
23#
24# The workflow is read-only and emits no PR until those are provisioned;
25# it simply prints what it would have done.
26
27name: docs-generate
28
29on:
30 schedule:
31 # Daily at 08:00 UTC = 01:00 PST. Low-traffic window so churn from
32 # this PR does not collide with other automated PRs.
33 - cron: "0 8 * * *"
34 workflow_dispatch:
35 inputs:
36 dry-run:
37 description: "Dry run — analyse and render only, don't write or open PR"
38 type: boolean
39 default: false
40 packages:
41 description: "Comma-separated package names to regenerate (overrides change detection)"
42 type: string
43 default: ""
44 since:
45 description: "Override the watermark — git ref to diff against (e.g. main, HEAD~10)"
46 type: string
47 default: ""
48 llm:
49 description: "Use Azure OpenAI to author the Overview section (skeleton-only when off)"
50 type: boolean
51 default: true
52 max-packages:
53 description: "Per-run cap on packages touched (defaults to 25)"
54 type: string
55 default: "25"
56
57# Never cancel an in-flight docs-autogen run; let it finish and let the
58# next scheduled run supersede the resulting PR if necessary.
59concurrency:
60 group: ${{ github.workflow }}
61 cancel-in-progress: false
62
63permissions:
64 contents: write
65 pull-requests: write
66
67jobs:
68 regenerate:
69 runs-on: ubuntu-latest
70
71 steps:
72 - uses: actions/checkout@v4
73 with:
74 fetch-depth: 0
75
76 - uses: pnpm/action-setup@v4
77 name: Install pnpm
78 with:
79 package_json_file: ts/package.json
80
81 - uses: actions/setup-node@v4
82 with:
83 node-version: 22
84 cache: "pnpm"
85 cache-dependency-path: ts/pnpm-lock.yaml
86
87 - name: Generate GitHub App token
88 id: app-token
89 uses: actions/create-github-app-token@v1
90 with:
91 app-id: ${{ vars.DOCS_BOT_APP_ID }}
92 private-key: ${{ secrets.DOCS_BOT_APP_PRIVATE_KEY }}
93
94 - name: Install ts dependencies
95 working-directory: ts
96 run: |
97 corepack enable
98 pnpm install --frozen-lockfile
99
100 - name: Build docs-autogen tool
101 working-directory: ts
102 run: |
103 pnpm --filter aiclient build
104 pnpm --filter @typeagent/docs-autogen build
105
106 # Compose the CLI flags from dispatch inputs once so each
107 # subsequent step uses the same set. `--write` is omitted on
108 # dry-run; otherwise the CLI writes to disk in-place and we
109 # detect the diff afterwards.
110 - name: Compose CLI flags
111 id: flags
112 env:
113 INPUT_PACKAGES: ${{ inputs.packages }}
114 INPUT_SINCE: ${{ inputs.since }}
115 INPUT_LLM: ${{ inputs.llm }}
116 INPUT_MAX_PACKAGES: ${{ inputs.max-packages }}
117 INPUT_DRY_RUN: ${{ inputs.dry-run }}
118 run: |
119 FLAGS="--render"
120 if [ "$INPUT_DRY_RUN" != "true" ]; then
121 FLAGS="$FLAGS --write"
122 else
123 FLAGS="$FLAGS --dry-run"
124 fi
125 if [ -n "$INPUT_PACKAGES" ]; then
126 # Convert comma-separated list into repeated --package flags.
127 IFS=',' read -ra PKGS <<< "$INPUT_PACKAGES"
128 for PKG in "${PKGS[@]}"; do
129 PKG_TRIMMED=$(echo "$PKG" | xargs)
130 if [ -n "$PKG_TRIMMED" ]; then
131 FLAGS="$FLAGS --package $PKG_TRIMMED"
132 fi
133 done
134 fi
135 if [ -n "$INPUT_SINCE" ]; then
136 FLAGS="$FLAGS --since $INPUT_SINCE"
137 fi
138 if [ "$INPUT_LLM" = "true" ]; then
139 FLAGS="$FLAGS --llm"
140 fi
141 if [ -n "$INPUT_MAX_PACKAGES" ]; then
142 FLAGS="$FLAGS --max-packages $INPUT_MAX_PACKAGES"
143 fi
144 echo "flags=$FLAGS" >> "$GITHUB_OUTPUT"
145 echo "Composed CLI flags: $FLAGS"
146
147 - name: Regenerate package READMEs
148 id: regen
149 working-directory: ts
150 env:
151 AZURE_OPENAI_ENDPOINT: ${{ secrets.AZURE_OPENAI_ENDPOINT }}
152 AZURE_OPENAI_API_KEY: ${{ secrets.AZURE_OPENAI_API_KEY }}
153 DEBUG: "docs-autogen:*"
154 run: |
155 # Run with stdout captured for the job summary; stderr passes
156 # through so diagnostics are visible in the live log.
157 set -o pipefail
158 node tools/docsAutogen/dist/cli.js ${{ steps.flags.outputs.flags }} \
159 | tee /tmp/docs-autogen.log
160 echo "exit_code=$?" >> "$GITHUB_OUTPUT"
161
162 # Detect whether anything actually changed under ts/packages/**.
163 # When the CLI ran with --write, all README edits are already on
164 # disk. We rely on git status alone — if it's clean there's no PR
165 # to open, even if the CLI processed packages (footer-only or
166 # unchanged verdicts skip writes).
167 - name: Detect changes
168 id: detect
169 working-directory: ts
170 run: |
171 if git diff --quiet --exit-code -- 'packages/**/README.md'; then
172 echo "changes=false" >> "$GITHUB_OUTPUT"
173 echo "No README changes after regeneration."
174 else
175 echo "changes=true" >> "$GITHUB_OUTPUT"
176 CHANGED=$(git diff --name-only -- 'packages/**/README.md' | wc -l)
177 echo "changed_files=$CHANGED" >> "$GITHUB_OUTPUT"
178 echo "$CHANGED README(s) modified."
179 fi
180
181 # ── Create PR (and supersede prior bot PRs) ─────────────────────
182 - name: Create pull request
183 if: ${{ steps.detect.outputs.changes == 'true' && inputs.dry-run != true }}
184 env:
185 GH_TOKEN: ${{ steps.app-token.outputs.token }}
186 run: |
187 BRANCH="automated/docs-readmes-$(date +%Y%m%d)-${{ github.run_number }}"
188
189 git config user.name "github-actions[bot]"
190 git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
191
192 git checkout -b "$BRANCH"
193 # Scope strictly to ts/packages/**/README.md to avoid sweeping
194 # in any incidental working-tree noise from CI.
195 git add 'ts/packages/**/README.md'
196 CHANGED_LIST=$(git diff --cached --name-only)
197
198 git commit -m "docs: regenerate package READMEs ($(date +%Y-%m-%d))
199
200 Automated by docs-generate workflow.
201
202 ${{ steps.detect.outputs.changed_files }} file(s) updated.
203
204 Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
205 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>"
206
207 git push origin "$BRANCH"
208
209 # Close any previously-open bot PRs before opening the new
210 # one. Each daily run produces a unique branch, so without
211 # dedup the repo accumulates stacking duplicate PRs whenever
212 # yesterday's hasn't been merged. Always keep the freshest.
213 PREV_PRS=$(gh pr list \
214 --state open \
215 --search 'head:automated/docs-readmes- in:branch' \
216 --json number,headRefName \
217 --jq '.[] | select(.headRefName != "'"$BRANCH"'") | .number')
218 if [ -n "$PREV_PRS" ]; then
219 echo "Closing superseded docs-autogen PRs: $PREV_PRS"
220 for PR in $PREV_PRS; do
221 gh pr close "$PR" \
222 --delete-branch \
223 --comment "Superseded by a newer automated docs PR." \
224 || echo "::warning::Failed to close PR #$PR"
225 done
226 fi
227
228 BODY=$(cat <<PREOF
229 ## Automated package README regeneration
230
231 This PR was automatically generated by the \`docs-generate\` workflow.
232
233 ### Summary
234 - **Files updated:** ${{ steps.detect.outputs.changed_files }}
235 - **LLM mode:** ${{ inputs.llm == false && 'skeleton-only' || 'aiclient (Overview only)' }}
236 - **Per-run cap:** ${{ inputs.max-packages || '25' }} package(s)
237 - **Trigger:** ${{ github.event_name == 'schedule' && 'scheduled (daily)' || format('manual ({0})', github.actor) }}
238
239 ### Changed READMEs
240 \`\`\`
241 $CHANGED_LIST
242 \`\`\`
243
244 ### How this works
245 1. Diffs \`ts/packages/**\` source files against the last successful run
246 (tracked by the \`docs-bot/last-run\` git tag).
247 2. For each affected package, regenerates the AUTOGEN region of its
248 README — Reference section deterministically, Overview section
249 via Azure OpenAI when enabled.
250 3. Validates every generated link resolves on disk and that the
251 canonical Trademarks block survives intact; refuses to write
252 otherwise.
253 4. Skips packages whose new content differs only in the staleness
254 footer (so daily PRs don't churn unchanged docs).
255
256 ### Review checklist
257 - [ ] Sample one or two READMEs and confirm the Reference section
258 accurately describes the package.
259 - [ ] Sample one Overview and confirm it reads as contributor-grade
260 documentation (not marketing prose, no hallucinated APIs).
261 - [ ] Confirm no \`## Trademarks\` block has been altered.
262
263 See [\`ts/docs/architecture/doc-autogen.md\`](../blob/main/ts/docs/architecture/doc-autogen.md) for design details.
264 PREOF
265 )
266
267 gh pr create \
268 --base main \
269 --head "$BRANCH" \
270 --title "docs: regenerate package READMEs ($(date +%Y-%m-%d))" \
271 --body "$BODY" \
272 --label "documentation"
273
274 # Advance the watermark only on a successful scheduled run with a
275 # PR. Manual dispatches and dry-runs intentionally do not move
276 # the tag, so they remain idempotent against the daily cron.
277 - name: Advance watermark tag
278 if: ${{ github.event_name == 'schedule' && steps.detect.outputs.changes == 'true' && inputs.dry-run != true }}
279 env:
280 GH_TOKEN: ${{ steps.app-token.outputs.token }}
281 run: |
282 # Tag the commit we generated against (the original SHA before
283 # the PR branch was created), not the branch tip.
284 git tag -f docs-bot/last-run ${{ github.sha }}
285 git push origin docs-bot/last-run --force
286
287 # ── Job summary ─────────────────────────────────────────────────
288 - name: Job summary
289 if: always()
290 run: |
291 {
292 echo "## Package README regeneration"
293 echo ""
294 echo "| Metric | Value |"
295 echo "|--------|-------|"
296 echo "| Trigger | ${{ github.event_name }} |"
297 echo "| Dry run | ${{ inputs.dry-run || 'false' }} |"
298 echo "| LLM enabled | ${{ inputs.llm || 'true' }} |"
299 echo "| Per-run cap | ${{ inputs.max-packages || '25' }} |"
300 echo "| Changes detected | ${{ steps.detect.outputs.changes || 'false' }} |"
301 echo "| Files modified | ${{ steps.detect.outputs.changed_files || '0' }} |"
302 echo ""
303 if [ -f /tmp/docs-autogen.log ]; then
304 echo "### CLI output (truncated)"
305 echo '```'
306 tail -n 200 /tmp/docs-autogen.log || true
307 echo '```'
308 fi
309 } >> "$GITHUB_STEP_SUMMARY"
310