Features: 1) Introduce CLI utility lessy.py to streamline project management tasks (e.g., install, run, restart, test); 2) Add Unix and Windows script commands for make-messages and compile-messages to improve translation workflow; 3) Include shared utility libraries (utils.sh, utils.ps1) for reusable functions across scripts.

Fixes: 1) Remove obsolete `reboot` scripts for Unix and Windows to prevent redundancy; 2) Update Windows `test.ps1` to handle omitted coverage patterns and improve error feedback.

Extra: 1) Refactor Windows scripts (`make-messages.ps1`, `compile-messages.ps1`, `backup.ps1`) to use shared utilities for better consistency and output formatting; 2) Add spinner-based progress indicators to enhance user experience in interactive environments.
This commit is contained in:
Egor Pavlovich Gorbunov 2026-01-04 19:18:27 +03:00
parent 4c7b40b899
commit 42b40627de
23 changed files with 1218 additions and 237 deletions

129
lessy.py Executable file
View file

@ -0,0 +1,129 @@
#!/usr/bin/env python3
import platform
import subprocess
import sys
from pathlib import Path
import click
OS = platform.system().lower()
SCRIPT_EXT = ".ps1" if OS == "windows" else ".sh"
SCRIPT_DIR = "Windows" if OS == "windows" else "Unix"
PROJECT_ROOT = Path(__file__).parent.absolute()
SCRIPTS_PATH = PROJECT_ROOT / "scripts" / SCRIPT_DIR
def get_script_path(command: str) -> Path:
return SCRIPTS_PATH / f"{command}{SCRIPT_EXT}"
def run_script(script_name: str, *args) -> int:
script_path = get_script_path(script_name)
if not script_path.exists():
click.secho(f"Error: Script '{script_name}' not found at {script_path}", fg="red", err=True)
return 1
if OS == "windows":
cmd = ["pwsh", "-File", str(script_path)]
else:
cmd = ["bash", str(script_path)]
cmd.extend(args)
try:
result = subprocess.run(cmd, cwd=PROJECT_ROOT)
return result.returncode
except FileNotFoundError:
shell_name = "PowerShell" if OS == "windows" else "bash"
click.secho(f"Error: {shell_name} not found. Please ensure it's installed.", fg="red", err=True)
return 127
except KeyboardInterrupt:
click.secho("\nOperation cancelled by user.", fg="yellow")
return 130
@click.group(
context_settings={"help_option_names": ["-h", "--help"]},
invoke_without_command=True,
)
@click.pass_context
def cli(ctx):
if ctx.invoked_subcommand is None:
click.echo(ctx.get_help())
@cli.command()
def install():
return sys.exit(run_script("install"))
@cli.command()
def run():
return sys.exit(run_script("run"))
@cli.command()
def restart():
return sys.exit(run_script("restart"))
@cli.command()
@click.option("-r", "--report", type=click.Choice(["xml", "html"]), help="Generate coverage report (xml or html)")
def test(report):
args = []
if report:
args.extend(["-r", report])
return sys.exit(run_script("test", *args))
@cli.command()
def uninstall():
if click.confirm("This will remove all Docker containers, volumes, and generated files. Continue?"):
return sys.exit(run_script("uninstall"))
else:
click.secho("Uninstall cancelled.", fg="yellow")
return 0
@cli.command()
def backup():
return sys.exit(run_script("backup"))
@cli.command(name="generate-env")
def generate_env():
return sys.exit(run_script("generate-environment-file"))
@cli.command(name="export-env")
def export_env():
return sys.exit(run_script("export-environment-file"))
@cli.command(name="make-messages")
def make_messages():
return sys.exit(run_script("make-messages"))
@cli.command(name="compile-messages")
def compile_messages():
return sys.exit(run_script("compile-messages"))
@cli.command()
def info():
click.echo(f"{'='*60}")
click.secho("lessy - eVibes Project CLI", fg="cyan", bold=True)
click.echo(f"{'='*60}")
click.echo(f"Operating System: {platform.system()} ({platform.release()})")
click.echo(f"Python Version: {platform.python_version()}")
click.echo(f"Architecture: {platform.machine()}")
click.echo(f"Project Root: {PROJECT_ROOT}")
click.echo(f"Scripts Directory: {SCRIPTS_PATH}")
click.echo(f"Script Extension: {SCRIPT_EXT}")
click.echo(f"{'='*60}")
if __name__ == "__main__":
cli()

View file

@ -3,10 +3,21 @@ set -euo pipefail
source ./scripts/Unix/starter.sh
echo "Starting database backup process..."
docker compose exec app uv run manage.py dbbackup
echo "Database backup created under ./dbbackup"
# Database backup
log_step "Starting database backup process..."
if ! docker compose exec app uv run manage.py dbbackup; then
log_error "Database backup failed"
exit 1
fi
log_success "Database backup created under ./dbbackup"
echo "Starting media backup process..."
docker compose exec app uv run manage.py mediabackup
echo "Media backup created under ./dbbackup"
# Media backup
log_step "Starting media backup process..."
if ! docker compose exec app uv run manage.py mediabackup; then
log_error "Media backup failed"
exit 1
fi
log_success "Media backup created under ./dbbackup"
echo
log_result "Backup completed successfully!"

View file

@ -0,0 +1,28 @@
#!/usr/bin/env bash
set -euo pipefail
source ./scripts/Unix/starter.sh
if [ ! -f .env ]; then
log_warning ".env file not found. Exiting without running Docker steps."
exit 0
fi
# Check placeholders
log_step "Checking placeholders in PO files..."
if ! docker compose exec app uv run manage.py check_translated -l ALL -a ALL; then
log_error "PO files have placeholder issues"
exit 1
fi
log_success "PO files have no placeholder issues!"
# Compile messages
log_step "Compiling PO files into MO files..."
if ! docker compose exec app uv run manage.py compilemessages -l ar_AR -l cs_CZ -l da_DK -l de_DE -l en_GB -l en_US -l es_ES -l fa_IR -l fr_FR -l he_IL -l hi_IN -l hr_HR -l id_ID -l it_IT -l ja_JP -l kk_KZ -l ko_KR -l nl_NL -l no_NO -l pl_PL -l pt_BR -l ro_RO -l ru_RU -l sv_SE -l th_TH -l tr_TR -l vi_VN -l zh_Hans; then
log_error "Failed to compile messages"
exit 1
fi
log_success "Compiled successfully!"
echo
log_result "Translation compilation complete!"

View file

@ -4,41 +4,30 @@ set -euo pipefail
source ./scripts/Unix/starter.sh
if [ ! -f .env ]; then
echo ".env file not found. Exiting without running Docker steps." >&2
log_warning ".env file not found. Exiting without running Docker steps."
exit 0
fi
cpu_count=$(getconf _NPROCESSORS_ONLN)
if [ "$cpu_count" -lt 4 ]; then
# Check system requirements
if ! check_system_requirements 4 6 20; then
exit 1
fi
if [ -f /proc/meminfo ]; then
mem_kb=$(grep MemTotal /proc/meminfo | awk '{print $2}')
total_mem_gb=$(awk "BEGIN {printf \"%.2f\", $mem_kb/1024/1024}")
else
total_mem_bytes=$(sysctl -n hw.memsize)
total_mem_gb=$(awk "BEGIN {printf \"%.2f\", $total_mem_bytes/1024/1024/1024}")
fi
if ! awk "BEGIN {exit !($total_mem_gb >= 6)}"; then
# Pull Docker images
log_step "Pulling images..."
if ! docker compose pull; then
log_error "Failed to pull Docker images"
exit 1
fi
log_success "Images pulled successfully"
avail_kb=$(df -k . | tail -1 | awk '{print $4}')
free_gb=$(awk "BEGIN {printf \"%.2f\", $avail_kb/1024/1024}")
if ! awk "BEGIN {exit !($free_gb >= 20)}"; then
# Build Docker images
log_step "Building images..."
if ! docker compose build; then
log_error "Failed to build Docker images"
exit 1
fi
echo "System requirements met: CPU cores=$cpu_count, RAM=${total_mem_gb}GB, FreeDisk=${free_gb}GB"
echo "Pulling images"
docker compose pull
echo "Images pulled successfully"
echo "Building images"
docker compose build
echo "Images built successfully"
log_success "Images built successfully"
echo
echo "You can now use run.sh script."
log_result "You can now use run.sh script or run: ./lessy.py run"

44
scripts/Unix/make-messages.sh Executable file
View file

@ -0,0 +1,44 @@
#!/usr/bin/env bash
set -euo pipefail
source ./scripts/Unix/starter.sh
if [ ! -f .env ]; then
log_warning ".env file not found. Exiting without running Docker steps."
exit 0
fi
# Remove old fuzzy entries
log_step "Remove old fuzzy entries..."
if ! docker compose exec app uv run manage.py fix_fuzzy; then
log_error "Failed to remove old fuzzy entries"
exit 1
fi
log_success "Old fuzzy entries removed successfully!"
# Update PO files
log_step "Updating PO files..."
if ! docker compose exec app uv run manage.py makemessages -l ar_AR -l cs_CZ -l da_DK -l de_DE -l en_GB -l en_US -l es_ES -l fa_IR -l fr_FR -l he_IL -l hi_IN -l hr_HR -l id_ID -l it_IT -l ja_JP -l kk_KZ -l ko_KR -l nl_NL -l no_NO -l pl_PL -l pt_BR -l ro_RO -l ru_RU -l sv_SE -l th_TH -l tr_TR -l vi_VN -l zh_Hans; then
log_error "Failed to update PO files"
exit 1
fi
log_success "PO files updated successfully!"
# Fix new fuzzy entries
log_step "Fixing new fuzzy entries..."
if ! docker compose exec app uv run manage.py fix_fuzzy; then
log_error "Failed to fix new fuzzy entries"
exit 1
fi
log_success "New fuzzy entries fixed successfully!"
# Translate with DeepL
log_step "Translating with DeepL..."
if ! docker compose exec app uv run manage.py deepl_translate -l ALL -a ALL; then
log_error "Translation failed"
exit 1
fi
log_success "Translated successfully!"
echo
log_result "You can now use compile-messages.sh script or run: ./lessy.py compile-messages"

View file

@ -1,26 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
source ./scripts/Unix/starter.sh
echo "Shutting down..."
docker compose down
echo "Services were shut down successfully!"
echo "Spinning services up..."
docker compose up -d --build --wait
echo "Services are up and healthy!"
echo "Completing pre-run tasks..."
docker compose exec app uv run manage.py migrate --no-input --verbosity 0
docker compose exec app uv run manage.py initialize
docker compose exec app uv run manage.py set_default_caches
docker compose exec app uv run manage.py search_index --rebuild -f
docker compose exec app uv run manage.py collectstatic --clear --no-input --verbosity 0
echo "Pre-run tasks completed successfully!"
echo "Cleaning up unused Docker data..."
docker system prune -f
echo "Unused Docker data cleaned successfully!"
echo "All done! eVibes is up and running!"

65
scripts/Unix/restart.sh Executable file
View file

@ -0,0 +1,65 @@
#!/usr/bin/env bash
set -euo pipefail
source ./scripts/Unix/starter.sh
# Shutdown services
log_step "Shutting down..."
if ! docker compose down; then
log_error "Failed to shut down services"
exit 1
fi
log_success "Services were shut down successfully!"
# Rebuild and start services
log_step "Spinning services up with rebuild..."
if ! docker compose up -d --build --wait; then
log_error "Failed to start services"
exit 1
fi
log_success "Services are up and healthy!"
# Run pre-run tasks
log_step "Completing pre-run tasks..."
log_info " → Running migrations..."
if ! docker compose exec app uv run manage.py migrate --no-input --verbosity 0; then
log_error "Migrations failed"
exit 1
fi
log_info " → Initializing..."
if ! docker compose exec app uv run manage.py initialize; then
log_error "Initialization failed"
exit 1
fi
log_info " → Setting default caches..."
if ! docker compose exec app uv run manage.py set_default_caches; then
log_error "Cache setup failed"
exit 1
fi
log_info " → Rebuilding search index..."
if ! docker compose exec app uv run manage.py search_index --rebuild -f; then
log_error "Search index rebuild failed"
exit 1
fi
log_info " → Collecting static files..."
if ! docker compose exec app uv run manage.py collectstatic --clear --no-input --verbosity 0; then
log_error "Static files collection failed"
exit 1
fi
log_success "Pre-run tasks completed successfully!"
# Cleanup
log_step "Cleaning up unused Docker data..."
if ! docker system prune -f; then
log_warning "Docker cleanup had issues, but continuing..."
fi
log_success "Unused Docker data cleaned successfully!"
echo
log_result "All done! eVibes is up and running!"

View file

@ -3,36 +3,72 @@ set -euo pipefail
source ./scripts/Unix/starter.sh
echo "Verifying all images are present..."
# Verify Docker images
log_step "Verifying all images are present..."
if command -v jq >/dev/null 2>&1; then
images=$(docker compose config --format json | jq -r '.services[].image // empty')
if [ -n "$images" ]; then
for image in $images; do
if ! docker image inspect "$image" > /dev/null 2>&1; then
echo "Required images not found. Please run install.sh first." >&2
log_error "Required images not found. Please run install.sh first."
exit 1
fi
echo " - Found image: $image"
log_info " Found image: $image"
done
fi
else
echo "jq is not installed; skipping image verification step."
log_warning "jq is not installed; skipping image verification step."
fi
echo "Spinning services up..."
docker compose up --no-build --detach --wait
echo "Services are up and healthy!"
# Start services
log_step "Spinning services up..."
if ! docker compose up --no-build --detach --wait; then
log_error "Failed to start services"
exit 1
fi
log_success "Services are up and healthy!"
echo "Completing pre-run tasks..."
docker compose exec app uv run manage.py migrate --no-input --verbosity 0
docker compose exec app uv run manage.py initialize
docker compose exec app uv run manage.py set_default_caches
docker compose exec app uv run manage.py search_index --rebuild -f
docker compose exec app uv run manage.py collectstatic --clear --no-input --verbosity 0
echo "Pre-run tasks completed successfully!"
# Run pre-run tasks
log_step "Completing pre-run tasks..."
echo "Cleaning unused Docker data..."
docker system prune -f
echo "Unused Docker data cleaned successfully!"
log_info " → Running migrations..."
if ! docker compose exec app uv run manage.py migrate --no-input --verbosity 0; then
log_error "Migrations failed"
exit 1
fi
echo "All done! eVibes is up and running!"
log_info " → Initializing..."
if ! docker compose exec app uv run manage.py initialize; then
log_error "Initialization failed"
exit 1
fi
log_info " → Setting default caches..."
if ! docker compose exec app uv run manage.py set_default_caches; then
log_error "Cache setup failed"
exit 1
fi
log_info " → Rebuilding search index..."
if ! docker compose exec app uv run manage.py search_index --rebuild -f; then
log_error "Search index rebuild failed"
exit 1
fi
log_info " → Collecting static files..."
if ! docker compose exec app uv run manage.py collectstatic --clear --no-input --verbosity 0; then
log_error "Static files collection failed"
exit 1
fi
log_success "Pre-run tasks completed successfully!"
# Cleanup
log_step "Cleaning unused Docker data..."
if ! docker system prune -f; then
log_warning "Docker cleanup had issues, but continuing..."
fi
log_success "Unused Docker data cleaned successfully!"
echo
log_result "All done! eVibes is up and running!"

View file

@ -7,18 +7,30 @@ else script_path="$0"
fi
script_dir="$(cd "$(dirname "$script_path")" && pwd -P)"
# Load shared utilities
# shellcheck source=../lib/utils.sh
source "$script_dir/../lib/utils.sh"
if [ ! -d "./evibes" ]; then
echo "❌ Please run this script from the project's root (where the 'evibes' directory lives)." >&2
log_error "❌ Please run this script from the project's root (where the 'evibes' directory lives)."
exit 1
fi
art_path="$script_dir/../ASCII_ART_EVIBES"
if [ ! -f "$art_path" ]; then
echo "❌ Could not find ASCII art at $art_path" >&2
log_error "❌ Could not find ASCII art at $art_path"
exit 1
fi
cat "$art_path"
echo
echo " by WISELESS TEAM"
echo
if is_interactive; then
# In interactive mode, show colorful banner
purple='\033[38;2;121;101;209m'
reset='\033[0m'
echo -e "${purple}$(cat "$art_path")${reset}"
echo
echo -e "${COLOR_GRAY} by WISELESS TEAM${COLOR_RESET}"
echo
else
# In non-interactive mode, show simple banner
echo "eVibes by WISELESS TEAM"
fi

View file

@ -4,12 +4,13 @@ set -euo pipefail
source ./scripts/Unix/starter.sh
report=""
omit_pattern='storefront/*,monitoring/*,Dockerfiles/*,*/__init__.py,*/tests/*,*/migrations/*,manage.py,evibes/*'
while [ "$#" -gt 0 ]; do
case "$1" in
--report|-r)
if [ "${2-}" = "" ]; then
echo "Error: --report/-r requires an argument: xml or html" >&2
log_error "Error: --report/-r requires an argument: xml or html"
exit 1
fi
report="$2"
@ -24,7 +25,7 @@ while [ "$#" -gt 0 ]; do
shift
;;
*)
echo "Unknown argument: $1" >&2
log_error "Unknown argument: $1"
echo "Usage: $0 [--report|-r xml|html]" >&2
exit 1
;;
@ -33,18 +34,43 @@ done
case "${report:-}" in
"")
docker compose exec app uv run coverage erase
docker compose exec app uv run coverage run --source='.' --omit='storefront/*,monitoring/*,Dockerfiles/*,*/__init__.py,*/tests/*,*/migrations/*,manage.py,evibes/*' manage.py test
log_step "Running tests with coverage..."
log_info " → Erasing previous coverage data..."
if ! docker compose exec app uv run coverage erase; then
log_error "Failed to erase coverage data"
exit 1
fi
log_info " → Running tests..."
if ! docker compose exec app uv run coverage run --source='.' --omit="$omit_pattern" manage.py test; then
log_error "Tests failed"
exit 1
fi
log_info " → Generating coverage report..."
docker compose exec app uv run coverage report -m
;;
xml)
docker compose exec app uv run coverage xml --omit='storefront/*,monitoring/*,Dockerfiles/*,*/__init__.py,*/tests/*,*/migrations/*,manage.py,evibes/*'
log_step "Generating XML coverage report..."
if docker compose exec app uv run coverage xml --omit="$omit_pattern"; then
log_success "XML coverage report generated"
else
log_error "Failed to generate XML coverage report"
exit 1
fi
;;
html)
docker compose exec app uv run coverage html --omit='storefront/*,monitoring/*,Dockerfiles/*,*/__init__.py,*/tests/*,*/migrations/*,manage.py,evibes/*'
log_step "Generating HTML coverage report..."
if docker compose exec app uv run coverage html --omit="$omit_pattern"; then
log_success "HTML coverage report generated"
else
log_error "Failed to generate HTML coverage report"
exit 1
fi
;;
*)
echo "Invalid report type: $report (expected xml or html)" >&2
log_error "Invalid report type: $report (expected xml or html)"
exit 1
;;
esac

View file

@ -3,21 +3,31 @@ set -euo pipefail
source ./scripts/Unix/starter.sh
echo "Shutting down..."
docker compose down
echo "Services were shut down successfully!"
# Shutdown services
log_step "Shutting down..."
if ! docker compose down; then
log_error "Failed to shut down services"
exit 1
fi
log_success "Services were shut down successfully!"
echo "Removing volumes..."
docker volume rm -f evibes_prometheus-data
docker volume rm -f evibes_es-data
echo "Volumes were removed successfully!"
# Remove volumes
log_step "Removing volumes..."
docker volume rm -f evibes_prometheus-data || log_warning "Failed to remove prometheus-data volume"
docker volume rm -f evibes_es-data || log_warning "Failed to remove es-data volume"
log_success "Volumes were removed successfully!"
echo "Cleaning up unused Docker data..."
docker system prune -a -f --volumes
echo "Unused Docker data cleaned successfully!"
# Cleanup Docker
log_step "Cleaning up unused Docker data..."
if ! docker system prune -a -f --volumes; then
log_warning "Docker cleanup had issues, but continuing..."
fi
log_success "Unused Docker data cleaned successfully!"
echo "Removing related files..."
# Remove local files
log_step "Removing related files..."
rm -rf ./media ./static
echo "Related files removed successfully!"
log_success "Related files removed successfully!"
echo "Bye-bye, hope you return later!"
echo
log_result "Bye-bye, hope you return later!"

View file

@ -2,21 +2,32 @@
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
# Load shared utilities
$utilsPath = Join-Path $PSScriptRoot '..\lib\utils.ps1'
. $utilsPath
.\scripts\Windows\starter.ps1
if ($LASTEXITCODE -ne 0) {
exit $LASTEXITCODE
}
Write-Host "Starting database backup process..." -ForegroundColor Magenta
# Database backup
Write-Step "Starting database backup process..."
docker compose exec app uv run manage.py dbbackup
if ($LASTEXITCODE -ne 0) {
Write-Error-Custom "Database backup failed"
exit $LASTEXITCODE
}
Write-Host "Database backup created under ./dbbackup" -ForegroundColor Green
Write-Success "Database backup created under ./dbbackup"
Write-Host "Starting media backup process..." -ForegroundColor Magenta
# Media backup
Write-Step "Starting media backup process..."
docker compose exec app uv run manage.py mediabackup
if ($LASTEXITCODE -ne 0) {
Write-Error-Custom "Media backup failed"
exit $LASTEXITCODE
}
Write-Host "Media backup created under ./dbbackup" -ForegroundColor Green
Write-Success "Media backup created under ./dbbackup"
Write-Result ""
Write-Result "Backup completed successfully!"

View file

@ -1,6 +1,10 @@
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
# Load shared utilities
$utilsPath = Join-Path $PSScriptRoot '..\lib\utils.ps1'
. $utilsPath
.\scripts\Windows\starter.ps1
if ($LASTEXITCODE -ne 0) {
exit $LASTEXITCODE
@ -8,20 +12,27 @@ if ($LASTEXITCODE -ne 0) {
if (-not (Test-Path '.env'))
{
Write-Warning ".env file not found. Exiting without running Docker steps."
Write-Warning-Custom ".env file not found. Exiting without running Docker steps."
exit 0
}
Write-Host "Checking placeholders in PO files..." -ForegroundColor Magenta
# Check placeholders
Write-Step "Checking placeholders in PO files..."
docker compose exec app uv run manage.py check_translated -l ALL -a ALL
if ($LASTEXITCODE -ne 0) {
Write-Error-Custom "PO files have placeholder issues"
exit $LASTEXITCODE
}
Write-Host "PO files have no placeholder issues!" -ForegroundColor Green
Write-Success "PO files have no placeholder issues!"
Write-Host "Compiling PO files into MO files..." -ForegroundColor Magenta
# Compile messages
Write-Step "Compiling PO files into MO files..."
docker compose exec app uv run manage.py compilemessages -l ar_AR -l cs_CZ -l da_DK -l de_DE -l en_GB -l en_US -l es_ES -l fa_IR -l fr_FR -l he_IL -l hi_IN -l hr_HR -l id_ID -l it_IT -l ja_JP -l kk_KZ -l ko_KR -l nl_NL -l no_NO -l pl_PL -l pt_BR -l ro_RO -l ru_RU -l sv_SE -l th_TH -l tr_TR -l vi_VN -l zh_Hans
if ($LASTEXITCODE -ne 0) {
Write-Error-Custom "Failed to compile messages"
exit $LASTEXITCODE
}
Write-Host "Compiled successfully!" -ForegroundColor Green
Write-Success "Compiled successfully!"
Write-Result ""
Write-Result "Translation compilation complete!"

View file

@ -1,6 +1,10 @@
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
# Load shared utilities
$utilsPath = Join-Path $PSScriptRoot '..\lib\utils.ps1'
. $utilsPath
.\scripts\Windows\starter.ps1
if ($LASTEXITCODE -ne 0) {
exit $LASTEXITCODE
@ -8,39 +12,33 @@ if ($LASTEXITCODE -ne 0) {
if (-not (Test-Path '.env'))
{
Write-Warning ".env file not found. Exiting without running Docker steps."
Write-Warning-Custom ".env file not found. Exiting without running Docker steps."
exit 0
}
$cpuCount = [Environment]::ProcessorCount
if ($cpuCount -lt 4)
# Check system requirements
if (-not (Test-SystemRequirements -MinCpu 4 -MinRamGB 6 -MinDiskGB 20))
{
exit 1
}
$totalMemBytes = (Get-CimInstance Win32_ComputerSystem).TotalPhysicalMemory
$totalMemGB = [Math]::Round($totalMemBytes / 1GB, 2)
if ($totalMemGB -lt 6)
{
exit 1
}
$currentDrive = Split-Path -Qualifier $PWD
$driveLetter = $currentDrive.Substring(0, 1)
$freeGB = [Math]::Round((Get-PSDrive -Name $driveLetter).Free / 1GB, 2)
if ($freeGB -lt 20)
{
exit 1
}
Write-Host "System requirements met: CPU cores=$cpuCount, RAM=${totalMemGB}GB, FreeDisk=${freeGB}GB" -ForegroundColor Green
Write-Host "Pulling images" -ForegroundColor Magenta
# Pull Docker images
Write-Step "Pulling images..."
docker compose pull
Write-Host "Images pulled successfully" -ForegroundColor Green
if ($LASTEXITCODE -ne 0) {
Write-Error-Custom "Failed to pull Docker images"
exit $LASTEXITCODE
}
Write-Success "Images pulled successfully"
Write-Host "Building images" -ForegroundColor Magenta
# Build Docker images
Write-Step "Building images..."
docker compose build
Write-Host "Images built successfully" -ForegroundColor Green
Write-Host ""
Write-Host "You can now use run.ps1 script." -ForegroundColor Cyan
if ($LASTEXITCODE -ne 0) {
Write-Error-Custom "Failed to build Docker images"
exit $LASTEXITCODE
}
Write-Success "Images built successfully"
Write-Result ""
Write-Result "You can now use run.ps1 script or run: lessy.py run"

View file

@ -1,6 +1,10 @@
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
# Load shared utilities
$utilsPath = Join-Path $PSScriptRoot '..\lib\utils.ps1'
. $utilsPath
.\scripts\Windows\starter.ps1
if ($LASTEXITCODE -ne 0) {
exit $LASTEXITCODE
@ -8,37 +12,45 @@ if ($LASTEXITCODE -ne 0) {
if (-not (Test-Path '.env'))
{
Write-Warning ".env file not found. Exiting without running Docker steps."
Write-Warning-Custom ".env file not found. Exiting without running Docker steps."
exit 0
}
Write-Host "Remove old fuzzy entries..." -ForegroundColor Magenta
# Remove old fuzzy entries
Write-Step "Remove old fuzzy entries..."
docker compose exec app uv run manage.py fix_fuzzy
if ($LASTEXITCODE -ne 0) {
Write-Error-Custom "Failed to remove old fuzzy entries"
exit $LASTEXITCODE
}
Write-Host "Old fuzzy entries removed successfully!" -ForegroundColor Green
Write-Success "Old fuzzy entries removed successfully!"
Write-Host "Updating PO files..." -ForegroundColor Magenta
# Update PO files
Write-Step "Updating PO files..."
docker compose exec app uv run manage.py makemessages -l ar_AR -l cs_CZ -l da_DK -l de_DE -l en_GB -l en_US -l es_ES -l fa_IR -l fr_FR -l he_IL -l hi_IN -l hr_HR -l id_ID -l it_IT -l ja_JP -l kk_KZ -l ko_KR -l nl_NL -l no_NO -l pl_PL -l pt_BR -l ro_RO -l ru_RU -l sv_SE -l th_TH -l tr_TR -l vi_VN -l zh_Hans
if ($LASTEXITCODE -ne 0) {
Write-Error-Custom "Failed to update PO files"
exit $LASTEXITCODE
}
Write-Host "PO files updated successfully!" -ForegroundColor Green
Write-Success "PO files updated successfully!"
Write-Host "Fixing new fuzzy entries..." -ForegroundColor Magenta
# Fix new fuzzy entries
Write-Step "Fixing new fuzzy entries..."
docker compose exec app uv run manage.py fix_fuzzy
if ($LASTEXITCODE -ne 0) {
Write-Error-Custom "Failed to fix new fuzzy entries"
exit $LASTEXITCODE
}
Write-Host "New fuzzy entries fixed successfully!" -ForegroundColor Green
Write-Success "New fuzzy entries fixed successfully!"
Write-Host "Translating with DeepL..." -ForegroundColor Magenta
# Translate with DeepL
Write-Step "Translating with DeepL..."
docker compose exec app uv run manage.py deepl_translate -l ALL -a ALL
if ($LASTEXITCODE -ne 0) {
Write-Error-Custom "Translation failed"
exit $LASTEXITCODE
}
Write-Host "Translated successfully!" -ForegroundColor Green
Write-Success "Translated successfully!"
Write-Host ""
Write-Host "You can now use compile-messages.ps1 script." -ForegroundColor Cyan
Write-Result ""
Write-Result "You can now use compile-messages.ps1 script or run: lessy.py compile-messages"

View file

@ -1,54 +0,0 @@
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
.\scripts\Windows\starter.ps1
if ($LASTEXITCODE -ne 0) {
exit $LASTEXITCODE
}
Write-Host "Shutting down..." -ForegroundColor Magenta
docker compose down
if ($LASTEXITCODE -ne 0) {
exit $LASTEXITCODE
}
Write-Host "Services were shut down successfully!" -ForegroundColor Green
Write-Host "Spinning services up..." -ForegroundColor Magenta
docker compose up -d --build --wait
if ($LASTEXITCODE -ne 0) {
exit $LASTEXITCODE
}
Write-Host "Services are up and healthy!" -ForegroundColor Green
Write-Host "Completing pre-run tasks..." -ForegroundColor Magenta
docker compose exec app uv run manage.py migrate --no-input
if ($LASTEXITCODE -ne 0) {
exit $LASTEXITCODE
}
docker compose exec app uv run manage.py initialize
if ($LASTEXITCODE -ne 0) {
exit $LASTEXITCODE
}
docker compose exec app uv run manage.py set_default_caches
if ($LASTEXITCODE -ne 0) {
exit $LASTEXITCODE
}
docker compose exec app uv run manage.py search_index --rebuild -f
if ($LASTEXITCODE -ne 0) {
exit $LASTEXITCODE
}
docker compose exec app uv run manage.py collectstatic --clear --no-input
if ($LASTEXITCODE -ne 0) {
exit $LASTEXITCODE
}
Write-Host "Pre-run tasks completed successfully!" -ForegroundColor Green
Write-Host "Cleaning up unused Docker data..." -ForegroundColor Magenta
docker system prune -f
if ($LASTEXITCODE -ne 0) {
exit $LASTEXITCODE
}
Write-Host "Unused Docker data cleaned successfully!" -ForegroundColor Green
Write-Host "All done! eVibes is up and running!" -ForegroundColor Cyan

View file

@ -0,0 +1,80 @@
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
# Load shared utilities
$utilsPath = Join-Path $PSScriptRoot '..\lib\utils.ps1'
. $utilsPath
.\scripts\Windows\starter.ps1
if ($LASTEXITCODE -ne 0) {
exit $LASTEXITCODE
}
# Shutdown services
Write-Step "Shutting down..."
docker compose down
if ($LASTEXITCODE -ne 0) {
Write-Error-Custom "Failed to shut down services"
exit $LASTEXITCODE
}
Write-Success "Services were shut down successfully!"
# Rebuild and start services
Write-Step "Spinning services up with rebuild..."
docker compose up -d --build --wait
if ($LASTEXITCODE -ne 0) {
Write-Error-Custom "Failed to start services"
exit $LASTEXITCODE
}
Write-Success "Services are up and healthy!"
# Run pre-run tasks
Write-Step "Completing pre-run tasks..."
Write-Info " → Running migrations..."
docker compose exec app uv run manage.py migrate --no-input
if ($LASTEXITCODE -ne 0) {
Write-Error-Custom "Migrations failed"
exit $LASTEXITCODE
}
Write-Info " → Initializing..."
docker compose exec app uv run manage.py initialize
if ($LASTEXITCODE -ne 0) {
Write-Error-Custom "Initialization failed"
exit $LASTEXITCODE
}
Write-Info " → Setting default caches..."
docker compose exec app uv run manage.py set_default_caches
if ($LASTEXITCODE -ne 0) {
Write-Error-Custom "Cache setup failed"
exit $LASTEXITCODE
}
Write-Info " → Rebuilding search index..."
docker compose exec app uv run manage.py search_index --rebuild -f
if ($LASTEXITCODE -ne 0) {
Write-Error-Custom "Search index rebuild failed"
exit $LASTEXITCODE
}
Write-Info " → Collecting static files..."
docker compose exec app uv run manage.py collectstatic --clear --no-input
if ($LASTEXITCODE -ne 0) {
Write-Error-Custom "Static files collection failed"
exit $LASTEXITCODE
}
Write-Success "Pre-run tasks completed successfully!"
# Cleanup
Write-Step "Cleaning up unused Docker data..."
docker system prune -f
if ($LASTEXITCODE -ne 0) {
Write-Warning-Custom "Docker cleanup had issues, but continuing..."
}
Write-Success "Unused Docker data cleaned successfully!"
Write-Result ""
Write-Result "All done! eVibes is up and running!"

View file

@ -1,12 +1,17 @@
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
# Load shared utilities
$utilsPath = Join-Path $PSScriptRoot '..\lib\utils.ps1'
. $utilsPath
.\scripts\Windows\starter.ps1
if ($LASTEXITCODE -ne 0) {
exit $LASTEXITCODE
}
Write-Host "Verifying all images are present…" -ForegroundColor Green
# Verify Docker images
Write-Step "Verifying all images are present…"
$config = docker compose config --format json | ConvertFrom-Json
@ -21,50 +26,71 @@ foreach ($prop in $config.services.PSObject.Properties)
$image = $svc.PSObject.Properties['image'].Value
if (-not (docker image inspect $image))
if (-not (docker image inspect $image 2>$null))
{
Write-Error "Required images not found. Please run install.ps1 first."
Write-Error-Custom "Required images not found. Please run install.ps1 first."
exit 1
}
Write-Host " • Found image: $image"
Write-Info " • Found image: $image"
}
Write-Host "Spinning services up..." -ForegroundColor Magenta
# Start services
Write-Step "Spinning services up..."
docker compose up --no-build --detach --wait
if ($LASTEXITCODE -ne 0) {
Write-Error-Custom "Failed to start services"
exit $LASTEXITCODE
}
Write-Host "Services are up and healthy!" -ForegroundColor Green
Write-Success "Services are up and healthy!"
Write-Host "Completing pre-run tasks..." -ForegroundColor Magenta
# Run pre-run tasks
Write-Step "Completing pre-run tasks..."
Write-Info " → Running migrations..."
docker compose exec app uv run manage.py migrate --no-input
if ($LASTEXITCODE -ne 0) {
Write-Error-Custom "Migrations failed"
exit $LASTEXITCODE
}
Write-Info " → Initializing..."
docker compose exec app uv run manage.py initialize
if ($LASTEXITCODE -ne 0) {
Write-Error-Custom "Initialization failed"
exit $LASTEXITCODE
}
Write-Info " → Setting default caches..."
docker compose exec app uv run manage.py set_default_caches
if ($LASTEXITCODE -ne 0) {
Write-Error-Custom "Cache setup failed"
exit $LASTEXITCODE
}
Write-Info " → Rebuilding search index..."
docker compose exec app uv run manage.py search_index --rebuild -f
if ($LASTEXITCODE -ne 0) {
Write-Error-Custom "Search index rebuild failed"
exit $LASTEXITCODE
}
Write-Info " → Collecting static files..."
docker compose exec app uv run manage.py collectstatic --clear --no-input
if ($LASTEXITCODE -ne 0) {
Write-Error-Custom "Static files collection failed"
exit $LASTEXITCODE
}
Write-Host "Pre-run tasks completed successfully!" -ForegroundColor Green
Write-Host "Cleaning unused Docker data..." -ForegroundColor Magenta
Write-Success "Pre-run tasks completed successfully!"
# Cleanup
Write-Step "Cleaning unused Docker data..."
docker system prune -f
if ($LASTEXITCODE -ne 0) {
exit $LASTEXITCODE
Write-Warning-Custom "Docker cleanup had issues, but continuing..."
}
Write-Host "Unused Docker data cleaned successfully!" -ForegroundColor Green
Write-Success "Unused Docker data cleaned successfully!"
Write-Host "All done! eVibes is up and running!" -ForegroundColor Cyan
Write-Result ""
Write-Result "All done! eVibes is up and running!"

View file

@ -1,22 +1,35 @@
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
# Load shared utilities
$utilsPath = Join-Path (Split-Path -Parent $MyInvocation.MyCommand.Definition) '..\lib\utils.ps1'
. $utilsPath
if (-not (Test-Path -Path ".\evibes" -PathType Container))
{
Write-Host "❌ Please run this script from the project's root (where the 'evibes' directory lives)." -ForegroundColor Red
Write-Error-Custom "❌ Please run this script from the project's root (where the 'evibes' directory lives)."
exit 1
}
$purple = "`e[38;2;121;101;209m"
$reset = "`e[0m"
$artPath = Join-Path (Split-Path -Parent $MyInvocation.MyCommand.Definition) '..\ASCII_ART_EVIBES'
if (-not (Test-Path $artPath))
{
Write-Host "❌ Could not find ASCII art at $artPath" -ForegroundColor Red
Write-Error-Custom "❌ Could not find ASCII art at $artPath"
exit 1
}
Get-Content -Raw -Path $artPath | ForEach-Object { Write-Host "$purple$_$reset" }
Write-Host "`n by WISELESS TEAM`n" -ForegroundColor Gray
if (Test-Interactive)
{
$purple = "`e[38;2;121;101;209m"
$reset = "`e[0m"
Get-Content -Raw -Path $artPath | ForEach-Object { Write-Host "$purple$_$reset" }
Write-Host "`n by WISELESS TEAM`n" -ForegroundColor Gray
}
else
{
# In non-interactive mode, just show simple banner
Write-Output "eVibes by WISELESS TEAM"
}
exit 0

View file

@ -6,33 +6,62 @@ param(
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
# Load shared utilities
$utilsPath = Join-Path $PSScriptRoot '..\lib\utils.ps1'
. $utilsPath
& .\scripts\Windows\starter.ps1
if ($LASTEXITCODE -ne 0) {
exit $LASTEXITCODE
}
$omitPattern = 'storefront/*,monitoring/*,Dockerfiles/*,*/__init__.py,*/tests/*,*/migrations/*,manage.py,evibes/*'
if (-not $PSBoundParameters.ContainsKey('Report') -or [string]::IsNullOrWhiteSpace($Report)) {
Write-Step "Running tests with coverage..."
Write-Info " → Erasing previous coverage data..."
docker compose exec app uv run coverage erase
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
if ($LASTEXITCODE -ne 0) {
Write-Error-Custom "Failed to erase coverage data"
exit $LASTEXITCODE
}
docker compose exec app uv run coverage run --source='.' --omit='storefront/*,monitoring/*,Dockerfiles/*,*/__init__.py,*/tests/*,*/migrations/*,manage.py,evibes/*' manage.py test
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
Write-Info " → Running tests..."
docker compose exec app uv run coverage run --source='.' --omit=$omitPattern manage.py test
if ($LASTEXITCODE -ne 0) {
Write-Error-Custom "Tests failed"
exit $LASTEXITCODE
}
Write-Info " → Generating coverage report..."
docker compose exec app uv run coverage report -m
exit $LASTEXITCODE
}
switch ($Report.ToLowerInvariant()) {
'xml' {
docker compose exec app uv run coverage xml --omit='storefront/*,monitoring/*,Dockerfiles/*,*/__init__.py,*/tests/*,*/migrations/*,manage.py,evibes/*'
Write-Step "Generating XML coverage report..."
docker compose exec app uv run coverage xml --omit=$omitPattern
if ($LASTEXITCODE -eq 0) {
Write-Success "XML coverage report generated"
} else {
Write-Error-Custom "Failed to generate XML coverage report"
}
exit $LASTEXITCODE
}
'html' {
docker compose exec app uv run coverage html --omit='storefront/*,monitoring/*,Dockerfiles/*,*/__init__.py,*/tests/*,*/migrations/*,manage.py,evibes/*'
Write-Step "Generating HTML coverage report..."
docker compose exec app uv run coverage html --omit=$omitPattern
if ($LASTEXITCODE -eq 0) {
Write-Success "HTML coverage report generated"
} else {
Write-Error-Custom "Failed to generate HTML coverage report"
}
exit $LASTEXITCODE
}
default {
Write-Error "Invalid -Report/-r value '$Report'. Expected 'xml' or 'html'."
Write-Error-Custom "Invalid -Report/-r value '$Report'. Expected 'xml' or 'html'."
exit 1
}
}

View file

@ -1,39 +1,49 @@
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
# Load shared utilities
$utilsPath = Join-Path $PSScriptRoot '..\lib\utils.ps1'
. $utilsPath
.\scripts\Windows\starter.ps1
if ($LASTEXITCODE -ne 0) {
exit $LASTEXITCODE
}
Write-Host "Shutting down..." -ForegroundColor Magenta
# Shutdown services
Write-Step "Shutting down..."
docker compose down
if ($LASTEXITCODE -ne 0) {
Write-Error-Custom "Failed to shut down services"
exit $LASTEXITCODE
}
Write-Host "Services were shut down successfully!" -ForegroundColor Green
Write-Success "Services were shut down successfully!"
Write-Host "Removing volumes..." -ForegroundColor Magenta
# Remove volumes
Write-Step "Removing volumes..."
docker volume remove -f evibes_prometheus-data
if ($LASTEXITCODE -ne 0) {
exit $LASTEXITCODE
Write-Warning-Custom "Failed to remove prometheus-data volume"
}
docker volume remove -f evibes_es-data
if ($LASTEXITCODE -ne 0) {
exit $LASTEXITCODE
Write-Warning-Custom "Failed to remove es-data volume"
}
Write-Host "Volumes were removed successfully!" -ForegroundColor Green
Write-Success "Volumes were removed successfully!"
Write-Host "Cleaning up unused Docker data..." -ForegroundColor Magenta
# Cleanup Docker
Write-Step "Cleaning up unused Docker data..."
docker system prune -a -f --volumes
if ($LASTEXITCODE -ne 0) {
exit $LASTEXITCODE
Write-Warning-Custom "Docker cleanup had issues, but continuing..."
}
Write-Host "Unused Docker data cleaned successfully!" -ForegroundColor Green
Write-Success "Unused Docker data cleaned successfully!"
Write-Host "Removing related files..." -ForegroundColor Magenta
# Remove local files
Write-Step "Removing related files..."
Remove-Item -Recurse -Force -ErrorAction SilentlyContinue ./media
Remove-Item -Recurse -Force -ErrorAction SilentlyContinue ./static
Write-Host "Related files removed successfully!" -ForegroundColor Green
Write-Success "Related files removed successfully!"
Write-Host "Bye-bye, hope you return later!" -ForegroundColor Red
Write-Result ""
Write-Result "Bye-bye, hope you return later!"

298
scripts/lib/utils.ps1 Normal file
View file

@ -0,0 +1,298 @@
# Shared utilities for Windows scripts
# Provides: colors, progress indicators, interactive detection
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
# Detect if running in interactive shell
function Test-Interactive
{
# Check if both input and output are from terminal
# In non-interactive environments (CI/CD), this will be false
$isInteractive = [Environment]::UserInteractive -and
(-not [Console]::IsInputRedirected) -and
(-not [Console]::IsOutputRedirected)
return $isInteractive
}
# Global spinner state
$script:SpinnerJob = $null
$script:SpinnerFrames = @('⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏')
# Logging functions
function Write-Info
{
param([string]$Message)
if (Test-Interactive)
{
Write-Host $Message -ForegroundColor Blue
}
else
{
Write-Output $Message
}
}
function Write-Success
{
param([string]$Message)
if (Test-Interactive)
{
Write-Host $Message -ForegroundColor Green
}
else
{
Write-Output $Message
}
}
function Write-Warning-Custom
{
param([string]$Message)
if (Test-Interactive)
{
Write-Host $Message -ForegroundColor Yellow
}
else
{
Write-Output "WARNING: $Message"
}
}
function Write-Error-Custom
{
param([string]$Message)
if (Test-Interactive)
{
Write-Host $Message -ForegroundColor Red
}
else
{
Write-Output "ERROR: $Message" *>&1
}
}
function Write-Step
{
param([string]$Message)
if (Test-Interactive)
{
Write-Host $Message -ForegroundColor Magenta
}
else
{
Write-Output $Message
}
}
function Write-Result
{
param([string]$Message)
if (Test-Interactive)
{
Write-Host $Message -ForegroundColor Cyan
}
else
{
Write-Output $Message
}
}
# Spinner functions (only for interactive shells)
function Start-Spinner
{
param([string]$Message = "Processing...")
if (-not (Test-Interactive))
{
Write-Output $Message
return
}
$script:SpinnerJob = Start-Job -ScriptBlock {
param($Frames, $Msg)
$i = 0
while ($true)
{
$frame = $Frames[$i % $Frames.Length]
[Console]::SetCursorPosition(0, [Console]::CursorTop)
Write-Host -NoNewline "$frame $Msg"
Start-Sleep -Milliseconds 100
$i++
}
} -ArgumentList $script:SpinnerFrames, $Message
}
function Stop-Spinner
{
if ($script:SpinnerJob -ne $null)
{
Stop-Job -Job $script:SpinnerJob -ErrorAction SilentlyContinue
Remove-Job -Job $script:SpinnerJob -Force -ErrorAction SilentlyContinue
$script:SpinnerJob = $null
if (Test-Interactive)
{
# Clear the spinner line
[Console]::SetCursorPosition(0, [Console]::CursorTop)
Write-Host (' ' * ([Console]::WindowWidth - 1)) -NoNewline
[Console]::SetCursorPosition(0, [Console]::CursorTop)
}
}
}
function Update-Spinner
{
param([string]$Message)
if (-not (Test-Interactive))
{
Write-Output $Message
return
}
if ($script:SpinnerJob -ne $null)
{
Stop-Spinner
Start-Spinner -Message $Message
}
}
# Execute command with progress indicator
function Invoke-WithProgress
{
param(
[string]$Message,
[scriptblock]$ScriptBlock
)
if (Test-Interactive)
{
Start-Spinner -Message $Message
try
{
$output = & $ScriptBlock 2>&1
$exitCode = $LASTEXITCODE
Stop-Spinner
if ($exitCode -eq 0 -or $null -eq $exitCode)
{
Write-Success "$Message - Done!"
}
else
{
Write-Error-Custom "$Message - Failed!"
if ($output)
{
Write-Output $output
}
}
return $exitCode
}
catch
{
Stop-Spinner
Write-Error-Custom "$Message - Failed!"
throw
}
}
else
{
# Non-interactive: just show message and run
Write-Output "$Message"
try
{
& $ScriptBlock
$exitCode = $LASTEXITCODE
if ($exitCode -eq 0 -or $null -eq $exitCode)
{
Write-Output "$Message - Done!"
}
else
{
Write-Output "$Message - Failed!"
}
return $exitCode
}
catch
{
Write-Output "$Message - Failed!"
throw
}
}
}
# Check system requirements
function Test-SystemRequirements
{
param(
[int]$MinCpu = 4,
[int]$MinRamGB = 6,
[int]$MinDiskGB = 20
)
Write-Step "Checking system requirements..."
# Check CPU cores
$cpuCount = [Environment]::ProcessorCount
if ($cpuCount -lt $MinCpu)
{
Write-Error-Custom "Insufficient CPU cores: $cpuCount (minimum: $MinCpu)"
return $false
}
# Check RAM
$totalMemBytes = (Get-CimInstance Win32_ComputerSystem).TotalPhysicalMemory
$totalMemGB = [Math]::Round($totalMemBytes / 1GB, 2)
if ($totalMemGB -lt $MinRamGB)
{
Write-Error-Custom "Insufficient RAM: ${totalMemGB}GB (minimum: ${MinRamGB}GB)"
return $false
}
# Check disk space
$currentDrive = Split-Path -Qualifier $PWD
$driveLetter = $currentDrive.Substring(0, 1)
$freeGB = [Math]::Round((Get-PSDrive -Name $driveLetter).Free / 1GB, 2)
if ($freeGB -lt $MinDiskGB)
{
Write-Error-Custom "Insufficient disk space: ${freeGB}GB (minimum: ${MinDiskGB}GB)"
return $false
}
Write-Success "System requirements met: CPU cores=$cpuCount, RAM=${totalMemGB}GB, FreeDisk=${freeGB}GB"
return $true
}
# Confirm action
function Confirm-Action
{
param([string]$Prompt = "Are you sure?")
if (-not (Test-Interactive))
{
# In non-interactive mode, assume yes
return $true
}
$response = Read-Host "$Prompt [y/N]"
return $response -match '^[yY]([eE][sS])?$'
}
# Ensure spinner cleanup on exit
$MyInvocation.MyCommand.ScriptBlock.Module.OnRemove = {
Stop-Spinner
}

223
scripts/lib/utils.sh Normal file
View file

@ -0,0 +1,223 @@
#!/usr/bin/env bash
# Shared utilities for Unix scripts
# Provides: colors, progress indicators, interactive detection
# Detect if running in interactive shell
is_interactive() {
[[ -t 0 && -t 1 ]]
}
# Color definitions (only used in interactive mode)
if is_interactive && [[ "${NO_COLOR:-}" != "1" ]]; then
COLOR_RESET='\033[0m'
COLOR_RED='\033[0;31m'
COLOR_GREEN='\033[0;32m'
COLOR_YELLOW='\033[0;33m'
COLOR_BLUE='\033[0;34m'
COLOR_MAGENTA='\033[0;35m'
COLOR_CYAN='\033[0;36m'
COLOR_GRAY='\033[0;90m'
COLOR_BOLD='\033[1m'
else
COLOR_RESET=''
COLOR_RED=''
COLOR_GREEN=''
COLOR_YELLOW=''
COLOR_BLUE=''
COLOR_MAGENTA=''
COLOR_CYAN=''
COLOR_GRAY=''
COLOR_BOLD=''
fi
# Logging functions
log_info() {
echo -e "${COLOR_BLUE}${1}${COLOR_RESET}"
}
log_success() {
echo -e "${COLOR_GREEN}${1}${COLOR_RESET}"
}
log_warning() {
echo -e "${COLOR_YELLOW}${1}${COLOR_RESET}"
}
log_error() {
echo -e "${COLOR_RED}${1}${COLOR_RESET}" >&2
}
log_step() {
echo -e "${COLOR_MAGENTA}${1}${COLOR_RESET}"
}
log_result() {
echo -e "${COLOR_CYAN}${1}${COLOR_RESET}"
}
# Spinner animation (only for interactive shells)
SPINNER_FRAMES=('⠋' '⠙' '⠹' '⠸' '⠼' '⠴' '⠦' '⠧' '⠇' '⠏')
SPINNER_PID=""
start_spinner() {
local message="${1:-Processing...}"
if ! is_interactive; then
echo "$message"
return
fi
{
local i=0
while true; do
printf "\r${COLOR_CYAN}${SPINNER_FRAMES[$i]}${COLOR_RESET} %s" "$message"
i=$(( (i + 1) % ${#SPINNER_FRAMES[@]} ))
sleep 0.1
done
} &
SPINNER_PID=$!
# Ensure spinner is cleaned up on script exit
trap "stop_spinner" EXIT INT TERM
}
stop_spinner() {
if [[ -n "$SPINNER_PID" ]] && kill -0 "$SPINNER_PID" 2>/dev/null; then
kill "$SPINNER_PID" 2>/dev/null
wait "$SPINNER_PID" 2>/dev/null
SPINNER_PID=""
fi
if is_interactive; then
printf "\r\033[K" # Clear the spinner line
fi
}
update_spinner_message() {
local message="${1:-Processing...}"
if ! is_interactive; then
echo "$message"
return
fi
if [[ -n "$SPINNER_PID" ]] && kill -0 "$SPINNER_PID" 2>/dev/null; then
stop_spinner
start_spinner "$message"
fi
}
# Execute command with progress indicator
run_with_progress() {
local message="$1"
shift
local cmd=("$@")
if is_interactive; then
start_spinner "$message"
local output
local exit_code
# Capture output and exit code
output=$("${cmd[@]}" 2>&1)
exit_code=$?
stop_spinner
if [[ $exit_code -eq 0 ]]; then
log_success "$message - Done!"
else
log_error "$message - Failed!"
echo "$output"
fi
return $exit_code
else
# Non-interactive: just show message and run
echo "$message"
"${cmd[@]}"
local exit_code=$?
if [[ $exit_code -eq 0 ]]; then
echo "$message - Done!"
else
echo "$message - Failed!"
fi
return $exit_code
fi
}
# Check if command exists
command_exists() {
command -v "$1" >/dev/null 2>&1
}
# Validate system requirements
check_system_requirements() {
local min_cpu="${1:-4}"
local min_ram_gb="${2:-6}"
local min_disk_gb="${3:-20}"
log_step "Checking system requirements..."
# Check CPU cores
local cpu_count
cpu_count=$(getconf _NPROCESSORS_ONLN)
if [[ "$cpu_count" -lt "$min_cpu" ]]; then
log_error "Insufficient CPU cores: $cpu_count (minimum: $min_cpu)"
return 1
fi
# Check RAM
local total_mem_gb
if [[ -f /proc/meminfo ]]; then
local mem_kb
mem_kb=$(grep MemTotal /proc/meminfo | awk '{print $2}')
total_mem_gb=$(awk "BEGIN {printf \"%.2f\", $mem_kb/1024/1024}")
else
local total_mem_bytes
total_mem_bytes=$(sysctl -n hw.memsize 2>/dev/null || echo "0")
total_mem_gb=$(awk "BEGIN {printf \"%.2f\", $total_mem_bytes/1024/1024/1024}")
fi
if ! awk "BEGIN {exit !($total_mem_gb >= $min_ram_gb)}"; then
log_error "Insufficient RAM: ${total_mem_gb}GB (minimum: ${min_ram_gb}GB)"
return 1
fi
# Check disk space
local avail_kb free_gb
avail_kb=$(df -k . | tail -1 | awk '{print $4}')
free_gb=$(awk "BEGIN {printf \"%.2f\", $avail_kb/1024/1024}")
if ! awk "BEGIN {exit !($free_gb >= $min_disk_gb)}"; then
log_error "Insufficient disk space: ${free_gb}GB (minimum: ${min_disk_gb}GB)"
return 1
fi
log_success "System requirements met: CPU cores=$cpu_count, RAM=${total_mem_gb}GB, FreeDisk=${free_gb}GB"
return 0
}
# Confirm action (returns 0 for yes, 1 for no)
confirm() {
local prompt="${1:-Are you sure?}"
local response
if ! is_interactive; then
# In non-interactive mode, assume yes
return 0
fi
read -r -p "$prompt [y/N]: " response
case "$response" in
[yY][eE][sS]|[yY])
return 0
;;
*)
return 1
;;
esac
}