Swap-Aggregator-Subgraph/node_modules/fs-jetpack/lib/copy.js
2022-07-03 07:27:35 +05:30

340 lines
9.8 KiB
JavaScript

"use strict";
const pathUtil = require("path");
const fs = require("./utils/fs");
const dir = require("./dir");
const exists = require("./exists");
const inspect = require("./inspect");
const write = require("./write");
const matcher = require("./utils/matcher");
const fileMode = require("./utils/mode");
const treeWalker = require("./utils/tree_walker");
const validate = require("./utils/validate");
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", "function"],
matching: ["string", "array of string"],
ignoreCase: ["boolean"]
});
};
const parseOptions = (options, from) => {
const opts = options || {};
const parsedOptions = {};
if (opts.ignoreCase === undefined) {
opts.ignoreCase = false;
}
parsedOptions.overwrite = opts.overwrite;
if (opts.matching) {
parsedOptions.allowedToCopy = matcher.create(
from,
opts.matching,
opts.ignoreCase
);
} else {
parsedOptions.allowedToCopy = () => {
// Default behaviour - copy everything.
return true;
};
}
return parsedOptions;
};
const generateNoSourceError = path => {
const err = new Error(`Path to copy doesn't exist ${path}`);
err.code = "ENOENT";
return err;
};
const generateDestinationExistsError = path => {
const err = new Error(`Destination path already exists ${path}`);
err.code = "EEXIST";
return err;
};
const inspectOptions = {
mode: true,
symlinks: "report",
times: true,
absolutePath: true
};
const shouldThrowDestinationExistsError = context => {
return (
typeof context.opts.overwrite !== "function" &&
context.opts.overwrite !== true
);
};
// ---------------------------------------------------------
// Sync
// ---------------------------------------------------------
const checksBeforeCopyingSync = (from, to, opts) => {
if (!exists.sync(from)) {
throw generateNoSourceError(from);
}
if (exists.sync(to) && !opts.overwrite) {
throw generateDestinationExistsError(to);
}
};
const canOverwriteItSync = context => {
if (typeof context.opts.overwrite === "function") {
const destInspectData = inspect.sync(context.destPath, inspectOptions);
return context.opts.overwrite(context.srcInspectData, destInspectData);
}
return context.opts.overwrite === true;
};
const copyFileSync = (srcPath, destPath, mode, context) => {
const data = fs.readFileSync(srcPath);
try {
fs.writeFileSync(destPath, data, { mode, flag: "wx" });
} catch (err) {
if (err.code === "ENOENT") {
write.sync(destPath, data, { mode });
} else if (err.code === "EEXIST") {
if (canOverwriteItSync(context)) {
fs.writeFileSync(destPath, data, { mode });
} else if (shouldThrowDestinationExistsError(context)) {
throw generateDestinationExistsError(context.destPath);
}
} else {
throw err;
}
}
};
const copySymlinkSync = (from, to) => {
const symlinkPointsAt = fs.readlinkSync(from);
try {
fs.symlinkSync(symlinkPointsAt, to);
} catch (err) {
// There is already file/symlink with this name on destination location.
// Must erase it manually, otherwise system won't allow us to place symlink there.
if (err.code === "EEXIST") {
fs.unlinkSync(to);
// Retry...
fs.symlinkSync(symlinkPointsAt, to);
} else {
throw err;
}
}
};
const copyItemSync = (srcPath, srcInspectData, destPath, opts) => {
const context = { srcPath, destPath, srcInspectData, opts };
const mode = fileMode.normalizeFileMode(srcInspectData.mode);
if (srcInspectData.type === "dir") {
dir.createSync(destPath, { mode });
} else if (srcInspectData.type === "file") {
copyFileSync(srcPath, destPath, mode, context);
} else if (srcInspectData.type === "symlink") {
copySymlinkSync(srcPath, destPath);
}
};
const copySync = (from, to, options) => {
const opts = parseOptions(options, from);
checksBeforeCopyingSync(from, to, opts);
treeWalker.sync(from, { inspectOptions }, (srcPath, srcInspectData) => {
const rel = pathUtil.relative(from, srcPath);
const destPath = pathUtil.resolve(to, rel);
if (opts.allowedToCopy(srcPath, destPath, srcInspectData)) {
copyItemSync(srcPath, srcInspectData, destPath, opts);
}
});
};
// ---------------------------------------------------------
// Async
// ---------------------------------------------------------
const checksBeforeCopyingAsync = (from, to, opts) => {
return exists
.async(from)
.then(srcPathExists => {
if (!srcPathExists) {
throw generateNoSourceError(from);
} else {
return exists.async(to);
}
})
.then(destPathExists => {
if (destPathExists && !opts.overwrite) {
throw generateDestinationExistsError(to);
}
});
};
const canOverwriteItAsync = context => {
return new Promise((resolve, reject) => {
if (typeof context.opts.overwrite === "function") {
inspect
.async(context.destPath, inspectOptions)
.then(destInspectData => {
resolve(
context.opts.overwrite(context.srcInspectData, destInspectData)
);
})
.catch(reject);
} else {
resolve(context.opts.overwrite === true);
}
});
};
const copyFileAsync = (srcPath, destPath, mode, context, runOptions) => {
return new Promise((resolve, reject) => {
const runOpts = runOptions || {};
let flags = "wx";
if (runOpts.overwrite) {
flags = "w";
}
const readStream = fs.createReadStream(srcPath);
const writeStream = fs.createWriteStream(destPath, { mode, flags });
readStream.on("error", reject);
writeStream.on("error", err => {
// Force read stream to close, since write stream errored
// read stream serves us no purpose.
readStream.resume();
if (err.code === "ENOENT") {
// Some parent directory doesn't exits. Create it and retry.
dir
.createAsync(pathUtil.dirname(destPath))
.then(() => {
copyFileAsync(srcPath, destPath, mode, context).then(
resolve,
reject
);
})
.catch(reject);
} else if (err.code === "EEXIST") {
canOverwriteItAsync(context)
.then(canOverwite => {
if (canOverwite) {
copyFileAsync(srcPath, destPath, mode, context, {
overwrite: true
}).then(resolve, reject);
} else if (shouldThrowDestinationExistsError(context)) {
reject(generateDestinationExistsError(destPath));
} else {
resolve();
}
})
.catch(reject);
} else {
reject(err);
}
});
writeStream.on("finish", resolve);
readStream.pipe(writeStream);
});
};
const copySymlinkAsync = (from, to) => {
return fs.readlink(from).then(symlinkPointsAt => {
return new Promise((resolve, reject) => {
fs.symlink(symlinkPointsAt, to)
.then(resolve)
.catch(err => {
if (err.code === "EEXIST") {
// There is already file/symlink with this name on destination location.
// Must erase it manually, otherwise system won't allow us to place symlink there.
fs.unlink(to)
.then(() => {
// Retry...
return fs.symlink(symlinkPointsAt, to);
})
.then(resolve, reject);
} else {
reject(err);
}
});
});
});
};
const copyItemAsync = (srcPath, srcInspectData, destPath, opts) => {
const context = { srcPath, destPath, srcInspectData, opts };
const mode = fileMode.normalizeFileMode(srcInspectData.mode);
if (srcInspectData.type === "dir") {
return dir.createAsync(destPath, { mode });
} else if (srcInspectData.type === "file") {
return copyFileAsync(srcPath, destPath, mode, context);
} else if (srcInspectData.type === "symlink") {
return copySymlinkAsync(srcPath, destPath);
}
// Ha! This is none of supported file system entities. What now?
// Just continuing without actually copying sounds sane.
return Promise.resolve();
};
const copyAsync = (from, to, options) => {
return new Promise((resolve, reject) => {
const opts = parseOptions(options, from);
checksBeforeCopyingAsync(from, to, opts)
.then(() => {
let allFilesDelivered = false;
let filesInProgress = 0;
const stream = treeWalker
.stream(from, { inspectOptions })
.on("readable", () => {
const item = stream.read();
if (item) {
const rel = pathUtil.relative(from, item.path);
const destPath = pathUtil.resolve(to, rel);
if (opts.allowedToCopy(item.path, item.item, destPath)) {
filesInProgress += 1;
copyItemAsync(item.path, item.item, destPath, opts)
.then(() => {
filesInProgress -= 1;
if (allFilesDelivered && filesInProgress === 0) {
resolve();
}
})
.catch(reject);
}
}
})
.on("error", reject)
.on("end", () => {
allFilesDelivered = true;
if (allFilesDelivered && filesInProgress === 0) {
resolve();
}
});
})
.catch(reject);
});
};
// ---------------------------------------------------------
// API
// ---------------------------------------------------------
exports.validateInput = validateInput;
exports.sync = copySync;
exports.async = copyAsync;