Matched functionality of the CLI with the Bash predecessor.
This commit is contained in:
545
dist/cli.js
vendored
545
dist/cli.js
vendored
@@ -1,16 +1,553 @@
|
||||
#!/usr/bin/env node
|
||||
"use strict";var O=Object.create;var z=Object.defineProperty;var H=Object.getOwnPropertyDescriptor;var U=Object.getOwnPropertyNames;var D=Object.getPrototypeOf,X=Object.prototype.hasOwnProperty;var Y=(t,e,i,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of U(e))!X.call(t,s)&&s!==i&&z(t,s,{get:()=>e[s],enumerable:!(r=H(e,s))||r.enumerable});return t};var p=(t,e,i)=>(i=t!=null?O(D(t)):{},Y(e||!t||!t.__esModule?z(i,"default",{value:t,enumerable:!0}):i,t));var F=require("util"),f=p(require("path")),l=p(require("fs")),P=p(require("os"));var T=p(require("os")),G={linux:"linux",darwin:"(darwin|macos|mac|osx)",win32:"(windows|win)"},q={x64:"(x86_64|x64|amd64)",arm64:"(aarch64|arm64)"};function M(){let t=T.platform(),e=T.arch();return{system:t,arch:e,systemPattern:G[t]||t,archPattern:q[e]||e}}function j(t,e,i){let{fileName:r,fileType:s="archive"}=i,o;if(s==="archive"?o="\\.(zip|tar\\.gz|tar|tgz|7z)":s==="package"?o="\\.(deb|rpm|pkg)":o=s,r)if(r.startsWith("~")){let a=r.substring(1),c=a.includes("{{SYSTEM}}"),n=a.includes("{{ARCH}}"),m=a.includes("{{EXT_PATTERN}}"),S=a.endsWith("$");!c&&!n&&!m&&!S?a+=".*{{SYSTEM}}[_-]{{ARCH}}.*{{EXT_PATTERN}}$":c&&n&&!m&&!S&&(a+=".*{{EXT_PATTERN}}$");let h=a.replace(/{{SYSTEM}}/g,e.systemPattern).replace(/{{ARCH}}/g,e.archPattern).replace(/{{EXT_PATTERN}}/g,o),b=new RegExp(h,"i"),u=t.filter(w=>b.test(w.name));if(u.length===0)throw new Error(`No assets matched the regex: ${h}`);if(u.length>1)throw new Error(`Multiple assets matched the criteria: ${u.map(w=>w.name).join(", ")}`);return u[0]}else{let a=t.find(c=>c.name===r);if(!a)throw new Error(`No asset found matching the exact name: ${r}`);return a}else{let a=`${e.systemPattern}[_-]${e.archPattern}.*${o}$`,c=new RegExp(a,"i"),n=t.filter(m=>c.test(m.name));if(n.length===0)throw new Error(`No assets matched the default criteria: ${a}`);if(n.length>1)throw new Error(`Multiple assets matched the default criteria: ${n.map(m=>m.name).join(", ")}`);return n[0]}}var A=p(require("fs")),_=p(require("path"));function I(t,e,i,r){let s=A.readdirSync(t);i&&(r(`Searching for binary in ${t}...`),s.forEach(o=>r(` - ${o}`)));for(let o of s){let a=_.join(t,o);if(A.statSync(a).isDirectory()){let n=I(a,e,i,r);if(n)return n}else{let n=!1;if(e instanceof RegExp?n=e.test(o):(n=o===e,!n&&process.platform==="win32"&&!e.toLowerCase().endsWith(".exe")&&(n=o.toLowerCase()===`${e.toLowerCase()}.exe`)),n)return a}}}async function W(t,e){let i=`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(i,{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 L(t,e,i){let r={"User-Agent":"setup-github-release-action"};i&&(r.Authorization=`token ${i}`);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:a}=await import("stream"),{finished:c}=await import("stream/promises"),n=o.createWriteStream(e);await c(a.fromWeb(s.body).pipe(n))}var x=require("child_process"),y=p(require("path")),E=p(require("fs"));async function B(t,e){let i=y.extname(t).toLowerCase(),r=y.basename(t).toLowerCase();if(E.existsSync(e)||E.mkdirSync(e,{recursive:!0}),r.endsWith(".tar.gz")||r.endsWith(".tgz")||r.endsWith(".tar")){let o=(0,x.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"){if((0,x.spawnSync)("tar",["-xf",t,"-C",e]).status===0)return;let o=t.replace(/'/g,"''"),a=e.replace(/'/g,"''"),c=`Add-Type -AssemblyName System.IO.Compression.FileSystem; [System.IO.Compression.ZipFile]::ExtractToDirectory('${o}', '${a}')`;for(let n of["pwsh","powershell"])if((0,x.spawnSync)(n,["-NoProfile","-ExecutionPolicy","Bypass","-Command",c]).status===0)return;throw new Error("Extraction failed: Both tar and PowerShell fallback failed. Make sure your system can extract ZIP files.")}else{let s=(0,x.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,x.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=y.join(e,y.basename(t));E.copyFileSync(t,s)}}async function Z(){let{values:t,positionals:e}=(0,F.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(`
|
||||
"use strict";
|
||||
var __create = Object.create;
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __getProtoOf = Object.getPrototypeOf;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
||||
// If the importer is in node compatibility mode or this is not an ESM
|
||||
// file that has been converted to a CommonJS file using a Babel-
|
||||
// compatible transform (i.e. "__esModule" has not been set), then set
|
||||
// "default" to the CommonJS "module.exports" for node compatibility.
|
||||
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
||||
mod
|
||||
));
|
||||
|
||||
// src/cli.ts
|
||||
var path3 = __toESM(require("path"));
|
||||
var fs3 = __toESM(require("fs"));
|
||||
var os2 = __toESM(require("os"));
|
||||
var import_child_process2 = require("child_process");
|
||||
|
||||
// src/core/platform.ts
|
||||
var os = __toESM(require("os"));
|
||||
var systemPatterns = {
|
||||
linux: "linux",
|
||||
darwin: "(darwin|macos|mac|osx)",
|
||||
win32: "(windows|win)"
|
||||
};
|
||||
var archPatterns = {
|
||||
x64: "(x86_64|x64|amd64)",
|
||||
arm64: "(aarch64|arm64)"
|
||||
};
|
||||
function getPlatformInfo(overrides) {
|
||||
const system = (overrides?.system || os.platform()).toLowerCase();
|
||||
const arch2 = (overrides?.arch || os.arch()).toLowerCase();
|
||||
return {
|
||||
system,
|
||||
arch: arch2,
|
||||
systemPattern: systemPatterns[system] || system,
|
||||
archPattern: archPatterns[arch2] || arch2
|
||||
};
|
||||
}
|
||||
|
||||
// src/core/matcher.ts
|
||||
function normalizeCustomExtensionPattern(fileType) {
|
||||
let pattern = fileType;
|
||||
if (!pattern.endsWith("$")) {
|
||||
pattern += "$";
|
||||
}
|
||||
if (!pattern.startsWith("\\.")) {
|
||||
pattern = `\\.${pattern}`;
|
||||
}
|
||||
return pattern;
|
||||
}
|
||||
function getExtPattern(fileType, system) {
|
||||
const normalizedType = (fileType || "").toLowerCase();
|
||||
if (!normalizedType) {
|
||||
if (system === "linux") {
|
||||
return "\\.(deb|rpm|zip|tar\\.gz|tgz)$";
|
||||
}
|
||||
if (system === "darwin" || system === "macos" || system === "mac" || system === "osx") {
|
||||
return "\\.(pkg|zip|tar\\.gz|tgz)$";
|
||||
}
|
||||
return "\\.(zip|tar\\.gz|tgz)$";
|
||||
}
|
||||
if (normalizedType === "archive") {
|
||||
return "\\.(zip|tar\\.gz|tgz)$";
|
||||
}
|
||||
if (normalizedType === "package") {
|
||||
return "\\.(deb|pkg|rpm)$";
|
||||
}
|
||||
const shorthandTypePatterns = {
|
||||
zip: "\\.(zip)$",
|
||||
gzip: "\\.(tar\\.gz|tgz)$",
|
||||
gz: "\\.(tar\\.gz|tgz)$",
|
||||
tar: "\\.(tar)$",
|
||||
"tar.gz": "\\.(tar\\.gz)$",
|
||||
tgz: "\\.(tgz)$",
|
||||
deb: "\\.(deb)$",
|
||||
pkg: "\\.(pkg)$",
|
||||
rpm: "\\.(rpm)$"
|
||||
};
|
||||
if (shorthandTypePatterns[normalizedType]) {
|
||||
return shorthandTypePatterns[normalizedType];
|
||||
}
|
||||
return normalizeCustomExtensionPattern(fileType || "");
|
||||
}
|
||||
function getMatchingAsset(assets, platform2, options) {
|
||||
const { fileName, fileType } = options;
|
||||
const extPattern = getExtPattern(fileType, platform2.system);
|
||||
if (!fileName) {
|
||||
const pattern = `${platform2.systemPattern}[_-]${platform2.archPattern}.*${extPattern}`;
|
||||
const regex = new RegExp(pattern, "i");
|
||||
const matchingAssets = assets.filter((a) => regex.test(a.name));
|
||||
if (matchingAssets.length === 0) {
|
||||
throw new Error(`No assets matched the default criteria: ${pattern}`);
|
||||
}
|
||||
if (matchingAssets.length > 1) {
|
||||
throw new Error(`Multiple assets matched the default criteria: ${matchingAssets.map((a) => a.name).join(", ")}`);
|
||||
}
|
||||
return matchingAssets[0];
|
||||
} else if (fileName.startsWith("~")) {
|
||||
let pattern = fileName.substring(1);
|
||||
const hasSystem = pattern.includes("{{SYSTEM}}");
|
||||
const hasArch = pattern.includes("{{ARCH}}");
|
||||
const hasExt = pattern.includes("{{EXT_PATTERN}}");
|
||||
const hasEnd = pattern.endsWith("$");
|
||||
if (!hasSystem && !hasArch && !hasExt && !hasEnd) {
|
||||
pattern += `.*{{SYSTEM}}[_-]{{ARCH}}.*{{EXT_PATTERN}}$`;
|
||||
} else if (hasSystem && hasArch && !hasExt && !hasEnd) {
|
||||
pattern += `.*{{EXT_PATTERN}}$`;
|
||||
}
|
||||
const finalPattern = pattern.replace(/{{SYSTEM}}/g, platform2.systemPattern).replace(/{{ARCH}}/g, platform2.archPattern).replace(/{{EXT_PATTERN}}/g, extPattern);
|
||||
const regex = new RegExp(finalPattern, "i");
|
||||
const matchingAssets = assets.filter((a) => regex.test(a.name));
|
||||
if (matchingAssets.length === 0) {
|
||||
throw new Error(`No assets matched the regex: ${finalPattern}`);
|
||||
}
|
||||
if (matchingAssets.length > 1) {
|
||||
throw new Error(`Multiple assets matched the criteria: ${matchingAssets.map((a) => a.name).join(", ")}`);
|
||||
}
|
||||
return matchingAssets[0];
|
||||
} else {
|
||||
const asset = assets.find((a) => a.name === fileName);
|
||||
if (!asset) {
|
||||
throw new Error(`No asset found matching the exact name: ${fileName}`);
|
||||
}
|
||||
return asset;
|
||||
}
|
||||
}
|
||||
|
||||
// src/core/finder.ts
|
||||
var fs = __toESM(require("fs"));
|
||||
var path = __toESM(require("path"));
|
||||
function findBinary(dir, pattern, debug, logger) {
|
||||
const items = fs.readdirSync(dir);
|
||||
if (debug) {
|
||||
logger(`Searching for binary in ${dir}...`);
|
||||
items.forEach((item) => logger(` - ${item}`));
|
||||
}
|
||||
for (const item of items) {
|
||||
const fullPath = path.join(dir, item);
|
||||
const stat = fs.statSync(fullPath);
|
||||
if (stat.isDirectory()) {
|
||||
const found = findBinary(fullPath, pattern, debug, logger);
|
||||
if (found) return found;
|
||||
} else {
|
||||
let isMatch = false;
|
||||
if (pattern instanceof RegExp) {
|
||||
isMatch = pattern.test(item);
|
||||
} else {
|
||||
isMatch = item === pattern;
|
||||
if (!isMatch && process.platform === "win32" && !pattern.toLowerCase().endsWith(".exe")) {
|
||||
isMatch = item.toLowerCase() === `${pattern.toLowerCase()}.exe`;
|
||||
}
|
||||
}
|
||||
if (isMatch) return fullPath;
|
||||
}
|
||||
}
|
||||
return void 0;
|
||||
}
|
||||
|
||||
// src/core/downloader.ts
|
||||
function getGithubApiHeaders(token) {
|
||||
const headers = {
|
||||
"Accept": "application/vnd.github.v3+json",
|
||||
"User-Agent": "setup-github-release-action"
|
||||
};
|
||||
if (token) {
|
||||
headers["Authorization"] = `token ${token}`;
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
async function fetchLatestRelease(repository, token) {
|
||||
const url = `https://api.github.com/repos/${repository}/releases/latest`;
|
||||
const headers = getGithubApiHeaders(token);
|
||||
const response = await fetch(url, { headers });
|
||||
if (!response.ok) {
|
||||
const errorBody = await response.text();
|
||||
throw new Error(`Failed to fetch latest release for ${repository}: ${response.statusText}. ${errorBody}`);
|
||||
}
|
||||
return await response.json();
|
||||
}
|
||||
async function fetchLatestReleaseRaw(repository, token) {
|
||||
const url = `https://api.github.com/repos/${repository}/releases/latest`;
|
||||
const headers = getGithubApiHeaders(token);
|
||||
const response = await fetch(url, { headers });
|
||||
const body = await response.text();
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch latest release for ${repository}: ${response.statusText}. ${body}`);
|
||||
}
|
||||
return body;
|
||||
}
|
||||
async function downloadAsset(url, destPath, token) {
|
||||
const headers = {
|
||||
"User-Agent": "setup-github-release-action"
|
||||
};
|
||||
if (token) {
|
||||
headers["Authorization"] = `token ${token}`;
|
||||
}
|
||||
const response = await fetch(url, { headers });
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to download asset: ${response.statusText}`);
|
||||
}
|
||||
const fs4 = await import("fs");
|
||||
const { Readable } = await import("stream");
|
||||
const { finished } = await import("stream/promises");
|
||||
const fileStream = fs4.createWriteStream(destPath);
|
||||
await finished(Readable.fromWeb(response.body).pipe(fileStream));
|
||||
}
|
||||
|
||||
// src/core/extractor.ts
|
||||
var import_child_process = require("child_process");
|
||||
var path2 = __toESM(require("path"));
|
||||
var fs2 = __toESM(require("fs"));
|
||||
async function extractAsset(filePath, destDir) {
|
||||
const ext = path2.extname(filePath).toLowerCase();
|
||||
const name = path2.basename(filePath).toLowerCase();
|
||||
if (!fs2.existsSync(destDir)) {
|
||||
fs2.mkdirSync(destDir, { recursive: true });
|
||||
}
|
||||
if (name.endsWith(".tar.gz") || name.endsWith(".tgz") || name.endsWith(".tar")) {
|
||||
const args = ["-xf", filePath, "-C", destDir];
|
||||
const result = (0, import_child_process.spawnSync)("tar", args);
|
||||
if (result.status !== 0) {
|
||||
throw new Error(`tar failed with status ${result.status}: ${result.stderr.toString()}`);
|
||||
}
|
||||
} else if (name.endsWith(".zip")) {
|
||||
if (process.platform === "win32") {
|
||||
const tarResult = (0, import_child_process.spawnSync)("tar", ["-xf", filePath, "-C", destDir]);
|
||||
if (tarResult.status === 0) return;
|
||||
const escapedFilePath = filePath.replace(/'/g, "''");
|
||||
const escapedDestDir = destDir.replace(/'/g, "''");
|
||||
const dotNetCommand = `Add-Type -AssemblyName System.IO.Compression.FileSystem; [System.IO.Compression.ZipFile]::ExtractToDirectory('${escapedFilePath}', '${escapedDestDir}')`;
|
||||
for (const shell of ["pwsh", "powershell"]) {
|
||||
const result = (0, import_child_process.spawnSync)(shell, ["-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", dotNetCommand]);
|
||||
if (result.status === 0) return;
|
||||
}
|
||||
throw new Error(`Extraction failed: Both tar and PowerShell fallback failed. Make sure your system can extract ZIP files.`);
|
||||
} else {
|
||||
const result = (0, import_child_process.spawnSync)("unzip", ["-q", filePath, "-d", destDir]);
|
||||
if (result.status !== 0) {
|
||||
throw new Error(`unzip failed with status ${result.status}: ${result.stderr.toString()}`);
|
||||
}
|
||||
}
|
||||
} else if (name.endsWith(".7z")) {
|
||||
const result = (0, import_child_process.spawnSync)("7z", ["x", filePath, `-o${destDir}`, "-y"]);
|
||||
if (result.status !== 0) {
|
||||
throw new Error(`7z failed with status ${result.status}. Make sure 7z is installed.`);
|
||||
}
|
||||
} else {
|
||||
const destPath = path2.join(destDir, path2.basename(filePath));
|
||||
fs2.copyFileSync(filePath, destPath);
|
||||
}
|
||||
}
|
||||
|
||||
// src/cli.ts
|
||||
function usage() {
|
||||
return `
|
||||
Usage: install-github-release [options] <repository>
|
||||
|
||||
Arguments:
|
||||
repository The GitHub repository (owner/repo)
|
||||
|
||||
Options:
|
||||
--dry-run [level] Run in test mode (default level: 1)
|
||||
-l, --list [repository] List available assets from latest release and exit
|
||||
-a, --app-name <name> Application name (optional, for output messages)
|
||||
-f, --file-name <name> Asset file name or regex pattern (prefixed with ~)
|
||||
-b, --binary-name <name> Binary to search for (prefixed with ~ for regex)
|
||||
-t, --file-type <type> 'archive', 'package', or custom regex (default: archive)
|
||||
-b, --binary-name <name> Binary name (supports source:destination form)
|
||||
-t, --file-type <type> archive|package|zip|gzip|gz|tar|tar.gz|tgz|deb|pkg|rpm
|
||||
-p, --install-path <path> Custom installation directory
|
||||
-o, --output-directory <path>
|
||||
Only download selected asset to the specified directory
|
||||
-j, --releases-json Download latest release JSON only
|
||||
--system <name> Override detected system for asset matching
|
||||
--arch <name> Override detected architecture for asset matching
|
||||
-k, --token <token> GitHub token
|
||||
-d, --debug Enable debug logging
|
||||
-h, --help Show this help message
|
||||
`),process.exit(0));let i=e[0];i||(console.error("Error: Repository is required."),process.exit(1));let r=t["file-name"],s=t["binary-name"],o=t["file-type"],a=!!t.debug,c=t.token||process.env.GITHUB_TOKEN;try{let n=M(),m=i.split("/").pop()||i;console.log(`Fetching latest release for ${i}...`);let S=await W(i,c),h=j(S.assets,n,{fileName:r,fileType:o});console.log(`Selected asset: ${h.name}`);let b=l.mkdtempSync(f.join(P.tmpdir(),"setup-gh-release-")),u=f.join(b,h.name);console.log(`Downloading ${h.name}...`),await L(h.browser_download_url,u,c);let w=f.join(b,"extract");console.log(`Extracting ${h.name}...`),await B(u,w);let R=s||m,k;R.startsWith("~")?k=new RegExp(R.substring(1),"i"):k=R;let C=I(w,k,a,console.log);if(!C)throw new Error(`Could not find binary "${R}" in the extracted asset.`);let g;if(t["install-path"])g=f.resolve(t["install-path"]);else if(process.platform==="win32"){let d=process.env.LOCALAPPDATA||f.join(P.homedir(),"AppData","Local");g=f.join(d,"bin")}else if(process.getuid&&process.getuid()===0)g="/usr/local/bin";else{let N=f.join(P.homedir(),"bin");l.existsSync(N)?g=N:g="/usr/local/bin"}l.existsSync(g)||l.mkdirSync(g,{recursive:!0});let v=f.basename(C),$=f.join(g,v);console.log(`Installing ${v} to ${$}...`);try{l.copyFileSync(C,$)}catch(d){throw d.code==="EBUSY"?new Error(`The file ${$} is currently in use. Please close any running instances and try again.`):d.code==="EACCES"||d.code==="EPERM"?new Error(`Permission denied while installing to ${$}. Try running with sudo or as administrator, or use -p to specify a custom path.`):d}process.platform!=="win32"&&l.chmodSync($,"755"),l.rmSync(b,{recursive:!0,force:!0}),console.log("Installation successful!")}catch(n){console.error(`Error: ${n.message}`),process.exit(1)}}Z();
|
||||
`;
|
||||
}
|
||||
function ensureOptionValue(argv, index, option) {
|
||||
const value = argv[index + 1];
|
||||
if (!value || value.startsWith("-")) {
|
||||
throw new Error(`Missing value for ${option}.`);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
function parseCliArgs(argv) {
|
||||
const envDryRun = process.env.TEST_MODE;
|
||||
const dryRunLevelFromEnv = envDryRun && /^\d+$/.test(envDryRun) ? parseInt(envDryRun, 10) : 0;
|
||||
const opts = {
|
||||
releasesJsonOnly: false,
|
||||
listOnly: false,
|
||||
debug: false,
|
||||
help: false,
|
||||
dryRunLevel: dryRunLevelFromEnv,
|
||||
positionals: []
|
||||
};
|
||||
for (let i = 0; i < argv.length; i++) {
|
||||
const arg = argv[i];
|
||||
switch (arg) {
|
||||
case "-h":
|
||||
case "--help":
|
||||
opts.help = true;
|
||||
break;
|
||||
case "--dry-run": {
|
||||
const next = argv[i + 1];
|
||||
if (next && /^\d+$/.test(next)) {
|
||||
opts.dryRunLevel = parseInt(next, 10);
|
||||
i++;
|
||||
} else {
|
||||
opts.dryRunLevel = 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "-l":
|
||||
case "--list": {
|
||||
opts.listOnly = true;
|
||||
const next = argv[i + 1];
|
||||
if (next && !next.startsWith("-")) {
|
||||
opts.listRepo = next;
|
||||
i++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "-a":
|
||||
case "--app-name":
|
||||
opts.appName = ensureOptionValue(argv, i, arg);
|
||||
i++;
|
||||
break;
|
||||
case "-f":
|
||||
case "--file-name":
|
||||
opts.fileName = ensureOptionValue(argv, i, arg);
|
||||
i++;
|
||||
break;
|
||||
case "-b":
|
||||
case "--binary-name":
|
||||
opts.binaryName = ensureOptionValue(argv, i, arg);
|
||||
i++;
|
||||
break;
|
||||
case "-t":
|
||||
case "--file-type": {
|
||||
const fileType = ensureOptionValue(argv, i, arg).toLowerCase();
|
||||
const knownType = /^(archive|package|zip|gzip|gz|tar|tar\.gz|tgz|deb|pkg|rpm)$/i;
|
||||
if (!knownType.test(fileType)) {
|
||||
throw new Error(`Unknown asset type: ${fileType}`);
|
||||
}
|
||||
opts.fileType = fileType;
|
||||
i++;
|
||||
break;
|
||||
}
|
||||
case "-p":
|
||||
case "--install-path":
|
||||
opts.installPath = ensureOptionValue(argv, i, arg);
|
||||
i++;
|
||||
break;
|
||||
case "-o":
|
||||
case "--output-directory":
|
||||
opts.outputDirectory = ensureOptionValue(argv, i, arg);
|
||||
i++;
|
||||
break;
|
||||
case "-j":
|
||||
case "--releases-json":
|
||||
opts.releasesJsonOnly = true;
|
||||
break;
|
||||
case "--system":
|
||||
opts.systemOverride = ensureOptionValue(argv, i, arg);
|
||||
i++;
|
||||
break;
|
||||
case "--arch":
|
||||
opts.archOverride = ensureOptionValue(argv, i, arg);
|
||||
i++;
|
||||
break;
|
||||
case "-k":
|
||||
case "--token":
|
||||
opts.token = ensureOptionValue(argv, i, arg);
|
||||
i++;
|
||||
break;
|
||||
case "-d":
|
||||
case "--debug":
|
||||
opts.debug = true;
|
||||
break;
|
||||
default:
|
||||
if (arg.startsWith("-")) {
|
||||
throw new Error(`Unknown option: ${arg}`);
|
||||
}
|
||||
opts.positionals.push(arg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return opts;
|
||||
}
|
||||
function validateOutputDirectory(outputDirectory) {
|
||||
const resolvedPath = path3.resolve(outputDirectory);
|
||||
if (!fs3.existsSync(resolvedPath) || !fs3.statSync(resolvedPath).isDirectory()) {
|
||||
throw new Error(`Output directory "${resolvedPath}" does not exist.`);
|
||||
}
|
||||
return resolvedPath;
|
||||
}
|
||||
function getInstallDir(installPath) {
|
||||
if (installPath) {
|
||||
return path3.resolve(installPath);
|
||||
}
|
||||
if (process.platform === "win32") {
|
||||
const localAppData = process.env.LOCALAPPDATA || path3.join(os2.homedir(), "AppData", "Local");
|
||||
return path3.join(localAppData, "bin");
|
||||
}
|
||||
const isRoot = process.getuid && process.getuid() === 0;
|
||||
if (isRoot) {
|
||||
return "/usr/local/bin";
|
||||
}
|
||||
const homeBin = path3.join(os2.homedir(), "bin");
|
||||
if (fs3.existsSync(homeBin)) {
|
||||
return homeBin;
|
||||
}
|
||||
return "/usr/local/bin";
|
||||
}
|
||||
function installSystemPackage(downloadPath) {
|
||||
const fileName = path3.basename(downloadPath).toLowerCase();
|
||||
const command = fileName.endsWith(".deb") ? { binary: "dpkg", args: ["-i", downloadPath] } : fileName.endsWith(".pkg") ? { binary: "installer", args: ["-pkg", downloadPath, "-target", "/"] } : fileName.endsWith(".rpm") ? { binary: "rpm", args: ["-i", downloadPath] } : void 0;
|
||||
if (!command) {
|
||||
throw new Error(`Unsupported package type: ${fileName}`);
|
||||
}
|
||||
const isRoot = process.getuid && process.getuid() === 0;
|
||||
const commandToRun = isRoot ? command.binary : "sudo";
|
||||
const argsToRun = isRoot ? command.args : [command.binary, ...command.args];
|
||||
const result = (0, import_child_process2.spawnSync)(commandToRun, argsToRun, { stdio: "inherit" });
|
||||
if (result.status !== 0) {
|
||||
throw new Error(`Failed to install package using ${commandToRun} ${argsToRun.join(" ")}.`);
|
||||
}
|
||||
}
|
||||
async function run() {
|
||||
let tempDir;
|
||||
try {
|
||||
const options = parseCliArgs(process.argv.slice(2));
|
||||
const repository = options.listRepo || options.positionals[0];
|
||||
if (options.help || !repository) {
|
||||
console.log(usage());
|
||||
process.exit(options.help ? 0 : 1);
|
||||
}
|
||||
const token = options.token || process.env.GITHUB_TOKEN;
|
||||
if (options.listOnly) {
|
||||
const release2 = await fetchLatestRelease(repository, token);
|
||||
release2.assets.forEach((asset2) => console.log(`- ${asset2.browser_download_url}`));
|
||||
process.exit(0);
|
||||
}
|
||||
const toolName = repository.split("/").pop() || repository;
|
||||
const appName = options.appName || toolName.charAt(0).toUpperCase() + toolName.slice(1);
|
||||
const binaryOption = options.binaryName || toolName;
|
||||
const [binarySource, binaryDestination] = binaryOption.includes(":") ? [binaryOption.split(":")[0], binaryOption.split(":")[1]] : [binaryOption, binaryOption];
|
||||
if (options.releasesJsonOnly) {
|
||||
const rawRelease = await fetchLatestReleaseRaw(repository, token);
|
||||
const outputBase = binaryDestination || toolName;
|
||||
const outputName = `${outputBase}.releases.json`;
|
||||
const outputPath = options.outputDirectory ? path3.join(validateOutputDirectory(options.outputDirectory), outputName) : outputName;
|
||||
fs3.writeFileSync(outputPath, rawRelease, "utf8");
|
||||
console.log(`Downloaded GitHub releases to ${outputPath}.`);
|
||||
process.exit(0);
|
||||
}
|
||||
const platformInfo = getPlatformInfo({
|
||||
system: options.systemOverride,
|
||||
arch: options.archOverride
|
||||
});
|
||||
console.log(`Fetching latest release for ${repository}...`);
|
||||
const release = await fetchLatestRelease(repository, token);
|
||||
const asset = getMatchingAsset(release.assets, platformInfo, {
|
||||
fileName: options.fileName,
|
||||
fileType: options.fileType
|
||||
});
|
||||
const version = release.tag_name.replace(/^v/i, "");
|
||||
const downloadUrl = asset.browser_download_url;
|
||||
console.log(`Will download '${appName}' version: ${version}`);
|
||||
console.log(`Download URL: "${downloadUrl}".`);
|
||||
if (options.dryRunLevel > 0) {
|
||||
process.exit(0);
|
||||
}
|
||||
if (options.outputDirectory) {
|
||||
const outputDir = validateOutputDirectory(options.outputDirectory);
|
||||
const outputPath = path3.join(outputDir, path3.basename(downloadUrl));
|
||||
console.log(`Downloading '${appName}' version ${version} to '${outputPath}'...`);
|
||||
await downloadAsset(downloadUrl, outputPath, token);
|
||||
process.exit(0);
|
||||
}
|
||||
tempDir = fs3.mkdtempSync(path3.join(os2.tmpdir(), "setup-gh-release-"));
|
||||
const downloadPath = path3.join(tempDir, asset.name);
|
||||
await downloadAsset(downloadUrl, downloadPath, token);
|
||||
if (/\.(deb|pkg|rpm)$/i.test(asset.name)) {
|
||||
installSystemPackage(downloadPath);
|
||||
console.log("Installation successful!");
|
||||
process.exit(0);
|
||||
}
|
||||
const extractDir = path3.join(tempDir, "extract");
|
||||
console.log(`Extracting ${asset.name}...`);
|
||||
await extractAsset(downloadPath, extractDir);
|
||||
let binaryPattern;
|
||||
if (binarySource.startsWith("~")) {
|
||||
binaryPattern = new RegExp(binarySource.substring(1), "i");
|
||||
} else {
|
||||
binaryPattern = binarySource;
|
||||
}
|
||||
const binaryPath = findBinary(extractDir, binaryPattern, options.debug, console.log);
|
||||
if (!binaryPath) {
|
||||
throw new Error(`Could not find binary "${binarySource}" in the extracted asset.`);
|
||||
}
|
||||
const installDir = getInstallDir(options.installPath);
|
||||
if (!fs3.existsSync(installDir)) {
|
||||
fs3.mkdirSync(installDir, { recursive: true });
|
||||
}
|
||||
const finalName = binaryDestination || path3.basename(binaryPath);
|
||||
const destPath = path3.join(installDir, finalName);
|
||||
console.log(`Installing ${finalName} to ${destPath}...`);
|
||||
try {
|
||||
fs3.copyFileSync(binaryPath, destPath);
|
||||
} catch (err) {
|
||||
if (err.code === "EBUSY") {
|
||||
throw new Error(`The file ${destPath} is currently in use. Please close any running instances and try again.`);
|
||||
}
|
||||
if (err.code === "EACCES" || err.code === "EPERM") {
|
||||
throw new Error(`Permission denied while installing to ${destPath}. Try running with sudo or as administrator, or use -p to specify a custom path.`);
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
if (process.platform !== "win32") {
|
||||
fs3.chmodSync(destPath, "755");
|
||||
}
|
||||
console.log("Installation successful!");
|
||||
process.exit(0);
|
||||
} catch (error) {
|
||||
if (error?.message) {
|
||||
console.error(`Error: ${error.message}`);
|
||||
} else {
|
||||
console.error("Error: Unknown failure.");
|
||||
}
|
||||
process.exit(1);
|
||||
} finally {
|
||||
if (tempDir && fs3.existsSync(tempDir)) {
|
||||
fs3.rmSync(tempDir, { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
run();
|
||||
|
||||
Reference in New Issue
Block a user