Error an error will be thrown if there isn't any common ancestral directory between the two provided paths.
import { assertEquals, assertThrows } from "jsr:@std/assert"
// aliasing our functions for brevity
const eq = assertEquals, err = assertThrows, fn = relativePath
eq(fn(
"././hello/world/a/b/c/d/g/../e.txt",
"././hello/world/a/b/x/y/w/../z/",
), "../../x/y/z/")
eq(fn(
"././hello/world/a/b/c/d/g/../e.txt",
"././hello/world/a/b/x/y/w/../z/e.md",
), "../../x/y/z/e.md")
eq(fn(
".\\./hello\\world\\a/b\\c/d/g/../",
"././hello/world/a/b/x/y/w/../z/e.md",
), "../../x/y/z/e.md")
eq(fn(
"././hello/world/a/b/c/d/",
"././hello/world/a/b/x/y/w/../z/e.md",
), "../../x/y/z/e.md")
eq(fn(
"././hello/world/a/b/c/d/g/../",
"././hello/world/a/b/x/y/w/../z/e.md",
), "../../x/y/z/e.md")
eq(fn(
"././hello/world/a/b/c/d/",
"././hello/world/a/b/x/y/w/../z/",
), "../../x/y/z/")
eq(fn(
"./././e.txt",
"./e.md",
), "./e.md")
eq(fn(
"/e.txt",
"/e.md",
), "./e.md")
eq(fn(
"C:/e.txt",
"C:/e.md",
), "./e.md")
eq(fn(
"././hello/world/a/b/c/d/g/../e.txt",
"././hello/world/a/k/../b/q/../c/d/e.md",
), "./e.md")
eq(fn(
"./",
"./",
), "./")
eq(fn(
"/",
"/",
), "./")
// there is no common ancestral root between the two paths (since one is absolute, while the other is relative)
err(() => fn(
"/e.txt",
"./e.md",
))
// there is no common ancestral root between the two paths
err(() => fn(
"C:/e.txt",
"D:/e.md",
))
// there is no common ancestral root between the two paths
err(() => fn(
"http://e.txt",
"./e.md",
))
// there is no common ancestral root between the two paths
err(() => fn(
"file:///C:/e.txt",
"C:/e.md",
))
find the path
to_path, relative tofrom_path.TODO: the claim below is wrong, because
joinSlashcannot do "./" traversal correctly. for instancejoinSlash("a/b/c.txt", "./") === "a/b/c.txt/", but you'd expect it to be"a/b/"if we had correctly resolved the path. which is why the output from this function will not work withjoinSlash, but it will work with resolveAsUrl orURL.parse(when you providefrom_pathas the base path, andfrom_pathis NOT a relative path, otherwise theURLconstructor will fail).if we call the resultrel_path, then joining thefrom_pathwithrel_pathand normalizing it should give you backto_path.note that both
from_pathandto_pathmust have a common root folder in their path strings. for instance, if both paths begin with a relative segment"./", then it will be assumed that both paths are referring to the same common root ancestral directory. however, if for instance,from_pathbegins with a"C:/"segment, whileto_pathbegins with either"./"or"D:/"orhttp://segment, then this function will fail, as it will not be possible for it to navigate/transcend from one point of reference to a completely different point of reference.so, to be safe, wherever you are certain that both paths are of a certain common type: before passing them here, you should either apply the ensureStartDotSlash function for relative paths, or apply the ensureStartSlash for absolute local paths, or write a custom "ensure" function for your situation. (for example, you could write an "ensureHttp" function that ensures that your path begins with
"http").