mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-09 01:16:15 -08:00
218 lines
6.6 KiB
JavaScript
218 lines
6.6 KiB
JavaScript
#!/usr/bin/env node
|
|
import fs from 'node:fs';
|
|
import process from 'node:process';
|
|
import { fileURLToPath } from 'node:url';
|
|
import {
|
|
readWindowsRuntimePin,
|
|
serializeWindowsRuntimePin,
|
|
validateWindowsRuntimePin,
|
|
WINDOWS_RUNTIME_LICENSE_CLAIM,
|
|
WINDOWS_RUNTIME_PIN_PATH,
|
|
WINDOWS_RUNTIME_REPOSITORY,
|
|
} from './windows-runtime-pin.mjs';
|
|
|
|
const modulePath = fileURLToPath(import.meta.url);
|
|
const RELEASES_API = `https://api.github.com/repos/${WINDOWS_RUNTIME_REPOSITORY}/releases?per_page=50`;
|
|
const ASSET_NAME_PATTERN = /^mpv-dev-lgpl-x86_64-\d{8}-git-[a-f0-9]{10}\.7z$/;
|
|
|
|
export const WINDOWS_RUNTIME_REFRESH_AFTER_DAYS = 14;
|
|
|
|
function githubApiHeaders() {
|
|
return {
|
|
Accept: 'application/vnd.github+json',
|
|
'User-Agent': 'iptvnator-windows-runtime-pin-updater',
|
|
...(process.env.GITHUB_TOKEN
|
|
? { Authorization: `Bearer ${process.env.GITHUB_TOKEN}` }
|
|
: {}),
|
|
};
|
|
}
|
|
|
|
async function fetchJson(url, fetchImpl) {
|
|
const response = await fetchImpl(url, { headers: githubApiHeaders() });
|
|
if (!response.ok) {
|
|
throw new Error(
|
|
`Unable to query Windows runtime releases: ${response.status} ${response.statusText}`
|
|
);
|
|
}
|
|
return response.json();
|
|
}
|
|
|
|
function extractUpstreamEvidence(body) {
|
|
const mpvCommit = body?.match(
|
|
/github\.com\/mpv-player\/mpv\/commit\/([a-f0-9]{40})/i
|
|
)?.[1];
|
|
const buildRunId = body?.match(
|
|
/github\.com\/zhongfly\/mpv-winbuild\/actions\/runs\/(\d+)/i
|
|
)?.[1];
|
|
if (!mpvCommit || !buildRunId) {
|
|
throw new Error(
|
|
'Latest Windows runtime release lacks the expected mpv commit or build-run evidence.'
|
|
);
|
|
}
|
|
return {
|
|
mpvCommit: mpvCommit.toLowerCase(),
|
|
buildRunUrl: `https://github.com/zhongfly/mpv-winbuild/actions/runs/${buildRunId}`,
|
|
};
|
|
}
|
|
|
|
export function pinFromUpstreamRelease(release) {
|
|
if (release?.draft || release?.prerelease) {
|
|
throw new Error(
|
|
'Windows runtime pin cannot use a draft or prerelease.'
|
|
);
|
|
}
|
|
const matchingAssets = (release?.assets ?? []).filter((asset) =>
|
|
ASSET_NAME_PATTERN.test(asset?.name ?? '')
|
|
);
|
|
if (matchingAssets.length !== 1) {
|
|
throw new Error(
|
|
`Release ${release?.tag_name ?? '<unknown>'} must contain exactly one non-v3 x86_64 LGPL dev archive.`
|
|
);
|
|
}
|
|
const asset = matchingAssets[0];
|
|
const sha256 = asset.digest?.match(/^sha256:([a-f0-9]{64})$/)?.[1];
|
|
if (!sha256) {
|
|
throw new Error(
|
|
`Release asset ${asset.name} must expose a GitHub SHA-256 digest.`
|
|
);
|
|
}
|
|
const evidence = extractUpstreamEvidence(release.body);
|
|
return validateWindowsRuntimePin({
|
|
schemaVersion: 1,
|
|
repository: WINDOWS_RUNTIME_REPOSITORY,
|
|
releaseTag: release.tag_name,
|
|
publishedAt: release.published_at,
|
|
retentionDays: 30,
|
|
asset: {
|
|
name: asset.name,
|
|
url: asset.browser_download_url,
|
|
sha256,
|
|
},
|
|
upstream: {
|
|
...evidence,
|
|
licenseClaim: WINDOWS_RUNTIME_LICENSE_CLAIM,
|
|
},
|
|
});
|
|
}
|
|
|
|
export function selectNewestWindowsRuntimePin(releases) {
|
|
if (!Array.isArray(releases)) {
|
|
throw new Error('Windows runtime releases response must be an array.');
|
|
}
|
|
const candidates = releases
|
|
.filter((release) => !release?.draft && !release?.prerelease)
|
|
.filter((release) =>
|
|
(release?.assets ?? []).some((asset) =>
|
|
ASSET_NAME_PATTERN.test(asset?.name ?? '')
|
|
)
|
|
)
|
|
.sort(
|
|
(left, right) =>
|
|
Date.parse(right.published_at) - Date.parse(left.published_at)
|
|
);
|
|
if (candidates.length === 0) {
|
|
throw new Error('No suitable Windows LGPL runtime release was found.');
|
|
}
|
|
return pinFromUpstreamRelease(candidates[0]);
|
|
}
|
|
|
|
export function runtimePinAgeDays(pin, now = new Date()) {
|
|
return (now.getTime() - Date.parse(pin.publishedAt)) / 86_400_000;
|
|
}
|
|
|
|
export async function isRuntimeAssetAvailable(url, fetchImpl = fetch) {
|
|
try {
|
|
const response = await fetchImpl(url, {
|
|
method: 'HEAD',
|
|
redirect: 'follow',
|
|
headers: {
|
|
'User-Agent': 'iptvnator-windows-runtime-pin-updater',
|
|
},
|
|
});
|
|
return response.ok;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
export async function refreshWindowsRuntimePin({
|
|
pinPath = WINDOWS_RUNTIME_PIN_PATH,
|
|
now = new Date(),
|
|
force = false,
|
|
dryRun = false,
|
|
fetchImpl = fetch,
|
|
} = {}) {
|
|
const current = readWindowsRuntimePin(pinPath);
|
|
const currentAvailable = await isRuntimeAssetAvailable(
|
|
current.asset.url,
|
|
fetchImpl
|
|
);
|
|
const ageDays = runtimePinAgeDays(current, now);
|
|
const refreshReason = force
|
|
? 'forced'
|
|
: !currentAvailable
|
|
? 'unavailable'
|
|
: ageDays >= WINDOWS_RUNTIME_REFRESH_AFTER_DAYS
|
|
? 'age-threshold'
|
|
: null;
|
|
|
|
if (!refreshReason) {
|
|
return {
|
|
changed: false,
|
|
reason: 'current',
|
|
current,
|
|
next: current,
|
|
ageDays,
|
|
};
|
|
}
|
|
|
|
const releases = await fetchJson(RELEASES_API, fetchImpl);
|
|
const next = selectNewestWindowsRuntimePin(releases);
|
|
if (!(await isRuntimeAssetAvailable(next.asset.url, fetchImpl))) {
|
|
throw new Error(
|
|
`Selected Windows runtime asset is not downloadable: ${next.asset.url}`
|
|
);
|
|
}
|
|
const changed =
|
|
serializeWindowsRuntimePin(next) !==
|
|
serializeWindowsRuntimePin(current);
|
|
if (changed && !dryRun) {
|
|
fs.writeFileSync(pinPath, serializeWindowsRuntimePin(next));
|
|
}
|
|
return { changed, reason: refreshReason, current, next, ageDays };
|
|
}
|
|
|
|
function appendGitHubOutputs(result) {
|
|
if (!process.env.GITHUB_OUTPUT) {
|
|
return;
|
|
}
|
|
fs.appendFileSync(
|
|
process.env.GITHUB_OUTPUT,
|
|
[
|
|
`changed=${result.changed}`,
|
|
`reason=${result.reason}`,
|
|
`release-tag=${result.next.releaseTag}`,
|
|
'',
|
|
].join('\n')
|
|
);
|
|
}
|
|
|
|
async function main() {
|
|
const result = await refreshWindowsRuntimePin({
|
|
force: process.argv.includes('--force'),
|
|
dryRun: process.argv.includes('--dry-run'),
|
|
});
|
|
appendGitHubOutputs(result);
|
|
const action = result.changed ? 'updated' : 'kept';
|
|
console.log(
|
|
`Windows Embedded MPV runtime pin ${action}: ${result.next.releaseTag} (${result.reason}).`
|
|
);
|
|
}
|
|
|
|
if (process.argv[1] === modulePath) {
|
|
main().catch((error) => {
|
|
console.error(error instanceof Error ? error.message : String(error));
|
|
process.exit(1);
|
|
});
|
|
}
|