fix: sanitize all invalide chars when generating npm repository names (#532)

diff --git a/npm/private/test/BUILD.bazel b/npm/private/test/BUILD.bazel
index eb31104..165b161 100644
--- a/npm/private/test/BUILD.bazel
+++ b/npm/private/test/BUILD.bazel
@@ -55,6 +55,7 @@
         ":node_modules/typescript",
         ":node_modules/inline-fixtures",
         ":node_modules/json-stable-stringify",
+        ":node_modules/@blockprotocol/type-system-web",
         # intentionally don't include node_modules/unused
     ],
 )
diff --git a/npm/private/test/defs_checked.bzl b/npm/private/test/defs_checked.bzl
index 90ce65a..3d4d7fb 100755
--- a/npm/private/test/defs_checked.bzl
+++ b/npm/private/test/defs_checked.bzl
@@ -705,21 +705,22 @@
 load("@npm__yargs__16.2.0__links//:defs.bzl", store_698 = "npm_imported_package_store")
 load("@npm__yocto-queue__0.1.0__links//:defs.bzl", store_699 = "npm_imported_package_store")
 load("@npm__at_foo_jsonify__at_github.com_aspect-build_test-packages_releases_download_0.0.0_at_foo-jsonify-0.0.0.tgz__links//:defs.bzl", store_700 = "npm_imported_package_store")
-load("@npm__inline-fixtures__at_registry.yarnpkg.com_inline-fixtures_-_inline-fixtures-1.1.0.tgz_5f9edf644ac02a1322012dbbb9f0a4739831d662__links//:defs.bzl", link_701 = "npm_link_imported_package_store", store_701 = "npm_imported_package_store")
-load("@npm__debug__github.com_ngokevin_debug_9742c5f383a6f8046241920156236ade8ec30d53__links//:defs.bzl", link_702 = "npm_link_imported_package_store", store_702 = "npm_imported_package_store")
-load("@npm__at_types_cacheable-request__registry.npmjs.org_at_types_cacheable-request_6.0.2__links//:defs.bzl", store_703 = "npm_imported_package_store")
-load("@npm__at_types_eslint-scope__registry.npmjs.org_at_types_eslint-scope_3.7.3__links//:defs.bzl", store_704 = "npm_imported_package_store")
-load("@npm__at_types_eslint__registry.npmjs.org_at_types_eslint_8.4.3__links//:defs.bzl", store_705 = "npm_imported_package_store")
-load("@npm__at_types_estree__registry.npmjs.org_at_types_estree_0.0.39__links//:defs.bzl", store_706 = "npm_imported_package_store")
-load("@npm__at_types_estree__registry.npmjs.org_at_types_estree_0.0.51__links//:defs.bzl", store_707 = "npm_imported_package_store")
-load("@npm__at_types_http-cache-semantics__registry.npmjs.org_at_types_http-cache-semantics_4.0.1__links//:defs.bzl", store_708 = "npm_imported_package_store")
-load("@npm__at_types_istanbul-lib-coverage__registry.npmjs.org_at_types_istanbul-lib-coverage_2.0.4__links//:defs.bzl", store_709 = "npm_imported_package_store")
-load("@npm__at_types_json-buffer__registry.npmjs.org_at_types_json-buffer_3.0.0__links//:defs.bzl", store_710 = "npm_imported_package_store")
-load("@npm__at_types_json-schema__registry.npmjs.org_at_types_json-schema_7.0.11__links//:defs.bzl", store_711 = "npm_imported_package_store")
-load("@npm__at_types_keyv__registry.npmjs.org_at_types_keyv_3.1.4__links//:defs.bzl", store_712 = "npm_imported_package_store")
-load("@npm__at_types_node__registry.npmjs.org_at_types_node_16.11.59__links//:defs.bzl", link_713 = "npm_link_imported_package_store", store_713 = "npm_imported_package_store")
-load("@npm__at_types_resolve__registry.npmjs.org_at_types_resolve_1.20.2__links//:defs.bzl", store_714 = "npm_imported_package_store")
-load("@npm__at_types_responselike__registry.npmjs.org_at_types_responselike_1.0.0__links//:defs.bzl", store_715 = "npm_imported_package_store")
+load("@npm__at_blockprotocol_type-system-web__at_gitpkg.vercel.app_blockprotocol_blockprotocol_packages__2540blockprotocol_type-system-web_6526c0e__links//:defs.bzl", link_701 = "npm_link_imported_package_store", store_701 = "npm_imported_package_store")
+load("@npm__inline-fixtures__at_registry.yarnpkg.com_inline-fixtures_-_inline-fixtures-1.1.0.tgz_5f9edf644ac02a1322012dbbb9f0a4739831d662__links//:defs.bzl", link_702 = "npm_link_imported_package_store", store_702 = "npm_imported_package_store")
+load("@npm__debug__github.com_ngokevin_debug_9742c5f383a6f8046241920156236ade8ec30d53__links//:defs.bzl", link_703 = "npm_link_imported_package_store", store_703 = "npm_imported_package_store")
+load("@npm__at_types_cacheable-request__registry.npmjs.org_at_types_cacheable-request_6.0.2__links//:defs.bzl", store_704 = "npm_imported_package_store")
+load("@npm__at_types_eslint-scope__registry.npmjs.org_at_types_eslint-scope_3.7.3__links//:defs.bzl", store_705 = "npm_imported_package_store")
+load("@npm__at_types_eslint__registry.npmjs.org_at_types_eslint_8.4.3__links//:defs.bzl", store_706 = "npm_imported_package_store")
+load("@npm__at_types_estree__registry.npmjs.org_at_types_estree_0.0.39__links//:defs.bzl", store_707 = "npm_imported_package_store")
+load("@npm__at_types_estree__registry.npmjs.org_at_types_estree_0.0.51__links//:defs.bzl", store_708 = "npm_imported_package_store")
+load("@npm__at_types_http-cache-semantics__registry.npmjs.org_at_types_http-cache-semantics_4.0.1__links//:defs.bzl", store_709 = "npm_imported_package_store")
+load("@npm__at_types_istanbul-lib-coverage__registry.npmjs.org_at_types_istanbul-lib-coverage_2.0.4__links//:defs.bzl", store_710 = "npm_imported_package_store")
+load("@npm__at_types_json-buffer__registry.npmjs.org_at_types_json-buffer_3.0.0__links//:defs.bzl", store_711 = "npm_imported_package_store")
+load("@npm__at_types_json-schema__registry.npmjs.org_at_types_json-schema_7.0.11__links//:defs.bzl", store_712 = "npm_imported_package_store")
+load("@npm__at_types_keyv__registry.npmjs.org_at_types_keyv_3.1.4__links//:defs.bzl", store_713 = "npm_imported_package_store")
+load("@npm__at_types_node__registry.npmjs.org_at_types_node_16.11.59__links//:defs.bzl", link_714 = "npm_link_imported_package_store", store_714 = "npm_imported_package_store")
+load("@npm__at_types_resolve__registry.npmjs.org_at_types_resolve_1.20.2__links//:defs.bzl", store_715 = "npm_imported_package_store")
+load("@npm__at_types_responselike__registry.npmjs.org_at_types_responselike_1.0.0__links//:defs.bzl", store_716 = "npm_imported_package_store")
 
 def npm_link_all_packages(name = "node_modules", imported_links = []):
     """Generated list of npm_link_package() target generators and first-party linked packages corresponding to the packages in //:pnpm-lock.yaml
@@ -1461,21 +1462,22 @@
         store_698(name = "{}/yargs".format(name))
         store_699(name = "{}/yocto-queue".format(name))
         store_700(name = "{}/@foo/jsonify".format(name))
-        store_701(name = "{}/inline-fixtures".format(name))
-        store_702(name = "{}/debug".format(name))
-        store_703(name = "{}/@types/cacheable-request".format(name))
-        store_704(name = "{}/@types/eslint-scope".format(name))
-        store_705(name = "{}/@types/eslint".format(name))
-        store_706(name = "{}/@types/estree".format(name))
+        store_701(name = "{}/@blockprotocol/type-system-web".format(name))
+        store_702(name = "{}/inline-fixtures".format(name))
+        store_703(name = "{}/debug".format(name))
+        store_704(name = "{}/@types/cacheable-request".format(name))
+        store_705(name = "{}/@types/eslint-scope".format(name))
+        store_706(name = "{}/@types/eslint".format(name))
         store_707(name = "{}/@types/estree".format(name))
-        store_708(name = "{}/@types/http-cache-semantics".format(name))
-        store_709(name = "{}/@types/istanbul-lib-coverage".format(name))
-        store_710(name = "{}/@types/json-buffer".format(name))
-        store_711(name = "{}/@types/json-schema".format(name))
-        store_712(name = "{}/@types/keyv".format(name))
-        store_713(name = "{}/@types/node".format(name))
-        store_714(name = "{}/@types/resolve".format(name))
-        store_715(name = "{}/@types/responselike".format(name))
+        store_708(name = "{}/@types/estree".format(name))
+        store_709(name = "{}/@types/http-cache-semantics".format(name))
+        store_710(name = "{}/@types/istanbul-lib-coverage".format(name))
+        store_711(name = "{}/@types/json-buffer".format(name))
+        store_712(name = "{}/@types/json-schema".format(name))
+        store_713(name = "{}/@types/keyv".format(name))
+        store_714(name = "{}/@types/node".format(name))
+        store_715(name = "{}/@types/resolve".format(name))
+        store_716(name = "{}/@types/responselike".format(name))
     if link:
         if native.package_name() == "examples/npm_deps":
             link_targets.append(link_0(name = "{}/@aspect-test/a".format(name)))
@@ -1507,8 +1509,10 @@
             link_targets.append(link_640(name = "{}/typescript".format(name)))
             link_targets.append(link_644(name = "{}/unused".format(name)))
             link_targets.append(link_674(name = "{}/webpack-bundle-analyzer".format(name)))
-            link_targets.append(link_701(name = "{}/inline-fixtures".format(name)))
-            link_targets.append(link_702(name = "{}/debug".format(name)))
+            link_targets.append(link_701(name = "{}/@blockprotocol/type-system-web".format(name)))
+            scope_targets["@blockprotocol"] = scope_targets["@blockprotocol"] + [link_targets[-1]] if "@blockprotocol" in scope_targets else [link_targets[-1]]
+            link_targets.append(link_702(name = "{}/inline-fixtures".format(name)))
+            link_targets.append(link_703(name = "{}/debug".format(name)))
         if native.package_name() == "js/private/coverage/bundle":
             link_targets.append(link_34(name = "{}/@rollup/plugin-commonjs".format(name)))
             scope_targets["@rollup"] = scope_targets["@rollup"] + [link_targets[-1]] if "@rollup" in scope_targets else [link_targets[-1]]
@@ -1529,7 +1533,7 @@
         if native.package_name() == "":
             link_targets.append(link_346(name = "{}/inline-fixtures".format(name)))
             link_targets.append(link_641(name = "{}/typescript".format(name)))
-            link_targets.append(link_713(name = "{}/@types/node".format(name)))
+            link_targets.append(link_714(name = "{}/@types/node".format(name)))
             scope_targets["@types"] = scope_targets["@types"] + [link_targets[-1]] if "@types" in scope_targets else [link_targets[-1]]
         if native.package_name() == "examples/webpack_cli":
             link_targets.append(link_421(name = "{}/mathjs".format(name)))
diff --git a/npm/private/test/package.json b/npm/private/test/package.json
index e6ef742..3ce8fe1 100644
--- a/npm/private/test/package.json
+++ b/npm/private/test/package.json
@@ -14,6 +14,7 @@
         "@figma/nodegit": "^0.28.0-figma.2",
         "typescript": "*",
         "inline-fixtures": "https://registry.yarnpkg.com/inline-fixtures/-/inline-fixtures-1.1.0.tgz#5f9edf644ac02a1322012dbbb9f0a4739831d662",
-        "json-stable-stringify": "1.0.1"
+        "json-stable-stringify": "1.0.1",
+        "@blockprotocol/type-system-web": "https://gitpkg.vercel.app/blockprotocol/blockprotocol/packages/%40blockprotocol/type-system-web?6526c0e"
     }
 }
diff --git a/npm/private/test/repositories_checked.bzl b/npm/private/test/repositories_checked.bzl
index 9b64628..ba59dad 100755
--- a/npm/private/test/repositories_checked.bzl
+++ b/npm/private/test/repositories_checked.bzl
@@ -15935,6 +15935,22 @@
     )
 
     npm_import(
+        name = "npm__at_blockprotocol_type-system-web__at_gitpkg.vercel.app_blockprotocol_blockprotocol_packages__2540blockprotocol_type-system-web_6526c0e",
+        root_package = "",
+        link_workspace = "",
+        link_packages = {
+            "npm/private/test": ["@blockprotocol/type-system-web"],
+        },
+        package = "@blockprotocol/type-system-web",
+        version = "@gitpkg.vercel.app/blockprotocol/blockprotocol/packages/%2540blockprotocol/type-system-web?6526c0e",
+        url = "https://gitpkg.vercel.app/blockprotocol/blockprotocol/packages/%40blockprotocol/type-system-web?6526c0e",
+        lifecycle_hooks_no_sandbox = True,
+        transitive_closure = {
+            "@blockprotocol/type-system-web": ["@gitpkg.vercel.app/blockprotocol/blockprotocol/packages/%2540blockprotocol/type-system-web?6526c0e"],
+        },
+    )
+
+    npm_import(
         name = "npm__inline-fixtures__at_registry.yarnpkg.com_inline-fixtures_-_inline-fixtures-1.1.0.tgz_5f9edf644ac02a1322012dbbb9f0a4739831d662",
         root_package = "",
         link_workspace = "",
diff --git a/npm/private/test/utils_tests.bzl b/npm/private/test/utils_tests.bzl
index 474ba65..36c9b5b 100644
--- a/npm/private/test/utils_tests.bzl
+++ b/npm/private/test/utils_tests.bzl
@@ -19,12 +19,12 @@
     env = unittest.begin(ctx)
     asserts.equals(
         env,
-        "at_scope_pkgat_21.1.0_rollupat_2.70.2_at_scope_yat_1.1.1",
+        "at_scope_pkg_21.1.0_rollup_2.70.2_at_scope_y_1.1.1",
         utils.bazel_name("@scope/pkg@21.1.0_rollup@2.70.2_@scope/y@1.1.1"),
     )
     asserts.equals(
         env,
-        "at_scope_pkgat_21.1.0",
+        "at_scope_pkg_21.1.0",
         utils.bazel_name("@scope/pkg@21.1.0"),
     )
     return unittest.end(env)
diff --git a/npm/private/utils.bzl b/npm/private/utils.bzl
index 9ea51c3..0fbe267 100644
--- a/npm/private/utils.bzl
+++ b/npm/private/utils.bzl
@@ -3,18 +3,28 @@
 load("@aspect_bazel_lib//lib:paths.bzl", "relative_file")
 load(":yaml.bzl", _parse_yaml = "parse")
 
+def _sanitize_string(string):
+    # Workspace names may contain only A-Z, a-z, 0-9, '-', '_' and '.'
+    result = ""
+    for i in range(0, len(string)):
+        c = string[i]
+        if c == "@" and (not result or result[-1] == "_"):
+            result += "at"
+        if not c.isalnum() and c != "-" and c != "_" and c != ".":
+            c = "_"
+        result += c
+    return result
+
 def _bazel_name(name, version = None):
     "Make a bazel friendly name from a package name and (optionally) a version that can be used in repository and target names"
-    escaped_name = name.replace("@", "at_").replace("/", "_").replace("#", "_")
+    escaped_name = _sanitize_string(name)
     if not version:
         return escaped_name
     version_segments = version.split("_")
-    escaped_version = version_segments[0].replace("@", "at_").replace("/", "_").replace("#", "_")
+    escaped_version = _sanitize_string(version_segments[0])
     peer_version = "_".join(version_segments[1:])
-    if peer_version.startswith("@"):
-        peer_version = "at_" + peer_version[1:]
     if peer_version:
-        escaped_version = "%s__%s" % (escaped_version, peer_version.replace("/", "_").replace("@", "_").replace("#", "_").replace("+", "_"))
+        escaped_version = "%s__%s" % (escaped_version, _sanitize_string(peer_version))
     return "%s__%s" % (escaped_name, escaped_version)
 
 def _strip_peer_dep_version(version):
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index d0dcba5..7161924 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -110,6 +110,7 @@
 
   npm/private/test:
     specifiers:
+      '@blockprotocol/type-system-web': https://gitpkg.vercel.app/blockprotocol/blockprotocol/packages/%40blockprotocol/type-system-web?6526c0e
       '@figma/nodegit': ^0.28.0-figma.2
       '@plotly/regl': 2.1.2
       bufferutil: 4.0.1
@@ -124,6 +125,7 @@
       unused: latest
       webpack-bundle-analyzer: 4.5.0
     devDependencies:
+      '@blockprotocol/type-system-web': '@gitpkg.vercel.app/blockprotocol/blockprotocol/packages/%2540blockprotocol/type-system-web?6526c0e'
       '@figma/nodegit': 0.28.0-figma.2
       '@plotly/regl': 2.1.2
       bufferutil: 4.0.1
@@ -4809,6 +4811,12 @@
     version: 0.0.0
     dev: true
 
+  '@gitpkg.vercel.app/blockprotocol/blockprotocol/packages/%2540blockprotocol/type-system-web?6526c0e':
+    resolution: {tarball: https://gitpkg.vercel.app/blockprotocol/blockprotocol/packages/%40blockprotocol/type-system-web?6526c0e}
+    name: '@blockprotocol/type-system-web'
+    version: 0.0.1
+    dev: true
+
   '@registry.yarnpkg.com/inline-fixtures/-/inline-fixtures-1.1.0.tgz#5f9edf644ac02a1322012dbbb9f0a4739831d662':
     resolution: {tarball: https://registry.yarnpkg.com/inline-fixtures/-/inline-fixtures-1.1.0.tgz#5f9edf644ac02a1322012dbbb9f0a4739831d662}
     name: inline-fixtures