diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e30f3d4..22fb2fd 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -187,6 +187,22 @@ jobs: exit 1 fi + test-tool-versions-file-version: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + - name: Install from .tools-versions file + id: setup-uv + uses: ./ + with: + version-file: "__tests__/fixtures/.tool-versions" + - name: Correct version gets installed + run: | + if [ "$(uv --version)" != "uv 0.5.15" ]; then + echo "Wrong uv version: $(uv --version)" + exit 1 + fi + test-checksum: runs-on: ${{ matrix.inputs.os }} strategy: @@ -635,6 +651,7 @@ jobs: - test-uv-file-version - test-version-file-version - test-version-file-hash-version + - test-tool-versions-file-version - test-checksum - test-with-explicit-token - test-uvx diff --git a/README.md b/README.md index f18c121..865e756 100644 --- a/README.md +++ b/README.md @@ -100,6 +100,8 @@ You can use the `version-file` input to specify a file that contains the version 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`. +[asdf](https://asdf-vm.com/) `.tool-versions` is also supported, but without the `ref` syntax. + ```yaml - name: Install uv based on the version defined in pyproject.toml uses: astral-sh/setup-uv@v6 diff --git a/__tests__/fixtures/.tool-versions b/__tests__/fixtures/.tool-versions new file mode 100644 index 0000000..d1369b5 --- /dev/null +++ b/__tests__/fixtures/.tool-versions @@ -0,0 +1 @@ +uv 0.5.15 diff --git a/__tests__/version/tool-versions-file.test.ts b/__tests__/version/tool-versions-file.test.ts new file mode 100644 index 0000000..a5f3336 --- /dev/null +++ b/__tests__/version/tool-versions-file.test.ts @@ -0,0 +1,115 @@ +jest.mock("node:fs"); +jest.mock("@actions/core", () => ({ + warning: jest.fn(), +})); + +import fs from "node:fs"; +import * as core from "@actions/core"; +import { beforeEach, describe, expect, it, jest } from "@jest/globals"; +import { getUvVersionFromToolVersions } from "../../src/version/tool-versions-file"; + +const mockedFs = fs as jest.Mocked; +const mockedCore = core as jest.Mocked; + +describe("getUvVersionFromToolVersions", () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it("should return undefined for non-.tool-versions files", () => { + const result = getUvVersionFromToolVersions("package.json"); + expect(result).toBeUndefined(); + expect(mockedFs.readFileSync).not.toHaveBeenCalled(); + }); + + it("should return version for valid uv entry", () => { + const fileContent = "python 3.11.0\nuv 0.1.0\nnodejs 18.0.0"; + mockedFs.readFileSync.mockReturnValue(fileContent); + + const result = getUvVersionFromToolVersions(".tool-versions"); + + expect(result).toBe("0.1.0"); + expect(mockedFs.readFileSync).toHaveBeenCalledWith( + ".tool-versions", + "utf8", + ); + }); + + it("should return version for uv entry with v prefix", () => { + const fileContent = "uv v0.2.0"; + mockedFs.readFileSync.mockReturnValue(fileContent); + + const result = getUvVersionFromToolVersions(".tool-versions"); + + expect(result).toBe("0.2.0"); + }); + + it("should handle whitespace around uv entry", () => { + const fileContent = " uv 0.3.0 "; + mockedFs.readFileSync.mockReturnValue(fileContent); + + const result = getUvVersionFromToolVersions(".tool-versions"); + + expect(result).toBe("0.3.0"); + }); + + it("should skip commented lines", () => { + const fileContent = "# uv 0.1.0\npython 3.11.0\nuv 0.2.0"; + mockedFs.readFileSync.mockReturnValue(fileContent); + + const result = getUvVersionFromToolVersions(".tool-versions"); + + expect(result).toBe("0.2.0"); + }); + + it("should return first matching uv version", () => { + const fileContent = "uv 0.1.0\npython 3.11.0\nuv 0.2.0"; + mockedFs.readFileSync.mockReturnValue(fileContent); + + const result = getUvVersionFromToolVersions(".tool-versions"); + + expect(result).toBe("0.1.0"); + }); + + it("should return undefined when no uv entry found", () => { + const fileContent = "python 3.11.0\nnodejs 18.0.0"; + mockedFs.readFileSync.mockReturnValue(fileContent); + + const result = getUvVersionFromToolVersions(".tool-versions"); + + expect(result).toBeUndefined(); + }); + + it("should return undefined for empty file", () => { + mockedFs.readFileSync.mockReturnValue(""); + + const result = getUvVersionFromToolVersions(".tool-versions"); + + expect(result).toBeUndefined(); + }); + + it("should warn and return undefined for ref syntax", () => { + const fileContent = "uv ref:main"; + mockedFs.readFileSync.mockReturnValue(fileContent); + + const result = getUvVersionFromToolVersions(".tool-versions"); + + expect(result).toBeUndefined(); + expect(mockedCore.warning).toHaveBeenCalledWith( + "The ref syntax of .tool-versions is not supported. Please use a released version instead.", + ); + }); + + it("should handle file path with .tool-versions extension", () => { + const fileContent = "uv 0.1.0"; + mockedFs.readFileSync.mockReturnValue(fileContent); + + const result = getUvVersionFromToolVersions("path/to/.tool-versions"); + + expect(result).toBe("0.1.0"); + expect(mockedFs.readFileSync).toHaveBeenCalledWith( + "path/to/.tool-versions", + "utf8", + ); + }); +}); diff --git a/dist/setup/index.js b/dist/setup/index.js index 5fca5e4..777d287 100644 --- a/dist/setup/index.js +++ b/dist/setup/index.js @@ -127216,6 +127216,7 @@ const node_fs_1 = __importDefault(__nccwpck_require__(73024)); const core = __importStar(__nccwpck_require__(37484)); const config_file_1 = __nccwpck_require__(9931); const requirements_file_1 = __nccwpck_require__(4569); +const tool_versions_file_1 = __nccwpck_require__(4895); function getUvVersionFromFile(filePath) { core.info(`Trying to find version for uv in: ${filePath}`); if (!node_fs_1.default.existsSync(filePath)) { @@ -127224,7 +127225,10 @@ function getUvVersionFromFile(filePath) { } let uvVersion; try { - uvVersion = (0, config_file_1.getRequiredVersionFromConfigFile)(filePath); + uvVersion = (0, tool_versions_file_1.getUvVersionFromToolVersions)(filePath); + if (uvVersion === undefined) { + uvVersion = (0, config_file_1.getRequiredVersionFromConfigFile)(filePath); + } if (uvVersion === undefined) { uvVersion = (0, requirements_file_1.getUvVersionFromRequirementsFile)(filePath); } @@ -127244,6 +127248,78 @@ function getUvVersionFromFile(filePath) { } +/***/ }), + +/***/ 4895: +/***/ (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.getUvVersionFromToolVersions = getUvVersionFromToolVersions; +const node_fs_1 = __importDefault(__nccwpck_require__(73024)); +const core = __importStar(__nccwpck_require__(37484)); +function getUvVersionFromToolVersions(filePath) { + if (!filePath.endsWith(".tool-versions")) { + return undefined; + } + const fileContents = node_fs_1.default.readFileSync(filePath, "utf8"); + const lines = fileContents.split("\n"); + for (const line of lines) { + // Skip commented lines + if (line.trim().startsWith("#")) { + continue; + } + const match = line.match(/^\s*uv\s*v?\s*(?[^\s]+)\s*$/); + if (match) { + const matchedVersion = match.groups?.version.trim(); + if (matchedVersion?.startsWith("ref")) { + core.warning("The ref syntax of .tool-versions is not supported. Please use a released version instead."); + return undefined; + } + return matchedVersion; + } + } + return undefined; +} + + /***/ }), /***/ 42078: diff --git a/src/version/resolve.ts b/src/version/resolve.ts index 055b4a3..6746f7e 100644 --- a/src/version/resolve.ts +++ b/src/version/resolve.ts @@ -2,6 +2,7 @@ import fs from "node:fs"; import * as core from "@actions/core"; import { getRequiredVersionFromConfigFile } from "./config-file"; import { getUvVersionFromRequirementsFile } from "./requirements-file"; +import { getUvVersionFromToolVersions } from "./tool-versions-file"; export function getUvVersionFromFile(filePath: string): string | undefined { core.info(`Trying to find version for uv in: ${filePath}`); @@ -11,7 +12,10 @@ export function getUvVersionFromFile(filePath: string): string | undefined { } let uvVersion: string | undefined; try { - uvVersion = getRequiredVersionFromConfigFile(filePath); + uvVersion = getUvVersionFromToolVersions(filePath); + if (uvVersion === undefined) { + uvVersion = getRequiredVersionFromConfigFile(filePath); + } if (uvVersion === undefined) { uvVersion = getUvVersionFromRequirementsFile(filePath); } diff --git a/src/version/tool-versions-file.ts b/src/version/tool-versions-file.ts new file mode 100644 index 0000000..c0ddf3c --- /dev/null +++ b/src/version/tool-versions-file.ts @@ -0,0 +1,31 @@ +import fs from "node:fs"; +import * as core from "@actions/core"; + +export function getUvVersionFromToolVersions( + filePath: string, +): string | undefined { + if (!filePath.endsWith(".tool-versions")) { + return undefined; + } + const fileContents = fs.readFileSync(filePath, "utf8"); + const lines = fileContents.split("\n"); + + for (const line of lines) { + // Skip commented lines + if (line.trim().startsWith("#")) { + continue; + } + const match = line.match(/^\s*uv\s*v?\s*(?[^\s]+)\s*$/); + if (match) { + const matchedVersion = match.groups?.version.trim(); + if (matchedVersion?.startsWith("ref")) { + core.warning( + "The ref syntax of .tool-versions is not supported. Please use a released version instead.", + ); + return undefined; + } + return matchedVersion; + } + } + return undefined; +}