From a4fa526ddcaf396e57f0f4b9f3201a9991081861 Mon Sep 17 00:00:00 2001 From: Livia Medeiros Date: Mon, 3 Oct 2022 10:04:28 +0900 Subject: [PATCH] fs: add directory autodetection to fsPromises.symlink() PR-URL: https://github.com/nodejs/node/pull/42894 Reviewed-By: Matteo Collina Reviewed-By: Antoine du Hamel --- doc/api/fs.md | 15 ++++++++++++--- lib/internal/fs/promises.js | 13 ++++++++++++- test/parallel/test-fs-symlink-dir.js | 15 +++++++++++++++ 3 files changed, 39 insertions(+), 4 deletions(-) diff --git a/doc/api/fs.md b/doc/api/fs.md index 7e3fbcb3f144eb..2ab9a711cb9631 100644 --- a/doc/api/fs.md +++ b/doc/api/fs.md @@ -1463,18 +1463,27 @@ changes: * `target` {string|Buffer|URL} * `path` {string|Buffer|URL} -* `type` {string} **Default:** `'file'` +* `type` {string|null} **Default:** `null` * Returns: {Promise} Fulfills with `undefined` upon success. Creates a symbolic link. The `type` argument is only used on Windows platforms and can be one of `'dir'`, -`'file'`, or `'junction'`. Windows junction points require the destination path -to be absolute. When using `'junction'`, the `target` argument will +`'file'`, or `'junction'`. If the `type` argument is not a string, Node.js will +autodetect `target` type and use `'file'` or `'dir'`. If the `target` does not +exist, `'file'` will be used. Windows junction points require the destination +path to be absolute. When using `'junction'`, the `target` argument will automatically be normalized to absolute path. ### `fsPromises.truncate(path[, len])` diff --git a/lib/internal/fs/promises.js b/lib/internal/fs/promises.js index 4f9dfb7dd0bd57..39f9cfc6c2e1c7 100644 --- a/lib/internal/fs/promises.js +++ b/lib/internal/fs/promises.js @@ -117,6 +117,8 @@ const { const getDirectoryEntriesPromise = promisify(getDirents); const validateRmOptionsPromise = promisify(validateRmOptions); +const isWindows = process.platform === 'win32'; + let cpPromises; function lazyLoadCpPromises() { return cpPromises ??= require('internal/fs/cp/cp').cpFn; @@ -714,7 +716,16 @@ async function readlink(path, options) { } async function symlink(target, path, type_) { - const type = (typeof type_ === 'string' ? type_ : null); + let type = (typeof type_ === 'string' ? type_ : null); + if (isWindows && type === null) { + try { + const absoluteTarget = pathModule.resolve(`${path}`, '..', `${target}`); + type = (await stat(absoluteTarget)).isDirectory() ? 'dir' : 'file'; + } catch { + // Default to 'file' if path is invalid or file does not exist + type = 'file'; + } + } target = getValidatedPath(target, 'target'); path = getValidatedPath(path); return binding.symlink(preprocessSymlinkDestination(target, type, path), diff --git a/test/parallel/test-fs-symlink-dir.js b/test/parallel/test-fs-symlink-dir.js index 012d34c301e835..7ce3039d4aa150 100644 --- a/test/parallel/test-fs-symlink-dir.js +++ b/test/parallel/test-fs-symlink-dir.js @@ -12,6 +12,7 @@ if (!common.canCreateSymLink()) const assert = require('assert'); const path = require('path'); const fs = require('fs'); +const fsPromises = fs.promises; const tmpdir = require('../common/tmpdir'); tmpdir.refresh(); @@ -36,11 +37,18 @@ function testAsync(target, path) { })); } +async function testPromises(target, path) { + await fsPromises.symlink(target, path); + fs.readdirSync(path); +} + for (const linkTarget of linkTargets) { fs.mkdirSync(path.resolve(tmpdir.path, linkTarget)); for (const linkPath of linkPaths) { testSync(linkTarget, `${linkPath}-${path.basename(linkTarget)}-sync`); testAsync(linkTarget, `${linkPath}-${path.basename(linkTarget)}-async`); + testPromises(linkTarget, `${linkPath}-${path.basename(linkTarget)}-promises`) + .then(common.mustCall()); } } @@ -57,10 +65,17 @@ for (const linkTarget of linkTargets) { })); } + async function testPromises(target, path) { + await fsPromises.symlink(target, path); + assert(!fs.existsSync(path)); + } + for (const linkTarget of linkTargets.map((p) => p + '-broken')) { for (const linkPath of linkPaths) { testSync(linkTarget, `${linkPath}-${path.basename(linkTarget)}-sync`); testAsync(linkTarget, `${linkPath}-${path.basename(linkTarget)}-async`); + testPromises(linkTarget, `${linkPath}-${path.basename(linkTarget)}-promises`) + .then(common.mustCall()); } } }