@j-ulrich/release-it-regex-bumper 3.0.0 → 4.0.0

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