1
0
mirror of https://github.com/tw93/Mole.git synced 2026-02-04 19:09:43 +00:00

chore: restructure windows branch (move windows/ content to root, remove macos files)

This commit is contained in:
Tw93
2026-01-10 13:23:29 +08:00
parent e84a457c2f
commit edf5ed09a9
140 changed files with 1472 additions and 34059 deletions

174
scripts/build.ps1 Normal file
View File

@@ -0,0 +1,174 @@
# Mole Windows - Build Script
# Builds Go binaries and validates PowerShell scripts
#Requires -Version 5.1
param(
[switch]$Clean,
[switch]$Release,
[switch]$Validate,
[switch]$ShowHelp
)
$ErrorActionPreference = "Stop"
# Get script directory
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
$windowsDir = Split-Path -Parent $scriptDir
$binDir = Join-Path $windowsDir "bin"
function Show-BuildHelp {
Write-Host ""
Write-Host "Mole Windows Build Script" -ForegroundColor Cyan
Write-Host ""
Write-Host "Usage: .\build.ps1 [options]"
Write-Host ""
Write-Host "Options:"
Write-Host " -Clean Clean build artifacts before building"
Write-Host " -Release Build optimized release binaries"
Write-Host " -Validate Validate PowerShell script syntax"
Write-Host " -ShowHelp Show this help message"
Write-Host ""
}
if ($ShowHelp) {
Show-BuildHelp
return
}
Write-Host ""
Write-Host "========================================" -ForegroundColor Cyan
Write-Host " Mole Windows - Build" -ForegroundColor Cyan
Write-Host "========================================" -ForegroundColor Cyan
Write-Host ""
# ============================================================================
# Clean
# ============================================================================
if ($Clean) {
Write-Host "[Clean] Removing build artifacts..." -ForegroundColor Yellow
$artifacts = @(
(Join-Path $binDir "analyze.exe"),
(Join-Path $binDir "status.exe"),
(Join-Path $windowsDir "coverage-go.out"),
(Join-Path $windowsDir "coverage-pester.xml")
)
foreach ($artifact in $artifacts) {
if (Test-Path $artifact) {
Remove-Item $artifact -Force
Write-Host " Removed: $artifact" -ForegroundColor Gray
}
}
Write-Host ""
}
# ============================================================================
# Validate PowerShell Scripts
# ============================================================================
if ($Validate) {
Write-Host "[Validate] Checking PowerShell script syntax..." -ForegroundColor Yellow
$scripts = Get-ChildItem -Path $windowsDir -Filter "*.ps1" -Recurse
$errors = @()
foreach ($script in $scripts) {
try {
$null = [System.Management.Automation.Language.Parser]::ParseFile(
$script.FullName,
[ref]$null,
[ref]$null
)
Write-Host " OK: $($script.Name)" -ForegroundColor Green
}
catch {
Write-Host " ERROR: $($script.Name)" -ForegroundColor Red
$errors += $script.FullName
}
}
if ($errors.Count -gt 0) {
Write-Host ""
Write-Host " $($errors.Count) script(s) have syntax errors!" -ForegroundColor Red
exit 1
}
Write-Host ""
}
# ============================================================================
# Build Go Binaries
# ============================================================================
Write-Host "[Build] Building Go binaries..." -ForegroundColor Yellow
# Check if Go is installed
$goVersion = & go version 2>&1
if ($LASTEXITCODE -ne 0) {
Write-Host " Error: Go is not installed" -ForegroundColor Red
Write-Host " Please install Go from https://golang.org/dl/" -ForegroundColor Gray
exit 1
}
Write-Host " $goVersion" -ForegroundColor Gray
Write-Host ""
# Create bin directory if needed
if (-not (Test-Path $binDir)) {
New-Item -ItemType Directory -Path $binDir -Force | Out-Null
}
Push-Location $windowsDir
try {
# Build flags
$ldflags = ""
if ($Release) {
$ldflags = "-s -w" # Strip debug info for smaller binaries
}
# Build analyze
Write-Host " Building analyze.exe..." -ForegroundColor Gray
if ($Release) {
& go build -ldflags "$ldflags" -o "$binDir\analyze.exe" "./cmd/analyze/"
}
else {
& go build -o "$binDir\analyze.exe" "./cmd/analyze/"
}
if ($LASTEXITCODE -ne 0) {
Write-Host " Failed to build analyze.exe" -ForegroundColor Red
exit 1
}
$analyzeSize = (Get-Item "$binDir\analyze.exe").Length / 1MB
Write-Host " Built: analyze.exe ($([math]::Round($analyzeSize, 2)) MB)" -ForegroundColor Green
# Build status
Write-Host " Building status.exe..." -ForegroundColor Gray
if ($Release) {
& go build -ldflags "$ldflags" -o "$binDir\status.exe" "./cmd/status/"
}
else {
& go build -o "$binDir\status.exe" "./cmd/status/"
}
if ($LASTEXITCODE -ne 0) {
Write-Host " Failed to build status.exe" -ForegroundColor Red
exit 1
}
$statusSize = (Get-Item "$binDir\status.exe").Length / 1MB
Write-Host " Built: status.exe ($([math]::Round($statusSize, 2)) MB)" -ForegroundColor Green
}
finally {
Pop-Location
}
Write-Host ""
Write-Host "========================================" -ForegroundColor Cyan
Write-Host " Build complete!" -ForegroundColor Green
Write-Host "========================================" -ForegroundColor Cyan
Write-Host ""

View File

@@ -1,221 +0,0 @@
#!/bin/bash
# Code quality checks for Mole.
# Auto-formats code, then runs lint and syntax checks.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
MODE="all"
usage() {
cat << 'EOF'
Usage: ./scripts/check.sh [--format|--no-format]
Options:
--format Apply formatting fixes only (shfmt, gofmt)
--no-format Skip formatting and run checks only
--help Show this help
EOF
}
while [[ $# -gt 0 ]]; do
case "$1" in
--format)
MODE="format"
shift
;;
--no-format)
MODE="check"
shift
;;
--help | -h)
usage
exit 0
;;
*)
echo "Unknown option: $1"
usage
exit 1
;;
esac
done
cd "$PROJECT_ROOT"
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
readonly ICON_SUCCESS="✓"
readonly ICON_ERROR="☻"
readonly ICON_WARNING="●"
readonly ICON_LIST="•"
echo -e "${BLUE}=== Mole Check (${MODE}) ===${NC}\n"
SHELL_FILES=$(find . -type f \( -name "*.sh" -o -name "mole" \) \
-not -path "./.git/*" \
-not -path "*/node_modules/*" \
-not -path "*/tests/tmp-*/*" \
-not -path "*/.*" \
2> /dev/null)
if [[ "$MODE" == "format" ]]; then
echo -e "${YELLOW}Formatting shell scripts...${NC}"
if command -v shfmt > /dev/null 2>&1; then
echo "$SHELL_FILES" | xargs shfmt -i 4 -ci -sr -w
echo -e "${GREEN}${ICON_SUCCESS} Shell formatting complete${NC}\n"
else
echo -e "${RED}${ICON_ERROR} shfmt not installed${NC}"
exit 1
fi
if command -v goimports > /dev/null 2>&1; then
echo -e "${YELLOW}Formatting Go code (goimports)...${NC}"
goimports -w -local github.com/tw93/Mole ./cmd
echo -e "${GREEN}${ICON_SUCCESS} Go formatting complete${NC}\n"
elif command -v go > /dev/null 2>&1; then
echo -e "${YELLOW}Formatting Go code (gofmt)...${NC}"
gofmt -w ./cmd
echo -e "${GREEN}${ICON_SUCCESS} Go formatting complete${NC}\n"
else
echo -e "${YELLOW}${ICON_WARNING} go not installed, skipping gofmt${NC}\n"
fi
echo -e "${GREEN}=== Format Completed ===${NC}"
exit 0
fi
if [[ "$MODE" != "check" ]]; then
echo -e "${YELLOW}1. Formatting shell scripts...${NC}"
if command -v shfmt > /dev/null 2>&1; then
echo "$SHELL_FILES" | xargs shfmt -i 4 -ci -sr -w
echo -e "${GREEN}${ICON_SUCCESS} Shell formatting applied${NC}\n"
else
echo -e "${YELLOW}${ICON_WARNING} shfmt not installed, skipping${NC}\n"
fi
if command -v goimports > /dev/null 2>&1; then
echo -e "${YELLOW}2. Formatting Go code (goimports)...${NC}"
goimports -w -local github.com/tw93/Mole ./cmd
echo -e "${GREEN}${ICON_SUCCESS} Go formatting applied${NC}\n"
elif command -v go > /dev/null 2>&1; then
echo -e "${YELLOW}2. Formatting Go code (gofmt)...${NC}"
gofmt -w ./cmd
echo -e "${GREEN}${ICON_SUCCESS} Go formatting applied${NC}\n"
fi
fi
echo -e "${YELLOW}3. Running Go linters...${NC}"
if command -v golangci-lint > /dev/null 2>&1; then
if ! golangci-lint config verify; then
echo -e "${RED}${ICON_ERROR} golangci-lint config invalid${NC}\n"
exit 1
fi
if golangci-lint run ./cmd/...; then
echo -e "${GREEN}${ICON_SUCCESS} golangci-lint passed${NC}\n"
else
echo -e "${RED}${ICON_ERROR} golangci-lint failed${NC}\n"
exit 1
fi
elif command -v go > /dev/null 2>&1; then
echo -e "${YELLOW}${ICON_WARNING} golangci-lint not installed, falling back to go vet${NC}"
if go vet ./cmd/...; then
echo -e "${GREEN}${ICON_SUCCESS} go vet passed${NC}\n"
else
echo -e "${RED}${ICON_ERROR} go vet failed${NC}\n"
exit 1
fi
else
echo -e "${YELLOW}${ICON_WARNING} Go not installed, skipping Go checks${NC}\n"
fi
echo -e "${YELLOW}4. Running ShellCheck...${NC}"
if command -v shellcheck > /dev/null 2>&1; then
if shellcheck mole bin/*.sh lib/*/*.sh scripts/*.sh; then
echo -e "${GREEN}${ICON_SUCCESS} ShellCheck passed${NC}\n"
else
echo -e "${RED}${ICON_ERROR} ShellCheck failed${NC}\n"
exit 1
fi
else
echo -e "${YELLOW}${ICON_WARNING} shellcheck not installed, skipping${NC}\n"
fi
echo -e "${YELLOW}5. Running syntax check...${NC}"
if ! bash -n mole; then
echo -e "${RED}${ICON_ERROR} Syntax check failed (mole)${NC}\n"
exit 1
fi
for script in bin/*.sh; do
if ! bash -n "$script"; then
echo -e "${RED}${ICON_ERROR} Syntax check failed ($script)${NC}\n"
exit 1
fi
done
find lib -name "*.sh" | while read -r script; do
if ! bash -n "$script"; then
echo -e "${RED}${ICON_ERROR} Syntax check failed ($script)${NC}\n"
exit 1
fi
done
echo -e "${GREEN}${ICON_SUCCESS} Syntax check passed${NC}\n"
echo -e "${YELLOW}6. Checking optimizations...${NC}"
OPTIMIZATION_SCORE=0
TOTAL_CHECKS=0
((TOTAL_CHECKS++))
if grep -q "read -r -s -n 1 -t 1" lib/core/ui.sh; then
echo -e "${GREEN} ${ICON_SUCCESS} Keyboard timeout configured${NC}"
((OPTIMIZATION_SCORE++))
else
echo -e "${YELLOW} ${ICON_WARNING} Keyboard timeout may be misconfigured${NC}"
fi
((TOTAL_CHECKS++))
DRAIN_PASSES=$(grep -c "while IFS= read -r -s -n 1" lib/core/ui.sh 2> /dev/null || true)
DRAIN_PASSES=${DRAIN_PASSES:-0}
if [[ $DRAIN_PASSES -eq 1 ]]; then
echo -e "${GREEN} ${ICON_SUCCESS} drain_pending_input optimized${NC}"
((OPTIMIZATION_SCORE++))
else
echo -e "${YELLOW} ${ICON_WARNING} drain_pending_input has multiple passes${NC}"
fi
((TOTAL_CHECKS++))
if grep -q "rotate_log_once" lib/core/log.sh; then
echo -e "${GREEN} ${ICON_SUCCESS} Log rotation optimized${NC}"
((OPTIMIZATION_SCORE++))
else
echo -e "${YELLOW} ${ICON_WARNING} Log rotation not optimized${NC}"
fi
((TOTAL_CHECKS++))
if ! grep -q "cache_meta\|cache_dir_mtime" bin/uninstall.sh; then
echo -e "${GREEN} ${ICON_SUCCESS} Cache validation simplified${NC}"
((OPTIMIZATION_SCORE++))
else
echo -e "${YELLOW} ${ICON_WARNING} Cache still uses redundant metadata${NC}"
fi
((TOTAL_CHECKS++))
if grep -q "Consecutive slashes" bin/clean.sh; then
echo -e "${GREEN} ${ICON_SUCCESS} Path validation enhanced${NC}"
((OPTIMIZATION_SCORE++))
else
echo -e "${YELLOW} ${ICON_WARNING} Path validation not enhanced${NC}"
fi
echo -e "${BLUE} Optimization score: $OPTIMIZATION_SCORE/$TOTAL_CHECKS${NC}\n"
echo -e "${GREEN}=== Checks Completed ===${NC}"
if [[ $OPTIMIZATION_SCORE -eq $TOTAL_CHECKS ]]; then
echo -e "${GREEN}${ICON_SUCCESS} All optimizations applied${NC}"
else
echo -e "${YELLOW}${ICON_WARNING} Some optimizations missing${NC}"
fi

View File

@@ -1,424 +0,0 @@
#!/bin/bash
# Create Raycast script commands and Alfred keywords for Mole (clean + uninstall).
set -euo pipefail
BLUE='\033[0;34m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
NC='\033[0m'
ICON_STEP="➜"
ICON_SUCCESS="✓"
ICON_WARN="!"
ICON_ERR="✗"
log_step() { echo -e "${BLUE}${ICON_STEP}${NC} $1"; }
log_success() { echo -e "${GREEN}${ICON_SUCCESS}${NC} $1"; }
log_warn() { echo -e "${YELLOW}${ICON_WARN}${NC} $1"; }
log_error() { echo -e "${RED}${ICON_ERR}${NC} $1"; }
log_header() { echo -e "\n${BLUE}==== $1 ====${NC}\n"; }
is_interactive() { [[ -t 1 && -r /dev/tty ]]; }
prompt_enter() {
local prompt="$1"
if is_interactive; then
read -r -p "$prompt" < /dev/tty || true
else
echo "$prompt"
fi
}
detect_mo() {
if command -v mo > /dev/null 2>&1; then
command -v mo
elif command -v mole > /dev/null 2>&1; then
command -v mole
else
log_error "Mole not found. Install it first via Homebrew or ./install.sh."
exit 1
fi
}
write_raycast_script() {
local target="$1"
local title="$2"
local mo_bin="$3"
local subcommand="$4"
local raw_cmd="\"${mo_bin}\" ${subcommand}"
local cmd_escaped="${raw_cmd//\\/\\\\}"
cmd_escaped="${cmd_escaped//\"/\\\"}"
cat > "$target" << EOF
#!/bin/bash
# Required parameters:
# @raycast.schemaVersion 1
# @raycast.title ${title}
# @raycast.mode fullOutput
# @raycast.packageName Mole
# Optional parameters:
# @raycast.icon 🐹
set -euo pipefail
echo "🐹 Running ${title}..."
echo ""
CMD="${raw_cmd}"
CMD_ESCAPED="${cmd_escaped}"
has_app() {
local name="\$1"
[[ -d "/Applications/\${name}.app" || -d "\$HOME/Applications/\${name}.app" ]]
}
has_bin() {
command -v "\$1" >/dev/null 2>&1
}
launcher_available() {
local app="\$1"
case "\$app" in
Terminal) return 0 ;;
iTerm|iTerm2) has_app "iTerm" || has_app "iTerm2" ;;
Alacritty) has_app "Alacritty" ;;
Kitty) has_bin "kitty" || has_app "kitty" ;;
WezTerm) has_bin "wezterm" || has_app "WezTerm" ;;
Ghostty) has_bin "ghostty" || has_app "Ghostty" ;;
Hyper) has_app "Hyper" ;;
WindTerm) has_app "WindTerm" ;;
Warp) has_app "Warp" ;;
*)
return 1 ;;
esac
}
detect_launcher_app() {
if [[ -n "\${MO_LAUNCHER_APP:-}" ]]; then
echo "\${MO_LAUNCHER_APP}"
return
fi
local candidates=(Warp Ghostty Alacritty Kitty WezTerm WindTerm Hyper iTerm2 iTerm Terminal)
local app
for app in "\${candidates[@]}"; do
if launcher_available "\$app"; then
echo "\$app"
return
fi
done
echo "Terminal"
}
launch_with_app() {
local app="\$1"
case "\$app" in
Terminal)
if command -v osascript >/dev/null 2>&1; then
osascript <<'APPLESCRIPT'
set targetCommand to "${cmd_escaped}"
tell application "Terminal"
activate
do script targetCommand
end tell
APPLESCRIPT
return 0
fi
;;
iTerm|iTerm2)
if command -v osascript >/dev/null 2>&1; then
osascript <<'APPLESCRIPT'
set targetCommand to "${cmd_escaped}"
tell application "iTerm2"
activate
try
tell current window
tell current session
write text targetCommand
end tell
end tell
on error
create window with default profile
tell current window
tell current session
write text targetCommand
end tell
end tell
end try
end tell
APPLESCRIPT
return 0
fi
;;
Alacritty)
if launcher_available "Alacritty" && command -v open >/dev/null 2>&1; then
open -na "Alacritty" --args -e /bin/zsh -lc "${raw_cmd}"
return \$?
fi
;;
Kitty)
if has_bin "kitty"; then
kitty --hold /bin/zsh -lc "${raw_cmd}"
return \$?
elif [[ -x "/Applications/kitty.app/Contents/MacOS/kitty" ]]; then
"/Applications/kitty.app/Contents/MacOS/kitty" --hold /bin/zsh -lc "${raw_cmd}"
return \$?
fi
;;
WezTerm)
if has_bin "wezterm"; then
wezterm start -- /bin/zsh -lc "${raw_cmd}"
return \$?
elif [[ -x "/Applications/WezTerm.app/Contents/MacOS/wezterm" ]]; then
"/Applications/WezTerm.app/Contents/MacOS/wezterm" start -- /bin/zsh -lc "${raw_cmd}"
return \$?
fi
;;
Ghostty)
if has_bin "ghostty"; then
ghostty --command "/bin/zsh" -- -lc "${raw_cmd}"
return \$?
elif [[ -x "/Applications/Ghostty.app/Contents/MacOS/ghostty" ]]; then
"/Applications/Ghostty.app/Contents/MacOS/ghostty" --command "/bin/zsh" -- -lc "${raw_cmd}"
return \$?
fi
;;
Hyper)
if launcher_available "Hyper" && command -v open >/dev/null 2>&1; then
open -na "Hyper" --args /bin/zsh -lc "${raw_cmd}"
return \$?
fi
;;
WindTerm)
if launcher_available "WindTerm" && command -v open >/dev/null 2>&1; then
open -na "WindTerm" --args /bin/zsh -lc "${raw_cmd}"
return \$?
fi
;;
Warp)
if launcher_available "Warp" && command -v open >/dev/null 2>&1; then
open -na "Warp" --args /bin/zsh -lc "${raw_cmd}"
return \$?
fi
;;
esac
return 1
}
if [[ -n "\${TERM:-}" && "\${TERM}" != "dumb" ]]; then
"${mo_bin}" ${subcommand}
exit \$?
fi
TERM_APP="\$(detect_launcher_app)"
if launch_with_app "\$TERM_APP"; then
exit 0
fi
if [[ "\$TERM_APP" != "Terminal" ]]; then
echo "Could not control \$TERM_APP, falling back to Terminal..."
if launch_with_app "Terminal"; then
exit 0
fi
fi
echo "TERM environment variable not set and no launcher succeeded."
echo "Run this manually:"
echo " ${raw_cmd}"
exit 1
EOF
chmod +x "$target"
}
create_raycast_commands() {
local mo_bin="$1"
local default_dir="$HOME/Library/Application Support/Raycast/script-commands"
local dir="$default_dir"
log_step "Installing Raycast commands..."
mkdir -p "$dir"
write_raycast_script "$dir/mole-clean.sh" "clean" "$mo_bin" "clean"
write_raycast_script "$dir/mole-uninstall.sh" "uninstall" "$mo_bin" "uninstall"
write_raycast_script "$dir/mole-optimize.sh" "optimize" "$mo_bin" "optimize"
write_raycast_script "$dir/mole-analyze.sh" "analyze" "$mo_bin" "analyze"
write_raycast_script "$dir/mole-status.sh" "status" "$mo_bin" "status"
log_success "Scripts ready in: $dir"
log_header "Raycast Configuration"
if command -v open > /dev/null 2>&1; then
if open "raycast://extensions/raycast/raycast-settings/extensions" > /dev/null 2>&1; then
log_step "Raycast settings opened."
else
log_warn "Could not auto-open Raycast."
fi
else
log_warn "open command not available; please open Raycast manually."
fi
echo "If Raycast asks to add a Script Directory, use:"
echo " $dir"
if is_interactive; then
log_header "Finalizing Setup"
prompt_enter "Press [Enter] to reload script directories in Raycast..."
if command -v open > /dev/null 2>&1 && open "raycast://extensions/raycast/raycast/reload-script-directories" > /dev/null 2>&1; then
log_step "Raycast script directories reloaded."
else
log_warn "Could not auto-reload Raycast script directories."
fi
log_success "Raycast setup complete!"
else
log_warn "Non-interactive mode; skip Raycast reload. Please run 'Reload Script Directories' in Raycast."
fi
}
uuid() {
if command -v uuidgen > /dev/null 2>&1; then
uuidgen
else
# Fallback pseudo UUID in format: 8-4-4-4-12
local hex=$(openssl rand -hex 16)
echo "${hex:0:8}-${hex:8:4}-${hex:12:4}-${hex:16:4}-${hex:20:12}"
fi
}
create_alfred_workflow() {
local mo_bin="$1"
local prefs_dir="${ALFRED_PREFS_DIR:-$HOME/Library/Application Support/Alfred/Alfred.alfredpreferences}"
local workflows_dir="$prefs_dir/workflows"
if [[ ! -d "$workflows_dir" ]]; then
return
fi
log_step "Installing Alfred workflows..."
local workflows=(
"fun.tw93.mole.clean|Mole clean|clean|Run Mole clean|\"${mo_bin}\" clean"
"fun.tw93.mole.uninstall|Mole uninstall|uninstall|Uninstall apps via Mole|\"${mo_bin}\" uninstall"
"fun.tw93.mole.optimize|Mole optimize|optimize|System health & optimization|\"${mo_bin}\" optimize"
"fun.tw93.mole.analyze|Mole analyze|analyze|Disk space analysis|\"${mo_bin}\" analyze"
"fun.tw93.mole.status|Mole status|status|Live system dashboard|\"${mo_bin}\" status"
)
for entry in "${workflows[@]}"; do
IFS="|" read -r bundle name keyword subtitle command <<< "$entry"
local workflow_uid="user.workflow.$(uuid | LC_ALL=C tr '[:upper:]' '[:lower:]')"
local input_uid
local action_uid
input_uid="$(uuid)"
action_uid="$(uuid)"
local dir="$workflows_dir/$workflow_uid"
mkdir -p "$dir"
cat > "$dir/info.plist" << EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>bundleid</key>
<string>${bundle}</string>
<key>createdby</key>
<string>Mole</string>
<key>name</key>
<string>${name}</string>
<key>objects</key>
<array>
<dict>
<key>config</key>
<dict>
<key>argumenttype</key>
<integer>2</integer>
<key>keyword</key>
<string>${keyword}</string>
<key>subtext</key>
<string>${subtitle}</string>
<key>text</key>
<string>${name}</string>
<key>withspace</key>
<true/>
</dict>
<key>type</key>
<string>alfred.workflow.input.keyword</string>
<key>uid</key>
<string>${input_uid}</string>
<key>version</key>
<integer>1</integer>
</dict>
<dict>
<key>config</key>
<dict>
<key>concurrently</key>
<true/>
<key>escaping</key>
<integer>102</integer>
<key>script</key>
<string>#!/bin/bash
PATH="/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin"
${command}
</string>
<key>scriptargtype</key>
<integer>1</integer>
<key>scriptfile</key>
<string></string>
<key>type</key>
<integer>0</integer>
</dict>
<key>type</key>
<string>alfred.workflow.action.script</string>
<key>uid</key>
<string>${action_uid}</string>
<key>version</key>
<integer>2</integer>
</dict>
</array>
<key>connections</key>
<dict>
<key>${input_uid}</key>
<array>
<dict>
<key>destinationuid</key>
<string>${action_uid}</string>
<key>modifiers</key>
<integer>0</integer>
<key>modifiersubtext</key>
<string></string>
</dict>
</array>
</dict>
<key>uid</key>
<string>${workflow_uid}</string>
<key>version</key>
<integer>1</integer>
</dict>
</plist>
EOF
log_success "Workflow ready: ${name} (keyword: ${keyword})"
done
log_step "Open Alfred preferences → Workflows if you need to adjust keywords."
}
main() {
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo " Mole Quick Launchers"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
local mo_bin
mo_bin="$(detect_mo)"
log_step "Detected Mole binary at: ${mo_bin}"
create_raycast_commands "$mo_bin"
create_alfred_workflow "$mo_bin"
echo ""
log_success "Done! Raycast and Alfred are ready with 5 commands:"
echo " • clean - Deep system cleanup"
echo " • uninstall - Remove applications"
echo " • optimize - System health & tuning"
echo " • analyze - Disk space explorer"
echo " • status - Live system monitor"
echo ""
}
main "$@"

141
scripts/test.ps1 Normal file
View File

@@ -0,0 +1,141 @@
# Mole Windows - Test Runner Script
# Runs all tests (Pester for PowerShell, go test for Go)
#Requires -Version 5.1
param(
[switch]$Verbose,
[switch]$NoPester,
[switch]$NoGo,
[switch]$Coverage
)
$ErrorActionPreference = "Stop"
$script:ExitCode = 0
# Get script directory
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
$windowsDir = Split-Path -Parent $scriptDir
Write-Host ""
Write-Host "========================================" -ForegroundColor Cyan
Write-Host " Mole Windows - Test Suite" -ForegroundColor Cyan
Write-Host "========================================" -ForegroundColor Cyan
Write-Host ""
# ============================================================================
# Pester Tests
# ============================================================================
if (-not $NoPester) {
Write-Host "[Pester] Running PowerShell tests..." -ForegroundColor Yellow
Write-Host ""
# Check if Pester is installed
$pesterModule = Get-Module -ListAvailable -Name Pester | Where-Object { $_.Version -ge "5.0.0" }
if (-not $pesterModule) {
Write-Host " Installing Pester 5.x..." -ForegroundColor Gray
Install-Module -Name Pester -MinimumVersion 5.0.0 -Force -SkipPublisherCheck -Scope CurrentUser
}
Import-Module Pester -MinimumVersion 5.0.0
$testsDir = Join-Path $windowsDir "tests"
$config = New-PesterConfiguration
$config.Run.Path = $testsDir
$config.Run.Exit = $false
$config.Output.Verbosity = if ($Verbose) { "Detailed" } else { "Normal" }
if ($Coverage) {
$config.CodeCoverage.Enabled = $true
$config.CodeCoverage.Path = @(
(Join-Path $windowsDir "lib\core\*.ps1"),
(Join-Path $windowsDir "lib\clean\*.ps1"),
(Join-Path $windowsDir "bin\*.ps1")
)
$config.CodeCoverage.OutputPath = Join-Path $windowsDir "coverage-pester.xml"
}
try {
$result = Invoke-Pester -Configuration $config
Write-Host ""
Write-Host "[Pester] Results:" -ForegroundColor Yellow
Write-Host " Passed: $($result.PassedCount)" -ForegroundColor Green
Write-Host " Failed: $($result.FailedCount)" -ForegroundColor $(if ($result.FailedCount -gt 0) { "Red" } else { "Green" })
Write-Host " Skipped: $($result.SkippedCount)" -ForegroundColor Gray
if ($result.FailedCount -gt 0) {
$script:ExitCode = 1
}
}
catch {
Write-Host " Error running Pester tests: $_" -ForegroundColor Red
$script:ExitCode = 1
}
Write-Host ""
}
# ============================================================================
# Go Tests
# ============================================================================
if (-not $NoGo) {
Write-Host "[Go] Running Go tests..." -ForegroundColor Yellow
Write-Host ""
# Check if Go is installed
$goVersion = & go version 2>&1
if ($LASTEXITCODE -ne 0) {
Write-Host " Go is not installed, skipping Go tests" -ForegroundColor Gray
}
else {
Write-Host " $goVersion" -ForegroundColor Gray
Write-Host ""
Push-Location $windowsDir
try {
$goArgs = @("test")
if ($Verbose) {
$goArgs += "-v"
}
if ($Coverage) {
$goArgs += "-coverprofile=coverage-go.out"
}
$goArgs += "./..."
& go @goArgs
if ($LASTEXITCODE -ne 0) {
$script:ExitCode = 1
}
else {
Write-Host ""
Write-Host "[Go] All tests passed" -ForegroundColor Green
}
}
finally {
Pop-Location
}
}
Write-Host ""
}
# ============================================================================
# Summary
# ============================================================================
Write-Host "========================================" -ForegroundColor Cyan
if ($script:ExitCode -eq 0) {
Write-Host " All tests passed!" -ForegroundColor Green
}
else {
Write-Host " Some tests failed!" -ForegroundColor Red
}
Write-Host "========================================" -ForegroundColor Cyan
Write-Host ""
exit $script:ExitCode

View File

@@ -1,207 +0,0 @@
#!/bin/bash
# Test runner for Mole.
# Runs unit, Go, and integration tests.
# Exits non-zero on failures.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
cd "$PROJECT_ROOT"
# shellcheck source=lib/core/file_ops.sh
source "$PROJECT_ROOT/lib/core/file_ops.sh"
echo "==============================="
echo "Mole Test Runner"
echo "==============================="
echo ""
FAILED=0
report_unit_result() {
if [[ $1 -eq 0 ]]; then
printf "${GREEN}${ICON_SUCCESS} Unit tests passed${NC}\n"
else
printf "${RED}${ICON_ERROR} Unit tests failed${NC}\n"
((FAILED++))
fi
}
echo "1. Linting test scripts..."
if command -v shellcheck > /dev/null 2>&1; then
TEST_FILES=()
while IFS= read -r file; do
TEST_FILES+=("$file")
done < <(find tests -type f \( -name '*.bats' -o -name '*.sh' \) | sort)
if [[ ${#TEST_FILES[@]} -gt 0 ]]; then
if shellcheck --rcfile "$PROJECT_ROOT/.shellcheckrc" "${TEST_FILES[@]}"; then
printf "${GREEN}${ICON_SUCCESS} Test script lint passed${NC}\n"
else
printf "${RED}${ICON_ERROR} Test script lint failed${NC}\n"
((FAILED++))
fi
else
printf "${YELLOW}${ICON_WARNING} No test scripts found, skipping${NC}\n"
fi
else
printf "${YELLOW}${ICON_WARNING} shellcheck not installed, skipping${NC}\n"
fi
echo ""
echo "2. Running unit tests..."
if command -v bats > /dev/null 2>&1 && [ -d "tests" ]; then
if [[ -z "${TERM:-}" ]]; then
export TERM="xterm-256color"
fi
if [[ $# -eq 0 ]]; then
fd_available=0
zip_available=0
zip_list_available=0
if command -v fd > /dev/null 2>&1; then
fd_available=1
fi
if command -v zip > /dev/null 2>&1; then
zip_available=1
fi
if command -v zipinfo > /dev/null 2>&1 || command -v unzip > /dev/null 2>&1; then
zip_list_available=1
fi
TEST_FILES=()
while IFS= read -r file; do
case "$file" in
tests/installer_fd.bats)
if [[ $fd_available -eq 1 ]]; then
TEST_FILES+=("$file")
fi
;;
tests/installer_zip.bats)
if [[ $zip_available -eq 1 && $zip_list_available -eq 1 ]]; then
TEST_FILES+=("$file")
fi
;;
*)
TEST_FILES+=("$file")
;;
esac
done < <(find tests -type f -name '*.bats' | sort)
if [[ ${#TEST_FILES[@]} -gt 0 ]]; then
set -- "${TEST_FILES[@]}"
else
set -- tests
fi
fi
use_color=false
if [[ -t 1 && "${TERM:-}" != "dumb" ]]; then
use_color=true
fi
if bats --help 2>&1 | grep -q -- "--formatter"; then
formatter="${BATS_FORMATTER:-pretty}"
if [[ "$formatter" == "tap" ]]; then
if $use_color; then
esc=$'\033'
if bats --formatter tap "$@" |
sed -e "s/^ok /${esc}[32mok ${esc}[0m /" \
-e "s/^not ok /${esc}[31mnot ok ${esc}[0m /"; then
report_unit_result 0
else
report_unit_result 1
fi
else
if bats --formatter tap "$@"; then
report_unit_result 0
else
report_unit_result 1
fi
fi
else
# Pretty format for local development
if bats --formatter "$formatter" "$@"; then
report_unit_result 0
else
report_unit_result 1
fi
fi
else
if $use_color; then
esc=$'\033'
if bats --tap "$@" |
sed -e "s/^ok /${esc}[32mok ${esc}[0m /" \
-e "s/^not ok /${esc}[31mnot ok ${esc}[0m /"; then
report_unit_result 0
else
report_unit_result 1
fi
else
if bats --tap "$@"; then
report_unit_result 0
else
report_unit_result 1
fi
fi
fi
else
printf "${YELLOW}${ICON_WARNING} bats not installed or no tests found, skipping${NC}\n"
fi
echo ""
echo "3. Running Go tests..."
if command -v go > /dev/null 2>&1; then
if go build ./... > /dev/null 2>&1 && go vet ./cmd/... > /dev/null 2>&1 && go test ./cmd/... > /dev/null 2>&1; then
printf "${GREEN}${ICON_SUCCESS} Go tests passed${NC}\n"
else
printf "${RED}${ICON_ERROR} Go tests failed${NC}\n"
((FAILED++))
fi
else
printf "${YELLOW}${ICON_WARNING} Go not installed, skipping Go tests${NC}\n"
fi
echo ""
echo "4. Testing module loading..."
if bash -c 'source lib/core/common.sh && echo "OK"' > /dev/null 2>&1; then
printf "${GREEN}${ICON_SUCCESS} Module loading passed${NC}\n"
else
printf "${RED}${ICON_ERROR} Module loading failed${NC}\n"
((FAILED++))
fi
echo ""
echo "5. Running integration tests..."
# Quick syntax check for main scripts
if bash -n mole && bash -n bin/clean.sh && bash -n bin/optimize.sh; then
printf "${GREEN}${ICON_SUCCESS} Integration tests passed${NC}\n"
else
printf "${RED}${ICON_ERROR} Integration tests failed${NC}\n"
((FAILED++))
fi
echo ""
echo "6. Testing installation..."
# Skip if Homebrew mole is installed (install.sh will refuse to overwrite)
if brew list mole &> /dev/null; then
printf "${GREEN}${ICON_SUCCESS} Installation test skipped (Homebrew)${NC}\n"
elif ./install.sh --prefix /tmp/mole-test > /dev/null 2>&1; then
if [ -f /tmp/mole-test/mole ]; then
printf "${GREEN}${ICON_SUCCESS} Installation test passed${NC}\n"
else
printf "${RED}${ICON_ERROR} Installation test failed${NC}\n"
((FAILED++))
fi
else
printf "${RED}${ICON_ERROR} Installation test failed${NC}\n"
((FAILED++))
fi
safe_remove "/tmp/mole-test" true || true
echo ""
echo "==============================="
if [[ $FAILED -eq 0 ]]; then
printf "${GREEN}${ICON_SUCCESS} All tests passed!${NC}\n"
exit 0
fi
printf "${RED}${ICON_ERROR} $FAILED test(s) failed!${NC}\n"
exit 1