diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index cb54246..3833061 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -155,6 +155,22 @@ jobs: exit 1 fi + test-version-file-version: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install from requirements file + id: setup-uv + uses: ./ + with: + version-file: "__tests__/fixtures/uv-in-requirements-txt-project/requirements.txt" + - name: Correct version gets installed + run: | + if [ "$(uv --version)" != "uv 0.6.17" ]; then + echo "Wrong uv version: $(uv --version)" + exit 1 + fi + test-checksum: runs-on: ${{ matrix.inputs.os }} strategy: @@ -561,6 +577,7 @@ jobs: - test-pyproject-file-version - test-malformed-pyproject-file-fallback - test-uv-file-version + - test-version-file-version - test-checksum - test-with-explicit-token - test-uvx diff --git a/README.md b/README.md index f67885a..a944246 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ Set up your GitHub Actions workflow with a specific version of [uv](https://docs - [Install the latest version](#install-the-latest-version) - [Install a specific version](#install-a-specific-version) - [Install a version by supplying a semver range or pep440 specifier](#install-a-version-by-supplying-a-semver-range-or-pep440-specifier) + - [Install a version defined in a requirements or config file](#install-a-version-defined-in-a-requirements-or-config-file) - [Python version](#python-version) - [Activate environment](#activate-environment) - [Working directory](#working-directory) @@ -92,6 +93,19 @@ to install the latest version that satisfies the range. version: ">=0.4.25,<0.5" ``` +### Install a version defined in a requirements or config file + +You can use the `version-file` input to specify a file that contains the version of uv to install. +This can either be a `pyproject.toml` or `uv.toml` file which defines a `required-version` or +uv defined as a dependency in `pyproject.toml` or `requirements.txt`. + +```yaml +- name: Install uv based on the version defined in pyproject.toml + uses: astral-sh/setup-uv@v6 + with: + version-file: "pyproject.toml" +``` + ### Python version You can use the input `python-version` to set the environment variable `UV_PYTHON` for the rest of your workflow diff --git a/__tests__/fixtures/uv-in-requirements-txt-project/hello_world.py b/__tests__/fixtures/uv-in-requirements-txt-project/hello_world.py new file mode 100644 index 0000000..44159b3 --- /dev/null +++ b/__tests__/fixtures/uv-in-requirements-txt-project/hello_world.py @@ -0,0 +1 @@ +print("Hello world") diff --git a/__tests__/fixtures/uv-in-requirements-txt-project/requirements.txt b/__tests__/fixtures/uv-in-requirements-txt-project/requirements.txt new file mode 100644 index 0000000..7815491 --- /dev/null +++ b/__tests__/fixtures/uv-in-requirements-txt-project/requirements.txt @@ -0,0 +1 @@ +uv==0.6.17 diff --git a/action.yml b/action.yml index 0eabbeb..cf4b6d1 100644 --- a/action.yml +++ b/action.yml @@ -6,6 +6,9 @@ inputs: version: description: "The version of uv to install e.g., `0.5.0` Defaults to the version in pyproject.toml or 'latest'." default: "" + version-file: + description: "Path to a file containing the version of uv to install. Defaults to searching for uv.toml and if not found pyproject.toml." + default: "" python-version: description: "The version of Python to set UV_PYTHON to" required: false diff --git a/dist/save-cache/index.js b/dist/save-cache/index.js index 48c6801..2dd3804 100644 --- a/dist/save-cache/index.js +++ b/dist/save-cache/index.js @@ -88998,10 +88998,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", ({ value: true })); -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; +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.versionFile = exports.version = void 0; const core = __importStar(__nccwpck_require__(7484)); const node_path_1 = __importDefault(__nccwpck_require__(6760)); exports.version = core.getInput("version"); +exports.versionFile = core.getInput("version-file"); exports.pythonVersion = core.getInput("python-version"); exports.activateEnvironment = core.getBooleanInput("activate-environment"); exports.workingDirectory = core.getInput("working-directory"); diff --git a/dist/setup/index.js b/dist/setup/index.js index 3bf7cee..c0e32f9 100644 --- a/dist/setup/index.js +++ b/dist/setup/index.js @@ -125217,7 +125217,7 @@ const platforms_1 = __nccwpck_require__(98361); const inputs_1 = __nccwpck_require__(9612); const exec = __importStar(__nccwpck_require__(95236)); const node_fs_1 = __importDefault(__nccwpck_require__(73024)); -const config_file_1 = __nccwpck_require__(27846); +const resolve_1 = __nccwpck_require__(36772); async function run() { detectEmptyWorkdir(); const platform = await (0, platforms_1.getPlatform)(); @@ -125285,8 +125285,15 @@ async function determineVersion(manifestFile) { if (inputs_1.version !== "") { return await (0, download_version_1.resolveVersion)(inputs_1.version, manifestFile, inputs_1.githubToken); } - const versionFromUvToml = (0, config_file_1.getUvVersionFromConfigFile)(`${inputs_1.workingDirectory}${path.sep}uv.toml`); - const versionFromPyproject = (0, config_file_1.getUvVersionFromConfigFile)(`${inputs_1.workingDirectory}${path.sep}pyproject.toml`); + if (inputs_1.versionFile !== "") { + const versionFromFile = (0, resolve_1.getUvVersionFromFile)(inputs_1.versionFile); + if (versionFromFile === undefined) { + throw new Error(`Could not determine uv version from file: ${inputs_1.versionFile}`); + } + return await (0, download_version_1.resolveVersion)(versionFromFile, manifestFile, inputs_1.githubToken); + } + const versionFromUvToml = (0, resolve_1.getUvVersionFromFile)(`${inputs_1.workingDirectory}${path.sep}uv.toml`); + const versionFromPyproject = (0, resolve_1.getUvVersionFromFile)(`${inputs_1.workingDirectory}${path.sep}pyproject.toml`); if (versionFromUvToml === undefined && versionFromPyproject === undefined) { core.info("Could not determine uv version from uv.toml or pyproject.toml. Falling back to latest."); } @@ -125356,88 +125363,6 @@ function addMatchers() { run(); -/***/ }), - -/***/ 27846: -/***/ (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; - }; -})(); -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.getUvVersionFromConfigFile = getUvVersionFromConfigFile; -const node_fs_1 = __importDefault(__nccwpck_require__(73024)); -const core = __importStar(__nccwpck_require__(37484)); -const toml = __importStar(__nccwpck_require__(27106)); -function getUvVersionFromConfigFile(filePath) { - core.info(`Trying to find required-version for uv in: ${filePath}`); - if (!node_fs_1.default.existsSync(filePath)) { - core.info(`Could not find file: ${filePath}`); - return undefined; - } - let requiredVersion; - try { - requiredVersion = getRequiredVersion(filePath); - } - catch (err) { - const message = err.message; - core.warning(`Error while parsing ${filePath}: ${message}`); - return undefined; - } - if (requiredVersion?.startsWith("==")) { - requiredVersion = requiredVersion.slice(2); - } - if (requiredVersion !== undefined) { - core.info(`Found required-version for uv in ${filePath}: ${requiredVersion}`); - } - return requiredVersion; -} -function getRequiredVersion(filePath) { - const fileContent = node_fs_1.default.readFileSync(filePath, "utf-8"); - if (filePath.endsWith("pyproject.toml")) { - const tomlContent = toml.parse(fileContent); - return tomlContent?.tool?.uv?.["required-version"]; - } - const tomlContent = toml.parse(fileContent); - return tomlContent["required-version"]; -} - - /***/ }), /***/ 56156: @@ -125525,10 +125450,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", ({ value: true })); -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; +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.versionFile = exports.version = void 0; const core = __importStar(__nccwpck_require__(37484)); const node_path_1 = __importDefault(__nccwpck_require__(76760)); exports.version = core.getInput("version"); +exports.versionFile = core.getInput("version-file"); exports.pythonVersion = core.getInput("python-version"); exports.activateEnvironment = core.getBooleanInput("activate-environment"); exports.workingDirectory = core.getInput("working-directory"); @@ -125740,6 +125666,216 @@ async function isMuslOs() { } +/***/ }), + +/***/ 9931: +/***/ (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; + }; +})(); +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.getRequiredVersionFromConfigFile = getRequiredVersionFromConfigFile; +const node_fs_1 = __importDefault(__nccwpck_require__(73024)); +const toml = __importStar(__nccwpck_require__(27106)); +function getRequiredVersionFromConfigFile(filePath) { + if (!filePath.endsWith(".toml")) { + return undefined; + } + const fileContent = node_fs_1.default.readFileSync(filePath, "utf-8"); + if (filePath.endsWith("pyproject.toml")) { + const tomlContent = toml.parse(fileContent); + return tomlContent?.tool?.uv?.["required-version"]; + } + const tomlContent = toml.parse(fileContent); + return tomlContent["required-version"]; +} + + +/***/ }), + +/***/ 4569: +/***/ (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; + }; +})(); +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.getUvVersionFromRequirementsFile = getUvVersionFromRequirementsFile; +const toml = __importStar(__nccwpck_require__(27106)); +const node_fs_1 = __importDefault(__nccwpck_require__(73024)); +function getUvVersionFromRequirementsFile(filePath) { + const fileContent = node_fs_1.default.readFileSync(filePath, "utf-8"); + if (filePath.endsWith(".txt")) { + return getUvVersionFromAllDependencies(fileContent.split("\n")); + } + const dependencies = parsePyprojectDependencies(fileContent); + return getUvVersionFromAllDependencies(dependencies); +} +function getUvVersionFromAllDependencies(allDependencies) { + return allDependencies + .find((dep) => dep.startsWith("uv")) + ?.match(/^uv([^A-Z0-9._-]+.*)$/)?.[1] + .trim(); +} +function parsePyprojectDependencies(pyprojectContent) { + const pyproject = toml.parse(pyprojectContent); + const dependencies = pyproject?.project?.dependencies || []; + const optionalDependencies = Object.values(pyproject?.project?.["optional-dependencies"] || {}).flat(); + const devDependencies = Object.values(pyproject?.["dependency-groups"] || {}) + .flat() + .filter((item) => typeof item === "string"); + return dependencies.concat(optionalDependencies, devDependencies); +} + + +/***/ }), + +/***/ 36772: +/***/ (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; + }; +})(); +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.getUvVersionFromFile = getUvVersionFromFile; +const core = __importStar(__nccwpck_require__(37484)); +const node_fs_1 = __importDefault(__nccwpck_require__(73024)); +const config_file_1 = __nccwpck_require__(9931); +const requirements_file_1 = __nccwpck_require__(4569); +function getUvVersionFromFile(filePath) { + core.info(`Trying to find version for uv in: ${filePath}`); + if (!node_fs_1.default.existsSync(filePath)) { + core.info(`Could not find file: ${filePath}`); + return undefined; + } + let uvVersion; + try { + uvVersion = (0, config_file_1.getRequiredVersionFromConfigFile)(filePath); + if (uvVersion === undefined) { + uvVersion = (0, requirements_file_1.getUvVersionFromRequirementsFile)(filePath); + } + } + catch (err) { + const message = err.message; + core.warning(`Error while parsing ${filePath}: ${message}`); + return undefined; + } + if (uvVersion?.startsWith("==")) { + uvVersion = uvVersion.slice(2); + } + if (uvVersion !== undefined) { + core.info(`Found version for uv in ${filePath}: ${uvVersion}`); + } + return uvVersion; +} + + /***/ }), /***/ 42078: diff --git a/src/setup-uv.ts b/src/setup-uv.ts index f6ed2af..05b8568 100644 --- a/src/setup-uv.ts +++ b/src/setup-uv.ts @@ -25,13 +25,14 @@ import { toolBinDir, toolDir, version as versionInput, + versionFile as versionFileInput, workingDirectory, serverUrl, manifestFile, } from "./utils/inputs"; import * as exec from "@actions/exec"; import fs from "node:fs"; -import { getUvVersionFromConfigFile } from "./utils/config-file"; +import { getUvVersionFromFile } from "./version/resolve"; async function run(): Promise { detectEmptyWorkdir(); @@ -133,10 +134,19 @@ async function determineVersion( if (versionInput !== "") { return await resolveVersion(versionInput, manifestFile, githubToken); } - const versionFromUvToml = getUvVersionFromConfigFile( + if (versionFileInput !== "") { + const versionFromFile = getUvVersionFromFile(versionFileInput); + if (versionFromFile === undefined) { + throw new Error( + `Could not determine uv version from file: ${versionFileInput}`, + ); + } + return await resolveVersion(versionFromFile, manifestFile, githubToken); + } + const versionFromUvToml = getUvVersionFromFile( `${workingDirectory}${path.sep}uv.toml`, ); - const versionFromPyproject = getUvVersionFromConfigFile( + const versionFromPyproject = getUvVersionFromFile( `${workingDirectory}${path.sep}pyproject.toml`, ); if (versionFromUvToml === undefined && versionFromPyproject === undefined) { diff --git a/src/utils/config-file.ts b/src/utils/config-file.ts deleted file mode 100644 index e42824e..0000000 --- a/src/utils/config-file.ts +++ /dev/null @@ -1,46 +0,0 @@ -import fs from "node:fs"; -import * as core from "@actions/core"; -import * as toml from "smol-toml"; - -export function getUvVersionFromConfigFile( - filePath: string, -): string | undefined { - core.info(`Trying to find required-version for uv in: ${filePath}`); - if (!fs.existsSync(filePath)) { - core.info(`Could not find file: ${filePath}`); - return undefined; - } - let requiredVersion: string | undefined; - try { - requiredVersion = getRequiredVersion(filePath); - } catch (err) { - const message = (err as Error).message; - core.warning(`Error while parsing ${filePath}: ${message}`); - return undefined; - } - - if (requiredVersion?.startsWith("==")) { - requiredVersion = requiredVersion.slice(2); - } - if (requiredVersion !== undefined) { - core.info( - `Found required-version for uv in ${filePath}: ${requiredVersion}`, - ); - } - return requiredVersion; -} - -function getRequiredVersion(filePath: string): string | undefined { - const fileContent = fs.readFileSync(filePath, "utf-8"); - - if (filePath.endsWith("pyproject.toml")) { - const tomlContent = toml.parse(fileContent) as { - tool?: { uv?: { "required-version"?: string } }; - }; - return tomlContent?.tool?.uv?.["required-version"]; - } - const tomlContent = toml.parse(fileContent) as { - "required-version"?: string; - }; - return tomlContent["required-version"]; -} diff --git a/src/utils/inputs.ts b/src/utils/inputs.ts index 8238ed9..9b7741e 100644 --- a/src/utils/inputs.ts +++ b/src/utils/inputs.ts @@ -3,6 +3,7 @@ import path from "node:path"; import { getManifestFromRepo } from "@actions/tool-cache"; export const version = core.getInput("version"); +export const versionFile = core.getInput("version-file"); export const pythonVersion = core.getInput("python-version"); export const activateEnvironment = core.getBooleanInput("activate-environment"); export const workingDirectory = core.getInput("working-directory"); diff --git a/src/version/config-file.ts b/src/version/config-file.ts new file mode 100644 index 0000000..20ec190 --- /dev/null +++ b/src/version/config-file.ts @@ -0,0 +1,22 @@ +import fs from "node:fs"; +import * as toml from "smol-toml"; + +export function getRequiredVersionFromConfigFile( + filePath: string, +): string | undefined { + if (!filePath.endsWith(".toml")) { + return undefined; + } + const fileContent = fs.readFileSync(filePath, "utf-8"); + + if (filePath.endsWith("pyproject.toml")) { + const tomlContent = toml.parse(fileContent) as { + tool?: { uv?: { "required-version"?: string } }; + }; + return tomlContent?.tool?.uv?.["required-version"]; + } + const tomlContent = toml.parse(fileContent) as { + "required-version"?: string; + }; + return tomlContent["required-version"]; +} diff --git a/src/version/requirements-file.ts b/src/version/requirements-file.ts new file mode 100644 index 0000000..8db9697 --- /dev/null +++ b/src/version/requirements-file.ts @@ -0,0 +1,43 @@ +import * as toml from "smol-toml"; +import fs from "node:fs"; + +export function getUvVersionFromRequirementsFile( + filePath: string, +): string | undefined { + const fileContent = fs.readFileSync(filePath, "utf-8"); + if (filePath.endsWith(".txt")) { + return getUvVersionFromAllDependencies(fileContent.split("\n")); + } + const dependencies = parsePyprojectDependencies(fileContent); + return getUvVersionFromAllDependencies(dependencies); +} +function getUvVersionFromAllDependencies( + allDependencies: string[], +): string | undefined { + return allDependencies + .find((dep: string) => dep.startsWith("uv")) + ?.match(/^uv([^A-Z0-9._-]+.*)$/)?.[1] + .trim(); +} + +interface Pyproject { + project?: { + dependencies?: string[]; + "optional-dependencies"?: Record; + }; + "dependency-groups"?: Record>; +} + +function parsePyprojectDependencies(pyprojectContent: string): string[] { + const pyproject: Pyproject = toml.parse(pyprojectContent); + const dependencies: string[] = pyproject?.project?.dependencies || []; + const optionalDependencies: string[] = Object.values( + pyproject?.project?.["optional-dependencies"] || {}, + ).flat(); + const devDependencies: string[] = Object.values( + pyproject?.["dependency-groups"] || {}, + ) + .flat() + .filter((item: string | object) => typeof item === "string"); + return dependencies.concat(optionalDependencies, devDependencies); +} diff --git a/src/version/resolve.ts b/src/version/resolve.ts new file mode 100644 index 0000000..a094720 --- /dev/null +++ b/src/version/resolve.ts @@ -0,0 +1,30 @@ +import * as core from "@actions/core"; +import fs from "node:fs"; +import { getRequiredVersionFromConfigFile } from "./config-file"; +import { getUvVersionFromRequirementsFile } from "./requirements-file"; + +export function getUvVersionFromFile(filePath: string): string | undefined { + core.info(`Trying to find version for uv in: ${filePath}`); + if (!fs.existsSync(filePath)) { + core.info(`Could not find file: ${filePath}`); + return undefined; + } + let uvVersion: string | undefined; + try { + uvVersion = getRequiredVersionFromConfigFile(filePath); + if (uvVersion === undefined) { + uvVersion = getUvVersionFromRequirementsFile(filePath); + } + } catch (err) { + const message = (err as Error).message; + core.warning(`Error while parsing ${filePath}: ${message}`); + return undefined; + } + if (uvVersion?.startsWith("==")) { + uvVersion = uvVersion.slice(2); + } + if (uvVersion !== undefined) { + core.info(`Found version for uv in ${filePath}: ${uvVersion}`); + } + return uvVersion; +}