plugin-kit-ai 2.0.0__tar.gz → 2.0.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {plugin_kit_ai-2.0.0/src/plugin_kit_ai.egg-info → plugin_kit_ai-2.0.2}/PKG-INFO +1 -1
- {plugin_kit_ai-2.0.0 → plugin_kit_ai-2.0.2}/src/plugin_kit_ai/__init__.py +1 -1
- {plugin_kit_ai-2.0.0 → plugin_kit_ai-2.0.2}/src/plugin_kit_ai/install.py +22 -4
- {plugin_kit_ai-2.0.0 → plugin_kit_ai-2.0.2/src/plugin_kit_ai.egg-info}/PKG-INFO +1 -1
- {plugin_kit_ai-2.0.0 → plugin_kit_ai-2.0.2}/src/plugin_kit_ai.egg-info/SOURCES.txt +2 -1
- plugin_kit_ai-2.0.2/tests/test_install.py +79 -0
- {plugin_kit_ai-2.0.0 → plugin_kit_ai-2.0.2}/LICENSE +0 -0
- {plugin_kit_ai-2.0.0 → plugin_kit_ai-2.0.2}/README.md +0 -0
- {plugin_kit_ai-2.0.0 → plugin_kit_ai-2.0.2}/pyproject.toml +0 -0
- {plugin_kit_ai-2.0.0 → plugin_kit_ai-2.0.2}/setup.cfg +0 -0
- {plugin_kit_ai-2.0.0 → plugin_kit_ai-2.0.2}/src/plugin_kit_ai/cli.py +0 -0
- {plugin_kit_ai-2.0.0 → plugin_kit_ai-2.0.2}/src/plugin_kit_ai/platform.py +0 -0
- {plugin_kit_ai-2.0.0 → plugin_kit_ai-2.0.2}/src/plugin_kit_ai.egg-info/dependency_links.txt +0 -0
- {plugin_kit_ai-2.0.0 → plugin_kit_ai-2.0.2}/src/plugin_kit_ai.egg-info/entry_points.txt +0 -0
- {plugin_kit_ai-2.0.0 → plugin_kit_ai-2.0.2}/src/plugin_kit_ai.egg-info/top_level.txt +0 -0
|
@@ -5,6 +5,7 @@ from __future__ import annotations
|
|
|
5
5
|
import hashlib
|
|
6
6
|
import json
|
|
7
7
|
import os
|
|
8
|
+
import re
|
|
8
9
|
import shutil
|
|
9
10
|
import stat
|
|
10
11
|
import subprocess
|
|
@@ -25,10 +26,27 @@ PLACEHOLDER_VERSION = "0.0.0.dev0"
|
|
|
25
26
|
|
|
26
27
|
|
|
27
28
|
def normalize_tag(raw: str) -> str:
|
|
28
|
-
value = str(raw or "")
|
|
29
|
+
value = str(raw or "")
|
|
29
30
|
if not value or value == "latest":
|
|
30
31
|
return ""
|
|
31
|
-
|
|
32
|
+
match = re.fullmatch(r"(plugin-kit-ai-v|v)?((0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*))", value)
|
|
33
|
+
if not match:
|
|
34
|
+
raise ValueError(f"invalid plugin-kit-ai release tag: {value}")
|
|
35
|
+
prefix, version, major = match.group(1), match.group(2), int(match.group(3))
|
|
36
|
+
if major >= 2:
|
|
37
|
+
if prefix == "v":
|
|
38
|
+
raise ValueError("plugin-kit-ai v2+ requires a product-prefixed release tag")
|
|
39
|
+
return f"plugin-kit-ai-v{version}"
|
|
40
|
+
return f"{prefix or 'v'}{version}"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def version_from_tag(tag: str) -> str:
|
|
44
|
+
normalized = normalize_tag(tag)
|
|
45
|
+
if not normalized:
|
|
46
|
+
raise ValueError("an exact plugin-kit-ai release tag is required")
|
|
47
|
+
if normalized.startswith("plugin-kit-ai-v"):
|
|
48
|
+
return normalized[len("plugin-kit-ai-v") :]
|
|
49
|
+
return normalized[1:]
|
|
32
50
|
|
|
33
51
|
|
|
34
52
|
def derive_release_base(api_base: str, override: str) -> str:
|
|
@@ -79,7 +97,7 @@ def fetch_text(url: str, accept_json: bool = False) -> str:
|
|
|
79
97
|
def latest_tag(api_base: str, repository: str) -> str:
|
|
80
98
|
clean_base = str(api_base or DEFAULT_API_BASE).strip().rstrip("/")
|
|
81
99
|
payload = json.loads(fetch_text(f"{clean_base}/repos/{repository}/releases/latest", accept_json=True))
|
|
82
|
-
tag_name = str(payload.get("tag_name", ""))
|
|
100
|
+
tag_name = str(payload.get("tag_name", ""))
|
|
83
101
|
if not tag_name:
|
|
84
102
|
raise RuntimeError(f"could not resolve latest release tag from {clean_base}")
|
|
85
103
|
return normalize_tag(tag_name)
|
|
@@ -142,7 +160,7 @@ def ensure_installed(*, quiet: bool = False) -> Dict[str, str]:
|
|
|
142
160
|
tag = resolve_requested_tag()
|
|
143
161
|
if not tag:
|
|
144
162
|
tag = latest_tag(api_base, repository)
|
|
145
|
-
version = tag
|
|
163
|
+
version = version_from_tag(tag)
|
|
146
164
|
asset_name = asset_name_for_version(version, platform_info)
|
|
147
165
|
|
|
148
166
|
cache_root = default_cache_root()
|
|
@@ -9,4 +9,5 @@ src/plugin_kit_ai.egg-info/PKG-INFO
|
|
|
9
9
|
src/plugin_kit_ai.egg-info/SOURCES.txt
|
|
10
10
|
src/plugin_kit_ai.egg-info/dependency_links.txt
|
|
11
11
|
src/plugin_kit_ai.egg-info/entry_points.txt
|
|
12
|
-
src/plugin_kit_ai.egg-info/top_level.txt
|
|
12
|
+
src/plugin_kit_ai.egg-info/top_level.txt
|
|
13
|
+
tests/test_install.py
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""Cold-cache wrapper regressions, without network or user cache access."""
|
|
2
|
+
import hashlib
|
|
3
|
+
import io
|
|
4
|
+
import os
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
import tarfile
|
|
7
|
+
import tempfile
|
|
8
|
+
import unittest
|
|
9
|
+
from unittest.mock import patch
|
|
10
|
+
|
|
11
|
+
from plugin_kit_ai import install
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ReleaseTags(unittest.TestCase):
|
|
15
|
+
def test_numeric_and_product_tags(self):
|
|
16
|
+
for raw, tag, version in [
|
|
17
|
+
('2.0.2', 'plugin-kit-ai-v2.0.2', '2.0.2'),
|
|
18
|
+
('plugin-kit-ai-v2.0.2', 'plugin-kit-ai-v2.0.2', '2.0.2'),
|
|
19
|
+
('1.2.4', 'v1.2.4', '1.2.4'),
|
|
20
|
+
('v1.2.4', 'v1.2.4', '1.2.4'),
|
|
21
|
+
('plugin-kit-ai-v1.2.4', 'plugin-kit-ai-v1.2.4', '1.2.4'),
|
|
22
|
+
]:
|
|
23
|
+
with self.subTest(raw=raw):
|
|
24
|
+
self.assertEqual(install.normalize_tag(raw), tag)
|
|
25
|
+
self.assertEqual(install.version_from_tag(tag), version)
|
|
26
|
+
|
|
27
|
+
def test_reject_wrong_product_and_malformed_tags(self):
|
|
28
|
+
for tag in ['agentplugins-v0.1.62', 'v2.0.2', '2.00.2', '2.0',
|
|
29
|
+
'plugin-kit-ai-plugin-kit-ai-v2.0.2', '2.0.2-rc.1',
|
|
30
|
+
'2.0.2+build', '../2.0.2', 'vlatest', '2.0.2\n',
|
|
31
|
+
' plugin-kit-ai-v2.0.2']:
|
|
32
|
+
with self.subTest(tag=tag), self.assertRaises(ValueError):
|
|
33
|
+
install.normalize_tag(tag)
|
|
34
|
+
|
|
35
|
+
def test_packaged_version_and_latest_product_validation(self):
|
|
36
|
+
with patch.dict(os.environ, {}, clear=True), patch.object(install, '__version__', '2.0.2'):
|
|
37
|
+
self.assertEqual(install.resolve_requested_tag(), 'plugin-kit-ai-v2.0.2')
|
|
38
|
+
with patch.object(install, 'fetch_text', return_value='{"tag_name":"agentplugins-v0.1.62"}'):
|
|
39
|
+
with self.assertRaises(ValueError):
|
|
40
|
+
install.latest_tag(install.DEFAULT_API_BASE, install.DEFAULT_REPOSITORY)
|
|
41
|
+
|
|
42
|
+
def test_cold_cache_and_checksum(self):
|
|
43
|
+
platform = install.detect_platform()
|
|
44
|
+
stream = io.BytesIO()
|
|
45
|
+
binary = b'fixture executable bytes'
|
|
46
|
+
with tarfile.open(fileobj=stream, mode='w:gz') as archive:
|
|
47
|
+
member = tarfile.TarInfo(platform.binary_name)
|
|
48
|
+
member.size = len(binary)
|
|
49
|
+
archive.addfile(member, io.BytesIO(binary))
|
|
50
|
+
body = stream.getvalue()
|
|
51
|
+
for raw in ['2.0.2', 'plugin-kit-ai-v2.0.2', '1.2.4', 'v1.2.4']:
|
|
52
|
+
for bad_checksum in [False, True]:
|
|
53
|
+
with self.subTest(raw=raw, bad_checksum=bad_checksum), tempfile.TemporaryDirectory() as root:
|
|
54
|
+
tag = install.normalize_tag(raw)
|
|
55
|
+
version = install.version_from_tag(tag)
|
|
56
|
+
asset = install.asset_name_for_version(version, platform)
|
|
57
|
+
base = f'https://github.com/{install.DEFAULT_REPOSITORY}/releases/download/{tag}'
|
|
58
|
+
checksum = '0' * 64 if bad_checksum else hashlib.sha256(body).hexdigest()
|
|
59
|
+
def fetch(url, accept_json=False):
|
|
60
|
+
self.assertIn(url, [f'{base}/checksums.txt', f'{base}/{asset}'])
|
|
61
|
+
return f'{checksum} {asset}\n'.encode() if url.endswith('/checksums.txt') else body
|
|
62
|
+
with patch.dict(os.environ, {'PLUGIN_KIT_AI_VERSION': raw,
|
|
63
|
+
'PLUGIN_KIT_AI_CACHE_DIR': root}, clear=True), \
|
|
64
|
+
patch.object(install, 'fetch_bytes', side_effect=fetch) as downloaded:
|
|
65
|
+
if bad_checksum:
|
|
66
|
+
with self.assertRaisesRegex(RuntimeError, 'checksum mismatch'):
|
|
67
|
+
install.ensure_installed(quiet=True)
|
|
68
|
+
self.assertFalse((Path(root) / tag / platform.binary_name).exists())
|
|
69
|
+
else:
|
|
70
|
+
result = install.ensure_installed(quiet=True)
|
|
71
|
+
self.assertEqual(result['version'], version)
|
|
72
|
+
self.assertEqual(Path(result['installed_binary']).read_bytes(), binary)
|
|
73
|
+
self.assertEqual(downloaded.call_count, 2)
|
|
74
|
+
install.ensure_installed(quiet=True)
|
|
75
|
+
self.assertEqual(downloaded.call_count, 2)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
if __name__ == '__main__':
|
|
79
|
+
unittest.main()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|