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:

/^(?npm:|jsr:)(/(@(?[^/\s]+)/)?(?[^@/\s]+)(@(?[^/\s]+))?)?(?/.)?$/

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

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

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",
})
eq(fn("jsr:package@version/pathname/"), {
href: "jsr:/package@version/pathname/",
protocol: "jsr:",
scope: undefined,
pkg: "package",
version: "version",
pathname: "/pathname/",
host: "package@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",
})
eq(fn("npm:package"), {
href: "npm:/package/",
protocol: "npm:",
scope: undefined,
pkg: "package",
version: undefined,
pathname: "/",
host: "package",
})

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 "npm:" and "jsr:" protocols are recognized