/** | |
* Heavily inspired by https://github.com/huggingface/huggingface_hub/blob/fcfd14361bd03f23f82efced1aa65a7cbfa4b922/src/huggingface_hub/file_download.py#L517 | |
*/ | |
import * as fs from "node:fs/promises"; | |
import * as path from "node:path"; | |
import * as os from "node:os"; | |
function expandUser(path: string): string { | |
if (path.startsWith("~")) { | |
return path.replace("~", os.homedir()); | |
} | |
return path; | |
} | |
/** | |
* Create a symbolic link named dst pointing to src. | |
* | |
* By default, it will try to create a symlink using a relative path. Relative paths have 2 advantages: | |
* - If the cache_folder is moved (example: back-up on a shared drive), relative paths within the cache folder will | |
* not break. | |
* - Relative paths seems to be better handled on Windows. Issue was reported 3 times in less than a week when | |
* changing from relative to absolute paths. See https://github.com/huggingface/huggingface_hub/issues/1398, | |
* https://github.com/huggingface/diffusers/issues/2729 and https://github.com/huggingface/transformers/pull/22228. | |
* NOTE: The issue with absolute paths doesn't happen on admin mode. | |
* When creating a symlink from the cache to a local folder, it is possible that a relative path cannot be created. | |
* This happens when paths are not on the same volume. In that case, we use absolute paths. | |
* | |
* The result layout looks something like | |
* βββ [ 128] snapshots | |
* βββ [ 128] 2439f60ef33a0d46d85da5001d52aeda5b00ce9f | |
* β βββ [ 52] README.md -> ../../../blobs/d7edf6bd2a681fb0175f7735299831ee1b22b812 | |
* β βββ [ 76] pytorch_model.bin -> ../../../blobs/403450e234d65943a7dcf7e05a771ce3c92faa84dd07db4ac20f592037a1e4bd | |
* | |
* If symlinks cannot be created on this platform (most likely to be Windows), the workaround is to avoid symlinks by | |
* having the actual file in `dst`. If it is a new file (`new_blob=True`), we move it to `dst`. If it is not a new file | |
* (`new_blob=False`), we don't know if the blob file is already referenced elsewhere. To avoid breaking existing | |
* cache, the file is duplicated on the disk. | |
*/ | |
export async function createSymlink(params: { | |
/** | |
* The path to the symlink. | |
*/ | |
finalPath: string; | |
/** | |
* The path the symlink should point to. | |
*/ | |
sourcePath: string; | |
}): Promise<void> { | |
const abs_src = path.resolve(expandUser(params.sourcePath)); | |
const abs_dst = path.resolve(expandUser(params.finalPath)); | |
try { | |
await fs.rm(abs_dst); | |
} catch { | |
// ignore | |
} | |
try { | |
await fs.symlink(path.relative(path.dirname(abs_dst), abs_src), abs_dst); | |
} catch { | |
console.info(`Symlink not supported. Copying file from ${abs_src} to ${abs_dst}`); | |
await fs.copyFile(abs_src, abs_dst); | |
} | |
} | |