11#!/usr/bin/env node
22import process from 'node:process' ;
3+ import { styleText } from 'node:util' ;
34import meow from 'meow' ;
45import replaceInFiles from './api.js' ;
56
@@ -13,11 +14,13 @@ const cli = meow(`
1314 --replacement Replacement string (Required)
1415 --ignore-case Search case-insensitively
1516 --no-glob Disable globbing
17+ --dry-run Show what would be replaced without making changes
1618
1719 Examples
1820 $ replace-in-files --string='horse' --regex='unicorn|rainbow' --replacement='🦄' foo.md
1921 $ replace-in-files --regex='v\\d+\\.\\d+\\.\\d+' --replacement=v$npm_package_version foo.css
2022 $ replace-in-files --string='blob' --replacement='blog' 'some/**/[gb]lob/*' '!some/glob/foo'
23+ $ replace-in-files --dry-run --string='old' --replacement='new' file.txt
2124
2225 You can use the same replacement patterns as with \`String#replace()\`, like \`$&\`.
2326` , {
@@ -43,6 +46,10 @@ const cli = meow(`
4346 type : 'boolean' ,
4447 default : true ,
4548 } ,
49+ dryRun : {
50+ type : 'boolean' ,
51+ default : false ,
52+ } ,
4653 } ,
4754} ) ;
4855
@@ -56,12 +63,64 @@ if (!cli.flags.regex && !cli.flags.string) {
5663 process . exit ( 1 ) ;
5764}
5865
59- await replaceInFiles ( cli . input , {
66+ function displayDryRunResults ( changes ) {
67+ if ( changes . length === 0 ) {
68+ console . log ( 'No matches found.' ) ;
69+ return ;
70+ }
71+
72+ for ( const change of changes ) {
73+ console . log ( `\n${ styleText ( 'magenta' , change . filePath ) } ` ) ;
74+
75+ const lines = change . originalContent . split ( '\n' ) ;
76+ const newLines = change . newContent . split ( '\n' ) ;
77+
78+ for ( const [ index , line ] of lines . entries ( ) ) {
79+ if ( line !== newLines [ index ] ) {
80+ const lineNumber = index + 1 ;
81+ const linePrefix = styleText ( 'cyan' , `${ lineNumber } :` ) ;
82+
83+ console . log ( `${ linePrefix } ${ highlightDifferences ( line , newLines [ index ] , 'red' ) } ` ) ;
84+ console . log ( `${ linePrefix } ${ highlightDifferences ( newLines [ index ] , line , 'green' ) } ` ) ;
85+ }
86+ }
87+ }
88+ }
89+
90+ function highlightDifferences ( text , otherText , color ) {
91+ let start = 0 ;
92+ let end = text . length ;
93+ let otherEnd = otherText . length ;
94+
95+ // Find common prefix
96+ while ( start < Math . min ( text . length , otherText . length ) && text [ start ] === otherText [ start ] ) {
97+ start ++ ;
98+ }
99+
100+ // Find common suffix
101+ while ( end > start && otherEnd > start && text [ end - 1 ] === otherText [ otherEnd - 1 ] ) {
102+ end -- ;
103+ otherEnd -- ;
104+ }
105+
106+ if ( start >= end ) {
107+ return text ;
108+ }
109+
110+ return text . slice ( 0 , start ) + styleText ( color , text . slice ( start , end ) ) + text . slice ( end ) ;
111+ }
112+
113+ const result = await replaceInFiles ( cli . input , {
60114 find : [
61115 ...cli . flags . string ,
62116 ...cli . flags . regex . map ( regexString => new RegExp ( regexString , 'g' ) ) ,
63117 ] ,
64118 replacement : cli . flags . replacement ,
65119 ignoreCase : cli . flags . ignoreCase ,
66120 glob : cli . flags . glob ,
121+ dryRun : cli . flags . dryRun ,
67122} ) ;
123+
124+ if ( cli . flags . dryRun ) {
125+ displayDryRunResults ( result ) ;
126+ }
0 commit comments