@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.
Files changed (61) hide show
  1. package/README.adoc +33 -1064
  2. package/bin/doc-tools-mcp.js +720 -0
  3. package/bin/doc-tools.js +1050 -50
  4. package/bin/mcp-tools/antora.js +153 -0
  5. package/bin/mcp-tools/cache.js +89 -0
  6. package/bin/mcp-tools/cloud-regions.js +127 -0
  7. package/bin/mcp-tools/content-review.js +196 -0
  8. package/bin/mcp-tools/crd-docs.js +153 -0
  9. package/bin/mcp-tools/frontmatter.js +138 -0
  10. package/bin/mcp-tools/generated-docs-review.js +887 -0
  11. package/bin/mcp-tools/helm-docs.js +152 -0
  12. package/bin/mcp-tools/index.js +245 -0
  13. package/bin/mcp-tools/job-queue.js +468 -0
  14. package/bin/mcp-tools/mcp-validation.js +266 -0
  15. package/bin/mcp-tools/metrics-docs.js +146 -0
  16. package/bin/mcp-tools/openapi.js +174 -0
  17. package/bin/mcp-tools/prompt-discovery.js +283 -0
  18. package/bin/mcp-tools/property-docs.js +157 -0
  19. package/bin/mcp-tools/rpcn-docs.js +113 -0
  20. package/bin/mcp-tools/rpk-docs.js +141 -0
  21. package/bin/mcp-tools/telemetry.js +211 -0
  22. package/bin/mcp-tools/utils.js +131 -0
  23. package/bin/mcp-tools/versions.js +168 -0
  24. package/cli-utils/convert-doc-links.js +1 -1
  25. package/cli-utils/github-token.js +58 -0
  26. package/cli-utils/self-managed-docs-branch.js +2 -2
  27. package/cli-utils/setup-mcp.js +313 -0
  28. package/docker-compose/25.1/transactions.md +1 -1
  29. package/docker-compose/transactions.md +1 -1
  30. package/extensions/DEVELOPMENT.adoc +464 -0
  31. package/extensions/README.adoc +124 -0
  32. package/extensions/REFERENCE.adoc +768 -0
  33. package/extensions/USER_GUIDE.adoc +339 -0
  34. package/extensions/generate-rp-connect-info.js +3 -4
  35. package/extensions/version-fetcher/get-latest-console-version.js +38 -27
  36. package/extensions/version-fetcher/get-latest-redpanda-version.js +65 -54
  37. package/extensions/version-fetcher/retry-util.js +88 -0
  38. package/extensions/version-fetcher/set-latest-version.js +6 -3
  39. package/macros/DEVELOPMENT.adoc +377 -0
  40. package/macros/README.adoc +105 -0
  41. package/macros/REFERENCE.adoc +222 -0
  42. package/macros/USER_GUIDE.adoc +220 -0
  43. package/macros/rp-connect-components.js +6 -6
  44. package/package.json +12 -3
  45. package/tools/bundle-openapi.js +20 -10
  46. package/tools/cloud-regions/generate-cloud-regions.js +1 -1
  47. package/tools/fetch-from-github.js +18 -4
  48. package/tools/gen-rpk-ascii.py +3 -1
  49. package/tools/generate-cli-docs.js +325 -0
  50. package/tools/get-console-version.js +4 -2
  51. package/tools/get-redpanda-version.js +4 -2
  52. package/tools/metrics/metrics.py +19 -7
  53. package/tools/property-extractor/Makefile +7 -1
  54. package/tools/property-extractor/cloud_config.py +4 -4
  55. package/tools/property-extractor/constant_resolver.py +11 -11
  56. package/tools/property-extractor/property_extractor.py +18 -16
  57. package/tools/property-extractor/topic_property_extractor.py +2 -2
  58. package/tools/property-extractor/transformers.py +7 -7
  59. package/tools/property-extractor/type_definition_extractor.py +4 -4
  60. package/tools/redpanda-connect/README.adoc +1 -1
  61. 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
- if (!process.env.REDPANDA_GITHUB_TOKEN) return new Octokit()
17
- return new Octokit({
18
- auth: process.env.REDPANDA_GITHUB_TOKEN,
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
- // Filter valid semver tags and sort them to find the highest version
13
- const sortedReleases = releases.data
14
- .map(release => release.tag_name)
15
- .filter(tag => semver.valid(tag.replace(/^v/, '')))
16
- .sort((a, b) => semver.rcompare(a.replace(/^v/, ''), b.replace(/^v/, '')));
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
- if (sortedReleases.length > 0) {
19
- // Find the highest versions for stable and beta releases
20
- const latestStableReleaseVersion = sortedReleases.find(tag => !tag.includes('-beta'));
21
- const latestBetaReleaseVersion = sortedReleases.find(tag => tag.includes('-beta'));
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
- return {
24
- latestStableRelease: latestStableReleaseVersion || null,
25
- latestBetaRelease: latestBetaReleaseVersion || null
26
- };
27
- } else {
28
- console.log("No valid semver releases found.");
29
- return { latestStableRelease: null, latestBetaRelease: null };
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
- } catch (error) {
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
- let latestRedpandaReleaseCommitHash = null;
31
- if (latestRedpandaRelease) {
32
- const commitData = await github.rest.git.getRef({
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
- ref: `tags/${latestRedpandaRelease.tag_name}`
12
+ page: 1,
13
+ per_page: 50
36
14
  });
37
- latestRedpandaReleaseCommitHash = commitData.data.object.sha;
38
- }
39
15
 
40
- let latestRcReleaseCommitHash = null;
41
- if (latestRcRelease) {
42
- const rcCommitData = await github.rest.git.getRef({
43
- owner,
44
- repo,
45
- ref: `tags/${latestRcRelease.tag_name}`
46
- });
47
- latestRcReleaseCommitHash = rcCommitData.data.object.sha;
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
- return {
51
- latestRedpandaRelease: latestRedpandaRelease ? {
52
- version: latestRedpandaRelease.tag_name,
53
- commitHash: latestRedpandaReleaseCommitHash.substring(0, 7)
54
- } : null,
55
- latestRcRelease: latestRcRelease ? {
56
- version: latestRcRelease.tag_name,
57
- commitHash: latestRcReleaseCommitHash.substring(0, 7)
58
- } : null
59
- };
60
- } catch (error) {
61
- console.error('Failed to fetch Redpanda release information:', error);
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
- if (!process.env.REDPANDA_GITHUB_TOKEN) {
12
- logger.warn('REDPANDA_GITHUB_TOKEN environment variable not set. Attempting unauthenticated request.');
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: process.env.REDPANDA_GITHUB_TOKEN || undefined,
28
+ auth: token || undefined,
26
29
  };
27
30
  const github = new OctokitWithRetries(githubOptions);
28
31
  const dockerNamespace = 'redpandadata'