UNPKG

5.54 kBJavaScriptView Raw
1"use strict";
2
3const pathUtil = require("path");
4const fs = require("./utils/fs");
5const validate = require("./utils/validate");
6const copy = require("./copy");
7const dir = require("./dir");
8const exists = require("./exists");
9const remove = require("./remove");
10
11const validateInput = (methodName, from, to, options) => {
12 const methodSignature = `${methodName}(from, to, [options])`;
13 validate.argument(methodSignature, "from", from, ["string"]);
14 validate.argument(methodSignature, "to", to, ["string"]);
15 validate.options(methodSignature, "options", options, {
16 overwrite: ["boolean"]
17 });
18};
19
20const parseOptions = options => {
21 const opts = options || {};
22 return opts;
23};
24
25const generateDestinationExistsError = path => {
26 const err = new Error(`Destination path already exists ${path}`);
27 err.code = "EEXIST";
28 return err;
29};
30
31const generateSourceDoesntExistError = path => {
32 const err = new Error(`Path to move doesn't exist ${path}`);
33 err.code = "ENOENT";
34 return err;
35};
36
37// ---------------------------------------------------------
38// Sync
39// ---------------------------------------------------------
40
41const moveSync = (from, to, options) => {
42 const opts = parseOptions(options);
43
44 if (exists.sync(to) !== false && opts.overwrite !== true) {
45 throw generateDestinationExistsError(to);
46 }
47
48 // We now have permission to overwrite, since either `opts.overwrite` is true
49 // or the destination does not exist (in which overwriting is irrelevant).
50
51 try {
52 // If destination is a file, `fs.renameSync` will overwrite it.
53 fs.renameSync(from, to);
54 } catch (err) {
55 if (err.code === "EISDIR" || err.code === "EPERM") {
56 // Looks like the destination path is a directory in the same device,
57 // so we can remove it and call `fs.renameSync` again.
58 remove.sync(to);
59 fs.renameSync(from, to);
60 } else if (err.code === "EXDEV") {
61 // The destination path is in another device.
62 copy.sync(from, to, { overwrite: true });
63 remove.sync(from);
64 } else if (err.code === "ENOENT") {
65 // This can be caused by either the source not existing or one or more folders
66 // in the destination path not existing.
67 if (!exists.sync(from)) {
68 throw generateSourceDoesntExistError(from);
69 }
70
71 // One or more directories in the destination path don't exist.
72 dir.createSync(pathUtil.dirname(to));
73 // Retry the attempt
74 fs.renameSync(from, to);
75 } else {
76 // We can't make sense of this error. Rethrow it.
77 throw err;
78 }
79 }
80};
81
82// ---------------------------------------------------------
83// Async
84// ---------------------------------------------------------
85
86const ensureDestinationPathExistsAsync = to => {
87 return new Promise((resolve, reject) => {
88 const destDir = pathUtil.dirname(to);
89 exists
90 .async(destDir)
91 .then(dstExists => {
92 if (!dstExists) {
93 dir.createAsync(destDir).then(resolve, reject);
94 } else {
95 // Hah, no idea.
96 reject();
97 }
98 })
99 .catch(reject);
100 });
101};
102
103const moveAsync = (from, to, options) => {
104 const opts = parseOptions(options);
105
106 return new Promise((resolve, reject) => {
107 exists.async(to).then(destinationExists => {
108 if (destinationExists !== false && opts.overwrite !== true) {
109 reject(generateDestinationExistsError(to));
110 } else {
111 // We now have permission to overwrite, since either `opts.overwrite` is true
112 // or the destination does not exist (in which overwriting is irrelevant).
113 // If destination is a file, `fs.rename` will overwrite it.
114 fs.rename(from, to)
115 .then(resolve)
116 .catch(err => {
117 if (err.code === "EISDIR" || err.code === "EPERM") {
118 // Looks like the destination path is a directory in the same device,
119 // so we can remove it and call `fs.rename` again.
120 remove
121 .async(to)
122 .then(() => fs.rename(from, to))
123 .then(resolve, reject);
124 } else if (err.code === "EXDEV") {
125 // The destination path is in another device.
126 copy
127 .async(from, to, { overwrite: true })
128 .then(() => remove.async(from))
129 .then(resolve, reject);
130 } else if (err.code === "ENOENT") {
131 // This can be caused by either the source not existing or one or more folders
132 // in the destination path not existing.
133 exists
134 .async(from)
135 .then(srcExists => {
136 if (!srcExists) {
137 reject(generateSourceDoesntExistError(from));
138 } else {
139 // One or more directories in the destination path don't exist.
140 ensureDestinationPathExistsAsync(to)
141 .then(() => {
142 // Retry the attempt
143 return fs.rename(from, to);
144 })
145 .then(resolve, reject);
146 }
147 })
148 .catch(reject);
149 } else {
150 // Something unknown. Rethrow original error.
151 reject(err);
152 }
153 });
154 }
155 });
156 });
157};
158
159// ---------------------------------------------------------
160// API
161// ---------------------------------------------------------
162
163exports.validateInput = validateInput;
164exports.sync = moveSync;
165exports.async = moveAsync;