Skip to content

Commit

Permalink
feat: port @npmcli/move-file (#63)
Browse files Browse the repository at this point in the history
Closes #56
  • Loading branch information
lukekarrys authored Nov 3, 2022
1 parent 1268710 commit ecbb150
Show file tree
Hide file tree
Showing 4 changed files with 388 additions and 0 deletions.
38 changes: 38 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ polyfills, and extensions, of the core `fs` module.
- `fs.cp` polyfill for node < 16.7.0
- `fs.withTempDir` added
- `fs.readdirScoped` added
- `fs.moveFile` added

## `fs.withTempDir(root, fn, options) -> Promise`

Expand Down Expand Up @@ -57,3 +58,40 @@ const readdir = require('readdir-scoped-modules')
const entries = await readdir('node_modules')
// entries will be something like: ['a', '@org/foo', '@org/bar']
```

## `fs.moveFile(source, dest, options) -> Promise`

A fork of [move-file](https://github.com/sindresorhus/move-file) with
support for Common JS.

### Highlights

- Promise API.
- Supports moving a file across partitions and devices.
- Optionally prevent overwriting an existing file.
- Creates non-existent destination directories for you.
- Automatically recurses when source is a directory.

### Parameters

- `source`: File, or directory, you want to move.
- `dest`: Where you want the file or directory moved.
- `options`
- `overwrite` (`boolean`, default: `true`): Overwrite existing destination file(s).

### Usage

The built-in
[`fs.rename()`](https://nodejs.org/api/fs.html#fs_fs_rename_oldpath_newpath_callback)
is just a JavaScript wrapper for the C `rename(2)` function, which doesn't
support moving files across partitions or devices. This module is what you
would have expected `fs.rename()` to be.

```js
const { moveFile } = require('@npmcli/fs');

(async () => {
await moveFile('source/unicorn.png', 'destination/unicorn.png');
console.log('The file has been moved');
})();
```
2 changes: 2 additions & 0 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
const cp = require('./cp/index.js')
const withTempDir = require('./with-temp-dir.js')
const readdirScoped = require('./readdir-scoped.js')
const moveFile = require('./move-file.js')

module.exports = {
cp,
withTempDir,
readdirScoped,
moveFile,
}
78 changes: 78 additions & 0 deletions lib/move-file.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
const { dirname, join, resolve, relative, isAbsolute } = require('path')
const fs = require('fs/promises')

const pathExists = async path => {
try {
await fs.access(path)
return true
} catch (er) {
return er.code !== 'ENOENT'
}
}

const moveFile = async (source, destination, options = {}, root = true, symlinks = []) => {
if (!source || !destination) {
throw new TypeError('`source` and `destination` file required')
}

options = {
overwrite: true,
...options,
}

if (!options.overwrite && await pathExists(destination)) {
throw new Error(`The destination file exists: ${destination}`)
}

await fs.mkdir(dirname(destination), { recursive: true })

try {
await fs.rename(source, destination)
} catch (error) {
if (error.code === 'EXDEV' || error.code === 'EPERM') {
const sourceStat = await fs.lstat(source)
if (sourceStat.isDirectory()) {
const files = await fs.readdir(source)
await Promise.all(files.map((file) =>
moveFile(join(source, file), join(destination, file), options, false, symlinks)
))
} else if (sourceStat.isSymbolicLink()) {
symlinks.push({ source, destination })
} else {
await fs.copyFile(source, destination)
}
} else {
throw error
}
}

if (root) {
await Promise.all(symlinks.map(async ({ source: symSource, destination: symDestination }) => {
let target = await fs.readlink(symSource)
// junction symlinks in windows will be absolute paths, so we need to
// make sure they point to the symlink destination
if (isAbsolute(target)) {
target = resolve(symDestination, relative(symSource, target))
}
// try to determine what the actual file is so we can create the correct
// type of symlink in windows
let targetStat = 'file'
try {
targetStat = await fs.stat(resolve(dirname(symSource), target))
if (targetStat.isDirectory()) {
targetStat = 'junction'
}
} catch {
// targetStat remains 'file'
}
await fs.symlink(
target,
symDestination,
targetStat
)
}))
await fs.rm(source, { recursive: true, force: true })
}
}

module.exports = moveFile
Loading

0 comments on commit ecbb150

Please sign in to comment.