Skip to content

Commit

Permalink
Rimraf version 4, first pass
Browse files Browse the repository at this point in the history
  • Loading branch information
isaacs committed Jan 10, 2023
1 parent 8caf0de commit a71e7f9
Show file tree
Hide file tree
Showing 35 changed files with 8,086 additions and 3,772 deletions.
44 changes: 44 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: CI

on: [push, pull_request]

jobs:
build:
strategy:
matrix:
node-version: [10.0.x, 10.x, 12.0.x, 12.x, 14.0.x, 14.x, 16.x]
platform:
- os: ubuntu-latest
shell: bash
- os: macos-latest
shell: bash
- os: windows-latest
shell: bash
- os: windows-latest
shell: powershell
fail-fast: false

runs-on: ${{ matrix.platform.os }}
defaults:
run:
shell: ${{ matrix.platform.shell }}

steps:
- name: Checkout Repository
uses: actions/checkout@v1.1.0

- name: Use Nodejs ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}

- name: Install dependencies
run: npm install

- name: Run Tests
run: npm test -- -t0 -c
env:
# more thorough smoke test in CI
RIMRAF_TEST_START_CHAR: a
RIMRAF_TEST_END_CHAR: j
RIMRAF_TEST_DEPTH: 5
15 changes: 0 additions & 15 deletions .travis.yml

This file was deleted.

117 changes: 50 additions & 67 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,101 +1,84 @@
[![Build Status](https://travis-ci.org/isaacs/rimraf.svg?branch=master)](https://travis-ci.org/isaacs/rimraf) [![Dependency Status](https://david-dm.org/isaacs/rimraf.svg)](https://david-dm.org/isaacs/rimraf) [![devDependency Status](https://david-dm.org/isaacs/rimraf/dev-status.svg)](https://david-dm.org/isaacs/rimraf#info=devDependencies)

The [UNIX command](http://en.wikipedia.org/wiki/Rm_(Unix)) `rm -rf` for node.

Install with `npm install rimraf`, or just drop rimraf.js somewhere.

## API

`rimraf(f, [opts], callback)`

The first parameter will be interpreted as a globbing pattern for files. If you
want to disable globbing you can do so with `opts.disableGlob` (defaults to
`false`). This might be handy, for instance, if you have filenames that contain
globbing wildcard characters.
## Major Changes from v3 to v4

The callback will be called with an error if there is one. Certain
errors are handled for you:
* The function returns a `Promise` instead of taking a callback.
* Built-in glob support removed.
* Native implementation used by default when available.
* New implementation on Windows, making the exponential backoff for
`EBUSY` and `ENOTEMPTY` errors no longer necessary.
* Simplified implementation on Posix, since the Windows affordances are not
necessary there.

* Windows: `EBUSY` and `ENOTEMPTY` - rimraf will back off a maximum of
`opts.maxBusyTries` times before giving up, adding 100ms of wait
between each attempt. The default `maxBusyTries` is 3.
* `ENOENT` - If the file doesn't exist, rimraf will return
successfully, since your desired outcome is already the case.
* `EMFILE` - Since `readdir` requires opening a file descriptor, it's
possible to hit `EMFILE` if too many file descriptors are in use.
In the sync case, there's nothing to be done for this. But in the
async case, rimraf will gradually back off with timeouts up to
`opts.emfileWait` ms, which defaults to 1000.

## options

* unlink, chmod, stat, lstat, rmdir, readdir,
unlinkSync, chmodSync, statSync, lstatSync, rmdirSync, readdirSync
## API

In order to use a custom file system library, you can override
specific fs functions on the options object.
### `rimraf(f, [opts]) -> Promise`

If any of these functions are present on the options object, then
the supplied function will be used instead of the default fs
method.
This first parameter is a path. The second argument is an options object.

Sync methods are only relevant for `rimraf.sync()`, of course.
Options:

For example:
* `tmp`: Temp folder to use to place files and folders for the Windows
implementation. Must be on the same physical device as the path being
deleted. Defaults to `dirname(f)`.

```javascript
var myCustomFS = require('some-custom-fs')
Any other options are provided to the native Node.js `fs.rm` implementation
when that is used.

rimraf('some-thing', myCustomFS, callback)
```
This will attempt to choose the best implementation, based on Node.js
version and `process.platform`. To force a specific implementation, use
one of the other functions provided.

* maxBusyTries
### `rimraf.sync(f, [opts])` `rimraf.rimrafSync(f, [opts])`

If an `EBUSY`, `ENOTEMPTY`, or `EPERM` error code is encountered
on Windows systems, then rimraf will retry with a linear backoff
wait of 100ms longer on each try. The default maxBusyTries is 3.
Synchronous form of `rimraf()`

Only relevant for async usage.
Note that, unlike many file system operations, the synchronous form will
typically be significantly _slower_ than the async form, because recursive
deletion is extremely parallelizable.

* emfileWait
### `rimraf.native(f, [opts])`

If an `EMFILE` error is encountered, then rimraf will retry
repeatedly with a linear backoff of 1ms longer on each try, until
the timeout counter hits this max. The default limit is 1000.
Uses the built-in `fs.rm` implementation that Node.js provides. This is
used by default on Node.js versions greater than or equal to `14.14.0`.

If you repeatedly encounter `EMFILE` errors, then consider using
[graceful-fs](http://npm.im/graceful-fs) in your program.
### `rimraf.nativeSync(f, [opts])` `rimraf.native.sync(f, [opts])`

Only relevant for async usage.
Synchronous form of `rimraf.native`

* glob
### `rimraf.manual(f, [opts])`

Set to `false` to disable [glob](http://npm.im/glob) pattern
matching.
Use the JavaScript implementation appropriate for your operating system.

Set to an object to pass options to the glob module. The default
glob options are `{ nosort: true, silent: true }`.
### `rimraf.manualSync(f, [opts])` `rimraf.manualSync(f, opts)`

Glob version 6 is used in this module.
Synchronous form of `rimraf.manual()`

Relevant for both sync and async usage.
### `rimraf.windows(f, [opts])`

* disableGlob
JavaScript implementation of file removal appropriate for Windows
platforms, where `unlink` and `rmdir` are not atomic operations.

Set to any non-falsey value to disable globbing entirely.
(Equivalent to setting `glob: false`.)
Moves all files and folders to the parent directory of `f` with a temporary
filename prior to attempting to remove them.

## rimraf.sync
Note that, in cases where the operation fails, this _may_ leave files lying
around in the parent directory with names like
`.file-basename.txt.0.123412341`. Until the Windows kernel provides a way
to perform atomic `unlink` and `rmdir` operations, this is unfortunately
unavoidable.

It can remove stuff synchronously, too. But that's not so good. Use
the async API. It's better.
To move files to a different temporary directory other than the parent,
provide `opts.tmp`. Note that this _must_ be on the same physical device
as the folder being deleted, or else the operation will fail.

## CLI
### `rimraf.windows.sync(path, [opts])` `rimraf.windowsSync(path, [opts])`

If installed with `npm install rimraf -g` it can be used as a global
command `rimraf <path> [<path> ...]` which is useful for cross platform support.
Synchronous form of `rimraf.windows()`

## mkdirp

If you need to create a directory recursively, check out
If you need to _create_ a directory recursively, check out
[mkdirp](https://github.com/isaacs/node-mkdirp).
68 changes: 0 additions & 68 deletions bin.js

This file was deleted.

59 changes: 59 additions & 0 deletions lib/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
const pathArg = require('./path-arg.js')
const optsArg = require('./opts-arg.js')

const {rimrafNative, rimrafNativeSync} = require('./rimraf-native.js')
const {rimrafManual, rimrafManualSync} = require('./rimraf-manual.js')
const {rimrafWindows, rimrafWindowsSync} = require('./rimraf-windows.js')
const {rimrafPosix, rimrafPosixSync} = require('./rimraf-posix.js')
const {useNative, useNativeSync} = require('./use-native.js')

const rimraf = (path, opts) => {
path = pathArg(path)
opts = optsArg(opts)
return useNative(opts) ? rimrafNative(path, opts)
: rimrafManual(path, opts)
}

const rimrafSync = async (path, opts) => {
path = pathArg(path)
opts = optsArg(opts)
return useNativeSync(opts) ? rimrafNativeSync(path, opts)
: rimrafManualSync(path, opts)
}

rimraf.rimraf = rimraf
rimraf.sync = rimraf.rimrafSync = rimrafSync

const native = async (path, opts) =>
rimrafNative(pathArg(path), optsArg(opts))
const nativeSync = (path, opts) =>
rimrafNativeSync(pathArg(path), optsArg(opts))
native.sync = nativeSync
rimraf.native = native
rimraf.nativeSync = nativeSync

const manual = async (path, opts) =>
rimrafManual(pathArg(path), optsArg(opts))
const manualSync = (path, opts) =>
rimrafManualSync(pathArg(path), optsArg(opts))
manual.sync = manualSync
rimraf.manual = manual
rimraf.manualSync = manualSync

const windows = async (path, opts) =>
rimrafWindows(pathArg(path), optsArg(opts))
const windowsSync = (path, opts) =>
rimrafWindowsSync(pathArg(path), optsArg(opts))
windows.sync = windowsSync
rimraf.windows = windows
rimraf.windowsSync = windowsSync

const posix = async (path, opts) =>
rimrafPosix(pathArg(path), optsArg(opts))
const posixSync = (path, opts) =>
rimrafPosixSync(pathArg(path), optsArg(opts))
posix.sync = posixSync
rimraf.posix = posix
rimraf.posixSync = posixSync

module.exports = rimraf
2 changes: 2 additions & 0 deletions lib/opts-arg.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// TODO: validate options
module.exports = (opts = {}) => opts
29 changes: 29 additions & 0 deletions lib/path-arg.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
const platform = require('./platform.js')
const { resolve, parse } = require('path')
const pathArg = path => {
if (/\0/.test(path)) {
// simulate same failure that node raises
throw Object.assign(
new TypeError('path must be a string without null bytes'),
{
path,
code: 'ERR_INVALID_ARG_VALUE',
}
)
}

path = resolve(path)
if (platform === 'win32') {
const badWinChars = /[*|"<>?:]/
const {root} = parse(path)
if (badWinChars.test(path.substr(root.length))) {
throw Object.assign(new Error('Illegal characters in path.'), {
path,
code: 'EINVAL',
})
}
}

return path
}
module.exports = pathArg
1 change: 1 addition & 0 deletions lib/platform.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = process.env.__TESTING_RIMRAF_PLATFORM__ || process.platform
21 changes: 21 additions & 0 deletions lib/readdir-or-error.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// returns an array of entries if readdir() works,
// or the error that readdir() raised if not.
const {
readdirSync,
promises: {
readdir,
},
} = require('fs')
const readdirOrError = path => readdir(path).catch(er => er)
const readdirOrErrorSync = (path) => {
try {
return readdirSync(path)
} catch (er) {
return er
}
}

module.exports = {
readdirOrError,
readdirOrErrorSync,
}
Loading

0 comments on commit a71e7f9

Please sign in to comment.