Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
171 changes: 71 additions & 100 deletions .github/workflows/preview-docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,26 +10,21 @@ jobs:
run:
runs-on: ubuntu-latest
permissions:
pull-requests: write # Only for commenting
contents: read # For checking out code
pull-requests: write
contents: read
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0 # Fetch full history for git diff
fetch-depth: 0

- name: Checkout PR
run: |
git fetch origin pull/${{ github.event.pull_request.number }}/head:pr-${{ github.event.pull_request.number }}
git checkout pr-${{ github.event.pull_request.number }}

- name: Install Fern and CML
run: |
npm install -g fern-api@latest
npm install -g @dvcorg/cml

- name: Install Chrome for Puppeteer
run: npx puppeteer browsers install chrome@141.0.7390.54
- name: Install Fern
run: npm install -g fern-api@latest

- name: Generate preview URL
id: generate-docs
Expand All @@ -42,109 +37,85 @@ jobs:
echo "preview_url=$URL" >> $GITHUB_OUTPUT
echo "Preview URL: $URL"

- name: Run fern docs diff for changed MDX files
id: docs-diff
- name: Get changed MDX files and slugs
id: changed-files
env:
FERN_TOKEN: ${{ secrets.FERN_TOKEN }}
run: |
PREVIEW_URL="${{ steps.generate-docs.outputs.preview_url }}"
CHANGED_FILES=$(git diff --name-only origin/main...HEAD -- '*.mdx' 2>/dev/null || echo "")

if [ -z "$CHANGED_FILES" ] || [ -z "$PREVIEW_URL" ]; then
echo "has_diffs=false" >> $GITHUB_OUTPUT
echo "has_changes=false" >> $GITHUB_OUTPUT
exit 0
fi

# Convert newlines to space-separated list for the command
FILES_LIST=$(echo "$CHANGED_FILES" | tr '\n' ' ')

# Create output directory
mkdir -p .fern/diff
echo "has_changes=true" >> $GITHUB_OUTPUT

# Run fern docs diff and capture output
# Progress messages go to stderr, JSON goes to stdout
# We capture everything to a temp file, then extract just the JSON
npx fern-api@latest docs diff "$PREVIEW_URL" $FILES_LIST --output .fern/diff > .fern/diff/output.txt 2>&1 || true

# Extract just the JSON part (starts with { and ends with })
# The JSON is the last thing output, so we find the line starting with { and take everything from there
sed -n '/^{/,$p' .fern/diff/output.txt > .fern/diff/diff.json

# Debug: show what we captured
echo "=== Raw output ==="
cat .fern/diff/output.txt
echo "=== Extracted JSON ==="
cat .fern/diff/diff.json
# Call the API to get slugs for the changed files
FILES_PARAM=$(echo "$CHANGED_FILES" | tr '\n' ',' | sed 's/,$//')
RESPONSE=$(curl -sf -H "FERN_TOKEN: $FERN_TOKEN" "${PREVIEW_URL}/api/fern-docs/get-slug-for-file?files=${FILES_PARAM}" 2>/dev/null) || {
echo "api_response=" >> $GITHUB_OUTPUT
exit 0
}

# Check if diff JSON file exists and has valid content
if [ -f ".fern/diff/diff.json" ] && grep -q '"diffs"' .fern/diff/diff.json 2>/dev/null; then
echo "has_diffs=true" >> $GITHUB_OUTPUT
else
echo "has_diffs=false" >> $GITHUB_OUTPUT
echo "No diffs found or diff.json is invalid"
fi
# Store the API response for the next step
echo "api_response<<EOF" >> $GITHUB_OUTPUT
echo "$RESPONSE" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT

- name: Upload diff images as artifacts
if: steps.docs-diff.outputs.has_diffs == 'true'
uses: actions/upload-artifact@v4
- name: Post PR comment
uses: actions/github-script@v7
with:
name: docs-diff-images
path: .fern/diff/*.png
retention-days: 7

- name: Upload images and create comment
if: steps.docs-diff.outputs.has_diffs == 'true'
env:
REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PREVIEW_URL: ${{ steps.generate-docs.outputs.preview_url }}
run: |
# Parse diff.json and upload images using cml
PREVIEW_URL="${{ steps.generate-docs.outputs.preview_url }}"
BASE_URL=$(echo "$PREVIEW_URL" | grep -oP 'https?://[^/]+')

echo ":herb: **Preview your docs:** <${PREVIEW_URL}>" > comment.md
echo "" >> comment.md
echo "### Visual changes detected:" >> comment.md
echo "" >> comment.md

# Process each diff entry
jq -c '.diffs[]' .fern/diff/diff.json | while read -r diff; do
FILE=$(echo "$diff" | jq -r '.file')
SLUG=$(echo "$diff" | jq -r '.slug')
COMPARISON=$(echo "$diff" | jq -r '.comparison')
CHANGE_PERCENT=$(echo "$diff" | jq -r '.changePercent // "N/A"')
IS_NEW_PAGE=$(echo "$diff" | jq -r '.isNewPage')
script: |
const previewUrl = '${{ steps.generate-docs.outputs.preview_url }}';
const hasChanges = '${{ steps.changed-files.outputs.has_changes }}' === 'true';
const apiResponseRaw = `${{ steps.changed-files.outputs.api_response }}`;

echo "#### \`${FILE}\`" >> comment.md
echo "" >> comment.md
echo "**Page:** [${SLUG}](${BASE_URL}/${SLUG}) | **Change:** ${CHANGE_PERCENT}%" >> comment.md
if [ "$IS_NEW_PAGE" = "true" ]; then
echo " | *New page*" >> comment.md
fi
echo "" >> comment.md
let body = `:herb: **Preview your docs:** <${previewUrl}>\n\n`;

# Upload image using cml and get URL
if [ -f "$COMPARISON" ]; then
IMAGE_URL=$(cml publish "$COMPARISON" 2>/dev/null || echo "")
if [ -n "$IMAGE_URL" ]; then
echo "![${FILE} comparison](${IMAGE_URL})" >> comment.md
else
echo "*Comparison image available in [workflow artifacts](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})*" >> comment.md
fi
fi
echo "" >> comment.md
done

cat comment.md

- name: Create comment without diffs
if: steps.docs-diff.outputs.has_diffs != 'true'
run: |
echo ":herb: **Preview your docs:** <${{ steps.generate-docs.outputs.preview_url }}>" > comment.md

- name: Post PR comment
uses: thollander/actions-comment-pull-request@v2.4.3
with:
filePath: comment.md
comment_tag: preview-docs
mode: upsert
if (hasChanges && apiResponseRaw.trim()) {
try {
const response = JSON.parse(apiResponseRaw);
const mappings = response.mappings || [];
const validMappings = mappings.filter(m => m.slug != null);

if (validMappings.length > 0) {
body += `### Changed pages:\n\n`;
for (const mapping of validMappings) {
const pageUrl = `${previewUrl}/${mapping.slug}`;
body += `- \`${mapping.file}\` → [${mapping.slug}](${pageUrl})\n`;
}
}
} catch (e) {
console.log('Failed to parse API response:', e);
}
}

// Find existing comment to update
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
});

const botComment = comments.find(comment =>
comment.user.type === 'Bot' &&
comment.body.includes(':herb: **Preview your docs:**')
);

if (botComment) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: botComment.id,
body: body
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: body
});
}
Loading