@redpanda-data/docs-extensions-and-macros 5.14.0 → 5.22.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 +334 -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/validation-result.js +53 -0
- package/macros/REFERENCE.adoc +3 -2
- package/macros/prop.js +34 -6
- 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/README.adoc +17 -3
- package/tools/property-extractor/helpers/index.js +1 -2
- package/tools/property-extractor/helpers/seeAlsoView.js +76 -0
- package/tools/property-extractor/parser.py +26 -0
- package/tools/property-extractor/property_extractor.py +65 -6
- package/tools/property-extractor/templates/property.hbs +25 -37
- package/tools/property-extractor/templates/topic-property.hbs +25 -37
- package/tools/property-extractor/transformers.py +15 -2
- package/tools/property-extractor/validate-overrides.js +120 -0
- package/tools/rpk-docs/detect-platform-commands.js +8 -1
- package/tools/rpk-docs/scan-deprecated-commands.js +15 -18
- package/tools/rpk-docs/validate-overrides.js +1 -45
- package/tools/property-extractor/helpers/allTopicsConditional.js +0 -19
- package/tools/property-extractor/helpers/parseRelatedTopic.js +0 -13
|
@@ -963,6 +963,44 @@ def clean_private_fields_from_definitions(definitions):
|
|
|
963
963
|
|
|
964
964
|
|
|
965
965
|
# The definitions.json file contains type definitions that the extractor uses to standardize and centralize type information. After extracting and transforming the properties from the source code, the function merge_properties_and_definitions looks up each property's type in the definitions. If a property's type (or the type of its items, in the case of arrays) matches one of the definitions, the transformer replaces that type with a JSON pointer ( such as #/definitions/<type>) to the corresponding entry in definitions.json. The final JSON output then includes both a properties section (with types now referencing the definitions) and a definitions section, so that consumers of the output can easily resolve the full type information.
|
|
966
|
+
# Fields recorded for tooling only. lint-strings and preview-string read them
|
|
967
|
+
# from the raw --output to anchor a finding to its declaration; nothing on the
|
|
968
|
+
# docs site does. The enhanced JSON is copied verbatim into
|
|
969
|
+
# modules/reference/attachments and downloaded by every browser that hovers a
|
|
970
|
+
# prop: macro, so ~40 bytes per property (~56KB across the real ~1400-property
|
|
971
|
+
# file) would be paid by readers for data no reader uses.
|
|
972
|
+
TOOLING_ONLY_PROPERTY_FIELDS = ("line_start", "line_end")
|
|
973
|
+
|
|
974
|
+
|
|
975
|
+
def strip_tooling_only_fields(properties):
|
|
976
|
+
"""
|
|
977
|
+
Remove tooling-only span fields from properties bound for the PUBLISHED
|
|
978
|
+
JSON attachment.
|
|
979
|
+
|
|
980
|
+
Only the enhanced output is published; the raw --output keeps the spans,
|
|
981
|
+
because that is the file lint-strings and preview-string consume.
|
|
982
|
+
|
|
983
|
+
Args:
|
|
984
|
+
properties: Dictionary of properties
|
|
985
|
+
|
|
986
|
+
Returns:
|
|
987
|
+
The same dictionary, with the tooling-only fields removed
|
|
988
|
+
"""
|
|
989
|
+
removed = 0
|
|
990
|
+
for prop in properties.values():
|
|
991
|
+
if not isinstance(prop, dict):
|
|
992
|
+
continue
|
|
993
|
+
for field in TOOLING_ONLY_PROPERTY_FIELDS:
|
|
994
|
+
if field in prop:
|
|
995
|
+
del prop[field]
|
|
996
|
+
removed += 1
|
|
997
|
+
if removed:
|
|
998
|
+
logging.info(
|
|
999
|
+
f"Stripped {removed} tooling-only span field(s) from the published properties JSON"
|
|
1000
|
+
)
|
|
1001
|
+
return properties
|
|
1002
|
+
|
|
1003
|
+
|
|
966
1004
|
def merge_properties_and_definitions(properties, definitions):
|
|
967
1005
|
# Do not overwrite the resolved type/default with a reference. Just return the resolved properties and definitions.
|
|
968
1006
|
return dict(properties=properties, definitions=definitions)
|
|
@@ -1003,7 +1041,7 @@ def apply_property_overrides(properties, overrides, overrides_file_path=None):
|
|
|
1003
1041
|
- Otherwise, searches existing properties for an entry whose `"name"` equals the override key and applies the override if found.
|
|
1004
1042
|
- If no matching property is found, creates a new property from the override and adds it under the override key.
|
|
1005
1043
|
|
|
1006
|
-
The function supports overrides that add or replace description, version, example content, default, type, config_scope, related_topics, and other metadata. When examples reference external files, relative paths are resolved relative to overrides_file_path.
|
|
1044
|
+
The function supports overrides that add or replace description, version, example content, default, type, config_scope, see_also (related_topics is the deprecated predecessor, still read for existing overrides), and other metadata. When examples reference external files, relative paths are resolved relative to overrides_file_path.
|
|
1007
1045
|
|
|
1008
1046
|
Parameters:
|
|
1009
1047
|
properties (dict): Mapping of existing property entries (modified in-place).
|
|
@@ -1106,13 +1144,24 @@ def _apply_override_to_existing_property(property_dict, override, overrides_file
|
|
|
1106
1144
|
if "config_scope" in override:
|
|
1107
1145
|
property_dict["config_scope"] = override["config_scope"]
|
|
1108
1146
|
|
|
1109
|
-
# Apply related_topics override
|
|
1147
|
+
# Apply related_topics override (deprecated, see see_also)
|
|
1110
1148
|
if "related_topics" in override:
|
|
1111
1149
|
if isinstance(override["related_topics"], list):
|
|
1112
1150
|
property_dict["related_topics"] = override["related_topics"]
|
|
1113
1151
|
else:
|
|
1114
1152
|
logger.warning(f"related_topics for property must be an array")
|
|
1115
1153
|
|
|
1154
|
+
# Apply see_also override. Structural shape (plain string, or an object
|
|
1155
|
+
# naming exactly one of cloud_only/self_hosted_only) is enforced by
|
|
1156
|
+
# docs-data/property-overrides.schema.json via `doc-tools validate
|
|
1157
|
+
# property-overrides`, not here — this just passes the data through for
|
|
1158
|
+
# seeAlsoView.js to normalize at render time.
|
|
1159
|
+
if "see_also" in override:
|
|
1160
|
+
if isinstance(override["see_also"], list):
|
|
1161
|
+
property_dict["see_also"] = override["see_also"]
|
|
1162
|
+
else:
|
|
1163
|
+
logger.warning(f"see_also for property must be an array")
|
|
1164
|
+
|
|
1116
1165
|
# Apply exclude_from_docs override
|
|
1117
1166
|
if "exclude_from_docs" in override:
|
|
1118
1167
|
property_dict["exclude_from_docs"] = override["exclude_from_docs"]
|
|
@@ -1154,19 +1203,26 @@ def _create_property_from_override(prop_name, override, overrides_file_path):
|
|
|
1154
1203
|
if example_content:
|
|
1155
1204
|
new_property["example"] = example_content
|
|
1156
1205
|
|
|
1157
|
-
# Add related_topics if specified
|
|
1206
|
+
# Add related_topics if specified (deprecated, see see_also)
|
|
1158
1207
|
if "related_topics" in override:
|
|
1159
1208
|
if isinstance(override["related_topics"], list):
|
|
1160
1209
|
new_property["related_topics"] = override["related_topics"]
|
|
1161
1210
|
else:
|
|
1162
1211
|
logger.warning(f"related_topics for property '{prop_name}' must be an array")
|
|
1163
|
-
|
|
1212
|
+
|
|
1213
|
+
# Add see_also if specified
|
|
1214
|
+
if "see_also" in override:
|
|
1215
|
+
if isinstance(override["see_also"], list):
|
|
1216
|
+
new_property["see_also"] = override["see_also"]
|
|
1217
|
+
else:
|
|
1218
|
+
logger.warning(f"see_also for property '{prop_name}' must be an array")
|
|
1219
|
+
|
|
1164
1220
|
# Add any other custom fields from override
|
|
1165
1221
|
for key, value in override.items():
|
|
1166
1222
|
if key not in ["description", "type", "default", "config_scope", "version",
|
|
1167
1223
|
"example", "example_file", "example_yaml", "related_topics",
|
|
1168
|
-
"
|
|
1169
|
-
"accepted_values", "_comment"]:
|
|
1224
|
+
"see_also", "is_deprecated", "visibility", "exclude_from_docs",
|
|
1225
|
+
"category", "accepted_values", "_comment"]:
|
|
1170
1226
|
new_property[key] = value
|
|
1171
1227
|
|
|
1172
1228
|
# Add exclude_from_docs if specified
|
|
@@ -3143,6 +3199,9 @@ def main():
|
|
|
3143
3199
|
# 7. Clean private fields from definitions (keep JSON output clean)
|
|
3144
3200
|
filtered_enhanced_definitions = clean_private_fields_from_definitions(filtered_enhanced_definitions)
|
|
3145
3201
|
|
|
3202
|
+
# 8. Drop tooling-only spans: this JSON is the published attachment.
|
|
3203
|
+
enhanced_properties = strip_tooling_only_fields(enhanced_properties)
|
|
3204
|
+
|
|
3146
3205
|
# Generate enhanced properties JSON (with overrides)
|
|
3147
3206
|
enhanced_properties_and_definitions = merge_properties_and_definitions(
|
|
3148
3207
|
enhanced_properties, filtered_enhanced_definitions
|
|
@@ -167,78 +167,66 @@ endif::[]
|
|
|
167
167
|
|
|
|
168
168
|
{{{example}}}
|
|
169
169
|
{{/if}}
|
|
170
|
-
{{#
|
|
171
|
-
{{#
|
|
170
|
+
{{#with (seeAlsoView .) as |seeAlso|}}
|
|
171
|
+
{{#if seeAlso.items.length}}
|
|
172
172
|
|
|
173
|
-
{{#if (eq sectionType "cloud")}}
|
|
173
|
+
{{#if (eq seeAlso.sectionType "cloud")}}
|
|
174
174
|
ifdef::env-cloud[]
|
|
175
175
|
| Related topics
|
|
176
|
-
|{{#if (gt
|
|
177
|
-
{{
|
|
178
|
-
* {{{value}}}
|
|
179
|
-
{{/with}}
|
|
176
|
+
|{{#if (gt seeAlso.items.length 1)}}{{#each seeAlso.items}}
|
|
177
|
+
* {{{content}}}
|
|
180
178
|
{{/each}}
|
|
181
179
|
{{else}}
|
|
182
|
-
{{#each
|
|
183
|
-
{{
|
|
184
|
-
{{{value}}}
|
|
185
|
-
{{/with}}
|
|
180
|
+
{{#each seeAlso.items}}
|
|
181
|
+
{{{content}}}
|
|
186
182
|
{{/each}}
|
|
187
183
|
{{/if}}
|
|
188
184
|
endif::[]
|
|
189
|
-
{{else if (eq sectionType "self-managed")}}
|
|
185
|
+
{{else if (eq seeAlso.sectionType "self-managed")}}
|
|
190
186
|
ifndef::env-cloud[]
|
|
191
187
|
| Related topics
|
|
192
|
-
|{{#if (gt
|
|
193
|
-
{{
|
|
194
|
-
* {{{value}}}
|
|
195
|
-
{{/with}}
|
|
188
|
+
|{{#if (gt seeAlso.items.length 1)}}{{#each seeAlso.items}}
|
|
189
|
+
* {{{content}}}
|
|
196
190
|
{{/each}}
|
|
197
191
|
{{else}}
|
|
198
|
-
{{#each
|
|
199
|
-
{{
|
|
200
|
-
{{{value}}}
|
|
201
|
-
{{/with}}
|
|
192
|
+
{{#each seeAlso.items}}
|
|
193
|
+
{{{content}}}
|
|
202
194
|
{{/each}}
|
|
203
195
|
{{/if}}
|
|
204
196
|
endif::[]
|
|
205
197
|
{{else}}
|
|
206
198
|
| Related topics
|
|
207
|
-
|{{#if (gt
|
|
208
|
-
{{#
|
|
209
|
-
{{#if (eq type "cloud")}}
|
|
199
|
+
|{{#if (gt seeAlso.items.length 1)}}{{#each seeAlso.items}}
|
|
200
|
+
{{#if cloudOnly}}
|
|
210
201
|
ifdef::env-cloud[]
|
|
211
|
-
* {{{
|
|
202
|
+
* {{{content}}}
|
|
212
203
|
endif::[]
|
|
213
|
-
{{else if
|
|
204
|
+
{{else if selfHostedOnly}}
|
|
214
205
|
ifndef::env-cloud[]
|
|
215
|
-
* {{{
|
|
206
|
+
* {{{content}}}
|
|
216
207
|
endif::[]
|
|
217
208
|
{{else}}
|
|
218
|
-
* {{{
|
|
209
|
+
* {{{content}}}
|
|
219
210
|
{{/if}}
|
|
220
|
-
{{/with}}
|
|
221
211
|
{{/each}}
|
|
222
212
|
{{else}}
|
|
223
|
-
{{#each
|
|
224
|
-
{{#
|
|
225
|
-
{{#if (eq type "cloud")}}
|
|
213
|
+
{{#each seeAlso.items}}
|
|
214
|
+
{{#if cloudOnly}}
|
|
226
215
|
ifdef::env-cloud[]
|
|
227
|
-
{{{
|
|
216
|
+
{{{content}}}
|
|
228
217
|
endif::[]
|
|
229
|
-
{{else if
|
|
218
|
+
{{else if selfHostedOnly}}
|
|
230
219
|
ifndef::env-cloud[]
|
|
231
|
-
{{{
|
|
220
|
+
{{{content}}}
|
|
232
221
|
endif::[]
|
|
233
222
|
{{else}}
|
|
234
|
-
{{{
|
|
223
|
+
{{{content}}}
|
|
235
224
|
{{/if}}
|
|
236
|
-
{{/with}}
|
|
237
225
|
{{/each}}
|
|
238
226
|
{{/if}}
|
|
239
227
|
{{/if}}
|
|
240
|
-
{{/with}}
|
|
241
228
|
{{/if}}
|
|
229
|
+
{{/with}}
|
|
242
230
|
|
|
243
231
|
|===
|
|
244
232
|
|
|
@@ -130,78 +130,66 @@ endif::[]
|
|
|
130
130
|
|
|
|
131
131
|
{{{example}}}
|
|
132
132
|
{{/if}}
|
|
133
|
-
{{#
|
|
134
|
-
{{#
|
|
133
|
+
{{#with (seeAlsoView .) as |seeAlso|}}
|
|
134
|
+
{{#if seeAlso.items.length}}
|
|
135
135
|
|
|
136
|
-
{{#if (eq sectionType "cloud")}}
|
|
136
|
+
{{#if (eq seeAlso.sectionType "cloud")}}
|
|
137
137
|
ifdef::env-cloud[]
|
|
138
138
|
| Related topics
|
|
139
|
-
|{{#if (gt
|
|
140
|
-
{{
|
|
141
|
-
* {{{value}}}
|
|
142
|
-
{{/with}}
|
|
139
|
+
|{{#if (gt seeAlso.items.length 1)}}{{#each seeAlso.items}}
|
|
140
|
+
* {{{content}}}
|
|
143
141
|
{{/each}}
|
|
144
142
|
{{else}}
|
|
145
|
-
{{#each
|
|
146
|
-
{{
|
|
147
|
-
{{{value}}}
|
|
148
|
-
{{/with}}
|
|
143
|
+
{{#each seeAlso.items}}
|
|
144
|
+
{{{content}}}
|
|
149
145
|
{{/each}}
|
|
150
146
|
{{/if}}
|
|
151
147
|
endif::[]
|
|
152
|
-
{{else if (eq sectionType "self-managed")}}
|
|
148
|
+
{{else if (eq seeAlso.sectionType "self-managed")}}
|
|
153
149
|
ifndef::env-cloud[]
|
|
154
150
|
| Related topics
|
|
155
|
-
|{{#if (gt
|
|
156
|
-
{{
|
|
157
|
-
* {{{value}}}
|
|
158
|
-
{{/with}}
|
|
151
|
+
|{{#if (gt seeAlso.items.length 1)}}{{#each seeAlso.items}}
|
|
152
|
+
* {{{content}}}
|
|
159
153
|
{{/each}}
|
|
160
154
|
{{else}}
|
|
161
|
-
{{#each
|
|
162
|
-
{{
|
|
163
|
-
{{{value}}}
|
|
164
|
-
{{/with}}
|
|
155
|
+
{{#each seeAlso.items}}
|
|
156
|
+
{{{content}}}
|
|
165
157
|
{{/each}}
|
|
166
158
|
{{/if}}
|
|
167
159
|
endif::[]
|
|
168
160
|
{{else}}
|
|
169
161
|
| Related topics
|
|
170
|
-
|{{#if (gt
|
|
171
|
-
{{#
|
|
172
|
-
{{#if (eq type "cloud")}}
|
|
162
|
+
|{{#if (gt seeAlso.items.length 1)}}{{#each seeAlso.items}}
|
|
163
|
+
{{#if cloudOnly}}
|
|
173
164
|
ifdef::env-cloud[]
|
|
174
|
-
* {{{
|
|
165
|
+
* {{{content}}}
|
|
175
166
|
endif::[]
|
|
176
|
-
{{else if
|
|
167
|
+
{{else if selfHostedOnly}}
|
|
177
168
|
ifndef::env-cloud[]
|
|
178
|
-
* {{{
|
|
169
|
+
* {{{content}}}
|
|
179
170
|
endif::[]
|
|
180
171
|
{{else}}
|
|
181
|
-
* {{{
|
|
172
|
+
* {{{content}}}
|
|
182
173
|
{{/if}}
|
|
183
|
-
{{/with}}
|
|
184
174
|
{{/each}}
|
|
185
175
|
{{else}}
|
|
186
|
-
{{#each
|
|
187
|
-
{{#
|
|
188
|
-
{{#if (eq type "cloud")}}
|
|
176
|
+
{{#each seeAlso.items}}
|
|
177
|
+
{{#if cloudOnly}}
|
|
189
178
|
ifdef::env-cloud[]
|
|
190
|
-
{{{
|
|
179
|
+
{{{content}}}
|
|
191
180
|
endif::[]
|
|
192
|
-
{{else if
|
|
181
|
+
{{else if selfHostedOnly}}
|
|
193
182
|
ifndef::env-cloud[]
|
|
194
|
-
{{{
|
|
183
|
+
{{{content}}}
|
|
195
184
|
endif::[]
|
|
196
185
|
{{else}}
|
|
197
|
-
{{{
|
|
186
|
+
{{{content}}}
|
|
198
187
|
{{/if}}
|
|
199
|
-
{{/with}}
|
|
200
188
|
{{/each}}
|
|
201
189
|
{{/if}}
|
|
202
190
|
{{/if}}
|
|
203
|
-
{{/with}}
|
|
204
191
|
{{/if}}
|
|
192
|
+
{{/with}}
|
|
205
193
|
{{#if aliases}}
|
|
206
194
|
|
|
207
195
|
ifndef::env-cloud[]
|
|
@@ -655,8 +655,10 @@ class BasicInfoTransformer:
|
|
|
655
655
|
EXPECTED FINAL RESULT:
|
|
656
656
|
{
|
|
657
657
|
"name": "property_name", # Clean identifier for JSON keys
|
|
658
|
-
"description": "Human readable...", # Description for documentation
|
|
659
|
-
"defined_in": "src/v/config/..."
|
|
658
|
+
"description": "Human readable...", # Description for documentation
|
|
659
|
+
"defined_in": "src/v/config/...", # Normalized file path
|
|
660
|
+
"line_start": 1852, # First line of the declaration (1-indexed)
|
|
661
|
+
"line_end": 1858 # Last line of the declaration (inclusive)
|
|
660
662
|
}
|
|
661
663
|
|
|
662
664
|
DOWNSTREAM USAGE:
|
|
@@ -724,6 +726,17 @@ class BasicInfoTransformer:
|
|
|
724
726
|
property["defined_in"] = re.sub(
|
|
725
727
|
r"^.*src/", "src/", str(file_pair.implementation)
|
|
726
728
|
)
|
|
729
|
+
|
|
730
|
+
# --- Step 4: line span (additive) ---
|
|
731
|
+
# 1-indexed, inclusive lines of the full member-initializer entry in
|
|
732
|
+
# the implementation file named by defined_in. Recorded by the parser
|
|
733
|
+
# (parse_cpp_source) from the tree-sitter field_initializer node.
|
|
734
|
+
# Spans the whole declaration, not just the description literal, so
|
|
735
|
+
# diff-anchored tooling can match clang-format-wrapped strings.
|
|
736
|
+
if info.get("line_start") is not None:
|
|
737
|
+
property["line_start"] = info["line_start"]
|
|
738
|
+
property["line_end"] = info.get("line_end", info["line_start"])
|
|
739
|
+
|
|
727
740
|
return property
|
|
728
741
|
|
|
729
742
|
@debug_transformer
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const Ajv2020 = require('ajv/dist/2020')
|
|
4
|
+
const fs = require('fs')
|
|
5
|
+
const path = require('path')
|
|
6
|
+
const { ValidationResult } = require('../../cli-utils/validation-result')
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Load and compile the JSON Schema for validation
|
|
10
|
+
* @param {string} [overridesPath] - Path to the overrides file; the schema is
|
|
11
|
+
* looked up as a sibling (same directory) before falling back to the
|
|
12
|
+
* package-relative path.
|
|
13
|
+
* @returns {Function} Compiled schema validator
|
|
14
|
+
*/
|
|
15
|
+
function loadSchemaValidator(overridesPath) {
|
|
16
|
+
// Primary: look for the schema next to the overrides file (docs repo's docs-data/).
|
|
17
|
+
if (overridesPath) {
|
|
18
|
+
const siblingPath = path.join(path.dirname(path.resolve(overridesPath)), 'property-overrides.schema.json')
|
|
19
|
+
if (fs.existsSync(siblingPath)) {
|
|
20
|
+
const schema = JSON.parse(fs.readFileSync(siblingPath, 'utf8'))
|
|
21
|
+
const ajv = new Ajv2020({ allErrors: true, verbose: true })
|
|
22
|
+
return ajv.compile(schema)
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Fallback: package-relative path (legacy / monorepo usage).
|
|
27
|
+
const schemaPath = path.resolve(__dirname, '../../docs-data/property-overrides.schema.json')
|
|
28
|
+
|
|
29
|
+
if (!fs.existsSync(schemaPath)) {
|
|
30
|
+
throw new Error(`Schema file not found: ${schemaPath}`)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const schema = JSON.parse(fs.readFileSync(schemaPath, 'utf8'))
|
|
34
|
+
const ajv = new Ajv2020({ allErrors: true, verbose: true })
|
|
35
|
+
return ajv.compile(schema)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Validate overrides against the JSON Schema
|
|
40
|
+
* @param {Object} overrides - Overrides object to validate
|
|
41
|
+
* @param {string} [overridesPath] - Path to the overrides file (used to locate the schema)
|
|
42
|
+
* @returns {ValidationResult}
|
|
43
|
+
*/
|
|
44
|
+
function validateSchema(overrides, overridesPath) {
|
|
45
|
+
const result = new ValidationResult()
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
const validate = loadSchemaValidator(overridesPath)
|
|
49
|
+
const valid = validate(overrides)
|
|
50
|
+
|
|
51
|
+
if (!valid) {
|
|
52
|
+
for (const error of validate.errors || []) {
|
|
53
|
+
const instancePath = error.instancePath || '/'
|
|
54
|
+
result.addError(error.message, instancePath)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
} catch (err) {
|
|
58
|
+
result.addError(`Schema validation failed: ${err.message}`)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return result
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Run all validations on a property-overrides object.
|
|
66
|
+
* @param {Object} overrides - Overrides object to validate
|
|
67
|
+
* @param {string} [overridesPath] - Path to the overrides file (used to locate the schema)
|
|
68
|
+
* @returns {ValidationResult}
|
|
69
|
+
*/
|
|
70
|
+
function validateOverrides(overrides, overridesPath = null) {
|
|
71
|
+
const result = new ValidationResult()
|
|
72
|
+
|
|
73
|
+
if (!overrides || typeof overrides !== 'object') {
|
|
74
|
+
result.addError('Overrides must be a non-null object')
|
|
75
|
+
return result
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
result.merge(validateSchema(overrides, overridesPath))
|
|
79
|
+
|
|
80
|
+
return result
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Load and validate a property-overrides.json file.
|
|
85
|
+
* @param {string} overridesPath - Path to overrides JSON file
|
|
86
|
+
* @returns {{ overrides: Object|null, validation: ValidationResult }}
|
|
87
|
+
*/
|
|
88
|
+
function loadAndValidateOverrides(overridesPath) {
|
|
89
|
+
const validation = new ValidationResult()
|
|
90
|
+
|
|
91
|
+
if (!overridesPath) {
|
|
92
|
+
return { overrides: null, validation }
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (!fs.existsSync(overridesPath)) {
|
|
96
|
+
validation.addWarning(`Overrides file not found: ${overridesPath}`)
|
|
97
|
+
return { overrides: null, validation }
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
let overrides
|
|
101
|
+
try {
|
|
102
|
+
const content = fs.readFileSync(overridesPath, 'utf8')
|
|
103
|
+
overrides = JSON.parse(content)
|
|
104
|
+
} catch (err) {
|
|
105
|
+
validation.addError(`Failed to parse overrides JSON: ${err.message}`, overridesPath)
|
|
106
|
+
return { overrides: null, validation }
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
validation.merge(validateOverrides(overrides, overridesPath))
|
|
110
|
+
|
|
111
|
+
return { overrides, validation }
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
module.exports = {
|
|
115
|
+
ValidationResult,
|
|
116
|
+
loadSchemaValidator,
|
|
117
|
+
validateSchema,
|
|
118
|
+
validateOverrides,
|
|
119
|
+
loadAndValidateOverrides,
|
|
120
|
+
}
|
|
@@ -47,6 +47,7 @@
|
|
|
47
47
|
|
|
48
48
|
const fs = require('fs')
|
|
49
49
|
const path = require('path')
|
|
50
|
+
const { maskComments } = require('../lint-strings/go-source')
|
|
50
51
|
|
|
51
52
|
/** GOOS values recognized in filename suffixes and build tags */
|
|
52
53
|
const KNOWN_GOOS = new Set([
|
|
@@ -248,7 +249,13 @@ function commandNameFromConstructor(funcName) {
|
|
|
248
249
|
* @param {string} content - Go file content
|
|
249
250
|
* @returns {Object<string, {useName: string|null, excluded: boolean}>}
|
|
250
251
|
*/
|
|
251
|
-
function parseConstructors(
|
|
252
|
+
function parseConstructors(rawContent) {
|
|
253
|
+
// Mask comments first. Scanning raw text read a `Use: "..."` out of a
|
|
254
|
+
// commented-out or example command and reported it as the real one; the
|
|
255
|
+
// masker blanks comment bodies while preserving offsets, line breaks and
|
|
256
|
+
// every string literal, so the regexes below see only live code. Shared with
|
|
257
|
+
// the lint-strings Go surfaces rather than reimplemented here.
|
|
258
|
+
const content = maskComments(rawContent)
|
|
252
259
|
const constructors = {}
|
|
253
260
|
const funcRe = /^func\s+(\w+)\s*\([^)]*\)\s*\*cobra\.Command\s*\{/gm
|
|
254
261
|
const matches = []
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
const fs = require('fs');
|
|
9
9
|
const path = require('path');
|
|
10
|
+
const { maskComments, findBalancedClose } = require('../lint-strings/go-source');
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* Scan a Go file for cobra command definitions with deprecation/hidden status
|
|
@@ -17,31 +18,27 @@ function scanGoFile(filePath) {
|
|
|
17
18
|
const content = fs.readFileSync(filePath, 'utf8');
|
|
18
19
|
|
|
19
20
|
// Look for NewCommand function that returns *cobra.Command
|
|
20
|
-
|
|
21
|
+
// Mask comments before any scanning. The hand-rolled brace counter this
|
|
22
|
+
// replaced was comment- and string-blind, so a `{` inside a comment
|
|
23
|
+
// unbalanced it and truncated the cobra.Command block early, and the field
|
|
24
|
+
// regexes then read a `Use:` string out of a commented-out example. Both
|
|
25
|
+
// helpers are string-aware and already shared by the lint-strings Go
|
|
26
|
+
// surfaces; the masker preserves offsets and every string literal.
|
|
27
|
+
const masked = maskComments(content);
|
|
28
|
+
|
|
29
|
+
const newCommandMatch = masked.match(/func NewCommand\([^)]*\)\s*\*cobra\.Command\s*\{/);
|
|
21
30
|
if (!newCommandMatch) {
|
|
22
31
|
return null;
|
|
23
32
|
}
|
|
24
33
|
|
|
25
34
|
// Extract the cobra.Command struct definition
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
let braceCount = 0;
|
|
29
|
-
let startIndex = content.indexOf('&cobra.Command{');
|
|
30
|
-
|
|
31
|
-
if (startIndex === -1) {
|
|
35
|
+
const structIndex = masked.indexOf('&cobra.Command{');
|
|
36
|
+
if (structIndex === -1) {
|
|
32
37
|
return null;
|
|
33
38
|
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
const char = content[i];
|
|
38
|
-
if (char === '{') braceCount++;
|
|
39
|
-
if (char === '}') {
|
|
40
|
-
if (braceCount === 0) break;
|
|
41
|
-
braceCount--;
|
|
42
|
-
}
|
|
43
|
-
cmdBlock += char;
|
|
44
|
-
}
|
|
39
|
+
const openIndex = structIndex + '&cobra.Command'.length;
|
|
40
|
+
const closeIndex = findBalancedClose(masked, openIndex, '{', '}');
|
|
41
|
+
const cmdBlock = masked.slice(openIndex + 1, closeIndex === -1 ? masked.length : closeIndex);
|
|
45
42
|
|
|
46
43
|
// Extract fields - handle multiline strings with backticks or quotes
|
|
47
44
|
const useMatch = cmdBlock.match(/Use:\s*"([^"]+)"/);
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const Ajv2020 = require('ajv/dist/2020')
|
|
4
4
|
const fs = require('fs')
|
|
5
5
|
const path = require('path')
|
|
6
|
+
const { ValidationResult } = require('../../cli-utils/validation-result')
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Valid admonition locations for notes, warnings, tips, cautions, importants
|
|
@@ -50,51 +51,6 @@ const VALID_SECTION_POSITIONS = [
|
|
|
50
51
|
'end'
|
|
51
52
|
]
|
|
52
53
|
|
|
53
|
-
/**
|
|
54
|
-
* Validation result object
|
|
55
|
-
*/
|
|
56
|
-
class ValidationResult {
|
|
57
|
-
constructor() {
|
|
58
|
-
this.valid = true
|
|
59
|
-
this.errors = []
|
|
60
|
-
this.warnings = []
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
addError(message, context = null) {
|
|
64
|
-
this.valid = false
|
|
65
|
-
this.errors.push({ message, context })
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
addWarning(message, context = null) {
|
|
69
|
-
this.warnings.push({ message, context })
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
merge(other) {
|
|
73
|
-
if (!other.valid) this.valid = false
|
|
74
|
-
this.errors.push(...other.errors)
|
|
75
|
-
this.warnings.push(...other.warnings)
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
format() {
|
|
79
|
-
const lines = []
|
|
80
|
-
if (this.errors.length > 0) {
|
|
81
|
-
lines.push('ERRORS:')
|
|
82
|
-
for (const err of this.errors) {
|
|
83
|
-
lines.push(` ✗ ${err.message}`)
|
|
84
|
-
if (err.context) lines.push(` at: ${err.context}`)
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
if (this.warnings.length > 0) {
|
|
88
|
-
lines.push('WARNINGS:')
|
|
89
|
-
for (const warn of this.warnings) {
|
|
90
|
-
lines.push(` ⚠ ${warn.message}`)
|
|
91
|
-
if (warn.context) lines.push(` at: ${warn.context}`)
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
return lines.join('\n')
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
54
|
/**
|
|
99
55
|
* Normalize a command path (trim whitespace, collapse multiple spaces)
|
|
100
56
|
* @param {string} cmdPath - Command path to normalize
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
// Returns 'cloud' if all topics are cloud-only, 'self-managed' if all are self-managed-only, else 'normal'
|
|
2
|
-
module.exports = function allTopicsConditional(related_topics) {
|
|
3
|
-
if (!Array.isArray(related_topics) || related_topics.length === 0) return null;
|
|
4
|
-
let allCloud = true;
|
|
5
|
-
let allSelfManaged = true;
|
|
6
|
-
for (const t of related_topics) {
|
|
7
|
-
if (typeof t !== 'string') {
|
|
8
|
-
allCloud = false;
|
|
9
|
-
allSelfManaged = false;
|
|
10
|
-
break;
|
|
11
|
-
}
|
|
12
|
-
const trimmed = t.trim();
|
|
13
|
-
if (!trimmed.startsWith('cloud-only:')) allCloud = false;
|
|
14
|
-
if (!trimmed.startsWith('self-managed-only:')) allSelfManaged = false;
|
|
15
|
-
}
|
|
16
|
-
if (allCloud) return 'cloud';
|
|
17
|
-
if (allSelfManaged) return 'self-managed';
|
|
18
|
-
return 'normal';
|
|
19
|
-
};
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
// Returns an object with type and value for a related topic
|
|
2
|
-
// type: 'cloud', 'self-managed', or 'normal'
|
|
3
|
-
module.exports = function parseRelatedTopic(topic) {
|
|
4
|
-
if (typeof topic !== 'string') return { type: 'normal', value: topic };
|
|
5
|
-
const trimmed = topic.trim();
|
|
6
|
-
if (trimmed.startsWith('cloud-only:')) {
|
|
7
|
-
return { type: 'cloud', value: trimmed.replace(/^cloud-only:/, '').trim() };
|
|
8
|
-
}
|
|
9
|
-
if (trimmed.startsWith('self-managed-only:')) {
|
|
10
|
-
return { type: 'self-managed', value: trimmed.replace(/^self-managed-only:/, '').trim() };
|
|
11
|
-
}
|
|
12
|
-
return { type: 'normal', value: trimmed };
|
|
13
|
-
};
|