WIP new github-releases

This commit is contained in:
Jan Andrle 2024-05-12 10:26:24 +02:00
parent 7e64148005
commit bf5ee3437d
Signed by: jaandrle
GPG Key ID: B3A25AED155AFFAB
3 changed files with 501 additions and 11 deletions

View File

@ -0,0 +1,245 @@
{
"target": "/home/jaandrle/bin/",
"packages": [
{
"repository": "shiftkey/desktop",
"name": "GitHub Desktop",
"group": "dev",
"file_name": "github-desktop",
"exec": "yes",
"description": "Fork of GitHub Desktop to support various Linux distributions",
"last_update": "2024-03-31T17:49:36Z",
"downloads": "/home/jaandrle/bin/github-desktop",
"version": "release-3.3.12-linux2",
"glare": ".*x86_64.*.AppImage"
},
{
"repository": "jaandrle/jaaCSS-cli",
"name": "jaaCSS",
"description": "EXPERIMENT Helper for managing functional CSS classes",
"group": "dev",
"file_name": "jaaCSS.js",
"exec": "yes",
"downloads": "/home/jaandrle/bin/jaaCSS.js",
"version": "v1.3.2",
"last_update": "2022-09-02T13:33:16Z",
"glare": "jaaCSS.js"
},
{
"repository": "th-ch/youtube-music",
"name": "youtube-music",
"description": "YouTube Music Desktop App bundled with custom plugins (and built-in ad blocker / downloader)",
"group": "nondev",
"file_name": "youtube-music",
"exec": "yes",
"last_update": "2024-04-13T14:34:34Z",
"downloads": "/home/jaandrle/bin/youtube-music",
"version": "v3.3.6",
"glare": "AppImage"
},
{
"repository": "ArchGPT/insomnium",
"name": "insomnium",
"description": "Insomnium is a fast local API testing tool that is privacy-focused and 100% local. For testing GraphQL, REST, WebSockets and gRPC. This is a fork of Kong/insomnia",
"group": "dev",
"file_name": "insomnium",
"exec": "yes",
"last_update": "2023-11-13T10:03:28Z",
"downloads": "/home/jaandrle/bin/insomnium",
"tag_name_regex": "core@.*",
"version": "core@0.2.3-a",
"glare": "AppImage"
},
{
"repository": "Kong/insomnia",
"name": "insomnia",
"description": "The open-source, cross-platform API client for GraphQL, REST, and gRPC.",
"group": "skip",
"file_name": "insomnia",
"exec": "yes",
"last_update": "2024-04-17T16:38:03Z",
"downloads": "/home/jaandrle/bin/insomnia",
"tag_name_regex": "core@.*",
"version": "core@9.0.0-beta.4",
"glare": "AppImage"
},
{
"repository": "rvpanoz/luna",
"name": "luna",
"description": "Manage npm dependencies through a modern UI.",
"group": "skip",
"file_name": "luna",
"exec": "yes",
"glare": "AppImage"
},
{
"repository": "angela-d/wifi-channel-watcher",
"name": "wifi-channel-watcher",
"group": "skip",
"file_name": "wifi-channel-watcher",
"exec": "no",
"description": "Monitor channel usage of neighboring routers & get an alert if your active channel is not optimal.\tTroubleshoot wifi without lifting a finger!"
},
{
"repository": "vinceliuice/Tela-circle-icon-theme",
"name": "Tela-circle-icon-theme",
"description": "Tela-circle-icon-theme",
"group": "skip",
"file_name": "tela-circle-icon-theme.zip",
"last_update": "2021-07-19T14:12:05Z",
"exec": "no"
},
{
"repository": "AppImage/AppImageKit",
"name": "AppImageKit",
"group": "skip",
"file_name": "appimagekit",
"exec": "yes",
"description": "Package desktop applications as AppImages that run on common Linux-based operating systems, such as RHEL, CentOS, openSUSE, SLED, Ubuntu, Fedora, debian and derivatives. Join #AppImage on irc.freenode.net",
"glare": ".*x86_64.*.AppImage"
},
{
"repository": "dynobo/normcap",
"name": "NormCap",
"description": "Switched to flatpak version | OCR powered screen-capture tool to capture information instead of images",
"group": "skip",
"file_name": "normcap",
"exec": "yes",
"last_update": "2023-12-12T22:23:37Z",
"downloads": "/home/jaandrle/bin/normcap",
"version": "v0.5.2"
},
{
"repository": "upscayl/upscayl",
"name": "upscayl",
"description": "🆙 Upscayl - Free and Open Source AI Image Upscaler for Linux, MacOS and Windows built with Linux-First philosophy.",
"group": "nondev",
"file_name": "upscayl",
"exec": "yes",
"last_update": "2024-04-25T04:29:38Z",
"downloads": "/home/jaandrle/bin/upscayl",
"version": "v2.11.0",
"glare": "AppImage"
},
{
"repository": "RasmusLindroth/tut",
"name": "tut",
"description": "TUI for Mastodon with vim inspired keys",
"group": "nondev",
"file_name": "tut",
"exec": "yes",
"last_update": "2023-01-26T17:48:00Z",
"downloads": "/home/jaandrle/bin/tut",
"version": "2.0.1",
"glare": "tut-amd64"
},
{
"repository": "sunner/ChatALL",
"name": "ChatALL",
"description": " Concurrently chat with ChatGPT, Bing Chat, bard, Alpaca, Vincuna, Claude, ChatGLM, MOSS, iFlytek Spark, ERNIE and more, discover the best answers",
"group": "skip",
"file_name": "chatall",
"exec": "yes",
"last_update": "2024-04-14T02:09:42Z",
"downloads": "/home/jaandrle/bin/chatall",
"version": "v1.71.100",
"glare": ".*x86_64.*.AppImage"
},
{
"repository": "jaandrle/bs",
"name": "bs",
"description": "The simplest possible build system using executables",
"group": "dev",
"file_name": "bs",
"exec": "yes",
"last_update": "2024-03-28T13:16:41Z",
"downloads": "/home/jaandrle/bin/bs",
"version": "v0.7.4",
"glare": "bs-linux"
},
{
"repository": "h3poteto/fedistar",
"name": "Fedistar",
"description": "Multi-column Mastodon, Pleroma, and Friendica client for desktop",
"group": "nondev",
"file_name": "fedistar",
"exec": "yes",
"last_update": "2024-04-09T14:08:12Z",
"downloads": "/home/jaandrle/bin/fedistar",
"version": "v1.9.3",
"glare": ".*amd64.*.AppImage"
},
{
"repository": "ollama/ollama",
"name": "ollama",
"description": "Get up and running with Llama 2 and other large language models locally",
"group": "ai",
"file_name": "ollama",
"exec": "yes",
"last_update": "2024-04-28T17:51:17Z",
"downloads": "/home/jaandrle/bin/ollama",
"version": "v0.1.33-rc5",
"glare": "linux-amd64"
},
{
"repository": "neovim/neovim",
"name": "neovim",
"tag_name_regex": "v.*",
"description": "Vim-fork focused on extensibility and usability",
"group": "skip",
"file_name": "nvim",
"exec": "yes",
"downloads": "/home/jaandrle/bin/nvim",
"version": "v0.9.5",
"last_update": "2023-12-30T13:31:47Z"
},
{
"repository": "viarotel-org/escrcpy",
"name": "Escrcpy",
"description": "📱 Graphical Scrcpy to display and control Android, devices powered by Electron. | 使用图形化的 Scrcpy 显示和控制您的 Android 设备,由 Electron 驱动。",
"group": "dev",
"file_name": "escrcpy",
"exec": "yes",
"last_update": "2024-04-28T02:36:35Z",
"downloads": "/home/jaandrle/bin/escrcpy",
"version": "v1.18.3",
"glare": ".*x86_64.*.AppImage"
},
{
"repository": "drovp/drovp",
"name": "drovp",
"description": "Desktop app for encoding, converting, upscaling, and much more.",
"group": "dev-test",
"file_name": "drovp",
"exec": "yes",
"last_update": "2023-12-06T11:30:02Z",
"downloads": "/home/jaandrle/bin/drovp",
"version": "0.8.0",
"glare": "x64.AppImage"
},
{
"repository": "janhq/jan",
"name": "Jan",
"description": "Jan is an open source alternative to ChatGPT that runs 100% offline on your computer",
"group": "ai",
"file_name": "jan",
"exec": "yes",
"last_update": "2024-04-25T06:07:39Z",
"downloads": "/home/jaandrle/bin/jan",
"version": "v0.4.12",
"glare": ".*x86_64.*.AppImage"
},
{
"repository": "Bin-Huang/chatbox",
"name": "Chatbox",
"description": "Chatbox is a desktop client for ChatGPT, Claude and other LLMs, available on Windows, Mac, Linux",
"group": "ai",
"file_name": "Chatbox",
"exec": "yes",
"last_update": "2024-04-18T08:15:47Z",
"downloads": "/home/jaandrle/bin/Chatbox",
"version": "v1.3.5",
"glare": ".*x86_64.*.AppImage"
}
]
}

View File

@ -7,9 +7,9 @@
"file_name": "github-desktop", "file_name": "github-desktop",
"exec": "yes", "exec": "yes",
"description": "Fork of GitHub Desktop to support various Linux distributions", "description": "Fork of GitHub Desktop to support various Linux distributions",
"last_update": "2024-02-04T22:31:22Z", "last_update": "2024-03-31T17:49:36Z",
"downloads": "/home/jaandrle/bin/github-desktop", "downloads": "/home/jaandrle/bin/github-desktop",
"version": "release-3.3.8-linux2" "version": "release-3.3.12-linux2"
}, },
{ {
"repository": "jaandrle/jaaCSS-cli", "repository": "jaandrle/jaaCSS-cli",
@ -29,9 +29,9 @@
"group": "nondev", "group": "nondev",
"file_name": "youtube-music", "file_name": "youtube-music",
"exec": "yes", "exec": "yes",
"last_update": "2024-02-20T12:07:29Z", "last_update": "2024-03-26T10:58:44Z",
"downloads": "/home/jaandrle/bin/youtube-music", "downloads": "/home/jaandrle/bin/youtube-music",
"version": "v3.3.2" "version": "v3.3.5"
}, },
{ {
"repository": "ArchGPT/insomnium", "repository": "ArchGPT/insomnium",
@ -141,9 +141,9 @@
"group": "dev", "group": "dev",
"file_name": "bs", "file_name": "bs",
"exec": "yes", "exec": "yes",
"last_update": "2023-06-30T07:48:58Z", "last_update": "2024-03-28T13:16:41Z",
"downloads": "/home/jaandrle/bin/bs", "downloads": "/home/jaandrle/bin/bs",
"version": "v0.7.3" "version": "v0.7.4"
}, },
{ {
"repository": "h3poteto/fedistar", "repository": "h3poteto/fedistar",
@ -152,9 +152,9 @@
"group": "nondev", "group": "nondev",
"file_name": "fedistar", "file_name": "fedistar",
"exec": "yes", "exec": "yes",
"last_update": "2024-02-29T11:08:37Z", "last_update": "2024-03-29T15:39:36Z",
"downloads": "/home/jaandrle/bin/fedistar", "downloads": "/home/jaandrle/bin/fedistar",
"version": "v1.9.0" "version": "v1.9.2"
}, },
{ {
"repository": "ollama/ollama", "repository": "ollama/ollama",
@ -186,9 +186,9 @@
"group": "dev", "group": "dev",
"file_name": "escrcpy", "file_name": "escrcpy",
"exec": "yes", "exec": "yes",
"last_update": "2024-03-13T03:05:48Z", "last_update": "2024-03-29T03:30:14Z",
"downloads": "/home/jaandrle/bin/escrcpy", "downloads": "/home/jaandrle/bin/escrcpy",
"version": "v1.17.3" "version": "v1.17.8"
}, },
{ {
"repository": "drovp/drovp", "repository": "drovp/drovp",
@ -218,7 +218,21 @@
"description": "Chatbox is a desktop client for ChatGPT, Claude and other LLMs, available on Windows, Mac, Linux", "description": "Chatbox is a desktop client for ChatGPT, Claude and other LLMs, available on Windows, Mac, Linux",
"group": "ai", "group": "ai",
"file_name": "Chatbox", "file_name": "Chatbox",
"exec": "yes" "exec": "yes",
"last_update": "2024-03-15T15:58:59Z",
"downloads": "/home/jaandrle/bin/Chatbox",
"version": "v1.3.1"
},
{
"repository": "Helium314/HeliBoard",
"name": "HeliBoard",
"description": "Customizable and privacy-conscious open-source keyboard",
"group": "android",
"file_name": "heliboard.apk",
"exec": "no",
"last_update": "2024-03-31T20:11:03Z",
"downloads": "/home/jaandrle/bin/heliboard.apk",
"version": "v1.0"
} }
] ]
} }

231
bin/github-releases.mjs Executable file
View File

@ -0,0 +1,231 @@
#!/usr/bin/env nodejsscript
/* jshint esversion: 11,-W097, -W040, module: true, node: true, expr: true, undef: true *//* global echo, $, pipe, s, fetch, cyclicLoop */
// "\x1b[38;2;150;150;150m"
// https://talyian.github.io/ansicolors/
/**
* Represents a package record stored locally
*
* @typedef ConfigPackage
* @type {Object}
* @property {string} repository - Repo in the form `<owner>/<repo>`
* @property {string} name - Name/Identifier
* @property {string} description - A description of the repo/package
* @property {string} file_name - The name of the file as stored locally
* @property {"yes"|"no"} exec - Whether the file is executable
* @property {string} last_update - The date and time of the last update
* @property {string} downloads - The path to the file
* @property {string} version - The version of the file
* @property {string} tag_name_regex - Filter only matching releases
* */
/**
* @typedef Config
* @type {{ packages: ConfigPackage[] }}
* */
/**
* Represents a GitHub release information.
*
* @typedef {Object} GitHubRelease
* @property {number} id - The ID of the GitHub release.
* @property {string} url - The URL of the GitHub release.
* @property {string} assets_url - The URL of the assets associated with the GitHub release.
* @property {string} html_url - The HTML URL of the GitHub release.
* @property {string} tag_name - The tag name of the GitHub release.
* @property {string} published_at - The publication date of the GitHub release.
*/
import { join } from "node:path";
const path_config= $.xdg.config`github-releases`;
const path_config_json= join(path_config, "config.json");
const path_config_lock= join(path_config, "lock");
const path_temp= $.xdg.temp`github-releases.json`;
const url_api= "https://api.github.com/repos/";
const url_download= "https://glare.now.sh/"; // https://github.com/Contextualist/glare
const css= echo.css`
.pkg { color: lightcyan; }
.ok { color: lightgreen; }
.err { color: lightred; }
.skip { color: red; color: gray; }
.spin { display: list-item; list-style: --terminal-spin; }
`;
$.api()
.version("2.0.0")
.describe("Helper for working with “packages” stored in GitHub releases.")
.command("config [mode]", [ "Config (file), use `mode` with these options:",
"- `edit`: opens config file in terminal editor using `$EDITOR` (defaults to vim)",
"- `path`: prints path to config file"
])
.action(async function(mode= "path"){
switch(mode){
case "path": echo(path_config_json); break;
case "edit":
const editor= $.env.EDITOR || "vim";
await s.runA`${editor} ${path_config_json}`.pipe(process.stdout);
break;
default:
echo(`Unknown mode: '${mode}'. See '--help' for details.`);
}
$.exit(0);
})
.command("ls", [ "Lists registered packages",
"Repositories marked with `-` signifies that the package is in the 'skip' group.",
"These are registered by this script but not managed by it (updates, etc).",
"Repositories marked with `+` signify that updates of the package are checked."
])
.option("--group, -G", "Filter by group")
.option("--repository, -R", "Filter by repository")
.action(function(filter){
const config = readConfig();
for(const { repository, version, description, group } of grepPackages(config, filter))
if(group!=="skip")
echo(`+ %c${repository}%c@${version ? version : "—"}: %c${description}`, css.pkg, css.unset, css.skip);
else
echo(`- %c${repository}: ${description}`, css.skip);
$.exit(0);
})
.command("check", "Shows/checks updates for registered packages")
.option("--group, -G", "Filter by group")
.option("--repository, -R", "Filter by repository")
.option("--cache", "Use cache [yes, no]", "yes")
.action(async function({ cache, ...filter }){
const config = readConfig();
const results= await check(grepPackages(config, filter), cache);
for(const { status, value } of results)
echoPkgStatus(status, value);
if(!results.length) echo("Nothing to do.");
$.exit(0);
})
.command("update", "Updates registered packages")
.option("--group, -G", "Filter by group")
.option("--repository, -R", "Filter by repository")
.action(async function(filter){
if(s.test("-f", path_config_lock))
return $.error(`The lock file '${path_config_lock}' already exists! Check if some other instance is running.`);
s.touch(path_config_lock);
const config = readConfig();
const results= await check(grepPackages(config, filter));
const start= Date.now();
let done= 0;
let todo= [];
echo("Collecting packages to update…");
for(const { status, value } of results){
if(status!==3 || value.local.group==="skip") continue;
echo("%c"+value.local.repository, css.pkg);
todo.push(download(
value,
()=> done+= 1,
config.target
));
}
const { length }= todo;
if(!length){
echo("%cAll up-to-date!%c Nothing to do.", css.ok);
} else {
const id= setInterval(()=>
echo.use("-R", `%cUpdating packages (${done}/${length})`, css.spin), 500);
const updates= await Promise.allSettled(todo);
clearInterval(id);
echo("Updating packages completed:");
for (const { status, value, reason } of updates) {
if(status==="rejected"){
echo("%c✗ "+reason.local.repository+": %c"+reason.err, css.err);
continue;
}
const { local, remote }= value;
echo("%c✓ "+local.repository+"%c@"+remote.tag_name, css.ok, css.skip);
}
s.echo(JSON.stringify(config, null, "\t")).to(path_config_json);
}
s.rm(path_config_lock);
$.exit(0);
})
.parse();
import { createWriteStream } from "node:fs";
async function download(value, onprogress, target){
const { repository, glare }= value.local;
const { tag_name }= value.remote;
if(!glare) return Promise.reject({ err: "Missing 'glare' in config.", ...value });
const response= await fetch(url_download+repository+`@${tag_name}/${glare}`);
const buffer= Buffer.from(await response.arrayBuffer());
const downloads= target+value.local.file_name;
const ws= createWriteStream(downloads, { flags: "w" });
ws.write(buffer);
Object.assign(value.local, {
last_update: value.remote.published_at,
version: value.remote.tag_name,
downloads
});
if(value.local.exec==="yes")
s.chmod("+x", downloads);
onprogress();
return value;
}
function grepPackages({ packages }, { group, repository }){
const f= {};
let is_filter= false;
if(group){ is_filter= true; f.group= group; }
if(repository){ is_filter= true; f.repository= repository; }
if(!is_filter) return packages;
return packages.filter(r=> Object.keys(f).every(k=> r[k]===f[k]));
}
function echoPkgStatus(status, { local, remote }){
let status_css, status_text;
if(local.group==="skip"){
status_text= "skipped";
status_css= "skip";
} else {
status_text= status===3 ? "outdated" : "up-to-date";
status_css= status===3 ? "err" : "ok";
}
echo((status_text==="outdated" ? "+" : "-") + " %c"+local.repository + "%c: %c"+status_text+"%c (%c"+remote.tag_name+"%c)",
css.pkg, css.unset, css[status_css], css.unset, css.skip, css.unset);
}
/**
* @param {Config.packages} packages
* @return {{ status: 0|1|2|3, value: { remote: GitHubRelease, local: ConfigPackage } }}
* */
async function check(packages, cache){
return (await pipe(
ps=> ps.map(p=> fetchRelease(p, cache).then(remote=> ({ local: p, remote }))),
ps=> Promise.allSettled(ps)
)(packages))
.map(({ status, ...v })=> status==="rejected" ?
{ status: -1, value: v } :
{ status: packageStatus(v.value.local, v.value.remote), value: v.value })
.filter(({ status, value })=> {
if(status!==-1) return true;
echo("%c"+value.reason, css.err);
});
}
/** @type {(local: ConfigPackage, remote: GitHubRelease)=> 0|1|2|3} */
function packageStatus({ last_update: local }, { published_at: remote }){
if(!remote) return 0;
if(!local) return 3;
if(remote===local) return 1;
return 2+(local<remote);
}
/** @param {ConfigPackage} package */
async function fetchRelease({ repository, tag_name_regex }, cache){
const headers= { 'User-Agent': 'node' };
if(cache==="no") headers['Cache-Control'] = 'no-cache';
const url= url_api+repository+"/releases";
const releases= await fetch(url, { headers }).then(res=> res.json());
if(releases.message) return $.error(url+": "+releases.message);
return releases.find(function ({ draft, published_at, tag_name }){
if(draft||!published_at) return false;
if(!tag_name_regex) return true;
return (new RegExp(tag_name_regex, 'g')).test(tag_name);
});
}
function readConfig(){
if(!s.test("-f", path_config_json)) return { packages: [] };
const out= Object.assign({ target: "~/bin/" },
s.cat(path_config_json).xargs(JSON.parse));
if(out.target.startsWith("~/")) out.target= $.xdg.home(out.target.slice(2));
return out;
}