Skip to content

Commit a6f1e58

Browse files
author
Aaron Sun
committed
Display deprication warning during git extension setup
1 parent 56750c3 commit a6f1e58

File tree

4 files changed

+70
-17
lines changed

4 files changed

+70
-17
lines changed

extensions/git/extension.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,13 @@ extension:
88
author: spec-kit-core
99
repository: https://github.com/github/spec-kit
1010
license: MIT
11+
install_notice: |
12+
The git extension is currently enabled by default, but starting with
13+
v1.0.0 it will require explicit opt-in.
14+
15+
To opt in after v1.0.0:
16+
• specify init --extension git
17+
• specify extension add git (post-init)
1118
1219
requires:
1320
speckit_version: ">=0.2.0"

src/specify_cli/__init__.py

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1278,6 +1278,7 @@ def init(
12781278
ensure_constitution_from_template(project_path, tracker=tracker)
12791279

12801280
_git_ext_freshly_installed = False
1281+
_git_ext_install_notice: str | None = None
12811282
if not no_git:
12821283
tracker.start("git")
12831284
git_messages = []
@@ -1308,11 +1309,12 @@ def init(
13081309
if manager.registry.is_installed("git"):
13091310
git_messages.append("extension already installed")
13101311
else:
1311-
manager.install_from_directory(
1312+
ext_manifest = manager.install_from_directory(
13121313
bundled_path, get_speckit_version()
13131314
)
13141315
git_messages.append("extension installed")
13151316
_git_ext_freshly_installed = True
1317+
_git_ext_install_notice = ext_manifest.install_notice
13161318
else:
13171319
git_has_error = True
13181320
git_messages.append("bundled extension not found")
@@ -1456,15 +1458,11 @@ def init(
14561458
console.print(tracker.render())
14571459
console.print("\n[bold green]Project ready.[/bold green]")
14581460

1459-
if _git_ext_freshly_installed:
1461+
if _git_ext_freshly_installed and _git_ext_install_notice:
14601462
console.print()
14611463
console.print(
14621464
Panel(
1463-
"The [bold]git[/bold] extension is currently enabled by default, "
1464-
"but starting with [bold]v1.0.0[/bold] it will require explicit opt-in.\n\n"
1465-
"To opt in after v1.0.0:\n"
1466-
" • [cyan]specify init --extension git[/cyan]\n"
1467-
" • [cyan]specify extension add git[/cyan] (post-init)",
1465+
_git_ext_install_notice.strip(),
14681466
title="[yellow]⚠ Upcoming Change: git Extension[/yellow]",
14691467
border_style="yellow",
14701468
padding=(1, 2),

src/specify_cli/extensions.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,15 @@ def hooks(self) -> Dict[str, Any]:
346346
"""Get hook definitions."""
347347
return self.data.get("hooks", {})
348348

349+
@property
350+
def install_notice(self) -> str | None:
351+
"""Get optional install notice message.
352+
353+
Extensions can specify an 'install_notice' field to display
354+
important information to users when the extension is first installed.
355+
"""
356+
return self.data.get("extension", {}).get("install_notice")
357+
349358
def get_hash(self) -> str:
350359
"""Calculate SHA256 hash of manifest file."""
351360
with open(self.path, 'rb') as f:

tests/extensions/git/test_git_extension.py

Lines changed: 49 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -800,7 +800,7 @@ def test_check_feature_branch_rejects_malformed_timestamp(self, tmp_path: Path):
800800
capture_output=True, text=True,
801801
)
802802
assert result.returncode != 0
803-
803+
804804
def test_check_feature_branch_accepts_single_prefix(self, tmp_path: Path):
805805
"""git-common check_feature_branch matches core: one optional path prefix."""
806806
project = _setup_project(tmp_path)
@@ -839,26 +839,36 @@ def test_test_feature_branch_accepts_single_prefix(self, tmp_path: Path):
839839
assert result.returncode == 0
840840

841841

842-
# ── Deprecation Notice Tests ──────────────────────────────────────────────────
842+
# ── Upcoming Deprecation Notice Tests ──────────────────────────────────────────────────
843843

844844

845845
class TestGitExtDeprecationNotice:
846-
"""Tests for the v1.0.0 deprecation notice shown during specify init."""
846+
"""Tests for the v1.0.0 upcoming deprecation notice shown during specify init."""
847847

848-
def test_deprecation_notice_shown_on_fresh_install(self, tmp_path: Path):
849-
"""specify init shows the git extension deprecation notice on first install."""
848+
def test_upcoming_deprecation_notice_shown_on_fresh_install(self, tmp_path: Path):
849+
"""specify init shows the git extension upcoming deprecation notice on first install."""
850850
from typer.testing import CliRunner
851851
from unittest.mock import patch, MagicMock
852852
from specify_cli import app
853853

854854
project_dir = tmp_path / "test-project"
855855
runner = CliRunner()
856856

857+
mock_manifest = MagicMock()
858+
mock_manifest.install_notice = (
859+
"The git extension is currently enabled by default, but starting with\n"
860+
"v1.0.0 it will require explicit opt-in.\n\n"
861+
"To opt in after v1.0.0:\n"
862+
" • specify init --extension git\n"
863+
" • specify extension add git (post-init)"
864+
)
865+
857866
mock_registry = MagicMock()
858867
mock_registry.is_installed.return_value = False
859868

860869
mock_manager = MagicMock()
861870
mock_manager.registry = mock_registry
871+
mock_manager.install_from_directory.return_value = mock_manifest
862872

863873
with patch("specify_cli.extensions.ExtensionManager", return_value=mock_manager):
864874
result = runner.invoke(
@@ -870,10 +880,10 @@ def test_deprecation_notice_shown_on_fresh_install(self, tmp_path: Path):
870880
assert result.exit_code == 0, result.output
871881
assert "Upcoming Change: git Extension" in result.output
872882
assert "v1.0.0" in result.output
873-
assert "specify init --extension git" in result.output
883+
assert "specify extension add git" in result.output
874884

875-
def test_deprecation_notice_not_shown_when_already_installed(self, tmp_path: Path):
876-
"""specify init does NOT show the deprecation notice when git extension is already installed."""
885+
def test_upcoming_deprecation_notice_not_shown_when_already_installed(self, tmp_path: Path):
886+
"""specify init does NOT show the upcoming deprecation notice when git extension is already installed."""
877887
from typer.testing import CliRunner
878888
from unittest.mock import patch, MagicMock
879889
from specify_cli import app
@@ -897,8 +907,8 @@ def test_deprecation_notice_not_shown_when_already_installed(self, tmp_path: Pat
897907
assert result.exit_code == 0, result.output
898908
assert "Upcoming Change: git Extension" not in result.output
899909

900-
def test_deprecation_notice_not_shown_with_no_git_flag(self, tmp_path: Path):
901-
"""specify init does NOT show the deprecation notice when --no-git is passed."""
910+
def test_upcoming_deprecation_notice_not_shown_with_no_git_flag(self, tmp_path: Path):
911+
"""specify init does NOT show the upcoming deprecation notice when --no-git is passed."""
902912
from typer.testing import CliRunner
903913
from specify_cli import app
904914

@@ -913,3 +923,32 @@ def test_deprecation_notice_not_shown_with_no_git_flag(self, tmp_path: Path):
913923

914924
assert result.exit_code == 0, result.output
915925
assert "Upcoming Change: git Extension" not in result.output
926+
927+
def test_upcoming_deprecation_notice_not_shown_when_no_install_notice(self, tmp_path: Path):
928+
"""specify init does NOT show the upcoming deprecation notice if extension has no install_notice."""
929+
from typer.testing import CliRunner
930+
from unittest.mock import patch, MagicMock
931+
from specify_cli import app
932+
933+
project_dir = tmp_path / "test-project"
934+
runner = CliRunner()
935+
936+
mock_manifest = MagicMock()
937+
mock_manifest.install_notice = None # No notice defined
938+
939+
mock_registry = MagicMock()
940+
mock_registry.is_installed.return_value = False
941+
942+
mock_manager = MagicMock()
943+
mock_manager.registry = mock_registry
944+
mock_manager.install_from_directory.return_value = mock_manifest
945+
946+
with patch("specify_cli.extensions.ExtensionManager", return_value=mock_manager):
947+
result = runner.invoke(
948+
app,
949+
["init", str(project_dir), "--ai", "claude", "--ignore-agent-tools", "--script", "sh"],
950+
catch_exceptions=False,
951+
)
952+
953+
assert result.exit_code == 0, result.output
954+
assert "Upcoming Change: git Extension" not in result.output

0 commit comments

Comments
 (0)