Skip to content
Merged
Show file tree
Hide file tree
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
2 changes: 1 addition & 1 deletion .github/workflows/link-checker-full.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:
runs-on: "ubuntu-latest"
timeout-minutes: 15
steps:
- uses: "actions/checkout@v3"
- uses: "actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd" # v6
- name: "Link Checker"
uses: "filiph/linkcheck@3.0.0"
with:
Expand Down
73 changes: 66 additions & 7 deletions .github/workflows/link-checker.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,22 +14,81 @@ jobs:
preview_url: "${{ steps.waitForVercelPreviewDeployment.outputs.url }}"
steps:
- name: "Wait for Vercel preview deployment to be ready"
uses: "patrickedqvist/wait-for-vercel-preview@v1.3.1"
uses: "actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3" # v9
id: "waitForVercelPreviewDeployment"
with:
token: "${{ secrets.GITHUB_TOKEN }}"
check_interval: 30
max_timeout: 600
allow_inactive: true
script: |
const owner = context.repo.owner;
const repo = context.repo.repo;
const isPullRequest = Boolean(context.payload.pull_request);

// The docs repo deploys via its own workflows (vercel-preview.yml,
// vercel-production.yml), which create the GitHub Deployment as
// github-actions[bot], not vercel[bot].
const sha = isPullRequest
? context.payload.pull_request.head.sha
: context.sha;
const environment = isPullRequest
? "Preview (GitHub Actions)"
: "Production";

const checkIntervalSeconds = 30;
const maxTimeoutSeconds = 600;
const iterations = Math.floor(maxTimeoutSeconds / checkIntervalSeconds);

core.info("Waiting 30 seconds for deployment workflow to start...");
await new Promise((resolve) => setTimeout(resolve, 30000));

for (let i = 0; i < iterations; i++) {
const { data: deployments } = await github.rest.repos.listDeployments({
owner,
repo,
sha,
environment,
});

const deployment = deployments.sort((a, b) => b.id - a.id)[0];

if (deployment) {
const { data: statuses } = await github.rest.repos.listDeploymentStatuses({
owner,
repo,
deployment_id: deployment.id,
});

const status = statuses[0];

if (status && status.state === "success" && status.environment_url) {
core.info(`Deployment successful! URL: ${status.environment_url}`);
core.setOutput("url", status.environment_url);
return;
}

if (status && ["failure", "error", "inactive", "cancelled"].includes(status.state)) {
core.setFailed(`Vercel deployment ${deployment.id} reported state "${status.state}"`);
return;
}
}

core.info(`Waiting for Vercel deployment (attempt ${i + 1} / ${iterations})`);
await new Promise((resolve) => setTimeout(resolve, checkIntervalSeconds * 1000));
}

core.setFailed(`Timed out waiting for a successful Vercel deployment on ${sha}`);

link_checker:
name: "Link Checker"
runs-on: "ubuntu-latest"
needs: "preview"
timeout-minutes: 15
steps:
- uses: "actions/checkout@v3"
- uses: "actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd" # v6
- name: "Link Checker"
uses: "filiph/linkcheck@3.0.0"
with:
arguments: "--skip-file /github/workspace/linkcheck-skip.txt ${{ needs.preview.outputs.preview_url }}"
# --no-check-anchors: Nextra doesn't render heading `id`s into the
# static HTML server-side (they're attached client-side after
# hydration), so the crawler flags every #fragment link as a
# missing anchor. link-checker-full.yaml already disables this for
# the same reason.
arguments: "--skip-file /github/workspace/linkcheck-skip.txt --no-check-anchors ${{ needs.preview.outputs.preview_url }}"
6 changes: 6 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,12 @@ pnpm format # Format code
pnpm lint:markdown # Lint markdown files
```

## Known Quirks

- **Nextra breadcrumb 404s on section index paths.** `nextra-theme-docs`'s breadcrumb component links a top-level section's crumb (e.g. "SpiceDB", from `app/_meta.ts`) to `item.children[0].route` when that section has no index page of its own — the bare path of its first child folder (per that folder's `_meta.ts` order), not a recursively-resolved real page. If that first child also has no index page (true for `spicedb/getting-started`, `authzed/guides`, `materialize/getting-started`), every page in the section renders a breadcrumb crumb pointing at a 404. No content ever links to these bare paths directly — only the breadcrumb does, dynamically, on every page under that section — so the fix is a permanent redirect in `next.config.mjs` from the bare path to the intended real page, not an index page or a content edit. Watch for this again if a new top-level section (or a new first-listed subsection) is added without its own index page.
- **Link checker needs `--no-check-anchors`.** Nextra doesn't render heading `id`s into server-rendered HTML — they're attached client-side after hydration — so a static-HTML crawler (`filiph/linkcheck`, used by `link-checker.yaml`/`link-checker-full.yaml`) flags every `#fragment` link as a false-positive missing anchor without this flag. Both link-checker workflows pass it.
- **The Vercel preview-deploy wait step doesn't use `patrickedqvist/wait-for-vercel-preview`.** That action hardcodes `actorName: 'vercel[bot]'`, but this repo's own `vercel-preview.yml`/`vercel-production.yml` create GitHub Deployments as `github-actions[bot]` (no native Vercel GitHub App integration), so the action always timed out. `link-checker.yaml`'s `preview` job instead queries the Deployments API directly by commit sha + environment (`"Preview (GitHub Actions)"` for PRs, `"Production"` for pushes to main) via an inline `actions/github-script` step, mirroring the equivalent step in `authzed/web`'s `test.yml`.

## Notes

- Markdown linting is lenient (most rules disabled, custom sentence-per-line rule)
Expand Down
20 changes: 20 additions & 0 deletions next.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,26 @@ export default withNextra({
destination: "/spicedb/integrations/langchain-spicedb",
permanent: true,
},
// /spicedb/getting-started, /authzed/guides, and /materialize/getting-started
// are section folders with no index page of their own. Nextra's breadcrumb
// component links a section's top-level crumb (e.g. "SpiceDB") to its first
// child folder's bare route rather than resolving to a real leaf page, so
// without these redirects those crumbs 404.
{
source: "/spicedb/getting-started",
destination: "/spicedb/getting-started/discovering-spicedb",
permanent: true,
},
{
source: "/authzed/guides",
destination: "/authzed/guides/cloud",
permanent: true,
},
{
source: "/materialize/getting-started",
destination: "/materialize/getting-started/overview",
permanent: true,
},
];
},
// This is necessary because we're using CDN domains.
Expand Down
Loading