Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: various improvements #21

Merged
merged 5 commits into from
Nov 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ jobs:
- name: "Run Biome"
run: biome ci

build:
name: "Build"
test:
name: "Build & Test"
runs-on: ubuntu-latest
steps:
- name: "Checkout the repository"
Expand All @@ -40,7 +40,10 @@ jobs:
run: pnpm install

- name: "Run Build"
run: pnpm run build
run: pnpm build

- name: "Run Tests"
run: pnpm test

test-action:
name: "Test Action"
Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,4 @@ lerna-debug.log*
.eslintcache
.turbo
.build_cache
temp
.tmp/
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,10 @@ Install any tool or binary with full cache, dynamic latest version, easy to conf
## Presets

| Name | URL |
| ----------------- | ------------------------------------------------------ |
|-------------------|--------------------------------------------------------|
| `infisical-cli` | https://github.com/Infisical/infisical |
| `cloud-sql-proxy` | https://github.com/GoogleCloudPlatform/cloud-sql-proxy |
| `github-cli` | https://github.com/cli/cli |

## Custom Config

Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
"dev": "pnpm build --watch",
"build": "tsup src/index.ts --treeshake --minify --clean",
"typecheck": "tsc",
"test": "vitest run",
"test:watch": "vitest",
"lint": "biome lint",
"lint:fix": "biome lint -w",
"format": "biome format --write",
Expand Down
100 changes: 1 addition & 99 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,101 +1,3 @@
// noinspection ExceptionCaughtLocallyJS

import path from "node:path";
import * as core from "@actions/core";
import { shake, template } from "radash";
import { downloadTool, getVersion } from "./install";
import presets from "./presets";
import type { Config } from "./types";

export async function main() {
try {
const templateArgs = {
os: process.platform === "win32" ? "windows" : process.platform,
arch: process.arch === "x64" ? "amd64" : process.arch,
arch2: process.arch,
exe: process.platform === "win32" ? ".exe" : "",
archive: process.platform === "win32" ? "zip" : "tar.gz",
};

// load config
let config: Config = {
id: core.getInput("id"),
preset: core.getInput("preset"),
repo: core.getInput("repo"),
version: core.getInput("version"),
versionUrl: core.getInput("version_url"),
versionRegex: core.getInput("version_regex"),
downloadUrl: core.getInput("download_url"),
downloadName: core.getInput("download_name"),
binPath: core.getInput("bin_path"),
cache: core.getBooleanInput("cache"),
};

core.debug(`loaded config: ${JSON.stringify(config)}`);

// if preset is set, load preset config
if (config.preset) {
if (!presets[config.preset])
throw new Error(`Preset not found: ${config.preset}`);
config = {
...config,
...presets[config.preset],
...shake(config, (x) => x === ""),
};
}

// set defaults
if (config.versionRegex && typeof config.versionRegex === "string")
config.versionRegex = new RegExp(config.versionRegex);
if (!config.versionUrl && !config.versionPath && config.repo)
config.versionPath = "tag_name";
if (!config.versionUrl && config.repo)
config.versionUrl = `https://api.github.com/repos/${config.repo}/releases/latest`;

core.debug(`resolved config: ${JSON.stringify(config)}`);

if (!config.downloadUrl) {
throw new Error("Download URL missing");
}

// fetch latest version if not fixed
if (config.version === "latest") {
const version = await getVersion(config);
if (!version) throw new Error("Version not found");
config.version = version;
core.debug(`resolved version: ${config.version}`);
}

// template download url
config.downloadUrl = template(config.downloadUrl, {
...config,
...templateArgs,
});
if (config.downloadUrl.startsWith("/") && config.repo)
config.downloadUrl = `https://github.com/${config.repo}${config.downloadUrl}`;

core.debug(`templated download url: ${config.downloadUrl}`);

// download or pull from cache
const toolPath = await downloadTool(config);

if (config.binPath !== "") {
config.binPath = template(config.binPath, {
...config,
...templateArgs,
});
}

core.debug(`cached path: ${toolPath}`);
core.addPath(path.join(toolPath, config.binPath));
core.info(`Successfully installed version ${config.version}`);

core.setOutput("path", toolPath);
core.setOutput("version", config.version);
} catch (error) {
// fail the workflow run if an error occurs
if (error instanceof Error) core.setFailed(error.message);
}
}
import { main } from "./main";

main();
75 changes: 75 additions & 0 deletions src/main.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import * as fs from "node:fs/promises";
import path from "node:path";
import * as core from "@actions/core";
import { exec } from "@actions/exec";
import {
afterEach,
beforeAll,
beforeEach,
describe,
expect,
test,
vi,
} from "vitest";
import { main } from "./main";

const RUNNER_TEMP = path.join(process.cwd(), ".tmp");
const RUNNER_TOOL_CACHE = path.join(RUNNER_TEMP, "./tool-cache");

const verifyBinary = async (binPath: string | undefined, binary: string) => {
if (!binPath) throw new Error("Bin path not found");

const extension = process.platform === "win32" ? ".exe" : "";
const filePath = path.join(binPath, binary + extension);
expect(filePath.startsWith(RUNNER_TOOL_CACHE)).toBe(true);

// check if binary exists
await fs.lstat(filePath);

// try executing the binary
await exec(filePath, ["--version"]);
};

describe("action", () => {
beforeAll(async () => {
await fs.mkdir(RUNNER_TEMP, { recursive: true });
jcwillox marked this conversation as resolved.
Show resolved Hide resolved
await fs.rm(RUNNER_TEMP, { recursive: true });
});

beforeEach(() => {
// set default values
vi.stubEnv("INPUT_CACHE", "true");
vi.stubEnv("INPUT_VERSION", "latest");
vi.stubEnv("INPUT_VERSION_REGEX", "(?<version>[\\d.]+)");
vi.stubEnv("RUNNER_TOOL_CACHE", RUNNER_TOOL_CACHE);
vi.stubEnv("RUNNER_TEMP", RUNNER_TEMP);
});

afterEach(() => {
vi.restoreAllMocks();
});

test("install infisical-cli", async () => {
vi.stubEnv("INPUT_PRESET", "infisical-cli");
const spy = vi.spyOn(core, "addPath");
await main();
expect(spy).toHaveBeenCalled();
await verifyBinary(spy.mock.lastCall?.[0], "infisical");
}, 10000);

test("install cloud-sql-proxy", async () => {
vi.stubEnv("INPUT_PRESET", "cloud-sql-proxy");
const spy = vi.spyOn(core, "addPath");
await main();
expect(spy).toHaveBeenCalled();
await verifyBinary(spy.mock.lastCall?.[0], "cloud-sql-proxy");
}, 10000);

test("install github-cli", async () => {
vi.stubEnv("INPUT_PRESET", "github-cli");
const spy = vi.spyOn(core, "addPath");
await main();
expect(spy).toHaveBeenCalled();
await verifyBinary(spy.mock.lastCall?.[0], "gh");
}, 10000);
});
106 changes: 106 additions & 0 deletions src/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// noinspection ExceptionCaughtLocallyJS

import path from "node:path";
import * as core from "@actions/core";
import { shake, template } from "radash";
import { downloadTool, getVersion } from "./install";
import presets from "./presets";
import type { Config } from "./types";

export async function main() {
try {
const templateArgs = {
os: process.platform === "win32" ? "windows" : process.platform,
arch: process.arch === "x64" ? "amd64" : process.arch,
arch2: process.arch,
exe: process.platform === "win32" ? ".exe" : "",
archive: process.platform === "win32" ? "zip" : "tar.gz",
};

// load config
let config: Config = {
id: core.getInput("id"),
preset: core.getInput("preset"),
repo: core.getInput("repo"),
version: core.getInput("version"),
versionUrl: core.getInput("version_url"),
versionRegex: core.getInput("version_regex"),
downloadUrl: core.getInput("download_url"),
downloadName: core.getInput("download_name"),
binPath: core.getInput("bin_path"),
cache: core.getBooleanInput("cache"),
};

core.debug(`loaded config: ${JSON.stringify(config)}`);

// if preset is set, load preset config
if (config.preset) {
if (!presets[config.preset])
throw new Error(`Preset not found: ${config.preset}`);
config = {
...config,
...presets[config.preset],
...shake(config, (x) => x === ""),
};
}

// set defaults
if (config.versionRegex && typeof config.versionRegex === "string")
config.versionRegex = new RegExp(config.versionRegex);
if (!config.versionUrl && !config.versionPath && config.repo)
config.versionPath = "tag_name";
if (!config.versionUrl && config.repo)
config.versionUrl = `https://api.github.com/repos/${config.repo}/releases/latest`;

core.debug(`resolved config: ${JSON.stringify(config)}`);

if (!config.downloadUrl) {
throw new Error("Download URL missing");
}

if (config.downloadName) {
config.downloadName = template(config.downloadName, {
...config,
...templateArgs,
});
}

// fetch latest version if not fixed
if (config.version === "latest") {
const version = await getVersion(config);
if (!version) throw new Error("Version not found");
config.version = version;
core.debug(`resolved version: ${config.version}`);
}

// template download url
config.downloadUrl = template(config.downloadUrl, {
...config,
...templateArgs,
});
if (config.downloadUrl.startsWith("/") && config.repo)
config.downloadUrl = `https://github.com/${config.repo}${config.downloadUrl}`;

core.debug(`templated download url: ${config.downloadUrl}`);

// download or pull from cache
const toolPath = await downloadTool(config);

if (config.binPath !== "") {
config.binPath = template(config.binPath, {
...config,
...templateArgs,
});
}

core.debug(`cached path: ${toolPath}`);
core.addPath(path.join(toolPath, config.binPath));
core.info(`Successfully installed version ${config.version}`);

core.setOutput("path", toolPath);
core.setOutput("version", config.version);
} catch (error) {
// fail the workflow run if an error occurs
if (error instanceof Error) core.setFailed(error.message);
}
}
11 changes: 6 additions & 5 deletions src/presets/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,17 @@ const presets: Record<string, Partial<Config>> = {
},
"cloud-sql-proxy": {
repo: "GoogleCloudPlatform/cloudsql-proxy",
downloadUrl:
"https://storage.googleapis.com/cloud-sql-connectors/cloud-sql-proxy/v{{version}}/cloud-sql-proxy.{{os}}.{{arch}}",
downloadName: "cloud-sql-proxy",
downloadUrl: `https://storage.googleapis.com/cloud-sql-connectors/cloud-sql-proxy/v{{version}}/cloud-sql-proxy.${process.platform === "win32" ? "{{arch2}}.exe" : "{{os}}.{{arch}}"}`,
downloadName: "cloud-sql-proxy{{exe}}",
},
"github-cli": {
repo: "cli/cli",
version: "2.62.0",
downloadUrl:
"/releases/download/v{{version}}/gh_{{version}}_{{os}}_{{arch}}.{{archive}}",
binPath: "/gh_{{version}}_{{os}}_{{arch}}/bin",
binPath:
process.platform === "win32"
? "/bin"
: "/gh_{{version}}_{{os}}_{{arch}}/bin",
},
};

Expand Down