@redpanda-data/docs-extensions-and-macros 4.9.0 → 4.10.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 CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  const { execSync, spawnSync } = require('child_process');
4
4
  const os = require('os');
5
- const { Command } = require('commander');
5
+ const { Command, Option } = require('commander');
6
6
  const path = require('path');
7
7
  const yaml = require('yaml');
8
8
  const fs = require('fs');
@@ -676,6 +676,7 @@ automation
676
676
  .description('Generate RPCN connector docs and diff changes since the last version')
677
677
  .option('-d, --data-dir <path>', 'Directory where versioned connect JSON files live', path.resolve(process.cwd(), 'docs-data'))
678
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')
679
680
  .option('-f, --fetch-connectors', 'Fetch latest connector data using rpk')
680
681
  .option('-m, --draft-missing', 'Generate full-doc drafts for connectors missing in output')
681
682
  .option('--csv <path>', 'Path to connector metadata CSV file', 'internal/plugins/info.csv')
@@ -683,7 +684,9 @@ automation
683
684
  .option('--template-intro <path>', 'Intro section partial template', path.resolve(__dirname, '../tools/redpanda-connect/templates/intro.hbs'))
684
685
  .option('--template-fields <path>', 'Fields section partial template', path.resolve(__dirname, '../tools/redpanda-connect/templates/fields-partials.hbs'))
685
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')
686
688
  .option('--overrides <path>', 'Optional JSON file with overrides')
689
+ .option('--include-bloblang', 'Include Bloblang functions and methods in generation')
687
690
  .action(async (options) => {
688
691
  requireTool('rpk', {
689
692
  versionFlag: '--version',
@@ -744,7 +747,9 @@ automation
744
747
  templateIntro: options.templateIntro,
745
748
  templateFields: options.templateFields,
746
749
  templateExamples: options.templateExamples,
747
- writeFullDrafts: false
750
+ templateBloblang: options.templateBloblang,
751
+ writeFullDrafts: false,
752
+ includeBloblang: !!options.includeBloblang
748
753
  });
749
754
  partialsWritten = result.partialsWritten;
750
755
  partialFiles = result.partialFiles;
@@ -826,10 +831,13 @@ automation
826
831
  }
827
832
 
828
833
  let oldIndex = {};
834
+ let oldVersion = null;
829
835
  if (options.oldData && fs.existsSync(options.oldData)) {
830
836
  oldIndex = JSON.parse(fs.readFileSync(options.oldData, 'utf8'));
837
+ const m = options.oldData.match(/connect-([\d.]+)\.json$/);
838
+ if (m) oldVersion = m[1];
831
839
  } else {
832
- const oldVersion = getAntoraValue('asciidoc.attributes.latest-connect-version');
840
+ oldVersion = getAntoraValue('asciidoc.attributes.latest-connect-version');
833
841
  if (oldVersion) {
834
842
  const oldPath = path.join(dataDir, `connect-${oldVersion}.json`);
835
843
  if (fs.existsSync(oldPath)) {
@@ -841,6 +849,21 @@ automation
841
849
  const newIndex = JSON.parse(fs.readFileSync(dataFile, 'utf8'));
842
850
  printDeltaReport(oldIndex, newIndex);
843
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
+
844
867
  function logCollapsed(label, filesArray, maxToShow = 10) {
845
868
  console.log(` • ${label}: ${filesArray.length} total`);
846
869
  const sample = filesArray.slice(0, maxToShow);
@@ -872,6 +895,135 @@ automation
872
895
  logCollapsed('Draft files', draftFiles, 5);
873
896
  }
874
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
+
875
1027
  console.log('\n📄 Summary:');
876
1028
  console.log(` • Run time: ${timestamp}`);
877
1029
  console.log(` • Version used: ${newVersion}`);
@@ -1388,5 +1540,47 @@ automation
1388
1540
  }
1389
1541
  });
1390
1542
 
1543
+ automation
1544
+ .command('bundle-openapi')
1545
+ .description('Bundle Redpanda OpenAPI fragments for admin and connect APIs into complete OpenAPI 3.1 documents')
1546
+ .requiredOption('-t, --tag <tag>', 'Git tag to check out (e.g., v24.3.2 or 24.3.2 or dev)')
1547
+ .option('--repo <url>', 'Repository URL', 'https://github.com/redpanda-data/redpanda.git')
1548
+ .addOption(new Option('-s, --surface <surface>', 'Which API surface(s) to bundle').choices(['admin', 'connect', 'both']).makeOptionMandatory())
1549
+ .option('--out-admin <path>', 'Output path for admin API', 'admin/redpanda-admin-api.yaml')
1550
+ .option('--out-connect <path>', 'Output path for connect API', 'connect/redpanda-connect-api.yaml')
1551
+ .option('--admin-major <string>', 'Admin API major version', 'v2.0.0')
1552
+ .option('--use-admin-major-version', 'Use admin major version for info.version instead of git tag', false)
1553
+ .option('--quiet', 'Suppress logs', false)
1554
+ .action(async (options) => {
1555
+ // Verify dependencies
1556
+ requireCmd('git', 'Install Git: https://git-scm.com/downloads');
1557
+ requireCmd('buf', 'buf should be automatically available after npm install');
1558
+
1559
+ // Check for OpenAPI bundler using the existing detectBundler function
1560
+ try {
1561
+ const { detectBundler } = require('../tools/bundle-openapi.js');
1562
+ detectBundler(true); // quiet mode to avoid duplicate output
1563
+ } catch (err) {
1564
+ fail(err.message);
1565
+ }
1566
+
1567
+ try {
1568
+ const { bundleOpenAPI } = require('../tools/bundle-openapi.js');
1569
+ await bundleOpenAPI({
1570
+ tag: options.tag,
1571
+ repo: options.repo,
1572
+ surface: options.surface,
1573
+ outAdmin: options.outAdmin,
1574
+ outConnect: options.outConnect,
1575
+ adminMajor: options.adminMajor,
1576
+ useAdminMajorVersion: options.useAdminMajorVersion,
1577
+ quiet: options.quiet
1578
+ });
1579
+ } catch (err) {
1580
+ console.error(`❌ ${err.message}`);
1581
+ process.exit(err.message.includes('Validation failed') ? 2 : 1);
1582
+ }
1583
+ });
1584
+
1391
1585
  programCli.addCommand(automation);
1392
1586
  programCli.parse(process.argv);