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
href
string is quoted below:see the regex in action with the test cases on regex101 link: regex101.com/r/mX3v1z/2