From fe87c64b4ab5ab53f84c6f038709ab7f6745c0eb Mon Sep 17 00:00:00 2001 From: sachintu47 Date: Thu, 12 Mar 2026 02:54:29 -0400 Subject: [PATCH 1/3] support specific version installation --- bin/zopen-install | 32 +++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/bin/zopen-install b/bin/zopen-install index 7443ebfbd..4a2c399f8 100755 --- a/bin/zopen-install +++ b/bin/zopen-install @@ -244,12 +244,38 @@ handlePackageInstall() if [ -n "${versioned}" ]; then printVerbose "Specific version ${versioned} requested - checking existence and URL." requestedMajor=$(echo "${versioned}" | awk -F'.' '{print $1}') + if [ -z "${requestedMajor}" ]; then + printError "A major version must be provided when specifying a version (e.g., ${name}=1)." + fi requestedMinor=$(echo "${versioned}" | awk -F'.' '{print $2}') requestedPatch=$(echo "${versioned}" | awk -F'.' '{print $3}') requestedSubrelease=$(echo "${versioned}" | awk -F'.' '{print $4}') - requestedVersion="${requestedMajor}\\\.${requestedMinor}\\\.${requestedPatch}\\\.${requestedSubrelease}" - printVerbose "Finding URL for latest release matching version prefix: requestedVersion: ${requestedVersion}" - releaseMetadata=$(/bin/printf "%s" "${releases}" | jq -e -r '. | map(select(.assets[].name | test("'${requestedVersion}'")))[0]') + + # Construct the version string prefix, stripping any leading 'v' + reqVer="${requestedMajor#v}" + if [ -n "${requestedMinor}" ]; then + reqVer="${reqVer}\\\.${requestedMinor}" + fi + if [ -n "${requestedPatch}" ]; then + reqVer="${reqVer}\\\.${requestedPatch}" + fi + if [ -n "${requestedSubrelease}" ]; then + reqVer="${reqVer}\\\.${requestedSubrelease}" + fi + + # The regex matches the version string preceded by a hyphen and optional 'v', + # followed by a dot or a hyphen (to mark the end of the version part). + versionRegex="-v?${reqVer}([\\\\.-]|$)" + + printVerbose "Finding latest release matching version regex: ${versionRegex}" + # Sort matching releases using version-aware logic: + # 1. Map assets to extract version/release components + # 2. Sort numerically by Major, Minor, Patch, Subrelease (padded) + # 3. Use the build timestamp as a final tie-breaker + releaseMetadata=$(/bin/printf "%s" "${releases}" | jq -e -r ' + map(select(.assets[].name | test("'${versionRegex}'"))) | + sort_by(.assets[0].version | split(".") | map(tonumber? // 0)) | + reverse | .[0]') elif [ -n "${tagged}" ]; then printVerbose "Explicit tagged version '${tagged}' specified. Checking for match." releaseMetadata=$(/bin/printf "%s" "${releases}" | jq -e -r '.[] | select(.tag_name == "'${tagged}'")') From e844ba30e88f8dfc691c33b26e19afd19908cbe4 Mon Sep 17 00:00:00 2001 From: sachintu47 Date: Thu, 12 Mar 2026 04:48:47 -0400 Subject: [PATCH 2/3] zopen-install: add support for semantic versioning operators - Support semantic operators (>, >=, <, <=) in addition to '=' - Use jq-based numeric array comparison for accurate semantic versioning - Implement version-aware sorting to correctly prioritize versions (e.g., 1.10 over 1.9) - Maintain prefix matching behavior for the '=' operator - Update port validation to recognize and handle all semantic operators --- bin/zopen-install | 77 +++++++++++++++++++++++++---------------------- 1 file changed, 41 insertions(+), 36 deletions(-) diff --git a/bin/zopen-install b/bin/zopen-install index 4a2c399f8..ba8e01198 100755 --- a/bin/zopen-install +++ b/bin/zopen-install @@ -184,18 +184,32 @@ installDependencies() handlePackageInstall() { - fullname="$1" isRuntimeDependency=$2 if [ -z "$isRuntimeDependency" ]; then isRuntimeDependency=false fi - printVerbose "Name to install: ${fullname}, parsing any version ('=') or tag ('%') has been specified" - name=$(echo "${fullname}" | sed -e 's#[=%].*##') + + # Support: name=ver, name>=ver, name>ver, name<=ver, name=<]|%).*//') repo="${name}" - versioned=$(echo "${fullname}" | cut -s -d '=' -f 2) + + # Extract operator (can be >=, <=, >, <, =) + operator=$(echo "${fullname}" | sed -E -n "s/^${name}([>=<]{1,2}).*/\1/p") + + # Extract version (everything after operator but before %) + if [ -n "${operator}" ]; then + versioned=$(echo "${fullname}" | sed -n "s/^${name}${operator}\([^%]*\).*/\1/p") + else + # Fallback to older '=' parsing if no explicit operator found but versioned exists + versioned=$(echo "${fullname}" | cut -s -d '=' -f 2 | cut -d '%' -f 1) + if [ -n "${versioned}" ]; then operator="="; fi + fi + + # Extract tag tagged=$(echo "${fullname}" | cut -s -d '%' -f 2) - printDebug "Name:${name};version:${versioned};tag:${tagged};repo:${repo}" + + printDebug "Name:${name};operator:${operator};version:${versioned};tag:${tagged};repo:${repo}" printInfo "${NC}${HEADERCOLOR}${BOLD}Processing package: ${name}${NC}" nameSansPort=$(echo "${name}" | sed -e 's#\(.*\)port$#\1#') @@ -242,40 +256,31 @@ handlePackageInstall() # Options where the user explicitly sets a version/tag/releaseline currently ignore any configured release-line, # either for a previous package install or system default if [ -n "${versioned}" ]; then - printVerbose "Specific version ${versioned} requested - checking existence and URL." + printVerbose "Specific version ${versioned} requested with operator '${operator}' - checking existence and URL." requestedMajor=$(echo "${versioned}" | awk -F'.' '{print $1}') if [ -z "${requestedMajor}" ]; then printError "A major version must be provided when specifying a version (e.g., ${name}=1)." fi - requestedMinor=$(echo "${versioned}" | awk -F'.' '{print $2}') - requestedPatch=$(echo "${versioned}" | awk -F'.' '{print $3}') - requestedSubrelease=$(echo "${versioned}" | awk -F'.' '{print $4}') - - # Construct the version string prefix, stripping any leading 'v' - reqVer="${requestedMajor#v}" - if [ -n "${requestedMinor}" ]; then - reqVer="${reqVer}\\\.${requestedMinor}" - fi - if [ -n "${requestedPatch}" ]; then - reqVer="${reqVer}\\\.${requestedPatch}" - fi - if [ -n "${requestedSubrelease}" ]; then - reqVer="${reqVer}\\\.${requestedSubrelease}" - fi - # The regex matches the version string preceded by a hyphen and optional 'v', - # followed by a dot or a hyphen (to mark the end of the version part). - versionRegex="-v?${reqVer}([\\\\.-]|$)" - - printVerbose "Finding latest release matching version regex: ${versionRegex}" - # Sort matching releases using version-aware logic: - # 1. Map assets to extract version/release components - # 2. Sort numerically by Major, Minor, Patch, Subrelease (padded) - # 3. Use the build timestamp as a final tie-breaker - releaseMetadata=$(/bin/printf "%s" "${releases}" | jq -e -r ' - map(select(.assets[].name | test("'${versionRegex}'"))) | - sort_by(.assets[0].version | split(".") | map(tonumber? // 0)) | - reverse | .[0]') + # Convert requested version string to a JSON array of numbers for jq + req_v_json=$(echo "${versioned#v}" | tr '.' '\n' | jq -R 'tonumber? // 0' | jq -s -c .) + + printVerbose "Finding latest release matching ${operator} ${versioned}" + # Unified selection logic using jq: + # 1. to_v: Converts a dot-delimited version string to a numeric array + # 2. match_v: Handles prefix match for '=' and numeric comparison for others + releaseMetadata=$(/bin/printf "%s" "${releases}" | jq -e -r --arg op "${operator}" --argjson req_v "${req_v_json}" ' + def to_v: split(".") | map(tonumber? // 0); + def match_v(rv; op): + to_v as $av | + if op == "=" then $av[0:(rv | length)] == rv + elif op == ">=" then $av >= rv + elif op == ">" then $av > rv + elif op == "<=" then $av <= rv + elif op == "<" then $av < rv + else false end; + map(select(.assets[0].version | match_v($req_v; $op))) | + sort_by(.assets[0].version | to_v) | reverse | .[0]') elif [ -n "${tagged}" ]; then printVerbose "Explicit tagged version '${tagged}' specified. Checking for match." releaseMetadata=$(/bin/printf "%s" "${releases}" | jq -e -r '.[] | select(.tag_name == "'${tagged}'")') @@ -891,8 +896,8 @@ else invalidlist="" for chosenRepo in $(echo "${chosenRepos}" | tr ',' ' ' | tr -s ' '); do printVerbose "Processing repo: ${chosenRepo}" - printVerbose "Stripping any version (%), tag (#) or port suffixes" - toolrepo=$(echo "${chosenRepo}" | sed -e 's#%.*##' -e 's#=.*##') + printVerbose "Stripping any version operator (>, >=, <, <=, =), tag (%) or port suffixes" + toolrepo=$(echo "${chosenRepo}" | sed -E 's/([>=<]|%).*//') toolfound=$(echo "${repo_results}" | awk -vtoolrepo="${toolrepo}" '$0 == toolrepo {print}') if [ "${toolfound}" = "${toolrepo}" ]; then printVerbose "Adding '${chosenRepo}' to the install queue." From 561f6568b63fb4638d6e9635cf6e9c90c7b50401 Mon Sep 17 00:00:00 2001 From: sachintu47 Date: Sat, 21 Mar 2026 12:50:13 -0400 Subject: [PATCH 3/3] augment fixes --- bin/zopen-install | 93 ++++++++++++++++++++++++++++++++++++----------- 1 file changed, 71 insertions(+), 22 deletions(-) diff --git a/bin/zopen-install b/bin/zopen-install index ba8e01198..622e24727 100755 --- a/bin/zopen-install +++ b/bin/zopen-install @@ -190,25 +190,62 @@ handlePackageInstall() isRuntimeDependency=false fi + operator="" + versioned="" + tagged="" + # Support: name=ver, name>=ver, name>ver, name<=ver, name=<]|%).*//') + name=$(echo "${fullname}" | sed -E 's/[=><%].*//') repo="${name}" - - # Extract operator (can be >=, <=, >, <, =) - operator=$(echo "${fullname}" | sed -E -n "s/^${name}([>=<]{1,2}).*/\1/p") - - # Extract version (everything after operator but before %) + + # Get everything after the name + suffix="${fullname#$name}" + + # Extract operator (can be >=, <=, >, <, =) from the beginning of suffix + operator=$(echo "${suffix}" | sed -E -n 's/^([>=<]{1,2}).*/\1/p') + if [ -n "${operator}" ]; then - versioned=$(echo "${fullname}" | sed -n "s/^${name}${operator}\([^%]*\).*/\1/p") + # Validate operator + case "${operator}" in + "="|">"|">="|"<"|"<=") ;; + *) printError "Invalid operator '${operator}' in package specification. Supported operators are: =, >, >=, <, <=" ;; + esac + + # Extract version: take everything after operator and before % + rest="${suffix#$operator}" + versioned="${rest%%%*}" + + if [ -z "${versioned}" ]; then + printError "A version must be provided when using an operator (e.g., ${name}${operator}1.0)." + fi else # Fallback to older '=' parsing if no explicit operator found but versioned exists - versioned=$(echo "${fullname}" | cut -s -d '=' -f 2 | cut -d '%' -f 1) - if [ -n "${versioned}" ]; then operator="="; fi + # (Checking if suffix starts with '=' and taking everything before '%') + case "${suffix}" in + =*) + operator="=" + rest="${suffix#=}" + versioned="${rest%%%*}" + ;; + esac fi - + + # Validate that the version string contains valid characters (alphanumeric, dots, hyphens) + if [ -n "${versioned}" ]; then + # Strip leading 'v' if present for validation + v_to_check="${versioned#v}" + # Must contain at least one digit and only valid chars + if [ -z "${v_to_check}" ] || ! echo "${v_to_check}" | grep -q "[0-9]" || echo "${v_to_check}" | grep -q "[^0-9a-zA-Z.-]" || echo "${v_to_check}" | grep -q "^\." || echo "${v_to_check}" | grep -q "\.$" || echo "${v_to_check}" | grep -q "\.\."; then + printError "Invalid version string '${versioned}'. Versions must be numeric segments separated by dots (e.g., 1.2.3) and can include alphanumeric suffixes (e.g., 1.2.3-rc1)." + fi + fi + # Extract tag tagged=$(echo "${fullname}" | cut -s -d '%' -f 2) + if [ -n "${versioned}" ] && [ -n "${tagged}" ]; then + printError "Ambiguous package specification '${fullname}'. Provide either a version constraint or a tag, but not both." + fi printDebug "Name:${name};operator:${operator};version:${versioned};tag:${tagged};repo:${repo}" printInfo "${NC}${HEADERCOLOR}${BOLD}Processing package: ${name}${NC}" @@ -263,24 +300,28 @@ handlePackageInstall() fi # Convert requested version string to a JSON array of numbers for jq - req_v_json=$(echo "${versioned#v}" | tr '.' '\n' | jq -R 'tonumber? // 0' | jq -s -c .) + req_v_json=$(echo "${versioned#v}" | tr '.' '\n' | jq -R 'tonumber' | jq -s -c .) printVerbose "Finding latest release matching ${operator} ${versioned}" # Unified selection logic using jq: - # 1. to_v: Converts a dot-delimited version string to a numeric array + # 1. to_v: Converts a dot-delimited version string to a numeric array, padded with 0s to length 8 # 2. match_v: Handles prefix match for '=' and numeric comparison for others releaseMetadata=$(/bin/printf "%s" "${releases}" | jq -e -r --arg op "${operator}" --argjson req_v "${req_v_json}" ' - def to_v: split(".") | map(tonumber? // 0); + def to_v: if . == null then [0] else split(".") | map(tonumber? // 0) end | . + [range(0; 8 - length) | 0]; def match_v(rv; op): to_v as $av | + (rv + [range(0; 8 - (rv | length)) | 0]) as $nrv | if op == "=" then $av[0:(rv | length)] == rv - elif op == ">=" then $av >= rv - elif op == ">" then $av > rv - elif op == "<=" then $av <= rv - elif op == "<" then $av < rv + elif op == ">=" then $av >= $nrv + elif op == ">" then $av > $nrv + elif op == "<=" then $av <= $nrv + elif op == "<" then $av < $nrv else false end; - map(select(.assets[0].version | match_v($req_v; $op))) | - sort_by(.assets[0].version | to_v) | reverse | .[0]') + map(select(.assets[0].version != null and (.assets[0].version | match_v($req_v; $op)))) | + sort_by([(.assets[0].version | to_v), .date, .tag_name]) | reverse | .[0]') + if [ -z "${releaseMetadata}" ] || [ "${releaseMetadata}" = "null" ]; then + printError "Could not find a release of '${name}' matching '${operator}${versioned}'" + fi elif [ -n "${tagged}" ]; then printVerbose "Explicit tagged version '${tagged}' specified. Checking for match." releaseMetadata=$(/bin/printf "%s" "${releases}" | jq -e -r '.[] | select(.tag_name == "'${tagged}'")') @@ -892,18 +933,26 @@ if ${all}; then done installArray=$(strtrim "${installArray}") else + chosenRepos=$(echo "${chosenRepos}" | tr ',' ' ' | tr -s ' ') chosenRepos=$(strtrim "${chosenRepos}") invalidlist="" - for chosenRepo in $(echo "${chosenRepos}" | tr ',' ' ' | tr -s ' '); do + for chosenRepo in ${chosenRepos}; do printVerbose "Processing repo: ${chosenRepo}" printVerbose "Stripping any version operator (>, >=, <, <=, =), tag (%) or port suffixes" - toolrepo=$(echo "${chosenRepo}" | sed -E 's/([>=<]|%).*//') + toolrepo=$(echo "${chosenRepo}" | sed -E 's/[=><%].*//') toolfound=$(echo "${repo_results}" | awk -vtoolrepo="${toolrepo}" '$0 == toolrepo {print}') if [ "${toolfound}" = "${toolrepo}" ]; then printVerbose "Adding '${chosenRepo}' to the install queue." installArray="${installArray} ${chosenRepo}" printVerbose "Removing valid port from input list." - chosenRepos=$(echo "${chosenRepos}" | sed -e "s#^${chosenRepo}\$##") + # Safely remove the word from the space-separated list + newChosenRepos="" + for r in ${chosenRepos}; do + if [ "$r" != "${chosenRepo}" ]; then + newChosenRepos="${newChosenRepos} $r" + fi + done + chosenRepos=$(strtrim "${newChosenRepos}") else invalidlist=$(/bin/printf "%s %s" "${invalidlist}" "${chosenRepo}") fi