replace-in-file
Version:
A simple utility to quickly replace text in one or more files.
173 lines (150 loc) • 4.35 kB
JavaScript
import path from 'node:path'
import fs from 'node:fs/promises'
import fsSync from 'node:fs'
/**
* Helper to load options from a config file
*/
export async function loadConfig(file) {
//No config file provided?
if (!file) {
throw new Error(`No config file provided`)
}
//Read file
const json = await fs.readFile(path.resolve(file), 'utf8')
return JSON.parse(json)
}
/**
* Helper to convert a string to Regex if it matches the pattern
*/
export function stringToRegex(str) {
//Array given
if (Array.isArray(str)) {
return str.map(stringToRegex)
}
//Not a string, or no match
const regexMatch = /.*\/([gimyus]*)$/
if (typeof str !== 'string' || !str.match(regexMatch)) {
return str
}
//Extract flags and pattern
const flags = str.replace(/.*\/([gimyus]*)$/, '$1')
const pattern = str.replace(new RegExp(`^/(.*?)/${flags}$`), '$1')
//Return regex
return new RegExp(pattern, flags)
}
/**
* Parse config
*/
export function parseConfig(config) {
//Validate config
if (typeof config !== 'object' || config === null) {
throw new Error('Must specify configuration object')
}
//Fix glob
config.glob = config.glob || {}
//Extract data
const {files, getTargetFile, from, to, processor, processorAsync, ignore, encoding} = config
if (typeof processor !== 'undefined') {
if (typeof processor !== 'function' && !Array.isArray(processor)) {
throw new Error(`Processor should be either a function or an array of functions`)
}
}
else if (typeof processorAsync !== 'undefined') {
if (typeof processorAsync !== 'function' && !Array.isArray(processorAsync)) {
throw new Error(`ProcessorAsync should be either a function or an array of functions`)
}
}
else {
if (typeof files === 'undefined') {
throw new Error('Must specify file or files')
}
if (typeof from === 'undefined') {
throw new Error('Must specify string or regex to replace')
}
if (typeof to === 'undefined') {
throw new Error('Must specify a replacement (can be blank string)')
}
if (typeof getTargetFile !== 'undefined' && typeof getTargetFile !== 'function') {
throw new Error(`Target file transformation parameter should be a function that takes the source file path as argument and returns the target file path`)
}
}
//Ensure arrays
if (!Array.isArray(files)) {
config.files = [files]
}
if (!Array.isArray(ignore)) {
if (typeof ignore === 'undefined') {
config.ignore = []
}
else {
config.ignore = [ignore]
}
}
//Since we can't store Regexp in JSON, convert from string if needed
config.from = stringToRegex(from)
//Use default encoding if invalid
if (typeof encoding !== 'string' || encoding === '') {
config.encoding = 'utf-8'
}
//Merge config with defaults
return Object.assign({}, {
ignore: [],
encoding: 'utf-8',
disableGlobs: false,
allowEmptyPaths: false,
countMatches: false,
verbose: false,
quiet: false,
dry: false,
glob: {},
cwd: null,
getTargetFile: source => source,
fs,
fsSync,
}, config)
}
/**
* Combine CLI script arguments with config options
*/
export function combineConfig(config, argv) {
//Extract options from config
let {
from, to, files, ignore, encoding, verbose,
allowEmptyPaths, disableGlobs, dry, quiet,
} = config
//Get from/to parameters from CLI args if not defined in options
if (typeof from === 'undefined') {
from = argv._.shift()
}
if (typeof to === 'undefined') {
to = argv._.shift()
}
//Get files and ignored files
if (typeof files === 'undefined') {
files = argv._
}
if (typeof ignore === 'undefined' && typeof argv.ignore !== 'undefined') {
ignore = argv.ignore
}
//Other parameters
if (typeof encoding === 'undefined') {
encoding = argv.encoding
}
if (typeof disableGlobs === 'undefined') {
disableGlobs = !!argv.disableGlobs
}
if (typeof verbose === 'undefined') {
verbose = !!argv.verbose
}
if (typeof dry === 'undefined') {
dry = !!argv.dry
}
if (typeof quiet === 'undefined') {
quiet = !!argv.quiet
}
//Return through parser to validate
return parseConfig({
from, to, files, ignore, encoding, verbose,
allowEmptyPaths, disableGlobs, dry, quiet,
})
}