@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
|
@@ -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 }
|