diff --git a/config/omarchy/plugins/omacom.elsewhen b/config/omarchy/plugins/omacom.elsewhen new file mode 120000 index 00000000..19840c51 --- /dev/null +++ b/config/omarchy/plugins/omacom.elsewhen @@ -0,0 +1 @@ +/usr/share/omarchy/plugins/omacom.elsewhen \ No newline at end of file diff --git a/default/agents/skills/omarchy/plugins.md b/default/agents/skills/omarchy/plugins.md index 1c58c83a..6e3ab069 100644 --- a/default/agents/skills/omarchy/plugins.md +++ b/default/agents/skills/omarchy/plugins.md @@ -9,12 +9,9 @@ inside a single long-running Quickshell process (`omarchy-shell`). ``` ~/.config/omarchy/shell.json # User overrides: bar, plugins, idle ~/.config/omarchy/plugins// # User-owned shell plugins -/usr/share/omarchy/plugins// # Packaged plugins (pacman), e.g. omacom.elsewhen $OMARCHY_PATH/config/omarchy/shell.json # Canonical defaults ``` -Packaged plugins such as Elsewhen (`elsewhen` package) update with `omarchy update` and are removed with pacman; `omarchy plugin update` and `omarchy plugin remove` refuse them and name the package. - The shell hot-reloads `shell.json` on save — no restart needed for layout changes. `idle.screensaver` and `idle.lock` are seconds since user idle began. diff --git a/docs/file-layout.md b/docs/file-layout.md index 8bbcd5b2..f261085f 100644 --- a/docs/file-layout.md +++ b/docs/file-layout.md @@ -78,9 +78,6 @@ install/** ──► omarchy /usr/share/omarchy migrations/** ──► omarchy /usr/share/omarchy/migrations/ themes/** ──► omarchy /usr/share/omarchy/themes/ shell/** ──► omarchy /usr/share/omarchy/shell/ - (packaged plugins from their own - packages, e.g. elsewhen, land beside it - in /usr/share/omarchy/plugins//) version ──► omarchy /usr/share/omarchy/version + /etc/skel/.local/state/omarchy/migrations/* diff --git a/docs/omarchy-shell.md b/docs/omarchy-shell.md index dd8cc18a..6e999682 100644 --- a/docs/omarchy-shell.md +++ b/docs/omarchy-shell.md @@ -88,9 +88,9 @@ bar from the CLI — `use | reset | defaults | position | transparent | put | move | set`, with placement flags such as `--section` and `--index`. The lower-level IPC methods remain available through `omarchy-shell shell ...`. -## Packaged plugins +## Elsewhen -A default plugin can ship as its own Arch package instead of in the Omarchy checkout: Elsewhen (`omacom.elsewhen`) is the `elsewhen` package. pacman installs it at `/usr/share/omarchy/plugins//`, a root the shell scans between `$OMARCHY_PATH/shell/plugins` and `~/.config/omarchy/plugins`. A packaged `omarchy.*` id is trusted like a bundled plugin and loads by default; any other packaged id behaves like an installed plugin. Precedence is bundled, then packaged, then user, so a checkout under `omarchy dev link` overrides the package. Packaged plugins update through `omarchy update` and leave through pacman; `omarchy plugin update` and `omarchy plugin remove` refuse them and name the package, and Setup › Plugins › Remove does not list them. +Elsewhen (`omacom.elsewhen`) ships in the `elsewhen` package at `/usr/share/omarchy/plugins/omacom.elsewhen`. A symlink in `~/.config/omarchy/plugins/` makes it available to the shell. New installs place it immediately before the clock; the migration uses `omarchy bar put omacom.elsewhen --before omarchy.clock`, which preserves an existing placement and uses Elsewhen's normal right-side placement if the clock is absent. Existing plugin directories and symlinks are left intact. ## IPC diff --git a/migrations/1789581661.sh b/migrations/1789581661.sh index f581d4d0..10f18e66 100644 --- a/migrations/1789581661.sh +++ b/migrations/1789581661.sh @@ -1,88 +1,13 @@ echo "Install Elsewhen, the world clock plugin" -# Elsewhen ships as the elsewhen package rather than in this checkout; pacman -# puts it under /usr/share/omarchy/plugins, where the shell looks next to its -# bundled plugins. A package the mirror does not carry yet fails the migration -# on purpose: nothing before this line has changed anything, and the runner -# retries it on the next update or login rather than quietly leaving the -# machine without the plugin. omarchy-pkg-add elsewhen -# A shell that already knows the packaged root (dev-linked, or restarted -# since) picks the plugin up here; the one an update runs under predates it -# and is replaced by omarchy-update-restart right after the migrations. -omarchy-shell -q shell rescanPlugins || true - -# Before the package, the README had users run -# `omarchy plugin add https://github.com/omacom/elsewhen.git`, which left a -# git clone under ~/.config/omarchy/plugins. The packaged copy now shadows -# it: the shell logs a rejection on every scan, and `omarchy plugin update` -# keeps pulling a checkout nothing loads. A pristine clone of the upstream -# repo is retired the way `omarchy plugin remove` retires one (the repo -# remains upstream). Anything else -- a symlink, a plain directory, local -# changes, a fork, a checkout git cannot read -- is the user's and stays. This -# runs only once the package is in place, so the plugin never leaves a -# machine; its id stays in shell.json either way, which is what keeps it -# enabled. The shell an update runs under predates the packaged root, and it -# watches this directory: dropping the checkout empties the widget's slot in -# that shell until omarchy-update-restart replaces it a few steps later with -# one that finds the package. A one-time gap of a few minutes, during the -# update itself. -upstream_checkout() { - local dir="$1" origin status unpublished - [[ -d $dir && ! -L $dir && -d $dir/.git ]] || return 1 - origin=$(git -C "$dir" remote get-url origin 2>/dev/null) || return 1 - origin=${origin,,} - origin=${origin%/} - origin=${origin%.git} - [[ $origin =~ ^([a-z+]+://)?([^/@]+@)?github\.com[/:]omacom/elsewhen$ ]] || return 1 - status=$(git -C "$dir" status --porcelain --untracked-files=all --ignored 2>/dev/null) || return 1 - [[ -z $status ]] || return 1 - # Local branches, stashes, and reflogs may hold work absent from a clean tree. - unpublished=$(git -C "$dir" rev-list HEAD --all --reflog --not --remotes=origin 2>/dev/null) || return 1 - [[ -z $unpublished ]] -} - -checkout="$HOME/.config/omarchy/plugins/omacom.elsewhen" -if [[ -e $checkout || -L $checkout ]]; then - if upstream_checkout "$checkout"; then - rm -rf "$checkout" - echo "Retired the omacom.elsewhen checkout at $checkout in favour of the elsewhen package." - else - echo "The elsewhen package takes precedence over $checkout, which is left as it is." - fi +plugin="$HOME/.config/omarchy/plugins/omacom.elsewhen" +mkdir -p "$(dirname "$plugin")" +if [[ ! -e $plugin && ! -L $plugin ]]; then + ln -s /usr/share/omarchy/plugins/omacom.elsewhen "$plugin" fi -# The widget sits just before the clock in the center of the default bar, -# which a machine without a shell.json takes on as soon as the shell -# restarts. A customized bar gets the entry written into its file rather than -# placed over IPC: the shell this runs under cannot see a widget its scan -# never reached and would refuse it, whereas the file it hot-reloads carries -# the entry through to the restart. A bar that already carries the widget -# keeps it where the user put it. -source omarchy-shell-config - -[[ -s $CONFIG_FILE ]] || exit 0 -# Preserve configs whose bar comes from the shell fallback instead of an explicit layout. -jq -e 'type == "object" and .version == 1 and (.bar | type == "object") and (.bar.layout | type == "object")' "$CONFIG_FILE" >/dev/null 2>&1 || exit 0 - -entry_id='def entry_id: if type == "object" then (.id // "" | tostring) else tostring end;' - -if jq -e "$entry_id"' - any((.bar.layout // {} | .left, .center, .right | arrays)[]; entry_id == "omacom.elsewhen") -' "$CONFIG_FILE" >/dev/null; then - exit 0 -fi - -commit "$NORMALIZE | $entry_id"' - def ids: map(entry_id); - def insert_at($section; $index): - .bar.layout[$section] = .bar.layout[$section][:$index] + [{id: "omacom.elsewhen"}] + .bar.layout[$section][$index:]; - . as $config - | (["left", "center", "right"] | map(select($config.bar.layout[.] | ids | index("omarchy.clock") != null)) | first) as $section - | if $section != null then - insert_at($section; .bar.layout[$section] | ids | index("omarchy.clock")) - else - insert_at("center"; 0) - end -' +omarchy-shell shell rescanPlugins +omarchy-bar put omacom.elsewhen --before omarchy.clock +omarchy-restart-shell diff --git a/shell/README.md b/shell/README.md index 53425139..ca07b520 100644 --- a/shell/README.md +++ b/shell/README.md @@ -140,10 +140,6 @@ You can still drop a plugin in without git: `barWidget.defaultSection`, or in the center when it is omitted, and can be moved with `omarchy bar move`; a full bar replaces the one in use. -### Packaged plugins - -Some default plugins ship as their own Arch package rather than in this checkout: Elsewhen (`omacom.elsewhen`) is the `elsewhen` package. pacman installs such a plugin, root-owned, at `/usr/share/omarchy/plugins//`, and the shell scans that root between its bundled plugins and the user's. A packaged `omarchy.*` id is trusted exactly like a bundled one; a packaged plugin under any other id is treated like a user plugin. They update with `omarchy update` and are removed with pacman, so `omarchy plugin update` and `omarchy plugin remove` refuse them and name the package instead. A bundled copy in `$OMARCHY_PATH/shell/plugins` wins over the packaged one, which is how a packaged plugin is developed under `omarchy dev link`. - The lower-level IPC equivalents remain available via `omarchy-shell shell rescanPlugins`, `omarchy-shell shell enablePlugin '{}'`, and `omarchy-shell shell listPlugins`. The `omarchy plugin` commands wrap those calls. `omarchy bar move` and @@ -228,7 +224,6 @@ customization from the shipped defaults lives in it. |-----------------------------------|----------------|--------------------------------------------------------| | `~/.config/omarchy/shell.json` | the shell | full layout + per-entry settings + enabled plugin list | | `~/.config/omarchy/plugins//` | user | drop-in third-party plugin source files | -| `/usr/share/omarchy/plugins//` | pacman | packaged plugins (e.g. `elsewhen`), updated by `omarchy update` | The `config/omarchy/shell.json` default config describes the fresh-install state. When the user has no `shell.json`, the shell uses diff --git a/shell/plugins/README.md b/shell/plugins/README.md index 1a2d26fb..66cb74e1 100644 --- a/shell/plugins/README.md +++ b/shell/plugins/README.md @@ -9,10 +9,7 @@ First-party non-bar plugins are enabled unless listed in `disabledPlugins[]`; at startup; other panels, overlays, and menus are loaded on demand. User-installed plugins live alongside these conceptually but on disk under -`~/.config/omarchy/plugins//` rather than in this directory. A few -default plugins arrive as their own package instead: pacman installs them at -`/usr/share/omarchy/plugins//`, where an `omarchy.*` id is trusted -like the plugins here, and a copy in this directory shadows the packaged one. +`~/.config/omarchy/plugins//` rather than in this directory. | Plugin | id | kinds | entry point | |---------------|---------------------------|-------------------------|---------------------------------------| @@ -31,7 +28,6 @@ like the plugins here, and a copy in this directory shadows the packaged one. | Power | `omarchy.power` | `bar-widget` | `panels/power/Panel.qml` | | Tailscale | `omarchy.tailscale` | `bar-widget` | `panels/tailscale/Panel.qml` | | Agents | `omarchy.agents` | `bar-widget` | `agents/Panel.qml` | -| Elsewhen | `omacom.elsewhen` | `bar-widget` | packaged: `elsewhen` installs `/usr/share/omarchy/plugins/omacom.elsewhen/` | | Weather | `omarchy.weather` | `bar-widget` | `panels/weather/BarWidget.qml` | | Media | `omarchy.media` | `service`, `bar-widget` | `services/media/Service.qml`, `services/media/BarWidget.qml` | | Battery | `omarchy.battery` | `service` | `services/battery/Service.qml` | diff --git a/test/shell.d/config-test.sh b/test/shell.d/config-test.sh index 1cd47e33..82a51e6f 100755 --- a/test/shell.d/config-test.sh +++ b/test/shell.d/config-test.sh @@ -52,9 +52,9 @@ jq -e ' (.bar.layout.right | ids) as $ids | ($ids | index("omarchy.tray")) as $tray | ($ids | index("omarchy.agents")) as $agents | - $tray == 0 and $agents == $tray + 1 + $tray != null and $agents == $tray + 1 ' "$ROOT/config/omarchy/shell.json" >/dev/null -pass "default right layout opens with the tray, then agents" +pass "default right layout keeps agents next to the tray" ROOT="$ROOT" python3 <<'PY' import json diff --git a/test/shell.d/elsewhen-default-migration-test.sh b/test/shell.d/elsewhen-default-migration-test.sh index 525094e5..bda62fd6 100644 --- a/test/shell.d/elsewhen-default-migration-test.sh +++ b/test/shell.d/elsewhen-default-migration-test.sh @@ -3,361 +3,75 @@ set -euo pipefail source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh" - require_command jq -require_command git -migration="$ROOT/migrations/1789581661.sh" test_dir=$(mktemp -d) trap 'rm -rf "$test_dir"' EXIT +mkdir -p "$test_dir/bin" "$test_dir/home" +export CALL_LOG="$test_dir/calls" -mkdir -p "$test_dir/bin" -export INSTALLED_PACKAGES="$test_dir/installed" CALL_LOG="$test_dir/calls" SHELL_CALLS="$test_dir/shell-calls" -# The checkout cases use the real git; keep the developer's config (signing, -# hooks, safe.directory) out of what it sees. -export GIT_CONFIG_GLOBAL=/dev/null GIT_CONFIG_NOSYSTEM=1 -export GIT_AUTHOR_NAME=test GIT_AUTHOR_EMAIL=test@example.com -export GIT_COMMITTER_NAME=test GIT_COMMITTER_EMAIL=test@example.com - -# Keep the real package helpers, but contain every pacman transaction here. -cat >"$test_dir/bin/pacman" <<'SH' +cat >"$test_dir/bin/omarchy-pkg-add" <<'SH' #!/bin/bash -case "$1" in - -Q) grep -Fxq -- "$2" "$INSTALLED_PACKAGES" ;; - -S) - [[ ${FAIL_INSTALL:-0} == 0 ]] || exit 1 - shift 3 # -S --noconfirm --needed - printf '%s\n' "$@" >>"$INSTALLED_PACKAGES" - printf '%s\n' "$@" >>"$CALL_LOG" - ;; - *) exit 1 ;; -esac +printf 'package %s\n' "$*" >>"$CALL_LOG" +exit "${PACKAGE_STATUS:-0}" SH -cat >"$test_dir/bin/sudo" <<'SH' -#!/bin/bash -[[ $1 == "pacman" ]] || exit 1 -"$@" -SH -# The migration runs under a shell that predates the packaged root, or under -# none at all, so it must only ever ask the shell for best-effort refreshes. cat >"$test_dir/bin/omarchy-shell" <<'SH' #!/bin/bash -printf '%s\n' "$*" >>"$SHELL_CALLS" -[[ $1 != "-q" ]] || exit 0 -exit "${SHELL_STATUS:-0}" +printf '%s\n' "$*" >>"$CALL_LOG" +[[ $2 != "rescanPlugins" ]] || exit "${SCAN_STATUS:-0}" +printf '%s\n' "${TEST_PUT_RESULT:-ok}" +SH +cat >"$test_dir/bin/omarchy-restart-shell" <<'SH' +#!/bin/bash +printf 'restart\n' >>"$CALL_LOG" SH chmod +x "$test_dir/bin/"* -home="$test_dir/home" -config="$home/.config/omarchy/shell.json" -checkout="$home/.config/omarchy/plugins/omacom.elsewhen" -output="$test_dir/output" -mkdir -p "$home/.config/omarchy" - +plugin="$test_dir/home/.config/omarchy/plugins/omacom.elsewhen" run_migration() { : >"$CALL_LOG" - : >"$SHELL_CALLS" - HOME="$home" OMARCHY_PATH="$ROOT" PATH="$test_dir/bin:$ROOT/bin:$PATH" \ - bash -euo pipefail "$migration" >"$output" + HOME="$test_dir/home" OMARCHY_PATH="$ROOT" PATH="$test_dir/bin:$ROOT/bin:$PATH" \ + bash -euo pipefail "$ROOT/migrations/1789581661.sh" >"$test_dir/output" 2>&1 } -ids() { - jq -c ".bar.layout.$1 | map(if type == \"object\" then .id else . end)" "$config" -} - -# ------------------------------------------------------------ package install -: >"$INSTALLED_PACKAGES" -cat >"$config" <<'JSON' -{ - "version": 1, - "bar": { - "layout": { - "left": [{ "id": "omarchy.menu" }], - "center": [{ "id": "omarchy.clock", "format": "HH:mm" }], - "right": [ - { "id": "omarchy.tray" }, - { "id": "omarchy.agents", "syncMode": "On" }, - { "id": "omarchy.power" } - ] - } - }, - "plugins": [] -} -JSON - -run_migration -grep -Fxq elsewhen "$CALL_LOG" || fail "migration installs the elsewhen package" "$(cat "$CALL_LOG")" -pass "migration installs the elsewhen package" - -[[ $(ids center) == '["omacom.elsewhen","omarchy.clock"]' && $(ids right) == '["omarchy.tray","omarchy.agents","omarchy.power"]' ]] || - fail "migration puts the widget just before the center clock" "$(cat "$config")" -pass "migration puts the widget just before the center clock" - -[[ $(jq -c '.bar.layout.right[1]' "$config") == '{"id":"omarchy.agents","syncMode":"On"}' ]] || - fail "migration keeps the settings of its neighbours" "$(cat "$config")" -[[ $(jq -c '.bar.layout.center[1]' "$config") == '{"format":"HH:mm","id":"omarchy.clock"}' ]] || - fail "migration keeps the clock settings" "$(cat "$config")" -pass "migration leaves the rest of the layout alone" - -grep -q 'shell rescanPlugins' "$SHELL_CALLS" && grep -q 'shell reloadConfig' "$SHELL_CALLS" || - fail "migration asks the running shell to rescan and reload" "$(cat "$SHELL_CALLS")" -pass "migration asks the running shell to rescan and reload" - -before=$(sha256sum "$config") -run_migration -[[ $before == $(sha256sum "$config") ]] || fail "migration is idempotent" "$(cat "$config")" -[[ ! -s $CALL_LOG ]] || fail "migration does not reinstall a present package" "$(cat "$CALL_LOG")" -pass "migration is idempotent" - -# ------------------------------------------------------------- placements -cat >"$config" <<'JSON' -{ - "version": 1, - "bar": { "layout": { "left": [], "center": ["omarchy.clock", "omarchy.tray"], "right": ["omarchy.power"] } } -} -JSON -run_migration -[[ $(ids center) == '["omacom.elsewhen","omarchy.clock","omarchy.tray"]' && $(ids right) == '["omarchy.power"]' ]] || - fail "migration reads string clock entries" "$(cat "$config")" -pass "migration reads string clock entries" - -cat >"$config" <<'JSON' -{ "version": 1, "bar": { "layout": { "right": [{ "id": "omarchy.agents" }, { "id": "omarchy.power" }] } } } -JSON -run_migration -[[ $(ids center) == '["omacom.elsewhen"]' && $(ids right) == '["omarchy.agents","omarchy.power"]' ]] || - fail "migration prepends to the center section when the clock is off the bar" "$(cat "$config")" -pass "migration prepends to the center section when the clock is off the bar" - -cat >"$config" <<'JSON' -{ - "version": 1, - "bar": { "layout": { "left": [{ "id": "omacom.elsewhen" }], "center": [], "right": [{ "id": "omarchy.tray" }, { "id": "omarchy.agents" }] } } -} -JSON -before=$(sha256sum "$config") -run_migration -[[ $before == $(sha256sum "$config") ]] || - fail "migration leaves a widget the user already placed where it is" "$(cat "$config")" -pass "migration leaves a widget the user already placed where it is" - -# A customized clock keeps its section and settings; insert only its neighbour. -for section in left right; do - jq -n --arg section "$section" '{version: 1, bar: {layout: {center: ["omarchy.weather"], ($section): ["omarchy.menu", {id: "omarchy.clock", format: "HH:mm"}]}}}' >"$config" - run_migration - [[ $(ids "$section") == '["omarchy.menu","omacom.elsewhen","omarchy.clock"]' && $(ids center) == '["omarchy.weather"]' ]] || - fail "migration follows a clock customized into $section" "$(cat "$config")" - pass "migration follows a clock customized into $section" -done - -# ------------------------------------------------------------- edge cases -rm -f "$config" -run_migration -[[ ! -e $config ]] || fail "migration writes no shell.json where the defaults apply" "$(cat "$config")" -pass "migration writes no shell.json where the defaults apply" - -for partial in \ - '{"version":1,"idle":{"lock":600}}' \ - '{"version":1,"bar":null,"idle":{"lock":600}}' \ - '{"version":1,"bar":{"position":"bottom"}}' \ - '{"bar":{"layout":{"center":["omarchy.clock"]}}}'; do - printf '%s\n' "$partial" >"$config" - before=$(sha256sum "$config") - run_migration - [[ $before == $(sha256sum "$config") ]] || fail "migration preserves fallback configuration" "$(cat "$config")" - pass "migration preserves fallback configuration: $partial" -done - -printf '{ not json' >"$config" -run_migration -[[ $(cat "$config") == '{ not json' ]] || fail "migration leaves an unparsable config untouched" "$(cat "$config")" -pass "migration leaves an unparsable config untouched" - -cat >"$config" <<'JSON' -{ "version": 1, "bar": { "layout": { "right": [{ "id": "omarchy.tray" }, { "id": "omarchy.agents" }] } } } -JSON -SHELL_STATUS=1 run_migration -[[ $(ids center) == '["omacom.elsewhen"]' && $(ids right) == '["omarchy.tray","omarchy.agents"]' ]] || - fail "migration places the widget with no shell running" "$(cat "$config")" -pass "migration places the widget with no shell running" - -# A package the mirror does not carry yet leaves the migration pending rather -# than half-done: the layout must not name a widget nothing can install. -: >"$INSTALLED_PACKAGES" -cat >"$config" <<'JSON' -{ "version": 1, "bar": { "layout": { "right": [{ "id": "omarchy.tray" }, { "id": "omarchy.agents" }] } } } -JSON -before=$(sha256sum "$config") -if FAIL_INSTALL=1 run_migration 2>/dev/null; then - fail "a failed package installation must leave the migration pending" +if PACKAGE_STATUS=1 run_migration; then + fail "package failure stops the migration" fi -[[ $before == $(sha256sum "$config") ]] || - fail "a failed package installation leaves the layout untouched" "$(cat "$config")" -pass "a failed package installation is propagated and changes nothing" +[[ ! -e $plugin && ! -L $plugin && $(cat "$CALL_LOG") == "package elsewhen" ]] || + fail "package failure leaves the plugin and shell untouched" +pass "package failure stops before linking or changing the shell" -# ------------------------------------------------- pre-package checkouts -# Users who followed the README before the package existed hold a git clone -# under ~/.config/omarchy/plugins, which the packaged copy now shadows. Only a -# clone of the upstream repo with nothing local in it is retired; everything -# else is the user's and stays, and shell.json gets the widget either way. +run_migration +[[ $(readlink "$plugin") == /usr/share/omarchy/plugins/omacom.elsewhen ]] || fail "package link is installed" +[[ $(readlink "$ROOT/config/omarchy/plugins/omacom.elsewhen") == "$(readlink "$plugin")" ]] || fail "fresh installs use the same package link" +pass "migration and fresh installs link to the package" -# A clone made by `omarchy plugin add`: one commit, the given origin, nothing -# local. Built with init rather than clone so the origin can be any URL form. -make_checkout() { - local origin="$1" - rm -rf "$checkout" - mkdir -p "$checkout" - git -C "$checkout" init -q - git -C "$checkout" remote add origin "$origin" - printf '{ "id": "omacom.elsewhen" }\n' >"$checkout/manifest.json" - printf 'import QtQuick\nItem {}\n' >"$checkout/Panel.qml" - git -C "$checkout" add -A - git -C "$checkout" commit -q -m "Elsewhen" - git -C "$checkout" update-ref refs/remotes/origin/main HEAD -} +expected=$'package elsewhen\nshell rescanPlugins\nshell putBarWidget omacom.elsewhen {"before":"omarchy.clock"}\nrestart' +[[ $(cat "$CALL_LOG") == "$expected" ]] || fail "install, scan, placement and restart run in order" "$(cat "$CALL_LOG")" +pass "real bar helper enables and places before the clock, then restarts" -reset_layout() { - cat >"$config" <<'JSON' -{ "version": 1, "bar": { "layout": { "right": [{ "id": "omarchy.tray" }, { "id": "omarchy.power" }] } } } -JSON -} +run_migration +[[ $(cat "$CALL_LOG") == "$expected" ]] || fail "migration can be rerun" +pass "migration can be rerun with its package link present" -assert_widget_placed() { - [[ $(ids center) == '["omacom.elsewhen"]' && $(ids right) == '["omarchy.tray","omarchy.power"]' ]] || - fail "$1 still gets the widget" "$(cat "$config")" -} +rm "$plugin" +mkdir "$plugin" +printf 'local work\n' >"$plugin/notes" +run_migration +[[ ! -L $plugin && $(cat "$plugin/notes") == "local work" ]] || fail "existing checkout is preserved" +pass "existing checkout and local files are preserved" -assert_retired() { - local description="$1" - [[ ! -e $checkout && ! -L $checkout ]] || fail "$description is retired" "$(ls -la "$checkout")" - grep -q 'Retired the omacom.elsewhen checkout' "$output" || fail "$description is reported as retired" "$(cat "$output")" - assert_widget_placed "$description" - pass "$description is retired" -} +rm "$plugin/notes" +rmdir "$plugin" +ln -s "$test_dir/custom-plugin" "$plugin" +run_migration +[[ $(readlink "$plugin") == "$test_dir/custom-plugin" ]] || fail "existing symlink is preserved" +pass "existing symlink is preserved, including a missing target" -assert_kept() { - local description="$1" - [[ -e $checkout || -L $checkout ]] || fail "$description is kept" - grep -q 'takes precedence over' "$output" || fail "$description is reported as shadowed" "$(cat "$output")" - assert_widget_placed "$description" - pass "$description is kept" -} - -for origin in \ - https://github.com/omacom/elsewhen.git \ - https://github.com/omacom/elsewhen \ - git@github.com:omacom/elsewhen.git \ - ssh://git@github.com/omacom/elsewhen.git \ - https://GitHub.com/Omacom/Elsewhen/; do - make_checkout "$origin" - reset_layout - run_migration - assert_retired "a pristine clone with origin $origin" +for failure in 'SCAN_STATUS=1' 'TEST_PUT_RESULT=unknown'; do + if env "$failure" HOME="$test_dir/home" OMARCHY_PATH="$ROOT" PATH="$test_dir/bin:$ROOT/bin:$PATH" \ + bash -euo pipefail "$ROOT/migrations/1789581661.sh" >"$test_dir/output" 2>&1; then + fail "$failure must leave the migration pending" + fi + pass "$failure leaves the migration pending" done - -make_checkout https://github.com/omacom/elsewhen.git -printf 'import QtQuick\nItem { id: mine }\n' >"$checkout/Panel.qml" -reset_layout -run_migration -assert_kept "a clone with a modified tracked file" -[[ $(cat "$checkout/Panel.qml") == $'import QtQuick\nItem { id: mine }' ]] || - fail "a kept clone keeps its local change" "$(cat "$checkout/Panel.qml")" -pass "a kept clone keeps its local change" - -make_checkout https://github.com/omacom/elsewhen.git -printf 'notes\n' >"$checkout/NOTES.md" -reset_layout -run_migration -assert_kept "a clone with an untracked file" - -make_checkout https://github.com/omacom/elsewhen.git -printf 'import QtQuick\nItem { id: mine }\n' >"$checkout/Panel.qml" -git -C "$checkout" add Panel.qml -git -C "$checkout" commit -q -m "Local customization" -local_commit=$(git -C "$checkout" rev-parse HEAD) -reset_layout -run_migration -assert_kept "a clean clone with an unpublished commit" -[[ $(git -C "$checkout" rev-parse HEAD) == "$local_commit" ]] || fail "local commit survives" - -make_checkout https://github.com/omacom/elsewhen.git -git -C "$checkout" checkout -qb local-work -printf 'local branch\n' >"$checkout/Panel.qml" -git -C "$checkout" commit -qam "Unpublished branch" -git -C "$checkout" checkout -q --detach refs/remotes/origin/main -reset_layout -run_migration -assert_kept "an unpublished commit on another branch" - -make_checkout https://github.com/omacom/elsewhen.git -printf 'stashed work\n' >"$checkout/Panel.qml" -git -C "$checkout" stash push -q -reset_layout -run_migration -assert_kept "a clean clone with stashed work" - -make_checkout https://github.com/omacom/elsewhen.git -git -C "$checkout" config status.showUntrackedFiles no -printf 'private-notes\n' >"$checkout/.git/info/exclude" -printf 'ignored work\n' >"$checkout/private-notes" -reset_layout -run_migration -assert_kept "a clone with an ignored file" -[[ $(cat "$checkout/private-notes") == "ignored work" ]] || fail "ignored file survives" - -make_checkout https://github.com/omacom/elsewhen.git -printf 'recoverable work\n' >"$checkout/Panel.qml" -git -C "$checkout" commit -qam "Recoverable local commit" -git -C "$checkout" reset -q --hard refs/remotes/origin/main -reset_layout -run_migration -assert_kept "a local commit retained only by the reflog" - -make_checkout https://github.com/omacom/elsewhen.git -git -C "$checkout" update-ref -d refs/remotes/origin/main -reset_layout -run_migration -assert_kept "a clone without recorded upstream history" - -make_checkout https://github.com/omacom/elsewhen.git -git -C "$checkout" config status.showUntrackedFiles no -printf 'untracked work\n' >"$checkout/NOTES.md" -reset_layout -run_migration -assert_kept "untracked files hidden by the user Git configuration" - -make_checkout https://github.com/someone/elsewhen-fork.git -reset_layout -run_migration -assert_kept "a clone of another repository" - -make_checkout https://github.com/omacom/elsewhen.git -git -C "$checkout" remote remove origin -reset_layout -run_migration -assert_kept "a clone without an origin remote" - -rm -rf "$checkout" -mkdir -p "$checkout" -printf '{ "id": "omacom.elsewhen" }\n' >"$checkout/manifest.json" -reset_layout -run_migration -assert_kept "a plain directory without .git" - -rm -rf "$checkout" -make_checkout https://github.com/omacom/elsewhen.git -mv "$checkout" "$test_dir/elsewhen-src" -ln -s "$test_dir/elsewhen-src" "$checkout" -reset_layout -run_migration -assert_kept "a symlinked checkout" -[[ -L $checkout && -f $test_dir/elsewhen-src/manifest.json ]] || - fail "a symlinked checkout stays a symlink with its target intact" "$(ls -la "$checkout" "$test_dir/elsewhen-src")" -pass "a symlinked checkout stays a symlink with its target intact" - -rm -f "$checkout" -reset_layout -run_migration -grep -q 'omacom.elsewhen checkout\|takes precedence' "$output" && fail "no checkout means no checkout report" "$(cat "$output")" -assert_widget_placed "a machine without a checkout" -pass "a machine without a checkout is left alone"