constructor(rootDir: string) {
    this.#rootDir = rootDir;
    const rootDirnames = [];
    for (
      let next = rootDir, previous = null;
      previous !== next;
      previous = next, next = path.dirname(next)
    ) {
      rootDirnames.push(next);
    }
    this.#rootDirnames = rootDirnames;

    this.#rootParts = rootDir.split(path.sep);
    this.#rootDepth = rootDirnames.length - 1;

    // If rootDir is a filesystem root (C:\ or /), it will end in a separator and
    // give a spurious empty entry at the end of rootParts.
    if (this.#rootDepth === 0) {
      this.#rootParts.pop();
    }
  }
This module provides path utility functions - similar to `node:path` -
optimised for Metro's use case (many paths, few roots) under assumptions
typically safe to make within Metro - namely:

 - All input path separators must be system-native.
 - Double/redundant separators like '/foo//bar' are not supported.
 - All characters except separators are assumed to be valid in path segments.

 - A "well-formed" path is any path following the rules above.
 - A "normal" path is a root-relative well-formed path with no redundant
   indirections. Normal paths have no leading './`, and the normal path of
   the root is the empty string.

Output and input paths are at least well-formed (normal where indicated by
naming).

Trailing path separators are preserved, except for fs roots in
normalToAbsolute (fs roots always have a trailing separator), and the
project root in absoluteToNormal and relativeToNormal (the project root is
always the empty string, and is always a directory, so a trailing separator
is redundant).

As of Node 20, absoluteToNormal is ~8x faster than `path.relative` and
`normalToAbsolute` is ~20x faster than `path.resolve`, benchmarked on the
real inputs from building FB's product graph. Some well-formed inputs
(e.g., /project/./foo/../bar), are handled but not optimised, and we fall
back to `node:path` equivalents in those cases.