copy-file-util
Version:
Copy or rename a file with optional package version number (CLI tool designed for use in npm package.json scripts)
118 lines (116 loc) • 5.56 kB
JavaScript
//! copy-file-util v1.3.2 ~~ https://github.com/center-key/copy-file-util ~~ MIT License
import { cliArgvUtil } from 'cli-argv-util';
import { dna } from 'dna-engine';
import { EOL } from 'node:os';
import chalk from 'chalk';
import fs from 'fs';
import log from 'fancy-log';
import path from 'path';
import slash from 'slash';
const copyFile = {
assert(condition, errorMessage) {
if (!condition)
throw new Error('[copy-file-util] ' + String(errorMessage));
},
cli() {
const validFlags = ['cd', 'folder', 'move', 'no-overwrite', 'note', 'platform-eol', 'quiet'];
const cli = cliArgvUtil.parse(validFlags);
const source = cli.params[0];
const target = cli.params[1];
const getPkgField = (substring) => {
const pkg = JSON.parse(fs.readFileSync('package.json', 'utf-8'));
const value = dna.util.value({ package: pkg }, substring.replace(/[{}]/g, ''));
return value ?? 'MISSING-FIELD-ERROR';
};
const errorMessage = cli.invalidFlag ? cli.invalidFlagMsg :
cli.paramCount > 2 ? 'Extraneous parameter: ' + cli.params[2] :
!source ? 'Missing source file.' :
!target && cli.flagOn.folder ? 'Missing target folder.' :
!target ? 'Missing target file.' :
null;
copyFile.assert(!errorMessage, errorMessage);
const templateVariables = /{{[^{}]*}}/g;
const targetValue = target.replace(templateVariables, getPkgField);
const options = {
cd: cli.flagMap.cd ?? null,
targetFile: !cli.flagOn.folder ? targetValue : null,
targetFolder: cli.flagOn.folder ? targetValue : null,
fileExtension: null,
move: !!cli.flagOn.move,
overwrite: !cli.flagOn.noOverwrite,
platformEol: !!cli.flagOn.platformEol,
};
const result = copyFile.cp(source, options);
if (!cli.flagOn.quiet)
copyFile.reporter(result);
},
cp(sourceFile, options) {
const defaults = {
cd: null,
targetFile: null,
targetFolder: null,
fileExtension: null,
move: false,
overwrite: true,
platformEol: false,
};
const settings = { ...defaults, ...options };
const startTime = Date.now();
const missingTarget = !settings.targetFile && !settings.targetFolder;
const doubleTarget = !!settings.targetFile && !!settings.targetFolder;
const cleanUp = (folder) => slash(path.normalize(folder)).replace(/\/$/, '');
const cleanPath = (folder) => !folder ? '' : cleanUp(folder);
const startFolder = settings.cd ? cleanPath(settings.cd) + '/' : '';
const source = sourceFile ? cleanPath(startFolder + sourceFile) : '';
const sourceExists = source && fs.existsSync(source);
const sourceIsFile = sourceExists && fs.statSync(source).isFile();
const sourceFilename = sourceIsFile ? path.basename(source) : null;
const targetPath = settings.targetFile ? path.dirname(settings.targetFile) : settings.targetFolder;
const targetFolder = targetPath ? cleanPath(startFolder + targetPath) : null;
const targetFile = settings.targetFile ?? `${settings.targetFolder}/${sourceFilename}`;
const target = cleanPath(startFolder + targetFile);
const targetExists = !missingTarget && fs.existsSync(target);
const skip = targetExists && !settings.overwrite;
if (targetFolder)
fs.mkdirSync(targetFolder, { recursive: true });
const badTargetFolder = !targetFolder || !fs.existsSync(targetFolder);
const errorMessage = settings.fileExtension ? 'Option "fileExtension" not yet implemented.' :
!sourceFile ? 'Must specify the source file.' :
!sourceExists ? 'Source file does not exist: ' + source :
!sourceIsFile ? 'Source is not a file: ' + source :
missingTarget ? 'Must specify a target file or folder.' :
doubleTarget ? 'Target cannot be both a file and a folder.' :
badTargetFolder ? 'Target folder cannot be written to: ' + String(targetFolder) :
null;
copyFile.assert(!errorMessage, errorMessage);
const createTarget = () => {
if (settings.move)
fs.renameSync(source, target);
else
fs.copyFileSync(source, target);
const platformEol = (text) => text.replace(/\r?\n/g, EOL);
if (settings.platformEol)
fs.writeFileSync(target, platformEol(fs.readFileSync(target, 'utf-8')));
};
if (!skip)
createTarget();
return {
origin: source,
dest: target,
moved: settings.move,
skipped: skip,
duration: Date.now() - startTime,
};
},
reporter(result) {
const name = chalk.gray('copy-file');
const origin = chalk.blue.bold(result.origin);
const dest = chalk.magenta(result.dest);
const arrow = chalk.gray.bold('→');
const status = result.skipped ? ', skip -- target exists' : result.moved ? ', move' : '';
const info = chalk.white(`(${result.duration}ms${status})`);
log(name, origin, arrow, dest, info);
return result;
},
};
export { copyFile };