pub const JSR_DEFAULT_REGISTRY: &str = "https://npm.jsr.io/";
pub const JSR_NPM_SCOPE: &str = "@jsr";
pub fn jsr_to_npm_name(jsr_name: &str) -> Option<String> {
let rest = jsr_name.strip_prefix('@')?;
let (scope, name) = rest.split_once('/')?;
if !valid_jsr_name_part(scope) || !valid_jsr_name_part(name) {
return None;
}
Some(format!("@jsr/{scope}__{name}"))
}
fn valid_jsr_name_part(part: &str) -> bool {
if part.is_empty() || matches!(part, "." | "..") {
return false;
}
part.bytes().all(|b| {
matches!(
b,
b'a'..=b'z' | b'0'..=b'9' | b'-' | b'_' | b'.'
)
})
}
pub fn npm_to_jsr_name(npm_name: &str) -> Option<String> {
let rest = npm_name.strip_prefix("@jsr/")?;
let (scope, name) = rest.split_once("__")?;
if scope.is_empty() || name.is_empty() {
return None;
}
Some(format!("@{scope}/{name}"))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn translates_basic_scoped_name() {
assert_eq!(
jsr_to_npm_name("@std/collections").as_deref(),
Some("@jsr/std__collections"),
);
}
#[test]
fn translates_hyphenated_scope_and_name() {
assert_eq!(
jsr_to_npm_name("@foo-bar/baz-qux").as_deref(),
Some("@jsr/foo-bar__baz-qux"),
);
}
#[test]
fn rejects_unscoped_and_malformed_inputs() {
assert_eq!(jsr_to_npm_name("lodash"), None);
assert_eq!(jsr_to_npm_name("@std"), None);
assert_eq!(jsr_to_npm_name("@/collections"), None);
assert_eq!(jsr_to_npm_name("@std/"), None);
assert_eq!(jsr_to_npm_name("@std/../collections"), None);
assert_eq!(jsr_to_npm_name("@std/collections/more"), None);
assert_eq!(jsr_to_npm_name("@std//collections"), None);
assert_eq!(jsr_to_npm_name("@std/co llections"), None);
assert_eq!(jsr_to_npm_name("@std/%2fcollections"), None);
assert_eq!(jsr_to_npm_name("@Std/collections"), None);
}
#[test]
fn round_trips_via_npm_to_jsr_name() {
let jsr = "@std/collections";
let npm = jsr_to_npm_name(jsr).unwrap();
assert_eq!(npm_to_jsr_name(&npm).as_deref(), Some(jsr));
}
#[test]
fn rejects_non_jsr_npm_names() {
assert_eq!(npm_to_jsr_name("@scope/pkg"), None);
assert_eq!(npm_to_jsr_name("lodash"), None);
assert_eq!(npm_to_jsr_name("@jsr/no-separator"), None);
}
}