-
Notifications
You must be signed in to change notification settings - Fork 640
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(fs/unstable): add symlink and symlinkSync (#6352)
- Loading branch information
Showing
5 changed files
with
209 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
// Copyright 2018-2025 the Deno authors. MIT license. | ||
|
||
import { getNodeFs, isDeno } from "./_utils.ts"; | ||
import { mapError } from "./_map_error.ts"; | ||
import type { SymlinkOptions } from "./unstable_types.ts"; | ||
|
||
/** | ||
* Creates `newpath` as a symbolic link to `oldpath`. | ||
* | ||
* The `options.type` parameter can be set to `"file"`, `"dir"` or `"junction"`. | ||
* This argument is only available on Windows and ignored on other platforms. | ||
* | ||
* Requires full `allow-read` and `allow-write` permissions. | ||
* | ||
* @example Usage | ||
* ```ts ignore | ||
* import { symlink } from "@std/fs/unstable-symlink"; | ||
* await symlink("README.md", "README.md.link"); | ||
* ``` | ||
* | ||
* @tags allow-read, allow-write | ||
* | ||
* @param oldpath The path of the resource pointed by the symbolic link. | ||
* @param newpath The path of the symbolic link. | ||
*/ | ||
export async function symlink( | ||
oldpath: string | URL, | ||
newpath: string | URL, | ||
options?: SymlinkOptions, | ||
): Promise<void> { | ||
if (isDeno) { | ||
return Deno.symlink(oldpath, newpath, options); | ||
} else { | ||
try { | ||
return await getNodeFs().promises.symlink( | ||
oldpath, | ||
newpath, | ||
options?.type, | ||
); | ||
} catch (error) { | ||
throw mapError(error); | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* Creates `newpath` as a symbolic link to `oldpath`. | ||
* | ||
* The `options.type` parameter can be set to `"file"`, `"dir"` or `"junction"`. | ||
* This argument is only available on Windows and ignored on other platforms. | ||
* | ||
* Requires full `allow-read` and `allow-write` permissions. | ||
* | ||
* @example Usage | ||
* ```ts ignore | ||
* import { symlinkSync } from "@std/fs/unstable-symlink"; | ||
* symlinkSync("README.md", "README.md.link"); | ||
* ``` | ||
* | ||
* @tags allow-read, allow-write | ||
* | ||
* @param oldpath The path of the resource pointed by the symbolic link. | ||
* @param newpath The path of the symbolic link. | ||
*/ | ||
export function symlinkSync( | ||
oldpath: string | URL, | ||
newpath: string | URL, | ||
options?: SymlinkOptions, | ||
): void { | ||
if (isDeno) { | ||
return Deno.symlinkSync(oldpath, newpath, options); | ||
} else { | ||
try { | ||
return getNodeFs().symlinkSync(oldpath, newpath, options?.type); | ||
} catch (error) { | ||
throw mapError(error); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
// Copyright 2018-2025 the Deno authors. MIT license. | ||
|
||
import { assert, assertRejects, assertThrows } from "@std/assert"; | ||
import { symlink, symlinkSync } from "./unstable_symlink.ts"; | ||
import { AlreadyExists } from "./unstable_errors.js"; | ||
import { lstat, mkdir, mkdtemp, open, rm, stat } from "node:fs/promises"; | ||
import { | ||
closeSync, | ||
lstatSync, | ||
mkdirSync, | ||
mkdtempSync, | ||
openSync, | ||
rmSync, | ||
statSync, | ||
} from "node:fs"; | ||
import { tmpdir } from "node:os"; | ||
import { dirname, join, resolve } from "node:path"; | ||
import { fileURLToPath } from "node:url"; | ||
|
||
const moduleDir = dirname(fileURLToPath(import.meta.url)); | ||
const testdataDir = resolve(moduleDir, "testdata"); | ||
|
||
Deno.test("symlink() creates a link to a regular file", async () => { | ||
const tempDirPath = await mkdtemp(resolve(tmpdir(), "symlink_")); | ||
const testFile = join(tempDirPath, "testFile.txt"); | ||
const symlinkPath = join(tempDirPath, "testFile.txt.link"); | ||
|
||
const tempFh = await open(testFile, "w"); | ||
await symlink(testFile, symlinkPath); | ||
|
||
const symlinkLstat = await lstat(symlinkPath); | ||
const fileStat = await stat(testFile); | ||
|
||
assert(symlinkLstat.isSymbolicLink); | ||
assert(fileStat.isFile); | ||
|
||
await tempFh.close(); | ||
await rm(tempDirPath, { recursive: true, force: true }); | ||
}); | ||
|
||
Deno.test("symlink() creates a link to a directory", async () => { | ||
const tempDirPath = await mkdtemp(resolve(tmpdir(), "symlink_")); | ||
const testDir = join(tempDirPath, "testDir"); | ||
const symlinkPath = join(tempDirPath, "testDir.link"); | ||
|
||
await mkdir(testDir); | ||
await symlink(testDir, symlinkPath); | ||
|
||
const symlinkLstat = await lstat(symlinkPath); | ||
const dirStat = await stat(testDir); | ||
|
||
assert(symlinkLstat.isSymbolicLink); | ||
assert(dirStat.isDirectory); | ||
|
||
await rm(tempDirPath, { recursive: true, force: true }); | ||
}); | ||
|
||
Deno.test( | ||
"symlink() rejects with AlreadyExists for creating the same link path to the same file path", | ||
async () => { | ||
const existingFile = join(testdataDir, "0.ts"); | ||
const existingSymlink = join(testdataDir, "0-link"); | ||
|
||
await assertRejects(async () => { | ||
await symlink(existingFile, existingSymlink); | ||
}, AlreadyExists); | ||
}, | ||
); | ||
|
||
Deno.test( | ||
"symlinkSync() creates a link to a regular file", | ||
() => { | ||
const tempDirPath = mkdtempSync(resolve(tmpdir(), "symlinkSync_")); | ||
const filePath = join(tempDirPath, "testFile.txt"); | ||
const symlinkPath = join(tempDirPath, "testFile.txt.link"); | ||
|
||
const tempFd = openSync(filePath, "w"); | ||
symlinkSync(filePath, symlinkPath); | ||
|
||
const symlinkLstat = lstatSync(symlinkPath); | ||
const fileStat = statSync(filePath); | ||
|
||
assert(symlinkLstat.isSymbolicLink); | ||
assert(fileStat.isFile); | ||
|
||
closeSync(tempFd); | ||
rmSync(tempDirPath, { recursive: true, force: true }); | ||
}, | ||
); | ||
|
||
Deno.test("symlinkSync() creates a link to a directory", () => { | ||
const tempDirPath = mkdtempSync(resolve(tmpdir(), "symlinkSync_")); | ||
const testDir = join(tempDirPath, "testDir"); | ||
const symlinkPath = join(tempDirPath, "testDir.link"); | ||
|
||
mkdirSync(testDir); | ||
symlinkSync(testDir, symlinkPath); | ||
|
||
const symlinkLstat = lstatSync(symlinkPath); | ||
const dirStat = statSync(testDir); | ||
|
||
assert(symlinkLstat.isSymbolicLink); | ||
assert(dirStat.isDirectory); | ||
|
||
rmSync(tempDirPath, { recursive: true, force: true }); | ||
}); | ||
|
||
Deno.test( | ||
"symlinkSync() throws with AlreadyExists for creating the same link path to the same file path", | ||
() => { | ||
const existingFile = join(testdataDir, "0.ts"); | ||
const existingSymlink = join(testdataDir, "0-link"); | ||
|
||
assertThrows(() => { | ||
symlinkSync(existingFile, existingSymlink); | ||
}, AlreadyExists); | ||
}, | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters