diff --git a/Cargo.toml b/Cargo.toml index 03720427..aab44027 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,13 +5,14 @@ members = [ "src/lib-rust", "src/lib-nodejs/velopack_nodeffi", "src/lib-cpp", + "src/lib-python", ] exclude = [ "samples/RustIced", ] [workspace.package] -version = "0.0.0-local" +version = "0.0.1" authors = ["Velopack Ltd, Caelan Sayler "] homepage = "https://velopack.io" repository = "https://github.com/velopack/velopack" diff --git a/src/lib-python/.gitignore b/src/lib-python/.gitignore new file mode 100644 index 00000000..88632244 --- /dev/null +++ b/src/lib-python/.gitignore @@ -0,0 +1,201 @@ +build/* +dist/* +Releases/* +*.zip +*.tar.gz +test/app_version.py + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# UV +# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +#uv.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/latest/usage/project/#working-with-version-control +.pdm.toml +.pdm-python +.pdm-build/ + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +# Abstra +# Abstra is an AI-powered process automation framework. +# Ignore directories containing user credentials, local state, and settings. +# Learn more at https://abstra.io/docs +.abstra/ + +# Visual Studio Code +# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore +# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore +# and can be added to the global gitignore or merged into this file. However, if you prefer, +# you could uncomment the following to ignore the entire vscode folder +# .vscode/ + +# Ruff stuff: +.ruff_cache/ + +# PyPI configuration file +.pypirc + +# Cursor +# Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to +# exclude from AI features like autocomplete and code analysis. Recommended for sensitive data +# refer to https://docs.cursor.com/context/ignore-files +.cursorignore +.cursorindexingignore \ No newline at end of file diff --git a/src/lib-python/Cargo.toml b/src/lib-python/Cargo.toml new file mode 100644 index 00000000..524d35da --- /dev/null +++ b/src/lib-python/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "velopack-python" +version.workspace = true +authors.workspace = true +homepage.workspace = true +repository.workspace = true +keywords.workspace = true +categories.workspace = true +license.workspace = true +edition.workspace = true +rust-version.workspace = true + +[lib] +name = "velopack" +crate-type = ["cdylib"] + + +[dependencies] +velopack = { path = "../lib-rust" } +pyo3 = { version = "0.25.0", features = ["extension-module", "macros"] } +serde = { workspace = true } +serde_json = { workspace = true } +semver = { workspace = true } + diff --git a/src/lib-python/build.rs b/src/lib-python/build.rs new file mode 100644 index 00000000..e5c5b794 --- /dev/null +++ b/src/lib-python/build.rs @@ -0,0 +1,181 @@ +use std::env; +use std::fs; +use std::path::Path; + +fn main() { + + + + + + + + + + + + + + + + + + + // Get the workspace version + let version = get_workspace_version().unwrap_or_else(|| { + env::var("CARGO_PKG_VERSION").unwrap_or_else(|_| "0.1.0".to_string()) + }); + + let python_version = convert_to_python_version(&version); + + // Set environment variables for PyO3 + println!("cargo:rustc-env=PYTHON_VERSION={}", python_version); + + // Try setting the package version for PyO3 to pick up + println!("cargo:metadata=version={}", python_version); + + // Also set it as a cfg value + println!("cargo:rustc-cfg=version=\"{}\"", python_version); + + println!("cargo:rerun-if-changed=../../Cargo.toml"); +} + +fn get_workspace_version() -> Option { + // Navigate up to workspace root and read Cargo.toml + let manifest_dir = env::var("CARGO_MANIFEST_DIR").ok()?; + let workspace_toml = Path::new(&manifest_dir) + .parent()? // src + .parent()? // velopack root + .join("Cargo.toml"); + + if !workspace_toml.exists() { + return None; + } + + let content = fs::read_to_string(&workspace_toml).ok()?; + + // Simple parsing to extract version from [workspace.package] section + let mut in_workspace_package = false; + for (_line_num, line) in content.lines().enumerate() { + let trimmed = line.trim(); + + if trimmed == "[workspace.package]" { + in_workspace_package = true; + continue; + } + + if trimmed.starts_with('[') && trimmed != "[workspace.package]" { + if in_workspace_package { + } + in_workspace_package = false; + continue; + } + + if in_workspace_package && trimmed.starts_with("version") { + if let Some(equals_pos) = trimmed.find('=') { + let version_part = &trimmed[equals_pos + 1..].trim(); + // Remove quotes + let version = version_part.trim_matches('"').trim_matches('\''); + return Some(version.to_string()); + } + } + } + + None +} + +fn convert_to_python_version(rust_version: &str) -> String { + // Handle git-based versions like "0.0.1213-g57cf68d" - drop git ref, keep base + if let Some(git_pos) = rust_version.find("-g") { + let base = &rust_version[..git_pos]; + return ensure_xyz_format(base); + } + + // Handle local development versions like "0.0.0-local" - drop local suffix + if rust_version.ends_with("-local") { + let base = rust_version.trim_end_matches("-local"); + return ensure_xyz_format(base); + } + + // Handle Rust pre-release patterns and convert to Python equivalents + if rust_version.contains("-alpha") { + let base = rust_version.split("-alpha").next().unwrap(); + let alpha_num = extract_prerelease_number(rust_version, "-alpha"); + return format!("{}a{}", ensure_xyz_format(base), alpha_num); + } + + if rust_version.contains("-beta") { + let base = rust_version.split("-beta").next().unwrap(); + let beta_num = extract_prerelease_number(rust_version, "-beta"); + return format!("{}b{}", ensure_xyz_format(base), beta_num); + } + + if rust_version.contains("-rc") { + let base = rust_version.split("-rc").next().unwrap(); + let rc_num = extract_prerelease_number(rust_version, "-rc"); + return format!("{}rc{}", ensure_xyz_format(base), rc_num); + } + + // For any other dash-separated version, just take the base + if rust_version.contains('-') { + let base = rust_version.split('-').next().unwrap(); + return ensure_xyz_format(base); + } + + ensure_xyz_format(rust_version) +} + +fn extract_prerelease_number(version: &str, pattern: &str) -> String { + if let Some(pos) = version.find(pattern) { + let after_pattern = &version[pos + pattern.len()..]; + if after_pattern.starts_with('.') { + after_pattern.trim_start_matches('.').split('-').next().unwrap_or("0").to_string() + } else if after_pattern.is_empty() { + "0".to_string() + } else { + after_pattern.split('-').next().unwrap_or("0").to_string() + } + } else { + "0".to_string() + } +} + +fn ensure_xyz_format(version: &str) -> String { + let parts: Vec<&str> = version.split('.').collect(); + let result = match parts.len() { + 1 => format!("{}.0.0", parts[0]), + 2 => format!("{}.{}.0", parts[0], parts[1]), + _ => format!("{}.{}.{}", parts[0], parts[1], parts[2]), + }; + result +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_version_conversion() { + // Git versions - drop git ref + assert_eq!(convert_to_python_version("0.0.1213-g57cf68d"), "0.0.1213"); + assert_eq!(convert_to_python_version("1.2-g57cf68d"), "1.2.0"); + + // Local versions - drop local suffix + assert_eq!(convert_to_python_version("0.0.0-local"), "0.0.0"); + assert_eq!(convert_to_python_version("1.2.3-local"), "1.2.3"); + + // Pre-release versions - convert to Python format + assert_eq!(convert_to_python_version("1.0.0-alpha.1"), "1.0.0a1"); + assert_eq!(convert_to_python_version("1.0.0-alpha"), "1.0.0a0"); + assert_eq!(convert_to_python_version("1.0.0-beta.2"), "1.0.0b2"); + assert_eq!(convert_to_python_version("1.0.0-rc.1"), "1.0.0rc1"); + + // Standard versions - ensure x.y.z format + assert_eq!(convert_to_python_version("1.0.0"), "1.0.0"); + assert_eq!(convert_to_python_version("1.2"), "1.2.0"); + assert_eq!(convert_to_python_version("1"), "1.0.0"); + + // Other dash-separated versions - take base only + assert_eq!(convert_to_python_version("1.0.0-something-else"), "1.0.0"); + } +} \ No newline at end of file diff --git a/src/lib-python/pyproject.toml b/src/lib-python/pyproject.toml new file mode 100644 index 00000000..4e945ace --- /dev/null +++ b/src/lib-python/pyproject.toml @@ -0,0 +1,17 @@ +[project] +dynamic = ["version", "description", "authors", "license"] +name = "velopack" +requires-python = ">=3.8" + +[build-system] +requires = ["maturin>=1.0,<2.0"] +build-backend = "maturin" + +[dependency-groups] +dev = [ + "maturin>=1.8.6", + "pyinstaller>=6.13.0", +] +test = [ + "pyinstaller>=6.13.0", +] diff --git a/src/lib-python/src/app.rs b/src/lib-python/src/app.rs new file mode 100644 index 00000000..73da1acb --- /dev/null +++ b/src/lib-python/src/app.rs @@ -0,0 +1,178 @@ +use pyo3::prelude::*; +use pyo3::types::PyFunction; + +use velopack::VelopackApp as VelopackAppRust; + +/// Python wrapper for VelopackApp with builder pattern +#[pyclass(name = "App")] +pub struct VelopackAppWrapper { + // We'll store the callbacks as Python objects + install_hook: Option>, + update_hook: Option>, + obsolete_hook: Option>, + uninstall_hook: Option>, + firstrun_hook: Option>, + restarted_hook: Option>, + auto_apply: bool, + args: Option>, +} + +#[pymethods] +impl VelopackAppWrapper { + /// Create a new VelopackApp builder + #[new] + pub fn new() -> Self { + VelopackAppWrapper { + install_hook: None, + update_hook: None, + obsolete_hook: None, + uninstall_hook: None, + firstrun_hook: None, + restarted_hook: None, + auto_apply: true, + args: None, + } + } + + /// Override the command line arguments used by VelopackApp + pub fn set_args(mut slf: PyRefMut, args: Vec) -> PyRefMut { + slf.args = Some(args); + slf + } + + /// Set whether to automatically apply downloaded updates on startup + pub fn set_auto_apply_on_startup(mut slf: PyRefMut, apply: bool) -> PyRefMut { + slf.auto_apply = apply; + slf + } + + /// This hook is triggered when the application is started for the first time after installation + pub fn on_first_run(mut slf: PyRefMut, callback: Py) -> PyRefMut { + slf.firstrun_hook = Some(callback); + slf + } + + /// This hook is triggered when the application is restarted by Velopack after installing updates + pub fn on_restarted(mut slf: PyRefMut, callback: Py) -> PyRefMut { + slf.restarted_hook = Some(callback); + slf + } + + /// Fast callback hook for after installation (Windows only) + pub fn on_after_install_fast_callback(mut slf: PyRefMut, callback: Py) -> PyRefMut { + slf.install_hook = Some(callback); + slf + } + + /// Fast callback hook for after update (Windows only) + pub fn on_after_update_fast_callback(mut slf: PyRefMut, callback: Py) -> PyRefMut { + slf.update_hook = Some(callback); + slf + } + + /// Fast callback hook for before update (Windows only) + pub fn on_before_update_fast_callback(mut slf: PyRefMut, callback: Py) -> PyRefMut { + slf.obsolete_hook = Some(callback); + slf + } + + /// Fast callback hook for before uninstall (Windows only) + pub fn on_before_uninstall_fast_callback(mut slf: PyRefMut, callback: Py) -> PyRefMut { + slf.uninstall_hook = Some(callback); + slf + } + + /// Runs the Velopack startup logic + pub fn run(&mut self, _py: Python) -> PyResult<()> { + // Create the Rust VelopackApp with our stored configuration + let mut app = VelopackAppRust::build() + .set_auto_apply_on_startup(self.auto_apply); + + // Set args if provided + if let Some(ref args) = self.args { + app = app.set_args(args.clone()); + } + + // Set up hooks - we need to convert Python callbacks to Rust closures + if let Some(ref hook) = self.firstrun_hook { + let hook_clone = hook; + app = app.on_first_run(move |version| { + Python::with_gil(|py| { + let version_str = version.to_string(); + if let Err(e) = hook_clone.call1(py, (version_str,)) { + eprintln!("Error calling first_run hook: {:?}", e); + } + }); + }); + } + + if let Some(ref hook) = self.restarted_hook { + let hook_clone = hook; + app = app.on_restarted(move |version| { + Python::with_gil(|py| { + let version_str = version.to_string(); + if let Err(e) = hook_clone.call1(py, (version_str,)) { + eprintln!("Error calling restarted hook: {:?}", e); + } + }); + }); + } + + #[cfg(target_os = "windows")] + { + if let Some(ref hook) = self.install_hook { + let hook_clone = hook; + app = app.on_after_install_fast_callback(move |version| { + Python::with_gil(|py| { + let version_str = version.to_string(); + if let Err(e) = hook_clone.call1(py, (version_str,)) { + eprintln!("Error calling install hook: {:?}", e); + } + }); + }); + } + + if let Some(ref hook) = self.update_hook { + let hook_clone = hook; + app = app.on_after_update_fast_callback(move |version| { + Python::with_gil(|py| { + let version_str = version.to_string(); + if let Err(e) = hook_clone.call1(py, (version_str,)) { + eprintln!("Error calling update hook: {:?}", e); + } + }); + }); + } + + if let Some(ref hook) = self.obsolete_hook { + let hook_clone = hook; + app = app.on_before_update_fast_callback(move |version| { + Python::with_gil(|py| { + let version_str = version.to_string(); + if let Err(e) = hook_clone.call1(py, (version_str,)) { + eprintln!("Error calling obsolete hook: {:?}", e); + } + }); + }); + } + + if let Some(ref hook) = self.uninstall_hook { + let hook_clone = hook; + app = app.on_before_uninstall_fast_callback(move |version| { + Python::with_gil(|py| { + let version_str = version.to_string(); + if let Err(e) = hook_clone.call1(py, (version_str,)) { + eprintln!("Error calling uninstall hook: {:?}", e); + } + }); + }); + } + } + + // do not Release the GIL before calling the potentially blocking run method + app.run(); + + Ok(()) + } +} + diff --git a/src/lib-python/src/exceptions.rs b/src/lib-python/src/exceptions.rs new file mode 100644 index 00000000..e3357c56 --- /dev/null +++ b/src/lib-python/src/exceptions.rs @@ -0,0 +1,20 @@ +use pyo3::prelude::*; +use pyo3::exceptions::PyException; + +#[pyclass(name="VelopackError", extends=PyException, module="velopack.exceptions")] +#[derive(Debug)] +pub struct VelopackError { + pub message: String, +} + +#[pymethods] +impl VelopackError { + #[new] + fn new(message: String) -> Self { + VelopackError { message } + } + + fn __str__(&self) -> String { + self.message.clone() + } +} \ No newline at end of file diff --git a/src/lib-python/src/lib.rs b/src/lib-python/src/lib.rs new file mode 100644 index 00000000..534dbeef --- /dev/null +++ b/src/lib-python/src/lib.rs @@ -0,0 +1,31 @@ +use pyo3::prelude::*; +use pyo3::types::PyModule; + +mod exceptions; +pub use exceptions::VelopackError; + +mod app; +pub use app::VelopackAppWrapper; + +mod manager; +pub use manager::UpdateManagerWrapper; + + +#[pymodule] +fn velopack(m: &Bound<'_, PyModule>) -> PyResult<()> { + m.add_class::()?; + + + m.add_class::()?; + m.add_class::()?; + + // add __version__ attribute + m.add("__version__", env!("CARGO_PKG_VERSION"))?; + + + + // add __author__ attribute + m.add("__author__", env!("CARGO_PKG_AUTHORS"))?; + + Ok(()) +} \ No newline at end of file diff --git a/src/lib-python/src/manager.rs b/src/lib-python/src/manager.rs new file mode 100644 index 00000000..ccedd882 --- /dev/null +++ b/src/lib-python/src/manager.rs @@ -0,0 +1,93 @@ +use std::sync::mpsc; +use std::thread; + +use pyo3::prelude::*; + +use velopack::{UpdateCheck, UpdateInfo, UpdateManager as VelopackUpdateManagerRust}; +use velopack::sources::AutoSource; + +use crate::exceptions::VelopackError; + +#[pyclass(name = "UpdateManager")] +pub struct UpdateManagerWrapper { + inner: VelopackUpdateManagerRust, + updates: UpdateInfo, +} + +#[pymethods] +impl UpdateManagerWrapper { + // for new, just take in a string, which is the source +#[new] +pub fn new(source: String) -> PyResult { + let source = AutoSource::new(&source); + // set myinner to a new VelopackUpdateManager with the source + let inner = VelopackUpdateManagerRust::new(source, None, None) + .map_err(|e| PyErr::new::(format!("Failed to create UpdateManager: {}", e)))?; + Ok(UpdateManagerWrapper { + inner, + updates: UpdateInfo::default(), +} + ) +} + + +// check_for_updates return a bool indicating if updates are available + /// This method checks for updates and returns true if updates are available, false otherwise. + pub fn check_for_updates(&mut self) -> PyResult { + match self.inner.check_for_updates() { + Ok(UpdateCheck::UpdateAvailable(updates)) => { + self.updates = updates; + Ok(true) + } + Ok(_) => { + self.updates = UpdateInfo::default(); + Ok(false) + } + Err(e) => Err(PyErr::new::(format!("Failed to check for updates: {}", e))) + } + } + + #[pyo3(signature = (progress_callback = None))] + pub fn download_updates(&mut self, progress_callback: Option) -> PyResult<()> { + if let Some(callback) = progress_callback { + // Create a channel for progress updates + let (sender, receiver) = mpsc::channel::(); + + // Spawn a thread to handle progress updates + let progress_thread = thread::spawn(move || { + Python::with_gil(|py| { + while let Ok(progress) = receiver.recv() { + if let Err(e) = callback.call1(py, (progress,)) { + // Log error but continue - don't break the download + eprintln!("Progress callback error: {}", e); + break; + } + } + }); + }); + + // Call download with the sender + let result = self.inner.download_updates(&self.updates, Some(sender)) + .map_err(|e| PyErr::new::(format!("Failed to download updates: {}", e))); + + // Wait for the progress thread to finish + let _ = progress_thread.join(); + + result.map(|_| ()) + } else { + // No progress callback provided + self.inner.download_updates(&self.updates, None) + .map_err(|e| PyErr::new::(format!("Failed to download updates: {}", e))) + .map(|_| ()) + } + } + +pub fn apply_updates_and_restart(&mut self) -> PyResult<()> { + self.inner.apply_updates_and_restart(&self.updates) + .map_err(|e| PyErr::new::(format!("Failed to apply updates and restart: {}", e))) + .map(|_| ()) +} + + +} + diff --git a/src/lib-python/test/app.py b/src/lib-python/test/app.py new file mode 100644 index 00000000..ca639143 --- /dev/null +++ b/src/lib-python/test/app.py @@ -0,0 +1,15 @@ + +import velopack + +import app_version + +version = app_version.version + +if __name__ == "__main__": + velopack.App().run() + um = velopack.UpdateManager("http://localhost:8080") + if um.check_for_updates(): + um.download_updates() + um.apply_updates_and_restart() + with open("version_result.txt", "w") as f: + f.write(f"{version}") \ No newline at end of file diff --git a/src/lib-python/test/run_test.py b/src/lib-python/test/run_test.py new file mode 100644 index 00000000..7d1f18a3 --- /dev/null +++ b/src/lib-python/test/run_test.py @@ -0,0 +1,108 @@ +import atexit +import functools +from http.server import HTTPServer, SimpleHTTPRequestHandler +import platform +import shutil +import subprocess +from pathlib import Path +import threading +import time +import zipfile + +httpd = None + +def log(msg): + print(f"[LOG] {msg}") + + +# Register a cleanup function to ensure the HTTP server is stopped on exit +def cleanup(): + global httpd + if httpd: + log("Stopping HTTP server...") + httpd.shutdown() + httpd.server_close() + log("HTTP server stopped.") + shutil.rmtree("output", ignore_errors=True) + shutil.rmtree("dist", ignore_errors=True) + shutil.rmtree("build", ignore_errors=True) + shutil.rmtree("Releases", ignore_errors=True) + +atexit.register(cleanup) + +def _run_cmd(args): + log(f"Running command: {' '.join(args)}") + result = subprocess.run(args, capture_output=True, text=True) + if result.returncode != 0: + raise RuntimeError(f"Command failed: {' '.join(args)}\n{result.stdout}\n{result.stderr}") + return result.stdout.strip() + +def write_app_version(version): + log(f"Writing app version: {version}") + with open("app_version.py", "w") as f: + f.write(f'version = "{version}"\n') + log("App version written successfully") + +def read_app_version(path): + log(f"Reading app version from: {path}") + with open(path, "r") as f: + version = f.read() + log(f"App version read successfully: {version}") + return version + +def extract_full_path(zip_file, target_dir): + log(f"Extracting {zip_file} to {target_dir}") + with zipfile.ZipFile(zip_file, 'r') as zip_ref: + zip_ref.extractall(target_dir) + log("Extraction completed") + +# check if we are running on Windows +if platform.system() != "Windows": + raise RuntimeError("This script is intended to run on Windows only, for now") + +# check if we are running from the test dir +if Path(__file__).parent.name != "test": + raise RuntimeError("This script must be run from the 'test' directory") + +# check for vpk cli +_run_cmd(["vpk", "-h"]) + +write_app_version("1.0.0") + +_run_cmd(["uv", "run", "pyinstaller", "app.spec", "-y"]) + +# make app version +_run_cmd(["vpk", "pack", "--packId", "test-app", "--packVersion", "1.0.0", "--packDir", "dist/app/", "--mainExe", "app.exe"]) + +extract_full_path("Releases/test-app-win-Portable.zip", "output") + +log("Starting HTTP server thread serving ./Releases …") +handler = functools.partial(SimpleHTTPRequestHandler, directory=str(Path("Releases").resolve())) +httpd = HTTPServer(("localhost", 8080), handler) +server_thread = threading.Thread(target=httpd.serve_forever, daemon=True) +server_thread.start() +log("HTTP server is now running at http://localhost:8000") +# Give it a moment to start +time.sleep(1) + + +# check if the app version is correct +_run_cmd(["output/test-app.exe"]) + +current_version = read_app_version("output/current/version_result.txt") +if current_version.strip() != "1.0.0": + raise RuntimeError(f"Version mismatch: expected '1.0.0', got '{current_version.strip()}'") + +log("App version is correct: 1.0.0") +log("Trying to create update package...") +write_app_version("1.0.1") +_run_cmd(["uv", "run", "pyinstaller", "app.spec", "-y"]) +_run_cmd(["vpk", "pack", "--packId", "test-app", "--packVersion", "1.0.1", "--packDir", "dist/app/", "--mainExe", "app.exe"]) +# check if the app version is correct +_run_cmd(["output/test-app.exe"]) +new_version = read_app_version("output/current/version_result.txt") +if new_version.strip() != "1.0.1": + raise RuntimeError(f"Version mismatch: expected '1.0.1' after update, got '{new_version.strip()}'") + +log("Update package created successfully and version is now 1.0.1") +log("Test completed successfully") \ No newline at end of file diff --git a/src/lib-python/uv.lock b/src/lib-python/uv.lock new file mode 100644 index 00000000..014cafbc --- /dev/null +++ b/src/lib-python/uv.lock @@ -0,0 +1,264 @@ +version = 1 +revision = 2 +requires-python = ">=3.8" +resolution-markers = [ + "python_full_version >= '3.9'", + "python_full_version < '3.9'", +] + +[[package]] +name = "altgraph" +version = "0.17.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/de/a8/7145824cf0b9e3c28046520480f207df47e927df83aa9555fb47f8505922/altgraph-0.17.4.tar.gz", hash = "sha256:1b5afbb98f6c4dcadb2e2ae6ab9fa994bbb8c1d75f4fa96d340f9437ae454406", size = 48418, upload-time = "2023-09-25T09:04:52.164Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4d/3f/3bc3f1d83f6e4a7fcb834d3720544ca597590425be5ba9db032b2bf322a2/altgraph-0.17.4-py2.py3-none-any.whl", hash = "sha256:642743b4750de17e655e6711601b077bc6598dbfa3ba5fa2b2a35ce12b508dff", size = 21212, upload-time = "2023-09-25T09:04:50.691Z" }, +] + +[[package]] +name = "importlib-metadata" +version = "8.5.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.9'", +] +dependencies = [ + { name = "zipp", version = "3.20.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cd/12/33e59336dca5be0c398a7482335911a33aa0e20776128f038019f1a95f1b/importlib_metadata-8.5.0.tar.gz", hash = "sha256:71522656f0abace1d072b9e5481a48f07c138e00f079c38c8f883823f9c26bd7", size = 55304, upload-time = "2024-09-11T14:56:08.937Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/d9/a1e041c5e7caa9a05c925f4bdbdfb7f006d1f74996af53467bc394c97be7/importlib_metadata-8.5.0-py3-none-any.whl", hash = "sha256:45e54197d28b7a7f1559e60b95e7c567032b602131fbd588f1497f47880aa68b", size = 26514, upload-time = "2024-09-11T14:56:07.019Z" }, +] + +[[package]] +name = "importlib-metadata" +version = "8.7.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.9'", +] +dependencies = [ + { name = "zipp", version = "3.22.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/76/66/650a33bd90f786193e4de4b3ad86ea60b53c89b669a5c7be931fac31cdb0/importlib_metadata-8.7.0.tar.gz", hash = "sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000", size = 56641, upload-time = "2025-04-27T15:29:01.736Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/b0/36bd937216ec521246249be3bf9855081de4c5e06a0c9b4219dbeda50373/importlib_metadata-8.7.0-py3-none-any.whl", hash = "sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd", size = 27656, upload-time = "2025-04-27T15:29:00.214Z" }, +] + +[[package]] +name = "macholib" +version = "1.16.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "altgraph" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/95/ee/af1a3842bdd5902ce133bd246eb7ffd4375c38642aeb5dc0ae3a0329dfa2/macholib-1.16.3.tar.gz", hash = "sha256:07ae9e15e8e4cd9a788013d81f5908b3609aa76f9b1421bae9c4d7606ec86a30", size = 59309, upload-time = "2023-09-25T09:10:16.155Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/5d/c059c180c84f7962db0aeae7c3b9303ed1d73d76f2bfbc32bc231c8be314/macholib-1.16.3-py2.py3-none-any.whl", hash = "sha256:0e315d7583d38b8c77e815b1ecbdbf504a8258d8b3e17b61165c6feb60d18f2c", size = 38094, upload-time = "2023-09-25T09:10:14.188Z" }, +] + +[[package]] +name = "maturin" +version = "1.8.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/34/bc/c7df50a359c3a31490785c77d1ddd5fc83cc8cc07a4eddd289dbae53545a/maturin-1.8.6.tar.gz", hash = "sha256:0e0dc2e0bfaa2e1bd238e0236cf8a2b7e2250ccaa29c1aa8d0e61fa664b0289d", size = 203320, upload-time = "2025-05-13T13:56:11.033Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d3/f1/e493add40aebdab88ac1aefb41b9c84ac288fb00025bc96b9213ee02c958/maturin-1.8.6-py3-none-linux_armv6l.whl", hash = "sha256:1bf4c743dd2b24448e82b8c96251597818956ddf848e1d16b59356512c7e58d8", size = 7831195, upload-time = "2025-05-13T13:55:42.634Z" }, + { url = "https://files.pythonhosted.org/packages/f0/1c/588afdb7bf79c4f15e33e9af6d7f3b12ec662bc63a22919e3bf39afa2a1e/maturin-1.8.6-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:4ea89cf76048bc760e12b36b608fc3f5ef4f7359c0895e9afe737be34041d948", size = 15276471, upload-time = "2025-05-13T13:55:46.196Z" }, + { url = "https://files.pythonhosted.org/packages/62/0b/4ce97f7f3a42068fbb42ba47d8b072e098c060fc1f64d8523e5588c57543/maturin-1.8.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:4dd2e2f005ca63ac7ef0dddf2d65324ee480277a11544dcc4e7e436af68034dd", size = 7966129, upload-time = "2025-05-13T13:55:48.633Z" }, + { url = "https://files.pythonhosted.org/packages/84/bf/4eae9d12920c580baf9d47ee63fec3ae0233e90a3aa8987bd7909cdc36a0/maturin-1.8.6-py3-none-manylinux_2_12_i686.manylinux2010_i686.musllinux_1_1_i686.whl", hash = "sha256:b0637604774e2c50ab48a0e9023fe2f071837ecbc817c04ec28e1cfcc25224c2", size = 7835935, upload-time = "2025-05-13T13:55:51.235Z" }, + { url = "https://files.pythonhosted.org/packages/f9/aa/8090f8b3f5f7ec46bc95deb0f5b29bf52c98156ef594f2e65d20bf94cea1/maturin-1.8.6-py3-none-manylinux_2_12_x86_64.manylinux2010_x86_64.musllinux_1_1_x86_64.whl", hash = "sha256:bec5948c6475954c8089b17fae349966258756bb2ca05e54099e476a08070795", size = 8282553, upload-time = "2025-05-13T13:55:53.266Z" }, + { url = "https://files.pythonhosted.org/packages/bc/50/4348da6cc16c006dab4e6cd479cf00dc0afa80db289a115a314df9909ee6/maturin-1.8.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:62a65f70ebaadd6eb6083f5548413744f2ef12400445778e08d41d4facf15bbe", size = 7618339, upload-time = "2025-05-13T13:55:55.706Z" }, + { url = "https://files.pythonhosted.org/packages/ce/77/16458e29487d068c8cdb7f06a4403393568a10b44993fe2ec9c3b29fdccd/maturin-1.8.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.musllinux_1_1_armv7l.whl", hash = "sha256:5c0ff7ad43883920032b63c94c76dcdd5758710d7b72b68db69e7826c40534ac", size = 7686748, upload-time = "2025-05-13T13:55:57.97Z" }, + { url = "https://files.pythonhosted.org/packages/0c/c1/a52f3c1171c053810606c7c7fae5ce4637446ef9df44f281862d2bef3750/maturin-1.8.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.musllinux_1_1_ppc64le.whl", hash = "sha256:ca30fdb158a24cf312f3e53072a6e987182c103fa613efea2d28b5f52707d04a", size = 9767394, upload-time = "2025-05-13T13:56:00.694Z" }, + { url = "https://files.pythonhosted.org/packages/e2/ab/abae74f36a0f200384eda985ebeb9ee5dcbb19bfe1558c3335ef6f297094/maturin-1.8.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b1e786ec9b5f7315c7e3fcc62b0715f9d99ffe477b06d0d62655a71e6a51a67b", size = 10995574, upload-time = "2025-05-13T13:56:03.101Z" }, + { url = "https://files.pythonhosted.org/packages/8e/1c/c478578a62c1e34b5b0641a474de78cb56d6c4aad0ba88f90dfa9f2a15f7/maturin-1.8.6-py3-none-win32.whl", hash = "sha256:dade5edfaf508439ff6bbc7be4f207e04c0999c47d9ef7e1bae16258e76b1518", size = 7027171, upload-time = "2025-05-13T13:56:05.472Z" }, + { url = "https://files.pythonhosted.org/packages/c9/89/2c57d29f25e06696cb3c5b3770ba0b40dfe87f91a879ecbcdc92e071a26b/maturin-1.8.6-py3-none-win_amd64.whl", hash = "sha256:6bc9281b90cd37e2a7985f2e5d6e3d35a1d64cf6e4d04ce5ed25603d162995b9", size = 7947998, upload-time = "2025-05-13T13:56:07.428Z" }, + { url = "https://files.pythonhosted.org/packages/9d/f5/3ee1c6aa4e277323bef38ea0ec07262a9b88711d1a29cb5bb08ce3807a6f/maturin-1.8.6-py3-none-win_arm64.whl", hash = "sha256:24f66624db69b895b134a8f1592efdf04cd223c9b3b65243ad32080477936d14", size = 6684974, upload-time = "2025-05-13T13:56:09.415Z" }, +] + +[[package]] +name = "packaging" +version = "25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, +] + +[[package]] +name = "pefile" +version = "2023.2.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/78/c5/3b3c62223f72e2360737fd2a57c30e5b2adecd85e70276879609a7403334/pefile-2023.2.7.tar.gz", hash = "sha256:82e6114004b3d6911c77c3953e3838654b04511b8b66e8583db70c65998017dc", size = 74854, upload-time = "2023-02-07T12:23:55.958Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/55/26/d0ad8b448476d0a1e8d3ea5622dc77b916db84c6aa3cb1e1c0965af948fc/pefile-2023.2.7-py3-none-any.whl", hash = "sha256:da185cd2af68c08a6cd4481f7325ed600a88f6a813bad9dea07ab3ef73d8d8d6", size = 71791, upload-time = "2023-02-07T12:28:36.678Z" }, +] + +[[package]] +name = "pyinstaller" +version = "6.13.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "altgraph" }, + { name = "importlib-metadata", version = "8.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, + { name = "importlib-metadata", version = "8.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, + { name = "macholib", marker = "sys_platform == 'darwin'" }, + { name = "packaging" }, + { name = "pefile", marker = "sys_platform == 'win32'" }, + { name = "pyinstaller-hooks-contrib" }, + { name = "pywin32-ctypes", marker = "sys_platform == 'win32'" }, + { name = "setuptools", version = "75.3.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, + { name = "setuptools", version = "80.9.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a8/b1/2949fe6d3874e961898ca5cfc1bf2cf13bdeea488b302e74a745bc28c8ba/pyinstaller-6.13.0.tar.gz", hash = "sha256:38911feec2c5e215e5159a7e66fdb12400168bd116143b54a8a7a37f08733456", size = 4276427, upload-time = "2025-04-15T23:25:31.646Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b4/02/d1a347d35b1b627da1e148159e617576555619ac3bb8bbd5fed661fc7bb5/pyinstaller-6.13.0-py3-none-macosx_10_13_universal2.whl", hash = "sha256:aa404f0b02cd57948098055e76ee190b8e65ccf7a2a3f048e5000f668317069f", size = 1001923, upload-time = "2025-04-15T23:24:17.646Z" }, + { url = "https://files.pythonhosted.org/packages/6b/80/6da39f7aeac65c9ca5afad0fac37887d75fdfd480178a7077c9d30b0704c/pyinstaller-6.13.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:92efcf2f09e78f07b568c5cb7ed48c9940f5dad627af4b49bede6320fab2a06e", size = 718135, upload-time = "2025-04-15T23:24:22.385Z" }, + { url = "https://files.pythonhosted.org/packages/05/2c/d21d31f780a489609e7bf6385c0f7635238dc98b37cba8645b53322b7450/pyinstaller-6.13.0-py3-none-manylinux2014_i686.whl", hash = "sha256:9f82f113c463f012faa0e323d952ca30a6f922685d9636e754bd3a256c7ed200", size = 728543, upload-time = "2025-04-15T23:24:27.02Z" }, + { url = "https://files.pythonhosted.org/packages/e1/20/e6ca87bbed6c0163533195707f820f05e10b8da1223fc6972cfe3c3c50c7/pyinstaller-6.13.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:db0e7945ebe276f604eb7c36e536479556ab32853412095e19172a5ec8fca1c5", size = 726868, upload-time = "2025-04-15T23:24:31.779Z" }, + { url = "https://files.pythonhosted.org/packages/20/d5/53b19285f8817ab6c4b07c570208d62606bab0e5a049d50c93710a1d9dc6/pyinstaller-6.13.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:92fe7337c5aa08d42b38d7a79614492cb571489f2cb0a8f91dc9ef9ccbe01ed3", size = 725037, upload-time = "2025-04-15T23:24:36.244Z" }, + { url = "https://files.pythonhosted.org/packages/84/5b/08e0b305ba71e6d7cb247e27d714da7536895b0283132d74d249bf662366/pyinstaller-6.13.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:bc09795f5954135dd4486c1535650958c8218acb954f43860e4b05fb515a21c0", size = 721027, upload-time = "2025-04-15T23:24:40.16Z" }, + { url = "https://files.pythonhosted.org/packages/1f/9c/d8d0a7120103471be8dbe1c5419542aa794b9b9ec2ef628b542f9e6f9ef0/pyinstaller-6.13.0-py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:589937548d34978c568cfdc39f31cf386f45202bc27fdb8facb989c79dfb4c02", size = 723443, upload-time = "2025-04-15T23:24:50.63Z" }, + { url = "https://files.pythonhosted.org/packages/52/c7/8a9d81569dda2352068ecc6ee779d5feff6729569dd1b4ffd1236ecd38fe/pyinstaller-6.13.0-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:b7260832f7501ba1d2ce1834d4cddc0f2b94315282bc89c59333433715015447", size = 719915, upload-time = "2025-04-15T23:24:54.917Z" }, + { url = "https://files.pythonhosted.org/packages/d5/e6/cccadb02b90198c7ed4ffb8bc34d420efb72b996f47cbd4738067a602d65/pyinstaller-6.13.0-py3-none-win32.whl", hash = "sha256:80c568848529635aa7ca46d8d525f68486d53e03f68b7bb5eba2c88d742e302c", size = 1294997, upload-time = "2025-04-15T23:25:01.391Z" }, + { url = "https://files.pythonhosted.org/packages/1a/06/15cbe0e25d1e73d5b981fa41ff0bb02b15e924e30b8c61256f4a28c4c837/pyinstaller-6.13.0-py3-none-win_amd64.whl", hash = "sha256:8d4296236b85aae570379488c2da833b28828b17c57c2cc21fccd7e3811fe372", size = 1352714, upload-time = "2025-04-15T23:25:08.061Z" }, + { url = "https://files.pythonhosted.org/packages/83/ef/74379298d46e7caa6aa7ceccc865106d3d4b15ac487ffdda2a35bfb6fe79/pyinstaller-6.13.0-py3-none-win_arm64.whl", hash = "sha256:d9f21d56ca2443aa6a1e255e7ad285c76453893a454105abe1b4d45e92bb9a20", size = 1293589, upload-time = "2025-04-15T23:25:14.523Z" }, +] + +[[package]] +name = "pyinstaller-hooks-contrib" +version = "2025.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "importlib-metadata", version = "8.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, + { name = "importlib-metadata", version = "8.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, + { name = "packaging" }, + { name = "setuptools", version = "75.3.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, + { name = "setuptools", version = "80.9.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e3/94/dfc5c7903306211798f990e6794c2eb7b8685ac487b26979e9255790419c/pyinstaller_hooks_contrib-2025.4.tar.gz", hash = "sha256:5ce1afd1997b03e70f546207031cfdf2782030aabacc102190677059e2856446", size = 162628, upload-time = "2025-05-03T20:15:55.983Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d3/e1/ed48c7074145898e5c5b0072e87be975c5bd6a1d0f08c27a1daa7064fca0/pyinstaller_hooks_contrib-2025.4-py3-none-any.whl", hash = "sha256:6c2d73269b4c484eb40051fc1acee0beb113c2cfb3b37437b8394faae6f0d072", size = 434451, upload-time = "2025-05-03T20:15:54.579Z" }, +] + +[[package]] +name = "pywin32-ctypes" +version = "0.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/85/9f/01a1a99704853cb63f253eea009390c88e7131c67e66a0a02099a8c917cb/pywin32-ctypes-0.2.3.tar.gz", hash = "sha256:d162dc04946d704503b2edc4d55f3dba5c1d539ead017afa00142c38b9885755", size = 29471, upload-time = "2024-08-14T10:15:34.626Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/3d/8161f7711c017e01ac9f008dfddd9410dff3674334c233bde66e7ba65bbf/pywin32_ctypes-0.2.3-py3-none-any.whl", hash = "sha256:8a1513379d709975552d202d942d9837758905c8d01eb82b8bcc30918929e7b8", size = 30756, upload-time = "2024-08-14T10:15:33.187Z" }, +] + +[[package]] +name = "setuptools" +version = "75.3.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.9'", +] +sdist = { url = "https://files.pythonhosted.org/packages/5c/01/771ea46cce201dd42cff043a5eea929d1c030fb3d1c2ee2729d02ca7814c/setuptools-75.3.2.tar.gz", hash = "sha256:3c1383e1038b68556a382c1e8ded8887cd20141b0eb5708a6c8d277de49364f5", size = 1354489, upload-time = "2025-03-12T00:02:19.004Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/15/65/3f0dba35760d902849d39d38c0a72767794b1963227b69a587f8a336d08c/setuptools-75.3.2-py3-none-any.whl", hash = "sha256:90ab613b6583fc02d5369cbca13ea26ea0e182d1df2d943ee9cbe81d4c61add9", size = 1251198, upload-time = "2025-03-12T00:02:17.554Z" }, +] + +[[package]] +name = "setuptools" +version = "80.9.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.9'", +] +sdist = { url = "https://files.pythonhosted.org/packages/18/5d/3bf57dcd21979b887f014ea83c24ae194cfcd12b9e0fda66b957c69d1fca/setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c", size = 1319958, upload-time = "2025-05-27T00:56:51.443Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922", size = 1201486, upload-time = "2025-05-27T00:56:49.664Z" }, +] + +[[package]] +name = "tomli" +version = "2.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175, upload-time = "2024-11-27T22:38:36.873Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/ca/75707e6efa2b37c77dadb324ae7d9571cb424e61ea73fad7c56c2d14527f/tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", size = 131077, upload-time = "2024-11-27T22:37:54.956Z" }, + { url = "https://files.pythonhosted.org/packages/c7/16/51ae563a8615d472fdbffc43a3f3d46588c264ac4f024f63f01283becfbb/tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", size = 123429, upload-time = "2024-11-27T22:37:56.698Z" }, + { url = "https://files.pythonhosted.org/packages/f1/dd/4f6cd1e7b160041db83c694abc78e100473c15d54620083dbd5aae7b990e/tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", size = 226067, upload-time = "2024-11-27T22:37:57.63Z" }, + { url = "https://files.pythonhosted.org/packages/a9/6b/c54ede5dc70d648cc6361eaf429304b02f2871a345bbdd51e993d6cdf550/tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", size = 236030, upload-time = "2024-11-27T22:37:59.344Z" }, + { url = "https://files.pythonhosted.org/packages/1f/47/999514fa49cfaf7a92c805a86c3c43f4215621855d151b61c602abb38091/tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", size = 240898, upload-time = "2024-11-27T22:38:00.429Z" }, + { url = "https://files.pythonhosted.org/packages/73/41/0a01279a7ae09ee1573b423318e7934674ce06eb33f50936655071d81a24/tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", size = 229894, upload-time = "2024-11-27T22:38:02.094Z" }, + { url = "https://files.pythonhosted.org/packages/55/18/5d8bc5b0a0362311ce4d18830a5d28943667599a60d20118074ea1b01bb7/tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", size = 245319, upload-time = "2024-11-27T22:38:03.206Z" }, + { url = "https://files.pythonhosted.org/packages/92/a3/7ade0576d17f3cdf5ff44d61390d4b3febb8a9fc2b480c75c47ea048c646/tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", size = 238273, upload-time = "2024-11-27T22:38:04.217Z" }, + { url = "https://files.pythonhosted.org/packages/72/6f/fa64ef058ac1446a1e51110c375339b3ec6be245af9d14c87c4a6412dd32/tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", size = 98310, upload-time = "2024-11-27T22:38:05.908Z" }, + { url = "https://files.pythonhosted.org/packages/6a/1c/4a2dcde4a51b81be3530565e92eda625d94dafb46dbeb15069df4caffc34/tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", size = 108309, upload-time = "2024-11-27T22:38:06.812Z" }, + { url = "https://files.pythonhosted.org/packages/52/e1/f8af4c2fcde17500422858155aeb0d7e93477a0d59a98e56cbfe75070fd0/tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", size = 132762, upload-time = "2024-11-27T22:38:07.731Z" }, + { url = "https://files.pythonhosted.org/packages/03/b8/152c68bb84fc00396b83e7bbddd5ec0bd3dd409db4195e2a9b3e398ad2e3/tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", size = 123453, upload-time = "2024-11-27T22:38:09.384Z" }, + { url = "https://files.pythonhosted.org/packages/c8/d6/fc9267af9166f79ac528ff7e8c55c8181ded34eb4b0e93daa767b8841573/tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", size = 233486, upload-time = "2024-11-27T22:38:10.329Z" }, + { url = "https://files.pythonhosted.org/packages/5c/51/51c3f2884d7bab89af25f678447ea7d297b53b5a3b5730a7cb2ef6069f07/tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", size = 242349, upload-time = "2024-11-27T22:38:11.443Z" }, + { url = "https://files.pythonhosted.org/packages/ab/df/bfa89627d13a5cc22402e441e8a931ef2108403db390ff3345c05253935e/tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", size = 252159, upload-time = "2024-11-27T22:38:13.099Z" }, + { url = "https://files.pythonhosted.org/packages/9e/6e/fa2b916dced65763a5168c6ccb91066f7639bdc88b48adda990db10c8c0b/tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", size = 237243, upload-time = "2024-11-27T22:38:14.766Z" }, + { url = "https://files.pythonhosted.org/packages/b4/04/885d3b1f650e1153cbb93a6a9782c58a972b94ea4483ae4ac5cedd5e4a09/tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", size = 259645, upload-time = "2024-11-27T22:38:15.843Z" }, + { url = "https://files.pythonhosted.org/packages/9c/de/6b432d66e986e501586da298e28ebeefd3edc2c780f3ad73d22566034239/tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", size = 244584, upload-time = "2024-11-27T22:38:17.645Z" }, + { url = "https://files.pythonhosted.org/packages/1c/9a/47c0449b98e6e7d1be6cbac02f93dd79003234ddc4aaab6ba07a9a7482e2/tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", size = 98875, upload-time = "2024-11-27T22:38:19.159Z" }, + { url = "https://files.pythonhosted.org/packages/ef/60/9b9638f081c6f1261e2688bd487625cd1e660d0a85bd469e91d8db969734/tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", size = 109418, upload-time = "2024-11-27T22:38:20.064Z" }, + { url = "https://files.pythonhosted.org/packages/04/90/2ee5f2e0362cb8a0b6499dc44f4d7d48f8fff06d28ba46e6f1eaa61a1388/tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7", size = 132708, upload-time = "2024-11-27T22:38:21.659Z" }, + { url = "https://files.pythonhosted.org/packages/c0/ec/46b4108816de6b385141f082ba99e315501ccd0a2ea23db4a100dd3990ea/tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", size = 123582, upload-time = "2024-11-27T22:38:22.693Z" }, + { url = "https://files.pythonhosted.org/packages/a0/bd/b470466d0137b37b68d24556c38a0cc819e8febe392d5b199dcd7f578365/tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", size = 232543, upload-time = "2024-11-27T22:38:24.367Z" }, + { url = "https://files.pythonhosted.org/packages/d9/e5/82e80ff3b751373f7cead2815bcbe2d51c895b3c990686741a8e56ec42ab/tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", size = 241691, upload-time = "2024-11-27T22:38:26.081Z" }, + { url = "https://files.pythonhosted.org/packages/05/7e/2a110bc2713557d6a1bfb06af23dd01e7dde52b6ee7dadc589868f9abfac/tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", size = 251170, upload-time = "2024-11-27T22:38:27.921Z" }, + { url = "https://files.pythonhosted.org/packages/64/7b/22d713946efe00e0adbcdfd6d1aa119ae03fd0b60ebed51ebb3fa9f5a2e5/tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", size = 236530, upload-time = "2024-11-27T22:38:29.591Z" }, + { url = "https://files.pythonhosted.org/packages/38/31/3a76f67da4b0cf37b742ca76beaf819dca0ebef26d78fc794a576e08accf/tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", size = 258666, upload-time = "2024-11-27T22:38:30.639Z" }, + { url = "https://files.pythonhosted.org/packages/07/10/5af1293da642aded87e8a988753945d0cf7e00a9452d3911dd3bb354c9e2/tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", size = 243954, upload-time = "2024-11-27T22:38:31.702Z" }, + { url = "https://files.pythonhosted.org/packages/5b/b9/1ed31d167be802da0fc95020d04cd27b7d7065cc6fbefdd2f9186f60d7bd/tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", size = 98724, upload-time = "2024-11-27T22:38:32.837Z" }, + { url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383, upload-time = "2024-11-27T22:38:34.455Z" }, + { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257, upload-time = "2024-11-27T22:38:35.385Z" }, +] + +[[package]] +name = "velopack" +source = { editable = "." } + +[package.dev-dependencies] +dev = [ + { name = "maturin" }, + { name = "pyinstaller" }, +] +test = [ + { name = "pyinstaller" }, +] + +[package.metadata] + +[package.metadata.requires-dev] +dev = [ + { name = "maturin", specifier = ">=1.8.6" }, + { name = "pyinstaller", specifier = ">=6.13.0" }, +] +test = [{ name = "pyinstaller", specifier = ">=6.13.0" }] + +[[package]] +name = "zipp" +version = "3.20.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.9'", +] +sdist = { url = "https://files.pythonhosted.org/packages/54/bf/5c0000c44ebc80123ecbdddba1f5dcd94a5ada602a9c225d84b5aaa55e86/zipp-3.20.2.tar.gz", hash = "sha256:bc9eb26f4506fda01b81bcde0ca78103b6e62f991b381fec825435c836edbc29", size = 24199, upload-time = "2024-09-13T13:44:16.101Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/8b/5ba542fa83c90e09eac972fc9baca7a88e7e7ca4b221a89251954019308b/zipp-3.20.2-py3-none-any.whl", hash = "sha256:a817ac80d6cf4b23bf7f2828b7cabf326f15a001bea8b1f9b49631780ba28350", size = 9200, upload-time = "2024-09-13T13:44:14.38Z" }, +] + +[[package]] +name = "zipp" +version = "3.22.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.9'", +] +sdist = { url = "https://files.pythonhosted.org/packages/12/b6/7b3d16792fdf94f146bed92be90b4eb4563569eca91513c8609aebf0c167/zipp-3.22.0.tar.gz", hash = "sha256:dd2f28c3ce4bc67507bfd3781d21b7bb2be31103b51a4553ad7d90b84e57ace5", size = 25257, upload-time = "2025-05-26T14:46:32.217Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ad/da/f64669af4cae46f17b90798a827519ce3737d31dbafad65d391e49643dc4/zipp-3.22.0-py3-none-any.whl", hash = "sha256:fe208f65f2aca48b81f9e6fd8cf7b8b32c26375266b009b413d45306b6148343", size = 9796, upload-time = "2025-05-26T14:46:30.775Z" }, +]