Skip to content

Commit

Permalink
feat(fs/unstable): add readLink and readLinkSync (#6373)
Browse files Browse the repository at this point in the history
  • Loading branch information
jbronder authored Jan 31, 2025
1 parent ace6a0e commit d499310
Show file tree
Hide file tree
Showing 4 changed files with 156 additions and 0 deletions.
1 change: 1 addition & 0 deletions _tools/node_test_runner/run_test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ import "../../collections/without_all_test.ts";
import "../../collections/zip_test.ts";
import "../../fs/unstable_link_test.ts";
import "../../fs/unstable_read_dir_test.ts";
import "../../fs/unstable_read_link_test.ts";
import "../../fs/unstable_real_path_test.ts";
import "../../fs/unstable_stat_test.ts";
import "../../fs/unstable_symlink_test.ts";
Expand Down
1 change: 1 addition & 0 deletions fs/deno.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"./unstable-link": "./unstable_link.ts",
"./unstable-lstat": "./unstable_lstat.ts",
"./unstable-read-dir": "./unstable_read_dir.ts",
"./unstable-read-link": "./unstable_read_link.ts",
"./unstable-real-path": "./unstable_real_path.ts",
"./unstable-stat": "./unstable_stat.ts",
"./unstable-symlink": "./unstable_symlink.ts",
Expand Down
69 changes: 69 additions & 0 deletions fs/unstable_read_link.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// Copyright 2018-2025 the Deno authors. MIT license.

import { getNodeFs, isDeno } from "./_utils.ts";
import { mapError } from "./_map_error.ts";

/**
* Resolves to the path destination of the named symbolic link.
*
* Throws Error if called with a hard link.
*
* Requires `allow-read` permission.
*
* @example Usage
* ```ts ignore
* import { readLink } from "@std/fs/unstable-read-link";
* import { symlink } from "@std/fs/unstable-symlink";
* await symlink("./test.txt", "./test_link.txt");
* const target = await readLink("./test_link.txt"); // full path of ./test.txt
* ```
*
* @tags allow-read
*
* @param path The path of the symbolic link.
* @returns A promise that resolves to the file path pointed by the symbolic
* link.
*/
export async function readLink(path: string | URL): Promise<string> {
if (isDeno) {
return Deno.readLink(path);
} else {
try {
return await getNodeFs().promises.readlink(path);
} catch (error) {
throw mapError(error);
}
}
}

/**
* Synchronously returns the path destination of the named symbolic link.
*
* Throws Error if called with a hard link.
*
* Requires `allow-read` permission.
*
* @example Usage
* ```ts ignore
* import { readLinkSync } from "@std/fs/unstable-read-link";
* import { symlinkSync } from "@std/fs/unstable-symlink";
* symlinkSync("./test.txt", "./test_link.txt");
* const target = readLinkSync("./test_link.txt"); // full path of ./test.txt
* ```
*
* @tags allow-read
*
* @param path The path of the symbolic link.
* @returns The file path pointed by the symbolic link.
*/
export function readLinkSync(path: string | URL): string {
if (isDeno) {
return Deno.readLinkSync(path);
} else {
try {
return getNodeFs().readlinkSync(path);
} catch (error) {
throw mapError(error);
}
}
}
85 changes: 85 additions & 0 deletions fs/unstable_read_link_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// Copyright 2018-2025 the Deno authors. MIT license.

import { assertEquals, assertRejects, assertThrows } from "@std/assert";
import { readLink, readLinkSync } from "./unstable_read_link.ts";
import { NotFound } from "./unstable_errors.js";
import {
linkSync,
mkdtempSync,
rmSync,
symlinkSync,
writeFileSync,
} from "node:fs";
import { link, mkdtemp, rm, symlink, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join, resolve } from "node:path";

Deno.test("readLink() can read through symlink", async () => {
const tempDirPath = await mkdtemp(resolve(tmpdir(), "readLink_"));
const testFile = join(tempDirPath, "testFile.txt");
const symlinkFile = join(tempDirPath, "testFile.txt.link");

await writeFile(testFile, "Hello, Standard Library");
await symlink(testFile, symlinkFile);

const realFile = await readLink(symlinkFile);
assertEquals(testFile, realFile);

await rm(tempDirPath, { recursive: true, force: true });
});

Deno.test("readLink() rejects with Error when reading from a hard link", async () => {
const tempDirPath = await mkdtemp(resolve(tmpdir(), "readLink_"));
const testFile = join(tempDirPath, "testFile.txt");
const linkFile = join(tempDirPath, "testFile.txt.hlink");

await writeFile(testFile, "Hello, Standard Library");
await link(testFile, linkFile);

await assertRejects(async () => {
await readLink(linkFile);
}, Error);

await rm(tempDirPath, { recursive: true, force: true });
});

Deno.test("readLink() rejects with NotFound when reading through a non-existent file", async () => {
await assertRejects(async () => {
await readLink("non-existent-file.txt.link");
}, NotFound);
});

Deno.test("readLinkSync() can read through symlink", () => {
const tempDirPath = mkdtempSync(resolve(tmpdir(), "readLink_"));
const testFile = join(tempDirPath, "testFile.txt");
const symlinkFile = join(tempDirPath, "testFile.txt.link");

writeFileSync(testFile, "Hello, Standard Library");
symlinkSync(testFile, symlinkFile);

const realFile = readLinkSync(symlinkFile);
assertEquals(testFile, realFile);

rmSync(tempDirPath, { recursive: true, force: true });
});

Deno.test("readLinkSync() throws Error when reading from a hard link", () => {
const tempDirPath = mkdtempSync(resolve(tmpdir(), "readLinkSync_"));
const testFile = join(tempDirPath, "testFile.txt");
const linkFile = join(tempDirPath, "testFile.txt.hlink");

writeFileSync(testFile, "Hello, Standard Library!");
linkSync(testFile, linkFile);

assertThrows(() => {
readLinkSync(linkFile);
}, Error);

rmSync(tempDirPath, { recursive: true, force: true });
});

Deno.test("readLinkSync() throws NotFound when reading through a non-existent file", () => {
assertThrows(() => {
readLinkSync("non-existent-file.txt.hlink");
}, NotFound);
});

0 comments on commit d499310

Please sign in to comment.