omoshiroi-packager 0.1.0__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.
- omoshiroi_packager-0.1.0/.env.example +11 -0
- omoshiroi_packager-0.1.0/LICENSE +21 -0
- omoshiroi_packager-0.1.0/MANIFEST.in +14 -0
- omoshiroi_packager-0.1.0/PKG-INFO +39 -0
- omoshiroi_packager-0.1.0/README.md +8 -0
- omoshiroi_packager-0.1.0/omoshiroi_packager/__init__.py +96 -0
- omoshiroi_packager-0.1.0/omoshiroi_packager/config.py +104 -0
- omoshiroi_packager-0.1.0/omoshiroi_packager/core/__init__.py +11 -0
- omoshiroi_packager-0.1.0/omoshiroi_packager/core/manifest.py +160 -0
- omoshiroi_packager-0.1.0/omoshiroi_packager/core/packager.py +108 -0
- omoshiroi_packager-0.1.0/omoshiroi_packager/core/updater.py +155 -0
- omoshiroi_packager-0.1.0/omoshiroi_packager/exceptions.py +38 -0
- omoshiroi_packager-0.1.0/omoshiroi_packager/models.py +122 -0
- omoshiroi_packager-0.1.0/omoshiroi_packager/packages.py +196 -0
- omoshiroi_packager-0.1.0/omoshiroi_packager/utils/__init__.py +17 -0
- omoshiroi_packager-0.1.0/omoshiroi_packager/utils/file_utils.py +197 -0
- omoshiroi_packager-0.1.0/omoshiroi_packager/utils/hash_utils.py +71 -0
- omoshiroi_packager-0.1.0/omoshiroi_packager/utils/zip_utils.py +99 -0
- omoshiroi_packager-0.1.0/omoshiroi_packager/version.py +407 -0
- omoshiroi_packager-0.1.0/omoshiroi_packager.egg-info/PKG-INFO +39 -0
- omoshiroi_packager-0.1.0/omoshiroi_packager.egg-info/SOURCES.txt +24 -0
- omoshiroi_packager-0.1.0/omoshiroi_packager.egg-info/dependency_links.txt +1 -0
- omoshiroi_packager-0.1.0/omoshiroi_packager.egg-info/requires.txt +10 -0
- omoshiroi_packager-0.1.0/omoshiroi_packager.egg-info/top_level.txt +1 -0
- omoshiroi_packager-0.1.0/pyproject.toml +47 -0
- omoshiroi_packager-0.1.0/setup.cfg +4 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Omoshiroi Packager Configuration
|
|
2
|
+
# Copy this file to .env and update the paths
|
|
3
|
+
|
|
4
|
+
# Directory for manifest files
|
|
5
|
+
MANIFEST_DIR=/path/to/manifests
|
|
6
|
+
|
|
7
|
+
# Root directory of the project
|
|
8
|
+
PROJECT_DIR=/path/to/project
|
|
9
|
+
|
|
10
|
+
# Temporary directory for build artifacts
|
|
11
|
+
TMP_DIR=/path/to/tmp
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ovcharenski
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Include files for distribution
|
|
2
|
+
include README.md
|
|
3
|
+
include LICENSE
|
|
4
|
+
include pyproject.toml
|
|
5
|
+
include .env.example
|
|
6
|
+
|
|
7
|
+
# Exclude test files
|
|
8
|
+
prune tests/
|
|
9
|
+
prune .github/
|
|
10
|
+
|
|
11
|
+
# Exclude development files
|
|
12
|
+
exclude build.py
|
|
13
|
+
exclude .gitignore
|
|
14
|
+
exclude .pre-commit-config.yaml
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: omoshiroi-packager
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Version parser with multiple channels support
|
|
5
|
+
Author-email: ovcharenski <stun.bread.frog@outlook.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://ns-staff.ovcharenski.ru/projects/omoshiroi-packager
|
|
8
|
+
Project-URL: Repository, https://github.com/ovcharenski/omoshiroi-packager
|
|
9
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Requires-Python: >=3.8
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE
|
|
21
|
+
Requires-Dist: python-dotenv>=1.0.0
|
|
22
|
+
Provides-Extra: dev
|
|
23
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
24
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
|
|
25
|
+
Requires-Dist: build>=0.10.0; extra == "dev"
|
|
26
|
+
Requires-Dist: twine>=4.0.0; extra == "dev"
|
|
27
|
+
Requires-Dist: black>=22.0.0; extra == "dev"
|
|
28
|
+
Requires-Dist: ruff>=0.1.0; extra == "dev"
|
|
29
|
+
Requires-Dist: mypy>=1.0.0; extra == "dev"
|
|
30
|
+
Dynamic: license-file
|
|
31
|
+
|
|
32
|
+
# Omoshiroi Packager
|
|
33
|
+
|
|
34
|
+
Python package for parsing and comparing software versions with multiple channels.
|
|
35
|
+
|
|
36
|
+
## Installation
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pip install omoshiroi-packager
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Omoshiroi Packager - Version management and packaging utilities.
|
|
3
|
+
|
|
4
|
+
A comprehensive package for parsing, comparing, and managing software versions
|
|
5
|
+
with multiple channel support (dev, alpha, beta, rc, stable, hotfix).
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from .config import Config
|
|
9
|
+
from .core import ManifestManager, Packager, Updater
|
|
10
|
+
from .exceptions import (
|
|
11
|
+
ConfigError,
|
|
12
|
+
FileError,
|
|
13
|
+
HashError,
|
|
14
|
+
ManifestError,
|
|
15
|
+
PackageError,
|
|
16
|
+
PackError,
|
|
17
|
+
VersionError,
|
|
18
|
+
)
|
|
19
|
+
from .models import FileProperties, Manifest, UpdateCheckResult
|
|
20
|
+
from .packages import (
|
|
21
|
+
check_update,
|
|
22
|
+
get_all_versions,
|
|
23
|
+
get_latest_version,
|
|
24
|
+
make_manifest,
|
|
25
|
+
pack_by_channel,
|
|
26
|
+
pack_version,
|
|
27
|
+
reset_config,
|
|
28
|
+
)
|
|
29
|
+
from .utils import (
|
|
30
|
+
IgnorePattern,
|
|
31
|
+
ProjectConfig,
|
|
32
|
+
ProjectFiles,
|
|
33
|
+
create_zip_from_files,
|
|
34
|
+
extract_zip,
|
|
35
|
+
generate_file_hash,
|
|
36
|
+
get_zip_file_list,
|
|
37
|
+
verify_file_hash,
|
|
38
|
+
)
|
|
39
|
+
from .version import (
|
|
40
|
+
InvalidVersion, # ← ДОБАВИТЬ ЭТО
|
|
41
|
+
Version,
|
|
42
|
+
is_valid,
|
|
43
|
+
latest_stable,
|
|
44
|
+
latest_version,
|
|
45
|
+
parse,
|
|
46
|
+
sort_versions,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
__version__ = "0.1.0"
|
|
50
|
+
__author__ = "ovcharenski"
|
|
51
|
+
|
|
52
|
+
__all__ = [
|
|
53
|
+
# Version
|
|
54
|
+
"InvalidVersion", # ← ДОБАВИТЬ ЭТО
|
|
55
|
+
"Version",
|
|
56
|
+
"parse",
|
|
57
|
+
"is_valid",
|
|
58
|
+
"sort_versions",
|
|
59
|
+
"latest_version",
|
|
60
|
+
"latest_stable",
|
|
61
|
+
# Config
|
|
62
|
+
"Config",
|
|
63
|
+
# Models
|
|
64
|
+
"Manifest",
|
|
65
|
+
"FileProperties",
|
|
66
|
+
"UpdateCheckResult",
|
|
67
|
+
# Exceptions
|
|
68
|
+
"PackageError",
|
|
69
|
+
"ManifestError",
|
|
70
|
+
"FileError",
|
|
71
|
+
"HashError",
|
|
72
|
+
"VersionError",
|
|
73
|
+
"ConfigError",
|
|
74
|
+
"PackError",
|
|
75
|
+
# Main API
|
|
76
|
+
"get_latest_version",
|
|
77
|
+
"check_update",
|
|
78
|
+
"pack_version",
|
|
79
|
+
"pack_by_channel",
|
|
80
|
+
"make_manifest",
|
|
81
|
+
"get_all_versions",
|
|
82
|
+
"reset_config",
|
|
83
|
+
# Core
|
|
84
|
+
"ManifestManager",
|
|
85
|
+
"Packager",
|
|
86
|
+
"Updater",
|
|
87
|
+
# Utils
|
|
88
|
+
"IgnorePattern",
|
|
89
|
+
"ProjectFiles",
|
|
90
|
+
"ProjectConfig",
|
|
91
|
+
"generate_file_hash",
|
|
92
|
+
"verify_file_hash",
|
|
93
|
+
"create_zip_from_files",
|
|
94
|
+
"extract_zip",
|
|
95
|
+
"get_zip_file_list",
|
|
96
|
+
]
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Configuration management for the packages module.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
from dotenv import load_dotenv
|
|
12
|
+
|
|
13
|
+
from .exceptions import ConfigError
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class Config:
|
|
18
|
+
"""
|
|
19
|
+
Application configuration.
|
|
20
|
+
|
|
21
|
+
Attributes:
|
|
22
|
+
manifest_dir: Directory for manifest files
|
|
23
|
+
project_dir: Root directory of the project
|
|
24
|
+
tmp_dir: Temporary directory for build artifacts
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
manifest_dir: Path
|
|
28
|
+
project_dir: Path
|
|
29
|
+
tmp_dir: Path
|
|
30
|
+
|
|
31
|
+
@classmethod
|
|
32
|
+
def from_env(cls, load_dotenv_file: bool = True) -> Config:
|
|
33
|
+
"""
|
|
34
|
+
Create configuration from environment variables.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
load_dotenv_file: Whether to load .env file
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
Config instance
|
|
41
|
+
|
|
42
|
+
Raises:
|
|
43
|
+
ConfigError: If required variables are missing
|
|
44
|
+
"""
|
|
45
|
+
if load_dotenv_file:
|
|
46
|
+
load_dotenv()
|
|
47
|
+
|
|
48
|
+
manifest_dir = os.getenv("MANIFEST_DIR")
|
|
49
|
+
project_dir = os.getenv("PROJECT_DIR")
|
|
50
|
+
tmp_dir = os.getenv("TMP_DIR")
|
|
51
|
+
|
|
52
|
+
missing = []
|
|
53
|
+
if not manifest_dir:
|
|
54
|
+
missing.append("MANIFEST_DIR")
|
|
55
|
+
if not project_dir:
|
|
56
|
+
missing.append("PROJECT_DIR")
|
|
57
|
+
if not tmp_dir:
|
|
58
|
+
missing.append("TMP_DIR")
|
|
59
|
+
|
|
60
|
+
if missing:
|
|
61
|
+
raise ConfigError(
|
|
62
|
+
f"Missing required environment variables: {', '.join(missing)}"
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
return cls(
|
|
66
|
+
manifest_dir=Path(manifest_dir),
|
|
67
|
+
project_dir=Path(project_dir),
|
|
68
|
+
tmp_dir=Path(tmp_dir),
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
def ensure_directories(self) -> None:
|
|
72
|
+
"""Ensure all required directories exist."""
|
|
73
|
+
for dir_path in [self.manifest_dir, self.tmp_dir]:
|
|
74
|
+
dir_path.mkdir(parents=True, exist_ok=True)
|
|
75
|
+
|
|
76
|
+
@property
|
|
77
|
+
def manifest_stable_path(self) -> Path:
|
|
78
|
+
"""Path to stable manifest file."""
|
|
79
|
+
return self.manifest_dir / "manifest-stable.json"
|
|
80
|
+
|
|
81
|
+
@property
|
|
82
|
+
def manifest_beta_path(self) -> Path:
|
|
83
|
+
"""Path to beta manifest file."""
|
|
84
|
+
return self.manifest_dir / "manifest-beta.json"
|
|
85
|
+
|
|
86
|
+
def get_manifest_path(self, channel: str) -> Path:
|
|
87
|
+
"""
|
|
88
|
+
Get manifest path for a channel.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
channel: Channel name (stable, beta, dev, etc.)
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
Path to manifest file
|
|
95
|
+
"""
|
|
96
|
+
return self.manifest_dir / f"manifest-{channel}.json"
|
|
97
|
+
|
|
98
|
+
def get_ignore_file_path(self) -> Path:
|
|
99
|
+
"""Get path to .ignore file."""
|
|
100
|
+
return self.project_dir / ".ignore"
|
|
101
|
+
|
|
102
|
+
def get_config_file_path(self) -> Path:
|
|
103
|
+
"""Get path to .config file."""
|
|
104
|
+
return self.project_dir / ".config"
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Manifest management for the packages module.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
import time
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Any, Dict, List, Optional
|
|
11
|
+
|
|
12
|
+
from ..config import Config
|
|
13
|
+
from ..models import FileProperties, Manifest
|
|
14
|
+
from ..exceptions import ManifestError
|
|
15
|
+
from ..utils.file_utils import ProjectFiles
|
|
16
|
+
from ..utils.hash_utils import generate_file_hash
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class ManifestManager:
|
|
20
|
+
"""Manager for package manifests."""
|
|
21
|
+
|
|
22
|
+
def __init__(self, config: Config):
|
|
23
|
+
"""
|
|
24
|
+
Initialize manifest manager.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
config: Application configuration
|
|
28
|
+
"""
|
|
29
|
+
self.config = config
|
|
30
|
+
self._cache: Dict[str, Optional[Manifest]] = {}
|
|
31
|
+
|
|
32
|
+
def create_manifest(
|
|
33
|
+
self,
|
|
34
|
+
version: str,
|
|
35
|
+
channel: str,
|
|
36
|
+
project_files: Optional[ProjectFiles] = None,
|
|
37
|
+
) -> Manifest:
|
|
38
|
+
"""
|
|
39
|
+
Create a new manifest for a version.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
version: Version string
|
|
43
|
+
channel: Channel name
|
|
44
|
+
project_files: ProjectFiles instance (creates new if not provided)
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
Manifest object
|
|
48
|
+
"""
|
|
49
|
+
if project_files is None:
|
|
50
|
+
project_files = ProjectFiles(self.config.project_dir)
|
|
51
|
+
|
|
52
|
+
files_list = []
|
|
53
|
+
for file_path in project_files.get_files():
|
|
54
|
+
full_path = project_files.get_full_path(file_path)
|
|
55
|
+
|
|
56
|
+
if full_path.exists():
|
|
57
|
+
sha256, sha512 = generate_file_hash(full_path)
|
|
58
|
+
files_list.append(
|
|
59
|
+
FileProperties(
|
|
60
|
+
path=file_path,
|
|
61
|
+
size=full_path.stat().st_size,
|
|
62
|
+
sha256=sha256,
|
|
63
|
+
sha512=sha512,
|
|
64
|
+
)
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
return Manifest(
|
|
68
|
+
version=version,
|
|
69
|
+
channel=channel,
|
|
70
|
+
timestamp=int(time.time()),
|
|
71
|
+
files=files_list,
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
def save_manifest(self, manifest: Manifest, channel: Optional[str] = None) -> Path:
|
|
75
|
+
"""
|
|
76
|
+
Save a manifest to disk.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
manifest: Manifest object
|
|
80
|
+
channel: Channel name (uses manifest.channel if not provided)
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
Path to saved manifest file
|
|
84
|
+
"""
|
|
85
|
+
channel = channel or manifest.channel
|
|
86
|
+
manifest_path = self.config.get_manifest_path(channel)
|
|
87
|
+
|
|
88
|
+
# Ensure manifest directory exists
|
|
89
|
+
manifest_path.parent.mkdir(parents=True, exist_ok=True)
|
|
90
|
+
|
|
91
|
+
try:
|
|
92
|
+
with open(manifest_path, "w", encoding="utf-8") as f:
|
|
93
|
+
json.dump(manifest.to_dict(), f, indent=2)
|
|
94
|
+
except OSError as e:
|
|
95
|
+
raise ManifestError(f"Failed to save manifest {manifest_path}: {e}")
|
|
96
|
+
|
|
97
|
+
return manifest_path
|
|
98
|
+
|
|
99
|
+
def load_manifest(self, channel: str) -> Optional[Manifest]:
|
|
100
|
+
"""
|
|
101
|
+
Load a manifest from disk.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
channel: Channel name
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
Manifest object or None if not found
|
|
108
|
+
"""
|
|
109
|
+
# Check cache
|
|
110
|
+
if channel in self._cache:
|
|
111
|
+
return self._cache[channel]
|
|
112
|
+
|
|
113
|
+
manifest_path = self.config.get_manifest_path(channel)
|
|
114
|
+
|
|
115
|
+
if not manifest_path.exists():
|
|
116
|
+
return None
|
|
117
|
+
|
|
118
|
+
try:
|
|
119
|
+
with open(manifest_path, "r", encoding="utf-8") as f:
|
|
120
|
+
data = json.load(f)
|
|
121
|
+
|
|
122
|
+
manifest = Manifest.from_dict(data)
|
|
123
|
+
self._cache[channel] = manifest
|
|
124
|
+
return manifest
|
|
125
|
+
|
|
126
|
+
except (OSError, json.JSONDecodeError) as e:
|
|
127
|
+
raise ManifestError(f"Failed to load manifest {manifest_path}: {e}")
|
|
128
|
+
|
|
129
|
+
def get_version(self, channel: str) -> Optional[str]:
|
|
130
|
+
"""
|
|
131
|
+
Get version from a channel's manifest.
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
channel: Channel name
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
Version string or None
|
|
138
|
+
"""
|
|
139
|
+
manifest = self.load_manifest(channel)
|
|
140
|
+
return manifest.version if manifest else None
|
|
141
|
+
|
|
142
|
+
def get_all_manifests(self) -> Dict[str, Optional[Manifest]]:
|
|
143
|
+
"""
|
|
144
|
+
Get manifests for all channels.
|
|
145
|
+
|
|
146
|
+
Returns:
|
|
147
|
+
Dictionary mapping channel to Manifest
|
|
148
|
+
"""
|
|
149
|
+
# Channels list - can be extended
|
|
150
|
+
channels = ["dev", "alpha", "beta", "rc", "stable", "hotfix"]
|
|
151
|
+
|
|
152
|
+
result = {}
|
|
153
|
+
for channel in channels:
|
|
154
|
+
result[channel] = self.load_manifest(channel)
|
|
155
|
+
|
|
156
|
+
return result
|
|
157
|
+
|
|
158
|
+
def clear_cache(self) -> None:
|
|
159
|
+
"""Clear the manifest cache."""
|
|
160
|
+
self._cache.clear()
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Packaging functionality for the packages module.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from io import BytesIO
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Optional, Tuple
|
|
10
|
+
|
|
11
|
+
from ..config import Config
|
|
12
|
+
from ..exceptions import PackError
|
|
13
|
+
from ..utils.file_utils import ProjectFiles, ProjectConfig
|
|
14
|
+
from ..utils.zip_utils import create_zip_from_files
|
|
15
|
+
from ..version import parse
|
|
16
|
+
from .manifest import ManifestManager
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class Packager:
|
|
20
|
+
"""Handler for packaging versions."""
|
|
21
|
+
|
|
22
|
+
def __init__(self, config: Config):
|
|
23
|
+
"""
|
|
24
|
+
Initialize packager.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
config: Application configuration
|
|
28
|
+
"""
|
|
29
|
+
self.config = config
|
|
30
|
+
self.manifest_manager = ManifestManager(config)
|
|
31
|
+
|
|
32
|
+
def pack_version(
|
|
33
|
+
self,
|
|
34
|
+
version: Optional[str] = None,
|
|
35
|
+
channel: Optional[str] = None,
|
|
36
|
+
) -> Tuple[BytesIO, Path]:
|
|
37
|
+
"""
|
|
38
|
+
Package a version.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
version: Version string (reads from .config if not provided)
|
|
42
|
+
channel: Channel name (parsed from version if not provided)
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
Tuple of (zip_data, manifest_path)
|
|
46
|
+
|
|
47
|
+
Raises:
|
|
48
|
+
PackError: If packaging fails
|
|
49
|
+
"""
|
|
50
|
+
try:
|
|
51
|
+
# Get version from config if not provided
|
|
52
|
+
if version is None:
|
|
53
|
+
project_config = ProjectConfig(self.config.get_config_file_path())
|
|
54
|
+
version = project_config.get_version()
|
|
55
|
+
|
|
56
|
+
if version is None:
|
|
57
|
+
raise PackError("Version not found in .config file")
|
|
58
|
+
|
|
59
|
+
# Parse channel from version if not provided
|
|
60
|
+
if channel is None:
|
|
61
|
+
version_obj = parse(version)
|
|
62
|
+
channel = version_obj.channel
|
|
63
|
+
|
|
64
|
+
# Create project files handler
|
|
65
|
+
project_files = ProjectFiles(self.config.project_dir)
|
|
66
|
+
|
|
67
|
+
# Create manifest
|
|
68
|
+
manifest = self.manifest_manager.create_manifest(
|
|
69
|
+
version=version,
|
|
70
|
+
channel=channel,
|
|
71
|
+
project_files=project_files,
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
# Save manifest
|
|
75
|
+
manifest_path = self.manifest_manager.save_manifest(manifest, channel)
|
|
76
|
+
|
|
77
|
+
# Get files to zip
|
|
78
|
+
files = project_files.get_files()
|
|
79
|
+
|
|
80
|
+
# Create ZIP archive
|
|
81
|
+
zip_data = create_zip_from_files(
|
|
82
|
+
files=files,
|
|
83
|
+
base_dir=self.config.project_dir,
|
|
84
|
+
manifest_path=manifest_path,
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
return zip_data, manifest_path
|
|
88
|
+
|
|
89
|
+
except Exception as e:
|
|
90
|
+
raise PackError(f"Failed to pack version: {e}")
|
|
91
|
+
|
|
92
|
+
def pack_by_channel(self, channel: str) -> Tuple[BytesIO, Path]:
|
|
93
|
+
"""
|
|
94
|
+
Package the latest version for a channel.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
channel: Channel name
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
Tuple of (zip_data, manifest_path)
|
|
101
|
+
"""
|
|
102
|
+
# Get version from manifest
|
|
103
|
+
version = self.manifest_manager.get_version(channel)
|
|
104
|
+
|
|
105
|
+
if version is None:
|
|
106
|
+
raise PackError(f"No version found for channel: {channel}")
|
|
107
|
+
|
|
108
|
+
return self.pack_version(version, channel)
|