@redpanda-data/docs-extensions-and-macros 5.13.5 → 5.17.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 +88 -0
- package/bin/doc-tools.js +349 -102
- package/bin/mcp-tools/index.js +20 -0
- package/bin/mcp-tools/lint-doc-strings.js +75 -0
- package/bin/mcp-tools/overrides-audit.js +100 -0
- package/bin/mcp-tools/preview-doc-string.js +71 -0
- package/cli-utils/diff-utils.js +77 -0
- package/cli-utils/helm-commented-values.js +172 -23
- package/cli-utils/sync-schemas.js +144 -0
- package/docs-data/rpk-overrides.schema.json +4 -0
- package/package.json +2 -1
- package/tools/generate-cli-docs.js +28 -67
- package/tools/lint-strings/diff.js +137 -0
- package/tools/lint-strings/engine.js +163 -0
- package/tools/lint-strings/fixtures/connect/lint_connect.go +67 -0
- package/tools/lint-strings/fixtures/crd/crd-ref-docs-config.yaml +15 -0
- package/tools/lint-strings/fixtures/crd/lint_types.go +57 -0
- package/tools/lint-strings/fixtures/helm/lint_values.yaml +47 -0
- package/tools/lint-strings/fixtures/metrics/lint_probe.cc +41 -0
- package/tools/lint-strings/fixtures/properties/lint_fixture.cc +51 -0
- package/tools/lint-strings/fixtures/properties/lint_fixture.h +33 -0
- package/tools/lint-strings/fixtures/rpk/lint_cmd.go +69 -0
- package/tools/lint-strings/go-source.js +327 -0
- package/tools/lint-strings/index.js +229 -0
- package/tools/lint-strings/rules/common.js +148 -0
- package/tools/lint-strings/rules/verbatim-asciidoc.js +108 -0
- package/tools/lint-strings/source-text.js +41 -0
- package/tools/lint-strings/surfaces/connect.js +233 -0
- package/tools/lint-strings/surfaces/crd.js +239 -0
- package/tools/lint-strings/surfaces/helm.js +159 -0
- package/tools/lint-strings/surfaces/metrics.js +368 -0
- package/tools/lint-strings/surfaces/properties.js +216 -0
- package/tools/lint-strings/surfaces/rpk.js +377 -0
- package/tools/overrides-audit/README.adoc +152 -0
- package/tools/overrides-audit/adapters/connect.js +144 -0
- package/tools/overrides-audit/adapters/properties.js +117 -0
- package/tools/overrides-audit/adapters/rpk.js +192 -0
- package/tools/overrides-audit/classify.js +546 -0
- package/tools/overrides-audit/index.js +185 -0
- package/tools/preview-string/index.js +245 -0
- package/tools/property-extractor/Makefile +9 -4
- package/tools/property-extractor/parser.py +26 -0
- package/tools/property-extractor/property_extractor.py +41 -0
- package/tools/property-extractor/transformers.py +15 -2
- package/tools/rpk-docs/detect-platform-commands.js +8 -1
- package/tools/rpk-docs/scan-deprecated-commands.js +15 -18
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Overrides Audit - Properties Adapter
|
|
3
|
+
*
|
|
4
|
+
* Reads docs-data/property-overrides.json and an extracted-properties JSON
|
|
5
|
+
* (the property extractor's raw --output, WITHOUT overrides applied) and
|
|
6
|
+
* classifies every override field. This is the complete reference adapter;
|
|
7
|
+
* rpk and connect adapters follow the same shape.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
'use strict'
|
|
11
|
+
|
|
12
|
+
const fs = require('fs')
|
|
13
|
+
const classify = require('../classify')
|
|
14
|
+
const { compareProperties } = require('../../property-extractor/compare-properties')
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Load and parse a JSON file with a helpful error message.
|
|
18
|
+
*
|
|
19
|
+
* @param {string} filePath - Path to the JSON file.
|
|
20
|
+
* @param {string} label - Human label for error messages.
|
|
21
|
+
* @returns {Object} Parsed JSON.
|
|
22
|
+
*/
|
|
23
|
+
function loadJson (filePath, label) {
|
|
24
|
+
let raw
|
|
25
|
+
try {
|
|
26
|
+
raw = fs.readFileSync(filePath, 'utf8')
|
|
27
|
+
} catch (err) {
|
|
28
|
+
throw new Error(`Cannot read ${label} file at ${filePath}: ${err.message}`)
|
|
29
|
+
}
|
|
30
|
+
try {
|
|
31
|
+
return JSON.parse(raw)
|
|
32
|
+
} catch (err) {
|
|
33
|
+
throw new Error(`Invalid JSON in ${label} file ${filePath}: ${err.message}`)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Cross-check the audit with compare-properties.js: overlay the override
|
|
39
|
+
* descriptions onto a copy of the extracted properties and diff the two.
|
|
40
|
+
* Every description the raw diff reports as unchanged must have classified
|
|
41
|
+
* REDUNDANT (the audit's normalization only widens equality, never narrows
|
|
42
|
+
* it), so a violation indicates a classifier bug.
|
|
43
|
+
*
|
|
44
|
+
* @param {Object} extractedDoc - Extracted properties document.
|
|
45
|
+
* @param {Object} overrides - Override entries map.
|
|
46
|
+
* @param {Object[]} manifest - Classified manifest rows.
|
|
47
|
+
* @returns {Object} { changedDescriptions, emptyDescriptions, violations }.
|
|
48
|
+
*/
|
|
49
|
+
function crossCheckWithCompare (extractedDoc, overrides, manifest) {
|
|
50
|
+
const extractedProps = (extractedDoc && extractedDoc.properties) || {}
|
|
51
|
+
const overlaidProps = {}
|
|
52
|
+
for (const [name, prop] of Object.entries(extractedProps)) {
|
|
53
|
+
const override = overrides[name]
|
|
54
|
+
overlaidProps[name] = override && typeof override.description === 'string'
|
|
55
|
+
? { ...prop, description: override.description }
|
|
56
|
+
: prop
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const report = compareProperties(
|
|
60
|
+
{ properties: extractedProps },
|
|
61
|
+
{ properties: overlaidProps },
|
|
62
|
+
'source',
|
|
63
|
+
'source+overrides'
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
// Normalization-only differences legitimately appear "changed" in the raw
|
|
67
|
+
// diff while classifying REDUNDANT. The inverse can never happen: a
|
|
68
|
+
// raw-equal description that did not classify REDUNDANT is a classifier bug.
|
|
69
|
+
const changedNames = new Set(report.changedDescriptions.map((entry) => entry.name))
|
|
70
|
+
const violations = manifest.filter(
|
|
71
|
+
(row) =>
|
|
72
|
+
row.field === 'description' &&
|
|
73
|
+
row.class !== classify.CLASSES.REDUNDANT &&
|
|
74
|
+
overrides[row.name] &&
|
|
75
|
+
typeof overrides[row.name].description === 'string' &&
|
|
76
|
+
extractedProps[row.name] &&
|
|
77
|
+
!changedNames.has(row.name)
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
changedDescriptions: report.changedDescriptions.length,
|
|
82
|
+
emptyDescriptions: report.emptyDescriptions.length,
|
|
83
|
+
violations: violations.map((row) => row.name)
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Run the audit for the properties surface.
|
|
89
|
+
*
|
|
90
|
+
* @param {Object} args - { overridesPath, extractedPath, attrAllowlist }.
|
|
91
|
+
* @returns {Object} { surface, manifest, summary, cross_check }.
|
|
92
|
+
*/
|
|
93
|
+
function audit ({ overridesPath, extractedPath, attrAllowlist }) {
|
|
94
|
+
const overridesDoc = loadJson(overridesPath, 'overrides')
|
|
95
|
+
const extractedDoc = loadJson(extractedPath, 'extracted properties')
|
|
96
|
+
|
|
97
|
+
if (!overridesDoc.properties || typeof overridesDoc.properties !== 'object') {
|
|
98
|
+
throw new Error(`Overrides file ${overridesPath} has no top-level "properties" object`)
|
|
99
|
+
}
|
|
100
|
+
if (!extractedDoc.properties || typeof extractedDoc.properties !== 'object') {
|
|
101
|
+
throw new Error(`Extracted file ${extractedPath} has no top-level "properties" object; pass the property extractor's JSON output`)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const { manifest, summary } = classify.classifyProperties(overridesDoc, extractedDoc, { attrAllowlist })
|
|
105
|
+
const crossCheck = crossCheckWithCompare(extractedDoc, overridesDoc.properties, manifest)
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
surface: 'properties',
|
|
109
|
+
overrides_file: overridesPath,
|
|
110
|
+
extracted_file: extractedPath,
|
|
111
|
+
manifest,
|
|
112
|
+
summary,
|
|
113
|
+
cross_check: crossCheck
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
module.exports = { audit, loadJson }
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Overrides Audit - rpk Adapter (structural)
|
|
3
|
+
*
|
|
4
|
+
* Reads docs-data/rpk-overrides.json (schema: docs-data/rpk-overrides.schema.json)
|
|
5
|
+
* and enumerates every override unit per command:
|
|
6
|
+
* - command `description` and `flags.<flag>.description` are the
|
|
7
|
+
* upstreamable prose fields (rpk cobra Short/Long and flag usage strings)
|
|
8
|
+
* - everything else (seeAlso, pageAliases, introducedInVersion, platforms,
|
|
9
|
+
* selfHostedOnly, pageAttributes, $refs, _note, content, prerequisites,
|
|
10
|
+
* descriptionScope, appendToDescription, exclude, asPartial) is docs-site
|
|
11
|
+
* structure that stays in the override layer by design.
|
|
12
|
+
*
|
|
13
|
+
* TODO(quality logic): source comparison for rpk descriptions is deferred.
|
|
14
|
+
* Two blockers, both known:
|
|
15
|
+
* 1. The versioned rpk-v<ver>.json files in docs repos are POST-override
|
|
16
|
+
* snapshots (rpk-docs generation applies rpk-overrides.json before
|
|
17
|
+
* writing them), so auditing against them classifies every override
|
|
18
|
+
* REDUNDANT. The audit needs a raw `rpk --print-tree` snapshot instead.
|
|
19
|
+
* 2. Generated docs pass source strings through formatDescription()
|
|
20
|
+
* (tools/rpk-docs/generate-rpk-docs.js) — ~40 regex passes (md->adoc,
|
|
21
|
+
* auto-backticking, ensurePeriod, ...). Equality must be computed on
|
|
22
|
+
* formatDescription(source) vs the override, not on the raw cobra string,
|
|
23
|
+
* or near-identical strings will misclassify as UPSTREAMABLE.
|
|
24
|
+
* Until then, prose fields classify REVIEW with an explicit note, and
|
|
25
|
+
* docs-structure fields classify KEEP, so the manifest shape is already
|
|
26
|
+
* stable for the retirement workflow.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
'use strict'
|
|
30
|
+
|
|
31
|
+
const classify = require('../classify')
|
|
32
|
+
const { loadJson } = require('./properties')
|
|
33
|
+
|
|
34
|
+
// Top-level keys of rpk-overrides.json that are not command entries.
|
|
35
|
+
const NON_COMMAND_KEYS = ['$schema', '_notes', 'textTransformations', 'definitions']
|
|
36
|
+
|
|
37
|
+
// Per-command fields that stay in the override layer by design.
|
|
38
|
+
const KEEP_BY_DESIGN_FIELDS = [
|
|
39
|
+
'seeAlso',
|
|
40
|
+
'pageAliases',
|
|
41
|
+
'introducedInVersion',
|
|
42
|
+
'platforms',
|
|
43
|
+
'selfHostedOnly',
|
|
44
|
+
'pageAttributes',
|
|
45
|
+
'$refs',
|
|
46
|
+
'_note',
|
|
47
|
+
'content',
|
|
48
|
+
'prerequisites',
|
|
49
|
+
'descriptionScope',
|
|
50
|
+
'appendToDescription',
|
|
51
|
+
'exclude',
|
|
52
|
+
'asPartial'
|
|
53
|
+
]
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Find a command node in an extracted `rpk --print-tree` JSON by its full
|
|
57
|
+
* name (for example "rpk topic create").
|
|
58
|
+
*
|
|
59
|
+
* @param {Object} tree - Root tree node ({ name, commands: [...] }).
|
|
60
|
+
* @param {string} fullName - Space-separated command path.
|
|
61
|
+
* @returns {Object|null} The command node, or null when absent.
|
|
62
|
+
*/
|
|
63
|
+
function findCommandNode (tree, fullName) {
|
|
64
|
+
if (!tree || typeof tree !== 'object') return null
|
|
65
|
+
const parts = fullName.trim().split(/\s+/)
|
|
66
|
+
if (parts[0] !== tree.name) return null
|
|
67
|
+
let node = tree
|
|
68
|
+
for (const part of parts.slice(1)) {
|
|
69
|
+
const children = Array.isArray(node.commands) ? node.commands : []
|
|
70
|
+
node = children.find((child) => child && child.name === part) || null
|
|
71
|
+
if (!node) return null
|
|
72
|
+
}
|
|
73
|
+
return node
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Build the manifest row for an rpk prose field (deferred quality logic).
|
|
78
|
+
*
|
|
79
|
+
* @param {string} name - Unit name ("rpk topic create" or "rpk topic create --partitions").
|
|
80
|
+
* @param {string} field - Manifest field label.
|
|
81
|
+
* @param {string} text - Override description text.
|
|
82
|
+
* @param {Object|undefined} upstreamRef - upstream_ref carried from the override.
|
|
83
|
+
* @param {boolean|null} foundInTree - Whether the command exists in the extracted tree (null when no tree given).
|
|
84
|
+
* @returns {Object} Manifest row.
|
|
85
|
+
*/
|
|
86
|
+
function proseRow (name, field, text, upstreamRef, foundInTree) {
|
|
87
|
+
const row = {
|
|
88
|
+
name,
|
|
89
|
+
field,
|
|
90
|
+
class: classify.CLASSES.REVIEW,
|
|
91
|
+
content_hash: classify.contentHash(name, text)
|
|
92
|
+
}
|
|
93
|
+
if (upstreamRef !== undefined) row.upstream_ref = upstreamRef
|
|
94
|
+
if (foundInTree === false) {
|
|
95
|
+
row.note = 'Command not found in the extracted rpk tree; the override may be stale. TODO: rpk source comparison not implemented yet.'
|
|
96
|
+
} else {
|
|
97
|
+
row.note = 'TODO: rpk source comparison not implemented yet (needs a raw pre-override --print-tree snapshot and the formatDescription() pipeline).'
|
|
98
|
+
}
|
|
99
|
+
return row
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Run the audit for the rpk surface (structural pass).
|
|
104
|
+
*
|
|
105
|
+
* @param {Object} args - { overridesPath, extractedPath }.
|
|
106
|
+
* @returns {Object} { surface, manifest, summary }.
|
|
107
|
+
*/
|
|
108
|
+
function audit ({ overridesPath, extractedPath }) {
|
|
109
|
+
const overridesDoc = loadJson(overridesPath, 'rpk overrides')
|
|
110
|
+
const commands = overridesDoc.commands
|
|
111
|
+
if (!commands || typeof commands !== 'object') {
|
|
112
|
+
throw new Error(`rpk overrides file ${overridesPath} has no top-level "commands" object`)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
let tree = null
|
|
116
|
+
if (extractedPath) {
|
|
117
|
+
const extractedDoc = loadJson(extractedPath, 'extracted rpk tree')
|
|
118
|
+
tree = extractedDoc.raw_tree || extractedDoc.tree || null
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const manifest = []
|
|
122
|
+
for (const [commandName, entry] of Object.entries(commands)) {
|
|
123
|
+
if (typeof entry !== 'object' || entry === null) continue
|
|
124
|
+
|
|
125
|
+
const node = tree ? findCommandNode(tree, commandName) : null
|
|
126
|
+
const foundInTree = tree ? node !== null : null
|
|
127
|
+
|
|
128
|
+
for (const [field, value] of Object.entries(entry)) {
|
|
129
|
+
if (field === 'description') {
|
|
130
|
+
manifest.push(proseRow(commandName, 'description', value, entry.upstream_ref, foundInTree))
|
|
131
|
+
} else if (field === 'flags' && value && typeof value === 'object') {
|
|
132
|
+
for (const [flagName, flagEntry] of Object.entries(value)) {
|
|
133
|
+
if (flagEntry && typeof flagEntry.description === 'string') {
|
|
134
|
+
manifest.push(proseRow(
|
|
135
|
+
`${commandName} --${flagName}`,
|
|
136
|
+
'flags.description',
|
|
137
|
+
flagEntry.description,
|
|
138
|
+
flagEntry.upstream_ref,
|
|
139
|
+
foundInTree
|
|
140
|
+
))
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
} else if (field === 'upstream_ref' || field === '_comment') {
|
|
144
|
+
// Meta fields; upstream_ref is carried onto the prose rows above.
|
|
145
|
+
} else if (KEEP_BY_DESIGN_FIELDS.includes(field)) {
|
|
146
|
+
manifest.push({
|
|
147
|
+
name: commandName,
|
|
148
|
+
field,
|
|
149
|
+
class: classify.CLASSES.KEEP,
|
|
150
|
+
note: 'Docs-site structure; stays in the rpk override file by design.'
|
|
151
|
+
})
|
|
152
|
+
} else {
|
|
153
|
+
manifest.push({
|
|
154
|
+
name: commandName,
|
|
155
|
+
field,
|
|
156
|
+
class: classify.CLASSES.KEEP,
|
|
157
|
+
note: `Unrecognized rpk override field '${field}'; kept, verify it against docs-data/rpk-overrides.schema.json.`
|
|
158
|
+
})
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Shared flag definitions referenced via $refs also carry descriptions.
|
|
164
|
+
// Shape: definitions.<def-name>.<flag-name>.description
|
|
165
|
+
const definitions = overridesDoc.definitions
|
|
166
|
+
if (definitions && typeof definitions === 'object') {
|
|
167
|
+
for (const [defName, flags] of Object.entries(definitions)) {
|
|
168
|
+
if (!flags || typeof flags !== 'object') continue
|
|
169
|
+
for (const [flagName, flagEntry] of Object.entries(flags)) {
|
|
170
|
+
if (flagEntry && typeof flagEntry.description === 'string') {
|
|
171
|
+
manifest.push(proseRow(
|
|
172
|
+
`definitions/${defName} --${flagName}`,
|
|
173
|
+
'flags.description',
|
|
174
|
+
flagEntry.description,
|
|
175
|
+
flagEntry.upstream_ref,
|
|
176
|
+
null
|
|
177
|
+
))
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return {
|
|
184
|
+
surface: 'rpk',
|
|
185
|
+
overrides_file: overridesPath,
|
|
186
|
+
extracted_file: extractedPath || null,
|
|
187
|
+
manifest,
|
|
188
|
+
summary: classify.summarize(manifest)
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
module.exports = { audit, findCommandNode, NON_COMMAND_KEYS }
|