@redpanda-data/docs-extensions-and-macros 5.10.0 → 5.11.1

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/bin/doc-tools.js CHANGED
@@ -1279,6 +1279,56 @@ automation
1279
1279
  }
1280
1280
  })
1281
1281
 
1282
+ /**
1283
+ * generate rpk-env-partial
1284
+ *
1285
+ * @description
1286
+ * Generates the -X option -> RPK_* environment variable mapping table as an
1287
+ * AsciiDoc partial from rpk's own -X option data, so the table cannot drift
1288
+ * from the CLI. Prefers the structured x_options array in `rpk --print-tree`
1289
+ * (which carries the env var names rpk itself derives) and falls back to
1290
+ * parsing `-X list` text for rpk versions that predate it. Hidden -X options
1291
+ * appear in neither source, so they are excluded automatically.
1292
+ *
1293
+ * The main rpk-docs pipeline also writes this partial from the tree it
1294
+ * already holds; this standalone command is for targeted refreshes without
1295
+ * a full generation run.
1296
+ *
1297
+ * @example
1298
+ * # Generate for a release tag (sparse-clones redpanda, builds rpk with Go)
1299
+ * npx doc-tools generate rpk-env-partial --ref v26.2.1 --output modules/reference/partials/rpk-env-vars.adoc
1300
+ *
1301
+ * # Use a local source checkout (as-is, no checkout changes), an existing
1302
+ * # rpk binary, or a versioned tree snapshot (no clone or build at all)
1303
+ * npx doc-tools generate rpk-env-partial --from-source ~/redpanda --output modules/reference/partials/rpk-env-vars.adoc
1304
+ * npx doc-tools generate rpk-env-partial --rpk-bin "$(command -v rpk)" --output modules/reference/partials/rpk-env-vars.adoc
1305
+ * npx doc-tools generate rpk-env-partial --from-json docs-data/rpk-v26.2.1.json --output modules/reference/partials/rpk-env-vars.adoc
1306
+ */
1307
+ automation
1308
+ .command('rpk-env-partial')
1309
+ .description('Generate the -X -> RPK_* env var mapping partial from rpk -X list output.')
1310
+ .option('-r, --ref <ref>', 'Git branch or tag to build rpk from (e.g., dev, v26.2.1). Clones from GitHub.')
1311
+ .option('--from-source <path>', 'Path to local rpk source (src/go/rpk directory)')
1312
+ .option('--rpk-bin <path>', 'Path to an existing rpk binary (skips clone and build)')
1313
+ .option('--from-json <path>', 'Versioned tree snapshot from docs-data (skips clone and build; requires a snapshot with x_options)')
1314
+ .option('--output <path>', 'Path to write the partial to', 'modules/reference/partials/rpk-env-vars.adoc')
1315
+ .action((options) => {
1316
+ try {
1317
+ const { handleXEnvPartialGeneration } = require('../tools/rpk-docs/generate-x-env-partial.js')
1318
+ const result = handleXEnvPartialGeneration({
1319
+ ref: options.ref,
1320
+ fromSource: options.fromSource,
1321
+ rpkBin: options.rpkBin,
1322
+ fromJson: options.fromJson,
1323
+ output: options.output
1324
+ })
1325
+ console.log(`Wrote ${result.keyCount} -X option mappings to ${result.output} (source: ${result.source})`)
1326
+ } catch (err) {
1327
+ console.error(`Error: ${err.message}`)
1328
+ process.exit(1)
1329
+ }
1330
+ })
1331
+
1282
1332
  /**
1283
1333
  * generate rpk-plugin-stubs
1284
1334
  *
@@ -1677,7 +1727,50 @@ automation
1677
1727
  if (r.status !== 0) process.exit(r.status)
1678
1728
 
1679
1729
  const { formatHelmSpec } = require('../cli-utils/format-helm-spec')
1680
- fs.writeFileSync(outFile, formatHelmSpec(fs.readFileSync(outFile, 'utf8')), 'utf8')
1730
+ let doc = formatHelmSpec(fs.readFileSync(outFile, 'utf8'))
1731
+
1732
+ // helm-docs only renders keys that exist in the YAML tree, so optional
1733
+ // values that ship commented out never appear in the reference. Pull
1734
+ // documented commented-out keys from values.yaml and inject them. This
1735
+ // runs after formatHelmSpec so the injected body sections are not affected
1736
+ // by the blank-line normalization that its header fix performs.
1737
+ const valuesFile = path.join(chartPath, 'values.yaml')
1738
+ if (fs.existsSync(valuesFile)) {
1739
+ const { extractCommentedValueDocs, injectIntoAsciiDoc, filterEntriesBySchema } = require('../cli-utils/helm-commented-values')
1740
+ try {
1741
+ let entries = extractCommentedValueDocs(fs.readFileSync(valuesFile, 'utf8'))
1742
+
1743
+ // Deprecation notices and other prose can produce paths the chart
1744
+ // no longer accepts (for example the removed
1745
+ // storage.tiered.credentialsSecretRef.configurationKey). When the
1746
+ // chart ships a values schema, drop any path it provably rejects.
1747
+ const schemaFile = path.join(chartPath, 'values.schema.json')
1748
+ if (entries.length > 0 && fs.existsSync(schemaFile)) {
1749
+ const schema = JSON.parse(fs.readFileSync(schemaFile, 'utf8'))
1750
+ const { accepted, rejected } = filterEntriesBySchema(entries, schema)
1751
+ if (rejected.length > 0) {
1752
+ console.warn(`Warning: Skipping ${rejected.length} commented-out value(s) rejected by values.schema.json: ${rejected.map((e) => e.path).join(', ')}`)
1753
+ }
1754
+ entries = accepted
1755
+ }
1756
+
1757
+ if (entries.length > 0) {
1758
+ const { doc: withInjected, injected, sectionsFound } = injectIntoAsciiDoc(doc, entries)
1759
+ doc = withInjected
1760
+ if (injected.length > 0) {
1761
+ console.log(`Documented ${injected.length} commented-out value(s): ${injected.join(', ')}`)
1762
+ } else if (sectionsFound === 0) {
1763
+ // Without this, a heading-level change upstream would silently
1764
+ // drop every documented commented-out value from the reference.
1765
+ console.warn(`Warning: No value section headings found in ${name} to anchor commented-out value docs; dropped: ${entries.map((e) => e.path).join(', ')}`)
1766
+ }
1767
+ }
1768
+ } catch (err) {
1769
+ console.warn(`Warning: Skipping commented-out value docs for ${name}: ${err.message}`)
1770
+ }
1771
+ }
1772
+
1773
+ fs.writeFileSync(outFile, doc, 'utf8')
1681
1774
 
1682
1775
  console.log(`Done: Wrote ${outFile}`)
1683
1776
  }
@@ -2130,7 +2223,7 @@ const validation = new Command('validate').description('Validate docs data again
2130
2223
  * @why A feature must only be documented as enterprise under its approved
2131
2224
  * external name. This check catches new license-gated features in core that
2132
2225
  * have no registry entry yet, registry pointers that no longer match core,
2133
- * and stale connect enterprise lists.
2226
+ * and registry connect-plugin entries that no longer match info.csv.
2134
2227
  *
2135
2228
  * @example
2136
2229
  * # Check everything against the default remote sources
@@ -2151,58 +2244,27 @@ validation
2151
2244
  .description('Check the enterprise features registry against core, connect, and docs sources of truth')
2152
2245
  .option('--registry <path>', 'Local path to enterprise-features.yml (default: fetch from docs repo main)')
2153
2246
  .option('--tag <ref>', 'Redpanda git ref for the core headers', 'dev')
2154
- .option('--connect-ref <ref>', 'Connect git ref for info.csv', 'main')
2247
+ .option('--connect-ref <ref>', "Connect git ref for info.csv ('latest' resolves the newest release tag; the registry documents released state)", 'latest')
2155
2248
  .option('--docs-ref <ref>', 'Docs repo git ref for remote fetches', 'main')
2156
2249
  .option('--disable-page <path>', 'Local path to disable-enterprise-features.adoc (default: fetch from docs repo)')
2157
- .option('--antora <path>', 'Local path to rp-connect-docs antora.yml (default: fetch from rp-connect-docs main)')
2158
2250
  .option('--properties <path>', 'Local path to a generated cluster-properties JSON; enables existence checks for gating properties')
2159
- .option('--skip-connect', 'Skip the connect info.csv comparison')
2251
+ .option('--skip-connect', 'Skip the connect check (info.csv is not fetched, so registry connect-plugin entries go unverified)')
2160
2252
  .option('--format <format>', 'Output format: text or json', 'text')
2161
2253
  .option('--write-mapping <path>', 'Write the internal-to-external name mapping partial to this path')
2162
2254
  .action(async (options) => {
2163
2255
  const { runChecks, buildMappingPartial } = require('../tools/enterprise-features/verify')
2164
- const { extractAntoraEnterpriseComponents } = require('../tools/enterprise-features/parsers')
2165
- const yamlLib = require('js-yaml')
2166
-
2167
- const RAW = 'https://raw.githubusercontent.com'
2168
- // Sources that fail to load are reported as error-level findings rather
2169
- // than silently skipped, so CI cannot pass on a check that never ran.
2170
- const failedSources = []
2171
- async function fetchText (url, sourceName) {
2172
- const resp = await fetch(url)
2173
- if (!resp.ok) {
2174
- if (sourceName) {
2175
- failedSources.push({ level: 'error', check: 'fetch', message: `Could not load ${sourceName} (${url}): ${resp.status} ${resp.statusText}. The related checks did not run.` })
2176
- return undefined
2177
- }
2178
- throw new Error(`Failed to fetch ${url}: ${resp.status} ${resp.statusText}`)
2179
- }
2180
- return resp.text()
2181
- }
2182
- const readLocal = (p) => fs.readFileSync(path.resolve(p), 'utf8')
2256
+ // Fetch wiring (GitHub auth, rejected-token retry, transient-failure
2257
+ // retries, --skip-connect short-circuits) lives in
2258
+ // cli-utils/enterprise-sources so it is testable without a network.
2259
+ const { loadEnterpriseSources } = require('../cli-utils/enterprise-sources')
2183
2260
 
2184
2261
  try {
2185
- const registryYaml = options.registry
2186
- ? readLocal(options.registry)
2187
- : await fetchText(`${RAW}/redpanda-data/docs/${options.docsRef}/shared/modules/ROOT/partials/enterprise-features.yml`)
2188
- const coreHeader = await fetchText(`${RAW}/redpanda-data/redpanda/${options.tag}/src/v/features/enterprise_features.h`, 'core enterprise_features.h')
2189
- const configurationHeader = await fetchText(`${RAW}/redpanda-data/redpanda/${options.tag}/src/v/config/configuration.h`, 'core configuration.h')
2190
- const infoCsv = options.skipConnect
2191
- ? undefined
2192
- : await fetchText(`${RAW}/redpanda-data/connect/${options.connectRef}/internal/plugins/info.csv`, 'connect info.csv')
2193
- const disablePage = options.disablePage
2194
- ? readLocal(options.disablePage)
2195
- : await fetchText(`${RAW}/redpanda-data/docs/${options.docsRef}/modules/get-started/pages/licensing/disable-enterprise-features.adoc`, 'disable-enterprise-features.adoc')
2196
- const antoraYaml = options.antora
2197
- ? readLocal(options.antora)
2198
- : await fetchText(`${RAW}/redpanda-data/rp-connect-docs/main/antora.yml`, 'rp-connect-docs antora.yml')
2199
- const antoraEnterpriseComponents = antoraYaml
2200
- ? extractAntoraEnterpriseComponents(yamlLib.load(antoraYaml))
2201
- : undefined
2262
+ const { registryYaml, coreHeader, configurationHeader, infoCsv, connectRef, disablePage, failedSources } =
2263
+ await loadEnterpriseSources(options)
2202
2264
 
2203
2265
  let allPropertyNames
2204
2266
  if (options.properties) {
2205
- const propertyData = JSON.parse(readLocal(options.properties))
2267
+ const propertyData = JSON.parse(fs.readFileSync(path.resolve(options.properties), 'utf8'))
2206
2268
  allPropertyNames = Object.keys(propertyData.properties || propertyData)
2207
2269
  }
2208
2270
 
@@ -2211,7 +2273,7 @@ validation
2211
2273
  coreHeader,
2212
2274
  configurationHeader,
2213
2275
  infoCsv,
2214
- antoraEnterpriseComponents,
2276
+ connectRef,
2215
2277
  disablePage,
2216
2278
  allPropertyNames,
2217
2279
  })
@@ -0,0 +1,244 @@
1
+ 'use strict'
2
+
3
+ /**
4
+ * Source loading for `doc-tools validate enterprise-features`.
5
+ *
6
+ * Assembles the raw text of every source of truth the validator compares
7
+ * against: the enterprise features registry, the core headers, the connect
8
+ * plugin list, and the disable-enterprise-features page. Extracted from
9
+ * bin/doc-tools.js so the fetch wiring (auth, retries, --skip-connect
10
+ * short-circuits) is testable without a network.
11
+ */
12
+
13
+ const fs = require('fs')
14
+ const path = require('path')
15
+ const { getGitHubToken } = require('./github-token')
16
+
17
+ const RAW = 'https://raw.githubusercontent.com'
18
+
19
+ // Repos known to be private, so a 404 can legitimately mean "needs auth".
20
+ // Public sources (redpanda core headers, connect info.csv) 404 only when the
21
+ // file is genuinely missing, and suggesting credentials there sends the
22
+ // reader after a problem that does not exist (e.g. during a transient
23
+ // GitHub blip).
24
+ const PRIVATE_REPO_PREFIXES = [
25
+ `${RAW}/redpanda-data/docs/`,
26
+ `${RAW}/redpanda-data/rp-connect-docs/`,
27
+ ]
28
+
29
+ const TOKEN_HINT = ' This source is in a private repository: set GIT_CREDENTIALS (or GITHUB_TOKEN / REDPANDA_GITHUB_TOKEN / ACTIONS_BOT_TOKEN) so the fetch can authenticate.'
30
+ const REJECTED_HINT = ' A GitHub token was sent but rejected, so it may be expired or not grant access to this repository (the default Actions GITHUB_TOKEN is scoped to its own repository).'
31
+
32
+ const TRANSIENT_RETRIES = 3
33
+ const TRANSIENT_RETRY_DELAY_MS = 2000
34
+
35
+ function isPrivateSource (url) {
36
+ return PRIVATE_REPO_PREFIXES.some((prefix) => url.startsWith(prefix))
37
+ }
38
+
39
+ // The response body of a failed fetch is never read; cancel it so the
40
+ // underlying connection is released instead of lingering until GC.
41
+ async function discardBody (resp) {
42
+ await resp.body?.cancel().catch(() => {})
43
+ }
44
+
45
+ /**
46
+ * Build a fetchText function that authenticates raw.githubusercontent.com
47
+ * requests and records failures for named sources.
48
+ *
49
+ * Some sources live in private repos (the docs repo), which
50
+ * raw.githubusercontent serves as 404 rather than 401 when unauthenticated —
51
+ * indistinguishable from a genuinely missing file. A token is sent when one
52
+ * is available so those fetches resolve; public sources are unaffected.
53
+ *
54
+ * raw.githubusercontent also returns 404 (not 401) for a REJECTED token,
55
+ * even on public files, so a stale token in any of the token env vars would
56
+ * break fetches that work tokenless. On a 404 with a token, the fetch is
57
+ * retried once without authentication before the 404 is treated as real.
58
+ *
59
+ * Transient failures (network errors, 429, 5xx) are retried a few times
60
+ * with a delay — the same treatment the CGO test gives curl — because a
61
+ * single blip on any source would otherwise fail the whole run, and the
62
+ * check runs on a weekly cron where nobody is watching live.
63
+ *
64
+ * @param {object} [deps]
65
+ * @param {string|null} [deps.token] - GitHub token (defaults to getGitHubToken())
66
+ * @param {Function} [deps.fetchImpl] - fetch implementation (defaults to global fetch)
67
+ * @param {Function} [deps.warn] - warning sink (defaults to console.warn)
68
+ * @param {Function} [deps.sleep] - delay implementation, for tests
69
+ * @returns {{fetchText: Function, failedSources: Array}} fetchText and the
70
+ * findings array it appends to. Sources that fail to load are reported as
71
+ * error-level findings rather than silently skipped, so CI cannot pass on a
72
+ * check that never ran. Failures without a sourceName throw instead.
73
+ */
74
+ function createSourceFetcher (deps = {}) {
75
+ const ghToken = deps.token !== undefined ? deps.token : getGitHubToken()
76
+ const fetchImpl = deps.fetchImpl || ((...args) => fetch(...args))
77
+ const warn = deps.warn || ((msg) => console.warn(msg))
78
+ const sleep = deps.sleep || ((ms) => new Promise((resolve) => setTimeout(resolve, ms)))
79
+ const failedSources = []
80
+ let warnedRejectedToken = false
81
+
82
+ // One HTTP attempt with transient-failure retries. Returns the final
83
+ // response; network errors that persist through every retry are rethrown.
84
+ async function attemptFetch (url, opts) {
85
+ let lastError
86
+ for (let attempt = 1; attempt <= TRANSIENT_RETRIES; attempt++) {
87
+ let resp
88
+ try {
89
+ resp = await fetchImpl(url, opts)
90
+ } catch (err) {
91
+ lastError = err
92
+ if (attempt < TRANSIENT_RETRIES) await sleep(TRANSIENT_RETRY_DELAY_MS)
93
+ continue
94
+ }
95
+ if (resp.status === 429 || resp.status >= 500) {
96
+ await discardBody(resp)
97
+ lastError = null
98
+ if (attempt < TRANSIENT_RETRIES) {
99
+ await sleep(TRANSIENT_RETRY_DELAY_MS)
100
+ continue
101
+ }
102
+ return resp
103
+ }
104
+ return resp
105
+ }
106
+ throw lastError
107
+ }
108
+
109
+ async function fetchText (url, sourceName) {
110
+ let resp
111
+ try {
112
+ resp = await attemptFetch(url, ghToken ? { headers: { Authorization: `Bearer ${ghToken}` } } : undefined)
113
+ } catch (err) {
114
+ if (sourceName) {
115
+ failedSources.push({ level: 'error', check: 'fetch', message: `Could not load ${sourceName} (${url}): ${err.message}. The related checks did not run.` })
116
+ return undefined
117
+ }
118
+ throw new Error(`Failed to fetch ${url}: ${err.message}`)
119
+ }
120
+ if (resp.ok) return resp.text()
121
+ await discardBody(resp)
122
+
123
+ let hint = ''
124
+ if (resp.status === 404 && ghToken) {
125
+ const retry = await attemptFetch(url)
126
+ if (retry.ok) {
127
+ if (!warnedRejectedToken) {
128
+ warnedRejectedToken = true
129
+ warn('Warning: the configured GitHub token was rejected by raw.githubusercontent.com (it may be expired); fetching without authentication instead.')
130
+ }
131
+ return retry.text()
132
+ }
133
+ await discardBody(retry)
134
+ if (isPrivateSource(url)) hint = REJECTED_HINT
135
+ } else if (resp.status === 404 && isPrivateSource(url)) {
136
+ // A tokenless 404 on a known-private repo is the signature of an
137
+ // unauthenticated fetch, so say so instead of leaving it to look
138
+ // like a missing file. Public sources get no credential hint: their
139
+ // 404s mean the file is missing (or GitHub is having a bad moment).
140
+ hint = TOKEN_HINT
141
+ }
142
+
143
+ if (sourceName) {
144
+ failedSources.push({ level: 'error', check: 'fetch', message: `Could not load ${sourceName} (${url}): ${resp.status} ${resp.statusText}. The related checks did not run.${hint}` })
145
+ return undefined
146
+ }
147
+ throw new Error(`Failed to fetch ${url}: ${resp.status} ${resp.statusText}.${hint}`)
148
+ }
149
+
150
+ return { fetchText, failedSources }
151
+ }
152
+
153
+ /**
154
+ * Resolve the ref to compare connect plugins at.
155
+ *
156
+ * Primary: the newest published release tag from the GitHub API. Fallback
157
+ * when the API is unavailable: the `latest-connect-version` asciidoc
158
+ * attribute in rp-connect-docs antora.yml — the latest version the docs
159
+ * actually document, which is the state the registry describes. (This is a
160
+ * different use of that file than the retired enterprise-components list:
161
+ * the version attribute is actively maintained by the connect version-update
162
+ * automation.) If both routes fail, the connect check is skipped with an
163
+ * error finding rather than silently comparing against main.
164
+ */
165
+ async function resolveLatestConnectRef ({ fetchText, failedSources, warn }) {
166
+ try {
167
+ const releaseJson = await fetchText('https://api.github.com/repos/redpanda-data/connect/releases/latest')
168
+ const tag = JSON.parse(releaseJson).tag_name
169
+ if (tag) return String(tag)
170
+ throw new Error('the GitHub API response has no tag_name')
171
+ } catch (err) {
172
+ warn(`Warning: could not resolve the latest connect release from the GitHub API (${err.message}); falling back to the latest documented connect version in rp-connect-docs antora.yml.`)
173
+ }
174
+ const antoraYaml = await fetchText(`${RAW}/redpanda-data/rp-connect-docs/main/antora.yml`, 'latest documented connect version (rp-connect-docs antora.yml fallback)')
175
+ if (antoraYaml === undefined) return undefined // fetch failure already reported
176
+ const yamlLib = require('js-yaml')
177
+ let documented
178
+ try {
179
+ const attributes = yamlLib.load(antoraYaml)?.asciidoc?.attributes
180
+ documented = attributes && attributes['latest-connect-version']
181
+ } catch (err) {
182
+ failedSources.push({ level: 'error', check: 'fetch', message: `Could not resolve the connect ref: rp-connect-docs antora.yml did not parse (${err.message}). The connect check did not run.` })
183
+ return undefined
184
+ }
185
+ if (!documented) {
186
+ failedSources.push({ level: 'error', check: 'fetch', message: 'Could not resolve the connect ref: rp-connect-docs antora.yml has no latest-connect-version attribute. The connect check did not run.' })
187
+ return undefined
188
+ }
189
+ const version = String(documented).trim()
190
+ return version.startsWith('v') ? version : `v${version}`
191
+ }
192
+
193
+ /**
194
+ * Load every source the enterprise-features validation consumes.
195
+ *
196
+ * The rp-connect-docs antora.yml is deliberately NOT a source anymore:
197
+ * rp-connect-docs#485 removed the enterprise-components list it carried, so
198
+ * fetching it bought a cross-repo credential requirement in exchange for a
199
+ * comparison against nothing. The connect check now validates the registry's
200
+ * connect-plugin entries directly against info.csv.
201
+ *
202
+ * The connect ref defaults to 'latest', which resolves to the latest
203
+ * published connect release tag. The registry documents released state, so
204
+ * comparing it against main would flag plugins renamed or newly marked
205
+ * enterprise ahead of any release. The core headers deliberately stay on
206
+ * 'dev' for the opposite reason: their check exists to give early warning
207
+ * of NEW license-gated features before they ship.
208
+ *
209
+ * @param {object} options - Parsed CLI options: registry, tag, connectRef,
210
+ * docsRef, disablePage, skipConnect.
211
+ * @param {object} [deps] - Injectable dependencies for tests: token,
212
+ * fetchImpl, warn, sleep (see createSourceFetcher), and readLocal.
213
+ * @returns {Promise<object>} { registryYaml, coreHeader, configurationHeader,
214
+ * infoCsv, connectRef, disablePage, failedSources }
215
+ */
216
+ async function loadEnterpriseSources (options, deps = {}) {
217
+ const { fetchText, failedSources } = createSourceFetcher(deps)
218
+ const readLocal = deps.readLocal || ((p) => fs.readFileSync(path.resolve(p), 'utf8'))
219
+
220
+ const registryYaml = options.registry
221
+ ? readLocal(options.registry)
222
+ : await fetchText(`${RAW}/redpanda-data/docs/${options.docsRef}/shared/modules/ROOT/partials/enterprise-features.yml`)
223
+ const coreHeader = await fetchText(`${RAW}/redpanda-data/redpanda/${options.tag}/src/v/features/enterprise_features.h`, 'core enterprise_features.h')
224
+ const configurationHeader = await fetchText(`${RAW}/redpanda-data/redpanda/${options.tag}/src/v/config/configuration.h`, 'core configuration.h')
225
+
226
+ let connectRef = options.connectRef
227
+ let infoCsv
228
+ if (!options.skipConnect) {
229
+ if (!connectRef || connectRef === 'latest') {
230
+ connectRef = await resolveLatestConnectRef({ fetchText, failedSources, warn: deps.warn || ((msg) => console.warn(msg)) })
231
+ }
232
+ if (connectRef) {
233
+ infoCsv = await fetchText(`${RAW}/redpanda-data/connect/${connectRef}/internal/plugins/info.csv`, `connect info.csv (${connectRef})`)
234
+ }
235
+ }
236
+
237
+ const disablePage = options.disablePage
238
+ ? readLocal(options.disablePage)
239
+ : await fetchText(`${RAW}/redpanda-data/docs/${options.docsRef}/modules/get-started/pages/licensing/disable-enterprise-features.adoc`, 'disable-enterprise-features.adoc')
240
+
241
+ return { registryYaml, coreHeader, configurationHeader, infoCsv, connectRef, disablePage, failedSources }
242
+ }
243
+
244
+ module.exports = { createSourceFetcher, loadEnterpriseSources, RAW }