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