UNPKG

sync-content

Version:

Synchronize the contents of one folder to another location, only copying files if contents differ.

293 lines 10.4 kB
"use strict"; 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