116 lines
3.1 KiB
Python
116 lines
3.1 KiB
Python
#!/usr/bin/env python3
|
|
"""Convert changelog-draft.json to the release-pipeline-compatible changelog-release.json.
|
|
|
|
Reads the audit artifact produced by the changelog-draft skill and emits the
|
|
flat JSON structure consumed by the create_release workflow (Slack payload
|
|
builder + in-app changelog.json step).
|
|
|
|
Usage:
|
|
python3 convert_to_release_json.py --input <changelog-draft.json> --output <changelog-release.json>
|
|
|
|
The output schema:
|
|
{
|
|
"newFeatures": ["..."],
|
|
"improvements": ["..."],
|
|
"bugFixes": ["..."],
|
|
"images": ["..."],
|
|
"oz_updates": ["..."]
|
|
}
|
|
"""
|
|
|
|
import argparse
|
|
import json
|
|
import sys
|
|
|
|
# Map from changelog-draft.json category names to release JSON keys.
|
|
CATEGORY_MAP = {
|
|
"NEW-FEATURE": "newFeatures",
|
|
"IMPROVEMENT": "improvements",
|
|
"BUG-FIX": "bugFixes",
|
|
"OZ": "oz_updates",
|
|
"IMAGE": "images",
|
|
}
|
|
|
|
|
|
def github_profile_link(username: str) -> str:
|
|
"""Format a GitHub username as a markdown profile link."""
|
|
return f"[@{username}](https://github.com/{username})"
|
|
|
|
|
|
def format_entry(entry: dict) -> str:
|
|
"""Format a single changelog entry as a text line with a PR link.
|
|
|
|
Includes external contributor attribution when applicable.
|
|
"""
|
|
text = entry["text"]
|
|
pr_number = entry.get("pr_number") or entry.get("number")
|
|
url = entry.get("url") or entry.get("pr_url")
|
|
|
|
link = ""
|
|
if url and pr_number:
|
|
link = f" ([#{pr_number}]({url}))"
|
|
|
|
attribution = ""
|
|
if entry.get("is_external") and entry.get("author"):
|
|
attribution = f" — {github_profile_link(entry['author'])} ✨"
|
|
return f"{text}{link}{attribution}"
|
|
|
|
|
|
def convert(draft: dict) -> dict:
|
|
"""Convert a changelog-draft.json dict to changelog-release.json dict."""
|
|
release: dict[str, list[str]] = {
|
|
"newFeatures": [],
|
|
"improvements": [],
|
|
"bugFixes": [],
|
|
"images": [],
|
|
"oz_updates": [],
|
|
}
|
|
|
|
for entry in draft.get("entries", []):
|
|
category = entry.get("category", "")
|
|
release_key = CATEGORY_MAP.get(category)
|
|
if release_key is None:
|
|
continue
|
|
|
|
if category == "IMAGE":
|
|
# IMAGE entries store a URL in "text" — pass through directly.
|
|
release["images"].append(entry["text"])
|
|
else:
|
|
release[release_key].append(format_entry(entry))
|
|
|
|
return release
|
|
|
|
|
|
def main() -> None:
|
|
parser = argparse.ArgumentParser(
|
|
description="Convert changelog-draft.json to changelog-release.json"
|
|
)
|
|
parser.add_argument(
|
|
"--input",
|
|
required=True,
|
|
help="Path to changelog-draft.json",
|
|
)
|
|
parser.add_argument(
|
|
"--output",
|
|
required=True,
|
|
help="Path to write changelog-release.json",
|
|
)
|
|
args = parser.parse_args()
|
|
|
|
with open(args.input) as f:
|
|
draft = json.load(f)
|
|
|
|
release = convert(draft)
|
|
|
|
with open(args.output, "w") as f:
|
|
json.dump(release, f, indent=2)
|
|
f.write("\n")
|
|
|
|
# Summary to stdout for CI logs
|
|
for key, items in release.items():
|
|
print(f" {key}: {len(items)} entries")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|