mirror of
https://github.com/github/spec-kit.git
synced 2026-08-03 06:26:30 +08:00
fix(presets): escape catalog metadata in discovery output (#3773)
Assisted-by: GitHub Copilot (model: GPT-5.6 Sol, autonomous) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -292,8 +292,13 @@ def preset_search(
|
||||
|
||||
console.print(f"\n[bold cyan]Presets ({len(results)} found):[/bold cyan]\n")
|
||||
for pack in results:
|
||||
console.print(f" [bold]{pack.get('name', pack['id'])}[/bold] ({pack['id']}) v{pack.get('version', '?')}")
|
||||
console.print(f" {pack.get('description', '')}")
|
||||
name = _escape_markup(str(pack.get("name", pack["id"])))
|
||||
pack_id = _escape_markup(str(pack["id"]))
|
||||
version = _escape_markup(str(pack.get("version", "?")))
|
||||
console.print(f" [bold]{name}[/bold] ({pack_id}) v{version}")
|
||||
console.print(
|
||||
f" {_escape_markup(str(pack.get('description', '')))}"
|
||||
)
|
||||
tags = pack.get("tags", [])
|
||||
if isinstance(tags, list) and tags:
|
||||
tags_str = _escape_markup(", ".join(str(t) for t in tags))
|
||||
@@ -375,6 +380,7 @@ def preset_info(
|
||||
from . import PresetCatalog, PresetManager, PresetError
|
||||
|
||||
project_root = _require_specify_project()
|
||||
safe_preset_id = _escape_markup(str(preset_id))
|
||||
# Check if installed locally first
|
||||
manager = PresetManager(project_root)
|
||||
local_pack = manager.get_pack(preset_id)
|
||||
@@ -417,21 +423,32 @@ def preset_info(
|
||||
console.print(f"[red]Error:[/red] Preset '{preset_id}' not found (not installed and not in catalog)")
|
||||
raise typer.Exit(1)
|
||||
|
||||
console.print(f"\n[bold cyan]Preset: {pack_info.get('name', preset_id)}[/bold cyan]\n")
|
||||
console.print(f" ID: {pack_info['id']}")
|
||||
console.print(f" Version: {pack_info.get('version', '?')}")
|
||||
console.print(f" Description: {pack_info.get('description', '')}")
|
||||
name = _escape_markup(str(pack_info.get("name", preset_id)))
|
||||
console.print(f"\n[bold cyan]Preset: {name}[/bold cyan]\n")
|
||||
console.print(f" ID: {_escape_markup(str(pack_info['id']))}")
|
||||
console.print(
|
||||
f" Version: {_escape_markup(str(pack_info.get('version', '?')))}"
|
||||
)
|
||||
console.print(
|
||||
f" Description: {_escape_markup(str(pack_info.get('description', '')))}"
|
||||
)
|
||||
if pack_info.get("author"):
|
||||
console.print(f" Author: {pack_info['author']}")
|
||||
console.print(
|
||||
f" Author: {_escape_markup(str(pack_info['author']))}"
|
||||
)
|
||||
catalog_tags = pack_info.get("tags", [])
|
||||
if isinstance(catalog_tags, list) and catalog_tags:
|
||||
console.print(f" Tags: {', '.join(str(t) for t in catalog_tags)}")
|
||||
if pack_info.get("repository"):
|
||||
console.print(f" Repository: {pack_info['repository']}")
|
||||
console.print(
|
||||
f" Repository: {_escape_markup(str(pack_info['repository']))}"
|
||||
)
|
||||
if pack_info.get("license"):
|
||||
console.print(f" License: {pack_info['license']}")
|
||||
console.print(
|
||||
f" License: {_escape_markup(str(pack_info['license']))}"
|
||||
)
|
||||
console.print("\n [yellow]Status: not installed[/yellow]")
|
||||
console.print(f" Install with: [cyan]specify preset add {preset_id}[/cyan]")
|
||||
console.print(f" Install with: [cyan]specify preset add {safe_preset_id}[/cyan]")
|
||||
console.print()
|
||||
|
||||
|
||||
|
||||
@@ -12104,3 +12104,69 @@ class TestPresetTagsNonString:
|
||||
|
||||
assert result.exit_code == 0, result.output
|
||||
assert "[bold]ci" in strip_ansi(result.output)
|
||||
|
||||
|
||||
class TestPresetCatalogRichMarkup:
|
||||
"""Catalog metadata must render as literal text in Rich output."""
|
||||
|
||||
MARKUP_PRESET = {
|
||||
"id": "[red]markup-id[/red]",
|
||||
"name": "[green]Markup Name[/green]",
|
||||
"version": "[blue]1.0.0[/blue]",
|
||||
"description": "[yellow]Markup Description[/yellow]",
|
||||
"author": "[magenta]Markup Author[/magenta]",
|
||||
"tags": ["[italic]markup-tag[/italic]"],
|
||||
"repository": "[bold]Markup Repository[/bold]",
|
||||
"license": "[cyan]Markup License[/cyan]",
|
||||
}
|
||||
|
||||
def test_search_escapes_catalog_markup(self, project_dir):
|
||||
from typer.testing import CliRunner
|
||||
from unittest.mock import patch
|
||||
from specify_cli import app
|
||||
|
||||
with patch.object(Path, "cwd", return_value=project_dir), patch.object(
|
||||
PresetCatalog,
|
||||
"search",
|
||||
return_value=[self.MARKUP_PRESET],
|
||||
):
|
||||
result = CliRunner().invoke(app, ["preset", "search"])
|
||||
|
||||
assert result.exit_code == 0, result.output
|
||||
output = " ".join(strip_ansi(result.output).split())
|
||||
for value in (
|
||||
self.MARKUP_PRESET["id"],
|
||||
self.MARKUP_PRESET["name"],
|
||||
self.MARKUP_PRESET["version"],
|
||||
self.MARKUP_PRESET["description"],
|
||||
):
|
||||
assert value in output
|
||||
|
||||
def test_info_escapes_catalog_markup(self, project_dir):
|
||||
from typer.testing import CliRunner
|
||||
from unittest.mock import patch
|
||||
from specify_cli import app
|
||||
|
||||
with patch.object(Path, "cwd", return_value=project_dir), patch.object(
|
||||
PresetCatalog,
|
||||
"get_pack_info",
|
||||
return_value=self.MARKUP_PRESET,
|
||||
):
|
||||
result = CliRunner().invoke(
|
||||
app,
|
||||
["preset", "info", self.MARKUP_PRESET["id"]],
|
||||
)
|
||||
|
||||
assert result.exit_code == 0, result.output
|
||||
output = " ".join(strip_ansi(result.output).split())
|
||||
for field in (
|
||||
"id",
|
||||
"name",
|
||||
"version",
|
||||
"description",
|
||||
"author",
|
||||
"repository",
|
||||
"license",
|
||||
):
|
||||
value = self.MARKUP_PRESET[field]
|
||||
assert value in output
|
||||
|
||||
Reference in New Issue
Block a user