@redpanda-data/docs-extensions-and-macros 5.38.3 → 5.38.5
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/package.json +1 -1
- package/tools/lint-strings/rules/common.js +55 -0
- package/tools/redpanda-connect/generate-rpcn-connector-docs.js +41 -1
- package/tools/redpanda-connect/pr-summary-formatter.js +32 -2
- package/tools/redpanda-connect/rpcn-connector-docs-handler.js +18 -4
- package/tools/rpk-docs/generate-rpk-docs.js +9 -1
package/package.json
CHANGED
|
@@ -127,6 +127,18 @@ function findBareCodeTokens (text, name = null) {
|
|
|
127
127
|
return found
|
|
128
128
|
}
|
|
129
129
|
|
|
130
|
+
/**
|
|
131
|
+
* A short window of `text` around the first occurrence of `needle`, for use in
|
|
132
|
+
* rule messages so the writer can see the offending phrase in context.
|
|
133
|
+
*/
|
|
134
|
+
function excerpt (text, needle, width = 60) {
|
|
135
|
+
const at = text.toLowerCase().indexOf(needle.toLowerCase())
|
|
136
|
+
if (at === -1) return text.slice(0, width)
|
|
137
|
+
const start = Math.max(0, at - Math.floor(width / 2))
|
|
138
|
+
const slice = text.slice(start, start + width)
|
|
139
|
+
return `${start > 0 ? '...' : ''}${slice}${start + width < text.length ? '...' : ''}`
|
|
140
|
+
}
|
|
141
|
+
|
|
130
142
|
const COMMON_RULES = [
|
|
131
143
|
{
|
|
132
144
|
name: 'empty-description',
|
|
@@ -203,6 +215,49 @@ const COMMON_RULES = [
|
|
|
203
215
|
return issues
|
|
204
216
|
}
|
|
205
217
|
},
|
|
218
|
+
{
|
|
219
|
+
name: 'em-dash',
|
|
220
|
+
description: 'Em dash in prose that ships to docs.redpanda.com',
|
|
221
|
+
severity: 'warning',
|
|
222
|
+
check: (decl) => {
|
|
223
|
+
const text = decl.string || ''
|
|
224
|
+
if (!text.includes('\u2014')) return []
|
|
225
|
+
// The docs style guide takes commas, parentheses or a sentence break
|
|
226
|
+
// instead of em dashes. Reference docs generated from these strings pass
|
|
227
|
+
// prose through unchanged, so an em dash here is published verbatim, and
|
|
228
|
+
// correcting it in the generated partial is undone by the next
|
|
229
|
+
// regeneration.
|
|
230
|
+
const count = (text.match(/\u2014/g) || []).length
|
|
231
|
+
return [{
|
|
232
|
+
message: `Contains ${count} em dash${count === 1 ? '' : 'es'}. Use a comma, parentheses, or a separate sentence: "${excerpt(text, '\u2014')}"`
|
|
233
|
+
}]
|
|
234
|
+
}
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
name: 'latin-abbreviation',
|
|
238
|
+
description: 'Latin abbreviation instead of plain English',
|
|
239
|
+
severity: 'warning',
|
|
240
|
+
check: (decl) => {
|
|
241
|
+
const text = decl.string || ''
|
|
242
|
+
if (!text) return []
|
|
243
|
+
const issues = []
|
|
244
|
+
// "e.g." and "i.e." are both ruled out by the style guide's terminology
|
|
245
|
+
// list. Match the abbreviation only, so "e.g" inside a longer token such
|
|
246
|
+
// as a URL or an identifier is left alone.
|
|
247
|
+
for (const [abbr, replacement] of [['e.g.', '"for example"'], ['i.e.', '"that is"']]) {
|
|
248
|
+
// Boundaries on both sides: the abbreviation must not be part of a
|
|
249
|
+
// longer token, so a host or identifier that happens to contain it
|
|
250
|
+
// (https://e.g.example/path) is left alone.
|
|
251
|
+
const pattern = new RegExp(`(^|[^\\w.])${abbr.replace(/\./g, '\\.')}(?=$|[^\\w])`, 'i')
|
|
252
|
+
if (pattern.test(text)) {
|
|
253
|
+
issues.push({
|
|
254
|
+
message: `Uses "${abbr}". Write ${replacement} instead: "${excerpt(text, abbr)}"`
|
|
255
|
+
})
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
return issues
|
|
259
|
+
}
|
|
260
|
+
},
|
|
206
261
|
{
|
|
207
262
|
name: 'unbalanced-backticks',
|
|
208
263
|
description: 'Odd number of backticks ships broken markup',
|
|
@@ -196,6 +196,33 @@ function mergeOverrides(target, overrides) {
|
|
|
196
196
|
return target;
|
|
197
197
|
}
|
|
198
198
|
|
|
199
|
+
/**
|
|
200
|
+
* House-style regressions that a regeneration would reintroduce.
|
|
201
|
+
*
|
|
202
|
+
* Generated prose passes through unchanged by design (see
|
|
203
|
+
* helpers/renderConnectDescription.js), so when a writer corrects an em dash or
|
|
204
|
+
* an "e.g." in a published partial instead of upstream, the next regeneration
|
|
205
|
+
* silently puts it back. Comparing the published text with the incoming text
|
|
206
|
+
* catches exactly that case: a style issue the published file does not have and
|
|
207
|
+
* the new one does. Same rules the doc-strings lint applies at the source, so
|
|
208
|
+
* the two agree on what counts as a regression.
|
|
209
|
+
*
|
|
210
|
+
* @param {string} published current on-disk partial
|
|
211
|
+
* @param {string} incoming regenerated partial
|
|
212
|
+
* @returns {string[]} rule names whose hit count increases
|
|
213
|
+
*/
|
|
214
|
+
function styleRegressions (published, incoming) {
|
|
215
|
+
return STYLE_REGRESSION_RULES
|
|
216
|
+
.filter(({ count }) => count(incoming) > count(published))
|
|
217
|
+
.map(({ label }) => label);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const STYLE_REGRESSION_RULES = [
|
|
221
|
+
{ label: 'em dash', count: (text) => (text.match(/\u2014/g) || []).length },
|
|
222
|
+
{ label: '"e.g."', count: (text) => (text.match(/(^|[^\w.])e\.g\./gi) || []).length },
|
|
223
|
+
{ label: '"i.e."', count: (text) => (text.match(/(^|[^\w.])i\.e\./gi) || []).length }
|
|
224
|
+
];
|
|
225
|
+
|
|
199
226
|
/**
|
|
200
227
|
* Resolves $ref references in an object by replacing them with their definitions.
|
|
201
228
|
* Supports JSON Pointer style references like "#/definitions/client_certs".
|
|
@@ -448,6 +475,7 @@ async function generateRpcnConnectorDocs(options) {
|
|
|
448
475
|
let partialsWritten = 0;
|
|
449
476
|
const descriptionReports = [];
|
|
450
477
|
const lostSectionWarnings = [];
|
|
478
|
+
const styleRegressionWarnings = [];
|
|
451
479
|
// typeDir/name keys handled this run; used after the loop to blank
|
|
452
480
|
// description partials orphaned by a connector deleted upstream.
|
|
453
481
|
const visitedDescriptionPartials = new Set();
|
|
@@ -487,6 +515,12 @@ async function generateRpcnConnectorDocs(options) {
|
|
|
487
515
|
if (fieldsOut.trim()) {
|
|
488
516
|
const fPath = path.join(fieldsOutRoot, type, `${name}.adoc`);
|
|
489
517
|
fs.mkdirSync(path.dirname(fPath), { recursive: true });
|
|
518
|
+
if (fs.existsSync(fPath)) {
|
|
519
|
+
const regressions = styleRegressions(fs.readFileSync(fPath, 'utf8'), fieldsOut);
|
|
520
|
+
if (regressions.length) {
|
|
521
|
+
styleRegressionWarnings.push({ partial: path.relative(process.cwd(), fPath), issues: regressions });
|
|
522
|
+
}
|
|
523
|
+
}
|
|
490
524
|
fs.writeFileSync(fPath, fieldsOut);
|
|
491
525
|
if (!writeFullDrafts) {
|
|
492
526
|
partialsWritten++;
|
|
@@ -587,7 +621,12 @@ async function generateRpcnConnectorDocs(options) {
|
|
|
587
621
|
// because it left the upstream description or because a human
|
|
588
622
|
// added it here -- so warn before overwriting instead of
|
|
589
623
|
// dropping it without a trace.
|
|
590
|
-
const
|
|
624
|
+
const publishedDescription = fs.readFileSync(dPath, 'utf8');
|
|
625
|
+
const regressions = styleRegressions(publishedDescription, descriptionOut);
|
|
626
|
+
if (regressions.length) {
|
|
627
|
+
styleRegressionWarnings.push({ partial: path.relative(process.cwd(), dPath), issues: regressions });
|
|
628
|
+
}
|
|
629
|
+
const lost = lostMetadataSections(publishedDescription, descriptionOut);
|
|
591
630
|
if (lost.length) {
|
|
592
631
|
lostSectionWarnings.push({ partial: path.relative(process.cwd(), dPath), sections: lost });
|
|
593
632
|
console.warn(
|
|
@@ -938,6 +977,7 @@ async function generateRpcnConnectorDocs(options) {
|
|
|
938
977
|
draftFiles,
|
|
939
978
|
descriptionReports,
|
|
940
979
|
lostSectionWarnings,
|
|
980
|
+
styleRegressionWarnings,
|
|
941
981
|
orphanSweepSkipped,
|
|
942
982
|
descriptionBackfill
|
|
943
983
|
};
|
|
@@ -164,6 +164,29 @@ function renderLostSectionWarnings(warnings) {
|
|
|
164
164
|
return lines;
|
|
165
165
|
}
|
|
166
166
|
|
|
167
|
+
/**
|
|
168
|
+
* Render the house-style regression block.
|
|
169
|
+
*
|
|
170
|
+
* A regeneration reintroducing an em dash or an "e.g." that the published
|
|
171
|
+
* partial does not have means the correction was made in the generated file
|
|
172
|
+
* instead of upstream, so every release undoes it again. Surfacing it in the
|
|
173
|
+
* summary is what tells the reviewer the diff contains reverted copy, which
|
|
174
|
+
* the schema delta above never shows.
|
|
175
|
+
* @param {Array<{partial: string, issues: string[]}>} warnings
|
|
176
|
+
* @returns {string[]} lines (empty when there is nothing to warn about)
|
|
177
|
+
*/
|
|
178
|
+
function renderStyleRegressionWarnings(warnings) {
|
|
179
|
+
if (!Array.isArray(warnings) || warnings.length === 0) return [];
|
|
180
|
+
const lines = [];
|
|
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 Connect source, or in `docs-data/overrides.json`: correcting the generated partial is undone by the next regeneration.');
|
|
183
|
+
for (const w of warnings) {
|
|
184
|
+
lines.push(`> - \`${w.partial}\` adds: ${w.issues.join(', ')}`);
|
|
185
|
+
}
|
|
186
|
+
lines.push('');
|
|
187
|
+
return lines;
|
|
188
|
+
}
|
|
189
|
+
|
|
167
190
|
function generateMultiVersionPRSummary(masterDiff, binaryAnalysis = null, draftedConnectors = null) {
|
|
168
191
|
const lines = [];
|
|
169
192
|
|
|
@@ -176,6 +199,7 @@ function generateMultiVersionPRSummary(masterDiff, binaryAnalysis = null, drafte
|
|
|
176
199
|
lines.push('<!-- PR_SUMMARY_START -->');
|
|
177
200
|
lines.push('');
|
|
178
201
|
lines.push(...renderLostSectionWarnings(masterDiff?.lostSectionWarnings));
|
|
202
|
+
lines.push(...renderStyleRegressionWarnings(masterDiff?.styleRegressionWarnings));
|
|
179
203
|
lines.push(...renderDescriptionReports(masterDiff?.descriptionReports));
|
|
180
204
|
lines.push('## Redpanda Connect Documentation Update');
|
|
181
205
|
lines.push('');
|
|
@@ -583,6 +607,7 @@ function generatePRSummary(diffData, binaryAnalysis = null, draftedConnectors =
|
|
|
583
607
|
lines.push('<!-- PR_SUMMARY_START -->');
|
|
584
608
|
lines.push('');
|
|
585
609
|
lines.push(...renderLostSectionWarnings(diffData.lostSectionWarnings));
|
|
610
|
+
lines.push(...renderStyleRegressionWarnings(diffData.styleRegressionWarnings));
|
|
586
611
|
lines.push(...renderDescriptionReports(diffData.descriptionReports));
|
|
587
612
|
|
|
588
613
|
// Detect if this is a master diff
|
|
@@ -1364,13 +1389,17 @@ function printPRSummary(diffData, binaryAnalysis = null, draftedConnectors = nul
|
|
|
1364
1389
|
* something each call site has to remember.
|
|
1365
1390
|
*
|
|
1366
1391
|
* @param {object} result generator result
|
|
1367
|
-
* @param {{descriptionReports: Array, lostSectionWarnings: Array}} into accumulators, mutated in place
|
|
1368
|
-
* @returns {{descriptionReports: Array, lostSectionWarnings: Array}} the same accumulators
|
|
1392
|
+
* @param {{descriptionReports: Array, lostSectionWarnings: Array, styleRegressionWarnings?: Array}} into accumulators, mutated in place
|
|
1393
|
+
* @returns {{descriptionReports: Array, lostSectionWarnings: Array, styleRegressionWarnings: Array}} the same accumulators
|
|
1369
1394
|
*/
|
|
1370
1395
|
function collectGeneratorReports (result, into) {
|
|
1371
1396
|
if (!result) return into;
|
|
1372
1397
|
into.descriptionReports.push(...(result.descriptionReports || []));
|
|
1373
1398
|
into.lostSectionWarnings.push(...(result.lostSectionWarnings || []));
|
|
1399
|
+
// Tolerate an accumulator created before this key existed, so an older call
|
|
1400
|
+
// site collects what it knows about instead of throwing.
|
|
1401
|
+
if (!into.styleRegressionWarnings) into.styleRegressionWarnings = [];
|
|
1402
|
+
into.styleRegressionWarnings.push(...(result.styleRegressionWarnings || []));
|
|
1374
1403
|
return into;
|
|
1375
1404
|
}
|
|
1376
1405
|
|
|
@@ -1381,5 +1410,6 @@ module.exports = {
|
|
|
1381
1410
|
printPRSummary,
|
|
1382
1411
|
renderDescriptionReports,
|
|
1383
1412
|
renderLostSectionWarnings,
|
|
1413
|
+
renderStyleRegressionWarnings,
|
|
1384
1414
|
truncateToSentence
|
|
1385
1415
|
};
|
|
@@ -500,7 +500,18 @@ function updateWhatsNew ({ dataDir, oldVersion, newVersion, binaryAnalysis }) {
|
|
|
500
500
|
const firstMatch = versionHeading.exec(contentWithoutOldSection)
|
|
501
501
|
const insertIdx = firstMatch ? firstMatch.index : contentWithoutOldSection.length
|
|
502
502
|
|
|
503
|
-
|
|
503
|
+
// Entries in this file are separated by two blank lines. Normalize both
|
|
504
|
+
// boundaries instead of appending a newline to whatever is already there:
|
|
505
|
+
// the insertion point already carries the separator that preceded the
|
|
506
|
+
// previous first entry, so adding to it gave every new entry one more blank
|
|
507
|
+
// line than the entries below it, which then had to be corrected by hand
|
|
508
|
+
// after each release. Mirrors the rpk what's-new fix in #285.
|
|
509
|
+
const before = contentWithoutOldSection.slice(0, insertIdx).replace(/\n*$/, '')
|
|
510
|
+
const after = contentWithoutOldSection.slice(insertIdx).replace(/^\n*/, '')
|
|
511
|
+
const entry = section.replace(/^\n*/, '').replace(/\n*$/, '')
|
|
512
|
+
const updated = after
|
|
513
|
+
? `${before}\n\n\n${entry}\n\n\n${after}`
|
|
514
|
+
: `${before}\n\n\n${entry}\n`
|
|
504
515
|
|
|
505
516
|
if (startIdx !== -1) {
|
|
506
517
|
console.log(`♻️ whats-new.adoc: replaced section for Version ${diff.comparison.newVersion}`)
|
|
@@ -1236,13 +1247,14 @@ async function handleRpcnConnectorDocs (options) {
|
|
|
1236
1247
|
let partialsWritten, partialFiles
|
|
1237
1248
|
const descriptionReports = []
|
|
1238
1249
|
const lostSectionWarnings = []
|
|
1250
|
+
const styleRegressionWarnings = []
|
|
1239
1251
|
// Both generator call sites (partials and drafts) feed the same PR summary,
|
|
1240
1252
|
// so they collect through one function. Pushed inline, the draft call site
|
|
1241
1253
|
// silently dropped descriptionReports for months: the structure reports for
|
|
1242
1254
|
// newly drafted connectors, the ones most likely to need an upstream fix,
|
|
1243
1255
|
// never reached the summary.
|
|
1244
1256
|
const { collectGeneratorReports } = require('./pr-summary-formatter.js')
|
|
1245
|
-
const collect = (result) => collectGeneratorReports(result, { descriptionReports, lostSectionWarnings })
|
|
1257
|
+
const collect = (result) => collectGeneratorReports(result, { descriptionReports, lostSectionWarnings, styleRegressionWarnings })
|
|
1246
1258
|
|
|
1247
1259
|
try {
|
|
1248
1260
|
const result = await generateRpcnConnectorDocs({
|
|
@@ -2109,22 +2121,24 @@ async function handleRpcnConnectorDocs (options) {
|
|
|
2109
2121
|
|
|
2110
2122
|
// Generate PR summary
|
|
2111
2123
|
try {
|
|
2112
|
-
const { printPRSummary, renderLostSectionWarnings, renderDescriptionReports } = require('./pr-summary-formatter.js')
|
|
2124
|
+
const { printPRSummary, renderLostSectionWarnings, renderStyleRegressionWarnings, renderDescriptionReports } = require('./pr-summary-formatter.js')
|
|
2113
2125
|
// Use master diff if available, otherwise use single diff. Content-loss
|
|
2114
2126
|
// warnings and structure reports ride the diff object so they land in the
|
|
2115
2127
|
// PR summary body instead of dying in the collapsed workflow log.
|
|
2116
2128
|
const summaryDiff = masterDiff || diffJson
|
|
2117
2129
|
if (summaryDiff) {
|
|
2118
2130
|
if (lostSectionWarnings.length) summaryDiff.lostSectionWarnings = lostSectionWarnings
|
|
2131
|
+
if (styleRegressionWarnings.length) summaryDiff.styleRegressionWarnings = styleRegressionWarnings
|
|
2119
2132
|
if (descriptionReports.length) summaryDiff.descriptionReports = descriptionReports
|
|
2120
2133
|
printPRSummary(summaryDiff, binaryAnalysis, draftFiles, masterDiff ? true : false)
|
|
2121
|
-
} else if (lostSectionWarnings.length || descriptionReports.length) {
|
|
2134
|
+
} else if (lostSectionWarnings.length || styleRegressionWarnings.length || descriptionReports.length) {
|
|
2122
2135
|
// No diff to summarize (no prior version, or versions match), but this
|
|
2123
2136
|
// run still produced content-loss warnings or description-structure
|
|
2124
2137
|
// reports that a reviewer needs to see -- print them on their own
|
|
2125
2138
|
// instead of losing them because there was nothing to diff against.
|
|
2126
2139
|
const lines = [
|
|
2127
2140
|
...renderLostSectionWarnings(lostSectionWarnings),
|
|
2141
|
+
...renderStyleRegressionWarnings(styleRegressionWarnings),
|
|
2128
2142
|
...renderDescriptionReports(descriptionReports)
|
|
2129
2143
|
]
|
|
2130
2144
|
console.log('\n' + lines.join('\n') + '\n')
|
|
@@ -1718,7 +1718,15 @@ function formatDescription(desc, customTransformations = null, options = {}) {
|
|
|
1718
1718
|
|
|
1719
1719
|
// Merge backticked environment variables with adjacent paths
|
|
1720
1720
|
// e.g., `$HOME`/.local/bin → `$HOME/.local/bin`
|
|
1721
|
-
|
|
1721
|
+
// The continuation excludes a trailing ) . , ; : ! ? so sentence punctuation
|
|
1722
|
+
// immediately after the path (no space) lands outside the code span instead
|
|
1723
|
+
// of being swallowed into it, e.g. "...(defaults to `$HOME/.local/bin)`" or
|
|
1724
|
+
// "...saved in `$HOME/.local/bin.` This" -- both published on
|
|
1725
|
+
// rpk-plugin-install.adoc before this fix. The continuation past the slash
|
|
1726
|
+
// is optional, so a bare trailing slash ($HOME/, $PWD/ -- both real in the
|
|
1727
|
+
// rpk source) still merges into `$HOME/` instead of staying split as
|
|
1728
|
+
// `$HOME`/.
|
|
1729
|
+
result = result.replace(/`(\$[A-Z_][A-Z0-9_]*)`(\/(?:[^\s`]*[^\s`).,;:!?])?)/g, '`$1$2`')
|
|
1722
1730
|
|
|
1723
1731
|
// Add backticks around file paths (but not if already backticked)
|
|
1724
1732
|
// Must check both the slash and the path aren't already inside backticks
|