conventional-commits-parser 3.0.8 → 3.2.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/CHANGELOG.md CHANGED
@@ -3,6 +3,54 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ # [3.2.0](https://github.com/conventional-changelog/conventional-changelog/compare/conventional-commits-parser@3.1.0...conventional-commits-parser@3.2.0) (2020-11-05)
7
+
8
+
9
+ ### Bug Fixes
10
+
11
+ * ignore gpg lines ([#685](https://github.com/conventional-changelog/conventional-changelog/issues/685)) ([f8fcbc2](https://github.com/conventional-changelog/conventional-changelog/commit/f8fcbc2e8b0834c29178ace6382b438a020ad828))
12
+ * **deps:** update dependency through2 to v4 ([#657](https://github.com/conventional-changelog/conventional-changelog/issues/657)) ([7ae618c](https://github.com/conventional-changelog/conventional-changelog/commit/7ae618c81491841e5b1d796d3933aac0c54bc312))
13
+
14
+
15
+ ### Features
16
+
17
+ * allows notes pattern to be customized ([#586](https://github.com/conventional-changelog/conventional-changelog/issues/586)) ([9c00f32](https://github.com/conventional-changelog/conventional-changelog/commit/9c00f3242d916be1774a618d943f908f8d9699a6))
18
+
19
+
20
+
21
+
22
+
23
+ # [3.1.0](https://github.com/conventional-changelog/conventional-changelog/compare/conventional-commits-parser@3.0.8...conventional-commits-parser@3.1.0) (2020-05-08)
24
+
25
+
26
+ ### Bug Fixes
27
+
28
+ * **deps:** update yargs-parser to move off a flagged-vulnerable version. ([#635](https://github.com/conventional-changelog/conventional-changelog/issues/635)) ([aafc0f0](https://github.com/conventional-changelog/conventional-changelog/commit/aafc0f00412c3e4b23b8418300e5a570a48fe24d))
29
+
30
+
31
+ ### Features
32
+
33
+ * **conventional-commits-parser:** add issuePrefixesCaseSensitive parser option ([#580](https://github.com/conventional-changelog/conventional-changelog/issues/580)) ([526b282](https://github.com/conventional-changelog/conventional-changelog/commit/526b28214d12c55158eb2e4d44408378587ceb97))
34
+ * support slash in headerPattern default options ([93a547d](https://github.com/conventional-changelog/conventional-changelog/commit/93a547d742634d8676f499cfa2a274bc3792d020))
35
+
36
+
37
+
38
+
39
+
40
+ ### [3.2.2](https://www.github.com/conventional-changelog/conventional-changelog/compare/conventional-commits-parser-v3.2.1...conventional-commits-parser-v3.2.2) (2021-09-09)
41
+
42
+
43
+ ### Bug Fixes
44
+
45
+ * **conventional-commits-parser:** address CVE-2021-23425 ([#841](https://www.github.com/conventional-changelog/conventional-changelog/issues/841)) ([02b3d53](https://www.github.com/conventional-changelog/conventional-changelog/commit/02b3d53a0c142f0c28ee7d190d210c76a62887c2))
46
+
47
+ ### [3.2.1](https://www.github.com/conventional-changelog/conventional-changelog/compare/conventional-commits-parser@3.2.0...v3.2.1) (2021-02-15)
48
+
49
+
50
+ ### Bug Fixes
51
+
52
+ * handle missing header in merge commit ([#757](https://www.github.com/conventional-changelog/conventional-changelog/issues/757)) ([d189d3e](https://www.github.com/conventional-changelog/conventional-changelog/commit/d189d3e45b82e7141115ce8eccd95c8cf2d7db77))
53
+
6
54
  ## [3.0.8](https://github.com/conventional-changelog/conventional-changelog/compare/conventional-commits-parser@3.0.7...conventional-commits-parser@3.0.8) (2019-11-14)
7
55
 
8
56
 
package/README.md CHANGED
@@ -193,12 +193,24 @@ Type: `array` of `string` or `string` Default: `['#']`
193
193
 
194
194
  The prefixes of an issue. EG: In `gh-123` `gh-` is the prefix.
195
195
 
196
+ ##### issuePrefixesCaseSensitive
197
+
198
+ Type: `boolean` Default: false
199
+
200
+ Used to define if `issuePrefixes` should be considered case sensitive.
201
+
196
202
  ##### noteKeywords
197
203
 
198
204
  Type: `array` of `string` or `string` Default: `['BREAKING CHANGE']`
199
205
 
200
206
  Keywords for important notes. This value is case **insensitive**. If it's a `string` it will be converted to an `array` separated by a comma.
201
207
 
208
+ ##### notesPattern
209
+
210
+ Type: `function` Default: `noteKeywordsSelection => ^[\\s|*]*(' + noteKeywordsSelection + ')[:\\s]+(.*)` where `noteKeywordsSelection` is `join(noteKeywords, '|')`
211
+
212
+ A function that takes `noteKeywordsSelection` and returns a `RegExp` to be matched against the notes.
213
+
202
214
  ##### fieldPattern
203
215
 
204
216
  Type: `regex` or `string` Default: `/^-(.*?)-$/`
package/cli.js CHANGED
@@ -1,19 +1,19 @@
1
1
  #!/usr/bin/env node
2
2
  'use strict'
3
- var conventionalCommitsParser = require('./')
4
- var forEach = require('lodash').forEach
5
- var fs = require('fs')
6
- var isTextPath = require('is-text-path')
7
- var JSONStream = require('JSONStream')
8
- var meow = require('meow')
9
- var readline = require('readline')
10
- var split = require('split2')
11
- var through = require('through2')
3
+ const conventionalCommitsParser = require('./')
4
+ const forEach = require('lodash').forEach
5
+ const fs = require('fs')
6
+ const isTextPath = require('is-text-path')
7
+ const JSONStream = require('JSONStream')
8
+ const meow = require('meow')
9
+ const readline = require('readline')
10
+ const split = require('split2')
11
+ const through = require('through2')
12
12
 
13
- var filePaths = []
14
- var separator = '\n\n\n'
13
+ const filePaths = []
14
+ let separator = '\n\n\n'
15
15
 
16
- var cli = meow(`
16
+ const cli = meow(`
17
17
  Practice writing commit messages or parse messages from files.
18
18
  If used without specifying a text file path, you will enter an interactive shell.
19
19
  Otherwise the commit messages in the files are parsed and printed
@@ -31,15 +31,16 @@ var cli = meow(`
31
31
  conventional-commits-parser log2.txt '===' >> parsed.txt
32
32
 
33
33
  Options
34
- -p, --header-pattern Regex to match header pattern
35
- -c, --header-correspondence Comma separated parts used to define what capturing group of 'headerPattern' captures what
36
- -r, --reference-actions Comma separated keywords that used to reference issues
37
- -i, --issue-prefixes Comma separated prefixes of an issue
38
- -n, --note-keywords Comma separated keywords for important notes
39
- -f, --field-pattern Regex to match other fields
40
- --revert-pattern Regex to match revert pattern
41
- --revert-correspondence Comma separated fields used to define what the commit reverts
42
- -v, --verbose Verbose output
34
+ -p, --header-pattern Regex to match header pattern
35
+ -c, --header-correspondence Comma separated parts used to define what capturing group of 'headerPattern' captures what
36
+ -r, --reference-actions Comma separated keywords that used to reference issues
37
+ -i, --issue-prefixes Comma separated prefixes of an issue
38
+ --issue-prefixes-case-sensitive Treat issue prefixes as case sensitive
39
+ -n, --note-keywords Comma separated keywords for important notes
40
+ -f, --field-pattern Regex to match other fields
41
+ --revert-pattern Regex to match revert pattern
42
+ --revert-correspondence Comma separated fields used to define what the commit reverts
43
+ -v, --verbose Verbose output
43
44
  `, {
44
45
  flags: {
45
46
  'header-pattern': {
@@ -58,6 +59,9 @@ var cli = meow(`
58
59
  alias: 'i',
59
60
  type: 'string'
60
61
  },
62
+ 'issue-prefixes-case-sensitive': {
63
+ type: 'boolean'
64
+ },
61
65
  'note-keywords': {
62
66
  alias: 'n',
63
67
  type: 'string'
@@ -87,15 +91,15 @@ forEach(cli.input, function (arg) {
87
91
  }
88
92
  })
89
93
 
90
- var length = filePaths.length
91
- var options = cli.flags
94
+ const length = filePaths.length
95
+ const options = cli.flags
92
96
 
93
97
  if (options.verbose) {
94
98
  options.warn = console.log.bind(console)
95
99
  }
96
100
 
97
101
  function processFile (fileIndex) {
98
- var filePath = filePaths[fileIndex]
102
+ const filePath = filePaths[fileIndex]
99
103
  fs.createReadStream(filePath)
100
104
  .on('error', function (err) {
101
105
  console.warn('Failed to read file ' + filePath + '\n' + err)
@@ -118,9 +122,9 @@ if (process.stdin.isTTY) {
118
122
  if (length > 0) {
119
123
  processFile(0)
120
124
  } else {
121
- var commit = ''
122
- var stream = through()
123
- var rl = readline.createInterface({
125
+ let commit = ''
126
+ const stream = through()
127
+ const rl = readline.createInterface({
124
128
  input: process.stdin,
125
129
  output: process.stdout,
126
130
  terminal: true
package/index.js CHANGED
@@ -1,13 +1,13 @@
1
1
  'use strict'
2
2
 
3
- var parser = require('./lib/parser')
4
- var regex = require('./lib/regex')
5
- var through = require('through2')
6
- var _ = require('lodash')
3
+ const parser = require('./lib/parser')
4
+ const regex = require('./lib/regex')
5
+ const through = require('through2')
6
+ const _ = require('lodash')
7
7
 
8
8
  function assignOpts (options) {
9
9
  options = _.extend({
10
- headerPattern: /^(\w*)(?:\(([\w$.\-* ]*)\))?: (.*)$/,
10
+ headerPattern: /^(\w*)(?:\(([\w$.\-*/ ]*)\))?: (.*)$/,
11
11
  headerCorrespondence: ['type', 'scope', 'subject'],
12
12
  referenceActions: [
13
13
  'close',
@@ -71,10 +71,10 @@ function assignOpts (options) {
71
71
 
72
72
  function conventionalCommitsParser (options) {
73
73
  options = assignOpts(options)
74
- var reg = regex(options)
74
+ const reg = regex(options)
75
75
 
76
76
  return through.obj(function (data, enc, cb) {
77
- var commit
77
+ let commit
78
78
 
79
79
  try {
80
80
  commit = parser(data.toString(), options, reg)
@@ -92,7 +92,7 @@ function conventionalCommitsParser (options) {
92
92
 
93
93
  function sync (commit, options) {
94
94
  options = assignOpts(options)
95
- var reg = regex(options)
95
+ const reg = regex(options)
96
96
 
97
97
  return parser(commit, options, reg)
98
98
  }
package/lib/parser.js CHANGED
@@ -1,9 +1,12 @@
1
1
  'use strict'
2
- var trimOffNewlines = require('trim-off-newlines')
3
- var _ = require('lodash')
2
+ const _ = require('lodash')
4
3
 
5
- var CATCH_ALL = /()(.+)/gi
6
- var SCISSOR = '# ------------------------ >8 ------------------------'
4
+ const CATCH_ALL = /()(.+)/gi
5
+ const SCISSOR = '# ------------------------ >8 ------------------------'
6
+
7
+ function trimOffNewlines (input) {
8
+ return input.replace(/^(?:\r|\n)+|(?:\r|\n)+$/g, '')
9
+ }
7
10
 
8
11
  function append (src, line) {
9
12
  if (src) {
@@ -22,7 +25,7 @@ function getCommentFilter (char) {
22
25
  }
23
26
 
24
27
  function truncateToScissor (lines) {
25
- var scissorIndex = lines.indexOf(SCISSOR)
28
+ const scissorIndex = lines.indexOf(SCISSOR)
26
29
 
27
30
  if (scissorIndex === -1) {
28
31
  return lines
@@ -32,29 +35,29 @@ function truncateToScissor (lines) {
32
35
  }
33
36
 
34
37
  function getReferences (input, regex) {
35
- var references = []
36
- var referenceSentences
37
- var referenceMatch
38
+ const references = []
39
+ let referenceSentences
40
+ let referenceMatch
38
41
 
39
- var reApplicable = input.match(regex.references) !== null
42
+ const reApplicable = input.match(regex.references) !== null
40
43
  ? regex.references
41
44
  : CATCH_ALL
42
45
 
43
46
  while ((referenceSentences = reApplicable.exec(input))) {
44
- var action = referenceSentences[1] || null
45
- var sentence = referenceSentences[2]
47
+ const action = referenceSentences[1] || null
48
+ const sentence = referenceSentences[2]
46
49
 
47
50
  while ((referenceMatch = regex.referenceParts.exec(sentence))) {
48
- var owner = null
49
- var repository = referenceMatch[1] || ''
50
- var ownerRepo = repository.split('/')
51
+ let owner = null
52
+ let repository = referenceMatch[1] || ''
53
+ const ownerRepo = repository.split('/')
51
54
 
52
55
  if (ownerRepo.length > 1) {
53
56
  owner = ownerRepo.shift()
54
57
  repository = ownerRepo.join('/')
55
58
  }
56
59
 
57
- var reference = {
60
+ const reference = {
58
61
  action: action,
59
62
  owner: owner,
60
63
  repository: repository || null,
@@ -87,39 +90,37 @@ function parser (raw, options, regex) {
87
90
  throw new TypeError('Expected regex')
88
91
  }
89
92
 
90
- var headerMatch
91
- var mergeMatch
92
- var currentProcessedField
93
- var mentionsMatch
94
- var revertMatch
95
- var otherFields = {}
96
- var commentFilter = typeof options.commentChar === 'string'
93
+ let currentProcessedField
94
+ let mentionsMatch
95
+ const otherFields = {}
96
+ const commentFilter = typeof options.commentChar === 'string'
97
97
  ? getCommentFilter(options.commentChar)
98
98
  : passTrough
99
+ const gpgFilter = line => !line.match(/^\s*gpg:/)
99
100
 
100
- var rawLines = trimOffNewlines(raw).split(/\r?\n/)
101
- var lines = truncateToScissor(rawLines).filter(commentFilter)
101
+ const rawLines = trimOffNewlines(raw).split(/\r?\n/)
102
+ const lines = truncateToScissor(rawLines).filter(commentFilter).filter(gpgFilter)
102
103
 
103
- var continueNote = false
104
- var isBody = true
105
- var headerCorrespondence = _.map(options.headerCorrespondence, function (part) {
104
+ let continueNote = false
105
+ let isBody = true
106
+ const headerCorrespondence = _.map(options.headerCorrespondence, function (part) {
106
107
  return part.trim()
107
108
  })
108
- var revertCorrespondence = _.map(options.revertCorrespondence, function (field) {
109
+ const revertCorrespondence = _.map(options.revertCorrespondence, function (field) {
109
110
  return field.trim()
110
111
  })
111
- var mergeCorrespondence = _.map(options.mergeCorrespondence, function (field) {
112
+ const mergeCorrespondence = _.map(options.mergeCorrespondence, function (field) {
112
113
  return field.trim()
113
114
  })
114
115
 
115
- var body = null
116
- var footer = null
117
- var header = null
118
- var mentions = []
119
- var merge = null
120
- var notes = []
121
- var references = []
122
- var revert = null
116
+ let body = null
117
+ let footer = null
118
+ let header = null
119
+ const mentions = []
120
+ let merge = null
121
+ const notes = []
122
+ const references = []
123
+ let revert = null
123
124
 
124
125
  if (lines.length === 0) {
125
126
  return {
@@ -139,22 +140,25 @@ function parser (raw, options, regex) {
139
140
 
140
141
  // msg parts
141
142
  merge = lines.shift()
142
- var mergeParts = {}
143
- var headerParts = {}
143
+ const mergeParts = {}
144
+ const headerParts = {}
144
145
  body = ''
145
146
  footer = ''
146
147
 
147
- mergeMatch = merge.match(options.mergePattern)
148
+ const mergeMatch = merge.match(options.mergePattern)
148
149
  if (mergeMatch && options.mergePattern) {
149
150
  merge = mergeMatch[0]
150
151
 
151
152
  header = lines.shift()
152
- while (!header.trim()) {
153
+ while (header !== undefined && !header.trim()) {
153
154
  header = lines.shift()
154
155
  }
156
+ if (!header) {
157
+ header = ''
158
+ }
155
159
 
156
160
  _.forEach(mergeCorrespondence, function (partName, index) {
157
- var partValue = mergeMatch[index + 1] || null
161
+ const partValue = mergeMatch[index + 1] || null
158
162
  mergeParts[partName] = partValue
159
163
  })
160
164
  } else {
@@ -166,10 +170,10 @@ function parser (raw, options, regex) {
166
170
  })
167
171
  }
168
172
 
169
- headerMatch = header.match(options.headerPattern)
173
+ const headerMatch = header.match(options.headerPattern)
170
174
  if (headerMatch) {
171
175
  _.forEach(headerCorrespondence, function (partName, index) {
172
- var partValue = headerMatch[index + 1] || null
176
+ const partValue = headerMatch[index + 1] || null
173
177
  headerParts[partName] = partValue
174
178
  })
175
179
  } else {
@@ -186,7 +190,7 @@ function parser (raw, options, regex) {
186
190
  // body or footer
187
191
  _.forEach(lines, function (line) {
188
192
  if (options.fieldPattern) {
189
- var fieldMatch = options.fieldPattern.exec(line)
193
+ const fieldMatch = options.fieldPattern.exec(line)
190
194
 
191
195
  if (fieldMatch) {
192
196
  currentProcessedField = fieldMatch[1]
@@ -201,16 +205,16 @@ function parser (raw, options, regex) {
201
205
  }
202
206
  }
203
207
 
204
- var referenceMatched
208
+ let referenceMatched
205
209
 
206
210
  // this is a new important note
207
- var notesMatch = line.match(regex.notes)
211
+ const notesMatch = line.match(regex.notes)
208
212
  if (notesMatch) {
209
213
  continueNote = true
210
214
  isBody = false
211
215
  footer = append(footer, line)
212
216
 
213
- var note = {
217
+ const note = {
214
218
  title: notesMatch[1],
215
219
  text: notesMatch[2]
216
220
  }
@@ -220,7 +224,7 @@ function parser (raw, options, regex) {
220
224
  return
221
225
  }
222
226
 
223
- var lineReferences = getReferences(line, {
227
+ const lineReferences = getReferences(line, {
224
228
  references: regex.references,
225
229
  referenceParts: regex.referenceParts
226
230
  })
@@ -254,7 +258,7 @@ function parser (raw, options, regex) {
254
258
  })
255
259
 
256
260
  if (options.breakingHeaderPattern && notes.length === 0) {
257
- var breakingHeader = header.match(options.breakingHeaderPattern)
261
+ const breakingHeader = header.match(options.breakingHeaderPattern)
258
262
  if (breakingHeader) {
259
263
  const noteText = breakingHeader[3] // the description of the change.
260
264
  notes.push({
@@ -269,11 +273,11 @@ function parser (raw, options, regex) {
269
273
  }
270
274
 
271
275
  // does this commit revert any other commit?
272
- revertMatch = raw.match(options.revertPattern)
276
+ const revertMatch = raw.match(options.revertPattern)
273
277
  if (revertMatch) {
274
278
  revert = {}
275
279
  _.forEach(revertCorrespondence, function (partName, index) {
276
- var partValue = revertMatch[index + 1] || null
280
+ const partValue = revertMatch[index + 1] || null
277
281
  revert[partName] = partValue
278
282
  })
279
283
  } else {
@@ -286,7 +290,7 @@ function parser (raw, options, regex) {
286
290
  return note
287
291
  })
288
292
 
289
- var msg = _.merge(headerParts, mergeParts, {
293
+ const msg = _.merge(headerParts, mergeParts, {
290
294
  merge: merge,
291
295
  header: header,
292
296
  body: body ? trimOffNewlines(body) : null,
package/lib/regex.js CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict'
2
2
 
3
- var reNomatch = /(?!.*)/
3
+ const reNomatch = /(?!.*)/
4
4
 
5
5
  function join (array, joiner) {
6
6
  return array
@@ -13,20 +13,27 @@ function join (array, joiner) {
13
13
  .join(joiner)
14
14
  }
15
15
 
16
- function getNotesRegex (noteKeywords) {
16
+ function getNotesRegex (noteKeywords, notesPattern) {
17
17
  if (!noteKeywords) {
18
18
  return reNomatch
19
19
  }
20
20
 
21
- return new RegExp('^[\\s|*]*(' + join(noteKeywords, '|') + ')[:\\s]+(.*)', 'i')
21
+ const noteKeywordsSelection = join(noteKeywords, '|')
22
+
23
+ if (!notesPattern) {
24
+ return new RegExp('^[\\s|*]*(' + noteKeywordsSelection + ')[:\\s]+(.*)', 'i')
25
+ }
26
+
27
+ return notesPattern(noteKeywordsSelection)
22
28
  }
23
29
 
24
- function getReferencePartsRegex (issuePrefixes) {
30
+ function getReferencePartsRegex (issuePrefixes, issuePrefixesCaseSensitive) {
25
31
  if (!issuePrefixes) {
26
32
  return reNomatch
27
33
  }
28
34
 
29
- return new RegExp('(?:.*?)??\\s*([\\w-\\.\\/]*?)??(' + join(issuePrefixes, '|') + ')([\\w-]*\\d+)', 'gi')
35
+ const flags = issuePrefixesCaseSensitive ? 'g' : 'gi'
36
+ return new RegExp('(?:.*?)??\\s*([\\w-\\.\\/]*?)??(' + join(issuePrefixes, '|') + ')([\\w-]*\\d+)', flags)
30
37
  }
31
38
 
32
39
  function getReferencesRegex (referenceActions) {
@@ -35,15 +42,15 @@ function getReferencesRegex (referenceActions) {
35
42
  return /()(.+)/gi
36
43
  }
37
44
 
38
- var joinedKeywords = join(referenceActions, '|')
45
+ const joinedKeywords = join(referenceActions, '|')
39
46
  return new RegExp('(' + joinedKeywords + ')(?:\\s+(.*?))(?=(?:' + joinedKeywords + ')|$)', 'gi')
40
47
  }
41
48
 
42
49
  module.exports = function (options) {
43
50
  options = options || {}
44
- var reNotes = getNotesRegex(options.noteKeywords)
45
- var reReferenceParts = getReferencePartsRegex(options.issuePrefixes)
46
- var reReferences = getReferencesRegex(options.referenceActions)
51
+ const reNotes = getNotesRegex(options.noteKeywords, options.notesPattern)
52
+ const reReferenceParts = getReferencePartsRegex(options.issuePrefixes, options.issuePrefixesCaseSensitive)
53
+ const reReferences = getReferencesRegex(options.referenceActions)
47
54
 
48
55
  return {
49
56
  notes: reNotes,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "conventional-commits-parser",
3
- "version": "3.0.8",
3
+ "version": "3.2.2",
4
4
  "description": "Parse raw conventional commits",
5
5
  "bugs": {
6
6
  "url": "https://github.com/conventional-changelog/conventional-changelog/issues"
@@ -17,7 +17,7 @@
17
17
  },
18
18
  "license": "MIT",
19
19
  "engines": {
20
- "node": ">=6.9.0"
20
+ "node": ">=10"
21
21
  },
22
22
  "files": [
23
23
  "index.js",
@@ -33,13 +33,12 @@
33
33
  "logs"
34
34
  ],
35
35
  "dependencies": {
36
- "JSONStream": "^1.0.4",
37
36
  "is-text-path": "^1.0.1",
37
+ "JSONStream": "^1.0.4",
38
38
  "lodash": "^4.17.15",
39
- "meow": "^5.0.0",
40
- "split2": "^2.0.0",
41
- "through2": "^3.0.0",
42
- "trim-off-newlines": "^1.0.0"
39
+ "meow": "^8.0.0",
40
+ "split2": "^3.0.0",
41
+ "through2": "^4.0.0"
43
42
  },
44
43
  "scripts": {
45
44
  "test-windows": "echo 'make work on windows'"
@@ -47,5 +46,7 @@
47
46
  "bin": {
48
47
  "conventional-commits-parser": "cli.js"
49
48
  },
50
- "gitHead": "79217815a7ce5f3d3f833961ce9a14bd454e5789"
49
+ "devDependencies": {
50
+ "forceable-tty": "^0.1.0"
51
+ }
51
52
  }