replace-in-file
Advanced tools
Comparing version 2.6.4 to 3.0.0-beta.1
117
bin/cli.js
@@ -7,122 +7,51 @@ #!/usr/bin/env node | ||
*/ | ||
const path = require('path'); | ||
const chalk = require('chalk'); | ||
const argv = require('yargs').argv; | ||
const {argv} = require('yargs'); | ||
const replace = require('../lib/replace-in-file'); | ||
const loadConfig = require('../lib/helpers/load-config'); | ||
const combineConfig = require('../lib/helpers/combine-config'); | ||
const errorHandler = require('../lib/helpers/error-handler'); | ||
const successHandler = require('../lib/helpers/success-handler'); | ||
//Extract parameters | ||
const {configFile} = argv; | ||
//Verify arguments | ||
if (argv._.length < 3 && !argv.config) { | ||
console.error(chalk.red('Replace in file needs at least 3 arguments')); | ||
process.exit(1); | ||
if (argv._.length < 3 && !configFile) { | ||
errorHandler('Replace in file needs at least 3 arguments'); | ||
} | ||
//Prepare vars | ||
let from, to, files, ignore; | ||
//Load config and combine with passed arguments | ||
const config = loadConfig(configFile); | ||
const options = combineConfig(config, argv); | ||
//If config is set, load config file | ||
if (argv.config) { | ||
//Extract settings | ||
const {from, to, files, isRegex, verbose} = options; | ||
//Read config file | ||
let config; | ||
try { | ||
config = require(path.join(process.cwd(), argv.config)); | ||
} | ||
catch (error) { | ||
console.error(chalk.red('Error loading config file:')); | ||
console.error(error); | ||
process.exit(1); | ||
} | ||
//Set from/to params | ||
from = config.from; | ||
to = config.to; | ||
//Set files param | ||
if (typeof config.files === 'string') { | ||
config.files = [config.files]; | ||
} | ||
files = config.files; | ||
//Set ignore param | ||
if (typeof config.ignore === 'string') { | ||
config.ignore = [config.ignore]; | ||
} | ||
ignore = config.ignore; | ||
} | ||
//Get from/to parameters from CLI args if not defined in config file | ||
if (typeof from === 'undefined') { | ||
from = argv._.shift(); | ||
} | ||
if (typeof to === 'undefined') { | ||
to = argv._.shift(); | ||
} | ||
//Get files | ||
if (!files) { | ||
files = argv._; | ||
} | ||
//Validate data | ||
if (typeof from === 'undefined' || typeof to === 'undefined') { | ||
console.error(chalk.red('Must set from & to options')); | ||
process.exit(1); | ||
} | ||
if (!files) { | ||
console.error(chalk.red('Must pass a list of files')); | ||
process.exit(1); | ||
} | ||
//Single star globs already get expanded in the command line | ||
files = files.reduce((files, file) => { | ||
options.files = files.reduce((files, file) => { | ||
return files.concat(file.split(',')); | ||
}, []); | ||
//If the isRegex flag is passed, send the from parameter as a RegExp object | ||
if (argv.isRegex) { | ||
//If the isRegex flag is passed, convert the from parameter to a RegExp object | ||
if (isRegex) { | ||
const flags = from.replace(/.*\/([gimy]*)$/, '$1'); | ||
const pattern = from.replace(new RegExp(`^/(.*?)/${flags}$`), '$1'); | ||
try { | ||
from = new RegExp(pattern, flags); | ||
options.from = new RegExp(pattern, flags); | ||
} | ||
catch (error) { | ||
console.error(chalk.red('Error creating RegExp from `from` parameter:')); | ||
console.error(error); | ||
process.exit(1); | ||
errorHandler(error, 'Error creating RegExp from `from` parameter'); | ||
} | ||
} | ||
//Get ignored files from ignore flag | ||
if (!ignore && typeof argv.ignore !== 'undefined') { | ||
ignore = argv.ignore; | ||
} | ||
//Log | ||
console.log(`Replacing '${from}' with '${to}'`); | ||
//Create options | ||
const options = {files, from, to, ignore}; | ||
if (typeof argv.encoding !== 'undefined') { | ||
options.encoding = argv.encoding; | ||
} | ||
if (typeof argv.allowEmptyPaths !== 'undefined') { | ||
options.allowEmptyPaths = argv.allowEmptyPaths; | ||
} | ||
//Replace | ||
try { | ||
const changedFiles = replace.sync(options); | ||
if (changedFiles.length > 0) { | ||
console.log(chalk.green(changedFiles.length, 'file(s) were changed')); | ||
if (argv.verbose) { | ||
changedFiles.forEach(file => console.log(chalk.grey('-', file))); | ||
} | ||
} | ||
else { | ||
console.log(chalk.yellow('No files were changed')); | ||
} | ||
const changes = replace.sync(options); | ||
successHandler(changes, verbose); | ||
} | ||
catch (error) { | ||
console.error(chalk.red('Error making replacements:')); | ||
console.error(error); | ||
errorHandler(error); | ||
} |
'use strict'; | ||
/** | ||
* Module dependencies | ||
* Dependencies | ||
*/ | ||
const fs = require('fs'); | ||
const glob = require('glob'); | ||
const chalk = require('chalk'); | ||
const parseConfig = require('./helpers/parse-config'); | ||
const getPathsSync = require('./helpers/get-paths-sync'); | ||
const getPathsAsync = require('./helpers/get-paths-async'); | ||
const replaceSync = require('./helpers/replace-sync'); | ||
const replaceAsync = require('./helpers/replace-async'); | ||
/** | ||
* Defaults | ||
*/ | ||
const defaults = { | ||
allowEmptyPaths: false, | ||
encoding: 'utf-8', | ||
ignore: [], | ||
}; | ||
/** | ||
* Parse config | ||
*/ | ||
function parseConfig(config) { | ||
//Validate config | ||
if (typeof config !== 'object' || config === null) { | ||
throw new Error('Must specify configuration object'); | ||
} | ||
//Backwards compatibilility | ||
if (typeof config.replace !== 'undefined' && | ||
typeof config.from === 'undefined') { | ||
console.log( | ||
chalk.yellow('Option `replace` is deprecated. Use `from` instead.') | ||
); | ||
config.from = config.replace; | ||
} | ||
if (typeof config.with !== 'undefined' && | ||
typeof config.to === 'undefined') { | ||
console.log( | ||
chalk.yellow('Option `with` is deprecated. Use `to` instead.') | ||
); | ||
config.to = config.with; | ||
} | ||
//Validate values | ||
if (typeof config.files === 'undefined') { | ||
throw new Error('Must specify file or files'); | ||
} | ||
if (typeof config.from === 'undefined') { | ||
throw new Error('Must specify string or regex to replace'); | ||
} | ||
if (typeof config.to === 'undefined') { | ||
throw new Error('Must specify a replacement (can be blank string)'); | ||
} | ||
if (typeof config.ignore === 'undefined') { | ||
config.ignore = []; | ||
} | ||
//Use default encoding if invalid | ||
if (typeof config.encoding !== 'string' || config.encoding === '') { | ||
config.encoding = 'utf-8'; | ||
} | ||
//Merge config with defaults | ||
return Object.assign({}, defaults, config); | ||
} | ||
/** | ||
* Get replacement helper | ||
*/ | ||
function getReplacement(replace, isArray, i) { | ||
if (isArray && typeof replace[i] === 'undefined') { | ||
return null; | ||
} | ||
if (isArray) { | ||
return replace[i]; | ||
} | ||
return replace; | ||
} | ||
/** | ||
* Helper to make replacements | ||
*/ | ||
function makeReplacements(contents, from, to) { | ||
//Turn into array | ||
if (!Array.isArray(from)) { | ||
from = [from]; | ||
} | ||
//Check if replace value is an array | ||
const isArray = Array.isArray(to); | ||
//Make replacements | ||
from.forEach((item, i) => { | ||
//Get replacement value | ||
const replacement = getReplacement(to, isArray, i); | ||
if (replacement === null) { | ||
return; | ||
} | ||
//Make replacement | ||
contents = contents.replace(item, replacement); | ||
}); | ||
//Return modified contents | ||
return contents; | ||
} | ||
/** | ||
* Helper to replace in a single file (sync) | ||
*/ | ||
function replaceSync(file, from, to, enc) { | ||
//Read contents | ||
const contents = fs.readFileSync(file, enc); | ||
//Replace contents and check if anything changed | ||
const newContents = makeReplacements(contents, from, to); | ||
if (newContents === contents) { | ||
return false; | ||
} | ||
//Write to file | ||
fs.writeFileSync(file, newContents, enc); | ||
return true; | ||
} | ||
/** | ||
* Helper to replace in a single file (async) | ||
*/ | ||
function replaceAsync(file, from, to, enc) { | ||
return new Promise((resolve, reject) => { | ||
fs.readFile(file, enc, (error, contents) => { | ||
//istanbul ignore if | ||
if (error) { | ||
return reject(error); | ||
} | ||
//Replace contents and check if anything changed | ||
let newContents = makeReplacements(contents, from, to); | ||
if (newContents === contents) { | ||
return resolve({file, hasChanged: false}); | ||
} | ||
//Write to file | ||
fs.writeFile(file, newContents, enc, error => { | ||
//istanbul ignore if | ||
if (error) { | ||
return reject(error); | ||
} | ||
resolve({file, hasChanged: true}); | ||
}); | ||
}); | ||
}); | ||
} | ||
/** | ||
* Promise wrapper for glob | ||
*/ | ||
function globPromise(pattern, ignore, allowEmptyPaths) { | ||
return new Promise((resolve, reject) => { | ||
glob(pattern, {ignore: ignore, nodir: true}, (error, files) => { | ||
//istanbul ignore if: hard to make glob error | ||
if (error) { | ||
return reject(error); | ||
} | ||
//Error if no files match, unless allowEmptyPaths is true | ||
if (files.length === 0 && !allowEmptyPaths) { | ||
return reject(new Error('No files match the pattern: ' + pattern)); | ||
} | ||
//Resolve | ||
resolve(files); | ||
}); | ||
}); | ||
} | ||
/** | ||
* Replace in file helper | ||
@@ -198,22 +28,12 @@ */ | ||
//Get config and globs | ||
// const {files, from, to, allowEmptyPaths, encoding, ignore} = config; | ||
const files = config.files; | ||
const from = config.from; | ||
const to = config.to; | ||
const encoding = config.encoding; | ||
const allowEmptyPaths = config.allowEmptyPaths; | ||
const ignore = config.ignore; | ||
const globs = Array.isArray(files) ? files : [files]; | ||
const ignored = Array.isArray(ignore) ? ignore : [ignore]; | ||
//Get config | ||
const { | ||
files, from, to, encoding, ignore, allowEmptyPaths, disableGlobs, | ||
} = config; | ||
//Find files | ||
return Promise | ||
.all(globs.map(pattern => globPromise(pattern, ignored, allowEmptyPaths))) | ||
//Find paths | ||
return getPathsAsync(files, ignore, disableGlobs, allowEmptyPaths) | ||
//Flatten array | ||
.then(files => [].concat.apply([], files)) | ||
//Make replacements | ||
.then(files => Promise.all(files.map(file => { | ||
.then(paths => Promise.all(paths.map(file => { | ||
return replaceAsync(file, from, to, encoding); | ||
@@ -229,3 +49,3 @@ }))) | ||
//Handle via callback or return | ||
//Success handler | ||
.then(changedFiles => { | ||
@@ -238,3 +58,3 @@ if (cb) { | ||
//Handle error via callback, or rethrow | ||
//Error handler | ||
.catch(error => { | ||
@@ -258,22 +78,12 @@ if (cb) { | ||
//Get config and globs | ||
// const {files, from, to, encoding, ignore} = config; | ||
const files = config.files; | ||
const from = config.from; | ||
const to = config.to; | ||
const encoding = config.encoding; | ||
const ignore = config.ignore; | ||
const globs = Array.isArray(files) ? files : [files]; | ||
const ignored = Array.isArray(ignore) ? ignore : [ignore]; | ||
//Get config, paths, and initialize changed files | ||
const {files, from, to, encoding, ignore, disableGlobs} = config; | ||
const paths = getPathsSync(files, ignore, disableGlobs); | ||
const changedFiles = []; | ||
//Process synchronously | ||
globs.forEach(pattern => { | ||
glob | ||
.sync(pattern, {ignore: ignored, nodir: true}) | ||
.forEach(file => { | ||
if (replaceSync(file, from, to, encoding)) { | ||
changedFiles.push(file); | ||
} | ||
}); | ||
paths.forEach(path => { | ||
if (replaceSync(path, from, to, encoding)) { | ||
changedFiles.push(path); | ||
} | ||
}); | ||
@@ -280,0 +90,0 @@ |
@@ -507,2 +507,17 @@ 'use strict'; | ||
}); | ||
it('should work without expanding globs if disabled', done => { | ||
replace({ | ||
files: ['test1', 'test2'], | ||
from: /re\splace/g, | ||
to: 'b', | ||
disableGlobs: true, | ||
}, () => { | ||
const test1 = fs.readFileSync('test1', 'utf8'); | ||
const test2 = fs.readFileSync('test2', 'utf8'); | ||
expect(test1).to.equal('a b c'); | ||
expect(test2).to.equal('a b c'); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
@@ -554,12 +569,2 @@ | ||
it('should support `replace` and `with` params for now', () => { | ||
expect(function() { | ||
replace.sync({ | ||
files: 'test1', | ||
replace: /re\splace/g, | ||
with: 'b', | ||
}); | ||
}).to.not.throw(Error); | ||
}); | ||
it('should support the encoding parameter', () => { | ||
@@ -729,2 +734,15 @@ expect(function() { | ||
it('should support an array of ignored files', () => { | ||
replace.sync({ | ||
files: 'test*', | ||
ignore: ['test1', 'test3'], | ||
from: /re\splace/g, | ||
to: 'b', | ||
}); | ||
const test1 = fs.readFileSync('test1', 'utf8'); | ||
const test2 = fs.readFileSync('test2', 'utf8'); | ||
expect(test1).to.equal('a re place c'); | ||
expect(test2).to.equal('a b c'); | ||
}); | ||
it('should not fail when the ignore parameter is undefined', () => { | ||
@@ -742,3 +760,16 @@ replace.sync({ | ||
}); | ||
it('should work without expanding globs if disabled', () => { | ||
replace.sync({ | ||
files: ['test1', 'test2'], | ||
from: /re\splace/g, | ||
to: 'b', | ||
disableGlobs: true, | ||
}); | ||
const test1 = fs.readFileSync('test1', 'utf8'); | ||
const test2 = fs.readFileSync('test2', 'utf8'); | ||
expect(test1).to.equal('a b c'); | ||
expect(test2).to.equal('a b c'); | ||
}); | ||
}); | ||
}); |
{ | ||
"name": "replace-in-file", | ||
"version": "2.6.4", | ||
"version": "3.0.0-beta.1", | ||
"description": "A simple utility to quickly replace text in one or more files.", | ||
@@ -28,3 +28,2 @@ "homepage": "https://github.com/adamreisnz/replace-in-file#readme", | ||
"scripts": { | ||
"lint": "eslint . --fix", | ||
"istanbul": "babel-node ./node_modules/istanbul/lib/cli cover ./node_modules/mocha/bin/_mocha lib/**/*.spec.js", | ||
@@ -42,3 +41,2 @@ "test": "npm run istanbul -s", | ||
"dirty-chai": "^2.0.1", | ||
"eslint": "^4.5.0", | ||
"istanbul": "^1.0.0-alpha.2", | ||
@@ -45,0 +43,0 @@ "mocha": "^3.5.0" |
224
README.md
@@ -20,14 +20,83 @@ # Replace in file | ||
## Usage | ||
## Basic usage | ||
### Specify options | ||
```js | ||
//Load the library and specify options | ||
const replace = require('replace-in-file'); | ||
const options = { | ||
files: 'path/to/file', | ||
from: /foo/g, | ||
to: 'bar', | ||
}; | ||
``` | ||
//Single file or glob | ||
### Asynchronous replacement with promises | ||
```js | ||
replace(options) | ||
.then(changes => { | ||
console.log('Modified files:', changes.join(', ')); | ||
}) | ||
.catch(error => { | ||
console.error('Error occurred:', error); | ||
}); | ||
``` | ||
### Asynchronous replacement with callback | ||
```js | ||
replace(options, (error, changes) => { | ||
if (error) { | ||
return console.error('Error occurred:', error); | ||
} | ||
console.log('Modified files:', changes.join(', ')); | ||
}); | ||
``` | ||
### Synchronous replacement | ||
```js | ||
try { | ||
const changes = replace.sync(options); | ||
console.log('Modified files:', changes.join(', ')); | ||
} | ||
catch (error) { | ||
console.error('Error occurred:', error); | ||
} | ||
``` | ||
### Return value | ||
The return value of the library is an array of file names of files that were modified (e.g. | ||
had some of the contents replaced). If no replacements were made, the return array will be empty. | ||
```js | ||
const changes = replace.sync({ | ||
files: 'path/to/files/*.html', | ||
from: 'foo', | ||
to: 'bar', | ||
}); | ||
console.log(changes); | ||
// [ | ||
// 'path/to/files/file1.html', | ||
// 'path/to/files/file3.html', | ||
// 'path/to/files/file5.html', | ||
// ] | ||
``` | ||
## Advanced usage | ||
### Replace a single file or glob | ||
```js | ||
const options = { | ||
files: 'path/to/file', | ||
}; | ||
``` | ||
//Multiple files or globs | ||
### Replace multiple files or globs | ||
```js | ||
const options = { | ||
files: [ | ||
@@ -39,40 +108,48 @@ 'path/to/file', | ||
], | ||
}; | ||
``` | ||
//Replacement to make (string or regex) | ||
from: /foo/g, | ||
to: 'bar', | ||
### Replace first occurrence only | ||
//Multiple replacements with the same string (replaced sequentially) | ||
from: [/foo/g, /baz/g], | ||
```js | ||
const options = { | ||
from: 'foo', | ||
to: 'bar', | ||
}; | ||
``` | ||
//Multiple replacements with different strings (replaced sequentially) | ||
from: [/foo/g, /baz/g], | ||
to: ['bar', 'bax'], | ||
### Replace all occurrences | ||
Please note that the value specified in the `from` parameter is passed straight to the native [String replace method](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace). As such, if you pass a string as the `from` parameter, it will _only replace the first occurrence_. | ||
//Specify if empty/invalid file paths are allowed (defaults to false) | ||
//If set to true these paths will fail silently and no error will be thrown. | ||
allowEmptyPaths: false, | ||
To replace multiple occurrences at once, you must use a regular expression for the `from` parameter with the global flag enabled, e.g. `/foo/g`. | ||
//Character encoding for reading/writing files (defaults to utf-8) | ||
encoding: 'utf8', | ||
```js | ||
const options = { | ||
from: /foo/g, | ||
to: 'bar', | ||
}; | ||
``` | ||
//Single file or glob to ignore | ||
ignore: 'path/to/ignored/file', | ||
### Multiple values with the same replacement | ||
//Multiple files or globs to ignore | ||
ignore: [ | ||
'path/to/ignored/file', | ||
'path/to/other/ignored_file', | ||
'path/to/ignored_files/*.html', | ||
'another/**/*.ignore', | ||
] | ||
These will be replaced sequentially. | ||
```js | ||
const options = { | ||
from: [/foo/g, /baz/g], | ||
to: 'bar', | ||
}; | ||
``` | ||
### Replacing multiple occurrences | ||
Please note that the value specified in the `from` parameter is passed straight to the native [String replace method](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace). As such, if you pass a string as the `from` parameter, it will _only replace the first occurrence_. | ||
### Multiple values with different replacements | ||
To replace multiple occurrences at once, you must use a regular expression for the `from` parameter with the global flag enabled, e.g. `/foo/g`. | ||
These will be replaced sequentially. | ||
```js | ||
const options = { | ||
from: [/foo/g, /baz/g], | ||
to: ['bar', 'bax'], | ||
}; | ||
``` | ||
### Using callbacks for `to` | ||
@@ -89,66 +166,58 @@ As the `to` parameter is passed straight to the native [String replace method](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace), you can also specify a callback. The following example uses a callback to convert matching strings to lowercase: | ||
### Asynchronous replacement with promises | ||
### Ignore a single file or glob | ||
```js | ||
replace(options) | ||
.then(changedFiles => { | ||
console.log('Modified files:', changedFiles.join(', ')); | ||
}) | ||
.catch(error => { | ||
console.error('Error occurred:', error); | ||
}); | ||
const options = { | ||
ignore: 'path/to/ignored/file', | ||
}; | ||
``` | ||
### Asynchronous replacement with callback | ||
### Ignore multiple files or globs | ||
```js | ||
replace(options, (error, changedFiles) => { | ||
if (error) { | ||
return console.error('Error occurred:', error); | ||
} | ||
console.log('Modified files:', changedFiles.join(', ')); | ||
}); | ||
const options = { | ||
ignore: [ | ||
'path/to/ignored/file', | ||
'path/to/other/ignored_file', | ||
'path/to/ignored_files/*.html', | ||
'another/**/*.ignore', | ||
], | ||
}; | ||
``` | ||
### Synchronous replacement | ||
### Allow empty/invalid paths | ||
If set to true, empty or invalid paths will fail silently and no error will be thrown. For asynchronous replacement only. Defaults to `false`. | ||
```js | ||
try { | ||
const changedFiles = replace.sync(options); | ||
console.log('Modified files:', changedFiles.join(', ')); | ||
} | ||
catch (error) { | ||
console.error('Error occurred:', error); | ||
} | ||
const options = { | ||
allowEmptyPaths: true, | ||
}; | ||
``` | ||
### Return value | ||
### Disable globs | ||
You can disable globs if needed using this flag. Use this when you run into issues with file paths like files like `//SERVER/share/file.txt`. Defaults to `false`. | ||
The return value of the library is an array of file names of files that were modified (e.g. | ||
had some of the contents replaced). If no replacements were made, the return array will be empty. | ||
```js | ||
const options = { | ||
disableGlobs: true, | ||
}; | ||
``` | ||
For example: | ||
### Specify character encoding | ||
Use a different character encoding for reading/writing files. Defaults to `utf-8`. | ||
```js | ||
const changedFiles = replace.sync({ | ||
files: 'path/to/files/*.html', | ||
from: 'a', | ||
to: 'b', | ||
}); | ||
// changedFiles could be an array like: | ||
[ | ||
'path/to/files/file1.html', | ||
'path/to/files/file3.html', | ||
'path/to/files/file5.html', | ||
] | ||
const options = { | ||
encoding: 'utf8', | ||
}; | ||
``` | ||
### CLI usage | ||
## CLI usage | ||
```sh | ||
replace-in-file from to some/file.js,some/**/glob.js | ||
[--configFile=replace-config.js] | ||
[--ignore=ignore/files.js,ignore/**/glob.js] | ||
[--encoding=utf-8] | ||
[--allowEmptyPaths] | ||
[--disableGlobs] | ||
[--isRegex] | ||
@@ -158,9 +227,16 @@ [--verbose] | ||
The flags `allowEmptyPaths`, `ignore` and `encoding` are supported in the CLI. | ||
In addition, the CLI supports the `verbose` flag to list the changed files. | ||
Multiple files or globs can be replaced by providing a comma separated list. | ||
The flags `disableGlobs`, `ignore` and `encoding` are supported in the CLI. | ||
The flag `allowEmptyPaths` is not supported in the CLI as the replacement is | ||
synchronous, and the flag is only relevant for asynchronous replacement. | ||
To list the changed files, use the `verbose` flag. | ||
A regular expression may be used for the `from` parameter by specifying the `--isRegex` flag. | ||
## Version information | ||
From version 3.0.0 onwards, replace in file requires Node 6 or higher. If you need support for Node 4 or 5, use version 2.x.x. | ||
## License | ||
@@ -167,0 +243,0 @@ (MIT License) |
Sorry, the diff of this file is not supported yet
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
145314
8
27
1145
242
1
4