From 13a969e1ab500a692e1e4134ed6380453fc92f5c Mon Sep 17 00:00:00 2001 From: Omarchybot Date: Sat, 22 Aug 2026 16:24:21 +0200 Subject: [PATCH] Only offer Update > Extra Themes when there is one (#7775) * Only offer Update > Extra Themes when there is one omarchy-theme-update pulls the themes under ~/.config/omarchy/themes that came from a git clone, so on a machine that has never installed one by hand the row opens a terminal that prints nothing and closes. Guard it with the same predicates the command itself applies, since a row that shows over a symlinked theme or a worktree's `.git` file is the same dead end in a narrower shape, and pin the two to each other in the guard test. Co-Authored-By: Codex XHigh * Extract the Extra Themes guard into omarchy-theme-extras The row's `when:` and omarchy-theme-update each carried their own idea of which themes came from a git clone, and the two only matched because a test held them together. Name it once instead: omarchy-theme-extras lists those directories and exits nonzero when there are none, so the row asks exactly the command its action runs. Living in a script also puts the glob out of reach of whatever shopt a login shell left set for the guard batch. Co-Authored-By: Codex XHigh --------- Co-authored-by: Codex XHigh --- bin/omarchy-theme-extras | 17 +++++++ bin/omarchy-theme-update | 10 ++-- default/omarchy/omarchy-menu.jsonc | 2 +- test/shell.d/menu-guards-test.sh | 79 ++++++++++++++++++++++++++++++ test/shell.d/menu-test.sh | 5 ++ 5 files changed, 107 insertions(+), 6 deletions(-) create mode 100755 bin/omarchy-theme-extras diff --git a/bin/omarchy-theme-extras b/bin/omarchy-theme-extras new file mode 100755 index 00000000..7cad2c11 --- /dev/null +++ b/bin/omarchy-theme-extras @@ -0,0 +1,17 @@ +#!/bin/bash + +# omarchy:summary=List the user-installed themes that came from a git clone +# omarchy:examples=omarchy theme extras + +# Exits nonzero when there are none, so a caller can ask whether any exist +# without reading the list. A symlinked theme is someone's working copy and a +# `.git` file is a worktree pointing elsewhere; neither is ours to pull. +status=1 + +for theme in ~/.config/omarchy/themes/*; do + [[ ! -L $theme && -d $theme/.git ]] || continue + echo "$theme" + status=0 +done + +exit $status diff --git a/bin/omarchy-theme-update b/bin/omarchy-theme-update index a68cb176..5a700ca8 100755 --- a/bin/omarchy-theme-update +++ b/bin/omarchy-theme-update @@ -2,9 +2,9 @@ # omarchy:summary=Update user-installed git themes -for dir in ~/.config/omarchy/themes/*/; do - if [[ -d $dir ]] && [[ ! -L ${dir%/} ]] && [[ -d $dir/.git ]]; then - echo "Updating: $(basename "$dir")" - git -C "$dir" pull - fi +mapfile -t themes < <(omarchy-theme-extras) + +for theme in "${themes[@]}"; do + echo "Updating: $(basename "$theme")" + git -C "$theme" pull done diff --git a/default/omarchy/omarchy-menu.jsonc b/default/omarchy/omarchy-menu.jsonc index e204da69..0ad5dbfb 100644 --- a/default/omarchy/omarchy-menu.jsonc +++ b/default/omarchy/omarchy-menu.jsonc @@ -338,7 +338,7 @@ "update.omarchy": {"icon":"","iconFont":"omarchy","label":"Omarchy","action":"omarchy-launch-floating-terminal-with-presentation omarchy-update"}, "update.channel": {"icon":"󰔫","label":"Channel"}, "update.config": {"icon":"","label":"Config","title":"Reset to default"}, - "update.themes": {"icon":"󰸌","label":"Extra Themes","action":"omarchy-launch-floating-terminal-with-presentation omarchy-theme-update"}, + "update.themes": {"icon":"󰸌","label":"Extra Themes","when":"omarchy-theme-extras","action":"omarchy-launch-floating-terminal-with-presentation omarchy-theme-update"}, "update.process": {"icon":"","label":"Process","title":"Restart"}, "update.hardware": {"icon":"󰇅","label":"Hardware","title":"Restart"}, "update.firmware": {"icon":"","label":"Firmware","action":"omarchy-launch-floating-terminal-with-presentation omarchy-update-firmware"}, diff --git a/test/shell.d/menu-guards-test.sh b/test/shell.d/menu-guards-test.sh index 243adf3b..660d77c5 100755 --- a/test/shell.d/menu-guards-test.sh +++ b/test/shell.d/menu-guards-test.sh @@ -194,3 +194,82 @@ printf "survived\n"' 2>/dev/null) [[ $errexit_result == $'hit:c:1\nmiss:c:0\nsurvived' ]] || fail "guard batch survives a failing reader under errexit" "got: $errexit_result" pass "guard batch survives a reader that exits nonzero under errexit" + +# Update > Extra Themes runs omarchy-theme-update, which pulls the themes that +# came from a git clone and skips everything else, so the guard has to answer +# for the same set: a row that appears over a symlinked theme or a worktree's +# `.git` file opens a terminal that prints nothing and closes. Both sides ask +# omarchy-theme-extras today; the shapes below are what would tell us if one +# of them stopped. +themes_guard=$(node -e ' + const fs = require("fs") + const path = require("path") + const menu = require(path.join(process.env.ROOT, "shell/plugins/menu/MenuModel.js")) + const items = menu.parseMenuJsonc(fs.readFileSync(path.join(process.env.ROOT, "default/omarchy/omarchy-menu.jsonc"), "utf8")) + process.stdout.write(items.find(item => item.id === "update.themes").when) +') + +cat >"$stub_dir/git" <<'STUB' +#!/bin/bash +: "${GIT_CALLS:=/dev/null}" +{ printf '<%s>' "$@"; printf '\n'; } >>"$GIT_CALLS" +STUB +chmod +x "$stub_dir/git" + +# The updater names each theme it pulls, so what it printed is what the row +# would have been for. Run the guard the way the batch does, braces and all, +# and say which shapes are meant to show it rather than only that the two +# agree: they read the same command now, and agreement alone would hold even +# if both went wrong together. +assert_themes_guard_agrees() { + local description="$1" home="$2" expected="$3" + local guarded=0 updated=0 + + HOME="$home" PATH="$ROOT/bin:$PATH" bash -e -c "{ $themes_guard; } >/dev/null 2>&1" || guarded=$? + [[ -n $(HOME="$home" PATH="$ROOT/bin:$stub_dir:$PATH" "$ROOT/bin/omarchy-theme-update" 2>/dev/null) ]] || updated=1 + ((guarded == expected)) || fail "$description" "$home: guard=$guarded expected=$expected" + ((updated == expected)) || fail "$description" "$home: update=$updated expected=$expected" +} + +themes_home=$(mktemp -d) +trap 'rm -rf "$stub_dir" "$themes_home"' EXIT + +# A theme copied by hand has nothing to pull, a symlinked one is someone's +# working copy, and a `.git` file is a worktree living elsewhere. +mkdir -p "$themes_home/missing" +mkdir -p "$themes_home/empty/.config/omarchy/themes" +mkdir -p "$themes_home/copied/.config/omarchy/themes/handmade" +mkdir -p "$themes_home/cloned/.config/omarchy/themes/tokyo-night/.git" +mkdir -p "$themes_home/linked/.config/omarchy/themes" "$themes_home/checkout/.git" +ln -s "$themes_home/checkout" "$themes_home/linked/.config/omarchy/themes/in-progress" +mkdir -p "$themes_home/worktree/.config/omarchy/themes/branch" +printf 'gitdir: /elsewhere\n' >"$themes_home/worktree/.config/omarchy/themes/branch/.git" + +for shape in missing:1 empty:1 copied:1 cloned:0 linked:1 worktree:1; do + assert_themes_guard_agrees \ + "Extra Themes shows exactly when omarchy-theme-update has something to pull" \ + "$themes_home/${shape%:*}" "${shape#*:}" +done +pass "Extra Themes shows exactly when omarchy-theme-update has something to pull" + +# Which themes get pulled, not just that something did: a name with a space in +# it is the one that goes missing the moment a path is split rather than passed +# whole, and it would still print an Updating: line on its way to the wrong +# directory. +many="$themes_home/many/.config/omarchy/themes" +mkdir -p "$many/tokyo night/.git" "$many/zen/.git" "$many/handmade" +ln -s "$themes_home/checkout" "$many/in-progress" + +listed=$(HOME="$themes_home/many" LC_ALL=C "$ROOT/bin/omarchy-theme-extras") +[[ $listed == "$many/tokyo night"$'\n'"$many/zen" ]] || + fail "omarchy-theme-extras lists every clone and nothing else" "got: $listed" +pass "omarchy-theme-extras lists every clone and nothing else" + +git_calls=$(mktemp) +trap 'rm -rf "$stub_dir" "$themes_home" "$git_calls"' EXIT +HOME="$themes_home/many" LC_ALL=C GIT_CALLS="$git_calls" PATH="$ROOT/bin:$stub_dir:$PATH" \ + "$ROOT/bin/omarchy-theme-update" >/dev/null 2>&1 +pulled=$(<"$git_calls") +[[ $pulled == "<-C><$many/tokyo night>"$'\n'"<-C><$many/zen>" ]] || + fail "omarchy-theme-update pulls each clone by its whole path" "got: $pulled" +pass "omarchy-theme-update pulls each clone by its whole path" diff --git a/test/shell.d/menu-test.sh b/test/shell.d/menu-test.sh index 6cf51650..236831c3 100644 --- a/test/shell.d/menu-test.sh +++ b/test/shell.d/menu-test.sh @@ -192,6 +192,11 @@ assert( defaultById['update.omarchy'].iconFont === 'omarchy', 'menu update Omarchy entry renders the private glyph with the Omarchy font' ) +assertEqual( + defaultById['update.themes'].when, + 'omarchy-theme-extras', + 'menu hides Extra Themes until a theme cloned from git is there to update' +) assert( defaultById['setup.input'].action.includes('input.lua'), 'menu keeps Input as a direct config action'