@redpanda-data/docs-extensions-and-macros 5.53.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 +166 -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 +64 -12
- package/extensions/add-git-dates.js +13 -1
- package/extensions/aggregate-terms.js +34 -20
- package/extensions/generate-rp-connect-info.js +79 -24
- package/extensions/modify-connect-tag-playbook.js +498 -75
- package/extensions/resolve-xrefs-in-attachments.js +1 -12
- package/extensions/url-to-xref.js +3 -0
- package/extensions/util/connect-catalog.js +10 -5
- package/extensions/util/connect-docs-asset.js +188 -0
- package/extensions/util/connect-github.js +16 -0
- 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 +10 -0
- 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/connect-docs/check-build-log.js +311 -0
- package/tools/connect-docs/check-rendered-html.js +469 -0
- package/tools/connect-docs/ci-playbook.yml +77 -0
- package/tools/connect-docs/diff-generated.js +382 -0
- package/tools/connect-docs/pr-comment.js +104 -0
- 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
|
@@ -22,7 +22,7 @@ npx doc-tools overrides audit \
|
|
|
22
22
|
--format human
|
|
23
23
|
|
|
24
24
|
# Standalone
|
|
25
|
-
node tools/overrides-audit/index.js --overrides <path> --extracted <path> [--surface properties|rpk
|
|
25
|
+
node tools/overrides-audit/index.js --overrides <path> --extracted <path> [--surface properties|rpk] [--format json|human] [--output <file>]
|
|
26
26
|
----
|
|
27
27
|
|
|
28
28
|
IMPORTANT: `--extracted` must be the property extractor's *raw* output (the `--output` file), never the enhanced output (`--enhanced-output`) or the versioned `redpanda-properties-<tag>.json` attachments: those already have overrides applied, so every override would classify as REDUNDANT. The same trap exists for rpk: the versioned `rpk-v<version>.json` files in docs repos are post-override snapshots.
|
|
@@ -133,7 +133,6 @@ JSON (default): `{ surface, overrides_file, extracted_file, manifest, summary, c
|
|
|
133
133
|
|
|
134
134
|
* `properties` (complete): `property-overrides.json` vs the property extractor's raw JSON.
|
|
135
135
|
* `rpk` (structural): enumerates `rpk-overrides.json` (see `docs-data/rpk-overrides.schema.json`) -- command/flag `description` fields are the upstreamable prose (classified `REVIEW` with a TODO note until source comparison lands; it needs a raw pre-override `--print-tree` snapshot plus the `formatDescription()` pipeline from `tools/rpk-docs`), everything else (`seeAlso`, `pageAliases`, `content`, platform scoping, ...) is `KEEP` by design.
|
|
136
|
-
* `connect` (structural): enumerates the rp-connect-docs `overrides.json` (`definitions` + per-component arrays with nested `config.children`); prose fields classify `REVIEW` with a TODO note until `$ref` resolution and plugin-JSON comparison land.
|
|
137
136
|
|
|
138
137
|
== Golden run (local checkouts required)
|
|
139
138
|
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Reads docs-data/property-overrides.json and an extracted-properties JSON
|
|
5
5
|
* (the property extractor's raw --output, WITHOUT overrides applied) and
|
|
6
6
|
* classifies every override field. This is the complete reference adapter;
|
|
7
|
-
* rpk
|
|
7
|
+
* The rpk adapter follows the same shape.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
'use strict'
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* node tools/overrides-audit/index.js \
|
|
13
13
|
* --overrides docs-data/property-overrides.json \
|
|
14
14
|
* --extracted tools/property-extractor/gen/properties-output.json \
|
|
15
|
-
* [--surface properties|rpk
|
|
15
|
+
* [--surface properties|rpk] [--format json|human] [--output <file>]
|
|
16
16
|
*
|
|
17
17
|
* Also exposed as `doc-tools overrides audit` and the `audit_overrides`
|
|
18
18
|
* MCP tool. See README.adoc in this directory for the classification rules
|
|
@@ -26,8 +26,7 @@ const path = require('path')
|
|
|
26
26
|
|
|
27
27
|
const ADAPTERS = {
|
|
28
28
|
properties: () => require('./adapters/properties'),
|
|
29
|
-
rpk: () => require('./adapters/rpk')
|
|
30
|
-
connect: () => require('./adapters/connect')
|
|
29
|
+
rpk: () => require('./adapters/rpk')
|
|
31
30
|
}
|
|
32
31
|
|
|
33
32
|
/**
|
|
@@ -153,7 +152,7 @@ function main (argv) {
|
|
|
153
152
|
.description('Classify docs-side override fields against extracted source strings')
|
|
154
153
|
.requiredOption('--overrides <path>', 'Path to the overrides JSON file (for example docs-data/property-overrides.json)')
|
|
155
154
|
.option('--extracted <path>', 'Path to the extracted source JSON (property extractor raw output; required for the properties surface)')
|
|
156
|
-
.option('--surface <surface>', 'Override surface: properties
|
|
155
|
+
.option('--surface <surface>', 'Override surface: properties or rpk', 'properties')
|
|
157
156
|
.option('--format <format>', 'Output format: json or human', 'json')
|
|
158
157
|
.option('--output <path>', 'Also write the JSON result to this file')
|
|
159
158
|
.action((options) => {
|
|
@@ -581,7 +581,7 @@ npx doc-tools generate rpcn-connector-docs \
|
|
|
581
581
|
| `--draft-missing` | Generate full page drafts for new connectors | false |
|
|
582
582
|
| `--update-whats-new` | Update whats-new.adoc with changes | false |
|
|
583
583
|
| `--include-bloblang` | Include Bloblang functions/methods | false |
|
|
584
|
-
| `--overrides <path>` | JSON file with description overrides |
|
|
584
|
+
| `--overrides <path>` | Optional JSON file with description overrides | None |
|
|
585
585
|
|
|
586
586
|
### Examples
|
|
587
587
|
|
|
@@ -766,41 +766,8 @@ Templates are in `tools/redpanda-connect/templates/`:
|
|
|
766
766
|
- `connector-examples.hbs` - Examples template
|
|
767
767
|
- `connector-full.hbs` - Full page draft template
|
|
768
768
|
|
|
769
|
-
###
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
```json
|
|
773
|
-
{
|
|
774
|
-
"inputs": {
|
|
775
|
-
"kafka": {
|
|
776
|
-
"fields": {
|
|
777
|
-
"addresses": {
|
|
778
|
-
"description": "Custom description override",
|
|
779
|
-
"examples": ["localhost:9092"]
|
|
780
|
-
}
|
|
781
|
-
}
|
|
782
|
-
}
|
|
783
|
-
}
|
|
784
|
-
}
|
|
785
|
-
```
|
|
786
|
-
|
|
787
|
-
Supports `$ref` syntax for deduplication:
|
|
788
|
-
```json
|
|
789
|
-
{
|
|
790
|
-
"inputs": {
|
|
791
|
-
"kafka": {
|
|
792
|
-
"fields": {
|
|
793
|
-
"tls": { "$ref": "#/common/tls" }
|
|
794
|
-
}
|
|
795
|
-
}
|
|
796
|
-
},
|
|
797
|
-
"common": {
|
|
798
|
-
"tls": {
|
|
799
|
-
"description": "TLS configuration (reused across components)"
|
|
800
|
-
}
|
|
801
|
-
}
|
|
802
|
-
}
|
|
803
|
-
```
|
|
769
|
+
### Fixing connector descriptions
|
|
770
|
+
Field, component, and Bloblang descriptions come from the component specs in `redpanda-data/connect` and `redpanda-data/benthos`. To fix wrong or missing text, open a PR against the spec. The fix ships in the next Connect release.
|
|
804
771
|
|
|
805
772
|
## Troubleshooting
|
|
806
773
|
|
|
@@ -46,11 +46,6 @@ npm install @redpanda-data/docs-extensions-and-macros
|
|
|
46
46
|
# Generate connector documentation with latest data
|
|
47
47
|
npx doc-tools generate rpcn-connector-docs --fetch-connectors
|
|
48
48
|
|
|
49
|
-
# Generate with custom overrides
|
|
50
|
-
npx doc-tools generate rpcn-connector-docs \
|
|
51
|
-
--fetch-connectors \
|
|
52
|
-
--overrides path/to/overrides.json
|
|
53
|
-
|
|
54
49
|
# Generate and draft missing connectors
|
|
55
50
|
npx doc-tools generate rpcn-connector-docs \
|
|
56
51
|
--fetch-connectors \
|
|
@@ -176,11 +171,6 @@ You can generate connector documentation with various options:
|
|
|
176
171
|
# Basic generation with fetching latest data
|
|
177
172
|
npx doc-tools generate rpcn-connector-docs --fetch-connectors
|
|
178
173
|
|
|
179
|
-
# Generate with custom overrides for descriptions/examples
|
|
180
|
-
npx doc-tools generate rpcn-connector-docs \
|
|
181
|
-
--fetch-connectors \
|
|
182
|
-
--overrides overrides.json
|
|
183
|
-
|
|
184
174
|
# Draft full pages for connectors missing documentation
|
|
185
175
|
npx doc-tools generate rpcn-connector-docs \
|
|
186
176
|
--fetch-connectors \
|
|
@@ -202,7 +192,7 @@ npx doc-tools generate rpcn-connector-docs \
|
|
|
202
192
|
* `--fetch-connectors` - Fetch latest data using `rpk connect list`
|
|
203
193
|
* `--connect-version <version>` - Specific Connect version to install and fetch (requires `--fetch-connectors`)
|
|
204
194
|
* `--draft-missing` - Generate full draft pages for undocumented connectors
|
|
205
|
-
* `--overrides <path>` -
|
|
195
|
+
* `--overrides <path>` - Merge descriptions and examples from an optional JSON file. Nothing is merged unless you pass it
|
|
206
196
|
* `--data-dir <path>` - Directory containing versioned connect JSON files
|
|
207
197
|
* `--old-data <path>` - Specific old version file for diff comparison
|
|
208
198
|
* `--include-bloblang` - Include Bloblang functions/methods
|
|
@@ -791,7 +781,9 @@ console.log('<!-- PR_SUMMARY_END -->');
|
|
|
791
781
|
|
|
792
782
|
=== Override system
|
|
793
783
|
|
|
794
|
-
|
|
784
|
+
Connector field, component, and Bloblang descriptions come from the component specs in https://github.com/redpanda-data/connect[redpanda-data/connect^] and https://github.com/redpanda-data/benthos[redpanda-data/benthos^]. To fix wrong or missing text, open a PR against the spec. The fix ships in the next Connect release.
|
|
785
|
+
|
|
786
|
+
The generator can also merge an optional JSON file passed with `--overrides` into the extracted data. Nothing is merged unless you pass the flag:
|
|
795
787
|
|
|
796
788
|
[,json]
|
|
797
789
|
----
|
|
@@ -840,11 +832,9 @@ Overrides support:
|
|
|
840
832
|
* Connector catalog enrichment
|
|
841
833
|
* Version tracking and change detection
|
|
842
834
|
* Handlebars template rendering
|
|
843
|
-
* Override system for custom documentation
|
|
844
835
|
|
|
845
836
|
=== Current limitations
|
|
846
837
|
|
|
847
|
-
* *Manual override maintenance*: Custom descriptions must be maintained in overrides.json
|
|
848
838
|
* *Example quality*: Auto-generated examples may not represent best practices
|
|
849
839
|
* *Duplicate YAML keys*: Some examples contain multiple configurations in one block, creating invalid YAML with duplicate top-level keys (for example, multiple `processors:` keys)
|
|
850
840
|
* *CSV catalog dependency*: Relies on manually maintained CSV in Connect repository
|
|
@@ -876,25 +866,7 @@ This occurs when multiple separate configurations are concatenated into a single
|
|
|
876
866
|
|
|
877
867
|
=== Workarounds
|
|
878
868
|
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
[,json]
|
|
882
|
-
----
|
|
883
|
-
{
|
|
884
|
-
"inputs": [
|
|
885
|
-
{
|
|
886
|
-
"name": "problematic_connector",
|
|
887
|
-
"description": "Custom description overriding auto-extracted text",
|
|
888
|
-
"examples": [
|
|
889
|
-
{
|
|
890
|
-
"title": "Corrected example",
|
|
891
|
-
"config": "input:\n problematic_connector:\n field: value"
|
|
892
|
-
}
|
|
893
|
-
]
|
|
894
|
-
}
|
|
895
|
-
]
|
|
896
|
-
}
|
|
897
|
-
----
|
|
869
|
+
To fix an unsupported pattern, a wrong description, or a broken example, edit the component spec in redpanda-data/connect or redpanda-data/benthos. The fix ships in the next Connect release.
|
|
898
870
|
|
|
899
871
|
== Contributing
|
|
900
872
|
|
|
@@ -101,9 +101,9 @@ function mergeOverrides(target, overrides) {
|
|
|
101
101
|
throw new Error('Target must be a valid object');
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
-
//
|
|
105
|
-
//
|
|
106
|
-
//
|
|
104
|
+
// Scalar keys are replaced outright. 'summary' must be listed: a key that
|
|
105
|
+
// falls through every branch below is silently dropped, and summaries feed
|
|
106
|
+
// page meta descriptions.
|
|
107
107
|
const scalarKeys = ['description', 'summary', 'type', 'annotated_field', 'version'];
|
|
108
108
|
|
|
109
109
|
for (const key in overrides) {
|
|
@@ -179,7 +179,7 @@ function renderStyleRegressionWarnings(warnings) {
|
|
|
179
179
|
if (!Array.isArray(warnings) || warnings.length === 0) return [];
|
|
180
180
|
const lines = [];
|
|
181
181
|
lines.push('> [!WARNING]');
|
|
182
|
-
lines.push('> **This update reintroduces house-style issues that the published docs do not have.** The fix belongs upstream in the
|
|
182
|
+
lines.push('> **This update reintroduces house-style issues that the published docs do not have.** The fix belongs upstream in the connect or benthos component spec: correcting the generated partial is undone by the next regeneration.');
|
|
183
183
|
for (const w of warnings) {
|
|
184
184
|
lines.push(`> - \`${w.partial}\` adds: ${w.issues.join(', ')}`);
|
|
185
185
|
}
|
|
@@ -323,7 +323,8 @@ function generateMultiVersionPRSummary(masterDiff, binaryAnalysis = null, drafte
|
|
|
323
323
|
lines.push('|-----------|-------|-------------|');
|
|
324
324
|
|
|
325
325
|
newFields.forEach(field => {
|
|
326
|
-
const
|
|
326
|
+
const text = field.shortDescription || field.description;
|
|
327
|
+
const desc = text ? truncateToSentence(text, 1).replace(/\|/g, '\\|') : '_No description_';
|
|
327
328
|
lines.push(`| \`${field.component}\` | \`${field.field}\` | ${desc} |`);
|
|
328
329
|
});
|
|
329
330
|
lines.push('');
|
|
@@ -359,7 +360,8 @@ function generateMultiVersionPRSummary(masterDiff, binaryAnalysis = null, drafte
|
|
|
359
360
|
lines.push(`**Deprecated Fields (${summary.deprecatedFields}):**`);
|
|
360
361
|
lines.push('');
|
|
361
362
|
deprecatedFields.forEach(field => {
|
|
362
|
-
const
|
|
363
|
+
const text = field.shortDescription || field.description;
|
|
364
|
+
const guidance = text ? ` — ${truncateToSentence(text, 1)}` : '';
|
|
363
365
|
lines.push(`- \`${field.component}.${field.field}\`${guidance}`);
|
|
364
366
|
});
|
|
365
367
|
lines.push('');
|
|
@@ -436,7 +438,7 @@ function generateMultiVersionPRSummary(masterDiff, binaryAnalysis = null, drafte
|
|
|
436
438
|
allDeprecatedFields.push({
|
|
437
439
|
component: field.component,
|
|
438
440
|
field: field.field,
|
|
439
|
-
description: field.description,
|
|
441
|
+
description: field.shortDescription || field.description,
|
|
440
442
|
version: release.toVersion
|
|
441
443
|
});
|
|
442
444
|
});
|
|
@@ -77,7 +77,8 @@ function generateConnectorDiffJson(oldIndex, newIndex, opts = {}) {
|
|
|
77
77
|
// span being compared: inputs:oracledb_cdc/snapshot_mode reports
|
|
78
78
|
// 4.99.0 in the 4.98.0 to 4.103.1 diff.
|
|
79
79
|
introducedIn: (rawFieldObj && (rawFieldObj.introducedInVersion || rawFieldObj.version)) || opts.newVersion || null,
|
|
80
|
-
description: rawFieldObj && rawFieldObj.description
|
|
80
|
+
description: rawFieldObj && rawFieldObj.description,
|
|
81
|
+
shortDescription: rawFieldObj && rawFieldObj.short_description
|
|
81
82
|
});
|
|
82
83
|
});
|
|
83
84
|
});
|
|
@@ -193,7 +194,8 @@ function generateConnectorDiffJson(oldIndex, newIndex, opts = {}) {
|
|
|
193
194
|
deprecatedFields.push({
|
|
194
195
|
component: cKey,
|
|
195
196
|
field: fName,
|
|
196
|
-
description: newFieldObj && newFieldObj.description
|
|
197
|
+
description: newFieldObj && newFieldObj.description,
|
|
198
|
+
shortDescription: newFieldObj && newFieldObj.short_description
|
|
197
199
|
});
|
|
198
200
|
}
|
|
199
201
|
|
|
@@ -212,7 +214,8 @@ function generateConnectorDiffJson(oldIndex, newIndex, opts = {}) {
|
|
|
212
214
|
field: fName,
|
|
213
215
|
oldDefault: oldDefault,
|
|
214
216
|
newDefault: newDefault,
|
|
215
|
-
description: newFieldObj && newFieldObj.description
|
|
217
|
+
description: newFieldObj && newFieldObj.description,
|
|
218
|
+
shortDescription: newFieldObj && newFieldObj.short_description
|
|
216
219
|
});
|
|
217
220
|
}
|
|
218
221
|
}
|
|
@@ -47,6 +47,21 @@ function capToTwoSentences (description) {
|
|
|
47
47
|
/\bNo\./gi
|
|
48
48
|
]
|
|
49
49
|
|
|
50
|
+
// A table cell takes one paragraph of plain text. Keep only the text before
|
|
51
|
+
// the first blank line or list item, so a description such as
|
|
52
|
+
// "Controls how X is sized:\n\n- `a`: ...\n- `b`: ..." does not leak half a
|
|
53
|
+
// list into the cell. A lead-in that ends with a colon is left unterminated
|
|
54
|
+
// so the sentence match below drops it whenever a complete sentence comes
|
|
55
|
+
// before it ("Configure TCP settings. These controls are useful for:" keeps
|
|
56
|
+
// only the first sentence). Only a lead-in with nothing before it is kept,
|
|
57
|
+
// with the colon turned into a period.
|
|
58
|
+
let colonLeadIn = false
|
|
59
|
+
const firstBlock = description.split(/\n\s*\n|\n(?=\s*(?:[-*]|\d+\.)\s)/)[0].trim()
|
|
60
|
+
if (firstBlock && firstBlock !== description.trim() && !/^(?:[=#]|```|----)/.test(firstBlock)) {
|
|
61
|
+
description = firstBlock
|
|
62
|
+
colonLeadIn = firstBlock.endsWith(':')
|
|
63
|
+
}
|
|
64
|
+
|
|
50
65
|
let normalized = description
|
|
51
66
|
const placeholders = []
|
|
52
67
|
|
|
@@ -65,16 +80,19 @@ function capToTwoSentences (description) {
|
|
|
65
80
|
})
|
|
66
81
|
|
|
67
82
|
const sentenceRegex = /[^.!?]+[.!?]+(?:\s|$)/g
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
//
|
|
71
|
-
//
|
|
72
|
-
//
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
83
|
+
|
|
84
|
+
// An unprotected mid-token period (`fetch.max.partition.bytes`) stops a
|
|
85
|
+
// match, and the regex then resumes after it, so the text before that
|
|
86
|
+
// period never belongs to any match. Merge each such gap into the sentence
|
|
87
|
+
// that follows it rather than silently discarding it, otherwise the second
|
|
88
|
+
// "sentence" can be a tail such as "bytes`.". Text after the last match
|
|
89
|
+
// (an unterminated colon lead-in, for example) is still dropped.
|
|
90
|
+
let sentences = null
|
|
91
|
+
let lastEnd = 0
|
|
92
|
+
for (const m of normalized.matchAll(sentenceRegex)) {
|
|
93
|
+
if (!sentences) sentences = []
|
|
94
|
+
sentences.push(normalized.slice(lastEnd, m.index) + m[0])
|
|
95
|
+
lastEnd = m.index + m[0].length
|
|
78
96
|
}
|
|
79
97
|
|
|
80
98
|
if (!sentences || sentences.length === 0) {
|
|
@@ -82,7 +100,7 @@ function capToTwoSentences (description) {
|
|
|
82
100
|
placeholders.forEach(({ placeholder, original }) => {
|
|
83
101
|
result = result.replace(placeholder, original)
|
|
84
102
|
})
|
|
85
|
-
return result
|
|
103
|
+
return colonLeadIn ? result.replace(/:$/, '.') : result
|
|
86
104
|
}
|
|
87
105
|
|
|
88
106
|
let maxSentences = 2
|
|
@@ -592,7 +610,7 @@ function buildFieldsTable (fields, capFn, opts = {}) {
|
|
|
592
610
|
if (!byField[key]) {
|
|
593
611
|
byField[key] = {
|
|
594
612
|
field: field.field,
|
|
595
|
-
description: field.description,
|
|
613
|
+
description: field.shortDescription || field.description,
|
|
596
614
|
introducedIn: field.introducedIn,
|
|
597
615
|
components: []
|
|
598
616
|
}
|
|
@@ -655,7 +673,7 @@ function buildChangedDefaultsTable (changedDefaults, capFn) {
|
|
|
655
673
|
field: change.field,
|
|
656
674
|
oldDefault: change.oldDefault,
|
|
657
675
|
newDefault: change.newDefault,
|
|
658
|
-
description: change.description,
|
|
676
|
+
description: change.shortDescription || change.description,
|
|
659
677
|
components: []
|
|
660
678
|
}
|
|
661
679
|
}
|
|
@@ -667,11 +685,15 @@ function buildChangedDefaultsTable (changedDefaults, capFn) {
|
|
|
667
685
|
section += '|Field |Old default |New default |Description |Affected components\n\n'
|
|
668
686
|
|
|
669
687
|
for (const [, info] of Object.entries(byFieldAndDefaults)) {
|
|
688
|
+
// Values render as literal inline code, so interpolations like
|
|
689
|
+
// ${!uuid_v4()} and characters like * or _ show as typed.
|
|
670
690
|
const formatDefault = (val) => {
|
|
671
691
|
if (val === undefined || val === null) return 'none'
|
|
672
|
-
|
|
673
|
-
if (typeof val === '
|
|
674
|
-
|
|
692
|
+
let text
|
|
693
|
+
if (typeof val === 'string') text = val === '' ? '""' : val
|
|
694
|
+
else if (typeof val === 'number' || typeof val === 'boolean') text = String(val)
|
|
695
|
+
else text = JSON.stringify(val)
|
|
696
|
+
return `\`+${text.replace(/\|/g, '\\|')}+\``
|
|
675
697
|
}
|
|
676
698
|
|
|
677
699
|
const oldVal = formatDefault(info.oldDefault)
|
|
@@ -131,6 +131,118 @@ function insertReleaseSection(pageContent, sectionText) {
|
|
|
131
131
|
return appendAtEnd();
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
+
/**
|
|
135
|
+
* Reorders the entries within each `=== category` of a curated section so that
|
|
136
|
+
* entries sharing an `Area::` label sit together. Areas are ordered
|
|
137
|
+
* ALPHABETICALLY (case-insensitive), so a given area sits in the same place on
|
|
138
|
+
* every release instead of moving with the source's merge order; entries keep
|
|
139
|
+
* their source order within an area, so the sort is stable. The label is
|
|
140
|
+
* repeated per entry (not merged into one heading), so the page format is
|
|
141
|
+
* unchanged — only the order of entries within a category changes. Running it on
|
|
142
|
+
* an already-clustered section is a no-op, so it is safe to apply on every
|
|
143
|
+
* release and on a backfill.
|
|
144
|
+
*
|
|
145
|
+
* Only the clean single-line `Area:: description` definition-list form is
|
|
146
|
+
* reordered. A category whose entries are not all of that form — a phase-1
|
|
147
|
+
* candidate's plain `*` bullets, or a multi-line entry — is left in its original
|
|
148
|
+
* order, so this is a safe no-op on anything it does not recognize. A curated
|
|
149
|
+
* category (one with at least one `Area::` entry) that bails logs a warning, since
|
|
150
|
+
* it then ships unclustered. Area keys are trimmed for grouping, so `Kafka API`
|
|
151
|
+
* and `Kafka API ` are one area. Headings and any prose outside a category are
|
|
152
|
+
* passed through untouched.
|
|
153
|
+
*
|
|
154
|
+
* @param {string} sectionText - A rendered release section.
|
|
155
|
+
* @return {string} The section with each category's entries clustered by area,
|
|
156
|
+
* ending in a single newline.
|
|
157
|
+
*/
|
|
158
|
+
function clusterSectionByArea(sectionText) {
|
|
159
|
+
// The area label is the text up to the first `:: ` (double-colon + space),
|
|
160
|
+
// which is the AsciiDoc definition-list term separator. A lazy match stops at
|
|
161
|
+
// that first separator, so a `::` later in the description is left alone. A
|
|
162
|
+
// leading list marker (`*`, `-`, `.`), at column zero or indented, is rejected
|
|
163
|
+
// so a plain bullet that happens to carry a `::` — a phase-1 candidate line —
|
|
164
|
+
// is not mistaken for an area entry; its category then bails and is left
|
|
165
|
+
// untouched.
|
|
166
|
+
const ENTRY_RE = /^(?!\s*[*.\-]+\s)(.+?)::\s/;
|
|
167
|
+
|
|
168
|
+
// Tokenize into paragraphs: maximal runs of non-blank lines. In this format
|
|
169
|
+
// every heading and every entry is one such paragraph.
|
|
170
|
+
const lines = String(sectionText).replace(/\n+$/, '').split('\n');
|
|
171
|
+
const paragraphs = [];
|
|
172
|
+
let current = [];
|
|
173
|
+
for (const line of lines) {
|
|
174
|
+
if (/^\s*$/.test(line)) {
|
|
175
|
+
if (current.length) { paragraphs.push(current); current = []; }
|
|
176
|
+
} else {
|
|
177
|
+
current.push(line);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
if (current.length) paragraphs.push(current);
|
|
181
|
+
|
|
182
|
+
const out = [];
|
|
183
|
+
let categoryEntries = null; // entry paragraphs of the open `=== category`
|
|
184
|
+
let categoryBail = false; // the open category has an unrecognized entry
|
|
185
|
+
let categoryTitle = ''; // the open category's heading text, for warnings
|
|
186
|
+
|
|
187
|
+
const flushCategory = () => {
|
|
188
|
+
if (!categoryEntries) return;
|
|
189
|
+
if (categoryBail) {
|
|
190
|
+
// A phase-1 bullet category is expected to bail. A curated one (it has at
|
|
191
|
+
// least one `Area::` entry) bailing means some entry spans several lines,
|
|
192
|
+
// so the whole category ships unclustered — say so rather than fail quietly.
|
|
193
|
+
if (categoryEntries.some((entry) => ENTRY_RE.test(entry[0]))) {
|
|
194
|
+
console.warn(`${LOG_TAG} WARN: "${categoryTitle}" not grouped by area: an entry is not a single-line \`Area:: description\` (for example a \`+\` continuation or a code block).`);
|
|
195
|
+
}
|
|
196
|
+
for (const entry of categoryEntries) out.push(entry);
|
|
197
|
+
} else {
|
|
198
|
+
const order = [];
|
|
199
|
+
const groups = new Map();
|
|
200
|
+
for (const entry of categoryEntries) {
|
|
201
|
+
// Trimmed, so a stray space before `::` does not split one area in two.
|
|
202
|
+
// Only the grouping key is trimmed; the entry text is emitted verbatim.
|
|
203
|
+
const key = entry[0].match(ENTRY_RE)[1].trim();
|
|
204
|
+
if (!groups.has(key)) { groups.set(key, []); order.push(key); }
|
|
205
|
+
groups.get(key).push(entry);
|
|
206
|
+
}
|
|
207
|
+
// Areas alphabetical (case-insensitive), so an area lands in the same
|
|
208
|
+
// place on every release; entries keep their source order within an area.
|
|
209
|
+
order.sort((a, b) => a.localeCompare(b, 'en', { sensitivity: 'base' }));
|
|
210
|
+
for (const key of order) {
|
|
211
|
+
for (const entry of groups.get(key)) out.push(entry);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
categoryEntries = null;
|
|
215
|
+
categoryBail = false;
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
for (const para of paragraphs) {
|
|
219
|
+
const first = para[0];
|
|
220
|
+
if (/^===\s/.test(first)) {
|
|
221
|
+
// A category heading closes the previous category and opens a new one.
|
|
222
|
+
flushCategory();
|
|
223
|
+
out.push(para);
|
|
224
|
+
categoryEntries = [];
|
|
225
|
+
categoryBail = false;
|
|
226
|
+
categoryTitle = first.replace(/^===\s+/, '').trim();
|
|
227
|
+
} else if (/^==\s/.test(first)) {
|
|
228
|
+
// The release heading (or any other top-level heading) closes the open
|
|
229
|
+
// category; entries do not cross it.
|
|
230
|
+
flushCategory();
|
|
231
|
+
out.push(para);
|
|
232
|
+
categoryEntries = null;
|
|
233
|
+
} else if (categoryEntries) {
|
|
234
|
+
categoryEntries.push(para);
|
|
235
|
+
if (para.length !== 1 || !ENTRY_RE.test(para[0])) categoryBail = true;
|
|
236
|
+
} else {
|
|
237
|
+
// Prose outside any category (for example an intro paragraph): untouched.
|
|
238
|
+
out.push(para);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
flushCategory();
|
|
242
|
+
|
|
243
|
+
return out.map((p) => p.join('\n')).join('\n\n') + '\n';
|
|
244
|
+
}
|
|
245
|
+
|
|
134
246
|
/**
|
|
135
247
|
* Builds a candidate release section from a raw release body.
|
|
136
248
|
*
|
|
@@ -221,7 +333,9 @@ function generateReleaseNotes({ body, section, tag, date, pageContent }) {
|
|
|
221
333
|
let finalSection;
|
|
222
334
|
if (section != null) {
|
|
223
335
|
assertSectionMatchesTag(section, version);
|
|
224
|
-
|
|
336
|
+
// Deterministically cluster entries by area so the curation step does not
|
|
337
|
+
// have to, and so a hand-written section is normalized the same way.
|
|
338
|
+
finalSection = clusterSectionByArea(section);
|
|
225
339
|
} else {
|
|
226
340
|
finalSection = buildReleaseSection({ body, version, date });
|
|
227
341
|
}
|
|
@@ -232,6 +346,7 @@ function generateReleaseNotes({ body, section, tag, date, pageContent }) {
|
|
|
232
346
|
module.exports = {
|
|
233
347
|
generateReleaseNotes,
|
|
234
348
|
buildReleaseSection,
|
|
349
|
+
clusterSectionByArea,
|
|
235
350
|
assertSectionMatchesTag,
|
|
236
351
|
insertReleaseSection,
|
|
237
352
|
compareVersions,
|
|
@@ -1,144 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Overrides Audit - Redpanda Connect Adapter (structural)
|
|
3
|
-
*
|
|
4
|
-
* Reads the rp-connect-docs docs-data/overrides.json:
|
|
5
|
-
* - top-level `definitions`: shared field descriptions referenced with
|
|
6
|
-
* `{"$ref": "#/definitions/<name>"}`
|
|
7
|
-
* - component sections (`inputs`, `outputs`, `processors`, `caches`,
|
|
8
|
-
* `tracers`, `scanners`, `rate-limits`): ARRAYS of connector entries
|
|
9
|
-
* `{ name, summary?, description?, config?: { children: [...] },
|
|
10
|
-
* examples?, version?, ... }` where config children nest recursively
|
|
11
|
-
* (`{ name, description?, children? , $ref? }`).
|
|
12
|
-
*
|
|
13
|
-
* The prose fields (summary, description, config field descriptions) map to
|
|
14
|
-
* ConfigSpec strings in connect Go source (`.Summary()`, `.Description()`,
|
|
15
|
-
* `NewXField(n).Description(...)`).
|
|
16
|
-
*
|
|
17
|
-
* TODO(quality logic): source comparison for connect is deferred. The
|
|
18
|
-
* extracted side is the connect plugin JSON (rpk connect list --format json
|
|
19
|
-
* or the cached connect-v<ver>.json in rp-connect-docs docs-data), and the
|
|
20
|
-
* comparison must resolve $refs before classifying, then apply the same
|
|
21
|
-
* markup rules as the properties surface (connect descriptions are AsciiDoc
|
|
22
|
-
* inside Go strings, so xref:/glossterm: usage is docs-only there too).
|
|
23
|
-
* Until then, prose fields classify REVIEW with an explicit note and
|
|
24
|
-
* everything else classifies KEEP, so the manifest shape is stable.
|
|
25
|
-
*/
|
|
26
|
-
|
|
27
|
-
'use strict'
|
|
28
|
-
|
|
29
|
-
const classify = require('../classify')
|
|
30
|
-
const { loadJson } = require('./properties')
|
|
31
|
-
|
|
32
|
-
const COMPONENT_SECTIONS = [
|
|
33
|
-
'inputs',
|
|
34
|
-
'outputs',
|
|
35
|
-
'processors',
|
|
36
|
-
'caches',
|
|
37
|
-
'tracers',
|
|
38
|
-
'scanners',
|
|
39
|
-
'rate-limits',
|
|
40
|
-
'buffers',
|
|
41
|
-
'metrics'
|
|
42
|
-
]
|
|
43
|
-
|
|
44
|
-
const TODO_NOTE = 'TODO: connect source comparison not implemented yet (needs the connect plugin JSON and $ref resolution).'
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Build a REVIEW row for a connect prose field.
|
|
48
|
-
*
|
|
49
|
-
* @param {string} name - Unit name (for example "inputs/amqp_0_9/urls").
|
|
50
|
-
* @param {string} field - Manifest field label.
|
|
51
|
-
* @param {string} text - Override text.
|
|
52
|
-
* @returns {Object} Manifest row.
|
|
53
|
-
*/
|
|
54
|
-
function proseRow (name, field, text) {
|
|
55
|
-
return {
|
|
56
|
-
name,
|
|
57
|
-
field,
|
|
58
|
-
class: classify.CLASSES.REVIEW,
|
|
59
|
-
content_hash: classify.contentHash(name, text),
|
|
60
|
-
note: TODO_NOTE
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Walk a config children tree, emitting one row per field description.
|
|
66
|
-
*
|
|
67
|
-
* @param {string} prefix - Name prefix ("inputs/amqp_0_9").
|
|
68
|
-
* @param {Object[]} children - Config children array.
|
|
69
|
-
* @param {Object[]} manifest - Output rows (mutated).
|
|
70
|
-
*/
|
|
71
|
-
function walkConfigChildren (prefix, children, manifest) {
|
|
72
|
-
if (!Array.isArray(children)) return
|
|
73
|
-
for (const child of children) {
|
|
74
|
-
if (!child || typeof child !== 'object') continue
|
|
75
|
-
const childName = `${prefix}/${child.name || '(unnamed)'}`
|
|
76
|
-
if (typeof child.description === 'string') {
|
|
77
|
-
manifest.push(proseRow(childName, 'config.description', child.description))
|
|
78
|
-
}
|
|
79
|
-
if (child.$ref) {
|
|
80
|
-
manifest.push({
|
|
81
|
-
name: childName,
|
|
82
|
-
field: 'config.$ref',
|
|
83
|
-
class: classify.CLASSES.KEEP,
|
|
84
|
-
note: `Shared definition reference (${child.$ref}); audit the referenced definition instead.`
|
|
85
|
-
})
|
|
86
|
-
}
|
|
87
|
-
if (child.children) walkConfigChildren(childName, child.children, manifest)
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Run the audit for the connect surface (structural pass).
|
|
93
|
-
*
|
|
94
|
-
* @param {Object} args - { overridesPath, extractedPath }.
|
|
95
|
-
* @returns {Object} { surface, manifest, summary }.
|
|
96
|
-
*/
|
|
97
|
-
function audit ({ overridesPath, extractedPath }) {
|
|
98
|
-
const overridesDoc = loadJson(overridesPath, 'connect overrides')
|
|
99
|
-
|
|
100
|
-
const manifest = []
|
|
101
|
-
|
|
102
|
-
const definitions = overridesDoc.definitions
|
|
103
|
-
if (definitions && typeof definitions === 'object') {
|
|
104
|
-
for (const [defName, def] of Object.entries(definitions)) {
|
|
105
|
-
if (def && typeof def.description === 'string') {
|
|
106
|
-
manifest.push(proseRow(`definitions/${defName}`, 'description', def.description))
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
for (const section of COMPONENT_SECTIONS) {
|
|
112
|
-
const entries = overridesDoc[section]
|
|
113
|
-
if (!Array.isArray(entries)) continue
|
|
114
|
-
for (const entry of entries) {
|
|
115
|
-
if (!entry || typeof entry !== 'object') continue
|
|
116
|
-
const entryName = `${section}/${entry.name || '(unnamed)'}`
|
|
117
|
-
for (const [field, value] of Object.entries(entry)) {
|
|
118
|
-
if (field === 'name') continue
|
|
119
|
-
if (field === 'summary' || field === 'description') {
|
|
120
|
-
manifest.push(proseRow(entryName, field, value))
|
|
121
|
-
} else if (field === 'config' && value && typeof value === 'object') {
|
|
122
|
-
walkConfigChildren(entryName, value.children, manifest)
|
|
123
|
-
} else {
|
|
124
|
-
manifest.push({
|
|
125
|
-
name: entryName,
|
|
126
|
-
field,
|
|
127
|
-
class: classify.CLASSES.KEEP,
|
|
128
|
-
note: 'Docs-side connector metadata; stays in the override file by design.'
|
|
129
|
-
})
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
return {
|
|
136
|
-
surface: 'connect',
|
|
137
|
-
overrides_file: overridesPath,
|
|
138
|
-
extracted_file: extractedPath || null,
|
|
139
|
-
manifest,
|
|
140
|
-
summary: classify.summarize(manifest)
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
module.exports = { audit, COMPONENT_SECTIONS }
|