path-utils.ts 1.8 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364
  1. /**
  2. * Normalize a path to use forward slashes (works on both macOS and Windows).
  3. * Windows APIs accept forward slashes, so normalizing to / is safe everywhere.
  4. */
  5. export function normalizePath(p: string): string {
  6. return p.replace(/\\/g, "/")
  7. }
  8. /**
  9. * Join path segments with forward slashes.
  10. */
  11. export function joinPath(...segments: string[]): string {
  12. return segments
  13. .map((s) => s.replace(/\\/g, "/"))
  14. .join("/")
  15. .replace(/\/+/g, "/")
  16. }
  17. /**
  18. * Get the filename from a path (handles both / and \).
  19. */
  20. export function getFileName(p: string): string {
  21. const normalized = p.replace(/\\/g, "/")
  22. return normalized.split("/").pop() ?? p
  23. }
  24. /**
  25. * Get the file stem (filename without extension).
  26. */
  27. export function getFileStem(p: string): string {
  28. const name = getFileName(p)
  29. const lastDot = name.lastIndexOf(".")
  30. return lastDot > 0 ? name.slice(0, lastDot) : name
  31. }
  32. /**
  33. * Get relative path from base.
  34. */
  35. export function getRelativePath(fullPath: string, basePath: string): string {
  36. const normalFull = normalizePath(fullPath)
  37. const normalBase = normalizePath(basePath).replace(/\/$/, "")
  38. if (normalFull.startsWith(normalBase + "/")) {
  39. return normalFull.slice(normalBase.length + 1)
  40. }
  41. return normalFull
  42. }
  43. /**
  44. * Cross-platform absolute-path detection.
  45. *
  46. * Unix: "/foo/bar"
  47. * Windows: "C:\foo", "C:/foo", "\\server\share", "//server/share"
  48. *
  49. * A bare `.startsWith("/")` check wrongly treats Windows paths like
  50. * "C:/project/file.pdf" as relative, which produced double-joined
  51. * garbage like "C:/project/C:/project/file.pdf" in the ingest queue.
  52. */
  53. export function isAbsolutePath(p: string): boolean {
  54. if (!p) return false
  55. if (p.startsWith("/")) return true
  56. if (/^[A-Za-z]:[\\/]/.test(p)) return true
  57. if (p.startsWith("\\\\") || p.startsWith("//")) return true
  58. return false
  59. }