Improve safety and CLI fidelity for everyday use

Drop default --noconfirm (opt-in via --noconfirm), fix --noremovemake,
forward pacman argv intact for -R/-D/-F/-T/-U, deepen PKGBUILD review,
show an install plan before building, and add shellcheck CI plus unit tests.

Co-authored-by: Frank Vitrano <WHC2006@users.noreply.github.com>
This commit is contained in:
Cursor Agent
2026-07-23 14:39:03 +00:00
co-authored by Frank Vitrano
parent d0f2cfe335
commit bba71040d6
16 changed files with 402 additions and 81 deletions
+30
View File
@@ -0,0 +1,30 @@
name: CI
on:
push:
branches: [main]
pull_request:
jobs:
shellcheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install shellcheck
run: sudo apt-get update && sudo apt-get install -y shellcheck
- name: Run shellcheck
# SC2034: globals shared across sourced libs
# SC2086: intentional word-splitting for configurable command paths/flags
# SC1007: empty color assignments when colors disabled
# SC2206: MFlags word-splitting is intentional
run: |
shellcheck --shell=bash \
-e SC2034,SC2086,SC1007,SC2206 \
miaur install.sh lib/*.sh tests/run.sh
tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run unit tests
run: bash tests/run.sh
+1
View File
@@ -36,6 +36,7 @@ miaur -Ss <query> # Search packages
miaur -Qua # List available AUR upgrades miaur -Qua # List available AUR upgrades
miaur -Sua # Upgrade AUR packages only miaur -Sua # Upgrade AUR packages only
miaur -G <package> # Download a PKGBUILD miaur -G <package> # Download a PKGBUILD
miaur --noconfirm -Syu # Upgrade without prompts
``` ```
Run `miaur -h` for more options. Run `miaur -h` for more options.
+1 -1
View File
@@ -6,7 +6,7 @@ _miaur() {
prev=${COMP_WORDS[COMP_CWORD - 1]} prev=${COMP_WORDS[COMP_CWORD - 1]}
local ops='-S -Syu -Sua -Ss -Si -Sc -Q -Qua -Qm -R -Rns -G -Gp -B -Bi -h -V' local ops='-S -Syu -Sua -Ss -Si -Sc -Q -Qua -Qm -R -Rns -G -Gp -B -Bi -h -V'
local longs='--aur --repo --skipreview --cleanafter --removemake --noremovermake --bottomup --topdown --clonedir --mflags --sudoloop --nosudoloop --help --version' local longs='--aur --repo --skipreview --cleanafter --removemake --noremovemake --noremovermake --bottomup --topdown --clonedir --mflags --sudoloop --nosudoloop --noconfirm --help --version'
case "$prev" in case "$prev" in
-S | -Si | -Ss | -G | -Gp | -R | -Rns | -Qi) -S | -Si | -Ss | -G | -Gp | -R | -Rns | -Qi)
+2
View File
@@ -17,6 +17,8 @@ _miaur() {
'--aur:use the AUR only' '--aur:use the AUR only'
'--repo:use repositories only' '--repo:use repositories only'
'--skipreview:skip PKGBUILD review' '--skipreview:skip PKGBUILD review'
'--noremovemake:keep make dependencies'
'--noconfirm:bypass confirmation prompts'
'--bottomup:show AUR results first' '--bottomup:show AUR results first'
'--help:show help' '--help:show help'
) )
+1
View File
@@ -10,6 +10,7 @@ CleanAfter = false
RemoveMake = true RemoveMake = true
BottomUp = false BottomUp = false
SudoLoop = true SudoLoop = true
Noconfirm = false
# MFlags = -si # MFlags = -si
# SortBy = votes # SortBy = votes
+1 -1
View File
@@ -95,7 +95,7 @@ aur_print_info() {
# Print search line for AUR result: aur/name version (votes) desc # Print search line for AUR result: aur/name version (votes) desc
aur_format_search_line() { aur_format_search_line() {
local name=$1 ver=$2 votes=$3 pop=$4 desc=$5 local name=$1 ver=$2 votes=$3 desc=$5
local installed= local installed=
if is_pkg_installed "$name"; then if is_pkg_installed "$name"; then
local localver local localver
+37 -2
View File
@@ -27,8 +27,10 @@ build_pkg() {
*) clean_flags+=("$f") ;; *) clean_flags+=("$f") ;;
esac esac
done done
local -a mk_extra=()
mapfile -t mk_extra < <(pac_noconfirm_args)
# Always sync deps for repo side via makepkg -s when needed # Always sync deps for repo side via makepkg -s when needed
${MIAUR_MAKEPKG} -s --noconfirm "${clean_flags[@]}" || exit 1 ${MIAUR_MAKEPKG} -s "${mk_extra[@]}" "${clean_flags[@]}" || exit 1
) || die "build failed: $name" ) || die "build failed: $name"
if [[ $do_install -eq 1 ]]; then if [[ $do_install -eq 1 ]]; then
@@ -36,7 +38,10 @@ build_pkg() {
mapfile -t pkgs < <(find "$dir" -maxdepth 1 -type f \( -name '*.pkg.tar.zst' -o -name '*.pkg.tar.xz' -o -name '*.pkg.tar.gz' \) ! -name '*-debug-*' | sort) mapfile -t pkgs < <(find "$dir" -maxdepth 1 -type f \( -name '*.pkg.tar.zst' -o -name '*.pkg.tar.xz' -o -name '*.pkg.tar.gz' \) ! -name '*-debug-*' | sort)
[[ ${#pkgs[@]} -eq 0 ]] && die "no build artifacts found: $name" [[ ${#pkgs[@]} -eq 0 ]] && die "no build artifacts found: $name"
msg "Installing $name..." msg "Installing $name..."
local -a uflags=(-U --noconfirm) local -a uflags=(-U)
local -a conf=()
mapfile -t conf < <(pac_noconfirm_args)
uflags+=("${conf[@]}")
[[ $asdeps -eq 1 ]] && uflags+=(--asdeps) [[ $asdeps -eq 1 ]] && uflags+=(--asdeps)
as_root ${MIAUR_PACMAN} "${uflags[@]}" "${pkgs[@]}" || die "installation failed: $name" as_root ${MIAUR_PACMAN} "${uflags[@]}" "${pkgs[@]}" || die "installation failed: $name"
fi fi
@@ -60,6 +65,13 @@ install_aur_packages() {
msg "Resolving dependencies..." msg "Resolving dependencies..."
local -a order=() local -a order=()
mapfile -t order < <(aur_resolve_order "${targets[@]}") mapfile -t order < <(aur_resolve_order "${targets[@]}")
# Drop empty line if order was empty print
local -a order_clean=()
local _o
for _o in "${order[@]}"; do
[[ -n $_o ]] && order_clean+=("$_o")
done
order=("${order_clean[@]}")
[[ ${#order[@]} -eq 0 ]] && die "no packages to install" [[ ${#order[@]} -eq 0 ]] && die "no packages to install"
# Collect all repo deps across the graph # Collect all repo deps across the graph
@@ -81,6 +93,28 @@ install_aur_packages() {
done < <(collect_deps_for_dir "$dir" 1 1) done < <(collect_deps_for_dir "$dir" 1 1)
done done
msg "Packages to build (${#order[@]}):"
declare -A is_target_preview=()
for pkg in "${targets[@]}"; do
is_target_preview[$pkg]=1
done
for pkg in "${order[@]}"; do
if [[ -n ${is_target_preview[$pkg]+x} ]]; then
msg2 "aur/$pkg (target)"
else
msg2 "aur/$pkg"
fi
done
if [[ ${#repo_deps[@]} -gt 0 ]]; then
msg "Repository dependencies (${#repo_deps[@]}):"
msg2 "${repo_deps[*]}"
fi
confirm_ask "Proceed with installation?" || {
warn "Cancelled by user"
sudoloop_stop
return 1
}
install_repo_deps "${repo_deps[@]}" install_repo_deps "${repo_deps[@]}"
# Review all packages first (like paru) # Review all packages first (like paru)
@@ -157,6 +191,7 @@ install_aur_packages() {
done done
if [[ ${#to_rm[@]} -gt 0 ]]; then if [[ ${#to_rm[@]} -gt 0 ]]; then
msg "Removing make dependencies: ${to_rm[*]}" msg "Removing make dependencies: ${to_rm[*]}"
# Cleanup after an already-confirmed install: avoid a second prompt by default
as_root ${MIAUR_PACMAN} -Rns --noconfirm "${to_rm[@]}" || warn "failed to remove make dependencies" as_root ${MIAUR_PACMAN} -Rns --noconfirm "${to_rm[@]}" || warn "failed to remove make dependencies"
fi fi
fi fi
+63 -2
View File
@@ -1,4 +1,5 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# shellcheck disable=SC2034
# CLI parsing — paru-like short option clusters # CLI parsing — paru-like short option clusters
# Globals set by parse_args # Globals set by parse_args
@@ -61,9 +62,33 @@ Options:
--mflags <flags> Pass additional flags to makepkg --mflags <flags> Pass additional flags to makepkg
--sudoloop Keep sudo credentials alive (default) --sudoloop Keep sudo credentials alive (default)
--nosudoloop Disable the sudo keep-alive loop --nosudoloop Disable the sudo keep-alive loop
--noconfirm Bypass any confirmation prompts
EOF EOF
} }
# Strip miaur-only options so the remainder can be forwarded to pacman.
# Prints one argument per line (safe for mapfile with typical package names).
strip_miaur_opts() {
local -a raw=("$@")
local i=0
while ((i < ${#raw[@]})); do
local arg=${raw[$i]}
case "$arg" in
--aur | --repo | --skipreview | --review | --cleanafter | --nocleanafter | \
--removemake | --noremovemake | --noremovermake | --bottomup | --topdown | \
--sudoloop | --nosudoloop | --noconfirm) ;;
--clonedir | --mflags | --mode)
i=$((i + 1))
;;
--clonedir=* | --mflags=* | --mode=*) ;;
*)
printf '%s\n' "$arg"
;;
esac
i=$((i + 1))
done
}
parse_args() { parse_args() {
if [[ $# -eq 0 ]]; then if [[ $# -eq 0 ]]; then
OP=S OP=S
@@ -104,11 +129,14 @@ parse_args() {
--cleanafter) MIAUR_CLEANAFTER=1 ;; --cleanafter) MIAUR_CLEANAFTER=1 ;;
--nocleanafter) MIAUR_CLEANAFTER=0 ;; --nocleanafter) MIAUR_CLEANAFTER=0 ;;
--removemake) MIAUR_REMOVEMAKE=1 ;; --removemake) MIAUR_REMOVEMAKE=1 ;;
--noremovemake) MIAUR_REMOVEMAKE=0 ;;
# Typo alias kept for compatibility with older docs/scripts
--noremovermake) MIAUR_REMOVEMAKE=0 ;; --noremovermake) MIAUR_REMOVEMAKE=0 ;;
--bottomup) MIAUR_BOTTOMUP=1 ;; --bottomup) MIAUR_BOTTOMUP=1 ;;
--topdown) MIAUR_BOTTOMUP=0 ;; --topdown) MIAUR_BOTTOMUP=0 ;;
--sudoloop) MIAUR_SUDOLOOP=1 ;; --sudoloop) MIAUR_SUDOLOOP=1 ;;
--nosudoloop) MIAUR_SUDOLOOP=0 ;; --nosudoloop) MIAUR_SUDOLOOP=0 ;;
--noconfirm) MIAUR_NOCONFIRM=1 ;;
--clonedir) --clonedir)
i=$((i + 1)) i=$((i + 1))
MIAUR_CLONEDIR=${raw[$i]:-} MIAUR_CLONEDIR=${raw[$i]:-}
@@ -133,7 +161,20 @@ parse_args() {
;; ;;
--getpkgbuild) OP=G ;; --getpkgbuild) OP=G ;;
--build) OP=B ;; --build) OP=B ;;
--database | --files | --deptest | --upgrade) --database)
OP=D
DO_PASSTHRU=1
;;
--files)
OP=F
DO_PASSTHRU=1
;;
--deptest)
OP=T
DO_PASSTHRU=1
;;
--upgrade)
OP=U
DO_PASSTHRU=1 DO_PASSTHRU=1
;; ;;
--) --)
@@ -144,6 +185,11 @@ parse_args() {
done done
break break
;; ;;
--*)
# Unknown long option: keep for pacman passthrough (e.g. --ignore)
PACMAN_EXTRA+=("$arg")
DO_PASSTHRU=1
;;
-*) -*)
local cluster=${arg:1} local cluster=${arg:1}
local j c local j c
@@ -158,7 +204,22 @@ parse_args() {
;; ;;
G) OP=G ;; G) OP=G ;;
B) OP=B ;; B) OP=B ;;
D | F | T | U) DO_PASSTHRU=1 ;; D)
OP=D
DO_PASSTHRU=1
;;
F)
OP=F
DO_PASSTHRU=1
;;
T)
OP=T
DO_PASSTHRU=1
;;
U)
OP=U
DO_PASSTHRU=1
;;
y) OP_REFRESH=1 ;; y) OP_REFRESH=1 ;;
u) OP_UPGRADE=1 ;; u) OP_UPGRADE=1 ;;
s) OP_SEARCH=$((OP_SEARCH + 1)) ;; s) OP_SEARCH=$((OP_SEARCH + 1)) ;;
+22
View File
@@ -34,6 +34,28 @@ warn() { printf '%s==>%s WARNING: %s\n' "${C_BOLD}${C_YELLOW}" "${C_RESET}" "$*"
error() { printf '%s==>%s ERROR: %s\n' "${C_BOLD}${C_RED}" "${C_RESET}" "$*" >&2; } error() { printf '%s==>%s ERROR: %s\n' "${C_BOLD}${C_RED}" "${C_RESET}" "$*" >&2; }
die() { error "$*"; exit 1; } die() { error "$*"; exit 1; }
# Append --noconfirm when requested (default: ask, like pacman/paru).
pac_noconfirm_args() {
if [[ ${MIAUR_NOCONFIRM:-0} -eq 1 ]]; then
printf '%s\n' --noconfirm
fi
}
# Prompt Y/n; returns 0 to continue. Skipped when --noconfirm.
confirm_ask() {
local prompt=${1:-Continue?}
if [[ ${MIAUR_NOCONFIRM:-0} -eq 1 ]]; then
return 0
fi
printf '%s::%s %s [Y/n] ' "${C_BOLD}${C_BLUE}" "${C_RESET}" "$prompt" >&2
local ans=
read -r ans || return 1
case "${ans:-y}" in
y | Y | yes | YES | '') return 0 ;;
*) return 1 ;;
esac
}
need_cmd() { need_cmd() {
local c local c
for c in "$@"; do for c in "$@"; do
+2
View File
@@ -10,6 +10,7 @@ MIAUR_CLEANAFTER="${MIAUR_CLEANAFTER:-0}"
MIAUR_REMOVEMAKE="${MIAUR_REMOVEMAKE:-1}" MIAUR_REMOVEMAKE="${MIAUR_REMOVEMAKE:-1}"
MIAUR_BOTTOMUP="${MIAUR_BOTTOMUP:-0}" MIAUR_BOTTOMUP="${MIAUR_BOTTOMUP:-0}"
MIAUR_SUDOLOOP="${MIAUR_SUDOLOOP:-1}" MIAUR_SUDOLOOP="${MIAUR_SUDOLOOP:-1}"
MIAUR_NOCONFIRM="${MIAUR_NOCONFIRM:-0}"
MIAUR_MFLAGS="${MIAUR_MFLAGS:--si}" MIAUR_MFLAGS="${MIAUR_MFLAGS:--si}"
MIAUR_SORTBY="${MIAUR_SORTBY:-votes}" MIAUR_SORTBY="${MIAUR_SORTBY:-votes}"
MIAUR_SEARCHBY="${MIAUR_SEARCHBY:-name-desc}" MIAUR_SEARCHBY="${MIAUR_SEARCHBY:-name-desc}"
@@ -61,6 +62,7 @@ miaur_load_config() {
RemoveMake) MIAUR_REMOVEMAKE=$(_bool_val "$val") ;; RemoveMake) MIAUR_REMOVEMAKE=$(_bool_val "$val") ;;
BottomUp) MIAUR_BOTTOMUP=$(_bool_val "$val") ;; BottomUp) MIAUR_BOTTOMUP=$(_bool_val "$val") ;;
SudoLoop) MIAUR_SUDOLOOP=$(_bool_val "$val") ;; SudoLoop) MIAUR_SUDOLOOP=$(_bool_val "$val") ;;
Noconfirm | NoConfirm) MIAUR_NOCONFIRM=$(_bool_val "$val") ;;
MFlags) MIAUR_MFLAGS=$val ;; MFlags) MIAUR_MFLAGS=$val ;;
SortBy) MIAUR_SORTBY=$val ;; SortBy) MIAUR_SORTBY=$val ;;
SearchBy) MIAUR_SEARCHBY=$val ;; SearchBy) MIAUR_SEARCHBY=$val ;;
+5 -8
View File
@@ -23,8 +23,8 @@ srcinfo_dump() {
# Returns 0 if dep is satisfied (installed or provided by installed pkg) # Returns 0 if dep is satisfied (installed or provided by installed pkg)
dep_satisfied() { dep_satisfied() {
local dep name local _name
name=$(dep_name "$1") _name=$(dep_name "$1")
# pacman -T returns unsatisfied deps; empty means all ok # pacman -T returns unsatisfied deps; empty means all ok
local out local out
out=$(pacman -T "$1" 2>/dev/null) || true out=$(pacman -T "$1" 2>/dev/null) || true
@@ -55,11 +55,6 @@ resolve_one_dep() {
echo "aur:$name" echo "aur:$name"
return 0 return 0
fi fi
# Try provides via pacman -Ssq exact? fallback AUR search by name
if [[ ${MIAUR_MODE:-} != repo ]] && aur_exists "$name"; then
echo "aur:$name"
return 0
fi
echo "missing:$name" echo "missing:$name"
} }
@@ -152,5 +147,7 @@ install_repo_deps() {
done done
[[ ${#list[@]} -eq 0 ]] && return 0 [[ ${#list[@]} -eq 0 ]] && return 0
msg "Installing repository dependencies: ${list[*]}" msg "Installing repository dependencies: ${list[*]}"
as_root ${MIAUR_PACMAN} -S --needed --asdeps --noconfirm "${list[@]}" local -a conf=()
mapfile -t conf < <(pac_noconfirm_args)
as_root ${MIAUR_PACMAN} -S --needed --asdeps "${conf[@]}" "${list[@]}"
} }
+30 -25
View File
@@ -108,7 +108,9 @@ cmd_interactive() {
done done
if [[ ${#repo_pkgs[@]} -gt 0 ]]; then if [[ ${#repo_pkgs[@]} -gt 0 ]]; then
as_root ${MIAUR_PACMAN} -S --needed --noconfirm "${repo_pkgs[@]}" local -a conf=()
mapfile -t conf < <(pac_noconfirm_args)
as_root ${MIAUR_PACMAN} -S --needed "${conf[@]}" "${repo_pkgs[@]}"
fi fi
if [[ ${#aur_pkgs[@]} -gt 0 ]]; then if [[ ${#aur_pkgs[@]} -gt 0 ]]; then
install_aur_packages "${aur_pkgs[@]}" install_aur_packages "${aur_pkgs[@]}"
@@ -154,7 +156,9 @@ cmd_sync_install() {
done done
if [[ ${#repo_pkgs[@]} -gt 0 ]]; then if [[ ${#repo_pkgs[@]} -gt 0 ]]; then
as_root ${MIAUR_PACMAN} -S --needed --noconfirm "${repo_pkgs[@]}" local -a conf=()
mapfile -t conf < <(pac_noconfirm_args)
as_root ${MIAUR_PACMAN} -S --needed "${conf[@]}" "${repo_pkgs[@]}"
fi fi
if [[ ${#aur_pkgs[@]} -gt 0 ]]; then if [[ ${#aur_pkgs[@]} -gt 0 ]]; then
install_aur_packages "${aur_pkgs[@]}" install_aur_packages "${aur_pkgs[@]}"
@@ -185,30 +189,18 @@ cmd_getpkgbuild() {
cmd_clean() { cmd_clean() {
# -Sc: clean pacman cache + AUR clone cache # -Sc: clean pacman cache + AUR clone cache
if [[ ${MIAUR_MODE:-} != aur ]]; then if [[ ${MIAUR_MODE:-} != aur ]]; then
as_root ${MIAUR_PACMAN} -Sc --noconfirm || true local -a conf=()
mapfile -t conf < <(pac_noconfirm_args)
as_root ${MIAUR_PACMAN} -Sc "${conf[@]}" || true
fi fi
aur_clean_cache aur_clean_cache
} }
dispatch() { dispatch() {
# Full passthrough ops (R, D, F, T, U, or unknown) # Passthrough for R/D/F/T/U is handled in main() via strip_miaur_opts.
if [[ $DO_PASSTHRU -eq 1 && $OP != S && $OP != Q ]]; then # Keep a reconstruction fallback only if somehow reached without orig argv.
# Rebuild approximate pacman command from original? Simpler: use saved extras if [[ $DO_PASSTHRU -eq 1 && $OP != S && $OP != Q && $OP != G && $OP != B && $OP != interactive ]]; then
local -a args=() die "internal error: passthrough should be handled in main"
case "$OP" in
R) args+=(-R) ;;
*)
# Fall back: user should use pacman; try to forward
args+=(-"${OP}")
;;
esac
local c
for c in "${PACMAN_EXTRA[@]}"; do
args+=("-$c")
done
args+=("${TARGETS[@]}")
pacman_passthru "${args[@]}"
return $?
fi fi
case "$OP" in case "$OP" in
@@ -233,9 +225,11 @@ dispatch() {
fi fi
if [[ $OP_UPGRADE -eq 1 ]]; then if [[ $OP_UPGRADE -eq 1 ]]; then
need_cmd curl jq need_cmd curl jq
local -a conf=()
mapfile -t conf < <(pac_noconfirm_args)
if [[ $OP_AUR_ONLY -eq 1 ]]; then if [[ $OP_AUR_ONLY -eq 1 ]]; then
# -Sua or -Su --aur # -Sua or -Su --aur
[[ $OP_REFRESH -eq 1 ]] && as_root ${MIAUR_PACMAN} -Sy --noconfirm [[ $OP_REFRESH -eq 1 ]] && as_root ${MIAUR_PACMAN} -Sy "${conf[@]}"
cmd_upgrade_aur cmd_upgrade_aur
else else
cmd_upgrade_all "$OP_REFRESH" cmd_upgrade_all "$OP_REFRESH"
@@ -243,11 +237,17 @@ dispatch() {
return return
fi fi
if [[ $OP_REFRESH -eq 1 && ${#TARGETS[@]} -eq 0 ]]; then if [[ $OP_REFRESH -eq 1 && ${#TARGETS[@]} -eq 0 ]]; then
as_root ${MIAUR_PACMAN} -Sy --noconfirm local -a conf=()
mapfile -t conf < <(pac_noconfirm_args)
as_root ${MIAUR_PACMAN} -Sy "${conf[@]}"
return return
fi fi
# Install packages; optionally refresh first # Install packages; optionally refresh first
[[ $OP_REFRESH -eq 1 ]] && as_root ${MIAUR_PACMAN} -Sy --noconfirm if [[ $OP_REFRESH -eq 1 ]]; then
local -a conf=()
mapfile -t conf < <(pac_noconfirm_args)
as_root ${MIAUR_PACMAN} -Sy "${conf[@]}"
fi
need_cmd curl jq need_cmd curl jq
cmd_sync_install "${TARGETS[@]}" cmd_sync_install "${TARGETS[@]}"
;; ;;
@@ -268,7 +268,12 @@ dispatch() {
local -a qargs=(-Q) local -a qargs=(-Q)
local c local c
for c in "${PACMAN_EXTRA[@]}"; do for c in "${PACMAN_EXTRA[@]}"; do
qargs+=("-$c") # Long options like --explicit may land in PACMAN_EXTRA as full tokens
if [[ $c == --* ]]; then
qargs+=("$c")
else
qargs+=("-$c")
fi
done done
[[ $OP_AUR_ONLY -eq 1 ]] && qargs+=(-m) [[ $OP_AUR_ONLY -eq 1 ]] && qargs+=(-m)
[[ $OP_SEARCH -gt 0 ]] && qargs+=(-s) [[ $OP_SEARCH -gt 0 ]] && qargs+=(-s)
+70 -15
View File
@@ -1,7 +1,37 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# PKGBUILD review # PKGBUILD review
# Review PKGBUILD in dir. Returns 0 to continue, 1 to abort. # Show a file with bat/less/cat
_review_show_file() {
local file=$1
[[ -f $file ]] || return 0
msg2 "File: ${file#"$2"/}"
if command -v "${MIAUR_BAT}" >/dev/null 2>&1; then
"${MIAUR_BAT}" --style=plain --paging=always --language=bash "$file" || true
elif command -v less >/dev/null 2>&1; then
less -R "$file" || true
else
cat "$file"
fi
}
# List reviewable files in a package directory (relative paths)
_review_list_files() {
local dir=$1
(
cd "$dir" || exit 1
# PKGBUILD first, then install scripts and other shell helpers
[[ -f PKGBUILD ]] && printf '%s\n' PKGBUILD
find . -maxdepth 1 -type f \( \
-name '*.install' -o \
-name '*.sh' -o \
-name 'apply-patches' -o \
-name '*.hook' \
\) ! -name 'PKGBUILD' -printf '%P\n' 2>/dev/null | sort
)
}
# Review PKGBUILD in dir. Returns 0 to continue, 1 to abort, 2 to skip.
review_pkgbuild() { review_pkgbuild() {
local dir=$1 local dir=$1
local name=${2:-$(basename "$dir")} local name=${2:-$(basename "$dir")}
@@ -11,23 +41,17 @@ review_pkgbuild() {
local pb="$dir/PKGBUILD" local pb="$dir/PKGBUILD"
[[ -f $pb ]] || die "PKGBUILD not found: $dir" [[ -f $pb ]] || die "PKGBUILD not found: $dir"
msg "Reviewing the PKGBUILD for $name" msg "Reviewing $name"
if command -v "${MIAUR_BAT}" >/dev/null 2>&1; then local -a files=()
"${MIAUR_BAT}" --style=plain --paging=always --language=bash "$pb" || true mapfile -t files < <(_review_list_files "$dir")
elif command -v less >/dev/null 2>&1; then if [[ ${#files[@]} -gt 1 ]]; then
less -R "$pb" || true msg2 "Files: ${files[*]}"
else
cat "$pb"
fi fi
# Also show install script / other files briefly _review_show_file "$pb" "$dir"
local f
for f in "$dir"/*.install "$dir"/PKGBUILD; do
[[ -f $f ]] || continue
done
while true; do while true; do
printf '%s::%s View diff (d), edit (e), continue (y), skip (s), or quit (q)? [Y/d/e/s/q] ' \ printf '%s::%s View diff (d), files (f), edit (e), continue (y), skip (s), or quit (q)? [Y/d/f/e/s/q] ' \
"${C_BOLD}${C_BLUE}" "${C_RESET}" "${C_BOLD}${C_BLUE}" "${C_RESET}"
local ans= local ans=
read -r ans || return 1 read -r ans || return 1
@@ -42,9 +66,40 @@ review_pkgbuild() {
e | edit) e | edit)
"${MIAUR_EDITOR}" "$pb" "${MIAUR_EDITOR}" "$pb"
;; ;;
f | files)
local -a again=()
mapfile -t again < <(_review_list_files "$dir")
local i
for i in "${!again[@]}"; do
printf ' %s%2d%s %s\n' "${C_DIM}" "$((i + 1))" "${C_RESET}" "${again[$i]}"
done
printf '%s::%s Open file number (empty to return): ' \
"${C_BOLD}${C_BLUE}" "${C_RESET}"
local pick=
read -r pick || true
if [[ $pick =~ ^[0-9]+$ ]]; then
local idx=$((pick - 1))
if ((idx >= 0 && idx < ${#again[@]})); then
_review_show_file "$dir/${again[$idx]}" "$dir"
else
warn "Invalid number: $pick"
fi
fi
;;
d | diff) d | diff)
if [[ -d $dir/.git ]]; then if [[ -d $dir/.git ]]; then
(cd "$dir" && ${MIAUR_GIT} diff -- PKGBUILD) || true (
cd "$dir" || exit 0
# Prefer unstaged/staged local edits; fall back to last fetch range
if ! ${MIAUR_GIT} diff --stat --quiet HEAD 2>/dev/null; then
${MIAUR_GIT} --no-pager diff -- HEAD
elif ${MIAUR_GIT} rev-parse --verify 'HEAD@{1}' >/dev/null 2>&1; then
${MIAUR_GIT} --no-pager diff -- 'HEAD@{1}'..HEAD
else
warn "No prior revision available for comparison"
${MIAUR_GIT} --no-pager diff -- PKGBUILD || true
fi
) || true
else else
warn "No Git history is available for comparison" warn "No Git history is available for comparison"
fi fi
+6 -8
View File
@@ -60,8 +60,8 @@ cmd_query_aur_upgrades() {
# Upgrade all outdated AUR packages # Upgrade all outdated AUR packages
cmd_upgrade_aur() { cmd_upgrade_aur() {
local -a pkgs=() local -a pkgs=()
local name old arrow new local name old _arrow new
while read -r name old arrow new; do while read -r name old _arrow new; do
[[ -z $name ]] && continue [[ -z $name ]] && continue
pkgs+=("$name") pkgs+=("$name")
msg2 "$name ${old} -> ${new}" msg2 "$name ${old} -> ${new}"
@@ -79,16 +79,14 @@ cmd_upgrade_aur() {
cmd_upgrade_all() { cmd_upgrade_all() {
local refresh=${1:-0} local refresh=${1:-0}
sudoloop_start sudoloop_start
local -a pac_args=() local -a conf=()
[[ $refresh -eq 1 ]] && pac_args+=(-y) mapfile -t conf < <(pac_noconfirm_args)
pac_args+=(-Su)
msg "Synchronizing databases and upgrading repository packages..." msg "Synchronizing databases and upgrading repository packages..."
# Use -Syu style via pacman
if [[ $refresh -eq 1 ]]; then if [[ $refresh -eq 1 ]]; then
as_root ${MIAUR_PACMAN} -Syu --noconfirm || die "repository upgrade failed" as_root ${MIAUR_PACMAN} -Syu "${conf[@]}" || die "repository upgrade failed"
else else
as_root ${MIAUR_PACMAN} -Su --noconfirm || die "repository upgrade failed" as_root ${MIAUR_PACMAN} -Su "${conf[@]}" || die "repository upgrade failed"
fi fi
msg "Checking for AUR updates..." msg "Checking for AUR updates..."
Regular → Executable
+7 -19
View File
@@ -29,28 +29,16 @@ source "$MIAUR_HOME/lib/dispatch.sh"
main() { main() {
miaur_load_config miaur_load_config
# Fast path: operations that are pure pacman wrappers with no AUR flags
# Keep original argv for -R/-D/-F/-T/-U when no miaur-specific long opts mixed in awkwardly.
# We still parse to detect them.
local -a orig=("$@") local -a orig=("$@")
parse_args "$@" parse_args "$@"
# Special: if DO_PASSTHRU and OP empty, forward entire original command # Pure pacman passthrough: forward original argv minus miaur-only options
if [[ $DO_PASSTHRU -eq 1 && -z ${OP:-} ]]; then # so long options like --ignore survive (unlike short-cluster reconstruction).
# Replace program name conceptually — strip miaur-only long options already consumed if [[ $DO_PASSTHRU -eq 1 && $OP != S && $OP != Q && $OP != G && $OP != B && $OP != interactive ]]; then
as_root ${MIAUR_PACMAN} "${orig[@]}" local -a filtered=()
return $? mapfile -t filtered < <(strip_miaur_opts "${orig[@]}")
fi [[ ${#filtered[@]} -gt 0 ]] || die "nothing to pass through to pacman"
as_root ${MIAUR_PACMAN} "${filtered[@]}"
# For -R with extras like -Rns: OP=R DO_PASSTHRU=1
if [[ ${OP:-} == R ]]; then
local -a rargs=(-R)
local c
for c in "${PACMAN_EXTRA[@]}"; do
rargs+=("-$c")
done
rargs+=("${TARGETS[@]}")
as_root ${MIAUR_PACMAN} "${rargs[@]}"
return $? return $?
fi fi
Executable
+124
View File
@@ -0,0 +1,124 @@
#!/usr/bin/env bash
# Lightweight self-tests for miaur (no Arch/pacman required)
set -euo pipefail
ROOT=$(cd "$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")/.." && pwd)
# shellcheck source=lib/common.sh
source "$ROOT/lib/common.sh"
# shellcheck source=lib/cli.sh
source "$ROOT/lib/cli.sh"
fail() { echo "FAIL: $*" >&2; exit 1; }
pass() { echo "ok - $*"; }
# --- strip_miaur_opts ---
{
mapfile -t out < <(strip_miaur_opts -Rns --nosudoloop --ignore linux foo)
[[ ${#out[@]} -eq 4 ]] || fail "strip count=${#out[@]} contents=${out[*]}"
[[ ${out[0]} == -Rns && ${out[1]} == --ignore && ${out[2]} == linux && ${out[3]} == foo ]] \
|| fail "strip values: ${out[*]}"
pass "strip_miaur_opts keeps pacman argv"
}
{
mapfile -t out < <(strip_miaur_opts -S --noconfirm --aur pkg)
[[ ${#out[@]} -eq 2 && ${out[0]} == -S && ${out[1]} == pkg ]] \
|| fail "strip noconfirm/aur: ${out[*]}"
pass "strip_miaur_opts drops miaur-only flags"
}
# --- parse_args: --noremovemake ---
{
OP=
MIAUR_REMOVEMAKE=1
MIAUR_NOCONFIRM=0
DO_PASSTHRU=0
OP_REFRESH=0
OP_UPGRADE=0
TARGETS=()
PACMAN_EXTRA=()
parse_args --noremovemake -S
[[ $MIAUR_REMOVEMAKE -eq 0 ]] || fail "noremovemake not applied"
[[ $OP == S ]] || fail "OP after noremovermake+S"
pass "parse_args --noremovemake"
}
{
OP=
MIAUR_REMOVEMAKE=1
TARGETS=()
PACMAN_EXTRA=()
parse_args --noremovermake
[[ $MIAUR_REMOVEMAKE -eq 0 ]] || fail "typo alias noremovermake not applied"
pass "parse_args typo alias --noremovermake"
}
{
OP=
MIAUR_NOCONFIRM=0
OP_REFRESH=0
OP_UPGRADE=0
TARGETS=()
PACMAN_EXTRA=()
parse_args --noconfirm -Syu
[[ $MIAUR_NOCONFIRM -eq 1 ]] || fail "noconfirm not set"
[[ $OP == S && $OP_REFRESH -eq 1 && $OP_UPGRADE -eq 1 ]] || fail "Syu parse"
pass "parse_args --noconfirm -Syu"
}
{
OP=
DO_PASSTHRU=0
TARGETS=()
PACMAN_EXTRA=()
parse_args -R --ignore linux foo
[[ $OP == R && $DO_PASSTHRU -eq 1 ]] || fail "R passthrough flags"
found=0
for x in "${PACMAN_EXTRA[@]}"; do
[[ $x == --ignore ]] && found=1
done
[[ $found -eq 1 ]] || fail "PACMAN_EXTRA missing --ignore: ${PACMAN_EXTRA[*]}"
[[ ${TARGETS[0]} == linux && ${TARGETS[1]} == foo ]] || fail "targets: ${TARGETS[*]}"
pass "parse_args -R --ignore passthrough tokens"
}
# --- pac_noconfirm_args / confirm_ask ---
{
MIAUR_NOCONFIRM=0
mapfile -t c < <(pac_noconfirm_args)
[[ ${#c[@]} -eq 0 ]] || fail "expected no noconfirm by default"
MIAUR_NOCONFIRM=1
mapfile -t c < <(pac_noconfirm_args)
[[ ${#c[@]} -eq 1 && ${c[0]} == --noconfirm ]] || fail "noconfirm args: ${c[*]}"
pass "pac_noconfirm_args"
}
{
MIAUR_NOCONFIRM=1
confirm_ask "should skip" || fail "confirm_ask should succeed with noconfirm"
pass "confirm_ask respects noconfirm"
}
# --- install.sh resolve_src: process substitution must not cd-fail ---
{
rs_out=$(
bash <(
# shellcheck disable=SC2016
sed -n '/^resolve_src()/,/^}/p' "$ROOT/install.sh"
cat <<'INNER'
REPO_URL=https://example.invalid/miaur.git
BRANCH=main
die() { echo "die:$*" >&2; exit 1; }
git() { echo "git-called"; exit 0; }
resolve_src
echo "SRC=${SRC:-}"
INNER
) 2>&1
) || true
[[ $rs_out != *'No such file or directory'* ]] || fail "process substitution still cd-fails: $rs_out"
[[ $rs_out == *'Cloning'* || $rs_out == *'git-called'* || $rs_out == *'die:'* ]] \
|| fail "unexpected resolve_src output: $rs_out"
pass "resolve_src survives process substitution"
}
echo "All tests passed."