UNPKG

6.56 kBJavaScriptView Raw
1"use strict";
2
3const pathUtil = require("path");
4const fs = require("./utils/fs");
5const modeUtil = require("./utils/mode");
6const validate = require("./utils/validate");
7const remove = require("./remove");
8
9const validateInput = (methodName, path, criteria) => {
10 const methodSignature = `${methodName}(path, [criteria])`;
11 validate.argument(methodSignature, "path", path, ["string"]);
12 validate.options(methodSignature, "criteria", criteria, {
13 empty: ["boolean"],
14 mode: ["string", "number"]
15 });
16};
17
18const getCriteriaDefaults = passedCriteria => {
19 const criteria = passedCriteria || {};
20 if (typeof criteria.empty !== "boolean") {
21 criteria.empty = false;
22 }
23 if (criteria.mode !== undefined) {
24 criteria.mode = modeUtil.normalizeFileMode(criteria.mode);
25 }
26 return criteria;
27};
28
29const generatePathOccupiedByNotDirectoryError = path => {
30 return new Error(
31 `Path ${path} exists but is not a directory. Halting jetpack.dir() call for safety reasons.`
32 );
33};
34
35// ---------------------------------------------------------
36// Sync
37// ---------------------------------------------------------
38
39const checkWhatAlreadyOccupiesPathSync = path => {
40 let stat;
41
42 try {
43 stat = fs.statSync(path);
44 } catch (err) {
45 // Detection if path already exists
46 if (err.code !== "ENOENT") {
47 throw err;
48 }
49 }
50
51 if (stat && !stat.isDirectory()) {
52 throw generatePathOccupiedByNotDirectoryError(path);
53 }
54
55 return stat;
56};
57
58const createBrandNewDirectorySync = (path, opts) => {
59 const options = opts || {};
60
61 try {
62 fs.mkdirSync(path, options.mode);
63 } catch (err) {
64 if (err.code === "ENOENT") {
65 // Parent directory doesn't exist. Need to create it first.
66 createBrandNewDirectorySync(pathUtil.dirname(path), options);
67 // Now retry creating this directory.
68 fs.mkdirSync(path, options.mode);
69 } else if (err.code === "EEXIST") {
70 // The path already exists. We're fine.
71 } else {
72 throw err;
73 }
74 }
75};
76
77const checkExistingDirectoryFulfillsCriteriaSync = (path, stat, criteria) => {
78 const checkMode = () => {
79 const mode = modeUtil.normalizeFileMode(stat.mode);
80 if (criteria.mode !== undefined && criteria.mode !== mode) {
81 fs.chmodSync(path, criteria.mode);
82 }
83 };
84
85 const checkEmptiness = () => {
86 if (criteria.empty) {
87 // Delete everything inside this directory
88 const list = fs.readdirSync(path);
89 list.forEach(filename => {
90 remove.sync(pathUtil.resolve(path, filename));
91 });
92 }
93 };
94
95 checkMode();
96 checkEmptiness();
97};
98
99const dirSync = (path, passedCriteria) => {
100 const criteria = getCriteriaDefaults(passedCriteria);
101 const stat = checkWhatAlreadyOccupiesPathSync(path);
102 if (stat) {
103 checkExistingDirectoryFulfillsCriteriaSync(path, stat, criteria);
104 } else {
105 createBrandNewDirectorySync(path, criteria);
106 }
107};
108
109// ---------------------------------------------------------
110// Async
111// ---------------------------------------------------------
112
113const checkWhatAlreadyOccupiesPathAsync = path => {
114 return new Promise((resolve, reject) => {
115 fs.stat(path)
116 .then(stat => {
117 if (stat.isDirectory()) {
118 resolve(stat);
119 } else {
120 reject(generatePathOccupiedByNotDirectoryError(path));
121 }
122 })
123 .catch(err => {
124 if (err.code === "ENOENT") {
125 // Path doesn't exist
126 resolve(undefined);
127 } else {
128 // This is other error that nonexistent path, so end here.
129 reject(err);
130 }
131 });
132 });
133};
134
135// Delete all files and directores inside given directory
136const emptyAsync = path => {
137 return new Promise((resolve, reject) => {
138 fs.readdir(path)
139 .then(list => {
140 const doOne = index => {
141 if (index === list.length) {
142 resolve();
143 } else {
144 const subPath = pathUtil.resolve(path, list[index]);
145 remove.async(subPath).then(() => {
146 doOne(index + 1);
147 });
148 }
149 };
150
151 doOne(0);
152 })
153 .catch(reject);
154 });
155};
156
157const checkExistingDirectoryFulfillsCriteriaAsync = (path, stat, criteria) => {
158 return new Promise((resolve, reject) => {
159 const checkMode = () => {
160 const mode = modeUtil.normalizeFileMode(stat.mode);
161 if (criteria.mode !== undefined && criteria.mode !== mode) {
162 return fs.chmod(path, criteria.mode);
163 }
164 return Promise.resolve();
165 };
166
167 const checkEmptiness = () => {
168 if (criteria.empty) {
169 return emptyAsync(path);
170 }
171 return Promise.resolve();
172 };
173
174 checkMode()
175 .then(checkEmptiness)
176 .then(resolve, reject);
177 });
178};
179
180const createBrandNewDirectoryAsync = (path, opts) => {
181 const options = opts || {};
182
183 return new Promise((resolve, reject) => {
184 fs.mkdir(path, options.mode)
185 .then(resolve)
186 .catch(err => {
187 if (err.code === "ENOENT") {
188 // Parent directory doesn't exist. Need to create it first.
189 createBrandNewDirectoryAsync(pathUtil.dirname(path), options)
190 .then(() => {
191 // Now retry creating this directory.
192 return fs.mkdir(path, options.mode);
193 })
194 .then(resolve)
195 .catch(err2 => {
196 if (err2.code === "EEXIST") {
197 // Hmm, something other have already created the directory?
198 // No problem for us.
199 resolve();
200 } else {
201 reject(err2);
202 }
203 });
204 } else if (err.code === "EEXIST") {
205 // The path already exists. We're fine.
206 resolve();
207 } else {
208 reject(err);
209 }
210 });
211 });
212};
213
214const dirAsync = (path, passedCriteria) => {
215 return new Promise((resolve, reject) => {
216 const criteria = getCriteriaDefaults(passedCriteria);
217
218 checkWhatAlreadyOccupiesPathAsync(path)
219 .then(stat => {
220 if (stat !== undefined) {
221 return checkExistingDirectoryFulfillsCriteriaAsync(
222 path,
223 stat,
224 criteria
225 );
226 }
227 return createBrandNewDirectoryAsync(path, criteria);
228 })
229 .then(resolve, reject);
230 });
231};
232
233// ---------------------------------------------------------
234// API
235// ---------------------------------------------------------
236
237exports.validateInput = validateInput;
238exports.sync = dirSync;
239exports.createSync = createBrandNewDirectorySync;
240exports.async = dirAsync;
241exports.createAsync = createBrandNewDirectoryAsync;