Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/publish-to-npm.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ jobs:
needs: publish
strategy:
matrix:
repo: ['redpanda-data/docs', 'redpanda-data/cloud-docs', 'redpanda-data/rp-connect-docs']
repo: ['redpanda-data/docs', 'redpanda-data/cloud-docs', 'redpanda-data/rp-connect-docs', 'redpanda-data/api-docs']
runs-on: ubuntu-latest
steps:
- uses: aws-actions/configure-aws-credentials@v4
Expand Down
172 changes: 84 additions & 88 deletions bin/doc-tools.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@ const { execSync, spawnSync } = require('child_process');
const os = require('os');
const { Command, Option } = require('commander');
const path = require('path');
const yaml = require('yaml');
const fs = require('fs');
const handlebars = require('handlebars');
const { determineDocsBranch } = require('../cli-utils/self-managed-docs-branch.js');
const fetchFromGithub = require('../tools/fetch-from-github.js');
const { urlToXref } = require('../cli-utils/convert-doc-links.js');
Expand Down Expand Up @@ -1031,12 +1029,12 @@ automation
});

automation
.command('property-docs')
.command('property-docs')
.description(
'Generate JSON and consolidated AsciiDoc partials for Redpanda configuration properties. ' +
'By default, only extracts properties to JSON. Use --generate-partials to create consolidated ' +
'AsciiDoc partials (including deprecated properties).'
)
'Generate JSON and consolidated AsciiDoc partials for Redpanda configuration properties. ' +
'By default, only extracts properties to JSON. Use --generate-partials to create consolidated ' +
'AsciiDoc partials (including deprecated properties).'
)
.option('--tag <tag>', 'Git tag or branch to extract from', 'dev')
.option('--diff <oldTag>', 'Also diff autogenerated properties from <oldTag> to <tag>')
.option('--overrides <path>', 'Optional JSON file with property description overrides')
Expand All @@ -1049,14 +1047,12 @@ automation
.option('--template-deprecated-property <path>', 'Custom Handlebars template for individual deprecated property sections')
.option('--generate-partials', 'Generate consolidated property partials (cluster-properties.adoc, topic-properties.adoc, etc.) in the partials directory')
.option('--partials-dir <path>', 'Directory for property partials (relative to output-dir)', 'partials')
.action((options) => {
verifyPropertyDependencies();
.action((options) => {
verifyPropertyDependencies();

// Validate cloud support dependencies if requested
if (options.cloudSupport) {
console.log('🔍 Validating cloud support dependencies...');

// Check for GITHUB_TOKEN, GH_TOKEN, or REDPANDA_GITHUB_TOKEN
const token = process.env.GITHUB_TOKEN || process.env.GH_TOKEN || process.env.REDPANDA_GITHUB_TOKEN;
if (!token) {
console.error('❌ Cloud support requires GITHUB_TOKEN, GH_TOKEN, or REDPANDA_GITHUB_TOKEN environment variable');
Expand All @@ -1068,7 +1064,6 @@ automation
console.error(' Or: export REDPANDA_GITHUB_TOKEN=your_token_here');
process.exit(1);
}

console.log('📦 Cloud support enabled - Python dependencies will be validated during execution');
if (process.env.VIRTUAL_ENV) {
console.log(` Using virtual environment: ${process.env.VIRTUAL_ENV}`);
Expand All @@ -1077,87 +1072,88 @@ automation
console.log('✅ GitHub token validated');
}

const newTag = options.tag;
const oldTag = options.diff;
const overridesPath = options.overrides;
const outputDir = options.outputDir;
const cwd = path.resolve(__dirname, '../tools/property-extractor');

const make = (tag, overrides, templates = {}, outDir = 'modules/reference/', tempDir = null) => {
console.log(`⏳ Building property docs for ${tag}${tempDir ? ' (for diff)' : ''}…`);
const args = ['build', `TAG=${tag}`];

// Pass all paths as environment variables for consistency
const env = { ...process.env };

if (overrides) {
env.OVERRIDES = path.resolve(overrides);
}
if (options.cloudSupport) {
env.CLOUD_SUPPORT = '1';
}
if (templates.property) {
env.TEMPLATE_PROPERTY = path.resolve(templates.property);
}
if (templates.topicProperty) {
env.TEMPLATE_TOPIC_PROPERTY = path.resolve(templates.topicProperty);
}
if (templates.topicPropertyMappings) {
env.TEMPLATE_TOPIC_PROPERTY_MAPPINGS = path.resolve(templates.topicPropertyMappings);
}
if (templates.deprecated) {
env.TEMPLATE_DEPRECATED = path.resolve(templates.deprecated);
}
if (templates.deprecatedProperty) {
env.TEMPLATE_DEPRECATED_PROPERTY = path.resolve(templates.deprecatedProperty);
}

if (tempDir) {
env.OUTPUT_JSON_DIR = path.resolve(tempDir, 'examples');
env.OUTPUT_AUTOGENERATED_DIR = path.resolve(tempDir);
} else {
env.OUTPUT_JSON_DIR = path.resolve(outDir, 'examples');
env.OUTPUT_AUTOGENERATED_DIR = path.resolve(outDir);
}

// Partials generation options
if (options.generatePartials) {
env.GENERATE_PARTIALS = '1';
env.OUTPUT_PARTIALS_DIR = path.resolve(outDir, options.partialsDir || 'partials');
}

const r = spawnSync('make', args, { cwd, stdio: 'inherit', env });
if (r.error) {
console.error(`❌ ${r.error.message}`);
process.exit(1);
}
if (r.status !== 0) process.exit(r.status);
};

// Collect template options
const templates = {
property: options.templateProperty,
topicProperty: options.templateTopicProperty,
topicPropertyMappings: options.templateTopicPropertyMappings,
deprecated: options.templateDeprecated,
deprecatedProperty: options.templateDeprecatedProperty,
};

const newTag = options.tag;
let oldTag = options.diff;
const overridesPath = options.overrides;
const outputDir = options.outputDir;
const cwd = path.resolve(__dirname, '../tools/property-extractor');

// If --diff is not provided, try to get the latest-redpanda-tag from Antora attributes
if (!oldTag) {
oldTag = getAntoraValue('asciidoc.attributes.latest-redpanda-tag');
if (oldTag) {
// Build old version first so its JSON exists for the diff step
make(oldTag, overridesPath, templates, outputDir, null);
console.log(`Using latest-redpanda-tag from Antora attributes for --diff: ${oldTag}`);
} else {
console.log('No --diff provided and no latest-redpanda-tag found in Antora attributes. Skipping diff.');
}
}

// Build new version
make(newTag, overridesPath, templates, outputDir, null);

if (oldTag) {
// Generate property comparison report using the JSON now in outputDir/examples
generatePropertyComparisonReport(oldTag, newTag, outputDir);
const make = (tag, overrides, templates = {}, outDir = 'modules/reference/') => {
console.log(`⏳ Building property docs for ${tag}…`);
const args = ['build', `TAG=${tag}`];
const env = { ...process.env };
if (overrides) {
env.OVERRIDES = path.resolve(overrides);
}
if (options.cloudSupport) {
env.CLOUD_SUPPORT = '1';
}
if (templates.property) {
env.TEMPLATE_PROPERTY = path.resolve(templates.property);
}
if (templates.topicProperty) {
env.TEMPLATE_TOPIC_PROPERTY = path.resolve(templates.topicProperty);
}
if (templates.topicPropertyMappings) {
env.TEMPLATE_TOPIC_PROPERTY_MAPPINGS = path.resolve(templates.topicPropertyMappings);
}
if (templates.deprecated) {
env.TEMPLATE_DEPRECATED = path.resolve(templates.deprecated);
}
if (templates.deprecatedProperty) {
env.TEMPLATE_DEPRECATED_PROPERTY = path.resolve(templates.deprecatedProperty);
}
env.OUTPUT_JSON_DIR = path.resolve(outDir, 'examples');
env.OUTPUT_AUTOGENERATED_DIR = path.resolve(outDir);
if (options.generatePartials) {
env.GENERATE_PARTIALS = '1';
env.OUTPUT_PARTIALS_DIR = path.resolve(outDir, options.partialsDir || 'partials');
}
const r = spawnSync('make', args, { cwd, stdio: 'inherit', env });
if (r.error) {
console.error(`❌ ${r.error.message}`);
process.exit(1);
}
if (r.status !== 0) process.exit(r.status);
};

const templates = {
property: options.templateProperty,
topicProperty: options.templateTopicProperty,
topicPropertyMappings: options.templateTopicPropertyMappings,
deprecated: options.templateDeprecated,
deprecatedProperty: options.templateDeprecatedProperty,
};

const tagsAreSame = oldTag && newTag && oldTag === newTag;
if (oldTag && !tagsAreSame) {
make(oldTag, overridesPath, templates, outputDir);
}
make(newTag, overridesPath, templates, outputDir);
if (oldTag && !tagsAreSame) {
generatePropertyComparisonReport(oldTag, newTag, outputDir);
} else if (tagsAreSame) {
console.log('--diff and --tag are the same. Skipping diff and Antora config update.');
}

process.exit(0);
});
// If we used Antora's latest-redpanda-tag for diff, update it to the new tag
if (!options.diff && !tagsAreSame) {
setAntoraValue('asciidoc.attributes.latest-redpanda-tag', newTag);
console.log(`✅ Updated Antora latest-redpanda-tag to: ${newTag}`);
}

process.exit(0);
});

automation
.command('rpk-docs')
Expand Down
67 changes: 57 additions & 10 deletions cli-utils/antora-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,30 @@ const yaml = require('js-yaml');
* @returns {Object|undefined} The parsed YAML as a JavaScript object, or undefined if not found or on error.
*/
function loadAntoraConfig() {
const antoraPath = path.join(process.cwd(), 'antora.yml');
if (!fs.existsSync(antoraPath)) {
// No antora.yml in project root
// Support both antora.yml and antora.yaml
const cwd = process.cwd();
const ymlPath = path.join(cwd, 'antora.yml');
const yamlPath = path.join(cwd, 'antora.yaml');
let antoraPath;
if (fs.existsSync(ymlPath)) {
antoraPath = ymlPath;
} else if (fs.existsSync(yamlPath)) {
antoraPath = yamlPath;
} else {
// No antora.yml or antora.yaml in project root
return undefined;
}

try {
const fileContents = fs.readFileSync(antoraPath, 'utf8');
const config = yaml.load(fileContents);
if (typeof config !== 'object' || config === null) {
console.error('Warning: antora.yml parsed to a non‐object value.');
console.error(`Warning: ${path.basename(antoraPath)} parsed to a non‐object value.`);
return undefined;
}
return config;
} catch (err) {
console.error(`Error reading/parsing antora.yml: ${err.message}`);
console.error(`Error reading/parsing ${path.basename(antoraPath)}: ${err.message}`);
return undefined;
}
}
Expand Down Expand Up @@ -74,9 +82,17 @@ function getAntoraValue(keyPath) {
* True if it succeeded, false otherwise.
*/
function setAntoraValue(keyPath, newValue) {
const antoraPath = path.join(process.cwd(), 'antora.yml');
if (!fs.existsSync(antoraPath)) {
console.error('Cannot update antora.yml: file not found in project root.');
// Support both antora.yml and antora.yaml
const cwd = process.cwd();
const ymlPath = path.join(cwd, 'antora.yml');
const yamlPath = path.join(cwd, 'antora.yaml');
let antoraPath;
if (fs.existsSync(ymlPath)) {
antoraPath = ymlPath;
} else if (fs.existsSync(yamlPath)) {
antoraPath = yamlPath;
} else {
console.error('Cannot update antora.yml or antora.yaml: file not found in project root.');
return false;
}

Expand All @@ -88,7 +104,7 @@ function setAntoraValue(keyPath, newValue) {
config = {};
}
} catch (err) {
console.error(`Error reading/parsing antora.yml: ${err.message}`);
console.error(`Error reading/parsing ${path.basename(antoraPath)}: ${err.message}`);
return false;
}

Expand All @@ -115,7 +131,37 @@ function setAntoraValue(keyPath, newValue) {
fs.writeFileSync(antoraPath, newYaml, 'utf8');
return true;
} catch (err) {
console.error(`Error writing antora.yml: ${err.message}`);
console.error(`Error writing ${path.basename(antoraPath)}: ${err.message}`);
return false;
}
}

/**
* Look for antora.yml in the current working directory
* (the project's root), load it if present, and return
* its `prerelease` value (boolean). If missing or on error,
* returns false.
*/
function getPrereleaseFromAntora() {
// Support both antora.yml and antora.yaml
const cwd = process.cwd();
const ymlPath = path.join(cwd, 'antora.yml');
const yamlPath = path.join(cwd, 'antora.yaml');
let antoraPath;
if (fs.existsSync(ymlPath)) {
antoraPath = ymlPath;
} else if (fs.existsSync(yamlPath)) {
antoraPath = yamlPath;
} else {
return false;
}

try {
const fileContents = fs.readFileSync(antoraPath, 'utf8');
const antoraConfig = yaml.load(fileContents);
return antoraConfig.prerelease === true;
} catch (error) {
console.error(`Error reading ${path.basename(antoraPath)}:`, error.message);
return false;
}
}
Expand All @@ -124,4 +170,5 @@ module.exports = {
loadAntoraConfig,
getAntoraValue,
setAntoraValue,
getPrereleaseFromAntora
};
27 changes: 0 additions & 27 deletions cli-utils/beta-from-antora.js

This file was deleted.

4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@redpanda-data/docs-extensions-and-macros",
"version": "4.10.6",
"version": "4.10.7",
"description": "Antora extensions and macros developed for Redpanda documentation.",
"keywords": [
"antora",
Expand Down
2 changes: 1 addition & 1 deletion tools/get-console-version.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const yaml = require('js-yaml');
const fs = require('fs');
const path = require('path');
const GetLatestConsoleVersion = require('../extensions/version-fetcher/get-latest-console-version.js');
const { getPrereleaseFromAntora } = require('../cli-utils/beta-from-antora.js');
const { getPrereleaseFromAntora } = require('../cli-utils/antora-utils.js');

/**
* Fetches and prints the latest Console version and Docker repo.
Expand Down
Loading