@cjsa/cpy
Version:
Copy files
223 lines (219 loc) • 7.54 kB
JavaScript
// index.js
import process from "process";
import EventEmitter from "events";
import path2 from "path";
import os from "os";
import pMap from "p-map";
import arrify from "arrify";
import cpFile from "cp-file";
import pFilter from "p-filter";
import { isDynamicPattern as isDynamicPattern2 } from "globby";
import micromatch from "micromatch";
// cpy-error.js
import NestedError from "nested-error-stacks";
var CpyError = class extends NestedError {
constructor(message, nested) {
super(message, nested);
Object.assign(this, nested);
this.name = "CpyError";
}
};
// glob-pattern.js
import path from "path";
import fs from "fs";
import { globbySync, isDynamicPattern } from "globby";
import { isNotJunk } from "junk";
var GlobPattern = class {
constructor(pattern, destination, options) {
this.path = pattern;
this.originalPath = pattern;
this.destination = destination;
this.options = options;
this.isDirectory = false;
if (!isDynamicPattern(pattern) && fs.existsSync(pattern) && fs.lstatSync(pattern).isDirectory()) {
this.path = [pattern, "**"].join("/");
this.isDirectory = true;
}
}
get name() {
return path.basename(this.originalPath);
}
get normalizedPath() {
const segments = this.originalPath.split("/");
const magicIndex = segments.findIndex((item) => item ? isDynamicPattern(item) : false);
const normalized = segments.slice(0, magicIndex).join("/");
if (normalized) {
return path.isAbsolute(normalized) ? normalized : path.join(this.options.cwd, normalized);
}
return this.destination;
}
hasMagic() {
return isDynamicPattern(this.options.flat ? this.path : this.originalPath);
}
getMatches() {
let matches = globbySync(this.path, {
...this.options,
dot: true,
absolute: true,
onlyFiles: true
});
if (this.options.ignoreJunk) {
matches = matches.filter((file) => isNotJunk(path.basename(file)));
}
return matches;
}
};
// index.js
var defaultConcurrency = (os.cpus().length || 1) * 2;
var defaultOptions = {
ignoreJunk: true,
flat: false,
cwd: process.cwd()
};
var Entry = class {
constructor(source, relativePath, pattern) {
this.path = source.split("/").join(path2.sep);
this.relativePath = relativePath.split("/").join(path2.sep);
this.pattern = pattern;
Object.freeze(this);
}
get name() {
return path2.basename(this.path);
}
get nameWithoutExtension() {
return path2.basename(this.path, path2.extname(this.path));
}
get extension() {
return path2.extname(this.path).slice(1);
}
};
var expandPatternsWithBraceExpansion = (patterns) => patterns.flatMap((pattern) => micromatch.braces(pattern, {
expand: true,
nodupes: true
}));
var preprocessDestinationPath = ({ entry, destination, options }) => {
if (entry.pattern.hasMagic()) {
if (options.flat) {
if (path2.isAbsolute(destination)) {
return path2.join(destination, entry.name);
}
return path2.join(options.cwd, destination, entry.name);
}
return path2.join(destination, path2.relative(entry.pattern.normalizedPath, entry.path));
}
if (path2.isAbsolute(destination)) {
return path2.join(destination, entry.name);
}
if (entry.pattern.isDirectory && path2.relative(options.cwd, entry.path).startsWith("..")) {
return path2.join(options.cwd, destination, path2.basename(entry.pattern.originalPath), path2.relative(entry.pattern.originalPath, entry.path));
}
if (!entry.pattern.isDirectory && entry.path === entry.relativePath) {
return path2.join(options.cwd, destination, path2.basename(entry.pattern.originalPath), path2.relative(entry.pattern.originalPath, entry.path));
}
if (!entry.pattern.isDirectory && options.flat) {
return path2.join(options.cwd, destination, path2.basename(entry.pattern.originalPath));
}
return path2.join(options.cwd, destination, path2.relative(options.cwd, entry.path));
};
var renameFile = (source, rename) => {
const filename = path2.basename(source, path2.extname(source));
const fileExtension = path2.extname(source);
const directory = path2.dirname(source);
if (typeof rename === "string") {
return path2.join(directory, rename);
}
if (typeof rename === "function") {
return path2.join(directory, `${rename(filename)}${fileExtension}`);
}
return source;
};
function cpy(source, destination, { concurrency = defaultConcurrency, ...options } = {}) {
const copyStatus = /* @__PURE__ */ new Map();
const progressEmitter = new EventEmitter();
options = {
...defaultOptions,
...options
};
const promise = (async () => {
let entries = [];
let completedFiles = 0;
let completedSize = 0;
let patterns = expandPatternsWithBraceExpansion(arrify(source)).map((string) => string.replace(/\\/g, "/"));
const sources = patterns.filter((item) => !item.startsWith("!"));
const ignore = patterns.filter((item) => item.startsWith("!"));
if (sources.length === 0 || !destination) {
throw new CpyError("`source` and `destination` required");
}
patterns = patterns.map((pattern) => new GlobPattern(pattern, destination, { ...options, ignore }));
for (const pattern of patterns) {
let matches = [];
try {
matches = pattern.getMatches();
} catch (error) {
throw new CpyError(`Cannot glob \`${pattern.originalPath}\`: ${error.message}`, error);
}
if (matches.length === 0 && !isDynamicPattern2(pattern.originalPath) && !isDynamicPattern2(ignore)) {
throw new CpyError(`Cannot copy \`${pattern.originalPath}\`: the file doesn't exist`);
}
entries = [
...entries,
...matches.map((sourcePath) => new Entry(sourcePath, path2.relative(options.cwd, sourcePath), pattern))
];
}
if (options.filter !== void 0) {
entries = await pFilter(entries, options.filter, { concurrency: 1024 });
}
if (entries.length === 0) {
progressEmitter.emit("progress", {
totalFiles: 0,
percent: 1,
completedFiles: 0,
completedSize: 0
});
}
const fileProgressHandler = (event) => {
const fileStatus = copyStatus.get(event.sourcePath) || {
writtenBytes: 0,
percent: 0
};
if (fileStatus.writtenBytes !== event.writtenBytes || fileStatus.percent !== event.percent) {
completedSize -= fileStatus.writtenBytes;
completedSize += event.writtenBytes;
if (event.percent === 1 && fileStatus.percent !== 1) {
completedFiles++;
}
copyStatus.set(event.sourcePath, {
writtenBytes: event.writtenBytes,
percent: event.percent
});
progressEmitter.emit("progress", {
totalFiles: entries.length,
percent: completedFiles / entries.length,
completedFiles,
completedSize
});
}
};
return pMap(entries, async (entry) => {
const to = renameFile(preprocessDestinationPath({
entry,
destination,
options
}), options.rename);
try {
await cpFile(entry.path, to, options).on("progress", fileProgressHandler);
} catch (error) {
throw new CpyError(`Cannot copy from \`${entry.relativePath}\` to \`${to}\`: ${error.message}`, error);
}
return to;
}, { concurrency });
})();
promise.on = (...arguments_) => {
progressEmitter.on(...arguments_);
return promise;
};
return promise;
}
export {
cpy as default
};