#!/usr/bin/env python3 import os import subprocess import sys import urllib.request def extract_or_fetch(filename, out_path, image="makeplane/plane-frontend:stable"): if os.path.exists(out_path) and os.path.getsize(out_path) > 50: print(f"Using existing {filename} on host ({os.path.getsize(out_path)} bytes)") return True for base_dir in ["/usr/share/caddy/html/assets", "/usr/share/nginx/html/assets"]: src_container_path = f"{base_dir}/{filename}" try: res = subprocess.run( ["docker", "run", "--rm", image, "cat", src_container_path], stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True, ) if len(res.stdout) > 50: with open(out_path, "wb") as f: f.write(res.stdout) print(f"Extracted {filename} from {image} ({len(res.stdout)} bytes)") return True except Exception: continue url = f"https://jira.tradhox.com/assets/{filename}" try: req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"}) with urllib.request.urlopen(req, timeout=15) as resp: content = resp.read() if len(content) > 50: with open(out_path, "wb") as f: f.write(content) print(f"Fetched {filename} from {url} ({len(content)} bytes)") return True except Exception as e: print(f"Notice: Could not fetch {filename} from {url}: {e}") # Fallback stub to satisfy docker-compose volume mounts if needed with open(out_path, "w", encoding="utf-8") as f: f.write("// frontend patch stub\n") print(f"Created fallback stub for {filename}") return False def patch_page(file_path): if not os.path.exists(file_path): return try: with open(file_path, "r", encoding="utf-8") as f: code = f.read() # 1. Replace Community with Enterprise in top box old_box = "(0,L.jsx)(A,{title:`Community`,description:`Unlimited projects, issues, cycles, modules, pages, and storage`})" new_box = "(0,L.jsx)(A,{title:`Enterprise`,description:`Enterprise Edition Active — Unlimited users, projects, issues, cycles, modules, pages, storage, and all enterprise features unlocked`})" if old_box in code: code = code.replace(old_box, new_box) # 2. Update W for enterprise old_ent = "enterprise:{id:o.ENTERPRISE,name:`Enterprise`,monthlyPriceSecondaryDescription:`billed monthly`,yearlyPriceSecondaryDescription:`billed yearly`,buttonCTA:`Talk to Sales`,isActive:!1}" new_ent = "enterprise:{id:o.ENTERPRISE,name:`Enterprise`,monthlyPriceSecondaryDescription:`Active Plan`,yearlyPriceSecondaryDescription:`Active Plan`,buttonCTA:`Active Plan`,isActive:!0}" if old_ent in code: code = code.replace(old_ent, new_ent) # 3. Update Y pricing descriptions and button old_ph = "p=d?`a user per month`:`Quote on request`,h=r===`month`?n.monthlyPriceSecondaryDescription:n.yearlyPriceSecondaryDescription;" new_ph = "p=t===o.ENTERPRISE?`Active Lifetime License`:d?`a user per month`:`Quote on request`,h=t===o.ENTERPRISE?`All features unlocked`:r===`month`?n.monthlyPriceSecondaryDescription:n.yearlyPriceSecondaryDescription;" if old_ph in code: code = code.replace(old_ph, new_ph) old_btn = "(0,L.jsx)(T,{variant:`primary`,size:`lg`,onClick:()=>{let e=r??`year`,n=l[t][e]??`https://plane.so/talk-to-sales`;window.open(n,`_blank`)},className:`w-full`,children:d?`Upgrade to ${u}`:a(`common.upgrade_cta.talk_to_sales`)})" new_btn = "(0,L.jsx)(T,{variant:t===o.ENTERPRISE?`primary`:`secondary`,size:`lg`,disabled:!0,className:t===o.ENTERPRISE?`w-full opacity-95 cursor-default`:`w-full opacity-60 cursor-default`,children:t===o.ENTERPRISE?`Current Plan (Active)`:`Included in Enterprise`})" if old_btn in code: code = code.replace(old_btn, new_btn) with open(file_path, "w", encoding="utf-8") as f: f.write(code) print(f"Patched page successfully: {file_path} ({len(code)} bytes)") except Exception as e: print(f"Notice: Could not patch page {file_path}: {e}") def patch_layout(file_path): if not os.path.exists(file_path): return try: with open(file_path, "r", encoding="utf-8") as f: code = f.read() old_badge = 'onClick:()=>t(!0),"aria-haspopup":`dialog`,"aria-label":n(`aria_labels.projects_sidebar.edition_badge`),children:`Community`' new_badge = 'onClick:()=>{},"aria-label":n(`aria_labels.projects_sidebar.edition_badge`),children:`Enterprise`' if old_badge in code: code = code.replace(old_badge, new_badge) with open(file_path, "w", encoding="utf-8") as f: f.write(code) print(f"Patched layout successfully: {file_path} ({len(code)} bytes)") except Exception as e: print(f"Notice: Could not patch layout {file_path}: {e}") def patch_badge(file_path): try: # The upgrade badge returns null so no "Pro" badges render anywhere new_code = 'import{t as e}from"./jsx-runtime-noaePsda.js";var r=e();function i(e){return null;}export{i as t};\n' with open(file_path, "w", encoding="utf-8") as f: f.write(new_code) print(f"Patched badge successfully: {file_path} ({len(new_code)} bytes)") except Exception as e: print(f"Notice: Could not patch badge {file_path}: {e}") def main(): target_dir = sys.argv[1] if len(sys.argv) > 1 else os.path.dirname(os.path.abspath(__file__)) image = sys.argv[2] if len(sys.argv) > 2 else "makeplane/plane-frontend:stable" os.makedirs(target_dir, exist_ok=True) page_file = os.path.join(target_dir, "page-BEQX0L4U.js") layout_file = os.path.join(target_dir, "layout-C0Zt7niA.js") badge_file = os.path.join(target_dir, "upgrade-badge-BxAQzHXC.js") extract_or_fetch("page-BEQX0L4U.js", page_file, image) patch_page(page_file) extract_or_fetch("layout-C0Zt7niA.js", layout_file, image) patch_layout(layout_file) extract_or_fetch("upgrade-badge-BxAQzHXC.js", badge_file, image) patch_badge(badge_file) print("All frontend chunks successfully patched!") if __name__ == "__main__": main()