Skip to content

Commit 20e5f97

Browse files
committed
fix: address fourth round of PR review feedback
- Remove unused _write_catalog helper from test file - Fix comment: tests use monkeypatched urlopen, not file:// URLs - Wrap cache unlink calls in OSError handler - Add explicit encoding='utf-8' to all cache read_text/write_text calls - Restore packaging.version.Version for descriptor version validation to align with extension/preset validators - Add missing goose entry to integrations/catalog.json
1 parent 8ca42b6 commit 20e5f97

File tree

3 files changed

+25
-19
lines changed

3 files changed

+25
-19
lines changed

integrations/catalog.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,15 @@
245245
"author": "spec-kit-core",
246246
"repository": "https://github.com/github/spec-kit",
247247
"tags": ["generic"]
248+
},
249+
"goose": {
250+
"id": "goose",
251+
"name": "Goose",
252+
"version": "1.0.0",
253+
"description": "Goose CLI integration with YAML recipe format",
254+
"author": "spec-kit-core",
255+
"repository": "https://github.com/github/spec-kit",
256+
"tags": ["cli"]
248257
}
249258
}
250259
}

src/specify_cli/integrations/catalog.py

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from typing import Any, Dict, List, Optional
2020

2121
import yaml
22+
from packaging import version as pkg_version
2223

2324

2425
# ---------------------------------------------------------------------------
@@ -239,17 +240,20 @@ def _fetch_single_catalog(
239240

240241
if not force_refresh and cache_file.exists() and cache_meta.exists():
241242
try:
242-
meta = json.loads(cache_meta.read_text())
243+
meta = json.loads(cache_meta.read_text(encoding="utf-8"))
243244
cached_at = datetime.fromisoformat(meta.get("cached_at", ""))
244245
if cached_at.tzinfo is None:
245246
cached_at = cached_at.replace(tzinfo=timezone.utc)
246247
age = (datetime.now(timezone.utc) - cached_at).total_seconds()
247248
if age < self.CACHE_DURATION:
248-
return json.loads(cache_file.read_text())
249+
return json.loads(cache_file.read_text(encoding="utf-8"))
249250
except (json.JSONDecodeError, ValueError, KeyError, TypeError):
250251
# Cache is invalid or stale metadata; delete and refetch from source.
251-
cache_file.unlink(missing_ok=True)
252-
cache_meta.unlink(missing_ok=True)
252+
try:
253+
cache_file.unlink(missing_ok=True)
254+
cache_meta.unlink(missing_ok=True)
255+
except OSError:
256+
pass
253257

254258
try:
255259
with urllib.request.urlopen(entry.url, timeout=10) as resp:
@@ -273,15 +277,16 @@ def _fetch_single_catalog(
273277

274278
try:
275279
self.cache_dir.mkdir(parents=True, exist_ok=True)
276-
cache_file.write_text(json.dumps(catalog_data, indent=2))
280+
cache_file.write_text(json.dumps(catalog_data, indent=2), encoding="utf-8")
277281
cache_meta.write_text(
278282
json.dumps(
279283
{
280284
"cached_at": datetime.now(timezone.utc).isoformat(),
281285
"catalog_url": entry.url,
282286
},
283287
indent=2,
284-
)
288+
),
289+
encoding="utf-8",
285290
)
286291
except OSError:
287292
pass # Cache is best-effort; proceed with fetched data
@@ -473,9 +478,11 @@ def _validate(self) -> None:
473478
"must be lowercase alphanumeric with hyphens only"
474479
)
475480

476-
if not re.match(r"^\d+\.\d+\.\d+$", integ["version"]):
481+
try:
482+
pkg_version.Version(integ["version"])
483+
except pkg_version.InvalidVersion:
477484
raise IntegrationDescriptorError(
478-
f"Invalid version '{integ['version']}': must use semantic versioning (e.g., 1.0.0)"
485+
f"Invalid version '{integ['version']}'"
479486
)
480487

481488
requires = self.data["requires"]

tests/integrations/test_integration_catalog.py

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -118,20 +118,10 @@ def test_empty_config_raises(self, tmp_path):
118118

119119

120120
# ---------------------------------------------------------------------------
121-
# IntegrationCatalog — fetch & search (using local file:// catalog)
121+
# IntegrationCatalog — fetch & search (using monkeypatched urlopen responses)
122122
# ---------------------------------------------------------------------------
123123

124124

125-
def _write_catalog(path: Path, integrations: dict) -> None:
126-
"""Helper: write a catalog JSON file."""
127-
path.parent.mkdir(parents=True, exist_ok=True)
128-
path.write_text(json.dumps({
129-
"schema_version": "1.0",
130-
"updated_at": "2026-01-01T00:00:00Z",
131-
"integrations": integrations,
132-
}, indent=2))
133-
134-
135125
class TestCatalogFetch:
136126
"""Tests that use a local HTTP server stub via monkeypatch."""
137127

0 commit comments

Comments
 (0)