Skip to content

Commit 6c8cf38

Browse files
authored
ci(docs): add docs website automation (#1788)
* ci(docs): add docs website automation * ci(docs): harden sync-docs inputs and add script tests Route free-form workflow_dispatch inputs through env vars to avoid shell injection, switch uv install to the approved setup-uv action, and add a pytest suite for sync_docs_website.py wired into the test task.
1 parent ec197a4 commit 6c8cf38

5 files changed

Lines changed: 762 additions & 1 deletion

File tree

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
name: Publish Docs Website
5+
6+
on:
7+
workflow_dispatch:
8+
inputs:
9+
mode:
10+
description: "Fern publish mode"
11+
required: true
12+
default: preview
13+
type: choice
14+
options:
15+
- preview
16+
- production
17+
preview_id:
18+
description: "Optional Fern preview id when mode=preview"
19+
required: false
20+
type: string
21+
22+
permissions:
23+
contents: read
24+
25+
concurrency:
26+
group: docs-website
27+
cancel-in-progress: false
28+
29+
defaults:
30+
run:
31+
shell: bash
32+
33+
jobs:
34+
publish:
35+
runs-on: ubuntu-latest
36+
timeout-minutes: 15
37+
steps:
38+
- name: Checkout docs website branch
39+
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
40+
with:
41+
ref: docs-website
42+
43+
- name: Setup Node.js
44+
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
45+
with:
46+
node-version: "24"
47+
48+
- name: Install Fern CLI
49+
run: |
50+
FERN_VERSION=$(node -p "require('./fern/fern.config.json').version")
51+
npm install -g "fern-api@${FERN_VERSION}"
52+
53+
- name: Validate docs website
54+
working-directory: ./fern
55+
run: fern check
56+
57+
- name: Generate Fern preview
58+
if: ${{ inputs.mode == 'preview' }}
59+
env:
60+
FERN_TOKEN: ${{ secrets.FERN_TOKEN }}
61+
INPUT_PREVIEW_ID: ${{ inputs.preview_id }}
62+
working-directory: ./fern
63+
run: |
64+
set -euo pipefail
65+
PREVIEW_ID="${INPUT_PREVIEW_ID:-docs-website-${{ github.run_id }}}"
66+
fern generate --docs --preview --id "$PREVIEW_ID"
67+
68+
- name: Publish Fern docs
69+
if: ${{ inputs.mode == 'production' }}
70+
env:
71+
FERN_TOKEN: ${{ secrets.FERN_TOKEN }}
72+
working-directory: ./fern
73+
run: fern generate --docs

.github/workflows/sync-docs.yml

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
name: Sync Docs Website
5+
6+
on:
7+
workflow_dispatch:
8+
inputs:
9+
operation:
10+
description: "Whether to sync or remove a docs snapshot"
11+
required: true
12+
default: sync
13+
type: choice
14+
options:
15+
- sync
16+
- remove
17+
channel:
18+
description: "Docs channel to update or remove"
19+
required: true
20+
type: choice
21+
options:
22+
- dev
23+
- latest
24+
- version
25+
source_ref:
26+
description: "Source commit SHA, branch, or tag to snapshot when operation=sync"
27+
required: false
28+
type: string
29+
version_slug:
30+
description: "Version slug when channel=version, e.g. v0.0.36"
31+
required: false
32+
type: string
33+
display_name:
34+
description: "Optional version selector display name"
35+
required: false
36+
type: string
37+
38+
permissions:
39+
contents: write
40+
41+
concurrency:
42+
group: docs-website
43+
cancel-in-progress: false
44+
45+
defaults:
46+
run:
47+
shell: bash
48+
49+
jobs:
50+
sync:
51+
runs-on: ubuntu-latest
52+
timeout-minutes: 20
53+
steps:
54+
- name: Checkout automation
55+
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
56+
with:
57+
path: automation
58+
59+
- name: Validate inputs
60+
# Pass dispatch inputs through env vars rather than interpolating
61+
# ${{ inputs.* }} directly into the script. source_ref/version_slug are
62+
# free-form strings, so inlining them would allow shell injection into
63+
# the runner (e.g. version_slug = "$(...)"). Quoted env vars are inert.
64+
env:
65+
OPERATION: ${{ inputs.operation }}
66+
CHANNEL: ${{ inputs.channel }}
67+
SOURCE_REF: ${{ inputs.source_ref }}
68+
VERSION_SLUG: ${{ inputs.version_slug }}
69+
run: |
70+
set -euo pipefail
71+
if [[ "$OPERATION" == "sync" && -z "$SOURCE_REF" ]]; then
72+
echo "source_ref is required when operation=sync" >&2
73+
exit 1
74+
fi
75+
if [[ "$CHANNEL" == "version" && -z "$VERSION_SLUG" ]]; then
76+
echo "version_slug is required when channel=version" >&2
77+
exit 1
78+
fi
79+
80+
- name: Checkout source docs
81+
if: ${{ inputs.operation == 'sync' }}
82+
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
83+
with:
84+
ref: ${{ inputs.source_ref }}
85+
fetch-depth: 0
86+
path: source
87+
88+
- name: Checkout docs website branch
89+
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
90+
with:
91+
ref: docs-website
92+
fetch-depth: 0
93+
path: docs-website
94+
95+
- name: Install uv
96+
uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0
97+
with:
98+
version: "0.10.12"
99+
100+
- name: Update docs snapshot
101+
# Inputs flow in as quoted env vars to avoid shell injection; see the
102+
# Validate inputs step above.
103+
env:
104+
OPERATION: ${{ inputs.operation }}
105+
CHANNEL: ${{ inputs.channel }}
106+
SOURCE_REF: ${{ inputs.source_ref }}
107+
VERSION_SLUG: ${{ inputs.version_slug }}
108+
DISPLAY_NAME: ${{ inputs.display_name }}
109+
run: |
110+
uv run automation/tasks/scripts/sync_docs_website.py \
111+
--operation "$OPERATION" \
112+
--source-root source \
113+
--docs-website-root docs-website \
114+
--channel "$CHANNEL" \
115+
--source-ref "$SOURCE_REF" \
116+
--version-slug "$VERSION_SLUG" \
117+
--display-name "$DISPLAY_NAME"
118+
119+
- name: Setup Node.js
120+
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
121+
with:
122+
node-version: "24"
123+
124+
- name: Install Fern CLI
125+
working-directory: docs-website
126+
run: |
127+
FERN_VERSION=$(node -p "require('./fern/fern.config.json').version")
128+
npm install -g "fern-api@${FERN_VERSION}"
129+
130+
- name: Validate docs website
131+
working-directory: docs-website/fern
132+
run: fern check
133+
134+
- name: Commit docs website changes
135+
working-directory: docs-website
136+
# Inputs flow in as quoted env vars to avoid shell injection (the commit
137+
# message embeds free-form source_ref/version_slug); see Validate inputs.
138+
env:
139+
OPERATION: ${{ inputs.operation }}
140+
CHANNEL: ${{ inputs.channel }}
141+
SOURCE_REF: ${{ inputs.source_ref }}
142+
VERSION_SLUG: ${{ inputs.version_slug }}
143+
run: |
144+
set -euo pipefail
145+
git config user.name "github-actions[bot]"
146+
# https://api.github.com/users/github-actions%5Bbot%5D
147+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
148+
git add .
149+
if git diff --cached --quiet; then
150+
echo "No docs website changes to commit."
151+
exit 0
152+
fi
153+
target="$VERSION_SLUG"
154+
if [[ -z "${target}" ]]; then
155+
target="$CHANNEL"
156+
fi
157+
if [[ "$OPERATION" == "sync" ]]; then
158+
git commit -m "docs(website): sync ${target} docs from ${SOURCE_REF}"
159+
else
160+
git commit -m "docs(website): remove ${target} docs"
161+
fi
162+
git push origin HEAD:docs-website

0 commit comments

Comments
 (0)