From c8669a3a6649d09091082f74f62a02435007f730 Mon Sep 17 00:00:00 2001 From: Slawomir Koszewski Date: Sun, 11 Jan 2026 11:12:06 +0100 Subject: [PATCH] Update: Add 'install-path' option for custom installation directory in CLI and documentation --- README.md | 4 +++- dist/cli.js | 5 +++-- src/cli.ts | 23 +++++++++++++++-------- 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 7002234..b93bf31 100644 --- a/README.md +++ b/README.md @@ -115,7 +115,8 @@ The following inputs are available for the GitHub Action, and as options for the - 'archive': matches common archive file extensions like .zip, .tar.gz, .tar, .tgz, .7z. - 'package': matches common package file extensions like .deb, .rpm, .pkg. - or a custom regex pattern can be provided to match specific file types. -- `update-cache` (optional, default: 'false'): When set to 'false', the action will use the cached version of the tool if it is already available. If set to 'true', the action will check the latest release and update the cache if a newer version is found. If set to 'always', it will always download and install, updating the cache regardless. +- `install-path` (optional, CLI only): Custom installation directory for the CLI tool. +- `update-cache` (optional, default: 'false', Action only): When set to 'false', the action will use the cached version of the tool if it is already available. If set to 'true', the action will check the latest release and update the cache if a newer version is found. If set to 'always', it will always download and install, updating the cache regardless. - `debug` (optional, default: 'false'): When set to `true`, the action will log the contents of the unpacked directory to the console. - `token` (optional): A GitHub token for authentication, useful for accessing private repositories or increasing rate limits. @@ -135,6 +136,7 @@ Options: -f, --file-name Asset file name or regex pattern (prefixed with ~) -b, --binary-name Binary to search for (prefixed with ~ for regex) -t, --file-type 'archive', 'package', or custom regex (default: archive) + -p, --install-path Custom installation directory -k, --token GitHub token -d, --debug Enable debug logging -h, --help Show this help message diff --git a/dist/cli.js b/dist/cli.js index d924b09..9e05c6d 100755 --- a/dist/cli.js +++ b/dist/cli.js @@ -1,5 +1,5 @@ #!/usr/bin/env node -"use strict";var F=Object.create;var N=Object.defineProperty;var H=Object.getOwnPropertyDescriptor;var O=Object.getOwnPropertyNames;var U=Object.getPrototypeOf,X=Object.prototype.hasOwnProperty;var G=(t,e,n,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of O(e))!X.call(t,s)&&s!==n&&N(t,s,{get:()=>e[s],enumerable:!(r=H(e,s))||r.enumerable});return t};var f=(t,e,n)=>(n=t!=null?F(U(t)):{},G(e||!t||!t.__esModule?N(n,"default",{value:t,enumerable:!0}):n,t));var B=require("util"),h=f(require("path")),c=f(require("fs")),P=f(require("os"));var S=f(require("os")),Y={linux:"linux",darwin:"(darwin|macos|mac|osx)",win32:"(windows|win)"},q={x64:"(x86_64|x64|amd64)",arm64:"(aarch64|arm64)"};function _(){let t=S.platform(),e=S.arch();return{system:t,arch:e,systemPattern:Y[t]||t,archPattern:q[e]||e}}function M(t,e,n){let{fileName:r,fileType:s="archive"}=n,o;if(s==="archive"?o="\\.(zip|tar\\.gz|tar|tgz|7z)":s==="package"?o="\\.(deb|rpm|pkg)":o=s,r)if(r.startsWith("~")){let i=r.substring(1),l=i.includes("{{SYSTEM}}"),a=i.includes("{{ARCH}}"),p=i.includes("{{EXT_PATTERN}}"),$=i.endsWith("$");!l&&!a&&!p&&!$?i+=".*{{SYSTEM}}[_-]{{ARCH}}.*{{EXT_PATTERN}}$":l&&a&&!p&&!$&&(i+=".*{{EXT_PATTERN}}$");let m=i.replace(/{{SYSTEM}}/g,e.systemPattern).replace(/{{ARCH}}/g,e.archPattern).replace(/{{EXT_PATTERN}}/g,o),x=new RegExp(m,"i"),g=t.filter(d=>x.test(d.name));if(g.length===0)throw new Error(`No assets matched the regex: ${m}`);if(g.length>1)throw new Error(`Multiple assets matched the criteria: ${g.map(d=>d.name).join(", ")}`);return g[0]}else{let i=t.find(l=>l.name===r);if(!i)throw new Error(`No asset found matching the exact name: ${r}`);return i}else{let i=`${e.systemPattern}[_-]${e.archPattern}.*${o}$`,l=new RegExp(i,"i"),a=t.filter(p=>l.test(p.name));if(a.length===0)throw new Error(`No assets matched the default criteria: ${i}`);if(a.length>1)throw new Error(`Multiple assets matched the default criteria: ${a.map(p=>p.name).join(", ")}`);return a[0]}}var b=f(require("fs")),j=f(require("path"));function k(t,e,n,r){let s=b.readdirSync(t);n&&(r(`Searching for binary in ${t}...`),s.forEach(o=>r(` - ${o}`)));for(let o of s){let i=j.join(t,o);if(b.statSync(i).isDirectory()){let a=k(i,e,n,r);if(a)return a}else{let a=!1;if(e instanceof RegExp?a=e.test(o):(a=o===e,!a&&process.platform==="win32"&&!e.toLowerCase().endsWith(".exe")&&(a=o.toLowerCase()===`${e.toLowerCase()}.exe`)),a)return i}}}async function C(t,e){let n=`https://api.github.com/repos/${t}/releases/latest`,r={Accept:"application/vnd.github.v3+json","User-Agent":"setup-github-release-action"};e&&(r.Authorization=`token ${e}`);let s=await fetch(n,{headers:r});if(!s.ok){let o=await s.text();throw new Error(`Failed to fetch latest release for ${t}: ${s.statusText}. ${o}`)}return await s.json()}async function W(t,e,n){let r={"User-Agent":"setup-github-release-action"};n&&(r.Authorization=`token ${n}`);let s=await fetch(t,{headers:r});if(!s.ok)throw new Error(`Failed to download asset: ${s.statusText}`);let o=await import("fs"),{Readable:i}=await import("stream"),{finished:l}=await import("stream/promises"),a=o.createWriteStream(e);await l(i.fromWeb(s.body).pipe(a))}var E=require("child_process"),u=f(require("path")),y=f(require("fs"));async function L(t,e){let n=u.extname(t).toLowerCase(),r=u.basename(t).toLowerCase();if(y.existsSync(e)||y.mkdirSync(e,{recursive:!0}),r.endsWith(".tar.gz")||r.endsWith(".tgz")||r.endsWith(".tar")){let o=(0,E.spawnSync)("tar",["-xf",t,"-C",e]);if(o.status!==0)throw new Error(`tar failed with status ${o.status}: ${o.stderr.toString()}`)}else if(r.endsWith(".zip"))if(process.platform==="win32"){let s=`Expand-Archive -Path "${t}" -DestinationPath "${e}" -Force`,o=(0,E.spawnSync)("powershell",["-Command",s]);if(o.status!==0)throw new Error(`powershell Expand-Archive failed with status ${o.status}: ${o.stderr.toString()}`)}else{let s=(0,E.spawnSync)("unzip",["-q",t,"-d",e]);if(s.status!==0)throw new Error(`unzip failed with status ${s.status}: ${s.stderr.toString()}`)}else if(r.endsWith(".7z")){let s=(0,E.spawnSync)("7z",["x",t,`-o${e}`,"-y"]);if(s.status!==0)throw new Error(`7z failed with status ${s.status}. Make sure 7z is installed.`)}else{let s=u.join(e,u.basename(t));y.copyFileSync(t,s)}}async function K(){let{values:t,positionals:e}=(0,B.parseArgs)({options:{"file-name":{type:"string",short:"f"},"binary-name":{type:"string",short:"b"},"file-type":{type:"string",short:"t",default:"archive"},token:{type:"string",short:"k"},debug:{type:"boolean",short:"d",default:!1},help:{type:"boolean",short:"h"}},allowPositionals:!0});(t.help||e.length===0)&&(console.log(` +"use strict";var F=Object.create;var N=Object.defineProperty;var H=Object.getOwnPropertyDescriptor;var O=Object.getOwnPropertyNames;var U=Object.getPrototypeOf,X=Object.prototype.hasOwnProperty;var G=(t,e,n,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of O(e))!X.call(t,s)&&s!==n&&N(t,s,{get:()=>e[s],enumerable:!(r=H(e,s))||r.enumerable});return t};var f=(t,e,n)=>(n=t!=null?F(U(t)):{},G(e||!t||!t.__esModule?N(n,"default",{value:t,enumerable:!0}):n,t));var B=require("util"),p=f(require("path")),c=f(require("fs")),P=f(require("os"));var S=f(require("os")),Y={linux:"linux",darwin:"(darwin|macos|mac|osx)",win32:"(windows|win)"},q={x64:"(x86_64|x64|amd64)",arm64:"(aarch64|arm64)"};function _(){let t=S.platform(),e=S.arch();return{system:t,arch:e,systemPattern:Y[t]||t,archPattern:q[e]||e}}function C(t,e,n){let{fileName:r,fileType:s="archive"}=n,o;if(s==="archive"?o="\\.(zip|tar\\.gz|tar|tgz|7z)":s==="package"?o="\\.(deb|rpm|pkg)":o=s,r)if(r.startsWith("~")){let i=r.substring(1),l=i.includes("{{SYSTEM}}"),a=i.includes("{{ARCH}}"),h=i.includes("{{EXT_PATTERN}}"),$=i.endsWith("$");!l&&!a&&!h&&!$?i+=".*{{SYSTEM}}[_-]{{ARCH}}.*{{EXT_PATTERN}}$":l&&a&&!h&&!$&&(i+=".*{{EXT_PATTERN}}$");let m=i.replace(/{{SYSTEM}}/g,e.systemPattern).replace(/{{ARCH}}/g,e.archPattern).replace(/{{EXT_PATTERN}}/g,o),x=new RegExp(m,"i"),g=t.filter(w=>x.test(w.name));if(g.length===0)throw new Error(`No assets matched the regex: ${m}`);if(g.length>1)throw new Error(`Multiple assets matched the criteria: ${g.map(w=>w.name).join(", ")}`);return g[0]}else{let i=t.find(l=>l.name===r);if(!i)throw new Error(`No asset found matching the exact name: ${r}`);return i}else{let i=`${e.systemPattern}[_-]${e.archPattern}.*${o}$`,l=new RegExp(i,"i"),a=t.filter(h=>l.test(h.name));if(a.length===0)throw new Error(`No assets matched the default criteria: ${i}`);if(a.length>1)throw new Error(`Multiple assets matched the default criteria: ${a.map(h=>h.name).join(", ")}`);return a[0]}}var b=f(require("fs")),M=f(require("path"));function k(t,e,n,r){let s=b.readdirSync(t);n&&(r(`Searching for binary in ${t}...`),s.forEach(o=>r(` - ${o}`)));for(let o of s){let i=M.join(t,o);if(b.statSync(i).isDirectory()){let a=k(i,e,n,r);if(a)return a}else{let a=!1;if(e instanceof RegExp?a=e.test(o):(a=o===e,!a&&process.platform==="win32"&&!e.toLowerCase().endsWith(".exe")&&(a=o.toLowerCase()===`${e.toLowerCase()}.exe`)),a)return i}}}async function j(t,e){let n=`https://api.github.com/repos/${t}/releases/latest`,r={Accept:"application/vnd.github.v3+json","User-Agent":"setup-github-release-action"};e&&(r.Authorization=`token ${e}`);let s=await fetch(n,{headers:r});if(!s.ok){let o=await s.text();throw new Error(`Failed to fetch latest release for ${t}: ${s.statusText}. ${o}`)}return await s.json()}async function W(t,e,n){let r={"User-Agent":"setup-github-release-action"};n&&(r.Authorization=`token ${n}`);let s=await fetch(t,{headers:r});if(!s.ok)throw new Error(`Failed to download asset: ${s.statusText}`);let o=await import("fs"),{Readable:i}=await import("stream"),{finished:l}=await import("stream/promises"),a=o.createWriteStream(e);await l(i.fromWeb(s.body).pipe(a))}var E=require("child_process"),d=f(require("path")),y=f(require("fs"));async function L(t,e){let n=d.extname(t).toLowerCase(),r=d.basename(t).toLowerCase();if(y.existsSync(e)||y.mkdirSync(e,{recursive:!0}),r.endsWith(".tar.gz")||r.endsWith(".tgz")||r.endsWith(".tar")){let o=(0,E.spawnSync)("tar",["-xf",t,"-C",e]);if(o.status!==0)throw new Error(`tar failed with status ${o.status}: ${o.stderr.toString()}`)}else if(r.endsWith(".zip"))if(process.platform==="win32"){let s=`Expand-Archive -Path "${t}" -DestinationPath "${e}" -Force`,o=(0,E.spawnSync)("powershell",["-Command",s]);if(o.status!==0)throw new Error(`powershell Expand-Archive failed with status ${o.status}: ${o.stderr.toString()}`)}else{let s=(0,E.spawnSync)("unzip",["-q",t,"-d",e]);if(s.status!==0)throw new Error(`unzip failed with status ${s.status}: ${s.stderr.toString()}`)}else if(r.endsWith(".7z")){let s=(0,E.spawnSync)("7z",["x",t,`-o${e}`,"-y"]);if(s.status!==0)throw new Error(`7z failed with status ${s.status}. Make sure 7z is installed.`)}else{let s=d.join(e,d.basename(t));y.copyFileSync(t,s)}}async function K(){let{values:t,positionals:e}=(0,B.parseArgs)({options:{"file-name":{type:"string",short:"f"},"binary-name":{type:"string",short:"b"},"file-type":{type:"string",short:"t",default:"archive"},"install-path":{type:"string",short:"p"},token:{type:"string",short:"k"},debug:{type:"boolean",short:"d",default:!1},help:{type:"boolean",short:"h"}},allowPositionals:!0});(t.help||e.length===0)&&(console.log(` Usage: install-github-release [options] Arguments: @@ -9,7 +9,8 @@ Options: -f, --file-name Asset file name or regex pattern (prefixed with ~) -b, --binary-name Binary to search for (prefixed with ~ for regex) -t, --file-type 'archive', 'package', or custom regex (default: archive) + -p, --install-path Custom installation directory -k, --token GitHub token -d, --debug Enable debug logging -h, --help Show this help message - `),process.exit(0));let n=e[0];n||(console.error("Error: Repository is required."),process.exit(1));let r=t["file-name"],s=t["binary-name"],o=t["file-type"],i=!!t.debug,l=t.token||process.env.GITHUB_TOKEN;try{let a=_(),p=n.split("/").pop()||n;console.log(`Fetching latest release for ${n}...`);let $=await C(n,l),m=M($.assets,a,{fileName:r,fileType:o});console.log(`Selected asset: ${m.name}`);let x=c.mkdtempSync(h.join(P.tmpdir(),"setup-gh-release-")),g=h.join(x,m.name);console.log(`Downloading ${m.name}...`),await W(m.browser_download_url,g,l);let d=h.join(x,"extract");console.log(`Extracting ${m.name}...`),await L(g,d);let A=s||p,R;A.startsWith("~")?R=new RegExp(A.substring(1),"i"):R=A;let T=k(d,R,i,console.log);if(!T)throw new Error(`Could not find binary "${A}" in the extracted asset.`);let w;if(process.getuid&&process.getuid()===0)w="/usr/local/bin";else{let z=h.join(P.homedir(),"bin");c.existsSync(z)?w=z:w="/usr/local/bin"}c.existsSync(w)||c.mkdirSync(w,{recursive:!0});let I=h.basename(T),v=h.join(w,I);console.log(`Installing ${I} to ${v}...`),c.copyFileSync(T,v),process.platform!=="win32"&&c.chmodSync(v,"755"),c.rmSync(x,{recursive:!0,force:!0}),console.log("Installation successful!")}catch(a){console.error(`Error: ${a.message}`),process.exit(1)}}K(); + `),process.exit(0));let n=e[0];n||(console.error("Error: Repository is required."),process.exit(1));let r=t["file-name"],s=t["binary-name"],o=t["file-type"],i=!!t.debug,l=t.token||process.env.GITHUB_TOKEN;try{let a=_(),h=n.split("/").pop()||n;console.log(`Fetching latest release for ${n}...`);let $=await j(n,l),m=C($.assets,a,{fileName:r,fileType:o});console.log(`Selected asset: ${m.name}`);let x=c.mkdtempSync(p.join(P.tmpdir(),"setup-gh-release-")),g=p.join(x,m.name);console.log(`Downloading ${m.name}...`),await W(m.browser_download_url,g,l);let w=p.join(x,"extract");console.log(`Extracting ${m.name}...`),await L(g,w);let A=s||h,R;A.startsWith("~")?R=new RegExp(A.substring(1),"i"):R=A;let T=k(w,R,i,console.log);if(!T)throw new Error(`Could not find binary "${A}" in the extracted asset.`);let u;if(t["install-path"])u=p.resolve(t["install-path"]);else if(process.getuid&&process.getuid()===0)u="/usr/local/bin";else{let z=p.join(P.homedir(),"bin");c.existsSync(z)?u=z:u="/usr/local/bin"}c.existsSync(u)||c.mkdirSync(u,{recursive:!0});let I=p.basename(T),v=p.join(u,I);console.log(`Installing ${I} to ${v}...`),c.copyFileSync(T,v),process.platform!=="win32"&&c.chmodSync(v,"755"),c.rmSync(x,{recursive:!0,force:!0}),console.log("Installation successful!")}catch(a){console.error(`Error: ${a.message}`),process.exit(1)}}K(); diff --git a/src/cli.ts b/src/cli.ts index cf56650..5178dba 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -14,6 +14,7 @@ async function run() { 'file-name': { type: 'string', short: 'f' }, 'binary-name': { type: 'string', short: 'b' }, 'file-type': { type: 'string', short: 't', default: 'archive' }, + 'install-path': { type: 'string', short: 'p' }, 'token': { type: 'string', short: 'k' }, 'debug': { type: 'boolean', short: 'd', default: false }, 'help': { type: 'boolean', short: 'h' } @@ -32,6 +33,7 @@ Options: -f, --file-name Asset file name or regex pattern (prefixed with ~) -b, --binary-name Binary to search for (prefixed with ~ for regex) -t, --file-type 'archive', 'package', or custom regex (default: archive) + -p, --install-path Custom installation directory -k, --token GitHub token -d, --debug Enable debug logging -h, --help Show this help message @@ -88,17 +90,22 @@ Options: // Determine install directory let installDir: string; - const isRoot = process.getuid && process.getuid() === 0; - if (isRoot) { - installDir = '/usr/local/bin'; + if (values['install-path']) { + installDir = path.resolve(values['install-path']); } else { - const homeBin = path.join(os.homedir(), 'bin'); - if (fs.existsSync(homeBin)) { - installDir = homeBin; - } else { - // Fallback or error? Let's use a local bin if possible or /usr/local/bin (might fail) + const isRoot = process.getuid && process.getuid() === 0; + + if (isRoot) { installDir = '/usr/local/bin'; + } else { + const homeBin = path.join(os.homedir(), 'bin'); + if (fs.existsSync(homeBin)) { + installDir = homeBin; + } else { + // Fallback or error? Let's use a local bin if possible or /usr/local/bin (might fail) + installDir = '/usr/local/bin'; + } } }