@redpanda-data/docs-extensions-and-macros 5.56.0 → 5.56.1
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.js
CHANGED
|
@@ -3055,9 +3055,10 @@ programCli
|
|
|
3055
3055
|
.requiredOption('--repo <path>', 'Path to a Redpanda core checkout. Nothing is cloned.')
|
|
3056
3056
|
.requiredOption('--diff <base>', 'Base ref: changes in <base>...HEAD are inspected')
|
|
3057
3057
|
.option('--format <format>', 'Output format: json or human', 'json')
|
|
3058
|
+
.option('--exclude <file>', 'File of already-dispatched findings, one "name change_kind" per line, to leave out of the report')
|
|
3058
3059
|
.action((options) => {
|
|
3059
3060
|
const { spawnSync } = require('child_process')
|
|
3060
|
-
const { detectGatingChanges, buildDocImpact, enterprisePropertyNames, ENUM_HEADER, CONFIG_HEADER, CONFIG_SOURCE } = require('../tools/enterprise-features/detect-gating-changes')
|
|
3061
|
+
const { detectGatingChanges, buildDocImpact, withoutSent, enterprisePropertyNames, ENUM_HEADER, CONFIG_HEADER, CONFIG_SOURCE } = require('../tools/enterprise-features/detect-gating-changes')
|
|
3061
3062
|
const empty = { findings: [] }
|
|
3062
3063
|
const result = spawnSync('git', ['diff', '--no-color', '--no-ext-diff', `${options.diff}...HEAD`, '--', ENUM_HEADER, CONFIG_HEADER, CONFIG_SOURCE], {
|
|
3063
3064
|
cwd: options.repo, encoding: 'utf8', maxBuffer: 64 * 1024 * 1024
|
|
@@ -3072,7 +3073,14 @@ programCli
|
|
|
3072
3073
|
const header = spawnSync('git', ['show', `HEAD:${CONFIG_HEADER}`], { cwd: options.repo, encoding: 'utf8', maxBuffer: 64 * 1024 * 1024 })
|
|
3073
3074
|
if (header.status !== 0) console.error(`git show HEAD:${CONFIG_HEADER} failed in ${options.repo}; configuration.cc changes are not checked: ${(header.stderr || '').trim()}`)
|
|
3074
3075
|
const enterpriseNames = header.status === 0 ? enterprisePropertyNames(header.stdout) : []
|
|
3075
|
-
|
|
3076
|
+
let findings = detectGatingChanges(result.stdout, { enterpriseNames })
|
|
3077
|
+
if (options.exclude) {
|
|
3078
|
+
try {
|
|
3079
|
+
findings = withoutSent(findings, fs.readFileSync(options.exclude, 'utf8'))
|
|
3080
|
+
} catch (err) {
|
|
3081
|
+
console.error(`Could not read ${options.exclude}; nothing excluded: ${err.message}`)
|
|
3082
|
+
}
|
|
3083
|
+
}
|
|
3076
3084
|
const report = buildDocImpact(findings) || empty
|
|
3077
3085
|
if (options.format === 'human') {
|
|
3078
3086
|
if (findings.length === 0) { console.log('No enterprise gating changes.'); return }
|
package/package.json
CHANGED
|
@@ -26,10 +26,13 @@
|
|
|
26
26
|
* so a test can hand it a saved patch; the CLI wrapper runs git.
|
|
27
27
|
*
|
|
28
28
|
* Detection is by added and removed lines, paired by name, so a value that
|
|
29
|
-
* merely moves within the enum is not reported.
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
29
|
+
* merely moves within the enum is not reported. Only the member line inside
|
|
30
|
+
* the enum counts; `case license_required_feature::<name>:` labels are
|
|
31
|
+
* ignored, because they move whenever the formatter is rewritten.
|
|
32
|
+
*
|
|
33
|
+
* Not covered: node properties (src/v/config/node_config.h, where
|
|
34
|
+
* `fips_mode` is gated) are not scanned, and a restricted value that names a
|
|
35
|
+
* constant defined elsewhere is not seen (see restrictedValueChanges).
|
|
33
36
|
*
|
|
34
37
|
* Each finding names ONE affected page, the licensing overview. The docs-site
|
|
35
38
|
* receiver falls back to matching a finding with no name match against open
|
|
@@ -54,15 +57,20 @@ const REGISTRY_URL = 'https://github.com/redpanda-data/docs/blob/main/shared/mod
|
|
|
54
57
|
function splitFiles (diffText) {
|
|
55
58
|
const files = []
|
|
56
59
|
let current = null
|
|
60
|
+
let inHunk = false
|
|
57
61
|
for (const raw of String(diffText || '').split('\n')) {
|
|
58
62
|
const m = /^diff --git a\/(.+?) b\/(.+)$/.exec(raw)
|
|
59
63
|
if (m) {
|
|
60
64
|
current = { path: m[2], lines: [] }
|
|
61
65
|
files.push(current)
|
|
66
|
+
inHunk = false
|
|
62
67
|
continue
|
|
63
68
|
}
|
|
64
69
|
if (!current) continue
|
|
65
|
-
|
|
70
|
+
// File header lines come before the first hunk. Inside a hunk, a removed
|
|
71
|
+
// `--x;` or an added `++x;` reads as `---x;` or `+++x;` and is content.
|
|
72
|
+
if (raw.startsWith('@@')) inHunk = true
|
|
73
|
+
if (!inHunk && /^(\+\+\+|---|index |new file|deleted file|similarity|rename|old mode|new mode)/.test(raw)) continue
|
|
66
74
|
current.lines.push(raw)
|
|
67
75
|
}
|
|
68
76
|
return files
|
|
@@ -92,7 +100,7 @@ function enumChanges (lines) {
|
|
|
92
100
|
const sign = line[0]
|
|
93
101
|
if (sign !== '+' && sign !== '-') continue
|
|
94
102
|
const target = sign === '+' ? added : removed
|
|
95
|
-
const member = inEnum && /^\s*([a-z][a-z0-9_]*)\s*,?\s*(\/\/.*)?$/.exec(body)
|
|
103
|
+
const member = inEnum && /^\s*([a-z][a-z0-9_]*)\s*(?:=\s*[^,/]+?)?\s*,?\s*(\/\/.*)?$/.exec(body)
|
|
96
104
|
if (member) target.add(member[1])
|
|
97
105
|
}
|
|
98
106
|
return {
|
|
@@ -107,6 +115,8 @@ function enumChanges (lines) {
|
|
|
107
115
|
// at a `;`, and also at a brace, an access specifier or a preprocessor line,
|
|
108
116
|
// so a declaration never absorbs the line before it.
|
|
109
117
|
const ENTERPRISE_DECL = /^\s*(?:config::)?enterprise<[\s\S]*>\s*([a-z][a-z0-9_]*)\s*;\s*$/
|
|
118
|
+
// Any templated member declaration, wrapped or not (`property<bool> x;`).
|
|
119
|
+
const ANY_DECL = /^\s*[\w:]+<[\s\S]*>\s*([a-z][a-z0-9_]*)\s*;\s*$/
|
|
110
120
|
|
|
111
121
|
// Comments and attributes are dropped before joining: a `/* ... */` or
|
|
112
122
|
// `[[attr]]` line above a declaration has no `;` or brace, so it would
|
|
@@ -186,24 +196,29 @@ function enterprisePropertyNames (headerText) {
|
|
|
186
196
|
* configuration.h. A declaration changed in place shows as a -/+ pair with
|
|
187
197
|
* the same name; the wrapper is on exactly one side. Each side of each hunk
|
|
188
198
|
* (context plus its own changed lines) is joined into statements, so a
|
|
189
|
-
* declaration that wraps across lines is read whole.
|
|
190
|
-
*
|
|
199
|
+
* declaration that wraps across lines is read whole. A wrapped declaration
|
|
200
|
+
* that is removed and declared nowhere on the new side, wrapped or not, is
|
|
201
|
+
* `deleted`: the property is gone, not ungated.
|
|
202
|
+
* @returns {{added: string[], removed: string[], deleted: string[]}}
|
|
191
203
|
*/
|
|
192
204
|
function enterprisePropertyChanges (lines) {
|
|
193
205
|
const added = new Set()
|
|
194
206
|
const removed = new Set()
|
|
195
|
-
const
|
|
207
|
+
const declaredNew = new Set()
|
|
208
|
+
const collect = (side, target, declared) => {
|
|
196
209
|
for (const st of joinStatements(side)) {
|
|
197
210
|
if (!st.changed) continue
|
|
198
211
|
const m = ENTERPRISE_DECL.exec(st.text)
|
|
199
212
|
if (m) target.add(m[1])
|
|
213
|
+
const any = declared && ANY_DECL.exec(st.text)
|
|
214
|
+
if (any) declared.add(any[1])
|
|
200
215
|
}
|
|
201
216
|
}
|
|
202
217
|
let oldSide = []
|
|
203
218
|
let newSide = []
|
|
204
219
|
const flushHunk = () => {
|
|
205
220
|
collect(oldSide, removed)
|
|
206
|
-
collect(newSide, added)
|
|
221
|
+
collect(newSide, added, declaredNew)
|
|
207
222
|
oldSide = []
|
|
208
223
|
newSide = []
|
|
209
224
|
}
|
|
@@ -216,9 +231,11 @@ function enterprisePropertyChanges (lines) {
|
|
|
216
231
|
else if (sign === ' ') { oldSide.push({ text }); newSide.push({ text }) }
|
|
217
232
|
}
|
|
218
233
|
flushHunk()
|
|
234
|
+
const gone = [...removed].filter((n) => !added.has(n))
|
|
219
235
|
return {
|
|
220
236
|
added: [...added].filter((n) => !removed.has(n)).sort(),
|
|
221
|
-
removed:
|
|
237
|
+
removed: gone.filter((n) => declaredNew.has(n)).sort(),
|
|
238
|
+
deleted: gone.filter((n) => !declaredNew.has(n)).sort()
|
|
222
239
|
}
|
|
223
240
|
}
|
|
224
241
|
|
|
@@ -253,6 +270,14 @@ function restrictedValueChanges (lines, enterpriseNames) {
|
|
|
253
270
|
const body = line.slice(1)
|
|
254
271
|
const opens = /^\s*,\s*([a-z][a-z0-9_]*)\s*\(/.exec(body)
|
|
255
272
|
if (opens) { init = opens[1]; changed = false; continue }
|
|
273
|
+
// `*this, <value>, "name",` on one line carries the whole restricted
|
|
274
|
+
// argument list.
|
|
275
|
+
const oneLine = /^\s*\*this\s*,.*"([a-z][a-z0-9_]*)"\s*,/.exec(body)
|
|
276
|
+
if (oneLine && enterpriseNames.has(oneLine[1])) {
|
|
277
|
+
if (sign !== ' ') found.add(oneLine[1])
|
|
278
|
+
changed = false
|
|
279
|
+
continue
|
|
280
|
+
}
|
|
256
281
|
if (/^\s*\*this\s*,/.test(body)) { changed = sign !== ' '; continue }
|
|
257
282
|
// The name literal closes the restricted arguments. A string literal
|
|
258
283
|
// that is neither this initializer's name nor an enterprise property
|
|
@@ -307,7 +332,7 @@ function detectGatingChanges (diffText, { enterpriseNames = [] } = {}) {
|
|
|
307
332
|
})
|
|
308
333
|
}
|
|
309
334
|
} else if (file.path === CONFIG_HEADER) {
|
|
310
|
-
const { added, removed } = enterprisePropertyChanges(file.lines)
|
|
335
|
+
const { added, removed, deleted } = enterprisePropertyChanges(file.lines)
|
|
311
336
|
for (const name of added) {
|
|
312
337
|
findings.push({
|
|
313
338
|
surface: 'enterprise-property',
|
|
@@ -326,6 +351,15 @@ function detectGatingChanges (diffText, { enterpriseNames = [] } = {}) {
|
|
|
326
351
|
summary: `\`${name}\` is no longer wrapped in \`config::enterprise<>\` in \`${CONFIG_HEADER}\`, so it no longer needs a license. The registry entry that names it (${REGISTRY_URL}) and its row on ${DISABLE_PAGE} need to change.`
|
|
327
352
|
})
|
|
328
353
|
}
|
|
354
|
+
for (const name of deleted) {
|
|
355
|
+
findings.push({
|
|
356
|
+
surface: 'enterprise-property',
|
|
357
|
+
name,
|
|
358
|
+
change_kind: 'enterprise-property-removed',
|
|
359
|
+
affected_pages: [LICENSING_OVERVIEW],
|
|
360
|
+
summary: `\`${name}\`, a \`config::enterprise<>\` property, was removed from \`${CONFIG_HEADER}\`. From the first release that contains this change the property no longer exists, so its row on ${DISABLE_PAGE} goes and the registry entry that names it (${REGISTRY_URL}) needs a new source pointer or a note, never deletion: a published external name stays in the registry.`
|
|
361
|
+
})
|
|
362
|
+
}
|
|
329
363
|
} else if (file.path === CONFIG_SOURCE) {
|
|
330
364
|
// A property whose wrapper changed in the header is already reported
|
|
331
365
|
// above; its initializer changes with it.
|
|
@@ -344,6 +378,18 @@ function detectGatingChanges (diffText, { enterpriseNames = [] } = {}) {
|
|
|
344
378
|
return findings
|
|
345
379
|
}
|
|
346
380
|
|
|
381
|
+
/**
|
|
382
|
+
* Drop findings already dispatched for this PR. `sent` is the text of the
|
|
383
|
+
* review state's dispatched-gating.txt: one `name change_kind` key per line.
|
|
384
|
+
* Filtering here, before buildDocImpact, keeps the ticket title and body to
|
|
385
|
+
* the findings that remain, and lets an eleventh finding in once the first
|
|
386
|
+
* ten are sent.
|
|
387
|
+
*/
|
|
388
|
+
function withoutSent (findings, sent) {
|
|
389
|
+
const done = new Set(String(sent || '').split('\n').map((l) => l.trim()).filter(Boolean))
|
|
390
|
+
return findings.filter((f) => !done.has(`${f.name} ${f.change_kind}`))
|
|
391
|
+
}
|
|
392
|
+
|
|
347
393
|
/**
|
|
348
394
|
* Shape findings as the doc-impact report the review workflow dispatches
|
|
349
395
|
* (the same JSON the model writes to doc-impact.json), or null when there is
|
|
@@ -385,6 +431,7 @@ module.exports = {
|
|
|
385
431
|
CONFIG_SOURCE,
|
|
386
432
|
detectGatingChanges,
|
|
387
433
|
buildDocImpact,
|
|
434
|
+
withoutSent,
|
|
388
435
|
enumChanges,
|
|
389
436
|
enterprisePropertyChanges,
|
|
390
437
|
enterprisePropertyNames,
|