zkochan / packages

Zoltan Kochan's npm packages
MIT License
99 stars 26 forks source link

`realpath-missing`: Get real path of a non-existent folder #167

Open tukusejssirs opened 2 years ago

tukusejssirs commented 2 years ago

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

// 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'