# Copyright 2026 Canonical Ltd.## This program is free software; you can redistribute it and/or# modify it under the terms of the GNU Lesser General Public# License version 3 as published by the Free Software Foundation.## This program is distributed in the hope that it will be useful,# but WITHOUT ANY WARRANTY; without even the implied warranty of# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU# Lesser General Public License for more details.## You should have received a copy of the GNU Lesser General Public License# along with this program. If not, see <http://www.gnu.org/licenses/>."""Utility functions for NPM plugin."""importjsonfromglobimportescapefrompathlibimportPathfromtypingimportAny,castdef_get_npm_basename(pkg_name:str)->str:# scoped packages eg. @scope/my-package# are packed as scope-my-package-version.tgzifpkg_name.startswith("@"):scope,name=pkg_name[1:].split("/",1)returnf"{scope}-{name}"returnpkg_name
[docs]deffind_tarballs(dependencies:dict[str,str],cache_dir:Path)->list[tuple[str,str,list[str]]]:"""Find tarballs in cache directory. Returns a list of (dependency, specified_version, available_versions) """found:list[tuple[str,str,list[str]]]=[]fordep,specified_versionindependencies.items():basename=_get_npm_basename(dep)ifnot(tarballs:=sorted(cache_dir.glob(f"{escape(basename)}-*.tgz"))):raiseRuntimeError(f"Error: could not resolve dependency '{dep} ({specified_version})'")available_versions=[t.name.removeprefix(f"{basename}-").removesuffix(".tgz")fortintarballs]found.append((basename,specified_version,available_versions))returnfound
[docs]defread_pkg(pkg_path:Path)->dict[str,Any]:"""Read and return contents of json file."""ifnotpkg_path.exists():raiseRuntimeError(f"Error: could not find '{pkg_path}'.")withpkg_path.open()asf:returncast(dict[str,Any],json.load(f))