diff options
-rw-r--r-- | cli/js/lib.deno.unstable.d.ts | 28 | ||||
-rw-r--r-- | cli/js/ops/fs/symlink.ts | 20 | ||||
-rw-r--r-- | cli/js/tests/remove_test.ts | 128 | ||||
-rw-r--r-- | cli/js/tests/symlink_test.ts | 62 | ||||
-rw-r--r-- | cli/ops/fs.rs | 40 | ||||
-rw-r--r-- | std/fs/copy.ts | 19 | ||||
-rw-r--r-- | std/fs/copy_test.ts | 35 | ||||
-rw-r--r-- | std/fs/ensure_symlink.ts | 18 | ||||
-rw-r--r-- | std/fs/ensure_symlink_test.ts | 49 |
9 files changed, 155 insertions, 244 deletions
diff --git a/cli/js/lib.deno.unstable.d.ts b/cli/js/lib.deno.unstable.d.ts index d27a3da9e..38260c8ff 100644 --- a/cli/js/lib.deno.unstable.d.ts +++ b/cli/js/lib.deno.unstable.d.ts @@ -41,48 +41,44 @@ declare namespace Deno { * Requires `allow-read` and `allow-write` permissions. */ export function link(oldpath: string, newpath: string): Promise<void>; - /** **UNSTABLE**: `type` argument type may be changed to `"dir" | "file"`. - * - * **UNSTABLE**: needs security review. + export type SymlinkOptions = { + type: "file" | "dir"; + }; + + /** **UNSTABLE**: needs security review. * * Creates `newpath` as a symbolic link to `oldpath`. * - * The type argument can be set to `dir` or `file`. This argument is only + * The options.type parameter can be set to `file` or `dir`. This argument is only * available on Windows and ignored on other platforms. * - * NOTE: This function is not yet implemented on Windows. - * * ```ts * Deno.symlinkSync("old/name", "new/name"); * ``` * - * Requires `allow-read` and `allow-write` permissions. */ + * Requires `allow-write` permission. */ export function symlinkSync( oldpath: string, newpath: string, - type?: string + options?: SymlinkOptions ): void; - /** **UNSTABLE**: `type` argument may be changed to `"dir" | "file"` - * - * **UNSTABLE**: needs security review. + /** **UNSTABLE**: needs security review. * * Creates `newpath` as a symbolic link to `oldpath`. * - * The type argument can be set to `dir` or `file`. This argument is only + * The options.type parameter can be set to `file` or `dir`. This argument is only * available on Windows and ignored on other platforms. * - * NOTE: This function is not yet implemented on Windows. - * * ```ts * await Deno.symlink("old/name", "new/name"); * ``` * - * Requires `allow-read` and `allow-write` permissions. */ + * Requires `allow-write` permission. */ export function symlink( oldpath: string, newpath: string, - type?: string + options?: SymlinkOptions ): Promise<void>; /** **UNSTABLE** */ diff --git a/cli/js/ops/fs/symlink.ts b/cli/js/ops/fs/symlink.ts index 64074ec2d..fde611b55 100644 --- a/cli/js/ops/fs/symlink.ts +++ b/cli/js/ops/fs/symlink.ts @@ -1,26 +1,22 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. import { sendSync, sendAsync } from "../dispatch_json.ts"; -import * as util from "../../util.ts"; -import { build } from "../../build.ts"; + +export type symlinkOptions = { + type: "file" | "dir"; +}; export function symlinkSync( oldpath: string, newpath: string, - type?: string + options?: symlinkOptions ): void { - if (build.os === "windows" && type) { - return util.notImplemented(); - } - sendSync("op_symlink", { oldpath, newpath }); + sendSync("op_symlink", { oldpath, newpath, options }); } export async function symlink( oldpath: string, newpath: string, - type?: string + options?: symlinkOptions ): Promise<void> { - if (build.os === "windows" && type) { - return util.notImplemented(); - } - await sendAsync("op_symlink", { oldpath, newpath }); + await sendAsync("op_symlink", { oldpath, newpath, options }); } diff --git a/cli/js/tests/remove_test.ts b/cli/js/tests/remove_test.ts index 8de577838..35e5c821e 100644 --- a/cli/js/tests/remove_test.ts +++ b/cli/js/tests/remove_test.ts @@ -83,27 +83,23 @@ unitTest( { perms: { write: true, read: true } }, function removeSyncDanglingSymlinkSuccess(): void { const danglingSymlinkPath = Deno.makeTempDirSync() + "/dangling_symlink"; - // TODO(#3832): Remove "not Implemented" error checking when symlink creation is implemented for Windows - let errOnWindows; - try { - Deno.symlinkSync("unexistent_file", danglingSymlinkPath); - } catch (err) { - errOnWindows = err; - } if (Deno.build.os === "windows") { - assertEquals(errOnWindows.message, "not implemented"); + Deno.symlinkSync("unexistent_file", danglingSymlinkPath, { + type: "file", + }); } else { - const pathInfo = Deno.lstatSync(danglingSymlinkPath); - assert(pathInfo.isSymlink); - Deno.removeSync(danglingSymlinkPath); - let err; - try { - Deno.lstatSync(danglingSymlinkPath); - } catch (e) { - err = e; - } - assert(err instanceof Deno.errors.NotFound); + Deno.symlinkSync("unexistent_file", danglingSymlinkPath); + } + const pathInfo = Deno.lstatSync(danglingSymlinkPath); + assert(pathInfo.isSymlink); + Deno.removeSync(danglingSymlinkPath); + let err; + try { + Deno.lstatSync(danglingSymlinkPath); + } catch (e) { + err = e; } + assert(err instanceof Deno.errors.NotFound); } ); @@ -116,28 +112,22 @@ unitTest( const filePath = tempDir + "/test.txt"; const validSymlinkPath = tempDir + "/valid_symlink"; Deno.writeFileSync(filePath, data, { mode: 0o666 }); - // TODO(#3832): Remove "not Implemented" error checking when symlink creation is implemented for Windows - let errOnWindows; - try { - Deno.symlinkSync(filePath, validSymlinkPath); - } catch (err) { - errOnWindows = err; - } if (Deno.build.os === "windows") { - assertEquals(errOnWindows.message, "not implemented"); + Deno.symlinkSync(filePath, validSymlinkPath, { type: "file" }); } else { - const symlinkPathInfo = Deno.statSync(validSymlinkPath); - assert(symlinkPathInfo.isFile); - Deno.removeSync(validSymlinkPath); - let err; - try { - Deno.statSync(validSymlinkPath); - } catch (e) { - err = e; - } - Deno.removeSync(filePath); - assert(err instanceof Deno.errors.NotFound); + Deno.symlinkSync(filePath, validSymlinkPath); + } + const symlinkPathInfo = Deno.statSync(validSymlinkPath); + assert(symlinkPathInfo.isFile); + Deno.removeSync(validSymlinkPath); + let err; + try { + Deno.statSync(validSymlinkPath); + } catch (e) { + err = e; } + Deno.removeSync(filePath); + assert(err instanceof Deno.errors.NotFound); } ); @@ -319,27 +309,23 @@ unitTest( { perms: { write: true, read: true } }, async function removeDanglingSymlinkSuccess(): Promise<void> { const danglingSymlinkPath = Deno.makeTempDirSync() + "/dangling_symlink"; - // TODO(#3832): Remove "not Implemented" error checking when symlink creation is implemented for Windows - let errOnWindows; - try { - Deno.symlinkSync("unexistent_file", danglingSymlinkPath); - } catch (e) { - errOnWindows = e; - } if (Deno.build.os === "windows") { - assertEquals(errOnWindows.message, "not implemented"); + Deno.symlinkSync("unexistent_file", danglingSymlinkPath, { + type: "file", + }); } else { - const pathInfo = Deno.lstatSync(danglingSymlinkPath); - assert(pathInfo.isSymlink); - await Deno.remove(danglingSymlinkPath); - let err; - try { - Deno.lstatSync(danglingSymlinkPath); - } catch (e) { - err = e; - } - assert(err instanceof Deno.errors.NotFound); + Deno.symlinkSync("unexistent_file", danglingSymlinkPath); + } + const pathInfo = Deno.lstatSync(danglingSymlinkPath); + assert(pathInfo.isSymlink); + await Deno.remove(danglingSymlinkPath); + let err; + try { + Deno.lstatSync(danglingSymlinkPath); + } catch (e) { + err = e; } + assert(err instanceof Deno.errors.NotFound); } ); @@ -352,28 +338,22 @@ unitTest( const filePath = tempDir + "/test.txt"; const validSymlinkPath = tempDir + "/valid_symlink"; Deno.writeFileSync(filePath, data, { mode: 0o666 }); - // TODO(#3832): Remove "not Implemented" error checking when symlink creation is implemented for Windows - let errOnWindows; - try { - Deno.symlinkSync(filePath, validSymlinkPath); - } catch (e) { - errOnWindows = e; - } if (Deno.build.os === "windows") { - assertEquals(errOnWindows.message, "not implemented"); + Deno.symlinkSync(filePath, validSymlinkPath, { type: "file" }); } else { - const symlinkPathInfo = Deno.statSync(validSymlinkPath); - assert(symlinkPathInfo.isFile); - await Deno.remove(validSymlinkPath); - let err; - try { - Deno.statSync(validSymlinkPath); - } catch (e) { - err = e; - } - Deno.removeSync(filePath); - assert(err instanceof Deno.errors.NotFound); + Deno.symlinkSync(filePath, validSymlinkPath); + } + const symlinkPathInfo = Deno.statSync(validSymlinkPath); + assert(symlinkPathInfo.isFile); + await Deno.remove(validSymlinkPath); + let err; + try { + Deno.statSync(validSymlinkPath); + } catch (e) { + err = e; } + Deno.removeSync(filePath); + assert(err instanceof Deno.errors.NotFound); } ); diff --git a/cli/js/tests/symlink_test.ts b/cli/js/tests/symlink_test.ts index 681ace1db..505a49bab 100644 --- a/cli/js/tests/symlink_test.ts +++ b/cli/js/tests/symlink_test.ts @@ -8,22 +8,12 @@ unitTest( const oldname = testDir + "/oldname"; const newname = testDir + "/newname"; Deno.mkdirSync(oldname); - let errOnWindows; // Just for now, until we implement symlink for Windows. - try { - Deno.symlinkSync(oldname, newname); - } catch (e) { - errOnWindows = e; - } - if (errOnWindows) { - assertEquals(Deno.build.os, "windows"); - assertEquals(errOnWindows.message, "not implemented"); - } else { - const newNameInfoLStat = Deno.lstatSync(newname); - const newNameInfoStat = Deno.statSync(newname); - assert(newNameInfoLStat.isSymlink); - assert(newNameInfoStat.isDirectory); - } + Deno.symlinkSync(oldname, newname); + const newNameInfoLStat = Deno.lstatSync(newname); + const newNameInfoStat = Deno.statSync(newname); + assert(newNameInfoLStat.isSymlink); + assert(newNameInfoStat.isDirectory); } ); @@ -38,28 +28,6 @@ unitTest(function symlinkSyncPerm(): void { assertEquals(err.name, "PermissionDenied"); }); -// Just for now, until we implement symlink for Windows. -// Symlink with type should succeed on other platforms with type ignored -unitTest( - { perms: { write: true } }, - function symlinkSyncNotImplemented(): void { - const testDir = Deno.makeTempDirSync(); - const oldname = testDir + "/oldname"; - const newname = testDir + "/newname"; - let err; - try { - Deno.symlinkSync(oldname, newname, "dir"); - } catch (e) { - err = e; - } - if (err) { - assertEquals(Deno.build.os, "windows"); - // from cli/js/util.ts:notImplemented - assertEquals(err.message, "not implemented"); - } - } -); - unitTest( { perms: { read: true, write: true } }, async function symlinkSuccess(): Promise<void> { @@ -67,20 +35,10 @@ unitTest( const oldname = testDir + "/oldname"; const newname = testDir + "/newname"; Deno.mkdirSync(oldname); - let errOnWindows; - // Just for now, until we implement symlink for Windows. - try { - await Deno.symlink(oldname, newname); - } catch (e) { - errOnWindows = e; - } - if (errOnWindows) { - assertEquals(errOnWindows.message, "not implemented"); - } else { - const newNameInfoLStat = Deno.lstatSync(newname); - const newNameInfoStat = Deno.statSync(newname); - assert(newNameInfoLStat.isSymlink); - assert(newNameInfoStat.isDirectory); - } + await Deno.symlink(oldname, newname); + const newNameInfoLStat = Deno.lstatSync(newname); + const newNameInfoStat = Deno.statSync(newname); + assert(newNameInfoLStat.isSymlink, "NOT SYMLINK"); + assert(newNameInfoStat.isDirectory, "NOT DIRECTORY"); } ); diff --git a/cli/ops/fs.rs b/cli/ops/fs.rs index 068dbaf7e..9d5be3077 100644 --- a/cli/ops/fs.rs +++ b/cli/ops/fs.rs @@ -687,6 +687,15 @@ struct SymlinkArgs { promise_id: Option<u64>, oldpath: String, newpath: String, + #[cfg(not(unix))] + options: Option<SymlinkOptions>, +} + +#[cfg(not(unix))] +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct SymlinkOptions { + _type: String, } fn op_symlink( @@ -710,13 +719,34 @@ fn op_symlink( symlink(&oldpath, &newpath)?; Ok(json!({})) } - // TODO Implement symlink, use type for Windows #[cfg(not(unix))] { - // Unlike with chmod/chown, here we don't - // require `oldpath` to exist on Windows - let _ = oldpath; // avoid unused warning - Err(OpError::not_implemented()) + use std::os::windows::fs::{symlink_dir, symlink_file}; + + match args.options { + Some(options) => match options._type.as_ref() { + "file" => symlink_file(&oldpath, &newpath)?, + "dir" => symlink_dir(&oldpath, &newpath)?, + _ => return Err(OpError::type_error("unsupported type".to_string())), + }, + None => { + let old_meta = std::fs::metadata(&oldpath); + match old_meta { + Ok(metadata) => { + if metadata.is_file() { + symlink_file(&oldpath, &newpath)? + } else if metadata.is_dir() { + symlink_dir(&oldpath, &newpath)? + } + } + Err(_) => return Err(OpError::type_error( + "you must pass a `options` argument for non-existent target path in windows" + .to_string(), + )), + } + } + }; + Ok(json!({})) } }) } diff --git a/std/fs/copy.ts b/std/fs/copy.ts index 8ebec3ae1..d45ac17c9 100644 --- a/std/fs/copy.ts +++ b/std/fs/copy.ts @@ -4,6 +4,8 @@ import { ensureDir, ensureDirSync } from "./ensure_dir.ts"; import { isSubdir, getFileInfoType } from "./_util.ts"; import { assert } from "../testing/asserts.ts"; +const isWindows = Deno.build.os === "windows"; + export interface CopyOptions { /** * overwrite existing file or directory. Default is `false` @@ -111,7 +113,13 @@ async function copySymLink( await ensureValidCopy(src, dest, options); const originSrcFilePath = await Deno.readLink(src); const type = getFileInfoType(await Deno.lstat(src)); - await Deno.symlink(originSrcFilePath, dest, type); + if (isWindows) { + await Deno.symlink(originSrcFilePath, dest, { + type: type === "dir" ? "dir" : "file", + }); + } else { + await Deno.symlink(originSrcFilePath, dest); + } if (options.preserveTimestamps) { const statInfo = await Deno.lstat(src); assert(statInfo.atime instanceof Date, `statInfo.atime is unavailable`); @@ -129,7 +137,14 @@ function copySymlinkSync( ensureValidCopySync(src, dest, options); const originSrcFilePath = Deno.readLinkSync(src); const type = getFileInfoType(Deno.lstatSync(src)); - Deno.symlinkSync(originSrcFilePath, dest, type); + if (isWindows) { + Deno.symlinkSync(originSrcFilePath, dest, { + type: type === "dir" ? "dir" : "file", + }); + } else { + Deno.symlinkSync(originSrcFilePath, dest); + } + if (options.preserveTimestamps) { const statInfo = Deno.lstatSync(src); assert(statInfo.atime instanceof Date, `statInfo.atime is unavailable`); diff --git a/std/fs/copy_test.ts b/std/fs/copy_test.ts index 1f3330226..cb97d4ba7 100644 --- a/std/fs/copy_test.ts +++ b/std/fs/copy_test.ts @@ -14,9 +14,6 @@ import { ensureSymlink, ensureSymlinkSync } from "./ensure_symlink.ts"; const testdataDir = path.resolve("fs", "testdata"); -// TODO(axetroy): Add test for Windows once symlink is implemented for Windows. -const isWindows = Deno.build.os === "windows"; - function testCopy(name: string, cb: (tempDir: string) => Promise<void>): void { Deno.test({ name, @@ -257,14 +254,6 @@ testCopy( const srcLink = path.join(dir, "0.txt"); const destLink = path.join(tempDir, "0_copy.txt"); - if (isWindows) { - await assertThrowsAsync( - // (): Promise<void> => copy(srcLink, destLink), - (): Promise<void> => ensureSymlink(srcLink, destLink) - ); - return; - } - assert( (await Deno.lstat(srcLink)).isSymlink, `'${srcLink}' should be symlink type` @@ -285,14 +274,6 @@ testCopy( const srcLink = path.join(tempDir, "copy_dir_link"); const destLink = path.join(tempDir, "copy_dir_link_copy"); - if (isWindows) { - await assertThrowsAsync( - // (): Promise<void> => copy(srcLink, destLink), - (): Promise<void> => ensureSymlink(srcLink, destLink) - ); - return; - } - await ensureSymlink(srcDir, srcLink); assert( @@ -497,14 +478,6 @@ testCopySync( const srcLink = path.join(dir, "0.txt"); const destLink = path.join(tempDir, "0_copy.txt"); - if (isWindows) { - assertThrows( - // (): void => copySync(srcLink, destLink), - (): void => ensureSymlinkSync(srcLink, destLink) - ); - return; - } - assert( Deno.lstatSync(srcLink).isSymlink, `'${srcLink}' should be symlink type` @@ -525,14 +498,6 @@ testCopySync( const srcLink = path.join(tempDir, "copy_dir_link"); const destLink = path.join(tempDir, "copy_dir_link_copy"); - if (isWindows) { - assertThrows( - // (): void => copySync(srcLink, destLink), - (): void => ensureSymlinkSync(srcLink, destLink) - ); - return; - } - ensureSymlinkSync(originDir, srcLink); assert( diff --git a/std/fs/ensure_symlink.ts b/std/fs/ensure_symlink.ts index 2a184bb4f..a07f97220 100644 --- a/std/fs/ensure_symlink.ts +++ b/std/fs/ensure_symlink.ts @@ -28,7 +28,14 @@ export async function ensureSymlink(src: string, dest: string): Promise<void> { await ensureDir(path.dirname(dest)); - await Deno.symlink(src, dest, srcFilePathType); + ensureDirSync(path.dirname(dest)); + if (Deno.build.os === "windows") { + await Deno.symlink(src, dest, { + type: srcFilePathType === "dir" ? "dir" : "file", + }); + } else { + await Deno.symlink(src, dest); + } } /** @@ -54,6 +61,11 @@ export function ensureSymlinkSync(src: string, dest: string): void { } ensureDirSync(path.dirname(dest)); - - Deno.symlinkSync(src, dest, srcFilePathType); + if (Deno.build.os === "windows") { + Deno.symlinkSync(src, dest, { + type: srcFilePathType === "dir" ? "dir" : "file", + }); + } else { + Deno.symlinkSync(src, dest); + } } diff --git a/std/fs/ensure_symlink_test.ts b/std/fs/ensure_symlink_test.ts index 5fda1c86a..d90495cbf 100644 --- a/std/fs/ensure_symlink_test.ts +++ b/std/fs/ensure_symlink_test.ts @@ -9,7 +9,6 @@ import * as path from "../path/mod.ts"; import { ensureSymlink, ensureSymlinkSync } from "./ensure_symlink.ts"; const testdataDir = path.resolve("fs", "testdata"); -const isWindows = Deno.build.os === "windows"; Deno.test("ensureSymlinkIfItNotExist", async function (): Promise<void> { const testDir = path.join(testdataDir, "link_file_1"); @@ -52,17 +51,7 @@ Deno.test("ensureSymlinkIfItExist", async function (): Promise<void> { await Deno.mkdir(testDir, { recursive: true }); await Deno.writeFile(testFile, new Uint8Array()); - if (isWindows) { - await assertThrowsAsync( - (): Promise<void> => ensureSymlink(testFile, linkFile), - Error, - "not implemented" - ); - await Deno.remove(testDir, { recursive: true }); - return; - } else { - await ensureSymlink(testFile, linkFile); - } + await ensureSymlink(testFile, linkFile); const srcStat = await Deno.lstat(testFile); const linkStat = await Deno.lstat(linkFile); @@ -81,17 +70,7 @@ Deno.test("ensureSymlinkSyncIfItExist", function (): void { Deno.mkdirSync(testDir, { recursive: true }); Deno.writeFileSync(testFile, new Uint8Array()); - if (isWindows) { - assertThrows( - (): void => ensureSymlinkSync(testFile, linkFile), - Error, - "not implemented" - ); - Deno.removeSync(testDir, { recursive: true }); - return; - } else { - ensureSymlinkSync(testFile, linkFile); - } + ensureSymlinkSync(testFile, linkFile); const srcStat = Deno.lstatSync(testFile); @@ -111,17 +90,7 @@ Deno.test("ensureSymlinkDirectoryIfItExist", async function (): Promise<void> { await Deno.mkdir(testDir, { recursive: true }); await Deno.writeFile(testFile, new Uint8Array()); - if (isWindows) { - await assertThrowsAsync( - (): Promise<void> => ensureSymlink(testDir, linkDir), - Error, - "not implemented" - ); - await Deno.remove(testDir, { recursive: true }); - return; - } else { - await ensureSymlink(testDir, linkDir); - } + await ensureSymlink(testDir, linkDir); const testDirStat = await Deno.lstat(testDir); const linkDirStat = await Deno.lstat(linkDir); @@ -143,17 +112,7 @@ Deno.test("ensureSymlinkSyncDirectoryIfItExist", function (): void { Deno.mkdirSync(testDir, { recursive: true }); Deno.writeFileSync(testFile, new Uint8Array()); - if (isWindows) { - assertThrows( - (): void => ensureSymlinkSync(testDir, linkDir), - Error, - "not implemented" - ); - Deno.removeSync(testDir, { recursive: true }); - return; - } else { - ensureSymlinkSync(testDir, linkDir); - } + ensureSymlinkSync(testDir, linkDir); const testDirStat = Deno.lstatSync(testDir); const linkDirStat = Deno.lstatSync(linkDir); |