mirror of
https://github.com/tw93/Mole.git
synced 2026-03-22 16:45:07 +00:00
Improve update checks and cleanup UX, add timeout regressions
This commit is contained in:
36
Makefile
36
Makefile
@@ -5,6 +5,10 @@
|
|||||||
# Output directory
|
# Output directory
|
||||||
BIN_DIR := bin
|
BIN_DIR := bin
|
||||||
|
|
||||||
|
# Go toolchain
|
||||||
|
GO ?= go
|
||||||
|
GO_DOWNLOAD_RETRIES ?= 3
|
||||||
|
|
||||||
# Binaries
|
# Binaries
|
||||||
ANALYZE := analyze
|
ANALYZE := analyze
|
||||||
STATUS := status
|
STATUS := status
|
||||||
@@ -18,22 +22,36 @@ LDFLAGS := -s -w
|
|||||||
|
|
||||||
all: build
|
all: build
|
||||||
|
|
||||||
|
# Download modules with retries to mitigate transient proxy/network EOF errors.
|
||||||
|
mod-download:
|
||||||
|
@attempt=1; \
|
||||||
|
while [ $$attempt -le $(GO_DOWNLOAD_RETRIES) ]; do \
|
||||||
|
echo "Downloading Go modules ($$attempt/$(GO_DOWNLOAD_RETRIES))..."; \
|
||||||
|
if $(GO) mod download; then \
|
||||||
|
exit 0; \
|
||||||
|
fi; \
|
||||||
|
sleep $$((attempt * 2)); \
|
||||||
|
attempt=$$((attempt + 1)); \
|
||||||
|
done; \
|
||||||
|
echo "Go module download failed after $(GO_DOWNLOAD_RETRIES) attempts"; \
|
||||||
|
exit 1
|
||||||
|
|
||||||
# Local build (current architecture)
|
# Local build (current architecture)
|
||||||
build:
|
build: mod-download
|
||||||
@echo "Building for local architecture..."
|
@echo "Building for local architecture..."
|
||||||
go build -ldflags="$(LDFLAGS)" -o $(BIN_DIR)/$(ANALYZE)-go $(ANALYZE_SRC)
|
$(GO) build -ldflags="$(LDFLAGS)" -o $(BIN_DIR)/$(ANALYZE)-go $(ANALYZE_SRC)
|
||||||
go build -ldflags="$(LDFLAGS)" -o $(BIN_DIR)/$(STATUS)-go $(STATUS_SRC)
|
$(GO) build -ldflags="$(LDFLAGS)" -o $(BIN_DIR)/$(STATUS)-go $(STATUS_SRC)
|
||||||
|
|
||||||
# Release build targets (run on native architectures for CGO support)
|
# Release build targets (run on native architectures for CGO support)
|
||||||
release-amd64:
|
release-amd64: mod-download
|
||||||
@echo "Building release binaries (amd64)..."
|
@echo "Building release binaries (amd64)..."
|
||||||
GOOS=darwin GOARCH=amd64 go build -ldflags="$(LDFLAGS)" -o $(BIN_DIR)/$(ANALYZE)-darwin-amd64 $(ANALYZE_SRC)
|
GOOS=darwin GOARCH=amd64 $(GO) build -ldflags="$(LDFLAGS)" -o $(BIN_DIR)/$(ANALYZE)-darwin-amd64 $(ANALYZE_SRC)
|
||||||
GOOS=darwin GOARCH=amd64 go build -ldflags="$(LDFLAGS)" -o $(BIN_DIR)/$(STATUS)-darwin-amd64 $(STATUS_SRC)
|
GOOS=darwin GOARCH=amd64 $(GO) build -ldflags="$(LDFLAGS)" -o $(BIN_DIR)/$(STATUS)-darwin-amd64 $(STATUS_SRC)
|
||||||
|
|
||||||
release-arm64:
|
release-arm64: mod-download
|
||||||
@echo "Building release binaries (arm64)..."
|
@echo "Building release binaries (arm64)..."
|
||||||
GOOS=darwin GOARCH=arm64 go build -ldflags="$(LDFLAGS)" -o $(BIN_DIR)/$(ANALYZE)-darwin-arm64 $(ANALYZE_SRC)
|
GOOS=darwin GOARCH=arm64 $(GO) build -ldflags="$(LDFLAGS)" -o $(BIN_DIR)/$(ANALYZE)-darwin-arm64 $(ANALYZE_SRC)
|
||||||
GOOS=darwin GOARCH=arm64 go build -ldflags="$(LDFLAGS)" -o $(BIN_DIR)/$(STATUS)-darwin-arm64 $(STATUS_SRC)
|
GOOS=darwin GOARCH=arm64 $(GO) build -ldflags="$(LDFLAGS)" -o $(BIN_DIR)/$(STATUS)-darwin-arm64 $(STATUS_SRC)
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
@echo "Cleaning binaries..."
|
@echo "Cleaning binaries..."
|
||||||
|
|||||||
223
RELEASE_TEST_CHECKLIST.md
Normal file
223
RELEASE_TEST_CHECKLIST.md
Normal file
@@ -0,0 +1,223 @@
|
|||||||
|
# Mole V1.29.0 发布前测试清单
|
||||||
|
|
||||||
|
## 1. 基础功能测试
|
||||||
|
|
||||||
|
### 1.1 主菜单和导航
|
||||||
|
- [ ] `mo` - 主菜单正常显示
|
||||||
|
- [ ] 方向键 ↑↓ 导航正常
|
||||||
|
- [ ] Enter 进入子菜单正常
|
||||||
|
- [ ] M 键显示帮助信息
|
||||||
|
- [ ] Q 键退出正常
|
||||||
|
|
||||||
|
### 1.2 版本和帮助
|
||||||
|
- [ ] `mo --version` - 显示 1.29.0
|
||||||
|
- [ ] `mo --help` - 帮助信息完整
|
||||||
|
- [ ] `mo version` - 显示详细版本信息(macOS版本、架构、SIP状态等)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Clean 功能测试
|
||||||
|
|
||||||
|
### 2.1 基础清理
|
||||||
|
- [ ] `mo clean` - 交互式清理正常
|
||||||
|
- [ ] `mo clean --dry-run` - 预览模式显示正确
|
||||||
|
- [ ] `mo clean --whitelist` - 白名单管理正常
|
||||||
|
|
||||||
|
### 2.2 新增功能: Xcode DeviceSupport
|
||||||
|
- [ ] 清理旧的 Xcode DeviceSupport 版本(而不是仅缓存)
|
||||||
|
- [ ] 保留当前使用的版本
|
||||||
|
|
||||||
|
### 2.3 Bug修复验证
|
||||||
|
- [ ] Go cache 清理时尊重 whitelist
|
||||||
|
- [ ] Homebrew dry-run 模式尊重 whitelist
|
||||||
|
- [ ] pip3 是 macOS stub 时跳过 pip 缓存清理
|
||||||
|
- [ ] 修复后的 ICON_WARNING 显示正确
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Analyze 功能测试
|
||||||
|
|
||||||
|
### 3.1 基础分析
|
||||||
|
- [ ] `mo analyze` - 交互式分析正常
|
||||||
|
- [ ] `mo analyze /path` - 分析指定路径
|
||||||
|
- [ ] `mo analyze /Volumes` - 分析外部磁盘
|
||||||
|
|
||||||
|
### 3.2 新增功能: JSON 输出 (PR #533)
|
||||||
|
- [ ] `mo analyze --json` - JSON 格式输出
|
||||||
|
- [ ] `mo analyze --json /path` - 指定路径 JSON 输出
|
||||||
|
- [ ] 非 TTY 环境自动使用 JSON(如管道): `mo analyze | cat`
|
||||||
|
- [ ] JSON 包含字段: path, total_size, file_count, items[]
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Status 功能测试
|
||||||
|
|
||||||
|
### 4.1 基础状态
|
||||||
|
- [ ] `mo status` - 显示系统健康状态
|
||||||
|
- [ ] CPU、内存、磁盘、网络数据显示正常
|
||||||
|
|
||||||
|
### 4.2 新增功能: JSON 输出 (PR #529)
|
||||||
|
- [ ] `mo status --json` - JSON 格式输出
|
||||||
|
- [ ] 非 TTY 环境自动使用 JSON: `mo status | cat`
|
||||||
|
- [ ] JSON 包含网络数据 (PR #532 fix)
|
||||||
|
- [ ] JSON 字段验证: cpu, memory, disk, network, load_avg, uptime
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Uninstall 功能测试
|
||||||
|
|
||||||
|
- [ ] `mo uninstall` - 应用卸载界面正常
|
||||||
|
- [ ] `mo uninstall --dry-run` - 预览卸载
|
||||||
|
- [ ] `mo uninstall --whitelist` - 白名单管理
|
||||||
|
- [ ] 卸载后能正确发现相关文件
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Optimize 功能测试
|
||||||
|
|
||||||
|
- [ ] `mo optimize` - 系统优化正常
|
||||||
|
- [ ] `mo optimize --dry-run` - 预览模式
|
||||||
|
- [ ] `mo optimize --whitelist` - 白名单管理
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Purge 功能测试
|
||||||
|
|
||||||
|
- [ ] `mo purge` - 项目清理正常
|
||||||
|
- [ ] `mo purge --dry-run` - 预览模式
|
||||||
|
- [ ] `mo purge --paths` - 配置扫描目录
|
||||||
|
- [ ] dry-run 不计入失败移除 (bug fix验证)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Installer 功能测试
|
||||||
|
|
||||||
|
- [ ] `mo installer` - 安装包清理正常
|
||||||
|
- [ ] `mo installer --dry-run` - 预览模式
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. TouchID 功能测试
|
||||||
|
|
||||||
|
- [ ] `mo touchid` - TouchID 配置界面
|
||||||
|
- [ ] `mo touchid enable --dry-run` - 预览模式
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Update 功能测试
|
||||||
|
|
||||||
|
### 10.1 基础更新
|
||||||
|
- [ ] `mo update` - 检查更新(当前已是最新版)
|
||||||
|
- [ ] `mo update --force` - 强制重新安装
|
||||||
|
|
||||||
|
### 10.2 新增功能: Nightly 更新 (PR #517)
|
||||||
|
- [ ] `mo update --nightly` - 安装 nightly 版本
|
||||||
|
- [ ] nightly 安装后 `mo --version` 显示 commit hash
|
||||||
|
- [ ] nightly 安装后 `mo version` 显示 "Channel: Nightly (xxxxxx)"
|
||||||
|
- [ ] Homebrew 安装时 `mo update --nightly` 应被拒绝
|
||||||
|
|
||||||
|
### 10.3 Bug修复验证
|
||||||
|
- [ ] 更新时保持 sudo 会话活跃
|
||||||
|
- [ ] 避免 SIGPIPE 在 Homebrew 检测中
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. Completion 功能测试
|
||||||
|
|
||||||
|
- [ ] `mo completion` - 补全脚本安装
|
||||||
|
- [ ] `mo completion --dry-run` - 预览模式
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 12. Remove 功能测试
|
||||||
|
|
||||||
|
- [ ] `mo remove --dry-run` - 预览移除 Mole
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 13. 边界情况测试
|
||||||
|
|
||||||
|
### 13.1 安全性
|
||||||
|
- [ ] 不删除 /System、/Library/Apple 等受保护路径
|
||||||
|
- [ ] 不删除 com.apple.* 系统文件
|
||||||
|
- [ ] dry-run 模式绝不执行实际删除
|
||||||
|
|
||||||
|
### 13.2 并发和超时
|
||||||
|
- [ ] 长时间运行的命令有超时处理
|
||||||
|
- [ ] 网络请求有超时处理
|
||||||
|
|
||||||
|
### 13.3 错误处理
|
||||||
|
- [ ] 网络不可用时的优雅降级
|
||||||
|
- [ ] 权限不足时的正确提示
|
||||||
|
- [ ] 文件不存在时不报错
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 14. 多场景测试
|
||||||
|
|
||||||
|
### 14.1 不同安装方式
|
||||||
|
- [ ] 脚本安装的功能正常
|
||||||
|
- [ ] Homebrew 安装的功能正常(如果可测)
|
||||||
|
|
||||||
|
### 14.2 不同 macOS 版本
|
||||||
|
- [ ] 在支持的 macOS 版本上测试
|
||||||
|
|
||||||
|
### 14.3 不同架构
|
||||||
|
- [ ] Apple Silicon (arm64) - 测试通过
|
||||||
|
- [ ] Intel (x86_64) - 如可测
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 15. JSON 输出格式验证
|
||||||
|
|
||||||
|
### 15.1 Analyze JSON 结构
|
||||||
|
```bash
|
||||||
|
mo analyze --json /tmp 2>/dev/null | jq '.'
|
||||||
|
```
|
||||||
|
应包含:
|
||||||
|
- [ ] path
|
||||||
|
- [ ] total_size
|
||||||
|
- [ ] file_count
|
||||||
|
- [ ] items (name, path, size, size_human, count)
|
||||||
|
|
||||||
|
### 15.2 Status JSON 结构
|
||||||
|
```bash
|
||||||
|
mo status --json 2>/dev/null | jq '.'
|
||||||
|
```
|
||||||
|
应包含:
|
||||||
|
- [ ] cpu (usage, cores)
|
||||||
|
- [ ] memory (total, used, free, cached, usage_percent)
|
||||||
|
- [ ] disk (total, used, free, usage_percent)
|
||||||
|
- [ ] network (interfaces with rx_bytes, tx_bytes)
|
||||||
|
- [ ] load_avg (1min, 5min, 15min)
|
||||||
|
- [ ] uptime
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 快速验证命令
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. 版本检查
|
||||||
|
mo --version # 应为 1.29.0
|
||||||
|
|
||||||
|
# 2. 核心功能快速测试
|
||||||
|
mo clean --dry-run
|
||||||
|
mo analyze --json /tmp 2>/dev/null | head -20
|
||||||
|
mo status --json 2>/dev/null | jq '.'
|
||||||
|
|
||||||
|
# 3. 测试脚本验证
|
||||||
|
./scripts/test.sh
|
||||||
|
|
||||||
|
# 4. 代码格式检查
|
||||||
|
./scripts/check.sh --format
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 测试通过标准
|
||||||
|
|
||||||
|
- [ ] 所有勾选测试通过
|
||||||
|
- [ ] 无崩溃、无异常退出
|
||||||
|
- [ ] JSON 输出格式正确
|
||||||
|
- [ ] dry-run 模式安全
|
||||||
|
- [ ] 测试脚本全部通过: 464 tests, 0 failures
|
||||||
@@ -790,6 +790,7 @@ load_applications() {
|
|||||||
|
|
||||||
# Cleanup: restore cursor and kill keepalive.
|
# Cleanup: restore cursor and kill keepalive.
|
||||||
cleanup() {
|
cleanup() {
|
||||||
|
local exit_code="${1:-$?}"
|
||||||
if [[ "${MOLE_ALT_SCREEN_ACTIVE:-}" == "1" ]]; then
|
if [[ "${MOLE_ALT_SCREEN_ACTIVE:-}" == "1" ]]; then
|
||||||
leave_alt_screen
|
leave_alt_screen
|
||||||
unset MOLE_ALT_SCREEN_ACTIVE
|
unset MOLE_ALT_SCREEN_ACTIVE
|
||||||
@@ -802,7 +803,7 @@ cleanup() {
|
|||||||
# Log session end
|
# Log session end
|
||||||
log_operation_session_end "uninstall" "${files_cleaned:-0}" "${total_size_cleaned:-0}"
|
log_operation_session_end "uninstall" "${files_cleaned:-0}" "${total_size_cleaned:-0}"
|
||||||
show_cursor
|
show_cursor
|
||||||
exit "${1:-0}"
|
exit "$exit_code"
|
||||||
}
|
}
|
||||||
|
|
||||||
trap cleanup EXIT INT TERM
|
trap cleanup EXIT INT TERM
|
||||||
@@ -825,6 +826,22 @@ main() {
|
|||||||
"--dry-run" | "-n")
|
"--dry-run" | "-n")
|
||||||
export MOLE_DRY_RUN=1
|
export MOLE_DRY_RUN=1
|
||||||
;;
|
;;
|
||||||
|
"--whitelist")
|
||||||
|
echo "Unknown uninstall option: $arg"
|
||||||
|
echo "Whitelist management is currently supported by: mo clean --whitelist / mo optimize --whitelist"
|
||||||
|
echo "Use 'mo uninstall --help' for supported options."
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
-*)
|
||||||
|
echo "Unknown uninstall option: $arg"
|
||||||
|
echo "Use 'mo uninstall --help' for supported options."
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Unknown uninstall argument: $arg"
|
||||||
|
echo "Use 'mo uninstall --help' for supported options."
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,20 @@ var (
|
|||||||
jsonOutput = flag.Bool("json", false, "output metrics as JSON instead of TUI")
|
jsonOutput = flag.Bool("json", false, "output metrics as JSON instead of TUI")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func shouldUseJSONOutput(forceJSON bool, stdout *os.File) bool {
|
||||||
|
if forceJSON {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if stdout == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
info, err := stdout.Stat()
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return (info.Mode() & os.ModeCharDevice) == 0
|
||||||
|
}
|
||||||
|
|
||||||
type tickMsg struct{}
|
type tickMsg struct{}
|
||||||
type animTickMsg struct{}
|
type animTickMsg struct{}
|
||||||
|
|
||||||
@@ -246,7 +260,7 @@ func runTUIMode() {
|
|||||||
func main() {
|
func main() {
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
if *jsonOutput {
|
if shouldUseJSONOutput(*jsonOutput, os.Stdout) {
|
||||||
runJSONMode()
|
runJSONMode()
|
||||||
} else {
|
} else {
|
||||||
runTUIMode()
|
runTUIMode()
|
||||||
|
|||||||
44
cmd/status/main_test.go
Normal file
44
cmd/status/main_test.go
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestShouldUseJSONOutput_ForceFlag(t *testing.T) {
|
||||||
|
if !shouldUseJSONOutput(true, nil) {
|
||||||
|
t.Fatalf("expected force JSON flag to enable JSON mode")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShouldUseJSONOutput_NilStdout(t *testing.T) {
|
||||||
|
if shouldUseJSONOutput(false, nil) {
|
||||||
|
t.Fatalf("expected nil stdout to keep TUI mode")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShouldUseJSONOutput_NonTTYPipe(t *testing.T) {
|
||||||
|
reader, writer, err := os.Pipe()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("create pipe: %v", err)
|
||||||
|
}
|
||||||
|
defer reader.Close()
|
||||||
|
defer writer.Close()
|
||||||
|
|
||||||
|
if !shouldUseJSONOutput(false, writer) {
|
||||||
|
t.Fatalf("expected pipe stdout to use JSON mode")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShouldUseJSONOutput_NonTTYFile(t *testing.T) {
|
||||||
|
tmpFile, err := os.CreateTemp("", "mole-status-stdout-*.txt")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("create temp file: %v", err)
|
||||||
|
}
|
||||||
|
defer os.Remove(tmpFile.Name())
|
||||||
|
defer tmpFile.Close()
|
||||||
|
|
||||||
|
if !shouldUseJSONOutput(false, tmpFile) {
|
||||||
|
t.Fatalf("expected file stdout to use JSON mode")
|
||||||
|
}
|
||||||
|
}
|
||||||
104
lib/check/all.sh
104
lib/check/all.sh
@@ -243,6 +243,99 @@ get_software_updates() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
check_homebrew_updates() {
|
||||||
|
# Check whitelist
|
||||||
|
if command -v is_whitelisted > /dev/null && is_whitelisted "check_homebrew_updates"; then return; fi
|
||||||
|
|
||||||
|
export BREW_OUTDATED_COUNT=0
|
||||||
|
export BREW_FORMULA_OUTDATED_COUNT=0
|
||||||
|
export BREW_CASK_OUTDATED_COUNT=0
|
||||||
|
|
||||||
|
if ! command -v brew > /dev/null 2>&1; then
|
||||||
|
printf " ${GRAY}${ICON_EMPTY}${NC} %-12s %s\n" "Homebrew" "Not installed"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
local cache_file="$CACHE_DIR/brew_updates"
|
||||||
|
local formula_count=0
|
||||||
|
local cask_count=0
|
||||||
|
local total_count=0
|
||||||
|
local use_cache=false
|
||||||
|
|
||||||
|
if is_cache_valid "$cache_file"; then
|
||||||
|
local cached_formula=""
|
||||||
|
local cached_cask=""
|
||||||
|
IFS=' ' read -r cached_formula cached_cask < "$cache_file" || true
|
||||||
|
if [[ "$cached_formula" =~ ^[0-9]+$ && "$cached_cask" =~ ^[0-9]+$ ]]; then
|
||||||
|
formula_count="$cached_formula"
|
||||||
|
cask_count="$cached_cask"
|
||||||
|
use_cache=true
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$use_cache" == "false" ]]; then
|
||||||
|
local formula_outdated=""
|
||||||
|
local cask_outdated=""
|
||||||
|
local formula_status=0
|
||||||
|
local cask_status=0
|
||||||
|
local spinner_started=false
|
||||||
|
|
||||||
|
if [[ -t 1 ]]; then
|
||||||
|
MOLE_SPINNER_PREFIX=" " start_inline_spinner "Checking Homebrew updates..."
|
||||||
|
spinner_started=true
|
||||||
|
fi
|
||||||
|
|
||||||
|
if formula_outdated=$(run_with_timeout 8 brew outdated --formula --quiet 2> /dev/null); then
|
||||||
|
:
|
||||||
|
else
|
||||||
|
formula_status=$?
|
||||||
|
fi
|
||||||
|
|
||||||
|
if cask_outdated=$(run_with_timeout 8 brew outdated --cask --quiet 2> /dev/null); then
|
||||||
|
:
|
||||||
|
else
|
||||||
|
cask_status=$?
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$spinner_started" == "true" ]]; then
|
||||||
|
stop_inline_spinner
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ $formula_status -eq 0 || $cask_status -eq 0 ]]; then
|
||||||
|
formula_count=$(printf '%s\n' "$formula_outdated" | awk 'NF {count++} END {print count + 0}')
|
||||||
|
cask_count=$(printf '%s\n' "$cask_outdated" | awk 'NF {count++} END {print count + 0}')
|
||||||
|
ensure_user_file "$cache_file"
|
||||||
|
printf '%s %s\n' "$formula_count" "$cask_count" > "$cache_file" 2> /dev/null || true
|
||||||
|
elif [[ $formula_status -eq 124 || $cask_status -eq 124 ]]; then
|
||||||
|
printf " ${GRAY}${ICON_WARNING}${NC} %-12s ${YELLOW}%s${NC}\n" "Homebrew" "Check timed out"
|
||||||
|
return
|
||||||
|
else
|
||||||
|
printf " ${GRAY}${ICON_WARNING}${NC} %-12s ${YELLOW}%s${NC}\n" "Homebrew" "Check failed"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
total_count=$((formula_count + cask_count))
|
||||||
|
export BREW_FORMULA_OUTDATED_COUNT="$formula_count"
|
||||||
|
export BREW_CASK_OUTDATED_COUNT="$cask_count"
|
||||||
|
export BREW_OUTDATED_COUNT="$total_count"
|
||||||
|
|
||||||
|
if [[ $total_count -gt 0 ]]; then
|
||||||
|
local detail=""
|
||||||
|
if [[ $formula_count -gt 0 ]]; then
|
||||||
|
detail="${formula_count} formula"
|
||||||
|
fi
|
||||||
|
if [[ $cask_count -gt 0 ]]; then
|
||||||
|
[[ -n "$detail" ]] && detail="${detail}, "
|
||||||
|
detail="${detail}${cask_count} cask"
|
||||||
|
fi
|
||||||
|
[[ -z "$detail" ]] && detail="${total_count} updates"
|
||||||
|
printf " ${GRAY}%s${NC} %-12s ${YELLOW}%s${NC}\n" "$ICON_WARNING" "Homebrew" "${detail} available"
|
||||||
|
else
|
||||||
|
printf " ${GREEN}✓${NC} %-12s %s\n" "Homebrew" "Up to date"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
check_appstore_updates() {
|
check_appstore_updates() {
|
||||||
# Skipped for speed optimization - consolidated into check_macos_update
|
# Skipped for speed optimization - consolidated into check_macos_update
|
||||||
# We can't easily distinguish app store vs macos updates without the slow softwareupdate -l call
|
# We can't easily distinguish app store vs macos updates without the slow softwareupdate -l call
|
||||||
@@ -298,9 +391,9 @@ check_macos_update() {
|
|||||||
export MACOS_UPDATE_AVAILABLE="$updates_available"
|
export MACOS_UPDATE_AVAILABLE="$updates_available"
|
||||||
|
|
||||||
if [[ "$updates_available" == "true" ]]; then
|
if [[ "$updates_available" == "true" ]]; then
|
||||||
echo -e " ${GRAY}${ICON_WARNING}${NC} macOS ${YELLOW}Update available${NC}"
|
printf " ${GRAY}%s${NC} %-12s ${YELLOW}%s${NC}\n" "$ICON_WARNING" "macOS" "Update available"
|
||||||
else
|
else
|
||||||
echo -e " ${GREEN}✓${NC} macOS System up to date"
|
printf " ${GREEN}✓${NC} %-12s %s\n" "macOS" "System up to date"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -366,12 +459,12 @@ check_mole_update() {
|
|||||||
# Compare versions
|
# Compare versions
|
||||||
if [[ "$(printf '%s\n' "$current_version" "$latest_version" | sort -V | head -1)" == "$current_version" ]]; then
|
if [[ "$(printf '%s\n' "$current_version" "$latest_version" | sort -V | head -1)" == "$current_version" ]]; then
|
||||||
export MOLE_UPDATE_AVAILABLE="true"
|
export MOLE_UPDATE_AVAILABLE="true"
|
||||||
echo -e " ${GRAY}${ICON_WARNING}${NC} Mole ${YELLOW}${latest_version} available${NC}, running ${current_version}"
|
printf " ${GRAY}%s${NC} %-12s ${YELLOW}%s${NC}, running %s\n" "$ICON_WARNING" "Mole" "${latest_version} available" "${current_version}"
|
||||||
else
|
else
|
||||||
echo -e " ${GREEN}✓${NC} Mole Latest version ${current_version}"
|
printf " ${GREEN}✓${NC} %-12s %s\n" "Mole" "Latest version ${current_version}"
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
echo -e " ${GREEN}✓${NC} Mole Latest version ${current_version}"
|
printf " ${GREEN}✓${NC} %-12s %s\n" "Mole" "Latest version ${current_version}"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -384,6 +477,7 @@ check_all_updates() {
|
|||||||
get_software_updates > /dev/null
|
get_software_updates > /dev/null
|
||||||
|
|
||||||
echo -e "${BLUE}${ICON_ARROW}${NC} System Updates"
|
echo -e "${BLUE}${ICON_ARROW}${NC} System Updates"
|
||||||
|
check_homebrew_updates
|
||||||
check_appstore_updates
|
check_appstore_updates
|
||||||
check_macos_update
|
check_macos_update
|
||||||
check_mole_update
|
check_mole_update
|
||||||
|
|||||||
@@ -645,13 +645,15 @@ clean_dev_mobile() {
|
|||||||
local unavailable_udid=""
|
local unavailable_udid=""
|
||||||
|
|
||||||
# Check if simctl is accessible and working
|
# Check if simctl is accessible and working
|
||||||
|
local simctl_available=true
|
||||||
if ! xcrun simctl list devices > /dev/null 2>&1; then
|
if ! xcrun simctl list devices > /dev/null 2>&1; then
|
||||||
debug_log "simctl not accessible or CoreSimulator service not running"
|
debug_log "simctl not accessible or CoreSimulator service not running"
|
||||||
echo -e " ${GRAY}${ICON_WARNING}${NC} Xcode unavailable simulators · simctl not available"
|
echo -e " ${GRAY}${ICON_WARNING}${NC} Xcode unavailable simulators · simctl not available"
|
||||||
note_activity
|
note_activity
|
||||||
return 0
|
simctl_available=false
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [[ "$simctl_available" == "true" ]]; then
|
||||||
unavailable_before=$(xcrun simctl list devices unavailable 2> /dev/null | command awk '/\(unavailable/ { count++ } END { print count+0 }' || echo "0")
|
unavailable_before=$(xcrun simctl list devices unavailable 2> /dev/null | command awk '/\(unavailable/ { count++ } END { print count+0 }' || echo "0")
|
||||||
[[ "$unavailable_before" =~ ^[0-9]+$ ]] || unavailable_before=0
|
[[ "$unavailable_before" =~ ^[0-9]+$ ]] || unavailable_before=0
|
||||||
while IFS= read -r unavailable_udid; do
|
while IFS= read -r unavailable_udid; do
|
||||||
@@ -682,9 +684,7 @@ clean_dev_mobile() {
|
|||||||
if ((unavailable_before == 0)); then
|
if ((unavailable_before == 0)); then
|
||||||
echo -e " ${GREEN}${ICON_SUCCESS}${NC} Xcode unavailable simulators · already clean"
|
echo -e " ${GREEN}${ICON_SUCCESS}${NC} Xcode unavailable simulators · already clean"
|
||||||
note_activity
|
note_activity
|
||||||
return 0
|
else
|
||||||
fi
|
|
||||||
|
|
||||||
start_section_spinner "Checking unavailable simulators..."
|
start_section_spinner "Checking unavailable simulators..."
|
||||||
|
|
||||||
# Capture error output for diagnostics
|
# Capture error output for diagnostics
|
||||||
@@ -765,7 +765,9 @@ clean_dev_mobile() {
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
fi # Close if ((unavailable_before == 0))
|
||||||
note_activity
|
note_activity
|
||||||
|
fi # End of simctl_available check
|
||||||
fi
|
fi
|
||||||
# Old iOS/watchOS/tvOS DeviceSupport versions (debug symbols for connected devices).
|
# Old iOS/watchOS/tvOS DeviceSupport versions (debug symbols for connected devices).
|
||||||
# Each iOS version creates a 1-3 GB folder of debug symbols. Only the versions
|
# Each iOS version creates a 1-3 GB folder of debug symbols. Only the versions
|
||||||
|
|||||||
@@ -10,16 +10,23 @@ clean_user_essentials() {
|
|||||||
|
|
||||||
if ! is_path_whitelisted "$HOME/.Trash"; then
|
if ! is_path_whitelisted "$HOME/.Trash"; then
|
||||||
local trash_count
|
local trash_count
|
||||||
trash_count=$(osascript -e 'tell application "Finder" to count items in trash' 2> /dev/null || echo "0")
|
local trash_count_status=0
|
||||||
|
trash_count=$(run_with_timeout 3 osascript -e 'tell application "Finder" to count items in trash' 2> /dev/null) || trash_count_status=$?
|
||||||
|
if [[ $trash_count_status -eq 124 ]]; then
|
||||||
|
debug_log "Finder trash count timed out, using direct .Trash scan"
|
||||||
|
trash_count=$(command find "$HOME/.Trash" -mindepth 1 -maxdepth 1 -exec printf '.' ';' 2> /dev/null |
|
||||||
|
wc -c | awk '{print $1}' || echo "0")
|
||||||
|
fi
|
||||||
[[ "$trash_count" =~ ^[0-9]+$ ]] || trash_count="0"
|
[[ "$trash_count" =~ ^[0-9]+$ ]] || trash_count="0"
|
||||||
|
|
||||||
if [[ "$DRY_RUN" == "true" ]]; then
|
if [[ "$DRY_RUN" == "true" ]]; then
|
||||||
[[ $trash_count -gt 0 ]] && echo -e " ${YELLOW}${ICON_DRY_RUN}${NC} Trash · would empty, $trash_count items" || echo -e " ${GREEN}${ICON_SUCCESS}${NC} Trash · already empty"
|
[[ $trash_count -gt 0 ]] && echo -e " ${YELLOW}${ICON_DRY_RUN}${NC} Trash · would empty, $trash_count items" || echo -e " ${GREEN}${ICON_SUCCESS}${NC} Trash · already empty"
|
||||||
elif [[ $trash_count -gt 0 ]]; then
|
elif [[ $trash_count -gt 0 ]]; then
|
||||||
if osascript -e 'tell application "Finder" to empty trash' > /dev/null 2>&1; then
|
if run_with_timeout 5 osascript -e 'tell application "Finder" to empty trash' > /dev/null 2>&1; then
|
||||||
echo -e " ${GREEN}${ICON_SUCCESS}${NC} Trash · emptied, $trash_count items"
|
echo -e " ${GREEN}${ICON_SUCCESS}${NC} Trash · emptied, $trash_count items"
|
||||||
note_activity
|
note_activity
|
||||||
else
|
else
|
||||||
|
debug_log "Finder trash empty failed or timed out, falling back to direct deletion"
|
||||||
local cleaned_count=0
|
local cleaned_count=0
|
||||||
while IFS= read -r -d '' item; do
|
while IFS= read -r -d '' item; do
|
||||||
if safe_remove "$item" true; then
|
if safe_remove "$item" true; then
|
||||||
@@ -435,6 +442,8 @@ clean_support_app_data() {
|
|||||||
|
|
||||||
# App caches (merged: macOS system caches + Sandboxed apps).
|
# App caches (merged: macOS system caches + Sandboxed apps).
|
||||||
clean_app_caches() {
|
clean_app_caches() {
|
||||||
|
start_section_spinner "Scanning app caches..."
|
||||||
|
|
||||||
# macOS system caches (merged from clean_macos_system_caches)
|
# macOS system caches (merged from clean_macos_system_caches)
|
||||||
safe_clean ~/Library/Saved\ Application\ State/* "Saved application states" || true
|
safe_clean ~/Library/Saved\ Application\ State/* "Saved application states" || true
|
||||||
safe_clean ~/Library/Caches/com.apple.photoanalysisd "Photo analysis cache" || true
|
safe_clean ~/Library/Caches/com.apple.photoanalysisd "Photo analysis cache" || true
|
||||||
@@ -454,8 +463,10 @@ clean_app_caches() {
|
|||||||
safe_clean ~/Library/Application\ Support/AddressBook/Sources/*/Photos.cache "Address Book photo cache" || true
|
safe_clean ~/Library/Application\ Support/AddressBook/Sources/*/Photos.cache "Address Book photo cache" || true
|
||||||
clean_support_app_data
|
clean_support_app_data
|
||||||
|
|
||||||
# Sandboxed app caches
|
# Stop initial scan indicator before entering per-group scans.
|
||||||
stop_section_spinner
|
stop_section_spinner
|
||||||
|
|
||||||
|
# Sandboxed app caches
|
||||||
safe_clean ~/Library/Containers/com.apple.wallpaper.agent/Data/Library/Caches/* "Wallpaper agent cache"
|
safe_clean ~/Library/Containers/com.apple.wallpaper.agent/Data/Library/Caches/* "Wallpaper agent cache"
|
||||||
safe_clean ~/Library/Containers/com.apple.mediaanalysisd/Data/Library/Caches/* "Media analysis cache"
|
safe_clean ~/Library/Containers/com.apple.mediaanalysisd/Data/Library/Caches/* "Media analysis cache"
|
||||||
safe_clean ~/Library/Containers/com.apple.AppStore/Data/Library/Caches/* "App Store cache"
|
safe_clean ~/Library/Containers/com.apple.AppStore/Data/Library/Caches/* "App Store cache"
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ show_uninstall_help() {
|
|||||||
echo ""
|
echo ""
|
||||||
echo "Options:"
|
echo "Options:"
|
||||||
echo " --dry-run Preview app uninstallation without making changes"
|
echo " --dry-run Preview app uninstallation without making changes"
|
||||||
|
echo " --whitelist Not supported for uninstall (use clean/optimize)"
|
||||||
echo " --debug Show detailed operation logs"
|
echo " --debug Show detailed operation logs"
|
||||||
echo " -h, --help Show this help message"
|
echo " -h, --help Show this help message"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,8 +4,8 @@
|
|||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
# Format Homebrew update label for display
|
# Format Homebrew update details for display
|
||||||
format_brew_update_label() {
|
format_brew_update_detail() {
|
||||||
local total="${BREW_OUTDATED_COUNT:-0}"
|
local total="${BREW_OUTDATED_COUNT:-0}"
|
||||||
if [[ -z "$total" || "$total" -le 0 ]]; then
|
if [[ -z "$total" || "$total" -le 0 ]]; then
|
||||||
return
|
return
|
||||||
@@ -18,14 +18,50 @@ format_brew_update_label() {
|
|||||||
((formulas > 0)) && details+=("${formulas} formula")
|
((formulas > 0)) && details+=("${formulas} formula")
|
||||||
((casks > 0)) && details+=("${casks} cask")
|
((casks > 0)) && details+=("${casks} cask")
|
||||||
|
|
||||||
local detail_str=", ${total} updates"
|
local detail_str="${total} updates"
|
||||||
if ((${#details[@]} > 0)); then
|
if ((${#details[@]} > 0)); then
|
||||||
detail_str=", $(
|
detail_str="$(
|
||||||
IFS=', '
|
IFS=', '
|
||||||
printf '%s' "${details[*]}"
|
printf '%s' "${details[*]}"
|
||||||
)"
|
)"
|
||||||
fi
|
fi
|
||||||
printf " %s Homebrew%s" "$ICON_LIST" "$detail_str"
|
printf "%s" "$detail_str"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Keep for compatibility with existing callers/tests.
|
||||||
|
format_brew_update_label() {
|
||||||
|
local detail
|
||||||
|
detail=$(format_brew_update_detail || true)
|
||||||
|
[[ -n "$detail" ]] && printf "Homebrew, %s" "$detail"
|
||||||
|
}
|
||||||
|
|
||||||
|
populate_brew_update_counts_if_unset() {
|
||||||
|
local need_probe=false
|
||||||
|
[[ -z "${BREW_OUTDATED_COUNT:-}" ]] && need_probe=true
|
||||||
|
[[ -z "${BREW_FORMULA_OUTDATED_COUNT:-}" ]] && need_probe=true
|
||||||
|
[[ -z "${BREW_CASK_OUTDATED_COUNT:-}" ]] && need_probe=true
|
||||||
|
|
||||||
|
if [[ "$need_probe" == "false" ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
local formula_count="${BREW_FORMULA_OUTDATED_COUNT:-0}"
|
||||||
|
local cask_count="${BREW_CASK_OUTDATED_COUNT:-0}"
|
||||||
|
|
||||||
|
if command -v brew > /dev/null 2>&1; then
|
||||||
|
local formula_outdated=""
|
||||||
|
local cask_outdated=""
|
||||||
|
|
||||||
|
formula_outdated=$(run_with_timeout 8 brew outdated --formula --quiet 2> /dev/null || true)
|
||||||
|
cask_outdated=$(run_with_timeout 8 brew outdated --cask --quiet 2> /dev/null || true)
|
||||||
|
|
||||||
|
formula_count=$(printf '%s\n' "$formula_outdated" | awk 'NF {count++} END {print count + 0}')
|
||||||
|
cask_count=$(printf '%s\n' "$cask_outdated" | awk 'NF {count++} END {print count + 0}')
|
||||||
|
fi
|
||||||
|
|
||||||
|
BREW_FORMULA_OUTDATED_COUNT="$formula_count"
|
||||||
|
BREW_CASK_OUTDATED_COUNT="$cask_count"
|
||||||
|
BREW_OUTDATED_COUNT="$((formula_count + cask_count))"
|
||||||
}
|
}
|
||||||
|
|
||||||
brew_has_outdated() {
|
brew_has_outdated() {
|
||||||
@@ -42,61 +78,53 @@ brew_has_outdated() {
|
|||||||
# Ask user if they want to update
|
# Ask user if they want to update
|
||||||
# Returns: 0 if yes, 1 if no
|
# Returns: 0 if yes, 1 if no
|
||||||
ask_for_updates() {
|
ask_for_updates() {
|
||||||
local has_updates=false
|
populate_brew_update_counts_if_unset
|
||||||
local -a update_list=()
|
|
||||||
|
|
||||||
local brew_entry
|
local has_updates=false
|
||||||
brew_entry=$(format_brew_update_label || true)
|
if [[ -n "${BREW_OUTDATED_COUNT:-}" && "${BREW_OUTDATED_COUNT:-0}" -gt 0 ]]; then
|
||||||
if [[ -n "$brew_entry" ]]; then
|
|
||||||
has_updates=true
|
has_updates=true
|
||||||
update_list+=("$brew_entry")
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ -n "${APPSTORE_UPDATE_COUNT:-}" && "${APPSTORE_UPDATE_COUNT:-0}" -gt 0 ]]; then
|
if [[ -n "${APPSTORE_UPDATE_COUNT:-}" && "${APPSTORE_UPDATE_COUNT:-0}" -gt 0 ]]; then
|
||||||
has_updates=true
|
has_updates=true
|
||||||
update_list+=(" ${ICON_LIST} App Store, ${APPSTORE_UPDATE_COUNT} apps")
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ -n "${MACOS_UPDATE_AVAILABLE:-}" && "${MACOS_UPDATE_AVAILABLE}" == "true" ]]; then
|
if [[ -n "${MACOS_UPDATE_AVAILABLE:-}" && "${MACOS_UPDATE_AVAILABLE}" == "true" ]]; then
|
||||||
has_updates=true
|
has_updates=true
|
||||||
update_list+=(" ${ICON_LIST} macOS system")
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ -n "${MOLE_UPDATE_AVAILABLE:-}" && "${MOLE_UPDATE_AVAILABLE}" == "true" ]]; then
|
if [[ -n "${MOLE_UPDATE_AVAILABLE:-}" && "${MOLE_UPDATE_AVAILABLE}" == "true" ]]; then
|
||||||
has_updates=true
|
has_updates=true
|
||||||
update_list+=(" ${ICON_LIST} Mole")
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ "$has_updates" == "false" ]]; then
|
if [[ "$has_updates" == "false" ]]; then
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo -e "${BLUE}AVAILABLE UPDATES${NC}"
|
|
||||||
for item in "${update_list[@]}"; do
|
|
||||||
echo -e "$item"
|
|
||||||
done
|
|
||||||
echo ""
|
|
||||||
# If only Mole is relevant for automation, prompt just for Mole
|
|
||||||
if [[ "${MOLE_UPDATE_AVAILABLE:-}" == "true" ]]; then
|
if [[ "${MOLE_UPDATE_AVAILABLE:-}" == "true" ]]; then
|
||||||
echo ""
|
|
||||||
echo -ne "${YELLOW}Update Mole now?${NC} ${GRAY}Enter confirm / ESC cancel${NC}: "
|
echo -ne "${YELLOW}Update Mole now?${NC} ${GRAY}Enter confirm / ESC cancel${NC}: "
|
||||||
|
|
||||||
local key
|
local key
|
||||||
if ! key=$(read_key); then
|
if ! key=$(read_key); then
|
||||||
echo "skip"
|
echo "skip"
|
||||||
echo ""
|
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ "$key" == "ENTER" ]]; then
|
if [[ "$key" == "ENTER" ]]; then
|
||||||
echo "yes"
|
echo "yes"
|
||||||
echo ""
|
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo ""
|
if [[ -n "${BREW_OUTDATED_COUNT:-}" && "${BREW_OUTDATED_COUNT:-0}" -gt 0 ]]; then
|
||||||
echo -e "${ICON_REVIEW} Run ${GREEN}brew upgrade${NC} to update"
|
echo -e " ${GRAY}${ICON_REVIEW}${NC} Run ${GREEN}brew upgrade${NC} to update"
|
||||||
|
fi
|
||||||
|
if [[ -n "${MACOS_UPDATE_AVAILABLE:-}" && "${MACOS_UPDATE_AVAILABLE}" == "true" ]]; then
|
||||||
|
echo -e " ${GRAY}${ICON_REVIEW}${NC} Open ${GREEN}System Settings${NC} → ${GREEN}General${NC} → ${GREEN}Software Update${NC}"
|
||||||
|
fi
|
||||||
|
if [[ -n "${APPSTORE_UPDATE_COUNT:-}" && "${APPSTORE_UPDATE_COUNT:-0}" -gt 0 ]]; then
|
||||||
|
echo -e " ${GRAY}${ICON_REVIEW}${NC} Open ${GREEN}App Store${NC} → ${GREEN}Updates${NC}"
|
||||||
|
fi
|
||||||
|
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -403,7 +403,7 @@ opt_launch_services_rebuild() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ -t 1 ]]; then
|
if [[ -t 1 ]]; then
|
||||||
start_inline_spinner ""
|
MOLE_SPINNER_PREFIX=" " start_inline_spinner "Repairing LaunchServices..."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local lsregister
|
local lsregister
|
||||||
|
|||||||
@@ -148,16 +148,24 @@ refresh_launch_services_after_uninstall() {
|
|||||||
|
|
||||||
local success=0
|
local success=0
|
||||||
set +e
|
set +e
|
||||||
"$lsregister" -gc > /dev/null 2>&1 || true
|
# Add 10s timeout to prevent hanging (gc is usually fast)
|
||||||
"$lsregister" -r -f -domain local -domain user -domain system > /dev/null 2>&1
|
# run_with_timeout falls back to shell implementation if timeout command unavailable
|
||||||
|
run_with_timeout 10 "$lsregister" -gc > /dev/null 2>&1 || true
|
||||||
|
# Add 15s timeout for rebuild (can be slow on some systems)
|
||||||
|
run_with_timeout 15 "$lsregister" -r -f -domain local -domain user -domain system > /dev/null 2>&1
|
||||||
success=$?
|
success=$?
|
||||||
if [[ $success -ne 0 ]]; then
|
# 124 = timeout exit code (from run_with_timeout or timeout command)
|
||||||
"$lsregister" -r -f -domain local -domain user > /dev/null 2>&1
|
if [[ $success -eq 124 ]]; then
|
||||||
|
debug_log "LaunchServices rebuild timed out, trying lighter version"
|
||||||
|
run_with_timeout 10 "$lsregister" -r -f -domain local -domain user > /dev/null 2>&1
|
||||||
|
success=$?
|
||||||
|
elif [[ $success -ne 0 ]]; then
|
||||||
|
run_with_timeout 10 "$lsregister" -r -f -domain local -domain user > /dev/null 2>&1
|
||||||
success=$?
|
success=$?
|
||||||
fi
|
fi
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
[[ $success -eq 0 ]]
|
[[ $success -eq 0 || $success -eq 124 ]]
|
||||||
}
|
}
|
||||||
|
|
||||||
# Remove macOS Login Items for an app
|
# Remove macOS Login Items for an app
|
||||||
@@ -789,7 +797,9 @@ batch_uninstall_applications() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
local autoremove_output removed_count
|
local autoremove_output removed_count
|
||||||
autoremove_output=$(HOMEBREW_NO_ENV_HINTS=1 brew autoremove 2> /dev/null) || true
|
# Add 30s timeout to prevent hanging on slow brew operations
|
||||||
|
# Use run_with_timeout for consistent cross-platform behavior (has shell fallback)
|
||||||
|
autoremove_output=$(run_with_timeout 30 bash -c 'HOMEBREW_NO_ENV_HINTS=1 brew autoremove 2>/dev/null' || true)
|
||||||
removed_count=$(printf '%s\n' "$autoremove_output" | grep -c "^Uninstalling" || true)
|
removed_count=$(printf '%s\n' "$autoremove_output" | grep -c "^Uninstalling" || true)
|
||||||
removed_count=${removed_count:-0}
|
removed_count=${removed_count:-0}
|
||||||
|
|
||||||
|
|||||||
12
mole
12
mole
@@ -88,7 +88,11 @@ is_homebrew_install() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get_install_channel() {
|
get_install_channel() {
|
||||||
local channel_file="$SCRIPT_DIR/install_channel"
|
# Try user config dir first (matches install.sh behavior), fallback to SCRIPT_DIR
|
||||||
|
local channel_file="${MOLE_CONFIG_DIR:-$HOME/.config/mole}/install_channel"
|
||||||
|
if [[ ! -f "$channel_file" ]]; then
|
||||||
|
channel_file="$SCRIPT_DIR/install_channel"
|
||||||
|
fi
|
||||||
local channel="stable"
|
local channel="stable"
|
||||||
if [[ -f "$channel_file" ]]; then
|
if [[ -f "$channel_file" ]]; then
|
||||||
channel=$(sed -n 's/^CHANNEL=\(.*\)$/\1/p' "$channel_file" | head -1)
|
channel=$(sed -n 's/^CHANNEL=\(.*\)$/\1/p' "$channel_file" | head -1)
|
||||||
@@ -100,7 +104,11 @@ get_install_channel() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get_install_commit() {
|
get_install_commit() {
|
||||||
local channel_file="$SCRIPT_DIR/install_channel"
|
# Try user config dir first (matches install.sh behavior), fallback to SCRIPT_DIR
|
||||||
|
local channel_file="${MOLE_CONFIG_DIR:-$HOME/.config/mole}/install_channel"
|
||||||
|
if [[ ! -f "$channel_file" ]]; then
|
||||||
|
channel_file="$SCRIPT_DIR/install_channel"
|
||||||
|
fi
|
||||||
if [[ -f "$channel_file" ]]; then
|
if [[ -f "$channel_file" ]]; then
|
||||||
sed -n 's/^COMMIT_HASH=\(.*\)$/\1/p' "$channel_file" | head -1
|
sed -n 's/^COMMIT_HASH=\(.*\)$/\1/p' "$channel_file" | head -1
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -122,3 +122,52 @@ EOF
|
|||||||
|
|
||||||
[ "$status" -eq 0 ]
|
[ "$status" -eq 0 ]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@test "batch_uninstall_applications tolerates brew autoremove timeout" {
|
||||||
|
local app_bundle="$HOME/Applications/BrewTimeout.app"
|
||||||
|
mkdir -p "$app_bundle"
|
||||||
|
|
||||||
|
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc << 'EOF'
|
||||||
|
set -euo pipefail
|
||||||
|
source "$PROJECT_ROOT/lib/core/common.sh"
|
||||||
|
source "$PROJECT_ROOT/lib/uninstall/batch.sh"
|
||||||
|
|
||||||
|
request_sudo_access() { return 0; }
|
||||||
|
start_inline_spinner() { :; }
|
||||||
|
stop_inline_spinner() { :; }
|
||||||
|
get_file_owner() { whoami; }
|
||||||
|
get_path_size_kb() { echo "100"; }
|
||||||
|
bytes_to_human() { echo "$1"; }
|
||||||
|
drain_pending_input() { :; }
|
||||||
|
print_summary_block() { :; }
|
||||||
|
force_kill_app() { return 0; }
|
||||||
|
remove_apps_from_dock() { :; }
|
||||||
|
refresh_launch_services_after_uninstall() { echo "LS_REFRESH"; }
|
||||||
|
|
||||||
|
get_brew_cask_name() { echo "brew-timeout-cask"; return 0; }
|
||||||
|
brew_uninstall_cask() { return 0; }
|
||||||
|
|
||||||
|
run_with_timeout() {
|
||||||
|
local duration="$1"
|
||||||
|
shift
|
||||||
|
echo "TIMEOUT_CALL:$duration:$*" >> "$HOME/timeout_calls.log"
|
||||||
|
if [[ "$duration" == "30" ]]; then
|
||||||
|
return 124
|
||||||
|
fi
|
||||||
|
"$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
selected_apps=("0|$HOME/Applications/BrewTimeout.app|BrewTimeout|com.example.brewtimeout|0|Never")
|
||||||
|
files_cleaned=0
|
||||||
|
total_items=0
|
||||||
|
total_size_cleaned=0
|
||||||
|
|
||||||
|
printf '\n' | batch_uninstall_applications
|
||||||
|
|
||||||
|
cat "$HOME/timeout_calls.log"
|
||||||
|
EOF
|
||||||
|
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
[[ "$output" == *"TIMEOUT_CALL:30:bash -c HOMEBREW_NO_ENV_HINTS=1 brew autoremove 2>/dev/null"* ]]
|
||||||
|
[[ "$output" == *"LS_REFRESH"* ]]
|
||||||
|
}
|
||||||
|
|||||||
@@ -420,6 +420,80 @@ EOF
|
|||||||
[[ "$output" == *"COUNT=0"* ]]
|
[[ "$output" == *"COUNT=0"* ]]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@test "check_homebrew_updates reports counts and exports update variables" {
|
||||||
|
run bash --noprofile --norc << 'EOF'
|
||||||
|
set -euo pipefail
|
||||||
|
source "$PROJECT_ROOT/lib/core/common.sh"
|
||||||
|
source "$PROJECT_ROOT/lib/check/all.sh"
|
||||||
|
|
||||||
|
run_with_timeout() {
|
||||||
|
local timeout="${1:-}"
|
||||||
|
shift
|
||||||
|
"$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
brew() {
|
||||||
|
if [[ "$1" == "outdated" && "$2" == "--formula" && "$3" == "--quiet" ]]; then
|
||||||
|
printf "wget\njq\n"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
if [[ "$1" == "outdated" && "$2" == "--cask" && "$3" == "--quiet" ]]; then
|
||||||
|
printf "iterm2\n"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
check_homebrew_updates
|
||||||
|
echo "COUNTS=${BREW_OUTDATED_COUNT}:${BREW_FORMULA_OUTDATED_COUNT}:${BREW_CASK_OUTDATED_COUNT}"
|
||||||
|
EOF
|
||||||
|
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
[[ "$output" == *"Homebrew"* ]]
|
||||||
|
[[ "$output" == *"2 formula, 1 cask available"* ]]
|
||||||
|
[[ "$output" == *"COUNTS=3:2:1"* ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "check_homebrew_updates shows timeout warning when brew query times out" {
|
||||||
|
run bash --noprofile --norc << 'EOF'
|
||||||
|
set -euo pipefail
|
||||||
|
source "$PROJECT_ROOT/lib/core/common.sh"
|
||||||
|
source "$PROJECT_ROOT/lib/check/all.sh"
|
||||||
|
|
||||||
|
run_with_timeout() { return 124; }
|
||||||
|
brew() { return 0; }
|
||||||
|
rm -f "$HOME/.cache/mole/brew_updates"
|
||||||
|
|
||||||
|
check_homebrew_updates
|
||||||
|
echo "COUNTS=${BREW_OUTDATED_COUNT}:${BREW_FORMULA_OUTDATED_COUNT}:${BREW_CASK_OUTDATED_COUNT}"
|
||||||
|
EOF
|
||||||
|
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
[[ "$output" == *"Homebrew"* ]]
|
||||||
|
[[ "$output" == *"Check timed out"* ]]
|
||||||
|
[[ "$output" == *"COUNTS=0:0:0"* ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "check_homebrew_updates shows failure warning when brew query fails" {
|
||||||
|
run bash --noprofile --norc << 'EOF'
|
||||||
|
set -euo pipefail
|
||||||
|
source "$PROJECT_ROOT/lib/core/common.sh"
|
||||||
|
source "$PROJECT_ROOT/lib/check/all.sh"
|
||||||
|
|
||||||
|
run_with_timeout() { return 1; }
|
||||||
|
brew() { return 0; }
|
||||||
|
rm -f "$HOME/.cache/mole/brew_updates"
|
||||||
|
|
||||||
|
check_homebrew_updates
|
||||||
|
echo "COUNTS=${BREW_OUTDATED_COUNT}:${BREW_FORMULA_OUTDATED_COUNT}:${BREW_CASK_OUTDATED_COUNT}"
|
||||||
|
EOF
|
||||||
|
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
[[ "$output" == *"Homebrew"* ]]
|
||||||
|
[[ "$output" == *"Check failed"* ]]
|
||||||
|
[[ "$output" == *"COUNTS=0:0:0"* ]]
|
||||||
|
}
|
||||||
|
|
||||||
@test "check_macos_update avoids slow softwareupdate scans" {
|
@test "check_macos_update avoids slow softwareupdate scans" {
|
||||||
run bash --noprofile --norc << 'EOF'
|
run bash --noprofile --norc << 'EOF'
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|||||||
@@ -38,6 +38,44 @@ EOF
|
|||||||
[[ "$output" != *"Trash"* ]]
|
[[ "$output" != *"Trash"* ]]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@test "clean_user_essentials falls back when Finder trash operations time out" {
|
||||||
|
mkdir -p "$HOME/.Trash"
|
||||||
|
touch "$HOME/.Trash/one.tmp" "$HOME/.Trash/two.tmp"
|
||||||
|
|
||||||
|
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc <<'EOF'
|
||||||
|
set -euo pipefail
|
||||||
|
source "$PROJECT_ROOT/lib/core/common.sh"
|
||||||
|
source "$PROJECT_ROOT/lib/clean/user.sh"
|
||||||
|
DRY_RUN=false
|
||||||
|
start_section_spinner() { :; }
|
||||||
|
stop_section_spinner() { :; }
|
||||||
|
safe_clean() { :; }
|
||||||
|
note_activity() { :; }
|
||||||
|
is_path_whitelisted() { return 1; }
|
||||||
|
debug_log() { :; }
|
||||||
|
run_with_timeout() {
|
||||||
|
local _duration="$1"
|
||||||
|
shift
|
||||||
|
if [[ "$1" == "osascript" ]]; then
|
||||||
|
return 124
|
||||||
|
fi
|
||||||
|
"$@"
|
||||||
|
}
|
||||||
|
safe_remove() {
|
||||||
|
local target="$1"
|
||||||
|
/bin/rm -rf "$target"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
clean_user_essentials
|
||||||
|
[[ ! -e "$HOME/.Trash/one.tmp" ]] || exit 1
|
||||||
|
[[ ! -e "$HOME/.Trash/two.tmp" ]] || exit 1
|
||||||
|
EOF
|
||||||
|
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
[[ "$output" == *"Trash · emptied, 2 items"* ]]
|
||||||
|
}
|
||||||
|
|
||||||
@test "clean_app_caches includes macOS system caches" {
|
@test "clean_app_caches includes macOS system caches" {
|
||||||
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc <<'EOF'
|
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc <<'EOF'
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
@@ -58,6 +96,24 @@ EOF
|
|||||||
[[ "$output" == *"Saved application states"* ]] || [[ "$output" == *"App caches"* ]]
|
[[ "$output" == *"Saved application states"* ]] || [[ "$output" == *"App caches"* ]]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@test "clean_app_caches shows spinner during initial app cache scan" {
|
||||||
|
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc <<'EOF'
|
||||||
|
set -euo pipefail
|
||||||
|
source "$PROJECT_ROOT/lib/core/common.sh"
|
||||||
|
source "$PROJECT_ROOT/lib/clean/user.sh"
|
||||||
|
start_section_spinner() { echo "SPIN_START:$1"; }
|
||||||
|
stop_section_spinner() { echo "SPIN_STOP"; }
|
||||||
|
safe_clean() { :; }
|
||||||
|
clean_support_app_data() { :; }
|
||||||
|
clean_group_container_caches() { :; }
|
||||||
|
|
||||||
|
clean_app_caches
|
||||||
|
EOF
|
||||||
|
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
[[ "$output" == *"SPIN_START:Scanning app caches..."* ]]
|
||||||
|
}
|
||||||
|
|
||||||
@test "clean_support_app_data targets crash, wallpaper, and messages preview caches only" {
|
@test "clean_support_app_data targets crash, wallpaper, and messages preview caches only" {
|
||||||
local support_home="$HOME/support-cache-home-1"
|
local support_home="$HOME/support-cache-home-1"
|
||||||
run env HOME="$support_home" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc <<'EOF'
|
run env HOME="$support_home" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc <<'EOF'
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ setup_file() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
teardown_file() {
|
teardown_file() {
|
||||||
rm -f "$PROJECT_ROOT/install_channel"
|
rm -rf "$HOME/.config/mole"
|
||||||
rm -rf "$HOME"
|
rm -rf "$HOME"
|
||||||
if [[ -n "${ORIGINAL_HOME:-}" ]]; then
|
if [[ -n "${ORIGINAL_HOME:-}" ]]; then
|
||||||
export HOME="$ORIGINAL_HOME"
|
export HOME="$ORIGINAL_HOME"
|
||||||
@@ -46,9 +46,8 @@ SCRIPT
|
|||||||
}
|
}
|
||||||
|
|
||||||
setup() {
|
setup() {
|
||||||
rm -rf "$HOME/.config"
|
rm -rf "$HOME/.config/mole"
|
||||||
mkdir -p "$HOME"
|
mkdir -p "$HOME/.config/mole"
|
||||||
rm -f "$PROJECT_ROOT/install_channel"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@test "mole --help prints command overview" {
|
@test "mole --help prints command overview" {
|
||||||
@@ -67,7 +66,8 @@ setup() {
|
|||||||
|
|
||||||
@test "mole --version shows nightly channel metadata" {
|
@test "mole --version shows nightly channel metadata" {
|
||||||
expected_version="$(grep '^VERSION=' "$PROJECT_ROOT/mole" | head -1 | sed 's/VERSION=\"\(.*\)\"/\1/')"
|
expected_version="$(grep '^VERSION=' "$PROJECT_ROOT/mole" | head -1 | sed 's/VERSION=\"\(.*\)\"/\1/')"
|
||||||
cat > "$PROJECT_ROOT/install_channel" <<'EOF'
|
mkdir -p "$HOME/.config/mole"
|
||||||
|
cat > "$HOME/.config/mole/install_channel" <<'EOF'
|
||||||
CHANNEL=nightly
|
CHANNEL=nightly
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
@@ -83,6 +83,12 @@ EOF
|
|||||||
[[ "$output" == *"Unknown command: unknown-command"* ]]
|
[[ "$output" == *"Unknown command: unknown-command"* ]]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@test "mole uninstall --whitelist returns unsupported option error" {
|
||||||
|
run env HOME="$HOME" "$PROJECT_ROOT/mole" uninstall --whitelist
|
||||||
|
[ "$status" -ne 0 ]
|
||||||
|
[[ "$output" == *"Unknown uninstall option: --whitelist"* ]]
|
||||||
|
}
|
||||||
|
|
||||||
@test "touchid status reports current configuration" {
|
@test "touchid status reports current configuration" {
|
||||||
run env HOME="$HOME" "$PROJECT_ROOT/mole" touchid status
|
run env HOME="$HOME" "$PROJECT_ROOT/mole" touchid status
|
||||||
[ "$status" -eq 0 ]
|
[ "$status" -eq 0 ]
|
||||||
|
|||||||
@@ -299,3 +299,27 @@ EOF
|
|||||||
[[ "$output" == *"$volumes_root/unused-runtime"* ]]
|
[[ "$output" == *"$volumes_root/unused-runtime"* ]]
|
||||||
[[ "$output" != *"$volumes_root/in-use-runtime"* ]]
|
[[ "$output" != *"$volumes_root/in-use-runtime"* ]]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@test "clean_dev_mobile continues cleanup when simctl is unavailable" {
|
||||||
|
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc <<'EOF'
|
||||||
|
set -euo pipefail
|
||||||
|
source "$PROJECT_ROOT/lib/core/common.sh"
|
||||||
|
source "$PROJECT_ROOT/lib/clean/dev.sh"
|
||||||
|
|
||||||
|
check_android_ndk() { :; }
|
||||||
|
clean_xcode_documentation_cache() { :; }
|
||||||
|
clean_xcode_simulator_runtime_volumes() { :; }
|
||||||
|
clean_xcode_device_support() { echo "DEVICE_SUPPORT:$2"; }
|
||||||
|
safe_clean() { echo "SAFE_CLEAN:$2"; }
|
||||||
|
note_activity() { :; }
|
||||||
|
debug_log() { :; }
|
||||||
|
xcrun() { return 1; }
|
||||||
|
|
||||||
|
clean_dev_mobile
|
||||||
|
EOF
|
||||||
|
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
[[ "$output" == *"simctl not available"* ]]
|
||||||
|
[[ "$output" == *"DEVICE_SUPPORT:iOS DeviceSupport"* ]]
|
||||||
|
[[ "$output" == *"SAFE_CLEAN:Android SDK cache"* ]]
|
||||||
|
}
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ setup() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@test "Makefile has build target for Go binaries" {
|
@test "Makefile has build target for Go binaries" {
|
||||||
run bash -c "grep -q 'go build' '$PROJECT_ROOT/Makefile'"
|
run bash -c "grep -Eq '(^|[[:space:]])(go|\\$\\(GO\\))[[:space:]]+build' '$PROJECT_ROOT/Makefile'"
|
||||||
[ "$status" -eq 0 ]
|
[ "$status" -eq 0 ]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -313,6 +313,49 @@ EOF
|
|||||||
[ "$status" -eq 0 ]
|
[ "$status" -eq 0 ]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@test "refresh_launch_services_after_uninstall falls back after timeout" {
|
||||||
|
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc <<'EOF'
|
||||||
|
set -euo pipefail
|
||||||
|
source "$PROJECT_ROOT/lib/core/common.sh"
|
||||||
|
source "$PROJECT_ROOT/lib/uninstall/batch.sh"
|
||||||
|
|
||||||
|
log_file="$HOME/lsregister-timeout.log"
|
||||||
|
: > "$log_file"
|
||||||
|
call_index=0
|
||||||
|
|
||||||
|
get_lsregister_path() { echo "/bin/echo"; }
|
||||||
|
debug_log() { echo "DEBUG:$*" >> "$log_file"; }
|
||||||
|
run_with_timeout() {
|
||||||
|
local duration="$1"
|
||||||
|
shift
|
||||||
|
call_index=$((call_index + 1))
|
||||||
|
echo "CALL${call_index}:$duration:$*" >> "$log_file"
|
||||||
|
|
||||||
|
if [[ "$call_index" -eq 2 ]]; then
|
||||||
|
return 124
|
||||||
|
fi
|
||||||
|
if [[ "$call_index" -eq 3 ]]; then
|
||||||
|
return 124
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if refresh_launch_services_after_uninstall; then
|
||||||
|
echo "RESULT:ok"
|
||||||
|
else
|
||||||
|
echo "RESULT:fail"
|
||||||
|
fi
|
||||||
|
|
||||||
|
cat "$log_file"
|
||||||
|
EOF
|
||||||
|
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
[[ "$output" == *"RESULT:ok"* ]]
|
||||||
|
[[ "$output" == *"CALL2:15:/bin/echo -r -f -domain local -domain user -domain system"* ]]
|
||||||
|
[[ "$output" == *"CALL3:10:/bin/echo -r -f -domain local -domain user"* ]]
|
||||||
|
[[ "$output" == *"DEBUG:LaunchServices rebuild timed out, trying lighter version"* ]]
|
||||||
|
}
|
||||||
|
|
||||||
@test "remove_mole deletes manual binaries and caches" {
|
@test "remove_mole deletes manual binaries and caches" {
|
||||||
mkdir -p "$HOME/.local/bin"
|
mkdir -p "$HOME/.local/bin"
|
||||||
touch "$HOME/.local/bin/mole"
|
touch "$HOME/.local/bin/mole"
|
||||||
|
|||||||
@@ -78,10 +78,32 @@ ask_for_updates
|
|||||||
EOF
|
EOF
|
||||||
|
|
||||||
[ "$status" -eq 1 ] # ESC cancels
|
[ "$status" -eq 1 ] # ESC cancels
|
||||||
[[ "$output" == *"Homebrew, 3 formula, 2 cask"* ]]
|
[[ "$output" == *"Update Mole now?"* ]]
|
||||||
[[ "$output" == *"App Store, 1 apps"* ]]
|
[[ "$output" == *"Run "* ]]
|
||||||
[[ "$output" == *"macOS system"* ]]
|
[[ "$output" == *"brew upgrade"* ]]
|
||||||
[[ "$output" == *"Mole"* ]]
|
[[ "$output" == *"Software Update"* ]]
|
||||||
|
[[ "$output" == *"App Store"* ]]
|
||||||
|
[[ "$output" != *"AVAILABLE UPDATES"* ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "ask_for_updates with only macOS update shows settings hint without brew hint" {
|
||||||
|
run bash --noprofile --norc <<'EOF'
|
||||||
|
set -euo pipefail
|
||||||
|
source "$PROJECT_ROOT/lib/core/common.sh"
|
||||||
|
source "$PROJECT_ROOT/lib/manage/update.sh"
|
||||||
|
BREW_OUTDATED_COUNT=0
|
||||||
|
BREW_FORMULA_OUTDATED_COUNT=0
|
||||||
|
BREW_CASK_OUTDATED_COUNT=0
|
||||||
|
APPSTORE_UPDATE_COUNT=0
|
||||||
|
MACOS_UPDATE_AVAILABLE=true
|
||||||
|
MOLE_UPDATE_AVAILABLE=false
|
||||||
|
ask_for_updates
|
||||||
|
EOF
|
||||||
|
|
||||||
|
[ "$status" -eq 1 ]
|
||||||
|
[[ "$output" == *"Software Update"* ]]
|
||||||
|
[[ "$output" != *"brew upgrade"* ]]
|
||||||
|
[[ "$output" != *"AVAILABLE UPDATES"* ]]
|
||||||
}
|
}
|
||||||
|
|
||||||
@test "ask_for_updates accepts Enter when updates exist" {
|
@test "ask_for_updates accepts Enter when updates exist" {
|
||||||
@@ -97,10 +119,50 @@ ask_for_updates
|
|||||||
EOF
|
EOF
|
||||||
|
|
||||||
[ "$status" -eq 0 ]
|
[ "$status" -eq 0 ]
|
||||||
[[ "$output" == *"AVAILABLE UPDATES"* ]]
|
[[ "$output" == *"Update Mole now?"* ]]
|
||||||
[[ "$output" == *"yes"* ]]
|
[[ "$output" == *"yes"* ]]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@test "ask_for_updates auto-detects brew updates when counts are unset" {
|
||||||
|
run bash --noprofile --norc <<'EOF'
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
cat > "$MOCK_BIN_DIR/brew" <<'SCRIPT'
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
if [[ "$1" == "outdated" && "$2" == "--formula" && "$3" == "--quiet" ]]; then
|
||||||
|
printf "wget\njq\n"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
if [[ "$1" == "outdated" && "$2" == "--cask" && "$3" == "--quiet" ]]; then
|
||||||
|
printf "iterm2\n"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
exit 0
|
||||||
|
SCRIPT
|
||||||
|
chmod +x "$MOCK_BIN_DIR/brew"
|
||||||
|
|
||||||
|
source "$PROJECT_ROOT/lib/core/common.sh"
|
||||||
|
source "$PROJECT_ROOT/lib/manage/update.sh"
|
||||||
|
unset BREW_OUTDATED_COUNT BREW_FORMULA_OUTDATED_COUNT BREW_CASK_OUTDATED_COUNT
|
||||||
|
APPSTORE_UPDATE_COUNT=0
|
||||||
|
MACOS_UPDATE_AVAILABLE=false
|
||||||
|
MOLE_UPDATE_AVAILABLE=false
|
||||||
|
|
||||||
|
set +e
|
||||||
|
ask_for_updates
|
||||||
|
ask_status=$?
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "COUNTS:${BREW_OUTDATED_COUNT}:${BREW_FORMULA_OUTDATED_COUNT}:${BREW_CASK_OUTDATED_COUNT}"
|
||||||
|
exit "$ask_status"
|
||||||
|
EOF
|
||||||
|
|
||||||
|
[ "$status" -eq 1 ]
|
||||||
|
[[ "$output" == *"brew upgrade"* ]]
|
||||||
|
[[ "$output" == *"COUNTS:3:2:1"* ]]
|
||||||
|
[[ "$output" != *"AVAILABLE UPDATES"* ]]
|
||||||
|
}
|
||||||
|
|
||||||
@test "format_brew_update_label lists formula and cask counts" {
|
@test "format_brew_update_label lists formula and cask counts" {
|
||||||
run bash --noprofile --norc <<'EOF'
|
run bash --noprofile --norc <<'EOF'
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|||||||
Reference in New Issue
Block a user