fs-jetpack
Advanced tools
Comparing version 2.4.0 to 3.0.0
@@ -0,1 +1,4 @@ | ||
# 3.0.0 (2020-07-15) | ||
- **(breaking change)** `move()` and `rename()` default overwrite behaviour have changed, now by default both methods throw error if destination path already exists. | ||
# 2.4.0 (2020-05-15) | ||
@@ -2,0 +5,0 @@ - `write()` can accept `mode` as a parameter |
@@ -177,9 +177,9 @@ "use strict"; | ||
move: (from, to) => { | ||
move.validateInput("move", from, to); | ||
move.sync(resolvePath(from), resolvePath(to)); | ||
move: (from, to, options) => { | ||
move.validateInput("move", from, to, options); | ||
move.sync(resolvePath(from), resolvePath(to), options); | ||
}, | ||
moveAsync: (from, to) => { | ||
move.validateInput("moveAsync", from, to); | ||
return move.async(resolvePath(from), resolvePath(to)); | ||
moveAsync: (from, to, options) => { | ||
move.validateInput("moveAsync", from, to, options); | ||
return move.async(resolvePath(from), resolvePath(to), options); | ||
}, | ||
@@ -207,9 +207,9 @@ | ||
rename: (path, newName) => { | ||
rename.validateInput("rename", path, newName); | ||
rename.sync(resolvePath(path), newName); | ||
rename: (path, newName, options) => { | ||
rename.validateInput("rename", path, newName, options); | ||
rename.sync(resolvePath(path), newName, options); | ||
}, | ||
renameAsync: (path, newName) => { | ||
rename.validateInput("renameAsync", path, newName); | ||
return rename.async(resolvePath(path), newName); | ||
renameAsync: (path, newName, options) => { | ||
rename.validateInput("renameAsync", path, newName, options); | ||
return rename.async(resolvePath(path), newName, options); | ||
}, | ||
@@ -216,0 +216,0 @@ |
120
lib/move.js
@@ -8,9 +8,24 @@ "use strict"; | ||
const exists = require("./exists"); | ||
const remove = require("./remove"); | ||
const validateInput = (methodName, from, to) => { | ||
const methodSignature = `${methodName}(from, to)`; | ||
const validateInput = (methodName, from, to, options) => { | ||
const methodSignature = `${methodName}(from, to, [options])`; | ||
validate.argument(methodSignature, "from", from, ["string"]); | ||
validate.argument(methodSignature, "to", to, ["string"]); | ||
validate.options(methodSignature, "options", options, { | ||
overwrite: ["boolean"] | ||
}); | ||
}; | ||
const parseOptions = options => { | ||
const opts = options || {}; | ||
return opts; | ||
}; | ||
const generateDestinationExistsError = path => { | ||
const err = new Error(`Destination path already exists ${path}`); | ||
err.code = "EEXIST"; | ||
return err; | ||
}; | ||
const generateSourceDoesntExistError = path => { | ||
@@ -26,10 +41,22 @@ const err = new Error(`Path to move doesn't exist ${path}`); | ||
const moveSync = (from, to) => { | ||
const moveSync = (from, to, options) => { | ||
const opts = parseOptions(options); | ||
if (exists.sync(to) !== false && opts.overwrite !== true) { | ||
throw generateDestinationExistsError(to); | ||
} | ||
try { | ||
fs.renameSync(from, to); | ||
} catch (err) { | ||
if (err.code !== "ENOENT") { | ||
// We can't make sense of this error. Rethrow it. | ||
throw err; | ||
} else { | ||
if ( | ||
(err.code === "EISDIR" || err.code === "EPERM") && | ||
opts.overwrite === true | ||
) { | ||
// Looks like the destination path is a directory, | ||
// and we have permission for overwriting, so can remove it. | ||
remove.sync(to); | ||
// Retry the attempt | ||
fs.renameSync(from, to); | ||
} else if (err.code === "ENOENT") { | ||
// Ok, source or destination path doesn't exist. | ||
@@ -46,2 +73,5 @@ // Must do more investigation. | ||
} | ||
} else { | ||
// We can't make sense of this error. Rethrow it. | ||
throw err; | ||
} | ||
@@ -72,30 +102,52 @@ } | ||
const moveAsync = (from, to) => { | ||
const moveAsync = (from, to, options) => { | ||
const opts = parseOptions(options); | ||
return new Promise((resolve, reject) => { | ||
fs.rename(from, to) | ||
.then(resolve) | ||
.catch(err => { | ||
if (err.code !== "ENOENT") { | ||
// Something unknown. Rethrow original error. | ||
reject(err); | ||
} else { | ||
// Ok, source or destination path doesn't exist. | ||
// Must do more investigation. | ||
exists | ||
.async(from) | ||
.then(srcExists => { | ||
if (!srcExists) { | ||
reject(generateSourceDoesntExistError(from)); | ||
} else { | ||
ensureDestinationPathExistsAsync(to) | ||
.then(() => { | ||
// Retry the attempt | ||
return fs.rename(from, to); | ||
}) | ||
.then(resolve, reject); | ||
} | ||
}) | ||
.catch(reject); | ||
} | ||
}); | ||
exists.async(to).then(destinationExists => { | ||
if (destinationExists !== false && opts.overwrite !== true) { | ||
reject(generateDestinationExistsError(to)); | ||
} else { | ||
fs.rename(from, to) | ||
.then(resolve) | ||
.catch(err => { | ||
if ( | ||
(err.code === "EISDIR" || err.code === "EPERM") && | ||
opts.overwrite === true | ||
) { | ||
// Looks like the destination path is a directory, | ||
// and we have permission for overwriting, so can remove it. | ||
remove | ||
.async(to) | ||
.then(() => { | ||
// Retry the attempt | ||
return fs.rename(from, to); | ||
}) | ||
.then(resolve) | ||
.catch(reject); | ||
} else if (err.code === "ENOENT") { | ||
// Ok, source or destination path doesn't exist. | ||
// Must do more investigation. | ||
exists | ||
.async(from) | ||
.then(srcExists => { | ||
if (!srcExists) { | ||
reject(generateSourceDoesntExistError(from)); | ||
} else { | ||
ensureDestinationPathExistsAsync(to) | ||
.then(() => { | ||
// Retry the attempt | ||
return fs.rename(from, to); | ||
}) | ||
.then(resolve, reject); | ||
} | ||
}) | ||
.catch(reject); | ||
} else { | ||
// Something unknown. Rethrow original error. | ||
reject(err); | ||
} | ||
}); | ||
} | ||
}); | ||
}); | ||
@@ -102,0 +154,0 @@ }; |
@@ -7,6 +7,9 @@ "use strict"; | ||
const validateInput = (methodName, path, newName) => { | ||
const methodSignature = `${methodName}(path, newName)`; | ||
const validateInput = (methodName, path, newName, options) => { | ||
const methodSignature = `${methodName}(path, newName, [options])`; | ||
validate.argument(methodSignature, "path", path, ["string"]); | ||
validate.argument(methodSignature, "newName", newName, ["string"]); | ||
validate.options(methodSignature, "options", options, { | ||
overwrite: ["boolean"] | ||
}); | ||
@@ -24,5 +27,5 @@ if (pathUtil.basename(newName) !== newName) { | ||
const renameSync = (path, newName) => { | ||
const renameSync = (path, newName, options) => { | ||
const newPath = pathUtil.join(pathUtil.dirname(path), newName); | ||
move.sync(path, newPath); | ||
move.sync(path, newPath, options); | ||
}; | ||
@@ -34,5 +37,5 @@ | ||
const renameAsync = (path, newName) => { | ||
const renameAsync = (path, newName, options) => { | ||
const newPath = pathUtil.join(pathUtil.dirname(path), newName); | ||
return move.async(path, newPath); | ||
return move.async(path, newPath, options); | ||
}; | ||
@@ -39,0 +42,0 @@ |
{ | ||
"name": "fs-jetpack", | ||
"description": "Better file system API", | ||
"version": "2.4.0", | ||
"version": "3.0.0", | ||
"author": "Jakub Szwacz <jakub@szwacz.com>", | ||
@@ -28,5 +28,2 @@ "dependencies": { | ||
}, | ||
"engines": { | ||
"node": ">=4" | ||
}, | ||
"scripts": { | ||
@@ -33,0 +30,0 @@ "test": "mocha -r ts-node/register \"spec/**/*.spec.ts\"", |
@@ -53,3 +53,3 @@ fs-jetpack [![Build Status](https://travis-ci.org/szwacz/fs-jetpack.svg?branch=master)](https://travis-ci.org/szwacz/fs-jetpack) [![Build status](https://ci.appveyor.com/api/projects/status/er206e91fpuuqf58?svg=true)](https://ci.appveyor.com/project/szwacz/fs-jetpack) [![codecov](https://codecov.io/gh/szwacz/fs-jetpack/branch/master/graph/badge.svg)](https://codecov.io/gh/szwacz/fs-jetpack) | ||
If you don't see the word "Async" in method name it returns value immediately. | ||
If you don't see the word "Async" in method name it always, across the whole API returns value immediately. | ||
```js | ||
@@ -409,4 +409,4 @@ const data = jetpack.read('file.txt'); | ||
## move(from, to) | ||
asynchronous: **moveAsync(from, to)** | ||
## move(from, to, [options]) | ||
asynchronous: **moveAsync(from, to, [options])** | ||
@@ -417,3 +417,5 @@ Moves given path to new location. | ||
`from` path to directory or file you want to move. | ||
`to` path where the thing should be moved. | ||
`to` path where the thing should be moved. | ||
`options` (optional) additional options for customization. Is an `Object` with possible fields: | ||
* `overwrite` (default: `false`) Whether to overwrite destination path when it already exists. If `true`, the overwrite will be performed. | ||
@@ -485,4 +487,4 @@ **returns:** | ||
## rename(path, newName) | ||
asynchronous: **renameAsync(path, newName)** | ||
## rename(path, newName, [options]) | ||
asynchronous: **renameAsync(path, newName, [options])** | ||
@@ -493,3 +495,5 @@ Renames given file or directory. | ||
`path` path to thing you want to change name of. | ||
`newName` new name for this thing (not full path, just a name). | ||
`newName` new name for this thing (not full path, just a name). | ||
`options` (optional) additional options for customization. Is an `Object` with possible fields: | ||
* `overwrite` (default: `false`) Whether to overwrite destination path when it already exists. If `true`, the overwrite will be performed. | ||
@@ -496,0 +500,0 @@ **returns:** |
@@ -110,2 +110,89 @@ import * as fse from "fs-extra"; | ||
describe("overwriting behaviour", () => { | ||
describe("does not overwrite by default", () => { | ||
const preparations = () => { | ||
fse.outputFileSync("file1.txt", "abc"); | ||
fse.outputFileSync("file2.txt", "xyz"); | ||
}; | ||
const expectations = (err: any) => { | ||
expect(err.code).to.equal("EEXIST"); | ||
expect(err.message).to.have.string("Destination path already exists"); | ||
path("file2.txt").shouldBeFileWithContent("xyz"); | ||
}; | ||
it("sync", () => { | ||
preparations(); | ||
try { | ||
jetpack.move("file1.txt", "file2.txt"); | ||
throw new Error("Expected error to be thrown"); | ||
} catch (err) { | ||
expectations(err); | ||
} | ||
}); | ||
it("async", done => { | ||
preparations(); | ||
jetpack.moveAsync("file1.txt", "file2.txt").catch(err => { | ||
expectations(err); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
describe("overwrites if it was specified", () => { | ||
const preparations = () => { | ||
fse.outputFileSync("file1.txt", "abc"); | ||
fse.outputFileSync("file2.txt", "xyz"); | ||
}; | ||
const expectations = () => { | ||
path("file1.txt").shouldNotExist(); | ||
path("file2.txt").shouldBeFileWithContent("abc"); | ||
}; | ||
it("sync", () => { | ||
preparations(); | ||
jetpack.move("file1.txt", "file2.txt", { overwrite: true }); | ||
expectations(); | ||
}); | ||
it("async", done => { | ||
preparations(); | ||
jetpack | ||
.moveAsync("file1.txt", "file2.txt", { overwrite: true }) | ||
.then(() => { | ||
expectations(); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
describe("can overwrite a directory", () => { | ||
const preparations = () => { | ||
fse.outputFileSync("file1.txt", "abc"); | ||
fse.mkdirsSync("dir"); | ||
}; | ||
const expectations = () => { | ||
path("file1.txt").shouldNotExist(); | ||
path("dir").shouldBeFileWithContent("abc"); | ||
}; | ||
it("sync", () => { | ||
preparations(); | ||
jetpack.move("file1.txt", "dir", { overwrite: true }); | ||
expectations(); | ||
}); | ||
it("async", done => { | ||
preparations(); | ||
jetpack.moveAsync("file1.txt", "dir", { overwrite: true }).then(() => { | ||
expectations(); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
}); | ||
describe("respects internal CWD of jetpack instance", () => { | ||
@@ -156,3 +243,3 @@ const preparations = () => { | ||
test.methodName | ||
}(from, to) must be a string. Received undefined` | ||
}(from, to, [options]) must be a string. Received undefined` | ||
); | ||
@@ -171,3 +258,3 @@ }); | ||
test.methodName | ||
}(from, to) must be a string. Received undefined` | ||
}(from, to, [options]) must be a string. Received undefined` | ||
); | ||
@@ -177,3 +264,19 @@ }); | ||
}); | ||
describe('"options" object', () => { | ||
describe('"overwrite" argument', () => { | ||
tests.forEach(test => { | ||
it(test.type, () => { | ||
expect(() => { | ||
test.method("abc", "xyz", { overwrite: 1 }); | ||
}).to.throw( | ||
`Argument "options.overwrite" passed to ${ | ||
test.methodName | ||
}(from, to, [options]) must be a boolean. Received number` | ||
); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); |
@@ -62,2 +62,89 @@ import * as pathUtil from "path"; | ||
describe("overwriting behaviour", () => { | ||
describe("does not overwrite by default", () => { | ||
const preparations = () => { | ||
fse.outputFileSync("file1.txt", "abc"); | ||
fse.outputFileSync("file2.txt", "xyz"); | ||
}; | ||
const expectations = (err: any) => { | ||
expect(err.code).to.equal("EEXIST"); | ||
expect(err.message).to.have.string("Destination path already exists"); | ||
path("file2.txt").shouldBeFileWithContent("xyz"); | ||
}; | ||
it("sync", () => { | ||
preparations(); | ||
try { | ||
jetpack.rename("file1.txt", "file2.txt"); | ||
throw new Error("Expected error to be thrown"); | ||
} catch (err) { | ||
expectations(err); | ||
} | ||
}); | ||
it("async", done => { | ||
preparations(); | ||
jetpack.renameAsync("file1.txt", "file2.txt").catch(err => { | ||
expectations(err); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
describe("overwrites if it was specified", () => { | ||
const preparations = () => { | ||
fse.outputFileSync("file1.txt", "abc"); | ||
fse.outputFileSync("file2.txt", "xyz"); | ||
}; | ||
const expectations = () => { | ||
path("file1.txt").shouldNotExist(); | ||
path("file2.txt").shouldBeFileWithContent("abc"); | ||
}; | ||
it("sync", () => { | ||
preparations(); | ||
jetpack.rename("file1.txt", "file2.txt", { overwrite: true }); | ||
expectations(); | ||
}); | ||
it("async", done => { | ||
preparations(); | ||
jetpack | ||
.renameAsync("file1.txt", "file2.txt", { overwrite: true }) | ||
.then(() => { | ||
expectations(); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
}); | ||
describe("can overwrite a directory", () => { | ||
const preparations = () => { | ||
fse.outputFileSync("file1.txt", "abc"); | ||
fse.mkdirsSync("dir"); | ||
}; | ||
const expectations = () => { | ||
path("file1.txt").shouldNotExist(); | ||
path("dir").shouldBeFileWithContent("abc"); | ||
}; | ||
it("sync", () => { | ||
preparations(); | ||
jetpack.rename("file1.txt", "dir", { overwrite: true }); | ||
expectations(); | ||
}); | ||
it("async", done => { | ||
preparations(); | ||
jetpack.renameAsync("file1.txt", "dir", { overwrite: true }).then(() => { | ||
expectations(); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
describe("respects internal CWD of jetpack instance", () => { | ||
@@ -108,3 +195,3 @@ const preparations = () => { | ||
test.methodName | ||
}(path, newName) must be a string. Received undefined` | ||
}(path, newName, [options]) must be a string. Received undefined` | ||
); | ||
@@ -124,3 +211,3 @@ }); | ||
test.methodName | ||
}(path, newName) must be a string. Received undefined` | ||
}(path, newName, [options]) must be a string. Received undefined` | ||
); | ||
@@ -140,3 +227,3 @@ }); | ||
test.methodName | ||
}(path, newName) should be a filename, not a path. Received "${pathToTest}"` | ||
}(path, newName, [options]) should be a filename, not a path. Received "${pathToTest}"` | ||
); | ||
@@ -147,3 +234,19 @@ }); | ||
}); | ||
describe('"options" object', () => { | ||
describe('"overwrite" argument', () => { | ||
tests.forEach(test => { | ||
it(test.type, () => { | ||
expect(() => { | ||
test.method("abc", "xyz", { overwrite: 1 }); | ||
}).to.throw( | ||
`Argument "options.overwrite" passed to ${ | ||
test.methodName | ||
}(path, newName, [options]) must be a boolean. Received number` | ||
); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); |
@@ -90,2 +90,10 @@ /// <reference types="node" /> | ||
type MoveOptions = { | ||
overwrite?: boolean; | ||
}; | ||
type RenameOptions = { | ||
overwrite?: boolean; | ||
}; | ||
// API has the same set of synchronous and asynchronous methods. | ||
@@ -310,4 +318,5 @@ // All async methods are promise based (no callbacks). | ||
* @param to path | ||
* @param options | ||
*/ | ||
move(from: string, to: string): void; | ||
move(from: string, to: string, options?: MoveOptions): void; | ||
@@ -319,4 +328,5 @@ /** | ||
* @param to path | ||
* @param options | ||
*/ | ||
moveAsync(from: string, to: string): Promise<void>; | ||
moveAsync(from: string, to: string, options?: MoveOptions): Promise<void>; | ||
@@ -367,4 +377,5 @@ /** | ||
* @param newName just the name of the thing being renamed | ||
* @param options | ||
*/ | ||
rename(path: string, newName: string): void; | ||
rename(path: string, newName: string, options?: RenameOptions): void; | ||
/** | ||
@@ -375,4 +386,9 @@ * Renames given file or directory. | ||
* @param newName just the name of the thing being renamed | ||
* @param options | ||
*/ | ||
renameAsync(path: string, newName: string): Promise<void>; | ||
renameAsync( | ||
path: string, | ||
newName: string, | ||
options?: RenameOptions | ||
): Promise<void>; | ||
@@ -379,0 +395,0 @@ /** |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
294151
8535
553