Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/src/scripts/check_npm_packages.py
Views: 687
#!/usr/bin/env python31"""2Consistency check for npm packages across node modules34Hint: to get a "real time" info while working on resolving this, run5$ /usr/bin/watch -n1 check_npm_packages.py6in the COCALC_ROOT dir in a separate terminal.7"""89import os10from os.path import abspath, dirname, basename11import json12from collections import defaultdict13from pprint import pprint14from subprocess import run, PIPE15from typing import List, Set, Dict, Tuple, Optional16from typing_extensions import Final1718T_installs = Dict[str, Dict[str, str]]1920root: Final[str] = os.environ.get('COCALC_ROOT', abspath(os.curdir))2122# these packages are known to be inconsistent on purpose23# async and immutable are a little bit more modern in packages/frontend,24# while they are behind elsewhere (but at the same vesion)25# we don't want to introduce any other inconsistencies...2627# whitelisting typescript is temporary really just for @cocalc/util -- see https://github.com/sagemathinc/cocalc/issues/58882829whitelist: Final[List[str]] = [30'async', # we really want to get rid of using this at all! And we have to use two very different versions across our packages :-(31'entities', # it breaks when you upgrade in static to 4.x32]333435# NOTE: test/puppeteer is long dead...36def pkg_dirs() -> List[str]:37search = run(['git', 'ls-files', '--', '../**package.json'], stdout=PIPE)38data = search.stdout.decode('utf8')39packages = [40abspath(x) for x in data.splitlines() if 'test/puppeteer/' not in x41]42return packages434445def get_versions(packages) -> Tuple[T_installs, Set[str]]:46installs: T_installs = defaultdict(dict)47modules: Set[str] = set()4849for pkg in packages:50for dep_type in ['dependencies', 'devDependencies']:51pkgs = json.load(open(pkg))52module = basename(dirname(pkg))53modules.add(module)54for name, vers in pkgs.get(dep_type, {}).items():55assert installs[name].get(module) is None, \56f"{name}/{module} already exists as a dependency – don't add it as a devDepedency as well"57# don't worry about the patch version58installs[name][module] = vers[:vers.rfind('.')]59return installs, modules606162def print_table(installs: T_installs, modules) -> Tuple[str, int, List[str]]:63cnt = 064incon = [] # new, not whitelisted inconsistencies65table = ""6667table += f"{'':<30s}"68for mod in sorted(modules):69table += f"{mod:<15s}"70table += "\n"7172for pkg, inst in sorted(installs.items()):73if len(set(inst.values())) == 1: continue74cnt += 175if pkg not in whitelist and not pkg.startswith('@cocalc'):76incon.append(pkg)77table += f"{pkg:<30s}"78for mod in sorted(modules):79vers = inst.get(mod, '')80table += f"{vers:<15s}"81table += "\n"82return table, cnt, incon838485def main() -> None:86packages: Final = pkg_dirs()8788# We mix up dependencies and devDepdencies into one. Otherwise cross-inconsistencies do not show up.89# Also, get_versions fails if there is the same module as a dependencies AND devDependencies in the same package.90main_pkgs, main_mods = get_versions(packages)9192table, cnt, incon = print_table(main_pkgs, main_mods)9394if cnt < len(whitelist):95msg = f"EVERY whitelisted package *must* have inconsistent versions, or you just badly broke something in one of these packages!: {', '.join(whitelist)}"96print(msg)97raise RuntimeError(msg)9899elif cnt > len(whitelist):100print(table)101print(f"\nThere are {cnt} inconsistencies")102if len(incon) > 0:103print(104f"of which these are not whitelisted: {incon} -- they must be fixed"105)106raise RuntimeError(107f"fix new package version inconsistencies of {incon}\n\n\n")108else:109if cnt > 0:110print(f"All are whitelisted and no action is warranted.")111112else:113print("SUCCESS: All package.json consistency checks passed.")114115116if __name__ == '__main__':117main()118119120