/****************************************************************************** __ ____ __ / / ___ ____ _/ __/_____________ _/ /__ / / / _ \/ __ `/ /_/ ___/ ___/ __ `/ / _ \ / /___/ __/ /_/ / __(__ ) /__/ /_/ / / __/ /_____/\___/\__,_/_/ /____/\___/\__,_/_/\___/ (C)opyright 2025, Leafscale, LLC - https://www.leafscale.com Project: Zygaena Filename: upgrade.reef Authors: Chris Tusa License: Description: Upgrade command - upgrade installed packages ******************************************************************************/ module commands.upgrade import sys.args import sys.process import io.file import io.dir import io.path import core.str import core.config import core.database import core.port import core.portindex import core.package import types import util.color import util.version as ver import exitcodes as ec export fn execute(opts: types.GlobalOptions): int end export fn execute(opts: types.GlobalOptions): int let argc = args.count() // Load configuration and apply command-line overrides let base_cfg = config.load() let cfg = config.apply_overrides(base_cfg, opts.root, opts.prefix, opts.no_chroot) let root_path = config.get_root(cfg) // Auto-refresh port index if configured if config.get_auto_refresh(cfg) portindex.rebuild_port_index() end if // Show root if set (verbose mode) if opts.verbose and str.length(root_path) > 0 color.print_info("Using root: " + root_path) end if // Count non-flag arguments (package names) // Skip flag values (--root, --prefix take a value) mut pkg_count = 0 mut i = opts.cmd_index + 1 while i < argc let arg = args.get(i) if arg == "--root" or arg == "--prefix" i = i + 2 // Skip flag and its value continue elif arg == "--no-chroot-scripts" i = i + 1 // valueless flag: skip only itself continue end if if not str.starts_with(arg, "-") pkg_count = pkg_count + 1 end if i = i + 1 end while if pkg_count == 0 // No packages specified - upgrade all return upgrade_all(cfg) else // Upgrade specified packages mut all_success = true i = opts.cmd_index + 1 while i < argc let pkg_name = args.get(i) // Skip flags and their values if pkg_name == "--root" or pkg_name == "--prefix" i = i + 2 // Skip flag and its value continue elif pkg_name == "--no-chroot-scripts" i = i + 1 // valueless flag: skip only itself continue end if if str.starts_with(pkg_name, "-") i = i + 1 continue end if // Upgrade this package if not upgrade_package(pkg_name, cfg) color.print_error("Failed to upgrade: " + pkg_name) all_success = false end if i = i + 1 end while if all_success return ec.EXIT_SUCCESS() else return ec.EXIT_UPGRADE_FAILED() end if end if end execute fn upgrade_all(cfg: config.Config): int color.print_action("Checking for upgrades...") // Get root path for rooted operations let root_path = config.get_root(cfg) // List all installed packages (use rooted version if root is set) mut installed: [string] = new [string](512) let installed_count = list_installed_pkgs(installed, 512, root_path) if installed_count == 0 color.print_info("No packages installed") return ec.EXIT_SUCCESS() end if // Check each package for updates mut upgradable: [string] = new [string](512) mut upgradable_count = 0 mut i = 0 while i < installed_count let pkg_name = installed[i] if has_upgrade(pkg_name, root_path) upgradable[upgradable_count] = pkg_name upgradable_count = upgradable_count + 1 end if i = i + 1 end while if upgradable_count == 0 color.print_success("All packages are up to date") return ec.EXIT_SUCCESS() end if color.print_info("Packages to upgrade: " + int_to_str(upgradable_count)) // List packages to upgrade i = 0 while i < upgradable_count let pkg_name = upgradable[i] let installed_pkg = get_installed_pkg(pkg_name, root_path) let port_result = port.find(pkg_name) if port_result.success let p = port_result.port println(" " + pkg_name + " " + installed_pkg.info.version + " -> " + p.info.version) end if i = i + 1 end while // Ask for confirmation unless -y flag if not args.has_flag("y") and not args.has_flag("yes") color.print_info("Use -y flag to skip confirmation and proceed with upgrade") return ec.EXIT_CANCELLED() end if // Dry-run: show what would be done and exit if args.has_flag("n") or args.has_flag("dry-run") color.print_info("Dry run — no changes made") return ec.EXIT_SUCCESS() end if // Perform upgrades mut all_success = true i = 0 while i < upgradable_count if not upgrade_package(upgradable[i], cfg) color.print_error("Failed to upgrade: " + upgradable[i]) all_success = false end if i = i + 1 end while if all_success return ec.EXIT_SUCCESS() else return ec.EXIT_UPGRADE_FAILED() end if end upgrade_all fn has_upgrade(pkg_name: string, root_path: string): bool // Get installed version (use rooted version if root is set) let installed_pkg = get_installed_pkg(pkg_name, root_path) if str.length(installed_pkg.info.name) == 0 return false end if // Find port let port_result = port.find(pkg_name) if not port_result.success return false end if let p = port_result.port // Compare versions using semantic versioning return ver.compare_versions(installed_pkg.info.version, p.info.version) < 0 end has_upgrade // Helper functions for rooted operations fn list_installed_pkgs(names: [string], max_count: int, root_path: string): int if str.length(root_path) > 0 return database.list_installed_rooted(names, max_count, root_path) end if return database.list_installed(names, max_count) end list_installed_pkgs fn get_installed_pkg(name: string, root_path: string): types.InstalledPackage if str.length(root_path) > 0 return database.get_installed_rooted(name, root_path) end if return database.get_installed(name) end get_installed_pkg fn is_installed_pkg(name: string, root_path: string): bool if str.length(root_path) > 0 return database.is_installed_rooted(name, root_path) end if return database.is_installed(name) end is_installed_pkg fn get_files_list(name: string, files: [string], max_count: int, root_path: string): int if str.length(root_path) > 0 return database.get_files_rooted(name, files, max_count, root_path) end if return database.get_files(name, files, max_count) end get_files_list fn unregister_pkg(name: string, root_path: string): bool if str.length(root_path) > 0 return database.unregister_package_rooted(name, root_path) end if return database.unregister_package(name) end unregister_pkg fn register_pkg(pkg: types.InstalledPackage, root_path: string): bool if str.length(root_path) > 0 return database.register_from_extract_dir_rooted(pkg, "", root_path) end if return database.register_package(pkg) end register_pkg fn get_actual_root(root_path: string): string if str.length(root_path) == 0 return "/" end if return root_path end get_actual_root fn upgrade_package(pkg_name: string, cfg: config.Config): bool // Get root path for rooted operations let root_path = config.get_root(cfg) // Check if installed (use rooted version if root is set) if not is_installed_pkg(pkg_name, root_path) color.print_error(pkg_name + " is not installed") return false end if // Get installed info (use rooted version if root is set) let installed_pkg = get_installed_pkg(pkg_name, root_path) // Find port let port_result = port.find(pkg_name) if not port_result.success color.print_error("Port not found: " + pkg_name) return false end if let p = port_result.port // Check if upgrade is available using semantic versioning let cmp = ver.compare_versions(installed_pkg.info.version, p.info.version) if cmp >= 0 color.print_info(pkg_name + " is already at version " + installed_pkg.info.version) return true end if color.print_action("Upgrading " + pkg_name + " from " + installed_pkg.info.version + " to " + p.info.version + "...") // Look for pre-built package let pkg_path = find_package(pkg_name, p.info.version, p.info.release, cfg) if str.length(pkg_path) == 0 color.print_error("Package not found. Build it first with: coral build") return false end if // Remove old version (keep database entry for now) mut files: [string] = new [string](4096) let file_count = get_files_list(pkg_name, files, 4096, root_path) color.print_info("Removing old files...") let actual_root = get_actual_root(root_path) remove_files(files, file_count, actual_root) // Install new version color.print_info("Installing new version...") // Create extraction directory let extract_dir = create_extract_dir(cfg) if str.length(extract_dir) == 0 color.print_error("Failed to create extraction directory") return false end if // Extract package let result = package.extract_package(pkg_path, extract_dir) if not result.success color.print_error(result.error) cleanup_extract_dir(extract_dir) return false end if // Read new file list mut new_files: [string] = new [string](4096) let new_file_count = package.read_footprint(extract_dir, new_files, 4096) // Install files to proper root if not package.install_files(extract_dir, actual_root) color.print_error("Failed to install files") cleanup_extract_dir(extract_dir) return false end if // Update database (remove and re-add) using rooted versions if needed unregister_pkg(pkg_name, root_path) let new_info = package.read_pkginfo(extract_dir) mut new_pkg = types.new_installed_package() new_pkg.info = new_info new_pkg.install_date = "2025-01-24" new_pkg.install_reason = "explicit" new_pkg.files = new_files new_pkg.files_count = new_file_count // Register using rooted version if root is set if str.length(root_path) > 0 if not database.register_from_extract_dir_rooted(new_pkg, extract_dir, root_path) color.print_warning("Failed to update database") end if else if not database.register_from_extract_dir(new_pkg, extract_dir) color.print_warning("Failed to update database") end if end if // Cleanup cleanup_extract_dir(extract_dir) color.print_success("Upgraded " + pkg_name + " to " + p.info.version) return true end upgrade_package // Find a package file by name and version fn find_package(name: string, version: string, release: int, cfg: config.Config): string let packages_dir = config.get_packages_dir(cfg) let pkg_name = name + "-" + version + "-" + int_to_str(release) + ".pkg.tar.xz" let pkg_path = path.join_path(packages_dir, pkg_name) if file.fileExists(pkg_path) return pkg_path end if return "" end find_package // Remove files from system fn remove_files(files: [string], file_count: int, root_path: string): bool mut i = file_count - 1 while i >= 0 let rel_path = files[i] let full_path = path.join_path(root_path, rel_path) if file.fileExists(full_path) let cmd = "rm -f \"" + full_path + "\"" let pid = process.process_spawn_shell(cmd) if pid > 0 process.process_wait(pid) end if end if i = i - 1 end while return true end remove_files // Create extraction directory fn create_extract_dir(cfg: config.Config): string let work_dir = config.get_work_dir(cfg) let pid = process.getpid() let extract_dir = path.join_path(work_dir, "upgrade_" + int_to_str(pid)) if dir.dir_exists(extract_dir) let cmd = "rm -rf \"" + extract_dir + "\"" let rm_pid = process.process_spawn_shell(cmd) if rm_pid > 0 process.process_wait(rm_pid) end if end if if not dir.create_dir_all(extract_dir) return "" end if return extract_dir end create_extract_dir // Cleanup extraction directory fn cleanup_extract_dir(dir_path: string): bool if str.length(dir_path) == 0 return true end if let cmd = "rm -rf \"" + dir_path + "\"" let pid = process.process_spawn_shell(cmd) if pid < 0 return false end if process.process_wait(pid) return true end cleanup_extract_dir // Helper: convert int to string fn int_to_str(n: int): string if n == 0 return "0" end if mut negative = false mut value = n if n < 0 negative = true value = 0 - n end if mut result = "" while value > 0 let digit = value % 10 result = str.concat(str.substring("0123456789", digit, 1), result) value = value / 10 end while if negative result = str.concat("-", result) end if return result end int_to_str end module