1 | ;
|
2 | /*
|
3 | * Copyright (c) 2020, salesforce.com, inc.
|
4 | * All rights reserved.
|
5 | * Licensed under the BSD 3-Clause license.
|
6 | * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
|
7 | */
|
8 | Object.defineProperty(exports, "__esModule", { value: true });
|
9 | exports.fs = void 0;
|
10 | const crypto = require("crypto");
|
11 | const path = require("path");
|
12 | const util_1 = require("util");
|
13 | const kit_1 = require("@salesforce/kit");
|
14 | const fsLib = require("graceful-fs");
|
15 | const mkdirpLib = require("mkdirp");
|
16 | const sfdxError_1 = require("../sfdxError");
|
17 | /**
|
18 | * @deprecated Use fs/promises instead
|
19 | */
|
20 | exports.fs = Object.assign({}, fsLib, {
|
21 | /**
|
22 | * The default file system mode to use when creating directories.
|
23 | */
|
24 | DEFAULT_USER_DIR_MODE: '700',
|
25 | /**
|
26 | * The default file system mode to use when creating files.
|
27 | */
|
28 | DEFAULT_USER_FILE_MODE: '600',
|
29 | /**
|
30 | * A convenience reference to {@link https://nodejs.org/api/fsLib.html#fs_fs_constants}
|
31 | * to reduce the need to import multiple `fs` modules.
|
32 | */
|
33 | constants: fsLib.constants,
|
34 | /**
|
35 | * Promisified version of {@link https://nodejs.org/api/fsLib.html#fs_fs_readfile_path_options_callback|fsLib.readFile}.
|
36 | */
|
37 | readFile: util_1.promisify(fsLib.readFile),
|
38 | /**
|
39 | * Promisified version of {@link https://nodejs.org/api/fsLib.html#fs_fs_readdir_path_options_callback|fsLib.readdir}.
|
40 | */
|
41 | readdir: util_1.promisify(fsLib.readdir),
|
42 | /**
|
43 | * Promisified version of {@link https://nodejs.org/api/fsLib.html#fs_fs_writefile_file_data_options_callback|fsLib.writeFile}.
|
44 | */
|
45 | writeFile: util_1.promisify(fsLib.writeFile),
|
46 | /**
|
47 | * Promisified version of {@link https://nodejs.org/api/fsLib.html#fs_fs_access_path_mode_callback|fsLib.access}.
|
48 | */
|
49 | access: util_1.promisify(fsLib.access),
|
50 | /**
|
51 | * Promisified version of {@link https://nodejs.org/api/fsLib.html#fs_fs_open_path_flags_mode_callback|fsLib.open}.
|
52 | */
|
53 | open: util_1.promisify(fsLib.open),
|
54 | /**
|
55 | * Promisified version of {@link https://nodejs.org/api/fsLib.html#fs_fs_unlink_path_callback|fsLib.unlink}.
|
56 | */
|
57 | unlink: util_1.promisify(fsLib.unlink),
|
58 | /**
|
59 | * Promisified version of {@link https://nodejs.org/api/fsLib.html#fs_fs_readdir_path_options_callback|fsLib.rmdir}.
|
60 | */
|
61 | rmdir: util_1.promisify(fsLib.rmdir),
|
62 | /**
|
63 | * Promisified version of {@link https://nodejs.org/api/fsLib.html#fs_fs_fstat_fd_callback|fsLib.stat}.
|
64 | */
|
65 | stat: util_1.promisify(fsLib.stat),
|
66 | /**
|
67 | * Promisified version of {@link https://npmjs.com/package/mkdirp|mkdirp}.
|
68 | */
|
69 | // eslint-disable-next-line @typescript-eslint/ban-types
|
70 | mkdirp: (folderPath, mode) => mkdirpLib(folderPath, mode),
|
71 | mkdirpSync: mkdirpLib.sync,
|
72 | /**
|
73 | * Deletes a folder recursively, removing all descending files and folders.
|
74 | *
|
75 | * **Throws** *PathIsNullOrUndefined* The path is not defined.
|
76 | * **Throws** *DirMissingOrNoAccess* The folder or any sub-folder is missing or has no access.
|
77 | *
|
78 | * @param {string} dirPath The path to remove.
|
79 | */
|
80 | remove: async (dirPath) => {
|
81 | if (!dirPath) {
|
82 | throw new sfdxError_1.SfdxError('Path is null or undefined.', 'PathIsNullOrUndefined');
|
83 | }
|
84 | try {
|
85 | await exports.fs.access(dirPath, fsLib.constants.R_OK);
|
86 | }
|
87 | catch (err) {
|
88 | throw new sfdxError_1.SfdxError(`The path: ${dirPath} doesn't exist or access is denied.`, 'DirMissingOrNoAccess');
|
89 | }
|
90 | const files = await exports.fs.readdir(dirPath);
|
91 | const stats = await Promise.all(files.map((file) => exports.fs.stat(path.join(dirPath, file))));
|
92 | const metas = stats.map((value, index) => Object.assign(value, { path: path.join(dirPath, files[index]) }));
|
93 | await Promise.all(metas.map((meta) => (meta.isDirectory() ? exports.fs.remove(meta.path) : exports.fs.unlink(meta.path))));
|
94 | await exports.fs.rmdir(dirPath);
|
95 | },
|
96 | /**
|
97 | * Deletes a folder recursively, removing all descending files and folders.
|
98 | *
|
99 | * NOTE: It is recommended to call the asynchronous `remove` when possible as it will remove all files in parallel rather than serially.
|
100 | *
|
101 | * **Throws** *PathIsNullOrUndefined* The path is not defined.
|
102 | * **Throws** *DirMissingOrNoAccess* The folder or any sub-folder is missing or has no access.
|
103 | *
|
104 | * @param {string} dirPath The path to remove.
|
105 | */
|
106 | removeSync: (dirPath) => {
|
107 | if (!dirPath) {
|
108 | throw new sfdxError_1.SfdxError('Path is null or undefined.', 'PathIsNullOrUndefined');
|
109 | }
|
110 | try {
|
111 | exports.fs.accessSync(dirPath, fsLib.constants.R_OK);
|
112 | }
|
113 | catch (err) {
|
114 | throw new sfdxError_1.SfdxError(`The path: ${dirPath} doesn't exist or access is denied.`, 'DirMissingOrNoAccess');
|
115 | }
|
116 | exports.fs.actOnSync(dirPath, (fullPath, file) => {
|
117 | if (file) {
|
118 | exports.fs.unlinkSync(fullPath);
|
119 | }
|
120 | else {
|
121 | // All files in this directory will be acted on before the directory.
|
122 | exports.fs.rmdirSync(fullPath);
|
123 | }
|
124 | }, 'all');
|
125 | // Remove the top level
|
126 | exports.fs.rmdirSync(dirPath);
|
127 | },
|
128 | /**
|
129 | * Searches a file path in an ascending manner (until reaching the filesystem root) for the first occurrence a
|
130 | * specific file name. Resolves with the directory path containing the located file, or `null` if the file was
|
131 | * not found.
|
132 | *
|
133 | * @param dir The directory path in which to start the upward search.
|
134 | * @param file The file name to look for.
|
135 | */
|
136 | traverseForFile: async (dir, file) => {
|
137 | let foundProjectDir;
|
138 | try {
|
139 | await exports.fs.stat(path.join(dir, file));
|
140 | foundProjectDir = dir;
|
141 | }
|
142 | catch (err) {
|
143 | if (err && err.code === 'ENOENT') {
|
144 | const nextDir = path.resolve(dir, '..');
|
145 | if (nextDir !== dir) {
|
146 | // stop at root
|
147 | foundProjectDir = await exports.fs.traverseForFile(nextDir, file);
|
148 | }
|
149 | }
|
150 | }
|
151 | return foundProjectDir;
|
152 | },
|
153 | /**
|
154 | * Searches a file path synchronously in an ascending manner (until reaching the filesystem root) for the first occurrence a
|
155 | * specific file name. Resolves with the directory path containing the located file, or `null` if the file was
|
156 | * not found.
|
157 | *
|
158 | * @param dir The directory path in which to start the upward search.
|
159 | * @param file The file name to look for.
|
160 | */
|
161 | traverseForFileSync: (dir, file) => {
|
162 | let foundProjectDir;
|
163 | try {
|
164 | exports.fs.statSync(path.join(dir, file));
|
165 | foundProjectDir = dir;
|
166 | }
|
167 | catch (err) {
|
168 | if (err && err.code === 'ENOENT') {
|
169 | const nextDir = path.resolve(dir, '..');
|
170 | if (nextDir !== dir) {
|
171 | // stop at root
|
172 | foundProjectDir = exports.fs.traverseForFileSync(nextDir, file);
|
173 | }
|
174 | }
|
175 | }
|
176 | return foundProjectDir;
|
177 | },
|
178 | /**
|
179 | * Read a file and convert it to JSON. Returns the contents of the file as a JSON object
|
180 | *
|
181 | * @param jsonPath The path of the file.
|
182 | * @param throwOnEmpty Whether to throw an error if the JSON file is empty.
|
183 | */
|
184 | readJson: async (jsonPath, throwOnEmpty) => {
|
185 | const fileData = await exports.fs.readFile(jsonPath, 'utf8');
|
186 | return kit_1.parseJson(fileData, jsonPath, throwOnEmpty);
|
187 | },
|
188 | /**
|
189 | * Read a file and convert it to JSON. Returns the contents of the file as a JSON object
|
190 | *
|
191 | * @param jsonPath The path of the file.
|
192 | * @param throwOnEmpty Whether to throw an error if the JSON file is empty.
|
193 | */
|
194 | readJsonSync: (jsonPath, throwOnEmpty) => {
|
195 | const fileData = exports.fs.readFileSync(jsonPath, 'utf8');
|
196 | return kit_1.parseJson(fileData, jsonPath, throwOnEmpty);
|
197 | },
|
198 | /**
|
199 | * Read a file and convert it to JSON, throwing an error if the parsed contents are not a `JsonMap`.
|
200 | *
|
201 | * @param jsonPath The path of the file.
|
202 | * @param throwOnEmpty Whether to throw an error if the JSON file is empty.
|
203 | */
|
204 | readJsonMap: async (jsonPath, throwOnEmpty) => {
|
205 | const fileData = await exports.fs.readFile(jsonPath, 'utf8');
|
206 | return kit_1.parseJsonMap(fileData, jsonPath, throwOnEmpty);
|
207 | },
|
208 | /**
|
209 | * Read a file and convert it to JSON, throwing an error if the parsed contents are not a `JsonMap`.
|
210 | *
|
211 | * @param jsonPath The path of the file.
|
212 | * @param throwOnEmpty Whether to throw an error if the JSON file is empty.
|
213 | */
|
214 | readJsonMapSync: (jsonPath, throwOnEmpty) => {
|
215 | const fileData = exports.fs.readFileSync(jsonPath, 'utf8');
|
216 | return kit_1.parseJsonMap(fileData, jsonPath, throwOnEmpty);
|
217 | },
|
218 | /**
|
219 | * Convert a JSON-compatible object to a `string` and write it to a file.
|
220 | *
|
221 | * @param jsonPath The path of the file to write.
|
222 | * @param data The JSON object to write.
|
223 | */
|
224 | writeJson: async (jsonPath, data, options = {}) => {
|
225 | options = Object.assign({ space: 2 }, options);
|
226 | const fileData = JSON.stringify(data, null, options.space);
|
227 | await exports.fs.writeFile(jsonPath, fileData, {
|
228 | encoding: 'utf8',
|
229 | mode: exports.fs.DEFAULT_USER_FILE_MODE,
|
230 | });
|
231 | },
|
232 | /**
|
233 | * Convert a JSON-compatible object to a `string` and write it to a file.
|
234 | *
|
235 | * @param jsonPath The path of the file to write.
|
236 | * @param data The JSON object to write.
|
237 | */
|
238 | writeJsonSync: (jsonPath, data, options = {}) => {
|
239 | options = Object.assign({ space: 2 }, options);
|
240 | const fileData = JSON.stringify(data, null, options.space);
|
241 | exports.fs.writeFileSync(jsonPath, fileData, {
|
242 | encoding: 'utf8',
|
243 | mode: exports.fs.DEFAULT_USER_FILE_MODE,
|
244 | });
|
245 | },
|
246 | /**
|
247 | * Checks if a file path exists
|
248 | *
|
249 | * @param filePath the file path to check the existence of
|
250 | */
|
251 | fileExists: async (filePath) => {
|
252 | try {
|
253 | await exports.fs.access(filePath);
|
254 | return true;
|
255 | }
|
256 | catch (err) {
|
257 | return false;
|
258 | }
|
259 | },
|
260 | /**
|
261 | * Checks if a file path exists
|
262 | *
|
263 | * @param filePath the file path to check the existence of
|
264 | */
|
265 | fileExistsSync: (filePath) => {
|
266 | try {
|
267 | exports.fs.accessSync(filePath);
|
268 | return true;
|
269 | }
|
270 | catch (err) {
|
271 | return false;
|
272 | }
|
273 | },
|
274 | /**
|
275 | * Recursively act on all files or directories in a directory
|
276 | *
|
277 | * @param dir path to directory
|
278 | * @param perform function to be run on contents of dir
|
279 | * @param onType optional parameter to specify type to actOn
|
280 | * @returns void
|
281 | */
|
282 | actOn: async (dir, perform, onType = 'file') => {
|
283 | for (const file of await exports.fs.readdir(dir)) {
|
284 | const filePath = path.join(dir, file);
|
285 | const stat = await exports.fs.stat(filePath);
|
286 | if (stat) {
|
287 | if (stat.isDirectory()) {
|
288 | await exports.fs.actOn(filePath, perform, onType);
|
289 | if (onType === 'dir' || onType === 'all') {
|
290 | await perform(filePath);
|
291 | }
|
292 | }
|
293 | else if (stat.isFile() && (onType === 'file' || onType === 'all')) {
|
294 | await perform(filePath, file, dir);
|
295 | }
|
296 | }
|
297 | }
|
298 | },
|
299 | /**
|
300 | * Recursively act on all files or directories in a directory
|
301 | *
|
302 | * @param dir path to directory
|
303 | * @param perform function to be run on contents of dir
|
304 | * @param onType optional parameter to specify type to actOn
|
305 | * @returns void
|
306 | */
|
307 | actOnSync: (dir, perform, onType = 'file') => {
|
308 | for (const file of exports.fs.readdirSync(dir)) {
|
309 | const filePath = path.join(dir, file);
|
310 | const stat = exports.fs.statSync(filePath);
|
311 | if (stat) {
|
312 | if (stat.isDirectory()) {
|
313 | exports.fs.actOnSync(filePath, perform, onType);
|
314 | if (onType === 'dir' || onType === 'all') {
|
315 | perform(filePath);
|
316 | }
|
317 | }
|
318 | else if (stat.isFile() && (onType === 'file' || onType === 'all')) {
|
319 | perform(filePath, file, dir);
|
320 | }
|
321 | }
|
322 | }
|
323 | },
|
324 | /**
|
325 | * Checks if files are the same
|
326 | *
|
327 | * @param file1Path the first file path to check
|
328 | * @param file2Path the second file path to check
|
329 | * @returns boolean
|
330 | */
|
331 | areFilesEqual: async (file1Path, file2Path) => {
|
332 | try {
|
333 | const file1Size = (await exports.fs.stat(file1Path)).size;
|
334 | const file2Size = (await exports.fs.stat(file2Path)).size;
|
335 | if (file1Size !== file2Size) {
|
336 | return false;
|
337 | }
|
338 | const contentA = await exports.fs.readFile(file1Path);
|
339 | const contentB = await exports.fs.readFile(file2Path);
|
340 | return exports.fs.getContentHash(contentA) === exports.fs.getContentHash(contentB);
|
341 | }
|
342 | catch (err) {
|
343 | throw new sfdxError_1.SfdxError(`The path: ${err.path} doesn't exist or access is denied.`, 'DirMissingOrNoAccess');
|
344 | }
|
345 | },
|
346 | /**
|
347 | * Checks if files are the same
|
348 | *
|
349 | * @param file1Path the first file path to check
|
350 | * @param file2Path the second file path to check
|
351 | * @returns boolean
|
352 | */
|
353 | areFilesEqualSync: (file1Path, file2Path) => {
|
354 | try {
|
355 | const file1Size = exports.fs.statSync(file1Path).size;
|
356 | const file2Size = exports.fs.statSync(file2Path).size;
|
357 | if (file1Size !== file2Size) {
|
358 | return false;
|
359 | }
|
360 | const contentA = exports.fs.readFileSync(file1Path);
|
361 | const contentB = exports.fs.readFileSync(file2Path);
|
362 | return exports.fs.getContentHash(contentA) === exports.fs.getContentHash(contentB);
|
363 | }
|
364 | catch (err) {
|
365 | throw new sfdxError_1.SfdxError(`The path: ${err.path} doesn't exist or access is denied.`, 'DirMissingOrNoAccess');
|
366 | }
|
367 | },
|
368 | /**
|
369 | * Creates a hash for the string that's passed in
|
370 | *
|
371 | * @param contents The string passed into the function
|
372 | * @returns string
|
373 | */
|
374 | getContentHash(contents) {
|
375 | return crypto.createHash('sha1').update(contents).digest('hex');
|
376 | },
|
377 | });
|
378 | //# sourceMappingURL=fs.js.map |
\ | No newline at end of file |