Skip to content
Open
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
128 changes: 48 additions & 80 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,6 @@ 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';

// Returns the binary name for the current OS/arch from GitHub releases
function getOsBinName() {
const osBinName = core.getInput('binary');
if (osBinName !== "") {
Expand Down Expand Up @@ -47,116 +41,90 @@ 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() {
if (core.getInput('binarySource')) return null;
const version = core.getInput('version') || 'latest';
if (version.toLowerCase().trim() === 'latest') return null;
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) {
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) {

Choose a reason for hiding this comment

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

This is a big change for a small thing => the warning.

I would propose to revert most changes and put a small logic change right after const version = smartVersionUpdate(versionInput); in line 105.

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}`);
Comment on lines -105 to -106
Copy link

@twoGiants twoGiants Jan 30, 2026

Choose a reason for hiding this comment

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

const version = smartVersionUpdate(versionInput);
warnStaleVersion(version);

I would put the core.info(Using specific version ${version}); in the warnStaleVersion and log it if the version is fine.

return buildUrlString(version);

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

Choose a reason for hiding this comment

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

This can be done a bit simpler using the github redirect to the latest version. No need for JSON etc.:

async function getLatestVersion() {
    const response = await fetch('https://github.com/knative/func/releases/latest', {
        method: 'HEAD',
        redirect: 'manual'
    });
    
    const location = response.headers.get('location');
    // location: "https://github.com/knative/func/releases/tag/knative-v1.21.0"
    return location.split('/').pop(); // "knative-v1.21.0"
}

Then you can use this function to get the version here:

async function warnStaleVersion(version) {
    let latestVersion;
    try {
        latestVersion = getLatestVersion()
    } catch (error) {
        core.debug(`Failed fetching latest version: ${error.message}`);
        return;
    }

    const offset = 3;
    if (hasVersionDrift(latestVersion, version, offset)) {
        core.warning(
            `You are using func ${version}, which is ${offset} versions behind the latest (${latestVersion}). Upgrading is recommended.`
        );
        return;
    }

    core.info(`Using specific version ${version}`);
}

versionDiff has a bug. I would not write a warning in this case of version = 1.0.0 and latestVersion = 2.0.0.

I would change versionDiff to hasVersionDrift and change the implementation to use major and minor versions for checking, e.g. knative-v1.21.0 becomes 121 and knative-v1.18.0 becomes 118 and then you subtract them and compare the abs(121 - 118) to offset.

It looks like the time is coming closer for unit tests for this action 😆 .

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Well, the comment does say its a "difference of minor versions" 😄 but i get your point. I didnt take it would be needed any time soon. Will update it. The redirect location is super cool! will update that.

Choose a reason for hiding this comment

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

the comment does say its a "difference of minor versions" 😄

You're right of course. My thinking was that if someone adds a very old version for some reason or in future when we have v2+. But you got it 😸 👍

try {
const res = await fetch('https://github.com/knative/func/releases/latest', {
method: 'HEAD',
redirect: 'manual',
});
const loc = res.headers.get('location');
if (!loc) return;

const latest = loc.split('/').pop();
const toNum = (v) => { const m = v.match(/(\d+)\.(\d+)/); return m && m[1] * 100 + +m[2]; };
const diff = toNum(latest) - toNum(version);

if (diff >= 3) {
core.warning(`You are using func ${version}, which is ${diff} minor versions behind the latest (${latest}). Upgrading is recommended.`);
}
} catch {
core.debug('Skipping stale version check');
}
}

async function run() {
let osBinName;
try {
osBinName = getOsBinName();
} catch (error) {
core.setFailed(error.message);
return;
}

let url;
try {
url = resolveDownloadUrl(osBinName);
} catch (error) {
core.setFailed(`Failed to resolve url: ${error.message}`);
return;
}

let fullPathBin;
try {
fullPathBin = resolveFullPathBin();
} catch (error) {
core.setFailed(error.message);
return;
}
const osBinName = getOsBinName();
const version = resolveVersion();
const url = resolveDownloadUrl(version, osBinName);
const fullPathBin = resolveFullPathBin();

try {
await downloadFuncBinary(url, fullPathBin);
} catch (error) {
core.setFailed(`Download failed: ${error.message}`);
return;
}

try {
addBinToPath(fullPathBin);
} catch (error) {
core.setFailed(error.message);
return;
}

try {
if (version) await warnStaleVersion(version);
await exec.exec(fullPathBin, ['version']);
} catch (error) {
core.setFailed(error.message);
return;
}
}

Expand Down
Loading