sync-content
Version:
Synchronize the contents of one folder to another location, only copying files if contents differ.
293 lines • 10.4 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.syncContentSync = exports.syncContent = void 0;
const glob_1 = require("glob");
const mkdirp_1 = require("mkdirp");
const node_constants_1 = __importDefault(require("node:constants"));
const node_crypto_1 = require("node:crypto");
const node_fs_1 = require("node:fs");
const promises_1 = require("node:fs/promises");
const node_path_1 = require("node:path");
const path_scurry_1 = require("path-scurry");
const rimraf_1 = require("rimraf");
const mkdirpClobber = async (dir) => {
try {
return await (0, mkdirp_1.mkdirp)(dir);
}
catch (e) {
await mkdirpClobber((0, node_path_1.dirname)(dir));
await (0, rimraf_1.rimraf)(dir);
return await (0, mkdirp_1.mkdirp)(dir);
}
};
const mkdirpClobberSync = (dir) => {
try {
return (0, mkdirp_1.mkdirpSync)(dir);
}
catch (e) {
mkdirpClobberSync((0, node_path_1.dirname)(dir));
(0, rimraf_1.rimrafSync)(dir);
return (0, mkdirp_1.mkdirpSync)(dir);
}
};
const syncFile = async (src, dest) => {
// only sync files, dirs, and symlinks
// Creating these is a pain to test cross platform
/* c8 ignore start */
if (!(src.isSymbolicLink() || src.isDirectory() || src.isFile())) {
if (!dest.isENOENT())
await (0, rimraf_1.rimraf)(dest.fullpath());
return;
}
/* c8 ignore stop */
if (src.isSymbolicLink()) {
const target = await (0, promises_1.readlink)(src.fullpath());
if (!dest.isENOENT()) {
const dp = dest.isSymbolicLink() && (await (0, promises_1.readlink)(dest.fullpath()));
if (dp === target)
return;
await (0, rimraf_1.rimraf)(dest.fullpath());
}
await mkdirpClobber((0, node_path_1.dirname)(dest.fullpath()));
await (0, promises_1.symlink)(target, dest.fullpath());
/* c8 ignore start */
if (node_constants_1.default.O_SYMLINK && src.mode) {
try {
await (0, promises_1.chmod)(dest.fullpath(), src.mode);
}
catch { }
}
/* c8 ignore stop */
return;
}
if (src.isDirectory()) {
if (!dest.isDirectory()) {
await mkdirpClobber(dest.fullpath());
}
}
else {
// must be file
let write = true;
if (!dest.isENOENT() && !dest.isFile())
await (0, rimraf_1.rimraf)(dest.fullpath());
else if (await contentMatch(src, dest))
write = false;
if (write) {
await mkdirpClobber((0, node_path_1.dirname)(dest.fullpath()));
// platform specific
/* c8 ignore start */
await (0, promises_1.link)(src.fullpath(), dest.fullpath()).catch(() => (0, promises_1.copyFile)(src.fullpath(), dest.fullpath()));
/* c8 ignore stop */
}
}
const mode = src.mode;
/* c8 ignore start */
if (!mode)
return;
/* c8 ignore stop */
await (0, promises_1.chmod)(dest.fullpath(), mode);
};
const syncFileSync = (src, dest) => {
// only sync files, dirs, and symlinks
// Creating these is a pain to test cross platform
/* c8 ignore start */
if (!(src.isSymbolicLink() || src.isDirectory() || src.isFile())) {
if (!dest.isENOENT())
(0, rimraf_1.rimrafSync)(dest.fullpath());
return;
}
/* c8 ignore stop */
if (src.isSymbolicLink()) {
const target = (0, node_fs_1.readlinkSync)(src.fullpath());
if (!dest.isENOENT()) {
const dp = dest.isSymbolicLink() && (0, node_fs_1.readlinkSync)(dest.fullpath());
if (dp === target)
return;
(0, rimraf_1.rimrafSync)(dest.fullpath());
}
mkdirpClobberSync((0, node_path_1.dirname)(dest.fullpath()));
(0, node_fs_1.symlinkSync)(target, dest.fullpath());
/* c8 ignore start */
if (node_constants_1.default.O_SYMLINK && src.mode) {
try {
(0, node_fs_1.chmodSync)(dest.fullpath(), src.mode);
}
catch { }
}
/* c8 ignore stop */
return;
}
if (src.isDirectory()) {
if (!dest.isDirectory()) {
mkdirpClobberSync(dest.fullpath());
}
}
else {
// must be file
let write = true;
if (!dest.isENOENT() && !dest.isFile())
(0, rimraf_1.rimrafSync)(dest.fullpath());
else if (contentMatchSync(src, dest))
write = false;
if (write) {
mkdirpClobberSync((0, node_path_1.dirname)(dest.fullpath()));
// platform specific
/* c8 ignore start */
try {
(0, node_fs_1.linkSync)(src.fullpath(), dest.fullpath());
}
catch {
(0, node_fs_1.copyFileSync)(src.fullpath(), dest.fullpath());
}
/* c8 ignore stop */
}
}
const mode = src.mode;
/* c8 ignore start */
if (!mode)
return;
/* c8 ignore stop */
(0, node_fs_1.chmodSync)(dest.fullpath(), mode);
};
const contentMatch = async (src, dest) => {
try {
return ((0, node_crypto_1.createHash)('sha512')
.update(await (0, promises_1.readFile)(src.fullpath()))
.digest('hex') ===
(0, node_crypto_1.createHash)('sha512')
.update(await (0, promises_1.readFile)(dest.fullpath()))
.digest('hex'));
// we should only be doing this if we know it's a valid file already
// but just in case we can't read it, that's not a match.
/* c8 ignore start */
}
catch {
return false;
}
/* c8 ignore stop */
};
const contentMatchSync = (src, dest) => {
try {
return ((0, node_crypto_1.createHash)('sha512')
.update((0, node_fs_1.readFileSync)(src.fullpath()))
.digest('hex') ===
(0, node_crypto_1.createHash)('sha512')
.update((0, node_fs_1.readFileSync)(dest.fullpath()))
.digest('hex'));
// we should only be doing this if we know it's a valid file already
// but just in case we can't read it, that's not a match.
/* c8 ignore start */
}
catch {
return false;
}
/* c8 ignore stop */
};
// if a is a parent of b, or b is a parent of a, then one of them
// will not start with .. in the relative path.
const dots = `..${node_path_1.sep}`;
const dirsRelated = (a, b) => {
if (a === b)
return true;
const relab = (0, node_path_1.relative)(a, b);
const relba = (0, node_path_1.relative)(a, b);
if (!relab.startsWith(dots) || !relba.startsWith(dots))
return true;
return false;
};
const syncContent = async (from, to) => {
const scurry = new path_scurry_1.PathScurry(from);
const rfrom = (0, node_path_1.resolve)(from);
const rto = (0, node_path_1.resolve)(to);
if ((0, node_path_1.dirname)(rfrom) === rfrom || (0, node_path_1.dirname)(rto) === rto) {
throw new Error('cannot sync root directory');
}
/* c8 ignore start */
if (dirsRelated(rto, rfrom)) {
/* c8 ignore stop */
throw new Error('cannot copy directory into itself or its parent');
}
const [src, dest] = await Promise.all([
await (0, glob_1.glob)('**', { scurry, withFileTypes: true }),
await (0, glob_1.glob)('**', { cwd: rto, withFileTypes: true }),
]);
await Promise.all([
...src.map(async (s) => {
/* c8 ignore start */
if (!s.parent)
throw new Error('cannot sync root directory');
/* c8 ignore stop */
const d = s.resolve((0, node_path_1.resolve)(rto, s.relative()));
const parent = d.parent;
/* c8 ignore start */
if (!parent)
throw new Error('cannot sync root directory');
/* c8 ignore stop */
await d.lstat();
await mkdirpClobber(parent.fullpath());
await syncFile(s, d);
}),
...dest.map(async (d) => {
const s = scurry.cwd.resolve((0, node_path_1.resolve)(rfrom, d.relative()));
await s.lstat();
if (s.isENOENT()) {
// race
/* c8 ignore start */
try {
await (0, rimraf_1.rimraf)(d.fullpath());
}
catch { }
/* c8 ignore stop */
}
}),
]);
};
exports.syncContent = syncContent;
const syncContentSync = (from, to) => {
const scurry = new path_scurry_1.PathScurry(from);
const rfrom = (0, node_path_1.resolve)(from);
const rto = (0, node_path_1.resolve)(to);
if ((0, node_path_1.dirname)(rfrom) === rfrom || (0, node_path_1.dirname)(rto) === rto) {
throw new Error('cannot sync root directory');
}
if (dirsRelated(rto, rfrom)) {
throw new Error('cannot copy directory into itself or its parent');
}
const [src, dest] = [
(0, glob_1.globSync)('**', { scurry, withFileTypes: true }),
(0, glob_1.globSync)('**', { cwd: rto, withFileTypes: true }),
];
for (const s of src) {
/* c8 ignore start */
if (!s.parent)
throw new Error('cannot sync root directory');
/* c8 ignore stop */
const d = s.resolve((0, node_path_1.resolve)(rto, s.relative()));
const parent = d.parent;
/* c8 ignore start */
if (!parent)
throw new Error('cannot sync root directory');
/* c8 ignore stop */
d.lstatSync();
mkdirpClobberSync(parent.fullpath());
syncFileSync(s, d);
}
for (const d of dest) {
const s = scurry.cwd.resolve((0, node_path_1.resolve)(rfrom, d.relative()));
s.lstatSync();
if (s.isENOENT()) {
// race
/* c8 ignore start */
try {
(0, rimraf_1.rimrafSync)(d.fullpath());
}
catch { }
/* c8 ignore stop */
}
}
};
exports.syncContentSync = syncContentSync;
//# sourceMappingURL=index.js.map