@redpanda-data/docs-extensions-and-macros 5.46.0 → 5.47.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 +15 -2
- package/cli-utils/sync-schemas.js +66 -12
- package/docs-data/property-overrides.schema.json +202 -29
- package/package.json +1 -1
- package/tools/overrides-audit/adapters/properties.js +14 -3
- package/tools/overrides-audit/classify.js +175 -3
- package/tools/property-extractor/README.adoc +141 -2
- package/tools/property-extractor/compare-properties.js +12 -2
- package/tools/property-extractor/generate-handlebars-docs.js +262 -11
- package/tools/property-extractor/helpers/applyPropertyLinks.js +586 -0
- package/tools/property-extractor/helpers/audienceScope.js +172 -0
- package/tools/property-extractor/helpers/findAudienceScopeProblems.js +121 -0
- package/tools/property-extractor/helpers/index.js +3 -0
- package/tools/property-extractor/helpers/seeAlsoView.js +10 -24
- package/tools/property-extractor/property_extractor.py +30 -2
- package/tools/property-extractor/templates/property.hbs +32 -2
- package/tools/property-extractor/templates/topic-property.hbs +32 -2
- package/tools/redpanda-connect/helpers/renderConnectDescription.js +7 -1
- package/tools/redpanda-connect/helpers/renderConnectFields.js +4 -1
package/bin/doc-tools.js
CHANGED
|
@@ -558,7 +558,15 @@ programCli
|
|
|
558
558
|
* copy is a strict superset of the destination; otherwise it reports which
|
|
559
559
|
* keys only the destination has and leaves the file alone (status
|
|
560
560
|
* 'diverged') unless --force is passed. --check never writes, for a CI
|
|
561
|
-
* gate.
|
|
561
|
+
* gate. A schema is also only synced into a repo that is plausibly its
|
|
562
|
+
* home: one that already has the schema, or that has the *.json the schema
|
|
563
|
+
* documents. Anything else is reported as 'not for this repo' and skipped,
|
|
564
|
+
* and never counts as drift. kapa-source-groups.json is the reason: it is
|
|
565
|
+
* generated into this package and read from node_modules by an Antora
|
|
566
|
+
* extension, so it never lives in a content repo at all, and planting its
|
|
567
|
+
* schema in redpanda-data/docs would leave a file describing data that repo
|
|
568
|
+
* will never have, with --check reporting its absence as drift on every run
|
|
569
|
+
* afterwards.
|
|
562
570
|
* @example
|
|
563
571
|
* # Sync into ./docs-data (writes any missing or out-of-date schema)
|
|
564
572
|
* npx doc-tools sync-schemas
|
|
@@ -604,8 +612,13 @@ programCli
|
|
|
604
612
|
created: '+ created',
|
|
605
613
|
updated: '↻ updated',
|
|
606
614
|
diverged: options.force ? '↻ updated (forced)' : '⚠ diverged, left alone',
|
|
615
|
+
'not-applicable': '- not for this repo',
|
|
607
616
|
}[status]
|
|
608
617
|
console.log(` ${label} ${name}`)
|
|
618
|
+
if (status === 'not-applicable') {
|
|
619
|
+
// Said out loud, because a silently missing schema looks like a bug.
|
|
620
|
+
console.log(` no ${name.replace(/\.schema\.json$/, '.json')} here, so this schema has no data file to document`)
|
|
621
|
+
}
|
|
609
622
|
if (status === 'diverged' && !options.force) {
|
|
610
623
|
hasUnresolvedDivergence = true
|
|
611
624
|
for (const p of destOnlyPaths) console.log(` only in the destination: ${p}`)
|
|
@@ -2630,7 +2643,7 @@ validation
|
|
|
2630
2643
|
* Validates docs-data/property-overrides.json against its JSON Schema:
|
|
2631
2644
|
* unknown keys (a typo that would otherwise be silently dropped by the
|
|
2632
2645
|
* extractor), and the see_also shape (a plain string, or an object naming
|
|
2633
|
-
* exactly one of cloud_only/
|
|
2646
|
+
* exactly one of cloud_only/self_managed_only).
|
|
2634
2647
|
*
|
|
2635
2648
|
* @why
|
|
2636
2649
|
* property-overrides.json has no catch-all pass-through when an override
|
|
@@ -23,6 +23,16 @@ const { isDeepStrictEqual } = require('util')
|
|
|
23
23
|
* package's copy is a superset of it (every object key the destination has,
|
|
24
24
|
* the source also has) — otherwise it reports the destination-only keys and
|
|
25
25
|
* refuses, same as `check` mode, unless the caller passes `force: true`.
|
|
26
|
+
*
|
|
27
|
+
* Not every schema belongs in every content repo. kapa-source-groups.json is
|
|
28
|
+
* generated into THIS package and read from node_modules by an Antora
|
|
29
|
+
* extension, so it never lives in a content repo at all; copying its schema
|
|
30
|
+
* into redpanda-data/docs leaves a file describing data that repo will never
|
|
31
|
+
* have, and `check` mode then reports its absence as drift forever. So a
|
|
32
|
+
* schema is only synced into a destination that is plausibly its home: the
|
|
33
|
+
* destination already has the schema, or it has the *.json the schema
|
|
34
|
+
* documents. Anything else is reported as 'not-applicable' and does not count
|
|
35
|
+
* as drift.
|
|
26
36
|
*/
|
|
27
37
|
|
|
28
38
|
const PACKAGE_SCHEMA_DIR = path.resolve(__dirname, '..', 'docs-data')
|
|
@@ -42,16 +52,27 @@ function listPackageSchemas () {
|
|
|
42
52
|
/**
|
|
43
53
|
* Find object keys present in `dest` but absent from the same path in
|
|
44
54
|
* `source` — the shape of "the destination knows something the source
|
|
45
|
-
* doesn't".
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
55
|
+
* doesn't". Most arrays are compared as opaque leaves: `required`/`enum`
|
|
56
|
+
* hold literal values, not named content, so there is nothing inside one to
|
|
57
|
+
* be "destination-only" in the sense this function cares about.
|
|
58
|
+
*
|
|
59
|
+
* `oneOf`/`anyOf`/`allOf` are the exception, and are recursed into by
|
|
60
|
+
* matching index. Those are JSON Schema's own combinators, and their array
|
|
61
|
+
* ELEMENTS are full schema objects that can carry real, named content --
|
|
62
|
+
* `see_also.items.oneOf[1].properties.cloud_only` is exactly the
|
|
63
|
+
* distinguishable content this function otherwise only looks for inside a
|
|
64
|
+
* plain object. Treating them as opaque like `required`/`enum` missed
|
|
65
|
+
* every audience flag living inside see_also's object variant, and would
|
|
66
|
+
* let a write-mode sync silently delete a destination-only flag added
|
|
67
|
+
* there, with no warning and no --force.
|
|
49
68
|
*
|
|
50
69
|
* @param {*} source
|
|
51
70
|
* @param {*} dest
|
|
52
71
|
* @param {string} [pathPrefix]
|
|
53
72
|
* @returns {string[]} Dotted paths that exist in dest but not source.
|
|
54
73
|
*/
|
|
74
|
+
const SCHEMA_COMBINATOR_KEYS = new Set(['oneOf', 'anyOf', 'allOf'])
|
|
75
|
+
|
|
55
76
|
function findDestOnlyPaths (source, dest, pathPrefix = '') {
|
|
56
77
|
if (
|
|
57
78
|
dest === null || typeof dest !== 'object' || Array.isArray(dest) ||
|
|
@@ -67,11 +88,35 @@ function findDestOnlyPaths (source, dest, pathPrefix = '') {
|
|
|
67
88
|
onlyInDest.push(keyPath)
|
|
68
89
|
continue
|
|
69
90
|
}
|
|
70
|
-
|
|
91
|
+
const sourceValue = source[key]
|
|
92
|
+
if (SCHEMA_COMBINATOR_KEYS.has(key) && Array.isArray(destValue) && Array.isArray(sourceValue)) {
|
|
93
|
+
const length = Math.max(destValue.length, sourceValue.length)
|
|
94
|
+
for (let i = 0; i < length; i++) {
|
|
95
|
+
const itemPath = `${keyPath}[${i}]`
|
|
96
|
+
if (i >= sourceValue.length) {
|
|
97
|
+
onlyInDest.push(itemPath)
|
|
98
|
+
continue
|
|
99
|
+
}
|
|
100
|
+
onlyInDest.push(...findDestOnlyPaths(sourceValue[i], destValue[i], itemPath))
|
|
101
|
+
}
|
|
102
|
+
continue
|
|
103
|
+
}
|
|
104
|
+
onlyInDest.push(...findDestOnlyPaths(sourceValue, destValue, keyPath))
|
|
71
105
|
}
|
|
72
106
|
return onlyInDest
|
|
73
107
|
}
|
|
74
108
|
|
|
109
|
+
/**
|
|
110
|
+
* The docs-data file a schema documents: property-overrides.schema.json
|
|
111
|
+
* documents property-overrides.json.
|
|
112
|
+
*
|
|
113
|
+
* @param {string} schemaName - Bare schema filename.
|
|
114
|
+
* @returns {string} The data filename it documents.
|
|
115
|
+
*/
|
|
116
|
+
function dataFileFor (schemaName) {
|
|
117
|
+
return schemaName.replace(/\.schema\.json$/, '.json')
|
|
118
|
+
}
|
|
119
|
+
|
|
75
120
|
/**
|
|
76
121
|
* Compare this package's schemas against the copies in a destination
|
|
77
122
|
* directory (a content repo's docs-data/), and optionally write updates.
|
|
@@ -88,15 +133,18 @@ function findDestOnlyPaths (source, dest, pathPrefix = '') {
|
|
|
88
133
|
* destDir: string,
|
|
89
134
|
* results: Array<{
|
|
90
135
|
* name: string,
|
|
91
|
-
* status: ('created'|'updated'|'unchanged'|'diverged'),
|
|
136
|
+
* status: ('created'|'updated'|'unchanged'|'diverged'|'not-applicable'),
|
|
92
137
|
* sourcePath: string,
|
|
93
138
|
* destPath: string,
|
|
94
139
|
* destOnlyPaths?: string[]
|
|
95
140
|
* }>,
|
|
96
141
|
* drift: boolean
|
|
97
|
-
* }} drift is true when any schema is missing, differs, or has
|
|
98
|
-
* status 'diverged' means the destination has content this
|
|
99
|
-
* doesn't — nothing was written for that file unless `force`
|
|
142
|
+
* }} drift is true when any applicable schema is missing, differs, or has
|
|
143
|
+
* diverged. status 'diverged' means the destination has content this
|
|
144
|
+
* package's copy doesn't — nothing was written for that file unless `force`
|
|
145
|
+
* was set. status 'not-applicable' means neither the schema nor the *.json it
|
|
146
|
+
* documents is in the destination, so that schema does not belong there;
|
|
147
|
+
* nothing is written and it never counts as drift.
|
|
100
148
|
*/
|
|
101
149
|
function syncSchemas ({ destDir, check = false, force = false } = {}) {
|
|
102
150
|
const resolvedDest = path.resolve(destDir || 'docs-data')
|
|
@@ -110,7 +158,13 @@ function syncSchemas ({ destDir, check = false, force = false } = {}) {
|
|
|
110
158
|
|
|
111
159
|
let status
|
|
112
160
|
let destOnlyPaths
|
|
113
|
-
if (!destExists) {
|
|
161
|
+
if (!destExists && !fs.existsSync(path.join(resolvedDest, dataFileFor(name)))) {
|
|
162
|
+
// Neither the schema nor the data file it documents is here, so this
|
|
163
|
+
// repo is not where that schema lives. Writing it would plant a file
|
|
164
|
+
// describing data this repo never has, and `check` would then call its
|
|
165
|
+
// absence drift on every run.
|
|
166
|
+
status = 'not-applicable'
|
|
167
|
+
} else if (!destExists) {
|
|
114
168
|
status = 'created'
|
|
115
169
|
} else {
|
|
116
170
|
// Compare parsed content, not raw text — a destination reformatted by a
|
|
@@ -136,9 +190,9 @@ function syncSchemas ({ destDir, check = false, force = false } = {}) {
|
|
|
136
190
|
return { name, status, sourcePath, destPath, ...(destOnlyPaths ? { destOnlyPaths } : {}) }
|
|
137
191
|
})
|
|
138
192
|
|
|
139
|
-
const drift = results.some((r) => r.status !== 'unchanged')
|
|
193
|
+
const drift = results.some((r) => r.status !== 'unchanged' && r.status !== 'not-applicable')
|
|
140
194
|
|
|
141
195
|
return { destDir: resolvedDest, results, drift }
|
|
142
196
|
}
|
|
143
197
|
|
|
144
|
-
module.exports = { listPackageSchemas, syncSchemas, findDestOnlyPaths, PACKAGE_SCHEMA_DIR }
|
|
198
|
+
module.exports = { listPackageSchemas, syncSchemas, findDestOnlyPaths, dataFileFor, PACKAGE_SCHEMA_DIR }
|
|
@@ -7,8 +7,10 @@
|
|
|
7
7
|
"properties": {
|
|
8
8
|
"properties": {
|
|
9
9
|
"type": "object",
|
|
10
|
-
"description": "Keyed by property key or name (see property_extractor.py's name-fallback lookup). An override key that matches neither creates a phantom stub property
|
|
11
|
-
"additionalProperties": {
|
|
10
|
+
"description": "Keyed by property key or name (see property_extractor.py's name-fallback lookup). An override key that matches neither creates a phantom stub property \u2014 see property_extractor.py's report_phantom_stubs().",
|
|
11
|
+
"additionalProperties": {
|
|
12
|
+
"$ref": "#/$defs/propertyOverride"
|
|
13
|
+
}
|
|
12
14
|
}
|
|
13
15
|
},
|
|
14
16
|
"additionalProperties": false,
|
|
@@ -21,10 +23,40 @@
|
|
|
21
23
|
"type": "string",
|
|
22
24
|
"description": "Free-text note to whoever edits this entry next. Never read by the generator."
|
|
23
25
|
},
|
|
24
|
-
"description": {
|
|
26
|
+
"description": {
|
|
27
|
+
"description": "Property description. A plain string, or an array of paragraphs when one of them belongs to a single docs build: an array entry may carry a 'cloud-only:' / 'self-managed-only:' prefix and the generator wraps it in the matching ifdef::env-cloud[] conditional. Prefer the array form over writing the conditional into the string, because the overrides audit can only compare prose it can separate from markup, so a hand-written conditional pins the override forever.",
|
|
28
|
+
"oneOf": [
|
|
29
|
+
{
|
|
30
|
+
"type": "string"
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"type": "array",
|
|
34
|
+
"minItems": 1,
|
|
35
|
+
"items": {
|
|
36
|
+
"type": "string"
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
]
|
|
40
|
+
},
|
|
41
|
+
"links": {
|
|
42
|
+
"type": "object",
|
|
43
|
+
"description": "Inline links, keyed by the literal text to find in the prose (backticks included, as written) and valued by the target. Only the first occurrence of a key is linked, and the longest key wins. Two target forms: '#<property_name>' emits a prop macro, which resolves the reference page itself and renders plain code in a build that does not publish the target, so it needs no conditional and cannot dangle; 'xref:<target>' emits a literal xref with the key as its link text; 'glossterm' wraps the key in a glossary tooltip (use 'glossterm:<Term>' when the prose says something other than the term's own name). A target may carry a 'cloud-only:' / 'self-managed-only:' prefix, in which case the containing paragraph is emitted twice: linked in that branch, plain in the other. Keeping links here rather than in the sentence is what lets the description stay prose the audit can compare against the source string.",
|
|
44
|
+
"additionalProperties": {
|
|
45
|
+
"type": "string",
|
|
46
|
+
"pattern": "^(?:(?:cloud-only|self-managed-only):\\s*)?(?:#[^\\s#]+|xref:[^\\s\\[\\]]+|glossterm(?::[^\\[\\]]+)?)$"
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
"includes": {
|
|
50
|
+
"description": "Shared partials pulled into this property's entry, as Antora resource IDs (for example reference:partial$internal-use-property.adoc). Rendered as include:: directives immediately after the description, which is where they used to be written by hand. Keeping them here rather than inside the description string is what lets the prose stay comparable to the source doc string. An entry may carry a 'cloud-only:' / 'self-managed-only:' prefix, and its own [attrs] if it needs them.",
|
|
51
|
+
"type": "array",
|
|
52
|
+
"minItems": 1,
|
|
53
|
+
"items": {
|
|
54
|
+
"type": "string"
|
|
55
|
+
}
|
|
56
|
+
},
|
|
25
57
|
"upstream_ref": {
|
|
26
58
|
"type": "string",
|
|
27
|
-
"description": "The upstream issue or PR (against the engineering repo whose defective string this description patches, either the short form 'redpanda-data/streaming-enterprise#12345' or the full URL) that will make this description override redundant once merged and released. A description override is a stopgap, not a fix: set this so the override is born with a retirement path, and remove both the override and this field once the fix ships. Only meaningful alongside 'description'
|
|
59
|
+
"description": "The upstream issue or PR (against the engineering repo whose defective string this description patches, either the short form 'redpanda-data/streaming-enterprise#12345' or the full URL) that will make this description override redundant once merged and released. A description override is a stopgap, not a fix: set this so the override is born with a retirement path, and remove both the override and this field once the fix ships. Only meaningful alongside 'description' \u2014 has no effect on generation or rendering."
|
|
28
60
|
},
|
|
29
61
|
"version": {
|
|
30
62
|
"type": "string",
|
|
@@ -33,17 +65,30 @@
|
|
|
33
65
|
"example": {
|
|
34
66
|
"description": "Literal example content, or an example_file/example_yaml reference resolved relative to this overrides file."
|
|
35
67
|
},
|
|
36
|
-
"example_file": {
|
|
37
|
-
|
|
68
|
+
"example_file": {
|
|
69
|
+
"type": "string"
|
|
70
|
+
},
|
|
71
|
+
"example_yaml": {
|
|
72
|
+
"type": "string"
|
|
73
|
+
},
|
|
38
74
|
"default": {
|
|
39
|
-
"description": "Replaces the extracted default. Prefer leaving this unset
|
|
75
|
+
"description": "Replaces the extracted default. Prefer leaving this unset \u2014 it silently pins a value the extractor would otherwise keep in sync with the C++ source on every release."
|
|
76
|
+
},
|
|
77
|
+
"type": {
|
|
78
|
+
"type": "string"
|
|
40
79
|
},
|
|
41
|
-
"type": { "type": "string" },
|
|
42
80
|
"config_scope": {
|
|
43
|
-
"enum": [
|
|
44
|
-
|
|
81
|
+
"enum": [
|
|
82
|
+
"cluster",
|
|
83
|
+
"broker",
|
|
84
|
+
"topic",
|
|
85
|
+
null
|
|
86
|
+
],
|
|
87
|
+
"description": "Almost always inferred automatically from defined_in \u2014 see property_extractor.py's add_config_scope(). Only set this for an override-created property (defined_in: 'override'), which has no source file to infer from."
|
|
88
|
+
},
|
|
89
|
+
"exclude_from_docs": {
|
|
90
|
+
"type": "boolean"
|
|
45
91
|
},
|
|
46
|
-
"exclude_from_docs": { "type": "boolean" },
|
|
47
92
|
"category": {
|
|
48
93
|
"type": "string",
|
|
49
94
|
"description": "Documentation grouping. Not inferable for cluster/broker properties; topic properties get a mechanical default from infer_category() that this can still override."
|
|
@@ -53,17 +98,23 @@
|
|
|
53
98
|
"description": "Replaces the enum field to filter displayed values (e.g. hiding an internal-only mode).",
|
|
54
99
|
"items": {}
|
|
55
100
|
},
|
|
56
|
-
"is_deprecated": {
|
|
57
|
-
|
|
101
|
+
"is_deprecated": {
|
|
102
|
+
"type": "boolean"
|
|
103
|
+
},
|
|
104
|
+
"visibility": {
|
|
105
|
+
"type": "string"
|
|
106
|
+
},
|
|
58
107
|
"related_topics": {
|
|
59
108
|
"type": "array",
|
|
60
109
|
"deprecated": true,
|
|
61
|
-
"description": "Deprecated
|
|
62
|
-
"items": {
|
|
110
|
+
"description": "Deprecated \u2014 use see_also. A plain string, or one prefixed 'cloud-only:'/'self-managed-only:' for conditional display. The prefix is free text with no validation; still read for existing entries, normalized alongside see_also by seeAlsoView.js.",
|
|
111
|
+
"items": {
|
|
112
|
+
"type": "string"
|
|
113
|
+
}
|
|
63
114
|
},
|
|
64
115
|
"see_also": {
|
|
65
116
|
"type": "array",
|
|
66
|
-
"description": "Related commands or documentation pages
|
|
117
|
+
"description": "Related commands or documentation pages. Each item is a plain string (shown everywhere) or an object naming exactly one of cloud_only/self_managed_only (shown only in that build). Same shape as rpk-overrides.json's 'seeAlso', except that file spells the flags in camelCase to match its own key style.",
|
|
67
118
|
"items": {
|
|
68
119
|
"oneOf": [
|
|
69
120
|
{
|
|
@@ -79,24 +130,80 @@
|
|
|
79
130
|
"description": "The xref link or related-topic text."
|
|
80
131
|
},
|
|
81
132
|
"cloud_only": {
|
|
82
|
-
"
|
|
83
|
-
"description": "If true, only shown in Cloud docs (wrapped with ifdef::env-cloud[])."
|
|
133
|
+
"const": true,
|
|
134
|
+
"description": "If true, only shown in Cloud docs (wrapped with ifdef::env-cloud[]). Only `true` is accepted: the emitters test for exactly true, so `false` behaves as if the key were absent while reading as though it meant the opposite audience. Omit the key instead."
|
|
84
135
|
},
|
|
85
136
|
"self_hosted_only": {
|
|
86
|
-
"
|
|
87
|
-
"
|
|
137
|
+
"const": true,
|
|
138
|
+
"deprecated": true,
|
|
139
|
+
"description": "Deprecated spelling of self_managed_only. Still read, so an override written against the older schema keeps working, but nothing emits it. Use self_managed_only. Only `true` is accepted: the emitters test for exactly true, so `false` behaves as if the key were absent while reading as though it meant the opposite audience. Omit the key instead."
|
|
140
|
+
},
|
|
141
|
+
"self_managed_only": {
|
|
142
|
+
"const": true,
|
|
143
|
+
"description": "If true, only shown in self-managed docs (wrapped with ifndef::env-cloud[]). Only `true` is accepted: the emitters test for exactly true, so `false` behaves as if the key were absent while reading as though it meant the opposite audience. Omit the key instead."
|
|
88
144
|
}
|
|
89
145
|
},
|
|
90
|
-
"required": [
|
|
146
|
+
"required": [
|
|
147
|
+
"content"
|
|
148
|
+
],
|
|
91
149
|
"additionalProperties": false,
|
|
92
150
|
"oneOf": [
|
|
93
151
|
{
|
|
94
|
-
"required": [
|
|
95
|
-
|
|
152
|
+
"required": [
|
|
153
|
+
"cloud_only"
|
|
154
|
+
],
|
|
155
|
+
"not": {
|
|
156
|
+
"anyOf": [
|
|
157
|
+
{
|
|
158
|
+
"required": [
|
|
159
|
+
"self_managed_only"
|
|
160
|
+
]
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
"required": [
|
|
164
|
+
"self_hosted_only"
|
|
165
|
+
]
|
|
166
|
+
}
|
|
167
|
+
]
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
"required": [
|
|
172
|
+
"self_managed_only"
|
|
173
|
+
],
|
|
174
|
+
"not": {
|
|
175
|
+
"anyOf": [
|
|
176
|
+
{
|
|
177
|
+
"required": [
|
|
178
|
+
"cloud_only"
|
|
179
|
+
]
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
"required": [
|
|
183
|
+
"self_hosted_only"
|
|
184
|
+
]
|
|
185
|
+
}
|
|
186
|
+
]
|
|
187
|
+
}
|
|
96
188
|
},
|
|
97
189
|
{
|
|
98
|
-
"required": [
|
|
99
|
-
|
|
190
|
+
"required": [
|
|
191
|
+
"self_hosted_only"
|
|
192
|
+
],
|
|
193
|
+
"not": {
|
|
194
|
+
"anyOf": [
|
|
195
|
+
{
|
|
196
|
+
"required": [
|
|
197
|
+
"cloud_only"
|
|
198
|
+
]
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
"required": [
|
|
202
|
+
"self_managed_only"
|
|
203
|
+
]
|
|
204
|
+
}
|
|
205
|
+
]
|
|
206
|
+
}
|
|
100
207
|
}
|
|
101
208
|
]
|
|
102
209
|
}
|
|
@@ -108,14 +215,31 @@
|
|
|
108
215
|
"description": "Structured NOTE/TIP/IMPORTANT/WARNING/CAUTION callouts, rendered as Asciidoctor admonition blocks instead of hand-written markup in description. Each entry needs type and text; title is optional. See property_extractor.py's _normalize_admonitions() -- an entry that doesn't match this shape is dropped with a warning, not rejected here, so the schema stays permissive on casing/whitespace that the generator itself normalizes.",
|
|
109
216
|
"items": {
|
|
110
217
|
"type": "object",
|
|
111
|
-
"required": [
|
|
218
|
+
"required": [
|
|
219
|
+
"type",
|
|
220
|
+
"text"
|
|
221
|
+
],
|
|
112
222
|
"properties": {
|
|
113
223
|
"type": {
|
|
114
224
|
"type": "string",
|
|
115
225
|
"description": "Case-insensitive; normalized to uppercase. One of note, tip, important, warning, caution.",
|
|
116
|
-
"enum": [
|
|
117
|
-
|
|
118
|
-
|
|
226
|
+
"enum": [
|
|
227
|
+
"note",
|
|
228
|
+
"tip",
|
|
229
|
+
"important",
|
|
230
|
+
"warning",
|
|
231
|
+
"caution",
|
|
232
|
+
"NOTE",
|
|
233
|
+
"TIP",
|
|
234
|
+
"IMPORTANT",
|
|
235
|
+
"WARNING",
|
|
236
|
+
"CAUTION",
|
|
237
|
+
"Note",
|
|
238
|
+
"Tip",
|
|
239
|
+
"Important",
|
|
240
|
+
"Warning",
|
|
241
|
+
"Caution"
|
|
242
|
+
]
|
|
119
243
|
},
|
|
120
244
|
"text": {
|
|
121
245
|
"type": "string",
|
|
@@ -124,8 +248,57 @@
|
|
|
124
248
|
"title": {
|
|
125
249
|
"type": "string",
|
|
126
250
|
"description": "Optional admonition title, shown in place of the default type label."
|
|
251
|
+
},
|
|
252
|
+
"cloud_only": {
|
|
253
|
+
"const": true,
|
|
254
|
+
"description": "Show this admonition only in the Cloud build (wrapped with ifdef::env-cloud[]). Only `true` is accepted: the emitters test for exactly true, so `false` behaves as if the key were absent while reading as though it meant the opposite audience. Omit the key instead."
|
|
255
|
+
},
|
|
256
|
+
"self_managed_only": {
|
|
257
|
+
"const": true,
|
|
258
|
+
"description": "Show this admonition only in the self-managed build (wrapped with ifndef::env-cloud[]). Only `true` is accepted: the emitters test for exactly true, so `false` behaves as if the key were absent while reading as though it meant the opposite audience. Omit the key instead."
|
|
259
|
+
},
|
|
260
|
+
"self_hosted_only": {
|
|
261
|
+
"const": true,
|
|
262
|
+
"deprecated": true,
|
|
263
|
+
"description": "Deprecated spelling of self_managed_only. Still read, so an override written against the older schema keeps working, but nothing emits it: the product is Self-Managed and the string prefix has always been 'self-managed-only:', so one concept was going by two words depending on whether you wrote it as a prefix or a boolean. Use self_managed_only. Only `true` is accepted: the emitters test for exactly true, so `false` behaves as if the key were absent while reading as though it meant the opposite audience. Omit the key instead."
|
|
127
264
|
}
|
|
128
265
|
},
|
|
266
|
+
"allOf": [
|
|
267
|
+
{
|
|
268
|
+
"not": {
|
|
269
|
+
"$comment": "Setting both audiences would wrap the block in ifdef and ifndef at once, so it would render in neither build. Only an exact true counts, so cloud_only: false beside self_managed_only: true stays valid.",
|
|
270
|
+
"properties": {
|
|
271
|
+
"cloud_only": {
|
|
272
|
+
"const": true
|
|
273
|
+
},
|
|
274
|
+
"self_managed_only": {
|
|
275
|
+
"const": true
|
|
276
|
+
}
|
|
277
|
+
},
|
|
278
|
+
"required": [
|
|
279
|
+
"cloud_only",
|
|
280
|
+
"self_managed_only"
|
|
281
|
+
]
|
|
282
|
+
}
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
"not": {
|
|
286
|
+
"$comment": "Same check against the deprecated spelling.",
|
|
287
|
+
"properties": {
|
|
288
|
+
"cloud_only": {
|
|
289
|
+
"const": true
|
|
290
|
+
},
|
|
291
|
+
"self_hosted_only": {
|
|
292
|
+
"const": true
|
|
293
|
+
}
|
|
294
|
+
},
|
|
295
|
+
"required": [
|
|
296
|
+
"cloud_only",
|
|
297
|
+
"self_hosted_only"
|
|
298
|
+
]
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
],
|
|
129
302
|
"additionalProperties": false
|
|
130
303
|
}
|
|
131
304
|
}
|
package/package.json
CHANGED
|
@@ -41,6 +41,13 @@ function loadJson (filePath, label) {
|
|
|
41
41
|
* REDUNDANT (the audit's normalization only widens equality, never narrows
|
|
42
42
|
* it), so a violation indicates a classifier bug.
|
|
43
43
|
*
|
|
44
|
+
* One exemption, not an exception to the rule: a description carrying
|
|
45
|
+
* audience-scoped paragraphs classifies KEEP even when its unconditional prose
|
|
46
|
+
* is raw-equal to source, because retiring it would delete the scoped
|
|
47
|
+
* paragraphs too. Those entries are compared on their unconditional prose --
|
|
48
|
+
* skipping them outright would quietly drop the array form out of the
|
|
49
|
+
* invariant's coverage.
|
|
50
|
+
*
|
|
44
51
|
* @param {Object} extractedDoc - Extracted properties document.
|
|
45
52
|
* @param {Object} overrides - Override entries map.
|
|
46
53
|
* @param {Object[]} manifest - Classified manifest rows.
|
|
@@ -51,8 +58,11 @@ function crossCheckWithCompare (extractedDoc, overrides, manifest) {
|
|
|
51
58
|
const overlaidProps = {}
|
|
52
59
|
for (const [name, prop] of Object.entries(extractedProps)) {
|
|
53
60
|
const override = overrides[name]
|
|
54
|
-
|
|
55
|
-
?
|
|
61
|
+
const description = override && override.description !== undefined
|
|
62
|
+
? classify.unconditionalProse(override.description).prose
|
|
63
|
+
: undefined
|
|
64
|
+
overlaidProps[name] = description !== undefined
|
|
65
|
+
? { ...prop, description }
|
|
56
66
|
: prop
|
|
57
67
|
}
|
|
58
68
|
|
|
@@ -72,7 +82,8 @@ function crossCheckWithCompare (extractedDoc, overrides, manifest) {
|
|
|
72
82
|
row.field === 'description' &&
|
|
73
83
|
row.class !== classify.CLASSES.REDUNDANT &&
|
|
74
84
|
overrides[row.name] &&
|
|
75
|
-
|
|
85
|
+
overrides[row.name].description !== undefined &&
|
|
86
|
+
classify.unconditionalProse(overrides[row.name].description).scoped === 0 &&
|
|
76
87
|
extractedProps[row.name] &&
|
|
77
88
|
!changedNames.has(row.name)
|
|
78
89
|
)
|