@redpanda-data/docs-extensions-and-macros 4.6.0 → 4.6.2

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
@@ -269,7 +269,11 @@ function verifyMetricsDependencies() {
269
269
  // --------------------------------------------------------------------
270
270
  const programCli = new Command();
271
271
 
272
- programCli.name('doc-tools').description('Redpanda Document Automation CLI').version('1.1.0');
272
+ const pkg = require('../package.json');
273
+ programCli
274
+ .name('doc-tools')
275
+ .description('Redpanda Document Automation CLI')
276
+ .version(pkg.version);
273
277
 
274
278
  // Top-level commands.
275
279
  programCli
@@ -542,22 +546,27 @@ automation
542
546
  }
543
547
 
544
548
  if (options.draftMissing) {
545
- console.log('⏳ Drafting missing connectors...');
549
+ console.log('⏳ Drafting missing connectors');
546
550
  try {
547
551
  const connectorList = await parseCSVConnectors(options.csv, console);
548
- const validConnectors = connectorList.filter(row => row.name && row.type);
552
+ const validConnectors = connectorList.filter(r => r.name && r.type);
553
+
554
+ const roots = {
555
+ pages: path.resolve(process.cwd(), 'modules/components/pages'),
556
+ partials:path.resolve(process.cwd(), 'modules/components/partials/components'),
557
+ };
549
558
 
550
- const pagesRoot = path.resolve(process.cwd(), 'modules/components/pages');
559
+ // find any connector that has NO .adoc under pages/TYPEs or partials/TYPEs
551
560
  const allMissing = validConnectors.filter(({ name, type }) => {
552
- if (!name || !type) {
553
- console.warn(`⚠️ Skipping invalid connector entry:`, { name, type });
554
- return false;
555
- }
556
- const expected = path.join(pagesRoot, `${type}s`, `${name}.adoc`);
557
- return !fs.existsSync(expected);
561
+ const relPath = path.join(`${type}s`, `${name}.adoc`);
562
+ const existsInAny = Object.values(roots).some(root =>
563
+ fs.existsSync(path.join(root, relPath))
564
+ );
565
+ return !existsInAny;
558
566
  });
559
567
 
560
- const missingConnectors = allMissing.filter(({ name }) => !name.includes('sql_driver'));
568
+ // still skip sql_driver
569
+ const missingConnectors = allMissing.filter(c => !c.name.includes('sql_driver'));
561
570
 
562
571
  if (missingConnectors.length === 0) {
563
572
  console.log('✅ All connectors (excluding sql_drivers) already have docs—nothing to draft.');
@@ -568,17 +577,20 @@ automation
568
577
  });
569
578
  console.log('');
570
579
 
580
+ // build your filtered JSON as before…
571
581
  const rawData = fs.readFileSync(dataFile, 'utf8');
572
582
  const dataObj = JSON.parse(rawData);
573
-
574
583
  const filteredDataObj = {};
584
+
575
585
  for (const [key, arr] of Object.entries(dataObj)) {
576
586
  if (!Array.isArray(arr)) {
577
587
  filteredDataObj[key] = arr;
578
588
  continue;
579
589
  }
580
590
  filteredDataObj[key] = arr.filter(component =>
581
- missingConnectors.some(m => m.name === component.name && `${m.type}s` === key)
591
+ missingConnectors.some(
592
+ m => m.name === component.name && `${m.type}s` === key
593
+ )
582
594
  );
583
595
  }
584
596
 
@@ -586,12 +598,12 @@ automation
586
598
  fs.writeFileSync(tempDataPath, JSON.stringify(filteredDataObj, null, 2), 'utf8');
587
599
 
588
600
  const draftResult = await generateRpcnConnectorDocs({
589
- data: tempDataPath,
590
- overrides: options.overrides,
591
- template: options.templateMain,
592
- templateFields: options.templateFields,
593
- templateExamples: options.templateExamples,
594
- templateIntro: options.templateIntro,
601
+ data: tempDataPath,
602
+ overrides: options.overrides,
603
+ template: options.templateMain,
604
+ templateFields: options.templateFields,
605
+ templateExamples:options.templateExamples,
606
+ templateIntro: options.templateIntro,
595
607
  writeFullDrafts: true
596
608
  });
597
609
 
@@ -1,23 +1,34 @@
1
1
  #!/usr/bin/env bash
2
2
  set -euo pipefail
3
+ IFS=$'\n\t'
3
4
 
4
- # Check if Docker is installed and running
5
+ ###############################################################################
6
+ # Pre-flight: Ensure Docker is available and running
7
+ ###############################################################################
5
8
  if ! command -v docker &> /dev/null; then
6
9
  echo "❌ Docker is not installed or not in PATH. Please install Docker to continue."
7
10
  exit 1
8
11
  fi
9
12
 
10
- # Check if Docker daemon is running
11
13
  if ! docker info &> /dev/null; then
12
14
  echo "❌ Docker daemon is not running. Please start Docker to continue."
13
15
  exit 1
14
16
  fi
15
17
 
16
- # Remember where we started so we can always come back
17
- ORIGINAL_PWD="$(pwd)"
18
+ ###############################################################################
19
+ # Load overrides from an optional .env file in the current directory
20
+ ###############################################################################
21
+ if [[ -f .env ]]; then
22
+ # shellcheck disable=SC2046
23
+ export $(grep -Ev '^#' .env | xargs)
24
+ fi
18
25
 
19
- # All "cli-utils…" calls should be relative to this script’s dir
26
+ ###############################################################################
27
+ # Environment setup
28
+ ###############################################################################
29
+ ORIGINAL_PWD="$(pwd)"
20
30
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
31
+ PROJECT_NAME="${PROJECT_NAME:-redpanda_quickstart}"
21
32
 
22
33
  MODE="${1:-metrics}"
23
34
  TAG="${2:-latest}"
@@ -25,16 +36,15 @@ DOCKER_REPO="${3:-redpanda}"
25
36
  CONSOLE_TAG="${4:-latest}"
26
37
  CONSOLE_REPO="${5:-console}"
27
38
 
28
- # if it's an RC tag, switch Docker repo
39
+ # Adjust Docker repo for release candidates
29
40
  shopt -s nocasematch
30
41
  if [[ "$TAG" =~ rc[0-9]+ ]]; then
31
42
  DOCKER_REPO="redpanda-unstable"
32
43
  fi
33
44
  shopt -u nocasematch
34
45
 
35
- if [[ "$TAG" == "latest" ]]; then
36
- MAJOR_MINOR="latest"
37
- else
46
+ MAJOR_MINOR="latest"
47
+ if [[ "$TAG" != "latest" ]]; then
38
48
  MAJOR_MINOR="$(echo "$TAG" | sed -E 's/^v?([0-9]+\.[0-9]+).*$/\1/')"
39
49
  fi
40
50
 
@@ -43,41 +53,43 @@ export REDPANDA_DOCKER_REPO="$DOCKER_REPO"
43
53
  export REDPANDA_CONSOLE_VERSION="$CONSOLE_TAG"
44
54
  export REDPANDA_CONSOLE_DOCKER_REPO="$CONSOLE_REPO"
45
55
 
46
- # Start up the cluster
47
- "$SCRIPT_DIR"/start-cluster.sh "$TAG"
56
+ ###############################################################################
57
+ # Start Redpanda cluster
58
+ ###############################################################################
59
+ "$SCRIPT_DIR/start-cluster.sh" "$TAG"
48
60
 
49
- # Wait for it to settle
61
+ # Wait for the cluster to settle
50
62
  if [[ "$MODE" == "metrics" ]]; then
51
- echo "Waiting 300 seconds for metrics to be available…"
63
+ echo "Waiting 300 seconds for metrics to be available…"
52
64
  sleep 300
53
65
  else
54
- echo "Waiting 30 seconds for cluster to be ready…"
66
+ echo "Waiting 30 seconds for cluster to be ready…"
55
67
  sleep 30
56
68
  fi
57
69
 
58
- # Go back to where we were
59
- cd "$ORIGINAL_PWD"
60
-
61
- # Ensure Python venv (always create under cli-utils/venv)
62
- "$SCRIPT_DIR"/python-venv.sh \
63
- "$SCRIPT_DIR"/venv \
64
- "$SCRIPT_DIR"/../tools/metrics/requirements.txt
70
+ ###############################################################################
71
+ # Python virtual environment setup
72
+ ###############################################################################
73
+ "$SCRIPT_DIR/python-venv.sh" \
74
+ "$SCRIPT_DIR/venv" \
75
+ "$SCRIPT_DIR/../tools/metrics/requirements.txt"
65
76
 
77
+ ###############################################################################
78
+ # Run documentation generator
79
+ ###############################################################################
66
80
  if [[ "$MODE" == "metrics" ]]; then
67
- "$SCRIPT_DIR"/venv/bin/python \
68
- "$SCRIPT_DIR"/../tools/metrics/metrics.py \
69
- "$TAG"
81
+ "$SCRIPT_DIR/venv/bin/python" \
82
+ "$SCRIPT_DIR/../tools/metrics/metrics.py" "$TAG"
70
83
  else
71
- "$SCRIPT_DIR"/venv/bin/python \
72
- "$SCRIPT_DIR"/../tools/gen-rpk-ascii.py \
73
- "$TAG"
84
+ "$SCRIPT_DIR/venv/bin/python" \
85
+ "$SCRIPT_DIR/../tools/gen-rpk-ascii.py" "$TAG"
74
86
  fi
75
87
 
76
88
  echo "✅ $MODE docs generated successfully!"
77
89
 
78
90
  # Tear down the cluster
79
91
  cd "$SCRIPT_DIR"/../docker-compose
80
- docker compose down --volumes
92
+ docker compose -p "$PROJECT_NAME" down --volumes
81
93
 
82
94
  # Return to the original directory
83
- cd "$ORIGINAL_PWD"
95
+ cd "$ORIGINAL_PWD" || exit 1
@@ -1,63 +1,103 @@
1
1
  #!/usr/bin/env bash
2
2
  set -euo pipefail
3
+ IFS=$'\n\t'
3
4
 
4
- # Usage: start-cluster.sh <tag>
5
- TAG="${1:-latest}"
5
+ ###############################################################################
6
+ # Set START_CLUSTER_LOG=/path/to/logfile to capture output
7
+ ###############################################################################
8
+ if [[ -n "${START_CLUSTER_LOG:-}" ]]; then
9
+ exec > >(tee -a "$START_CLUSTER_LOG") 2>&1
10
+ fi
6
11
 
7
- # Where this script lives (cli-utils)
8
- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
12
+ ###############################################################################
13
+ # Prevent concurrent runs with a simple lockfile
14
+ ###############################################################################
15
+ LOCKFILE="/tmp/start-cluster.lock"
16
+ if [[ -e "$LOCKFILE" ]]; then
17
+ echo "❗ Another start-cluster.sh is already running. Remove $LOCKFILE if stale." >&2
18
+ exit 1
19
+ fi
20
+ trap 'rm -f "$LOCKFILE"' EXIT
21
+ touch "$LOCKFILE"
9
22
 
10
- # One level up is the package root, where we expect docker‑compose/
11
- PACKAGE_ROOT="$(cd "$SCRIPT_DIR"/.. && pwd)"
12
- QUICKSTART_DIR="$PACKAGE_ROOT/docker-compose"
23
+ ###############################################################################
24
+ # Input parameters and defaults
25
+ ###############################################################################
26
+ TAG="${1:-${CLUSTER_TAG:-latest}}"
27
+ PROJECT_NAME="${PROJECT_NAME:-redpanda_quickstart}"
13
28
 
14
- # Remember where the user called us from
29
+ ###############################################################################
30
+ # Directory discovery
31
+ ###############################################################################
32
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
33
+ PACKAGE_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
34
+ QUICKSTART_DIR="$PACKAGE_ROOT/docker-compose"
15
35
  CALLER_PWD="$(pwd)"
16
36
 
17
- # Default quickstart version
37
+ ###############################################################################
38
+ # Determine major.minor for quickstart override
39
+ ###############################################################################
18
40
  MAJOR_MINOR="latest"
19
41
  if [[ "$TAG" != "latest" ]]; then
20
42
  MAJOR_MINOR="$(echo "$TAG" | sed -E 's/^v?([0-9]+\.[0-9]+).*$/\1/')"
21
43
  fi
22
44
 
23
- # Conditionally override quickstart dir for >= 25.1
24
45
  if [[ "$MAJOR_MINOR" =~ ^([0-9]+)\.([0-9]+)$ ]]; then
25
46
  major="${BASH_REMATCH[1]}"
26
47
  minor="${BASH_REMATCH[2]}"
27
-
28
48
  if (( major > 25 )) || (( major == 25 && minor >= 1 )); then
29
49
  QUICKSTART_DIR="$PACKAGE_ROOT/docker-compose/25.1"
30
50
  fi
31
51
  fi
32
52
 
33
- # Fetch quickstart into package root if needed
53
+ ###############################################################################
54
+ # Fetch quickstart bundle if missing
55
+ ###############################################################################
34
56
  if [[ ! -d "$QUICKSTART_DIR" ]]; then
35
57
  echo "📥 Fetching Redpanda quickstart for ${MAJOR_MINOR}…"
58
+ url="https://docs.redpanda.com"
36
59
  if [[ "$TAG" == "latest" ]]; then
37
- curl -sSLf --retry 3 https://docs.redpanda.com/redpanda-quickstart.tar.gz \
38
- | tar -C "$PACKAGE_ROOT" -xzf -
60
+ url="$url/redpanda-quickstart.tar.gz"
39
61
  else
40
- curl -sSLf --retry 3 "https://docs.redpanda.com/${MAJOR_MINOR}-redpanda-quickstart.tar.gz" \
41
- | tar -C "$PACKAGE_ROOT" -xzf -
42
- fi
43
-
44
- if [[ ! -d "$QUICKSTART_DIR" ]]; then
45
- echo "❌ Expected '$QUICKSTART_DIR' but none was found after extraction."
46
- exit 1
62
+ url="$url/${MAJOR_MINOR}-redpanda-quickstart.tar.gz"
47
63
  fi
64
+ curl -sSLf --retry 3 "$url" | tar -C "$PACKAGE_ROOT" -xzf -
65
+ [[ -d "$QUICKSTART_DIR" ]] || { echo "❌ Expected '$QUICKSTART_DIR' but none was found."; exit 1; }
48
66
  fi
49
67
 
50
- # Switch into the quickstart dir and (re)start the cluster
51
- cd "$QUICKSTART_DIR"
68
+ ###############################################################################
69
+ # Move into compose directory and clean up existing cluster
70
+ ###############################################################################
71
+ cd "$QUICKSTART_DIR" || { echo "❌ Cannot cd to '$QUICKSTART_DIR'"; exit 1; }
52
72
 
53
- if docker compose ps | grep -q Up; then
54
- echo "🛑 Stopping existing cluster…"
55
- docker compose down --volumes
73
+ if docker compose -p "$PROJECT_NAME" ps -q | grep -q .; then
74
+ echo "🛑 Cleaning up existing cluster…"
75
+ docker compose -p "$PROJECT_NAME" down --volumes
76
+ else
77
+ echo "No running containers to remove for project \"$PROJECT_NAME\"."
56
78
  fi
57
79
 
58
- echo "▶️ Starting Redpanda cluster…"
59
- docker compose up -d
80
+ ###############################################################################
81
+ # Remove globally conflicting containers (dynamic list + legacy minio)
82
+ ###############################################################################
83
+ services=$(docker compose -p "$PROJECT_NAME" config --services 2>/dev/null || true)
84
+ services+=" minio" # ensure legacy /minio container is handled
85
+
86
+ for svc in $services; do
87
+ if docker ps -a --format '{{.Names}}' | grep -wq "$svc"; then
88
+ echo "🧹 Removing existing container: $svc"
89
+ docker rm -f "$svc"
90
+ fi
91
+ done
92
+
93
+ ###############################################################################
94
+ # Start cluster
95
+ ###############################################################################
96
+ echo "▶️ Starting Redpanda cluster (version: ${TAG})…"
97
+ docker compose -p "$PROJECT_NAME" up -d
60
98
 
61
- # Return to original directory
62
- cd "$CALLER_PWD"
99
+ ###############################################################################
100
+ # Return to caller and report success
101
+ ###############################################################################
102
+ cd "$CALLER_PWD" || exit 1
63
103
  echo "✅ Cluster is up (version: ${TAG})"
@@ -82,14 +82,14 @@ module.exports.register = function ({ config }) {
82
82
  }
83
83
 
84
84
  /**
85
- * Translates the parsed CSV data into our expected format.
86
- * If "enterprise" is found in the `support` column, it is replaced with "certified" in the output.
85
+ * Transforms and enriches parsed CSV connector data with normalized fields and documentation URLs.
87
86
  *
88
- * @param {object} parsedData - The CSV data parsed into an object.
89
- * @param {array} pages - The list of pages to map the URLs (used for enrichment with URLs).
90
- * @param {object} logger - The logger used for error handling.
87
+ * Each row is trimmed, mapped to expected output fields, and enriched with documentation URLs for Redpanda Connect and Cloud components if available. The `support` field is normalized, and licensing information is derived. Logs a warning if documentation URLs are missing for non-deprecated, non-SQL driver connectors that indicate cloud support.
91
88
  *
92
- * @returns {array} - The translated and enriched data.
89
+ * @param {object} parsedData - Parsed CSV data containing connector rows.
90
+ * @param {array} pages - Array of page objects used to resolve documentation URLs.
91
+ * @param {object} logger - Logger instance for warning about missing documentation.
92
+ * @returns {array} Array of enriched connector objects with normalized fields and URLs.
93
93
  */
94
94
  function translateCsvData(parsedData, pages, logger) {
95
95
  return parsedData.data.map(row => {
@@ -139,12 +139,17 @@ module.exports.register = function ({ config }) {
139
139
  }
140
140
  }
141
141
 
142
- // Log a warning if neither URL was found (only warn for missing cloud if it should support cloud)
143
- // Ignore sql_driver connectors because they are not real connectors and only used as a utility for grouping sql driver types.
144
- if (!connector.includes('sql_driver') && !redpandaConnectUrl && (!redpandaCloudUrl && is_cloud_supported === 'y')) {
142
+ // Log a warning if neither URL was found and the component is not deprecated
143
+ if (
144
+ deprecated !== 'y' &&
145
+ !connector.includes('sql_driver') &&
146
+ !redpandaConnectUrl &&
147
+ (!redpandaCloudUrl && is_cloud_supported === 'y')
148
+ ) {
145
149
  logger.warn(`Docs missing for: ${connector} of type: ${type}`);
146
150
  }
147
151
 
152
+
148
153
  // Return the translated and enriched row
149
154
  return {
150
155
  connector,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@redpanda-data/docs-extensions-and-macros",
3
- "version": "4.6.0",
3
+ "version": "4.6.2",
4
4
  "description": "Antora extensions and macros developed for Redpanda documentation.",
5
5
  "keywords": [
6
6
  "antora",
@@ -54,6 +54,10 @@ function mergeOverrides(target, overrides) {
54
54
  item[field] = overrideItem[field];
55
55
  }
56
56
  });
57
+ // Copy through selfManagedOnly flag
58
+ if (Object.hasOwn(overrideItem, 'selfManagedOnly')) {
59
+ item.selfManagedOnly = overrideItem.selfManagedOnly;
60
+ }
57
61
  // Recurse for nested children
58
62
  item = mergeOverrides(item, overrideItem);
59
63
  }
@@ -75,6 +79,26 @@ function mergeOverrides(target, overrides) {
75
79
  return target;
76
80
  }
77
81
 
82
+ /**
83
+ * Generates documentation files for RPCN connectors using Handlebars templates.
84
+ *
85
+ * Depending on the {@link writeFullDrafts} flag, generates either partial documentation files for connector fields and examples, or full draft documentation for each connector component. Supports merging override data and skips draft generation for components marked as deprecated.
86
+ *
87
+ * @param {Object} options - Configuration options for documentation generation.
88
+ * @param {string} options.data - Path to the connector data file (JSON or YAML).
89
+ * @param {string} [options.overrides] - Optional path to a JSON file with override data.
90
+ * @param {string} options.template - Path to the main Handlebars template.
91
+ * @param {string} [options.templateIntro] - Path to the intro partial template (used in full draft mode).
92
+ * @param {string} [options.templateFields] - Path to the fields partial template.
93
+ * @param {string} [options.templateExamples] - Path to the examples partial template.
94
+ * @param {boolean} options.writeFullDrafts - If true, generates full draft documentation; otherwise, generates partials.
95
+ * @returns {Promise<Object>} An object summarizing the number and paths of generated partials and drafts.
96
+ *
97
+ * @throws {Error} If reading or parsing input files fails, or if template rendering fails for a component.
98
+ *
99
+ * @remark
100
+ * When generating full drafts, components with a `status` of `'deprecated'` are skipped.
101
+ */
78
102
  async function generateRpcnConnectorDocs(options) {
79
103
  const {
80
104
  data,
@@ -161,7 +185,7 @@ async function generateRpcnConnectorDocs(options) {
161
185
  partialFiles.push(path.relative(process.cwd(), fPath));
162
186
  }
163
187
 
164
- if (examplesOut.trim()) {
188
+ if (examplesOut.trim() && type !== 'bloblang-functions' && type !== 'bloblang-methods') {
165
189
  const ePath = path.join(examplesOutRoot, type, `${name}.adoc`);
166
190
  fs.mkdirSync(path.dirname(ePath), { recursive: true });
167
191
  fs.writeFileSync(ePath, examplesOut);
@@ -171,6 +195,10 @@ async function generateRpcnConnectorDocs(options) {
171
195
  }
172
196
 
173
197
  if (writeFullDrafts) {
198
+ if (String(item.status || '').toLowerCase() === 'deprecated') {
199
+ console.log(`Skipping draft for deprecated component: ${type}/${name}`);
200
+ continue;
201
+ }
174
202
  let content;
175
203
  try {
176
204
  content = compiledTemplate(item);
@@ -1,10 +1,7 @@
1
- 'use strict';
2
-
3
1
  const yaml = require('yaml');
4
2
  const renderYamlList = require('./renderYamlList');
5
3
  const handlebars = require('handlebars');
6
4
 
7
-
8
5
  /**
9
6
  * Renders the children of a configuration object into AsciiDoc.
10
7
  *
@@ -27,120 +24,120 @@ module.exports = function renderConnectFields(children, prefix = '') {
27
24
  prefix = typeof prefix === 'string' ? prefix : '';
28
25
 
29
26
  sorted.forEach(child => {
30
- if (child.is_deprecated) {
31
- return;
27
+ if (child.is_deprecated || !child.name) return;
28
+
29
+ // Normalize type: arrays and unknown-map as object
30
+ let displayType;
31
+ if (child.type === 'string' && child.kind === 'array') {
32
+ displayType = 'array';
33
+ } else if (child.type === 'unknown' && child.kind === 'map') {
34
+ displayType = 'object';
35
+ } else {
36
+ displayType = child.type;
32
37
  }
38
+
39
+ let block = '';
33
40
  const isArray = child.kind === 'array';
34
- if (!child.name) return;
35
41
  const currentPath = prefix
36
42
  ? `${prefix}.${child.name}${isArray ? '[]' : ''}`
37
43
  : `${child.name}${isArray ? '[]' : ''}`;
38
44
 
39
- output += `=== \`${currentPath}\`\n\n`;
45
+ block += `=== \`${currentPath}\`\n\n`;
40
46
 
41
47
  if (child.description) {
42
- output += `${child.description}\n\n`;
48
+ block += `${child.description}\n\n`;
43
49
  }
44
-
45
- if (child.is_secret === true) {
46
- output += `include::redpanda-connect:components:partial$secret_warning.adoc[]\n\n`;
50
+ if (child.is_secret) {
51
+ block += `include::redpanda-connect:components:partial$secret_warning.adoc[]\n\n`;
47
52
  }
48
-
49
53
  if (child.version) {
50
- output += `ifndef::env-cloud[]\nRequires version ${child.version} or later.\nendif::[]\n\n`;
54
+ block += `ifndef::env-cloud[]\nRequires version ${child.version} or later.\nendif::[]\n\n`;
51
55
  }
52
56
 
53
- output += `*Type*: \`${child.type}\`\n\n`;
57
+ block += `*Type*: \`${displayType}\`\n\n`;
54
58
 
55
- if (child.type !== 'object' && child.default !== undefined) {
56
- if (typeof child.default !== 'object') {
57
- const display = child.default === '' ? '""' : String(child.default);
58
- output += `*Default*: \`${display}\`\n\n`;
59
- } else {
59
+ // Default value
60
+ if (child.default !== undefined) {
61
+ // Empty array
62
+ if (Array.isArray(child.default) && child.default.length === 0) {
63
+ block += `*Default*: \`[]\`\n\n`;
64
+ }
65
+ // Empty object
66
+ else if (
67
+ child.default !== null &&
68
+ typeof child.default === 'object' &&
69
+ !Array.isArray(child.default) &&
70
+ Object.keys(child.default).length === 0
71
+ ) {
72
+ block += `*Default*: \`{}\`\n\n`;
73
+ }
74
+ // Complex object/array
75
+ else if (typeof child.default === 'object') {
60
76
  const defYaml = yaml.stringify(child.default).trim();
61
- output += `*Default*:\n[source,yaml]\n----\n${defYaml}\n----\n\n`;
77
+ block += `*Default*:\n[source,yaml]\n----\n${defYaml}\n----\n\n`;
78
+ }
79
+ // Primitive
80
+ else {
81
+ const display = typeof child.default === 'string'
82
+ ? (child.default.startsWith('"') && child.default.endsWith('"')
83
+ ? child.default
84
+ : child.default === ''
85
+ ? '""'
86
+ : child.default)
87
+ : String(child.default);
88
+ block += `*Default*: \`${display}\`\n\n`;
62
89
  }
63
90
  }
64
91
 
65
- if (
66
- child.annotated_options &&
67
- Array.isArray(child.annotated_options) &&
68
- child.annotated_options.length > 0
69
- ) {
70
- output += '[cols="1m,2a"]\n';
71
- output += '|===\n';
72
- output += '|Option |Summary\n\n';
73
- child.annotated_options.forEach(optionPair => {
74
- if (Array.isArray(optionPair) && optionPair.length >= 2) {
75
- output += `|${optionPair[0]}\n|${optionPair[1]}\n\n`;
76
- }
92
+ // Annotated options table
93
+ if (child.annotated_options && child.annotated_options.length) {
94
+ block += `[cols="1m,2a"]\n|===\n|Option |Summary\n\n`;
95
+ child.annotated_options.forEach(([opt, summary]) => {
96
+ block += `|${opt}\n|${summary}\n\n`;
77
97
  });
78
- output += '|===\n\n';
98
+ block += `|===\n\n`;
79
99
  }
80
100
 
81
- if (child.options && Array.isArray(child.options) && child.options.length > 0) {
82
- output += `*Options*: ${child.options.map(opt => `\`${opt}\``).join(', ')}\n\n`;
101
+ // Simple options list
102
+ if (child.options && child.options.length) {
103
+ block += `*Options*: ${child.options.map(opt => `\`${opt}\``).join(', ')}\n\n`;
83
104
  }
84
105
 
106
+ // Examples
85
107
  if (child.examples && child.examples.length) {
86
- output += '[source,yaml]\n----\n';
87
- output += '# Examples:\n';
88
-
89
- if (child.type === 'string') {
90
- if (child.kind === 'array') {
91
- output += renderYamlList(child.name, child.examples);
92
- } else {
93
- child.examples.forEach(example => {
94
- if (typeof example === 'string' && example.includes('\n')) {
95
- output += `${child.name}: |-\n`;
96
- const indentedLines = example.split('\n').map(line => ' ' + line).join('\n');
97
- output += `${indentedLines}\n`;
98
- } else {
99
- output += `${child.name}: ${example}\n`;
100
- }
101
- });
102
- output += '\n';
103
- }
104
- } else if (child.type === 'processor') {
105
- if (child.kind === 'array') {
106
- output += renderYamlList(child.name, child.examples);
107
- } else {
108
- child.examples.forEach(example => {
109
- output += `${child.name}: ${example}\n`;
110
- });
111
- output += '\n';
112
- }
113
- } else if (child.type === 'object') {
114
- if (child.kind === 'array') {
115
- output += renderYamlList(child.name, child.examples);
116
- } else {
117
- child.examples.forEach(example => {
118
- if (typeof example === 'object') {
119
- const snippet = yaml.stringify(example).trim();
120
- const lines = snippet.split('\n');
121
- // Prefix two spaces to every line
122
- const formattedLines = lines.map(line => ' ' + line).join('\n');
123
- output += `${child.name}:\n${formattedLines}\n`;
124
- } else {
125
- output += `${child.name}: ${example}\n`;
126
- }
127
- });
128
- output += '\n';
129
- }
108
+ block += `[source,yaml]\n----\n# Examples:\n`;
109
+ if (child.kind === 'array') {
110
+ block += renderYamlList(child.name, child.examples);
130
111
  } else {
131
112
  child.examples.forEach(example => {
132
- output += `${child.name}: ${example}\n`;
113
+ if (typeof example === 'object') {
114
+ const snippet = yaml.stringify(example).trim();
115
+ block += `${child.name}:\n`;
116
+ block += snippet.split('\n').map(line => ' ' + line).join('\n') + '\n';
117
+ } else if (typeof example === 'string' && example.includes('\n')) {
118
+ block += `${child.name}: |-\n`;
119
+ block += example.split('\n').map(line => ' ' + line).join('\n') + '\n';
120
+ } else {
121
+ // Primitive values
122
+ block += `${child.name}: ${example}\n`;
123
+ }
133
124
  });
134
- output += '\n';
135
125
  }
126
+ block += `----\n\n`;
127
+ }
136
128
 
137
- output += '----\n\n';
129
+ // Nested children
130
+ if (child.children && child.children.length) {
131
+ block += renderConnectFields(child.children, currentPath);
138
132
  }
139
133
 
140
- if (child.children && Array.isArray(child.children) && child.children.length > 0) {
141
- output += renderConnectFields(child.children, currentPath);
134
+ // Cloud guard
135
+ if (child.selfManagedOnly) {
136
+ output += `ifndef::env-cloud[]\n${block}endif::[]\n\n`;
137
+ } else {
138
+ output += block;
142
139
  }
143
140
  });
144
141
 
145
142
  return new handlebars.SafeString(output);
146
- }
143
+ };
@@ -31,34 +31,57 @@ module.exports = function renderLeafField(field, indentLevel) {
31
31
 
32
32
  // If a default is provided, use it:
33
33
  if (field.default !== undefined) {
34
- // If default is itself an object or array → dump as YAML block
34
+ // Empty array inline
35
+ if (Array.isArray(field.default) && field.default.length === 0) {
36
+ return `${indent}${name}: []`;
37
+ }
38
+ // Empty object inline
39
+ if (
40
+ field.default !== null &&
41
+ typeof field.default === 'object' &&
42
+ !Array.isArray(field.default) &&
43
+ Object.keys(field.default).length === 0
44
+ ) {
45
+ return `${indent}${name}: {}`;
46
+ }
47
+
48
+ // Complex object/array: dump as YAML block
35
49
  if (typeof field.default === 'object') {
36
- try {
37
- // Turn the object/array into a YAML string. We also need to indent that block
38
- const rawYaml = yaml.stringify(field.default).trim();
39
- // Indent each line of rawYaml by (indentLevel + 2) spaces:
40
- const indentedYaml = rawYaml
50
+ try {
51
+ const rawYaml = yaml.stringify(field.default).trim();
52
+ const indentedYaml = rawYaml
41
53
  .split('\n')
42
54
  .map(line => ' '.repeat(indentLevel + 2) + line)
43
55
  .join('\n');
44
- return `${indent}${name}:\n${indentedYaml}`;
45
- } catch (error) {
46
- console.warn(`Failed to serialize default value for field ${field.name}:`, error);
47
- return `${indent}${name}: {} # Error serializing default value`;
48
- }
56
+ return `${indent}${name}:\n${indentedYaml}`;
57
+ } catch (error) {
58
+ console.warn(`Failed to serialize default for ${field.name}:`, error);
59
+ return `${indent}${name}: {} # Error serializing default`;
60
+ }
49
61
  }
50
62
 
51
- // Otherwise, default is a primitive (string/number/bool)
52
- if (field.type === 'string') {
53
- return `${indent}${name}: ${yaml.stringify(field.default)}`;
63
+ // Primitive default: string, number, boolean
64
+ let value;
65
+ if (typeof field.default === 'string') {
66
+ // Preserve existing quotes
67
+ if (field.default.startsWith('"') && field.default.endsWith('"')) {
68
+ value = field.default;
69
+ } else if (field.default === '') {
70
+ value = '""';
71
+ } else {
72
+ value = field.default;
73
+ }
74
+ } else {
75
+ value = String(field.default);
54
76
  }
55
- return `${indent}${name}: ${field.default}`;
77
+
78
+ return `${indent}${name}: ${value}`;
56
79
  }
57
80
 
58
- // No default → choose representation based on kind
81
+ // No default → choose representation
59
82
  if (field.kind === 'array') {
60
83
  return `${indent}${name}: [] ${comment}`;
61
84
  } else {
62
85
  return `${indent}${name}: "" ${comment}`;
63
86
  }
64
- }
87
+ };
@@ -12,13 +12,27 @@ module.exports = function renderYamlList(name, exampleGroups) {
12
12
  exampleGroups.forEach(group => {
13
13
  const items = Array.isArray(group) ? group : [group];
14
14
  items.forEach(item => {
15
- const snippet = yaml.stringify(item).trim();
16
- const lines = snippet.split('\n');
17
- out += lines
18
- .map((line, idx) => (idx === 0 ? ` - ${line}` : ` ${line}`))
19
- .join('\n') + '\n';
15
+ // Scalars
16
+ if (typeof item === 'string' || typeof item === 'number' || typeof item === 'boolean') {
17
+ let value = String(item);
18
+ // Quote when needed: already-quoted scalars stay as-is; otherwise quote `*`
19
+ // and any value containing YAML-special characters.
20
+ if (!(value.startsWith('"') && value.endsWith('"'))) {
21
+ if (value === '*' || /[:\[\]\{\},&>|%@`]/.test(value)) {
22
+ value = `"${value}"`;
23
+ }
24
+ }
25
+ out += ` - ${value}\n`;
26
+ } else {
27
+ // Objects/arrays: stringify with indentation
28
+ const snippet = yaml.stringify(item).trim();
29
+ const lines = snippet.split('\n');
30
+ out += lines
31
+ .map((line, idx) => (idx === 0 ? ` - ${line}` : ` ${line}`))
32
+ .join('\n') + '\n';
33
+ }
20
34
  });
21
35
  out += '\n';
22
36
  });
23
37
  return out;
24
- }
38
+ };
@@ -1,5 +1,3 @@
1
- // This content is autogenerated. Do not edit manually. To override descriptions or summaries, use the doc-tools CLI with the --overrides option: https://redpandadata.atlassian.net/wiki/spaces/DOC/pages/1247543314/Generate+reference+docs+for+Redpanda+Connect
2
-
3
1
  {{{summary}}}
4
2
 
5
3
  {{#if version}}