deploy_pipeline.py (9921B)
1 # Author : Amit Dutta <amitdutta4255@gmail.com> 2 # Date : 06 Feb 2026 3 # Repo : https://github.com/notamitgamer/bsc 4 # License : MIT License (See the LICENSE file for details) 5 # Copyright (c) 2026 Amit Dutta 6 7 import os 8 import subprocess 9 import shutil 10 import stat 11 import socket 12 import sys 13 from datetime import datetime 14 15 BSC_REPO_ROOT = r"G:\bsc" 16 GENERATE_INDEX_SCRIPT = r"G:\bsc\docs\generate_index.py" 17 ARANAG_SITE_DESKTOP = r"C:\Users\PC\Desktop\amit.is-a.dev" 18 BSC_LOCAL_BACKUP = r"G:\bsc_local" 19 ARANAG_REPO_ROOT = r"G:\aranag" 20 21 MIN_DISK_SPACE_MB = 500 22 23 ARANAG_PRESERVE = [ 24 "ada-web", 25 "compiler", 26 "README.md", 27 "sitemap.xml", 28 ".firebase", 29 ".git", 30 "esal", 31 "amit.is-a.dev" 32 ] 33 34 def remove_readonly(func, path, exc_info): 35 try: 36 os.chmod(path, stat.S_IWRITE) 37 func(path) 38 except Exception as e: 39 print(f"Failed to force delete {path}: {e}") 40 41 def check_internet(host="8.8.8.8", port=53, timeout=3): 42 try: 43 socket.setdefaulttimeout(timeout) 44 socket.socket(socket.AF_INET, socket.SOCK_STREAM).connect((host, port)) 45 return True 46 except socket.error: 47 return False 48 49 def get_free_space_mb(folder): 50 total, used, free = shutil.disk_usage(folder) 51 return free // (2**20) 52 53 def is_tool_installed(name): 54 return shutil.which(name) is not None 55 56 def pre_check(): 57 print("\n--- Step 0: System Pre-Checks ---") 58 59 print("Checking internet connection...", end=" ") 60 if check_internet(): 61 print("[OK]") 62 else: 63 print("[FAILED]") 64 print("Error: No internet connection detected. Cannot push to Git or Firebase.") 65 return False 66 67 print("Checking required tools...", end=" ") 68 missing_tools = [] 69 if not is_tool_installed("git"): 70 missing_tools.append("git") 71 72 if not is_tool_installed("firebase"): 73 if not is_tool_installed("firebase.cmd"): 74 missing_tools.append("firebase") 75 76 if missing_tools: 77 print("[FAILED]") 78 print(f"Error: The following tools are missing from PATH: {', '.join(missing_tools)}") 79 return False 80 print("[OK]") 81 82 backup_drive = os.path.splitdrive(BSC_LOCAL_BACKUP)[0] 83 if not backup_drive: 84 backup_drive = "." 85 else: 86 backup_drive += "\\" 87 88 print(f"Checking disk space on {backup_drive}...", end=" ") 89 try: 90 free_space = get_free_space_mb(backup_drive) 91 if free_space < MIN_DISK_SPACE_MB: 92 print("[FAILED]") 93 print(f"Error: Not enough disk space. Free: {free_space}MB, Required: {MIN_DISK_SPACE_MB}MB") 94 return False 95 print(f"[OK] ({free_space} MB free)") 96 except Exception as e: 97 print(f"[WARNING] Could not check disk space: {e}") 98 99 return True 100 101 def run_generate_index(): 102 print("\n--- Step 1: Generating Index HTML ---") 103 104 index_path = os.path.join(BSC_REPO_ROOT, "docs", "index.html") 105 old_content = "" 106 107 if os.path.exists(index_path): 108 try: 109 with open(index_path, 'r', encoding='utf-8') as f: 110 old_content = f.read() 111 except Exception: 112 pass 113 114 if os.path.exists(GENERATE_INDEX_SCRIPT): 115 try: 116 subprocess.run(["python", GENERATE_INDEX_SCRIPT], check=True) 117 print("[Success] Index generation script executed.") 118 except subprocess.CalledProcessError: 119 print("[Error] Failed to run generate_index.py") 120 return False 121 else: 122 print(f"[Error] Script not found at: {GENERATE_INDEX_SCRIPT}") 123 return False 124 125 new_content = "" 126 if os.path.exists(index_path): 127 try: 128 with open(index_path, 'r', encoding='utf-8') as f: 129 new_content = f.read() 130 except Exception: 131 pass 132 133 if old_content != new_content: 134 print("[Info] Changes detected in index.html.") 135 return True 136 else: 137 print("[Info] No changes detected in index.html.") 138 return False 139 140 def git_workflow_bsc(): 141 print("\n--- Step 2: Git Push (Primary Repo: bsc) ---") 142 143 if not os.path.exists(BSC_REPO_ROOT): 144 print(f"[Error] Repository root not found: {BSC_REPO_ROOT}") 145 return 146 147 os.chdir(BSC_REPO_ROOT) 148 149 print("> git add .") 150 try: 151 subprocess.run(["git", "add", "."], check=True) 152 except subprocess.CalledProcessError: 153 print("[Error] 'git add' failed.") 154 return 155 156 try: 157 result = subprocess.run( 158 ["git", "diff", "--name-only", "--cached"], 159 capture_output=True, text=True, check=True 160 ) 161 changed_files = result.stdout.strip().splitlines() 162 except subprocess.CalledProcessError: 163 changed_files = [] 164 165 if not changed_files: 166 print("[Info] No changes detected in 'bsc'. Skipping commit/push.") 167 return 168 169 # 1. Filter out docs/index.html (it's auto-generated) 170 relevant_files = [f for f in changed_files if "docs/index.html" not in f.replace('\\', '/')] 171 172 # 2. Separate "docs" files from "source code" files 173 # We check if a file starts with "docs/" 174 non_docs_files = [f for f in relevant_files if not f.replace('\\', '/').startswith('docs/')] 175 176 # 3. Decision Logic: 177 # If we have non-docs changes (like Semester_1/code.c), we prioritize those paths. 178 # We ignore 'docs/' changes in the path calculation so it doesn't default to Root. 179 if non_docs_files: 180 files_to_check = non_docs_files 181 else: 182 # If ONLY docs changed (or only index.html changed), use whatever we have 183 files_to_check = relevant_files if relevant_files else changed_files 184 185 changed_dirs = [os.path.dirname(f.replace('/', os.sep)) for f in files_to_check] 186 187 if not changed_dirs: 188 common_path = "" 189 else: 190 try: 191 common_path = os.path.commonpath(changed_dirs) 192 except ValueError: 193 common_path = "" 194 195 path_str = f".\\{common_path}" if common_path else ".\\{root}" 196 197 print(f"[Detected Changes in]: {path_str}") 198 user_message = input(f"Enter commit message for '{path_str}': ") 199 if not user_message: 200 print("Commit message cannot be empty. Aborting git push.") 201 return 202 203 today = datetime.now().strftime("%Y-%m-%d") 204 full_commit_msg = f"[{today}] : {path_str} : {user_message}" 205 206 print(f"> git commit -m \"{full_commit_msg}\"") 207 try: 208 subprocess.run(["git", "commit", "-m", full_commit_msg], check=True) 209 print("> git push") 210 except subprocess.CalledProcessError as e: 211 print(f"[Error] Git command failed: {e}") 212 213 def deploy_firebase(): 214 print("\n--- Step 3: Firebase Deploy ---") 215 if not os.path.exists(ARANAG_SITE_DESKTOP): 216 print(f"[Error] Firebase project path not found: {ARANAG_SITE_DESKTOP}") 217 return 218 219 os.chdir(ARANAG_SITE_DESKTOP) 220 print(f"Deploying from: {ARANAG_SITE_DESKTOP}") 221 try: 222 subprocess.run("firebase deploy --only hosting", shell=True, check=True) 223 print("[Success] Firebase deployment complete.") 224 except subprocess.CalledProcessError: 225 print("[Error] Firebase deploy failed.") 226 227 def sync_bsc_local(): 228 print("\n--- Step 4: Syncing to G:\\bsc_local ---") 229 try: 230 if os.path.exists(BSC_LOCAL_BACKUP): 231 print(f"Cleaning {BSC_LOCAL_BACKUP}...") 232 shutil.rmtree(BSC_LOCAL_BACKUP, onerror=remove_readonly) 233 234 print(f"Copying from {BSC_REPO_ROOT} to {BSC_LOCAL_BACKUP}...") 235 shutil.copytree(BSC_REPO_ROOT, BSC_LOCAL_BACKUP) 236 print("[Success] bsc_local synced.") 237 except Exception as e: 238 print(f"[Error] Sync failed: {e}") 239 240 def update_and_push_aranag(): 241 print("\n--- Step 5: Updating G:\\aranag & Pushing ---") 242 243 if not os.path.exists(ARANAG_REPO_ROOT): 244 print(f"[Error] Path not found: {ARANAG_REPO_ROOT}") 245 return 246 247 print("Cleaning G:\\aranag (preserving specific files)...") 248 for item in os.listdir(ARANAG_REPO_ROOT): 249 if item in ARANAG_PRESERVE: 250 continue 251 252 item_path = os.path.join(ARANAG_REPO_ROOT, item) 253 try: 254 if os.path.isdir(item_path): 255 shutil.rmtree(item_path, onerror=remove_readonly) 256 else: 257 os.remove(item_path) 258 except Exception as e: 259 print(f"Warning: Could not delete {item}: {e}") 260 261 print(f"Copying from {ARANAG_SITE_DESKTOP} to {ARANAG_REPO_ROOT}...") 262 try: 263 shutil.copytree(ARANAG_SITE_DESKTOP, ARANAG_REPO_ROOT, dirs_exist_ok=True) 264 except Exception as e: 265 print(f"[Error] Copy failed: {e}") 266 return 267 268 os.chdir(ARANAG_REPO_ROOT) 269 print("Git pushing G:\\aranag...") 270 271 try: 272 subprocess.run(["git", "add", "."], check=True) 273 274 status = subprocess.run(["git", "status", "--porcelain"], capture_output=True, text=True) 275 if status.stdout.strip(): 276 commit_msg = f"Automated Update: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}" 277 subprocess.run(["git", "commit", "-m", commit_msg], check=True) 278 subprocess.run(["git", "push"], check=True) 279 print("[Success] G:\\aranag updated and pushed.") 280 else: 281 print("[Info] No changes to push in G:\\aranag.") 282 283 except subprocess.CalledProcessError as e: 284 print(f"[Error] Git operations on aranag failed: {e}") 285 286 def main(): 287 print("=== STARTING AUTOMATION PIPELINE ===") 288 289 if not pre_check(): 290 print("\n[Aborted] Pre-checks failed. Please resolve the errors above.") 291 return 292 293 index_changed = run_generate_index() 294 295 git_workflow_bsc() 296 297 if index_changed: 298 deploy_firebase() 299 else: 300 print("\n[Skip] Index unchanged. Skipping Firebase deploy.") 301 302 sync_bsc_local() 303 304 # removed the auto git upload for the main website folder. 305 306 print("\n=== PIPELINE FINISHED ===") 307 308 if __name__ == "__main__": 309 main()