From 93e0cd64cc9638ac55fd129953d74c308b98fd60 Mon Sep 17 00:00:00 2001 From: Yoshiya Hinosawa Date: Thu, 21 Nov 2024 23:44:00 +0900 Subject: [PATCH] fix(fs): improve the docs and error message of `ensureSymlink(Sync)` (#6198) --- fs/ensure_symlink.ts | 54 +++++++++++++++++++++++++++++++++------ fs/ensure_symlink_test.ts | 31 +++++++++++++++++++++- 2 files changed, 76 insertions(+), 9 deletions(-) diff --git a/fs/ensure_symlink.ts b/fs/ensure_symlink.ts index f2dec2cd9ea7..10b747d59130 100644 --- a/fs/ensure_symlink.ts +++ b/fs/ensure_symlink.ts @@ -36,16 +36,25 @@ function getSymlinkOption( * @see {@link https://docs.deno.com/runtime/manual/basics/permissions#file-system-access} * for more information on Deno's permissions system. * - * @param target The source file path as a string or URL. + * @param target The source file path as a string or URL. If it is a relative path string, it have to be relative to the link path. * @param linkName The destination link path as a string or URL. * * @returns A void promise that resolves once the link exists. * - * @example Usage + * @example Basic usage * ```ts ignore * import { ensureSymlink } from "@std/fs/ensure-symlink"; * - * await ensureSymlink("./folder/targetFile.dat", "./folder/targetFile.link.dat"); + * // Ensures the link `./targetFile.link.dat` exists and points to `./targetFile.dat` + * await ensureSymlink("./targetFile.dat", "./targetFile.link.dat"); + * ``` + * + * @example Ensuring a link in a folder + * ```ts ignore + * import { ensureSymlink } from "@std/fs/ensure-symlink"; + * + * // Ensures the link `./folder/targetFile.link.dat` exists and points to `./folder/targetFile.dat` + * await ensureSymlink("./targetFile.dat", "./folder/targetFile.link.dat"); * ``` */ export async function ensureSymlink( @@ -53,7 +62,17 @@ export async function ensureSymlink( linkName: string | URL, ) { const targetRealPath = resolveSymlinkTarget(target, linkName); - const srcStatInfo = await Deno.lstat(targetRealPath); + let srcStatInfo; + try { + srcStatInfo = await Deno.lstat(targetRealPath); + } catch (error) { + if (error instanceof Deno.errors.NotFound) { + throw new Deno.errors.NotFound( + `Cannot ensure symlink as the target path does not exist: ${targetRealPath}`, + ); + } + throw error; + } const srcFilePathType = getFileInfoType(srcStatInfo); await ensureDir(dirname(toPathString(linkName))); @@ -96,15 +115,24 @@ export async function ensureSymlink( * @see {@link https://docs.deno.com/runtime/manual/basics/permissions#file-system-access} * for more information on Deno's permissions system. * - * @param target The source file path as a string or URL. + * @param target The source file path as a string or URL. If it is a relative path string, it have to be relative to the link path. * @param linkName The destination link path as a string or URL. * @returns A void value that returns once the link exists. * - * @example Usage + * @example Basic usage * ```ts ignore * import { ensureSymlinkSync } from "@std/fs/ensure-symlink"; * - * ensureSymlinkSync("./folder/targetFile.dat", "./folder/targetFile.link.dat"); + * // Ensures the link `./targetFile.link.dat` exists and points to `./targetFile.dat` + * ensureSymlinkSync("./targetFile.dat", "./targetFile.link.dat"); + * ``` + * + * @example Ensuring a link in a folder + * ```ts ignore + * import { ensureSymlinkSync } from "@std/fs/ensure-symlink"; + * + * // Ensures the link `./folder/targetFile.link.dat` exists and points to `./folder/targetFile.dat` + * ensureSymlinkSync("./targetFile.dat", "./folder/targetFile.link.dat"); * ``` */ export function ensureSymlinkSync( @@ -112,7 +140,17 @@ export function ensureSymlinkSync( linkName: string | URL, ) { const targetRealPath = resolveSymlinkTarget(target, linkName); - const srcStatInfo = Deno.lstatSync(targetRealPath); + let srcStatInfo; + try { + srcStatInfo = Deno.lstatSync(targetRealPath); + } catch (error) { + if (error instanceof Deno.errors.NotFound) { + throw new Deno.errors.NotFound( + `Cannot ensure symlink as the target path does not exist: ${targetRealPath}`, + ); + } + throw error; + } const srcFilePathType = getFileInfoType(srcStatInfo); ensureDirSync(dirname(toPathString(linkName))); diff --git a/fs/ensure_symlink_test.ts b/fs/ensure_symlink_test.ts index 9bf4c4f2212c..be437f0acd31 100644 --- a/fs/ensure_symlink_test.ts +++ b/fs/ensure_symlink_test.ts @@ -1,6 +1,12 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. // TODO(axetroy): Add test for Windows once symlink is implemented for Windows. -import { assert, assertEquals, assertRejects, assertThrows } from "@std/assert"; +import { + assert, + assertEquals, + assertMatch, + assertRejects, + assertThrows, +} from "@std/assert"; import * as path from "@std/path"; import { ensureSymlink, ensureSymlinkSync } from "./ensure_symlink.ts"; import { IS_DENO_2 } from "../internal/_is_deno_2.ts"; @@ -239,6 +245,29 @@ Deno.test("ensureSymlinkSync() creates symlink with relative target", function ( Deno.removeSync(testDir, { recursive: true }); }); +Deno.test("ensureSymlink() rejects when the target path doesn't exist", async () => { + const e = await assertRejects( + async () => { + await ensureSymlink("non-existent-target", "non-existent-link"); + }, + Deno.errors.NotFound, + ); + assertMatch( + e.message, + /^Cannot ensure symlink as the target path does not exist: .*non-existent-target$/, + ); +}); + +Deno.test("ensureSymlinkSync() throws when the target path doesn't exist", () => { + const e = assertThrows(() => { + ensureSymlinkSync("non-existent-target", "non-existent-link"); + }, Deno.errors.NotFound); + assertMatch( + e.message, + /^Cannot ensure symlink as the target path does not exist: .*non-existent-target$/, + ); +}); + Deno.test("ensureSymlink() works with URLs", { // TODO(kt3k): The 2nd test case doesn't pass on Windows. Fix it. ignore: Deno.build.os === "windows",