Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/apm_cli/commands/marketplace/plugin/add.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
@click.option("-s", "--subdir", default=None, help="Subdirectory inside source repo")
@click.option("--tag-pattern", default=None, help="Tag pattern (e.g. 'v{version}')")
@click.option("--tags", default=None, help="Comma-separated tags")
@click.option("--category", default=None, help="Marketplace category")
@click.option("--include-prerelease", is_flag=True, help="Include prerelease versions")
@click.option("--no-verify", is_flag=True, help="Skip remote reachability check")
@click.option("--verbose", "-v", is_flag=True, help="Show detailed output")
Expand All @@ -47,6 +48,7 @@ def add(
subdir,
tag_pattern,
tags,
category,
include_prerelease,
no_verify,
verbose,
Expand Down Expand Up @@ -85,6 +87,7 @@ def add(
tag_pattern=tag_pattern,
tags=parsed_tags,
include_prerelease=include_prerelease,
category=category,
)
except MarketplaceYmlError as exc:
logger.error(str(exc), symbol="error")
Expand Down
4 changes: 4 additions & 0 deletions src/apm_cli/commands/marketplace/plugin/set.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
@click.option("--subdir", default=None, help="Subdirectory inside source repo")
@click.option("--tag-pattern", default=None, help="Tag pattern (e.g. 'v{version}')")
@click.option("--tags", default=None, help="Comma-separated tags")
@click.option("--category", default=None, help="Marketplace category")
@click.option(
"--include-prerelease",
is_flag=True,
Expand All @@ -42,6 +43,7 @@ def set_cmd(
subdir,
tag_pattern,
tags,
category,
include_prerelease,
verbose,
):
Expand Down Expand Up @@ -92,6 +94,8 @@ def set_cmd(
fields["tag_pattern"] = tag_pattern
if parsed_tags is not None:
fields["tags"] = parsed_tags
if category is not None:
fields["category"] = category
if include_prerelease is not None:
fields["include_prerelease"] = include_prerelease

Expand Down
16 changes: 15 additions & 1 deletion src/apm_cli/marketplace/yml_editor.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,12 @@ def _validate_subdir(subdir: str) -> None:
raise MarketplaceYmlError(str(exc)) from exc


def _validate_category(category: str) -> None:
"""Validate *category* is a non-empty string."""
if not isinstance(category, str) or not category.strip():
raise MarketplaceYmlError("'category' must be a non-empty string")

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 5b89a07. _validate_category() now returns the stripped value and both call sites store what it returns, so --category " Productivity " writes Productivity. Two regression tests cover the add and update paths.



# -------------------------------------------------------------------
# Public API
# -------------------------------------------------------------------
Expand All @@ -167,6 +173,7 @@ def add_plugin_entry(
tag_pattern: str | None = None,
tags: list[str] | None = None,
include_prerelease: bool = False,
category: str | None = None,
) -> str:
"""Append a new entry to ``packages[]``.

Expand All @@ -188,6 +195,9 @@ def add_plugin_entry(
if subdir is not None:
_validate_subdir(subdir)

if category is not None:
_validate_category(category)

# Derive name from source repo if not provided.
if name is None:
name = _default_name_from_source(source)
Expand Down Expand Up @@ -225,6 +235,8 @@ def add_plugin_entry(
new_entry["include_prerelease"] = True
if tags is not None and len(tags) > 0:
new_entry["tags"] = tags
if category is not None:
new_entry["category"] = category
Comment on lines +239 to +240

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 5b89a07: the stripped value is what gets stored, at both call sites.


packages.append(new_entry)

Expand Down Expand Up @@ -267,11 +279,13 @@ def update_plugin_entry(yml_path: Path, name: str, **fields) -> None:
del entry["version"]

# Simple scalar fields.
_SIMPLE_FIELDS = ("subdir", "tag_pattern")
_SIMPLE_FIELDS = ("subdir", "tag_pattern", "category")
for key in _SIMPLE_FIELDS:
if key in fields and fields[key] is not None:
if key == "subdir":
_validate_subdir(fields[key])
elif key == "category":
_validate_category(fields[key])
entry[key] = fields[key]

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 5b89a07: the stripped value is what gets stored, at both call sites.


# Boolean field: include_prerelease.
Expand Down
24 changes: 24 additions & 0 deletions tests/unit/marketplace/test_yml_editor.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ def test_add_with_all_optional_fields(self, tmp_path):
tag_pattern="v{version}",
tags=["utilities", "testing"],
include_prerelease=True,
category="Productivity",
)
assert name == "full-tool"
data = yaml.safe_load(yml.read_text(encoding="utf-8"))
Expand All @@ -85,6 +86,7 @@ def test_add_with_all_optional_fields(self, tmp_path):
assert added["tag_pattern"] == "v{version}"
assert added["tags"] == ["utilities", "testing"]
assert added["include_prerelease"] is True
assert added["category"] == "Productivity"

def test_name_defaults_to_repo_from_source(self, tmp_path):
yml = _write_yml(tmp_path, _BASIC_YML)
Expand Down Expand Up @@ -188,6 +190,16 @@ def test_path_traversal_in_subdir_raises(self, tmp_path):
subdir="../etc",
)

def test_blank_category_raises(self, tmp_path):
yml = _write_yml(tmp_path, _BASIC_YML)
with pytest.raises(MarketplaceYmlError, match="category"):
add_plugin_entry(
yml,
source="acme/tool",
version=">=1.0.0",
category=" ",
)


# ---------------------------------------------------------------------------
# add_plugin_entry - comment preservation
Expand Down Expand Up @@ -237,6 +249,13 @@ def test_update_subdir(self, tmp_path):
entry = data["packages"][0]
assert entry["subdir"] == "src/plugin"

def test_update_category(self, tmp_path):
yml = _write_yml(tmp_path, _BASIC_YML)
update_plugin_entry(yml, "existing-package", category="Productivity")
data = yaml.safe_load(yml.read_text(encoding="utf-8"))
entry = data["packages"][0]
assert entry["category"] == "Productivity"

def test_setting_ref_clears_version(self, tmp_path):
yml = _write_yml(tmp_path, _BASIC_YML)
update_plugin_entry(yml, "existing-package", ref="deadbeef")
Expand Down Expand Up @@ -289,6 +308,11 @@ def test_case_insensitive_match(self, tmp_path):


class TestUpdatePluginErrors:
def test_blank_category_raises(self, tmp_path):
yml = _write_yml(tmp_path, _BASIC_YML)
with pytest.raises(MarketplaceYmlError, match="category"):
update_plugin_entry(yml, "existing-package", category=" ")

def test_package_not_found_raises(self, tmp_path):
yml = _write_yml(tmp_path, _BASIC_YML)
with pytest.raises(MarketplaceYmlError, match="not found"):
Expand Down
Loading