@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
- const findings = detectGatingChanges(result.stdout, { enterpriseNames })
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@redpanda-data/docs-extensions-and-macros",
3
- "version": "5.56.0",
3
+ "version": "5.56.1",
4
4
  "description": "Antora extensions and macros developed for Redpanda documentation.",
5
5
  "keywords": [
6
6
  "antora",
@@ -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. Both signals the header
30
- * carries for an enum value are used (the member line inside the enum, and
31
- * the `case license_required_feature::<name>:` line in format_to), so a
32
- * hunk header that happens not to name the enum still detects.
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
- if (/^(\+\+\+|---|index |new file|deleted file|similarity|rename|old mode|new mode)/.test(raw)) continue
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
- * @returns {{added: string[], removed: string[]}}
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 collect = (side, target) => {
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: [...removed].filter((n) => !added.has(n)).sort()
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,