Skip to content

Commit f7316a8

Browse files
committed
Address PR feedback
1 parent ce866aa commit f7316a8

4 files changed

Lines changed: 82 additions & 49 deletions

File tree

scripts/changelog/new-entry.py

Lines changed: 15 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -7,36 +7,35 @@
77

88
import argparse
99
import json
10-
import time
1110
import uuid
1211
from pathlib import Path
1312

13+
from utils import get_package_changes_dir
14+
1415
PROJECT_ROOT_DIR = Path(__file__).resolve().parent.parent.parent
1516

1617

17-
def get_package_changes_dir(package_name: str) -> Path:
18-
package_path = PROJECT_ROOT_DIR / "clients" / package_name
19-
changes_dir = package_path / ".changes"
20-
return changes_dir
18+
def setup_changes_directories(package_name: str) -> Path:
19+
"""Set up and return the next-release directory for a package."""
20+
changes_dir = get_package_changes_dir(package_name)
21+
changes_dir.mkdir(exist_ok=True)
22+
23+
next_release_dir = changes_dir / "next-release"
24+
next_release_dir.mkdir(exist_ok=True)
25+
26+
return next_release_dir
2127

2228

2329
def create_change_entry(
2430
change_type: str,
2531
description: str,
2632
package_name: str,
2733
) -> str:
28-
# Get package .changes directory and ensure it exists
29-
changes_dir = get_package_changes_dir(package_name)
30-
changes_dir.mkdir(exist_ok=True)
31-
32-
# Create next-release directory for pending changes
33-
next_release_dir = changes_dir / "next-release"
34-
next_release_dir.mkdir(exist_ok=True)
34+
next_release_dir = setup_changes_directories(package_name)
3535

3636
# Generate unique filename
37-
timestamp = int(time.time() * 1_000_000)
3837
unique_id = uuid.uuid4().hex
39-
filename = f"{package_name}-{change_type}-{timestamp}-{unique_id}.json"
38+
filename = f"{package_name}-{change_type}-{unique_id}.json"
4039

4140
entry_data = {
4241
"type": change_type,
@@ -56,13 +55,7 @@ def create_summary_entry(
5655
package_name: str,
5756
) -> str:
5857
"""Create or update the release summary for the next release."""
59-
# Get package .changes directory and ensure it exists
60-
changes_dir = get_package_changes_dir(package_name)
61-
changes_dir.mkdir(exist_ok=True)
62-
63-
# Create next-release directory for pending changes
64-
next_release_dir = changes_dir / "next-release"
65-
next_release_dir.mkdir(exist_ok=True)
58+
next_release_dir = setup_changes_directories(package_name)
6659

6760
# Summary is stored in a fixed file (only one summary per release)
6861
summary_file = next_release_dir / "SUMMARY.json"
@@ -86,7 +79,7 @@ def main():
8679
parser.add_argument(
8780
"-t",
8881
"--type",
89-
# TODO: Remove the 'breaking' option once this project is stable.
82+
# TODO: Prompt the user for confirmation before allowing the 'breaking' or `feature` options.
9083
choices=(
9184
"feature",
9285
"enhancement",

scripts/changelog/new-release.py

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
from pathlib import Path
1414
from typing import Any
1515

16+
from utils import get_package_changes_dir
17+
1618
PROJECT_ROOT_DIR = Path(__file__).resolve().parent.parent.parent
1719
VERSION_PATTERN = r"^\d+\.\d+\.\d+$"
1820
CHANGE_TYPES_ORDER = {
@@ -30,12 +32,16 @@ def validate_change_entry(change_data: dict[str, Any], entry_file: Path) -> bool
3032
if "type" not in change_data or change_data["type"] not in CHANGE_TYPES:
3133
print(
3234
f"Error: Missing or invalid 'type' field in {entry_file}\n"
33-
f"Type must be one of: {CHANGE_TYPES}"
35+
f"Type must be one of: {CHANGE_TYPES}",
36+
file=sys.stderr,
3437
)
3538
return False
3639

3740
if "description" not in change_data or not change_data["description"]:
38-
print(f"Error: Missing or empty 'description' field in {entry_file}")
41+
print(
42+
f"Error: Missing or empty 'description' field in {entry_file}",
43+
file=sys.stderr,
44+
)
3945
return False
4046

4147
return True
@@ -53,7 +59,9 @@ def collect_next_release_summary(next_release_dir: Path) -> str | None:
5359
data = json.load(f)
5460
return data.get("summary")
5561
except (OSError, json.JSONDecodeError) as e:
56-
print(f"Warning: Could not read summary file {summary_file}: {e}", file=sys.stderr)
62+
print(
63+
f"Warning: Could not read summary file {summary_file}: {e}", file=sys.stderr
64+
)
5765
return None
5866

5967

@@ -97,7 +105,7 @@ def create_version_file(
97105
version_file = changes_dir / f"{version}.json"
98106

99107
if version_file.exists():
100-
print(f"Error: Version file {version_file} already exists!")
108+
print(f"Error: Version file {version_file} already exists!", file=sys.stderr)
101109
sys.exit(1)
102110

103111
version_data: dict[str, Any] = {"changes": changes}
@@ -107,6 +115,7 @@ def create_version_file(
107115

108116
with open(version_file, "w") as f:
109117
json.dump(version_data, f, indent=2)
118+
f.write("\n")
110119

111120
return version_file
112121

@@ -116,22 +125,22 @@ def cleanup_next_release_dir(next_release_dir: Path) -> int:
116125

117126
for entry_file in next_release_dir.iterdir():
118127
if entry_file.is_file() and entry_file.suffix == ".json":
119-
try:
120-
entry_file.unlink()
121-
removed_count += 1
122-
except OSError as e:
123-
print(f"Warning: Could not remove {entry_file}: {e}", file=sys.stderr)
128+
entry_file.unlink()
129+
removed_count += 1
124130

125131
return removed_count
126132

127133

128134
def create_new_release(package_name: str, version: str, dry_run: bool = False) -> int:
129-
# Get package directories
130-
changes_dir = PROJECT_ROOT_DIR / "clients" / package_name / ".changes"
135+
# Get package directories (validates package exists)
136+
changes_dir = get_package_changes_dir(package_name)
131137
next_release_dir = changes_dir / "next-release"
132138

133139
if not changes_dir.exists():
134-
print(f"Error: No .changes directory found for package: {package_name}")
140+
print(
141+
f"Error: No .changes directory found for package: {package_name}",
142+
file=sys.stderr,
143+
)
135144
return 1
136145

137146
# Collect summary and changes from next-release
@@ -141,7 +150,8 @@ def create_new_release(package_name: str, version: str, dry_run: bool = False) -
141150
if not changes:
142151
print(
143152
f"No changelog entries found in {next_release_dir}.\n"
144-
"Use 'python scripts/changelog/new-entry.py' to create entries first"
153+
"Use 'python scripts/changelog/new-entry.py' to create entries first",
154+
file=sys.stderr,
145155
)
146156
return 1
147157

@@ -164,7 +174,7 @@ def create_new_release(package_name: str, version: str, dry_run: bool = False) -
164174
version_file = create_version_file(changes_dir, version, changes, summary)
165175
print(f"\nCreated version file: {version_file}")
166176
except Exception as e:
167-
print(f"Error creating version file: {e}")
177+
print(f"Error creating version file: {e}", file=sys.stderr)
168178
return 1
169179

170180
# Clean up next-release directory
@@ -193,7 +203,7 @@ def main() -> int:
193203

194204
# Basic version format validation
195205
if not bool(re.match(VERSION_PATTERN, args.version)):
196-
print("Error: Version must be in format x.y.z (e.g., 1.2.3)")
206+
print("Error: Version must be in format x.y.z (e.g., 1.2.3)", file=sys.stderr)
197207
return 1
198208

199209
return create_new_release(

scripts/changelog/render.py

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from typing import IO, Any
1414

1515
from jinja2 import Template
16+
from utils import get_package_changes_dir
1617

1718
PROJECT_ROOT_DIR = Path(__file__).resolve().parent.parent.parent
1819

@@ -21,7 +22,7 @@
2122
VERSION_FILE_PATTERN = r"^\d+\.\d+\.\d+\.json$"
2223

2324

24-
def get_sorted_versions(changes_dir: Path) -> list[str]:
25+
def get_sorted_versions(changes_dir: Path, reverse: bool = True) -> list[str]:
2526
"""Get sorted list of version numbers from .changes directory."""
2627
version_pattern = re.compile(VERSION_FILE_PATTERN)
2728
versions: list[str] = []
@@ -30,16 +31,16 @@ def get_sorted_versions(changes_dir: Path) -> list[str]:
3031
if file.is_file() and version_pattern.match(file.name):
3132
versions.append(file.stem)
3233

33-
# Sort by semantic version (oldest first)
34-
versions.sort(key=lambda v: [int(x) for x in v.split(".")])
34+
# Sort by semantic version (reverse if True)
35+
versions.sort(key=lambda v: [int(x) for x in v.split(".")], reverse=reverse)
3536
return versions
3637

3738

3839
def load_package_releases(changes_dir: Path) -> dict[str, dict[str, Any]]:
3940
"""Load all changelog entries from version JSON files."""
4041
releases: dict[str, dict[str, Any]] = {}
4142

42-
for version_number in get_sorted_versions(changes_dir):
43+
for version_number in get_sorted_versions(changes_dir, reverse=True):
4344
filename = changes_dir / f"{version_number}.json"
4445
try:
4546
with open(filename) as f:
@@ -75,7 +76,7 @@ def render_changes(
7576
) -> None:
7677
"""Render changelog using Jinja template."""
7778
# Reverse order to show newest first
78-
context: dict[str, Any] = {"releases": reversed(list(changes.items()))}
79+
context: dict[str, Any] = {"releases": list(changes.items())}
7980

8081
template = Template(template_contents)
8182

@@ -86,33 +87,34 @@ def render_changes(
8687
def render_package_changelog(
8788
package_name: str, template_name: str | None = None, output_path: Path | None = None
8889
) -> int:
89-
# Determine changes directory from package name
90-
package_dir = PROJECT_ROOT_DIR / "clients" / package_name
91-
changes_dir = package_dir / ".changes"
90+
# Get changes directory (validates package exists)
91+
changes_dir = get_package_changes_dir(package_name)
9292

9393
if not changes_dir.exists():
94-
print(f"No .changes directory found for package: {package_name}")
94+
print(
95+
f"No .changes directory found for package: {package_name}", file=sys.stderr
96+
)
9597
return 1
9698

9799
# Load changes from the directory
98100
changes: dict[str, dict[str, Any]] = load_package_releases(changes_dir)
99101

100102
if not changes:
101-
print(f"No version JSON files found in {changes_dir}")
103+
print(f"No version JSON files found in {changes_dir}", file=sys.stderr)
102104
return 1
103105

104106
# Get template contents
105107
template_path = TEMPLATES_DIR / (template_name or DEFAULT_TEMPLATE_NAME)
106108

107109
if not template_path.exists():
108-
print(f"Template not found: {template_path}")
110+
print(f"Template not found: {template_path}", file=sys.stderr)
109111
return 1
110112

111113
try:
112114
with open(template_path) as f:
113115
template_contents = f.read()
114116
except OSError as e:
115-
print(f"Error reading template {template_path}: {e}")
117+
print(f"Error reading template {template_path}: {e}", file=sys.stderr)
116118
return 1
117119

118120
# Render to output

scripts/changelog/utils.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
#!/usr/bin/env python3
2+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
# SPDX-License-Identifier: Apache-2.0
4+
"""
5+
Common utilities for changelog management scripts.
6+
"""
7+
8+
import sys
9+
from pathlib import Path
10+
11+
PROJECT_ROOT_DIR = Path(__file__).resolve().parent.parent.parent
12+
13+
14+
def validate_package_name(package_name: str) -> None:
15+
"""Validate that the package exists in the clients directory."""
16+
package_path = PROJECT_ROOT_DIR / "clients" / package_name
17+
if not package_path.exists():
18+
print(
19+
f"Error: Package '{package_name}' not found in clients directory",
20+
file=sys.stderr,
21+
)
22+
sys.exit(1)
23+
24+
25+
def get_package_changes_dir(package_name: str) -> Path:
26+
"""Get the .changes directory for a package."""
27+
validate_package_name(package_name)
28+
return PROJECT_ROOT_DIR / "clients" / package_name / ".changes"

0 commit comments

Comments
 (0)