192b42cb3SPatrick Williams# 292b42cb3SPatrick Williams# Copyright OpenEmbedded Contributors 392b42cb3SPatrick Williams# 492b42cb3SPatrick Williams# SPDX-License-Identifier: MIT 592b42cb3SPatrick Williams# 692b42cb3SPatrick Williams 7615f2f11SAndrew Geisslerimport bb 8615f2f11SAndrew Geisslerimport json 9615f2f11SAndrew Geisslerimport subprocess 10615f2f11SAndrew Geissler 11615f2f11SAndrew Geissler_ALWAYS_SAFE = frozenset('ABCDEFGHIJKLMNOPQRSTUVWXYZ' 12615f2f11SAndrew Geissler 'abcdefghijklmnopqrstuvwxyz' 13615f2f11SAndrew Geissler '0123456789' 14*520786ccSPatrick Williams '_.-~()') 15615f2f11SAndrew Geissler 16615f2f11SAndrew GeisslerMISSING_OK = object() 17615f2f11SAndrew Geissler 18615f2f11SAndrew GeisslerREGISTRY = "https://registry.npmjs.org" 19615f2f11SAndrew Geissler 20615f2f11SAndrew Geissler# we can not use urllib.parse here because npm expects lowercase 21615f2f11SAndrew Geissler# hex-chars but urllib generates uppercase ones 22615f2f11SAndrew Geisslerdef uri_quote(s, safe = '/'): 23615f2f11SAndrew Geissler res = "" 24615f2f11SAndrew Geissler safe_set = set(safe) 25615f2f11SAndrew Geissler for c in s: 26615f2f11SAndrew Geissler if c in _ALWAYS_SAFE or c in safe_set: 27615f2f11SAndrew Geissler res += c 28615f2f11SAndrew Geissler else: 29615f2f11SAndrew Geissler res += '%%%02x' % ord(c) 30615f2f11SAndrew Geissler return res 31615f2f11SAndrew Geissler 32615f2f11SAndrew Geisslerclass PackageJson: 33615f2f11SAndrew Geissler def __init__(self, spec): 34615f2f11SAndrew Geissler self.__spec = spec 35615f2f11SAndrew Geissler 36615f2f11SAndrew Geissler @property 37615f2f11SAndrew Geissler def name(self): 38615f2f11SAndrew Geissler return self.__spec['name'] 39615f2f11SAndrew Geissler 40615f2f11SAndrew Geissler @property 41615f2f11SAndrew Geissler def version(self): 42615f2f11SAndrew Geissler return self.__spec['version'] 43615f2f11SAndrew Geissler 44615f2f11SAndrew Geissler @property 45615f2f11SAndrew Geissler def empty_manifest(self): 46615f2f11SAndrew Geissler return { 47615f2f11SAndrew Geissler 'name': self.name, 48615f2f11SAndrew Geissler 'description': self.__spec.get('description', ''), 49615f2f11SAndrew Geissler 'versions': {}, 50615f2f11SAndrew Geissler } 51615f2f11SAndrew Geissler 52615f2f11SAndrew Geissler def base_filename(self): 53615f2f11SAndrew Geissler return uri_quote(self.name, safe = '@') 54615f2f11SAndrew Geissler 55615f2f11SAndrew Geissler def as_manifest_entry(self, tarball_uri): 56615f2f11SAndrew Geissler res = {} 57615f2f11SAndrew Geissler 58615f2f11SAndrew Geissler ## NOTE: 'npm install' requires more than basic meta information; 59615f2f11SAndrew Geissler ## e.g. it takes 'bin' from this manifest entry but not the actual 60615f2f11SAndrew Geissler ## 'package.json' 61615f2f11SAndrew Geissler for (idx,dflt) in [('name', None), 62615f2f11SAndrew Geissler ('description', ""), 63615f2f11SAndrew Geissler ('version', None), 64615f2f11SAndrew Geissler ('bin', MISSING_OK), 65615f2f11SAndrew Geissler ('man', MISSING_OK), 66615f2f11SAndrew Geissler ('scripts', MISSING_OK), 67615f2f11SAndrew Geissler ('directories', MISSING_OK), 68615f2f11SAndrew Geissler ('dependencies', MISSING_OK), 69615f2f11SAndrew Geissler ('devDependencies', MISSING_OK), 70615f2f11SAndrew Geissler ('optionalDependencies', MISSING_OK), 71615f2f11SAndrew Geissler ('license', "unknown")]: 72615f2f11SAndrew Geissler if idx in self.__spec: 73615f2f11SAndrew Geissler res[idx] = self.__spec[idx] 74615f2f11SAndrew Geissler elif dflt == MISSING_OK: 75615f2f11SAndrew Geissler pass 76615f2f11SAndrew Geissler elif dflt != None: 77615f2f11SAndrew Geissler res[idx] = dflt 78615f2f11SAndrew Geissler else: 79615f2f11SAndrew Geissler raise Exception("%s-%s: missing key %s" % (self.name, 80615f2f11SAndrew Geissler self.version, 81615f2f11SAndrew Geissler idx)) 82615f2f11SAndrew Geissler 83615f2f11SAndrew Geissler res['dist'] = { 84615f2f11SAndrew Geissler 'tarball': tarball_uri, 85615f2f11SAndrew Geissler } 86615f2f11SAndrew Geissler 87615f2f11SAndrew Geissler return res 88615f2f11SAndrew Geissler 89615f2f11SAndrew Geisslerclass ManifestImpl: 90615f2f11SAndrew Geissler def __init__(self, base_fname, spec): 91615f2f11SAndrew Geissler self.__base = base_fname 92615f2f11SAndrew Geissler self.__spec = spec 93615f2f11SAndrew Geissler 94615f2f11SAndrew Geissler def load(self): 95615f2f11SAndrew Geissler try: 96615f2f11SAndrew Geissler with open(self.filename, "r") as f: 97615f2f11SAndrew Geissler res = json.load(f) 98615f2f11SAndrew Geissler except IOError: 99615f2f11SAndrew Geissler res = self.__spec.empty_manifest 100615f2f11SAndrew Geissler 101615f2f11SAndrew Geissler return res 102615f2f11SAndrew Geissler 103615f2f11SAndrew Geissler def save(self, meta): 104615f2f11SAndrew Geissler with open(self.filename, "w") as f: 105615f2f11SAndrew Geissler json.dump(meta, f, indent = 2) 106615f2f11SAndrew Geissler 107615f2f11SAndrew Geissler @property 108615f2f11SAndrew Geissler def filename(self): 109615f2f11SAndrew Geissler return self.__base + ".meta" 110615f2f11SAndrew Geissler 111615f2f11SAndrew Geisslerclass Manifest: 112615f2f11SAndrew Geissler def __init__(self, base_fname, spec): 113615f2f11SAndrew Geissler self.__base = base_fname 114615f2f11SAndrew Geissler self.__spec = spec 115615f2f11SAndrew Geissler self.__lockf = None 116615f2f11SAndrew Geissler self.__impl = None 117615f2f11SAndrew Geissler 118615f2f11SAndrew Geissler def __enter__(self): 119615f2f11SAndrew Geissler self.__lockf = bb.utils.lockfile(self.__base + ".lock") 120615f2f11SAndrew Geissler self.__impl = ManifestImpl(self.__base, self.__spec) 121615f2f11SAndrew Geissler return self.__impl 122615f2f11SAndrew Geissler 123615f2f11SAndrew Geissler def __exit__(self, exc_type, exc_val, exc_tb): 124615f2f11SAndrew Geissler bb.utils.unlockfile(self.__lockf) 125615f2f11SAndrew Geissler 126615f2f11SAndrew Geisslerclass NpmCache: 127615f2f11SAndrew Geissler def __init__(self, cache): 128615f2f11SAndrew Geissler self.__cache = cache 129615f2f11SAndrew Geissler 130615f2f11SAndrew Geissler @property 131615f2f11SAndrew Geissler def path(self): 132615f2f11SAndrew Geissler return self.__cache 133615f2f11SAndrew Geissler 134615f2f11SAndrew Geissler def run(self, type, key, fname): 135615f2f11SAndrew Geissler subprocess.run(['oe-npm-cache', self.__cache, type, key, fname], 136615f2f11SAndrew Geissler check = True) 137615f2f11SAndrew Geissler 138615f2f11SAndrew Geisslerclass NpmRegistry: 139615f2f11SAndrew Geissler def __init__(self, path, cache): 140615f2f11SAndrew Geissler self.__path = path 141615f2f11SAndrew Geissler self.__cache = NpmCache(cache + '/_cacache') 142615f2f11SAndrew Geissler bb.utils.mkdirhier(self.__path) 143615f2f11SAndrew Geissler bb.utils.mkdirhier(self.__cache.path) 144615f2f11SAndrew Geissler 145615f2f11SAndrew Geissler @staticmethod 146615f2f11SAndrew Geissler ## This function is critical and must match nodejs expectations 147615f2f11SAndrew Geissler def _meta_uri(spec): 148615f2f11SAndrew Geissler return REGISTRY + '/' + uri_quote(spec.name, safe = '@') 149615f2f11SAndrew Geissler 150615f2f11SAndrew Geissler @staticmethod 151615f2f11SAndrew Geissler ## Exact return value does not matter; just make it look like a 152615f2f11SAndrew Geissler ## usual registry url 153615f2f11SAndrew Geissler def _tarball_uri(spec): 154615f2f11SAndrew Geissler return '%s/%s/-/%s-%s.tgz' % (REGISTRY, 155615f2f11SAndrew Geissler uri_quote(spec.name, safe = '@'), 156615f2f11SAndrew Geissler uri_quote(spec.name, safe = '@/'), 157615f2f11SAndrew Geissler spec.version) 158615f2f11SAndrew Geissler 159615f2f11SAndrew Geissler def add_pkg(self, tarball, pkg_json): 160615f2f11SAndrew Geissler pkg_json = PackageJson(pkg_json) 161615f2f11SAndrew Geissler base = os.path.join(self.__path, pkg_json.base_filename()) 162615f2f11SAndrew Geissler 163615f2f11SAndrew Geissler with Manifest(base, pkg_json) as manifest: 164615f2f11SAndrew Geissler meta = manifest.load() 165615f2f11SAndrew Geissler tarball_uri = self._tarball_uri(pkg_json) 166615f2f11SAndrew Geissler 167615f2f11SAndrew Geissler meta['versions'][pkg_json.version] = pkg_json.as_manifest_entry(tarball_uri) 168615f2f11SAndrew Geissler 169615f2f11SAndrew Geissler manifest.save(meta) 170615f2f11SAndrew Geissler 171615f2f11SAndrew Geissler ## Cache entries are a little bit dependent on the nodejs 172615f2f11SAndrew Geissler ## version; version specific cache implementation must 173615f2f11SAndrew Geissler ## mitigate differences 174615f2f11SAndrew Geissler self.__cache.run('meta', self._meta_uri(pkg_json), manifest.filename); 175615f2f11SAndrew Geissler self.__cache.run('tgz', tarball_uri, tarball); 176