@redpanda-data/docs-extensions-and-macros 4.15.2 → 4.15.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,39 @@
1
+ 'use strict'
2
+
3
+ /**
4
+ * Shared URL utility functions for extensions
5
+ */
6
+
7
+ /**
8
+ * Convert HTML URL to markdown URL
9
+ * @param {string} htmlUrl - The HTML URL (e.g., '/path/to/page/' or '/path/to/page/index.html')
10
+ * @returns {string} - The markdown URL (e.g., '/path/to/page.md')
11
+ */
12
+ function toMarkdownUrl(htmlUrl) {
13
+ if (!htmlUrl) return ''
14
+
15
+ // Handle root path
16
+ if (htmlUrl === '/' || htmlUrl === '/index.html') {
17
+ return '/index.md'
18
+ }
19
+
20
+ // Remove trailing slash
21
+ let mdUrl = htmlUrl.replace(/\/$/, '')
22
+
23
+ // Replace /index.html with .md
24
+ mdUrl = mdUrl.replace(/\/index\.html$/, '.md')
25
+
26
+ // Replace .html with .md
27
+ mdUrl = mdUrl.replace(/\.html$/, '.md')
28
+
29
+ // If it doesn't end with .md yet, add it
30
+ if (!mdUrl.endsWith('.md')) {
31
+ mdUrl += '.md'
32
+ }
33
+
34
+ return mdUrl
35
+ }
36
+
37
+ module.exports = {
38
+ toMarkdownUrl,
39
+ }
@@ -77,6 +77,10 @@ IMPORTANT: Extensions must be registered under the `antora.extensions` key in yo
77
77
  * **replace-attributes-in-attachments** - Replace AsciiDoc attributes in attached files
78
78
  * **collect-bloblang-samples** - Collect Bloblang code samples
79
79
 
80
+ === Git integration
81
+ * **git-full-clone** - Ensure full git clones for accurate git dates
82
+ * **add-git-dates** - Add git-based created and modified dates to pages
83
+
80
84
  === Content enhancement
81
85
  * **add-global-attributes** - Add attributes globally to all pages
82
86
  * **add-pages-to-root** - Add pages to the root navigation
@@ -766,3 +766,427 @@ The processed attribute can be used in Handlebars templates:
766
766
  {{/each}}
767
767
  </div>
768
768
  ```
769
+
770
+ == Git full clone
771
+
772
+ This extension ensures Antora uses full git clones instead of shallow clones for remote repositories. Full git history is required for accurate git date extraction by the `add-git-dates` extension.
773
+
774
+ The extension uses a two-phase approach:
775
+
776
+ 1. **Phase 1**: Sets `git.depth = 0` in playbook during registration (best effort)
777
+ 2. **Phase 2**: After content aggregation, detects repos with a `shallow` file and runs `git fetch --unshallow` to convert them to full clones
778
+
779
+ This two-phase approach works around Antora's default shallow clone behavior, which can't always be overridden via playbook configuration alone.
780
+
781
+ === How it works
782
+
783
+ . During extension registration, modifies playbook to request `depth=0` for all remote repos
784
+ . After Antora aggregates content, checks each repo's git directory for a `shallow` file
785
+ . For any shallow repos found, runs `git fetch --unshallow` with timeout protection
786
+ . On subsequent builds with warm cache, repos are already full clones (no unshallow needed)
787
+
788
+ === Performance
789
+
790
+ **First build** (cold cache):
791
+
792
+ - Unshallow: ~1-5 seconds per remote repo
793
+ - Total overhead: ~5-20 seconds depending on repo count
794
+
795
+ **Subsequent builds** (warm cache):
796
+
797
+ - Unshallow: 0 seconds (repos already full clones)
798
+ - Total overhead: minimal
799
+
800
+ **Scalability:**
801
+
802
+ - 1k commits: ~1-2s unshallow time
803
+ - 5k commits: ~3-5s unshallow time
804
+ - 10k commits: ~5-10s unshallow time
805
+ - 50k+ commits: Consider increasing timeout or pre-populating cache
806
+
807
+ === Environment variables
808
+
809
+ This extension does not require any environment variables.
810
+
811
+ === Configuration options
812
+
813
+ The extension accepts the following configuration options:
814
+
815
+ skipUnshallow (optional, default: false):: Set to `true` to skip the unshallow phase entirely. Useful for air-gapped CI/CD environments or when git dates are not needed.
816
+
817
+ unshallowTimeout (optional, default: 60000):: Timeout in milliseconds for the unshallow operation per repository. Increase this for very large repositories (50k+ commits).
818
+
819
+ === Registration
820
+
821
+ Basic configuration (recommended):
822
+
823
+ [,yaml]
824
+ ----
825
+ antora:
826
+ extensions:
827
+ - require: '@redpanda-data/docs-extensions-and-macros/extensions/git-full-clone'
828
+ ----
829
+
830
+ With custom timeout for large repos:
831
+
832
+ [,yaml]
833
+ ----
834
+ antora:
835
+ extensions:
836
+ - require: '@redpanda-data/docs-extensions-and-macros/extensions/git-full-clone'
837
+ unshallowTimeout: 120000 # 2 minutes for very large repos
838
+ ----
839
+
840
+ For air-gapped environments:
841
+
842
+ [,yaml]
843
+ ----
844
+ antora:
845
+ extensions:
846
+ - require: '@redpanda-data/docs-extensions-and-macros/extensions/git-full-clone'
847
+ skipUnshallow: true # Skip network-dependent unshallow
848
+ ----
849
+
850
+ === Production considerations
851
+
852
+ **Netlify/CI builds:**
853
+
854
+ - First build or cache clear: Pays unshallow cost (~5-20s)
855
+ - Subsequent builds with warm cache: No unshallow needed
856
+ - Consider pre-populating Antora cache in CI for faster cold builds
857
+
858
+ **Timeout protection:**
859
+
860
+ - Default 60-second timeout prevents hanging on slow networks
861
+ - Graceful failure: Build continues even if unshallow times out
862
+ - Git dates may be incomplete for timed-out repos (warning logged)
863
+
864
+ **Error handling:**
865
+
866
+ - Network failures don't break the build
867
+ - Timeout failures are logged with guidance to increase timeout
868
+ - Falls back to available git history if unshallow fails
869
+
870
+ **Optimization for very large repos (50k+ commits):**
871
+
872
+ 1. Increase `unshallowTimeout` to 120000ms or higher
873
+ 2. Pre-populate Antora cache with full clones in CI/CD
874
+ 3. Use persistent cache in Netlify to avoid repeated unshallows
875
+ 4. Consider implementing Neon KV caching for git dates (future optimization)
876
+
877
+ == Add Git dates
878
+
879
+ This extension adds accurate git-based created and modified dates to documentation pages by analyzing commit history. It uses isomorphic-git (bundled with Antora) to walk the commit history of each repository and detects actual file modifications by comparing tree objects between commits.
880
+
881
+ The extension adds the following attributes to each page:
882
+
883
+ - `page-git-created-date`: The date when the file was first committed (YYYY-MM-DD format)
884
+ - `page-git-modified-date`: The date when the file was last modified (YYYY-MM-DD format)
885
+
886
+ These attributes are available for use in UI templates (for metadata, display banners, etc.) and are exported to markdown files for AI consumption.
887
+
888
+ === How it works
889
+
890
+ . Groups pages by repository AND branch (same repo can have multiple branches: v/23.3, v/24.1, main)
891
+ . Walks git log once per repo+branch using isomorphic-git
892
+ . Compares tree objects between commits to detect actual file modifications (not just file existence)
893
+ . Builds a filepath → {created, modified} map for each repo+branch
894
+ . Applies dates to pages based on their source file path
895
+
896
+ This approach is **O(commits)** instead of **O(files × commits)**, making it efficient for large repositories.
897
+
898
+ === Performance
899
+
900
+ - **Full clone** (depth=0): ~15s for 4128 pages across 12 branches (14.5s git processing)
901
+ - **Per-page overhead**: ~3-5ms per page
902
+
903
+ === Environment variables
904
+
905
+ This extension does not require any environment variables.
906
+
907
+ === Configuration options
908
+
909
+ This extension does not require configuration options. However, it works best with **full git clones** rather than shallow clones.
910
+
911
+ To enable full clones for remote repositories, use the `git-full-clone` extension:
912
+
913
+ ```yaml
914
+ antora:
915
+ extensions:
916
+ - require: '@redpanda-data/docs-extensions-and-macros/extensions/git-full-clone'
917
+ - require: '@redpanda-data/docs-extensions-and-macros/extensions/add-git-dates'
918
+ ```
919
+
920
+ === Registration
921
+
922
+ ```yaml
923
+ antora:
924
+ extensions:
925
+ - require: '@redpanda-data/docs-extensions-and-macros/extensions/add-git-dates'
926
+ ```
927
+
928
+ === UI integration
929
+
930
+ The git dates are available in Handlebars templates via `page.attributes`:
931
+
932
+ ```html
933
+ {{#with page.attributes.git-created-date}}
934
+ <meta name="datePublished" content="{{this}}">
935
+ {{/with}}
936
+ {{#with page.attributes.git-modified-date}}
937
+ <meta name="dateModified" content="{{this}}">
938
+ {{/with}}
939
+ ```
940
+
941
+ They are also included in schema.org structured data for SEO.
942
+
943
+ == FAQ structured data
944
+
945
+ This extension generates schema.org FAQPage JSON-LD for better SEO and Google rich results. Writers define FAQs using page attributes, and the extension generates compliant structured data in the page `<head>`.
946
+
947
+ === Environment variables
948
+
949
+ This extension does not require any environment variables.
950
+
951
+ === Configuration options
952
+
953
+ This extension does not require configuration options.
954
+
955
+ === Registration
956
+
957
+ ```yaml
958
+ antora:
959
+ extensions:
960
+ - require: '@redpanda-data/docs-extensions-and-macros/extensions/add-faq-structured-data'
961
+ ```
962
+
963
+ === Usage
964
+
965
+ Add FAQ attributes to your AsciiDoc page header:
966
+
967
+ ```asciidoc
968
+ = My Documentation Page
969
+ :page-faq-1-question: How do I install Redpanda?
970
+ :page-faq-1-answer: Download from redpanda.com and run the installer. See our installation guide for details.
971
+ :page-faq-1-anchor: #installation
972
+
973
+ :page-faq-2-question: What are the system requirements?
974
+ :page-faq-2-answer: You need at least 2GB of RAM and 2 CPU cores for development.
975
+ :page-faq-2-anchor: #requirements
976
+
977
+ :page-faq-3-question: Does Redpanda support Kafka APIs?
978
+ :page-faq-3-answer: Yes! Redpanda is fully compatible with Kafka APIs.
979
+ ```
980
+
981
+ **Required attributes per FAQ:**
982
+
983
+ - `page-faq-N-question` - The FAQ question
984
+ - `page-faq-N-answer` - The FAQ answer
985
+
986
+ **Optional:**
987
+
988
+ - `page-faq-N-anchor` - Link to page section (e.g., `#installation`)
989
+
990
+ **Tips:**
991
+
992
+ - FAQs must be numbered sequentially (1, 2, 3...)
993
+ - Answers can reference prose content: "See our installation guide for details"
994
+ - Anchors create deep links to related content on the page
995
+ - Keep answers concise (Google truncates after ~300 characters)
996
+
997
+ === Generated output
998
+
999
+ The extension generates schema.org FAQPage JSON-LD in the page `<head>`:
1000
+
1001
+ ```json
1002
+ {
1003
+ "@type": "FAQPage",
1004
+ "mainEntity": [
1005
+ {
1006
+ "@type": "Question",
1007
+ "name": "How do I install Redpanda?",
1008
+ "acceptedAnswer": {
1009
+ "@type": "Answer",
1010
+ "text": "Download from redpanda.com and run the installer."
1011
+ },
1012
+ "url": "https://docs.redpanda.com/page#installation"
1013
+ }
1014
+ ]
1015
+ }
1016
+ ```
1017
+
1018
+ This structured data appears in Google search results as rich snippets, improving SEO and click-through rates.
1019
+
1020
+ == Convert pages to markdown
1021
+
1022
+ The `convert-to-markdown` extension converts all HTML pages to markdown format and exports them as `.md` files alongside the HTML. This creates AI-friendly markdown versions of the documentation with proper frontmatter.
1023
+
1024
+ === Features
1025
+
1026
+ - Converts all HTML pages to GitHub-Flavored Markdown
1027
+ - Exports as `.md` files (e.g., `/page.html` → `/page.md`)
1028
+ - Adds YAML frontmatter with page metadata
1029
+ - Decodes HTML entities in titles
1030
+ - Supports content negotiation via `Accept: text/markdown` header
1031
+ - Stores markdown in `page.markdownContents` for other extensions
1032
+
1033
+ === Configuration
1034
+
1035
+ Add to your Antora playbook:
1036
+
1037
+ [,yaml]
1038
+ ----
1039
+ antora:
1040
+ extensions:
1041
+ - require: '@redpanda-data/docs-extensions-and-macros/extensions/convert-to-markdown'
1042
+ ----
1043
+
1044
+ === Frontmatter attributes
1045
+
1046
+ The extension exports these attributes to YAML frontmatter:
1047
+
1048
+ - `page-title`: Page title
1049
+ - `page-description`: Page description
1050
+ - `page-component-name`, `page-component-version`: Component info
1051
+ - `page-git-created-date`, `page-git-modified-date`: Git dates (if git-dates extension enabled)
1052
+ - `page-categories`: Categories
1053
+ - `page-topic-type`: Topic type (tutorial, how-to, concept, reference)
1054
+ - `page-personas`: Target audience
1055
+ - Custom attributes from allowlist
1056
+
1057
+ === Example output
1058
+
1059
+ [,yaml]
1060
+ ----
1061
+ ---
1062
+ title: "Get started with Redpanda"
1063
+ description: "Quick start guide for Redpanda"
1064
+ component: ROOT
1065
+ version: current
1066
+ created: 2024-01-15
1067
+ modified: 2024-03-29
1068
+ categories:
1069
+ - Getting Started
1070
+ topic-type: tutorial
1071
+ personas:
1072
+ - developer
1073
+ ---
1074
+
1075
+ # Get started with Redpanda
1076
+
1077
+ Your markdown content here...
1078
+ ----
1079
+
1080
+ == Generate llms.txt exports
1081
+
1082
+ The `convert-llms-to-txt` extension generates AI-optimized documentation exports in plain text format following the llms.txt standard.
1083
+
1084
+ === Features
1085
+
1086
+ - Generates `llms.txt` from a curated `llms.adoc` page in the home component
1087
+ - Creates `llms-full.txt` with all documentation pages
1088
+ - Generates component-specific exports (e.g., `ROOT-full.txt`, `cloud-full.txt`)
1089
+ - Unpublishes the HTML version of llms.adoc
1090
+ - Replaces site-url attribute dynamically for preview builds
1091
+
1092
+ === Configuration
1093
+
1094
+ Add to your Antora playbook after `convert-to-markdown`:
1095
+
1096
+ [,yaml]
1097
+ ----
1098
+ antora:
1099
+ extensions:
1100
+ - require: '@redpanda-data/docs-extensions-and-macros/extensions/convert-to-markdown'
1101
+ - require: '@redpanda-data/docs-extensions-and-macros/extensions/convert-llms-to-txt'
1102
+ ----
1103
+
1104
+ === Setup
1105
+
1106
+ Create `modules/home/pages/llms.adoc` in your docs repository with curated documentation overview content. The extension will:
1107
+
1108
+ 1. Convert it to markdown
1109
+ 2. Place `llms.txt` at site root
1110
+ 3. Generate `llms-full.txt` with all pages
1111
+ 4. Generate component-specific full.txt files
1112
+ 5. Unpublish the HTML version
1113
+
1114
+ === Generated files
1115
+
1116
+ - `{site-url}/llms.txt` - Curated overview
1117
+ - `{site-url}/llms-full.txt` - Complete documentation
1118
+ - `{site-url}/ROOT-full.txt` - Self-Managed docs only
1119
+ - `{site-url}/cloud-full.txt` - Cloud docs only
1120
+ - `{site-url}/redpanda-connect-full.txt` - Connect docs only
1121
+
1122
+ === Attributes
1123
+
1124
+ The extension replaces `{site-url}` attribute with:
1125
+
1126
+ - `playbook.site.url` in production builds
1127
+ - `DEPLOY_PRIME_URL` environment variable in preview builds (when `PREVIEW=true`)
1128
+
1129
+ == Convert sitemaps to markdown
1130
+
1131
+ The `convert-sitemap-to-markdown` extension generates human-readable markdown versions of XML sitemap files.
1132
+
1133
+ === Features
1134
+
1135
+ - Converts all sitemap XML files to markdown
1136
+ - Generates individual `.md` files for each sitemap
1137
+ - Creates master `sitemap-all.md` combining all pages
1138
+ - Organizes URLs by component/version
1139
+ - Includes page metadata (modified dates, priority)
1140
+ - Uses Antora content catalog (no filesystem operations)
1141
+
1142
+ === Configuration
1143
+
1144
+ Add to your Antora playbook:
1145
+
1146
+ [,yaml]
1147
+ ----
1148
+ antora:
1149
+ extensions:
1150
+ - require: '@redpanda-data/docs-extensions-and-macros/extensions/convert-sitemap-to-markdown'
1151
+ ----
1152
+
1153
+ === Generated files
1154
+
1155
+ For a site with component-specific sitemaps:
1156
+
1157
+ - `sitemap.md` - Sitemap index with links to sub-sitemaps
1158
+ - `sitemap-ROOT.md` - Self-Managed pages
1159
+ - `sitemap-home.md` - Home component pages
1160
+ - `sitemap-redpanda-cloud.md` - Cloud pages
1161
+ - `sitemap-redpanda-connect.md` - Connect pages
1162
+ - `sitemap-all.md` - Master combined sitemap with all pages
1163
+
1164
+ === Output format
1165
+
1166
+ [,markdown]
1167
+ ----
1168
+ # Complete documentation sitemap
1169
+
1170
+ > Combined view of all 4,126 documentation pages from 8 sitemaps
1171
+
1172
+ ## Source sitemaps
1173
+
1174
+ - [sitemap-ROOT.xml](sitemap-ROOT.md)
1175
+ - [sitemap-home.xml](sitemap-home.md)
1176
+
1177
+ ## Pages
1178
+
1179
+ Total pages: 4,126
1180
+
1181
+ ### Current
1182
+
1183
+ - [Get Started](https://docs.redpanda.com/current/get-started/) (modified: 2024-03-29)
1184
+ - [Deploy](https://docs.redpanda.com/current/deploy/) (modified: 2024-03-28)
1185
+ ----
1186
+
1187
+ === Use cases
1188
+
1189
+ - AI agents: Provide sitemap-all.md for quick site navigation
1190
+ - Documentation planning: Review complete site structure
1191
+ - Content audits: Identify gaps or outdated content by date
1192
+ - User discovery: Help users find content through browseable map
@@ -0,0 +1,153 @@
1
+ 'use strict'
2
+
3
+ /**
4
+ * Generates FAQPage JSON-LD structured data for SEO.
5
+ *
6
+ * USAGE:
7
+ * :page-faq-1-question: How do I install Redpanda?
8
+ * :page-faq-1-answer: Download from redpanda.com and run the installer.
9
+ * :page-faq-1-anchor: #installation (optional - links to section)
10
+ *
11
+ * :page-faq-2-question: What are the system requirements?
12
+ * :page-faq-2-answer: You need at least 2GB RAM and 2 CPU cores.
13
+ * :page-faq-2-anchor: #requirements
14
+ *
15
+ * The extension:
16
+ * - Generates schema.org FAQPage JSON-LD in <head>
17
+ * - Supports multiple FAQs numbered sequentially (1, 2, 3...)
18
+ * - Anchor is optional and adds URL to the FAQ question
19
+ * - Writers can reference existing page content in answers
20
+ */
21
+
22
+ /**
23
+ * Extract FAQ entries from page attributes
24
+ * @param {Object} attributes - Page attributes object
25
+ * @param {Object} logger - Logger instance
26
+ * @returns {Array<{question: string, answer: string, anchor?: string}>}
27
+ */
28
+ function extractFaqs(attributes, logger) {
29
+ const faqs = []
30
+ const faqNumbers = new Set()
31
+
32
+ // Find all FAQ numbers by scanning for -question attributes
33
+ Object.keys(attributes).forEach(key => {
34
+ const match = key.match(/^page-faq-(\d+)-question$/)
35
+ if (match) {
36
+ faqNumbers.add(parseInt(match[1], 10))
37
+ }
38
+ })
39
+
40
+ if (faqNumbers.size === 0) return faqs
41
+
42
+ // Extract FAQs in numerical order
43
+ const sortedNumbers = Array.from(faqNumbers).sort((a, b) => a - b)
44
+
45
+ sortedNumbers.forEach(num => {
46
+ const question = attributes[`page-faq-${num}-question`]
47
+ const answer = attributes[`page-faq-${num}-answer`]
48
+ const anchor = attributes[`page-faq-${num}-anchor`]
49
+
50
+ // Both question and answer are required
51
+ if (!question) {
52
+ logger.warn(`FAQ ${num}: question missing`)
53
+ return
54
+ }
55
+
56
+ if (!answer) {
57
+ logger.warn(`FAQ ${num}: answer missing`)
58
+ return
59
+ }
60
+
61
+ const faq = {
62
+ question: question.trim(),
63
+ answer: answer.trim()
64
+ }
65
+
66
+ if (anchor) {
67
+ faq.anchor = anchor.trim()
68
+ }
69
+
70
+ faqs.push(faq)
71
+ })
72
+
73
+ return faqs
74
+ }
75
+
76
+ /**
77
+ * Generate FAQPage JSON-LD structure
78
+ * @param {Array} faqs - Array of FAQ objects
79
+ * @param {string} baseUrl - Base URL for the page
80
+ * @returns {Object} FAQPage JSON-LD object
81
+ */
82
+ function generateFaqJsonLd(faqs, baseUrl) {
83
+ const mainEntity = faqs.map(faq => {
84
+ const question = {
85
+ '@type': 'Question',
86
+ 'name': faq.question,
87
+ 'acceptedAnswer': {
88
+ '@type': 'Answer',
89
+ 'text': faq.answer
90
+ }
91
+ }
92
+
93
+ // Add URL with anchor if provided
94
+ if (faq.anchor) {
95
+ const anchor = faq.anchor.startsWith('#') ? faq.anchor : `#${faq.anchor}`
96
+ question.url = `${baseUrl}${anchor}`
97
+ }
98
+
99
+ return question
100
+ })
101
+
102
+ return {
103
+ '@type': 'FAQPage',
104
+ 'mainEntity': mainEntity
105
+ }
106
+ }
107
+
108
+ module.exports.register = function () {
109
+ const logger = this.getLogger('add-faq-structured-data-extension')
110
+ let playbook
111
+
112
+ this.once('playbookBuilt', ({ playbook: pb }) => {
113
+ playbook = pb
114
+ })
115
+
116
+ this.on('documentsConverted', ({ contentCatalog }) => {
117
+ const pages = contentCatalog.getPages()
118
+ let processedCount = 0
119
+ let totalFaqs = 0
120
+ const siteUrl = playbook?.site?.url || 'https://docs.redpanda.com'
121
+
122
+ pages.forEach(page => {
123
+ const attributes = page.asciidoc?.attributes
124
+ if (!attributes) return
125
+
126
+ // Extract FAQs from attributes
127
+ const faqs = extractFaqs(attributes, logger)
128
+
129
+ if (faqs.length === 0) return
130
+
131
+ // Generate base URL for the page
132
+ let baseUrl = ''
133
+ if (page.pub?.url) {
134
+ baseUrl = `${siteUrl}${page.pub.url}`
135
+ }
136
+
137
+ // Generate FAQPage JSON-LD
138
+ const faqJsonLd = generateFaqJsonLd(faqs, baseUrl)
139
+
140
+ // Store as JSON string in page attribute for UI template
141
+ attributes['page-faq-json-ld'] = JSON.stringify(faqJsonLd, null, 2)
142
+
143
+ processedCount++
144
+ totalFaqs += faqs.length
145
+
146
+ logger.debug(`Added ${faqs.length} FAQs to ${page.src.relative}`)
147
+ })
148
+
149
+ if (processedCount > 0) {
150
+ logger.info(`Generated FAQ structured data for ${processedCount} pages (${totalFaqs} total FAQs)`)
151
+ }
152
+ })
153
+ }