fix: update docgen templates and automate API reference generation#140
fix: update docgen templates and automate API reference generation#140
Conversation
…t fixes
- Remove fuzzy reference matching that caused cross-standard contamination
(e.g., ERC20 constructor referencing Governor.name, IERC6909Metadata.symbol)
- Add same-page preference so {name} on an ERC20 page resolves to ERC20.name
- Use absolute paths for cross-page links via API_DOCS_PATH
- Fix multi-line callout handling (NOTE: blocks spanning multiple lines)
- Escape bare < in natspec (e.g., < 0x80) to prevent MDX parse errors
- Convert Antora xref patterns to markdown links
- Convert AsciiDoc headings (====) to markdown (####)
- Clean up orphaned block delimiters
- Strip /index from links for Fumadocs compatibility
Regenerated from openzeppelin-contracts (master) and openzeppelin-community-contracts (master) using updated docgen templates. - All cross-page links use absolute paths - No cross-standard contamination in reference resolution - No legacy AsciiDoc patterns in output - 0 link validation errors
Wrap inherited function/event/error listings in <details>/<summary> toggles so the contract's own items are prominent and inherited items are accessible but not overwhelming. Only renders toggles for inherited contracts that have items for that section (no empty toggles). Also regenerates API reference output with updated template.
The script now injects canonical MDX templates from docgen/templates-md/ into the cloned source repo before running docgen. This means source repos don't need to have MDX templates committed — the docs monorepo is the single source of truth. - Copies templates-md/ and config-md.js into cloned repo - Customizes API_DOCS_PATH, GitHub link, and import path per source repo - Patches hardhat config to use config-md - Overwrites config.js so prepare-docs.sh scripts still work - Handles local paths, broken symlinks, both .js and .ts hardhat configs - Adds --skip-template-inject flag for repos that already have MDX templates
Regenerated via generate-api-docs.js with template injection for: - openzeppelin-contracts (master) -> content/contracts/5.x/api/ - openzeppelin-community-contracts (master) -> content/community-contracts/api/ - openzeppelin-confidential-contracts (master) -> content/confidential-contracts/api/ All repos: 0 link errors, clean build.
New GitHub Actions receiver workflows for API doc generation: - generate-api-docs-contracts.yml: versioned paths (5.x/api), tag-based - generate-api-docs-confidential-contracts.yml: non-versioned (api/), tag-based Both include link validation step before PR creation.
Adds postinstall + lint:links step before PR creation to catch broken links in generated docs before they're merged.
Prevents temp directories from showing in source control if a generate-api-docs.js run is interrupted before cleanup.
For repos using Move, Rust, Cairo, or other languages that generate MDX through their own tooling, the script can now skip template injection and docgen entirely and just copy pre-generated MDX files. Usage: node generate-api-docs.js --repo <url> --api-output <dir> --pre-generated <path> This keeps the workflow generator-agnostic: Solidity repos use the default injection mode, other repos use --pre-generated.
✅ Deploy Preview for openzeppelin-docs-v2 ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
Replace the old manual template-copy instructions with documentation for the template injection approach and automated GitHub workflow. Covers Solidity repos, non-Solidity repos (--pre-generated flag), canonical template locations, and versioning strategy.
- Use execFileSync for git clone to prevent command injection from CLI arguments (array args instead of shell string interpolation) - Add explicit permissions block to all receiver workflows to limit GITHUB_TOKEN scope to contents:write and pull-requests:write
There was a problem hiding this comment.
This is still fuzzy. I believe we should update it to use the same-page preference logic that findBestMatch has
| result = result.replace(/^(NOTE|TIP):\s*(.+)$/gm, '<Callout>\n$2\n</Callout>'); | ||
| result = result.replace(/^(IMPORTANT|WARNING):\s*(.+)$/gm, '<Callout type="warn">\n$2\n</Callout>'); | ||
| // Handle single/multi-line admonitions (NOTE: content until blank line) | ||
| result = result.replace(/^(NOTE|TIP):\s*([\s\S]*?)(?=\n\n|$)/gm, '<Callout>\n$2\n</Callout>'); |
There was a problem hiding this comment.
With the m flag, $ matches end of each line, not end of string. This means multi-line content after NOTE: would still only capture the first line
Maybe this would work but it's a bit too dense:
| result = result.replace(/^(NOTE|TIP):\s*([\s\S]*?)(?=\n\n|$)/gm, '<Callout>\n$2\n</Callout>'); | |
| result = result.replace(/(^|\n)(NOTE|TIP):\s*([\s\S]*?)(?=\n\n|$(?![\s\S]))/g, '$1<Callout>\n$3\n</Callout>'); |
It may be better to match with a different strategy here. But please confirm
There was a problem hiding this comment.
The script does two things that overlap:
- Copies
config-md.jstodocs/config-md.jsAND also overwritesdocs/config.jswithconfig-md.jscontent. - Then patches
hardhat.config.jsto changerequire('./docs/config')→require('./docs/config-md').
The hardhat overwrite is redundant since config.js is already overwritten with the md config content. I believe we should simplify it. It seems we're carrying older design decisions and I'm worried the script might get even more confusing later on
| if (config.includes("require('./docs/config')")) { | ||
| config = config.replace( | ||
| "require('./docs/config')", | ||
| "require('./docs/config-md')", | ||
| ); |
There was a problem hiding this comment.
I find this expression pretty brittle because it's relying on single quotes '. If we change a linter or something like that, this may silently fail in the future.
Given my previous comment, this replace might be irrelevant
There was a problem hiding this comment.
These workflows (generate-api-docs-contracts.yml, generate-api-docs-confidential-contracts.yml, generate-api-docs-community-contracts.yml) are almost identical. We should parameterize them to reuse the logic
| BRANCH_NAME=$(echo "$BRANCH_NAME" | sed 's/\//-/g' | sed 's/OpenZeppelin-//g') | ||
|
|
||
| git checkout -b "$BRANCH_NAME" | ||
| git add . |
There was a problem hiding this comment.
This is too broad, maybe:
| git add . | |
| git add content/ examples/ |
| BRANCH_NAME=$(echo "$BRANCH_NAME" | sed 's/\//-/g' | sed 's/OpenZeppelin-//g') | ||
|
|
||
| git checkout -b "$BRANCH_NAME" | ||
| git add . |
|
|
||
| Auto-generated via workflow_dispatch" \ | ||
| --base main \ | ||
| --head "$BRANCH_NAME" || echo "⚠️ Pull Request creation failed" |
There was a problem hiding this comment.
If the PR creation fails we're only logging the failure. I believe we should fail this step or use GIthub CI annotations (e.g. ::warning / ::error)
| <Callout type="warn"> | ||
| In empty (or nearly empty) ERC-4626 vaults, deposits are at high risk of being stolen through frontrunning | ||
| [CAUTION] | ||
| #### In empty (or nearly empty) ERC-4626 vaults, deposits are at high risk of being stolen through frontrunning |

Summary
Details
Template fixes (docgen/templates-md/helpers.js)
The previous helpers.js used fuzzy matching for natspec references, which caused cross-standard contamination. For example, the ERC20 constructor description referenced Governor.name and IERC6909Metadata.symbol instead of ERC20.name and ERC20.symbol.
Fixes include:
<escaping in natspec to prevent MDX parse errorsCollapsible inherited functions (docgen/templates-md/contract.hbs)
Inherited function/event/error listings in TOC sections are now wrapped in collapsible toggles. The contract's own items are visible by default; inherited items are accessible but not overwhelming. Empty toggles are filtered out.
Template injection (scripts/generate-api-docs.js)
The script now injects canonical MDX templates from docgen/templates-md/ into the cloned source repo before running docgen. This makes the docs monorepo the single source of truth for templates. Source repos do not need MDX templates committed.
The script also supports a --pre-generated flag for non-Solidity repos that generate MDX through their own tooling.
Receiver workflows
New workflows for openzeppelin-contracts (versioned, tag-based) and confidential-contracts (non-versioned, tag-based). Link validation step added to all receiver workflows including the existing community-contracts workflow.
Test plan
pnpm run check(0 errors)pnpm run build(clean)