feat(docs): content variants — reuse one markdown file across navigation entries - #17640
feat(docs): content variants — reuse one markdown file across navigation entries#17640devin-ai-integration[bot] wants to merge 4 commits into
Conversation
…ages Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
There was a problem hiding this comment.
AI Review Summary
Solid, well-tested feature. Main concerns: the applyPageVariant call inside the raw-markdown loop runs before <Markdown src> inclusion (so raw markdown for variant pages can contain unresolved snippet variant syntax — presumably intentional but worth confirming), a duplicate-variant-page collision risk with the ~ separator, and getVariantPages failing hard while other places warn. Also a couple of regex robustness issues in applyContentVariant.
- 🟡 3 warning(s)
- 🔵 2 suggestion(s)
To request another review, comment /ai-review on this pull request.
There was a problem hiding this comment.
Devin Review found 4 potential issues.
2 flags not posted on this PR by your GitHub settings — view them in Devin Review. (Configure)
…keep literal <Variant> in code
- apply variant to page sources before frontmatter extraction so title/slug/sidebar-title resolve per variant
- section `variant` now applies to the section overview page (virtual path + source tracking)
- translation loading looks up translated files by source path and registers under the logical variant path; getTranslationPages applies the variant
- <Variant> tags inside fenced/inline code are left as literal text; {{variant.*}} still substitutes everywhere
- regenerate stale versions/products JSON schemas (lint CI)
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…ether, nested <Variant> warning, tighter name attr match Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Docs Generation Benchmark ResultsComparing PR branch against median of 5 nightly run(s) on
Docs generation runs |
SDK Generation Benchmark ResultsComparing PR branch against median of 5 nightly run(s) on Full benchmark table (click to expand)
main (generator): generator-only time via --skip-scripts (includes Docker image build, container startup, IR parsing, and code generation — this is the same Docker-based flow customers use via |
Description
Linear ticket: Refs (none)
Lets a single markdown file be referenced from several places in
docs.yml, each rendered as its own page with variant-specific code blocks, links, and text. Motivation: docs teams maintaining 5–8 near-identical articles (e.g. Apache vs NGINX config, mobile SDK v1 vs v2 guides).Design notes:
<Variant>blocks and{{variant.<key>}}are resolved in the CLI after<Markdown src>inclusion, so centralized snippets can carry variant syntax.(file, variant)pair is registered under a virtual page idpages/shared/server-config~nginx.mdx(getVariantPageFilepath) so the two references don't collide inparsedDocsConfig.pages. The virtual path lives in the same directory as the source, so relative<Markdown src>/<Code src>/image resolution is unchanged; edit-this-page URLs use the real source path.ConfigError. Unresolved{{variant.x}}placeholders and<Variant>blocks on a page with no variant selected produce warnings (all variant blocks are stripped in that case).<Variant>blocks are not supported (regex-based, same approach as other markdown utils).Changes Made
fern/apis/docs-yml/definition/docs.yml: top-levelvariants: map<VariantId, map<string,string>>;variantonPageConfigurationandSectionConfiguration(viaWithVariant). SDK + JSON schema regenerated.docs-markdown-utils/applyContentVariant.ts: pure function — keeps/strips<Variant name="a, b">blocks, substitutes{{variant.key}}, reports missing keys.configuration-loader:parsePageConfigresolvesitem.variant ?? inheritedVariant, setsabsolutePathto the virtual path andvariant.sourceAbsolutePathto the real file;getAllPageFiles/getVariantPagesload content from source and register under the virtual path;getAllPages(used by validators) still returns deduplicated physical files.docs-resolver:applyPageVariantruns on raw markdown and again afterreplaceReferencedMarkdown; edit-this-page URL uses the source path.docs-validator:variants: noopin the docs.yml AST visitor.packages/cli/cli/changes/unreleased/add-docs-content-variants.yml(feat).Testing
docs-markdown-utils/src/__test__/applyContentVariant.test.ts(block selection, multi-name blocks, quoted/JSXname, inline substitution, missing values, no variant).docs-resolver/src/__test__/content-variants.test.ts+fixtures/content-variants: same file →nginx/configurationandapache/configurationpages with distinct content, section-inherited variant, variant syntax inside a<Markdown src>snippet, frontmatter title substitution.pnpm compile,pnpm lint:biome,pnpm format:fix, cspell on new files;turbo run testfor docs-resolver, configuration-loader, docs-markdown-utils, docs-validator all green.Link to Devin session: https://app.devin.ai/sessions/6c13066a819e47b2aad130b1665b6858
Open in Devin Desktop: https://app.devin.ai/desktop/session/6c13066a819e47b2aad130b1665b6858?variant=devin