refactor(api-docs): write resolved specs to _build/ instead of mutating source

Post-process-specs.ts now writes resolved specs (with info, servers, and
tag overlays applied) to api-docs/_build/ instead of back to source files.
Downstream consumers (Redoc HTML generation, static spec copy) read from
_build/. Source specs in api-docs/ are never mutated by the pipeline.

This makes the pipeline idempotent — running it twice produces identical
output — and keeps source spec diffs free of YAML re-serialization noise.
pull/6942/head
Jason Stirnaman 2026-03-13 12:39:31 -05:00
parent ebc91d2263
commit f1674d0efa
6 changed files with 48 additions and 7143 deletions

1
.gitignore vendored
View File

@ -14,6 +14,7 @@ package-lock.json
/content/influxdb*/**/api/**/*.html
!api-docs/**/.config.yml
/api-docs/redoc-static.html*
/api-docs/_build/
/helper-scripts/output/*
/telegraf-build
!telegraf-build/templates

View File

@ -3,9 +3,11 @@
# Generate API reference documentation for all InfluxDB products.
#
# Pipeline:
# 1. post-process-specs.ts — apply info/servers overlays + tag configs
# 2. generateRedocHtml — generate Redoc HTML pages with Hugo frontmatter
# 3. generate-openapi-articles.js --static-only — copy specs to static/openapi/
# 1. post-process-specs.ts — apply info/servers overlays + tag configs,
# write resolved specs to _build/ (source specs are never mutated)
# 2. generateRedocHtml — generate Redoc HTML from _build/ specs
# 3. generate-openapi-articles.js --static-only — copy _build/ specs to
# static/openapi/ for download
#
# Specs must already be fetched and bundled (via getswagger.sh) before running.
#
@ -49,8 +51,9 @@ done
# ---------------------------------------------------------------------------
# Step 1: Post-process specs (info/servers overlays + tag configs)
# ---------------------------------------------------------------------------
# Runs from the repo root because post-process-specs reads api-docs/.config.yml
# paths relative to the api-docs/ directory.
# Writes resolved specs to api-docs/_build/. Source specs are never mutated.
# Runs from the repo root because post-process-specs reads .config.yml paths
# relative to the api-docs/ directory.
echo ""
echo "========================================"
@ -65,6 +68,7 @@ cd api-docs
# ---------------------------------------------------------------------------
# Iterates each product's .config.yml, generates Redoc HTML wrapped in Hugo
# frontmatter, and writes to content/{product}/api/_index.html.
# Reads resolved specs from _build/ (written by Step 1).
function generateRedocHtml {
local specPath="$1"
@ -79,14 +83,17 @@ function generateRedocHtml {
local apiName
apiName=$(echo "$api" | sed 's/@.*//g;')
# Resolve info.yml: try spec directory first, fall back to product directory.
# Resolve info.yml from source (not _build/) for Hugo frontmatter metadata.
local specDir
specDir=$(dirname "$specPath")
# Map _build/ path back to source for content file resolution.
local sourceSpecDir="${specDir#_build/}"
local sourceProductDir="${productDir}"
local infoYml=""
if [ -f "$specDir/content/info.yml" ]; then
infoYml="$specDir/content/info.yml"
elif [ -f "$productDir/content/info.yml" ]; then
infoYml="$productDir/content/info.yml"
if [ -f "$sourceSpecDir/content/info.yml" ]; then
infoYml="$sourceSpecDir/content/info.yml"
elif [ -f "$sourceProductDir/content/info.yml" ]; then
infoYml="$sourceProductDir/content/info.yml"
fi
local title
@ -167,7 +174,7 @@ echo "Step 2: Generating Redoc HTML"
echo "========================================"
# Iterate product directories that contain a .config.yml.
for configPath in $(find . -name '.config.yml' -not -path './.config.yml' -not -path '*/node_modules/*' -not -path '*/openapi/*'); do
for configPath in $(find . -name '.config.yml' -not -path './.config.yml' -not -path '*/node_modules/*' -not -path '*/openapi/*' -not -path './_build/*'); do
productDir=$(dirname "$configPath")
# Strip leading ./
productDir="${productDir#./}"
@ -186,17 +193,20 @@ for configPath in $(find . -name '.config.yml' -not -path './.config.yml' -not -
echo "======Building $productDir $api======"
specRootPath=$(yq e ".apis | .$api | .root" "$configPath")
specPath="$productDir/$specRootPath"
# Read resolved spec from _build/ (written by Step 1)
specPath="_build/$productDir/$specRootPath"
if [ -d "$specPath" ] || [ ! -f "$specPath" ]; then
echo "OpenAPI spec $specPath doesn't exist. Skipping."
echo "Resolved spec $specPath doesn't exist. Run Step 1 first. Skipping."
continue
fi
# If -c flag set, only regenerate specs that differ from master.
# Check the source spec (not _build/) for git diff.
update=0
if [[ $generate_changed == 0 ]]; then
diff_result=$(git diff --name-status master -- "${specPath}" 2>/dev/null || true)
sourceSpecPath="$productDir/$specRootPath"
diff_result=$(git diff --name-status master -- "${sourceSpecPath}" 2>/dev/null || true)
if [[ -z "$diff_result" ]]; then
update=1
fi

View File

@ -82,7 +82,9 @@ type ProductConfigMap = Record<string, ProductConfig>;
// Calculate the relative paths
const DOCS_ROOT = '.';
const API_DOCS_ROOT = 'api-docs';
// Read resolved specs from _build/ (written by post-process-specs.ts).
// Source specs in api-docs/ are never read directly by this script.
const API_DOCS_ROOT = 'api-docs/_build';
// CLI flags
const validateLinks = process.argv.includes('--validate-links');
@ -1036,9 +1038,9 @@ const LINK_PATTERN = /\/influxdb\/version\//g;
* 'api-docs/enterprise_influxdb/v1/influxdb-enterprise-v1-openapi.yaml' '/enterprise_influxdb/v1'
*/
function deriveProductPath(specPath: string): string {
// Match: api-docs/(enterprise_influxdb|influxdb3|influxdb)/(product-or-version)/...
// Match: api-docs/[_build/](enterprise_influxdb|influxdb3|influxdb)/(product-or-version)/...
const match = specPath.match(
/api-docs\/(enterprise_influxdb|influxdb3?)\/([\w-]+)\//
/api-docs\/(?:_build\/)?(enterprise_influxdb|influxdb3?)\/([\w-]+)\//
);
if (!match) {
throw new Error(`Cannot derive product path from: ${specPath}`);

View File

@ -97,6 +97,9 @@ interface OpenApiSpec {
const LOG_PREFIX = '[post-process]';
/** Build output directory for resolved specs. Source specs are never mutated. */
const BUILD_DIR = '_build';
/** Product directories that contain a .config.yml with `apis:` entries. */
const PRODUCT_DIRS = [
'influxdb3/core',
@ -338,7 +341,11 @@ function applyTagConfig(
/**
* Process a single product directory: read `.config.yml`, find spec files,
* apply content overlays and tag configs.
* apply content overlays and tag configs, write resolved specs to _build/.
*
* Source specs in api-docs/ are never mutated. Resolved output goes to
* api-docs/_build/{productDir}/{specFile} for downstream consumers
* (Redoc HTML, generate-openapi-articles.ts).
*/
function processProduct(apiDocsRoot: string, productDir: string): void {
const productAbsDir = path.join(apiDocsRoot, productDir);
@ -369,22 +376,22 @@ function processProduct(apiDocsRoot: string, productDir: string): void {
}
// Apply all transforms
let modified = false;
modified =
applyInfoOverlay(spec, specDir, productAbsDir, label) || modified;
modified =
applyServersOverlay(spec, specDir, productAbsDir, label) || modified;
applyInfoOverlay(spec, specDir, productAbsDir, label);
applyServersOverlay(spec, specDir, productAbsDir, label);
const tagConfigPath = path.join(specDir, 'tags.yml');
if (fs.existsSync(tagConfigPath)) {
modified = applyTagConfig(spec, tagConfigPath, label) || modified;
applyTagConfig(spec, tagConfigPath, label);
}
// Write only if something changed
if (modified) {
writeYaml(specAbsPath, spec);
log(`${label}: wrote ${path.basename(specAbsPath)}`);
// Write resolved spec to _build/, mirroring the source path structure
const outPath = path.join(apiDocsRoot, BUILD_DIR, productDir, specRelPath);
const outDir = path.dirname(outPath);
if (!fs.existsSync(outDir)) {
fs.mkdirSync(outDir, { recursive: true });
}
writeYaml(outPath, spec);
log(`${label}: wrote ${path.relative(apiDocsRoot, outPath)}`);
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff