@redpanda-data/docs-extensions-and-macros 4.8.1 ā 4.9.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 +334 -35
- package/package.json +1 -1
- package/tools/property-extractor/Makefile +8 -18
- package/tools/property-extractor/cloud_config.py +594 -0
- package/tools/property-extractor/compare-properties.js +378 -0
- package/tools/property-extractor/generate-handlebars-docs.js +132 -32
- package/tools/property-extractor/parser.py +27 -1
- package/tools/property-extractor/property_extractor.py +335 -99
- package/tools/property-extractor/requirements.txt +1 -0
- package/tools/property-extractor/templates/property-cloud.hbs +105 -0
- package/tools/property-extractor/templates/property.hbs +16 -1
- package/tools/property-extractor/templates/topic-property-cloud.hbs +97 -0
- package/tools/property-extractor/templates/topic-property.hbs +26 -12
- package/tools/property-extractor/transformers.py +98 -2
- package/tools/redpanda-connect/generate-rpcn-connector-docs.js +96 -2
- package/tools/redpanda-connect/helpers/bloblangExample.js +42 -0
- package/tools/redpanda-connect/helpers/index.js +4 -3
- package/tools/redpanda-connect/helpers/renderConnectFields.js +32 -5
- package/tools/redpanda-connect/report-delta.js +101 -0
- package/tools/redpanda-connect/templates/bloblang-function.hbs +28 -0
package/bin/doc-tools.js
CHANGED
|
@@ -459,18 +459,120 @@ const commonOptions = {
|
|
|
459
459
|
consoleDockerRepo: 'console',
|
|
460
460
|
};
|
|
461
461
|
|
|
462
|
+
/**
|
|
463
|
+
* Run the cluster documentation generator script for a specific release/tag.
|
|
464
|
+
*
|
|
465
|
+
* Invokes the external `generate-cluster-docs.sh` script with the provided mode, tag,
|
|
466
|
+
* and Docker-related options. The script's stdout/stderr are forwarded to the current
|
|
467
|
+
* process; if the script exits with a non-zero status, this function will terminate
|
|
468
|
+
* the Node.js process with that status code.
|
|
469
|
+
*
|
|
470
|
+
* @param {string} mode - Operation mode passed to the script (e.g., "generate" or "clean").
|
|
471
|
+
* @param {string} tag - Release tag or version to generate docs for.
|
|
472
|
+
* @param {Object} options - Runtime options.
|
|
473
|
+
* @param {string} options.dockerRepo - Docker repository used by the script.
|
|
474
|
+
* @param {string} options.consoleTag - Console image tag passed to the script.
|
|
475
|
+
* @param {string} options.consoleDockerRepo - Console Docker repository used by the script.
|
|
476
|
+
*/
|
|
462
477
|
function runClusterDocs(mode, tag, options) {
|
|
463
478
|
const script = path.join(__dirname, '../cli-utils/generate-cluster-docs.sh');
|
|
464
479
|
const args = [mode, tag, options.dockerRepo, options.consoleTag, options.consoleDockerRepo];
|
|
465
480
|
console.log(`ā³ Running ${script} with arguments: ${args.join(' ')}`);
|
|
466
|
-
const r = spawnSync('bash', [script, ...args], { stdio: 'inherit'
|
|
481
|
+
const r = spawnSync('bash', [script, ...args], { stdio: 'inherit' });
|
|
467
482
|
if (r.status !== 0) process.exit(r.status);
|
|
468
483
|
}
|
|
469
484
|
|
|
470
|
-
|
|
485
|
+
/**
|
|
486
|
+
* Generate a detailed JSON report describing property changes between two releases.
|
|
487
|
+
*
|
|
488
|
+
* Looks for `<oldTag>-properties.json` and `<newTag>-properties.json` in
|
|
489
|
+
* `modules/reference/examples`. If both files exist, invokes the external
|
|
490
|
+
* property comparison tool to produce `property-changes-<oldTag>-to-<newTag>.json`
|
|
491
|
+
* in the provided output directory.
|
|
492
|
+
*
|
|
493
|
+
* If either input JSON is missing the function logs a message and returns without
|
|
494
|
+
* error. Any errors from the comparison tool are logged; the function does not
|
|
495
|
+
* throw.
|
|
496
|
+
*
|
|
497
|
+
* @param {string} oldTag - Release tag or identifier for the "old" properties set.
|
|
498
|
+
* @param {string} newTag - Release tag or identifier for the "new" properties set.
|
|
499
|
+
* @param {string} outputDir - Directory where the comparison report will be written.
|
|
500
|
+
*/
|
|
501
|
+
function generatePropertyComparisonReport(oldTag, newTag, outputDir) {
|
|
502
|
+
try {
|
|
503
|
+
console.log(`\nš Generating detailed property comparison report...`);
|
|
504
|
+
|
|
505
|
+
// Look for the property JSON files in modules/reference/examples
|
|
506
|
+
const repoRoot = findRepoRoot();
|
|
507
|
+
const examplesDir = path.join(repoRoot, 'modules', 'reference', 'examples');
|
|
508
|
+
const oldJsonPath = path.join(examplesDir, `${oldTag}-properties.json`);
|
|
509
|
+
const newJsonPath = path.join(examplesDir, `${newTag}-properties.json`);
|
|
510
|
+
|
|
511
|
+
// Check if JSON files exist
|
|
512
|
+
if (!fs.existsSync(oldJsonPath)) {
|
|
513
|
+
console.log(`ā ļø Old properties JSON not found at: ${oldJsonPath}`);
|
|
514
|
+
console.log(` Skipping detailed property comparison.`);
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
if (!fs.existsSync(newJsonPath)) {
|
|
519
|
+
console.log(`ā ļø New properties JSON not found at: ${newJsonPath}`);
|
|
520
|
+
console.log(` Skipping detailed property comparison.`);
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
// Ensure output directory exists (use absolute path)
|
|
525
|
+
const absoluteOutputDir = path.resolve(outputDir);
|
|
526
|
+
fs.mkdirSync(absoluteOutputDir, { recursive: true });
|
|
527
|
+
|
|
528
|
+
// Run the property comparison tool with descriptive filename
|
|
529
|
+
const propertyExtractorDir = path.resolve(__dirname, '../tools/property-extractor');
|
|
530
|
+
const compareScript = path.join(propertyExtractorDir, 'compare-properties.js');
|
|
531
|
+
const reportFilename = `property-changes-${oldTag}-to-${newTag}.json`;
|
|
532
|
+
const reportPath = path.join(absoluteOutputDir, reportFilename);
|
|
533
|
+
const args = [compareScript, oldJsonPath, newJsonPath, oldTag, newTag, absoluteOutputDir, reportFilename];
|
|
534
|
+
|
|
535
|
+
const result = spawnSync('node', args, {
|
|
536
|
+
stdio: 'inherit',
|
|
537
|
+
cwd: propertyExtractorDir
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
if (result.error) {
|
|
541
|
+
console.error(`ā Property comparison failed: ${result.error.message}`);
|
|
542
|
+
} else if (result.status !== 0) {
|
|
543
|
+
console.error(`ā Property comparison exited with code: ${result.status}`);
|
|
544
|
+
} else {
|
|
545
|
+
console.log(`ā
Property comparison report saved to: ${reportPath}`);
|
|
546
|
+
}
|
|
547
|
+
} catch (error) {
|
|
548
|
+
console.error(`ā Error generating property comparison: ${error.message}`);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
/**
|
|
553
|
+
* Create a unified diff patch between two temporary directories and clean them up.
|
|
554
|
+
*
|
|
555
|
+
* Ensures both source directories exist, writes a recursive unified diff
|
|
556
|
+
* (changes.patch) to tmp/diffs/<kind>/<oldTag>_to_<newTag>/, and removes the
|
|
557
|
+
* provided temporary directories. On missing inputs or if the diff subprocess
|
|
558
|
+
* fails to spawn, the process exits with a non-zero status.
|
|
559
|
+
*
|
|
560
|
+
* @param {string} kind - Logical category for the diff (e.g., "metrics" or "rpk"); used in the output path.
|
|
561
|
+
* @param {string} oldTag - Identifier for the "old" version (used in the output path).
|
|
562
|
+
* @param {string} newTag - Identifier for the "new" version (used in the output path).
|
|
563
|
+
* @param {string} oldTempDir - Path to the existing temporary directory containing the old output; must exist.
|
|
564
|
+
* @param {string} newTempDir - Path to the existing temporary directory containing the new output; must exist.
|
|
565
|
+
*/
|
|
471
566
|
function diffDirs(kind, oldTag, newTag, oldTempDir, newTempDir) {
|
|
567
|
+
// Backwards compatibility: if temp directories not provided, use autogenerated paths
|
|
568
|
+
if (!oldTempDir) {
|
|
569
|
+
oldTempDir = path.join('autogenerated', oldTag, kind);
|
|
570
|
+
}
|
|
571
|
+
if (!newTempDir) {
|
|
572
|
+
newTempDir = path.join('autogenerated', newTag, kind);
|
|
573
|
+
}
|
|
574
|
+
|
|
472
575
|
const diffDir = path.join('tmp', 'diffs', kind, `${oldTag}_to_${newTag}`);
|
|
473
|
-
const patch = path.join(diffDir, 'changes.patch');
|
|
474
576
|
|
|
475
577
|
if (!fs.existsSync(oldTempDir)) {
|
|
476
578
|
console.error(`ā Cannot diff: missing ${oldTempDir}`);
|
|
@@ -483,6 +585,8 @@ function diffDirs(kind, oldTag, newTag, oldTempDir, newTempDir) {
|
|
|
483
585
|
|
|
484
586
|
fs.mkdirSync(diffDir, { recursive: true });
|
|
485
587
|
|
|
588
|
+
// Generate traditional patch for metrics and rpk
|
|
589
|
+
const patch = path.join(diffDir, 'changes.patch');
|
|
486
590
|
const cmd = `diff -ru "${oldTempDir}" "${newTempDir}" > "${patch}" || true`;
|
|
487
591
|
const res = spawnSync(cmd, { stdio: 'inherit', shell: true });
|
|
488
592
|
|
|
@@ -492,9 +596,35 @@ function diffDirs(kind, oldTag, newTag, oldTempDir, newTempDir) {
|
|
|
492
596
|
}
|
|
493
597
|
console.log(`ā
Wrote patch: ${patch}`);
|
|
494
598
|
|
|
495
|
-
//
|
|
496
|
-
|
|
497
|
-
|
|
599
|
+
// Safety guard: only clean up directories that are explicitly passed as temp directories
|
|
600
|
+
// For backwards compatibility with autogenerated paths, don't clean up automatically
|
|
601
|
+
const tmpRoot = path.resolve('tmp') + path.sep;
|
|
602
|
+
const workspaceRoot = path.resolve('.') + path.sep;
|
|
603
|
+
|
|
604
|
+
// Only clean up if directories were explicitly provided as temp directories
|
|
605
|
+
// (indicated by having all 5 parameters) and they're in the tmp/ directory
|
|
606
|
+
const explicitTempDirs = arguments.length >= 5;
|
|
607
|
+
|
|
608
|
+
if (explicitTempDirs) {
|
|
609
|
+
[oldTempDir, newTempDir].forEach(dirPath => {
|
|
610
|
+
const resolvedPath = path.resolve(dirPath) + path.sep;
|
|
611
|
+
const isInTmp = resolvedPath.startsWith(tmpRoot);
|
|
612
|
+
const isInWorkspace = resolvedPath.startsWith(workspaceRoot);
|
|
613
|
+
|
|
614
|
+
if (isInWorkspace && isInTmp) {
|
|
615
|
+
try {
|
|
616
|
+
fs.rmSync(dirPath, { recursive: true, force: true });
|
|
617
|
+
console.log(`š§¹ Cleaned up temporary directory: ${dirPath}`);
|
|
618
|
+
} catch (err) {
|
|
619
|
+
console.warn(`ā ļø Warning: Could not clean up directory ${dirPath}: ${err.message}`);
|
|
620
|
+
}
|
|
621
|
+
} else {
|
|
622
|
+
console.log(`ā¹ļø Skipping cleanup of directory outside tmp/: ${dirPath}`);
|
|
623
|
+
}
|
|
624
|
+
});
|
|
625
|
+
} else {
|
|
626
|
+
console.log(`ā¹ļø Using autogenerated directories - skipping cleanup for safety`);
|
|
627
|
+
}
|
|
498
628
|
}
|
|
499
629
|
|
|
500
630
|
automation
|
|
@@ -546,6 +676,7 @@ automation
|
|
|
546
676
|
.description('Generate RPCN connector docs and diff changes since the last version')
|
|
547
677
|
.option('-d, --data-dir <path>', 'Directory where versioned connect JSON files live', path.resolve(process.cwd(), 'docs-data'))
|
|
548
678
|
.option('--old-data <path>', 'Optional override for old data file (for diff)')
|
|
679
|
+
.option('--update-whats-new', 'Update whats-new.adoc with new section from diff JSON')
|
|
549
680
|
.option('-f, --fetch-connectors', 'Fetch latest connector data using rpk')
|
|
550
681
|
.option('-m, --draft-missing', 'Generate full-doc drafts for connectors missing in output')
|
|
551
682
|
.option('--csv <path>', 'Path to connector metadata CSV file', 'internal/plugins/info.csv')
|
|
@@ -553,7 +684,9 @@ automation
|
|
|
553
684
|
.option('--template-intro <path>', 'Intro section partial template', path.resolve(__dirname, '../tools/redpanda-connect/templates/intro.hbs'))
|
|
554
685
|
.option('--template-fields <path>', 'Fields section partial template', path.resolve(__dirname, '../tools/redpanda-connect/templates/fields-partials.hbs'))
|
|
555
686
|
.option('--template-examples <path>', 'Examples section partial template', path.resolve(__dirname, '../tools/redpanda-connect/templates/examples-partials.hbs'))
|
|
687
|
+
.option('--template-bloblang <path>', 'Custom Handlebars template for bloblang function/method partials')
|
|
556
688
|
.option('--overrides <path>', 'Optional JSON file with overrides')
|
|
689
|
+
.option('--include-bloblang', 'Include Bloblang functions and methods in generation')
|
|
557
690
|
.action(async (options) => {
|
|
558
691
|
requireTool('rpk', {
|
|
559
692
|
versionFlag: '--version',
|
|
@@ -614,7 +747,9 @@ automation
|
|
|
614
747
|
templateIntro: options.templateIntro,
|
|
615
748
|
templateFields: options.templateFields,
|
|
616
749
|
templateExamples: options.templateExamples,
|
|
617
|
-
|
|
750
|
+
templateBloblang: options.templateBloblang,
|
|
751
|
+
writeFullDrafts: false,
|
|
752
|
+
includeBloblang: !!options.includeBloblang
|
|
618
753
|
});
|
|
619
754
|
partialsWritten = result.partialsWritten;
|
|
620
755
|
partialFiles = result.partialFiles;
|
|
@@ -696,10 +831,13 @@ automation
|
|
|
696
831
|
}
|
|
697
832
|
|
|
698
833
|
let oldIndex = {};
|
|
834
|
+
let oldVersion = null;
|
|
699
835
|
if (options.oldData && fs.existsSync(options.oldData)) {
|
|
700
836
|
oldIndex = JSON.parse(fs.readFileSync(options.oldData, 'utf8'));
|
|
837
|
+
const m = options.oldData.match(/connect-([\d.]+)\.json$/);
|
|
838
|
+
if (m) oldVersion = m[1];
|
|
701
839
|
} else {
|
|
702
|
-
|
|
840
|
+
oldVersion = getAntoraValue('asciidoc.attributes.latest-connect-version');
|
|
703
841
|
if (oldVersion) {
|
|
704
842
|
const oldPath = path.join(dataDir, `connect-${oldVersion}.json`);
|
|
705
843
|
if (fs.existsSync(oldPath)) {
|
|
@@ -711,6 +849,21 @@ automation
|
|
|
711
849
|
const newIndex = JSON.parse(fs.readFileSync(dataFile, 'utf8'));
|
|
712
850
|
printDeltaReport(oldIndex, newIndex);
|
|
713
851
|
|
|
852
|
+
// Generate JSON diff file for whats-new.adoc
|
|
853
|
+
const { generateConnectorDiffJson } = require('../tools/redpanda-connect/report-delta.js');
|
|
854
|
+
const diffJson = generateConnectorDiffJson(
|
|
855
|
+
oldIndex,
|
|
856
|
+
newIndex,
|
|
857
|
+
{
|
|
858
|
+
oldVersion: oldVersion || '',
|
|
859
|
+
newVersion,
|
|
860
|
+
timestamp
|
|
861
|
+
}
|
|
862
|
+
);
|
|
863
|
+
const diffPath = path.join(dataDir, `connect-diff-${(oldVersion || 'unknown')}_to_${newVersion}.json`);
|
|
864
|
+
fs.writeFileSync(diffPath, JSON.stringify(diffJson, null, 2), 'utf8');
|
|
865
|
+
console.log(`ā
Connector diff JSON written to: ${diffPath}`);
|
|
866
|
+
|
|
714
867
|
function logCollapsed(label, filesArray, maxToShow = 10) {
|
|
715
868
|
console.log(` ⢠${label}: ${filesArray.length} total`);
|
|
716
869
|
const sample = filesArray.slice(0, maxToShow);
|
|
@@ -742,6 +895,135 @@ automation
|
|
|
742
895
|
logCollapsed('Draft files', draftFiles, 5);
|
|
743
896
|
}
|
|
744
897
|
|
|
898
|
+
// Optionally update whats-new.adoc
|
|
899
|
+
if (options.updateWhatsNew) {
|
|
900
|
+
try {
|
|
901
|
+
const whatsNewPath = path.join(findRepoRoot(), 'modules/get-started/pages/whats-new.adoc');
|
|
902
|
+
if (!fs.existsSync(whatsNewPath)) {
|
|
903
|
+
console.error(`ā Unable to update release notes: 'whats-new.adoc' was not found at: ${whatsNewPath}\nPlease ensure this file exists and is tracked in your repository.`);
|
|
904
|
+
return;
|
|
905
|
+
}
|
|
906
|
+
// Find the diff JSON file we just wrote
|
|
907
|
+
const diffPath = path.join(dataDir, `connect-diff-${(oldVersion || 'unknown')}_to_${newVersion}.json`);
|
|
908
|
+
if (!fs.existsSync(diffPath)) {
|
|
909
|
+
console.error(`ā Unable to update release notes: The connector diff JSON was not found at: ${diffPath}\nPlease ensure the diff was generated successfully before updating release notes.`);
|
|
910
|
+
return;
|
|
911
|
+
}
|
|
912
|
+
let diff;
|
|
913
|
+
try {
|
|
914
|
+
diff = JSON.parse(fs.readFileSync(diffPath, 'utf8'));
|
|
915
|
+
} catch (jsonErr) {
|
|
916
|
+
console.error(`ā Unable to parse connector diff JSON at ${diffPath}: ${jsonErr.message}\nPlease check the file for syntax errors or corruption.`);
|
|
917
|
+
return;
|
|
918
|
+
}
|
|
919
|
+
let whatsNewContent;
|
|
920
|
+
try {
|
|
921
|
+
whatsNewContent = fs.readFileSync(whatsNewPath, 'utf8');
|
|
922
|
+
} catch (readErr) {
|
|
923
|
+
console.error(`ā Unable to read whats-new.adoc at ${whatsNewPath}: ${readErr.message}\nPlease check file permissions and try again.`);
|
|
924
|
+
return;
|
|
925
|
+
}
|
|
926
|
+
const whatsNew = whatsNewContent;
|
|
927
|
+
// Regex to find section for this version
|
|
928
|
+
const versionTitle = `== Version ${diff.comparison.newVersion}`;
|
|
929
|
+
const versionRe = new RegExp(`^== Version ${diff.comparison.newVersion.replace(/[-.]/g, '\\$&')}(?:\\r?\\n|$)`, 'm');
|
|
930
|
+
const match = versionRe.exec(whatsNew);
|
|
931
|
+
let startIdx = match ? match.index : -1;
|
|
932
|
+
let endIdx = -1;
|
|
933
|
+
if (startIdx !== -1) {
|
|
934
|
+
// Find the start of the next version section
|
|
935
|
+
const rest = whatsNew.slice(startIdx + 1);
|
|
936
|
+
const nextMatch = /^== Version /m.exec(rest);
|
|
937
|
+
endIdx = nextMatch ? startIdx + 1 + nextMatch.index : whatsNew.length;
|
|
938
|
+
}
|
|
939
|
+
// Compose new section
|
|
940
|
+
let section = `\n== Version ${diff.comparison.newVersion}\n\n=== Component updates\n\n`;
|
|
941
|
+
// Add link to full release notes for this connector version after version heading and before component updates
|
|
942
|
+
let releaseNotesLink = '';
|
|
943
|
+
if (diff.comparison && diff.comparison.newVersion) {
|
|
944
|
+
releaseNotesLink = `link:https://github.com/redpanda-data/connect/releases/tag/v${diff.comparison.newVersion}[See the full release notes^].\n\n`;
|
|
945
|
+
}
|
|
946
|
+
section = `\n== Version ${diff.comparison.newVersion}\n\n${releaseNotesLink}=== Component updates\n\n`;
|
|
947
|
+
// New components
|
|
948
|
+
if (diff.details.newComponents && diff.details.newComponents.length) {
|
|
949
|
+
section += 'This release adds the following new components:\n\n';
|
|
950
|
+
// Group by type
|
|
951
|
+
const byType = {};
|
|
952
|
+
for (const comp of diff.details.newComponents) {
|
|
953
|
+
if (!byType[comp.type]) byType[comp.type] = [];
|
|
954
|
+
byType[comp.type].push(comp);
|
|
955
|
+
}
|
|
956
|
+
for (const [type, comps] of Object.entries(byType)) {
|
|
957
|
+
section += `* ${type.charAt(0).toUpperCase() + type.slice(1)}:\n`;
|
|
958
|
+
for (const comp of comps) {
|
|
959
|
+
section += `** xref:components:${type}/${comp.name}.adoc[\`${comp.name}\`]`;
|
|
960
|
+
if (comp.status) section += ` (${comp.status})`;
|
|
961
|
+
if (comp.description) section += `: ${comp.description}`;
|
|
962
|
+
section += '\n';
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
// New fields
|
|
968
|
+
if (diff.details.newFields && diff.details.newFields.length) {
|
|
969
|
+
section += '\nThis release adds support for the following new fields:\n\n';
|
|
970
|
+
// Group new fields by component type
|
|
971
|
+
const fieldsByType = {};
|
|
972
|
+
for (const field of diff.details.newFields) {
|
|
973
|
+
// component: "inputs:kafka", field: "timely_nacks_maximum_wait"
|
|
974
|
+
const [type, compName] = field.component.split(':');
|
|
975
|
+
if (!fieldsByType[type]) fieldsByType[type] = [];
|
|
976
|
+
fieldsByType[type].push({
|
|
977
|
+
compName,
|
|
978
|
+
field: field.field,
|
|
979
|
+
description: field.description || '',
|
|
980
|
+
});
|
|
981
|
+
}
|
|
982
|
+
for (const [type, fields] of Object.entries(fieldsByType)) {
|
|
983
|
+
section += `* ${type.charAt(0).toUpperCase() + type.slice(1)} components\n`;
|
|
984
|
+
// Group by component name
|
|
985
|
+
const byComp = {};
|
|
986
|
+
for (const f of fields) {
|
|
987
|
+
if (!byComp[f.compName]) byComp[f.compName] = [];
|
|
988
|
+
byComp[f.compName].push(f);
|
|
989
|
+
}
|
|
990
|
+
for (const [comp, compFields] of Object.entries(byComp)) {
|
|
991
|
+
section += `** xref:components:${type}/${comp}.adoc['${comp}']`;
|
|
992
|
+
if (compFields.length === 1) {
|
|
993
|
+
const f = compFields[0];
|
|
994
|
+
section += `: xref:components:${type}/${comp}.adoc#${f.field}['${f.field}']`;
|
|
995
|
+
if (f.description) section += ` - ${f.description}`;
|
|
996
|
+
section += '\n';
|
|
997
|
+
} else {
|
|
998
|
+
section += '\n';
|
|
999
|
+
for (const f of compFields) {
|
|
1000
|
+
section += `*** xref:components:${type}/${comp}.adoc#${f.field}['${f.field}']`;
|
|
1001
|
+
if (f.description) section += ` - ${f.description}`;
|
|
1002
|
+
section += '\n';
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
let updated;
|
|
1009
|
+
if (startIdx !== -1) {
|
|
1010
|
+
// Replace the existing section
|
|
1011
|
+
updated = whatsNew.slice(0, startIdx) + section + '\n' + whatsNew.slice(endIdx);
|
|
1012
|
+
console.log(`ā»ļø whats-new.adoc: replaced section for Version ${diff.comparison.newVersion}`);
|
|
1013
|
+
} else {
|
|
1014
|
+
// Insert above first version heading
|
|
1015
|
+
const versionHeading = /^== Version /m;
|
|
1016
|
+
const firstMatch = versionHeading.exec(whatsNew);
|
|
1017
|
+
let insertIdx = firstMatch ? firstMatch.index : 0;
|
|
1018
|
+
updated = whatsNew.slice(0, insertIdx) + section + '\n' + whatsNew.slice(insertIdx);
|
|
1019
|
+
console.log(`ā
whats-new.adoc updated with Version ${diff.comparison.newVersion}`);
|
|
1020
|
+
}
|
|
1021
|
+
fs.writeFileSync(whatsNewPath, updated, 'utf8');
|
|
1022
|
+
} catch (err) {
|
|
1023
|
+
console.error(`ā Failed to update whats-new.adoc: ${err.message}`);
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
|
|
745
1027
|
console.log('\nš Summary:');
|
|
746
1028
|
console.log(` ⢠Run time: ${timestamp}`);
|
|
747
1029
|
console.log(` ⢠Version used: ${newVersion}`);
|
|
@@ -755,20 +1037,47 @@ automation
|
|
|
755
1037
|
.option('--diff <oldTag>', 'Also diff autogenerated properties from <oldTag> ā <tag>')
|
|
756
1038
|
.option('--overrides <path>', 'Optional JSON file with property description overrides')
|
|
757
1039
|
.option('--output-dir <dir>', 'Where to write all generated files', 'modules/reference')
|
|
1040
|
+
.option('--cloud-support', 'Enable cloud support metadata by fetching configuration from the cloudv2 repository (requires GITHUB_TOKEN, GH_TOKEN, or REDPANDA_GITHUB_TOKEN)')
|
|
758
1041
|
.option('--template-property-page <path>', 'Custom Handlebars template for property page layout')
|
|
759
1042
|
.option('--template-property <path>', 'Custom Handlebars template for individual property sections')
|
|
1043
|
+
.option('--template-topic-property <path>', 'Custom Handlebars template for individual topic property sections')
|
|
760
1044
|
.option('--template-deprecated <path>', 'Custom Handlebars template for deprecated properties page')
|
|
761
1045
|
.option('--template-deprecated-property <path>', 'Custom Handlebars template for individual deprecated property sections')
|
|
762
1046
|
.action((options) => {
|
|
763
1047
|
verifyPropertyDependencies();
|
|
764
1048
|
|
|
1049
|
+
// Validate cloud support dependencies if requested
|
|
1050
|
+
if (options.cloudSupport) {
|
|
1051
|
+
console.log('š Validating cloud support dependencies...');
|
|
1052
|
+
|
|
1053
|
+
// Check for GITHUB_TOKEN, GH_TOKEN, or REDPANDA_GITHUB_TOKEN
|
|
1054
|
+
const token = process.env.GITHUB_TOKEN || process.env.GH_TOKEN || process.env.REDPANDA_GITHUB_TOKEN;
|
|
1055
|
+
if (!token) {
|
|
1056
|
+
console.error('ā Cloud support requires GITHUB_TOKEN, GH_TOKEN, or REDPANDA_GITHUB_TOKEN environment variable');
|
|
1057
|
+
console.error(' Set up GitHub token:');
|
|
1058
|
+
console.error(' 1. Go to https://github.com/settings/tokens');
|
|
1059
|
+
console.error(' 2. Generate token with "repo" scope');
|
|
1060
|
+
console.error(' 3. Set: export GITHUB_TOKEN=your_token_here');
|
|
1061
|
+
console.error(' Or: export GH_TOKEN=your_token_here');
|
|
1062
|
+
console.error(' Or: export REDPANDA_GITHUB_TOKEN=your_token_here');
|
|
1063
|
+
process.exit(1);
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
console.log('š¦ Cloud support enabled - Python dependencies will be validated during execution');
|
|
1067
|
+
if (process.env.VIRTUAL_ENV) {
|
|
1068
|
+
console.log(` Using virtual environment: ${process.env.VIRTUAL_ENV}`);
|
|
1069
|
+
}
|
|
1070
|
+
console.log(' Required packages: pyyaml, requests');
|
|
1071
|
+
console.log('ā
GitHub token validated');
|
|
1072
|
+
}
|
|
1073
|
+
|
|
765
1074
|
const newTag = options.tag;
|
|
766
1075
|
const oldTag = options.diff;
|
|
767
1076
|
const overridesPath = options.overrides;
|
|
768
1077
|
const outputDir = options.outputDir;
|
|
769
1078
|
const cwd = path.resolve(__dirname, '../tools/property-extractor');
|
|
770
1079
|
|
|
771
|
-
const make = (tag, overrides, templates = {}, outputDir = 'modules/reference/', tempDir = null) => {
|
|
1080
|
+
const make = (tag, overrides, templates = {}, outputDir = 'modules/reference/', tempDir = null, cloudSupport = false) => {
|
|
772
1081
|
console.log(`ā³ Building property docs for ${tag}${tempDir ? ' (for diff)' : ''}ā¦`);
|
|
773
1082
|
const args = ['build', `TAG=${tag}`];
|
|
774
1083
|
|
|
@@ -777,12 +1086,18 @@ automation
|
|
|
777
1086
|
if (overrides) {
|
|
778
1087
|
env.OVERRIDES = path.resolve(overrides);
|
|
779
1088
|
}
|
|
1089
|
+
if (cloudSupport) {
|
|
1090
|
+
env.CLOUD_SUPPORT = '1';
|
|
1091
|
+
}
|
|
780
1092
|
if (templates.propertyPage) {
|
|
781
1093
|
env.TEMPLATE_PROPERTY_PAGE = path.resolve(templates.propertyPage);
|
|
782
1094
|
}
|
|
783
1095
|
if (templates.property) {
|
|
784
1096
|
env.TEMPLATE_PROPERTY = path.resolve(templates.property);
|
|
785
1097
|
}
|
|
1098
|
+
if (templates.topicProperty) {
|
|
1099
|
+
env.TEMPLATE_TOPIC_PROPERTY = path.resolve(templates.topicProperty);
|
|
1100
|
+
}
|
|
786
1101
|
if (templates.deprecated) {
|
|
787
1102
|
env.TEMPLATE_DEPRECATED = path.resolve(templates.deprecated);
|
|
788
1103
|
}
|
|
@@ -791,13 +1106,9 @@ automation
|
|
|
791
1106
|
}
|
|
792
1107
|
|
|
793
1108
|
if (tempDir) {
|
|
794
|
-
// For diff purposes, generate to temporary directory
|
|
795
|
-
env.OUTPUT_ASCIIDOC_DIR = path.resolve(tempDir);
|
|
796
1109
|
env.OUTPUT_JSON_DIR = path.resolve(tempDir, 'examples');
|
|
797
1110
|
env.OUTPUT_AUTOGENERATED_DIR = path.resolve(tempDir);
|
|
798
1111
|
} else {
|
|
799
|
-
// Normal generation - go directly to final destination
|
|
800
|
-
// Let Makefile calculate OUTPUT_ASCIIDOC_DIR as OUTPUT_AUTOGENERATED_DIR/pages
|
|
801
1112
|
env.OUTPUT_JSON_DIR = path.resolve(outputDir, 'examples');
|
|
802
1113
|
env.OUTPUT_AUTOGENERATED_DIR = path.resolve(outputDir);
|
|
803
1114
|
}
|
|
@@ -814,34 +1125,22 @@ automation
|
|
|
814
1125
|
const templates = {
|
|
815
1126
|
propertyPage: options.templatePropertyPage,
|
|
816
1127
|
property: options.templateProperty,
|
|
1128
|
+
topicProperty: options.templateTopicProperty,
|
|
817
1129
|
deprecated: options.templateDeprecated,
|
|
818
1130
|
deprecatedProperty: options.templateDeprecatedProperty
|
|
819
1131
|
};
|
|
820
1132
|
|
|
821
|
-
let oldTempDir = null;
|
|
822
|
-
let newTempDir = null;
|
|
823
|
-
|
|
824
1133
|
if (oldTag) {
|
|
825
|
-
// Generate old version to
|
|
826
|
-
|
|
827
|
-
fs.mkdirSync(oldTempDir, { recursive: true });
|
|
828
|
-
make(oldTag, overridesPath, templates, outputDir, oldTempDir);
|
|
1134
|
+
// Generate old version directly to final destination so its JSON is available for comparison
|
|
1135
|
+
make(oldTag, overridesPath, templates, outputDir, null, options.cloudSupport);
|
|
829
1136
|
}
|
|
830
1137
|
|
|
1138
|
+
// Generate new version to final destination
|
|
1139
|
+
make(newTag, overridesPath, templates, outputDir, null, options.cloudSupport);
|
|
1140
|
+
|
|
831
1141
|
if (oldTag) {
|
|
832
|
-
// Generate
|
|
833
|
-
|
|
834
|
-
fs.mkdirSync(newTempDir, { recursive: true });
|
|
835
|
-
make(newTag, overridesPath, templates, outputDir, newTempDir);
|
|
836
|
-
|
|
837
|
-
// Then generate new version to final destination
|
|
838
|
-
make(newTag, overridesPath, templates, outputDir);
|
|
839
|
-
|
|
840
|
-
// Compare the temporary directories
|
|
841
|
-
diffDirs('properties', oldTag, newTag, oldTempDir, newTempDir);
|
|
842
|
-
} else {
|
|
843
|
-
// No diff requested, just generate to final destination
|
|
844
|
-
make(newTag, overridesPath, templates, outputDir);
|
|
1142
|
+
// Generate property comparison report using the JSON files now in modules/reference/examples
|
|
1143
|
+
generatePropertyComparisonReport(oldTag, newTag, 'modules/reference');
|
|
845
1144
|
}
|
|
846
1145
|
|
|
847
1146
|
process.exit(0);
|
|
@@ -1050,9 +1349,9 @@ automation
|
|
|
1050
1349
|
const { generateCloudRegions } = require('../tools/cloud-regions/generate-cloud-regions.js');
|
|
1051
1350
|
|
|
1052
1351
|
try {
|
|
1053
|
-
const token = process.env.GITHUB_TOKEN || process.env.GH_TOKEN;
|
|
1352
|
+
const token = process.env.GITHUB_TOKEN || process.env.GH_TOKEN || process.env.REDPANDA_GITHUB_TOKEN;
|
|
1054
1353
|
if (!token) {
|
|
1055
|
-
throw new Error('GITHUB_TOKEN environment variable is required to fetch from private cloudv2-infra repo.');
|
|
1354
|
+
throw new Error('GITHUB_TOKEN, GH_TOKEN, or REDPANDA_GITHUB_TOKEN environment variable is required to fetch from private cloudv2-infra repo.');
|
|
1056
1355
|
}
|
|
1057
1356
|
const fmt = (options.format || 'md').toLowerCase();
|
|
1058
1357
|
let templatePath = undefined;
|
package/package.json
CHANGED
|
@@ -1,17 +1,5 @@
|
|
|
1
1
|
.PHONY: build venv clean redpanda-git treesitter generate-docs check
|
|
2
2
|
|
|
3
|
-
# --- Main build: venv, fetch code, build parser, extract & docgen ---
|
|
4
|
-
build: venv redpanda-git treesitter
|
|
5
|
-
@echo "š§ Building with Redpanda tag: $(TAG)"
|
|
6
|
-
@mkdir -p $(TOOL_ROOT)/gen
|
|
7
|
-
@cd $(TOOL_ROOT) && \
|
|
8
|
-
$(PYTHON) -W ignore::FutureWarning property_extractor.py \
|
|
9
|
-
--recursive \
|
|
10
|
-
--path $(REDPANDA_SRC) \
|
|
11
|
-
--output gen/properties-output.json
|
|
12
|
-
@echo "ā
Cluster properties JSON generated at $(TOOL_ROOT)/gen/properties-output.json"
|
|
13
|
-
@$(MAKE) generate-docs
|
|
14
|
-
|
|
15
3
|
# Default tag (can be overridden via `make TAG=v25.1.1`)
|
|
16
4
|
TAG ?= dev
|
|
17
5
|
|
|
@@ -51,12 +39,13 @@ build: venv redpanda-git treesitter
|
|
|
51
39
|
exit 1; \
|
|
52
40
|
fi
|
|
53
41
|
@cd $(TOOL_ROOT) && \
|
|
54
|
-
$(PYTHON) -W ignore
|
|
42
|
+
$(PYTHON) -W ignore property_extractor.py \
|
|
55
43
|
--recursive \
|
|
56
44
|
--path $(REDPANDA_SRC) \
|
|
57
45
|
--output gen/properties-output.json \
|
|
58
46
|
--enhanced-output gen/$(TAG)-properties.json \
|
|
59
|
-
$(if $(OVERRIDES),$(if $(shell [ -f "$(OVERRIDES)" ] && echo exists),--overrides $(OVERRIDES),),)
|
|
47
|
+
$(if $(OVERRIDES),$(if $(shell [ -f "$(OVERRIDES)" ] && echo exists),--overrides $(OVERRIDES),),) \
|
|
48
|
+
$(if $(CLOUD_SUPPORT),--cloud-support,)
|
|
60
49
|
@echo "ā
JSON generated at $(TOOL_ROOT)/gen/properties-output.json"
|
|
61
50
|
@echo "ā
Enhanced JSON (with overrides) generated at $(TOOL_ROOT)/gen/$(TAG)-properties.json"
|
|
62
51
|
@$(MAKE) generate-docs
|
|
@@ -66,11 +55,12 @@ venv: $(TOOL_ROOT)/requirements.txt
|
|
|
66
55
|
@if [ ! -d "$(VENV)" ]; then \
|
|
67
56
|
echo "š Creating virtual environment in $(VENV)..."; \
|
|
68
57
|
python3 -m venv $(VENV); \
|
|
69
|
-
$(VENV)/bin/pip install --upgrade pip --quiet; \
|
|
70
|
-
$(VENV)/bin/pip install --no-cache-dir -r $<; \
|
|
71
58
|
else \
|
|
72
59
|
echo "š Virtual environment already exists at $(VENV)"; \
|
|
73
|
-
fi
|
|
60
|
+
fi; \
|
|
61
|
+
echo "š Upgrading pip and installing requirements..."; \
|
|
62
|
+
$(VENV)/bin/pip install --upgrade pip --quiet; \
|
|
63
|
+
$(VENV)/bin/pip install --no-cache-dir -r $<;
|
|
74
64
|
|
|
75
65
|
# --- Clean out all generated state ---
|
|
76
66
|
clean:
|
|
@@ -106,7 +96,7 @@ treesitter:
|
|
|
106
96
|
git fetch --tags -q && \
|
|
107
97
|
git checkout -q v0.20.5
|
|
108
98
|
@echo "š§ Generating parser in $(TREESITTER_DIR)ā¦"
|
|
109
|
-
@cd "$(TREESITTER_DIR)" && npm install --silent && $(TREE_SITTER) generate
|
|
99
|
+
@cd "$(TREESITTER_DIR)" && export CFLAGS="-Wno-unused-but-set-variable" && npm install --silent && export CFLAGS="-Wno-unused-but-set-variable" && $(TREE_SITTER) generate
|
|
110
100
|
|
|
111
101
|
# --- Install Node.js dependencies for Handlebars ---
|
|
112
102
|
node-deps:
|