98 lines
2.9 KiB
Python
98 lines
2.9 KiB
Python
|
|
#!/usr/bin/env python3
|
||
|
|
|
||
|
|
"""
|
||
|
|
OpenAPI Documentation Bundler
|
||
|
|
|
||
|
|
This script merges the modular OpenAPI specification files into a single
|
||
|
|
api-docs.bundled.yaml file that can be served to Swagger UI or other OpenAPI tools.
|
||
|
|
|
||
|
|
It merges paths from the paths/ directory and then uses Redocly CLI to resolve
|
||
|
|
all $ref references into inline definitions.
|
||
|
|
|
||
|
|
Usage: python bundle-api-docs.py
|
||
|
|
"""
|
||
|
|
|
||
|
|
import yaml
|
||
|
|
import os
|
||
|
|
import glob
|
||
|
|
import subprocess
|
||
|
|
|
||
|
|
public_dir = os.path.dirname(os.path.abspath(__file__))
|
||
|
|
components_dir = os.path.join(public_dir, "components", "schemas")
|
||
|
|
paths_dir = os.path.join(public_dir, "paths")
|
||
|
|
temp_file = os.path.join(public_dir, "api-docs.merged.yaml")
|
||
|
|
output_file = os.path.join(public_dir, "api-docs.bundled.yaml")
|
||
|
|
|
||
|
|
# Read the base api-docs.yaml
|
||
|
|
with open(os.path.join(public_dir, "api-docs.yaml"), "r", encoding="utf-8") as f:
|
||
|
|
api_docs = yaml.safe_load(f)
|
||
|
|
|
||
|
|
# Merge paths from all path files
|
||
|
|
paths = {}
|
||
|
|
path_files = glob.glob(os.path.join(paths_dir, "*.yaml"))
|
||
|
|
|
||
|
|
print(f"Found {len(path_files)} path files to merge...")
|
||
|
|
|
||
|
|
for filepath in path_files:
|
||
|
|
filename = os.path.basename(filepath)
|
||
|
|
try:
|
||
|
|
with open(filepath, "r", encoding="utf-8") as f:
|
||
|
|
parsed = yaml.safe_load(f)
|
||
|
|
|
||
|
|
if parsed:
|
||
|
|
paths.update(parsed)
|
||
|
|
print(f" [OK] Merged {len(parsed)} paths from {filename}")
|
||
|
|
except Exception as e:
|
||
|
|
print(f" [WARN] Failed to parse {filename}: {e}")
|
||
|
|
|
||
|
|
# Replace the empty paths with merged paths
|
||
|
|
api_docs["paths"] = paths
|
||
|
|
|
||
|
|
# Write the merged file (still has $refs)
|
||
|
|
with open(temp_file, "w", encoding="utf-8") as f:
|
||
|
|
yaml.dump(
|
||
|
|
api_docs,
|
||
|
|
f,
|
||
|
|
default_flow_style=False,
|
||
|
|
allow_unicode=True,
|
||
|
|
sort_keys=False,
|
||
|
|
width=4096,
|
||
|
|
)
|
||
|
|
|
||
|
|
print(f"\n[SUCCESS] Merged paths into: {temp_file}")
|
||
|
|
print(f" Total paths: {len(paths)}")
|
||
|
|
|
||
|
|
# Now use Redocly CLI to resolve all $ref references
|
||
|
|
print("\nResolving $ref references with Redocly CLI...")
|
||
|
|
try:
|
||
|
|
result = subprocess.run(
|
||
|
|
["redocly", "bundle", temp_file, "-o", output_file],
|
||
|
|
capture_output=True,
|
||
|
|
text=True,
|
||
|
|
cwd=public_dir,
|
||
|
|
)
|
||
|
|
|
||
|
|
if result.returncode == 0:
|
||
|
|
print(f" [SUCCESS] Resolved all $ref references to: {output_file}")
|
||
|
|
# Clean up temp file
|
||
|
|
os.remove(temp_file)
|
||
|
|
print(f" [CLEANUP] Removed temp file: {temp_file}")
|
||
|
|
else:
|
||
|
|
print(f" [ERROR] Redocly CLI failed:")
|
||
|
|
print(f" {result.stderr}")
|
||
|
|
exit(1)
|
||
|
|
except FileNotFoundError:
|
||
|
|
print(" [ERROR] Redocly CLI not found. Install with: npm install -g @redocly/cli")
|
||
|
|
exit(1)
|
||
|
|
except Exception as e:
|
||
|
|
print(f" [ERROR] Failed to run Redocly CLI: {e}")
|
||
|
|
exit(1)
|
||
|
|
|
||
|
|
print(f"\n{'=' * 60}")
|
||
|
|
print(f"Bundling complete!")
|
||
|
|
print(f" Output: {output_file}")
|
||
|
|
print(f" Total paths: {len(paths)}")
|
||
|
|
print(f" Total schemas: {len(api_docs.get('components', {}).get('schemas', {}))}")
|
||
|
|
print(f"\nYou can now serve this file to Swagger UI.")
|
||
|
|
print(f"{'=' * 60}")
|