jira/deploy/patches/patch_frontend.py
vickytechkey b0e37091d4
All checks were successful
Deploy Plane Project Tracker / Deploy to cisystem (push) Successful in 55s
fix(deploy): prevent frontend patch failure and pre-bundle patches
- Check local disk first in patch_frontend.py to avoid remote HTTP retrieval
- Use robust streaming with timeout and user-agent fallback instead of urlretrieve
- Include pre-patched frontend JS files in deploy/patches
- Add safe fallback in install.sh so optional patch generation does not fail deployment
2026-11-06 17:38:49 +05:30

133 lines
6.3 KiB
Python

#!/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()