Skip to content

Commit 07bd631

Browse files
committed
feat: add path utility
1 parent 2485093 commit 07bd631

File tree

2 files changed

+95
-0
lines changed

2 files changed

+95
-0
lines changed

website/src/utils/path.test.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { describe, expect, it } from "vitest";
2+
import { applyBasePath, joinPath } from "./path";
3+
4+
describe("joinPath", () => {
5+
it("should join base and path with single slash", () => {
6+
expect(joinPath("/base/", "/foo")).toBe("/base/foo");
7+
expect(joinPath("/base", "foo")).toBe("/base/foo");
8+
expect(joinPath("/base", "/foo/bar")).toBe("/base/foo/bar");
9+
expect(joinPath("/foo/bar/", "/baz/qux")).toBe("/foo/bar/baz/qux");
10+
expect(joinPath("/foo/bar", "baz/qux")).toBe("/foo/bar/baz/qux");
11+
});
12+
13+
it("should handle root base path correctly", () => {
14+
expect(joinPath("/", "/foo")).toBe("/foo");
15+
expect(joinPath("/", "foo")).toBe("/foo");
16+
});
17+
18+
it("should handle empty path", () => {
19+
expect(joinPath("/base", "")).toBe("/base/");
20+
expect(joinPath("/base/", "")).toBe("/base/");
21+
});
22+
23+
it("should handle empty base", () => {
24+
expect(joinPath("", "/foo")).toBe("/foo");
25+
expect(joinPath("", "foo")).toBe("/foo");
26+
});
27+
});
28+
29+
describe("applyBasePath", () => {
30+
it("should apply basePath to absolute path", () => {
31+
expect(applyBasePath("/base", "/foo")).toBe("/base/foo");
32+
expect(applyBasePath("/foo", "/bar/baz")).toBe("/foo/bar/baz");
33+
});
34+
35+
it("should not apply basePath to relative path", () => {
36+
expect(applyBasePath("/base", "./index.html")).toBe("./index.html");
37+
expect(applyBasePath("/foo/bar", "baz/qux")).toBe("baz/qux");
38+
});
39+
40+
it("should handle root basePath", () => {
41+
expect(applyBasePath("/", "/foo")).toBe("/foo");
42+
});
43+
44+
it("should handle empty basePath", () => {
45+
expect(applyBasePath("", "/foo")).toBe("/foo");
46+
});
47+
});

website/src/utils/path.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/**
2+
* Joins two paths by resolving any slash collisions between them.
3+
*
4+
* @param basePath - The base path to join.
5+
* @param path - The path to append to the basePath.
6+
* @returns The joined path with redundant slashes resolved.
7+
*
8+
* @example
9+
* ```ts
10+
* joinPath("/base/", "/foo") -> "/base/foo"
11+
* joinPath("/base", "foo") -> "/base/foo"
12+
* joinPath("/base", "/foo/bar") -> "/base/foo/bar"
13+
* joinPath("/foo/bar/", "/baz/qux") -> "/foo/bar/baz/qux"
14+
* joinPath("/foo/bar", "baz/qux") -> "/foo/bar/baz/qux"
15+
* ```
16+
*/
17+
export const joinPath = (basePath: string, path: string): string => {
18+
// Remove trailing slash from base.
19+
const baseClean = basePath !== "/" ? basePath.replace(/\/+$/, "") : basePath;
20+
// Remove leading slash from path.
21+
const pathClean = path.replace(/^\/+/, "");
22+
// Special case: if base is '/', avoid double slash
23+
if (baseClean === "/") {
24+
return `/${pathClean}`;
25+
}
26+
return `${baseClean}/${pathClean}`;
27+
};
28+
29+
/**
30+
* Applies a base path to internal non-relative URLs (starting with `/`).
31+
* If the path is relative, it is returned as-is.
32+
*
33+
* @param basePath - The base path to apply.
34+
* @param path - The original path to modify.
35+
* @returns The modified path with the basePath applied if it was an absolute path.
36+
*
37+
* @example
38+
* ```ts
39+
* applyBasePath("/base", "/foo") -> "/base/foo"
40+
* applyBasePath("/base", "./index.html") -> "./index.html"
41+
* applyBasePath("/foo", "/bar/baz") -> "/foo/bar/baz"
42+
* applyBasePath("/foo/bar", "baz/qux") -> "baz/qux"
43+
* ```
44+
*/
45+
export const applyBasePath = (basePath: string, path: string): string => {
46+
const isRelative = !path.startsWith("/");
47+
return isRelative ? path : joinPath(basePath, path);
48+
};

0 commit comments

Comments
 (0)