From a0efdd6b951513571052e2ebafd3a7d46018c2d5 Mon Sep 17 00:00:00 2001 From: kartsan03 Date: Sun, 8 Nov 2026 19:34:59 +0000 Subject: [PATCH 1/4] fix(bundles): keep user-set priorities when a bundle is refreshed `bundle install --refresh` and `bundle update` reinstall each component with the priority the bundle declares, so a priority the user had set with `preset set-priority` or `extension set-priority` went back to the bundle's value. docs/bundles/cli.md says a refresh keeps primitive-level overrides such as preset priority. On a refresh, read the priority already recorded for the component and pass that to the reinstall. A fresh install still uses the bundle's declaration, and a missing or malformed stored value falls back to it. Assisted-by: Codex CLI (model: gpt-6.1-sol, autonomous) Assisted-by: Claude Code (model: claude-opus-5-5, autonomous) --- src/specify_cli/bundles/primitives.py | 18 ++++++++ tests/specify_cli/bundles/conftest.py | 29 +++++++++++++ .../bundles/test_command_install.py | 13 ++++++ .../bundles/test_command_update.py | 18 ++++++++ tests/specify_cli/bundles/test_primitives.py | 42 +++++++++++++++++++ 5 files changed, 120 insertions(+) diff --git a/src/specify_cli/bundles/primitives.py b/src/specify_cli/bundles/primitives.py index f910592255..5279c1372c 100644 --- a/src/specify_cli/bundles/primitives.py +++ b/src/specify_cli/bundles/primitives.py @@ -32,6 +32,20 @@ DEFAULT_PRIORITY = 10 +def _installed_priority(registry, component_id: str, default: int) -> int: + """Return the priority recorded for an installed component, or ``default``. + + A refresh reinstalls the component, so it must carry over a priority the + user set with ``set-priority`` instead of resetting it to the bundle's. + """ + from ..extensions import normalize_priority + + metadata = registry.get(component_id) + if not isinstance(metadata, dict): + return default + return normalize_priority(metadata.get("priority"), default) + + @dataclass(frozen=True) class BundledPatchDetails: kind: str @@ -331,6 +345,8 @@ def _do_install( speckit_version = get_speckit_version() priority = DEFAULT_PRIORITY if component.priority is None else component.priority + if force: + priority = _installed_priority(self._manager.registry, component.id, priority) bundled = _locate_bundled_preset(component.id) if bundled is not None and component.source is None: @@ -422,6 +438,8 @@ def _do_install( speckit_version = get_speckit_version() priority = DEFAULT_PRIORITY if component.priority is None else component.priority + if force: + priority = _installed_priority(self._manager.registry, component.id, priority) bundled = _locate_bundled_extension(component.id) if bundled is not None and component.source is None: diff --git a/tests/specify_cli/bundles/conftest.py b/tests/specify_cli/bundles/conftest.py index f7fa8213cd..20c7327b6c 100644 --- a/tests/specify_cli/bundles/conftest.py +++ b/tests/specify_cli/bundles/conftest.py @@ -10,3 +10,32 @@ def project(tmp_path: Path, monkeypatch) -> Path: (tmp_path / ".specify").mkdir() monkeypatch.chdir(tmp_path) return tmp_path + + +@pytest.fixture(params=["extensions", "presets"]) +def priority_bundle(project: Path, request): + from typer.testing import CliRunner + + from specify_cli import app + from specify_cli.extensions import ExtensionRegistry + from specify_cli.presets import PresetRegistry + from tests.specify_cli.bundles.helpers import valid_manifest_dict, write_manifest + + kind = request.param + cid = "bug" if kind == "extensions" else "lean" + component = {"id": cid, "version": "1.0.2" if kind == "extensions" else "1.0.0", "priority": 10} + if kind == "presets": + component["strategy"] = "replace" + source = write_manifest(project / "source", valid_manifest_dict(provides={kind: [component]})) + runner = CliRunner() + result = runner.invoke(app, ["bundle", "install", str(source), "--offline"]) + assert result.exit_code == 0, result.output + result = runner.invoke(app, [kind[:-1], "set-priority", cid, "3"]) + assert result.exit_code == 0, result.output + registry_type = ExtensionRegistry if kind == "extensions" else PresetRegistry + + def read_metadata(): + return registry_type(project / ".specify" / kind).get(cid) + + assert read_metadata()["priority"] == 3 + return source, read_metadata diff --git a/tests/specify_cli/bundles/test_command_install.py b/tests/specify_cli/bundles/test_command_install.py index fb5d8e19fd..5ebe776794 100644 --- a/tests/specify_cli/bundles/test_command_install.py +++ b/tests/specify_cli/bundles/test_command_install.py @@ -1100,3 +1100,16 @@ def test_install_initializes_uninitialized_project(tmp_path: Path): assert marker.exists() data = json.loads(marker.read_text(encoding="utf-8")) assert "copilot" in json.dumps(data) + + +def test_refresh_preserves_primitive_priority(priority_bundle): + from specify_cli.bundles.records import load_records + + source, read_metadata = priority_bundle + result = runner.invoke(app, ["bundle", "install", str(source), "--offline", "--refresh"]) + + assert result.exit_code == 0, result.output + assert read_metadata()["priority"] == 3 + record = load_records(source.parent.parent)[0] + assert record.required_components[0].priority == 10 + assert record.contributed_components[0].priority == 10 diff --git a/tests/specify_cli/bundles/test_command_update.py b/tests/specify_cli/bundles/test_command_update.py index 631f17106b..7e7c042b6f 100644 --- a/tests/specify_cli/bundles/test_command_update.py +++ b/tests/specify_cli/bundles/test_command_update.py @@ -352,3 +352,21 @@ def resolve(self, bundle_id, version=None): assert result.exit_code == 0, result.output assert "Requires external tools" not in result.output assert "Updated 'flat' to v1.2.0." in result.output + + +def test_update_preserves_primitive_priority(project, priority_bundle, monkeypatch): + from specify_cli.bundles import command_update + from specify_cli.bundles.sources import _local_manifest_source + + source, read_metadata = priority_bundle + catalog = write_catalog_file(project / "catalog.json", {"demo-bundle": catalog_entry_dict()}) + (project / ".specify/bundle-catalogs.yml").write_text(yaml.safe_dump({ + "schema_version": "1.0", + "catalogs": [{"id": "qa", "url": str(catalog), "priority": 0, "install_policy": "install-allowed"}], + }), encoding="utf-8") + monkeypatch.setattr(command_update, "_download_manifest", lambda *a, **k: _local_manifest_source(str(source))) + + result = runner.invoke(app, ["bundle", "update", "demo-bundle", "--offline"]) + + assert result.exit_code == 0, result.output + assert read_metadata()["priority"] == 3 diff --git a/tests/specify_cli/bundles/test_primitives.py b/tests/specify_cli/bundles/test_primitives.py index dfb0788a20..c1345c7c67 100644 --- a/tests/specify_cli/bundles/test_primitives.py +++ b/tests/specify_cli/bundles/test_primitives.py @@ -522,6 +522,8 @@ def test_catalog_preset_install_and_refresh_forward_catalog_name( calls = [] class _FakeManager: + registry = PresetRegistry(tmp_path / ".specify/presets") + def install_from_zip(self, *args, **kwargs): calls.append(kwargs) @@ -561,6 +563,8 @@ def test_catalog_extension_install_and_refresh_forward_catalog_and_scaffolding( scaffolded = [] class _FakeManager: + registry = ExtensionRegistry(tmp_path) + def install_from_zip(self, *args, **kwargs): installs.append(kwargs) return SimpleNamespace(id="catalog-extension") @@ -2274,3 +2278,41 @@ def test_extension_pin_refuses_archive_declaring_another_version( manager.install(ComponentRef(kind="extensions", id="my-ext", version="0.4.12")) assert not manager.is_installed(ComponentRef(kind="extensions", id="my-ext")) assert not zip_path.exists() + + +@pytest.mark.parametrize("kind", ["extensions", "presets"]) +@pytest.mark.parametrize("catalog_source", [False, True]) +@pytest.mark.parametrize("refresh", [False, True]) +@pytest.mark.parametrize("stored_priority", [None, 3, True]) +def test_primitive_priority_on_install_and_refresh( + tmp_path, monkeypatch, kind, catalog_source, refresh, stored_priority, +): + import specify_cli._assets as assets + + manager = primitive_manager(kind, tmp_path, allow_network=catalog_source) + if stored_priority is not None: + manager._manager.registry.add("c", {"priority": stored_priority}) + calls = [] + + def install(*args, **kwargs): + calls.append(args[2] if kind == "presets" else kwargs["priority"]) + return SimpleNamespace(id="c") + + if catalog_source: + entry = {"id": "c", "version": "1.0.0", "download_url": "https://example.com/c.zip"} + patch_catalog = _patch_extension_catalog if kind == "extensions" else _patch_preset_catalog + patch_catalog(monkeypatch, entry, tmp_path / "archive.zip", []) + monkeypatch.setattr(manager._manager, "install_from_zip", install) + else: + bundled = _write_manifest(tmp_path / "source", kind[:-1], "1.0.0") + monkeypatch.setattr(assets, f"_locate_bundled_{kind[:-1]}", lambda cid: bundled) + monkeypatch.setattr(manager._manager, "install_from_directory", install) + if kind == "extensions": + monkeypatch.setattr(manager._manager, "scaffold_config", lambda cid: None) + component = ComponentRef(kind, "c", "1.0.0", priority=5) + + (manager.refresh if refresh else manager.install)(component) + + # A malformed stored priority falls back to the bundle's declaration. + expected = 3 if refresh and stored_priority == 3 else 5 + assert calls == [expected] From eef627960f109764ea4c4887d523555777dc10b9 Mon Sep 17 00:00:00 2001 From: kartsan03 Date: Sun, 8 Nov 2026 20:18:50 +0000 Subject: [PATCH 2/4] fix(bundles): apply a changed bundle priority unless the user set one The refresh kept every stored priority, including the one the bundle itself set at install. A bundle update that changes its own priority (say from 10 to 5) then kept 10. Keep the stored priority only when it differs from every priority the installed bundle records declared for the component, which means the user changed it. Otherwise the bundle's new declaration applies. Assisted-by: Claude Code (model: claude-opus-5-5, autonomous) --- src/specify_cli/bundles/primitives.py | 35 +++++++++++----- tests/specify_cli/bundles/conftest.py | 40 ++++++++++++++----- .../bundles/test_command_install.py | 18 +++++++++ 3 files changed, 74 insertions(+), 19 deletions(-) diff --git a/src/specify_cli/bundles/primitives.py b/src/specify_cli/bundles/primitives.py index 5279c1372c..449dfbf424 100644 --- a/src/specify_cli/bundles/primitives.py +++ b/src/specify_cli/bundles/primitives.py @@ -32,18 +32,29 @@ DEFAULT_PRIORITY = 10 -def _installed_priority(registry, component_id: str, default: int) -> int: - """Return the priority recorded for an installed component, or ``default``. - - A refresh reinstalls the component, so it must carry over a priority the - user set with ``set-priority`` instead of resetting it to the bundle's. +def _refresh_priority( + project_root: Path, registry, component: ComponentRef, declared: int +) -> int: + """Return the priority for reinstalling *component* on a refresh. + + A priority the user set with ``set-priority`` differs from every priority + the installed bundles declared for the component, so it is kept. Otherwise + the bundle's current declaration applies, so an update can change it. """ from ..extensions import normalize_priority + from .records import load_records - metadata = registry.get(component_id) + metadata = registry.get(component.id) if not isinstance(metadata, dict): - return default - return normalize_priority(metadata.get("priority"), default) + return declared + stored = normalize_priority(metadata.get("priority"), declared) + bundle_priorities = { + DEFAULT_PRIORITY if ref.priority is None else ref.priority + for record in load_records(project_root) + for ref in record.contributed_components + if (ref.kind, ref.id) == (component.kind, component.id) + } + return declared if stored in bundle_priorities else stored @dataclass(frozen=True) @@ -346,7 +357,9 @@ def _do_install( speckit_version = get_speckit_version() priority = DEFAULT_PRIORITY if component.priority is None else component.priority if force: - priority = _installed_priority(self._manager.registry, component.id, priority) + priority = _refresh_priority( + self._root, self._manager.registry, component, priority + ) bundled = _locate_bundled_preset(component.id) if bundled is not None and component.source is None: @@ -439,7 +452,9 @@ def _do_install( speckit_version = get_speckit_version() priority = DEFAULT_PRIORITY if component.priority is None else component.priority if force: - priority = _installed_priority(self._manager.registry, component.id, priority) + priority = _refresh_priority( + self._root, self._manager.registry, component, priority + ) bundled = _locate_bundled_extension(component.id) if bundled is not None and component.source is None: diff --git a/tests/specify_cli/bundles/conftest.py b/tests/specify_cli/bundles/conftest.py index 20c7327b6c..394dbeb898 100644 --- a/tests/specify_cli/bundles/conftest.py +++ b/tests/specify_cli/bundles/conftest.py @@ -1,6 +1,7 @@ from __future__ import annotations from pathlib import Path +from types import SimpleNamespace import pytest @@ -13,7 +14,8 @@ def project(tmp_path: Path, monkeypatch) -> Path: @pytest.fixture(params=["extensions", "presets"]) -def priority_bundle(project: Path, request): +def bundled_component(project: Path, request): + """Install a bundle whose only component, an extension or a preset, declares priority 10.""" from typer.testing import CliRunner from specify_cli import app @@ -23,19 +25,39 @@ def priority_bundle(project: Path, request): kind = request.param cid = "bug" if kind == "extensions" else "lean" - component = {"id": cid, "version": "1.0.2" if kind == "extensions" else "1.0.0", "priority": 10} + component = {"id": cid, "version": "1.0.2" if kind == "extensions" else "1.0.0"} if kind == "presets": component["strategy"] = "replace" - source = write_manifest(project / "source", valid_manifest_dict(provides={kind: [component]})) - runner = CliRunner() - result = runner.invoke(app, ["bundle", "install", str(source), "--offline"]) - assert result.exit_code == 0, result.output - result = runner.invoke(app, [kind[:-1], "set-priority", cid, "3"]) + + def declare(priority: int) -> Path: + component["priority"] = priority + return write_manifest(project / "source", valid_manifest_dict(provides={kind: [component]})) + + source = declare(10) + result = CliRunner().invoke(app, ["bundle", "install", str(source), "--offline"]) assert result.exit_code == 0, result.output registry_type = ExtensionRegistry if kind == "extensions" else PresetRegistry def read_metadata(): return registry_type(project / ".specify" / kind).get(cid) - assert read_metadata()["priority"] == 3 - return source, read_metadata + assert read_metadata()["priority"] == 10 + return SimpleNamespace( + kind=kind, cid=cid, source=source, declare=declare, read_metadata=read_metadata + ) + + +@pytest.fixture +def priority_bundle(bundled_component): + """The bundle above, with the component's priority set to 3 by the user.""" + from typer.testing import CliRunner + + from specify_cli import app + + component = bundled_component + result = CliRunner().invoke( + app, [component.kind[:-1], "set-priority", component.cid, "3"] + ) + assert result.exit_code == 0, result.output + assert component.read_metadata()["priority"] == 3 + return component.source, component.read_metadata diff --git a/tests/specify_cli/bundles/test_command_install.py b/tests/specify_cli/bundles/test_command_install.py index 5ebe776794..29640db573 100644 --- a/tests/specify_cli/bundles/test_command_install.py +++ b/tests/specify_cli/bundles/test_command_install.py @@ -1113,3 +1113,21 @@ def test_refresh_preserves_primitive_priority(priority_bundle): record = load_records(source.parent.parent)[0] assert record.required_components[0].priority == 10 assert record.contributed_components[0].priority == 10 + + +def test_refresh_applies_a_changed_bundle_priority(bundled_component): + source = bundled_component.declare(5) + result = runner.invoke(app, ["bundle", "install", str(source), "--offline", "--refresh"]) + + assert result.exit_code == 0, result.output + assert bundled_component.read_metadata()["priority"] == 5 + + +def test_refresh_keeps_a_user_priority_when_the_bundle_priority_changes( + bundled_component, priority_bundle +): + source = bundled_component.declare(5) + result = runner.invoke(app, ["bundle", "install", str(source), "--offline", "--refresh"]) + + assert result.exit_code == 0, result.output + assert bundled_component.read_metadata()["priority"] == 3 From 50950fbdd08805e40ea216c9ce081f76decf56b6 Mon Sep 17 00:00:00 2001 From: kartsan03 Date: Sun, 8 Nov 2026 20:47:56 +0000 Subject: [PATCH 3/4] fix(bundles): remember a kept user priority across refreshes A refresh treated the stored priority as the user's only while it differed from every priority the bundle records declared. After a bundle version declared the user's value, the record held that value, and the next version's priority replaced the user's. When a refresh keeps a user priority, mark the registry entry with "user_priority". The reinstall rewrites the entry, so the refresh writes the mark again afterwards, and later refreshes keep a marked priority. Assisted-by: Claude Code (model: claude-opus-5-5, autonomous) --- src/specify_cli/bundles/primitives.py | 62 ++++++++++++------- .../bundles/test_command_install.py | 13 ++++ 2 files changed, 52 insertions(+), 23 deletions(-) diff --git a/src/specify_cli/bundles/primitives.py b/src/specify_cli/bundles/primitives.py index 449dfbf424..76f85bb8b3 100644 --- a/src/specify_cli/bundles/primitives.py +++ b/src/specify_cli/bundles/primitives.py @@ -32,29 +32,39 @@ DEFAULT_PRIORITY = 10 -def _refresh_priority( - project_root: Path, registry, component: ComponentRef, declared: int -) -> int: - """Return the priority for reinstalling *component* on a refresh. - - A priority the user set with ``set-priority`` differs from every priority - the installed bundles declared for the component, so it is kept. Otherwise - the bundle's current declaration applies, so an update can change it. +# Registry key that marks a priority as set by the user. A refresh reinstalls +# the component, so it writes the mark again after the reinstall. +_USER_PRIORITY_KEY = "user_priority" + + +def _user_priority( + project_root: Path, registry, component: ComponentRef +) -> int | None: + """Return the priority the user set for an installed component, or ``None``. + + The stored priority is the user's when an earlier refresh marked it so, or + when it differs from every priority the installed bundles declared for the + component, which means ``set-priority`` changed it. """ from ..extensions import normalize_priority from .records import load_records metadata = registry.get(component.id) if not isinstance(metadata, dict): - return declared - stored = normalize_priority(metadata.get("priority"), declared) + return None + # 0 stands for a missing or malformed stored value. + stored = normalize_priority(metadata.get("priority"), 0) + if stored == 0: + return None + if metadata.get(_USER_PRIORITY_KEY) is True: + return stored bundle_priorities = { DEFAULT_PRIORITY if ref.priority is None else ref.priority for record in load_records(project_root) for ref in record.contributed_components if (ref.kind, ref.id) == (component.kind, component.id) } - return declared if stored in bundle_priorities else stored + return None if stored in bundle_priorities else stored @dataclass(frozen=True) @@ -346,19 +356,22 @@ def install(self, component: ComponentRef) -> BundledPatchWarning | None: return self._do_install(component, force=False) def refresh(self, component: ComponentRef) -> BundledPatchWarning | None: - return self._do_install(component, force=True) + priority = _user_priority(self._root, self._manager.registry, component) + warning = self._do_install(component, force=True, priority=priority) + if priority is not None: + self._manager.registry.update(component.id, {_USER_PRIORITY_KEY: True}) + return warning def _do_install( - self, component: ComponentRef, *, force: bool, + self, component: ComponentRef, *, force: bool, priority: int | None = None, ) -> BundledPatchWarning | None: from .. import get_speckit_version from .._assets import _locate_bundled_preset speckit_version = get_speckit_version() - priority = DEFAULT_PRIORITY if component.priority is None else component.priority - if force: - priority = _refresh_priority( - self._root, self._manager.registry, component, priority + if priority is None: + priority = ( + DEFAULT_PRIORITY if component.priority is None else component.priority ) bundled = _locate_bundled_preset(component.id) @@ -441,19 +454,22 @@ def install(self, component: ComponentRef) -> BundledPatchWarning | None: return self._do_install(component, force=False) def refresh(self, component: ComponentRef) -> BundledPatchWarning | None: - return self._do_install(component, force=True) + priority = _user_priority(self._root, self._manager.registry, component) + warning = self._do_install(component, force=True, priority=priority) + if priority is not None: + self._manager.registry.update(component.id, {_USER_PRIORITY_KEY: True}) + return warning def _do_install( - self, component: ComponentRef, *, force: bool, + self, component: ComponentRef, *, force: bool, priority: int | None = None, ) -> BundledPatchWarning | None: from .. import get_speckit_version from .._assets import _locate_bundled_extension speckit_version = get_speckit_version() - priority = DEFAULT_PRIORITY if component.priority is None else component.priority - if force: - priority = _refresh_priority( - self._root, self._manager.registry, component, priority + if priority is None: + priority = ( + DEFAULT_PRIORITY if component.priority is None else component.priority ) bundled = _locate_bundled_extension(component.id) diff --git a/tests/specify_cli/bundles/test_command_install.py b/tests/specify_cli/bundles/test_command_install.py index 29640db573..7a662091ca 100644 --- a/tests/specify_cli/bundles/test_command_install.py +++ b/tests/specify_cli/bundles/test_command_install.py @@ -1131,3 +1131,16 @@ def test_refresh_keeps_a_user_priority_when_the_bundle_priority_changes( assert result.exit_code == 0, result.output assert bundled_component.read_metadata()["priority"] == 3 + + +def test_refresh_keeps_a_user_priority_after_the_bundle_declares_the_same_value( + bundled_component, priority_bundle +): + for declared in (3, 5): + source = bundled_component.declare(declared) + result = runner.invoke( + app, ["bundle", "install", str(source), "--offline", "--refresh"] + ) + + assert result.exit_code == 0, result.output + assert bundled_component.read_metadata()["priority"] == 3 From 88d82cef0d6a265cac3db18f863769362195157d Mon Sep 17 00:00:00 2001 From: kartsan03 Date: Sun, 8 Nov 2026 21:04:30 +0000 Subject: [PATCH 4/4] fix(bundles): keep the stored priority on every refresh The last two commits guessed whether the user had set a component's priority, from the bundle records and then from a registry mark. Each guess had cases it got wrong: a failed refresh retried, an extension shared by two bundles, an `extension update` in between. Go back to the rule the docs state: a refresh reapplies components through their primitive update paths and preserves primitive-level overrides such as preset priority. A refresh always keeps the stored priority, as `extension update` does, and the declared priority is used only when nothing valid is stored. A bundle version that changes its declared priority therefore doesn't change an existing install. Assisted-by: Claude Code (model: claude-opus-5-5, autonomous) --- src/specify_cli/bundles/primitives.py | 65 +++++-------------- .../bundles/test_command_install.py | 8 --- 2 files changed, 17 insertions(+), 56 deletions(-) diff --git a/src/specify_cli/bundles/primitives.py b/src/specify_cli/bundles/primitives.py index 76f85bb8b3..5279c1372c 100644 --- a/src/specify_cli/bundles/primitives.py +++ b/src/specify_cli/bundles/primitives.py @@ -32,39 +32,18 @@ DEFAULT_PRIORITY = 10 -# Registry key that marks a priority as set by the user. A refresh reinstalls -# the component, so it writes the mark again after the reinstall. -_USER_PRIORITY_KEY = "user_priority" +def _installed_priority(registry, component_id: str, default: int) -> int: + """Return the priority recorded for an installed component, or ``default``. - -def _user_priority( - project_root: Path, registry, component: ComponentRef -) -> int | None: - """Return the priority the user set for an installed component, or ``None``. - - The stored priority is the user's when an earlier refresh marked it so, or - when it differs from every priority the installed bundles declared for the - component, which means ``set-priority`` changed it. + A refresh reinstalls the component, so it must carry over a priority the + user set with ``set-priority`` instead of resetting it to the bundle's. """ from ..extensions import normalize_priority - from .records import load_records - metadata = registry.get(component.id) + metadata = registry.get(component_id) if not isinstance(metadata, dict): - return None - # 0 stands for a missing or malformed stored value. - stored = normalize_priority(metadata.get("priority"), 0) - if stored == 0: - return None - if metadata.get(_USER_PRIORITY_KEY) is True: - return stored - bundle_priorities = { - DEFAULT_PRIORITY if ref.priority is None else ref.priority - for record in load_records(project_root) - for ref in record.contributed_components - if (ref.kind, ref.id) == (component.kind, component.id) - } - return None if stored in bundle_priorities else stored + return default + return normalize_priority(metadata.get("priority"), default) @dataclass(frozen=True) @@ -356,23 +335,18 @@ def install(self, component: ComponentRef) -> BundledPatchWarning | None: return self._do_install(component, force=False) def refresh(self, component: ComponentRef) -> BundledPatchWarning | None: - priority = _user_priority(self._root, self._manager.registry, component) - warning = self._do_install(component, force=True, priority=priority) - if priority is not None: - self._manager.registry.update(component.id, {_USER_PRIORITY_KEY: True}) - return warning + return self._do_install(component, force=True) def _do_install( - self, component: ComponentRef, *, force: bool, priority: int | None = None, + self, component: ComponentRef, *, force: bool, ) -> BundledPatchWarning | None: from .. import get_speckit_version from .._assets import _locate_bundled_preset speckit_version = get_speckit_version() - if priority is None: - priority = ( - DEFAULT_PRIORITY if component.priority is None else component.priority - ) + priority = DEFAULT_PRIORITY if component.priority is None else component.priority + if force: + priority = _installed_priority(self._manager.registry, component.id, priority) bundled = _locate_bundled_preset(component.id) if bundled is not None and component.source is None: @@ -454,23 +428,18 @@ def install(self, component: ComponentRef) -> BundledPatchWarning | None: return self._do_install(component, force=False) def refresh(self, component: ComponentRef) -> BundledPatchWarning | None: - priority = _user_priority(self._root, self._manager.registry, component) - warning = self._do_install(component, force=True, priority=priority) - if priority is not None: - self._manager.registry.update(component.id, {_USER_PRIORITY_KEY: True}) - return warning + return self._do_install(component, force=True) def _do_install( - self, component: ComponentRef, *, force: bool, priority: int | None = None, + self, component: ComponentRef, *, force: bool, ) -> BundledPatchWarning | None: from .. import get_speckit_version from .._assets import _locate_bundled_extension speckit_version = get_speckit_version() - if priority is None: - priority = ( - DEFAULT_PRIORITY if component.priority is None else component.priority - ) + priority = DEFAULT_PRIORITY if component.priority is None else component.priority + if force: + priority = _installed_priority(self._manager.registry, component.id, priority) bundled = _locate_bundled_extension(component.id) if bundled is not None and component.source is None: diff --git a/tests/specify_cli/bundles/test_command_install.py b/tests/specify_cli/bundles/test_command_install.py index 7a662091ca..2323b7a8de 100644 --- a/tests/specify_cli/bundles/test_command_install.py +++ b/tests/specify_cli/bundles/test_command_install.py @@ -1115,14 +1115,6 @@ def test_refresh_preserves_primitive_priority(priority_bundle): assert record.contributed_components[0].priority == 10 -def test_refresh_applies_a_changed_bundle_priority(bundled_component): - source = bundled_component.declare(5) - result = runner.invoke(app, ["bundle", "install", str(source), "--offline", "--refresh"]) - - assert result.exit_code == 0, result.output - assert bundled_component.read_metadata()["priority"] == 5 - - def test_refresh_keeps_a_user_priority_when_the_bundle_priority_changes( bundled_component, priority_bundle ):