From 3888c91ab35178f779c7fa29f3cb1d33c65116c4 Mon Sep 17 00:00:00 2001 From: anoracleofra-code Date: Wed, 4 Mar 2026 23:12:14 -0700 Subject: [PATCH] feat: add cross-platform start.sh script and update package.json for macOS/Linux --- README.md | 6 +++-- ShadowBroker_v0.1.zip | Bin 6173859 -> 6174633 bytes clean_zip.py | 37 ++++++++++++++++++++++++++++++ frontend/package.json | 4 ++-- start.sh | 52 ++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 95 insertions(+), 4 deletions(-) create mode 100644 clean_zip.py create mode 100644 start.sh diff --git a/README.md b/README.md index 6bb27cb..3eb29e9 100644 --- a/README.md +++ b/README.md @@ -143,9 +143,11 @@ Built with **Next.js**, **MapLibre GL**, **FastAPI**, and **Python**, it's desig If you just want to run the dashboard without dealing with terminal commands: 1. Go to the **[Releases](../../releases)** tab on the right side of this GitHub page. -2. Download the `ShadowBroker_vX.X.zip` file. +2. Download the `ShadowBroker_v0.1.zip` file. 3. Extract the folder to your computer. -4. Double-click the **`start.bat`** file. It will automatically install everything and launch the dashboard! +4. **Windows:** Double-click `start.bat`. + **Mac/Linux:** Open terminal, type `chmod +x start.sh`, and run `./start.sh`. +5. It will automatically install everything and launch the dashboard! --- diff --git a/ShadowBroker_v0.1.zip b/ShadowBroker_v0.1.zip index 26acc242a5d0a3af1c67ce51279ea66ddf4d9daf..ba57965389036194d6dd60e6de33d2e1e1bbf20b 100644 GIT binary patch delta 6220 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 diff --git a/clean_zip.py b/clean_zip.py new file mode 100644 index 0000000..3cda542 --- /dev/null +++ b/clean_zip.py @@ -0,0 +1,37 @@ +import os +import zipfile + +zip_name = 'ShadowBroker_v0.1.zip' + +if os.path.exists(zip_name): + try: + os.remove(zip_name) + except Exception as e: + print(f"Failed to delete old zip: {e}") + +def add_dir(zipf, dir_path, excludes): + for root, dirs, files in os.walk(dir_path): + dirs[:] = [d for d in dirs if d not in excludes] + for f in files: + file_path = os.path.join(root, f) + zipf.write(file_path, arcname=file_path) + +try: + with zipfile.ZipFile(zip_name, 'w', zipfile.ZIP_DEFLATED) as zipf: + print("Zipping backend...") + add_dir(zipf, 'backend', {'venv', '__pycache__'}) + + print("Zipping frontend...") + add_dir(zipf, 'frontend', {'node_modules', '.next'}) + + print("Zipping root files...") + zipf.write('docker-compose.yml') + zipf.write('start.bat') + zipf.write('start.sh') + zipf.write('README.md') + + final_size = os.path.getsize(zip_name) / (1024 * 1024) + print(f"\nāœ… SUCCESS! Created {zip_name}. Final size: {final_size:.2f} MB") + +except Exception as e: + print(f"\nāŒ ERROR creating zip: {e}") diff --git a/frontend/package.json b/frontend/package.json index d5ef4e6..1bce943 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -3,7 +3,7 @@ "version": "0.1.0", "private": true, "scripts": { - "dev": "concurrently --names \"NEXT,API\" --prefix-colors \"cyan,yellow\" \"next dev\" \"cd ../backend && venv\\Scripts\\python.exe main.py\"", + "dev": "concurrently \"npm run dev:frontend\" \"cd ../backend && python -m uvicorn main:app --reload\"", "dev:frontend": "next dev", "dev:backend": "cd ../backend && venv\\Scripts\\python.exe main.py", "build": "next build", @@ -37,4 +37,4 @@ "tailwindcss": "^4", "typescript": "^5" } -} +} \ No newline at end of file diff --git a/start.sh b/start.sh new file mode 100644 index 0000000..2684942 --- /dev/null +++ b/start.sh @@ -0,0 +1,52 @@ +#!/bin/bash +echo "=======================================================" +echo " S H A D O W B R O K E R - macOS / Linux Start " +echo "=======================================================" +echo "" + +# Check for Node.js +if ! command -v npm &> /dev/null; then + echo "[!] ERROR: npm is not installed. Please install Node.js (https://nodejs.org/)" + exit 1 +fi + +# Check for Python +if ! command -v python3 &> /dev/null; then + echo "[!] ERROR: python3 is not installed. Please install Python 3.10+ (https://python.org/)" + exit 1 +fi + +echo "[*] Setting up Backend Environment..." +cd backend +if [ ! -d "venv" ]; then + echo "[*] Creating Python Virtual Environment..." + python3 -m venv venv +fi + +echo "[*] Installing Backend dependencies..." +if [[ "$OSTYPE" == "msys" || "$OSTYPE" == "cygwin" ]]; then + # In case someone runs this in Git Bash on Windows + source venv/Scripts/activate +else + source venv/bin/activate +fi +pip install -r requirements.txt +cd .. + +echo "[*] Setting up Frontend Environment..." +cd frontend +if [ ! -d "node_modules" ]; then + echo "[*] Installing Frontend dependencies..." + npm install +fi + +echo "" +echo "=======================================================" +echo " šŸš€ Starting Services... " +echo " Dashboard will be available at: http://localhost:3000" +echo " Keep this window open! Note: Initial load takes ~10s " +echo "=======================================================" +echo "" + +# Start both services (npm run dev automatically calls the python backend on Mac/Linux if scripts are configured cross-platform) +npm run dev