"""Optional: download pinned Three.js r180 locally, on a computer with internet. python tools/vendor_three.py Then upload the generated locenice/vendor folder AND data/app-config.json. Does not upload anything. No credentials or server-side installer required. """ from pathlib import Path from urllib.request import Request, urlopen import json, re, hashlib ROOT=Path(__file__).resolve().parent.parent BASE='https://raw.githubusercontent.com/mrdoob/three.js/r180/' VENDOR=ROOT/'vendor'/'three-r180' seen=set() def download(relative: str): if relative in seen:return if '..' in Path(relative).parts or relative.startswith('/'): raise ValueError('Unsafe dependency path: '+relative) seen.add(relative) req=Request(BASE+relative,headers={'User-Agent':'Locenice-vendor-setup/1.0'}) with urlopen(req,timeout=30) as response: data=response.read() destination=VENDOR/relative destination.parent.mkdir(parents=True,exist_ok=True) destination.write_bytes(data) if relative.endswith('.js'): code=data.decode('utf-8') for dep in re.findall(r"(?:from\s*|import\s*)['\"]([^'\"]+)['\"]",code): if not dep.startswith('.'):continue joined=(destination.parent/dep).resolve() if not joined.is_relative_to(VENDOR.resolve()):raise ValueError('Out of vendor root') download(joined.relative_to(VENDOR.resolve()).as_posix()) print(relative,hashlib.sha256(data).hexdigest()) if __name__=='__main__': for filename in ['build/three.module.js','examples/jsm/loaders/GLTFLoader.js','LICENSE']: download(filename) config_path=ROOT/'data'/'app-config.json' config=json.loads(config_path.read_text()) config['threeModule']='./vendor/three-r180/build/three.module.js' config['threeLoader']='./vendor/three-r180/examples/jsm/loaders/GLTFLoader.js' config_path.write_text(json.dumps(config,ensure_ascii=False,indent=2)) print('Done. Upload the vendor folder and app-config.json.')