Skip to content
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
2 changes: 1 addition & 1 deletion .github/workflows/test-action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
test-custom-version:
runs-on: ubuntu-latest
env:
TEST_VERSION: '1.19.0'
TEST_VERSION: '1.18.0'
VERSION_OFFSET: 27 # internal version = minor + offset (v1.19 → v0.46)
steps:
- uses: actions/checkout@v4
Expand Down
151 changes: 70 additions & 81 deletions index.js

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, looks good, but lets clean it up a bit and then we're done. I hope it's ok for you to go another round 🙈 .

I would avoid working with null values if I can. Especially in JavaScript. Being explicit makes the code easier to reason about, at least for me 😸. JavaScript is a permissive language 😆 it has undefined, null, NaN as empty/null values and they behave differently 😆. Unlike in go where nil checks are common place => in JavaScript I would use them only when there is no other way. So in the JS code you write, where you can set non-null values => do it and check against that.

Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,11 @@ const exec = require('@actions/exec');
const path = require('path');
const fs = require('fs');

// Using latest as default
const DEFAULT_FUNC_VERSION = 'latest';
const DEFAULT_BINARY_SOURCE = 'https://github.com/knative/func/releases/download';
const DEFAULT_LATEST_BINARY_SOURCE = 'https://github.com/knative/func/releases/latest/download';
const LATEST = 'latest';

// Returns the binary name for the current OS/arch from GitHub releases
function getOsBinName() {
const osBinName = core.getInput('binary');
if (osBinName !== "") {
if (osBinName) {
return osBinName;
}

Expand All @@ -24,14 +20,14 @@ function getOsBinName() {
case 'ARM64': return 'func_linux_arm64';
case 'PPC64LE': return 'func_linux_ppc64le';
case 'S390X': return 'func_linux_s390x';
default: throw new Error(`unknown runner: ${runnerArch}`);
default: throw new Error(`unknown runner arch: ${runnerArch}`);
}
} else if (runnerOS === 'macOS') {
return runnerArch === 'X64' ? 'func_darwin_amd64' : 'func_darwin_arm64';
} else if (runnerOS === 'Windows') {
return 'func_windows_amd64.exe';
} else {
throw new Error(`unknown runner: ${runnerArch}`);
throw new Error(`unknown runner os: ${runnerOS}`);
}
}

Expand All @@ -47,117 +43,110 @@ function resolveFullPathBin() {
return path.resolve(destination, bin);
}

// Normalizes version to release tag format: knative-vX.Y.Z
// Ex.: '1.16' or 'v1.16' will return 'knative-v1.16.0'
function smartVersionUpdate(version) {
const versionRegex = /^(?<knprefix>knative-)?(?<prefix>v?)(?<major>\d+)\.(?<minor>\d+)(\.(?<patch>\d+))?$/;
const match = version.match(versionRegex);
if (!match) {
throw new Error(`Invalid version format (${version}). Expected format: "1.16[.X]" or "v1.16[.X]"`);
const match = version.match(/^(?:knative-)?v?(\d+)\.(\d+)(?:\.(\d+))?$/);
if (!match) throw new Error(`Invalid version format (${version}). Expected format: "1.16[.X]" or "v1.16[.X]"`);
return `knative-v${match[1]}.${match[2]}.${match[3] ?? 0}`;
}

function resolveVersion() {
const version = core.getInput('version') || LATEST;
if (version.toLowerCase().trim() === LATEST) return LATEST;
return smartVersionUpdate(version);
}

function resolveDownloadUrl(version, binName) {
const binarySource = core.getInput('binarySource');
if (binarySource) {
core.info(`Using custom binary source: ${binarySource}`);
return binarySource;
}

if (version === LATEST) {
core.info('Using latest version...');
return `https://github.com/knative/func/releases/latest/download/${binName}`;
}
const knprefix = 'knative-';
const prefix = 'v';
const patch = match.groups.patch ?? 0;
return `${knprefix}${prefix}${match.groups.major}.${match.groups.minor}.${patch}`;
core.info(`Using specific version ${version}`);
return `https://github.com/knative/func/releases/download/${version}/${binName}`;
}

// Downloads binary from release URL and makes it executable
async function downloadFuncBinary(url, binPath) {
core.info(`Downloading from: ${url}`);

await exec.exec('curl', ['-L', '--fail', '-o', binPath, url]);

if (!fs.existsSync(binPath)) {
throw new Error("Download failed, couldn't find the binary on disk");
}

if (process.env.RUNNER_OS !== 'Windows') {
await exec.exec('chmod', ['+x', binPath]);
}
}

// Adds binary directory to PATH for current and subsequent steps
function addBinToPath(binPath) {
const dir = path.dirname(binPath);
fs.appendFileSync(process.env.GITHUB_PATH, `\n${dir}`);

if (!process.env.PATH.split(path.delimiter).includes(dir)) {
process.env.PATH = process.env.PATH + path.delimiter + dir;
process.env.PATH += path.delimiter + dir;
core.info(`${dir} added to PATH`);
}
}

// Resolve download url based on given input
// binName: name of func binary when it is to be constructed for full URL
// (when not using binarySource)
function resolveDownloadUrl(binName) {
const binarySource = core.getInput('binarySource');
if (binarySource !== "") {
core.info(`Using custom binary source: ${binarySource}`);
return binarySource;
}

const versionInput = core.getInput('version') || DEFAULT_FUNC_VERSION;
if (versionInput.toLowerCase().trim() === DEFAULT_FUNC_VERSION) {
core.info("Using latest version...");
return buildUrlString(DEFAULT_FUNC_VERSION);
}
const version = smartVersionUpdate(versionInput);
core.info(`Using specific version ${version}`);
return buildUrlString(version);

function buildUrlString(version) {
return version === DEFAULT_FUNC_VERSION
? `${DEFAULT_LATEST_BINARY_SOURCE}/${binName}`
: `${DEFAULT_BINARY_SOURCE}/${version}/${binName}`;
}
}

async function run() {
let osBinName;
try {
osBinName = getOsBinName();
} catch (error) {
core.setFailed(error.message);
async function warnStaleVersion(version) {
// Skip version check for 'latest' or custom binary source
if (version === LATEST || core.getInput('binarySource')) {
return;
}

let url;
let res;
try {
url = resolveDownloadUrl(osBinName);
res = await fetch('https://github.com/knative/func/releases/latest', {
method: 'HEAD',
redirect: 'manual',
});
} catch (error) {
core.setFailed(`Failed to resolve url: ${error.message}`);
core.warning(`Failed to fetch latest version: ${error.message}`);
core.debug('Skipping stale version check');
return;
}

let fullPathBin;
try {
fullPathBin = resolveFullPathBin();
} catch (error) {
core.setFailed(error.message);
const loc = res.headers.get('location');
if (!loc) {
core.warning('Could not determine latest version: no redirect location');
core.debug('Skipping stale version check');
return;
}

try {
await downloadFuncBinary(url, fullPathBin);
} catch (error) {
core.setFailed(`Download failed: ${error.message}`);
const latest = loc.split('/').pop();
// Convert version string "vX.Y" to number (X*100 + Y) for comparison,
// e.g. "v1.21" → 121.
// Returns null if no match, otherwise multiplies major by 100 and adds minor
// (+ coerces string to number).
const toNum = (v) => { const m = v.match(/(\d+)\.(\d+)/); return m && m[1] * 100 + +m[2]; };
const latestNum = toNum(latest);
const versionNum = toNum(version);
if (versionNum == null || latestNum == null) {
core.debug('could not parse version numbers to compare');
return;
}

try {
addBinToPath(fullPathBin);
} catch (error) {
core.setFailed(error.message);
return;
const diff = latestNum - versionNum;
if (diff >= 3) {
core.warning(`You are using func ${version}, which is ${diff} minor versions behind the latest (${latest}). Upgrading is recommended.`);
}
}

try {
await exec.exec(fullPathBin, ['version']);
} catch (error) {
core.setFailed(error.message);
return;
}
async function run() {
const osBinName = getOsBinName();
const version = resolveVersion();
await warnStaleVersion(version);

const url = resolveDownloadUrl(version, osBinName);
const fullPathBin = resolveFullPathBin();

await downloadFuncBinary(url, fullPathBin);
addBinToPath(fullPathBin);

await exec.exec(fullPathBin, ['version']);
}

run();
run().catch(error => core.setFailed(error.message));

Loading