#!/usr/bin/env bash # Exit immediately if a command exits with a non-zero status set -euo pipefail # Enable debugging output to trace commands. # set -x # GitHub repository information REPO_URL="https://raw.githubusercontent.com/danfortsystems/apptools/main" SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" ARCHIVE_URL="https://github.com/danfortsystems/apptools/archive/refs/heads/main.tar.gz" # Check if AppTools is installed locally find_local_installation() { local local_paths=( "$(pwd)" "$HOME/.local/share/apptools" ) for path in "${local_paths[@]}"; do if [ -d "$path" ] && [ -f "$path/_utils" ]; then echo "$path" return 0 fi done echo "" return 1 } # Create or get AppTools directory setup_app_tools_dir() { # Check for local installation first local_installation=$(find_local_installation) if [ -n "$local_installation" ]; then echo "$local_installation" return 0 fi # Create temporary directory local temp_dir temp_dir=$(mktemp -d) # Download and extract archive if command -v curl >/dev/null 2>&1; then if ! curl -fsSL "$ARCHIVE_URL" | tar -xzf - -C "$temp_dir"; then echo "ERROR: Failed to download AppTools archive" >&2 rm -rf "$temp_dir" return 1 fi elif command -v wget >/dev/null 2>&1; then if ! wget -qO - "$ARCHIVE_URL" | tar -xzf - -C "$temp_dir"; then echo "ERROR: Failed to download AppTools archive" >&2 rm -rf "$temp_dir" return 1 fi else echo "ERROR: Neither curl nor wget is available" >&2 return 1 fi # The extracted directory will be apptools-main local extracted_dir="$temp_dir/apptools-main" echo "$extracted_dir" return 0 } # Check for help flag if [ $# -eq 0 ] || [ "$1" = "--help" ] || [ "$1" = "-h" ]; then echo "Usage: project [arguments]" >&2 echo "" >&2 echo "Actions:" >&2 echo "" >&2 echo "deps [--reset ]" >&2 echo " Manage dependencies (js, db, containers, browsers)" >&2 echo " --reset Reset targets before install (js, db, containers, browsers)" >&2 echo " Uses: DATABASE_URL, DB_PATH, OBJECT_STORAGE_BUCKET, MSG_FROM_EMAIL_ADDRESS, PLAYWRIGHT_BROWSERS_PATH" >&2 echo "" >&2 echo "build [--dest ] [--only app|exc|db] [--db-url ] [--db-path ] [--db-reset-ok]" >&2 echo " Build application, executable, and/or database" >&2 echo " --dest Output directory (default: ./dist)" >&2 echo " --only Comma-separated targets: app, exc, db (default: all)" >&2 echo " app: typecheck and bundle client/server code" >&2 echo " exc: build self-contained platform executable ({name}-{os}-{arch})" >&2 echo " db: generate db init/migration scripts" >&2 echo " --db-url PostgreSQL URL (overrides DATABASE_URL)" >&2 echo " --db-path SQLite path (overrides DB_PATH)" >&2 echo " --db-reset-ok Allow database reset without migration script" >&2 echo "" >&2 echo "test [--build-dir ] [--no-db-create] [--db-url ] [--keep-db-file] [--log-dir ] [--only ] [--fast]" >&2 echo " Run tests (units, api, gui, e2e)" >&2 echo " --build-dir Build artifacts directory (default: ./dist)" >&2 echo " --no-db-create Use DATABASE_URL as-is (skip test database creation)" >&2 echo " --db-url PostgreSQL URL (overrides DATABASE_URL)" >&2 echo " --keep-db-file Keep db.migrate.sql after successful migration" >&2 echo " --log-dir Log directory (default: ./logs/test/)" >&2 echo " --only Test types: units, api, gui, e2e (comma-separated)" >&2 echo " --fast Fast mode: Chrome only, no other browsers" >&2 echo "" >&2 echo "dev [--db-url ] [--port ]" >&2 echo " Start development server with file watching" >&2 echo " --db-url PostgreSQL URL (overrides DATABASE_URL)" >&2 echo " --port Server port (overrides PORT)" >&2 echo "" >&2 echo "start [--db-url ] [--db-path ] [--port ]" >&2 echo " Start production server" >&2 echo " --db-url PostgreSQL URL (overrides DATABASE_URL)" >&2 echo " --db-path SQLite path (overrides DB_PATH)" >&2 echo " --port Server port (overrides PORT)" >&2 echo "" >&2 echo "deploy [--update-level ] [--include ] [--exclude ] --repo " >&2 echo " Deploy to git repository with version bump" >&2 echo " --update-level Semver bump: patch, minor, or major (default: patch)" >&2 echo " --include Files to include, comma-separated (default: all)" >&2 echo " --exclude Files to exclude, comma-separated (default: none)" >&2 echo " --repo Target repository URL (required)" >&2 echo "" >&2 echo "install --src --name [--target ] [--version ]" >&2 echo " Install a project locally" >&2 echo " --src GitHub URL or local folder path (required)" >&2 echo " --name Package/command name (required); selects {name}-{os}-{arch} executable or falls back to {name}" >&2 echo " --target Installation directory (default: ~/.local/share/)" >&2 echo " --version Version tag to install (GitHub only; default: latest)" >&2 echo "" >&2 echo "Global:" >&2 echo " --version Show AppTools version" >&2 echo " --init No-op (AppTools has no initialization tasks; supported for install compatibility)" >&2 echo " --help, -h Show this help" >&2 exit 1 fi # Parse args to construct command action="$1" shift # Remove 1st arg (action) from the args list command="$action" for arg in "$@"; do command="${command} $arg" done # Setup AppTools directory (local installation or temp directory) APPTOOLS_DIR_PATH=$(setup_app_tools_dir) if [ $? -ne 0 ] || [ -z "$APPTOOLS_DIR_PATH" ]; then echo "ERROR: Failed to setup AppTools directory" >&2 exit 1 fi # If command passed is --version, just print version info and exit if [ "${command}" = "--version" ]; then if [ -f "$APPTOOLS_DIR_PATH/VERSION" ]; then printf '%s\n' "$(cat "$APPTOOLS_DIR_PATH/VERSION")" else echo "unknown" fi exit 0 fi # If command passed is --init, no-op (AppTools has no initialization tasks) if [ "${command}" = "--init" ]; then exit 0 fi # Load utility and environment functions source "$APPTOOLS_DIR_PATH/_utils" source "$APPTOOLS_DIR_PATH/_env" # Set log directory, file path, and command, based on the action TIMESTAMP=$(date +"%s__%Y-%m-%d__%I-%M-%S_%p") if [ "$action" = "test" ]; then LOG_DIRECTORY="./logs/$action/$TIMESTAMP" LOG_FILE_PATH="./${LOG_DIRECTORY}/consolidated.log" else LOG_DIRECTORY="./logs/$action" LOG_FILE_PATH="./${LOG_DIRECTORY}/${TIMESTAMP}.$action.log" fi # Create the log directory, if it does not exist mkdir -p "$LOG_DIRECTORY" # Set color codes to be stripped out of log strip_pattern="s/\x1B\[([0-9]{1,2}(;[0-9]{1,2})?)?[mGKABCDHJKSfn]//g" # Run the command start_timestamp=$(date +%s) formatted_start_time=$(date -r "$start_timestamp" "+%a, %b %d %Y at %I:%M:%S %p") print_warning_header "\nSTARTED ${italic}$action${unitalic} at $formatted_start_time\n" # Run the command with APPTOOLS_DIR_PATH injected and capture exit code set +e ( APPTOOLS_DIR_PATH="$APPTOOLS_DIR_PATH" "$APPTOOLS_DIR_PATH/$action" "$@" 2>&1 | (trap '' SIGINT SIGHUP SIGPIPE; tee >(sed -E "$strip_pattern" > "$LOG_FILE_PATH")) ) exit_code=$? set -e end_timestamp=$(date +%s) seconds=$((end_timestamp-start_timestamp)) # Report completion or failure if [ $exit_code -eq 0 ]; then formatted_end_time=$(date -r "$end_timestamp" "+%a, %b %d %Y at %I:%M:%S %p") print_success_header "\nFINISHED ${italic}$action${unitalic} after ${seconds}s\n" else print_error_header "\nFAILED ${italic}$action${unitalic} (exit code $exit_code) after ${seconds}s\n" exit $exit_code fi # Clean up temporary directory if it was created if [[ "$APPTOOLS_DIR_PATH" == *"tmp."* ]] || [[ "$APPTOOLS_DIR_PATH" == *"tmp/"* ]]; then rm -rf "$APPTOOLS_DIR_PATH" fi