-
Notifications
You must be signed in to change notification settings - Fork 3
docs-refresh: add registry-based external docs deploy check #146
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,103 @@ | ||
| name: External docs refresh dry run (temporary) | ||
|
|
||
| on: | ||
| pull_request: | ||
| branches: | ||
| - main | ||
| paths: | ||
| - '.github/workflows/docs-refresh*.yml' | ||
| - 'docs-sources.yml' | ||
| - 'docs/remote-markdown.md' | ||
| - 'lib/external-docs.js' | ||
| - 'lib/generate-docs-fingerprint.js' | ||
| - 'lib/generate-external-docs-manifest.js' | ||
| - 'lib/compare-docs-fingerprint.js' | ||
| - 'package.json' | ||
| - 'package-lock.json' | ||
|
|
||
| permissions: | ||
| contents: read | ||
|
|
||
| concurrency: | ||
| group: external-docs-refresh-pr-${{ github.event.pull_request.number }} | ||
| cancel-in-progress: true | ||
|
|
||
| jobs: | ||
| dry-run: | ||
| name: Compare external docs fingerprints without deploying | ||
| runs-on: ubuntu-latest | ||
|
|
||
| steps: | ||
| - name: Checkout docs repo | ||
| uses: actions/checkout@v4 | ||
|
|
||
| - name: Setup Node | ||
| uses: actions/setup-node@v4 | ||
| with: | ||
| node-version: 22 | ||
| cache: npm | ||
|
|
||
| - name: Install dependencies | ||
| run: npm ci | ||
|
|
||
| - name: Validate external docs manifest generation | ||
| run: npm run generate:external-docs | ||
|
|
||
| - name: Generate current docs fingerprint | ||
| run: npm run docs:fingerprint -- --output /tmp/current-docs-fingerprint.json | ||
| env: | ||
| GITHUB_TOKEN: ${{ github.token }} | ||
|
|
||
| - name: Download deployed docs fingerprint | ||
| env: | ||
| DOCS_FINGERPRINT_URL: https://docs.hypercerts.org/docs-fingerprint.json | ||
| run: | | ||
| set +e | ||
| http_code=$(curl -sS -L -w "%{http_code}" \ | ||
| -o /tmp/deployed-docs-fingerprint.json \ | ||
| "$DOCS_FINGERPRINT_URL") | ||
| curl_status=$? | ||
| set -e | ||
|
|
||
| if [ "$curl_status" -ne 0 ]; then | ||
| echo "::error::Failed to download deployed docs fingerprint from $DOCS_FINGERPRINT_URL. Dry-run cannot compare against deployed state." | ||
| exit 1 | ||
| fi | ||
|
|
||
| case "$http_code" in | ||
| 200) | ||
| ;; | ||
| 404) | ||
| echo '{}' > /tmp/deployed-docs-fingerprint.json | ||
| ;; | ||
| *) | ||
| echo "::error::Unexpected HTTP $http_code while downloading $DOCS_FINGERPRINT_URL. Dry-run cannot compare against deployed state." | ||
| exit 1 | ||
| ;; | ||
| esac | ||
|
|
||
| - name: Compare fingerprints | ||
| id: diff | ||
| run: | | ||
| node lib/compare-docs-fingerprint.js \ | ||
| /tmp/current-docs-fingerprint.json \ | ||
| /tmp/deployed-docs-fingerprint.json >> "$GITHUB_OUTPUT" | ||
|
|
||
| - name: Report dry-run result | ||
| run: | | ||
| if [ "${{ steps.diff.outputs.changed }}" = "true" ]; then | ||
| echo "::notice::External docs fingerprint differs from the deployed site. The production workflow would trigger the Vercel deploy hook." | ||
| else | ||
| echo "::notice::External docs fingerprint matches the deployed site. The production workflow would do nothing." | ||
| fi | ||
|
|
||
| { | ||
| echo "### External docs refresh dry run" | ||
| echo "" | ||
| echo "This temporary PR-only workflow never calls the Vercel deploy hook." | ||
| echo "" | ||
| echo "Changed: ${{ steps.diff.outputs.changed || 'unknown' }}" | ||
| echo "Reason: ${{ steps.diff.outputs.reason || 'not computed' }}" | ||
| echo "Current: ${{ steps.diff.outputs.current_fingerprint || 'n/a' }}" | ||
| echo "Deployed: ${{ steps.diff.outputs.deployed_fingerprint || 'n/a' }}" | ||
| } >> "$GITHUB_STEP_SUMMARY" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,121 @@ | ||
| name: Refresh external docs | ||
|
|
||
| on: | ||
| schedule: | ||
| - cron: '17 * * * *' | ||
| workflow_dispatch: | ||
| inputs: | ||
| dry_run: | ||
| description: 'Compare fingerprints without posting to the Vercel deploy hook.' | ||
| required: false | ||
| default: true | ||
| type: boolean | ||
| force_deploy: | ||
| description: 'Mark the run as changed even when the deployed fingerprint matches.' | ||
| required: false | ||
| default: false | ||
| type: boolean | ||
|
|
||
| permissions: | ||
| contents: read | ||
|
|
||
| concurrency: | ||
| group: docs-refresh | ||
| cancel-in-progress: false | ||
|
|
||
| jobs: | ||
| check-docs: | ||
| name: Check external docs fingerprint | ||
| runs-on: ubuntu-latest | ||
|
|
||
| steps: | ||
| - name: Checkout docs repo | ||
| uses: actions/checkout@v4 | ||
|
|
||
| - name: Setup Node | ||
| uses: actions/setup-node@v4 | ||
| with: | ||
| node-version: 22 | ||
| cache: npm | ||
|
Comment on lines
+32
to
+39
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
# List the workflow files to ensure they exist
ls -la .github/workflows | sed -n '1,200p'
# Show the relevant sections with line numbers
echo "---- docs-refresh.yml (lines 1-120) ----"
nl -ba .github/workflows/docs-refresh.yml | sed -n '1,140p'
echo "---- docs-refresh-pr-dry-run.yml (lines 1-140) ----"
nl -ba .github/workflows/docs-refresh-pr-dry-run.yml | sed -n '1,160p'
# Confirm all usages of actions/checkout and actions/setup-node in workflows
echo "---- occurrences of actions/checkout ----"
rg -n --hidden --glob '.github/workflows/**/*.yml' "uses:\s*actions/checkout@" .github/workflows || true
echo "---- occurrences of actions/setup-node ----"
rg -n --hidden --glob '.github/workflows/**/*.yml' "uses:\s*actions/setup-node@" .github/workflows || trueRepository: hypercerts-org/documentation Length of output: 428 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "---- docs-refresh.yml (cat -n lines 25-60) ----"
cat -n .github/workflows/docs-refresh.yml | sed -n '25,70p'
echo "---- docs-refresh-pr-dry-run.yml (cat -n lines 25-70) ----"
cat -n .github/workflows/docs-refresh-pr-dry-run.yml | sed -n '25,80p'
echo "---- occurrences of actions/checkout@ ----"
rg -n --glob '.github/workflows/**/*.yml' "uses:\s*actions/checkout@" .github/workflows || true
echo "---- occurrences of actions/setup-node@ ----"
rg -n --glob '.github/workflows/**/*.yml' "uses:\s*actions/setup-node@" .github/workflows || trueRepository: hypercerts-org/documentation Length of output: 4282 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "---- docs-refresh.yml triggers + job context (head 1-120) ----"
cat -n .github/workflows/docs-refresh.yml | sed -n '1,140p'
echo "---- docs-refresh.yml uses: lines (entire file) ----"
rg -n --glob '.github/workflows/docs-refresh.yml' "^\s*uses:\s*[^ ]+" .github/workflows/docs-refresh.yml || true
echo "---- docs-refresh-pr-dry-run.yml triggers + job context (head 1-140) ----"
cat -n .github/workflows/docs-refresh-pr-dry-run.yml | sed -n '1,160p'
echo "---- docs-refresh-pr-dry-run.yml uses: lines (entire file) ----"
rg -n --glob '.github/workflows/docs-refresh-pr-dry-run.yml' "^\s*uses:\s*[^ ]+" .github/workflows/docs-refresh-pr-dry-run.yml || true
echo "---- all occurrences of actions/checkout@v4 across workflows ----"
rg -n --glob '.github/workflows/**/*.yml' "uses:\s*actions/checkout@v4" .github/workflows || true
echo "---- all occurrences of actions/setup-node@v4 across workflows ----"
rg -n --glob '.github/workflows/**/*.yml' "uses:\s*actions/setup-node@v4" .github/workflows || trueRepository: hypercerts-org/documentation Length of output: 9903 Pin 🧰 Tools🪛 zizmor (1.25.2)[warning] 32-33: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false (artipacked) [error] 33-33: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy) (unpinned-uses) [error] 36-36: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy) (unpinned-uses) 🤖 Prompt for AI AgentsSource: Linters/SAST tools |
||
|
|
||
| - name: Install dependencies | ||
| run: npm ci | ||
|
|
||
| - name: Generate current docs fingerprint | ||
| run: npm run docs:fingerprint -- --output current-docs-fingerprint.json | ||
| env: | ||
| DOCS_SOURCE_TOKEN: ${{ secrets.DOCS_SOURCE_TOKEN }} | ||
| GITHUB_TOKEN: ${{ github.token }} | ||
|
|
||
| - name: Download deployed docs fingerprint | ||
| env: | ||
| DOCS_FINGERPRINT_URL: https://docs.hypercerts.org/docs-fingerprint.json | ||
| run: | | ||
| set +e | ||
| http_code=$(curl -sS -L -w "%{http_code}" \ | ||
| -o deployed-docs-fingerprint.json \ | ||
| "$DOCS_FINGERPRINT_URL") | ||
| curl_status=$? | ||
| set -e | ||
|
|
||
| if [ "$curl_status" -ne 0 ]; then | ||
| echo "::error::Failed to download deployed docs fingerprint from $DOCS_FINGERPRINT_URL. Refusing to deploy on an unknown diff." | ||
| exit 1 | ||
| fi | ||
|
|
||
| case "$http_code" in | ||
| 200) | ||
| ;; | ||
| 404) | ||
| echo '{}' > deployed-docs-fingerprint.json | ||
| ;; | ||
| *) | ||
| echo "::error::Unexpected HTTP $http_code while downloading $DOCS_FINGERPRINT_URL. Refusing to deploy on an unknown diff." | ||
| exit 1 | ||
| ;; | ||
| esac | ||
|
|
||
| - name: Check if external docs changed | ||
| id: diff | ||
| env: | ||
| FORCE_DEPLOY: ${{ github.event_name == 'workflow_dispatch' && inputs.force_deploy == true }} | ||
| run: | | ||
| node lib/compare-docs-fingerprint.js \ | ||
| current-docs-fingerprint.json \ | ||
| deployed-docs-fingerprint.json >> "$GITHUB_OUTPUT" | ||
|
|
||
| if [ "$FORCE_DEPLOY" = "true" ]; then | ||
| echo "changed=true" >> "$GITHUB_OUTPUT" | ||
| echo "reason=forced by workflow_dispatch input" >> "$GITHUB_OUTPUT" | ||
| fi | ||
|
|
||
| - name: Trigger Vercel deployment | ||
| if: steps.diff.outputs.changed == 'true' | ||
| env: | ||
| DRY_RUN: ${{ github.event_name == 'workflow_dispatch' && inputs.dry_run == true }} | ||
| VERCEL_DEPLOY_HOOK_URL: ${{ secrets.VERCEL_DEPLOY_HOOK_URL }} | ||
| run: | | ||
| if [ "$DRY_RUN" = "true" ]; then | ||
| echo "workflow_dispatch dry_run=true; skipping Vercel deploy hook." | ||
| exit 0 | ||
| fi | ||
|
|
||
| if [ -z "$VERCEL_DEPLOY_HOOK_URL" ]; then | ||
| echo "::error::VERCEL_DEPLOY_HOOK_URL is required when external docs have changed. Create a Vercel Deploy Hook for the production branch and store its URL as a GitHub Actions secret." | ||
| exit 1 | ||
| fi | ||
|
|
||
| curl -fsS -X POST "$VERCEL_DEPLOY_HOOK_URL" | ||
|
|
||
| - name: Write summary | ||
| if: always() | ||
| run: | | ||
| { | ||
| echo "### External docs refresh" | ||
| echo "" | ||
| echo "Changed: ${{ steps.diff.outputs.changed || 'unknown' }}" | ||
| echo "Reason: ${{ steps.diff.outputs.reason || 'not computed' }}" | ||
| echo "Dry run: ${{ github.event_name == 'workflow_dispatch' && inputs.dry_run == true }}" | ||
| echo "Current: ${{ steps.diff.outputs.current_fingerprint || 'n/a' }}" | ||
| echo "Deployed: ${{ steps.diff.outputs.deployed_fingerprint || 'n/a' }}" | ||
| } >> "$GITHUB_STEP_SUMMARY" | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,18 +1,59 @@ | ||
| import { useState } from 'react'; | ||
| import { useEffect, useState } from 'react'; | ||
| import { useRouter } from 'next/router'; | ||
|
|
||
| function getRawUrl(currentPath) { | ||
| const EXTERNAL_DOCS_MANIFEST_URL = '/external-docs.json'; | ||
|
|
||
| function getGeneratedRawUrl(currentPath) { | ||
| if (currentPath === '/') return '/raw/index.md'; | ||
| return `/raw${currentPath}.md`; | ||
| } | ||
|
|
||
| export function CopyRawButton() { | ||
| async function getExternalDocRawUrl(externalDoc, signal) { | ||
| const response = await fetch(EXTERNAL_DOCS_MANIFEST_URL, { | ||
| cache: 'no-store', | ||
| signal, | ||
| }); | ||
|
|
||
| if (!response.ok) { | ||
| throw new Error(`Could not load ${EXTERNAL_DOCS_MANIFEST_URL}. Run npm run generate:external-docs before starting the docs site.`); | ||
| } | ||
|
|
||
| const manifest = await response.json(); | ||
| const rawUrl = manifest?.sources?.[externalDoc]?.rawUrl; | ||
| if (!rawUrl) { | ||
| throw new Error(`External docs source "${externalDoc}" does not define a raw Markdown URL.`); | ||
| } | ||
|
|
||
| return rawUrl; | ||
| } | ||
|
|
||
| /** | ||
| * Render page-level actions for copying or viewing the Markdown source for the current docs page. | ||
| * Pages that render canonical Markdown from another repository can set `externalDoc` in frontmatter so these actions use the registry source instead of the generated local fallback. | ||
| */ | ||
| export function CopyRawButton({ frontmatter }) { | ||
| const [copied, setCopied] = useState(false); | ||
| const [copyError, setCopyError] = useState(false); | ||
| const [isCopying, setIsCopying] = useState(false); | ||
| const [externalRawUrl, setExternalRawUrl] = useState(null); | ||
| const router = useRouter(); | ||
| const currentPath = router.asPath.split('#')[0].split('?')[0] || '/'; | ||
| const rawUrl = getRawUrl(currentPath); | ||
| const generatedRawUrl = getGeneratedRawUrl(currentPath); | ||
| const rawUrl = frontmatter?.rawUrl || externalRawUrl || generatedRawUrl; | ||
|
|
||
| useEffect(() => { | ||
| if (!frontmatter?.externalDoc || frontmatter?.rawUrl) { | ||
| setExternalRawUrl(null); | ||
| return undefined; | ||
| } | ||
|
|
||
| const controller = new AbortController(); | ||
| getExternalDocRawUrl(frontmatter.externalDoc, controller.signal) | ||
| .then(setExternalRawUrl) | ||
| .catch(() => setExternalRawUrl(null)); | ||
|
|
||
| return () => controller.abort(); | ||
| }, [frontmatter?.externalDoc, frontmatter?.rawUrl]); | ||
|
Comment on lines
+42
to
+56
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Prefer the generated This now routes external-doc pages through the live Based on learnings from the PR objective and review stack context: external docs raw pages are generated during builds and are intended to back this runtime flow. 🤖 Prompt for AI Agents |
||
|
|
||
| const handleCopy = async () => { | ||
| setIsCopying(true); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
Repository: hypercerts-org/documentation
Length of output: 179
🏁 Script executed:
Repository: hypercerts-org/documentation
Length of output: 9735
🌐 Web query:
actions/checkout@v4 persist-credentials default value stores github.token in git config💡 Result:
The
persist-credentialsinput foractions/checkoutdefaults totrue[1][2][3]. When set totrue(the default), the action configures the authentication token (typicallygithub.token) or SSH key to allow your workflow scripts to run authenticated Git commands [3][4]. While the documentation historically stated this token was persisted directly in the local.git/configfile [3][4], recent updates (as of themainbranch) have improved this behavior by storing credentials in a separate file under$RUNNER_TEMPand using Git'sincludeIffunctionality to reference them, rather than placing them directly in.git/config[5][6]. Regardless of the exact storage mechanism, the action automatically removes these credentials during the post-job cleanup phase to ensure security [3][4]. You can opt out of this behavior by settingpersist-credentials: falsein your workflow configuration [3][4].Citations:
Disable persisted checkout credentials in both docs refresh workflows
Both workflows use
actions/checkout@v4withoutwith: persist-credentials: false; the default persists/configuresgithub.tokenfor authenticated git commands during the job, so later steps (includingnpm ci/ npm lifecycle scripts) can access it. Addwith: persist-credentials: falseto theactions/checkout@v4steps in.github/workflows/docs-refresh.ymland.github/workflows/docs-refresh-pr-dry-run.yml.🧰 Tools
🪛 zizmor (1.25.2)
[warning] 32-33: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false
(artipacked)
[error] 33-33: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
🤖 Prompt for AI Agents
Source: Linters/SAST tools