npm_lockfile.py 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  1. import base64
  2. import json
  3. import os
  4. import io
  5. from six.moves.urllib import parse as urlparse
  6. from six import iteritems
  7. from ..base import BaseLockfile, LockfilePackageMeta, LockfilePackageMetaInvalidError
  8. LOCKFILE_VERSION_FIELD = "lockfileVersion"
  9. class NpmLockfile(BaseLockfile):
  10. def read(self):
  11. with io.open(self.path, "rb") as f:
  12. self.data = json.load(f) or {LOCKFILE_VERSION_FIELD: 3}
  13. lockfile_version = self.data.get(LOCKFILE_VERSION_FIELD, "<no-version>")
  14. if lockfile_version != 3:
  15. raise Exception(
  16. f'Error of project configuration: {self.path} has lockfileVersion: {lockfile_version}. '
  17. + f'This version is not supported. Please, delete {os.path.basename(self.path)} and regenerate it using "ya tool nots --clean install --lockfile-only --npm"'
  18. )
  19. def write(self, path=None):
  20. """
  21. :param path: path to store lockfile, defaults to original path
  22. :type path: str
  23. """
  24. if path is None:
  25. path = self.path
  26. with open(path, "w") as f:
  27. json.dump(self.data, f, indent=2)
  28. def get_packages_meta(self):
  29. """
  30. Extracts packages meta from lockfile.
  31. :rtype: list of LockfilePackageMeta
  32. """
  33. packages = self.data.get("packages", {})
  34. for key, meta in packages.items():
  35. if self._should_skip_package(key, meta):
  36. continue
  37. yield _parse_package_meta(key, meta)
  38. def update_tarball_resolutions(self, fn):
  39. """
  40. :param fn: maps `LockfilePackageMeta` instance to new `resolution.tarball` value
  41. :type fn: lambda
  42. """
  43. packages = self.data.get("packages", {})
  44. for key, meta in iteritems(packages):
  45. if self._should_skip_package(key, meta):
  46. continue
  47. meta["resolved"] = fn(_parse_package_meta(key, meta, allow_file_protocol=True))
  48. packages[key] = meta
  49. def get_requires_build_packages(self):
  50. raise NotImplementedError()
  51. def validate_has_addons_flags(self):
  52. raise NotImplementedError()
  53. def _should_skip_package(self, key, meta):
  54. return not key or key.startswith(".") or meta.get("link", None) is True
  55. def _parse_package_meta(key, meta, allow_file_protocol=False):
  56. """
  57. :param key: uniq package key from lockfile
  58. :type key: string
  59. :param meta: package meta dict from lockfile
  60. :type meta: dict
  61. :rtype: LockfilePackageMetaInvalidError
  62. """
  63. try:
  64. tarball_url = _parse_tarball_url(meta["resolved"], allow_file_protocol)
  65. sky_id = _parse_sky_id_from_tarball_url(meta["resolved"])
  66. integrity_algorithm, integrity = _parse_package_integrity(meta["integrity"])
  67. except KeyError as e:
  68. raise TypeError("Invalid package meta for key '{}', missing '{}' key".format(key, e))
  69. except LockfilePackageMetaInvalidError as e:
  70. raise TypeError("Invalid package meta for key '{}', parse error: '{}'".format(key, e))
  71. return LockfilePackageMeta(key, tarball_url, sky_id, integrity, integrity_algorithm)
  72. def _parse_tarball_url(tarball_url, allow_file_protocol):
  73. if tarball_url.startswith("file:") and not allow_file_protocol:
  74. raise LockfilePackageMetaInvalidError("tarball cannot point to a file, got {}".format(tarball_url))
  75. return tarball_url.split("?")[0]
  76. def _parse_sky_id_from_tarball_url(tarball_url):
  77. """
  78. :param tarball_url: tarball url
  79. :type tarball_url: string
  80. :rtype: string
  81. """
  82. if tarball_url.startswith("file:"):
  83. return ""
  84. rbtorrent_param = urlparse.parse_qs(urlparse.urlparse(tarball_url).query).get("rbtorrent")
  85. if rbtorrent_param is None:
  86. return ""
  87. return "rbtorrent:{}".format(rbtorrent_param[0])
  88. def _parse_package_integrity(integrity):
  89. """
  90. Returns tuple of algorithm and hash (hex).
  91. :param integrity: package integrity in format "{algo}-{base64_of_hash}"
  92. :type integrity: string
  93. :rtype: (str, str)
  94. """
  95. algo, hash_b64 = integrity.split("-", 1)
  96. if algo not in ("sha1", "sha512"):
  97. raise LockfilePackageMetaInvalidError(
  98. f"Invalid package integrity algorithm, expected one of ('sha1', 'sha512'), got '{algo}'"
  99. )
  100. try:
  101. base64.b64decode(hash_b64)
  102. except TypeError as e:
  103. raise LockfilePackageMetaInvalidError(
  104. "Invalid package integrity encoding, integrity: {}, error: {}".format(integrity, e)
  105. )
  106. return (algo, hash_b64)