@redpanda-data/docs-extensions-and-macros 4.12.6 → 4.13.0
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.
- package/README.adoc +33 -1064
- package/bin/doc-tools-mcp.js +720 -0
- package/bin/doc-tools.js +1050 -50
- package/bin/mcp-tools/antora.js +153 -0
- package/bin/mcp-tools/cache.js +89 -0
- package/bin/mcp-tools/cloud-regions.js +127 -0
- package/bin/mcp-tools/content-review.js +196 -0
- package/bin/mcp-tools/crd-docs.js +153 -0
- package/bin/mcp-tools/frontmatter.js +138 -0
- package/bin/mcp-tools/generated-docs-review.js +887 -0
- package/bin/mcp-tools/helm-docs.js +152 -0
- package/bin/mcp-tools/index.js +245 -0
- package/bin/mcp-tools/job-queue.js +468 -0
- package/bin/mcp-tools/mcp-validation.js +266 -0
- package/bin/mcp-tools/metrics-docs.js +146 -0
- package/bin/mcp-tools/openapi.js +174 -0
- package/bin/mcp-tools/prompt-discovery.js +283 -0
- package/bin/mcp-tools/property-docs.js +157 -0
- package/bin/mcp-tools/rpcn-docs.js +113 -0
- package/bin/mcp-tools/rpk-docs.js +141 -0
- package/bin/mcp-tools/telemetry.js +211 -0
- package/bin/mcp-tools/utils.js +131 -0
- package/bin/mcp-tools/versions.js +168 -0
- package/cli-utils/convert-doc-links.js +1 -1
- package/cli-utils/github-token.js +58 -0
- package/cli-utils/self-managed-docs-branch.js +2 -2
- package/cli-utils/setup-mcp.js +313 -0
- package/docker-compose/25.1/transactions.md +1 -1
- package/docker-compose/transactions.md +1 -1
- package/extensions/DEVELOPMENT.adoc +464 -0
- package/extensions/README.adoc +124 -0
- package/extensions/REFERENCE.adoc +768 -0
- package/extensions/USER_GUIDE.adoc +339 -0
- package/extensions/generate-rp-connect-info.js +3 -4
- package/extensions/version-fetcher/get-latest-console-version.js +38 -27
- package/extensions/version-fetcher/get-latest-redpanda-version.js +65 -54
- package/extensions/version-fetcher/retry-util.js +88 -0
- package/extensions/version-fetcher/set-latest-version.js +6 -3
- package/macros/DEVELOPMENT.adoc +377 -0
- package/macros/README.adoc +105 -0
- package/macros/REFERENCE.adoc +222 -0
- package/macros/USER_GUIDE.adoc +220 -0
- package/macros/rp-connect-components.js +6 -6
- package/package.json +12 -3
- package/tools/bundle-openapi.js +20 -10
- package/tools/cloud-regions/generate-cloud-regions.js +1 -1
- package/tools/fetch-from-github.js +18 -4
- package/tools/gen-rpk-ascii.py +3 -1
- package/tools/generate-cli-docs.js +325 -0
- package/tools/get-console-version.js +4 -2
- package/tools/get-redpanda-version.js +4 -2
- package/tools/metrics/metrics.py +19 -7
- package/tools/property-extractor/Makefile +7 -1
- package/tools/property-extractor/cloud_config.py +4 -4
- package/tools/property-extractor/constant_resolver.py +11 -11
- package/tools/property-extractor/property_extractor.py +18 -16
- package/tools/property-extractor/topic_property_extractor.py +2 -2
- package/tools/property-extractor/transformers.py +7 -7
- package/tools/property-extractor/type_definition_extractor.py +4 -4
- package/tools/redpanda-connect/README.adoc +1 -1
- package/tools/redpanda-connect/generate-rpcn-connector-docs.js +5 -3
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
= Antora Extensions User Guide
|
|
2
|
+
:toc:
|
|
3
|
+
:toclevels: 3
|
|
4
|
+
|
|
5
|
+
Complete guide to using Antora extensions in your documentation projects.
|
|
6
|
+
|
|
7
|
+
== Getting started
|
|
8
|
+
|
|
9
|
+
=== Installation
|
|
10
|
+
|
|
11
|
+
Install the package via npm:
|
|
12
|
+
|
|
13
|
+
[,bash]
|
|
14
|
+
----
|
|
15
|
+
npm i @redpanda-data/docs-extensions-and-macros
|
|
16
|
+
----
|
|
17
|
+
|
|
18
|
+
=== Basic configuration
|
|
19
|
+
|
|
20
|
+
Extensions are configured in your Antora playbook under the `antora.extensions` key:
|
|
21
|
+
|
|
22
|
+
[,yaml]
|
|
23
|
+
----
|
|
24
|
+
antora:
|
|
25
|
+
extensions:
|
|
26
|
+
- '@redpanda-data/docs-extensions-and-macros/extensions/extension-name'
|
|
27
|
+
----
|
|
28
|
+
|
|
29
|
+
IMPORTANT: Extensions must be under `antora.extensions`, not `asciidoc.extensions`. AsciiDoc macros go under `asciidoc.extensions`.
|
|
30
|
+
|
|
31
|
+
=== Extension with options
|
|
32
|
+
|
|
33
|
+
Pass configuration options to extensions:
|
|
34
|
+
|
|
35
|
+
[,yaml]
|
|
36
|
+
----
|
|
37
|
+
antora:
|
|
38
|
+
extensions:
|
|
39
|
+
- require: '@redpanda-data/docs-extensions-and-macros/extensions/extension-name'
|
|
40
|
+
option1: value1
|
|
41
|
+
option2: value2
|
|
42
|
+
----
|
|
43
|
+
|
|
44
|
+
== Configuration patterns
|
|
45
|
+
|
|
46
|
+
=== Simple registration
|
|
47
|
+
|
|
48
|
+
For extensions without configuration options:
|
|
49
|
+
|
|
50
|
+
[,yaml]
|
|
51
|
+
----
|
|
52
|
+
antora:
|
|
53
|
+
extensions:
|
|
54
|
+
- '@redpanda-data/docs-extensions-and-macros/extensions/unlisted-pages.js'
|
|
55
|
+
----
|
|
56
|
+
|
|
57
|
+
=== With environment variables
|
|
58
|
+
|
|
59
|
+
Some extensions use environment variables for sensitive data:
|
|
60
|
+
|
|
61
|
+
[,yaml]
|
|
62
|
+
----
|
|
63
|
+
antora:
|
|
64
|
+
extensions:
|
|
65
|
+
- require: '@redpanda-data/docs-extensions-and-macros/extensions/algolia-indexer'
|
|
66
|
+
algolia_app_id: $ALGOLIA_APP_ID
|
|
67
|
+
algolia_api_key: $ALGOLIA_API_KEY
|
|
68
|
+
algolia_index_name: docs
|
|
69
|
+
----
|
|
70
|
+
|
|
71
|
+
[,bash]
|
|
72
|
+
----
|
|
73
|
+
export ALGOLIA_APP_ID=your_app_id
|
|
74
|
+
export ALGOLIA_API_KEY=your_api_key
|
|
75
|
+
npx antora local-antora-playbook.yml
|
|
76
|
+
----
|
|
77
|
+
|
|
78
|
+
=== Multiple extensions
|
|
79
|
+
|
|
80
|
+
Register multiple extensions in order:
|
|
81
|
+
|
|
82
|
+
[,yaml]
|
|
83
|
+
----
|
|
84
|
+
antora:
|
|
85
|
+
extensions:
|
|
86
|
+
- '@redpanda-data/docs-extensions-and-macros/extensions/version-fetcher/set-latest-version'
|
|
87
|
+
- '@redpanda-data/docs-extensions-and-macros/extensions/generate-index-data.js'
|
|
88
|
+
- '@redpanda-data/docs-extensions-and-macros/extensions/produce-redirects.js'
|
|
89
|
+
----
|
|
90
|
+
|
|
91
|
+
Extensions run in the order they're listed.
|
|
92
|
+
|
|
93
|
+
== Common workflows
|
|
94
|
+
|
|
95
|
+
=== Version management
|
|
96
|
+
|
|
97
|
+
Automatically fetch and set the latest Redpanda version:
|
|
98
|
+
|
|
99
|
+
[,yaml]
|
|
100
|
+
----
|
|
101
|
+
antora:
|
|
102
|
+
extensions:
|
|
103
|
+
- '@redpanda-data/docs-extensions-and-macros/extensions/version-fetcher/set-latest-version'
|
|
104
|
+
----
|
|
105
|
+
|
|
106
|
+
This sets the `latest-redpanda-version` attribute for use in your documentation.
|
|
107
|
+
|
|
108
|
+
=== Search integration
|
|
109
|
+
|
|
110
|
+
Enable Algolia search indexing:
|
|
111
|
+
|
|
112
|
+
[,yaml]
|
|
113
|
+
----
|
|
114
|
+
antora:
|
|
115
|
+
extensions:
|
|
116
|
+
- require: '@redpanda-data/docs-extensions-and-macros/extensions/algolia-indexer'
|
|
117
|
+
algolia_app_id: $ALGOLIA_APP_ID
|
|
118
|
+
algolia_api_key: $ALGOLIA_API_KEY
|
|
119
|
+
algolia_index_name: redpanda-docs
|
|
120
|
+
algolia_index_version: latest
|
|
121
|
+
----
|
|
122
|
+
|
|
123
|
+
=== Redirect management
|
|
124
|
+
|
|
125
|
+
Generate redirects from old URLs to new ones:
|
|
126
|
+
|
|
127
|
+
[,yaml]
|
|
128
|
+
----
|
|
129
|
+
antora:
|
|
130
|
+
extensions:
|
|
131
|
+
- '@redpanda-data/docs-extensions-and-macros/extensions/produce-redirects.js'
|
|
132
|
+
----
|
|
133
|
+
|
|
134
|
+
Define redirects in your `antora.yml`:
|
|
135
|
+
|
|
136
|
+
[,yaml]
|
|
137
|
+
----
|
|
138
|
+
name: redpanda
|
|
139
|
+
asciidoc:
|
|
140
|
+
attributes:
|
|
141
|
+
page-aliases: old-page.adoc,another-old-page.adoc
|
|
142
|
+
----
|
|
143
|
+
|
|
144
|
+
=== Content generation
|
|
145
|
+
|
|
146
|
+
Generate component categories for Redpanda Connect:
|
|
147
|
+
|
|
148
|
+
[,yaml]
|
|
149
|
+
----
|
|
150
|
+
antora:
|
|
151
|
+
extensions:
|
|
152
|
+
- '@redpanda-data/docs-extensions-and-macros/extensions/generate-rp-connect-categories.js'
|
|
153
|
+
- '@redpanda-data/docs-extensions-and-macros/extensions/generate-rp-connect-info.js'
|
|
154
|
+
----
|
|
155
|
+
|
|
156
|
+
=== Navigation customization
|
|
157
|
+
|
|
158
|
+
Manage unlisted pages that appear in navigation:
|
|
159
|
+
|
|
160
|
+
[,yaml]
|
|
161
|
+
----
|
|
162
|
+
antora:
|
|
163
|
+
extensions:
|
|
164
|
+
- '@redpanda-data/docs-extensions-and-macros/extensions/unlisted-pages.js'
|
|
165
|
+
----
|
|
166
|
+
|
|
167
|
+
Mark pages as unlisted in their AsciiDoc header:
|
|
168
|
+
|
|
169
|
+
[,asciidoc]
|
|
170
|
+
----
|
|
171
|
+
= Page Title
|
|
172
|
+
:page-unlisted:
|
|
173
|
+
|
|
174
|
+
Content here...
|
|
175
|
+
----
|
|
176
|
+
|
|
177
|
+
== Extension order matters
|
|
178
|
+
|
|
179
|
+
Extensions execute in registration order. Some extensions depend on others running first:
|
|
180
|
+
|
|
181
|
+
[,yaml]
|
|
182
|
+
----
|
|
183
|
+
antora:
|
|
184
|
+
extensions:
|
|
185
|
+
# 1. Fetch versions first
|
|
186
|
+
- '@redpanda-data/docs-extensions-and-macros/extensions/version-fetcher/set-latest-version'
|
|
187
|
+
# 2. Then generate content that uses versions
|
|
188
|
+
- '@redpanda-data/docs-extensions-and-macros/extensions/generate-index-data.js'
|
|
189
|
+
# 3. Finally index for search
|
|
190
|
+
- require: '@redpanda-data/docs-extensions-and-macros/extensions/algolia-indexer'
|
|
191
|
+
algolia_app_id: $ALGOLIA_APP_ID
|
|
192
|
+
algolia_api_key: $ALGOLIA_API_KEY
|
|
193
|
+
algolia_index_name: docs
|
|
194
|
+
----
|
|
195
|
+
|
|
196
|
+
== Troubleshooting
|
|
197
|
+
|
|
198
|
+
=== Extension not loading
|
|
199
|
+
|
|
200
|
+
*Problem:* Extension doesn't seem to run
|
|
201
|
+
|
|
202
|
+
*Solutions:*
|
|
203
|
+
|
|
204
|
+
* Check the extension is under `antora.extensions`, not `asciidoc.extensions`
|
|
205
|
+
* Verify the path to the extension is correct
|
|
206
|
+
* Ensure the package is installed (`npm install`)
|
|
207
|
+
* Check for typos in the extension name
|
|
208
|
+
|
|
209
|
+
=== Environment variables not working
|
|
210
|
+
|
|
211
|
+
*Problem:* Extensions using environment variables fail
|
|
212
|
+
|
|
213
|
+
*Solutions:*
|
|
214
|
+
|
|
215
|
+
* Set environment variables before running Antora:
|
|
216
|
+
+
|
|
217
|
+
[,bash]
|
|
218
|
+
----
|
|
219
|
+
export VAR_NAME=value
|
|
220
|
+
npx antora playbook.yml
|
|
221
|
+
----
|
|
222
|
+
|
|
223
|
+
* Check variable names match exactly (case-sensitive)
|
|
224
|
+
* Use `$VAR_NAME` syntax in playbook
|
|
225
|
+
* Verify variables are exported, not just set
|
|
226
|
+
|
|
227
|
+
=== Extension execution order issues
|
|
228
|
+
|
|
229
|
+
*Problem:* Extensions not working as expected
|
|
230
|
+
|
|
231
|
+
*Solutions:*
|
|
232
|
+
|
|
233
|
+
* Check extension order in playbook
|
|
234
|
+
* Some extensions must run before others
|
|
235
|
+
* Review extension dependencies in link:REFERENCE.adoc[Reference docs]
|
|
236
|
+
|
|
237
|
+
=== Build performance
|
|
238
|
+
|
|
239
|
+
*Problem:* Build is slow with extensions enabled
|
|
240
|
+
|
|
241
|
+
*Solutions:*
|
|
242
|
+
|
|
243
|
+
* Some extensions make network requests (for example, version fetcher, Algolia)
|
|
244
|
+
* Use caching when possible
|
|
245
|
+
* Disable non-essential extensions during development
|
|
246
|
+
* Run only necessary extensions for your workflow
|
|
247
|
+
|
|
248
|
+
=== Extension errors
|
|
249
|
+
|
|
250
|
+
*Problem:* Extension throws errors during build
|
|
251
|
+
|
|
252
|
+
*Solutions:*
|
|
253
|
+
|
|
254
|
+
* Check extension configuration options are correct
|
|
255
|
+
* Verify required environment variables are set
|
|
256
|
+
* Review extension logs for specific error messages
|
|
257
|
+
* Ensure your Antora version is compatible
|
|
258
|
+
* Check link:REFERENCE.adoc[Reference docs] for requirements
|
|
259
|
+
|
|
260
|
+
== Best practices
|
|
261
|
+
|
|
262
|
+
=== Development vs production
|
|
263
|
+
|
|
264
|
+
Use different playbooks for development and production:
|
|
265
|
+
|
|
266
|
+
*Development playbook* (`local-antora-playbook.yml`):
|
|
267
|
+
[,yaml]
|
|
268
|
+
----
|
|
269
|
+
antora:
|
|
270
|
+
extensions:
|
|
271
|
+
- '@redpanda-data/docs-extensions-and-macros/extensions/version-fetcher/set-latest-version'
|
|
272
|
+
# Skip search indexing during development
|
|
273
|
+
----
|
|
274
|
+
|
|
275
|
+
*Production playbook* (`antora-playbook.yml`):
|
|
276
|
+
[,yaml]
|
|
277
|
+
----
|
|
278
|
+
antora:
|
|
279
|
+
extensions:
|
|
280
|
+
- '@redpanda-data/docs-extensions-and-macros/extensions/version-fetcher/set-latest-version'
|
|
281
|
+
- '@redpanda-data/docs-extensions-and-macros/extensions/generate-index-data.js'
|
|
282
|
+
- require: '@redpanda-data/docs-extensions-and-macros/extensions/algolia-indexer'
|
|
283
|
+
algolia_app_id: $ALGOLIA_APP_ID
|
|
284
|
+
algolia_api_key: $ALGOLIA_API_KEY
|
|
285
|
+
algolia_index_name: docs
|
|
286
|
+
----
|
|
287
|
+
|
|
288
|
+
=== Organizing playbooks
|
|
289
|
+
|
|
290
|
+
Keep extension configuration organized:
|
|
291
|
+
|
|
292
|
+
[,yaml]
|
|
293
|
+
----
|
|
294
|
+
antora:
|
|
295
|
+
extensions:
|
|
296
|
+
# Version management
|
|
297
|
+
- '@redpanda-data/docs-extensions-and-macros/extensions/version-fetcher/set-latest-version'
|
|
298
|
+
|
|
299
|
+
# Content generation
|
|
300
|
+
- '@redpanda-data/docs-extensions-and-macros/extensions/generate-rp-connect-categories.js'
|
|
301
|
+
- '@redpanda-data/docs-extensions-and-macros/extensions/generate-rp-connect-info.js'
|
|
302
|
+
|
|
303
|
+
# Search integration
|
|
304
|
+
- require: '@redpanda-data/docs-extensions-and-macros/extensions/algolia-indexer'
|
|
305
|
+
algolia_app_id: $ALGOLIA_APP_ID
|
|
306
|
+
algolia_api_key: $ALGOLIA_API_KEY
|
|
307
|
+
algolia_index_name: docs
|
|
308
|
+
----
|
|
309
|
+
|
|
310
|
+
=== Security
|
|
311
|
+
|
|
312
|
+
* Never commit API keys or secrets to version control
|
|
313
|
+
* Use environment variables for sensitive data
|
|
314
|
+
* Set permissions appropriately in CI/CD
|
|
315
|
+
* Rotate API keys regularly
|
|
316
|
+
|
|
317
|
+
=== Testing
|
|
318
|
+
|
|
319
|
+
Test your playbook locally before deploying:
|
|
320
|
+
|
|
321
|
+
[,bash]
|
|
322
|
+
----
|
|
323
|
+
# Set test environment variables
|
|
324
|
+
export ALGOLIA_APP_ID=test_app_id
|
|
325
|
+
export ALGOLIA_API_KEY=test_api_key
|
|
326
|
+
|
|
327
|
+
# Run Antora
|
|
328
|
+
npx antora local-antora-playbook.yml
|
|
329
|
+
|
|
330
|
+
# Check the output
|
|
331
|
+
ls -la build/site/
|
|
332
|
+
----
|
|
333
|
+
|
|
334
|
+
== Related documentation
|
|
335
|
+
|
|
336
|
+
* link:REFERENCE.adoc[Extensions reference] - Complete documentation for all extensions
|
|
337
|
+
* link:DEVELOPMENT.adoc[Development guide] - How to create new extensions
|
|
338
|
+
* https://docs.antora.org/antora/latest/playbook/[Antora Playbook Reference]
|
|
339
|
+
* https://docs.antora.org/antora/latest/extend/extensions/[Antora Extensions]
|
|
@@ -13,10 +13,9 @@ module.exports.register = function ({ config }) {
|
|
|
13
13
|
|
|
14
14
|
async function loadOctokit() {
|
|
15
15
|
const { Octokit } = await import('@octokit/rest');
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
});
|
|
16
|
+
const { getGitHubToken } = require('../cli-utils/github-token')
|
|
17
|
+
const token = getGitHubToken()
|
|
18
|
+
return token ? new Octokit({ auth: token }) : new Octokit();
|
|
20
19
|
}
|
|
21
20
|
|
|
22
21
|
this.once('contentClassified', async ({ contentCatalog }) => {
|
|
@@ -1,35 +1,46 @@
|
|
|
1
|
+
const { retryWithBackoff, isRetryableGitHubError } = require('./retry-util');
|
|
2
|
+
|
|
1
3
|
module.exports = async (github, owner, repo) => {
|
|
2
4
|
const semver = require('semver');
|
|
3
|
-
try {
|
|
4
|
-
// Fetch all the releases from the repository
|
|
5
|
-
const releases = await github.rest.repos.listReleases({
|
|
6
|
-
owner,
|
|
7
|
-
repo,
|
|
8
|
-
page: 1,
|
|
9
|
-
per_page: 50
|
|
10
|
-
});
|
|
11
5
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
6
|
+
return retryWithBackoff(
|
|
7
|
+
async () => {
|
|
8
|
+
// Fetch all the releases from the repository
|
|
9
|
+
const releases = await github.rest.repos.listReleases({
|
|
10
|
+
owner,
|
|
11
|
+
repo,
|
|
12
|
+
page: 1,
|
|
13
|
+
per_page: 50
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
// Filter valid semver tags and sort them to find the highest version
|
|
17
|
+
const sortedReleases = releases.data
|
|
18
|
+
.map(release => release.tag_name)
|
|
19
|
+
.filter(tag => semver.valid(tag.replace(/^v/, '')))
|
|
20
|
+
.sort((a, b) => semver.rcompare(a.replace(/^v/, ''), b.replace(/^v/, '')));
|
|
17
21
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
+
if (sortedReleases.length > 0) {
|
|
23
|
+
// Find the highest versions for stable and beta releases
|
|
24
|
+
const latestStableReleaseVersion = sortedReleases.find(tag => !tag.includes('-beta'));
|
|
25
|
+
const latestBetaReleaseVersion = sortedReleases.find(tag => tag.includes('-beta'));
|
|
22
26
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
return {
|
|
28
|
+
latestStableRelease: latestStableReleaseVersion || null,
|
|
29
|
+
latestBetaRelease: latestBetaReleaseVersion || null
|
|
30
|
+
};
|
|
31
|
+
} else {
|
|
32
|
+
console.log("No valid semver releases found.");
|
|
33
|
+
return { latestStableRelease: null, latestBetaRelease: null };
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
maxRetries: 3,
|
|
38
|
+
initialDelay: 1000,
|
|
39
|
+
shouldRetry: isRetryableGitHubError,
|
|
40
|
+
operationName: `Fetch Console version from ${owner}/${repo}`
|
|
30
41
|
}
|
|
31
|
-
|
|
32
|
-
console.error('Failed to fetch release information:', error);
|
|
42
|
+
).catch(error => {
|
|
43
|
+
console.error('Failed to fetch release information after retries:', error);
|
|
33
44
|
return { latestStableRelease: null, latestBetaRelease: null };
|
|
34
|
-
}
|
|
45
|
+
});
|
|
35
46
|
};
|
|
@@ -1,64 +1,75 @@
|
|
|
1
|
+
const { retryWithBackoff, isRetryableGitHubError } = require('./retry-util');
|
|
2
|
+
|
|
1
3
|
module.exports = async (github, owner, repo) => {
|
|
2
4
|
const semver = require('semver');
|
|
3
|
-
try {
|
|
4
|
-
// Fetch all the releases from the repository
|
|
5
|
-
const releases = await github.rest.repos.listReleases({
|
|
6
|
-
owner,
|
|
7
|
-
repo,
|
|
8
|
-
page: 1,
|
|
9
|
-
per_page: 50
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
// Filter valid semver tags and sort them to find the highest version
|
|
13
|
-
const sortedReleases = releases.data
|
|
14
|
-
.filter(release => semver.valid(release.tag_name.replace(/^v/, '')))
|
|
15
|
-
.sort((a, b) => semver.rcompare(
|
|
16
|
-
a.tag_name.replace(/^v/, ''),
|
|
17
|
-
b.tag_name.replace(/^v/, '')
|
|
18
|
-
));
|
|
19
|
-
|
|
20
|
-
// Find latest non-RC release that is NOT a draft
|
|
21
|
-
const latestRedpandaRelease = sortedReleases.find(
|
|
22
|
-
release => !release.tag_name.includes('-rc') && !release.draft
|
|
23
|
-
);
|
|
24
|
-
|
|
25
|
-
// Find latest RC release (can be draft or not, adjust if needed)
|
|
26
|
-
const latestRcRelease = sortedReleases.find(
|
|
27
|
-
release => release.tag_name.includes('-rc')
|
|
28
|
-
);
|
|
29
5
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
6
|
+
return retryWithBackoff(
|
|
7
|
+
async () => {
|
|
8
|
+
// Fetch all the releases from the repository
|
|
9
|
+
const releases = await github.rest.repos.listReleases({
|
|
33
10
|
owner,
|
|
34
11
|
repo,
|
|
35
|
-
|
|
12
|
+
page: 1,
|
|
13
|
+
per_page: 50
|
|
36
14
|
});
|
|
37
|
-
latestRedpandaReleaseCommitHash = commitData.data.object.sha;
|
|
38
|
-
}
|
|
39
15
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
16
|
+
// Filter valid semver tags and sort them to find the highest version
|
|
17
|
+
const sortedReleases = releases.data
|
|
18
|
+
.filter(release => semver.valid(release.tag_name.replace(/^v/, '')))
|
|
19
|
+
.sort((a, b) => semver.rcompare(
|
|
20
|
+
a.tag_name.replace(/^v/, ''),
|
|
21
|
+
b.tag_name.replace(/^v/, '')
|
|
22
|
+
));
|
|
23
|
+
|
|
24
|
+
// Find latest non-RC release that is NOT a draft
|
|
25
|
+
const latestRedpandaRelease = sortedReleases.find(
|
|
26
|
+
release => !release.tag_name.includes('-rc') && !release.draft
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
// Find latest RC release (can be draft or not, adjust if needed)
|
|
30
|
+
const latestRcRelease = sortedReleases.find(
|
|
31
|
+
release => release.tag_name.includes('-rc')
|
|
32
|
+
);
|
|
49
33
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
34
|
+
let latestRedpandaReleaseCommitHash = null;
|
|
35
|
+
if (latestRedpandaRelease) {
|
|
36
|
+
const commitData = await github.rest.git.getRef({
|
|
37
|
+
owner,
|
|
38
|
+
repo,
|
|
39
|
+
ref: `tags/${latestRedpandaRelease.tag_name}`
|
|
40
|
+
});
|
|
41
|
+
latestRedpandaReleaseCommitHash = commitData.data.object.sha;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
let latestRcReleaseCommitHash = null;
|
|
45
|
+
if (latestRcRelease) {
|
|
46
|
+
const rcCommitData = await github.rest.git.getRef({
|
|
47
|
+
owner,
|
|
48
|
+
repo,
|
|
49
|
+
ref: `tags/${latestRcRelease.tag_name}`
|
|
50
|
+
});
|
|
51
|
+
latestRcReleaseCommitHash = rcCommitData.data.object.sha;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
latestRedpandaRelease: latestRedpandaRelease ? {
|
|
56
|
+
version: latestRedpandaRelease.tag_name,
|
|
57
|
+
commitHash: latestRedpandaReleaseCommitHash.substring(0, 7)
|
|
58
|
+
} : null,
|
|
59
|
+
latestRcRelease: latestRcRelease ? {
|
|
60
|
+
version: latestRcRelease.tag_name,
|
|
61
|
+
commitHash: latestRcReleaseCommitHash.substring(0, 7)
|
|
62
|
+
} : null
|
|
63
|
+
};
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
maxRetries: 3,
|
|
67
|
+
initialDelay: 1000,
|
|
68
|
+
shouldRetry: isRetryableGitHubError,
|
|
69
|
+
operationName: `Fetch Redpanda version from ${owner}/${repo}`
|
|
70
|
+
}
|
|
71
|
+
).catch(error => {
|
|
72
|
+
console.error('Failed to fetch Redpanda release information after retries:', error);
|
|
62
73
|
return { latestRedpandaRelease: null, latestRcRelease: null };
|
|
63
|
-
}
|
|
74
|
+
});
|
|
64
75
|
};
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Retry utility with exponential backoff
|
|
3
|
+
*
|
|
4
|
+
* @param {Function} fn - Async function to retry
|
|
5
|
+
* @param {Object} options - Retry options
|
|
6
|
+
* @param {number} options.maxRetries - Maximum number of retries (default: 3)
|
|
7
|
+
* @param {number} options.initialDelay - Initial delay in ms (default: 1000)
|
|
8
|
+
* @param {number} options.maxDelay - Maximum delay in ms (default: 10000)
|
|
9
|
+
* @param {Function} options.shouldRetry - Function to determine if error is retryable (default: all errors)
|
|
10
|
+
* @param {string} options.operationName - Name for logging
|
|
11
|
+
* @returns {Promise<*>} Result of the function
|
|
12
|
+
*/
|
|
13
|
+
async function retryWithBackoff(fn, options = {}) {
|
|
14
|
+
const {
|
|
15
|
+
maxRetries = 3,
|
|
16
|
+
initialDelay = 1000,
|
|
17
|
+
maxDelay = 10000,
|
|
18
|
+
shouldRetry = () => true,
|
|
19
|
+
operationName = 'operation'
|
|
20
|
+
} = options;
|
|
21
|
+
|
|
22
|
+
let lastError;
|
|
23
|
+
|
|
24
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
25
|
+
try {
|
|
26
|
+
return await fn();
|
|
27
|
+
} catch (error) {
|
|
28
|
+
lastError = error;
|
|
29
|
+
|
|
30
|
+
// Don't retry if this is the last attempt or error is not retryable
|
|
31
|
+
if (attempt === maxRetries || !shouldRetry(error)) {
|
|
32
|
+
throw error;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Calculate delay with exponential backoff and jitter
|
|
36
|
+
const baseDelay = Math.min(initialDelay * Math.pow(2, attempt), maxDelay);
|
|
37
|
+
const jitter = Math.random() * 0.3 * baseDelay; // Add up to 30% jitter
|
|
38
|
+
const delay = Math.floor(baseDelay + jitter);
|
|
39
|
+
|
|
40
|
+
console.error(`⚠️ ${operationName} failed (attempt ${attempt + 1}/${maxRetries + 1}): ${error.message}`);
|
|
41
|
+
console.error(` Retrying in ${delay}ms...`);
|
|
42
|
+
|
|
43
|
+
// Wait before retrying
|
|
44
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
throw lastError;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Determine if a GitHub API error is retryable
|
|
53
|
+
* @param {Error} error - The error to check
|
|
54
|
+
* @returns {boolean} True if the error is retryable
|
|
55
|
+
*/
|
|
56
|
+
function isRetryableGitHubError(error) {
|
|
57
|
+
// Retry on network errors
|
|
58
|
+
if (error.code === 'ECONNRESET' ||
|
|
59
|
+
error.code === 'ETIMEDOUT' ||
|
|
60
|
+
error.code === 'ENOTFOUND' ||
|
|
61
|
+
error.code === 'EAI_AGAIN') {
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Retry on rate limit errors (though they should have a retry-after)
|
|
66
|
+
if (error.status === 403 && error.message?.includes('rate limit')) {
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Retry on 5xx server errors
|
|
71
|
+
if (error.status >= 500 && error.status < 600) {
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Retry on specific 4xx errors that might be transient
|
|
76
|
+
if (error.status === 408 || // Request Timeout
|
|
77
|
+
error.status === 429) { // Too Many Requests
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Don't retry on other errors (4xx client errors, auth issues, etc.)
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
module.exports = {
|
|
86
|
+
retryWithBackoff,
|
|
87
|
+
isRetryableGitHubError
|
|
88
|
+
};
|
|
@@ -8,8 +8,11 @@ module.exports.register = function ({ config }) {
|
|
|
8
8
|
const GetLatestConnectVersion = require('./get-latest-connect');
|
|
9
9
|
const logger = this.getLogger('set-latest-version-extension');
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
const { getGitHubToken } = require('../../cli-utils/github-token');
|
|
12
|
+
const token = getGitHubToken();
|
|
13
|
+
|
|
14
|
+
if (!token) {
|
|
15
|
+
logger.warn('GitHub token not set (REDPANDA_GITHUB_TOKEN, GITHUB_TOKEN, or GH_TOKEN). Attempting unauthenticated request.');
|
|
13
16
|
}
|
|
14
17
|
|
|
15
18
|
this.on('contentClassified', async ({ contentCatalog }) => {
|
|
@@ -22,7 +25,7 @@ module.exports.register = function ({ config }) {
|
|
|
22
25
|
const githubOptions = {
|
|
23
26
|
userAgent: 'Redpanda Docs',
|
|
24
27
|
baseUrl: 'https://api.github.com',
|
|
25
|
-
auth:
|
|
28
|
+
auth: token || undefined,
|
|
26
29
|
};
|
|
27
30
|
const github = new OctokitWithRetries(githubOptions);
|
|
28
31
|
const dockerNamespace = 'redpandadata'
|