#!/usr/bin/env node import { writeFileSync } from 'node:fs'; import path from 'node:path'; import { fileURLToPath, pathToFileURL } from 'node:url'; const scriptDir = path.dirname(fileURLToPath(import.meta.url)); const manifestModulePath = path.join(scriptDir, '..', 'packages', 'cli', 'src', 'managed-uv-release.ts'); // Linux always uses the musl-static build: it runs on both glibc and musl // distributions, so the CLI never has to detect libc at runtime. const PLATFORM_ARTIFACTS = { 'darwin-arm64': 'uv-aarch64-apple-darwin.tar.gz', 'darwin-x64': 'uv-x86_64-apple-darwin.tar.gz', 'linux-arm64': 'uv-aarch64-unknown-linux-musl.tar.gz', 'linux-x64': 'uv-x86_64-unknown-linux-musl.tar.gz', 'win32-arm64': 'uv-aarch64-pc-windows-msvc.zip', 'win32-x64': 'uv-x86_64-pc-windows-msvc.zip', }; function parseSha256File(contents, file) { const hash = contents.trim().split(/\s+/)[0]?.replace(/^\*/, ''); if (!/^[a-f0-9]{64}$/.test(hash ?? '')) { throw new Error(`Unexpected sha256 file contents for ${file}: ${contents.slice(0, 120)}`); } return hash; } export function renderUvReleaseModule(version, artifacts) { const keys = Object.keys(PLATFORM_ARTIFACTS); for (const key of keys) { if (!artifacts[key]?.file || !artifacts[key]?.sha256) { throw new Error(`Missing artifact entry for ${key}`); } } const entries = keys .map( (key) => ` '${key}': { file: '${artifacts[key].file}', sha256: '${artifacts[key].sha256}' }, // pragma: allowlist secret`, ) .join('\n'); return `// Generated by scripts/refresh-uv-manifest.mjs. Do not edit by hand. // Regenerate with: node scripts/refresh-uv-manifest.mjs [] export type ManagedUvPlatformKey = ${keys.map((key) => ` | '${key}'`).join('\n')}; export interface ManagedUvArtifact { file: string; sha256: string; } export const MANAGED_UV_VERSION = '${version}'; export const MANAGED_UV_ARTIFACTS: Record = { ${entries} }; `; } export async function refreshUvManifest(options = {}) { const fetchImpl = options.fetch ?? fetch; const writeFile = options.writeFile ?? writeFileSync; const log = options.log ?? console.log; const outputPath = options.outputPath ?? manifestModulePath; let version = options.version; if (!version) { const response = await fetchImpl('https://api.github.com/repos/astral-sh/uv/releases/latest'); if (!response.ok) { throw new Error(`Failed to resolve latest uv release: HTTP ${response.status}`); } version = (await response.json()).tag_name; } const artifacts = {}; for (const [key, file] of Object.entries(PLATFORM_ARTIFACTS)) { const url = `https://github.com/astral-sh/uv/releases/download/${version}/${file}.sha256`; const response = await fetchImpl(url); if (!response.ok) { throw new Error(`Failed to fetch ${url}: HTTP ${response.status}`); } artifacts[key] = { file, sha256: parseSha256File(await response.text(), file) }; } writeFile(outputPath, renderUvReleaseModule(version, artifacts)); log(`Pinned uv ${version} into ${outputPath}`); return { version, artifacts }; } if (import.meta.url === pathToFileURL(process.argv[1] ?? '').href) { refreshUvManifest({ version: process.argv[2] }).catch((error) => { console.error(error); process.exit(1); }); }