ci: statically check doc code blocks on every PR#70
Conversation
Extracts every json and ts/tsx fenced block from docs/ and verifies it without executing anything: JSON blocks must parse, TypeScript blocks must typecheck against the pinned abstractionkit@0.4.0 (plus ethers, viem, wagmi, dotenv, siwe, safe-recovery-service-sdk as devDependencies; nothing ships to the site bundle). tsc runs in two passes because a single syntax error anywhere makes it skip semantic checking for the whole program. Pre-existing failures (448 of 627 blocks, mostly intentionally elided excerpts) are recorded in a baseline keyed by content hash: CI fails only on new failures, and editing a baselined block re-checks it. An inline <!-- docs-check: skip --> marker exempts intentional elisions. The workflow also runs yarn build on PRs, which catches broken links. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughThis PR adds a docs code-block checker, a baseline of known failures, npm wiring for the checker, and a GitHub Actions workflow that runs the checker and build on pull requests and pushes to main. ChangesDocumentation Code Verification
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant GitHub Actions
participant check-docs-code.mjs
participant tsc
participant docs-check-baseline.json
GitHub Actions->>check-docs-code.mjs: yarn check-docs
check-docs-code.mjs->>check-docs-code.mjs: extract fenced blocks
check-docs-code.mjs->>check-docs-code.mjs: hash and validate JSON blocks
check-docs-code.mjs->>tsc: pass1 syntax check for ts/tsx scratch files
tsc-->>check-docs-code.mjs: syntax diagnostics
check-docs-code.mjs->>tsc: pass2 semantic check on clean files
tsc-->>check-docs-code.mjs: semantic diagnostics
check-docs-code.mjs->>docs-check-baseline.json: load known-failure hashes
check-docs-code.mjs->>check-docs-code.mjs: compare current and baseline failures
check-docs-code.mjs-->>GitHub Actions: report summary and exit code
GitHub Actions->>GitHub Actions: yarn build
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
Comment |
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
.github/workflows/docs-check.yml (1)
1-32: 🔒 Security & Privacy | 🟡 Minor | ⚡ Quick winAdd an explicit
permissionsblock (least privilege).Both jobs run with the default
GITHUB_TOKENpermissions since no top-level or job-levelpermissions:is declared, flagged by static analysis as excessive-permissions. Neither job needs anything beyond read access to repo contents.🔒 Proposed fix
name: docs-check +permissions: + contents: read + on: pull_request: push: branches: [main]🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.github/workflows/docs-check.yml around lines 1 - 32, Add an explicit least-privilege permissions setting for this workflow so both the code-blocks and build jobs don’t inherit the default broad GITHUB_TOKEN scope. Update the docs-check workflow by adding a top-level permissions block, or equivalent job-level permissions on code-blocks and build, limited to read access for repository contents. Use the workflow jobs and triggers already defined in docs-check.yml to place the restriction without changing the existing steps.Source: Linters/SAST tools
🧹 Nitpick comments (3)
.github/workflows/docs-check.yml (1)
8-31: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low valueConsider a
concurrencygroup to cancel superseded runs.Both jobs run on every
pull_requestsync andpushtomain; without aconcurrencyblock, superseded pushes to the same PR branch will queue redundantyarn install+ typecheck runs.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.github/workflows/docs-check.yml around lines 8 - 31, Add a workflow-level concurrency group to the docs-check workflow so older runs are canceled when a new push/sync arrives. Update the workflow containing the code-blocks and build jobs to use a stable group key based on the branch or PR ref, and enable cancel-in-progress so superseded runs do not keep executing redundant yarn install and validation steps.scripts/check-docs-code.mjs (2)
180-186: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winBaseline key order isn't deterministic.
next[f.hash]is inserted infailuresiteration order, which follows filesystem traversal order (fs.readdirSync), not a stable sort. Re-running--update-baselinein a different environment/OS can reorder the whole ~390-entry file, producing noisy diffs unrelated to actual failure changes.♻️ Proposed fix
if (updateBaseline) { const next = {}; for (const f of failures) next[f.hash] = `${f.file}:${f.line} (${f.lang})`; - fs.writeFileSync(baselinePath, JSON.stringify(next, null, 2) + "\n"); + const sorted = Object.fromEntries(Object.keys(next).sort().map((k) => [k, next[k]])); + fs.writeFileSync(baselinePath, JSON.stringify(sorted, null, 2) + "\n");🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@scripts/check-docs-code.mjs` around lines 180 - 186, The baseline rewrite in updateBaseline is producing nondeterministic key order because next is populated directly from failures iteration order. Update the baseline generation path in scripts/check-docs-code.mjs so failures are sorted by a stable key before building next, or otherwise ensure deterministic insertion order before fs.writeFileSync writes baselinePath. Use the updateBaseline block and the failures collection as the place to apply the stable ordering.
150-171: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winSecond
tscpass runs even when pass 1 found zero syntax errors.
cleanFilesis "all files not present inpass1's error map," so on the common all-clean run (no syntax errors anywhere),pass1already contains full semantic diagnostics (per the comment, semantic checking is only globally suppressed when a syntax error exists), yetcleanFilesends up including essentially the entire block set and pass2 recompiles it all again for no new information. With ~300+ baselined TS blocks, this doublestscruntime on every "healthy" CI run.⚡ Proposed fix
const pass1 = runTsc(tsBlocks.map((b) => b.scratchFile), "tsconfig.json"); + const hasSyntaxErrors = [...pass1.values()].some((diags) => + diags.some((d) => /^error TS1\d{3}:/.test(d.message)) + ); const cleanFiles = tsBlocks.map((b) => b.scratchFile).filter((f) => !pass1.has(f)); - const pass2 = cleanFiles.length > 0 ? runTsc(cleanFiles, "tsconfig-pass2.json") : new Map(); + const pass2 = hasSyntaxErrors && cleanFiles.length > 0 ? runTsc(cleanFiles, "tsconfig-pass2.json") : new Map();🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@scripts/check-docs-code.mjs` around lines 150 - 171, The two-pass logic in runTsc is recompiling all clean TypeScript blocks even when pass1 found no syntax errors, which doubles runtime on healthy runs. Update the tsBlocks handling so pass2 only runs when pass1 actually reports at least one syntax-error file, and keep using pass1 diagnostics directly when there are no syntax failures. Use the existing runTsc, pass1, pass2, and cleanFiles flow to gate the second compilation more tightly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/workflows/docs-check.yml:
- Line 13: Set persist-credentials to false on each actions/checkout@v4 step in
the docs workflow so the GITHUB_TOKEN is not stored in the workspace. Update
both checkout invocations in the workflow jobs, since neither job needs to push
or reuse credentials, and keep the fix scoped to the checkout action
configuration.
In `@package.json`:
- Around line 34-43: The docs typecheck is using a mismatched Node API surface
because package.json pins `@types/node` to a newer major than the Node version
used by the docs-check workflow. Update the `@types/node` dependency in
package.json to match the CI Node runtime, or adjust the docs-check workflow to
run on the same Node major; use the `@types/node` entry and the docs-check
workflow as the two places to keep aligned.
In `@scripts/check-docs-code.mjs`:
- Around line 110-148: The runTsc helper is currently swallowing failed tsc/npx
executions when no parseable TS errors are emitted, causing an empty Map to be
treated as success. Update runTsc so that the execFileSync failure path in the
try/catch for npx tsc cannot silently return no errors: capture the thrown error
state and rethrow or otherwise signal a hard failure if output contains no
matched TS diagnostics. Keep the change localized to runTsc and its output
parsing logic so callers no longer treat invocation failures as passing.
---
Outside diff comments:
In @.github/workflows/docs-check.yml:
- Around line 1-32: Add an explicit least-privilege permissions setting for this
workflow so both the code-blocks and build jobs don’t inherit the default broad
GITHUB_TOKEN scope. Update the docs-check workflow by adding a top-level
permissions block, or equivalent job-level permissions on code-blocks and build,
limited to read access for repository contents. Use the workflow jobs and
triggers already defined in docs-check.yml to place the restriction without
changing the existing steps.
---
Nitpick comments:
In @.github/workflows/docs-check.yml:
- Around line 8-31: Add a workflow-level concurrency group to the docs-check
workflow so older runs are canceled when a new push/sync arrives. Update the
workflow containing the code-blocks and build jobs to use a stable group key
based on the branch or PR ref, and enable cancel-in-progress so superseded runs
do not keep executing redundant yarn install and validation steps.
In `@scripts/check-docs-code.mjs`:
- Around line 180-186: The baseline rewrite in updateBaseline is producing
nondeterministic key order because next is populated directly from failures
iteration order. Update the baseline generation path in
scripts/check-docs-code.mjs so failures are sorted by a stable key before
building next, or otherwise ensure deterministic insertion order before
fs.writeFileSync writes baselinePath. Use the updateBaseline block and the
failures collection as the place to apply the stable ordering.
- Around line 150-171: The two-pass logic in runTsc is recompiling all clean
TypeScript blocks even when pass1 found no syntax errors, which doubles runtime
on healthy runs. Update the tsBlocks handling so pass2 only runs when pass1
actually reports at least one syntax-error file, and keep using pass1
diagnostics directly when there are no syntax failures. Use the existing runTsc,
pass1, pass2, and cleanFiles flow to gate the second compilation more tightly.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 149f3f5e-4c12-4396-b2e7-fa00c64e766a
⛔ Files ignored due to path filters (1)
yarn.lockis excluded by!**/yarn.lock,!**/*.lock
📒 Files selected for processing (4)
.github/workflows/docs-check.ymlpackage.jsonscripts/check-docs-code.mjsscripts/docs-check-baseline.json
| name: Check doc code blocks | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/checkout@v4 |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win
Set persist-credentials: false on checkout steps.
Both actions/checkout@v4 steps default to persisting the GITHUB_TOKEN credential in the workspace, which is unnecessary here since neither job pushes or authenticates further. Flagged by zizmor as credential persistence (artipacked).
🔒 Proposed fix
- uses: actions/checkout@v4
+ with:
+ persist-credentials: false
- uses: actions/setup-node@v4Also applies to: 25-25
🧰 Tools
🪛 zizmor (1.26.1)
[warning] 13-13: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false
(artipacked)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/docs-check.yml at line 13, Set persist-credentials to
false on each actions/checkout@v4 step in the docs workflow so the GITHUB_TOKEN
is not stored in the workspace. Update both checkout invocations in the workflow
jobs, since neither job needs to push or reuse credentials, and keep the fix
scoped to the checkout action configuration.
Source: Linters/SAST tools
| "@types/node": "^26.1.0", | ||
| "abstractionkit": "0.4.0", | ||
| "dotenv": "^17.4.2", | ||
| "ethers": "^6.17.0", | ||
| "safe-recovery-service-sdk": "^0.0.4", | ||
| "siwe": "^3.0.0", | ||
| "tailwindcss": "^4.3.1", | ||
| "typescript": "^6.0.3" | ||
| "typescript": "^6.0.3", | ||
| "viem": "^2.54.2", | ||
| "wagmi": "^3.6.21" |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🌐 Web query:
Do @types/node major version mismatches with the Node.js runtime cause type errors with commonly used Node APIs?
💡 Result:
Yes, major version mismatches between @types/node and the Node.js runtime can cause type errors, primarily due to how TypeScript handles global declarations [1][2]. When multiple packages in a project (or its dependencies) rely on different versions of @types/node, it often results in duplicate declarations of global objects (such as AbortSignal, Buffer, or global Node.js APIs) [3][1][2]. Because TypeScript does not support multiple conflicting declarations for the same global variable, this leads to the common TS2403 error (Subsequent variable declarations must have the same type) [1][2]. The implications of a mismatch generally fall into two categories: 1. Build-Time Type Errors: If your project or its dependencies use incompatible versions, you may encounter compilation failures because the global type definitions provided by @types/node clash [3][2]. This is frequently solved by aligning all dependencies to the same major version of @types/node [3][1]. 2. Inaccurate Type Checking: - If @types/node is older than your runtime, TypeScript will not recognize newer Node.js APIs, leading to compilation errors when you use them [4]. - If @types/node is newer than your runtime, TypeScript might permit the use of APIs that are not actually available in your current Node.js environment, which can lead to runtime crashes when the code is executed [4]. To maintain stability, it is recommended to match the major version of @types/node to the major version of the Node.js environment being used [4]. In complex projects, ensure that sub-dependencies do not pull in conflicting versions of @types/node, often by using resolutions (in npm/yarn) or overrides to enforce a single version [3].
Citations:
- 1: [@types/node] a problem with AbortSignal DefinitelyTyped/DefinitelyTyped#64176
- 2:
pulumi package addfails in ts project due to incompatible@types/nodeversions pulumi/pulumi#18778 - 3: https://stackoverflow.com/questions/58519610/interface-nodemodule-cannot-simultaneously-extend-types-module-and-module
- 4: https://nodevibe.substack.com/p/using-typescript-in-nodejs
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== package.json ==\n'
cat -n package.json | sed -n '1,140p'
printf '\n== docs-check workflow ==\n'
cat -n .github/workflows/docs-check.yml | sed -n '1,120p'
printf '\n== search for node/version references ==\n'
rg -n --hidden --glob '!**/node_modules/**' 'setup-node|node-version|`@types/node`|engines' .github package.jsonRepository: candidelabs/developer-docs
Length of output: 3760
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== tsconfig files ==\n'
fd -a 'tsconfig*.json' .
printf '\n== docs-check script ==\n'
cat -n scripts/check-docs-code.mjs | sed -n '1,260p'
printf '\n== tsconfig / type references ==\n'
rg -n --hidden --glob '!**/node_modules/**' '"types"|`@types/node`|node:' tsconfig*.json . scripts package.json .github/workflowsRepository: candidelabs/developer-docs
Length of output: 12515
Align @types/node with the CI Node version. package.json pins @types/node to ^26.1.0, but .github/workflows/docs-check.yml runs the docs typecheck on Node 22. That makes the docs checker use a newer API surface than CI actually provides; either pin @types/node to 22 or bump the workflow to 26.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@package.json` around lines 34 - 43, The docs typecheck is using a mismatched
Node API surface because package.json pins `@types/node` to a newer major than the
Node version used by the docs-check workflow. Update the `@types/node` dependency
in package.json to match the CI Node runtime, or adjust the docs-check workflow
to run on the same Node major; use the `@types/node` entry and the docs-check
workflow as the two places to keep aligned.
| function runTsc(files, configName) { | ||
| const tsconfig = { | ||
| compilerOptions: { | ||
| target: "ES2022", | ||
| module: "ESNext", | ||
| moduleResolution: "Bundler", | ||
| jsx: "react-jsx", | ||
| lib: ["ES2022", "DOM"], | ||
| types: ["node"], | ||
| strict: false, | ||
| noEmit: true, | ||
| skipLibCheck: true, | ||
| esModuleInterop: true, | ||
| }, | ||
| files, | ||
| }; | ||
| const configPath = path.join(tmpDir, configName); | ||
| fs.writeFileSync(configPath, JSON.stringify(tsconfig, null, 2)); | ||
| let output = ""; | ||
| try { | ||
| execFileSync("npx", ["tsc", "-p", configPath, "--pretty", "false"], { | ||
| cwd: repoRoot, | ||
| encoding: "utf8", | ||
| maxBuffer: 64 * 1024 * 1024, | ||
| }); | ||
| } catch (err) { | ||
| output = `${err.stdout ?? ""}${err.stderr ?? ""}`; | ||
| } | ||
| const errors = new Map(); // scratchFile -> [{ line, message }] | ||
| for (const line of output.split("\n")) { | ||
| const m = line.match(/^(.*?)\((\d+),(\d+)\): (error TS\d+: .*)$/); | ||
| if (!m) continue; | ||
| const file = path.resolve(repoRoot, m[1]); | ||
| const entry = errors.get(file) ?? []; | ||
| entry.push({ line: Number(m[2]), message: m[4] }); | ||
| errors.set(file, entry); | ||
| } | ||
| return errors; | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== scripts/check-docs-code.mjs (relevant section) ==\n'
nl -ba scripts/check-docs-code.mjs | sed -n '1,220p'
printf '\n== package.json / lockfile references to typescript, npx, and node typings ==\n'
for f in package.json npm-shrinkwrap.json package-lock.json pnpm-lock.yaml yarn.lock; do
if [ -f "$f" ]; then
echo "--- $f ---"
rg -n '"typescript"|`@types/node`|npx|tsc' "$f" || true
fi
doneRepository: candidelabs/developer-docs
Length of output: 258
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== scripts/check-docs-code.mjs (relevant section) ==\n'
cat -n scripts/check-docs-code.mjs | sed -n '1,220p'
printf '\n== package.json ==\n'
cat -n package.json | sed -n '1,260p'
printf '\n== lockfile references to typescript / `@types/node` ==\n'
for f in package-lock.json npm-shrinkwrap.json pnpm-lock.yaml yarn.lock; do
if [ -f "$f" ]; then
echo "--- $f ---"
rg -n '"typescript"|`@types/node`|typescript@|`@types/node`@' "$f" || true
fi
doneRepository: candidelabs/developer-docs
Length of output: 13102
Fail closed when tsc invocation fails scripts/check-docs-code.mjs:110-148 — if npx/tsc exits before emitting error TS... lines, runTsc returns an empty map and the caller treats every TS block as passing. Surface that as a hard error instead.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@scripts/check-docs-code.mjs` around lines 110 - 148, The runTsc helper is
currently swallowing failed tsc/npx executions when no parseable TS errors are
emitted, causing an empty Map to be treated as success. Update runTsc so that
the execFileSync failure path in the try/catch for npx tsc cannot silently
return no errors: capture the thrown error state and rethrow or otherwise signal
a hard failure if output contains no matched TS diagnostics. Keep the change
localized to runTsc and its output parsing logic so callers no longer treat
invocation failures as passing.
What
Tier-1 docs CI, as discussed: static verification only, nothing executes, no network, no keys, zero impact on the shipped site (all new packages are devDependencies used only by the checker).
scripts/check-docs-code.mjsextracts every```jsonand```ts/typescript/tsxblock underdocs/(627 today). JSON mustJSON.parse; TypeScript must passtsc --noEmitagainst the pinnedabstractionkit@0.4.0, withethers,viem,wagmi,dotenv,siwe, andsafe-recovery-service-sdkresolvable.TS2339 Property 'thisMethodDoesNotExist' does not exist on type 'SafeMultiChainSigAccountV1'.scripts/docs-check-baseline.json, keyed by content hash. CI fails only on new failures. Editing a baselined block changes its hash, so it must then pass, be fixed, or get an explicit<!-- docs-check: skip -->marker. Coverage ratchets up as pages are touched..github/workflows/docs-check.ymlruns the checker plusyarn build(broken-link check) on every PR and on main.yarn check-docsruns it locally;--update-baselinerewrites the baseline.Numbers
627 blocks checked: 179 pass today, 448 baselined, 0 new. The baselined set is the to-do list for making pages self-contained, which is what the agent-consumability review called for anyway.
Verification
yarn check-docsexits 0; the synthetic semantic-error test fails it as expected;yarn buildstill passes.🤖 Generated with Claude Code
Summary by CodeRabbit