@redpanda-data/docs-extensions-and-macros 5.54.0 → 5.55.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/bin/doc-tools-mcp.js +4 -5
- package/bin/doc-tools.js +39 -2
- package/bin/mcp-tools/generated-docs-review.js +26 -9
- package/bin/mcp-tools/overrides-audit.js +1 -1
- package/bin/mcp-tools/rpcn-docs.js +4 -14
- package/cli-utils/format-helm-spec.js +4 -3
- package/cli-utils/helm-commented-values.js +88 -18
- package/cli-utils/self-managed-docs-branch.js +36 -30
- package/extension-utils/html-utils.js +27 -0
- package/extensions/REFERENCE.adoc +20 -4
- package/extensions/add-git-dates.js +13 -1
- package/extensions/aggregate-terms.js +34 -20
- package/extensions/modify-connect-tag-playbook.js +181 -3
- package/extensions/resolve-xrefs-in-attachments.js +1 -12
- package/extensions/util/connect-docs-asset.js +6 -5
- package/extensions/version-fetcher/fetch-latest-docker-tag.js +35 -13
- package/extensions/version-fetcher/get-latest-redpanda-helm-version-from-operator.js +64 -40
- package/extensions/version-fetcher/list-connect-releases.js +3 -3
- package/extensions/version-fetcher/release-lines.js +64 -0
- package/extensions/version-fetcher/set-latest-version.js +73 -3
- package/macros/README.adoc +1 -0
- package/macros/REFERENCE.adoc +99 -0
- package/macros/badge.js +5 -8
- package/macros/enterprise.js +2 -5
- package/macros/glossary.js +4 -14
- package/macros/iceberg-explorer.js +242 -0
- package/macros/prop.js +3 -13
- package/mcp/USER_GUIDE.adoc +25 -135
- package/package.json +4 -2
- package/tools/doc-impact-recording/index.js +190 -0
- package/tools/overrides-audit/README.adoc +1 -2
- package/tools/overrides-audit/adapters/properties.js +1 -1
- package/tools/overrides-audit/index.js +3 -4
- package/tools/redpanda-connect/AUTOMATION.md +3 -36
- package/tools/redpanda-connect/README.adoc +5 -33
- package/tools/redpanda-connect/generate-rpcn-connector-docs.js +3 -3
- package/tools/redpanda-connect/pr-summary-formatter.js +6 -4
- package/tools/redpanda-connect/report-delta.js +6 -3
- package/tools/redpanda-connect/rpcn-connector-docs-handler.js +38 -16
- package/tools/redpanda-release-notes/generate-release-notes.js +116 -1
- package/tools/rpk-docs/templates/command.hbs +1 -1
- package/tools/overrides-audit/adapters/connect.js +0 -144
package/bin/doc-tools-mcp.js
CHANGED
|
@@ -10,7 +10,6 @@
|
|
|
10
10
|
* - Context-aware: Works from any repository based on cwd
|
|
11
11
|
* - Antora intelligence: Understands component/module structure
|
|
12
12
|
* - Automation: Run doc-tools generate commands
|
|
13
|
-
* - Auto-discovery: Prompts loaded automatically from mcp/prompts/
|
|
14
13
|
* - Validation: Startup checks ensure all resources are accessible
|
|
15
14
|
* - Telemetry: Usage tracking for adoption metrics
|
|
16
15
|
*/
|
|
@@ -201,7 +200,7 @@ const tools = [
|
|
|
201
200
|
},
|
|
202
201
|
{
|
|
203
202
|
name: 'generate_rpcn_connector_docs',
|
|
204
|
-
description: 'Generate Redpanda Connect connector documentation. Creates component documentation for all connectors. Writers use this when updating connector reference docs.
|
|
203
|
+
description: 'Generate Redpanda Connect connector documentation. Creates component documentation for all connectors. Writers use this when updating connector reference docs. Field, component, and Bloblang descriptions come from the component specs in redpanda-data/connect and redpanda-data/benthos, so fix wrong text with a PR there.',
|
|
205
204
|
inputSchema: {
|
|
206
205
|
type: 'object',
|
|
207
206
|
properties: {
|
|
@@ -239,7 +238,7 @@ const tools = [
|
|
|
239
238
|
},
|
|
240
239
|
overrides: {
|
|
241
240
|
type: 'string',
|
|
242
|
-
description: 'Path to optional JSON file
|
|
241
|
+
description: 'Path to an optional JSON file of description overrides (optional). Nothing is merged unless you pass it.'
|
|
243
242
|
},
|
|
244
243
|
cloud_version: {
|
|
245
244
|
type: 'string',
|
|
@@ -516,7 +515,7 @@ const tools = [
|
|
|
516
515
|
surface: {
|
|
517
516
|
type: 'string',
|
|
518
517
|
description: 'Override surface to audit (optional, defaults to "properties")',
|
|
519
|
-
enum: ['properties', 'rpk'
|
|
518
|
+
enum: ['properties', 'rpk']
|
|
520
519
|
},
|
|
521
520
|
output: {
|
|
522
521
|
type: 'string',
|
|
@@ -594,7 +593,7 @@ const resourceMap = {
|
|
|
594
593
|
|
|
595
594
|
/**
|
|
596
595
|
* Load resource content from team-standards directory
|
|
597
|
-
* @param {string} uri - Resource URI (such as 'redpanda://
|
|
596
|
+
* @param {string} uri - Resource URI (such as 'redpanda://personas')
|
|
598
597
|
* @returns {Object} Resource content
|
|
599
598
|
*/
|
|
600
599
|
function getResourceContent(uri) {
|
package/bin/doc-tools.js
CHANGED
|
@@ -896,7 +896,7 @@ automation
|
|
|
896
896
|
.option('--template-metadata <path>', 'Metadata section partial template', path.resolve(__dirname, '../tools/redpanda-connect/templates/metadata-partials.hbs'))
|
|
897
897
|
.option('--template-description <path>', 'Description section partial template', path.resolve(__dirname, '../tools/redpanda-connect/templates/descriptions-partials.hbs'))
|
|
898
898
|
.option('--template-bloblang <path>', 'Custom Handlebars template for bloblang function/method partials')
|
|
899
|
-
.option('--overrides <path>', 'Optional JSON file
|
|
899
|
+
.option('--overrides <path>', 'Optional JSON file of description overrides to merge into the generated output. Nothing is merged unless you pass it. Connector descriptions come from the connect and benthos component specs, so fix wrong text there')
|
|
900
900
|
.option('--include-bloblang', 'Include Bloblang functions and methods in generation')
|
|
901
901
|
.option('--no-partials', 'Skip the generated partials, config snippets, and Bloblang reference because the connect repo publishes them. What\'s new, diffs, drafts, nav, and the version bump still run')
|
|
902
902
|
.option('--prune-orphaned-descriptions', 'Allow the description-partial orphan sweep to blank more than 10% of the tree. Only use this when the connector dataset is confirmed complete: an incomplete dataset looks identical to a mass upstream deletion and the sweep blanks published content')
|
|
@@ -2881,6 +2881,43 @@ programCli
|
|
|
2881
2881
|
runCli(options)
|
|
2882
2882
|
})
|
|
2883
2883
|
|
|
2884
|
+
/**
|
|
2885
|
+
* doc-impact-recording
|
|
2886
|
+
*
|
|
2887
|
+
* @description
|
|
2888
|
+
* Extracts every redpanda-docs MCP call and its result from a Claude Code
|
|
2889
|
+
* transcript (the claude-code-action execution file, or `claude -p
|
|
2890
|
+
* --output-format stream-json` output) and writes them as a doc-impact eval
|
|
2891
|
+
* recording. Only the tool name, arguments and result content are kept.
|
|
2892
|
+
* Writes nothing when the session made no docs calls.
|
|
2893
|
+
*
|
|
2894
|
+
* @why
|
|
2895
|
+
* The doc-impact eval replays docs answers per PR. Answers recorded later
|
|
2896
|
+
* come from docs that may already contain the writer's fix, so the eval
|
|
2897
|
+
* would score a correct "already documented" as a miss. The doc-strings
|
|
2898
|
+
* review saves what the docs server told it at review time, and the eval
|
|
2899
|
+
* miner turns that into the item's recording.
|
|
2900
|
+
*
|
|
2901
|
+
* @example
|
|
2902
|
+
* # What the doc-strings review runs after its Claude step
|
|
2903
|
+
* npx doc-tools doc-impact-recording --execution-file "$RUNNER_TEMP/claude-execution-output.json" \
|
|
2904
|
+
* --output recording.json --item redpanda-operator-1615 --server-url https://docs.redpanda.com/mcp
|
|
2905
|
+
*/
|
|
2906
|
+
programCli
|
|
2907
|
+
.command('doc-impact-recording')
|
|
2908
|
+
.description('Extract the docs MCP calls from a Claude Code transcript into a doc-impact eval recording')
|
|
2909
|
+
.requiredOption('--execution-file <path>', 'Transcript: a JSON array of SDK messages or stream-json lines')
|
|
2910
|
+
.requiredOption('--output <path>', 'Recording file to write (not written when there are no docs calls)')
|
|
2911
|
+
.option('--item <id>', 'Item id to store in the recording')
|
|
2912
|
+
.option('--server <name>', 'MCP server name in the transcript tool names', 'redpanda-docs')
|
|
2913
|
+
.option('--server-url <url>', 'URL of that MCP server, stored in the recording')
|
|
2914
|
+
.option('--model <name>', 'Model to store (default: the model in the transcript init message)')
|
|
2915
|
+
.option('--recorded-at <iso>', 'Timestamp to store (default: now)')
|
|
2916
|
+
.action((options) => {
|
|
2917
|
+
const { runCli } = require('../tools/doc-impact-recording')
|
|
2918
|
+
process.exit(runCli(options))
|
|
2919
|
+
})
|
|
2920
|
+
|
|
2884
2921
|
/**
|
|
2885
2922
|
* lint-screenshots
|
|
2886
2923
|
*
|
|
@@ -3136,7 +3173,7 @@ overridesGroup
|
|
|
3136
3173
|
.description('Classify each override field as redundant, upstreamable, or keep against extracted source strings')
|
|
3137
3174
|
.requiredOption('--overrides <path>', 'Path to the overrides JSON file (for example docs-data/property-overrides.json)')
|
|
3138
3175
|
.option('--extracted <path>', 'Path to the extracted source JSON (property extractor raw output; required for the properties surface)')
|
|
3139
|
-
.addOption(new Option('--surface <surface>', 'Override surface to audit').choices(['properties', 'rpk'
|
|
3176
|
+
.addOption(new Option('--surface <surface>', 'Override surface to audit').choices(['properties', 'rpk']).default('properties'))
|
|
3140
3177
|
.addOption(new Option('--format <format>', 'Output format').choices(['json', 'human']).default('json'))
|
|
3141
3178
|
.option('--repo <path>', 'Redpanda checkout to extract raw source strings from (alternative to --extracted)')
|
|
3142
3179
|
.option('--output <path>', 'Also write the JSON result to this file')
|
|
@@ -189,7 +189,7 @@ function generateReviewReport(results, outputPath) {
|
|
|
189
189
|
report += `. Regenerate docs\n\n`;
|
|
190
190
|
} else if (issue.issue.includes('Invalid $ref')) {
|
|
191
191
|
report += `*Action:*\n\n`;
|
|
192
|
-
report += `. Open
|
|
192
|
+
report += `. Open \`${issue.file}\`\n`;
|
|
193
193
|
report += `. Find the reference at \`${issue.path}\`\n`;
|
|
194
194
|
report += `. Either add the missing definition or fix the reference\n`;
|
|
195
195
|
report += `. Regenerate docs\n\n`;
|
|
@@ -273,6 +273,17 @@ function generateReviewReport(results, outputPath) {
|
|
|
273
273
|
return report;
|
|
274
274
|
}
|
|
275
275
|
|
|
276
|
+
/**
|
|
277
|
+
* Where to fix Redpanda Connect reference text. The connect repo generates
|
|
278
|
+
* the field, component, and Bloblang partials from its specs and the benthos
|
|
279
|
+
* specs, and each Connect release publishes them for the docs build.
|
|
280
|
+
*/
|
|
281
|
+
const RPCN_SOURCE_GUIDANCE =
|
|
282
|
+
'Connector field, component, and Bloblang descriptions are generated from the component specs in ' +
|
|
283
|
+
'redpanda-data/connect and redpanda-data/benthos. To fix wrong or missing text, open a PR against ' +
|
|
284
|
+
'the spec in that repository; the fix ships in the next Connect release. Edit hand-written page ' +
|
|
285
|
+
'prose in rp-connect-docs.';
|
|
286
|
+
|
|
276
287
|
/**
|
|
277
288
|
* Review generated documentation for quality issues
|
|
278
289
|
* @param {Object} args - Arguments
|
|
@@ -282,7 +293,7 @@ function generateReviewReport(results, outputPath) {
|
|
|
282
293
|
* @param {string} [args.properties_dir] - Custom path to properties directory (default: modules/reference)
|
|
283
294
|
* @param {string} [args.metrics_file] - Custom path to metrics file (default: modules/reference/pages/public-metrics-reference.adoc)
|
|
284
295
|
* @param {string} [args.rpk_dir] - Custom path to RPK docs directory (default: autogenerated/{version}/rpk)
|
|
285
|
-
* @param {string} [args.overrides_file] -
|
|
296
|
+
* @param {string} [args.overrides_file] - Optional connector overrides file to validate for RPCN connectors. Without it, the review points at the connect and benthos specs, where connector descriptions live
|
|
286
297
|
* @returns {Object} Review results with issues and suggestions
|
|
287
298
|
*/
|
|
288
299
|
function reviewGeneratedDocs(args) {
|
|
@@ -524,16 +535,21 @@ function reviewGeneratedDocs(args) {
|
|
|
524
535
|
}
|
|
525
536
|
|
|
526
537
|
case 'rpcn_connectors': {
|
|
527
|
-
//
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
538
|
+
// Connector field, component, and Bloblang descriptions are generated
|
|
539
|
+
// from the connect and benthos specs, so there is no overrides file to
|
|
540
|
+
// check by default. Only an overrides file passed explicitly is validated.
|
|
541
|
+
if (!args.overrides_file) {
|
|
542
|
+
suggestions.push(RPCN_SOURCE_GUIDANCE);
|
|
543
|
+
break;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
const overridesPath = path.join(repoRoot.root, args.overrides_file);
|
|
531
547
|
|
|
532
548
|
if (!fs.existsSync(overridesPath)) {
|
|
533
549
|
return {
|
|
534
550
|
success: false,
|
|
535
|
-
error: `
|
|
536
|
-
suggestion:
|
|
551
|
+
error: `Overrides file not found at ${overridesPath}`,
|
|
552
|
+
suggestion: `Check the overrides_file path, or omit it. ${RPCN_SOURCE_GUIDANCE}`
|
|
537
553
|
};
|
|
538
554
|
}
|
|
539
555
|
|
|
@@ -736,5 +752,6 @@ function reviewGeneratedDocs(args) {
|
|
|
736
752
|
|
|
737
753
|
module.exports = {
|
|
738
754
|
generateReviewReport,
|
|
739
|
-
reviewGeneratedDocs
|
|
755
|
+
reviewGeneratedDocs,
|
|
756
|
+
RPCN_SOURCE_GUIDANCE
|
|
740
757
|
};
|
|
@@ -11,7 +11,7 @@ const { findRepoRoot, getDocToolsCommand, MAX_EXEC_BUFFER_SIZE, DEFAULT_COMMAND_
|
|
|
11
11
|
* @param {string} args.overrides - Path to the overrides JSON file (for example "docs-data/property-overrides.json")
|
|
12
12
|
* @param {string} [args.extracted] - Path to the extracted source JSON (property extractor raw output; required for the properties surface)
|
|
13
13
|
* @param {string} [args.repo] - Redpanda checkout to extract raw source strings from (alternative to args.extracted, properties surface only)
|
|
14
|
-
* @param {string} [args.surface] - Override surface: 'properties'
|
|
14
|
+
* @param {string} [args.surface] - Override surface: 'properties' or 'rpk'
|
|
15
15
|
* @param {string} [args.output] - Also write the JSON result to this file (relative to repo root)
|
|
16
16
|
* @returns {Object} Audit results with manifest and per-class summary
|
|
17
17
|
*/
|
|
@@ -12,8 +12,6 @@ const { execSync, spawnSync } = require('child_process');
|
|
|
12
12
|
const { findRepoRoot, MAX_EXEC_BUFFER_SIZE, DEFAULT_COMMAND_TIMEOUT } = require('./utils');
|
|
13
13
|
const { getAntoraStructure } = require('./antora');
|
|
14
14
|
const cache = require('./cache');
|
|
15
|
-
const fs = require('fs');
|
|
16
|
-
const path = require('path');
|
|
17
15
|
|
|
18
16
|
/**
|
|
19
17
|
* Generate Redpanda Connect connector documentation
|
|
@@ -59,19 +57,11 @@ function generateRpConnectDocs(args = {}) {
|
|
|
59
57
|
cmdArgs.push(args.csv);
|
|
60
58
|
}
|
|
61
59
|
|
|
62
|
-
//
|
|
63
|
-
|
|
64
|
-
if (
|
|
65
|
-
// Try to find docs-data/overrides.json in current directory
|
|
66
|
-
const defaultOverrides = path.join(process.cwd(), 'docs-data', 'overrides.json');
|
|
67
|
-
if (fs.existsSync(defaultOverrides)) {
|
|
68
|
-
overridesPath = defaultOverrides;
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
if (overridesPath) {
|
|
60
|
+
// Overrides are opt-in. Connector descriptions come from the connect and
|
|
61
|
+
// benthos specs, so no file is picked up implicitly.
|
|
62
|
+
if (args.overrides) {
|
|
73
63
|
cmdArgs.push('--overrides');
|
|
74
|
-
cmdArgs.push(
|
|
64
|
+
cmdArgs.push(args.overrides);
|
|
75
65
|
}
|
|
76
66
|
if (args.cloud_version) {
|
|
77
67
|
cmdArgs.push('--cloud-version');
|
|
@@ -9,9 +9,10 @@
|
|
|
9
9
|
*/
|
|
10
10
|
function formatHelmSpec (doc) {
|
|
11
11
|
const formatted = doc
|
|
12
|
-
//
|
|
13
|
-
|
|
14
|
-
.replace(/(\[\d
|
|
12
|
+
// an array index inside a heading's link text (ingress.hosts[0].host)
|
|
13
|
+
// closes the link text early unless its "]" is escaped
|
|
14
|
+
.replace(/(\[\d+)\]\./g, '$1\\].')
|
|
15
|
+
.replace(/(\[\d+)\]\]/g, '$1\\]]')
|
|
15
16
|
// a section title that is a bare URL has to be a link macro to render
|
|
16
17
|
.replace(/^=== +(https?:\/\/[^[]*)\[([^\]]*)\]/gm, '=== link:++$1++[$2]')
|
|
17
18
|
.replace(/^== # (.*)$/gm, '= $1')
|
|
@@ -24,6 +24,19 @@
|
|
|
24
24
|
*
|
|
25
25
|
* yields `external.domain`.
|
|
26
26
|
*
|
|
27
|
+
* The description attaches to the first commented-out key after it. When
|
|
28
|
+
* more commented-out siblings follow in the same comment run, each one
|
|
29
|
+
* with its own plain comment above it is documented by that comment:
|
|
30
|
+
*
|
|
31
|
+
* external:
|
|
32
|
+
* # -- Optional domain advertised to external clients.
|
|
33
|
+
* # domain: local
|
|
34
|
+
* # Optional list of advertised addresses.
|
|
35
|
+
* # addresses:
|
|
36
|
+
*
|
|
37
|
+
* yields `external.domain` and `external.addresses`. A sibling with no
|
|
38
|
+
* comment of its own is not documented.
|
|
39
|
+
*
|
|
27
40
|
* 2. Explicit, fully-qualified syntax that works anywhere in the file:
|
|
28
41
|
*
|
|
29
42
|
* # @doc external.addresses -- Optional list of advertised addresses.
|
|
@@ -89,10 +102,26 @@ function parseValuesFile (yamlText, { attachRealKeys = false } = {}) {
|
|
|
89
102
|
const records = []
|
|
90
103
|
const stack = [] // enclosing real keys: { name, indent }
|
|
91
104
|
|
|
92
|
-
// Accumulating comment block: { descLines, default, candidate }.
|
|
93
|
-
//
|
|
94
|
-
//
|
|
95
|
-
//
|
|
105
|
+
// Accumulating comment block: { marked, descLines, default, candidate }.
|
|
106
|
+
// A marked block opens at a `# --` line. Its description is every comment
|
|
107
|
+
// line up to the first commented-out key, and that key (the candidate) is
|
|
108
|
+
// the one it documents, the same way helm-docs reads a `# --` block as the
|
|
109
|
+
// description of the key directly below it.
|
|
110
|
+
//
|
|
111
|
+
// Optional values often ship as a run of commented-out siblings under one
|
|
112
|
+
// marker, each with its own plain comment above it:
|
|
113
|
+
//
|
|
114
|
+
// # -- Optional domain.
|
|
115
|
+
// # domain: local
|
|
116
|
+
// # Optional list of addresses.
|
|
117
|
+
// # addresses:
|
|
118
|
+
//
|
|
119
|
+
// Once a block has its key, a plain comment line at that key's depth (or
|
|
120
|
+
// shallower) ends it and opens an unmarked block for the next sibling, so
|
|
121
|
+
// `addresses` is documented by its own comment instead of handing its key
|
|
122
|
+
// line to the domain description. An unmarked block is only ever emitted
|
|
123
|
+
// when it has description text and a key; it never becomes a dead marker.
|
|
124
|
+
// The run ends at the first non-comment line, as a marked block does.
|
|
96
125
|
let block = null
|
|
97
126
|
let atDoc = null // accumulating @doc entry: { path, descLines, default }
|
|
98
127
|
let skipScalarIndent = -1 // inside a block scalar when >= 0
|
|
@@ -135,6 +164,7 @@ function parseValuesFile (yamlText, { attachRealKeys = false } = {}) {
|
|
|
135
164
|
// The generator drops these; the linter reports them, because their
|
|
136
165
|
// description silently never ships.
|
|
137
166
|
const emitDead = (b) => {
|
|
167
|
+
if (!b.marked) return
|
|
138
168
|
record({
|
|
139
169
|
kind: 'dead-marker',
|
|
140
170
|
descLines: b.descLines,
|
|
@@ -145,12 +175,26 @@ function parseValuesFile (yamlText, { attachRealKeys = false } = {}) {
|
|
|
145
175
|
})
|
|
146
176
|
}
|
|
147
177
|
|
|
178
|
+
const openUnmarkedBlock = (line, firstDescLine) => ({
|
|
179
|
+
marked: false,
|
|
180
|
+
markerLine: line,
|
|
181
|
+
lastLine: line,
|
|
182
|
+
descLines: firstDescLine === null ? [] : [firstDescLine],
|
|
183
|
+
default: '',
|
|
184
|
+
annotations: {},
|
|
185
|
+
candidate: null,
|
|
186
|
+
})
|
|
187
|
+
|
|
148
188
|
// Emit the active block's candidate, if any, and close the block. The
|
|
149
189
|
// emitted key suppresses its own commented subtree until real content
|
|
150
190
|
// appears, so nested example structures do not emit bogus paths.
|
|
151
191
|
const flushBlock = () => {
|
|
152
192
|
if (!block) return
|
|
153
|
-
if (block.candidate) {
|
|
193
|
+
if (block.candidate && !block.marked && !block.descLines.some((l) => l.trim() !== '')) {
|
|
194
|
+
// An unmarked sibling with no comment of its own has nothing to say;
|
|
195
|
+
// still suppress its commented subtree.
|
|
196
|
+
suppressInnerIndent = block.candidate.effIndent
|
|
197
|
+
} else if (block.candidate) {
|
|
154
198
|
const { name, effIndent } = block.candidate
|
|
155
199
|
const parents = stack.filter((k) => k.indent < effIndent).map((k) => k.name)
|
|
156
200
|
record({
|
|
@@ -233,13 +277,26 @@ function parseValuesFile (yamlText, { attachRealKeys = false } = {}) {
|
|
|
233
277
|
// "key" is a URL scheme and the "value" is the scheme-relative part.
|
|
234
278
|
const simpleValue = (value === '' || !/\s/.test(value)) && !value.startsWith('/')
|
|
235
279
|
if (block && simpleValue) {
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
//
|
|
239
|
-
//
|
|
240
|
-
|
|
280
|
+
const key = { name: commentedKey[3], effIndent }
|
|
281
|
+
if (!block.candidate || effIndent < block.candidate.effIndent) {
|
|
282
|
+
// The first key line after the description is the documented key.
|
|
283
|
+
// A shallower key replaces it, because the deeper line was a nested
|
|
284
|
+
// example structure rather than the key itself.
|
|
285
|
+
block.candidate = key
|
|
286
|
+
block.lastLine = i
|
|
287
|
+
} else if (effIndent > block.candidate.effIndent) {
|
|
288
|
+
// A nested line of the documented key's commented-out example.
|
|
289
|
+
block.lastLine = i
|
|
290
|
+
} else {
|
|
291
|
+
// A sibling key with no comment of its own. The documented key is
|
|
292
|
+
// complete; the sibling opens an unmarked block, which flushBlock
|
|
293
|
+
// drops because it has no description, while still suppressing
|
|
294
|
+
// the sibling's own commented subtree.
|
|
295
|
+
flushBlock()
|
|
296
|
+
suppressInnerIndent = -1
|
|
297
|
+
block = openUnmarkedBlock(i, null)
|
|
298
|
+
block.candidate = key
|
|
241
299
|
}
|
|
242
|
-
block.lastLine = i
|
|
243
300
|
continue
|
|
244
301
|
}
|
|
245
302
|
}
|
|
@@ -248,18 +305,30 @@ function parseValuesFile (yamlText, { attachRealKeys = false } = {}) {
|
|
|
248
305
|
if (descMarker) {
|
|
249
306
|
flushAtDoc()
|
|
250
307
|
flushBlock()
|
|
251
|
-
block = { markerLine: i, lastLine: i, descLines: [descMarker[1]], default: '', annotations: {}, candidate: null }
|
|
308
|
+
block = { marked: true, markerLine: i, lastLine: i, descLines: [descMarker[1]], default: '', annotations: {}, candidate: null }
|
|
252
309
|
continue
|
|
253
310
|
}
|
|
254
311
|
|
|
255
312
|
const comment = line.match(COMMENT_RE)
|
|
256
313
|
if (comment && line.trim().startsWith('#')) {
|
|
257
314
|
if (atDoc) { atDoc.descLines.push(comment[1]); atDoc.lastLine = i }
|
|
258
|
-
// Description lines precede the documented key
|
|
259
|
-
// exists, trailing comment lines belong to its example structure and
|
|
260
|
-
// only extend the span.
|
|
315
|
+
// Description lines precede the documented key.
|
|
261
316
|
else if (block && !block.candidate) { block.descLines.push(comment[1]); block.lastLine = i }
|
|
262
|
-
else if (block)
|
|
317
|
+
else if (block) {
|
|
318
|
+
// After the key: deeper lines, list items, and bare `#` lines belong
|
|
319
|
+
// to its commented-out example. Prose at the key's own depth or
|
|
320
|
+
// shallower is the description of the next commented-out sibling.
|
|
321
|
+
const [, before, after] = line.match(/^(\s*)#(\s*)/)
|
|
322
|
+
const proseIndent = before.length + Math.max(0, after.length - 1)
|
|
323
|
+
const text = comment[1].trim()
|
|
324
|
+
if (text === '' || text.startsWith('- ') || text === '-' || proseIndent > block.candidate.effIndent) {
|
|
325
|
+
block.lastLine = i
|
|
326
|
+
} else {
|
|
327
|
+
flushBlock()
|
|
328
|
+
suppressInnerIndent = -1
|
|
329
|
+
block = openUnmarkedBlock(i, comment[1])
|
|
330
|
+
}
|
|
331
|
+
}
|
|
263
332
|
continue
|
|
264
333
|
}
|
|
265
334
|
|
|
@@ -270,8 +339,9 @@ function parseValuesFile (yamlText, { attachRealKeys = false } = {}) {
|
|
|
270
339
|
flushAtDoc()
|
|
271
340
|
|
|
272
341
|
const realKey = line.match(REAL_KEY_RE)
|
|
273
|
-
if (attachRealKeys && realKey && block && !block.candidate && block.lastLine === i - 1) {
|
|
274
|
-
// helm-docs' own attachment: the block sits DIRECTLY above a
|
|
342
|
+
if (attachRealKeys && realKey && block && block.marked && !block.candidate && block.lastLine === i - 1) {
|
|
343
|
+
// helm-docs' own attachment: the marked block sits DIRECTLY above a
|
|
344
|
+
// real key.
|
|
275
345
|
const indent = realKey[1].length
|
|
276
346
|
while (stack.length > 0 && stack[stack.length - 1].indent >= indent) stack.pop()
|
|
277
347
|
const name = realKey[2].replace(/^"|"$/g, '')
|
|
@@ -43,51 +43,57 @@ async function fetchRemoteAntoraVersion(deps = {}) {
|
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
/**
|
|
46
|
-
*
|
|
46
|
+
* Reports whether a branch exists in the docs repository.
|
|
47
47
|
*
|
|
48
|
-
*
|
|
48
|
+
* @param {string} branch - Branch name, such as "main" or "v/25.1".
|
|
49
|
+
* @returns {boolean} True when `git ls-remote` finds the branch.
|
|
50
|
+
*/
|
|
51
|
+
function docsBranchExists(branch) {
|
|
52
|
+
const repo = 'https://github.com/redpanda-data/docs.git'
|
|
53
|
+
return spawnSync('git', ['ls-remote', '--exit-code', '--heads', repo, `refs/heads/${branch}`], {
|
|
54
|
+
stdio: 'ignore'
|
|
55
|
+
}).status === 0
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Determines the appropriate documentation branch for a given operator tag or branch based on the remote Antora version.
|
|
60
|
+
*
|
|
61
|
+
* Normalizes the input, extracts the major.minor version, and applies version-specific logic to select the correct branch. Verifies that the chosen branch exists in the remote repository.
|
|
62
|
+
* A ref with no version, such as `main`, `dev`, or a feature branch, holds content for the next release, which the docs repository keeps on `main`.
|
|
49
63
|
*
|
|
50
|
-
* @param {string}
|
|
64
|
+
* @param {string} operatorRef - The operator tag or branch to evaluate (for example, "operator/v25.1.2", "v25.1.2", "release/v25.1.x", or "main").
|
|
65
|
+
* @param {object} [deps] - Injectable for tests: { antoraVersion, branchExists, fetchImpl, token }.
|
|
51
66
|
* @returns {Promise<string>} The name of the documentation branch to use.
|
|
52
67
|
*
|
|
53
|
-
* @throws {Error} If the
|
|
68
|
+
* @throws {Error} If the Antora version cannot be fetched or if the determined branch does not exist in the remote repository.
|
|
54
69
|
*/
|
|
55
|
-
async function determineDocsBranch(
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
const ANTORA = await fetchRemoteAntoraVersion()
|
|
70
|
+
async function determineDocsBranch(operatorRef, deps = {}) {
|
|
71
|
+
const branchExists = deps.branchExists || docsBranchExists
|
|
72
|
+
// Strip any "operator/" or "release/" prefix
|
|
73
|
+
const TAG = operatorRef.replace(/^(?:operator|release)\//, '')
|
|
60
74
|
// Extract v<major>.<minor>
|
|
61
75
|
const mm = TAG.match(/^v(\d+\.\d+)/)
|
|
62
76
|
const filtered = mm ? `v${mm[1]}` : null
|
|
63
77
|
|
|
64
|
-
if (!filtered) {
|
|
65
|
-
throw new Error(`Could not parse major.minor from ${TAG}`)
|
|
66
|
-
}
|
|
67
|
-
// We started versioning the operator in line with Redpanda core versions. But when v2.4.x was the latest version, the docs were still on 25.1 and v25.1.x of the operator was still in beta. So we need to handle this special case.
|
|
68
78
|
let branch
|
|
69
|
-
if (filtered
|
|
70
|
-
|
|
79
|
+
if (!filtered) {
|
|
80
|
+
branch = 'main'
|
|
81
|
+
} else {
|
|
82
|
+
// Pull in the remote Antora version
|
|
83
|
+
const ANTORA = deps.antoraVersion || await fetchRemoteAntoraVersion(deps)
|
|
84
|
+
// We started versioning the operator in line with Redpanda core versions. But when v2.4.x was the latest version, the docs were still on 25.1 and v25.1.x of the operator was still in beta. So we need to handle this special case.
|
|
85
|
+
if (filtered === 'v2.4') {
|
|
86
|
+
branch = ANTORA === '25.1' ? 'main' : 'v/24.3'
|
|
87
|
+
// For all other versions use the v<major>.<minor> branch unless it is the current version, in which case we use 'main'.
|
|
88
|
+
} else if (filtered === `v${ANTORA}`) {
|
|
71
89
|
branch = 'main'
|
|
72
90
|
} else {
|
|
73
|
-
branch =
|
|
91
|
+
branch = `v/${filtered.slice(1)}`
|
|
74
92
|
}
|
|
75
|
-
// For all other versions use the v<major>.<minor> branch unless it is the current version, in which case we use 'main'.
|
|
76
|
-
} else if (filtered === `v${ANTORA}`) {
|
|
77
|
-
branch = 'main'
|
|
78
|
-
} else {
|
|
79
|
-
branch = `v/${filtered.slice(1)}`
|
|
80
93
|
}
|
|
81
94
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
const ref = `refs/heads/${branch}`
|
|
85
|
-
const ok = spawnSync('git', ['ls-remote', '--exit-code', '--heads', repo, ref], {
|
|
86
|
-
stdio: 'ignore'
|
|
87
|
-
}).status === 0
|
|
88
|
-
|
|
89
|
-
if (!ok) {
|
|
90
|
-
throw new Error(`Docs branch ${branch} not found in ${repo}`)
|
|
95
|
+
if (!branchExists(branch)) {
|
|
96
|
+
throw new Error(`Docs branch ${branch} not found in https://github.com/redpanda-data/docs.git`)
|
|
91
97
|
}
|
|
92
98
|
|
|
93
99
|
return branch
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Shared HTML utility functions for extensions and macros.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Escape a value for safe inclusion in HTML text or inside a double-quoted
|
|
9
|
+
* attribute. Escaping `&` first is what keeps the result idempotent-safe for a
|
|
10
|
+
* single pass; escaping `"` is what stops an author-supplied string from ending
|
|
11
|
+
* the attribute it sits in.
|
|
12
|
+
*
|
|
13
|
+
* @param {*} value - Value to escape. `null` and `undefined` become ''.
|
|
14
|
+
* @returns {string} The escaped string.
|
|
15
|
+
*/
|
|
16
|
+
function escapeHtml (value) {
|
|
17
|
+
if (value === null || value === undefined) return ''
|
|
18
|
+
return String(value)
|
|
19
|
+
.replace(/&/g, '&')
|
|
20
|
+
.replace(/</g, '<')
|
|
21
|
+
.replace(/>/g, '>')
|
|
22
|
+
.replace(/"/g, '"')
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
module.exports = {
|
|
26
|
+
escapeHtml,
|
|
27
|
+
}
|
|
@@ -412,7 +412,8 @@ At build time, when the playbook has the `connect` component, the extension:
|
|
|
412
412
|
|
|
413
413
|
. Picks the release: the latest stable `vX.Y.Z` release by default, so the site never shows fields from unreleased code on connect's `main` branch. It asks the GitHub API for the latest release and falls back to the highest stable `vX.Y.Z` tag from `git ls-remote`. If neither works, the build stops with an error. The `tag` option picks a specific release instead.
|
|
414
414
|
. Downloads that release's `redpanda-connect-docs.tar.gz` asset. With a GitHub token it downloads through the releases API, which keeps working if the connect repository becomes private. Without one, or if GitHub rejects the token, it uses the public download URL.
|
|
415
|
-
. Adds `modules/components/partials/**` as partials
|
|
415
|
+
. Adds `modules/components/partials/**` as partials, `modules/components/examples/**` as examples, and `modules/components/attachments/**` (the release's `connect-<version>.json`, read by the Bloblang playground) as attachments of the `components` module, in every version of the `connect` component. A file that another source, such as rp-connect-docs, already provides is skipped and counted in the log, so rp-connect-docs can keep committing its own copies until it stops.
|
|
416
|
+
. When the playbook also registers `add-git-dates`, dates each page by the Connect release that last changed its reference. For every generated partial or example a page includes, directly or through another generated partial such as `bloblang/methods.adoc`, the extension compares the release with older releases that have the asset, newest first, and takes the publish date of the newest release that changed the file. A page that single-sources such a connect page, such as a Cloud stub, gets the same date. `add-git-dates` uses the date as `page-git-modified-date` when it is later than the page's last commit. The walk stops at v4.113.0, the first release with the asset, and after at most 10 older releases. A file that is unchanged for the whole walk keeps the page's git date. If the releases listing or an older release's download fails, the extension logs a warning and pages keep their git dates, so the build never stops for a date. Without `add-git-dates` in the playbook, the extension skips this step and downloads only the release it uses.
|
|
416
417
|
|
|
417
418
|
A release from before the asset existed has none, and the download returns 404. A new release can also be missing it for a while: Connect publishes the release first and attaches the asset 15 to 20 minutes later in a separate job, which can fail.
|
|
418
419
|
|
|
@@ -490,8 +491,8 @@ The following attributes are available to all versions of all Antora components:
|
|
|
490
491
|
`console-beta-version`:: The latest beta version of Redpanda Console.
|
|
491
492
|
`operator-beta-version`:: The latest beta version of the Redpanda Operator.
|
|
492
493
|
`helm-beta-version`:: The latest beta version of the Redpanda Helm chart.
|
|
493
|
-
`latest-operator-version`:: The latest release version of the Redpanda Operator. This value keeps its `v` prefix (for example, `v25.1.3`) because pages pass it straight to `helm --version`.
|
|
494
|
-
`latest-redpanda-helm-chart-version`:: The latest release version of the Redpanda Helm chart.
|
|
494
|
+
`latest-operator-version`:: The latest release version of the Redpanda Operator for the component version's release line (see <<operator-and-chart-per-version>>). This value keeps its `v` prefix (for example, `v25.1.3`) because pages pass it straight to `helm --version`.
|
|
495
|
+
`latest-redpanda-helm-chart-version`:: The latest release version of the Redpanda Helm chart for the component version's release line (see <<operator-and-chart-per-version>>). This value has no `v` prefix (for example, `25.3.11`).
|
|
495
496
|
|
|
496
497
|
The following attributes are available to the latest version of each Antora component:
|
|
497
498
|
|
|
@@ -511,6 +512,19 @@ ever moves forwards.
|
|
|
511
512
|
|
|
512
513
|
A short attribute is only set when a `major.minor` pair can be derived. Release channels that are not versions, such as `nightly` or `latest`, get a `-version` and a `-tag` but no `-version-short`, so a page never renders a fabricated release line.
|
|
513
514
|
|
|
515
|
+
[[operator-and-chart-per-version]]
|
|
516
|
+
==== Operator and Helm chart versions per component version
|
|
517
|
+
|
|
518
|
+
An operator or Helm chart release supports its own Redpanda version and one minor version above and below it, so each component version gets the release of its own line:
|
|
519
|
+
|
|
520
|
+
* A component version named `<major>.<minor>` that is older than the component's latest version gets the newest stable release in that line. For example, version `25.3` gets the newest `v25.3.x` operator and the newest `25.3.x` chart, even when a `26.2.x` release exists.
|
|
521
|
+
* The component's latest version, any prerelease version ahead of it, and unversioned components get the newest stable release overall.
|
|
522
|
+
* If an older line has no stable operator release (for example, lines that shipped only legacy `v2.x.y-<redpanda-version>` operator tags), the newest release does not support it, so both attributes keep the values the component version's `antora.yml` sets. If the line has an operator release but no chart release, only the chart attribute keeps its `antora.yml` value. An attribute that `antora.yml` leaves unset or empty falls back to the newest release overall. Either way, the build logs a warning that names the line.
|
|
523
|
+
|
|
524
|
+
The release lines come from the fetched operator tags, so no version map needs updating when a line ships. The extension reads up to three pages (300 tags) of the operator's Docker Hub tags, newest pushes first.
|
|
525
|
+
|
|
526
|
+
The chart version of a line is the newest stable `charts/redpanda/v<major>.<minor>.*` tag in the redpanda-operator repository, which marks a published chart (what `helm search repo` reports). A release branch's `Chart.yaml` can be ahead of the published chart, so the extension reads it only when the line has no chart tag, trying each path the chart has used in order: `charts/redpanda/chart/Chart.yaml`, then `charts/redpanda/Chart.yaml` (the layout of `release/v25.1.x`).
|
|
527
|
+
|
|
514
528
|
==== Seed a fallback in antora.yml
|
|
515
529
|
|
|
516
530
|
Every attribute here depends on a successful GitHub lookup at build time. If a lookup fails, for example because of a rate limit or a missing token, the attribute is never set and any page that references it renders the attribute name in braces.
|
|
@@ -878,6 +892,8 @@ The extension accepts the following configuration options:
|
|
|
878
892
|
|
|
879
893
|
termspath (optional):: Specifies the path to a local directory containing term files (in `.adoc` format). If this path is provided, the extension will attempt to load terms from this directory first. If this path is not provided or no valid terms are found in the specified directory, the extension will fall back to loading terms from the `shared` component.
|
|
880
894
|
|
|
895
|
+
glossarypage (optional):: The resource ID of one page that holds the glossary for the whole site, such as `home:ROOT:glossary.adoc`. The extension appends every term to that page and sets every component's `glossary-page` attribute to it, so `glossterm` links from any component point at the one glossary. Any `reference:glossary.adoc` pages that components still have are filled too, so they stay complete until they are removed. If the page does not exist, the extension logs a warning and links each component to its own `reference:glossary.adoc`, as without this option. When the page exists, it takes precedence over a `glossary-page` attribute set in a component's `antora.yml`, so a per-component value only applies if this page is missing.
|
|
896
|
+
|
|
881
897
|
Term files should follow the following structure:
|
|
882
898
|
|
|
883
899
|
```asciidoc
|
|
@@ -1113,7 +1129,7 @@ This extension adds accurate git-based created and modified dates to documentati
|
|
|
1113
1129
|
The extension adds the following attributes to each page:
|
|
1114
1130
|
|
|
1115
1131
|
- `page-git-created-date`: The date when the file was first committed (YYYY-MM-DD format)
|
|
1116
|
-
- `page-git-modified-date`: The date when the file was last modified (YYYY-MM-DD format)
|
|
1132
|
+
- `page-git-modified-date`: The date when the file was last modified (YYYY-MM-DD format). For a page with Connect reference content, this is the date of the most recent Connect release that changed that content, when it is later than the page's last commit (see <<Redpanda Connect tag modifier>>).
|
|
1117
1133
|
|
|
1118
1134
|
These attributes are available for use in UI templates (for metadata, display banners, etc.) and are exported to markdown files for AI consumption.
|
|
1119
1135
|
|
|
@@ -180,6 +180,16 @@ async function getTreeFiles (git, gitdir, oid, prefix, cache, treeCache) {
|
|
|
180
180
|
return files
|
|
181
181
|
}
|
|
182
182
|
|
|
183
|
+
// The later of two YYYY-MM-DD dates. modify-connect-tag-playbook sets
|
|
184
|
+
// page.connectReferenceModified to the date of the Connect release that last
|
|
185
|
+
// changed a page's generated reference, which lives in a release asset and so
|
|
186
|
+
// in no git commit of the page's repo.
|
|
187
|
+
function laterDate (a, b) {
|
|
188
|
+
if (!b) return a
|
|
189
|
+
if (!a) return b
|
|
190
|
+
return b > a ? b : a
|
|
191
|
+
}
|
|
192
|
+
|
|
183
193
|
module.exports.register = function () {
|
|
184
194
|
raiseListenerLimit(this)
|
|
185
195
|
const logger = this.getLogger('add-git-dates-extension')
|
|
@@ -271,7 +281,7 @@ module.exports.register = function () {
|
|
|
271
281
|
const dates = fileDateMap.get(relativeFilePath)
|
|
272
282
|
if (dates) {
|
|
273
283
|
page.asciidoc.attributes['page-git-created-date'] = dates.created
|
|
274
|
-
page.asciidoc.attributes['page-git-modified-date'] = dates.modified
|
|
284
|
+
page.asciidoc.attributes['page-git-modified-date'] = laterDate(dates.modified, page.connectReferenceModified)
|
|
275
285
|
processedCount++
|
|
276
286
|
}
|
|
277
287
|
}
|
|
@@ -288,3 +298,5 @@ module.exports.register = function () {
|
|
|
288
298
|
logger.info(`Git dates added: processed=${processedCount}, skipped=${skippedCount}, duration=${duration}ms (${perPage}ms/page)`)
|
|
289
299
|
})
|
|
290
300
|
}
|
|
301
|
+
|
|
302
|
+
module.exports._internal = Object.assign(module.exports._internal || {}, { laterDate })
|