@redpanda-data/docs-extensions-and-macros 5.51.1 → 5.52.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 (33) hide show
  1. package/bin/doc-tools-mcp.js +4 -0
  2. package/bin/doc-tools.js +1 -0
  3. package/bin/mcp-tools/rpcn-docs.js +1 -0
  4. package/extensions/REFERENCE.adoc +19 -4
  5. package/extensions/generate-rp-connect-categories.js +76 -25
  6. package/extensions/generate-rp-connect-info.js +150 -40
  7. package/extensions/modify-connect-tag-playbook.js +183 -17
  8. package/extensions/util/connect-catalog.js +153 -0
  9. package/macros/rp-connect-components.js +23 -8
  10. package/package.json +1 -1
  11. package/tools/lint-strings/diff.js +4 -1
  12. package/tools/lint-strings/engine.js +5 -0
  13. package/tools/lint-strings/fixtures/connect/lint_connect.go +1 -1
  14. package/tools/lint-strings/fixtures/connect/repo/go.mod.txt +5 -0
  15. package/tools/lint-strings/fixtures/connect/repo/internal/cli/lint.go +19 -0
  16. package/tools/lint-strings/fixtures/connect/repo/internal/impl/azure/common.go +20 -0
  17. package/tools/lint-strings/fixtures/connect/repo/internal/impl/azure/docs.go +8 -0
  18. package/tools/lint-strings/fixtures/connect/repo/internal/impl/azure/input_thing.go +32 -0
  19. package/tools/lint-strings/fixtures/connect/repo/internal/impl/azure/output_thing.go +12 -0
  20. package/tools/lint-strings/fixtures/connect/repo/internal/impl/widget/bloblang.go +21 -0
  21. package/tools/lint-strings/fixtures/connect/repo/internal/impl/widget/scanner.go +12 -0
  22. package/tools/lint-strings/fixtures/connect/repo/internal/plugins/info.csv +3 -0
  23. package/tools/lint-strings/fixtures/connect/repo/internal/retries/retries.go +13 -0
  24. package/tools/lint-strings/go-index.js +1143 -0
  25. package/tools/lint-strings/index.js +28 -2
  26. package/tools/lint-strings/rules/common.js +10 -2
  27. package/tools/lint-strings/surfaces/connect.js +1484 -137
  28. package/tools/lint-strings/surfaces/rpk.js +1 -0
  29. package/tools/redpanda-connect/generate-rpcn-connector-docs.js +23 -13
  30. package/tools/redpanda-connect/report-delta.js +1 -0
  31. package/tools/redpanda-connect/rpcn-connector-docs-handler.js +19 -13
  32. package/tools/redpanda-connect/templates/connector.hbs +20 -2
  33. package/tools/rpk-docs/helpers/index.js +33 -36
@@ -1,29 +1,195 @@
1
1
  'use strict';
2
2
 
3
+ // Sources the Redpanda Connect reference partials from the connect repo.
4
+ //
5
+ // A playbook lists the connect repo as a content source:
6
+ //
7
+ // - url: https://github.com/redpanda-data/connect
8
+ // tags: latest # replaced with the latest release tag at build time
9
+ // start_path: docs
10
+ //
11
+ // The extension does two things:
12
+ //
13
+ // 1. Pins the source to the latest connect release tag, so the docs never
14
+ // show fields from unreleased code on connect's main branch.
15
+ // 2. Filters what the source contributes: only the generated
16
+ // modules/components/partials and modules/components/examples trees of the
17
+ // `connect` component are kept, and any file another source (rp-connect-docs)
18
+ // already provides is dropped. Antora fails the build on a duplicate page or
19
+ // partial, so this makes it safe to add the source to a playbook before or
20
+ // after rp-connect-docs stops committing its own generated copies, and it
21
+ // ignores the full pages that older connect tags still carry.
22
+ //
23
+ // It also shares the tag it resolves (util/connect-catalog), so
24
+ // generate-rp-connect-info reads info.csv from the same ref as the reference
25
+ // content.
26
+
3
27
  const { raiseListenerLimit } = require('./util/raise-listener-limit')
28
+ const getLatestConnectTag = require('./version-fetcher/get-latest-connect')
29
+ const { getGitHubApiToken } = require('../cli-utils/github-token')
30
+ const { isConnectSource, isConnectOrigin, githubRepoOf, setResolvedConnectRef } = require('./util/connect-catalog')
31
+
32
+ const OWNER = 'redpanda-data'
33
+ const REPO = 'connect'
34
+ const COMPONENT = 'connect'
35
+ const KEPT_PATHS = ['modules/components/partials/', 'modules/components/examples/']
36
+
37
+ // HTTPS, SSH (ssh://), and scp-style (git@host:owner/repo) URLs are all
38
+ // remote content sources in Antora.
39
+ function isRemote (url) {
40
+ return /^(https?|ssh):\/\//.test(url || '') || /^[\w.-]+@[\w.-]+:/.test(url || '')
41
+ }
42
+
43
+ // Only the redpanda-data/connect remote resolves to its own latest release.
44
+ // A fork with the same repo name would otherwise be pinned to a tag looked up
45
+ // on redpanda-data/connect.
46
+ function isUpstreamConnect (url) {
47
+ const repo = githubRepoOf(url)
48
+ return !!repo && repo.owner === OWNER && repo.repo === REPO
49
+ }
50
+
51
+ // Removes user info from every URL in the text, so credentials embedded in a
52
+ // content-source URL never reach the build log.
53
+ function redact (text) {
54
+ return String(text || '').replace(/([a-z][a-z+.-]*:\/\/)[^@/\s]*@/gi, '$1')
55
+ }
56
+
57
+ // Highest stable vX.Y.Z tag from `git ls-remote` output. Used when the GitHub
58
+ // API is unavailable, for example when an unauthenticated build is rate limited.
59
+ function highestStableTag (lsRemoteOutput) {
60
+ const newer = (a, b) => a[0] - b[0] || a[1] - b[1] || a[2] - b[2]
61
+ let best = null
62
+ for (const line of String(lsRemoteOutput || '').split('\n')) {
63
+ const m = line.match(/refs\/tags\/(v(\d+)\.(\d+)\.(\d+))$/)
64
+ if (!m) continue
65
+ const v = m.slice(2, 5).map(Number)
66
+ if (!best || newer(v, best.v) > 0) best = { tag: m[1], v }
67
+ }
68
+ return best && best.tag
69
+ }
70
+
71
+ function latestTagFromGit (url) {
72
+ const { execFileSync } = require('child_process')
73
+ return highestStableTag(execFileSync('git', ['ls-remote', '--tags', '--refs', url], { encoding: 'utf8', timeout: 60000 }))
74
+ }
75
+
76
+ function toTag (tagName) {
77
+ if (!tagName) return null
78
+ return tagName.startsWith('v') ? tagName : `v${tagName}`
79
+ }
4
80
 
5
- const GetLatestConnectTag = require('./version-fetcher/get-latest-connect');
81
+ // The upstream connect source asks to be pinned with `tags: latest` as its
82
+ // only ref. A source with any other refs, such as a fork or a PR branch used
83
+ // to preview a connect change, keeps what the playbook says.
84
+ function wantsLatest (source) {
85
+ if (!isRemote(source.url) || !isConnectSource(source.url) || !isUpstreamConnect(source.url)) return false
86
+ const tags = Array.isArray(source.tags) ? source.tags : [source.tags]
87
+ const branches = source.branches == null ? [] : [].concat(source.branches)
88
+ return tags.length === 1 && tags[0] === 'latest' && branches.length === 0
89
+ }
90
+
91
+ // Rewrites the connect content sources that ask for the latest release to
92
+ // use `tag`. Returns true when a source was updated.
93
+ function pinConnectSource (playbook, tag) {
94
+ const sources = playbook && playbook.content && playbook.content.sources
95
+ if (!Array.isArray(sources) || !tag) return false
96
+ let updated = false
97
+ for (const source of sources) {
98
+ if (!wantsLatest(source)) continue
99
+ source.tags = [tag]
100
+ // Without this, Antora's default branch patterns also pull in main.
101
+ source.branches = []
102
+ updated = true
103
+ }
104
+ return updated
105
+ }
106
+
107
+ // Removes connect-sourced files that must not be published. Mutates the
108
+ // content aggregate and returns what it dropped.
109
+ function filterConnectContent (contentAggregate) {
110
+ const report = { kept: 0, outsideGenerated: 0, providedElsewhere: 0, otherComponents: 0 }
111
+ const fromConnect = (f) => isConnectOrigin(f.src && f.src.origin)
112
+ for (const bucket of contentAggregate) {
113
+ const files = bucket.files || []
114
+ if (!files.some(fromConnect)) continue
115
+ if (bucket.name !== COMPONENT) {
116
+ // Older connect tags name the component redpanda-connect and ship full
117
+ // pages. None of it belongs on the site.
118
+ report.otherComponents += files.filter(fromConnect).length
119
+ bucket.files = files.filter((f) => !fromConnect(f))
120
+ continue
121
+ }
122
+ const provided = new Set(files.filter((f) => !fromConnect(f)).map((f) => f.path))
123
+ bucket.files = files.filter((f) => {
124
+ if (!fromConnect(f)) return true
125
+ if (!KEPT_PATHS.some((p) => f.path.startsWith(p))) {
126
+ report.outsideGenerated++
127
+ return false
128
+ }
129
+ if (provided.has(f.path)) {
130
+ report.providedElsewhere++
131
+ return false
132
+ }
133
+ report.kept++
134
+ return true
135
+ })
136
+ }
137
+ // A bucket left with no files (an older connect tag's own component) would
138
+ // still register an empty component.
139
+ for (let i = contentAggregate.length - 1; i >= 0; i--) {
140
+ const b = contentAggregate[i]
141
+ if (!(b.files || []).length && (b.origins || []).every(isConnectOrigin)) contentAggregate.splice(i, 1)
142
+ }
143
+ return report
144
+ }
6
145
 
7
- module.exports.register = function ({ config }) {
146
+ module.exports.register = function () {
8
147
  raiseListenerLimit(this)
9
- const logger = this.getLogger('modify-connect-tag-playbook-extension');
148
+ const logger = this.getLogger('modify-connect-tag-playbook-extension')
10
149
 
11
150
  this.on('contextStarted', async ({ playbook }) => {
151
+ const sources = (playbook && playbook.content && playbook.content.sources) || []
152
+ // Clear a ref left over from an earlier build in the same process.
153
+ setResolvedConnectRef(null)
154
+ const source = sources.find(wantsLatest)
155
+ if (!source) return
156
+ let tag = null
12
157
  try {
13
- // Fetch the latest release tag for redpanda-data/connect
14
- const sourceUrl = 'https://github.com/redpanda-data/connect';
15
- if (playbook && playbook.content && playbook.content.sources) {
16
- const source = playbook.content.sources.find(source => source.url === sourceUrl);
17
- if (source) {
18
- const latestTag = await GetLatestConnectTag();
19
- if (latestTag) {
20
- source.tags[0] = `v${latestTag}`;
21
- this.updateVariables({ playbook });
22
- }
23
- }
24
- }
158
+ const { Octokit } = await import('@octokit/rest')
159
+ const { retry } = await import('@octokit/plugin-retry')
160
+ const token = getGitHubApiToken()
161
+ const github = new (Octokit.plugin(retry))({ userAgent: 'Redpanda Docs', auth: token || undefined, retry: { doNotRetry: [403, 404, 429] } })
162
+ tag = toTag(await getLatestConnectTag(github, OWNER, REPO, logger))
25
163
  } catch (error) {
26
- logger.error('Failed to update playbook with the latest Redpanda Connect tag:', error);
164
+ logger.warn(`GitHub API lookup of the latest Redpanda Connect release failed: ${error.message}`)
165
+ }
166
+ if (!tag) {
167
+ try {
168
+ tag = latestTagFromGit(source.url)
169
+ if (tag) logger.info(`Resolved the latest Redpanda Connect release from git tags: ${tag}`)
170
+ } catch (error) {
171
+ logger.warn(redact(`git ls-remote of ${source.url} failed: ${error.message}`))
172
+ }
27
173
  }
28
- });
174
+ if (!tag) {
175
+ // Guessing would let Antora's default branch patterns pull in connect's
176
+ // branches, whose docs are unreleased or missing, so stop the build.
177
+ throw new Error('Could not resolve the latest Redpanda Connect release tag for the connect content source. Set a GitHub token or check network access.')
178
+ }
179
+ pinConnectSource(playbook, tag)
180
+ setResolvedConnectRef(tag)
181
+ this.updateVariables({ playbook })
182
+ logger.info(`Sourcing Redpanda Connect reference content from ${tag}`)
183
+ })
184
+
185
+ this.on('contentAggregated', ({ contentAggregate }) => {
186
+ const r = filterConnectContent(contentAggregate)
187
+ if (r.kept + r.outsideGenerated + r.providedElsewhere + r.otherComponents === 0) return
188
+ logger.info(
189
+ `Redpanda Connect content: kept ${r.kept} generated files; skipped ${r.providedElsewhere} already provided by another source, ` +
190
+ `${r.outsideGenerated} outside the generated partials and examples, and ${r.otherComponents} from other components`
191
+ )
192
+ })
29
193
  }
194
+
195
+ module.exports._internal = { isRemote, isUpstreamConnect, redact, isConnectSource, isConnectOrigin, toTag, highestStableTag, wantsLatest, pinConnectSource, filterConnectContent }
@@ -0,0 +1,153 @@
1
+ 'use strict'
2
+
3
+ // Shared Redpanda Connect catalog helpers.
4
+ //
5
+ // The connect repo publishes its reference content (partials and examples)
6
+ // through a content source that modify-connect-tag-playbook pins to the latest
7
+ // release tag. Catalog data (support level, deprecation, Cloud availability)
8
+ // must come from the same ref, or the badges and catalog drift from the
9
+ // reference content as soon as connect's main branch changes a flag before a
10
+ // release. This module carries that ref between the extensions and turns the
11
+ // generated partials/platforms/catalog.json into rows shaped like the
12
+ // translated info.csv rows the macros already consume.
13
+
14
+ // Matches the GitHub URL (with or without .git) and a local clone whose
15
+ // directory is named connect, which is how the source looks in local builds.
16
+ function isConnectSource (url) {
17
+ return typeof url === 'string' && /(^|[/:])(redpanda-data\/)?connect(\.git)?\/?$/.test(url)
18
+ }
19
+
20
+ function isConnectOrigin (origin) {
21
+ return !!origin && (isConnectSource(origin.url) || isConnectSource(origin.worktree))
22
+ }
23
+
24
+ // The connect ref that modify-connect-tag-playbook resolved for this build.
25
+ // Module state, because both extensions run in the same Antora process and
26
+ // Antora gives extensions no other channel that survives from contextStarted
27
+ // to contentClassified without mutating the playbook.
28
+ let resolvedConnectRef = null
29
+
30
+ function setResolvedConnectRef (ref) {
31
+ resolvedConnectRef = ref || null
32
+ }
33
+
34
+ function getResolvedConnectRef () {
35
+ return resolvedConnectRef
36
+ }
37
+
38
+ // The GitHub owner and repo of a connect origin URL, so a playbook that
39
+ // previews a fork reads that fork's info.csv. Null for non-GitHub URLs.
40
+ function githubRepoOf (url) {
41
+ const m = String(url || '').match(/github\.com[/:]([^/]+)\/([^/]+?)(\.git)?\/?$/)
42
+ return m ? { owner: m[1], repo: m[2] } : null
43
+ }
44
+
45
+ // The ref of the connect content source as Antora aggregated it. Only refs
46
+ // that exist on the remote count: a tag, or a branch fetched from the remote.
47
+ // A branch of a local clone may exist only on disk, so it is not used.
48
+ function connectOriginRef (contentCatalog) {
49
+ if (!contentCatalog || typeof contentCatalog.findBy !== 'function') return null
50
+ for (const file of contentCatalog.findBy({ component: 'connect' })) {
51
+ const origin = file.src && file.src.origin
52
+ if (!isConnectOrigin(origin)) continue
53
+ const repo = githubRepoOf(origin.url)
54
+ if (origin.tag || origin.reftype === 'tag') {
55
+ return { ref: origin.tag || origin.refname, ...(repo || {}) }
56
+ }
57
+ if (!origin.worktree && (origin.branch || origin.refname)) {
58
+ return { ref: origin.branch || origin.refname, ...(repo || {}) }
59
+ }
60
+ }
61
+ return null
62
+ }
63
+
64
+ const CATALOG_RELATIVE = 'platforms/catalog.json'
65
+
66
+ // The generated catalog.json in the content catalog, preferring the copy
67
+ // from the connect content source over one another source provides.
68
+ function findConnectCatalogFile (contentCatalog) {
69
+ if (!contentCatalog || typeof contentCatalog.findBy !== 'function') return null
70
+ const candidates = contentCatalog
71
+ .findBy({ component: 'connect', module: 'components', family: 'partial' })
72
+ .filter((f) => f.src && f.src.relative === CATALOG_RELATIVE)
73
+ return candidates.find((f) => isConnectOrigin(f.src.origin)) || candidates[0] || null
74
+ }
75
+
76
+ // Page directories and data rows name some types differently: pages live in
77
+ // metrics/ and rate_limits/, while info.csv and the schema say metric and
78
+ // rate_limit. Normalize to the singular data form for lookups.
79
+ function normalizeType (type) {
80
+ const t = String(type || '').trim().toLowerCase().replace(/-/g, '_')
81
+ return t.endsWith('s') ? t.slice(0, -1) : t
82
+ }
83
+
84
+ const PAGE_TYPE_DIRS = ['inputs', 'outputs', 'processors', 'caches', 'rate_limits', 'buffers', 'metrics', 'tracers', 'scanners']
85
+
86
+ // The component type of a connector page from its path, for example
87
+ // inputs/kafka.adoc -> input. Null for pages outside the type directories.
88
+ function typeFromRelative (relative) {
89
+ const m = String(relative || '').match(/(?:^|\/)([a-z_]+)\/[^/]+\.adoc$/)
90
+ return m && PAGE_TYPE_DIRS.includes(m[1]) ? normalizeType(m[1]) : null
91
+ }
92
+
93
+ const yn = (value) => (value === true || String(value || '').trim().toLowerCase() === 'y' ? 'y' : 'n')
94
+
95
+ // Converts catalog.json entries into rows with the raw info.csv column names,
96
+ // so they go through the same translation as rows parsed from info.csv. The
97
+ // catalog adds status, categories, cgo_only, and the full commercial name list.
98
+ function catalogEntriesToCsvRows (entries) {
99
+ if (!Array.isArray(entries)) throw new Error('catalog.json is not a JSON array')
100
+ return entries
101
+ .filter((e) => e && e.name && e.type)
102
+ .map((e) => {
103
+ const commercialNames = Array.isArray(e.commercial_names) ? e.commercial_names.filter(Boolean) : []
104
+ const status = String(e.status || '').trim().toLowerCase()
105
+ return {
106
+ name: e.name,
107
+ type: e.type,
108
+ commercial_name: commercialNames[0] || e.name,
109
+ commercial_names: commercialNames,
110
+ support: e.support || '',
111
+ deprecated: status === 'deprecated' ? 'y' : 'n',
112
+ cloud: yn(e.cloud),
113
+ cloud_with_gpu: yn(e.cloud_ai),
114
+ status,
115
+ version: e.version || '',
116
+ categories: Array.isArray(e.categories) ? e.categories : [],
117
+ cgo_only: yn(e.cgo_only),
118
+ summary: e.summary || ''
119
+ }
120
+ })
121
+ }
122
+
123
+ // Cloud availability of a translated row. Cloud has two pipeline flavors:
124
+ // standard (info.csv `cloud`) and GPU (info.csv `cloud_with_gpu`, catalog
125
+ // `cloud_ai`). A component in either one has a Cloud page.
126
+ function isCloudAvailable (row) {
127
+ return !!row && (row.is_cloud_supported === 'y' || row.cloud_ai === 'y')
128
+ }
129
+
130
+ // gpu_only: only in GPU pipelines (for example the ollama processors).
131
+ // no_gpu: in standard pipelines but not in GPU pipelines (for example jira).
132
+ function gpuFlags (cloud, cloudAi) {
133
+ return {
134
+ gpu_only: cloud !== 'y' && cloudAi === 'y' ? 'y' : 'n',
135
+ no_gpu: cloud === 'y' && cloudAi !== 'y' ? 'y' : 'n'
136
+ }
137
+ }
138
+
139
+ module.exports = {
140
+ CATALOG_RELATIVE,
141
+ isConnectSource,
142
+ isConnectOrigin,
143
+ setResolvedConnectRef,
144
+ getResolvedConnectRef,
145
+ githubRepoOf,
146
+ connectOriginRef,
147
+ findConnectCatalogFile,
148
+ normalizeType,
149
+ typeFromRelative,
150
+ catalogEntriesToCsvRows,
151
+ isCloudAvailable,
152
+ gpuFlags
153
+ }
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
  const { posix: path } = require('path')
3
3
  const logger = require('@antora/logger')('rp-connect-components-macro')
4
+ const { isCloudAvailable, typeFromRelative } = require('../extensions/util/connect-catalog')
4
5
 
5
6
  /**
6
7
  * Registers macros for use in Redpanda Connect contexts in the Redpanda documentation.
@@ -220,8 +221,9 @@ module.exports.register = function (registry, context) {
220
221
  */
221
222
  function processConnectors(parsedData) {
222
223
  return parsedData.data.reduce((connectors, row) => {
223
- const { connector, commercial_name, type, support_level, is_cloud_supported, is_licensed, redpandaConnectUrl, redpandaCloudUrl } = row;
224
- const isCloudSupported = is_cloud_supported === 'y';
224
+ const { connector, commercial_name, type, support_level, is_licensed, redpandaConnectUrl, redpandaCloudUrl } = row;
225
+ // Available in standard or GPU Cloud pipelines
226
+ const isCloudSupported = isCloudAvailable(row);
225
227
 
226
228
  // Initialize the connector if it's not already in the map
227
229
  if (!connectors[connector]) {
@@ -245,7 +247,9 @@ module.exports.register = function (registry, context) {
245
247
  redpandaCloudUrl: redpandaCloudUrl || ''
246
248
  },
247
249
  supportLevel: support_level,
248
- isCloudSupported: isCloudSupported
250
+ isCloudSupported: isCloudSupported,
251
+ gpuOnly: row.gpu_only === 'y',
252
+ noGpu: row.no_gpu === 'y'
249
253
  };
250
254
  }
251
255
 
@@ -301,8 +305,8 @@ module.exports.register = function (registry, context) {
301
305
  };
302
306
 
303
307
  parsedData.data.forEach(row => {
304
- const { connector: driverName, commercial_name, support_level, is_cloud_supported } = row;
305
- const isCloudSupported = is_cloud_supported === 'y';
308
+ const { connector: driverName, commercial_name, support_level } = row;
309
+ const isCloudSupported = isCloudAvailable(row);
306
310
  const supportLevel = support_level.toLowerCase();
307
311
 
308
312
  // Only process SQL drivers
@@ -463,10 +467,20 @@ module.exports.register = function (registry, context) {
463
467
  const firstCloudSupportedType = Array.from(types.entries())
464
468
  .map(([_, commercialNames]) => Object.values(commercialNames).find(({ isCloudSupported }) => isCloudSupported))
465
469
  .find(entry => entry);
470
+ // Say which Cloud pipelines when every Cloud-available type agrees:
471
+ // the ollama processors run only in GPU pipelines, and jira only outside them.
472
+ const cloudEntries = Array.from(types.values())
473
+ .flatMap(commercialNames => Object.values(commercialNames))
474
+ .filter(({ isCloudSupported }) => isCloudSupported);
475
+ const pipelineNote = cloudEntries.length && cloudEntries.every(e => e.gpuOnly)
476
+ ? ' (GPU pipelines only)'
477
+ : cloudEntries.length && cloudEntries.every(e => e.noGpu)
478
+ ? ' (not in GPU pipelines)'
479
+ : '';
466
480
  const cloudLinkDisplay = firstCloudSupportedType
467
481
  ? firstCloudSupportedType.urls.redpandaCloudUrl
468
- ? `<a href="${firstCloudSupportedType.urls.redpandaCloudUrl}">Yes</a>`
469
- : `Yes`
482
+ ? `<a href="${firstCloudSupportedType.urls.redpandaCloudUrl}">Yes</a>${pipelineNote}`
483
+ : `Yes${pipelineNote}`
470
484
  : 'No';
471
485
 
472
486
  const firstUrl = getFirstUrlFromTypesArray(Array.from(types.entries()), isCloud);
@@ -1018,7 +1032,8 @@ module.exports.register = function (registry, context) {
1018
1032
  const attributes = parent.getDocument().getAttributes();
1019
1033
  const component = attributes['page-component-title']; // Current component (for example, 'Redpanda Cloud' or 'Redpanda Connect')
1020
1034
  const name = attributes['doctitle'];
1021
- const type = attributes['type'];
1035
+ // Drafted pages no longer freeze :type:, so fall back to the page's directory.
1036
+ const type = attributes['type'] || typeFromRelative(attributes['page-relative-src-path']);
1022
1037
  if (!name || !type) {
1023
1038
  return self.createBlock(parent, 'pass', '');
1024
1039
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@redpanda-data/docs-extensions-and-macros",
3
- "version": "5.51.1",
3
+ "version": "5.52.0",
4
4
  "description": "Antora extensions and macros developed for Redpanda documentation.",
5
5
  "keywords": [
6
6
  "antora",
@@ -33,7 +33,10 @@ const SURFACE_ROUTES = [
33
33
  // console) and charts/<name>/values.yaml (connectors).
34
34
  { surface: 'helm', pattern: /^charts\/[^/]+\/(chart\/)?values\.yaml$/ },
35
35
  { surface: 'crd', pattern: /^operator\/api\// },
36
- { surface: 'connect', pattern: /^internal\/impl\// },
36
+ // Redpanda Connect publishes strings from component packages, the helper
37
+ // packages they share (internal/httpclient, internal/retries, ...), the
38
+ // public schema and the `rpk connect` CLI, plus config templates.
39
+ { surface: 'connect', pattern: /^(?:internal|public|cmd)\/(?!(?:.*\/)?testdata\/)(?:.*(?<!_test)\.go|.*\.tmpl\.ya?ml)$/ },
37
40
  // API protos, whose comments and openapiv2 option strings reach readers as
38
41
  // OpenAPI descriptions. console holds the data plane and Console APIs under
39
42
  // proto/redpanda/api/; cloudv2's control plane lives under proto/public/.
@@ -36,6 +36,7 @@ const SEVERITIES = ['error', 'warning', 'info']
36
36
  * - runOnUnverifiable: also run when decl.meta.unverifiable is set
37
37
  * (all other rules are skipped for unverifiable declarations, so a
38
38
  * non-literal source string can never produce an error)
39
+ * A declaration skips any rule named in decl.meta.skip_rules.
39
40
  * @param {Object} options - { skipRules: [names], onlyRules: [names] }
40
41
  * @returns {Object} { findings, summary: { byRule, bySurface } }
41
42
  */
@@ -55,6 +56,10 @@ function runRules (declarations, rules, options = {}) {
55
56
  // Unverifiable declarations (for example, a metric description built
56
57
  // with fmt::format) only run rules that opt in; they must never error.
57
58
  if (decl.meta && decl.meta.unverifiable && !rule.runOnUnverifiable) continue
59
+ // A surface can opt one declaration out of a rule whose premise does
60
+ // not hold for it (connect's CLI help is not AsciiDoc, though the
61
+ // rest of the surface is).
62
+ if (decl.meta && Array.isArray(decl.meta.skip_rules) && decl.meta.skip_rules.includes(rule.name)) continue
58
63
 
59
64
  try {
60
65
  for (const issue of rule.check(decl) || []) {
@@ -53,7 +53,7 @@ func fixtureConfigFields() []*service.ConfigField {
53
53
  Description("Poll interval."),
54
54
  // Deprecated fields are exempt from the missing-description rule.
55
55
  service.NewStringField("old_token").Deprecated().Default(""),
56
- // Dynamic description: skipped silently, never guessed at.
56
+ // Dynamic description: reported with a placeholder, never guessed at.
57
57
  service.NewStringField("dynamic_field").
58
58
  Description(dynamicDescription()),
59
59
  }
@@ -0,0 +1,5 @@
1
+ module github.com/redpanda-data/connect/v4
2
+
3
+ go 1.22
4
+
5
+ require github.com/redpanda-data/benthos/v4 v4.81.0
@@ -0,0 +1,19 @@
1
+ package cli
2
+
3
+ import "github.com/urfave/cli/v2"
4
+
5
+ var lintCommand = &cli.Command{
6
+ Name: "lint",
7
+ Usage: "Parse configs and report any linting errors",
8
+ Description: "Exits with a status code 1 if any linting errors are detected.",
9
+ Flags: []cli.Flag{
10
+ &cli.BoolFlag{
11
+ Name: "deprecated",
12
+ Usage: "Print linting errors for the presence of deprecated fields.",
13
+ },
14
+ &cli.StringFlag{
15
+ Name: "format",
16
+ Usage: "Output format",
17
+ },
18
+ },
19
+ }
@@ -0,0 +1,20 @@
1
+ // Package azure is a lint-strings fixture mirroring connect's Azure package:
2
+ // specs start from a helper constructor and share parameterized fields.
3
+ package azure
4
+
5
+ import "github.com/redpanda-data/benthos/v4/public/service"
6
+
7
+ const azureFieldAccount = "storage_account"
8
+
9
+ // azureComponentSpec builds the spec every Azure component starts from.
10
+ func azureComponentSpec() *service.ConfigSpec {
11
+ return service.NewConfigSpec().
12
+ Categories("Services", "Azure")
13
+ }
14
+
15
+ // accountField is called once per component, with a different product.
16
+ func accountField(product string) *service.ConfigField {
17
+ return service.NewStringField(azureFieldAccount).
18
+ Description("The storage account to access for " + product + ".").
19
+ ShortDescription("The `storage account` to access.")
20
+ }
@@ -0,0 +1,8 @@
1
+ package azure
2
+
3
+ // A constant and a helper in another file of the package.
4
+ const thingProduct = "Azure Thing Storage"
5
+
6
+ func thingDescription() string {
7
+ return "Reads every thing in a container. See xref:components:outputs/azure_thing.adoc[the output] and xref:outputs/azure_thing.adoc[the output again]."
8
+ }
@@ -0,0 +1,32 @@
1
+ package azure
2
+
3
+ import (
4
+ "github.com/redpanda-data/benthos/v4/public/service"
5
+
6
+ "github.com/redpanda-data/connect/v4/internal/retries"
7
+ )
8
+
9
+ func thingInputSpec() *service.ConfigSpec {
10
+ spec := azureComponentSpec().
11
+ Summary("Reads things from " + thingProduct + ".").
12
+ Description(thingDescription()).
13
+ Footnotes("== Throughput\n\nThe input reads one page of things per request.").
14
+ Example("Read every thing", "Reads each thing once and stops.", `input: {}`).
15
+
16
+ // A blank line and a comment inside the chain do not end it.
17
+ Field(accountField("Azure Thing input")).
18
+ Fields(retries.CommonRetryBackOffFields(3, "1s", "5s", "30s")...)
19
+ spec = spec.Field(service.NewStringAnnotatedEnumField("mode", map[string]string{
20
+ "once": "Reads each thing once.",
21
+ "follow": "Keeps reading new things as they arrive.",
22
+ }))
23
+ // Documented by reassignment: not a missing description.
24
+ f := service.NewStringField("prefix")
25
+ f = f.Description("Only things whose name starts with this prefix are read.")
26
+ spec = spec.Field(f)
27
+ return spec
28
+ }
29
+
30
+ func init() {
31
+ service.MustRegisterInput("azure_thing", thingInputSpec(), nil)
32
+ }
@@ -0,0 +1,12 @@
1
+ package azure
2
+
3
+ import "github.com/redpanda-data/benthos/v4/public/service"
4
+
5
+ func init() {
6
+ conf := azureComponentSpec().
7
+ Summary("Writes things to " + thingProduct + ".")
8
+ conf = conf.Field(accountField("Azure Thing output"))
9
+ // A field added in place, without reassignment.
10
+ conf.Field(service.NewStringField("container").Description("The container to write things to."))
11
+ service.MustRegisterOutput("azure_thing", conf, nil)
12
+ }
@@ -0,0 +1,21 @@
1
+ package widget
2
+
3
+ import "github.com/redpanda-data/benthos/v4/public/bloblang"
4
+
5
+ func init() {
6
+ spec := bloblang.NewPluginSpec().
7
+ Category("Encoding").
8
+ Description("Encodes a widget as a string.").
9
+ Param(bloblang.NewStringParam("format").Description("The format to encode the widget in.")).
10
+ Example("Encode a widget as JSON.", `root = this.encode_widget("json")`, [2]string{`{}`, `"{}"`})
11
+ bloblang.MustRegisterMethodV2("encode_widget", spec, nil)
12
+ }
13
+
14
+ type note struct{}
15
+
16
+ // Description on a type that is not a benthos spec.
17
+ func (n note) Description(s string) note { return n }
18
+
19
+ func init() {
20
+ _ = note{}.Description("Not a published string.")
21
+ }
@@ -0,0 +1,12 @@
1
+ package widget
2
+
3
+ import "github.com/redpanda-data/benthos/v4/public/service"
4
+
5
+ func linesScannerSpec() *service.ConfigSpec {
6
+ return service.NewConfigSpec().
7
+ Summary("Splits a widget stream into lines.")
8
+ }
9
+
10
+ func init() {
11
+ service.MustRegisterBatchScannerCreator("widget_lines", linesScannerSpec(), nil)
12
+ }
@@ -0,0 +1,3 @@
1
+ name ,type ,commercial_name ,support ,deprecated ,cloud ,cloud_with_gpu ,cloud_unsupported_reason
2
+ azure_thing ,input ,Azure Thing ,certified ,n ,y ,n ,
3
+ azure_thing ,output ,Azure Thing ,community ,n ,n ,n ,needs local disk
@@ -0,0 +1,13 @@
1
+ // Package retries is a helper package outside internal/impl whose fields
2
+ // appear on every component that calls it.
3
+ package retries
4
+
5
+ import "github.com/redpanda-data/benthos/v4/public/service"
6
+
7
+ // CommonRetryBackOffFields returns the common retry fields.
8
+ func CommonRetryBackOffFields(defaultMaxRetries int, initial, maxInterval, maxElapsed string) []*service.ConfigField {
9
+ return []*service.ConfigField{
10
+ service.NewIntField("max_retries").
11
+ Description("The maximum number of retries before giving up on the request."),
12
+ }
13
+ }