1"""
2Biome standalone binary management for plain-code.
3"""
4
5import os
6import platform
7import subprocess
8
9import click
10import requests
11import tomlkit
12
13from plain.internal import internalcode
14from plain.runtime import PLAIN_TEMP_PATH
15
16
17@internalcode
18class Biome:
19 """Download, install, and invoke the Biome CLI standalone binary."""
20
21 TAG_PREFIX = "@biomejs/biome@"
22
23 @property
24 def target_directory(self) -> str:
25 # Directory under .plain to store the binary and lockfile
26 return str(PLAIN_TEMP_PATH)
27
28 @property
29 def standalone_path(self) -> str:
30 # On Windows, use .exe suffix
31 exe = ".exe" if platform.system() == "Windows" else ""
32 return os.path.join(self.target_directory, f"biome{exe}")
33
34 @property
35 def version_lockfile_path(self) -> str:
36 return os.path.join(self.target_directory, "biome.version")
37
38 def is_installed(self) -> bool:
39 td = self.target_directory
40 if not os.path.isdir(td):
41 os.makedirs(td, exist_ok=True)
42 return os.path.exists(self.standalone_path)
43
44 def needs_update(self) -> bool:
45 if not self.is_installed():
46 return True
47 if not os.path.exists(self.version_lockfile_path):
48 return True
49 with open(self.version_lockfile_path) as f:
50 locked = f.read().strip()
51 return locked != self.get_version_from_config()
52
53 def get_version_from_config(self) -> str:
54 # Read version from pyproject.toml under tool.plain.code.biome
55 project_root = os.path.dirname(self.target_directory)
56 pyproject = os.path.join(project_root, "pyproject.toml")
57 if not os.path.exists(pyproject):
58 return ""
59 doc = tomlkit.loads(open(pyproject, "rb").read().decode())
60 return (
61 doc.get("tool", {})
62 .get("plain", {})
63 .get("code", {})
64 .get("biome", {})
65 .get("version", "")
66 )
67
68 def set_version_in_config(self, version: str) -> None:
69 # Persist version to pyproject.toml under tool.plain.code.biome
70 project_root = os.path.dirname(self.target_directory)
71 pyproject = os.path.join(project_root, "pyproject.toml")
72 if not os.path.exists(pyproject):
73 return
74 doc = tomlkit.loads(open(pyproject, "rb").read().decode())
75 doc.setdefault("tool", {}).setdefault("plain", {}).setdefault(
76 "code", {}
77 ).setdefault("biome", {})["version"] = version
78 open(pyproject, "w").write(tomlkit.dumps(doc))
79
80 def detect_platform_slug(self) -> str:
81 # Determine the asset slug for the current OS/arch
82 system = platform.system()
83 arch = platform.machine()
84 if system == "Windows":
85 # use win32 glibc build
86 return "win32-arm64.exe" if arch.lower() == "arm64" else "win32-x64.exe"
87 if system == "Linux":
88 # prefer glibc builds
89 return "linux-arm64" if arch == "aarch64" else "linux-x64"
90 if system == "Darwin":
91 return "darwin-arm64" if arch == "arm64" else "darwin-x64"
92 raise RuntimeError(f"Unsupported platform for Biome: {system}/{arch}")
93
94 def download(self, version: str = "") -> str:
95 # Build download URL based on version (tag: cli/vX.Y.Z) or latest
96 slug = self.detect_platform_slug()
97 if version:
98 url = (
99 f"https://github.com/biomejs/biome/releases/download/{self.TAG_PREFIX}{version}/"
100 f"biome-{slug}"
101 )
102 else:
103 url = (
104 f"https://github.com/biomejs/biome/releases/latest/download/"
105 f"biome-{slug}"
106 )
107
108 resp = requests.get(url, stream=True)
109 resp.raise_for_status()
110
111 # Make sure the target directory exists
112 td = self.target_directory
113 if not os.path.isdir(td):
114 os.makedirs(td, exist_ok=True)
115
116 total = int(resp.headers.get("Content-Length", 0))
117 with open(self.standalone_path, "wb") as f:
118 if total:
119 with click.progressbar(
120 length=total,
121 label="Downloading Biome",
122 width=0,
123 ) as bar:
124 for chunk in resp.iter_content(chunk_size=8192):
125 f.write(chunk)
126 bar.update(len(chunk))
127 else:
128 for chunk in resp.iter_content(chunk_size=8192):
129 f.write(chunk)
130 os.chmod(self.standalone_path, 0o755)
131
132 # Determine resolved version for lockfile
133 if version:
134 resolved = version.lstrip("v")
135 else:
136 resolved = ""
137 if resp.history:
138 # Look for redirect to actual tag version
139 loc = resp.history[0].headers.get("Location", "")
140 if self.TAG_PREFIX in loc:
141 remaining = loc.split(self.TAG_PREFIX, 1)[-1]
142 resolved = remaining.split("/")[0]
143
144 if not resolved:
145 raise RuntimeError("Failed to determine resolved version from redirect")
146
147 open(self.version_lockfile_path, "w").write(resolved)
148
149 return resolved
150
151 def install(self, version: str = "") -> str:
152 v = self.download(version)
153 self.set_version_in_config(v)
154 return v
155
156 def invoke(self, *args, cwd=None) -> subprocess.CompletedProcess:
157 # Run the standalone biome binary with given args
158 config_path = os.path.abspath(
159 os.path.join(os.path.dirname(__file__), "biome_defaults.json")
160 )
161 args = list(args) + ["--config-path", config_path, "--vcs-root", os.getcwd()]
162 return subprocess.run([self.standalone_path, *args], cwd=cwd)