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