#!/usr/bin/env python3 import requests import json import time from pprint import pprint from deepdiff import DeepDiff from dataclasses import dataclass @dataclass class Manifest: name: str version: str dependencies: dict scripts: dict @dataclass class Package: name: str reported_manifest: Manifest actual_manifest: Manifest def __init__(self, name: str): self.name = name self.reported_manifest = parse_manifest(name) self.actual_manifest = parse_actual_manifest(name, self.reported_manifest.version) # https://www.npmjs.com/package/darcyclarke-manifest-pkg/v/2.1.15/index # hex checksum = file name # use hex to get *actual* manifest: # https://www.npmjs.com/package/darcyclarke-manifest-pkg/file/a1c6250cb3f94bb3487c1bfb673d279642208b5db39a6c052a5c764f0d1abea5 def parse_manifest(pkg): # get and parse the manifest which contains the values reported on the frontend url = 'https://registry.npmjs.com/{}/'.format(pkg) parsed = json.loads(requests.get(url).text) # extract the interesting bits try: latest_ver = parsed['dist-tags']['latest'] except KeyError: print(f'Failed to find latest version for {pkg} - might have been unpublished?') return None, None, None, None latest_manifest = parsed['versions'][latest_ver] try: dependencies = latest_manifest['dependencies'] except KeyError: dependencies = None try: scripts = latest_manifest['scripts'] except KeyError: scripts = json.loads('{}') name = latest_manifest['name'] return Manifest(name, latest_ver, dependencies, scripts) def parse_actual_manifest(pkg, ver): # get and parse the manifest as it would be installed # first, we need to find the package.json delivered with the package: index_url = 'https://www.npmjs.com/package/{}/v/{}/index'.format(pkg, ver) while True: try: index = json.loads(requests.get(index_url).text) break except json.decoder.JSONDecodeError: print('Failed to get index from webservice, retrying...') time.sleep(5) hexsum = index['files']['/package.json']['hex'] manifest_url = 'https://www.npmjs.com/package/{}/file/{}'.format(pkg, hexsum) # Sometimes the webservice seems to respond with an empty json - so this kludge got made: while True: try: manifest = json.loads(requests.get(manifest_url).text) break except json.decoder.JSONDecodeError: print('Failed getting manifest JSON from webserver, retrying...') time.sleep(5) # now we can parse it version = manifest['version'] try: dependencies = manifest['dependencies'] except KeyError: dependencies = None try: scripts = manifest['scripts'] except KeyError: scripts = json.loads('{}') name = manifest['name'] return Manifest(name, version, dependencies, scripts) def compare_manifests(pkg): mismatch = False if pkg.reported_manifest.version != pkg.actual_manifest.version: mismatch = True print('Version mismatch for {}!'.format(pkg.name)) print('Reported version: {}'.format(pkg.reported_manifest.version)) print('Actual version: {}'.format(pkg.actual_manifest.version)) if pkg.actual_manifest.dependencies != pkg.reported_manifest.dependencies: mismatch = True dep_diff = DeepDiff(pkg.reported_manifest.dependencies, pkg.actual_manifest.dependencies, verbose_level=2) print('Dependency mismatch detected for {}!'.format(pkg.name)) pprint(dep_diff, indent=2) if pkg.actual_manifest.scripts != pkg.reported_manifest.scripts: mismatch = True scripts_diff = DeepDiff(pkg.reported_manifest.scripts, pkg.actual_manifest.scripts, verbose_level=2) print('Scripts mismatch detected for {}!'.format(pkg.name)) pprint(scripts_diff, indent=2) if pkg.actual_manifest.name != pkg.reported_manifest.name: mismatch = True print('Name mismatch detected for {}!'.format(pkg.name)) print('Reported name: {}'.format(pkg.reported_manifest.name)) print('Actual name: {}'.format(pkg.actual_manifest.name)) if not mismatch: print('No mismatch detected for {}.'.format(pkg.name)) return mismatch def main(): import sys package_name = sys.argv[1] package = Package(package_name) mismatching = compare_manifests(package) if mismatching: sys.exit(1) if __name__ == '__main__': main()