Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

move: support changing case in case-insensitive systems #801

Closed
wants to merge 13 commits into from
13 changes: 13 additions & 0 deletions CHANGELOG.md
@@ -1,3 +1,16 @@
9.1.0 / 2021-01-19
------------------

- Add promise support for `fs.rm()` ([#841](https://github.com/jprichardson/node-fs-extra/issues/841), [#860](https://github.com/jprichardson/node-fs-extra/pull/860))
- Upgrade universalify for performance improvments ([#825](https://github.com/jprichardson/node-fs-extra/pull/825))

9.0.1 / 2020-06-03
------------------

- Fix issue with `ensureFile()` when used with Jest on Windows ([#804](https://github.com/jprichardson/node-fs-extra/issues/804), [#805](https://github.com/jprichardson/node-fs-extra/pull/805))
- Remove unneeded `process.umask()` call ([#791](https://github.com/jprichardson/node-fs-extra/pull/791))
- Docs improvements ([#753](https://github.com/jprichardson/node-fs-extra/pull/753), [#795](https://github.com/jprichardson/node-fs-extra/pull/795), [#797](https://github.com/jprichardson/node-fs-extra/pull/797))

9.0.0 / 2020-03-19
------------------

Expand Down
3 changes: 3 additions & 0 deletions README.md
Expand Up @@ -153,6 +153,9 @@ They were removed from `fs-extra` in v2.0.0. If you need the functionality, `wal
Third Party
-----------

### CLI

[fse-cli](https://www.npmjs.com/package/@atao60/fse-cli) allows you to run `fs-extra` from a console or from [npm](https://www.npmjs.com) scripts.

### TypeScript

Expand Down
4 changes: 2 additions & 2 deletions lib/copy-sync/__tests__/copy-sync-dir.test.js
Expand Up @@ -182,7 +182,7 @@ describe('+ copySync() / dir', () => {
it('should apply filter when it is applied only to dest', done => {
const timeCond = new Date().getTime()

const filter = (s, d) => fs.statSync(d).birthtime.getTime() < timeCond
const filter = (s, d) => fs.statSync(d).mtime.getTime() < timeCond

const dest = path.join(TEST_DIR, 'dest')

Expand All @@ -197,7 +197,7 @@ describe('+ copySync() / dir', () => {

it('should apply filter when it is applied to both src and dest', done => {
const timeCond = new Date().getTime()
const filter = (s, d) => s.split('.').pop() !== 'css' && fs.statSync(path.dirname(d)).birthtime.getTime() > timeCond
const filter = (s, d) => s.split('.').pop() !== 'css' && fs.statSync(path.dirname(d)).mtime.getTime() > timeCond

const dest = path.join(TEST_DIR, 'dest')

Expand Down
4 changes: 2 additions & 2 deletions lib/copy/__tests__/copy.test.js
Expand Up @@ -380,7 +380,7 @@ describe('fs-extra', () => {
it('should apply filter when it is applied only to dest', done => {
const timeCond = new Date().getTime()

const filter = (s, d) => fs.statSync(d).birthtime.getTime() < timeCond
const filter = (s, d) => fs.statSync(d).mtime.getTime() < timeCond

const src = path.join(TEST_DIR, 'src')
fse.mkdirsSync(src)
Expand All @@ -402,7 +402,7 @@ describe('fs-extra', () => {

it('should apply filter when it is applied to both src and dest', done => {
const timeCond = new Date().getTime()
const filter = (s, d) => s.split('.').pop() !== 'css' && fs.statSync(path.dirname(d)).birthtime.getTime() > timeCond
const filter = (s, d) => s.split('.').pop() !== 'css' && fs.statSync(path.dirname(d)).mtime.getTime() > timeCond

const dest = path.join(TEST_DIR, 'dest')
setTimeout(() => {
Expand Down
30 changes: 30 additions & 0 deletions lib/fs/__tests__/rm.test.js
@@ -0,0 +1,30 @@
'use strict'

const fse = require('../..')
const os = require('os')
const path = require('path')
const assert = require('assert')
const atLeastNode = require('at-least-node')

/* eslint-env mocha */

// Used for tests on Node 14.14.0+ only
const describeNode14 = atLeastNode('14.14.0') ? describe : describe.skip

describeNode14('fs.rm', () => {
let TEST_FILE

beforeEach(done => {
TEST_FILE = path.join(os.tmpdir(), 'fs-extra', 'fs-rm')
fse.remove(TEST_FILE, done)
})

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

it('supports promises', () => {
fse.writeFileSync(TEST_FILE, 'hello')
return fse.rm(TEST_FILE).then(() => {
assert(!fse.pathExistsSync(TEST_FILE))
})
})
})
2 changes: 2 additions & 0 deletions lib/fs/index.js
Expand Up @@ -31,6 +31,7 @@ const api = [
'readlink',
'realpath',
'rename',
'rm',
'rmdir',
'stat',
'symlink',
Expand All @@ -41,6 +42,7 @@ const api = [
].filter(key => {
// Some commands are not available on some systems. Ex:
// fs.opendir was added in Node.js v12.12.0
// fs.rm was added in Node.js v14.14.0
// fs.lchown is not available on at least some Linux
return typeof fs[key] === 'function'
})
Expand Down
89 changes: 20 additions & 69 deletions lib/move-sync/__tests__/move-sync-case-insensitive-paths.test.js
Expand Up @@ -4,7 +4,6 @@ const assert = require('assert')
const os = require('os')
const path = require('path')
const fs = require('../../')
const platform = os.platform()

/* global beforeEach, afterEach, describe, it */

Expand All @@ -21,104 +20,56 @@ describe('+ moveSync() - case insensitive paths', () => {
afterEach(() => fs.removeSync(TEST_DIR))

describe('> when src is a directory', () => {
it('should behave correctly based on the OS', () => {
it('should move successfully', () => {
src = path.join(TEST_DIR, 'srcdir')
fs.outputFileSync(path.join(src, 'subdir', 'file.txt'), 'some data')
dest = path.join(TEST_DIR, 'srcDir')
let errThrown = false

try {
fs.moveSync(src, dest)
} catch (err) {
if (platform === 'darwin' || platform === 'win32') {
assert.strictEqual(err.message, 'Source and destination must not be the same.')
errThrown = true
}
}
if (platform === 'darwin' || platform === 'win32') assert(errThrown)
if (platform === 'linux') {
assert(fs.existsSync(dest))
assert.strictEqual(fs.readFileSync(path.join(dest, 'subdir', 'file.txt'), 'utf8'), 'some data')
assert(!errThrown)
}
fs.moveSync(src, dest)
assert(fs.existsSync(dest))
assert.strictEqual(fs.readFileSync(path.join(dest, 'subdir', 'file.txt'), 'utf8'), 'some data')
})
})

describe('> when src is a file', () => {
it('should behave correctly based on the OS', () => {
it('should move successfully', () => {
src = path.join(TEST_DIR, 'srcfile')
fs.outputFileSync(src, 'some data')
dest = path.join(TEST_DIR, 'srcFile')
let errThrown = false

try {
fs.moveSync(src, dest)
} catch (err) {
if (platform === 'darwin' || platform === 'win32') {
assert.strictEqual(err.message, 'Source and destination must not be the same.')
errThrown = true
}
}
if (platform === 'darwin' || platform === 'win32') assert(errThrown)
if (platform === 'linux') {
assert(fs.existsSync(dest))
assert.strictEqual(fs.readFileSync(dest, 'utf8'), 'some data')
assert(!errThrown)
}
fs.moveSync(src, dest)
assert(fs.existsSync(dest))
assert.strictEqual(fs.readFileSync(dest, 'utf8'), 'some data')
})
})

describe('> when src is a symlink', () => {
it('should behave correctly based on the OS, symlink dir', () => {
it('should move successfully, symlink dir', () => {
src = path.join(TEST_DIR, 'srcdir')
fs.outputFileSync(path.join(src, 'subdir', 'file.txt'), 'some data')
const srcLink = path.join(TEST_DIR, 'src-symlink')
fs.symlinkSync(src, srcLink, 'dir')
dest = path.join(TEST_DIR, 'src-Symlink')
let errThrown = false

try {
fs.moveSync(srcLink, dest)
} catch (err) {
if (platform === 'darwin' || platform === 'win32') {
assert.strictEqual(err.message, 'Source and destination must not be the same.')
errThrown = true
}
}
if (platform === 'darwin' || platform === 'win32') assert(errThrown)
if (platform === 'linux') {
assert(fs.existsSync(dest))
assert.strictEqual(fs.readFileSync(path.join(dest, 'subdir', 'file.txt'), 'utf8'), 'some data')
const destLink = fs.readlinkSync(dest)
assert.strictEqual(destLink, src)
assert(!errThrown)
}
fs.moveSync(srcLink, dest)
assert(fs.existsSync(dest))
assert.strictEqual(fs.readFileSync(path.join(dest, 'subdir', 'file.txt'), 'utf8'), 'some data')
const destLink = fs.readlinkSync(dest)
assert.strictEqual(destLink, src)
})

it('should behave correctly based on the OS, symlink file', () => {
it('should move successfully, symlink file', () => {
src = path.join(TEST_DIR, 'srcfile')
fs.outputFileSync(src, 'some data')
const srcLink = path.join(TEST_DIR, 'src-symlink')
fs.symlinkSync(src, srcLink, 'file')
dest = path.join(TEST_DIR, 'src-Symlink')
let errThrown = false

try {
fs.moveSync(srcLink, dest)
} catch (err) {
if (platform === 'darwin' || platform === 'win32') {
assert.strictEqual(err.message, 'Source and destination must not be the same.')
errThrown = true
}
}
if (platform === 'darwin' || platform === 'win32') assert(errThrown)
if (platform === 'linux') {
assert(fs.existsSync(dest))
assert.strictEqual(fs.readFileSync(dest, 'utf8'), 'some data')
const destLink = fs.readlinkSync(dest)
assert.strictEqual(destLink, src)
assert(!errThrown)
}
fs.moveSync(srcLink, dest)
assert(fs.existsSync(dest))
assert.strictEqual(fs.readFileSync(dest, 'utf8'), 'some data')
const destLink = fs.readlinkSync(dest)
assert.strictEqual(destLink, src)
})
})
})
7 changes: 4 additions & 3 deletions lib/move-sync/move-sync.js
Expand Up @@ -11,13 +11,14 @@ function moveSync (src, dest, opts) {
opts = opts || {}
const overwrite = opts.overwrite || opts.clobber || false

const { srcStat } = stat.checkPathsSync(src, dest, 'move', opts)
const { srcStat, isChangingCase = false } = stat.checkPathsSync(src, dest, 'move', opts)
stat.checkParentPathsSync(src, srcStat, dest, 'move')
mkdirpSync(path.dirname(dest))
return doRename(src, dest, overwrite)
return doRename(src, dest, overwrite, isChangingCase)
}

function doRename (src, dest, overwrite) {
function doRename (src, dest, overwrite, isChangingCase) {
if (isChangingCase) return rename(src, dest, overwrite)
if (overwrite) {
removeSync(dest)
return rename(src, dest, overwrite)
Expand Down
61 changes: 20 additions & 41 deletions lib/move/__tests__/move-case-insensitive-paths.test.js
Expand Up @@ -4,7 +4,6 @@ const assert = require('assert')
const os = require('os')
const path = require('path')
const fs = require('../../')
const platform = os.platform()

/* global beforeEach, afterEach, describe, it */

Expand All @@ -21,86 +20,66 @@ describe('+ move() - case insensitive paths', () => {
afterEach(done => fs.remove(TEST_DIR, done))

describe('> when src is a directory', () => {
it('should behave correctly based on the OS', done => {
it('should move successfully', done => {
src = path.join(TEST_DIR, 'srcdir')
fs.outputFileSync(path.join(src, 'subdir', 'file.txt'), 'some data')
dest = path.join(TEST_DIR, 'srcDir')

fs.move(src, dest, err => {
if (platform === 'linux') {
assert.ifError(err)
assert(fs.existsSync(dest))
assert.strictEqual(fs.readFileSync(path.join(dest, 'subdir', 'file.txt'), 'utf8'), 'some data')
}
if (platform === 'darwin' || platform === 'win32') {
assert.strictEqual(err.message, 'Source and destination must not be the same.')
}
assert.ifError(err)
assert(fs.existsSync(dest))
assert.strictEqual(fs.readFileSync(path.join(dest, 'subdir', 'file.txt'), 'utf8'), 'some data')
done()
})
})
})

describe('> when src is a file', () => {
it('should behave correctly based on the OS', done => {
it('should move successfully', done => {
src = path.join(TEST_DIR, 'srcfile')
fs.outputFileSync(src, 'some data')
dest = path.join(TEST_DIR, 'srcFile')

fs.move(src, dest, err => {
if (platform === 'linux') {
assert.ifError(err)
assert(fs.existsSync(dest))
assert.strictEqual(fs.readFileSync(dest, 'utf8'), 'some data')
}
if (platform === 'darwin' || platform === 'win32') {
assert.strictEqual(err.message, 'Source and destination must not be the same.')
}
assert.ifError(err)
assert(fs.existsSync(dest))
assert.strictEqual(fs.readFileSync(dest, 'utf8'), 'some data')
done()
})
})
})

describe('> when src is a symlink', () => {
it('should behave correctly based on the OS, symlink dir', done => {
it('should move successfully, symlink dir', done => {
src = path.join(TEST_DIR, 'srcdir')
fs.outputFileSync(path.join(src, 'subdir', 'file.txt'), 'some data')
const srcLink = path.join(TEST_DIR, 'src-symlink')
fs.symlinkSync(src, srcLink, 'dir')
dest = path.join(TEST_DIR, 'src-Symlink')

fs.move(srcLink, dest, err => {
if (platform === 'linux') {
assert.ifError(err)
assert(fs.existsSync(dest))
assert.strictEqual(fs.readFileSync(path.join(dest, 'subdir', 'file.txt'), 'utf8'), 'some data')
const destLink = fs.readlinkSync(dest)
assert.strictEqual(destLink, src)
}
if (platform === 'darwin' || platform === 'win32') {
assert.strictEqual(err.message, 'Source and destination must not be the same.')
}
assert.ifError(err)
assert(fs.existsSync(dest))
assert.strictEqual(fs.readFileSync(path.join(dest, 'subdir', 'file.txt'), 'utf8'), 'some data')
const destLink = fs.readlinkSync(dest)
assert.strictEqual(destLink, src)
done()
})
})

it('should behave correctly based on the OS, symlink file', done => {
it('should move successfully, symlink file', done => {
src = path.join(TEST_DIR, 'srcfile')
fs.outputFileSync(src, 'some data')
const srcLink = path.join(TEST_DIR, 'src-symlink')
fs.symlinkSync(src, srcLink, 'file')
dest = path.join(TEST_DIR, 'src-Symlink')

fs.move(srcLink, dest, err => {
if (platform === 'linux') {
assert.ifError(err)
assert(fs.existsSync(dest))
assert.strictEqual(fs.readFileSync(dest, 'utf8'), 'some data')
const destLink = fs.readlinkSync(dest)
assert.strictEqual(destLink, src)
}
if (platform === 'darwin' || platform === 'win32') {
assert.strictEqual(err.message, 'Source and destination must not be the same.')
}
assert.ifError(err)
assert(fs.existsSync(dest))
assert.strictEqual(fs.readFileSync(dest, 'utf8'), 'some data')
const destLink = fs.readlinkSync(dest)
assert.strictEqual(destLink, src)
done()
})
})
Expand Down
7 changes: 4 additions & 3 deletions lib/move/move.js
Expand Up @@ -18,18 +18,19 @@ function move (src, dest, opts, cb) {

stat.checkPaths(src, dest, 'move', opts, (err, stats) => {
if (err) return cb(err)
const { srcStat } = stats
const { srcStat, isChangingCase = false } = stats
stat.checkParentPaths(src, srcStat, dest, 'move', err => {
if (err) return cb(err)
mkdirp(path.dirname(dest), err => {
if (err) return cb(err)
return doRename(src, dest, overwrite, cb)
return doRename(src, dest, overwrite, isChangingCase, cb)
})
})
})
}

function doRename (src, dest, overwrite, cb) {
function doRename (src, dest, overwrite, isChangingCase, cb) {
if (isChangingCase) return rename(src, dest, overwrite, cb)
if (overwrite) {
return remove(dest, err => {
manidlou marked this conversation as resolved.
Show resolved Hide resolved
if (err) return cb(err)
Expand Down