Error an error will be thrown if either the package name (pkg), or the protocol cannot be deduced by the regex.
import { assertEquals, assertThrows } from "jsr:@std/assert"
// aliasing our functions for brevity
const eq = assertEquals, err = assertThrows, fn = parsePackageUrl
// basic breakdown of a package's resource uri
eq(fn("jsr:@scope/package@version/pathname/file.ts"), {
	href: "jsr:/@scope/package@version/pathname/file.ts",
	protocol: "jsr:",
	scope: "scope",
	pkg: "package",
	version: "version",
	pathname: "/pathname/file.ts",
	host: "@scope/package@version",
})
// showing that jsr package uri's without a scope are perfectly permitted.
// even though it isn't actually possible to do so on "jsr.io".
// thus it is left up to the end-user to make of it what they will. 
eq(fn("jsr:package@version/pathname/"), {
	href: "jsr:/package@version/pathname/",
	protocol: "jsr:",
	scope: undefined,
	pkg: "package",
	version: "version",
	pathname: "/pathname/",
	host: "package@version",
})
// testing a case with multiple slashes ("/") after the protocol colon (":"), and no trailing slash after the version
eq(fn("npm:///@scope/package@version"), {
	href: "npm:/@scope/package@version/",
	protocol: "npm:",
	scope: "scope",
	pkg: "package",
	version: "version",
	pathname: "/",
	host: "@scope/package@version",
})
// testing a no-scope and no-version case
eq(fn("npm:package"), {
	href: "npm:/package/",
	protocol: "npm:",
	scope: undefined,
	pkg: "package",
	version: undefined,
	pathname: "/",
	host: "package",
})
// testing the "node:" protocol
eq(fn("node:fs"), {
	href: "node:/fs/",
	protocol: "node:",
	scope: undefined,
	pkg: "fs",
	version: undefined,
	pathname: "/",
	host: "fs",
})
// testing the "node:" protocol with a certain pathname
eq(fn("node:fs/promises"), {
	href: "node:/fs/promises",
	protocol: "node:",
	scope: undefined,
	pkg: "fs",
	version: undefined,
	pathname: "/promises",
	host: "fs",
})
// testing a `version` query string that contains whitespaces and url-encoded characters.
// NOTE: the url-encoded characters in vs-code's doc popup appear decoded, so don't be fooled!
//   but the `host` is always a url-decoded string.
eq(fn("jsr:@scope/package@1.0.0 - 1.2.0/pathname/file.ts"), {
	href: "jsr:/@scope/package@1.0.0%20-%201.2.0/pathname/file.ts",
	protocol: "jsr:",
	scope: "scope",
	pkg: "package",
	version: "1.0.0 - 1.2.0",
	pathname: "/pathname/file.ts",
	host: "@scope/package@1.0.0 - 1.2.0",
})
// testing a `version` query string that has its some of its characters (such as whitespaces) url-encoded.
// NOTE: the url-encoded characters in vs-code's doc popup appear decoded, so don't be fooled!
//   but the `host` is always a url-decoded string.
eq(fn("jsr:@scope/package@^2%20<2.2%20||%20>%202.3/pathname/file.ts"), {
	href: "jsr:/@scope/package@%5E2%20%3C2.2%20%7C%7C%20%3E%202.3/pathname/file.ts",
	protocol: "jsr:",
	scope: "scope",
	pkg: "package",
	version: "^2 <2.2 || > 2.3",
	pathname: "/pathname/file.ts",
	host: "@scope/package@^2 <2.2 || > 2.3",
})
// testing cases where an error should be invoked
err(() => fn("npm:@scope/"))                 // missing a package name
err(() => fn("npm:@scope//package"))         // more than one slash after scope
err(() => fn("pnpm:@scope/package@version")) // only "node:", "npm:", and "jsr:" protocols are recognized
this function parses npm and jsr package strings, and returns a pseudo URL-like object.
the regex we use for parsing the input
hrefstring is quoted below:see the regex in action with the test cases on regex101 link: regex101.com/r/mX3v1z/2