this function parses npm and jsr package strings, and returns a pseudo URL-like object.

the regex we use for parsing the input href string is quoted below:

/^(?jsr:|npm:|node:)(/(@(?[^/\s]+)/)?(?[^@/\s]+)(@(?[^/\r\n\t\f\v]+))?)?(?/.)?$/

see the regex in action with the test cases on regex101 link: regex101.com/r/mX3v1z/2

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
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
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