Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ Methods

- [copy](docs/copy.md)
- [emptyDir](docs/emptyDir.md)
- [isEmptyDir](docs/isEmptyDir.md)
- [ensureFile](docs/ensureFile.md)
- [ensureDir](docs/ensureDir.md)
- [ensureLink](docs/ensureLink.md)
Expand All @@ -155,6 +156,7 @@ Methods

- [copySync](docs/copy-sync.md)
- [emptyDirSync](docs/emptyDir-sync.md)
- [isEmptyDirSync](docs/isEmptyDir-sync.md)
- [ensureFileSync](docs/ensureFile-sync.md)
- [ensureDirSync](docs/ensureDir-sync.md)
- [ensureLinkSync](docs/ensureLink-sync.md)
Expand Down
14 changes: 14 additions & 0 deletions docs/isEmptyDir-sync.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# isEmptyDirSync(dir)

Returns `true` if the directory is empty, or `false` if it contains at least one entry. Only the first directory entry is read.

- `dir` `<String>`

## Example:

```js
const fs = require('fs-extra')

const empty = fs.isEmptyDirSync('/tmp/some/dir')
console.log(empty ? 'empty' : 'not empty')
```
29 changes: 29 additions & 0 deletions docs/isEmptyDir.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# isEmptyDir(dir[, callback])

Returns a promise that resolves to `true` if the directory is empty, or `false` if it contains at least one entry. Only the first directory entry is read.

- `dir` `<String>`
- `callback` `<Function>`
- `err` `<Error>`
- `empty` `<Boolean>`

## Example:

```js
const fs = require('fs-extra')

fs.isEmptyDir('/tmp/some/dir', (err, empty) => {
if (err) return console.error(err)
console.log(empty ? 'empty' : 'not empty')
})
```

```js
const fs = require('fs-extra')

fs.isEmptyDir('/tmp/some/dir')
.then(empty => {
console.log(empty ? 'empty' : 'not empty')
})
.catch(err => console.error(err))
```
4 changes: 4 additions & 0 deletions lib/esm.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import _copy from './copy/index.js'
import _empty from './empty/index.js'
import _ensure from './ensure/index.js'
import _isEmptyDir from './is-empty-dir/index.js'
import _json from './json/index.js'
import _mkdirs from './mkdirs/index.js'
import _move from './move/index.js'
Expand Down Expand Up @@ -28,6 +29,8 @@ export const createSymlink = _ensure.createSymlink
export const createSymlinkSync = _ensure.createSymlinkSync
export const ensureSymlink = _ensure.ensureSymlink
export const ensureSymlinkSync = _ensure.ensureSymlinkSync
export const isEmptyDir = _isEmptyDir.isEmptyDir
export const isEmptyDirSync = _isEmptyDir.isEmptyDirSync
export const readJson = _json.readJson
export const readJSON = _json.readJSON
export const readJsonSync = _json.readJsonSync
Expand Down Expand Up @@ -59,6 +62,7 @@ export default {
..._copy,
..._empty,
..._ensure,
..._isEmptyDir,
..._json,
..._mkdirs,
..._move,
Expand Down
1 change: 1 addition & 0 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ module.exports = {
...require('./copy'),
...require('./empty'),
...require('./ensure'),
...require('./is-empty-dir'),
...require('./json'),
...require('./mkdirs'),
...require('./move'),
Expand Down
192 changes: 192 additions & 0 deletions lib/is-empty-dir/__tests__/is-empty-dir.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
'use strict'

const fs = require('fs')
const os = require('os')
const fse = require('../..')
const path = require('path')
const assert = require('assert')
const proxyquire = require('proxyquire')

const { describe, it, beforeEach, afterEach } = global

describe('+ isEmptyDir()', () => {
let TEST_DIR

beforeEach(() => {
TEST_DIR = path.join(os.tmpdir(), 'test-fs-extra', 'is-empty-dir')
if (fs.existsSync(TEST_DIR)) {
fse.removeSync(TEST_DIR)
}
fse.ensureDirSync(TEST_DIR)
})

afterEach(done => fse.remove(TEST_DIR, done))

it('should return true for an empty directory', async () => {
assert.strictEqual(await fse.isEmptyDir(TEST_DIR), true)
})

it('should return false for a nonempty directory', async () => {
fs.writeFileSync(path.join(TEST_DIR, 'file'), '')
assert.strictEqual(await fse.isEmptyDir(TEST_DIR), false)
})

it('should support callbacks', done => {
fse.isEmptyDir(TEST_DIR, (err, result) => {
assert.ifError(err)
assert.strictEqual(result, true)
done()
})
})

it('should reject missing paths', async () => {
await assert.rejects(fse.isEmptyDir(path.join(TEST_DIR, 'missing')), err => err.code === 'ENOENT')
})

it('should reject file paths', async () => {
const file = path.join(TEST_DIR, 'file')
fs.writeFileSync(file, '')
await assert.rejects(fse.isEmptyDir(file), err => err.code === 'ENOTDIR')
})

it('should support symlinks to directories', async () => {
const target = path.join(TEST_DIR, 'target')
const symlink = path.join(TEST_DIR, 'symlink')
fs.mkdirSync(target)
fs.symlinkSync(target, symlink)
assert.strictEqual(await fse.isEmptyDir(symlink), true)
})

it('should preserve read errors and close the directory', async () => {
const error = Object.assign(new Error('read failed'), { code: 'EIO' })
let reads = 0
let closes = 0
const { isEmptyDir } = proxyquire('..', {
'../fs': {
opendir: async () => ({
read: callback => {
reads++
callback(error)
},
close: callback => {
closes++
callback()
}
})
}
})

await assert.rejects(isEmptyDir(TEST_DIR), err => err === error)
assert.strictEqual(reads, 1)
assert.strictEqual(closes, 1)
})

it('should close the directory after reading the first entry', async () => {
let reads = 0
let closes = 0
const { isEmptyDir } = proxyquire('..', {
'../fs': {
opendir: async () => ({
read: callback => {
reads++
callback(null, { name: 'file' })
},
close: callback => {
closes++
callback()
}
})
}
})

assert.strictEqual(await isEmptyDir(TEST_DIR), false)
assert.strictEqual(reads, 1)
assert.strictEqual(closes, 1)
})
})

describe('+ isEmptyDirSync()', () => {
let TEST_DIR

beforeEach(() => {
TEST_DIR = path.join(os.tmpdir(), 'test-fs-extra', 'is-empty-dir-sync')
if (fs.existsSync(TEST_DIR)) {
fse.removeSync(TEST_DIR)
}
fse.ensureDirSync(TEST_DIR)
})

afterEach(done => fse.remove(TEST_DIR, done))

it('should return true for an empty directory', () => {
assert.strictEqual(fse.isEmptyDirSync(TEST_DIR), true)
})

it('should return false for a nonempty directory', () => {
fs.writeFileSync(path.join(TEST_DIR, 'file'), '')
assert.strictEqual(fse.isEmptyDirSync(TEST_DIR), false)
})

it('should throw for missing paths', () => {
assert.throws(() => fse.isEmptyDirSync(path.join(TEST_DIR, 'missing')), err => err.code === 'ENOENT')
})

it('should throw for file paths', () => {
const file = path.join(TEST_DIR, 'file')
fs.writeFileSync(file, '')
assert.throws(() => fse.isEmptyDirSync(file), err => err.code === 'ENOTDIR')
})

it('should support symlinks to directories', () => {
const target = path.join(TEST_DIR, 'target')
const symlink = path.join(TEST_DIR, 'symlink')
fs.mkdirSync(target)
fs.symlinkSync(target, symlink)
assert.strictEqual(fse.isEmptyDirSync(symlink), true)
})

it('should preserve read errors and close the directory', () => {
const error = Object.assign(new Error('read failed'), { code: 'EIO' })
let reads = 0
let closes = 0
const { isEmptyDirSync } = proxyquire('..', {
'../fs': {
opendirSync: () => ({
readSync: () => {
reads++
throw error
},
closeSync: () => {
closes++
}
})
}
})

assert.throws(() => isEmptyDirSync(TEST_DIR), err => err === error)
assert.strictEqual(reads, 1)
assert.strictEqual(closes, 1)
})

it('should close the directory after reading the first entry', () => {
let reads = 0
let closes = 0
const { isEmptyDirSync } = proxyquire('..', {
'../fs': {
opendirSync: () => ({
readSync: () => {
reads++
return { name: 'file' }
},
closeSync: () => {
closes++
}
})
}
})

assert.strictEqual(isEmptyDirSync(TEST_DIR), false)
assert.strictEqual(reads, 1)
assert.strictEqual(closes, 1)
})
})
69 changes: 69 additions & 0 deletions lib/is-empty-dir/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
'use strict'

const u = require('universalify').fromPromise
const fs = require('../fs')

function readFirst (dir) {
return new Promise((resolve, reject) => {
dir.read((err, dirent) => {
if (err) return reject(err)
resolve(dirent)
})
})
}

function close (dir) {
return new Promise((resolve, reject) => {
dir.close(err => {
if (err) return reject(err)
resolve()
})
})
}

async function isEmptyDir (dir) {
const handle = await fs.opendir(dir)
let result
let error

try {
result = (await readFirst(handle)) === null
} catch (err) {
error = err
}

try {
await close(handle)
} catch (err) {
if (!error) error = err
}

if (error) throw error
return result
}

function isEmptyDirSync (dir) {
const handle = fs.opendirSync(dir)
let result
let error

try {
result = handle.readSync() === null
} catch (err) {
error = err
}

try {
handle.closeSync()
} catch (err) {
if (!error) error = err
}

if (error) throw error
return result
}

module.exports = {
isEmptyDir: u(isEmptyDir),
isEmptyDirSync
}
4 changes: 4 additions & 0 deletions test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import {
createSymlinkSync,
ensureSymlink,
ensureSymlinkSync,
isEmptyDir,
isEmptyDirSync,
readJson,
readJsonSync,
writeJson,
Expand Down Expand Up @@ -68,6 +70,8 @@ const fsNamed = [
createSymlinkSync,
ensureSymlink,
ensureSymlinkSync,
isEmptyDir,
isEmptyDirSync,
readJson,
readJsonSync,
writeJson,
Expand Down