(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, }; }));