@yagni-app/code 1.0.6 → 1.0.8

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.
Files changed (92) hide show
  1. package/README.md +91 -46
  2. package/dist/claudeCompat.d.ts +59 -0
  3. package/dist/claudeCompat.js +109 -2
  4. package/dist/claudePlugins.d.ts +45 -5
  5. package/dist/claudePlugins.js +129 -21
  6. package/dist/cli.js +16 -4
  7. package/dist/doctor.d.ts +21 -8
  8. package/dist/doctor.js +53 -28
  9. package/dist/extension/askUserQuestionTool.js +7 -2
  10. package/dist/extension/condensedTools.d.ts +12 -1
  11. package/dist/extension/condensedTools.js +17 -9
  12. package/dist/extension/config.d.ts +6 -0
  13. package/dist/extension/hooks.d.ts +3 -3
  14. package/dist/extension/hooks.js +30 -5
  15. package/dist/extension/index.d.ts +19 -0
  16. package/dist/extension/index.js +248 -43
  17. package/dist/extension/mcp/cliConfig.d.ts +1 -1
  18. package/dist/extension/mcp/cliConfig.js +1 -1
  19. package/dist/extension/mcp/config.d.ts +24 -2
  20. package/dist/extension/mcp/config.js +75 -3
  21. package/dist/extension/mcp/manager.d.ts +3 -1
  22. package/dist/extension/mcp/manager.js +2 -2
  23. package/dist/extension/mcp/panel.d.ts +0 -1
  24. package/dist/extension/mcp/panel.js +13 -3
  25. package/dist/extension/mcp/startup.js +8 -6
  26. package/dist/extension/permission/gate.d.ts +101 -2
  27. package/dist/extension/permission/gate.js +485 -42
  28. package/dist/extension/permissionRules/bashFileArgs.d.ts +39 -0
  29. package/dist/extension/permissionRules/bashFileArgs.js +236 -0
  30. package/dist/extension/permissionRules/engine.d.ts +50 -0
  31. package/dist/extension/permissionRules/engine.js +238 -0
  32. package/dist/extension/permissionRules/loadConfig.d.ts +64 -0
  33. package/dist/extension/permissionRules/loadConfig.js +105 -0
  34. package/dist/extension/permissionRules/parser.d.ts +38 -0
  35. package/dist/extension/permissionRules/parser.js +136 -0
  36. package/dist/extension/permissionRules/pathRules.d.ts +60 -0
  37. package/dist/extension/permissionRules/pathRules.js +122 -0
  38. package/dist/extension/permissionRules/shellRules.d.ts +52 -0
  39. package/dist/extension/permissionRules/shellRules.js +221 -0
  40. package/dist/extension/pipeline/invocation.d.ts +3 -6
  41. package/dist/extension/pipeline/invocation.js +3 -6
  42. package/dist/extension/pipeline/runner.d.ts +0 -1
  43. package/dist/extension/pipeline/runner.js +6 -14
  44. package/dist/extension/plugins/inventory.d.ts +88 -0
  45. package/dist/extension/plugins/inventory.js +144 -0
  46. package/dist/extension/plugins/panel.d.ts +45 -0
  47. package/dist/extension/plugins/panel.js +293 -0
  48. package/dist/extension/sandbox/bash.d.ts +99 -0
  49. package/dist/extension/sandbox/bash.js +190 -0
  50. package/dist/extension/sandbox/config.d.ts +115 -0
  51. package/dist/extension/sandbox/config.js +388 -0
  52. package/dist/extension/sandbox/manager.d.ts +108 -0
  53. package/dist/extension/sandbox/manager.js +243 -0
  54. package/dist/extension/sandbox/panel.d.ts +111 -0
  55. package/dist/extension/sandbox/panel.js +342 -0
  56. package/dist/extension/sandbox/session.d.ts +85 -0
  57. package/dist/extension/sandbox/session.js +829 -0
  58. package/dist/extension/settingsFiles.d.ts +50 -0
  59. package/dist/extension/settingsFiles.js +206 -0
  60. package/dist/extension/telemetry/attrs.d.ts +96 -0
  61. package/dist/extension/telemetry/attrs.js +149 -0
  62. package/dist/extension/telemetry/config.d.ts +103 -0
  63. package/dist/extension/telemetry/config.js +193 -0
  64. package/dist/extension/telemetry/index.d.ts +7 -0
  65. package/dist/extension/telemetry/index.js +7 -0
  66. package/dist/extension/telemetry/probe.d.ts +29 -0
  67. package/dist/extension/telemetry/probe.js +122 -0
  68. package/dist/extension/telemetry/register.d.ts +47 -0
  69. package/dist/extension/telemetry/register.js +207 -0
  70. package/dist/extension/telemetry/sdk.d.ts +63 -0
  71. package/dist/extension/telemetry/sdk.js +207 -0
  72. package/dist/extension/telemetry/tracker.d.ts +131 -0
  73. package/dist/extension/telemetry/tracker.js +551 -0
  74. package/dist/extension/vendor/IGNORE-LICENSE-MIT +21 -0
  75. package/dist/extension/vendor/ignore.d.ts +86 -0
  76. package/dist/extension/vendor/ignore.js +788 -0
  77. package/dist/goHeadless.d.ts +1 -1
  78. package/dist/goHeadless.js +2 -2
  79. package/dist/launch.d.ts +4 -3
  80. package/dist/launch.js +7 -4
  81. package/dist/mcpCommand.d.ts +10 -1
  82. package/dist/mcpCommand.js +42 -10
  83. package/dist/otel.d.ts +67 -90
  84. package/dist/otel.js +152 -195
  85. package/dist/paths.d.ts +13 -0
  86. package/dist/paths.js +18 -0
  87. package/dist/pluginCommand.d.ts +43 -0
  88. package/dist/pluginCommand.js +499 -0
  89. package/dist/pluginStore.d.ts +170 -0
  90. package/dist/pluginStore.js +554 -0
  91. package/dist/upgrade.js +10 -1
  92. package/package.json +19 -3
@@ -0,0 +1,788 @@
1
+ // VENDORED SOURCE — do not edit lightly. Copy of the npm `ignore` package
2
+ // v7.0.5 (https://github.com/kaelzhang/node-ignore), MIT license (see
3
+ // IGNORE-LICENSE-MIT alongside). Vendored as source because the bundled
4
+ // extension cannot resolve node_modules (dist is a file copy, not a real
5
+ // bundle). The only modification is the tail of this file: the CommonJS
6
+ // `module.exports` block was replaced by an ESM `export default` (the
7
+ // package is `"type": "module"`-incompatible as shipped).
8
+ //
9
+ // This is the same gitignore-pattern engine Claude Code uses for its
10
+ // Read/Edit permission rules, so pattern semantics stay byte-compatible.
11
+
12
+ // A simple implementation of make-array
13
+ function makeArray (subject) {
14
+ return Array.isArray(subject)
15
+ ? subject
16
+ : [subject]
17
+ }
18
+
19
+ const UNDEFINED = undefined
20
+ const EMPTY = ''
21
+ const SPACE = ' '
22
+ const ESCAPE = '\\'
23
+ const REGEX_TEST_BLANK_LINE = /^\s+$/
24
+ const REGEX_INVALID_TRAILING_BACKSLASH = /(?:[^\\]|^)\\$/
25
+ const REGEX_REPLACE_LEADING_EXCAPED_EXCLAMATION = /^\\!/
26
+ const REGEX_REPLACE_LEADING_EXCAPED_HASH = /^\\#/
27
+ const REGEX_SPLITALL_CRLF = /\r?\n/g
28
+
29
+ // Invalid:
30
+ // - /foo,
31
+ // - ./foo,
32
+ // - ../foo,
33
+ // - .
34
+ // - ..
35
+ // Valid:
36
+ // - .foo
37
+ const REGEX_TEST_INVALID_PATH = /^\.{0,2}\/|^\.{1,2}$/
38
+
39
+ const REGEX_TEST_TRAILING_SLASH = /\/$/
40
+
41
+ const SLASH = '/'
42
+
43
+ // Do not use ternary expression here, since "istanbul ignore next" is buggy
44
+ let TMP_KEY_IGNORE = 'node-ignore'
45
+ /* istanbul ignore else */
46
+ if (typeof Symbol !== 'undefined') {
47
+ TMP_KEY_IGNORE = Symbol.for('node-ignore')
48
+ }
49
+ const KEY_IGNORE = TMP_KEY_IGNORE
50
+
51
+ const define = (object, key, value) => {
52
+ Object.defineProperty(object, key, {value})
53
+ return value
54
+ }
55
+
56
+ const REGEX_REGEXP_RANGE = /([0-z])-([0-z])/g
57
+
58
+ const RETURN_FALSE = () => false
59
+
60
+ // Sanitize the range of a regular expression
61
+ // The cases are complicated, see test cases for details
62
+ const sanitizeRange = range => range.replace(
63
+ REGEX_REGEXP_RANGE,
64
+ (match, from, to) => from.charCodeAt(0) <= to.charCodeAt(0)
65
+ ? match
66
+ // Invalid range (out of order) which is ok for gitignore rules but
67
+ // fatal for JavaScript regular expression, so eliminate it.
68
+ : EMPTY
69
+ )
70
+
71
+ // See fixtures #59
72
+ const cleanRangeBackSlash = slashes => {
73
+ const {length} = slashes
74
+ return slashes.slice(0, length - length % 2)
75
+ }
76
+
77
+ // > If the pattern ends with a slash,
78
+ // > it is removed for the purpose of the following description,
79
+ // > but it would only find a match with a directory.
80
+ // > In other words, foo/ will match a directory foo and paths underneath it,
81
+ // > but will not match a regular file or a symbolic link foo
82
+ // > (this is consistent with the way how pathspec works in general in Git).
83
+ // '`foo/`' will not match regular file '`foo`' or symbolic link '`foo`'
84
+ // -> ignore-rules will not deal with it, because it costs extra `fs.stat` call
85
+ // you could use option `mark: true` with `glob`
86
+
87
+ // '`foo/`' should not continue with the '`..`'
88
+ const REPLACERS = [
89
+
90
+ [
91
+ // Remove BOM
92
+ // TODO:
93
+ // Other similar zero-width characters?
94
+ /^\uFEFF/,
95
+ () => EMPTY
96
+ ],
97
+
98
+ // > Trailing spaces are ignored unless they are quoted with backslash ("\")
99
+ [
100
+ // (a\ ) -> (a )
101
+ // (a ) -> (a)
102
+ // (a ) -> (a)
103
+ // (a \ ) -> (a )
104
+ /((?:\\\\)*?)(\\?\s+)$/,
105
+ (_, m1, m2) => m1 + (
106
+ m2.indexOf('\\') === 0
107
+ ? SPACE
108
+ : EMPTY
109
+ )
110
+ ],
111
+
112
+ // Replace (\ ) with ' '
113
+ // (\ ) -> ' '
114
+ // (\\ ) -> '\\ '
115
+ // (\\\ ) -> '\\ '
116
+ [
117
+ /(\\+?)\s/g,
118
+ (_, m1) => {
119
+ const {length} = m1
120
+ return m1.slice(0, length - length % 2) + SPACE
121
+ }
122
+ ],
123
+
124
+ // Escape metacharacters
125
+ // which is written down by users but means special for regular expressions.
126
+
127
+ // > There are 12 characters with special meanings:
128
+ // > - the backslash \,
129
+ // > - the caret ^,
130
+ // > - the dollar sign $,
131
+ // > - the period or dot .,
132
+ // > - the vertical bar or pipe symbol |,
133
+ // > - the question mark ?,
134
+ // > - the asterisk or star *,
135
+ // > - the plus sign +,
136
+ // > - the opening parenthesis (,
137
+ // > - the closing parenthesis ),
138
+ // > - and the opening square bracket [,
139
+ // > - the opening curly brace {,
140
+ // > These special characters are often called "metacharacters".
141
+ [
142
+ /[\\$.|*+(){^]/g,
143
+ match => `\\${match}`
144
+ ],
145
+
146
+ [
147
+ // > a question mark (?) matches a single character
148
+ /(?!\\)\?/g,
149
+ () => '[^/]'
150
+ ],
151
+
152
+ // leading slash
153
+ [
154
+
155
+ // > A leading slash matches the beginning of the pathname.
156
+ // > For example, "/*.c" matches "cat-file.c" but not "mozilla-sha1/sha1.c".
157
+ // A leading slash matches the beginning of the pathname
158
+ /^\//,
159
+ () => '^'
160
+ ],
161
+
162
+ // replace special metacharacter slash after the leading slash
163
+ [
164
+ /\//g,
165
+ () => '\\/'
166
+ ],
167
+
168
+ [
169
+ // > A leading "**" followed by a slash means match in all directories.
170
+ // > For example, "**/foo" matches file or directory "foo" anywhere,
171
+ // > the same as pattern "foo".
172
+ // > "**/foo/bar" matches file or directory "bar" anywhere that is directly
173
+ // > under directory "foo".
174
+ // Notice that the '*'s have been replaced as '\\*'
175
+ /^\^*\\\*\\\*\\\//,
176
+
177
+ // '**/foo' <-> 'foo'
178
+ () => '^(?:.*\\/)?'
179
+ ],
180
+
181
+ // starting
182
+ [
183
+ // there will be no leading '/'
184
+ // (which has been replaced by section "leading slash")
185
+ // If starts with '**', adding a '^' to the regular expression also works
186
+ /^(?=[^^])/,
187
+ function startingReplacer () {
188
+ // If has a slash `/` at the beginning or middle
189
+ return !/\/(?!$)/.test(this)
190
+ // > Prior to 2.22.1
191
+ // > If the pattern does not contain a slash /,
192
+ // > Git treats it as a shell glob pattern
193
+ // Actually, if there is only a trailing slash,
194
+ // git also treats it as a shell glob pattern
195
+
196
+ // After 2.22.1 (compatible but clearer)
197
+ // > If there is a separator at the beginning or middle (or both)
198
+ // > of the pattern, then the pattern is relative to the directory
199
+ // > level of the particular .gitignore file itself.
200
+ // > Otherwise the pattern may also match at any level below
201
+ // > the .gitignore level.
202
+ ? '(?:^|\\/)'
203
+
204
+ // > Otherwise, Git treats the pattern as a shell glob suitable for
205
+ // > consumption by fnmatch(3)
206
+ : '^'
207
+ }
208
+ ],
209
+
210
+ // two globstars
211
+ [
212
+ // Use lookahead assertions so that we could match more than one `'/**'`
213
+ /\\\/\\\*\\\*(?=\\\/|$)/g,
214
+
215
+ // Zero, one or several directories
216
+ // should not use '*', or it will be replaced by the next replacer
217
+
218
+ // Check if it is not the last `'/**'`
219
+ (_, index, str) => index + 6 < str.length
220
+
221
+ // case: /**/
222
+ // > A slash followed by two consecutive asterisks then a slash matches
223
+ // > zero or more directories.
224
+ // > For example, "a/**/b" matches "a/b", "a/x/b", "a/x/y/b" and so on.
225
+ // '/**/'
226
+ ? '(?:\\/[^\\/]+)*'
227
+
228
+ // case: /**
229
+ // > A trailing `"/**"` matches everything inside.
230
+
231
+ // #21: everything inside but it should not include the current folder
232
+ : '\\/.+'
233
+ ],
234
+
235
+ // normal intermediate wildcards
236
+ [
237
+ // Never replace escaped '*'
238
+ // ignore rule '\*' will match the path '*'
239
+
240
+ // 'abc.*/' -> go
241
+ // 'abc.*' -> skip this rule,
242
+ // coz trailing single wildcard will be handed by [trailing wildcard]
243
+ /(^|[^\\]+)(\\\*)+(?=.+)/g,
244
+
245
+ // '*.js' matches '.js'
246
+ // '*.js' doesn't match 'abc'
247
+ (_, p1, p2) => {
248
+ // 1.
249
+ // > An asterisk "*" matches anything except a slash.
250
+ // 2.
251
+ // > Other consecutive asterisks are considered regular asterisks
252
+ // > and will match according to the previous rules.
253
+ const unescaped = p2.replace(/\\\*/g, '[^\\/]*')
254
+ return p1 + unescaped
255
+ }
256
+ ],
257
+
258
+ [
259
+ // unescape, revert step 3 except for back slash
260
+ // For example, if a user escape a '\\*',
261
+ // after step 3, the result will be '\\\\\\*'
262
+ /\\\\\\(?=[$.|*+(){^])/g,
263
+ () => ESCAPE
264
+ ],
265
+
266
+ [
267
+ // '\\\\' -> '\\'
268
+ /\\\\/g,
269
+ () => ESCAPE
270
+ ],
271
+
272
+ [
273
+ // > The range notation, e.g. [a-zA-Z],
274
+ // > can be used to match one of the characters in a range.
275
+
276
+ // `\` is escaped by step 3
277
+ /(\\)?\[([^\]/]*?)(\\*)($|\])/g,
278
+ (match, leadEscape, range, endEscape, close) => leadEscape === ESCAPE
279
+ // '\\[bar]' -> '\\\\[bar\\]'
280
+ ? `\\[${range}${cleanRangeBackSlash(endEscape)}${close}`
281
+ : close === ']'
282
+ ? endEscape.length % 2 === 0
283
+ // A normal case, and it is a range notation
284
+ // '[bar]'
285
+ // '[bar\\\\]'
286
+ ? `[${sanitizeRange(range)}${endEscape}]`
287
+ // Invalid range notaton
288
+ // '[bar\\]' -> '[bar\\\\]'
289
+ : '[]'
290
+ : '[]'
291
+ ],
292
+
293
+ // ending
294
+ [
295
+ // 'js' will not match 'js.'
296
+ // 'ab' will not match 'abc'
297
+ /(?:[^*])$/,
298
+
299
+ // WTF!
300
+ // https://git-scm.com/docs/gitignore
301
+ // changes in [2.22.1](https://git-scm.com/docs/gitignore/2.22.1)
302
+ // which re-fixes #24, #38
303
+
304
+ // > If there is a separator at the end of the pattern then the pattern
305
+ // > will only match directories, otherwise the pattern can match both
306
+ // > files and directories.
307
+
308
+ // 'js*' will not match 'a.js'
309
+ // 'js/' will not match 'a.js'
310
+ // 'js' will match 'a.js' and 'a.js/'
311
+ match => /\/$/.test(match)
312
+ // foo/ will not match 'foo'
313
+ ? `${match}$`
314
+ // foo matches 'foo' and 'foo/'
315
+ : `${match}(?=$|\\/$)`
316
+ ]
317
+ ]
318
+
319
+ const REGEX_REPLACE_TRAILING_WILDCARD = /(^|\\\/)?\\\*$/
320
+ const MODE_IGNORE = 'regex'
321
+ const MODE_CHECK_IGNORE = 'checkRegex'
322
+ const UNDERSCORE = '_'
323
+
324
+ const TRAILING_WILD_CARD_REPLACERS = {
325
+ [MODE_IGNORE] (_, p1) {
326
+ const prefix = p1
327
+ // '\^':
328
+ // '/*' does not match EMPTY
329
+ // '/*' does not match everything
330
+
331
+ // '\\\/':
332
+ // 'abc/*' does not match 'abc/'
333
+ ? `${p1}[^/]+`
334
+
335
+ // 'a*' matches 'a'
336
+ // 'a*' matches 'aa'
337
+ : '[^/]*'
338
+
339
+ return `${prefix}(?=$|\\/$)`
340
+ },
341
+
342
+ [MODE_CHECK_IGNORE] (_, p1) {
343
+ // When doing `git check-ignore`
344
+ const prefix = p1
345
+ // '\\\/':
346
+ // 'abc/*' DOES match 'abc/' !
347
+ ? `${p1}[^/]*`
348
+
349
+ // 'a*' matches 'a'
350
+ // 'a*' matches 'aa'
351
+ : '[^/]*'
352
+
353
+ return `${prefix}(?=$|\\/$)`
354
+ }
355
+ }
356
+
357
+ // @param {pattern}
358
+ const makeRegexPrefix = pattern => REPLACERS.reduce(
359
+ (prev, [matcher, replacer]) =>
360
+ prev.replace(matcher, replacer.bind(pattern)),
361
+ pattern
362
+ )
363
+
364
+ const isString = subject => typeof subject === 'string'
365
+
366
+ // > A blank line matches no files, so it can serve as a separator for readability.
367
+ const checkPattern = pattern => pattern
368
+ && isString(pattern)
369
+ && !REGEX_TEST_BLANK_LINE.test(pattern)
370
+ && !REGEX_INVALID_TRAILING_BACKSLASH.test(pattern)
371
+
372
+ // > A line starting with # serves as a comment.
373
+ && pattern.indexOf('#') !== 0
374
+
375
+ const splitPattern = pattern => pattern
376
+ .split(REGEX_SPLITALL_CRLF)
377
+ .filter(Boolean)
378
+
379
+ class IgnoreRule {
380
+ constructor (
381
+ pattern,
382
+ mark,
383
+ body,
384
+ ignoreCase,
385
+ negative,
386
+ prefix
387
+ ) {
388
+ this.pattern = pattern
389
+ this.mark = mark
390
+ this.negative = negative
391
+
392
+ define(this, 'body', body)
393
+ define(this, 'ignoreCase', ignoreCase)
394
+ define(this, 'regexPrefix', prefix)
395
+ }
396
+
397
+ get regex () {
398
+ const key = UNDERSCORE + MODE_IGNORE
399
+
400
+ if (this[key]) {
401
+ return this[key]
402
+ }
403
+
404
+ return this._make(MODE_IGNORE, key)
405
+ }
406
+
407
+ get checkRegex () {
408
+ const key = UNDERSCORE + MODE_CHECK_IGNORE
409
+
410
+ if (this[key]) {
411
+ return this[key]
412
+ }
413
+
414
+ return this._make(MODE_CHECK_IGNORE, key)
415
+ }
416
+
417
+ _make (mode, key) {
418
+ const str = this.regexPrefix.replace(
419
+ REGEX_REPLACE_TRAILING_WILDCARD,
420
+
421
+ // It does not need to bind pattern
422
+ TRAILING_WILD_CARD_REPLACERS[mode]
423
+ )
424
+
425
+ const regex = this.ignoreCase
426
+ ? new RegExp(str, 'i')
427
+ : new RegExp(str)
428
+
429
+ return define(this, key, regex)
430
+ }
431
+ }
432
+
433
+ const createRule = ({
434
+ pattern,
435
+ mark
436
+ }, ignoreCase) => {
437
+ let negative = false
438
+ let body = pattern
439
+
440
+ // > An optional prefix "!" which negates the pattern;
441
+ if (body.indexOf('!') === 0) {
442
+ negative = true
443
+ body = body.substr(1)
444
+ }
445
+
446
+ body = body
447
+ // > Put a backslash ("\") in front of the first "!" for patterns that
448
+ // > begin with a literal "!", for example, `"\!important!.txt"`.
449
+ .replace(REGEX_REPLACE_LEADING_EXCAPED_EXCLAMATION, '!')
450
+ // > Put a backslash ("\") in front of the first hash for patterns that
451
+ // > begin with a hash.
452
+ .replace(REGEX_REPLACE_LEADING_EXCAPED_HASH, '#')
453
+
454
+ const regexPrefix = makeRegexPrefix(body)
455
+
456
+ return new IgnoreRule(
457
+ pattern,
458
+ mark,
459
+ body,
460
+ ignoreCase,
461
+ negative,
462
+ regexPrefix
463
+ )
464
+ }
465
+
466
+ class RuleManager {
467
+ constructor (ignoreCase) {
468
+ this._ignoreCase = ignoreCase
469
+ this._rules = []
470
+ }
471
+
472
+ _add (pattern) {
473
+ // #32
474
+ if (pattern && pattern[KEY_IGNORE]) {
475
+ this._rules = this._rules.concat(pattern._rules._rules)
476
+ this._added = true
477
+ return
478
+ }
479
+
480
+ if (isString(pattern)) {
481
+ pattern = {
482
+ pattern
483
+ }
484
+ }
485
+
486
+ if (checkPattern(pattern.pattern)) {
487
+ const rule = createRule(pattern, this._ignoreCase)
488
+ this._added = true
489
+ this._rules.push(rule)
490
+ }
491
+ }
492
+
493
+ // @param {Array<string> | string | Ignore} pattern
494
+ add (pattern) {
495
+ this._added = false
496
+
497
+ makeArray(
498
+ isString(pattern)
499
+ ? splitPattern(pattern)
500
+ : pattern
501
+ ).forEach(this._add, this)
502
+
503
+ return this._added
504
+ }
505
+
506
+ // Test one single path without recursively checking parent directories
507
+ //
508
+ // - checkUnignored `boolean` whether should check if the path is unignored,
509
+ // setting `checkUnignored` to `false` could reduce additional
510
+ // path matching.
511
+ // - check `string` either `MODE_IGNORE` or `MODE_CHECK_IGNORE`
512
+
513
+ // @returns {TestResult} true if a file is ignored
514
+ test (path, checkUnignored, mode) {
515
+ let ignored = false
516
+ let unignored = false
517
+ let matchedRule
518
+
519
+ this._rules.forEach(rule => {
520
+ const {negative} = rule
521
+
522
+ // | ignored : unignored
523
+ // -------- | ---------------------------------------
524
+ // negative | 0:0 | 0:1 | 1:0 | 1:1
525
+ // -------- | ------- | ------- | ------- | --------
526
+ // 0 | TEST | TEST | SKIP | X
527
+ // 1 | TESTIF | SKIP | TEST | X
528
+
529
+ // - SKIP: always skip
530
+ // - TEST: always test
531
+ // - TESTIF: only test if checkUnignored
532
+ // - X: that never happen
533
+ if (
534
+ unignored === negative && ignored !== unignored
535
+ || negative && !ignored && !unignored && !checkUnignored
536
+ ) {
537
+ return
538
+ }
539
+
540
+ const matched = rule[mode].test(path)
541
+
542
+ if (!matched) {
543
+ return
544
+ }
545
+
546
+ ignored = !negative
547
+ unignored = negative
548
+
549
+ matchedRule = negative
550
+ ? UNDEFINED
551
+ : rule
552
+ })
553
+
554
+ const ret = {
555
+ ignored,
556
+ unignored
557
+ }
558
+
559
+ if (matchedRule) {
560
+ ret.rule = matchedRule
561
+ }
562
+
563
+ return ret
564
+ }
565
+ }
566
+
567
+ const throwError = (message, Ctor) => {
568
+ throw new Ctor(message)
569
+ }
570
+
571
+ const checkPath = (path, originalPath, doThrow) => {
572
+ if (!isString(path)) {
573
+ return doThrow(
574
+ `path must be a string, but got \`${originalPath}\``,
575
+ TypeError
576
+ )
577
+ }
578
+
579
+ // We don't know if we should ignore EMPTY, so throw
580
+ if (!path) {
581
+ return doThrow(`path must not be empty`, TypeError)
582
+ }
583
+
584
+ // Check if it is a relative path
585
+ if (checkPath.isNotRelative(path)) {
586
+ const r = '`path.relative()`d'
587
+ return doThrow(
588
+ `path should be a ${r} string, but got "${originalPath}"`,
589
+ RangeError
590
+ )
591
+ }
592
+
593
+ return true
594
+ }
595
+
596
+ const isNotRelative = path => REGEX_TEST_INVALID_PATH.test(path)
597
+
598
+ checkPath.isNotRelative = isNotRelative
599
+
600
+ // On windows, the following function will be replaced
601
+ /* istanbul ignore next */
602
+ checkPath.convert = p => p
603
+
604
+
605
+ class Ignore {
606
+ constructor ({
607
+ ignorecase = true,
608
+ ignoreCase = ignorecase,
609
+ allowRelativePaths = false
610
+ } = {}) {
611
+ define(this, KEY_IGNORE, true)
612
+
613
+ this._rules = new RuleManager(ignoreCase)
614
+ this._strictPathCheck = !allowRelativePaths
615
+ this._initCache()
616
+ }
617
+
618
+ _initCache () {
619
+ // A cache for the result of `.ignores()`
620
+ this._ignoreCache = Object.create(null)
621
+
622
+ // A cache for the result of `.test()`
623
+ this._testCache = Object.create(null)
624
+ }
625
+
626
+ add (pattern) {
627
+ if (this._rules.add(pattern)) {
628
+ // Some rules have just added to the ignore,
629
+ // making the behavior changed,
630
+ // so we need to re-initialize the result cache
631
+ this._initCache()
632
+ }
633
+
634
+ return this
635
+ }
636
+
637
+ // legacy
638
+ addPattern (pattern) {
639
+ return this.add(pattern)
640
+ }
641
+
642
+ // @returns {TestResult}
643
+ _test (originalPath, cache, checkUnignored, slices) {
644
+ const path = originalPath
645
+ // Supports nullable path
646
+ && checkPath.convert(originalPath)
647
+
648
+ checkPath(
649
+ path,
650
+ originalPath,
651
+ this._strictPathCheck
652
+ ? throwError
653
+ : RETURN_FALSE
654
+ )
655
+
656
+ return this._t(path, cache, checkUnignored, slices)
657
+ }
658
+
659
+ checkIgnore (path) {
660
+ // If the path doest not end with a slash, `.ignores()` is much equivalent
661
+ // to `git check-ignore`
662
+ if (!REGEX_TEST_TRAILING_SLASH.test(path)) {
663
+ return this.test(path)
664
+ }
665
+
666
+ const slices = path.split(SLASH).filter(Boolean)
667
+ slices.pop()
668
+
669
+ if (slices.length) {
670
+ const parent = this._t(
671
+ slices.join(SLASH) + SLASH,
672
+ this._testCache,
673
+ true,
674
+ slices
675
+ )
676
+
677
+ if (parent.ignored) {
678
+ return parent
679
+ }
680
+ }
681
+
682
+ return this._rules.test(path, false, MODE_CHECK_IGNORE)
683
+ }
684
+
685
+ _t (
686
+ // The path to be tested
687
+ path,
688
+
689
+ // The cache for the result of a certain checking
690
+ cache,
691
+
692
+ // Whether should check if the path is unignored
693
+ checkUnignored,
694
+
695
+ // The path slices
696
+ slices
697
+ ) {
698
+ if (path in cache) {
699
+ return cache[path]
700
+ }
701
+
702
+ if (!slices) {
703
+ // path/to/a.js
704
+ // ['path', 'to', 'a.js']
705
+ slices = path.split(SLASH).filter(Boolean)
706
+ }
707
+
708
+ slices.pop()
709
+
710
+ // If the path has no parent directory, just test it
711
+ if (!slices.length) {
712
+ return cache[path] = this._rules.test(path, checkUnignored, MODE_IGNORE)
713
+ }
714
+
715
+ const parent = this._t(
716
+ slices.join(SLASH) + SLASH,
717
+ cache,
718
+ checkUnignored,
719
+ slices
720
+ )
721
+
722
+ // If the path contains a parent directory, check the parent first
723
+ return cache[path] = parent.ignored
724
+ // > It is not possible to re-include a file if a parent directory of
725
+ // > that file is excluded.
726
+ ? parent
727
+ : this._rules.test(path, checkUnignored, MODE_IGNORE)
728
+ }
729
+
730
+ ignores (path) {
731
+ return this._test(path, this._ignoreCache, false).ignored
732
+ }
733
+
734
+ createFilter () {
735
+ return path => !this.ignores(path)
736
+ }
737
+
738
+ filter (paths) {
739
+ return makeArray(paths).filter(this.createFilter())
740
+ }
741
+
742
+ // @returns {TestResult}
743
+ test (path) {
744
+ return this._test(path, this._testCache, true)
745
+ }
746
+ }
747
+
748
+ const factory = options => new Ignore(options)
749
+
750
+ const isPathValid = path =>
751
+ checkPath(path && checkPath.convert(path), path, RETURN_FALSE)
752
+
753
+ /* istanbul ignore next */
754
+ const setupWindows = () => {
755
+ /* eslint no-control-regex: "off" */
756
+ const makePosix = str => /^\\\\\?\\/.test(str)
757
+ || /["<>|\u0000-\u001F]+/u.test(str)
758
+ ? str
759
+ : str.replace(/\\/g, '/')
760
+
761
+ checkPath.convert = makePosix
762
+
763
+ // 'C:\\foo' <- 'C:\\foo' has been converted to 'C:/'
764
+ // 'd:\\foo'
765
+ const REGEX_TEST_WINDOWS_PATH_ABSOLUTE = /^[a-z]:\//i
766
+ checkPath.isNotRelative = path =>
767
+ REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path)
768
+ || isNotRelative(path)
769
+ }
770
+
771
+
772
+ // Windows
773
+ // --------------------------------------------------------------
774
+ /* istanbul ignore next */
775
+ if (
776
+ // Detect `process` so that it can run in browsers.
777
+ typeof process !== 'undefined'
778
+ && process.platform === 'win32'
779
+ ) {
780
+ setupWindows()
781
+ }
782
+
783
+ // COMMONJS_EXPORTS → ESM (the only vendored-code modification) ////////////
784
+ // The upstream module ends with `module.exports = factory` (CJS). This
785
+ // package is ESM ("type": "module"), so re-export the factory directly.
786
+
787
+ export { isPathValid }
788
+ export default factory