I have no idea if I got it right, but as I understood it, realpath-missing should return a path even for folders that does not exist (is missing), however, looking at the code, it is not the case.
I was looking for alternative of realpath from node:fs/promises which does not check for folder existence (like when we need to get realpath of a folder before its creation in case it does not exist yet), so I created the following function (using node:fs/promises). Are you interested in pulling the code into you package? We could add a configuration parameter if we should check if the path exists (simply run and return the original await realpath(path)) or not (run the function below).
Note that I wrote it in TypeScript.
import {realpath as rp} from 'node:fs/promises'
/**
* Get real path of a folder regardless if the folder exists
*
* @param path - Folder path to check
* @param append - Folders to append
* @returns Real path of the folder
*/
async function realpath({path, append}) {
if (/^\//.test(append)) {
path = append
append = undefined
}
if (/^\.\//.test(append)) {
append = append.replace(/^\.\//g, '')
}
path = path ? path.replace(/\/*$/, '') : path
path = path ? path.replace(/\/\/+/, '/') : path
append = append ? append.replace(/\/*$/, '') : append
append = append ? append.replace(/\/\/+/, '/') : append
while (/^\.\.\//.test(append)) {
const newPath = path ? path.replace(/\/[^/]*$/, '') : ''
if (path && newPath) {
path = newPath
} else if (path !== '/' && !newPath) {
path = '/'
} else {
throw Error(`Invalid path or append value provided.`)
}
append = append.replace(/^\.\.\//g, '')
}
if (!path) {
path = '/'
}
try {
return `${await rp(path)}${/^[^/]$/.test(path) && append ? '/' : ''}${append ? append : ''}`
} catch (e) {
if (e.code === 'ENOENT') {
let newPath = path.replace(/\/[^/]+$/, '')
if (newPath === path) {
if (path === '.') {
throw Error(e)
} else if (/^[^/]+$/.test(path)) {
const newAppend = `${path}${append ? `/${append}` : ''}`
return await this.realpath({path: '.', append: newAppend})
}
} else {
const newAppend = `${path && path !== '/' ? path.match(/[^/]+$/g)[0] : ''}${append ? `/${append}` : ''}`
return await this.realpath({path: newPath, append: newAppend})
}
}
throw Error(e)
}
}
Update
I added TSDoc.
I added many checks of path and append values in order to make the function robust, including:
if append starts with a slash, then replace path with append value;
if append starts with ./, remove these characters;
if append start with ../, remove the final folder from path:
I didn’t use join from node:path, as running join('/foo/bar', '../../../baz') results in /baz, but IMHO it should throw an error;
remove trailing slashes from append and path;
replace multiple consequent slashes with one slash;
test cases:
// Test existing relative paths
realpath({path: '.'}) // '${PWD}'
realpath({path: './src'}) // '${PWD}/src'
realpath({path: 'src'}) // '${PWD}/src'
// Test existing absolute paths
realpath({path: '/etc'}) // '/etc'
realpath({path: '/'}) // '/'
// Test non-existent relative paths
realpath({path: 'asdf'}) // '${PWD}/asdf'
realpath({path: './asdf'}) // '${PWD}/asdf'
realpath({path: 'asdf/qwer'}) // '${PWD}/asdf/qwer'
realpath({path: './asdf/qwer'}) // '${PWD}/asdf/qwer'
// Test combining `append` with `../`
realpath({path: '/qwer/asdf/xzcv/yuoi', append: '../../../asdf'}) // '/qwer/asdf'
realpath({path: '/qwer/asdf/xzcv', append: '../../../asdf'}) // '/asdf'
realpath({path: '/qwer/asdf', append: '../../../asdf'}) // error
// Test some special cases
// - trailing slashes are removed
realpath({path: '/qwer/asdf/xzcv////', append: '../../../asdf////'}) // '/asdf'
// - multiple consequent slashes should be replaced with a slash
realpath({path: '/qwer/asdf////xzcv', append: '../../////../asdf'}) // '/asdf'
// - falsy `path` results in `/`
realpath({path: '', append: 'asdf'}) // '/asdf'
realpath({path: undefined, append: 'asdf'}) // '/asdf'
realpath({path: null, append: 'asdf'}) // '/asdf'
I have no idea if I got it right, but as I understood it,
realpath-missing
should return a path even for folders that does not exist (is missing), however, looking at the code, it is not the case.I was looking for alternative of
realpath
fromnode:fs/promises
which does not check for folder existence (like when we need to get realpath of a folder before its creation in case it does not exist yet), so I created the following function (usingnode:fs/promises
). Are you interested in pulling the code into you package? We could add a configuration parameter if we should check if the path exists (simply run and return the originalawait realpath(path)
) or not (run the function below).Note that I wrote it in TypeScript.
Update
path
andappend
values in order to make the function robust, including:append
starts with a slash, then replacepath
withappend
value;append
starts with./
, remove these characters;append
start with../
, remove the final folder frompath
:join
fromnode:path
, as runningjoin('/foo/bar', '../../../baz')
results in/baz
, but IMHO it should throw an error;append
andpath
;