|  | 
|  | 1 | +/** | 
|  | 2 | + * Heavily inspired by https://github.com/huggingface/huggingface_hub/blob/fcfd14361bd03f23f82efced1aa65a7cbfa4b922/src/huggingface_hub/file_download.py#L517 | 
|  | 3 | + */ | 
|  | 4 | + | 
|  | 5 | +import * as fs from "node:fs/promises"; | 
|  | 6 | +import * as path from "node:path"; | 
|  | 7 | +import * as os from "node:os"; | 
|  | 8 | + | 
|  | 9 | +function expandUser(path: string): string { | 
|  | 10 | +	if (path.startsWith("~")) { | 
|  | 11 | +		return path.replace("~", os.homedir()); | 
|  | 12 | +	} | 
|  | 13 | +	return path; | 
|  | 14 | +} | 
|  | 15 | + | 
|  | 16 | +/** | 
|  | 17 | + * Create a symbolic link named dst pointing to src. | 
|  | 18 | + * | 
|  | 19 | + * By default, it will try to create a symlink using a relative path. Relative paths have 2 advantages: | 
|  | 20 | + * - If the cache_folder is moved (example: back-up on a shared drive), relative paths within the cache folder will | 
|  | 21 | + *  not break. | 
|  | 22 | + * - Relative paths seems to be better handled on Windows. Issue was reported 3 times in less than a week when | 
|  | 23 | + *   changing from relative to absolute paths. See https://github.com/huggingface/huggingface_hub/issues/1398, | 
|  | 24 | + *   https://github.com/huggingface/diffusers/issues/2729 and https://github.com/huggingface/transformers/pull/22228. | 
|  | 25 | + *   NOTE: The issue with absolute paths doesn't happen on admin mode. | 
|  | 26 | + * When creating a symlink from the cache to a local folder, it is possible that a relative path cannot be created. | 
|  | 27 | + * This happens when paths are not on the same volume. In that case, we use absolute paths. | 
|  | 28 | + * | 
|  | 29 | + * The result layout looks something like | 
|  | 30 | + *     └── [ 128]  snapshots | 
|  | 31 | + *         ├── [ 128]  2439f60ef33a0d46d85da5001d52aeda5b00ce9f | 
|  | 32 | + *         │   ├── [  52]  README.md -> ../../../blobs/d7edf6bd2a681fb0175f7735299831ee1b22b812 | 
|  | 33 | + *         │   └── [  76]  pytorch_model.bin -> ../../../blobs/403450e234d65943a7dcf7e05a771ce3c92faa84dd07db4ac20f592037a1e4bd | 
|  | 34 | + * | 
|  | 35 | + * If symlinks cannot be created on this platform (most likely to be Windows), the workaround is to avoid symlinks by | 
|  | 36 | + * 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 | 
|  | 37 | + * (`new_blob=False`), we don't know if the blob file is already referenced elsewhere. To avoid breaking existing | 
|  | 38 | + * cache, the file is duplicated on the disk. | 
|  | 39 | + * | 
|  | 40 | + * In case symlinks are not supported, a warning message is displayed to the user once when loading `huggingface_hub`. | 
|  | 41 | + * The warning message can be disabled with the `HF_HUB_DISABLE_SYMLINKS_WARNING` environment variable. | 
|  | 42 | + */ | 
|  | 43 | +export async function createSymlink(dst: string, src: string, new_blob?: boolean): Promise<void> { | 
|  | 44 | +	try { | 
|  | 45 | +		await fs.rm(dst); | 
|  | 46 | +	} catch (_e: unknown) { | 
|  | 47 | +		/* empty */ | 
|  | 48 | +	} | 
|  | 49 | +	const abs_src = path.resolve(expandUser(src)); | 
|  | 50 | +	const abs_dst = path.resolve(expandUser(dst)); | 
|  | 51 | + | 
|  | 52 | +	try { | 
|  | 53 | +		await fs.symlink(abs_dst, abs_src); | 
|  | 54 | +	} catch (_e: unknown) { | 
|  | 55 | +		if (new_blob) { | 
|  | 56 | +			console.info(`Symlink not supported. Moving file from ${abs_src} to ${abs_dst}`); | 
|  | 57 | +			await fs.rename(abs_src, abs_dst); | 
|  | 58 | +		} else { | 
|  | 59 | +			console.info(`Symlink not supported. Copying file from ${abs_src} to ${abs_dst}`); | 
|  | 60 | +			await fs.copyFile(abs_src, abs_dst); | 
|  | 61 | +		} | 
|  | 62 | +	} | 
|  | 63 | +} | 
0 commit comments