/** * Build-time CLI: определяет upstream-релиз Hysteria и печатает env-строки. * * Запускается только на build machine из tools/build/lib/hysteria.sh. * Target-сервер этот код никогда не выполняет. * * bun run tools/resolve-hysteria.ts --channel stable * bun run tools/resolve-hysteria.ts --channel stable --version v2.12.2 */ import { HYSTERIA_UPSTREAM_REPO, selectLatestStableRelease, selectReleaseByVersion, type GithubRelease, type ResolvedHysteriaRelease } from "../src/build/hysteriaRelease"; const RELEASES_PER_PAGE = 100; const MAX_PAGES = 5; function fail(message: string): never { console.error(`[hy2xs-build] ERROR: ${message}`); process.exit(1); } function parseArgs(argv: string[]): { channel: string; version: string } { let channel = "stable"; let version = ""; for (let i = 0; i < argv.length; i += 1) { const arg = argv[i]; if (arg === "--channel") { channel = argv[i + 1] ?? ""; i += 1; continue; } if (arg === "--version") { version = argv[i + 1] ?? ""; i += 1; continue; } fail(`unknown argument: ${arg}`); } if (channel !== "stable") { fail(`unsupported HYSTERIA_CHANNEL for resolution: ${channel} (expected stable)`); } return { channel, version: version.trim() }; } async function fetchReleases(): Promise { const headers: Record = { Accept: "application/vnd.github+json", "X-GitHub-Api-Version": "2022-11-28", "User-Agent": "hy2xs-build" }; const token = (process.env.GITHUB_TOKEN ?? "").trim(); if (token) { headers.Authorization = `Bearer ${token}`; } const releases: GithubRelease[] = []; for (let page = 1; page <= MAX_PAGES; page += 1) { const url = `https://api.github.com/repos/${HYSTERIA_UPSTREAM_REPO}/releases?per_page=${RELEASES_PER_PAGE}&page=${page}`; const response = await fetch(url, { headers }); if (response.status === 403 || response.status === 429) { fail( `GitHub API rate limit reached (HTTP ${response.status}). ` + "Set GITHUB_TOKEN, or build with HYSTERIA_CHANNEL=pinned." ); } if (!response.ok) { fail(`GitHub API request failed: HTTP ${response.status} for ${url}`); } const payload = (await response.json()) as unknown; if (!Array.isArray(payload)) { fail(`unexpected GitHub API payload for ${url}`); } releases.push(...(payload as GithubRelease[])); if (payload.length < RELEASES_PER_PAGE) { break; } } if (releases.length === 0) { fail(`no releases returned for ${HYSTERIA_UPSTREAM_REPO}`); } return releases; } function emit(resolved: ResolvedHysteriaRelease, resolution: string): void { // Значения читаются bash через eval-free парсинг, поэтому печатаем по строке. console.log(`HYSTERIA_VERSION=${resolved.version}`); console.log(`HYSTERIA_TAG=${resolved.tag}`); console.log(`HYSTERIA_ARTIFACT_URL=${resolved.artifactUrl}`); console.log(`HYSTERIA_HASHES_URL=${resolved.hashesUrl}`); console.log(`HYSTERIA_PUBLISHED_AT=${resolved.publishedAt}`); console.log(`HYSTERIA_RESOLUTION=${resolution}`); } async function main(): Promise { const { version } = parseArgs(Bun.argv.slice(2)); const releases = await fetchReleases(); try { if (version) { emit(selectReleaseByVersion(releases, version), "override"); return; } emit(selectLatestStableRelease(releases), "latest-stable"); } catch (error) { fail(error instanceof Error ? error.message : String(error)); } } await main();