From 60cc2b45855283f89ec62f1aba5ee88bf9ec7fc7 Mon Sep 17 00:00:00 2001 From: Kevin Stillhammer Date: Wed, 18 Jun 2025 22:33:20 +0200 Subject: [PATCH] Add input manifest-file (#454) Adds capability to maintain custom uv builds or to override the default sources --- .github/workflows/test.yml | 19 +++ README.md | 36 +++- __tests__/download/custom-manifest.json | 9 + action.yml | 5 +- dist/save-cache/index.js | 10 +- dist/setup/index.js | 208 ++++++++++++++++++++---- dist/update-known-versions/index.js | 87 +++++++--- src/download/download-version.ts | 89 ++++++++-- src/download/version-manifest.ts | 60 +++++-- src/setup-uv.ts | 35 +++- src/update-known-versions.ts | 8 +- src/utils/fetch.ts | 21 +++ src/utils/inputs.ts | 10 ++ src/utils/octokit.ts | 22 +-- 14 files changed, 493 insertions(+), 126 deletions(-) create mode 100644 __tests__/download/custom-manifest.json create mode 100644 src/utils/fetch.ts diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 19ae6e4..fb8505e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -533,6 +533,24 @@ jobs: - run: uv sync working-directory: __tests__/fixtures/old-python-constraint-project + test-custom-manifest-file: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install from custom manifest file + uses: ./ + with: + version: 0.7.12-alpha.1 + manifest-file: "https://raw.githubusercontent.com/astral-sh/setup-uv/${{ github.ref }}/__tests__/download/custom-manifest.json" + - run: uv sync + working-directory: __tests__/fixtures/uv-project + - name: Correct version gets installed + run: | + if [ "$(uv --version)" != "uv 0.7.12-alpha.1" ]; then + echo "Wrong uv version: $(uv --version)" + exit 1 + fi + all-tests-passed: runs-on: ubuntu-latest needs: @@ -565,6 +583,7 @@ jobs: - test-tilde-expansion-cache-dependency-glob - cleanup-tilde-expansion-tests - test-no-python-version + - test-custom-manifest-file if: always() steps: - name: All tests passed diff --git a/README.md b/README.md index 3d3e913..a75541d 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ Set up your GitHub Actions workflow with a specific version of [uv](https://docs - [UV_TOOL_DIR](#uv_tool_dir) - [UV_TOOL_BIN_DIR](#uv_tool_bin_dir) - [Tilde Expansion](#tilde-expansion) + - [Manifest file](#manifest-file) - [How it works](#how-it-works) - [FAQ](#faq) @@ -396,6 +397,39 @@ This action supports expanding the `~` character to the user's home directory fo cache-dependency-glob: "~/my-cache-buster" ``` +### Manifest file + +The `manifest-file` input allows you to specify a JSON manifest that lists available uv versions, +architectures, and their download URLs. By default, this action uses the manifest file contained +in this repository, which is automatically updated with each release of uv. + +The manifest file contains an array of objects, each describing a version, +architecture, platform, and the corresponding download URL. For example: + +```json +[ + { + "version": "0.7.13", + "artifactName": "uv-aarch64-apple-darwin.tar.gz", + "arch": "aarch64", + "platform": "apple-darwin", + "downloadUrl": "https://github.com/astral-sh/uv/releases/download/0.7.13/uv-aarch64-apple-darwin.tar.gz" + }, + ... +] +``` + +You can supply a custom manifest file URL to define additional versions, +architectures, or different download URLs. +This is useful if you maintain your own uv builds or want to override the default sources. + +```yaml +- name: Use a custom manifest file + uses: astral-sh/setup-uv@v6 + with: + manifest-file: "https://example.com/my-custom-manifest.json" +``` + ## How it works This action downloads uv from the uv repo's official @@ -500,7 +534,7 @@ Running `actions/checkout` after `setup-uv` **is not supported**. ### Does `setup-uv` also install my project or its dependencies automatically? -No, `setup-uv` alone wont install any libraries from your `pyproject.toml` or `requirements.txt`, it only sets up `uv`. +No, `setup-uv` alone wont install any libraries from your `pyproject.toml` or `requirements.txt`, it only sets up `uv`. You should run `uv sync` or `uv pip install .` separately, or use `uv run ...` to ensure necessary dependencies are installed. ## Acknowledgements diff --git a/__tests__/download/custom-manifest.json b/__tests__/download/custom-manifest.json new file mode 100644 index 0000000..fcc3709 --- /dev/null +++ b/__tests__/download/custom-manifest.json @@ -0,0 +1,9 @@ +[ + { + "version": "0.7.12-alpha.1", + "artifactName": "uv-x86_64-unknown-linux-gnu.tar.gz", + "arch": "x86_64", + "platform": "unknown-linux-gnu", + "downloadUrl": "https://release.pyx.dev/0.7.12-alpha.1/uv-x86_64-unknown-linux-gnu.tar.gz" + } +] diff --git a/action.yml b/action.yml index 165ca44..0eabbeb 100644 --- a/action.yml +++ b/action.yml @@ -19,7 +19,7 @@ inputs: description: "The checksum of the uv version to install" required: false server-url: - description: "The server url to use when downloading uv" + description: "(Deprecated) The server url to use when downloading uv" required: false default: "https://github.com" github-token: @@ -62,6 +62,9 @@ inputs: tool-bin-dir: description: "Custom path to set UV_TOOL_BIN_DIR to." required: false + manifest-file: + description: "URL to the manifest file containing available versions and download URLs." + required: false outputs: uv-version: description: "The installed uv version. Useful when using latest." diff --git a/dist/save-cache/index.js b/dist/save-cache/index.js index d6656ef..48c6801 100644 --- a/dist/save-cache/index.js +++ b/dist/save-cache/index.js @@ -88998,7 +88998,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.githubToken = exports.serverUrl = exports.toolDir = exports.toolBinDir = exports.ignoreEmptyWorkdir = exports.ignoreNothingToCache = exports.pruneCache = exports.cacheDependencyGlob = exports.cacheLocalPath = exports.cacheSuffix = exports.enableCache = exports.checkSum = exports.workingDirectory = exports.activateEnvironment = exports.pythonVersion = exports.version = void 0; +exports.manifestFile = exports.githubToken = exports.serverUrl = exports.toolDir = exports.toolBinDir = exports.ignoreEmptyWorkdir = exports.ignoreNothingToCache = exports.pruneCache = exports.cacheDependencyGlob = exports.cacheLocalPath = exports.cacheSuffix = exports.enableCache = exports.checkSum = exports.workingDirectory = exports.activateEnvironment = exports.pythonVersion = exports.version = void 0; const core = __importStar(__nccwpck_require__(7484)); const node_path_1 = __importDefault(__nccwpck_require__(6760)); exports.version = core.getInput("version"); @@ -89017,6 +89017,7 @@ exports.toolBinDir = getToolBinDir(); exports.toolDir = getToolDir(); exports.serverUrl = core.getInput("server-url"); exports.githubToken = core.getInput("github-token"); +exports.manifestFile = getManifestFile(); function getEnableCache() { const enableCacheInput = core.getInput("enable-cache"); if (enableCacheInput === "auto") { @@ -89072,6 +89073,13 @@ function expandTilde(input) { } return input; } +function getManifestFile() { + const manifestFileInput = core.getInput("manifest-file"); + if (manifestFileInput !== "") { + return manifestFileInput; + } + return undefined; +} /***/ }), diff --git a/dist/setup/index.js b/dist/setup/index.js index 4b64975..fda244c 100644 --- a/dist/setup/index.js +++ b/dist/setup/index.js @@ -124676,7 +124676,8 @@ var __importStar = (this && this.__importStar) || (function () { })(); Object.defineProperty(exports, "__esModule", ({ value: true })); exports.tryGetFromToolCache = tryGetFromToolCache; -exports.downloadVersion = downloadVersion; +exports.downloadVersionFromGithub = downloadVersionFromGithub; +exports.downloadVersionFromManifest = downloadVersionFromManifest; exports.resolveVersion = resolveVersion; const core = __importStar(__nccwpck_require__(37484)); const tc = __importStar(__nccwpck_require__(33472)); @@ -124686,6 +124687,7 @@ const node_fs_1 = __nccwpck_require__(73024); const constants_1 = __nccwpck_require__(56156); const checksum_1 = __nccwpck_require__(95391); const octokit_1 = __nccwpck_require__(73352); +const version_manifest_1 = __nccwpck_require__(54000); function tryGetFromToolCache(arch, version) { core.debug(`Trying to get uv from tool cache for ${version}...`); const cachedVersions = tc.findAllVersions(constants_1.TOOL_CACHE_NAME, arch); @@ -124697,18 +124699,26 @@ function tryGetFromToolCache(arch, version) { const installedPath = tc.find(constants_1.TOOL_CACHE_NAME, resolvedVersion, arch); return { version: resolvedVersion, installedPath }; } -async function downloadVersion(serverUrl, platform, arch, version, checkSum, githubToken) { - const resolvedVersion = await resolveVersion(version, githubToken); +async function downloadVersionFromGithub(serverUrl, platform, arch, version, checkSum, githubToken) { const artifact = `uv-${arch}-${platform}`; - let extension = ".tar.gz"; - if (platform === "pc-windows-msvc") { - extension = ".zip"; + const extension = getExtension(platform); + const downloadUrl = `${serverUrl}/${constants_1.OWNER}/${constants_1.REPO}/releases/download/${version}/${artifact}${extension}`; + return await downloadVersion(downloadUrl, artifact, platform, arch, version, checkSum, githubToken); +} +async function downloadVersionFromManifest(manifestUrl, platform, arch, version, checkSum, githubToken) { + const downloadUrl = await (0, version_manifest_1.getDownloadUrl)(manifestUrl, version, arch, platform); + if (!downloadUrl) { + core.warning(`manifest-file does not contain version ${version}, arch ${arch}, platform ${platform}. Falling back to GitHub releases.`); + return await downloadVersionFromGithub("https://github.com", platform, arch, version, checkSum, githubToken); } - const downloadUrl = `${serverUrl}/${constants_1.OWNER}/${constants_1.REPO}/releases/download/${resolvedVersion}/${artifact}${extension}`; + return await downloadVersion(downloadUrl, `uv-${arch}-${platform}`, platform, arch, version, checkSum, githubToken); +} +async function downloadVersion(downloadUrl, artifactName, platform, arch, version, checkSum, githubToken) { core.info(`Downloading uv from "${downloadUrl}" ...`); const downloadPath = await tc.downloadTool(downloadUrl, undefined, githubToken); - await (0, checksum_1.validateChecksum)(checkSum, downloadPath, arch, platform, resolvedVersion); + await (0, checksum_1.validateChecksum)(checkSum, downloadPath, arch, platform, version); let uvDir; + const extension = getExtension(platform); if (platform === "pc-windows-msvc") { const fullPathWithExtension = `${downloadPath}${extension}`; await node_fs_1.promises.copyFile(downloadPath, fullPathWithExtension); @@ -124717,10 +124727,13 @@ async function downloadVersion(serverUrl, platform, arch, version, checkSum, git } else { const extractedDir = await tc.extractTar(downloadPath); - uvDir = path.join(extractedDir, artifact); + uvDir = path.join(extractedDir, artifactName); } - const cachedToolDir = await tc.cacheDir(uvDir, constants_1.TOOL_CACHE_NAME, resolvedVersion, arch); - return { version: resolvedVersion, cachedToolDir }; + const cachedToolDir = await tc.cacheDir(uvDir, constants_1.TOOL_CACHE_NAME, version, arch); + return { version: version, cachedToolDir }; +} +function getExtension(platform) { + return platform === "pc-windows-msvc" ? ".zip" : ".tar.gz"; } async function resolveVersion(versionInput, githubToken) { core.debug(`Resolving version: ${versionInput}`); @@ -124807,6 +124820,108 @@ function maxSatisfying(versions, version) { } +/***/ }), + +/***/ 54000: +/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { + +"use strict"; + +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || (function () { + var ownKeys = function(o) { + ownKeys = Object.getOwnPropertyNames || function (o) { + var ar = []; + for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; + return ar; + }; + return ownKeys(o); + }; + return function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); + __setModuleDefault(result, mod); + return result; + }; +})(); +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.getLatestKnownVersion = getLatestKnownVersion; +exports.getDownloadUrl = getDownloadUrl; +exports.updateVersionManifest = updateVersionManifest; +const node_fs_1 = __nccwpck_require__(73024); +const core = __importStar(__nccwpck_require__(37484)); +const semver = __importStar(__nccwpck_require__(39318)); +const fetch_1 = __nccwpck_require__(3385); +async function getLatestKnownVersion(manifestUrl) { + const manifestEntries = await getManifestEntries(manifestUrl); + return manifestEntries.reduce((a, b) => semver.gt(a.version, b.version) ? a : b).version; +} +async function getDownloadUrl(manifestUrl, version, arch, platform) { + const manifestEntries = await getManifestEntries(manifestUrl); + const entry = manifestEntries.find((entry) => entry.version === version && + entry.arch === arch && + entry.platform === platform); + return entry ? entry.downloadUrl : undefined; +} +async function getManifestEntries(manifestUrl) { + let data; + if (manifestUrl !== undefined) { + core.info(`Fetching manifest-file from: ${manifestUrl}`); + const response = await (0, fetch_1.fetch)(manifestUrl, {}); + if (!response.ok) { + throw new Error(`Failed to fetch manifest-file: ${response.status} ${response.statusText}`); + } + data = await response.text(); + } + else { + core.info("manifest-file not provided, reading from local file."); + const fileContent = await node_fs_1.promises.readFile("version-manifest.json"); + data = fileContent.toString(); + } + return JSON.parse(data); +} +async function updateVersionManifest(manifestUrl, downloadUrls) { + const manifest = []; + for (const downloadUrl of downloadUrls) { + const urlParts = downloadUrl.split("/"); + const version = urlParts[urlParts.length - 2]; + const artifactName = urlParts[urlParts.length - 1]; + if (!artifactName.startsWith("uv-")) { + continue; + } + if (artifactName.startsWith("uv-installer")) { + continue; + } + const artifactParts = artifactName.split(".")[0].split("-"); + manifest.push({ + version: version, + artifactName: artifactName, + arch: artifactParts[1], + platform: artifactName.split(`uv-${artifactParts[1]}-`)[1].split(".")[0], + downloadUrl: downloadUrl, + }); + } + core.debug(`Updating manifest-file: ${JSON.stringify(manifest)}`); + await node_fs_1.promises.writeFile(manifestUrl, JSON.stringify(manifest)); +} + + /***/ }), /***/ 99660: @@ -124997,7 +125112,14 @@ async function setupUv(platform, arch, checkSum, githubToken) { version: toolCacheResult.version, }; } - const downloadVersionResult = await (0, download_version_1.downloadVersion)(inputs_1.serverUrl, platform, arch, resolvedVersion, checkSum, githubToken); + let downloadVersionResult; + if (inputs_1.serverUrl !== "https://github.com") { + core.warning("The input server-url is deprecated. Please use manifest-file instead."); + downloadVersionResult = await (0, download_version_1.downloadVersionFromGithub)(inputs_1.serverUrl, platform, arch, resolvedVersion, checkSum, githubToken); + } + else { + downloadVersionResult = await (0, download_version_1.downloadVersionFromManifest)(inputs_1.manifestFile, platform, arch, resolvedVersion, checkSum, githubToken); + } return { uvDir: downloadVersionResult.cachedToolDir, version: downloadVersionResult.version, @@ -125174,6 +125296,35 @@ exports.OWNER = "astral-sh"; exports.TOOL_CACHE_NAME = "uv"; +/***/ }), + +/***/ 3385: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.fetch = void 0; +exports.getProxyAgent = getProxyAgent; +const undici_1 = __nccwpck_require__(46752); +function getProxyAgent() { + const httpProxy = process.env.HTTP_PROXY || process.env.http_proxy; + if (httpProxy) { + return new undici_1.ProxyAgent(httpProxy); + } + const httpsProxy = process.env.HTTPS_PROXY || process.env.https_proxy; + if (httpsProxy) { + return new undici_1.ProxyAgent(httpsProxy); + } + return undefined; +} +const fetch = async (url, opts) => await (0, undici_1.fetch)(url, { + dispatcher: getProxyAgent(), + ...opts, +}); +exports.fetch = fetch; + + /***/ }), /***/ 9612: @@ -125218,7 +125369,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.githubToken = exports.serverUrl = exports.toolDir = exports.toolBinDir = exports.ignoreEmptyWorkdir = exports.ignoreNothingToCache = exports.pruneCache = exports.cacheDependencyGlob = exports.cacheLocalPath = exports.cacheSuffix = exports.enableCache = exports.checkSum = exports.workingDirectory = exports.activateEnvironment = exports.pythonVersion = exports.version = void 0; +exports.manifestFile = exports.githubToken = exports.serverUrl = exports.toolDir = exports.toolBinDir = exports.ignoreEmptyWorkdir = exports.ignoreNothingToCache = exports.pruneCache = exports.cacheDependencyGlob = exports.cacheLocalPath = exports.cacheSuffix = exports.enableCache = exports.checkSum = exports.workingDirectory = exports.activateEnvironment = exports.pythonVersion = exports.version = void 0; const core = __importStar(__nccwpck_require__(37484)); const node_path_1 = __importDefault(__nccwpck_require__(76760)); exports.version = core.getInput("version"); @@ -125237,6 +125388,7 @@ exports.toolBinDir = getToolBinDir(); exports.toolDir = getToolDir(); exports.serverUrl = core.getInput("server-url"); exports.githubToken = core.getInput("github-token"); +exports.manifestFile = getManifestFile(); function getEnableCache() { const enableCacheInput = core.getInput("enable-cache"); if (enableCacheInput === "auto") { @@ -125292,6 +125444,13 @@ function expandTilde(input) { } return input; } +function getManifestFile() { + const manifestFileInput = core.getInput("manifest-file"); + if (manifestFileInput !== "") { + return manifestFileInput; + } + return undefined; +} /***/ }), @@ -125302,38 +125461,21 @@ function expandTilde(input) { "use strict"; Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.Octokit = exports.customFetch = void 0; -exports.getProxyAgent = getProxyAgent; +exports.Octokit = void 0; const core_1 = __nccwpck_require__(60767); const plugin_paginate_rest_1 = __nccwpck_require__(93779); const plugin_rest_endpoint_methods_1 = __nccwpck_require__(49210); -const undici_1 = __nccwpck_require__(46752); +const fetch_1 = __nccwpck_require__(3385); const DEFAULTS = { baseUrl: "https://api.github.com", userAgent: "setup-uv", }; -function getProxyAgent() { - const httpProxy = process.env.HTTP_PROXY || process.env.http_prox; - if (httpProxy) { - return new undici_1.ProxyAgent(httpProxy); - } - const httpsProxy = process.env.HTTPS_PROXY || process.env.https_proxy; - if (httpsProxy) { - return new undici_1.ProxyAgent(httpsProxy); - } - return undefined; -} -const customFetch = async (url, opts) => await (0, undici_1.fetch)(url, { - dispatcher: getProxyAgent(), - ...opts, -}); -exports.customFetch = customFetch; exports.Octokit = core_1.Octokit.plugin(plugin_paginate_rest_1.paginateRest, plugin_rest_endpoint_methods_1.legacyRestEndpointMethods).defaults(function buildDefaults(options) { return { ...DEFAULTS, ...options, request: { - fetch: exports.customFetch, + fetch: fetch_1.fetch, ...options.request, }, }; diff --git a/dist/update-known-versions/index.js b/dist/update-known-versions/index.js index 59f96aa..5e23df4 100644 --- a/dist/update-known-versions/index.js +++ b/dist/update-known-versions/index.js @@ -62426,17 +62426,42 @@ var __importStar = (this && this.__importStar) || (function () { })(); Object.defineProperty(exports, "__esModule", ({ value: true })); exports.getLatestKnownVersion = getLatestKnownVersion; +exports.getDownloadUrl = getDownloadUrl; exports.updateVersionManifest = updateVersionManifest; const node_fs_1 = __nccwpck_require__(3024); const core = __importStar(__nccwpck_require__(7484)); const semver = __importStar(__nccwpck_require__(9318)); -async function getLatestKnownVersion(versionManifestFile) { - const data = await node_fs_1.promises.readFile(versionManifestFile); - const versionManifestEntries = JSON.parse(data.toString()); - return versionManifestEntries.reduce((a, b) => semver.gt(a.version, b.version) ? a : b).version; +const fetch_1 = __nccwpck_require__(3385); +async function getLatestKnownVersion(manifestUrl) { + const manifestEntries = await getManifestEntries(manifestUrl); + return manifestEntries.reduce((a, b) => semver.gt(a.version, b.version) ? a : b).version; } -async function updateVersionManifest(versionManifestFile, downloadUrls) { - const versionManifest = []; +async function getDownloadUrl(manifestUrl, version, arch, platform) { + const manifestEntries = await getManifestEntries(manifestUrl); + const entry = manifestEntries.find((entry) => entry.version === version && + entry.arch === arch && + entry.platform === platform); + return entry ? entry.downloadUrl : undefined; +} +async function getManifestEntries(manifestUrl) { + let data; + if (manifestUrl !== undefined) { + core.info(`Fetching manifest-file from: ${manifestUrl}`); + const response = await (0, fetch_1.fetch)(manifestUrl, {}); + if (!response.ok) { + throw new Error(`Failed to fetch manifest-file: ${response.status} ${response.statusText}`); + } + data = await response.text(); + } + else { + core.info("manifest-file not provided, reading from local file."); + const fileContent = await node_fs_1.promises.readFile("version-manifest.json"); + data = fileContent.toString(); + } + return JSON.parse(data); +} +async function updateVersionManifest(manifestUrl, downloadUrls) { + const manifest = []; for (const downloadUrl of downloadUrls) { const urlParts = downloadUrl.split("/"); const version = urlParts[urlParts.length - 2]; @@ -62448,7 +62473,7 @@ async function updateVersionManifest(versionManifestFile, downloadUrls) { continue; } const artifactParts = artifactName.split(".")[0].split("-"); - versionManifest.push({ + manifest.push({ version: version, artifactName: artifactName, arch: artifactParts[1], @@ -62456,8 +62481,8 @@ async function updateVersionManifest(versionManifestFile, downloadUrls) { downloadUrl: downloadUrl, }); } - core.debug(`Updating version manifest: ${JSON.stringify(versionManifest)}`); - await node_fs_1.promises.writeFile(versionManifestFile, JSON.stringify(versionManifest)); + core.debug(`Updating manifest-file: ${JSON.stringify(manifest)}`); + await node_fs_1.promises.writeFile(manifestUrl, JSON.stringify(manifest)); } @@ -62510,7 +62535,7 @@ const update_known_checksums_1 = __nccwpck_require__(6182); const version_manifest_1 = __nccwpck_require__(4000); async function run() { const checksumFilePath = process.argv.slice(2)[0]; - const versionsManifestFilePath = process.argv.slice(2)[1]; + const versionsManifestFile = process.argv.slice(2)[1]; const githubToken = process.argv.slice(2)[2]; const octokit = new octokit_1.Octokit({ auth: githubToken, @@ -62519,7 +62544,7 @@ async function run() { owner: constants_1.OWNER, repo: constants_1.REPO, }); - const latestKnownVersion = await (0, version_manifest_1.getLatestKnownVersion)(versionsManifestFilePath); + const latestKnownVersion = await (0, version_manifest_1.getLatestKnownVersion)(undefined); if (semver.lte(latestRelease.tag_name, latestKnownVersion)) { core.info(`Latest release (${latestRelease.tag_name}) is not newer than the latest known version (${latestKnownVersion}). Skipping update.`); return; @@ -62535,7 +62560,7 @@ async function run() { const artifactDownloadUrls = releases.flatMap((release) => release.assets .filter((asset) => !asset.name.endsWith(".sha256")) .map((asset) => asset.browser_download_url)); - await (0, version_manifest_1.updateVersionManifest)(versionsManifestFilePath, artifactDownloadUrls); + await (0, version_manifest_1.updateVersionManifest)(versionsManifestFile, artifactDownloadUrls); core.setOutput("latest-version", latestRelease.tag_name); } run(); @@ -62557,24 +62582,17 @@ exports.TOOL_CACHE_NAME = "uv"; /***/ }), -/***/ 3352: +/***/ 3385: /***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { "use strict"; Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.Octokit = exports.customFetch = void 0; +exports.fetch = void 0; exports.getProxyAgent = getProxyAgent; -const core_1 = __nccwpck_require__(767); -const plugin_paginate_rest_1 = __nccwpck_require__(3779); -const plugin_rest_endpoint_methods_1 = __nccwpck_require__(9210); const undici_1 = __nccwpck_require__(6752); -const DEFAULTS = { - baseUrl: "https://api.github.com", - userAgent: "setup-uv", -}; function getProxyAgent() { - const httpProxy = process.env.HTTP_PROXY || process.env.http_prox; + const httpProxy = process.env.HTTP_PROXY || process.env.http_proxy; if (httpProxy) { return new undici_1.ProxyAgent(httpProxy); } @@ -62584,17 +62602,36 @@ function getProxyAgent() { } return undefined; } -const customFetch = async (url, opts) => await (0, undici_1.fetch)(url, { +const fetch = async (url, opts) => await (0, undici_1.fetch)(url, { dispatcher: getProxyAgent(), ...opts, }); -exports.customFetch = customFetch; +exports.fetch = fetch; + + +/***/ }), + +/***/ 3352: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.Octokit = void 0; +const core_1 = __nccwpck_require__(767); +const plugin_paginate_rest_1 = __nccwpck_require__(3779); +const plugin_rest_endpoint_methods_1 = __nccwpck_require__(9210); +const fetch_1 = __nccwpck_require__(3385); +const DEFAULTS = { + baseUrl: "https://api.github.com", + userAgent: "setup-uv", +}; exports.Octokit = core_1.Octokit.plugin(plugin_paginate_rest_1.paginateRest, plugin_rest_endpoint_methods_1.legacyRestEndpointMethods).defaults(function buildDefaults(options) { return { ...DEFAULTS, ...options, request: { - fetch: exports.customFetch, + fetch: fetch_1.fetch, ...options.request, }, }; diff --git a/src/download/download-version.ts b/src/download/download-version.ts index 894560f..e06efae 100644 --- a/src/download/download-version.ts +++ b/src/download/download-version.ts @@ -7,6 +7,7 @@ import { OWNER, REPO, TOOL_CACHE_NAME } from "../utils/constants"; import type { Architecture, Platform } from "../utils/platforms"; import { validateChecksum } from "./checksum/checksum"; import { Octokit } from "../utils/octokit"; +import { getDownloadUrl } from "./version-manifest"; export function tryGetFromToolCache( arch: Architecture, @@ -23,7 +24,7 @@ export function tryGetFromToolCache( return { version: resolvedVersion, installedPath }; } -export async function downloadVersion( +export async function downloadVersionFromGithub( serverUrl: string, platform: Platform, arch: Architecture, @@ -31,29 +32,77 @@ export async function downloadVersion( checkSum: string | undefined, githubToken: string, ): Promise<{ version: string; cachedToolDir: string }> { - const resolvedVersion = await resolveVersion(version, githubToken); const artifact = `uv-${arch}-${platform}`; - let extension = ".tar.gz"; - if (platform === "pc-windows-msvc") { - extension = ".zip"; - } - const downloadUrl = `${serverUrl}/${OWNER}/${REPO}/releases/download/${resolvedVersion}/${artifact}${extension}`; - core.info(`Downloading uv from "${downloadUrl}" ...`); + const extension = getExtension(platform); + const downloadUrl = `${serverUrl}/${OWNER}/${REPO}/releases/download/${version}/${artifact}${extension}`; + return await downloadVersion( + downloadUrl, + artifact, + platform, + arch, + version, + checkSum, + githubToken, + ); +} +export async function downloadVersionFromManifest( + manifestUrl: string | undefined, + platform: Platform, + arch: Architecture, + version: string, + checkSum: string | undefined, + githubToken: string, +): Promise<{ version: string; cachedToolDir: string }> { + const downloadUrl = await getDownloadUrl( + manifestUrl, + version, + arch, + platform, + ); + if (!downloadUrl) { + core.warning( + `manifest-file does not contain version ${version}, arch ${arch}, platform ${platform}. Falling back to GitHub releases.`, + ); + return await downloadVersionFromGithub( + "https://github.com", + platform, + arch, + version, + checkSum, + githubToken, + ); + } + return await downloadVersion( + downloadUrl, + `uv-${arch}-${platform}`, + platform, + arch, + version, + checkSum, + githubToken, + ); +} + +async function downloadVersion( + downloadUrl: string, + artifactName: string, + platform: Platform, + arch: Architecture, + version: string, + checkSum: string | undefined, + githubToken: string, +): Promise<{ version: string; cachedToolDir: string }> { + core.info(`Downloading uv from "${downloadUrl}" ...`); const downloadPath = await tc.downloadTool( downloadUrl, undefined, githubToken, ); - await validateChecksum( - checkSum, - downloadPath, - arch, - platform, - resolvedVersion, - ); + await validateChecksum(checkSum, downloadPath, arch, platform, version); let uvDir: string; + const extension = getExtension(platform); if (platform === "pc-windows-msvc") { const fullPathWithExtension = `${downloadPath}${extension}`; await fs.copyFile(downloadPath, fullPathWithExtension); @@ -61,15 +110,19 @@ export async function downloadVersion( // On windows extracting the zip does not create an intermediate directory } else { const extractedDir = await tc.extractTar(downloadPath); - uvDir = path.join(extractedDir, artifact); + uvDir = path.join(extractedDir, artifactName); } const cachedToolDir = await tc.cacheDir( uvDir, TOOL_CACHE_NAME, - resolvedVersion, + version, arch, ); - return { version: resolvedVersion, cachedToolDir }; + return { version: version, cachedToolDir }; +} + +function getExtension(platform: Platform): string { + return platform === "pc-windows-msvc" ? ".zip" : ".tar.gz"; } export async function resolveVersion( diff --git a/src/download/version-manifest.ts b/src/download/version-manifest.ts index 90c2d49..60c3026 100644 --- a/src/download/version-manifest.ts +++ b/src/download/version-manifest.ts @@ -1,8 +1,9 @@ import { promises as fs } from "node:fs"; import * as core from "@actions/core"; import * as semver from "semver"; +import { fetch } from "../utils/fetch"; -interface VersionManifestEntry { +interface ManifestEntry { version: string; artifactName: string; arch: string; @@ -11,22 +12,57 @@ interface VersionManifestEntry { } export async function getLatestKnownVersion( - versionManifestFile: string, + manifestUrl: string | undefined, ): Promise { - const data = await fs.readFile(versionManifestFile); - const versionManifestEntries: VersionManifestEntry[] = JSON.parse( - data.toString(), - ); - return versionManifestEntries.reduce((a, b) => + const manifestEntries = await getManifestEntries(manifestUrl); + return manifestEntries.reduce((a, b) => semver.gt(a.version, b.version) ? a : b, ).version; } +export async function getDownloadUrl( + manifestUrl: string | undefined, + version: string, + arch: string, + platform: string, +): Promise { + const manifestEntries = await getManifestEntries(manifestUrl); + const entry = manifestEntries.find( + (entry) => + entry.version === version && + entry.arch === arch && + entry.platform === platform, + ); + return entry ? entry.downloadUrl : undefined; +} + +async function getManifestEntries( + manifestUrl: string | undefined, +): Promise { + let data: string; + if (manifestUrl !== undefined) { + core.info(`Fetching manifest-file from: ${manifestUrl}`); + const response = await fetch(manifestUrl, {}); + if (!response.ok) { + throw new Error( + `Failed to fetch manifest-file: ${response.status} ${response.statusText}`, + ); + } + data = await response.text(); + } else { + core.info("manifest-file not provided, reading from local file."); + const fileContent = await fs.readFile("version-manifest.json"); + data = fileContent.toString(); + } + + return JSON.parse(data); +} + export async function updateVersionManifest( - versionManifestFile: string, + manifestUrl: string, downloadUrls: string[], ): Promise { - const versionManifest: VersionManifestEntry[] = []; + const manifest: ManifestEntry[] = []; for (const downloadUrl of downloadUrls) { const urlParts = downloadUrl.split("/"); @@ -39,7 +75,7 @@ export async function updateVersionManifest( continue; } const artifactParts = artifactName.split(".")[0].split("-"); - versionManifest.push({ + manifest.push({ version: version, artifactName: artifactName, arch: artifactParts[1], @@ -47,6 +83,6 @@ export async function updateVersionManifest( downloadUrl: downloadUrl, }); } - core.debug(`Updating version manifest: ${JSON.stringify(versionManifest)}`); - await fs.writeFile(versionManifestFile, JSON.stringify(versionManifest)); + core.debug(`Updating manifest-file: ${JSON.stringify(manifest)}`); + await fs.writeFile(manifestUrl, JSON.stringify(manifest)); } diff --git a/src/setup-uv.ts b/src/setup-uv.ts index 8fac7cb..de9129d 100644 --- a/src/setup-uv.ts +++ b/src/setup-uv.ts @@ -1,9 +1,10 @@ import * as core from "@actions/core"; import * as path from "node:path"; import { - downloadVersion, tryGetFromToolCache, resolveVersion, + downloadVersionFromGithub, + downloadVersionFromManifest, } from "./download/download-version"; import { restoreCache } from "./cache/restore-cache"; @@ -26,6 +27,7 @@ import { version as versionInput, workingDirectory, serverUrl, + manifestFile, } from "./utils/inputs"; import * as exec from "@actions/exec"; import fs from "node:fs"; @@ -95,14 +97,29 @@ async function setupUv( }; } - const downloadVersionResult = await downloadVersion( - serverUrl, - platform, - arch, - resolvedVersion, - checkSum, - githubToken, - ); + let downloadVersionResult: { version: string; cachedToolDir: string }; + if (serverUrl !== "https://github.com") { + core.warning( + "The input server-url is deprecated. Please use manifest-file instead.", + ); + downloadVersionResult = await downloadVersionFromGithub( + serverUrl, + platform, + arch, + resolvedVersion, + checkSum, + githubToken, + ); + } else { + downloadVersionResult = await downloadVersionFromManifest( + manifestFile, + platform, + arch, + resolvedVersion, + checkSum, + githubToken, + ); + } return { uvDir: downloadVersionResult.cachedToolDir, diff --git a/src/update-known-versions.ts b/src/update-known-versions.ts index 7cf4291..e2e7367 100644 --- a/src/update-known-versions.ts +++ b/src/update-known-versions.ts @@ -12,7 +12,7 @@ import { async function run(): Promise { const checksumFilePath = process.argv.slice(2)[0]; - const versionsManifestFilePath = process.argv.slice(2)[1]; + const versionsManifestFile = process.argv.slice(2)[1]; const githubToken = process.argv.slice(2)[2]; const octokit = new Octokit({ @@ -24,9 +24,7 @@ async function run(): Promise { repo: REPO, }); - const latestKnownVersion = await getLatestKnownVersion( - versionsManifestFilePath, - ); + const latestKnownVersion = await getLatestKnownVersion(undefined); if (semver.lte(latestRelease.tag_name, latestKnownVersion)) { core.info( @@ -52,7 +50,7 @@ async function run(): Promise { .map((asset) => asset.browser_download_url), ); - await updateVersionManifest(versionsManifestFilePath, artifactDownloadUrls); + await updateVersionManifest(versionsManifestFile, artifactDownloadUrls); core.setOutput("latest-version", latestRelease.tag_name); } diff --git a/src/utils/fetch.ts b/src/utils/fetch.ts new file mode 100644 index 0000000..8ba73fc --- /dev/null +++ b/src/utils/fetch.ts @@ -0,0 +1,21 @@ +import { fetch as undiciFetch, ProxyAgent, type RequestInit } from "undici"; + +export function getProxyAgent() { + const httpProxy = process.env.HTTP_PROXY || process.env.http_proxy; + if (httpProxy) { + return new ProxyAgent(httpProxy); + } + + const httpsProxy = process.env.HTTPS_PROXY || process.env.https_proxy; + if (httpsProxy) { + return new ProxyAgent(httpsProxy); + } + + return undefined; +} + +export const fetch = async (url: string, opts: RequestInit) => + await undiciFetch(url, { + dispatcher: getProxyAgent(), + ...opts, + }); diff --git a/src/utils/inputs.ts b/src/utils/inputs.ts index 6c0da1f..8238ed9 100644 --- a/src/utils/inputs.ts +++ b/src/utils/inputs.ts @@ -1,5 +1,6 @@ import * as core from "@actions/core"; import path from "node:path"; +import { getManifestFromRepo } from "@actions/tool-cache"; export const version = core.getInput("version"); export const pythonVersion = core.getInput("python-version"); @@ -19,6 +20,7 @@ export const toolBinDir = getToolBinDir(); export const toolDir = getToolDir(); export const serverUrl = core.getInput("server-url"); export const githubToken = core.getInput("github-token"); +export const manifestFile = getManifestFile(); function getEnableCache(): boolean { const enableCacheInput = core.getInput("enable-cache"); @@ -85,3 +87,11 @@ function expandTilde(input: string): string { } return input; } + +function getManifestFile(): string | undefined { + const manifestFileInput = core.getInput("manifest-file"); + if (manifestFileInput !== "") { + return manifestFileInput; + } + return undefined; +} diff --git a/src/utils/octokit.ts b/src/utils/octokit.ts index e96ac05..57d2e13 100644 --- a/src/utils/octokit.ts +++ b/src/utils/octokit.ts @@ -8,7 +8,7 @@ import { type PaginateInterface, } from "@octokit/plugin-paginate-rest"; import { legacyRestEndpointMethods } from "@octokit/plugin-rest-endpoint-methods"; -import { fetch as undiciFetch, ProxyAgent, type RequestInit } from "undici"; +import { fetch as customFetch } from "./fetch"; export type { RestEndpointMethodTypes } from "@octokit/plugin-rest-endpoint-methods"; @@ -17,26 +17,6 @@ const DEFAULTS = { userAgent: "setup-uv", }; -export function getProxyAgent() { - const httpProxy = process.env.HTTP_PROXY || process.env.http_prox; - if (httpProxy) { - return new ProxyAgent(httpProxy); - } - - const httpsProxy = process.env.HTTPS_PROXY || process.env.https_proxy; - if (httpsProxy) { - return new ProxyAgent(httpsProxy); - } - - return undefined; -} - -export const customFetch = async (url: string, opts: RequestInit) => - await undiciFetch(url, { - dispatcher: getProxyAgent(), - ...opts, - }); - export const Octokit: typeof Core & Constructor< {