@j-ulrich/release-it-regex-bumper 2.0.0 → 3.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.js CHANGED
@@ -1,9 +1,18 @@
1
+ 'use strict';
2
+
1
3
  const fs = require( 'fs' );
2
4
  const assert = require( 'assert' ).strict;
3
5
  const util = require( 'util' );
4
6
  const glob = require( 'fast-glob' );
5
7
  const chalk = require( 'chalk' );
6
- const _ = require( 'lodash' );
8
+ const _ = {
9
+ isEmpty: require( 'lodash/isEmpty' ),
10
+ isNil: require( 'lodash/isNil' ),
11
+ isNull: require( 'lodash/isNull' ),
12
+ isString: require( 'lodash/isString' ),
13
+ isFunction: require( 'lodash/isFunction' ),
14
+ castArray: require( 'lodash/castArray' ),
15
+ };
7
16
  const XRegExp = require( 'xregexp' );
8
17
  const semver = require( 'semver' );
9
18
  const { Plugin } = require( 'release-it' );
@@ -15,9 +24,17 @@ const diff = optionalRequire( 'diff' );
15
24
  const readFile = util.promisify( fs.readFile );
16
25
  const writeFile = util.promisify( fs.writeFile );
17
26
 
27
+ const semanticVersionRegex = XRegExp(
28
+ // eslint-disable-next-line security/detect-unsafe-regex
29
+ /(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)(?:-(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*)?(?:\+[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*)?/
30
+ );
31
+ const placeholderRegex = XRegExp( '\\{\\{(?<placeholder>(?:[a-z][a-z0-9_]*|\\{))(?::(?<format>.*))?\\}\\}', 'ig' );
32
+
33
+ const prereleasePrefix = '-';
34
+ const buildPrefix = '+';
18
35
 
19
36
  const defaultEncoding = 'utf-8';
20
- const defaultSearchRegex = XRegExp( /(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)(?:-(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*)?(?:\+[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*)?/ );
37
+ const defaultSearchRegex = XRegExp( '{{semver}}' );
21
38
  const defaultVersionCaptureGroup = null;
22
39
  const defaultReplace = '{{version}}';
23
40
 
@@ -25,47 +42,77 @@ const defaultReplace = '{{version}}';
25
42
 
26
43
  class RegExBumper extends Plugin {
27
44
 
45
+ constructor( ...args ) {
46
+ super( ...args );
47
+ this.setContext( { executionTime: new Date() } );
48
+ }
49
+
28
50
  async getLatestVersion() {
29
51
 
30
52
  const { in: inOptions, search: globalSearchOptions, encoding: globalEncoding } = this.options;
31
53
  if ( _.isNil( inOptions ) ) {
32
- return;
54
+ return undefined;
33
55
  }
34
56
 
35
- const { searchRegex: globalSearchRegex, versionCaptureGroup: globalVersionCaptureGroup } = parseSearchOptions.call( this, globalSearchOptions );
36
- const { file, encoding, searchRegex, versionCaptureGroup } = parseInOptions.call( this, inOptions );
57
+ const context = Object.assign( {}, this.getContext(), this.config.contextOptions );
58
+
59
+ const {
60
+ searchRegex: globalSearchRegex,
61
+ flags: globalSearchFlags,
62
+ versionCaptureGroup: globalVersionCaptureGroup,
63
+ } = parseSearchOptions( globalSearchOptions );
64
+ const { file, encoding, searchRegex, flags: searchFlags, versionCaptureGroup } = parseInOptions( inOptions );
37
65
 
38
66
  const effectiveEncoding = firstNotNil( encoding, globalEncoding, defaultEncoding );
39
67
  const fileContent = await readFile( file, { encoding: effectiveEncoding } );
40
- const version = await extractVersion.call( this, fileContent, firstNotNil( searchRegex, globalSearchRegex, defaultSearchRegex ),
41
- firstNotNil( versionCaptureGroup, globalVersionCaptureGroup, defaultVersionCaptureGroup ) );
68
+ const effectiveSearchRegex = mergeSearchRegExes(
69
+ [ searchRegex, globalSearchRegex, defaultSearchRegex ],
70
+ [ searchFlags, globalSearchFlags ]
71
+ );
72
+ const replacedSearchRegex = prepareSearch( effectiveSearchRegex, context );
73
+ const version = await extractVersion(
74
+ fileContent,
75
+ replacedSearchRegex,
76
+ firstNotNil( versionCaptureGroup, globalVersionCaptureGroup, defaultVersionCaptureGroup )
77
+ );
42
78
  return version;
43
79
  }
44
80
 
45
-
46
81
  async bump( version ) {
47
-
48
- const { out: outOptions, search: globalSearchOptions, replace: globalReplace, encoding: globalEncoding } = this.options;
82
+ const {
83
+ out: outOptions,
84
+ search: globalSearchOptions,
85
+ replace: globalReplace,
86
+ encoding: globalEncoding,
87
+ } = this.options;
49
88
  const { isDryRun } = this.config;
50
89
  if ( _.isNil( outOptions ) ) {
51
90
  return;
52
91
  }
53
- const context = Object.assign( {}, this.config.contextOptions );
92
+ const context = Object.assign( {}, this.getContext(), this.config.contextOptions );
54
93
  if ( !context.version ) {
55
94
  context.version = version;
56
95
  }
57
96
 
58
- const { searchRegex: globalSearchRegex } = parseSearchOptions.call( this, globalSearchOptions );
97
+ const { searchRegex: globalSearchRegex, flags: globalSearchFlags } = parseSearchOptions.call(
98
+ this,
99
+ globalSearchOptions
100
+ );
59
101
  const expandedOutOptions = await expandOutOptionFiles.call( this, parseOutOptions.call( this, outOptions ) );
60
102
 
61
- for ( const outOptions of expandedOutOptions ) {
62
-
63
- const { files, encoding, searchRegex, replace } = outOptions;
103
+ /* eslint-disable no-await-in-loop */
104
+ for ( const outOption of expandedOutOptions ) {
105
+ const { files, encoding, searchRegex, flags: searchFlags, replace } = outOption;
64
106
 
65
107
  const effectiveEncoding = firstNotNil( encoding, globalEncoding, defaultEncoding );
66
- const effectiveSearchRegex = firstNotNil( searchRegex, globalSearchRegex, defaultSearchRegex );
108
+ const effectiveSearchRegex = mergeSearchRegExes(
109
+ [ searchRegex, globalSearchRegex, defaultSearchRegex ],
110
+ [ searchFlags, globalSearchFlags ]
111
+ );
67
112
  const effectiveReplacement = firstNotNil( replace, globalReplace, defaultReplace );
68
113
 
114
+ const replacedSearchRegex = prepareSearch( effectiveSearchRegex, context );
115
+
69
116
  for ( const file of files ) {
70
117
 
71
118
  this.log.info( `Updating version in ${file}` );
@@ -73,26 +120,96 @@ class RegExBumper extends Plugin {
73
120
  const fileContent = await readFile( file, { encoding: effectiveEncoding } );
74
121
 
75
122
  if ( isDryRun ) {
76
- loadDiff.call( this );
123
+ this.loadDiff();
77
124
  if ( this.diff ) {
78
- const processedFileContent = replaceVersion.call( this, fileContent, effectiveSearchRegex, effectiveReplacement, context );
79
- await diffAndReport.call( this, fileContent, processedFileContent, file );
125
+ const processedFileContent = replaceVersion( fileContent, replacedSearchRegex,
126
+ effectiveReplacement, context );
127
+ await this.diffAndReport( fileContent, processedFileContent, file );
80
128
  continue;
81
129
  }
82
- await searchAndReport.call( this, fileContent, effectiveSearchRegex, file );
130
+ await this.searchAndReport( fileContent, replacedSearchRegex, file );
83
131
  continue;
84
132
  }
85
133
 
86
- const processedFileContent = replaceVersion.call( this, fileContent, effectiveSearchRegex, effectiveReplacement, context );
134
+ const processedFileContent = replaceVersion(
135
+ fileContent,
136
+ replacedSearchRegex,
137
+ effectiveReplacement,
138
+ context
139
+ );
87
140
 
88
- if ( processedFileContent == fileContent ) {
89
- warnNoFileChange.call( this, file );
141
+ if ( processedFileContent === fileContent ) {
142
+ this.warnNoFileChange( file );
90
143
  }
91
144
  else {
92
145
  await writeFile( file, processedFileContent, effectiveEncoding );
93
146
  }
94
147
  }
95
148
  }
149
+ /* eslint-enable no-await-in-loop */
150
+ }
151
+
152
+ loadDiff() {
153
+ if ( !diff || diff instanceof Error ) {
154
+ this.log.info( 'Optional "diff" package not available' );
155
+ this.log.verbose( 'Exception was:', diff );
156
+ }
157
+ this.diff = diff;
158
+ }
159
+
160
+ diffAndReport( oldContent, newContent, filePath ) {
161
+ const { isDryRun } = this.config;
162
+ const diffResult = this.diff.structuredPatch( filePath, filePath, oldContent, newContent, undefined, undefined,
163
+ {
164
+ context: 0,
165
+ }
166
+ );
167
+
168
+ if ( _.isEmpty( diffResult.hunks ) ) {
169
+ this.warnNoFileChange( filePath );
170
+ }
171
+ diffResult.hunks.forEach( ( hunk ) => {
172
+ this.log.exec(
173
+ `Replacing at line ${hunk.oldStart}:\n` +
174
+ hunk.lines
175
+ .map( ( line ) => {
176
+ const lineText = '\t' + line;
177
+ if ( line.startsWith( '-' ) ) {
178
+ return chalk.red( lineText );
179
+ }
180
+ if ( line.startsWith( '+' ) ) {
181
+ return chalk.green( lineText );
182
+ }
183
+ return lineText;
184
+ } )
185
+ .join( '\n' ),
186
+ { isDryRun }
187
+ );
188
+ } );
189
+ }
190
+
191
+ searchAndReport( content, searchRegex, filePath ) {
192
+ const { isDryRun } = this.config;
193
+ let foundMatch = false;
194
+ const lineCounter = new LineCounter( content );
195
+ XRegExp.forEach( content, searchRegex, ( match ) => {
196
+ const matchText = match[0];
197
+ const matchIndex = searchRegex.lastIndex - matchText.length;
198
+ this.log.exec(
199
+ `Replacing match at line ${lineCounter.lineOfIndex( matchIndex )}, ` +
200
+ `column ${lineCounter.columnOfIndex( matchIndex )}:\n\t` +
201
+ matchText,
202
+ { isDryRun }
203
+ );
204
+ foundMatch = true;
205
+ } );
206
+ if ( !foundMatch ) {
207
+ this.warnNoFileChange( filePath );
208
+ }
209
+ }
210
+
211
+ warnNoFileChange( filePath ) {
212
+ this.log.warn( `File "${filePath}" did not change!` );
96
213
  }
97
214
 
98
215
  }
@@ -106,11 +223,11 @@ function parseInOptions( options ) {
106
223
  return { file, encoding, searchRegex, versionCaptureGroup };
107
224
  }
108
225
  const { file, encoding } = options;
109
- const { searchRegex, versionCaptureGroup } = parseSearchOptions( options.search );
226
+ const { searchRegex, flags, versionCaptureGroup } = parseSearchOptions( options.search );
110
227
  if ( !file ) {
111
- throw new Error( 'Missing "file" property in "in" options' );
228
+ throw new Error( "Missing 'file' property in 'in' options" );
112
229
  }
113
- return { file, encoding, searchRegex, versionCaptureGroup };
230
+ return { file, encoding, searchRegex, flags, versionCaptureGroup };
114
231
  }
115
232
 
116
233
  function parseSearchOptions( options ) {
@@ -123,8 +240,8 @@ function parseSearchOptions( options ) {
123
240
  return { searchRegex, versionCaptureGroup };
124
241
  }
125
242
  const { pattern, flags, versionCaptureGroup } = options;
126
- const searchRegex = XRegExp( pattern, _.isNull( flags ) ? undefined : flags );
127
- return { searchRegex, versionCaptureGroup };
243
+ const searchRegex = _.isNil( pattern ) ? pattern : XRegExp( pattern, _.isNull( flags ) ? undefined : flags );
244
+ return { searchRegex, flags, versionCaptureGroup };
128
245
  }
129
246
 
130
247
  function extractVersion( content, versionRegex, versionCaptureGroup ) {
@@ -133,39 +250,45 @@ function extractVersion( content, versionRegex, versionCaptureGroup ) {
133
250
  throw new Error( 'Could not extract version from file' );
134
251
  }
135
252
  if ( !_.isNil( versionCaptureGroup ) ) {
136
- if( match.hasOwnProperty( versionCaptureGroup ) ) {
137
- return match[ versionCaptureGroup ];
253
+ if ( hasOwnProperty( match, versionCaptureGroup ) ) {
254
+ // object injection mitigated by checking hasOwnProperty()
255
+ // eslint-disable-next-line security/detect-object-injection
256
+ return match[versionCaptureGroup];
138
257
  }
139
258
  }
140
259
  else {
141
- if ( match.hasOwnProperty( 'version' ) ) {
142
- return match[ 'version' ];
260
+ if ( hasOwnProperty( match, 'version' ) ) {
261
+ return match['version'];
143
262
  }
144
- if ( match.hasOwnProperty( 1 ) ) {
145
- return match[ 1 ];
263
+ if ( hasOwnProperty( match, 1 ) ) {
264
+ return match[1];
146
265
  }
147
266
  }
148
267
 
149
- return match[ 0 ];
268
+ return match[0];
269
+ }
270
+
271
+ function hasOwnProperty( obj, prop ) {
272
+ return Object.prototype.hasOwnProperty.call( obj, prop );
150
273
  }
151
274
 
152
275
  function parseOutOptions( options ) {
153
- return _.castArray( options ).map( options => {
154
- if ( _.isString( options ) ) {
276
+ return _.castArray( options ).map( ( option ) => {
277
+ if ( _.isString( option ) ) {
155
278
  return {
156
- files: [ options ],
279
+ files: [ option ],
157
280
  encoding: null,
158
281
  searchRegex: null,
159
282
  replace: null,
160
283
  };
161
284
  }
162
- const { encoding, replace } = options;
163
- const { searchRegex } = parseSearchOptions( options.search );
164
- const files = options.files ? _.castArray( options.files ) : [];
165
- if( options.file ) {
166
- files.unshift( options.file );
285
+ const { encoding, replace } = option;
286
+ const { searchRegex, flags } = parseSearchOptions( option.search );
287
+ const files = option.files ? _.castArray( option.files ) : [];
288
+ if ( option.file ) {
289
+ files.unshift( option.file );
167
290
  }
168
- return { files, encoding, searchRegex, replace };
291
+ return { files, encoding, searchRegex, flags, replace };
169
292
  } );
170
293
  }
171
294
 
@@ -186,132 +309,167 @@ async function expandOutOptionFiles( options ) {
186
309
  return options;
187
310
  }
188
311
 
189
- function loadDiff() {
190
- if ( !diff || diff instanceof Error ) {
191
- this.log.info( 'Optional "diff" package not available' );
192
- this.log.verbose( 'Exception was:', diff );
193
- }
194
- this.diff = diff;
312
+ function mergeSearchRegExes( regExCandidates, flagCandidates ) {
313
+ const searchRegEx = firstNotNil( ...regExCandidates );
314
+ const flags = firstNotNil( ...flagCandidates );
315
+ return XRegExp( searchRegEx.xregexp.source, flags );
195
316
  }
196
317
 
197
- function diffAndReport( oldContent, newContent, filePath ) {
198
- const { isDryRun } = this.config;
199
- const diffResult = this.diff.structuredPatch( filePath, filePath, oldContent, newContent, undefined, undefined, { context: 0 } );
318
+ function prepareSearch( searchRegEx, context ) {
319
+ const pattern = searchRegEx.xregexp.source;
320
+ const placeholderMap = {
321
+ now: ( format ) => {
322
+ if ( _.isNil( format ) ) {
323
+ throw new Error( "Missing required parameter 'format' for placeholder {{now}}" );
324
+ }
325
+ return XRegExp.escape( dateFormat( context.executionTime, format ) );
326
+ },
327
+ semver: `${semanticVersionRegex.xregexp.source || semanticVersionRegex.source}`,
328
+ };
329
+ const parsedVer = semver.parse( context.latestVersion );
330
+ if ( parsedVer ) {
331
+ Object.assign( placeholderMap, {
332
+ version: XRegExp.escape( parsedVer.raw ),
333
+ major: parsedVer.major,
334
+ minor: parsedVer.minor,
335
+ patch: parsedVer.patch,
336
+ prerelease: XRegExp.escape( parsedVer.prerelease.join( '.' ) ),
337
+ prefixedPrerelease:
338
+ parsedVer.prerelease.length > 0
339
+ ? XRegExp.escape( prereleasePrefix + parsedVer.prerelease.join( '.' ) )
340
+ : '',
341
+ build: XRegExp.escape( parsedVer.build.join( '.' ) ),
342
+ prefixedBuild:
343
+ parsedVer.build.length > 0 ? XRegExp.escape( buildPrefix + parsedVer.build.join( '.' ) ) : '',
344
+ versionWithoutBuild: XRegExp.escape( parsedVer.version ),
345
+ versionWithoutPrerelease: XRegExp.escape( `${parsedVer.major}.${parsedVer.minor}.${parsedVer.patch}` ),
346
+ } );
347
+ }
348
+ if ( context.latestTag ) {
349
+ Object.assign( placeholderMap, { tag: XRegExp.escape( context.latestTag ) } );
350
+ }
200
351
 
201
- if ( _.isEmpty( diffResult.hunks ) ) {
202
- warnNoFileChange.call( this, filePath );
352
+ if ( context.version ) {
353
+ Object.assign( placeholderMap, { newVersion: XRegExp.escape( context.version ) } );
203
354
  }
204
- diffResult.hunks.forEach( hunk => {
205
- this.log.exec( `Replacing at line ${hunk.oldStart}:\n` + hunk.lines.map( line => {
206
- const lineText = '\t' + line;
207
- if ( line.startsWith( '-' ) ) {
208
- return chalk.red( lineText );
209
- }
210
- if ( line.startsWith( '+' ) ) {
211
- return chalk.green( lineText );
212
- }
213
- return lineText;
214
- } ).join( '\n' ), { isDryRun } );
215
- } );
216
355
 
356
+ wrapSearchPatternPlaceholders( placeholderMap );
357
+ const replacedPattern = replacePlaceholders( pattern, placeholderMap );
358
+ return XRegExp( replacedPattern, searchRegEx.xregexp.flags || undefined );
217
359
  }
218
360
 
219
- function searchAndReport( content, searchRegex, filePath ) {
220
- const { isDryRun } = this.config;
221
- let foundMatch = false;
222
- const lineCounter = new LineCounter( content );
223
- XRegExp.forEach( content, searchRegex, ( match ) => {
224
- const matchText = match[ 0 ];
225
- const matchIndex = searchRegex.lastIndex - matchText.length;
226
- this.log.exec( `Replacing match at line ${lineCounter.lineOfIndex( matchIndex )}, column ${lineCounter.columnOfIndex( matchIndex )}:\n\t` + matchText, { isDryRun } );
227
- foundMatch = true;
228
- } );
229
- if ( !foundMatch ) {
230
- warnNoFileChange.call( this, filePath );
361
+ function wrapSearchPatternPlaceholders( placeholderMap ) {
362
+ for ( const placeholder of Object.keys( placeholderMap ) ) {
363
+ // object injection mitigated for placeholderMap since placeholder is from Object.keys( placeholderMap )
364
+ // eslint-disable-next-line security/detect-object-injection
365
+ const replacement = placeholderMap[ placeholder ];
366
+ if ( _.isFunction( replacement ) ) {
367
+ // eslint-disable-next-line security/detect-object-injection
368
+ placeholderMap[ placeholder ] = ( ...args ) => {
369
+ return wrapInRegexGroup( replacement( ...args ) );
370
+ };
371
+ continue;
372
+ }
373
+ // eslint-disable-next-line security/detect-object-injection
374
+ placeholderMap[ placeholder ] = wrapInRegexGroup( replacement );
231
375
  }
232
376
  }
233
377
 
234
- function warnNoFileChange( filePath ) {
235
- this.log.warn( `File "${filePath}" did not change!` );
378
+ function wrapInRegexGroup( pattern ) {
379
+ return `(?:${pattern})`;
236
380
  }
237
381
 
238
382
  function replaceVersion( content, searchRegex, replace, context ) {
239
- const processedReplace = prepareReplacement.call( this, replace, context );
383
+ const processedReplace = prepareReplacement( replace, context );
240
384
  const processedContent = XRegExp.replace( content, searchRegex, processedReplace );
241
385
  return processedContent;
242
386
  }
243
387
 
244
388
  function prepareReplacement( replace, context ) {
245
- const placeholderRegex = XRegExp( /\{\{(?<placeholder>(?:[a-z][a-z0-9_]*|\{))(?::(?<format>.*))?\}\}/ig );
246
- const now = new Date();
247
389
  const parsedVer = semver.parse( context.version );
248
390
  const placeholderMap = {
249
- '{': '{',
250
- 'version': parsedVer.raw,
251
- 'major': parsedVer.major,
252
- 'minor': parsedVer.minor,
253
- 'patch': parsedVer.patch,
254
- 'prerelease': parsedVer.prerelease.join( '.' ),
255
- 'build': parsedVer.build.join( '.' ),
256
- 'versionWithoutBuild': parsedVer.version,
257
- 'versionWithoutPrerelease': `${parsedVer.major}.${parsedVer.minor}.${parsedVer.patch}`,
258
- 'latestVersion': context.latestVersion,
259
- 'latestTag': context.latestTag,
260
- 'now': ( format ) => {
261
- if( _.isNil( format ) ) {
262
- return dateFormatIso( now );
391
+ version: parsedVer.raw,
392
+ major: parsedVer.major,
393
+ minor: parsedVer.minor,
394
+ patch: parsedVer.patch,
395
+ prerelease: parsedVer.prerelease.join( '.' ),
396
+ prefixedPrerelease:
397
+ parsedVer.prerelease.length > 0 ? prereleasePrefix + parsedVer.prerelease.join( '.' ) : '',
398
+ build: parsedVer.build.join( '.' ),
399
+ prefixedBuild: parsedVer.build.length > 0 ? buildPrefix + parsedVer.build.join( '.' ) : '',
400
+ versionWithoutBuild: parsedVer.version,
401
+ versionWithoutPrerelease: `${parsedVer.major}.${parsedVer.minor}.${parsedVer.patch}`,
402
+ latestVersion: context.latestVersion || '',
403
+ latestTag: context.latestTag || '',
404
+ now: ( format ) => {
405
+ if ( _.isNil( format ) ) {
406
+ return dateFormatIso( context.executionTime );
263
407
  }
264
- return dateFormat( now, format );
265
- }
408
+ return dateFormat( context.executionTime, format );
409
+ },
266
410
  };
267
- return XRegExp.replace( replace, placeholderRegex, ( match, placeholder, format ) => {
268
- const placeholderReplace = placeholderMap.hasOwnProperty( placeholder) ? placeholderMap[ placeholder ] : undefined;
269
- if ( _.isFunction( placeholderReplace ) ) {
411
+ return replacePlaceholders( replace, placeholderMap );
412
+ }
413
+
414
+ function replacePlaceholders( template, placeholderMap ) {
415
+ placeholderMap = Object.assign( {}, placeholderMap, { '{': '{' } );
416
+ return XRegExp.replace( template, placeholderRegex, ( match, placeholder, format ) => {
417
+ if ( !hasOwnProperty( placeholderMap, placeholder ) ) {
418
+ throw new Error( `Unknown placeholder encountered: {{${placeholder}}}` );
419
+ }
420
+ // object injection mitigated by checking hasOwnProperty()
421
+ // eslint-disable-next-line security/detect-object-injection
422
+ const replacement = placeholderMap[ placeholder ];
423
+ if ( _.isFunction( replacement ) ) {
270
424
  if ( _.isString( format ) ) {
271
- return placeholderReplace( format );
425
+ return replacement( format );
272
426
  }
273
- return placeholderReplace();
427
+ return replacement();
274
428
  }
275
- return placeholderReplace;
429
+ return replacement;
276
430
  } );
277
431
  }
278
432
 
279
-
280
433
  function optionalRequire( packageName ) {
281
434
  try {
435
+ // eslint-disable-next-line security/detect-non-literal-require
282
436
  return require( packageName );
283
437
  }
438
+ /* c8 ignore next 4 */
439
+ /* rewiremock can't simulate the absence of a package so we can't test this case */
284
440
  catch ( ex ) {
285
441
  return ex;
286
442
  }
287
443
  }
288
444
 
289
445
  function firstNotNil( ...args ) {
290
- return args.find( ele => !_.isNil( ele ) );
446
+ return args.find( ( ele ) => !_.isNil( ele ) );
291
447
  }
292
448
 
293
449
  class LineCounter {
294
450
  constructor( text ) {
295
451
  let index = 0;
296
- this.lineEndIndex = text.split( '\n' ).map( line => {
452
+ this.lineEndIndex = text.split( '\n' ).map( ( line ) => {
297
453
  index += line.length + 1;
298
454
  return index;
299
455
  } );
300
456
  }
301
457
 
302
458
  lineOfIndex( index ) {
303
- const arrayIndex = this.lineEndIndex.findIndex( lineEndIndex => ( index <= lineEndIndex ) );
459
+ // eslint-disable-next-line no-extra-parens
460
+ const arrayIndex = this.lineEndIndex.findIndex( ( lineEndIndex ) => index <= lineEndIndex );
304
461
  assert( arrayIndex >= 0 );
305
462
  return arrayIndex + 1;
306
463
  }
307
464
 
308
465
  columnOfIndex( index ) {
309
- const line = this.lineOfIndex( index );
310
- assert( line >= 1 );
311
- if ( line === 1 ) {
466
+ const lineNumber = this.lineOfIndex( index );
467
+ assert( lineNumber >= 1 );
468
+ if ( lineNumber === 1 ) {
312
469
  return index;
313
470
  }
314
- const previousLineEndIndex = this.lineEndIndex[ line - 2 ];
471
+ const previousLineNumber = lineNumber - 1;
472
+ const previousLineEndIndex = this.lineEndIndex[ previousLineNumber - 1 ];
315
473
  return index - previousLineEndIndex;
316
474
  }
317
475
  }
package/package.json CHANGED
@@ -1,11 +1,15 @@
1
1
  {
2
2
  "name": "@j-ulrich/release-it-regex-bumper",
3
- "version": "2.0.0",
3
+ "version": "3.0.2",
4
4
  "description": "Regular expression based version read/write plugin for release-it",
5
5
  "main": "index.js",
6
+ "files": [
7
+ "index.js"
8
+ ],
6
9
  "scripts": {
7
10
  "test": "npx bron test.js",
8
11
  "test+coverage": "npx c8 npx bron test.js",
12
+ "lint": "npx eslint .",
9
13
  "release": "npx release-it"
10
14
  },
11
15
  "keywords": [
@@ -48,12 +52,17 @@
48
52
  "diff": "^3.0.0 || ^4.0.0"
49
53
  },
50
54
  "devDependencies": {
55
+ "@j-ulrich/eslint-config": "^1.0.0",
56
+ "@release-it/keep-a-changelog": "^2.5.0",
51
57
  "bron": "^1.1.0",
52
58
  "c8": "^7.3.0",
53
- "mock-fs": "^4.13.0",
59
+ "eslint": "^8.6.0",
60
+ "eslint-plugin-security": "^1.4.0",
61
+ "mock-fs": "^5.1.1",
54
62
  "release-it": "^14.0.0",
55
63
  "rewiremock": "^3.14.3",
56
- "sinon": "^9.0.3"
64
+ "sinon": "^9.0.3",
65
+ "temp": "^0.9.4"
57
66
  },
58
67
  "engines": {
59
68
  "node": ">=10.12.0"
@@ -1,64 +0,0 @@
1
- # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
2
- # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
3
-
4
- name: CI
5
-
6
- on: [ push, pull_request ]
7
-
8
- jobs:
9
- compatibility-test-node:
10
- name: Test Node Version Compatibility
11
- runs-on: ubuntu-latest
12
-
13
- strategy:
14
- matrix:
15
- node-version: [ 10.x, 12.x ]
16
-
17
- steps:
18
- - uses: actions/checkout@v2
19
- - name: Use Node.js ${{ matrix.node-version }}
20
- uses: actions/setup-node@v1
21
- with:
22
- node-version: ${{ matrix.node-version }}
23
- - run: npm ci
24
- - run: npm install release-it
25
- - run: npm run build --if-present
26
- - run: npm run test
27
-
28
- compatibility-test-release:
29
- name: Test Release-it Version Compatibility
30
- runs-on: ubuntu-latest
31
-
32
- strategy:
33
- matrix:
34
- release-it-version: [ 11.x, 12.x, 13.x, 14.x ]
35
-
36
- steps:
37
- - uses: actions/checkout@v2
38
- - uses: actions/setup-node@v1
39
- with:
40
- node-version: 14.x
41
- - run: npm ci
42
- - run: npm install release-it@${{ matrix.release-it-version }}
43
- - run: npm run build --if-present
44
- - run: npm run test
45
-
46
- coverage:
47
- name: Coverage
48
- runs-on: ubuntu-latest
49
-
50
- steps:
51
- - uses: actions/checkout@v2
52
- - uses: actions/setup-node@v1
53
- with:
54
- node-version: 14.x
55
- - run: npm ci
56
- - run: npm install release-it
57
- - run: npm run build --if-present
58
- - run: npm run test+coverage
59
- - run: npx c8 report --reporter lcov
60
- - name: Upload coverage to Codacy
61
- uses: codacy/codacy-coverage-reporter-action@v1
62
- with:
63
- project-token: ${{ secrets.CODACY_PROJECT_TOKEN }}
64
- coverage-reports: coverage/lcov.info
package/.gitignore DELETED
@@ -1,6 +0,0 @@
1
- coverage/
2
- .*
3
- !.github
4
- !.gitignore
5
- !.release-it.json
6
- node_modules/