-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbuild.py
More file actions
173 lines (143 loc) · 5.36 KB
/
build.py
File metadata and controls
173 lines (143 loc) · 5.36 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
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
#!/usr/bin/env python3
"""
HyperWheel build script.
Assembles hyperwheel.html from modular source files:
src/html/head.html — <head> with /* CSS_PLACEHOLDER */ marker
src/css/styles.css — all CSS (injected in place of placeholder)
src/html/body.html — <body> content (header, main, trade drawer)
src/js/01-*.js — JS modules concatenated in numeric order
src/html/modals.html — overlay modals appended after <script> block
Outputs:
hyperwheel.html — local dev copy
public/index.html — Vercel deployment artifact
Usage:
python3 build.py # build only
python3 build.py --check # build + JS syntax check
"""
import sys
import subprocess
import os
import glob
BASE = os.path.dirname(os.path.abspath(__file__))
def read(path):
with open(path) as f:
return f.read()
def _git(args, cwd):
try:
result = subprocess.run(
['git', *args], cwd=cwd, capture_output=True, text=True,
)
except (FileNotFoundError, OSError):
return None
if result.returncode != 0:
return None
return result.stdout.strip()
def _ls_remote_targets():
"""Remotes to query for tags, in priority order.
Vercel's ``origin`` points at an internal mirror that lags GitHub by a
few seconds when a deploy hook fires immediately after a tag push, so
prefer GitHub directly when env vars expose the repo slug.
"""
targets = []
owner = os.environ.get('VERCEL_GIT_REPO_OWNER')
repo = os.environ.get('VERCEL_GIT_REPO_SLUG')
if owner and repo:
targets.append(f'https://github.com/{owner}/{repo}.git')
targets.append('origin')
return targets
def _tag_at_head_via_ls_remote(cwd):
head = (
os.environ.get('VERCEL_GIT_COMMIT_SHA')
or _git(['rev-parse', 'HEAD'], cwd)
)
if not head:
return None
for target in _ls_remote_targets():
refs = _git(['ls-remote', '--tags', target], cwd)
if not refs:
continue
for line in refs.splitlines():
parts = line.split()
if len(parts) >= 2 and parts[0] == head:
name = parts[1].replace('refs/tags/', '').replace('^{}', '')
if name:
return name
return None
def resolve_version(cwd=BASE):
"""Return git-tag version string for the repo at *cwd*.
Order of preference:
1. Local exact-match tag at HEAD (``git describe --exact-match --tags``).
2. Remote tag at HEAD via ``git ls-remote`` — preferring GitHub directly
(via ``VERCEL_GIT_REPO_*`` env vars) over ``origin``, since Vercel's
mirrored origin can lag immediately after a tag push.
3. ``git describe --tags --always`` (describe-with-distance or short SHA).
4. ``unknown`` if git is unavailable or *cwd* is not a repo.
"""
tag = _git(['describe', '--exact-match', '--tags', 'HEAD'], cwd)
if tag:
return tag
tag = _tag_at_head_via_ls_remote(cwd)
if tag:
return tag
described = _git(['describe', '--tags', '--always'], cwd)
return described or 'unknown'
def build():
css = read(os.path.join(BASE, 'src', 'css', 'styles.css'))
head_tmpl = read(os.path.join(BASE, 'src', 'html', 'head.html'))
body_html = read(os.path.join(BASE, 'src', 'html', 'body.html'))
modals_html = read(os.path.join(BASE, 'src', 'html', 'modals.html'))
# Concatenate JS modules in numeric order
js_files = sorted(glob.glob(os.path.join(BASE, 'src', 'js', '*.js')))
if not js_files:
raise RuntimeError("No JS files found in src/js/")
js = '\n'.join(read(f) for f in js_files)
# Inject CSS
if '/* CSS_PLACEHOLDER */' not in head_tmpl:
raise RuntimeError("head.html is missing /* CSS_PLACEHOLDER */ marker")
head = head_tmpl.replace('/* CSS_PLACEHOLDER */', css.rstrip('\n'))
output = (
head
+ '<body>\n'
+ body_html
+ '<script>\n'
+ js
+ '\n</script>\n'
+ '\n'
+ modals_html
+ '</body>\n'
+ '</html>\n'
)
# Inject git-tag version. {{VERSION}} and {{VERSION_CLEAN}} are now
# identical (we no longer surface the -dirty suffix); kept as separate
# placeholders so existing template references still resolve.
version = resolve_version()
output = output.replace('{{VERSION_CLEAN}}', version)
output = output.replace('{{VERSION}}', version)
# Write local copy
local_path = os.path.join(BASE, 'hyperwheel.html')
with open(local_path, 'w') as f:
f.write(output)
print(f"Built {local_path} ({len(output.splitlines())} lines, {len(js_files)} JS modules)")
# Write Vercel output
public_dir = os.path.join(BASE, 'public')
os.makedirs(public_dir, exist_ok=True)
public_path = os.path.join(public_dir, 'index.html')
with open(public_path, 'w') as f:
f.write(output)
print(f"Wrote {public_path}")
return output, js
def syntax_check(js):
tmp = '/tmp/hyperwheel_check.js'
with open(tmp, 'w') as f:
f.write(js)
result = subprocess.run(['node', '--check', tmp], capture_output=True, text=True)
if result.returncode == 0:
print("JS syntax check: OK")
else:
print("JS syntax check: FAILED")
print(result.stderr)
sys.exit(1)
if __name__ == '__main__':
output, js = build()
if '--check' in sys.argv:
syntax_check(js)