Skip to content

Commit f63e08c

Browse files
Copilotl0lawrence
andauthored
Add --kind and --message flags to changelog add for breaking changes
Add --kind (-k) and --message (-m) flags to `azpysdk changelog add` that forward to chronus's native --kind and --message options. This lets developers tag entries as breaking (or any other change kind) non-interactively: azpysdk changelog add --kind breaking -m "Removed deprecated API" azpysdk changelog add -k feature -m "Added new endpoint" Valid kinds: breaking, feature, deprecation, fix, dependencies, internal Also fix _ensure_chronus_installed to skip the interactive prompt when stdin is not a TTY (non-interactive / CI environments). Agent-Logs-Url: https://github.com/Azure/azure-sdk-for-python/sessions/aa3dee26-9283-4594-a49f-64fc64d3d995 Co-authored-by: l0lawrence <100643745+l0lawrence@users.noreply.github.com>
1 parent 81ef243 commit f63e08c

2 files changed

Lines changed: 130 additions & 9 deletions

File tree

eng/tools/azure-sdk-tools/azpysdk/changelog.py

Lines changed: 45 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@
1212
_CHRONUS_PACKAGE = "@chronus/chronus"
1313
_CHRONUS_MODULE_PATH = os.path.join("node_modules", "@chronus", "chronus")
1414

15+
# Valid change kinds from .chronus/config.yaml. Kept in sync manually
16+
# so the CLI can validate before calling out to chronus.
17+
_CHANGE_KINDS = ["breaking", "feature", "deprecation", "fix", "dependencies", "internal"]
18+
1519

1620
class changelog(Check):
1721
"""Manage changelogs with Chronus.
@@ -53,6 +57,23 @@ def register(
5357
"automatically. Otherwise chronus detects modified packages interactively."
5458
),
5559
)
60+
add_p.add_argument(
61+
"--kind", "-k",
62+
choices=_CHANGE_KINDS,
63+
default=None,
64+
help=(
65+
"Kind of change (e.g. breaking, feature, fix). "
66+
"If omitted, chronus will prompt interactively."
67+
),
68+
)
69+
add_p.add_argument(
70+
"--message", "-m",
71+
default=None,
72+
help=(
73+
"Short description of the change. "
74+
"If omitted, chronus will prompt interactively."
75+
),
76+
)
5677
add_p.set_defaults(func=self._run_add)
5778

5879
# changelog verify
@@ -122,14 +143,17 @@ def _ensure_chronus_installed(self) -> None:
122143
raise SystemExit(1)
123144

124145

125-
print(
126-
"\nChronus is not installed locally. It is listed as a dev dependency\n"
127-
f"in {os.path.join(REPO_ROOT, 'package.json')}.\n"
128-
)
129-
answer = input("Run 'npm install' in the repo root to install it? [Y/n] ").strip().lower()
130-
if answer not in ("", "y", "yes"):
131-
logger.info("Skipped Chronus installation.")
132-
raise SystemExit(1)
146+
if sys.stdin.isatty():
147+
print(
148+
"\nChronus is not installed locally. It is listed as a dev dependency\n"
149+
f"in {os.path.join(REPO_ROOT, 'package.json')}.\n"
150+
)
151+
answer = input("Run 'npm install' in the repo root to install it? [Y/n] ").strip().lower()
152+
if answer not in ("", "y", "yes"):
153+
logger.info("Skipped Chronus installation.")
154+
raise SystemExit(1)
155+
else:
156+
logger.info("Chronus not installed — running 'npm install' automatically (non-interactive).")
133157

134158
logger.info(f"Running: npm install (cwd: {REPO_ROOT})")
135159
rc = subprocess.call([npm, "install"], cwd=REPO_ROOT)
@@ -194,6 +218,10 @@ def _run_add(self, args: argparse.Namespace) -> int:
194218
When no *package* argument is given but CWD is inside a package
195219
directory (``sdk/<service>/<package>``), the package path is detected
196220
automatically so the developer doesn't have to specify it.
221+
222+
Optional ``--kind`` and ``--message`` flags are forwarded to chronus
223+
so the developer can skip the interactive prompts (e.g.
224+
``azpysdk changelog add --kind breaking -m "Removed foo API"``).
197225
"""
198226
chronus_args = ["add"]
199227
package = getattr(args, "package", None)
@@ -203,6 +231,15 @@ def _run_add(self, args: argparse.Namespace) -> int:
203231
logger.info(f"Detected package from current directory: {package}")
204232
if package:
205233
chronus_args.append(package)
234+
235+
kind = getattr(args, "kind", None)
236+
if kind:
237+
chronus_args.extend(["--kind", kind])
238+
239+
message = getattr(args, "message", None)
240+
if message:
241+
chronus_args.extend(["--message", message])
242+
206243
return self._run_chronus(chronus_args)
207244

208245
def _run_verify(self, args: argparse.Namespace) -> int:

eng/tools/azure-sdk-tools/tests/test_changelog_commands.py

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
import pytest
99

10-
from azpysdk.changelog import changelog, REPO_ROOT, _CHRONUS_MODULE_PATH
10+
from azpysdk.changelog import changelog, REPO_ROOT, _CHRONUS_MODULE_PATH, _CHANGE_KINDS
1111

1212

1313
# ---------------------------------------------------------------------------
@@ -47,6 +47,46 @@ def test_changelog_add_with_package(self):
4747
assert args.changelog_command == "add"
4848
assert args.package == "sdk/storage/azure-storage-blob"
4949

50+
def test_changelog_add_with_kind(self):
51+
parser = _build_parser()
52+
args = parser.parse_args(["changelog", "add", "--kind", "breaking"])
53+
assert args.kind == "breaking"
54+
55+
def test_changelog_add_with_kind_short(self):
56+
parser = _build_parser()
57+
args = parser.parse_args(["changelog", "add", "-k", "feature"])
58+
assert args.kind == "feature"
59+
60+
def test_changelog_add_with_message(self):
61+
parser = _build_parser()
62+
args = parser.parse_args(["changelog", "add", "--message", "Fixed the bug"])
63+
assert args.message == "Fixed the bug"
64+
65+
def test_changelog_add_with_message_short(self):
66+
parser = _build_parser()
67+
args = parser.parse_args(["changelog", "add", "-m", "Added new API"])
68+
assert args.message == "Added new API"
69+
70+
def test_changelog_add_with_kind_and_message(self):
71+
parser = _build_parser()
72+
args = parser.parse_args(
73+
["changelog", "add", "sdk/core/azure-core", "--kind", "breaking", "-m", "Removed old API"]
74+
)
75+
assert args.package == "sdk/core/azure-core"
76+
assert args.kind == "breaking"
77+
assert args.message == "Removed old API"
78+
79+
def test_changelog_add_invalid_kind_rejected(self):
80+
parser = _build_parser()
81+
with pytest.raises(SystemExit):
82+
parser.parse_args(["changelog", "add", "--kind", "notakind"])
83+
84+
def test_changelog_add_all_valid_kinds_accepted(self):
85+
parser = _build_parser()
86+
for kind in _CHANGE_KINDS:
87+
args = parser.parse_args(["changelog", "add", "--kind", kind])
88+
assert args.kind == kind
89+
5090
def test_changelog_create(self):
5191
parser = _build_parser()
5292
args = parser.parse_args(["changelog", "create"])
@@ -146,6 +186,50 @@ def test_add_explicit_package_overrides_cwd(self, mock_which, mock_call, _mock_i
146186
cmd = mock_call.call_args[0][0]
147187
assert cmd == ["/usr/bin/npx", "--no", "chronus", "add", "sdk/core/azure-core"]
148188

189+
@patch("azpysdk.changelog.changelog._is_chronus_installed", return_value=True)
190+
@patch("azpysdk.changelog.subprocess.call", return_value=0)
191+
@patch("azpysdk.changelog.shutil.which", return_value="/usr/bin/npx")
192+
def test_add_with_kind_passes_flag(self, mock_which, mock_call, _mock_installed):
193+
"""The --kind flag should be forwarded to chronus."""
194+
parser = _build_parser()
195+
args = parser.parse_args(["changelog", "add", "--kind", "breaking"])
196+
with patch("os.getcwd", return_value=REPO_ROOT):
197+
result = args.func(args)
198+
assert result == 0
199+
cmd = mock_call.call_args[0][0]
200+
assert cmd == ["/usr/bin/npx", "--no", "chronus", "add", "--kind", "breaking"]
201+
202+
@patch("azpysdk.changelog.changelog._is_chronus_installed", return_value=True)
203+
@patch("azpysdk.changelog.subprocess.call", return_value=0)
204+
@patch("azpysdk.changelog.shutil.which", return_value="/usr/bin/npx")
205+
def test_add_with_message_passes_flag(self, mock_which, mock_call, _mock_installed):
206+
"""The --message flag should be forwarded to chronus."""
207+
parser = _build_parser()
208+
args = parser.parse_args(["changelog", "add", "-m", "Fixed upload bug"])
209+
with patch("os.getcwd", return_value=REPO_ROOT):
210+
result = args.func(args)
211+
assert result == 0
212+
cmd = mock_call.call_args[0][0]
213+
assert cmd == ["/usr/bin/npx", "--no", "chronus", "add", "--message", "Fixed upload bug"]
214+
215+
@patch("azpysdk.changelog.changelog._is_chronus_installed", return_value=True)
216+
@patch("azpysdk.changelog.subprocess.call", return_value=0)
217+
@patch("azpysdk.changelog.shutil.which", return_value="/usr/bin/npx")
218+
def test_add_with_kind_message_and_package(self, mock_which, mock_call, _mock_installed):
219+
"""All flags (package, --kind, --message) should be forwarded together."""
220+
parser = _build_parser()
221+
args = parser.parse_args([
222+
"changelog", "add", "sdk/core/azure-core",
223+
"--kind", "breaking", "-m", "Removed deprecated API",
224+
])
225+
result = args.func(args)
226+
assert result == 0
227+
cmd = mock_call.call_args[0][0]
228+
assert cmd == [
229+
"/usr/bin/npx", "--no", "chronus", "add", "sdk/core/azure-core",
230+
"--kind", "breaking", "--message", "Removed deprecated API",
231+
]
232+
149233
@patch("azpysdk.changelog.changelog._is_chronus_installed", return_value=True)
150234
@patch("azpysdk.changelog.subprocess.call", return_value=0)
151235
@patch("azpysdk.changelog.shutil.which", return_value="/usr/bin/npx")

0 commit comments

Comments
 (0)