From bf5ee3437d0f46c046f78551ce9ecd4c704bbc08 Mon Sep 17 00:00:00 2001 From: Jan Andrle Date: Sun, 12 May 2024 10:26:24 +0200 Subject: [PATCH] :zap: WIP new github-releases --- .config/github-releases/config.json | 245 ++++++++++++++++++++++++++++ bin/github-releases.json | 36 ++-- bin/github-releases.mjs | 231 ++++++++++++++++++++++++++ 3 files changed, 501 insertions(+), 11 deletions(-) create mode 100644 .config/github-releases/config.json create mode 100755 bin/github-releases.mjs diff --git a/.config/github-releases/config.json b/.config/github-releases/config.json new file mode 100644 index 0000000..349198f --- /dev/null +++ b/.config/github-releases/config.json @@ -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" + } + ] +} \ No newline at end of file diff --git a/bin/github-releases.json b/bin/github-releases.json index e32efd2..005ed1b 100644 --- a/bin/github-releases.json +++ b/bin/github-releases.json @@ -7,9 +7,9 @@ "file_name": "github-desktop", "exec": "yes", "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", - "version": "release-3.3.8-linux2" + "version": "release-3.3.12-linux2" }, { "repository": "jaandrle/jaaCSS-cli", @@ -29,9 +29,9 @@ "group": "nondev", "file_name": "youtube-music", "exec": "yes", - "last_update": "2024-02-20T12:07:29Z", + "last_update": "2024-03-26T10:58:44Z", "downloads": "/home/jaandrle/bin/youtube-music", - "version": "v3.3.2" + "version": "v3.3.5" }, { "repository": "ArchGPT/insomnium", @@ -141,9 +141,9 @@ "group": "dev", "file_name": "bs", "exec": "yes", - "last_update": "2023-06-30T07:48:58Z", + "last_update": "2024-03-28T13:16:41Z", "downloads": "/home/jaandrle/bin/bs", - "version": "v0.7.3" + "version": "v0.7.4" }, { "repository": "h3poteto/fedistar", @@ -152,9 +152,9 @@ "group": "nondev", "file_name": "fedistar", "exec": "yes", - "last_update": "2024-02-29T11:08:37Z", + "last_update": "2024-03-29T15:39:36Z", "downloads": "/home/jaandrle/bin/fedistar", - "version": "v1.9.0" + "version": "v1.9.2" }, { "repository": "ollama/ollama", @@ -186,9 +186,9 @@ "group": "dev", "file_name": "escrcpy", "exec": "yes", - "last_update": "2024-03-13T03:05:48Z", + "last_update": "2024-03-29T03:30:14Z", "downloads": "/home/jaandrle/bin/escrcpy", - "version": "v1.17.3" + "version": "v1.17.8" }, { "repository": "drovp/drovp", @@ -218,7 +218,21 @@ "description": "Chatbox is a desktop client for ChatGPT, Claude and other LLMs, available on Windows, Mac, Linux", "group": "ai", "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" } ] } \ No newline at end of file diff --git a/bin/github-releases.mjs b/bin/github-releases.mjs new file mode 100755 index 0000000..87ce6ec --- /dev/null +++ b/bin/github-releases.mjs @@ -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 `/` + * @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 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; +}