141 lines
6.2 KiB
JavaScript
141 lines
6.2 KiB
JavaScript
(function initWebAVPUtils(root, factory) {
|
|
const api = factory();
|
|
if (typeof module === 'object' && module.exports) module.exports = api;
|
|
if (root) root.WebAVPUtils = api;
|
|
}(typeof globalThis !== 'undefined' ? globalThis : this, function createWebAVPUtils() {
|
|
'use strict';
|
|
|
|
const GITPEJI_ORIGIN = 'https://git.pejicorp.com';
|
|
const RELEASES_URL = `${GITPEJI_ORIGIN}/peji/WebAVP/releases`;
|
|
const LATEST_RELEASE_API = `${GITPEJI_ORIGIN}/api/v1/repos/peji/WebAVP/releases/latest`;
|
|
|
|
function parseVersion(value) {
|
|
const match = String(value || '').trim().match(/^v?(\d+)\.(\d+)(?:\.(\d+))?(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/);
|
|
if (!match) throw new Error(`Invalid semantic version: ${value}`);
|
|
return {
|
|
parts: [Number(match[1]), Number(match[2]), Number(match[3] || 0)],
|
|
prerelease: match[4] ? match[4].split('.') : [],
|
|
};
|
|
}
|
|
|
|
function compareIdentifiers(left, right) {
|
|
const leftNumber = /^\d+$/.test(left) ? Number(left) : null;
|
|
const rightNumber = /^\d+$/.test(right) ? Number(right) : null;
|
|
if (leftNumber !== null && rightNumber !== null) return Math.sign(leftNumber - rightNumber);
|
|
if (leftNumber !== null) return -1;
|
|
if (rightNumber !== null) return 1;
|
|
return left === right ? 0 : (left > right ? 1 : -1);
|
|
}
|
|
|
|
function compareVersions(leftValue, rightValue) {
|
|
const left = parseVersion(leftValue);
|
|
const right = parseVersion(rightValue);
|
|
for (let index = 0; index < 3; index += 1) {
|
|
if (left.parts[index] !== right.parts[index]) return Math.sign(left.parts[index] - right.parts[index]);
|
|
}
|
|
if (!left.prerelease.length && !right.prerelease.length) return 0;
|
|
if (!left.prerelease.length) return 1;
|
|
if (!right.prerelease.length) return -1;
|
|
const length = Math.max(left.prerelease.length, right.prerelease.length);
|
|
for (let index = 0; index < length; index += 1) {
|
|
if (left.prerelease[index] === undefined) return -1;
|
|
if (right.prerelease[index] === undefined) return 1;
|
|
const result = compareIdentifiers(left.prerelease[index], right.prerelease[index]);
|
|
if (result) return result;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
function getAutoGrid(countValue) {
|
|
const count = Math.max(1, Math.min(16, Number(countValue) || 1));
|
|
if (count === 1) return { cols: 1, rows: 1 };
|
|
if (count === 2) return { cols: 2, rows: 1 };
|
|
if (count <= 4) return { cols: 2, rows: 2 };
|
|
if (count <= 6) return { cols: 3, rows: 2 };
|
|
if (count <= 9) return { cols: 3, rows: 3 };
|
|
if (count <= 12) return { cols: 4, rows: 3 };
|
|
return { cols: 4, rows: 4 };
|
|
}
|
|
|
|
function assertGitPejiUrl(rawUrl, label) {
|
|
let url;
|
|
try {
|
|
url = new URL(rawUrl);
|
|
} catch {
|
|
throw new Error(`Invalid ${label} URL`);
|
|
}
|
|
if (url.origin !== GITPEJI_ORIGIN || url.username || url.password) {
|
|
throw new Error(`Invalid ${label} host`);
|
|
}
|
|
return url.href;
|
|
}
|
|
|
|
function normalizeRelease(rawRelease) {
|
|
if (!rawRelease || typeof rawRelease !== 'object') throw new Error('Invalid release response');
|
|
const version = String(rawRelease.tag_name || '').replace(/^v/, '');
|
|
parseVersion(version);
|
|
const pageUrl = assertGitPejiUrl(rawRelease.html_url || RELEASES_URL, 'release page');
|
|
const assets = Array.isArray(rawRelease.assets) ? rawRelease.assets.map((asset) => {
|
|
if (!asset || typeof asset.name !== 'string' || !asset.name.trim()) throw new Error('Invalid release asset');
|
|
const url = assertGitPejiUrl(asset.browser_download_url, 'release asset');
|
|
if (!new URL(url).pathname.startsWith('/peji/WebAVP/releases/download/')) throw new Error('Invalid release asset path');
|
|
return { name: asset.name.trim(), url };
|
|
}) : [];
|
|
return { version, name: String(rawRelease.name || rawRelease.tag_name), pageUrl, assets };
|
|
}
|
|
|
|
function normalizedPlatform(platform) {
|
|
if (platform === 'darwin' || platform === 'mac' || platform === 'macos') return 'mac';
|
|
if (platform === 'win32' || platform === 'windows' || platform === 'win') return 'win';
|
|
return platform === 'linux' ? 'linux' : '';
|
|
}
|
|
|
|
function assetScore(asset, platformValue, archValue) {
|
|
const platform = normalizedPlatform(platformValue);
|
|
const arch = String(archValue || '').toLowerCase();
|
|
const name = asset.name.toLowerCase();
|
|
const platformMatches = platform === 'linux'
|
|
? name.endsWith('.appimage') || name.endsWith('.deb') || /(^|[-_.])linux([-_.]|$)/.test(name)
|
|
: platform === 'mac'
|
|
? name.endsWith('.dmg') || /(^|[-_.])(mac|macos|darwin)([-_.]|$)/.test(name)
|
|
: platform === 'win'
|
|
? name.endsWith('.exe') || name.endsWith('.msi') || /(^|[-_.])(win|windows)([-_.]|$)/.test(name)
|
|
: false;
|
|
if (!platformMatches) return -1;
|
|
const hasArm64 = /(^|[-_.])(arm64|aarch64)([-_.]|$)/.test(name);
|
|
const hasX64 = /(^|[-_.])(x64|amd64|x86_64)([-_.]|$)/.test(name);
|
|
if ((arch === 'arm64' || arch === 'aarch64') && !hasArm64) return -1;
|
|
if ((arch === 'x64' || arch === 'amd64' || arch === 'x86_64') && !hasX64) return -1;
|
|
if (!['arm64', 'aarch64', 'x64', 'amd64', 'x86_64'].includes(arch)) return -1;
|
|
if (platform === 'linux' && name.endsWith('.appimage')) return 30;
|
|
if (platform === 'win' && name.endsWith('.exe')) return 30;
|
|
if (platform === 'mac' && name.endsWith('.dmg')) return 30;
|
|
return 10;
|
|
}
|
|
|
|
function selectReleaseAsset(release, platform, arch) {
|
|
if (!release || !Array.isArray(release.assets)) return null;
|
|
return release.assets
|
|
.map((asset) => ({ asset, score: assetScore(asset, platform, arch) }))
|
|
.filter((candidate) => candidate.score >= 0)
|
|
.sort((left, right) => right.score - left.score || left.asset.name.localeCompare(right.asset.name))[0]?.asset || null;
|
|
}
|
|
|
|
function evaluateUpdate(release, currentVersion, platform, arch) {
|
|
if (compareVersions(release.version, currentVersion) <= 0) return { status: 'current', asset: null };
|
|
const asset = selectReleaseAsset(release, platform, arch);
|
|
return asset ? { status: 'available', asset } : { status: 'unavailable-platform', asset: null };
|
|
}
|
|
|
|
return {
|
|
GITPEJI_ORIGIN,
|
|
LATEST_RELEASE_API,
|
|
RELEASES_URL,
|
|
compareVersions,
|
|
evaluateUpdate,
|
|
getAutoGrid,
|
|
normalizeRelease,
|
|
selectReleaseAsset,
|
|
};
|
|
}));
|