mirror of
https://github.com/AikidoSec/safe-chain.git
synced 2026-05-26 12:10:49 +00:00
125 lines
3 KiB
JavaScript
125 lines
3 KiB
JavaScript
import { parsePipPackageFromUrl } from "./parsePipPackageUrl.js";
|
|
|
|
/**
|
|
* @param {any} file
|
|
* @param {string} metadataUrl
|
|
* @returns {string | undefined}
|
|
*/
|
|
export function getPackageVersionFromMetadataFile(file, metadataUrl) {
|
|
const href = typeof file?.url === "string" ? file.url : undefined;
|
|
const filename = typeof file?.filename === "string" ? file.filename : undefined;
|
|
|
|
if (href) {
|
|
const resolvedHref = new URL(href, metadataUrl).toString();
|
|
return parsePipPackageFromUrl(
|
|
resolvedHref,
|
|
new URL(resolvedHref).host
|
|
).version;
|
|
}
|
|
|
|
if (filename) {
|
|
return parsePipPackageFromUrl(
|
|
new URL(filename, metadataUrl).toString(),
|
|
new URL(metadataUrl).host
|
|
).version;
|
|
}
|
|
|
|
return undefined;
|
|
}
|
|
|
|
/**
|
|
* @param {any} json
|
|
* @param {string} metadataUrl
|
|
* @returns {string[]}
|
|
*/
|
|
export function getAvailableVersionsFromJson(json, metadataUrl) {
|
|
if (json.releases && typeof json.releases === "object") {
|
|
return Object.keys(json.releases);
|
|
}
|
|
|
|
if (Array.isArray(json.files)) {
|
|
return [
|
|
...new Set(
|
|
json.files
|
|
.map((/** @type {any} */ file) =>
|
|
getPackageVersionFromMetadataFile(file, metadataUrl)
|
|
)
|
|
.filter((/** @type {string | undefined} */ version) =>
|
|
typeof version === "string"
|
|
)
|
|
),
|
|
];
|
|
}
|
|
|
|
return [];
|
|
}
|
|
|
|
/**
|
|
* @param {string[]} versions
|
|
* @returns {string | undefined}
|
|
*/
|
|
export function calculateLatestVersion(versions) {
|
|
const stableVersions = versions.filter((version) => !isPrerelease(version));
|
|
if (stableVersions.length > 0) {
|
|
return stableVersions.sort(comparePep440ishVersions).at(-1);
|
|
}
|
|
|
|
return versions.sort(comparePep440ishVersions).at(-1);
|
|
}
|
|
|
|
/**
|
|
* @param {string} left
|
|
* @param {string} right
|
|
* @returns {number}
|
|
*/
|
|
function comparePep440ishVersions(left, right) {
|
|
const leftParts = tokenizeVersion(left);
|
|
const rightParts = tokenizeVersion(right);
|
|
const maxLength = Math.max(leftParts.length, rightParts.length);
|
|
|
|
for (let index = 0; index < maxLength; index += 1) {
|
|
const leftPart = leftParts[index];
|
|
const rightPart = rightParts[index];
|
|
|
|
if (leftPart === undefined) return -1;
|
|
if (rightPart === undefined) return 1;
|
|
|
|
if (leftPart === rightPart) {
|
|
continue;
|
|
}
|
|
|
|
const leftNumeric = typeof leftPart === "number";
|
|
const rightNumeric = typeof rightPart === "number";
|
|
|
|
if (leftNumeric && rightNumeric) {
|
|
return leftPart - rightPart;
|
|
}
|
|
|
|
if (leftNumeric) return 1;
|
|
if (rightNumeric) return -1;
|
|
|
|
return String(leftPart).localeCompare(String(rightPart));
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @param {string} version
|
|
* @returns {(string | number)[]}
|
|
*/
|
|
function tokenizeVersion(version) {
|
|
return version
|
|
.toLowerCase()
|
|
.split(/[^a-z0-9]+/)
|
|
.flatMap((part) => part.match(/[a-z]+|\d+/g) || [])
|
|
.map((part) => (/^\d+$/.test(part) ? Number(part) : part));
|
|
}
|
|
|
|
/**
|
|
* @param {string} version
|
|
* @returns {boolean}
|
|
*/
|
|
function isPrerelease(version) {
|
|
return /(a|b|rc|dev)\d+/i.test(version);
|
|
}
|