diff --git a/.github/required-checks.ruleset.json b/.github/required-checks.ruleset.json new file mode 100644 index 000000000..1daa6b61e --- /dev/null +++ b/.github/required-checks.ruleset.json @@ -0,0 +1,23 @@ +{ + "name": "Documentation CI", + "target": "branch", + "enforcement": "active", + "conditions": { + "ref_name": { + "include": ["refs/heads/main"], + "exclude": [] + } + }, + "rules": [ + { + "type": "required_status_checks", + "parameters": { + "strict_required_status_checks_policy": true, + "required_status_checks": [ + { "context": "fern-check", "integration_id": 15368 }, + { "context": "Docs Preview", "integration_id": 15368 } + ] + } + } + ] +} diff --git a/.github/workflows/check-external-links.yml b/.github/workflows/check-external-links.yml new file mode 100644 index 000000000..5495a8b56 --- /dev/null +++ b/.github/workflows/check-external-links.yml @@ -0,0 +1,71 @@ +name: check-external-links + +on: + schedule: + - cron: "0 8 * * *" + workflow_dispatch: + pull_request: + paths: + - .github/workflows/check-external-links.yml + +permissions: + contents: read + +concurrency: + group: check-live-docs-links + cancel-in-progress: false + +jobs: + check-external-links: + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - uses: ./.github/actions/setup-fern + + - name: Check external and internal links on published docs + env: + FERN_TOKEN: ${{ secrets.FERN_TOKEN }} + run: node "$FERN_CLI_PATH" docs link check --url https://docs.vapi.ai --output json > "$RUNNER_TEMP/fern-links.json" + + - name: Summarize link findings + if: always() + run: | + node - <<'NODE' + const fs = require('node:fs'); + const path = `${process.env.RUNNER_TEMP}/fern-links.json`; + if (!fs.existsSync(path) || fs.statSync(path).size === 0) { + throw new Error('Fern did not produce a link report; see the scan logs.'); + } + const report = JSON.parse(fs.readFileSync(path, 'utf8')); + const { summary, brokenLinks, blockedLinks } = report; + const lines = [ + '## External and internal link scan', + '', + `Status: ${summary.status}. Scanned ${summary.totalPages} pages and ${summary.totalLinks} links.`, + `Reported ${summary.brokenLinks} broken and ${summary.blockedLinks} blocked links.`, + ]; + for (const [label, links] of [['Broken links', brokenLinks], ['Blocked links (non-blocking)', blockedLinks]]) { + lines.push('', `### ${label}`, ''); + for (const link of links) { + const sources = link.references.map(ref => ref.filePath ?? ref.slug).filter(Boolean).join(', '); + lines.push(`- ${link.statusCode ?? 'No response'}: ${link.url} — ${sources}`); + } + if (links.length === 0) lines.push('None.'); + } + fs.appendFileSync(process.env.GITHUB_STEP_SUMMARY, lines.join('\n') + '\n'); + if (summary.status !== 'complete') { + throw new Error('Fern returned an incomplete link scan; rerun the workflow.'); + } + NODE + + - name: Save link report + if: always() + uses: actions/upload-artifact@v4 + with: + name: fern-live-links + path: ${{ runner.temp }}/fern-links.json + retention-days: 14 diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index b8f63ce4b..b1591506d 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -6,6 +6,9 @@ on: branches: - main +permissions: + contents: read + jobs: fern-check: runs-on: ubuntu-latest @@ -15,5 +18,7 @@ jobs: - uses: ./.github/actions/setup-fern - - name: Check API is valid - run: node scripts/fern/run.cjs check + - name: Validate docs, links, and API configuration + env: + FERN_TOKEN: ${{ secrets.FERN_TOKEN }} + run: node scripts/fern/run.cjs check --warnings diff --git a/.github/workflows/preview-docs.yml b/.github/workflows/preview-docs.yml index d7b61f5d6..7959a7717 100644 --- a/.github/workflows/preview-docs.yml +++ b/.github/workflows/preview-docs.yml @@ -1,11 +1,26 @@ name: Preview Docs -on: pull_request +on: + pull_request: + types: [opened, synchronize, reopened, ready_for_review, closed] + +concurrency: + group: docs-preview-${{ github.event.pull_request.number }} + cancel-in-progress: true + +permissions: + contents: read jobs: run: + name: Docs Preview + if: github.event.action != 'closed' && github.event.pull_request.head.repo.full_name == github.repository runs-on: ubuntu-latest - permissions: write-all + permissions: + contents: read + pull-requests: write + env: + PREVIEW_ID: pr-${{ github.event.pull_request.number }} steps: - name: Checkout repository uses: actions/checkout@v4 @@ -19,8 +34,12 @@ jobs: POSTHOG_PROJECT_API_KEY: ${{ secrets.POSTHOG_PROJECT_API_KEY }} run: | set -euo pipefail - node scripts/fern/run.cjs generate --docs --preview --log-level debug 2>&1 | tee "$RUNNER_TEMP/fern-preview.log" - URL=$(grep -oP 'Published docs to \K.*(?= \()' "$RUNNER_TEMP/fern-preview.log") + node scripts/fern/run.cjs generate --docs --preview --id "$PREVIEW_ID" --no-prompt --log-level debug 2>&1 | tee "$RUNNER_TEMP/fern-preview.log" + URL=$(sed -nE 's/.*Published docs to (https?:\/\/[^[:space:]]+).*/\1/p' "$RUNNER_TEMP/fern-preview.log" | tail -n 1) + if [ -z "$URL" ]; then + echo "::error::Fern did not return a preview URL." + exit 1 + fi echo "Preview URL: $URL" echo "🌿 Preview your docs: $URL" > preview_url.txt @@ -28,3 +47,32 @@ jobs: uses: thollander/actions-comment-pull-request@v2.4.3 with: filePath: preview_url.txt + comment_tag: preview-docs + mode: upsert + + cleanup: + name: Delete Docs Preview + if: github.event.action == 'closed' && github.event.pull_request.head.repo.full_name == github.repository + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.base.sha }} + + - uses: ./.github/actions/setup-fern + + - name: Delete preview for the closed PR + env: + FERN_TOKEN: ${{ secrets.FERN_TOKEN }} + PREVIEW_ID: pr-${{ github.event.pull_request.number }} + run: | + set -euo pipefail + if node "$FERN_CLI_PATH" docs preview delete --id "$PREVIEW_ID" 2>&1 | tee "$RUNNER_TEMP/fern-preview-delete.log"; then + exit 0 + fi + if grep -q 'Preview site not found:' "$RUNNER_TEMP/fern-preview-delete.log"; then + echo "No preview exists for this PR; nothing to delete." + else + exit 1 + fi diff --git a/README.md b/README.md index f5fdbeae5..180de92cc 100644 --- a/README.md +++ b/README.md @@ -44,3 +44,26 @@ To preview your documentation, run fern generate --docs --preview ``` The repository contains GitHub workflows that will automatically run these commands for you. For example, when you make a PR a preview link will be auto-generated and when you merge to main the docs site will update. + +### Documentation checks + +Every pull request runs `fern check --warnings` with the pinned CLI and the +example-cache adapter. Internal broken links fail validation and publication. +API example validation and missing redirects are warnings while existing findings +are reviewed. Missing-redirect detection compares against the published site and +needs the `FERN_TOKEN` repository secret; fork PRs still receive local validation. +See [the cache setup and verification commands](scripts/fern/README.md). + +Same-repository PRs receive a stable preview named `pr-`. Subsequent pushes +update the preview and its PR comment, and closing or merging the PR deletes the +preview. Fork PRs skip hosted previews because repository secrets are unavailable. + +The **check-external-links** workflow scans the published site daily at 08:00 UTC +and can also be run manually. Changes to that workflow run the scan on +same-repository PRs to verify its configuration before merge. Broken links and +incomplete scans fail the job. +Blocked URLs (such as HTTP 403 responses) are reported separately by Fern and do +not fail the scan. The run summary lists findings and source pages; download the +`fern-live-links` artifact for the JSON report. This workflow monitors external +URLs as well as deployed internal links; ordinary docs PRs use the local link +validator. diff --git a/fern/docs.yml b/fern/docs.yml index 97c0dfb48..2a954c162 100644 --- a/fern/docs.yml +++ b/fern/docs.yml @@ -10,6 +10,11 @@ instances: branch: main title: Vapi +check: + rules: + broken-links: error + example-validation: warn + missing-redirects: warn favicon: static/images/favicon.ico logo: light: static/images/logo/logo-light.svg diff --git a/fern/quickstart/introduction.mdx b/fern/quickstart/introduction.mdx index 1ad99a1e3..70ce6783f 100644 --- a/fern/quickstart/introduction.mdx +++ b/fern/quickstart/introduction.mdx @@ -166,7 +166,7 @@ Work with Vapi from your terminal, or manage your whole configuration as code: -To manage Vapi configuration as code (assistants, squads, tools, structured outputs, simulations and evals as files in git, across one or more orgs or environments), use the open-source repository at https://github.com/VapiAI/gitops and read https://docs.vapi.ai/config-as-code.md before setting it up. +To manage Vapi configuration as code (assistants, squads, tools, structured outputs, simulations and evals as files in git, across one or more orgs or environments), use the open-source repository at https://github.com/VapiAI/gitops and read the [config-as-code guide](/config-as-code) before setting it up. ### Agent skills diff --git a/scripts/fern/README.md b/scripts/fern/README.md index ee197a375..d13f2e4d0 100644 --- a/scripts/fern/README.md +++ b/scripts/fern/README.md @@ -52,7 +52,7 @@ original Fern commands also removes this optimization. npm install --prefix /tmp/vapi-fern --no-save fern-api@5.151.1 export FERN_CLI_PATH=/tmp/vapi-fern/node_modules/fern-api/cli.cjs node --test scripts/fern/example-cache.test.cjs -node scripts/fern/run.cjs check --local --log-level debug +node scripts/fern/run.cjs check --local --warnings --log-level debug node scripts/fern/run.cjs generate --docs --preview --log-level debug ``` @@ -60,6 +60,19 @@ Only use `--preview` for verification. Production publishing remains in the existing main-branch workflow. Logs print cache hits, misses, and uncached diagnostic-producing calls at exit. +The required internal-link rule in `fern/docs.yml` also builds the API navigation. +This can be expensive on a cold cache. Keep it enabled when measuring cache +performance; a check that skips links does not exercise the same work as a docs +build. + +When upgrading Fern, update the pinned version in `fern/fern.config.json`, +`.github/actions/setup-fern/action.yml`, and `run.cjs` together. Review the new +bundle before updating its checksum and function markers. Run the safeguard tests, +compare the unmodified CLI with cold and warm adapter checks (including warnings), +and verify a preview publication before merging. Confirm the preview deletion and +live-link commands still work with the new CLI. Do not update only the checksum +to bypass an unexpected-bundle failure. + ## Measured validation results On the same machine and API inputs, the original generators took 697 seconds