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