Convert from composite to typescript (#17)

# Summary

Converts the action from a [composite to
javascript](https://docs.github.com/en/actions/sharing-automations/creating-actions/about-custom-actions#types-of-actions).
Most importantly to make use of prebuilt libraries and helpers like
[actions/toolkit](https://github.com/actions/toolkit).

The structure and features are modeled after
[astral-sh/setup-uv](https://github.com/astral-sh/setup-uv)

## Changes

1. Download the ruff executable for the current platform from the GitHub
releases
2. Add ruff to the PATH
3. Validate the downloaded ruff executable against its checksum
4. Cache ruff in the [Tool
Cache](https://github.com/actions/toolkit/tree/main/packages/tool-cache)
to speed up runs on self-hosted runners
5. Support semver ranges to define the ruff version to install

## 🚨 Breaking changes

Removes the `changed-files` input.

This input could previously be used to run ruff only on files changed in
a PR. The functionality was implemented by calling another action. This
repo should focus on providing a quick and easy way to use ruff in
GitHub Actions, not add more functionality on top of ruff.

The previous functionality can be replicated with:

```yaml
- uses: actions/checkout@v4
- name: Get changed files
  id: changed-files
  uses: tj-actions/changed-files@v45
  with:
    files: |
      **.py
- name: Run ruff on changed files only 
  uses: astral-sh/ruff-action@v2
  with:
    src: ${{ steps.changed-files.outputs.all_changed_files }}
```

This was tested here:
https://github.com/astral-sh/ruff-action/actions/runs/12017035736/job/33498508269
This commit is contained in:
Kevin Stillhammer
2024-12-03 17:18:31 +01:00
committed by GitHub
parent d0a0e814ec
commit f2e3221107
44 changed files with 78310 additions and 338 deletions
+57
View File
@@ -0,0 +1,57 @@
import * as fs from "node:fs";
import * as crypto from "node:crypto";
import * as core from "@actions/core";
import { KNOWN_CHECKSUMS } from "./known-checksums";
import type { Architecture, Platform } from "../../utils/platforms";
export async function validateChecksum(
checkSum: string | undefined,
downloadPath: string,
arch: Architecture,
platform: Platform,
version: string,
): Promise<void> {
let isValid: boolean | undefined = undefined;
if (checkSum !== undefined && checkSum !== "") {
isValid = await validateFileCheckSum(downloadPath, checkSum);
} else {
core.debug("Checksum not provided. Checking known checksums.");
const key = `${arch}-${platform}-${version}`;
if (key in KNOWN_CHECKSUMS) {
const knownChecksum = KNOWN_CHECKSUMS[`${arch}-${platform}-${version}`];
core.debug(`Checking checksum for ${arch}-${platform}-${version}.`);
isValid = await validateFileCheckSum(downloadPath, knownChecksum);
} else {
core.debug(`No known checksum found for ${key}.`);
}
}
if (isValid === false) {
throw new Error(`Checksum for ${downloadPath} did not match ${checkSum}.`);
}
if (isValid === true) {
core.debug(`Checksum for ${downloadPath} is valid.`);
}
}
async function validateFileCheckSum(
filePath: string,
expected: string,
): Promise<boolean> {
return new Promise((resolve, reject) => {
const hash = crypto.createHash("sha256");
const stream = fs.createReadStream(filePath);
stream.on("error", (err) => reject(err));
stream.on("data", (chunk) => hash.update(chunk));
stream.on("end", () => {
const actual = hash.digest("hex");
resolve(actual === expected);
});
});
}
export function isknownVersion(version: string): boolean {
const pattern = new RegExp(`^.*-.*-${version}$`);
return Object.keys(KNOWN_CHECKSUMS).some((key) => pattern.test(key));
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,65 @@
import { promises as fs } from "node:fs";
import * as tc from "@actions/tool-cache";
import { KNOWN_CHECKSUMS } from "./known-checksums";
export async function updateChecksums(
filePath: string,
downloadUrls: string[],
): Promise<void> {
await fs.rm(filePath);
await fs.appendFile(
filePath,
"// AUTOGENERATED_DO_NOT_EDIT\nexport const KNOWN_CHECKSUMS: { [key: string]: string } = {\n",
);
let firstLine = true;
for (const downloadUrl of downloadUrls) {
const key = getKey(downloadUrl);
if (key === undefined) {
continue;
}
const checksum = await getOrDownloadChecksum(key, downloadUrl);
if (!firstLine) {
await fs.appendFile(filePath, ",\n");
}
await fs.appendFile(filePath, ` "${key}":\n "${checksum}"`);
firstLine = false;
}
await fs.appendFile(filePath, ",\n};\n");
}
function getKey(downloadUrl: string): string | undefined {
const parts = downloadUrl.split("/");
const version = parts[parts.length - 2].replace("v", "");
const fileName = parts[parts.length - 1];
if (fileName.startsWith("source")) {
return undefined;
}
if (fileName.includes(version)) {
// https://github.com/astral-sh/ruff/releases/download/v0.4.10/ruff-0.4.10-aarch64-apple-darwin.tar.gz.sha256
const name = fileName.split(version)[1].split(".")[0].substring(1);
return `${name}-${version}`;
}
// https://github.com/astral-sh/ruff/releases/download/v0.1.7/ruff-aarch64-apple-darwin.tar.gz.sha256
// or
// https://github.com/astral-sh/ruff/releases/download/0.8.0/ruff-aarch64-apple-darwin.tar.gz.sha256
const name = fileName.split(".")[0].split("ruff-")[1];
return `${name}-${version}`;
}
async function getOrDownloadChecksum(
key: string,
downloadUrl: string,
): Promise<string> {
let checksum: string;
if (key in KNOWN_CHECKSUMS) {
checksum = KNOWN_CHECKSUMS[key];
} else {
const content = await downloadAssetContent(downloadUrl);
checksum = content.split(" ")[0].trim();
}
return checksum;
}
async function downloadAssetContent(downloadUrl: string): Promise<string> {
const downloadPath = await tc.downloadTool(downloadUrl);
return await fs.readFile(downloadPath, "utf8");
}