Files
mayan-edms/mayan/apps/dependencies/javascript.py
Roberto Rosario 4ff9794286 Update and move JavaScript install code
Update the JavaScript dependency installation code to handle scoped
packages. The code is also updated to use pathlib's Path.

Move the JavaScript dependency installation to its own app named
dependencies.

Signed-off-by: Roberto Rosario <Roberto.Rosario@mayan-edms.com>
2018-12-26 02:15:44 -04:00

189 lines
6.1 KiB
Python

from __future__ import print_function, unicode_literals
import base64
import hashlib
import json
import shutil
import tarfile
from furl import furl
from pathlib2 import Path
import requests
from semver import max_satisfying
from django.apps import apps
from django.utils.encoding import force_bytes, force_text
from django.utils.functional import cached_property
from mayan.apps.common.utils import mkdtemp
from .exceptions import DependenciesException
DEFAULT_REGISTRY_URL = 'http://registry.npmjs.com'
DEFAULT_MODULE_DIRECTORY = 'node_modules'
DEFAULT_PACKAGE_FILENAME = 'package.json'
DEFAULT_LOCK_FILENAME = 'package-lock.json'
class NPMPackage(object):
def __init__(self, registry, name, version):
self.registry = registry
self.name = name
self.version = version
def download(self):
algorithm_function = self.get_algorithm_function()
tar_file_path = self.get_tar_file_path()
with requests.get(self.version_metadata['dist']['tarball'], stream=True) as response:
with tar_file_path.open(mode='wb') as file_object:
shutil.copyfileobj(response.raw, file_object)
with tar_file_path.open(mode='rb') as file_object:
integrity_is_good = algorithm_function(file_object.read())
if not integrity_is_good:
tar_file_path.unlink()
raise DependenciesException(
'Hash of downloaded package doesn\'t match online version.'
)
def extract(self):
shutil.rmtree(
path=force_text(
Path(
self.registry.module_directory, self.name
)
), ignore_errors=True
)
with tarfile.open(name=force_text(self.get_tar_file_path()), mode='r') as file_object:
file_object.extractall(
path=force_text(self.registry.module_directory)
)
Path(self.registry.module_directory, 'package').rename(
target=Path(self.registry.module_directory, self.name)
)
def get_algorithm_function(self):
try:
integrity = self.version_metadata['dist']['integrity']
except KeyError:
algorithm_name = 'sha1'
integrity_value = self.version_metadata['dist']['shasum']
else:
algorithm_name, integrity_value = integrity.split('-', 1)
algorithms = {
'sha1': lambda data: hashlib.sha1(data).hexdigest() == integrity_value,
'sha256': lambda data: base64.b64encode(hashlib.sha256(data).digest()) == integrity_value,
'sha512': lambda data: base64.b64encode(hashlib.sha512(data).digest()) == integrity_value,
}
try:
algorithm = algorithms[algorithm_name]
except KeyError:
raise DependenciesException(
'Unknown hash algorithm: {}'.format(algorithm_name)
)
else:
return algorithm
def get_best_version(self):
return max_satisfying(
self.versions, force_bytes(self.version), loose=True
)
def get_tar_file_path(self):
return Path(
self.registry.cache_path, self.get_tar_filename()
)
def get_tar_filename(self):
return furl(
self.version_metadata['dist']['tarball']
).path.segments[-1]
def install(self, include_dependencies=False):
print('Installing package: {}{}'.format(self.name, self.version))
self.download()
self.extract()
if include_dependencies:
for name, version in self.version_metadata.get('dependencies', {}).items():
package = NPMPackage(
registry=self.registry, name=name, version=version
)
package.install()
@cached_property
def metadata(self):
response = requests.get(url=self.url)
return response.json()
@property
def url(self):
f = furl(self.registry.url)
f.path.segments = f.path.segments + [self.name]
return f.tostr()
@property
def version_metadata(self):
return self.metadata['versions'][self.get_best_version()]
@property
def versions(self):
return [
force_bytes(version) for version in self.metadata['versions'].keys()
]
class NPMRegistry(object):
def __init__(self, url=None, cache_path=None, module_directory=None, package_filename=None, lock_filename=None):
self.url = url or self.DEFAULT_REGISTRY_URL
self.cache_path = cache_path or mkdtemp()
self.module_directory = module_directory or self.DEFAULT_MODULE_DIRECTORY
self.package_file = package_filename or self.DEFAULT_PACKAGE_FILENAME
self.lock_filename = lock_filename or self.DEFAULT_LOCK_FILENAME
def _install_package(self, name, version):
package = NPMPackage(registry=self, name=name, version=version)
package.install()
def _read_package(self):
with self.package_file.open(mode='rb') as file_object:
self._package_data = json.loads(file_object.read())
def install(self, package=None):
if package:
name, version = package.split('@')
self._install_package(name=name, version=version)
else:
self._read_package()
for name, version in self._package_data['dependencies'].items():
self._install_package(name=name, version=version)
class JSDependencyManager(object):
def install(self, app_name=None):
if app_name:
app_config_list = [apps.get_app_config(app_label=app_name)]
else:
app_config_list = apps.get_app_configs()
for app in app_config_list:
path = Path(app.path, 'static')
entries = list(path.glob('*/package.json'))
if entries:
print('Installing JavaScript packages for app: {}'.format(app.label))
for entry in entries:
npm_client = NPMRegistry(
module_directory=entry.parent / 'node_modules',
package_filename=entry
)
npm_client.install()