{
  "version": 3,
  "sources": ["../../../../src/packages/workspace.injected-deps-syncer/DirPatcher.ts"],
  "sourcesContent": ["import fs from 'node:fs';\nimport path from 'node:path';\nimport util from 'node:util';\nimport {\n  type FetchFromDirOptions,\n  fetchFromDir,\n} from '../directory-fetcher/index.ts';\nimport { PnpmError } from '../error/index.ts';\n\nexport const DIR: unique symbol = Symbol('Path is a directory');\n\n// symbols and and numbers are used instead of discriminated union because\n// it's faster and simpler to compare primitives than to deep compare objects\nexport type File = number; // representing the file's inode, which is sufficient for hardlinks\nexport type Dir = typeof DIR;\n\nexport type Value = File | Dir;\nexport type InodeMap = Record<string, Value>;\n\nexport type DiffItemBase = {\n  path: string;\n  oldValue?: Value | undefined;\n  newValue?: Value | undefined;\n};\n\nexport interface AddedItem extends DiffItemBase {\n  path: string;\n  oldValue?: Value | undefined;\n  newValue: Value;\n}\n\nexport interface RemovedItem extends DiffItemBase {\n  path: string;\n  oldValue: Value;\n  newValue?: Value | undefined;\n}\n\nexport interface ModifiedItem extends DiffItemBase {\n  path: string;\n  oldValue: Value;\n  newValue: Value;\n}\n\nexport type DirDiff = {\n  added: AddedItem[];\n  removed: RemovedItem[];\n  modified: ModifiedItem[];\n};\n\n// length comparison should place every directory before the files it contains because\n// a directory path is always shorter than any file path it contains\nfunction comparePaths(a: string, b: string): number {\n  return (\n    a.split(/\\\\|\\//).length - b.split(/\\\\|\\//).length || a.localeCompare(b)\n  );\n}\n\n/**\n * Get the difference between 2 files tree.\n *\n * The arrays in the resulting object are sorted in such a way that every directory paths are placed before\n * the files it contains. This way, it would allow optimization for operations upon this diff.\n * Note that when performing removal of removed files according to this diff, the `removed` array should be reversed first.\n */\nexport function diffDir(oldIndex: InodeMap, newIndex: InodeMap): DirDiff {\n  const oldPaths = Object.keys(oldIndex).sort(comparePaths);\n\n  const newPaths = Object.keys(newIndex).sort(comparePaths);\n\n  const removed: RemovedItem[] = oldPaths\n    .filter((path: string): boolean => {\n      return !(path in newIndex);\n    })\n    .map((path: string): RemovedItem | null => {\n      const oldValue = oldIndex[path];\n\n      if (oldValue === undefined) {\n        return null;\n      }\n\n      return { path, oldValue };\n    })\n    .filter(Boolean);\n\n  const added: AddedItem[] = newPaths\n    .filter((path: string): boolean => {\n      return !(path in oldIndex);\n    })\n    .map((path: string): AddedItem | null => {\n      const newValue = newIndex[path];\n\n      if (typeof newValue === 'undefined') {\n        return null;\n      }\n\n      return { path, newValue };\n    })\n    .filter(Boolean);\n\n  const modified: ModifiedItem[] = oldPaths\n    .filter((path: string): boolean => {\n      return path in newIndex && oldIndex[path] !== newIndex[path];\n    })\n    .map((path: string): ModifiedItem | null => {\n      const oldValue = oldIndex[path];\n\n      const newValue = newIndex[path];\n\n      if (typeof oldValue === 'undefined' || typeof newValue === 'undefined') {\n        return null;\n      }\n\n      return { path, oldValue, newValue };\n    })\n    .filter(Boolean);\n\n  return { added, removed, modified };\n}\n\n/**\n * Apply a patch on a directory.\n *\n * The {@link optimizedDirPatch} is assumed to be already optimized (i.e. `removed` is already reversed).\n */\nexport async function applyPatch(\n  optimizedDirPatch: DirDiff,\n  sourceDir: string,\n  targetDir: string\n): Promise<void> {\n  async function addRecursive(\n    sourcePath: string,\n    targetPath: string,\n    value: Value\n  ): Promise<void> {\n    if (value === DIR) {\n      await fs.promises.mkdir(targetPath, { recursive: true });\n    } else if (typeof value === 'number') {\n      fs.mkdirSync(path.dirname(targetPath), { recursive: true });\n\n      await fs.promises.link(sourcePath, targetPath);\n    } else {\n      // const _: never = value; // static type guard\n    }\n  }\n\n  async function removeRecursive(targetPath: string): Promise<void> {\n    try {\n      await fs.promises.rm(targetPath, { recursive: true, force: true });\n    } catch (error) {\n      if (\n        !util.types.isNativeError(error) ||\n        !('code' in error) ||\n        error.code !== 'ENOENT'\n      ) {\n        throw error;\n      }\n    }\n  }\n\n  const adding = Promise.all(\n    optimizedDirPatch.added.map(async (item: AddedItem): Promise<void> => {\n      const sourcePath = path.join(sourceDir, item.path);\n\n      const targetPath = path.join(targetDir, item.path);\n\n      await addRecursive(sourcePath, targetPath, item.newValue);\n    })\n  );\n\n  const removing = Promise.all(\n    optimizedDirPatch.removed.map(async (item: RemovedItem): Promise<void> => {\n      const targetPath = path.join(targetDir, item.path);\n\n      await removeRecursive(targetPath);\n    })\n  );\n\n  const modifying = Promise.all(\n    optimizedDirPatch.modified.map(async (item) => {\n      const sourcePath = path.join(sourceDir, item.path);\n\n      const targetPath = path.join(targetDir, item.path);\n\n      if (item.oldValue === item.newValue) {\n        return;\n      }\n\n      await removeRecursive(targetPath);\n\n      await addRecursive(sourcePath, targetPath, item.newValue);\n    })\n  );\n\n  await Promise.all([adding, removing, modifying]);\n}\n\nexport type ExtendFilesMapStats = Pick<\n  fs.Stats,\n  'ino' | 'isFile' | 'isDirectory'\n>;\n\nexport interface ExtendFilesMapOptions {\n  /** Map relative path of each file to their real path */\n  filesIndex: Record<string, string>;\n  /** Map relative path of each file to their stats */\n  filesStats?: Record<string, ExtendFilesMapStats | null> | undefined;\n}\n\n/**\n * Convert a pair of a files index map, which is a map from relative path of each file to their real paths,\n * and an optional file stats map, which is a map from relative path of each file to their stats,\n * into an inodes map, which is a map from relative path of every file and directory to their inode type.\n */\nexport async function extendFilesMap({\n  filesIndex,\n  filesStats,\n}: ExtendFilesMapOptions): Promise<InodeMap> {\n  const result: InodeMap = {\n    '.': DIR,\n  };\n\n  function addInodeAndAncestors(relativePath: string, value: Value): void {\n    if (\n      relativePath &&\n      relativePath !== '.' &&\n      typeof result[relativePath] === 'undefined'\n    ) {\n      result[relativePath] = value;\n\n      addInodeAndAncestors(path.dirname(relativePath), DIR);\n    }\n  }\n\n  await Promise.all(\n    Object.entries(filesIndex).map(async ([relativePath, realPath]) => {\n      const stats =\n        filesStats?.[relativePath] ?? (await fs.promises.stat(realPath));\n\n      if (stats.isFile()) {\n        addInodeAndAncestors(relativePath, stats.ino);\n      } else if (stats.isDirectory()) {\n        addInodeAndAncestors(relativePath, DIR);\n      } else {\n        throw new PnpmError(\n          'UNSUPPORTED_INODE_TYPE',\n          `Filesystem inode at ${realPath} is neither a file, a directory, or a symbolic link`\n        );\n      }\n    })\n  );\n\n  return result;\n}\n\nexport class DirPatcher {\n  private readonly sourceDir: string;\n  private readonly targetDir: string;\n  private readonly patch: DirDiff;\n\n  private constructor(patch: DirDiff, sourceDir: string, targetDir: string) {\n    this.patch = patch;\n    this.sourceDir = sourceDir;\n    this.targetDir = targetDir;\n  }\n\n  static async fromMultipleTargets(\n    sourceDir: string,\n    targetDirs: string[]\n  ): Promise<DirPatcher[]> {\n    const fetchOptions: FetchFromDirOptions = {\n      resolveSymlinks: false,\n    };\n\n    async function loadMap(dir: string): Promise<[InodeMap, string]> {\n      const fetchResult = await fetchFromDir(dir, fetchOptions);\n\n      return [await extendFilesMap(fetchResult), dir];\n    }\n\n    const [[sourceMap], targetPairs] = await Promise.all([\n      loadMap(sourceDir),\n      Promise.all(targetDirs.map(loadMap)),\n    ]);\n\n    return targetPairs.map(([targetMap, targetDir]) => {\n      const diff = diffDir(targetMap, sourceMap);\n\n      // Before reversal, every directory in `diff.removed` are placed before its files.\n      // After reversal, every file is place before its ancestors,\n      // leading to children being deleted before parents, optimizing performance.\n      diff.removed.reverse();\n\n      // biome-ignore lint/complexity/noThisInStatic: <explanation>\n      return new this(diff, sourceDir, targetDir);\n    });\n  }\n\n  async apply(): Promise<void> {\n    await applyPatch(this.patch, this.sourceDir, this.targetDir);\n  }\n}\n"],
  "mappings": "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",
  "names": ["path"]
}
