Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

replace-in-file

Package Overview
Dependencies
Maintainers
1
Versions
81
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

replace-in-file - npm Package Compare versions

Comparing version 2.6.4 to 3.0.0-beta.1

lib/helpers/combine-config.js

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"

@@ -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

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc