186 lines
5.8 KiB
JavaScript
186 lines
5.8 KiB
JavaScript
import * as fs from 'node:fs';
|
|
import * as pathUtil from 'node:path';
|
|
import { stat, statSync, readdirSync, readdir } from 'fs';
|
|
import { promisify } from 'util';
|
|
import { sync as removeSync, async as removeAsync } from './remove.js';
|
|
import { normalizeFileMode as modeUtil } from './utils/mode.js';
|
|
import { validateArgument, validateOptions } from './utils/validate.js';
|
|
import { ErrNoDirectory } from './errors.js';
|
|
import { EError } from './interfaces.js';
|
|
import { sync as mkdirp } from 'mkdirp';
|
|
export const validateInput = (methodName, path, options) => {
|
|
const methodSignature = methodName + '(path, [criteria])';
|
|
validateArgument(methodSignature, 'path', path, ['string']);
|
|
validateOptions(methodSignature, 'criteria', options, {
|
|
empty: ['boolean'],
|
|
mode: ['string', 'number']
|
|
});
|
|
};
|
|
const defaults = (options) => {
|
|
const result = options || {};
|
|
if (typeof result.empty !== 'boolean') {
|
|
result.empty = false;
|
|
}
|
|
if (result.mode !== undefined) {
|
|
result.mode = modeUtil(result.mode);
|
|
}
|
|
return result;
|
|
};
|
|
// ---------------------------------------------------------
|
|
// Sync
|
|
// ---------------------------------------------------------
|
|
const dirStatsSync = (path) => {
|
|
let _stat;
|
|
try {
|
|
_stat = statSync(path);
|
|
}
|
|
catch (err) {
|
|
// Detection if path already exists
|
|
if (err.code !== EError.NOEXISTS) {
|
|
throw err;
|
|
}
|
|
}
|
|
if (_stat && !_stat.isDirectory()) {
|
|
throw ErrNoDirectory(path);
|
|
}
|
|
return _stat;
|
|
};
|
|
function mkdirSync(path, criteria) {
|
|
mkdirp(path, { mode: criteria.mode, fs: null });
|
|
}
|
|
function checkDirSync(path, _stat, options) {
|
|
const check = function () {
|
|
if (options.mode !== undefined) {
|
|
fs.chmodSync(path, options.mode);
|
|
}
|
|
};
|
|
const checkEmptiness = function () {
|
|
let list;
|
|
if (options.empty) {
|
|
// Delete everything inside this directory
|
|
list = readdirSync(path);
|
|
list.forEach(function (filename) {
|
|
removeSync(pathUtil.resolve(path, filename));
|
|
});
|
|
}
|
|
};
|
|
check();
|
|
checkEmptiness();
|
|
}
|
|
export const sync = (path, options) => {
|
|
const criteria = defaults(options);
|
|
const _stat = dirStatsSync(path);
|
|
if (_stat) {
|
|
checkDirSync(path, _stat, criteria);
|
|
}
|
|
else {
|
|
mkdirSync(path, criteria);
|
|
}
|
|
};
|
|
// ---------------------------------------------------------
|
|
// Async
|
|
// ---------------------------------------------------------
|
|
const promisedStat = promisify(stat);
|
|
const promisedReaddir = promisify(readdir);
|
|
const dirStatAsync = (path) => {
|
|
return new Promise((resolve, reject) => {
|
|
promisedStat(path)
|
|
.then((_stat) => {
|
|
if (_stat.isDirectory()) {
|
|
resolve(_stat);
|
|
}
|
|
else {
|
|
reject(ErrNoDirectory(path));
|
|
}
|
|
})
|
|
.catch((err) => (err.code === EError.NOEXISTS ? resolve(undefined) : reject(err)));
|
|
});
|
|
};
|
|
// Delete all files and directores inside given directory
|
|
const emptyAsync = (path) => {
|
|
return new Promise((resolve, reject) => {
|
|
promisedReaddir(path)
|
|
.then(function (list) {
|
|
const doOne = function (index) {
|
|
let subPath;
|
|
if (index === list.length) {
|
|
resolve(1);
|
|
}
|
|
else {
|
|
subPath = pathUtil.resolve(path, list[index]);
|
|
removeAsync(subPath).then(() => doOne(index + 1));
|
|
}
|
|
};
|
|
doOne(0);
|
|
})
|
|
.catch(reject);
|
|
});
|
|
};
|
|
const checkMode = function (criteria, _stat, path) {
|
|
if (criteria.mode !== undefined) {
|
|
return promisify(fs.chmod)(path, criteria.mode);
|
|
}
|
|
return Promise.resolve(null);
|
|
};
|
|
const checkDirAsync = (path, _stat, options) => {
|
|
return new Promise((resolve, reject) => {
|
|
const checkEmptiness = function () {
|
|
if (options.empty) {
|
|
return emptyAsync(path);
|
|
}
|
|
return Promise.resolve();
|
|
};
|
|
checkMode(options, _stat, path)
|
|
.then(checkEmptiness)
|
|
.then(resolve, reject);
|
|
});
|
|
};
|
|
const mkdirAsync = (path, criteria) => {
|
|
const options = criteria || {};
|
|
return new Promise((resolve, reject) => {
|
|
promisify(fs.mkdir)(path, options.mode)
|
|
.then(resolve)
|
|
.catch((err) => {
|
|
if (err.code === 'ENOENT') {
|
|
// Parent directory doesn't exist. Need to create it first.
|
|
mkdirAsync(pathUtil.dirname(path), options)
|
|
.then(() => {
|
|
// Now retry creating this directory.
|
|
return promisify(fs.mkdir)(path, options.mode);
|
|
})
|
|
.then(resolve)
|
|
.catch((err2) => {
|
|
if (err2.code === 'EEXIST') {
|
|
// Hmm, something other have already created the directory?
|
|
// No problem for us.
|
|
resolve(1);
|
|
}
|
|
else {
|
|
reject(err2);
|
|
}
|
|
});
|
|
}
|
|
else if (err.code === 'EEXIST') {
|
|
// The path already exists. We're fine.
|
|
resolve(1);
|
|
}
|
|
else {
|
|
reject(err);
|
|
}
|
|
});
|
|
});
|
|
};
|
|
export const async = (path, passedCriteria) => {
|
|
const criteria = defaults(passedCriteria);
|
|
return new Promise((resolve, reject) => {
|
|
dirStatAsync(path)
|
|
.then((_stat) => {
|
|
if (_stat !== undefined) {
|
|
return checkDirAsync(path, _stat, criteria);
|
|
}
|
|
return mkdirAsync(path, criteria);
|
|
})
|
|
.then(resolve, reject);
|
|
});
|
|
};
|