@redpanda-data/docs-extensions-and-macros 5.35.0 → 5.37.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 +268 -6
- package/cli-utils/enterprise-sources.js +3 -3
- package/cli-utils/github-token.js +53 -13
- package/cli-utils/kapa-credentials.js +109 -0
- package/cli-utils/octokit-client.js +2 -2
- package/cli-utils/self-managed-docs-branch.js +2 -2
- package/docs-data/kapa-source-groups.json +119 -0
- package/docs-data/kapa-source-groups.schema.json +73 -0
- package/extensions/kapa-source-groups.js +180 -0
- package/extensions/version-fetcher/set-latest-version.js +6 -3
- package/package.json +7 -3
- package/tools/generate-cli-docs.js +19 -4
- package/tools/get-redpanda-version.js +2 -2
- package/tools/kapa-source-groups/fetch-with-deadline.js +33 -0
- package/tools/kapa-source-groups/generate-kapa-source-groups.js +319 -0
- package/tools/kapa-source-groups/kapa-source-versions.js +83 -0
- package/tools/kapa-source-groups/published-segments.js +98 -0
- package/tools/property-extractor/rp-util-fetch.js +2 -2
- package/tools/rpk-docs/rpk-docs-handler.js +2 -2
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
{
|
|
2
|
+
"project_id": "97f44223-f930-4fb9-ae1e-ecd436a4d85c",
|
|
3
|
+
"parent_group": {
|
|
4
|
+
"id": "238b3c08-5cf6-4615-937c-fa2f5a2e83a4",
|
|
5
|
+
"name": "Streaming",
|
|
6
|
+
"type": "product"
|
|
7
|
+
},
|
|
8
|
+
"default_segment": "current",
|
|
9
|
+
"segments": {
|
|
10
|
+
"23.3": {
|
|
11
|
+
"group_id": "625e5538-aa73-4969-a5c1-4c35a0108a94",
|
|
12
|
+
"group_name": "23.3",
|
|
13
|
+
"source_ids": [
|
|
14
|
+
"21672ac9-cc94-4529-8835-55334ede0a18"
|
|
15
|
+
],
|
|
16
|
+
"source_names": [
|
|
17
|
+
"Documentation (23.3)"
|
|
18
|
+
]
|
|
19
|
+
},
|
|
20
|
+
"24.1": {
|
|
21
|
+
"group_id": "29357fc1-30b1-46d4-a9de-e30f9b696b2f",
|
|
22
|
+
"group_name": "24.1",
|
|
23
|
+
"source_ids": [
|
|
24
|
+
"0d68df8c-1f9b-45f6-baa3-25d8bb46b164"
|
|
25
|
+
],
|
|
26
|
+
"source_names": [
|
|
27
|
+
"Documentation (24.1)"
|
|
28
|
+
]
|
|
29
|
+
},
|
|
30
|
+
"24.2": {
|
|
31
|
+
"group_id": "3442b9cb-67db-4800-a320-9def40551b93",
|
|
32
|
+
"group_name": "24.2",
|
|
33
|
+
"source_ids": [
|
|
34
|
+
"11659ad5-b7df-4a06-9e8b-4e9913795889"
|
|
35
|
+
],
|
|
36
|
+
"source_names": [
|
|
37
|
+
"Documentation (24.2)"
|
|
38
|
+
]
|
|
39
|
+
},
|
|
40
|
+
"24.3": {
|
|
41
|
+
"group_id": "11288265-8a9e-4a49-8c63-2f9fbe459b76",
|
|
42
|
+
"group_name": "24.3",
|
|
43
|
+
"source_ids": [
|
|
44
|
+
"f1377953-8bcc-48c9-a0e7-acb8efae701e"
|
|
45
|
+
],
|
|
46
|
+
"source_names": [
|
|
47
|
+
"Documentation (24.3)"
|
|
48
|
+
]
|
|
49
|
+
},
|
|
50
|
+
"25.1": {
|
|
51
|
+
"group_id": "317edba8-6c78-4cca-bad5-1dfe47c9d3dc",
|
|
52
|
+
"group_name": "25.1",
|
|
53
|
+
"source_ids": [
|
|
54
|
+
"6cfe84a2-4d5b-4123-b302-f372a5500d02"
|
|
55
|
+
],
|
|
56
|
+
"source_names": [
|
|
57
|
+
"Documentation (25.1)"
|
|
58
|
+
]
|
|
59
|
+
},
|
|
60
|
+
"25.2": {
|
|
61
|
+
"group_id": "639427c4-06fb-4b8f-8df3-d6a444b85101",
|
|
62
|
+
"group_name": "25.2",
|
|
63
|
+
"source_ids": [
|
|
64
|
+
"4d4d1a66-08e7-46aa-ac72-fea301f96b5f"
|
|
65
|
+
],
|
|
66
|
+
"source_names": [
|
|
67
|
+
"Documentation (25.2)"
|
|
68
|
+
]
|
|
69
|
+
},
|
|
70
|
+
"25.3": {
|
|
71
|
+
"group_id": "65723bc1-941c-4b3b-b5ba-2c601f7b3ba9",
|
|
72
|
+
"group_name": "25.3",
|
|
73
|
+
"source_ids": [
|
|
74
|
+
"d1213d06-6132-47e2-abca-43fe0a85dde6"
|
|
75
|
+
],
|
|
76
|
+
"source_names": [
|
|
77
|
+
"Documentation (25.3)"
|
|
78
|
+
]
|
|
79
|
+
},
|
|
80
|
+
"26.1": {
|
|
81
|
+
"group_id": "f0751d2d-de07-4a9c-8d75-7960121178b7",
|
|
82
|
+
"group_name": "26.1",
|
|
83
|
+
"source_ids": [
|
|
84
|
+
"ec6810ab-6208-4e6b-acef-83da55d45762"
|
|
85
|
+
],
|
|
86
|
+
"source_names": [
|
|
87
|
+
"Documentation (26.1)"
|
|
88
|
+
]
|
|
89
|
+
},
|
|
90
|
+
"current": {
|
|
91
|
+
"group_id": "35567e94-30c7-4608-ab5d-1ff982339d05",
|
|
92
|
+
"group_name": "current",
|
|
93
|
+
"source_ids": [
|
|
94
|
+
"f5c1e522-be3a-45f6-8463-11abc1b35ea9"
|
|
95
|
+
],
|
|
96
|
+
"source_names": [
|
|
97
|
+
"Documentation (current)"
|
|
98
|
+
]
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
"global_sources": [
|
|
102
|
+
"Admin API",
|
|
103
|
+
"Agentic Data Plane",
|
|
104
|
+
"Cloud Controlplane API",
|
|
105
|
+
"Cloud Dataplane API",
|
|
106
|
+
"Corrected Responses",
|
|
107
|
+
"Documentation (Cloud)",
|
|
108
|
+
"Documentation (RPCN)",
|
|
109
|
+
"GitHub Discussions",
|
|
110
|
+
"HTTP Proxy API",
|
|
111
|
+
"pylon",
|
|
112
|
+
"Recruitment Scam Page",
|
|
113
|
+
"Redpanda Connect Release Notes",
|
|
114
|
+
"Redpanda Core Release Notes",
|
|
115
|
+
"Schema Registry API",
|
|
116
|
+
"Terraform provider",
|
|
117
|
+
"YouTube Tutorials"
|
|
118
|
+
]
|
|
119
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://redpanda.com/schemas/kapa-source-groups.schema.json",
|
|
4
|
+
"title": "Kapa source groups",
|
|
5
|
+
"description": "Generated mapping from a docs URL version segment to the Kapa source group that scopes retrieval to that version. Produced by `doc-tools generate kapa-source-groups` from Kapa's ingestion API; do not hand-edit. Kapa publishes no write API, so the groups and their source assignments are created in the Kapa dashboard and this file only records what is there. See tools/kapa-source-groups/generate-kapa-source-groups.js.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"required": ["project_id", "parent_group", "default_segment", "segments", "global_sources"],
|
|
9
|
+
"properties": {
|
|
10
|
+
"project_id": {
|
|
11
|
+
"type": "string",
|
|
12
|
+
"format": "uuid",
|
|
13
|
+
"description": "Kapa project the groups belong to. Matches KAPA_PROJECT_ID. Recorded so a mapping generated against the wrong project is obvious rather than silently wrong."
|
|
14
|
+
},
|
|
15
|
+
"parent_group": {
|
|
16
|
+
"type": "object",
|
|
17
|
+
"additionalProperties": false,
|
|
18
|
+
"required": ["id", "name", "type"],
|
|
19
|
+
"description": "The product-level group the version groups hang off. Kapa's hierarchy is two levels and is expressed top-down via sub_groups; there is no parent_id field anywhere in its schema, so the generator finds this by 'the group that has children' rather than by name. Renaming it in the dashboard is therefore safe.",
|
|
20
|
+
"properties": {
|
|
21
|
+
"id": { "type": "string", "format": "uuid" },
|
|
22
|
+
"name": { "type": "string" },
|
|
23
|
+
"type": {
|
|
24
|
+
"type": ["string", "null"],
|
|
25
|
+
"enum": ["product", "version", null],
|
|
26
|
+
"description": "Kapa requires every group to be typed. The enum appears only in Kapa's API schema, not in any prose doc. Nothing in Kapa's documented retrieval semantics reads it, so it is recorded for humans rather than relied on."
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"default_segment": {
|
|
31
|
+
"type": "string",
|
|
32
|
+
"description": "Segment used for any page with no version of its own: Cloud, Connect, Agentic Data Plane, labs, home, search, the 404 page, and the Bump-rendered /api/ pages. Must be a key of `segments`. Deliberately set rather than left unset: sending no group filter makes Kapa search every version at once, which is the DOC-2450 failure (a reader on an unversioned page was answered from 25.2 instead of current)."
|
|
33
|
+
},
|
|
34
|
+
"segments": {
|
|
35
|
+
"type": "object",
|
|
36
|
+
"minProperties": 1,
|
|
37
|
+
"description": "Keyed by the docs URL version segment, not by the Antora `page.version` attribute. The two disagree for the latest release: `latest_version_segment: 'current'` publishes 26.2 at /streaming/current/ while page.version reads 26.2. Kapa source_url values and the URL prefixes in docs-site's lib/platform-filter.mjs both use the segment, so that is what this keys on.",
|
|
38
|
+
"additionalProperties": { "$ref": "#/$defs/segment" }
|
|
39
|
+
},
|
|
40
|
+
"global_sources": {
|
|
41
|
+
"type": "array",
|
|
42
|
+
"items": { "type": "string" },
|
|
43
|
+
"description": "Names of the Kapa sources deliberately assigned to no group. These are the 'global' set: always returned alongside whichever group a query scopes to. Recorded because the design depends on it: Agentic Data Plane, Cloud and Connect content must stay reachable from every page, whatever version the reader is on. Note 'Global' is NOT a real Kapa group despite the dashboard showing a Global row: the API returns no such object and an unassigned source reports source_groups: []."
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
"$defs": {
|
|
47
|
+
"segment": {
|
|
48
|
+
"type": "object",
|
|
49
|
+
"additionalProperties": false,
|
|
50
|
+
"required": ["group_id", "group_name", "source_ids", "source_names"],
|
|
51
|
+
"properties": {
|
|
52
|
+
"group_id": {
|
|
53
|
+
"type": "string",
|
|
54
|
+
"format": "uuid",
|
|
55
|
+
"description": "The value to pass as sourceGroupIdsInclude (Agent SDK), sourceGroupIDsInclude (Chat SDK, note the capital ID), or _meta.source_group_ids_include (hosted MCP server). Kapa group IDs are stable across renames."
|
|
56
|
+
},
|
|
57
|
+
"group_name": { "type": "string" },
|
|
58
|
+
"source_ids": {
|
|
59
|
+
"type": "array",
|
|
60
|
+
"minItems": 1,
|
|
61
|
+
"items": { "type": "string", "format": "uuid" },
|
|
62
|
+
"description": "Sources assigned to this group. minItems is 1 on purpose: a version group with no sources is worse than no group at all, because scoping to it returns only the global set and the reader silently gets nothing version-specific. The generator refuses to emit such a mapping."
|
|
63
|
+
},
|
|
64
|
+
"source_names": {
|
|
65
|
+
"type": "array",
|
|
66
|
+
"minItems": 1,
|
|
67
|
+
"items": { "type": "string" },
|
|
68
|
+
"description": "Human-readable counterpart to source_ids, so a reviewer can sanity-check the mapping without opening the Kapa dashboard."
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/* Publishes the Kapa source-group mapping to the UI as an AsciiDoc attribute.
|
|
2
|
+
*
|
|
3
|
+
* Example use in the playbook:
|
|
4
|
+
* antora:
|
|
5
|
+
* extensions:
|
|
6
|
+
* - require: '@redpanda-data/docs-extensions-and-macros/extensions/kapa-source-groups'
|
|
7
|
+
*
|
|
8
|
+
* WHY THIS EXISTS
|
|
9
|
+
* ---------------
|
|
10
|
+
* docs-ui's get-kapa-source-groups helper needs to turn the page's version
|
|
11
|
+
* segment into a Kapa source group id, so Ask AI retrieval is scoped to the docs
|
|
12
|
+
* version the reader is actually on (DOC-1807, DOC-2450). docs-ui does not depend
|
|
13
|
+
* on this package and must not carry a second copy of the mapping, which would
|
|
14
|
+
* drift silently. So the mapping travels as an attribute: generated here by
|
|
15
|
+
* `doc-tools generate kapa-source-groups`, committed to docs-data, and merged
|
|
16
|
+
* onto every component version at build time.
|
|
17
|
+
*
|
|
18
|
+
* Set on every component version rather than only the versioned ones, because the
|
|
19
|
+
* helper needs the mapping on unversioned pages too. Those resolve to the
|
|
20
|
+
* mapping's default_segment, and sending no filter there is exactly what caused
|
|
21
|
+
* DOC-2450: the reporter was on a page with no version of its own.
|
|
22
|
+
*
|
|
23
|
+
* DEGRADATION
|
|
24
|
+
* -----------
|
|
25
|
+
* Every failure sets nothing and warns. The helper treats a missing attribute as
|
|
26
|
+
* "send no source group", which is the pre-DOC-2450 behaviour of searching every
|
|
27
|
+
* version. That is the right way to fail: a wrong group is worse than no group,
|
|
28
|
+
* because scoping to a group that does not hold the reader's version returns only
|
|
29
|
+
* Kapa's global sources, silently and with no error.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
const fs = require('fs')
|
|
33
|
+
const path = require('path')
|
|
34
|
+
const { raiseListenerLimit } = require('./util/raise-listener-limit')
|
|
35
|
+
|
|
36
|
+
const ATTRIBUTE_NAME = 'kapa-source-groups'
|
|
37
|
+
// Sits beside assets/widgets/, which proxy-api-docs.js already fetches from the
|
|
38
|
+
// site origin, so the /api/ path needs no new hostname or auth.
|
|
39
|
+
const ASSET_DIR = 'assets/data'
|
|
40
|
+
const ASSET_FILENAME = 'kapa-source-groups.json'
|
|
41
|
+
const DEFAULT_MAPPING_PATH = path.join(__dirname, '..', 'docs-data', 'kapa-source-groups.json')
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Validate the shape the helper depends on, so a malformed mapping is caught at
|
|
45
|
+
* build time with a named reason rather than producing pages that quietly lose
|
|
46
|
+
* version scoping.
|
|
47
|
+
*
|
|
48
|
+
* @param {*} mapping
|
|
49
|
+
* @returns {string|null} Reason it is unusable, or null when it is fine
|
|
50
|
+
*/
|
|
51
|
+
function validateMapping (mapping) {
|
|
52
|
+
if (!mapping || typeof mapping !== 'object') return 'not an object'
|
|
53
|
+
if (!mapping.segments || typeof mapping.segments !== 'object') return 'no segments object'
|
|
54
|
+
const segments = Object.keys(mapping.segments)
|
|
55
|
+
if (segments.length === 0) return 'segments is empty'
|
|
56
|
+
if (!mapping.default_segment) return 'no default_segment'
|
|
57
|
+
if (!mapping.segments[mapping.default_segment]) {
|
|
58
|
+
// The helper falls back to default_segment for every unversioned page, so a
|
|
59
|
+
// dangling default silently disables scoping across most of the site.
|
|
60
|
+
return `default_segment "${mapping.default_segment}" is not one of the segments (${segments.join(', ')})`
|
|
61
|
+
}
|
|
62
|
+
const missing = segments.filter((s) => !mapping.segments[s] || !mapping.segments[s].group_id)
|
|
63
|
+
if (missing.length) return `segments missing group_id: ${missing.join(', ')}`
|
|
64
|
+
return null
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
module.exports.register = function ({ config = {} } = {}) {
|
|
68
|
+
const logger = this.getLogger('kapa-source-groups-extension')
|
|
69
|
+
|
|
70
|
+
// Three listeners on a generator context that already carries 30+ extensions,
|
|
71
|
+
// which trips Node's default max-listeners warning. Raised before subscribing,
|
|
72
|
+
// per __tests__/extensions/raise-listener-limit.test.js.
|
|
73
|
+
raiseListenerLimit(this)
|
|
74
|
+
const mappingPath = config.mapping_file ? path.resolve(config.mapping_file) : DEFAULT_MAPPING_PATH
|
|
75
|
+
|
|
76
|
+
// Loaded on first use and reused by all three listeners, so a missing or
|
|
77
|
+
// broken mapping warns once per build rather than once per event.
|
|
78
|
+
let loaded = false
|
|
79
|
+
let cached = null
|
|
80
|
+
function load () {
|
|
81
|
+
if (loaded) return cached
|
|
82
|
+
loaded = true
|
|
83
|
+
cached = readMapping()
|
|
84
|
+
return cached
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Read + validate, or return null having warned with the consequence. */
|
|
88
|
+
function readMapping () {
|
|
89
|
+
let mapping
|
|
90
|
+
try {
|
|
91
|
+
if (!fs.existsSync(mappingPath)) {
|
|
92
|
+
logger.warn(
|
|
93
|
+
`Kapa source-group mapping not found at ${mappingPath}. Ask AI will search every docs version. ` +
|
|
94
|
+
'Generate it with: doc-tools generate kapa-source-groups'
|
|
95
|
+
)
|
|
96
|
+
return null
|
|
97
|
+
}
|
|
98
|
+
mapping = JSON.parse(fs.readFileSync(mappingPath, 'utf8'))
|
|
99
|
+
} catch (err) {
|
|
100
|
+
logger.warn(`Could not read the Kapa source-group mapping at ${mappingPath}: ${err.message}. Ask AI will search every docs version.`)
|
|
101
|
+
return null
|
|
102
|
+
}
|
|
103
|
+
const problem = validateMapping(mapping)
|
|
104
|
+
if (problem) {
|
|
105
|
+
logger.warn(`Kapa source-group mapping at ${mappingPath} is unusable (${problem}). Ask AI will search every docs version.`)
|
|
106
|
+
return null
|
|
107
|
+
}
|
|
108
|
+
return mapping
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// site.keys reaches EVERY page, including ones with no component: the 404 page
|
|
112
|
+
// renders the Ask AI panel but has no page.component or page.componentVersion,
|
|
113
|
+
// so a component-version attribute alone leaves it unscoped. Its layout pulls
|
|
114
|
+
// in head and header, both of which already read site.keys, so this is the one
|
|
115
|
+
// channel that covers it. Set in playbookBuilt because the site UI model is
|
|
116
|
+
// derived from the playbook.
|
|
117
|
+
this.on('playbookBuilt', ({ playbook }) => {
|
|
118
|
+
const mapping = load()
|
|
119
|
+
if (!mapping) return
|
|
120
|
+
playbook.site = playbook.site || {}
|
|
121
|
+
playbook.site.keys = playbook.site.keys || {}
|
|
122
|
+
if (playbook.site.keys[ATTRIBUTE_NAME] === undefined) {
|
|
123
|
+
playbook.site.keys[ATTRIBUTE_NAME] = JSON.stringify(mapping)
|
|
124
|
+
}
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
// The /api/ reference is Bump.sh HTML assembled by docs-site's proxy-api-docs
|
|
128
|
+
// edge function, which has no Antora page context and cannot import from
|
|
129
|
+
// node_modules. Publishing the mapping as a site asset lets that function read
|
|
130
|
+
// the default group at request time, in parallel with the widget fetches it
|
|
131
|
+
// already does. The alternative -- a UUID pasted into docs-ui's static widget
|
|
132
|
+
// context or a Netlify env var -- is a second copy of the mapping that drifts
|
|
133
|
+
// silently the first time a group is recreated.
|
|
134
|
+
this.on('beforePublish', ({ siteCatalog }) => {
|
|
135
|
+
const mapping = load()
|
|
136
|
+
if (!mapping) return
|
|
137
|
+
siteCatalog.addFile({
|
|
138
|
+
contents: Buffer.from(JSON.stringify(mapping, null, 2) + '\n', 'utf8'),
|
|
139
|
+
out: { path: path.join(ASSET_DIR, ASSET_FILENAME) },
|
|
140
|
+
})
|
|
141
|
+
logger.info(`Kapa source groups: published ${ASSET_DIR}/${ASSET_FILENAME} for the /api/ proxy.`)
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
this.on('contentClassified', async ({ contentCatalog }) => {
|
|
145
|
+
const mapping = load()
|
|
146
|
+
if (!mapping) return
|
|
147
|
+
|
|
148
|
+
// Serialised once. The helper accepts a string or an object, but a string is
|
|
149
|
+
// what survives Antora's attribute handling unchanged, and it keeps the
|
|
150
|
+
// attribute value comparable between builds.
|
|
151
|
+
const serialised = JSON.stringify(mapping)
|
|
152
|
+
|
|
153
|
+
let versions = 0
|
|
154
|
+
const components = await contentCatalog.getComponents()
|
|
155
|
+
for (const component of components) {
|
|
156
|
+
component.versions.forEach(({ asciidoc }) => {
|
|
157
|
+
if (!asciidoc) return
|
|
158
|
+
asciidoc.attributes = asciidoc.attributes || {}
|
|
159
|
+
// Do not clobber an explicit override: a playbook or antora.yml that
|
|
160
|
+
// already sets this wins, matching how add-global-attributes.js lets
|
|
161
|
+
// component attributes beat the shared file.
|
|
162
|
+
if (asciidoc.attributes[ATTRIBUTE_NAME] === undefined) {
|
|
163
|
+
asciidoc.attributes[ATTRIBUTE_NAME] = serialised
|
|
164
|
+
}
|
|
165
|
+
versions += 1
|
|
166
|
+
})
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
logger.info(
|
|
170
|
+
`Kapa source groups: set ${ATTRIBUTE_NAME} on ${versions} component version(s); ` +
|
|
171
|
+
`${Object.keys(mapping.segments).length} version segments, default "${mapping.default_segment}".`
|
|
172
|
+
)
|
|
173
|
+
})
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
module.exports.validateMapping = validateMapping
|
|
177
|
+
module.exports.ATTRIBUTE_NAME = ATTRIBUTE_NAME
|
|
178
|
+
module.exports.DEFAULT_MAPPING_PATH = DEFAULT_MAPPING_PATH
|
|
179
|
+
module.exports.ASSET_DIR = ASSET_DIR
|
|
180
|
+
module.exports.ASSET_FILENAME = ASSET_FILENAME
|
|
@@ -10,10 +10,13 @@ module.exports.register = function ({ config }) {
|
|
|
10
10
|
const GetLatestConnectVersion = require('./get-latest-connect');
|
|
11
11
|
const logger = this.getLogger('set-latest-version-extension');
|
|
12
12
|
|
|
13
|
-
const {
|
|
13
|
+
const { getGitHubApiToken } = require('../../cli-utils/github-token');
|
|
14
14
|
// Shared with tools/bundle-openapi.js so there is one major.minor derivation.
|
|
15
15
|
const { toShortVersion } = require('../../cli-utils/version');
|
|
16
|
-
|
|
16
|
+
// API token, not the git one: GIT_CREDENTIALS (Antora's clone credential)
|
|
17
|
+
// is only a last resort here, because on Netlify it is scoped to the docs
|
|
18
|
+
// content repos and cannot see streaming-enterprise.
|
|
19
|
+
const token = getGitHubApiToken();
|
|
17
20
|
|
|
18
21
|
if (!token) {
|
|
19
22
|
// Unauthenticated requests still work for the public repos this
|
|
@@ -21,7 +24,7 @@ module.exports.register = function ({ config }) {
|
|
|
21
24
|
// version now comes from the private streaming-enterprise repo, whose
|
|
22
25
|
// API 404s without a token -- the latest-redpanda-* attributes are then
|
|
23
26
|
// left unset and pages fall back to their antora.yml values.
|
|
24
|
-
logger.warn('GitHub token not set (
|
|
27
|
+
logger.warn('GitHub token not set (REDPANDA_GITHUB_TOKEN, GITHUB_TOKEN, GH_TOKEN, or GIT_CREDENTIALS). The Redpanda version lookup against the private streaming-enterprise repo will fail and leave latest-redpanda-* attributes unset; other lookups proceed unauthenticated.');
|
|
25
28
|
}
|
|
26
29
|
|
|
27
30
|
this.on('contentClassified', async ({ contentCatalog }) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@redpanda-data/docs-extensions-and-macros",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.37.0",
|
|
4
4
|
"description": "Antora extensions and macros developed for Redpanda documentation.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"antora",
|
|
@@ -92,7 +92,9 @@
|
|
|
92
92
|
"./macros/enterprise": "./macros/enterprise.js",
|
|
93
93
|
"./macros/prop": "./macros/prop.js",
|
|
94
94
|
"./extensions/property-page-index": "./extensions/property-page-index.js",
|
|
95
|
-
"./extensions/render-property-descriptions": "./extensions/render-property-descriptions.js"
|
|
95
|
+
"./extensions/render-property-descriptions": "./extensions/render-property-descriptions.js",
|
|
96
|
+
"./extensions/kapa-source-groups": "./extensions/kapa-source-groups.js",
|
|
97
|
+
"./docs-data/kapa-source-groups.json": "./docs-data/kapa-source-groups.json"
|
|
96
98
|
},
|
|
97
99
|
"files": [
|
|
98
100
|
"extensions",
|
|
@@ -104,7 +106,9 @@
|
|
|
104
106
|
"tools",
|
|
105
107
|
"docker-compose",
|
|
106
108
|
"mcp",
|
|
107
|
-
"docs-data/rpk-overrides.schema.json"
|
|
109
|
+
"docs-data/rpk-overrides.schema.json",
|
|
110
|
+
"docs-data/kapa-source-groups.json",
|
|
111
|
+
"docs-data/kapa-source-groups.schema.json"
|
|
108
112
|
],
|
|
109
113
|
"license": "ISC",
|
|
110
114
|
"engines": {
|
|
@@ -138,11 +138,19 @@ function parseJSDocComments(sourceFile) {
|
|
|
138
138
|
// was documented with the first command's prose. That is exactly what
|
|
139
139
|
// happened to `generate migrate-rpcn-descriptions`, which shipped a
|
|
140
140
|
// regenerated section describing migrate-rpcn-metadata.
|
|
141
|
-
|
|
141
|
+
//
|
|
142
|
+
// The group is captured too, because a command NAME is not unique: the same
|
|
143
|
+
// name can be registered under both `automation` (generate) and `validation`
|
|
144
|
+
// (validate). `kapa-source-groups` is, and keying on the bare name alone made
|
|
145
|
+
// the later registration silently overwrite the earlier, so the
|
|
146
|
+
// `generate kapa-source-groups` section shipped describing the validate
|
|
147
|
+
// command, complete with validate examples under a generate usage line.
|
|
148
|
+
// Same failure as the migrate-rpcn one above, one level up.
|
|
149
|
+
const pattern = /\/\*\*\s*((?:(?!\*\/)[\s\S])*?)\s*\*\/\s*(programCli|automation|validation)\s*\.command\(['"]([^'"]+)['"]\)/g;
|
|
142
150
|
|
|
143
151
|
let match;
|
|
144
152
|
while ((match = pattern.exec(content)) !== null) {
|
|
145
|
-
const [, commentText, commandName] = match;
|
|
153
|
+
const [, commentText, group, commandName] = match;
|
|
146
154
|
|
|
147
155
|
// Parse the comment into sections
|
|
148
156
|
const parsed = {
|
|
@@ -189,7 +197,12 @@ function parseJSDocComments(sourceFile) {
|
|
|
189
197
|
.trim();
|
|
190
198
|
}
|
|
191
199
|
|
|
192
|
-
|
|
200
|
+
// Keyed by the CLI path a reader would type, so `generate x` and
|
|
201
|
+
// `validate x` cannot collide. The bare name is kept as well, for the
|
|
202
|
+
// top-level commands registered straight onto programCli.
|
|
203
|
+
const groupPrefix = { automation: 'generate ', validation: 'validate ', programCli: '' }[group]
|
|
204
|
+
comments[`${groupPrefix}${commandName}`] = parsed;
|
|
205
|
+
if (comments[commandName] === undefined) comments[commandName] = parsed;
|
|
193
206
|
}
|
|
194
207
|
|
|
195
208
|
return comments;
|
|
@@ -290,7 +303,9 @@ IMPORTANT: This documentation is auto-generated. Do not edit manually. Run \`npm
|
|
|
290
303
|
const label = commandPath.join(' ');
|
|
291
304
|
console.log(`${' '.repeat(level - 1)}Generating docs for: ${label}`);
|
|
292
305
|
const data = parseHelp(getHelpOutput(label));
|
|
293
|
-
|
|
306
|
+
// Prefer the full command path, falling back to the bare name so that
|
|
307
|
+
// commands registered directly on programCli still resolve.
|
|
308
|
+
const jsdoc = jsdocs[label] || jsdocs[commandPath[commandPath.length - 1]];
|
|
294
309
|
doc += generateCommandDoc(label, data, jsdoc, level);
|
|
295
310
|
for (const child of data.commands) {
|
|
296
311
|
// Commander lists `help [command]` in every group; it is not a command
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const GetLatestRedpandaVersion = require('../extensions/version-fetcher/get-latest-redpanda-version.js');
|
|
4
4
|
const { getPrereleaseFromAntora } = require('../cli-utils/antora-utils.js');
|
|
5
|
-
const {
|
|
5
|
+
const { getGitHubApiToken } = require('../cli-utils/github-token');
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Fetches and prints the latest Redpanda version and Docker repository.
|
|
@@ -20,7 +20,7 @@ module.exports = async function getRedpandaVersion({ beta = false, fromAntora =
|
|
|
20
20
|
const owner = 'redpanda-data';
|
|
21
21
|
const repo = 'streaming-enterprise';
|
|
22
22
|
|
|
23
|
-
if (!
|
|
23
|
+
if (!getGitHubApiToken()) {
|
|
24
24
|
console.error(`❌ redpanda-data/${repo} is a private repository.`);
|
|
25
25
|
console.error(' Set GH_TOKEN, REDPANDA_GITHUB_TOKEN, or GITHUB_TOKEN to a token with access.');
|
|
26
26
|
process.exit(1);
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* fetch() with a deadline that covers the WHOLE exchange, body included.
|
|
3
|
+
*
|
|
4
|
+
* The obvious shape, `try { res = await fetch(url, { signal }) } finally
|
|
5
|
+
* { clearTimeout(timer) }` and then `await res.text()`, only bounds the time to
|
|
6
|
+
* headers. fetch resolves as soon as headers arrive, the timer is cleared, and a
|
|
7
|
+
* server that then stalls mid-body holds the caller forever. For a scheduled
|
|
8
|
+
* check that is a job that never finishes rather than one that exits 2, so
|
|
9
|
+
* nobody is told and the next run queues behind it.
|
|
10
|
+
*
|
|
11
|
+
* @param {typeof fetch} fetchImpl
|
|
12
|
+
* @param {string} url
|
|
13
|
+
* @param {RequestInit} init - Merged with the abort signal
|
|
14
|
+
* @param {number} timeoutMs
|
|
15
|
+
* @param {(res: Response, signal: AbortSignal) => Promise<*>} read
|
|
16
|
+
* Consumes the body while the timer is still armed. Return null for a
|
|
17
|
+
* response you do not want to read (a non-OK status, say) so the caller can
|
|
18
|
+
* inspect res.status first.
|
|
19
|
+
* @returns {Promise<{res: Response, body: *}>}
|
|
20
|
+
*/
|
|
21
|
+
async function fetchWithDeadline (fetchImpl, url, init, timeoutMs, read) {
|
|
22
|
+
const controller = new AbortController()
|
|
23
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs)
|
|
24
|
+
try {
|
|
25
|
+
const res = await fetchImpl(url, { ...init, signal: controller.signal })
|
|
26
|
+
const body = await read(res, controller.signal)
|
|
27
|
+
return { res, body }
|
|
28
|
+
} finally {
|
|
29
|
+
clearTimeout(timer)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
module.exports = { fetchWithDeadline }
|