Skip to content

Preview Fern Docs: Comment #47

Preview Fern Docs: Comment

Preview Fern Docs: Comment #47

# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Workflow 2 of 2 for Fern doc previews.
#
# Triggered by workflow_run after "Preview Fern Docs: Build" completes.
# Downloads the fern/ artifact, builds a preview with DOCS_FERN_TOKEN, and
# posts a stable :herb: comment on the PR. This workflow never checks out the
# PR branch directly, keeping secrets isolated from untrusted code.
#
# Required configuration:
# - Organization secret: DOCS_FERN_TOKEN (from `fern token` for the nvidia Fern org)
name: "Preview Fern Docs: Comment"
on:
workflow_run:
workflows: ["Preview Fern Docs: Build"]
types: [completed]
permissions:
pull-requests: write
actions: read
jobs:
# Build workflow can "succeed" with collect skipped (fork PR); then no artifact.
artifact-present:
runs-on: ubuntu-latest
if: github.event.workflow_run.conclusion == 'success'
outputs:
exists: ${{ steps.check.outputs.exists }}
steps:
- name: Check for fern-preview artifact
id: check
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
n=$(gh api "repos/${{ github.repository }}/actions/runs/${{ github.event.workflow_run.id }}/artifacts" \
--jq '.artifacts | map(select(.name == "fern-preview")) | length')
if [ "${n}" -gt 0 ]; then
echo "exists=true" >> "$GITHUB_OUTPUT"
else
echo "exists=false" >> "$GITHUB_OUTPUT"
echo "::notice::No fern-preview artifact (e.g. fork PR before ok-to-test); skipping preview comment."
fi
preview:
needs: artifact-present
runs-on: ubuntu-latest
if: needs.artifact-present.outputs.exists == 'true'
steps:
- name: Download fern sources and metadata
uses: actions/download-artifact@v4
with:
name: fern-preview
run-id: ${{ github.event.workflow_run.id }}
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Read PR metadata
id: metadata
run: |
echo "pr_number=$(cat preview-metadata/pr_number)" >> "$GITHUB_OUTPUT"
echo "head_ref=$(cat preview-metadata/head_ref)" >> "$GITHUB_OUTPUT"
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install Fern CLI
run: npm install -g fern-api@$(node -e "console.log(require('./fern/fern.config.json').version)")
- name: Generate preview URL
id: generate-docs
env:
FERN_TOKEN: ${{ secrets.DOCS_FERN_TOKEN }}
HEAD_REF: ${{ steps.metadata.outputs.head_ref }}
working-directory: ./fern
run: |
set -euo pipefail
# Preview --id becomes part of the hostname; '/' and other chars are invalid.
PREVIEW_ID=$(echo "${HEAD_REF:-preview}" | sed 's/[^a-zA-Z0-9._-]/-/g; s/-\{2,\}/-/g; s/^-//; s/-$//')
PREVIEW_ID="${PREVIEW_ID:-preview}"
# Actions bash uses -e; fern often exits non-zero on doc/link errors even when it logs a URL.
# Disable -e for the generate invocation so we always print output and can parse or fail clearly.
set +e
OUTPUT=$(fern generate --docs --preview --force --id "$PREVIEW_ID" 2>&1)
FERN_EXIT=$?
set -e
echo "$OUTPUT"
# Fern prints "[docs]: Published docs to <url>" (older CLIs sometimes added " (…)" after the URL).
URL=$(echo "$OUTPUT" | grep -oE 'Published docs to https?://[^[:space:]()]+' | head -1 | sed 's/^Published docs to //') || true
if [ -z "$URL" ]; then
echo "::error::Failed to generate preview URL (fern exit ${FERN_EXIT}). See Fern output above."
exit 1
fi
echo "preview_url=$URL" >> "$GITHUB_OUTPUT"
- name: Build page links for changed Markdown files
id: page-links
env:
FERN_TOKEN: ${{ secrets.DOCS_FERN_TOKEN }}
PREVIEW_URL: ${{ steps.generate-docs.outputs.preview_url }}
run: |
CHANGED_FILES=""
if [ -f preview-metadata/changed_md_files ]; then
CHANGED_FILES=$(cat preview-metadata/changed_md_files)
fi
if [ -z "$CHANGED_FILES" ] || [ -z "$PREVIEW_URL" ]; then
echo "page_links=" >> "$GITHUB_OUTPUT"; exit 0
fi
BASE_URL=$(echo "$PREVIEW_URL" | grep -oP 'https?://[^/]+')
FILES_PARAM=$(echo "$CHANGED_FILES" | tr '\n' ',' | sed 's/,$//' \
| python3 -c "import sys, urllib.parse; print(urllib.parse.quote(sys.stdin.read().strip(), safe=',/'))")
RESPONSE=$(curl -sf -H "FERN_TOKEN: $FERN_TOKEN" "${PREVIEW_URL}/api/fern-docs/get-slug-for-file?files=${FILES_PARAM}" 2>/dev/null) || {
echo "page_links=" >> "$GITHUB_OUTPUT"; exit 0
}
PAGE_LINKS=$(echo "$RESPONSE" | jq -r --arg url "$BASE_URL" \
'.mappings[] | select(.slug != null) | "- [\(.slug)](\($url)/\(.slug))"')
if [ -n "$PAGE_LINKS" ]; then
{ echo "page_links<<EOF"; echo "$PAGE_LINKS"; echo "EOF"; } >> "$GITHUB_OUTPUT"
else
echo "page_links=" >> "$GITHUB_OUTPUT"
fi
- name: Post or update PR comment
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ steps.metadata.outputs.pr_number }}
PREVIEW_URL: ${{ steps.generate-docs.outputs.preview_url }}
PAGE_LINKS: ${{ steps.page-links.outputs.page_links }}
run: |
# Build comment body
BODY=":herb: **Preview your docs:** <${PREVIEW_URL}>"
if [ -n "${PAGE_LINKS}" ]; then
BODY="${BODY}
Here are the markdown pages you've updated:
${PAGE_LINKS}"
fi
# Hidden marker for upsert
MARKER="<!-- preview-docs -->"
BODY="${BODY}
${MARKER}"
# Find existing comment with marker
COMMENT_ID=$(gh api "repos/${{ github.repository }}/issues/${PR_NUMBER}/comments" \
--jq ".[] | select(.body | contains(\"${MARKER}\")) | .id" | tr -d '\r' | head -1)
if [ -n "$COMMENT_ID" ]; then
gh api "repos/${{ github.repository }}/issues/comments/${COMMENT_ID}" \
-X PATCH -f body="$BODY"
else
gh api "repos/${{ github.repository }}/issues/${PR_NUMBER}/comments" \
-f body="$BODY"
fi