replace-regex
Version:
TS compatible modern nodeJS find-and-replace in files with Regex & Glob support
83 lines (82 loc) • 2.57 kB
JavaScript
import fastGlob from 'fast-glob';
import { isArray, isFunction, isString } from 'is-what';
import * as fs from 'node:fs/promises';
/**
* async use fast-glob to get all files
*/
async function getPathsAsync(patterns, options) {
const { ignore, disableGlobs, fastGlobOptions } = options;
// disable globs, just ensure file(s) name
if (disableGlobs)
return isString(patterns) ? [patterns] : patterns;
return await fastGlob(patterns, { ignore, ...fastGlobOptions });
}
/**
* replace main
*/
function replaceFactory(options) {
const { contents, from, to, file, ignoreCase } = options;
const result = {
file,
changed: false,
matchCount: 0,
replaceCount: 0,
};
const _from = isFunction(from) ? from(file) : from;
const flags = ignoreCase ? 'gi' : 'g';
const fromRegex = isString(_from) ? new RegExp(_from, flags) : _from;
const matches = contents.match(fromRegex);
if (matches) {
const replacements = matches.filter((match) => match !== to);
result.matchCount = matches.length;
result.replaceCount = replacements.length;
}
const newContents = isFunction(to)
? contents.replace(fromRegex, (match) => to(match, file))
: contents.replace(fromRegex, to);
result.changed = newContents !== contents;
return {
result,
newContents,
};
}
/**
* async replace string in single file
*/
async function replaceFileAsync(options) {
const { file, from, to, dry, ignoreCase } = options;
const contents = await fs.readFile(file);
// replace action
const { result, newContents } = replaceFactory({
contents: contents.toString(),
from,
to,
file,
ignoreCase,
});
if (!result.changed || dry)
return result;
// write action
await fs.writeFile(file, newContents);
return result;
}
/**
* Uses fast-glob to find and replace text in files. Supports RegExp.
*/
export async function replaceRegex(options) {
const { files, from, dry, to, ignoreCase } = options;
// dry mode, do not replace
if (dry)
console.log('[dry mode] no files will be overwritten');
const foundFiles = await getPathsAsync(files, options);
const fromClauses = isArray(from)
? from
: [from];
const results = [];
for (const from of fromClauses) {
for (const file of foundFiles) {
results.push(replaceFileAsync({ file, from, to, dry, ignoreCase }));
}
}
return await Promise.all(results);
}