refactor: detect and log duplicate package keys (#1744)
diff --git a/npm/private/npm_translate_lock_helpers.bzl b/npm/private/npm_translate_lock_helpers.bzl
index fe78ee7..d7a14cb 100644
--- a/npm/private/npm_translate_lock_helpers.bzl
+++ b/npm/private/npm_translate_lock_helpers.bzl
@@ -269,8 +269,8 @@
         importer_links[import_path] = links
 
     patches_used = []
-    result = []
-    for package, package_info in packages.items():
+    result = {}
+    for package_key, package_info in packages.items():
         name = package_info.get("name")
         version = package_info.get("version")
         friendly_version = package_info.get("friendly_version")
@@ -299,10 +299,10 @@
 
         if resolution_type == "git":
             if not repo or not commit:
-                msg = "expected package {} resolution to have repo and commit fields when resolution type is git".format(package)
+                msg = "expected package {} resolution to have repo and commit fields when resolution type is git".format(package_key)
                 fail(msg)
         elif not integrity and not tarball:
-            msg = "expected package {} resolution to have an integrity or tarball field but found none".format(package)
+            msg = "expected package {} resolution to have an integrity or tarball field but found none".format(package_key)
             fail(msg)
 
         if attr.prod and dev:
@@ -383,7 +383,7 @@
         link_packages = {}
         for import_path, links in importer_links.items():
             linked_packages = links["packages"]
-            link_names = linked_packages.get(package, [])
+            link_names = linked_packages.get(package_key, [])
             if link_names:
                 link_packages[links["link_package"]] = link_names
 
@@ -440,7 +440,7 @@
 
         npm_auth_bearer, npm_auth_basic, npm_auth_username, npm_auth_password = _select_npm_auth(url, npm_auth)
 
-        result.append(struct(
+        result_pkg = struct(
             custom_postinstall = custom_postinstall,
             deps = deps,
             integrity = integrity,
@@ -467,7 +467,15 @@
             package_info = package_info,
             dev = dev,
             replace_package = replace_package,
-        ))
+        )
+
+        if repo_name in result:
+            msg = "ERROR: duplicate package name: {}\n\t1: {}\n\t2: {}".format(repo_name, result[repo_name], result_pkg)
+            fail(msg)
+
+        result[repo_name] = result_pkg
+
+    result = result.values()
 
     # Check that all patches files specified were used; this is a defense-in-depth since it is too
     # easy to make a type in the patches keys or for a dep to change both of with could result
diff --git a/npm/private/transitive_closure.bzl b/npm/private/transitive_closure.bzl
index edb54c1..1524cbd 100644
--- a/npm/private/transitive_closure.bzl
+++ b/npm/private/transitive_closure.bzl
@@ -63,7 +63,7 @@
                 # Recurse into the next level of dependencies
                 stack.append(_get_package_info_deps(packages[package_key], no_optional))
             else:
-                msg = "Unknown package key: {} in {}".format(package_key, packages.keys())
+                msg = "Unknown package key: {} ({} @ {}) in {}".format(package_key, name, version, packages.keys())
                 fail(msg)
 
     result = dict()
@@ -155,12 +155,19 @@
     package_version_map = {}
 
     for package_path, package_snapshot in lock_packages.items():
-        package, package_info = _gather_package_info(package_path, package_snapshot)
-        packages[package] = package_info
+        package_key, package_info = _gather_package_info(package_path, package_snapshot)
+
+        if package_key in packages:
+            msg = "ERROR: duplicate package info: {}\n\t{}\n\t{}".format(package_key, packages[package_key], package_info)
+
+            # buildifier: disable=print
+            print(msg)
+
+        packages[package_key] = package_info
 
         # tarbal versions
         if package_info["resolution"].get("tarball", None) and package_info["resolution"]["tarball"].startswith("file:"):
-            package_version_map[package] = package_info
+            package_version_map[package_key] = package_info
 
     # Collect deps of each importer (workspace projects)
     importers = {}
diff --git a/npm/private/utils.bzl b/npm/private/utils.bzl
index f310c14..292ef41 100644
--- a/npm/private/utils.bzl
+++ b/npm/private/utils.bzl
@@ -161,7 +161,13 @@
                     dependencies[dep_name] = _convert_pnpm_v6_package_name(dep_version)
                 package_info[key] = dependencies
 
-        result[_convert_pnpm_v6_package_name(package)] = package_info
+        package_key = _convert_pnpm_v6_package_name(package)
+
+        if package_key in result:
+            msg = "ERROR: duplicate package: {}\n\t{}\n\t{}".format(package_key, result[package_key], package_info)
+            fail(msg)
+
+        result[package_key] = package_info
 
     return result
 
@@ -228,7 +234,13 @@
         for info_name, info_value in package_info.items():
             snapshot_info[info_name] = info_value
 
-        result[_convert_pnpm_v9_package_name(package)] = snapshot_info
+        package_key = _convert_pnpm_v9_package_name(package)
+
+        if package_key in result:
+            msg = "ERROR: duplicate package: {}\n\t{}\n\t{}".format(package_key, result[package_key], snapshot_info)
+            fail(msg)
+
+        result[package_key] = snapshot_info
 
     return result