Skip to content

Commit 5458ceb

Browse files
committed
Add release script
1 parent 035575f commit 5458ceb

1 file changed

Lines changed: 158 additions & 0 deletions

File tree

make_release.py

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
#!/usr/bin/env python3
2+
"""Create a release zip for InteractiveHtmlBom per user's steps.
3+
4+
Steps implemented:
5+
1. Determine current git tag; require repo clean and HEAD tagged.
6+
2. Verify LAST_TAG in InteractiveHtmlBom/version.py matches git tag.
7+
3. Create releases/<tag> folder.
8+
4. Create a tmp dir.
9+
5. Copy `resources` folder into tmp dir.
10+
6. Copy `InteractiveHtmlBom` into tmp/plugins,
11+
excluding __pycache__ and .ini files.
12+
7. Set `versions[0].version` in `releases/metadata.json` to tag
13+
without leading 'v'.
14+
8. Copy resulting metadata.json into tmp dir.
15+
9. Zip tmp dir contents to `InteractiveHtmlBom_{version}_pcm.zip`
16+
inside releases/<tag>.
17+
10.Zip plugin code into InteractiveHtmlBom.zip suitable for manual install.
18+
"""
19+
20+
from __future__ import annotations
21+
22+
import json
23+
import re
24+
import shutil
25+
import subprocess
26+
import sys
27+
import tempfile
28+
from pathlib import Path
29+
30+
31+
def run(cmd: list[str]) -> str:
32+
return subprocess.check_output(cmd, stderr=subprocess.STDOUT, text=True).strip()
33+
34+
35+
def get_head_tag() -> str:
36+
tags = run(["git", "tag", "--points-at", "HEAD"]).splitlines()
37+
if not tags:
38+
raise SystemExit("ERROR: current commit is not tagged. Aborting.")
39+
return tags[0]
40+
41+
42+
def repo_is_dirty() -> bool:
43+
status = run(["git", "status", "--porcelain"]).strip()
44+
return bool(status)
45+
46+
47+
def read_last_tag_from_version_file(path: Path) -> str | None:
48+
text = path.read_text(encoding="utf8")
49+
m = re.search(r"LAST_TAG\s*=\s*['\"]([^'\"]+)['\"]", text)
50+
return m.group(1) if m else None
51+
52+
53+
def update_metadata_version(metadata_path: Path, version: str) -> None:
54+
data = json.loads(metadata_path.read_text(encoding="utf8"))
55+
if "versions" not in data or not isinstance(data["versions"], list) or not data["versions"]:
56+
raise SystemExit(f"ERROR: {metadata_path} has no versions[0] entry")
57+
data["versions"][0]["version"] = version
58+
metadata_path.write_text(json.dumps(
59+
data, indent=4, ensure_ascii=False), encoding="utf8")
60+
61+
62+
def copy_interactive_html_bom(src: Path, dst: Path) -> None:
63+
# copytree with ignore patterns for __pycache__ directories and .ini files
64+
def _ignore(dir, names):
65+
ignored = set()
66+
for name in list(names):
67+
if name == "__pycache__" or name.endswith(".ini"):
68+
ignored.add(name)
69+
return ignored
70+
71+
shutil.copytree(src, dst, ignore=_ignore)
72+
73+
74+
def main() -> None:
75+
root = Path.cwd()
76+
77+
# 1. Ensure repo clean and HEAD is tagged
78+
if repo_is_dirty():
79+
raise SystemExit(
80+
"ERROR: repository has uncommitted changes (dirty). Commit or stash before releasing.")
81+
82+
tag = get_head_tag()
83+
print(f"Found tag: {tag}")
84+
85+
# 2. Verify LAST_TAG in InteractiveHtmlBom/version.py
86+
version_file = root / "InteractiveHtmlBom" / "version.py"
87+
if not version_file.exists():
88+
raise SystemExit(f"ERROR: {version_file} not found")
89+
last_tag = read_last_tag_from_version_file(version_file)
90+
if last_tag is None:
91+
raise SystemExit(f"ERROR: LAST_TAG not found in {version_file}")
92+
if last_tag != tag:
93+
raise SystemExit(
94+
f"ERROR: LAST_TAG ({last_tag}) does not match git tag ({tag})")
95+
print("LAST_TAG matches git tag.")
96+
97+
# 3. Create releases/<tag> folder
98+
releases_dir = root / "releases"
99+
version_dir = releases_dir / tag
100+
version_dir.mkdir(parents=True, exist_ok=True)
101+
print(f"Created/verified release folder: {version_dir}")
102+
103+
# 4-9 inside a temporary directory
104+
with tempfile.TemporaryDirectory() as tmp:
105+
tmp_path = Path(tmp)
106+
107+
# 5. Copy resources folder from repo root into tmp dir
108+
resources_src = root / "releases" / "resources"
109+
if not resources_src.exists() or not resources_src.is_dir():
110+
raise SystemExit(
111+
f"ERROR: resources folder not found at {resources_src}")
112+
shutil.copytree(resources_src, tmp_path / "resources")
113+
print("Copied resources into tmp dir.")
114+
115+
# 6. Copy InteractiveHtmlBom into tmp/plugins excluding patterns
116+
plugins_dir = tmp_path / "plugins"
117+
copy_interactive_html_bom(
118+
root / "InteractiveHtmlBom", plugins_dir)
119+
print("Copied InteractiveHtmlBom into tmp/plugins (excluded __pycache__ and .ini files)")
120+
121+
# 7. Set versions[0].version inside releases/metadata.json to package version without leading 'v'
122+
metadata_path = releases_dir / "metadata.json"
123+
if not metadata_path.exists():
124+
raise SystemExit(f"ERROR: {metadata_path} not found")
125+
package_version = tag.lstrip("v")
126+
update_metadata_version(metadata_path, package_version)
127+
print(
128+
f"Updated {metadata_path} versions[0].version to {package_version}")
129+
130+
# 8. Copy resulting metadata.json file into tmp dir
131+
shutil.copy2(metadata_path, tmp_path / "metadata.json")
132+
print("Copied metadata.json into tmp dir.")
133+
134+
# 9. Zip tmp dir contents into InteractiveHtmlBom-{version}-pcm.zip into the version folder
135+
zip_name = f"InteractiveHtmlBom_{tag}_pcm"
136+
archive_path = shutil.make_archive(
137+
str(version_dir / zip_name), 'zip', root_dir=tmp_path)
138+
print(f"Created archive: {archive_path}")
139+
140+
# 10. Create "normal" zip of just plugin code
141+
shutil.move(plugins_dir, tmp_path / "InteractiveHtmlBom")
142+
zip_name = f"InteractiveHtmlBom"
143+
archive_path = shutil.make_archive(
144+
str(version_dir / zip_name), 'zip', root_dir=tmp_path, base_dir="InteractiveHtmlBom")
145+
print(f"Created archive: {archive_path}")
146+
147+
print("Release package created successfully.")
148+
149+
150+
if __name__ == '__main__':
151+
try:
152+
main()
153+
except subprocess.CalledProcessError as e:
154+
print("ERROR: git command failed:\n", e.output, file=sys.stderr)
155+
sys.exit(2)
156+
except Exception as e:
157+
print(e, file=sys.stderr)
158+
sys.exit(1)

0 commit comments

Comments
 (0)