From 62e9e8e2e2e4c846a9b0f78499577e4413b762ed Mon Sep 17 00:00:00 2001 From: Allen Bargi Date: Sun, 14 Sep 2025 11:52:56 +0200 Subject: [PATCH] Add Bash and Node.js cask_report scripts alongside Ruby version --- bash/bin/cask_report_bash | 88 +++++++++++++++++++++++++++++ bash/bin/cask_report_nodejs | 110 ++++++++++++++++++++++++++++++++++++ 2 files changed, 198 insertions(+) create mode 100755 bash/bin/cask_report_bash create mode 100755 bash/bin/cask_report_nodejs diff --git a/bash/bin/cask_report_bash b/bash/bin/cask_report_bash new file mode 100755 index 0000000..50fe803 --- /dev/null +++ b/bash/bin/cask_report_bash @@ -0,0 +1,88 @@ +#!/usr/bin/env bash +# Dependencies: tail, head, grep, cat, git, ansifilter +# TODO +# 1. argument to prevent sha update + +report() { + local type="$1" + local latest_sha_file="$HOME/.homebrew-${type}-latest-sha" + local cask_repo="/opt/homebrew/Library/Taps/homebrew/homebrew-${type}" + local version_regex='^[+-]\s*version\s+["\'""']?(.+?)["\'""']?$' + local homepage_regex='^\s+homepage\s+["\'""'](.+)["\'""']\s*$' + local -a updated added removed update_available + + cd "$cask_repo" || return 1 + + local old_sha + if [[ -f "$latest_sha_file" ]]; then + old_sha=$(<"$latest_sha_file") + else + old_sha=$(git log | head -20 | /usr/bin/tail -r | head -1 | ansifilter | sed -nE 's/([a-z0-9]{7})\s.+/\1/p') + fi + local latest_sha + latest_sha=$(git log | head -1 | ansifilter | sed -nE 's/([a-z0-9]{7,})\s.+/\1/p') + + mapfile -t installed_casks < <(brew list --cask) + local changes + changes=$(git diff --name-status "$old_sha" | grep Casks/ | ansifilter) + + while IFS=$'\t' read -r status path; do + [[ -z "$path" ]] && continue + local app + app=$(sed -nE 's/^Casks\/(.+)\.rb$/\1/p' <<<"$path") + [[ -z "$app" ]] && continue + + if [[ "$status" == 'D' ]]; then + removed+=("\033[94m${app}\033[0m") + else + local h homepage + h=$(cat "Casks/${app}.rb" | grep -E '^\s+homepage') + if [[ -z "$h" ]]; then + homepage="" + else + homepage=$(sed -nE "${homepage_regex}" <<<"$h" | sed -nE 's/'"$homepage_regex"'/\1/p') + fi + if [[ "$status" == 'A' ]]; then + added+=("\033[94m${app}\033[0m [\033[92m${homepage}\033[0m]") + elif [[ "$status" == 'M' ]]; then + local version_diff last_version current_version + version_diff=$(git diff "$old_sha" "Casks/${app}.rb" | grep -E '^[+-]\s+version') + if [[ -n "$version_diff" ]]; then + last_version=$(sed -n 1p <<<"$version_diff" | sed -nE 's/'"$version_regex"'/\1/p') + current_version=$(sed -n 2p <<<"$version_diff" | sed -nE 's/'"$version_regex"'/\1/p') + fi + local needs_update="" + if [[ -n "$version_diff" ]]; then + if printf '%s\n' "${installed_casks[@]}" | grep -qx "$app"; then + needs_update=1 + update_available+=("✨ \033[32m${app}\033[0m") + fi + fi + updated+=("\033[94m${app}\033[0m${needs_update:+✨} [\033[92m${homepage}\033[0m]${version_diff:+ \${last_version} \033[33m<\033[0m \${current_version}}") + fi + fi + done <<<"$changes" + + echo -e "\n\033[35m🔮 ${type^^}\033[0m" + if ((${#updated[@]})); then + echo -e "\033[95mUPDATED\033[0m" + printf '%s\n' "${updated[@]}" + fi + if ((${#added[@]})); then + echo -e "\033[95mADDED\033[0m" + printf '%s\n' "${added[@]}" + fi + if ((${#removed[@]})); then + echo -e "\033[95mREMOVED\033[0m" + printf '%s\n' "${removed[@]}" + fi + if ((${#update_available[@]})); then + echo -e "\033[95mOUTDATED\033[0m" + printf '%s\n' "${update_available[@]}" + fi + echo -e "\033[90mOld SHA: ${old_sha} New SHA: ${latest_sha}\033[0m" + + printf '%s' "$latest_sha" >"$latest_sha_file" +} + +report cask diff --git a/bash/bin/cask_report_nodejs b/bash/bin/cask_report_nodejs new file mode 100755 index 0000000..076321b --- /dev/null +++ b/bash/bin/cask_report_nodejs @@ -0,0 +1,110 @@ +#!/usr/bin/env node +// Dependencies: tail, head, grep, cat, git, ansifilter +// TODO +// 1. argument to prevent sha update + +const fs = require('fs'); +const os = require('os'); +const { execSync } = require('child_process'); + +function color(code, str) { + return `\u001b[${code}m${str}\u001b[0m`; +} + +function report(type) { + const latestShaFile = `${os.homedir()}/.homebrew-${type}-latest-sha`; + const caskRepo = `/opt/homebrew/Library/Taps/homebrew/homebrew-${type}`; + const versionRegex = /^[+-]\s*version\s+['"]?(.+?)['"]?$/; + const homepageRegex = /^\s+homepage\s+["'](.+)["']\s*$/; + const updated = []; + const added = []; + const removed = []; + const updateAvailable = []; + + process.chdir(caskRepo); + + let oldSha; + if (fs.existsSync(latestShaFile)) { + oldSha = fs.readFileSync(latestShaFile, 'utf8').trim(); + } else { + const output = execSync('git log | head -20 | /usr/bin/tail -r | head -1 | ansifilter', { encoding: 'utf8' }); + const m = output.match(/([a-z0-9]{7})\s.+/); + oldSha = m ? m[1] : ''; + } + const latestShaOutput = execSync('git log | head -1 | ansifilter', { encoding: 'utf8' }); + const latestShaMatch = latestShaOutput.match(/([a-z0-9]{7,})\s.+/); + const latestSha = latestShaMatch ? latestShaMatch[1] : ''; + + const installedCasks = execSync('brew list --cask', { encoding: 'utf8' }).trim().split('\n'); + const changes = execSync(`git diff --name-status ${oldSha} | grep Casks/ | ansifilter`, { encoding: 'utf8' }).trim().split('\n'); + + const apps = changes.map(c => { + const [status, path] = c.split('\t'); + const m = path.match(/^Casks\/(.+)\.rb$/); + return m ? { status, name: m[1] } : null; + }).filter(Boolean); + + apps.forEach(a => { + if (a.status === 'D') { + removed.push(color('94', a.name)); + } else { + const h = execSync(`cat Casks/${a.name}.rb | grep -E '^\\s+homepage'`, { encoding: 'utf8' }); + const homepageMatch = h.match(homepageRegex); + const homepage = homepageMatch ? homepageMatch[1] : ''; + if (a.status === 'A') { + added.push(`${color('94', a.name)} [${color('92', homepage)}]`); + } else if (a.status === 'M') { + let versionDiff = ''; + try { + versionDiff = execSync(`git diff ${oldSha} Casks/${a.name}.rb | grep -E '^[+-]\\s+version'`, { encoding: 'utf8' }); + } catch {} + let lastVersion, currentVersion; + if (versionDiff) { + [lastVersion, currentVersion] = versionDiff.split('\n'); + if (lastVersion) { + const lm = lastVersion.match(versionRegex); + lastVersion = lm ? lm[1] : undefined; + } + if (currentVersion) { + const cm = currentVersion.match(versionRegex); + currentVersion = cm ? cm[1] : undefined; + } + } + let needsUpdate = false; + if (versionDiff && installedCasks.includes(a.name)) { + needsUpdate = true; + updateAvailable.push(`✨ ${color('32', a.name)}`); + } + updated.push([ + color('94', a.name), + needsUpdate ? '✨' : '', + ` [${color('92', homepage)}]`, + versionDiff ? ` ${lastVersion} ${color('33', '<')} ${currentVersion}` : '' + ].join('')); + } + } + }); + + console.log(`\n${color('35', '🔮 ' + type.toUpperCase())}`); + if (updated.length) { + console.log(color('95', 'UPDATED')); + console.log(updated.join('\n')); + } + if (added.length) { + console.log(color('95', 'ADDED')); + console.log(added.join('\n')); + } + if (removed.length) { + console.log(color('95', 'REMOVED')); + console.log(removed.join('\n')); + } + if (updateAvailable.length) { + console.log(color('95', 'OUTDATED')); + console.log(updateAvailable.join('\n')); + } + console.log(color('90', `Old SHA: ${oldSha} New SHA: ${latestSha}`)); + + fs.writeFileSync(latestShaFile, latestSha); +} + +report('cask');