@redpanda-data/docs-extensions-and-macros 5.13.5 → 5.14.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.js +97 -0
- package/cli-utils/sync-schemas.js +144 -0
- package/docs-data/rpk-overrides.schema.json +4 -0
- package/package.json +1 -1
package/bin/doc-tools.js
CHANGED
|
@@ -529,6 +529,103 @@ programCli
|
|
|
529
529
|
}
|
|
530
530
|
})
|
|
531
531
|
|
|
532
|
+
/**
|
|
533
|
+
* @description Copy this package's docs-data/*.schema.json files into a
|
|
534
|
+
* content repo's own docs-data/ (the file that documents docs-data/x.json
|
|
535
|
+
* lives alongside x.json itself, so a writer can change both in one PR
|
|
536
|
+
* without waiting on a package release first). Run this after bumping
|
|
537
|
+
* @redpanda-data/docs-extensions-and-macros so the local copy picks up
|
|
538
|
+
* whatever changed upstream.
|
|
539
|
+
* @why Nothing keeps a content repo's schema copy in sync with this
|
|
540
|
+
* package's copy today, in either direction. A content repo's copy can
|
|
541
|
+
* legitimately be ahead of this package's (confirmed happening in
|
|
542
|
+
* practice: the docs repo's rpk-overrides.schema.json documents a real,
|
|
543
|
+
* working `asPartial` override field that this package's own copy is
|
|
544
|
+
* missing) — overwriting it in that case would delete real, correct
|
|
545
|
+
* documentation, not fix drift. This only overwrites when this package's
|
|
546
|
+
* copy is a strict superset of the destination; otherwise it reports which
|
|
547
|
+
* keys only the destination has and leaves the file alone (status
|
|
548
|
+
* 'diverged') unless --force is passed. --check never writes, for a CI
|
|
549
|
+
* gate.
|
|
550
|
+
* @example
|
|
551
|
+
* # Sync into ./docs-data (writes any missing or out-of-date schema)
|
|
552
|
+
* npx doc-tools sync-schemas
|
|
553
|
+
*
|
|
554
|
+
* # Report drift without writing -- exits 1 if anything is out of sync
|
|
555
|
+
* npx doc-tools sync-schemas --check
|
|
556
|
+
*
|
|
557
|
+
* # Sync into a non-default location
|
|
558
|
+
* npx doc-tools sync-schemas --dest path/to/docs-data
|
|
559
|
+
*
|
|
560
|
+
* # Overwrite even a destination that has diverged (rarely correct --
|
|
561
|
+
* # read the 'diverged' output first; it names what would be lost)
|
|
562
|
+
* npx doc-tools sync-schemas --force
|
|
563
|
+
* @requirements None.
|
|
564
|
+
*/
|
|
565
|
+
programCli
|
|
566
|
+
.command('sync-schemas')
|
|
567
|
+
.description("Sync this package's docs-data/*.schema.json into a content repo's docs-data/")
|
|
568
|
+
.option('--dest <path>', 'Destination docs-data directory', 'docs-data')
|
|
569
|
+
.option('--check', 'Report drift without writing; exit 1 if any schema is missing, out of date, or diverged')
|
|
570
|
+
.option('--force', 'Overwrite even a destination that has diverged (has content this package lacks)')
|
|
571
|
+
.action((options) => {
|
|
572
|
+
const { syncSchemas } = require('../cli-utils/sync-schemas')
|
|
573
|
+
|
|
574
|
+
try {
|
|
575
|
+
const { destDir, results, drift } = syncSchemas({
|
|
576
|
+
destDir: options.dest,
|
|
577
|
+
check: Boolean(options.check),
|
|
578
|
+
force: Boolean(options.force),
|
|
579
|
+
})
|
|
580
|
+
|
|
581
|
+
if (results.length === 0) {
|
|
582
|
+
console.log('No *.schema.json files found in this package to sync.')
|
|
583
|
+
process.exit(0)
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
console.log(`${options.check ? 'Checking' : 'Syncing'} against: ${destDir}`)
|
|
587
|
+
console.log('')
|
|
588
|
+
let hasUnresolvedDivergence = false
|
|
589
|
+
for (const { name, status, destOnlyPaths } of results) {
|
|
590
|
+
const label = {
|
|
591
|
+
unchanged: '✓ up to date',
|
|
592
|
+
created: '+ created',
|
|
593
|
+
updated: '↻ updated',
|
|
594
|
+
diverged: options.force ? '↻ updated (forced)' : '⚠ diverged, left alone',
|
|
595
|
+
}[status]
|
|
596
|
+
console.log(` ${label} ${name}`)
|
|
597
|
+
if (status === 'diverged' && !options.force) {
|
|
598
|
+
hasUnresolvedDivergence = true
|
|
599
|
+
for (const p of destOnlyPaths) console.log(` only in the destination: ${p}`)
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
console.log('')
|
|
603
|
+
|
|
604
|
+
if (options.check) {
|
|
605
|
+
if (drift) {
|
|
606
|
+
const hint = hasUnresolvedDivergence
|
|
607
|
+
? 'A diverged file needs a human decision (see paths above), not just a re-run.'
|
|
608
|
+
: 'Run `npx doc-tools sync-schemas` to fix.'
|
|
609
|
+
console.log(`✗ One or more schemas are out of date. ${hint}`)
|
|
610
|
+
process.exit(1)
|
|
611
|
+
}
|
|
612
|
+
console.log('✓ All schemas are in sync.')
|
|
613
|
+
process.exit(0)
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
if (hasUnresolvedDivergence) {
|
|
617
|
+
console.log('⚠ Synced what was safe to sync. One or more files diverged and were left alone -- see paths above. Re-run with --force only after confirming the destination-only content should be lost.')
|
|
618
|
+
process.exit(1)
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
console.log(drift ? '✓ Synced.' : '✓ Already in sync -- nothing to do.')
|
|
622
|
+
process.exit(0)
|
|
623
|
+
} catch (err) {
|
|
624
|
+
console.error(`Error: ${err.message}`)
|
|
625
|
+
process.exit(1)
|
|
626
|
+
}
|
|
627
|
+
})
|
|
628
|
+
|
|
532
629
|
/**
|
|
533
630
|
* @description Show MCP server version information including available tools
|
|
534
631
|
* and optionally usage statistics from previous sessions.
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const fs = require('fs')
|
|
4
|
+
const path = require('path')
|
|
5
|
+
const { isDeepStrictEqual } = require('util')
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Schemas that document docs-data/*.json files (rpk-overrides.schema.json,
|
|
9
|
+
* property-overrides.schema.json, ...) are meant to live inside each content
|
|
10
|
+
* repo's own docs-data/, alongside the file they document — a writer can
|
|
11
|
+
* change both the override data and its schema in one PR, without waiting on
|
|
12
|
+
* a new @redpanda-data/docs-extensions-and-macros release first.
|
|
13
|
+
*
|
|
14
|
+
* That's exactly why this package's copy is not automatically authoritative:
|
|
15
|
+
* a content repo's copy can legitimately be AHEAD of this package's, when a
|
|
16
|
+
* writer documented a real override field here before anyone remembered to
|
|
17
|
+
* update the schema in this package. Confirmed happening in practice —
|
|
18
|
+
* docs-data/rpk-overrides.schema.json in this package is missing `asPartial`,
|
|
19
|
+
* a real field the generator (tools/rpk-docs/generate-rpk-docs.js) already
|
|
20
|
+
* reads, that the docs repo's copy documents correctly. A naive "package
|
|
21
|
+
* always wins" sync would have silently deleted that the first time someone
|
|
22
|
+
* ran it. So syncSchemas() only ever overwrites a destination file when this
|
|
23
|
+
* package's copy is a superset of it (every object key the destination has,
|
|
24
|
+
* the source also has) — otherwise it reports the destination-only keys and
|
|
25
|
+
* refuses, same as `check` mode, unless the caller passes `force: true`.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
const PACKAGE_SCHEMA_DIR = path.resolve(__dirname, '..', 'docs-data')
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* List the *.schema.json files this package ships.
|
|
32
|
+
* @returns {Array<{name: string, sourcePath: string}>}
|
|
33
|
+
*/
|
|
34
|
+
function listPackageSchemas () {
|
|
35
|
+
if (!fs.existsSync(PACKAGE_SCHEMA_DIR)) return []
|
|
36
|
+
return fs.readdirSync(PACKAGE_SCHEMA_DIR)
|
|
37
|
+
.filter((name) => name.endsWith('.schema.json'))
|
|
38
|
+
.sort()
|
|
39
|
+
.map((name) => ({ name, sourcePath: path.join(PACKAGE_SCHEMA_DIR, name) }))
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Find object keys present in `dest` but absent from the same path in
|
|
44
|
+
* `source` — the shape of "the destination knows something the source
|
|
45
|
+
* doesn't". Arrays are compared as opaque leaves (JSON Schema's own
|
|
46
|
+
* structure keeps named, distinguishable content in objects — `properties`,
|
|
47
|
+
* `$defs` — not in arrays like `required`/`enum`, so this is where a
|
|
48
|
+
* destination-only capability would actually show up).
|
|
49
|
+
*
|
|
50
|
+
* @param {*} source
|
|
51
|
+
* @param {*} dest
|
|
52
|
+
* @param {string} [pathPrefix]
|
|
53
|
+
* @returns {string[]} Dotted paths that exist in dest but not source.
|
|
54
|
+
*/
|
|
55
|
+
function findDestOnlyPaths (source, dest, pathPrefix = '') {
|
|
56
|
+
if (
|
|
57
|
+
dest === null || typeof dest !== 'object' || Array.isArray(dest) ||
|
|
58
|
+
source === null || typeof source !== 'object' || Array.isArray(source)
|
|
59
|
+
) {
|
|
60
|
+
return []
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const onlyInDest = []
|
|
64
|
+
for (const [key, destValue] of Object.entries(dest)) {
|
|
65
|
+
const keyPath = pathPrefix ? `${pathPrefix}.${key}` : key
|
|
66
|
+
if (!Object.hasOwn(source, key)) {
|
|
67
|
+
onlyInDest.push(keyPath)
|
|
68
|
+
continue
|
|
69
|
+
}
|
|
70
|
+
onlyInDest.push(...findDestOnlyPaths(source[key], destValue, keyPath))
|
|
71
|
+
}
|
|
72
|
+
return onlyInDest
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Compare this package's schemas against the copies in a destination
|
|
77
|
+
* directory (a content repo's docs-data/), and optionally write updates.
|
|
78
|
+
*
|
|
79
|
+
* @param {object} [opts]
|
|
80
|
+
* @param {string} [opts.destDir] - Directory to sync into. Defaults to
|
|
81
|
+
* docs-data/ under the current repo root.
|
|
82
|
+
* @param {boolean} [opts.check=false] - When true, never writes — only
|
|
83
|
+
* reports what's missing or out of date. Use this in CI.
|
|
84
|
+
* @param {boolean} [opts.force=false] - Overwrite a destination file even
|
|
85
|
+
* when it has content this package's copy lacks. Rarely correct — see the
|
|
86
|
+
* module doc comment. Ignored when `check` is true.
|
|
87
|
+
* @returns {{
|
|
88
|
+
* destDir: string,
|
|
89
|
+
* results: Array<{
|
|
90
|
+
* name: string,
|
|
91
|
+
* status: ('created'|'updated'|'unchanged'|'diverged'),
|
|
92
|
+
* sourcePath: string,
|
|
93
|
+
* destPath: string,
|
|
94
|
+
* destOnlyPaths?: string[]
|
|
95
|
+
* }>,
|
|
96
|
+
* drift: boolean
|
|
97
|
+
* }} drift is true when any schema is missing, differs, or has diverged.
|
|
98
|
+
* status 'diverged' means the destination has content this package's copy
|
|
99
|
+
* doesn't — nothing was written for that file unless `force` was set.
|
|
100
|
+
*/
|
|
101
|
+
function syncSchemas ({ destDir, check = false, force = false } = {}) {
|
|
102
|
+
const resolvedDest = path.resolve(destDir || 'docs-data')
|
|
103
|
+
const schemas = listPackageSchemas()
|
|
104
|
+
|
|
105
|
+
const results = schemas.map(({ name, sourcePath }) => {
|
|
106
|
+
const destPath = path.join(resolvedDest, name)
|
|
107
|
+
const sourceContent = fs.readFileSync(sourcePath, 'utf8')
|
|
108
|
+
const destExists = fs.existsSync(destPath)
|
|
109
|
+
const destContent = destExists ? fs.readFileSync(destPath, 'utf8') : null
|
|
110
|
+
|
|
111
|
+
let status
|
|
112
|
+
let destOnlyPaths
|
|
113
|
+
if (!destExists) {
|
|
114
|
+
status = 'created'
|
|
115
|
+
} else {
|
|
116
|
+
// Compare parsed content, not raw text — a destination reformatted by a
|
|
117
|
+
// content repo's own prettier/editorconfig (different indent, key
|
|
118
|
+
// order, trailing newline) with no real data change must not read as
|
|
119
|
+
// drift.
|
|
120
|
+
const sourceParsed = JSON.parse(sourceContent)
|
|
121
|
+
const destParsed = JSON.parse(destContent)
|
|
122
|
+
if (isDeepStrictEqual(sourceParsed, destParsed)) {
|
|
123
|
+
status = 'unchanged'
|
|
124
|
+
} else {
|
|
125
|
+
destOnlyPaths = findDestOnlyPaths(sourceParsed, destParsed)
|
|
126
|
+
status = destOnlyPaths.length > 0 ? 'diverged' : 'updated'
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const shouldWrite = !check && (status === 'created' || status === 'updated' || (status === 'diverged' && force))
|
|
131
|
+
if (shouldWrite) {
|
|
132
|
+
fs.mkdirSync(resolvedDest, { recursive: true })
|
|
133
|
+
fs.writeFileSync(destPath, sourceContent)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return { name, status, sourcePath, destPath, ...(destOnlyPaths ? { destOnlyPaths } : {}) }
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
const drift = results.some((r) => r.status !== 'unchanged')
|
|
140
|
+
|
|
141
|
+
return { destDir: resolvedDest, results, drift }
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
module.exports = { listPackageSchemas, syncSchemas, findDestOnlyPaths, PACKAGE_SCHEMA_DIR }
|
|
@@ -137,6 +137,10 @@
|
|
|
137
137
|
"type": "boolean",
|
|
138
138
|
"description": "If true, exclude this command from documentation entirely. Use for internal or hidden commands that shouldn't be documented."
|
|
139
139
|
},
|
|
140
|
+
"asPartial": {
|
|
141
|
+
"type": "boolean",
|
|
142
|
+
"description": "If true, write this command's .adoc file to the partials directory instead of the pages directory. Inherited by child commands — set on a parent command (e.g., 'rpk ai') to route the entire subtree to partials."
|
|
143
|
+
},
|
|
140
144
|
"excludeFlags": {
|
|
141
145
|
"type": "array",
|
|
142
146
|
"description": "List of flag names to exclude from documentation (without dashes)",
|