@mnemonik/cli 7.154.3 → 7.156.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/dist/auth/credentials.js +35 -22
- package/dist/auth/index.js +35 -22
- package/dist/auth/installSession.js +21 -10
- package/dist/auth/status.js +21 -10
- package/dist/authorizationWords.js +21 -10
- package/dist/diagnostics.js +35 -22
- package/dist/digests.json +47 -43
- package/dist/hooksLog.js +3 -1
- package/dist/identity/migrate.js +35 -22
- package/dist/install/hosts.js +21 -10
- package/dist/install/journal.js +35 -22
- package/dist/install/journey.d.ts.map +1 -1
- package/dist/install/journey.js +42 -22
- package/dist/install/journey.js.map +1 -1
- package/dist/install/ownership.js +35 -22
- package/dist/install/transaction.js +35 -22
- package/dist/install/ui.js +35 -22
- package/dist/installSession.js +21 -10
- package/dist/installation.js +35 -22
- package/dist/launcher.js +35 -22
- package/dist/preflight.js +23 -10
- package/dist/project/eligibility.js +35 -22
- package/dist/project/records.js +35 -22
- package/dist/project.js +35 -22
- package/dist/router.js +35 -22
- package/dist/runtime/releaseSource.js +35 -22
- package/dist/runtime/selfUpdate.js +35 -22
- package/dist/runtime/signers.js +97 -1
- package/dist/runtime/store.js +3 -1
- package/dist/runtime/updateCheck.js +35 -22
- package/dist/sbom.json +21 -21
- package/dist/scanner/control.js +35 -22
- package/dist/scanner/data.js +21 -10
- package/dist/scanner/discover.js +23 -10
- package/dist/scanner/enable.d.ts +5 -0
- package/dist/scanner/enable.d.ts.map +1 -1
- package/dist/scanner/enable.js +52 -22
- package/dist/scanner/enable.js.map +1 -1
- package/dist/scanner/estimate.d.ts +41 -0
- package/dist/scanner/estimate.d.ts.map +1 -0
- package/dist/scanner/estimate.js +2461 -0
- package/dist/scanner/estimate.js.map +1 -0
- package/dist/scanner/picker.js +23 -10
- package/dist/scanner/service.js +35 -22
- package/dist/scanner/update.js +35 -22
- package/dist/scanner-release.json +24 -24
- package/dist/status.js +35 -22
- package/dist/vendor/local-setup.js +35 -22
- package/package.json +31 -31
|
@@ -0,0 +1,2461 @@
|
|
|
1
|
+
import { createRequire as __createRequire } from "node:module"; const require = __createRequire(import.meta.url);
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __esm = (fn, res, err) => function __init() {
|
|
9
|
+
if (err) throw err[0];
|
|
10
|
+
try {
|
|
11
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
12
|
+
} catch (e) {
|
|
13
|
+
throw err = [e], e;
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
var __commonJS = (cb, mod) => function __require() {
|
|
17
|
+
try {
|
|
18
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
19
|
+
} catch (e) {
|
|
20
|
+
throw mod = 0, e;
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
var __export = (target, all) => {
|
|
24
|
+
for (var name in all)
|
|
25
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
26
|
+
};
|
|
27
|
+
var __copyProps = (to, from, except, desc) => {
|
|
28
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
29
|
+
for (let key of __getOwnPropNames(from))
|
|
30
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
31
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
32
|
+
}
|
|
33
|
+
return to;
|
|
34
|
+
};
|
|
35
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
36
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
37
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
38
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
39
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
40
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
41
|
+
mod
|
|
42
|
+
));
|
|
43
|
+
|
|
44
|
+
// node_modules/ignore/index.js
|
|
45
|
+
var require_ignore = __commonJS({
|
|
46
|
+
"node_modules/ignore/index.js"(exports, module) {
|
|
47
|
+
function makeArray(subject) {
|
|
48
|
+
return Array.isArray(subject) ? subject : [subject];
|
|
49
|
+
}
|
|
50
|
+
var UNDEFINED = void 0;
|
|
51
|
+
var EMPTY = "";
|
|
52
|
+
var SPACE = " ";
|
|
53
|
+
var ESCAPE = "\\";
|
|
54
|
+
var REGEX_LITERAL_SPECIAL = /[.*+?()[\]{}^$|\\/]/;
|
|
55
|
+
var BOM = "\uFEFF";
|
|
56
|
+
var REGEX_INVALID_TRAILING_BACKSLASH = /(?:[^\\]|^)\\$/;
|
|
57
|
+
var REGEX_REPLACE_LEADING_EXCAPED_EXCLAMATION = /^\\!/;
|
|
58
|
+
var REGEX_REPLACE_LEADING_EXCAPED_HASH = /^\\#/;
|
|
59
|
+
var REGEX_SPLITALL_CRLF = /\r?\n/g;
|
|
60
|
+
var DOUBLE_SLASH = "//";
|
|
61
|
+
var SLASH_CODE = 47;
|
|
62
|
+
var DOT_CODE = 46;
|
|
63
|
+
var SLASH = "/";
|
|
64
|
+
var TMP_KEY_IGNORE = "node-ignore";
|
|
65
|
+
if (typeof Symbol !== "undefined") {
|
|
66
|
+
TMP_KEY_IGNORE = /* @__PURE__ */ Symbol.for("node-ignore");
|
|
67
|
+
}
|
|
68
|
+
var KEY_IGNORE = TMP_KEY_IGNORE;
|
|
69
|
+
var define = (object, key, value) => {
|
|
70
|
+
Object.defineProperty(object, key, { value });
|
|
71
|
+
return value;
|
|
72
|
+
};
|
|
73
|
+
var RETURN_FALSE = () => false;
|
|
74
|
+
var cleanRangeBackSlash = (slashes) => {
|
|
75
|
+
const { length } = slashes;
|
|
76
|
+
return slashes.slice(0, length - length % 2);
|
|
77
|
+
};
|
|
78
|
+
var POSIX_CLASSES = {
|
|
79
|
+
alnum: "0-9A-Za-z",
|
|
80
|
+
alpha: "A-Za-z",
|
|
81
|
+
blank: " \\t",
|
|
82
|
+
cntrl: "\\x00-\\x1f\\x7f",
|
|
83
|
+
digit: "0-9",
|
|
84
|
+
graph: "!-.0-~",
|
|
85
|
+
lower: "a-z",
|
|
86
|
+
print: " -.0-~",
|
|
87
|
+
punct: "!-.:-@\\[-`{-~",
|
|
88
|
+
// git's `sane-ctype.h` classifies \v and \f as control, not space,
|
|
89
|
+
// unlike C's `isspace`
|
|
90
|
+
space: " \\t\\n\\r",
|
|
91
|
+
upper: "A-Z",
|
|
92
|
+
xdigit: "0-9A-Fa-f"
|
|
93
|
+
};
|
|
94
|
+
var CLASS_MEMBERS_TO_ESCAPE = "\\]^-[";
|
|
95
|
+
var escapeMember = (char) => CLASS_MEMBERS_TO_ESCAPE.indexOf(char) < 0 ? char : ESCAPE + char;
|
|
96
|
+
var NON_SLASH = "(?!\\/)";
|
|
97
|
+
var classSource = (negated, body) => {
|
|
98
|
+
if (negated) {
|
|
99
|
+
return `[^\\/${body}]`;
|
|
100
|
+
}
|
|
101
|
+
const source = `[${body}]`;
|
|
102
|
+
return new RegExp(source).test("/") ? NON_SLASH + source : source;
|
|
103
|
+
};
|
|
104
|
+
var scanBracket = (pattern, start) => {
|
|
105
|
+
const { length } = pattern;
|
|
106
|
+
let index = start + 1;
|
|
107
|
+
let negated = EMPTY;
|
|
108
|
+
const lead = pattern[index];
|
|
109
|
+
if (lead === "!" || lead === "^") {
|
|
110
|
+
negated = "^";
|
|
111
|
+
index++;
|
|
112
|
+
}
|
|
113
|
+
let body = EMPTY;
|
|
114
|
+
let prev = EMPTY;
|
|
115
|
+
for (; ; ) {
|
|
116
|
+
const char = pattern[index];
|
|
117
|
+
if (char === UNDEFINED) {
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
if (char === ESCAPE) {
|
|
121
|
+
const escaped = pattern[index + 1];
|
|
122
|
+
if (escaped === UNDEFINED) {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
body += escapeMember(escaped);
|
|
126
|
+
prev = escaped;
|
|
127
|
+
index++;
|
|
128
|
+
} else if (char === "-" && prev && index + 1 < length && pattern[index + 1] !== "]") {
|
|
129
|
+
index++;
|
|
130
|
+
let to = pattern[index];
|
|
131
|
+
if (to === ESCAPE) {
|
|
132
|
+
to = pattern[index += 1];
|
|
133
|
+
}
|
|
134
|
+
if (prev <= to) {
|
|
135
|
+
body += `-${escapeMember(to)}`;
|
|
136
|
+
}
|
|
137
|
+
prev = EMPTY;
|
|
138
|
+
} else if (char === "[" && pattern[index + 1] === ":") {
|
|
139
|
+
const nameStart = index + 2;
|
|
140
|
+
let end = nameStart;
|
|
141
|
+
while (end < length && pattern[end] !== "]") {
|
|
142
|
+
end++;
|
|
143
|
+
}
|
|
144
|
+
if (end === length) {
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
if (end > nameStart && pattern[end - 1] === ":") {
|
|
148
|
+
const expanded = POSIX_CLASSES[pattern.slice(nameStart, end - 1)];
|
|
149
|
+
if (expanded === UNDEFINED) {
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
body += expanded;
|
|
153
|
+
prev = EMPTY;
|
|
154
|
+
index = end;
|
|
155
|
+
} else {
|
|
156
|
+
body += escapeMember("[");
|
|
157
|
+
prev = "[";
|
|
158
|
+
index = nameStart - 2;
|
|
159
|
+
}
|
|
160
|
+
} else {
|
|
161
|
+
body += escapeMember(char);
|
|
162
|
+
prev = char;
|
|
163
|
+
}
|
|
164
|
+
index++;
|
|
165
|
+
if (pattern[index] === "]") {
|
|
166
|
+
return {
|
|
167
|
+
end: index,
|
|
168
|
+
source: classSource(negated, body)
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
var NEVER_MATCH = "[]";
|
|
174
|
+
var PLACEHOLDER = "\0";
|
|
175
|
+
var REGEX_RESTORE_PLACEHOLDER = new RegExp(
|
|
176
|
+
`${PLACEHOLDER}(\\d+)${PLACEHOLDER}`,
|
|
177
|
+
"g"
|
|
178
|
+
);
|
|
179
|
+
var TRAILING_WILDCARD = "\uE000";
|
|
180
|
+
var TRAILING_DOUBLESTAR = "\uE001";
|
|
181
|
+
var extractBrackets = (pattern) => {
|
|
182
|
+
const sources = [];
|
|
183
|
+
const hold = (source) => `${PLACEHOLDER}${sources.push(source) - 1}${PLACEHOLDER}`;
|
|
184
|
+
const { length } = pattern;
|
|
185
|
+
let out = EMPTY;
|
|
186
|
+
let index = 0;
|
|
187
|
+
while (index < length) {
|
|
188
|
+
const char = pattern[index];
|
|
189
|
+
if (char === ESCAPE) {
|
|
190
|
+
const escaped = pattern[index + 1];
|
|
191
|
+
if (escaped === UNDEFINED || escaped === SLASH && index + 2 === length) {
|
|
192
|
+
out += hold(NEVER_MATCH);
|
|
193
|
+
} else if (escaped === "*" || escaped === "[" || escaped === SPACE || escaped === ESCAPE) {
|
|
194
|
+
out += pattern.slice(index, index + 2);
|
|
195
|
+
} else {
|
|
196
|
+
out += hold(
|
|
197
|
+
REGEX_LITERAL_SPECIAL.test(escaped) ? ESCAPE + escaped : escaped
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
index += 2;
|
|
201
|
+
} else if (char === PLACEHOLDER) {
|
|
202
|
+
out += hold(`[${PLACEHOLDER}]`);
|
|
203
|
+
index++;
|
|
204
|
+
} else if (char === "[") {
|
|
205
|
+
const scanned = scanBracket(pattern, index);
|
|
206
|
+
if (scanned === null) {
|
|
207
|
+
out += hold(NEVER_MATCH);
|
|
208
|
+
index = length;
|
|
209
|
+
} else {
|
|
210
|
+
out += hold(scanned.source);
|
|
211
|
+
index = scanned.end + 1;
|
|
212
|
+
}
|
|
213
|
+
} else {
|
|
214
|
+
out += char;
|
|
215
|
+
index++;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return {
|
|
219
|
+
source: out,
|
|
220
|
+
sources
|
|
221
|
+
};
|
|
222
|
+
};
|
|
223
|
+
var DIRECT = null;
|
|
224
|
+
var REGEX_INNER_SLASH = /\/(?!$)/;
|
|
225
|
+
var REPLACERS = [
|
|
226
|
+
// Replace (\ ) with ' '
|
|
227
|
+
// Only a space: an escaped tab or other whitespace is already a literal by
|
|
228
|
+
// the time it reaches here, and a bare tab must be left as one, not turned
|
|
229
|
+
// into a space.
|
|
230
|
+
// (\ ) -> ' '
|
|
231
|
+
// (\\ ) -> '\\ '
|
|
232
|
+
// (\\\ ) -> '\\ '
|
|
233
|
+
[
|
|
234
|
+
// A run of backslashes is taken whole, from its first one, so no match
|
|
235
|
+
// ever starts again inside it -- `(\\+?) ` did, which made a long run
|
|
236
|
+
// quadratic.
|
|
237
|
+
/(\\+)( ?)/g,
|
|
238
|
+
(_, run, space) => space ? run.slice(0, run.length - run.length % 2) + SPACE : run,
|
|
239
|
+
ESCAPE + SPACE
|
|
240
|
+
],
|
|
241
|
+
// Escape metacharacters
|
|
242
|
+
// which is written down by users but means special for regular expressions.
|
|
243
|
+
// > There are 12 characters with special meanings:
|
|
244
|
+
// > - the backslash \,
|
|
245
|
+
// > - the caret ^,
|
|
246
|
+
// > - the dollar sign $,
|
|
247
|
+
// > - the period or dot .,
|
|
248
|
+
// > - the vertical bar or pipe symbol |,
|
|
249
|
+
// > - the question mark ?,
|
|
250
|
+
// > - the asterisk or star *,
|
|
251
|
+
// > - the plus sign +,
|
|
252
|
+
// > - the opening parenthesis (,
|
|
253
|
+
// > - the closing parenthesis ),
|
|
254
|
+
// > - and the opening square bracket [,
|
|
255
|
+
// > - the opening curly brace {,
|
|
256
|
+
// > These special characters are often called "metacharacters".
|
|
257
|
+
[
|
|
258
|
+
/[\\$.|*+(){^]/g,
|
|
259
|
+
(match) => `\\${match}`
|
|
260
|
+
],
|
|
261
|
+
[
|
|
262
|
+
// > a question mark (?) matches a single character
|
|
263
|
+
/(?!\\)\?/g,
|
|
264
|
+
() => "[^/]",
|
|
265
|
+
"?"
|
|
266
|
+
],
|
|
267
|
+
// leading slash
|
|
268
|
+
[
|
|
269
|
+
// > A leading slash matches the beginning of the pathname.
|
|
270
|
+
// > For example, "/*.c" matches "cat-file.c" but not "mozilla-sha1/sha1.c".
|
|
271
|
+
// A leading slash matches the beginning of the pathname
|
|
272
|
+
/^\//,
|
|
273
|
+
() => "^",
|
|
274
|
+
SLASH
|
|
275
|
+
],
|
|
276
|
+
// replace special metacharacter slash after the leading slash
|
|
277
|
+
[
|
|
278
|
+
/\//g,
|
|
279
|
+
() => "\\/",
|
|
280
|
+
SLASH
|
|
281
|
+
],
|
|
282
|
+
[
|
|
283
|
+
// > A leading "**" followed by a slash means match in all directories.
|
|
284
|
+
// > For example, "**/foo" matches file or directory "foo" anywhere,
|
|
285
|
+
// > the same as pattern "foo".
|
|
286
|
+
// > "**/foo/bar" matches file or directory "bar" anywhere that is directly
|
|
287
|
+
// > under directory "foo".
|
|
288
|
+
// Notice that the '*'s have been replaced as '\\*'
|
|
289
|
+
/^\^*(?:\\\*\\\*\\\/)+/,
|
|
290
|
+
// '**/foo' <-> 'foo'
|
|
291
|
+
() => "^(?:.*\\/)?",
|
|
292
|
+
"*"
|
|
293
|
+
],
|
|
294
|
+
// starting
|
|
295
|
+
[
|
|
296
|
+
// there will be no leading '/'
|
|
297
|
+
// (which has been replaced by section "leading slash")
|
|
298
|
+
// If starts with '**', adding a '^' to the regular expression also works
|
|
299
|
+
DIRECT,
|
|
300
|
+
(source, pattern) => {
|
|
301
|
+
if (!source || source[0] === "^") {
|
|
302
|
+
return source;
|
|
303
|
+
}
|
|
304
|
+
const anchor = !REGEX_INNER_SLASH.test(pattern) ? "(?:^|\\/)" : "^";
|
|
305
|
+
return anchor + source;
|
|
306
|
+
}
|
|
307
|
+
],
|
|
308
|
+
// two globstars
|
|
309
|
+
[
|
|
310
|
+
// Use lookahead assertions so that we could match more than one `'/**'`
|
|
311
|
+
/\\\/\\\*\\\*(?=\\\/|$)/g,
|
|
312
|
+
// Zero, one or several directories
|
|
313
|
+
// should not use '*', or it will be replaced by the next replacer
|
|
314
|
+
// Check if it is not the last `'/**'`
|
|
315
|
+
(_, index, str) => index + 6 < str.length ? str.slice(index + 6) === "\\/" ? "(?:\\/[^\\/]+)+" : "(?:\\/[^\\/]+)*" : `\\/${TRAILING_DOUBLESTAR}`,
|
|
316
|
+
"*"
|
|
317
|
+
],
|
|
318
|
+
// normal intermediate wildcards
|
|
319
|
+
[
|
|
320
|
+
// Never replace escaped '*'
|
|
321
|
+
// ignore rule '\*' will match the path '*'
|
|
322
|
+
// 'abc.*/' -> go
|
|
323
|
+
// 'abc.*' -> skip this rule,
|
|
324
|
+
// coz trailing single wildcard will be handed by [trailing wildcard]
|
|
325
|
+
/(^|[^\\]+)(\\\*)+(?=.+)/g,
|
|
326
|
+
// '*.js' matches '.js'
|
|
327
|
+
// '*.js' doesn't match 'abc'
|
|
328
|
+
(_, p1, p2) => {
|
|
329
|
+
const unescaped = p2.replace(/\\\*/g, "[^\\/]*");
|
|
330
|
+
return p1 + unescaped;
|
|
331
|
+
},
|
|
332
|
+
"*"
|
|
333
|
+
],
|
|
334
|
+
// trailing wildcard, held apart from a literal star
|
|
335
|
+
[
|
|
336
|
+
// The step above leaves a trailing `*` alone, so a single `\*` is all that
|
|
337
|
+
// can be left at the end here. Whether it is a wildcard or a literal
|
|
338
|
+
// turns on the backslashes the user put in front of it: the escaper has
|
|
339
|
+
// since doubled every one, so what stands here is those `2N` doubled
|
|
340
|
+
// backslashes and then the star's own escape. An even number of the
|
|
341
|
+
// original `N` leaves the star unescaped -- a wildcard -- and an odd
|
|
342
|
+
// number escapes it -- a literal. This runs while the two are still
|
|
343
|
+
// distinct, before the unescape steps below collapse the literal onto
|
|
344
|
+
// the very `\*` a wildcard leaves behind.
|
|
345
|
+
/(^|[^\\])((?:\\\\)*)\\\*$/,
|
|
346
|
+
(match, p1, p2) => (
|
|
347
|
+
// `p2` holds the doubled user backslashes; half of them is `N`.
|
|
348
|
+
p2.length / 2 % 2 === 0 ? p1 + p2 + TRAILING_WILDCARD : match
|
|
349
|
+
),
|
|
350
|
+
"*"
|
|
351
|
+
],
|
|
352
|
+
[
|
|
353
|
+
// unescape, revert step 3 except for back slash
|
|
354
|
+
// For example, if a user escape a '\\*',
|
|
355
|
+
// after step 3, the result will be '\\\\\\*'
|
|
356
|
+
/\\\\\\(?=[$.|*+(){^])/g,
|
|
357
|
+
() => ESCAPE,
|
|
358
|
+
ESCAPE + ESCAPE
|
|
359
|
+
],
|
|
360
|
+
[
|
|
361
|
+
// '\\\\' -> '\\'
|
|
362
|
+
/\\\\/g,
|
|
363
|
+
() => ESCAPE,
|
|
364
|
+
ESCAPE + ESCAPE
|
|
365
|
+
],
|
|
366
|
+
[
|
|
367
|
+
// Every real bracket expression -- POSIX classes included -- has already
|
|
368
|
+
// been held aside by `extractBrackets`, so the only `[` left in the
|
|
369
|
+
// pattern is an escaped, literal one.
|
|
370
|
+
// `\` is escaped by step 3
|
|
371
|
+
/\\\[([^\]/]*?)(\\*)($|\])/g,
|
|
372
|
+
// '\\[bar]' -> '\\\\[bar\\]'
|
|
373
|
+
(match, range, endEscape, close) => `\\[${range}${cleanRangeBackSlash(endEscape)}${close}`,
|
|
374
|
+
"["
|
|
375
|
+
],
|
|
376
|
+
// ending
|
|
377
|
+
[
|
|
378
|
+
// 'js' will not match 'js.'
|
|
379
|
+
// 'ab' will not match 'abc'
|
|
380
|
+
DIRECT,
|
|
381
|
+
// WTF!
|
|
382
|
+
// https://git-scm.com/docs/gitignore
|
|
383
|
+
// changes in [2.22.1](https://git-scm.com/docs/gitignore/2.22.1)
|
|
384
|
+
// which re-fixes #24, #38
|
|
385
|
+
// > If there is a separator at the end of the pattern then the pattern
|
|
386
|
+
// > will only match directories, otherwise the pattern can match both
|
|
387
|
+
// > files and directories.
|
|
388
|
+
// 'js*' will not match 'a.js'
|
|
389
|
+
// 'js/' will not match 'a.js'
|
|
390
|
+
// 'js' will match 'a.js' and 'a.js/'
|
|
391
|
+
(source) => {
|
|
392
|
+
const last = source[source.length - 1];
|
|
393
|
+
if (!last || last === TRAILING_WILDCARD || last === TRAILING_DOUBLESTAR) {
|
|
394
|
+
return source;
|
|
395
|
+
}
|
|
396
|
+
return last === SLASH ? `${source}$` : `${source}(?=$|\\/$)`;
|
|
397
|
+
}
|
|
398
|
+
]
|
|
399
|
+
];
|
|
400
|
+
var REGEX_REPLACE_TRAILING_WILDCARD = /(^|\\\/)?\uE000$/;
|
|
401
|
+
var MODE_IGNORE = "regex";
|
|
402
|
+
var MODE_CHECK_IGNORE = "checkRegex";
|
|
403
|
+
var UNDERSCORE = "_";
|
|
404
|
+
var replaceTrailingWildcard = (_, p1) => {
|
|
405
|
+
const prefix = p1 ? `${p1}[^/]+` : "[^/]*";
|
|
406
|
+
return `${prefix}(?=$|\\/$)`;
|
|
407
|
+
};
|
|
408
|
+
var REGEX_REPLACE_TRAILING_DOUBLESTAR = /\uE001$/;
|
|
409
|
+
var replaceTrailingDoublestar = () => ".+(?=$|\\/$)";
|
|
410
|
+
var WILDCARD = "[^\\/]*";
|
|
411
|
+
var separatorAfter = (run, at) => {
|
|
412
|
+
let separator = EMPTY;
|
|
413
|
+
for (let index = at + 1; index < run.length && !run[index].wildcard; index++) {
|
|
414
|
+
separator += run[index].single;
|
|
415
|
+
}
|
|
416
|
+
return separator;
|
|
417
|
+
};
|
|
418
|
+
var pinWildcards = (source) => {
|
|
419
|
+
if (source.indexOf(WILDCARD) < 0) {
|
|
420
|
+
return source;
|
|
421
|
+
}
|
|
422
|
+
const tokens = [];
|
|
423
|
+
const { length } = source;
|
|
424
|
+
let index = 0;
|
|
425
|
+
while (index < length) {
|
|
426
|
+
const char = source[index];
|
|
427
|
+
if (source.startsWith(WILDCARD, index)) {
|
|
428
|
+
tokens.push({ wildcard: true });
|
|
429
|
+
index += WILDCARD.length;
|
|
430
|
+
} else if (char === "[") {
|
|
431
|
+
let end = index + 1;
|
|
432
|
+
if (source[end] === "^") {
|
|
433
|
+
end++;
|
|
434
|
+
}
|
|
435
|
+
if (source[end] === "]") {
|
|
436
|
+
end++;
|
|
437
|
+
}
|
|
438
|
+
while (end < length && source[end] !== "]") {
|
|
439
|
+
end += source[end] === ESCAPE ? 2 : 1;
|
|
440
|
+
}
|
|
441
|
+
end++;
|
|
442
|
+
tokens.push({ single: source.slice(index, end) });
|
|
443
|
+
index = end;
|
|
444
|
+
} else if (char === ESCAPE) {
|
|
445
|
+
tokens.push({ single: source.slice(index, index + 2) });
|
|
446
|
+
index += 2;
|
|
447
|
+
} else if (char === "(") {
|
|
448
|
+
let depth = 0;
|
|
449
|
+
let end = index;
|
|
450
|
+
do {
|
|
451
|
+
if (source[end] === ESCAPE) {
|
|
452
|
+
end++;
|
|
453
|
+
} else if (source[end] === "(") {
|
|
454
|
+
depth++;
|
|
455
|
+
} else if (source[end] === ")") {
|
|
456
|
+
depth--;
|
|
457
|
+
}
|
|
458
|
+
end++;
|
|
459
|
+
} while (end < length && depth > 0);
|
|
460
|
+
if ("*+?".indexOf(source[end]) >= 0) {
|
|
461
|
+
end++;
|
|
462
|
+
}
|
|
463
|
+
tokens.push({ boundary: source.slice(index, end) });
|
|
464
|
+
index = end;
|
|
465
|
+
} else if (char === "^" || char === "$") {
|
|
466
|
+
tokens.push({ boundary: char });
|
|
467
|
+
index++;
|
|
468
|
+
} else {
|
|
469
|
+
tokens.push({ single: char });
|
|
470
|
+
index++;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
let out = EMPTY;
|
|
474
|
+
let run = [];
|
|
475
|
+
const flush = () => {
|
|
476
|
+
let lastWildcard;
|
|
477
|
+
run.forEach((token, at) => {
|
|
478
|
+
if (token.wildcard) {
|
|
479
|
+
lastWildcard = at;
|
|
480
|
+
}
|
|
481
|
+
});
|
|
482
|
+
run.forEach((token, at) => {
|
|
483
|
+
if (!token.wildcard) {
|
|
484
|
+
out += token.single;
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
out += at === lastWildcard ? WILDCARD : `(?:(?!${separatorAfter(run, at)})[^\\/])*`;
|
|
488
|
+
});
|
|
489
|
+
run = [];
|
|
490
|
+
};
|
|
491
|
+
tokens.forEach((token) => {
|
|
492
|
+
if (token.boundary === void 0) {
|
|
493
|
+
run.push(token);
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
flush();
|
|
497
|
+
out += token.boundary;
|
|
498
|
+
});
|
|
499
|
+
flush();
|
|
500
|
+
return out;
|
|
501
|
+
};
|
|
502
|
+
var makeRegexPrefix = (pattern) => {
|
|
503
|
+
const { source, sources } = extractBrackets(pattern);
|
|
504
|
+
const replaced = REPLACERS.reduce(
|
|
505
|
+
// A pass whose matcher finds nothing hands back the very string it was
|
|
506
|
+
// given, so asking first costs a search and saves a rewrite. Ten of the
|
|
507
|
+
// fifteen passes never fire for a typical .gitignore line, and between
|
|
508
|
+
// them they were 45% of this chain.
|
|
509
|
+
(prev, [matcher, replacer, required]) => {
|
|
510
|
+
if (matcher === DIRECT) {
|
|
511
|
+
return replacer(prev, pattern);
|
|
512
|
+
}
|
|
513
|
+
if (required !== UNDEFINED && prev.indexOf(required) < 0) {
|
|
514
|
+
return prev;
|
|
515
|
+
}
|
|
516
|
+
return matcher.test(prev) ? prev.replace(matcher, replacer.bind(pattern)) : prev;
|
|
517
|
+
},
|
|
518
|
+
source
|
|
519
|
+
);
|
|
520
|
+
return sources.length ? replaced.replace(
|
|
521
|
+
REGEX_RESTORE_PLACEHOLDER,
|
|
522
|
+
(match, index) => sources[index]
|
|
523
|
+
) : replaced;
|
|
524
|
+
};
|
|
525
|
+
var checkSourceOf = (body, prefix) => {
|
|
526
|
+
if (body[body.length - 1] === SLASH) {
|
|
527
|
+
prefix = makeRegexPrefix(body.slice(0, -1));
|
|
528
|
+
}
|
|
529
|
+
const head = prefix.slice(0, -1);
|
|
530
|
+
const last = prefix[prefix.length - 1];
|
|
531
|
+
return last === TRAILING_WILDCARD ? `${head}$` : last === TRAILING_DOUBLESTAR ? `${head}.*$` : NEVER_MATCH;
|
|
532
|
+
};
|
|
533
|
+
var matchesBasename = (body) => {
|
|
534
|
+
const index = body.indexOf(SLASH);
|
|
535
|
+
return index < 0 || index === body.length - 1;
|
|
536
|
+
};
|
|
537
|
+
var basenameOf = (path) => {
|
|
538
|
+
const end = path.length - 1;
|
|
539
|
+
const index = path.lastIndexOf(
|
|
540
|
+
SLASH,
|
|
541
|
+
path[end] === SLASH ? end - 1 : end
|
|
542
|
+
);
|
|
543
|
+
return index < 0 ? path : path.slice(index + 1);
|
|
544
|
+
};
|
|
545
|
+
var parentOf = (path) => {
|
|
546
|
+
if (path.charCodeAt(0) === SLASH_CODE || path.indexOf(DOUBLE_SLASH) >= 0) {
|
|
547
|
+
const slices = path.split(SLASH).filter(Boolean);
|
|
548
|
+
slices.pop();
|
|
549
|
+
return slices.length ? slices.join(SLASH) + SLASH : EMPTY;
|
|
550
|
+
}
|
|
551
|
+
const end = path.length - 1;
|
|
552
|
+
const cut = path.lastIndexOf(
|
|
553
|
+
SLASH,
|
|
554
|
+
path.charCodeAt(end) === SLASH_CODE ? end - 1 : end
|
|
555
|
+
);
|
|
556
|
+
return cut < 0 ? EMPTY : path.slice(0, cut + 1);
|
|
557
|
+
};
|
|
558
|
+
var isString = (subject) => typeof subject === "string";
|
|
559
|
+
var checkPattern = (pattern) => pattern && isString(pattern) && !REGEX_INVALID_TRAILING_BACKSLASH.test(pattern);
|
|
560
|
+
var splitPattern = (pattern) => pattern.split(REGEX_SPLITALL_CRLF).filter(Boolean);
|
|
561
|
+
var IgnoreRule = class {
|
|
562
|
+
constructor(pattern, mark, body, ignoreCase, negative, prefix) {
|
|
563
|
+
this.pattern = pattern;
|
|
564
|
+
this.mark = mark;
|
|
565
|
+
this.negative = negative;
|
|
566
|
+
define(this, "body", body);
|
|
567
|
+
define(this, "ignoreCase", ignoreCase);
|
|
568
|
+
define(this, "regexPrefix", prefix);
|
|
569
|
+
}
|
|
570
|
+
// Worked out on first use and kept behind an own property, the way `regex`
|
|
571
|
+
// caches itself in `_regex`. Deciding it in the constructor instead would
|
|
572
|
+
// add a fourth `defineProperty` to every rule ever built, which cost 4% of
|
|
573
|
+
// every compile -- including the compiles of rules that are never matched
|
|
574
|
+
// against anything.
|
|
575
|
+
get _basenameOnly() {
|
|
576
|
+
return define(this, "_basenameOnly", matchesBasename(this.body));
|
|
577
|
+
}
|
|
578
|
+
get regex() {
|
|
579
|
+
const key = UNDERSCORE + MODE_IGNORE;
|
|
580
|
+
if (this[key]) {
|
|
581
|
+
return this[key];
|
|
582
|
+
}
|
|
583
|
+
return this._make(MODE_IGNORE, key);
|
|
584
|
+
}
|
|
585
|
+
get checkRegex() {
|
|
586
|
+
const key = UNDERSCORE + MODE_CHECK_IGNORE;
|
|
587
|
+
if (this[key]) {
|
|
588
|
+
return this[key];
|
|
589
|
+
}
|
|
590
|
+
return this._make(MODE_CHECK_IGNORE, key);
|
|
591
|
+
}
|
|
592
|
+
_make(mode, key) {
|
|
593
|
+
const str = pinWildcards(
|
|
594
|
+
mode === MODE_IGNORE ? this.regexPrefix.replace(REGEX_REPLACE_TRAILING_WILDCARD, replaceTrailingWildcard).replace(REGEX_REPLACE_TRAILING_DOUBLESTAR, replaceTrailingDoublestar) : checkSourceOf(this.body, this.regexPrefix)
|
|
595
|
+
);
|
|
596
|
+
const regex = this.ignoreCase ? new RegExp(str, "i") : new RegExp(str);
|
|
597
|
+
return define(this, key, regex);
|
|
598
|
+
}
|
|
599
|
+
};
|
|
600
|
+
var isLineEnd = (char) => char === "\r" || char === "\n";
|
|
601
|
+
var trimEnd = (body) => {
|
|
602
|
+
let end = body.length;
|
|
603
|
+
while (end && isLineEnd(body[end - 1])) {
|
|
604
|
+
end--;
|
|
605
|
+
}
|
|
606
|
+
const lineEnd = end;
|
|
607
|
+
while (end && body[end - 1] === SPACE) {
|
|
608
|
+
end--;
|
|
609
|
+
}
|
|
610
|
+
if (end === lineEnd) {
|
|
611
|
+
return body.slice(0, end);
|
|
612
|
+
}
|
|
613
|
+
let backslashes = 0;
|
|
614
|
+
while (backslashes < end && body[end - backslashes - 1] === ESCAPE) {
|
|
615
|
+
backslashes++;
|
|
616
|
+
}
|
|
617
|
+
return backslashes % 2 ? body.slice(0, end - 1) + SPACE : body.slice(0, end);
|
|
618
|
+
};
|
|
619
|
+
var createRule = ({
|
|
620
|
+
pattern,
|
|
621
|
+
mark
|
|
622
|
+
}, ignoreCase) => {
|
|
623
|
+
let body = pattern[0] === BOM ? pattern.slice(1) : pattern;
|
|
624
|
+
if (body[0] === "#") {
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
627
|
+
let negative = false;
|
|
628
|
+
if (body[0] === "!") {
|
|
629
|
+
negative = true;
|
|
630
|
+
body = body.slice(1);
|
|
631
|
+
}
|
|
632
|
+
const last = body[body.length - 1];
|
|
633
|
+
if (last === SPACE || last === "\r" || last === "\n") {
|
|
634
|
+
body = trimEnd(body);
|
|
635
|
+
}
|
|
636
|
+
if (!body) {
|
|
637
|
+
return;
|
|
638
|
+
}
|
|
639
|
+
body = body.replace(REGEX_REPLACE_LEADING_EXCAPED_EXCLAMATION, "!").replace(REGEX_REPLACE_LEADING_EXCAPED_HASH, "#");
|
|
640
|
+
const regexPrefix = makeRegexPrefix(body);
|
|
641
|
+
return new IgnoreRule(
|
|
642
|
+
pattern,
|
|
643
|
+
mark,
|
|
644
|
+
body,
|
|
645
|
+
ignoreCase,
|
|
646
|
+
negative,
|
|
647
|
+
regexPrefix
|
|
648
|
+
);
|
|
649
|
+
};
|
|
650
|
+
var RuleManager = class {
|
|
651
|
+
constructor(ignoreCase) {
|
|
652
|
+
this._ignoreCase = ignoreCase;
|
|
653
|
+
this._rules = [];
|
|
654
|
+
this._basenameCount = 0;
|
|
655
|
+
}
|
|
656
|
+
_add(pattern) {
|
|
657
|
+
if (pattern && pattern[KEY_IGNORE]) {
|
|
658
|
+
this._rules = this._rules.concat(pattern._rules._rules);
|
|
659
|
+
this._basenameCount += pattern._rules._basenameCount;
|
|
660
|
+
this._added = true;
|
|
661
|
+
return;
|
|
662
|
+
}
|
|
663
|
+
if (isString(pattern)) {
|
|
664
|
+
pattern = {
|
|
665
|
+
pattern
|
|
666
|
+
};
|
|
667
|
+
}
|
|
668
|
+
const rule = checkPattern(pattern.pattern) && createRule(pattern, this._ignoreCase);
|
|
669
|
+
if (rule) {
|
|
670
|
+
this._added = true;
|
|
671
|
+
this._rules.push(rule);
|
|
672
|
+
if (matchesBasename(rule.body)) {
|
|
673
|
+
this._basenameCount++;
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
// @param {Array<string> | string | Ignore} pattern
|
|
678
|
+
add(pattern) {
|
|
679
|
+
this._added = false;
|
|
680
|
+
makeArray(
|
|
681
|
+
isString(pattern) ? splitPattern(pattern) : pattern
|
|
682
|
+
).forEach(this._add, this);
|
|
683
|
+
if (this._added) {
|
|
684
|
+
this._literalRules = UNDEFINED;
|
|
685
|
+
}
|
|
686
|
+
return this._added;
|
|
687
|
+
}
|
|
688
|
+
// Match the literal 'abc/' for `checkIgnore`, last rule wins. Only a rule
|
|
689
|
+
// ending in a wildcard can match it (see `checkSourceOf`), so the rest
|
|
690
|
+
// are left out once, rather than tested or compiled for every path.
|
|
691
|
+
testLiteral(path) {
|
|
692
|
+
const rules = this._literalRules || (this._literalRules = this._rules.filter(
|
|
693
|
+
({ body }) => body[body.length - (body[body.length - 1] === SLASH ? 2 : 1)] === "*"
|
|
694
|
+
));
|
|
695
|
+
let ignored = false;
|
|
696
|
+
let unignored = false;
|
|
697
|
+
let matchedRule;
|
|
698
|
+
for (let index = rules.length - 1; index >= 0; index--) {
|
|
699
|
+
const rule = rules[index];
|
|
700
|
+
if (rule.checkRegex.test(path)) {
|
|
701
|
+
ignored = !rule.negative;
|
|
702
|
+
unignored = rule.negative;
|
|
703
|
+
matchedRule = rule.negative ? UNDEFINED : rule;
|
|
704
|
+
break;
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
const ret = {
|
|
708
|
+
ignored,
|
|
709
|
+
unignored
|
|
710
|
+
};
|
|
711
|
+
if (matchedRule) {
|
|
712
|
+
ret.rule = matchedRule;
|
|
713
|
+
}
|
|
714
|
+
return ret;
|
|
715
|
+
}
|
|
716
|
+
// Test one single path without recursively checking parent directories
|
|
717
|
+
//
|
|
718
|
+
// - checkUnignored `boolean` whether should check if the path is unignored,
|
|
719
|
+
// setting `checkUnignored` to `false` could reduce additional
|
|
720
|
+
// path matching.
|
|
721
|
+
// - check `string` either `MODE_IGNORE` or `MODE_CHECK_IGNORE`
|
|
722
|
+
// @returns {TestResult} true if a file is ignored
|
|
723
|
+
test(path, checkUnignored, mode) {
|
|
724
|
+
let ignored = false;
|
|
725
|
+
let unignored = false;
|
|
726
|
+
let matchedRule;
|
|
727
|
+
const rules = this._rules;
|
|
728
|
+
const { length } = rules;
|
|
729
|
+
const shortcut = this._basenameCount * 2 >= length;
|
|
730
|
+
const basename = shortcut ? basenameOf(path) : path;
|
|
731
|
+
for (let index = 0; index < length; index++) {
|
|
732
|
+
const rule = rules[index];
|
|
733
|
+
const { negative } = rule;
|
|
734
|
+
const skip = unignored === negative && ignored !== unignored || negative && !ignored && !unignored && !checkUnignored;
|
|
735
|
+
if (!skip && rule[mode].test(
|
|
736
|
+
shortcut && rule._basenameOnly ? basename : path
|
|
737
|
+
)) {
|
|
738
|
+
ignored = !negative;
|
|
739
|
+
unignored = negative;
|
|
740
|
+
matchedRule = negative ? UNDEFINED : rule;
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
const ret = {
|
|
744
|
+
ignored,
|
|
745
|
+
unignored
|
|
746
|
+
};
|
|
747
|
+
if (matchedRule) {
|
|
748
|
+
ret.rule = matchedRule;
|
|
749
|
+
}
|
|
750
|
+
return ret;
|
|
751
|
+
}
|
|
752
|
+
};
|
|
753
|
+
var throwError = (message, Ctor) => {
|
|
754
|
+
throw new Ctor(message);
|
|
755
|
+
};
|
|
756
|
+
var checkPath = (path, originalPath, doThrow) => {
|
|
757
|
+
if (!isString(path)) {
|
|
758
|
+
return doThrow(
|
|
759
|
+
`path must be a string, but got \`${originalPath}\``,
|
|
760
|
+
TypeError
|
|
761
|
+
);
|
|
762
|
+
}
|
|
763
|
+
if (!path) {
|
|
764
|
+
return doThrow(`path must not be empty`, TypeError);
|
|
765
|
+
}
|
|
766
|
+
if (checkPath.isNotRelative(path)) {
|
|
767
|
+
const r = "`path.relative()`d";
|
|
768
|
+
return doThrow(
|
|
769
|
+
`path should be a ${r} string, but got "${originalPath}"`,
|
|
770
|
+
RangeError
|
|
771
|
+
);
|
|
772
|
+
}
|
|
773
|
+
return true;
|
|
774
|
+
};
|
|
775
|
+
var isNotRelative = (path) => {
|
|
776
|
+
const first = path.charCodeAt(0);
|
|
777
|
+
if (first === SLASH_CODE) {
|
|
778
|
+
return true;
|
|
779
|
+
}
|
|
780
|
+
if (first !== DOT_CODE) {
|
|
781
|
+
return false;
|
|
782
|
+
}
|
|
783
|
+
if (path.length === 1) {
|
|
784
|
+
return true;
|
|
785
|
+
}
|
|
786
|
+
const second = path.charCodeAt(1);
|
|
787
|
+
if (second === SLASH_CODE) {
|
|
788
|
+
return true;
|
|
789
|
+
}
|
|
790
|
+
if (second !== DOT_CODE) {
|
|
791
|
+
return false;
|
|
792
|
+
}
|
|
793
|
+
return path.length === 2 || path.charCodeAt(2) === SLASH_CODE;
|
|
794
|
+
};
|
|
795
|
+
checkPath.isNotRelative = isNotRelative;
|
|
796
|
+
checkPath.convert = (p) => p;
|
|
797
|
+
var Ignore = class {
|
|
798
|
+
constructor({
|
|
799
|
+
ignorecase = true,
|
|
800
|
+
ignoreCase = ignorecase,
|
|
801
|
+
allowRelativePaths = false
|
|
802
|
+
} = {}) {
|
|
803
|
+
define(this, KEY_IGNORE, true);
|
|
804
|
+
this._rules = new RuleManager(ignoreCase);
|
|
805
|
+
this._strictPathCheck = !allowRelativePaths;
|
|
806
|
+
this._initCache();
|
|
807
|
+
}
|
|
808
|
+
_initCache() {
|
|
809
|
+
this._ignoreCache = /* @__PURE__ */ Object.create(null);
|
|
810
|
+
this._testCache = /* @__PURE__ */ Object.create(null);
|
|
811
|
+
}
|
|
812
|
+
add(pattern) {
|
|
813
|
+
if (this._rules.add(pattern)) {
|
|
814
|
+
this._initCache();
|
|
815
|
+
}
|
|
816
|
+
return this;
|
|
817
|
+
}
|
|
818
|
+
// legacy
|
|
819
|
+
addPattern(pattern) {
|
|
820
|
+
return this.add(pattern);
|
|
821
|
+
}
|
|
822
|
+
// @returns {TestResult}
|
|
823
|
+
_test(originalPath, cache, checkUnignored) {
|
|
824
|
+
const path = originalPath && checkPath.convert(originalPath);
|
|
825
|
+
checkPath(
|
|
826
|
+
path,
|
|
827
|
+
originalPath,
|
|
828
|
+
this._strictPathCheck ? throwError : RETURN_FALSE
|
|
829
|
+
);
|
|
830
|
+
return this._t(path, cache, checkUnignored);
|
|
831
|
+
}
|
|
832
|
+
checkIgnore(path) {
|
|
833
|
+
if (path.charCodeAt(path.length - 1) !== SLASH_CODE) {
|
|
834
|
+
return this.test(path);
|
|
835
|
+
}
|
|
836
|
+
const dir = this._t(path, this._testCache, true);
|
|
837
|
+
if (dir.ignored) {
|
|
838
|
+
return dir;
|
|
839
|
+
}
|
|
840
|
+
const literal = this._rules.testLiteral(path);
|
|
841
|
+
return literal.ignored || literal.unignored ? literal : dir;
|
|
842
|
+
}
|
|
843
|
+
_t(path, cache, checkUnignored) {
|
|
844
|
+
if (path in cache) {
|
|
845
|
+
return cache[path];
|
|
846
|
+
}
|
|
847
|
+
const parentPath = parentOf(path);
|
|
848
|
+
const parent = parentPath ? this._t(parentPath, cache, checkUnignored) : UNDEFINED;
|
|
849
|
+
return cache[path] = parent && parent.ignored ? parent : this._rules.test(path, checkUnignored, MODE_IGNORE);
|
|
850
|
+
}
|
|
851
|
+
ignores(path) {
|
|
852
|
+
return this._test(path, this._ignoreCache, false).ignored;
|
|
853
|
+
}
|
|
854
|
+
createFilter() {
|
|
855
|
+
return (path) => !this.ignores(path);
|
|
856
|
+
}
|
|
857
|
+
filter(paths) {
|
|
858
|
+
return makeArray(paths).filter(this.createFilter());
|
|
859
|
+
}
|
|
860
|
+
// @returns {TestResult}
|
|
861
|
+
test(path) {
|
|
862
|
+
return this._test(path, this._testCache, true);
|
|
863
|
+
}
|
|
864
|
+
};
|
|
865
|
+
var factory = (options) => new Ignore(options);
|
|
866
|
+
var isPathValid = (path) => checkPath(path && checkPath.convert(path), path, RETURN_FALSE);
|
|
867
|
+
var setupWindows = () => {
|
|
868
|
+
const makePosix = (str) => /^\\\\\?\\/.test(str) || /["<>|\u0000-\u001F]+/u.test(str) ? str : str.replace(/\\/g, "/");
|
|
869
|
+
checkPath.convert = makePosix;
|
|
870
|
+
const REGEX_TEST_WINDOWS_PATH_ABSOLUTE = /^[a-z]:\//i;
|
|
871
|
+
checkPath.isNotRelative = (path) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path) || isNotRelative(path);
|
|
872
|
+
};
|
|
873
|
+
if (
|
|
874
|
+
// Detect `process` so that it can run in browsers.
|
|
875
|
+
typeof process !== "undefined" && process.platform === "win32"
|
|
876
|
+
) {
|
|
877
|
+
setupWindows();
|
|
878
|
+
}
|
|
879
|
+
module.exports = factory;
|
|
880
|
+
factory.default = factory;
|
|
881
|
+
module.exports.isPathValid = isPathValid;
|
|
882
|
+
define(module.exports, /* @__PURE__ */ Symbol.for("setupWindows"), setupWindows);
|
|
883
|
+
}
|
|
884
|
+
});
|
|
885
|
+
|
|
886
|
+
// packages/shared/dist/logger.js
|
|
887
|
+
var debug;
|
|
888
|
+
var init_logger = __esm({
|
|
889
|
+
"packages/shared/dist/logger.js"() {
|
|
890
|
+
"use strict";
|
|
891
|
+
debug = (_msg, _ctx) => {
|
|
892
|
+
};
|
|
893
|
+
}
|
|
894
|
+
});
|
|
895
|
+
|
|
896
|
+
// packages/shared/dist/protectedPaths.js
|
|
897
|
+
import { homedir } from "node:os";
|
|
898
|
+
import { posix, win32 } from "node:path";
|
|
899
|
+
function pathsFor(platform) {
|
|
900
|
+
return platform === "win32" ? win32 : posix;
|
|
901
|
+
}
|
|
902
|
+
function mnemonikStateDirectory(platform = process.platform, env = process.env, home = homedir()) {
|
|
903
|
+
if (env.MNEMONIK_STATE_DIR)
|
|
904
|
+
return env.MNEMONIK_STATE_DIR;
|
|
905
|
+
if (platform === "win32")
|
|
906
|
+
return win32.join(env.LOCALAPPDATA || win32.join(home, "AppData", "Local"), "Mnemonik");
|
|
907
|
+
if (platform === "darwin")
|
|
908
|
+
return posix.join(home, "Library", "Application Support", "Mnemonik");
|
|
909
|
+
return posix.join(env.XDG_STATE_HOME || posix.join(home, ".local", "state"), "mnemonik");
|
|
910
|
+
}
|
|
911
|
+
function protectedLocalPaths(platform = process.platform, env = process.env, home = homedir()) {
|
|
912
|
+
const path = pathsFor(platform);
|
|
913
|
+
const localAppData = env.LOCALAPPDATA || win32.join(home, "AppData", "Local");
|
|
914
|
+
const appData = env.APPDATA || win32.join(home, "AppData", "Roaming");
|
|
915
|
+
const state = mnemonikStateDirectory(platform, env, home);
|
|
916
|
+
const common = [
|
|
917
|
+
state,
|
|
918
|
+
path.join(home, ".mnemonik"),
|
|
919
|
+
path.join(home, ".claude", ".credentials.json"),
|
|
920
|
+
path.join(home, ".codex", "auth.json"),
|
|
921
|
+
path.join(home, ".cursor", "mcp.json"),
|
|
922
|
+
path.join(home, ".grok", "mcp_credentials.json")
|
|
923
|
+
];
|
|
924
|
+
const osPaths = platform === "darwin" ? [path.join(home, "Library", "Keychains")] : platform === "win32" ? [
|
|
925
|
+
win32.join(appData, "Microsoft", "Credentials"),
|
|
926
|
+
win32.join(localAppData, "Microsoft", "Credentials"),
|
|
927
|
+
win32.join(appData, "Microsoft", "Protect")
|
|
928
|
+
] : [
|
|
929
|
+
path.join(home, ".local", "share", "keyrings"),
|
|
930
|
+
path.join(home, ".gnupg"),
|
|
931
|
+
path.join(home, ".ssh"),
|
|
932
|
+
path.join(home, ".aws"),
|
|
933
|
+
path.join(home, ".config", "gcloud"),
|
|
934
|
+
path.join(home, ".docker", "config.json"),
|
|
935
|
+
path.join(home, ".npmrc"),
|
|
936
|
+
path.join(home, ".netrc"),
|
|
937
|
+
path.join(home, ".pypirc")
|
|
938
|
+
];
|
|
939
|
+
return [.../* @__PURE__ */ new Set([...common, ...osPaths])];
|
|
940
|
+
}
|
|
941
|
+
function comparable(value, platform) {
|
|
942
|
+
const path = pathsFor(platform);
|
|
943
|
+
const normalized = path.resolve(value);
|
|
944
|
+
return platform === "win32" || platform === "darwin" ? normalized.toLowerCase() : normalized;
|
|
945
|
+
}
|
|
946
|
+
function isProtectedLocalPath(candidate, protectedPaths = protectedLocalPaths(), platform = process.platform) {
|
|
947
|
+
const path = pathsFor(platform);
|
|
948
|
+
const target = comparable(candidate, platform);
|
|
949
|
+
return protectedPaths.some((protectedPath) => {
|
|
950
|
+
const parent = comparable(protectedPath, platform);
|
|
951
|
+
const relative2 = path.relative(parent, target);
|
|
952
|
+
return relative2 === "" || !relative2.startsWith(`..${path.sep}`) && relative2 !== ".." && !path.isAbsolute(relative2);
|
|
953
|
+
});
|
|
954
|
+
}
|
|
955
|
+
var init_protectedPaths = __esm({
|
|
956
|
+
"packages/shared/dist/protectedPaths.js"() {
|
|
957
|
+
"use strict";
|
|
958
|
+
}
|
|
959
|
+
});
|
|
960
|
+
|
|
961
|
+
// packages/shared/dist/projectIdentityFile.js
|
|
962
|
+
import { lstat, readFile } from "node:fs/promises";
|
|
963
|
+
import { join } from "node:path";
|
|
964
|
+
function parseIdentityFile(text) {
|
|
965
|
+
let value;
|
|
966
|
+
try {
|
|
967
|
+
value = JSON.parse(text);
|
|
968
|
+
} catch (error) {
|
|
969
|
+
return { kind: "malformed", detail: `unparseable JSON: ${error.message}` };
|
|
970
|
+
}
|
|
971
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
972
|
+
return { kind: "malformed", detail: "identity file must contain a JSON object" };
|
|
973
|
+
}
|
|
974
|
+
const record = value;
|
|
975
|
+
if (record.schemaVersion !== 1) {
|
|
976
|
+
return { kind: "unknown_version", version: record.schemaVersion };
|
|
977
|
+
}
|
|
978
|
+
const unknownKey = Object.keys(record).find((key) => !IDENTITY_KEYS.has(key));
|
|
979
|
+
if (unknownKey)
|
|
980
|
+
return { kind: "malformed", detail: `unknown key "${unknownKey}"` };
|
|
981
|
+
if (!isCanonicalUuid(record.projectId)) {
|
|
982
|
+
return { kind: "malformed", detail: '"projectId" must be a canonical RFC 4122 UUID string' };
|
|
983
|
+
}
|
|
984
|
+
if ("projectName" in record && typeof record.projectName !== "string") {
|
|
985
|
+
return { kind: "malformed", detail: '"projectName" must be a string when present' };
|
|
986
|
+
}
|
|
987
|
+
if ("repositoryFingerprint" in record && (!record.repositoryFingerprint || typeof record.repositoryFingerprint !== "object" || Array.isArray(record.repositoryFingerprint))) {
|
|
988
|
+
return { kind: "malformed", detail: '"repositoryFingerprint" must be an object when present' };
|
|
989
|
+
}
|
|
990
|
+
const fingerprint = record.repositoryFingerprint;
|
|
991
|
+
const unknownFingerprintKey = fingerprint ? Object.keys(fingerprint).find((key) => !FINGERPRINT_KEYS.has(key)) : void 0;
|
|
992
|
+
if (unknownFingerprintKey) {
|
|
993
|
+
return {
|
|
994
|
+
kind: "malformed",
|
|
995
|
+
detail: `unknown repositoryFingerprint key "${unknownFingerprintKey}"`
|
|
996
|
+
};
|
|
997
|
+
}
|
|
998
|
+
if (fingerprint && (fingerprint.algorithmVersion !== 1 || typeof fingerprint.hash !== "string")) {
|
|
999
|
+
return {
|
|
1000
|
+
kind: "malformed",
|
|
1001
|
+
detail: '"repositoryFingerprint" must contain algorithmVersion 1 and a string hash'
|
|
1002
|
+
};
|
|
1003
|
+
}
|
|
1004
|
+
const repositoryFingerprint = fingerprint ? { algorithmVersion: 1, hash: fingerprint.hash } : void 0;
|
|
1005
|
+
return {
|
|
1006
|
+
kind: "ok",
|
|
1007
|
+
identity: {
|
|
1008
|
+
schemaVersion: 1,
|
|
1009
|
+
projectId: record.projectId,
|
|
1010
|
+
...record.projectName === void 0 ? {} : { projectName: record.projectName },
|
|
1011
|
+
...repositoryFingerprint ? { repositoryFingerprint } : {}
|
|
1012
|
+
}
|
|
1013
|
+
};
|
|
1014
|
+
}
|
|
1015
|
+
function isConfigurationOnly(text) {
|
|
1016
|
+
try {
|
|
1017
|
+
const value = JSON.parse(text);
|
|
1018
|
+
return !!value && typeof value === "object" && !Array.isArray(value) && !("projectId" in value) && (!("schemaVersion" in value) || value.schemaVersion === 1);
|
|
1019
|
+
} catch {
|
|
1020
|
+
return false;
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
async function readIdentityFile(dir, options = {}) {
|
|
1024
|
+
const path = join(dir, ".mnemonik.json");
|
|
1025
|
+
try {
|
|
1026
|
+
const stat = await lstat(path);
|
|
1027
|
+
if (stat.isSymbolicLink())
|
|
1028
|
+
return { kind: "malformed", detail: "symlink" };
|
|
1029
|
+
if (!stat.isFile())
|
|
1030
|
+
return { kind: "malformed", detail: "identity path is not a regular file" };
|
|
1031
|
+
const text = await readFile(path, "utf8");
|
|
1032
|
+
if ((options.selectedRoot || options.configurationOnlyAbsent) && isConfigurationOnly(text))
|
|
1033
|
+
return { kind: "absent" };
|
|
1034
|
+
return parseIdentityFile(text);
|
|
1035
|
+
} catch (error) {
|
|
1036
|
+
if (error.code === "ENOENT")
|
|
1037
|
+
return { kind: "absent" };
|
|
1038
|
+
return { kind: "malformed", detail: `cannot read identity file: ${error.message}` };
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
var CANONICAL_UUID, isCanonicalUuid, IDENTITY_KEYS, FINGERPRINT_KEYS;
|
|
1042
|
+
var init_projectIdentityFile = __esm({
|
|
1043
|
+
"packages/shared/dist/projectIdentityFile.js"() {
|
|
1044
|
+
"use strict";
|
|
1045
|
+
CANONICAL_UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
|
|
1046
|
+
isCanonicalUuid = (value) => typeof value === "string" && CANONICAL_UUID.test(value);
|
|
1047
|
+
IDENTITY_KEYS = /* @__PURE__ */ new Set([
|
|
1048
|
+
"schemaVersion",
|
|
1049
|
+
"projectId",
|
|
1050
|
+
"projectName",
|
|
1051
|
+
"repositoryFingerprint"
|
|
1052
|
+
]);
|
|
1053
|
+
FINGERPRINT_KEYS = /* @__PURE__ */ new Set(["algorithmVersion", "hash"]);
|
|
1054
|
+
}
|
|
1055
|
+
});
|
|
1056
|
+
|
|
1057
|
+
// packages/shared/dist/indexClassifier.js
|
|
1058
|
+
import { posix as posix2 } from "path";
|
|
1059
|
+
function isTestOrSpecName(base) {
|
|
1060
|
+
return base.includes(".test.") || base.includes(".spec.");
|
|
1061
|
+
}
|
|
1062
|
+
function hasGeneratedStemToken(base) {
|
|
1063
|
+
const tokens = base.split(".");
|
|
1064
|
+
if (tokens.length < 2)
|
|
1065
|
+
return false;
|
|
1066
|
+
return tokens.slice(0, -1).some((token) => token.toLowerCase() === "generated");
|
|
1067
|
+
}
|
|
1068
|
+
function posixPath(relPath) {
|
|
1069
|
+
return relPath.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
1070
|
+
}
|
|
1071
|
+
function softDefaultForDirectory(dirRel) {
|
|
1072
|
+
const path = posixPath(dirRel).replace(/\/+$/, "");
|
|
1073
|
+
if (!path)
|
|
1074
|
+
return null;
|
|
1075
|
+
const withSlash = `${path}/`;
|
|
1076
|
+
for (const { reason, rule, re } of DIRECTORY_RULES) {
|
|
1077
|
+
if (re.test(withSlash))
|
|
1078
|
+
return { reason, rule };
|
|
1079
|
+
}
|
|
1080
|
+
return null;
|
|
1081
|
+
}
|
|
1082
|
+
function softDefaultForPath(relPath) {
|
|
1083
|
+
const path = posixPath(relPath);
|
|
1084
|
+
if (!path)
|
|
1085
|
+
return null;
|
|
1086
|
+
for (const { reason, rule, re } of DIRECTORY_RULES) {
|
|
1087
|
+
if (re.test(path))
|
|
1088
|
+
return { reason, rule };
|
|
1089
|
+
}
|
|
1090
|
+
const base = posix2.basename(path);
|
|
1091
|
+
if (base.startsWith("._"))
|
|
1092
|
+
return { reason: "binary", rule: "AppleDouble resource fork" };
|
|
1093
|
+
if (!isTestOrSpecName(base)) {
|
|
1094
|
+
if (hasGeneratedStemToken(base))
|
|
1095
|
+
return { reason: "generated", rule: "generated file name" };
|
|
1096
|
+
for (const { rule, re } of GENERATED_BASENAME_RULES) {
|
|
1097
|
+
if (re.test(base))
|
|
1098
|
+
return { reason: "generated", rule };
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
if (CHANGELOG_UPPER.test(base) || CHANGELOG_DOC.test(base)) {
|
|
1102
|
+
return { reason: "changelog", rule: "changelog file" };
|
|
1103
|
+
}
|
|
1104
|
+
return null;
|
|
1105
|
+
}
|
|
1106
|
+
function generatedByHeader(content) {
|
|
1107
|
+
const lines = content.split("\n", HEADER_LINES);
|
|
1108
|
+
let generation = false;
|
|
1109
|
+
let doNotEdit = false;
|
|
1110
|
+
for (const raw of lines) {
|
|
1111
|
+
const line = raw.replace(/\r$/, "");
|
|
1112
|
+
if (GO_GENERATED_HEADER.test(line))
|
|
1113
|
+
return "Go generated-code header";
|
|
1114
|
+
if (!COMMENT_LINE.test(line))
|
|
1115
|
+
continue;
|
|
1116
|
+
if (AT_GENERATED.test(line))
|
|
1117
|
+
return "@generated marker";
|
|
1118
|
+
if (GENERATION_PHRASE.test(line))
|
|
1119
|
+
generation = true;
|
|
1120
|
+
if (DO_NOT_EDIT_PHRASE.test(line))
|
|
1121
|
+
doNotEdit = true;
|
|
1122
|
+
}
|
|
1123
|
+
return generation && doNotEdit ? "generated + do-not-edit header" : null;
|
|
1124
|
+
}
|
|
1125
|
+
function utf8Length(line) {
|
|
1126
|
+
if (line.length * 3 <= MINIFIED_LONG_LINE_BYTES)
|
|
1127
|
+
return line.length;
|
|
1128
|
+
return Buffer.byteLength(line, "utf8");
|
|
1129
|
+
}
|
|
1130
|
+
function minified(relPath, content) {
|
|
1131
|
+
if (PROSE_EXTENSION.test(relPath))
|
|
1132
|
+
return null;
|
|
1133
|
+
const lines = content.split("\n");
|
|
1134
|
+
let longLines = 0;
|
|
1135
|
+
let total = 0;
|
|
1136
|
+
for (const line of lines) {
|
|
1137
|
+
total += line.length;
|
|
1138
|
+
if (line.length > MINIFIED_LONG_LINE_BYTES / 3 && utf8Length(line) > MINIFIED_LONG_LINE_BYTES) {
|
|
1139
|
+
longLines++;
|
|
1140
|
+
if (longLines > 1)
|
|
1141
|
+
return "two or more lines over 4,096 bytes";
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
if (MINIFIABLE_EXTENSION.test(relPath) && lines.length > 0) {
|
|
1145
|
+
const average = total / lines.length;
|
|
1146
|
+
if (average > MINIFIED_AVERAGE_LINE_LENGTH)
|
|
1147
|
+
return "average line length over 110";
|
|
1148
|
+
}
|
|
1149
|
+
return null;
|
|
1150
|
+
}
|
|
1151
|
+
function dataDump(relPath, content) {
|
|
1152
|
+
if (!/\.sql$/i.test(relPath))
|
|
1153
|
+
return null;
|
|
1154
|
+
if (/(^|\/)migrations\//i.test(posixPath(relPath)))
|
|
1155
|
+
return null;
|
|
1156
|
+
if (COPY_FROM_STDIN.test(content))
|
|
1157
|
+
return "SQL COPY ... FROM stdin";
|
|
1158
|
+
let inserts = 0;
|
|
1159
|
+
INSERT_INTO.lastIndex = 0;
|
|
1160
|
+
while (INSERT_INTO.exec(content) !== null) {
|
|
1161
|
+
if (++inserts >= DATA_DUMP_INSERT_THRESHOLD)
|
|
1162
|
+
return "SQL with 50+ INSERT INTO statements";
|
|
1163
|
+
}
|
|
1164
|
+
return null;
|
|
1165
|
+
}
|
|
1166
|
+
function softDefaultForContent(relPath, content) {
|
|
1167
|
+
if (content.slice(0, BINARY_SNIFF_BYTES).includes("\0")) {
|
|
1168
|
+
return { reason: "binary", rule: "NUL byte in the first 8,000 bytes" };
|
|
1169
|
+
}
|
|
1170
|
+
const header = generatedByHeader(content);
|
|
1171
|
+
if (header)
|
|
1172
|
+
return { reason: "generated", rule: header };
|
|
1173
|
+
const minifiedRule = minified(relPath, content);
|
|
1174
|
+
if (minifiedRule)
|
|
1175
|
+
return { reason: "minified", rule: minifiedRule };
|
|
1176
|
+
const dump = dataDump(relPath, content);
|
|
1177
|
+
if (dump)
|
|
1178
|
+
return { reason: "data_dump", rule: dump };
|
|
1179
|
+
return null;
|
|
1180
|
+
}
|
|
1181
|
+
function escapeRegex(char) {
|
|
1182
|
+
return /[.+^${}()|[\]\\*?/]/.test(char) ? `\\${char}` : char;
|
|
1183
|
+
}
|
|
1184
|
+
function globToRegex(glob) {
|
|
1185
|
+
let re = "";
|
|
1186
|
+
let i = 0;
|
|
1187
|
+
while (i < glob.length) {
|
|
1188
|
+
if (glob.startsWith("**", i)) {
|
|
1189
|
+
const atSegmentStart = i === 0 || glob[i - 1] === "/";
|
|
1190
|
+
const next = glob[i + 2];
|
|
1191
|
+
if (atSegmentStart && next === "/") {
|
|
1192
|
+
re += "(?:.*/)?";
|
|
1193
|
+
i += 3;
|
|
1194
|
+
continue;
|
|
1195
|
+
}
|
|
1196
|
+
if (atSegmentStart && next === void 0) {
|
|
1197
|
+
re += ".*";
|
|
1198
|
+
i += 2;
|
|
1199
|
+
continue;
|
|
1200
|
+
}
|
|
1201
|
+
re += "[^/]*";
|
|
1202
|
+
i += 2;
|
|
1203
|
+
continue;
|
|
1204
|
+
}
|
|
1205
|
+
const char = glob[i];
|
|
1206
|
+
if (char === "*") {
|
|
1207
|
+
re += "[^/]*";
|
|
1208
|
+
} else if (char === "?") {
|
|
1209
|
+
re += "[^/]";
|
|
1210
|
+
} else if (char === "[") {
|
|
1211
|
+
const close = glob.indexOf("]", i + 2);
|
|
1212
|
+
if (close === -1) {
|
|
1213
|
+
re += "\\[";
|
|
1214
|
+
} else {
|
|
1215
|
+
let body = glob.slice(i + 1, close);
|
|
1216
|
+
if (body.startsWith("!"))
|
|
1217
|
+
body = `^${body.slice(1)}`;
|
|
1218
|
+
re += `[${body.replace(/\\/g, "\\\\")}]`;
|
|
1219
|
+
i = close;
|
|
1220
|
+
}
|
|
1221
|
+
} else if (char === "\\" && i + 1 < glob.length) {
|
|
1222
|
+
re += escapeRegex(glob[i + 1]);
|
|
1223
|
+
i++;
|
|
1224
|
+
} else {
|
|
1225
|
+
re += escapeRegex(char);
|
|
1226
|
+
}
|
|
1227
|
+
i++;
|
|
1228
|
+
}
|
|
1229
|
+
return new RegExp(`^${re}$`);
|
|
1230
|
+
}
|
|
1231
|
+
function splitAttributesLine(line) {
|
|
1232
|
+
let pattern;
|
|
1233
|
+
let rest;
|
|
1234
|
+
if (line.startsWith('"')) {
|
|
1235
|
+
let i = 1;
|
|
1236
|
+
let out = "";
|
|
1237
|
+
for (; i < line.length && line[i] !== '"'; i++) {
|
|
1238
|
+
if (line[i] === "\\" && i + 1 < line.length) {
|
|
1239
|
+
i++;
|
|
1240
|
+
out += line[i] === "t" ? " " : line[i] === "n" ? "\n" : line[i];
|
|
1241
|
+
} else {
|
|
1242
|
+
out += line[i];
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
if (i >= line.length)
|
|
1246
|
+
return null;
|
|
1247
|
+
pattern = out;
|
|
1248
|
+
rest = line.slice(i + 1);
|
|
1249
|
+
} else {
|
|
1250
|
+
const match = /^(\S+)(.*)$/.exec(line);
|
|
1251
|
+
if (!match)
|
|
1252
|
+
return null;
|
|
1253
|
+
pattern = match[1];
|
|
1254
|
+
rest = match[2];
|
|
1255
|
+
}
|
|
1256
|
+
return { pattern, attrs: rest.split(/\s+/).filter(Boolean) };
|
|
1257
|
+
}
|
|
1258
|
+
function parseGitattributes(text) {
|
|
1259
|
+
const rules = [];
|
|
1260
|
+
for (const rawLine of text.split(/\r?\n/)) {
|
|
1261
|
+
const line = rawLine.trim();
|
|
1262
|
+
if (!line || line.startsWith("#") || line.startsWith("[attr]"))
|
|
1263
|
+
continue;
|
|
1264
|
+
const split = splitAttributesLine(line);
|
|
1265
|
+
if (!split || split.pattern.startsWith("!"))
|
|
1266
|
+
continue;
|
|
1267
|
+
let { pattern } = split;
|
|
1268
|
+
if (pattern.endsWith("/"))
|
|
1269
|
+
continue;
|
|
1270
|
+
const attrs = /* @__PURE__ */ new Map();
|
|
1271
|
+
for (const token of split.attrs) {
|
|
1272
|
+
if (token.startsWith("-"))
|
|
1273
|
+
attrs.set(token.slice(1), false);
|
|
1274
|
+
else if (token.startsWith("!"))
|
|
1275
|
+
attrs.set(token.slice(1), void 0);
|
|
1276
|
+
else if (token.includes("=")) {
|
|
1277
|
+
const eq = token.indexOf("=");
|
|
1278
|
+
attrs.set(token.slice(0, eq), token.slice(eq + 1));
|
|
1279
|
+
} else
|
|
1280
|
+
attrs.set(token, true);
|
|
1281
|
+
}
|
|
1282
|
+
if (attrs.get("binary") === true)
|
|
1283
|
+
attrs.set("text", false);
|
|
1284
|
+
if (attrs.size === 0)
|
|
1285
|
+
continue;
|
|
1286
|
+
const basenameOnly = !pattern.includes("/");
|
|
1287
|
+
if (pattern.startsWith("/"))
|
|
1288
|
+
pattern = pattern.slice(1);
|
|
1289
|
+
rules.push({ re: globToRegex(pattern), basenameOnly, attrs });
|
|
1290
|
+
}
|
|
1291
|
+
return rules;
|
|
1292
|
+
}
|
|
1293
|
+
function booleanAttribute(value) {
|
|
1294
|
+
if (value === true || value === "true")
|
|
1295
|
+
return true;
|
|
1296
|
+
if (value === false || value === "false")
|
|
1297
|
+
return false;
|
|
1298
|
+
return void 0;
|
|
1299
|
+
}
|
|
1300
|
+
function linguistAttributesFor(relPath, layers) {
|
|
1301
|
+
const path = posixPath(relPath);
|
|
1302
|
+
const state = /* @__PURE__ */ new Map();
|
|
1303
|
+
const ordered = [...layers].sort((a, b) => depthOf(a.baseRel) - depthOf(b.baseRel));
|
|
1304
|
+
for (const layer of ordered) {
|
|
1305
|
+
let sub;
|
|
1306
|
+
if (layer.baseRel === "")
|
|
1307
|
+
sub = path;
|
|
1308
|
+
else if (path.startsWith(`${layer.baseRel}/`))
|
|
1309
|
+
sub = path.slice(layer.baseRel.length + 1);
|
|
1310
|
+
else
|
|
1311
|
+
continue;
|
|
1312
|
+
const base = posix2.basename(sub);
|
|
1313
|
+
for (const rule of layer.rules) {
|
|
1314
|
+
if (!(rule.basenameOnly ? rule.re.test(base) : rule.re.test(sub)))
|
|
1315
|
+
continue;
|
|
1316
|
+
for (const [name, value] of rule.attrs)
|
|
1317
|
+
state.set(name, value);
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
return {
|
|
1321
|
+
vendored: booleanAttribute(state.get("linguist-vendored")),
|
|
1322
|
+
generated: booleanAttribute(state.get("linguist-generated")),
|
|
1323
|
+
binary: state.get("text") === false
|
|
1324
|
+
};
|
|
1325
|
+
}
|
|
1326
|
+
function depthOf(baseRel) {
|
|
1327
|
+
return baseRel === "" ? 0 : baseRel.split("/").length;
|
|
1328
|
+
}
|
|
1329
|
+
function gitattributesCanReinclude(layers) {
|
|
1330
|
+
return layers.some((layer) => layer.rules.some((rule) => ["linguist-vendored", "linguist-generated"].some((name) => booleanAttribute(rule.attrs.get(name)) === false)));
|
|
1331
|
+
}
|
|
1332
|
+
function classifyForIndex(relPath, options = {}) {
|
|
1333
|
+
if (options.ignoreFileVerdict === "unignored")
|
|
1334
|
+
return { include: true, override: "ignore_file" };
|
|
1335
|
+
if (options.attributes && options.attributes.length > 0) {
|
|
1336
|
+
const attrs = linguistAttributesFor(relPath, options.attributes);
|
|
1337
|
+
if (attrs.vendored === true) {
|
|
1338
|
+
return { include: false, reason: "vendored", rule: ".gitattributes linguist-vendored" };
|
|
1339
|
+
}
|
|
1340
|
+
if (attrs.generated === true) {
|
|
1341
|
+
return { include: false, reason: "generated", rule: ".gitattributes linguist-generated" };
|
|
1342
|
+
}
|
|
1343
|
+
if (attrs.binary)
|
|
1344
|
+
return { include: false, reason: "binary", rule: ".gitattributes binary" };
|
|
1345
|
+
if (attrs.vendored === false || attrs.generated === false) {
|
|
1346
|
+
return { include: true, override: "gitattributes" };
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1349
|
+
const byPath = softDefaultForPath(relPath);
|
|
1350
|
+
if (byPath)
|
|
1351
|
+
return { include: false, ...byPath };
|
|
1352
|
+
if (options.content !== void 0) {
|
|
1353
|
+
const byContent = softDefaultForContent(relPath, options.content);
|
|
1354
|
+
if (byContent)
|
|
1355
|
+
return { include: false, ...byContent };
|
|
1356
|
+
}
|
|
1357
|
+
return { include: true };
|
|
1358
|
+
}
|
|
1359
|
+
var DIRECTORY_RULES, GENERATED_BASENAME_RULES, CHANGELOG_UPPER, CHANGELOG_DOC, BINARY_SNIFF_BYTES, HEADER_LINES, MINIFIED_AVERAGE_LINE_LENGTH, MINIFIED_LONG_LINE_BYTES, DATA_DUMP_INSERT_THRESHOLD, MINIFIABLE_EXTENSION, PROSE_EXTENSION, COMMENT_LINE, GO_GENERATED_HEADER, AT_GENERATED, GENERATION_PHRASE, DO_NOT_EDIT_PHRASE, INSERT_INTO, COPY_FROM_STDIN;
|
|
1360
|
+
var init_indexClassifier = __esm({
|
|
1361
|
+
"packages/shared/dist/indexClassifier.js"() {
|
|
1362
|
+
"use strict";
|
|
1363
|
+
DIRECTORY_RULES = [
|
|
1364
|
+
// R2 - vendored dependencies and archive debris.
|
|
1365
|
+
{
|
|
1366
|
+
reason: "vendored",
|
|
1367
|
+
rule: "vendored directory",
|
|
1368
|
+
re: /(^|\/)(vendors?|Vendor|third[-_]?party|3rd[-_]?party|Godeps|Carthage|jspm_packages|site-packages|dist-packages|__MACOSX)\//
|
|
1369
|
+
},
|
|
1370
|
+
{
|
|
1371
|
+
reason: "vendored",
|
|
1372
|
+
rule: "Yarn release/plugin/cache directory",
|
|
1373
|
+
re: /(^|\/)\.yarn\/(releases|plugins|sdks|versions|unplugged|cache)\//
|
|
1374
|
+
},
|
|
1375
|
+
{ reason: "vendored", rule: "documentation build output", re: /(^|\/)docs?\/_?build\// },
|
|
1376
|
+
// Test fixtures are copies of other code: byte-identical snapshot trees
|
|
1377
|
+
// stole the content-hash dedup slot from the real file and outranked it in
|
|
1378
|
+
// code_search. This folds in the old built-in `/tests/fixtures/*` pattern.
|
|
1379
|
+
{ reason: "vendored", rule: "test fixtures", re: /(^|\/)([Tt]ests?|[Ss]pecs?)\/fixtures\// },
|
|
1380
|
+
{ reason: "vendored", rule: "test fixtures", re: /(^|\/)(testdata|__fixtures__)\// },
|
|
1381
|
+
{ reason: "vendored", rule: "test snapshots", re: /(^|\/)__snapshots__\// },
|
|
1382
|
+
// R3 - generated output directories.
|
|
1383
|
+
{
|
|
1384
|
+
reason: "generated",
|
|
1385
|
+
rule: "generated directory",
|
|
1386
|
+
re: /(^|\/)(__generated__|_generated|generated|autogen)\//
|
|
1387
|
+
}
|
|
1388
|
+
];
|
|
1389
|
+
GENERATED_BASENAME_RULES = [
|
|
1390
|
+
// Infixes: foo.gen.ts, foo.g.dart, foo.pb.go, foo.pb.gw.go, foo.freezed.dart,
|
|
1391
|
+
// Form1.Designer.cs. Case-insensitive: Visual Studio writes `.Designer.cs`.
|
|
1392
|
+
{ rule: "generated infix", re: /\.(gen|g|pb|pb\.gw|freezed|designer)\.[^.]+$/i },
|
|
1393
|
+
// Protocol Buffers / gRPC outputs.
|
|
1394
|
+
{ rule: "protobuf/gRPC output", re: /_pb2(_grpc)?\.pyi?$/ },
|
|
1395
|
+
{ rule: "protobuf/gRPC output", re: /_pb\.(js|ts)$/ },
|
|
1396
|
+
{ rule: "protobuf/gRPC output", re: /_grpc_pb\..+$/ },
|
|
1397
|
+
{ rule: "protobuf/gRPC output", re: /_grpc\.pb\.go$/ },
|
|
1398
|
+
{ rule: "protobuf/gRPC output", re: /\.pb\.(go|cc|h|swift|dart|ts|js)$/ },
|
|
1399
|
+
// SpecFlow, Yarn Plug'n'Play, Delphi type libraries.
|
|
1400
|
+
{ rule: "SpecFlow feature binding", re: /\.feature\.cs$/ },
|
|
1401
|
+
{ rule: "Yarn Plug'n'Play loader", re: /^\.pnp\./ },
|
|
1402
|
+
{ rule: "Delphi type library", re: /_tlb\.pas$/ }
|
|
1403
|
+
];
|
|
1404
|
+
CHANGELOG_UPPER = /^(CHANGELOG|CHANGES|HISTORY|NEWS)(\.[^/]*)?$/;
|
|
1405
|
+
CHANGELOG_DOC = /^(changelog|changes|history|news)(\.(md|markdown|rst|txt|adoc))?$/i;
|
|
1406
|
+
BINARY_SNIFF_BYTES = 8e3;
|
|
1407
|
+
HEADER_LINES = 10;
|
|
1408
|
+
MINIFIED_AVERAGE_LINE_LENGTH = 110;
|
|
1409
|
+
MINIFIED_LONG_LINE_BYTES = 4096;
|
|
1410
|
+
DATA_DUMP_INSERT_THRESHOLD = 50;
|
|
1411
|
+
MINIFIABLE_EXTENSION = /\.(js|mjs|cjs|css)$/i;
|
|
1412
|
+
PROSE_EXTENSION = /\.(md|mdc|mdx|markdown)$/i;
|
|
1413
|
+
COMMENT_LINE = /^\s*(\/\/|\/\*|\*|#|--|;|<!--|%|\(\*|\{-|"""|''')/;
|
|
1414
|
+
GO_GENERATED_HEADER = /^\/\/ Code generated .* DO NOT EDIT\.$/;
|
|
1415
|
+
AT_GENERATED = /@generated\b/;
|
|
1416
|
+
GENERATION_PHRASE = /\b(generated by|auto-generated|autogenerated|this file was generated|code generated)\b/i;
|
|
1417
|
+
DO_NOT_EDIT_PHRASE = /\b(do not edit|do not modify|manual changes will be overwritten)\b/i;
|
|
1418
|
+
INSERT_INTO = /\binsert\s+into\b/gi;
|
|
1419
|
+
COPY_FROM_STDIN = /^\s*COPY\s+[^\n]*\bFROM\s+stdin\b/im;
|
|
1420
|
+
}
|
|
1421
|
+
});
|
|
1422
|
+
|
|
1423
|
+
// packages/shared/dist/fileSelection.js
|
|
1424
|
+
var fileSelection_exports = {};
|
|
1425
|
+
__export(fileSelection_exports, {
|
|
1426
|
+
AUTHORITY_FILE_MATCHERS: () => AUTHORITY_FILE_MATCHERS,
|
|
1427
|
+
BUILT_IN_IGNORE_DIRS: () => BUILT_IN_IGNORE_DIRS,
|
|
1428
|
+
BUILT_IN_IGNORE_FILE_PATTERNS: () => BUILT_IN_IGNORE_FILE_PATTERNS,
|
|
1429
|
+
CMAKE_LISTS_PATH: () => CMAKE_LISTS_PATH,
|
|
1430
|
+
DEFAULT_IGNORE_PATTERNS: () => DEFAULT_IGNORE_PATTERNS,
|
|
1431
|
+
DEFAULT_INCLUDE_EXTENSIONS: () => DEFAULT_INCLUDE_EXTENSIONS,
|
|
1432
|
+
FIXTURE_PATH_RE: () => FIXTURE_PATH_RE,
|
|
1433
|
+
FileSelector: () => FileSelector,
|
|
1434
|
+
XCODE_AUTHORITY_PATH: () => XCODE_AUTHORITY_PATH,
|
|
1435
|
+
enterDirectory: () => enterDirectory,
|
|
1436
|
+
fileOp: () => fileOp,
|
|
1437
|
+
isAuthorityOnlyPath: () => isAuthorityOnlyPath,
|
|
1438
|
+
isCmakeListsPath: () => isCmakeListsPath,
|
|
1439
|
+
isCursorRulePath: () => isCursorRulePath,
|
|
1440
|
+
isFixturePath: () => isFixturePath,
|
|
1441
|
+
isGitBoundary: () => isGitBoundary,
|
|
1442
|
+
isLinkedWorktreeCheckout: () => isLinkedWorktreeCheckout,
|
|
1443
|
+
isProjectBoundary: () => isProjectBoundary,
|
|
1444
|
+
isSecretFile: () => isSecretFile,
|
|
1445
|
+
isXcodeAuthorityPath: () => isXcodeAuthorityPath,
|
|
1446
|
+
languageForExtension: () => languageForExtension,
|
|
1447
|
+
makeIgnoreMatcher: () => makeIgnoreMatcher,
|
|
1448
|
+
markUnreadable: () => markUnreadable,
|
|
1449
|
+
rootProjectId: () => rootProjectId,
|
|
1450
|
+
statTuple: () => statTuple
|
|
1451
|
+
});
|
|
1452
|
+
import { readdir, readFile as readFile2, open, lstat as lstat2, realpath } from "fs/promises";
|
|
1453
|
+
import { constants } from "fs";
|
|
1454
|
+
import { join as join2, relative, extname, sep, resolve, dirname } from "path";
|
|
1455
|
+
function timedOp(op, timeoutMs, label) {
|
|
1456
|
+
let timer;
|
|
1457
|
+
return Promise.race([
|
|
1458
|
+
op,
|
|
1459
|
+
new Promise((_resolve, reject) => {
|
|
1460
|
+
timer = setTimeout(() => reject(new FileOpTimeoutError(label, timeoutMs)), timeoutMs);
|
|
1461
|
+
})
|
|
1462
|
+
]).finally(() => clearTimeout(timer));
|
|
1463
|
+
}
|
|
1464
|
+
async function fileOp(op, label, budgets = {
|
|
1465
|
+
first: FILE_OP_TIMEOUT_MS,
|
|
1466
|
+
retry: FILE_OP_RETRY_TIMEOUT_MS
|
|
1467
|
+
}) {
|
|
1468
|
+
try {
|
|
1469
|
+
return await timedOp(op(), budgets.first, label);
|
|
1470
|
+
} catch (error) {
|
|
1471
|
+
if (!(error instanceof FileOpTimeoutError))
|
|
1472
|
+
throw error;
|
|
1473
|
+
return timedOp(op(), budgets.retry, label);
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1476
|
+
function statTuple(stats) {
|
|
1477
|
+
return { size: stats.size, mtimeMs: stats.mtimeMs, ctimeMs: stats.ctimeMs, ino: stats.ino };
|
|
1478
|
+
}
|
|
1479
|
+
function markUnreadable(walk, path, error) {
|
|
1480
|
+
walk.complete = false;
|
|
1481
|
+
walk.unreadable = (walk.unreadable ?? 0) + 1;
|
|
1482
|
+
walk.unreadableSamples ??= [];
|
|
1483
|
+
if (walk.unreadableSamples.length < UNREADABLE_SAMPLES)
|
|
1484
|
+
walk.unreadableSamples.push({
|
|
1485
|
+
path,
|
|
1486
|
+
reason: error?.code ?? (error instanceof Error ? error.message : "unreadable")
|
|
1487
|
+
});
|
|
1488
|
+
}
|
|
1489
|
+
async function directoryKey(path) {
|
|
1490
|
+
const stats = await lstat2(path, { bigint: true });
|
|
1491
|
+
return stats.ino ? `${stats.dev}:${stats.ino}` : null;
|
|
1492
|
+
}
|
|
1493
|
+
async function enterDirectory(walk, path) {
|
|
1494
|
+
let key;
|
|
1495
|
+
try {
|
|
1496
|
+
key = await directoryKey(path);
|
|
1497
|
+
} catch {
|
|
1498
|
+
key = null;
|
|
1499
|
+
}
|
|
1500
|
+
if (key === null)
|
|
1501
|
+
return () => void 0;
|
|
1502
|
+
walk.ancestors ??= /* @__PURE__ */ new Set();
|
|
1503
|
+
if (walk.ancestors.has(key)) {
|
|
1504
|
+
markUnreadable(walk, path, Object.assign(new Error("directory cycle"), { code: "ELOOP" }));
|
|
1505
|
+
return null;
|
|
1506
|
+
}
|
|
1507
|
+
const entered = key;
|
|
1508
|
+
walk.ancestors.add(entered);
|
|
1509
|
+
return () => {
|
|
1510
|
+
walk.ancestors?.delete(entered);
|
|
1511
|
+
};
|
|
1512
|
+
}
|
|
1513
|
+
function isSecretFile(relPath) {
|
|
1514
|
+
if (!relPath)
|
|
1515
|
+
return false;
|
|
1516
|
+
const segments = relPath.split(sep).join("/").split("/");
|
|
1517
|
+
const base = segments[segments.length - 1] ?? "";
|
|
1518
|
+
if (!base || SECRET_FILE_PLACEHOLDER_BASENAMES.has(base))
|
|
1519
|
+
return false;
|
|
1520
|
+
if (SECRET_FILE_BASENAME_PATTERNS.some((re) => re.test(base)))
|
|
1521
|
+
return true;
|
|
1522
|
+
const parent = segments[segments.length - 2];
|
|
1523
|
+
return base === "credentials" && parent !== void 0 && parent.startsWith(".");
|
|
1524
|
+
}
|
|
1525
|
+
function isCursorRulePath(relPath) {
|
|
1526
|
+
if (!relPath)
|
|
1527
|
+
return false;
|
|
1528
|
+
const posix3 = relPath.split(sep).join("/");
|
|
1529
|
+
return /(^|\/)\.cursorrules$/.test(posix3) || /(^|\/)\.cursor\/rules\/.+\.mdc$/.test(posix3);
|
|
1530
|
+
}
|
|
1531
|
+
function languageForExtension(filePathOrExt) {
|
|
1532
|
+
if (isCmakeListsPath(filePathOrExt))
|
|
1533
|
+
return "cmake";
|
|
1534
|
+
const fromPath = EXTENSION_LANGUAGES[extname(filePathOrExt).toLowerCase()];
|
|
1535
|
+
if (fromPath !== void 0)
|
|
1536
|
+
return fromPath;
|
|
1537
|
+
return EXTENSION_LANGUAGES[filePathOrExt.toLowerCase()] ?? "unknown";
|
|
1538
|
+
}
|
|
1539
|
+
function isAuthorityOnlyPath(relPath) {
|
|
1540
|
+
if (!relPath)
|
|
1541
|
+
return false;
|
|
1542
|
+
const posix3 = relPath.split(sep).join("/");
|
|
1543
|
+
return AUTHORITY_ONLY_PATH_PREDICATES.some((matches) => matches(posix3));
|
|
1544
|
+
}
|
|
1545
|
+
function isFixturePath(path) {
|
|
1546
|
+
return FIXTURE_PATH_RE.test(path);
|
|
1547
|
+
}
|
|
1548
|
+
function makeIgnoreMatcher(extraPatterns = []) {
|
|
1549
|
+
const cleaned = extraPatterns.filter((p) => typeof p === "string" && p.trim().length > 0);
|
|
1550
|
+
const ig = cleaned.length > 0 ? (0, import_ignore.default)().add([...cleaned]) : null;
|
|
1551
|
+
return (relPath) => {
|
|
1552
|
+
if (!relPath)
|
|
1553
|
+
return false;
|
|
1554
|
+
const norm = relPath.split(sep).join("/");
|
|
1555
|
+
if (norm.split("/").some((s) => BUILT_IN_IGNORE_DIR_SET.has(s)))
|
|
1556
|
+
return true;
|
|
1557
|
+
if (isSecretFile(norm))
|
|
1558
|
+
return true;
|
|
1559
|
+
if (ig) {
|
|
1560
|
+
try {
|
|
1561
|
+
return ig.ignores(norm);
|
|
1562
|
+
} catch {
|
|
1563
|
+
return false;
|
|
1564
|
+
}
|
|
1565
|
+
}
|
|
1566
|
+
return false;
|
|
1567
|
+
};
|
|
1568
|
+
}
|
|
1569
|
+
async function isGitBoundary(dirPath) {
|
|
1570
|
+
try {
|
|
1571
|
+
await fileOp(() => lstat2(join2(dirPath, ".git")), `lstat: ${join2(dirPath, ".git")}`);
|
|
1572
|
+
return true;
|
|
1573
|
+
} catch {
|
|
1574
|
+
return false;
|
|
1575
|
+
}
|
|
1576
|
+
}
|
|
1577
|
+
async function isProjectBoundary(dirPath, enclosingProjectId) {
|
|
1578
|
+
if (await lstatWithin(join2(dirPath, ".mnemonik.json"))) {
|
|
1579
|
+
const identity = await fileOp(() => readIdentityFile(dirPath, { configurationOnlyAbsent: true }), `identity read: ${dirPath}`).catch(() => void 0);
|
|
1580
|
+
if (!identity)
|
|
1581
|
+
return true;
|
|
1582
|
+
if (identity.kind === "ok") {
|
|
1583
|
+
if (identity.identity.projectId !== enclosingProjectId)
|
|
1584
|
+
return true;
|
|
1585
|
+
if ((await lstatWithin(join2(dirPath, ".git")))?.isDirectory())
|
|
1586
|
+
return true;
|
|
1587
|
+
} else if (identity.kind !== "absent")
|
|
1588
|
+
return true;
|
|
1589
|
+
}
|
|
1590
|
+
return isLinkedWorktreeCheckout(dirPath);
|
|
1591
|
+
}
|
|
1592
|
+
async function rootProjectId(rootPath) {
|
|
1593
|
+
const identity = await fileOp(() => readIdentityFile(rootPath), `identity read: ${rootPath}`).catch(() => void 0);
|
|
1594
|
+
return identity?.kind === "ok" ? identity.identity.projectId : void 0;
|
|
1595
|
+
}
|
|
1596
|
+
async function isLinkedWorktreeCheckout(dirPath) {
|
|
1597
|
+
const marker = await lstatWithin(join2(dirPath, ".git"));
|
|
1598
|
+
if (!marker?.isFile())
|
|
1599
|
+
return false;
|
|
1600
|
+
try {
|
|
1601
|
+
const pointer = /^gitdir:\s*(.+?)\s*$/im.exec(await fileOp(() => readFile2(join2(dirPath, ".git"), "utf8"), `read: ${join2(dirPath, ".git")}`))?.[1];
|
|
1602
|
+
if (!pointer)
|
|
1603
|
+
return false;
|
|
1604
|
+
const gitDirectory = resolve(dirPath, pointer);
|
|
1605
|
+
if (!await lstatWithin(gitDirectory))
|
|
1606
|
+
return true;
|
|
1607
|
+
return !!await lstatWithin(join2(gitDirectory, "commondir"));
|
|
1608
|
+
} catch {
|
|
1609
|
+
return false;
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1612
|
+
var import_ignore, FILE_OP_TIMEOUT_MS, FILE_OP_RETRY_TIMEOUT_MS, FileOpTimeoutError, UNREADABLE_SAMPLES, BUILT_IN_IGNORE_DIRS, BUILT_IN_IGNORE_FILE_PATTERNS, SECRET_FILE_PLACEHOLDER_BASENAMES, SECRET_FILE_BASENAME_PATTERNS, IGNORE_FILE_NAMES, ATTRIBUTES_FILE_NAME, DEFAULT_INCLUDE_EXTENSIONS, EXTENSION_LANGUAGES, isMigrationSqlAuthorityPath, XCODE_AUTHORITY_PATH, isXcodeAuthorityPath, CMAKE_LISTS_PATH, isCmakeListsPath, AUTHORITY_ONLY_PATH_PREDICATES, AUTHORITY_FILE_MATCHERS, FIXTURE_PATH_RE, BUILT_IN_IGNORE_DIR_SET, lstatWithin, DEFAULT_IGNORE_PATTERNS, FileSelector;
|
|
1613
|
+
var init_fileSelection = __esm({
|
|
1614
|
+
"packages/shared/dist/fileSelection.js"() {
|
|
1615
|
+
"use strict";
|
|
1616
|
+
import_ignore = __toESM(require_ignore(), 1);
|
|
1617
|
+
init_logger();
|
|
1618
|
+
init_protectedPaths();
|
|
1619
|
+
init_projectIdentityFile();
|
|
1620
|
+
init_indexClassifier();
|
|
1621
|
+
FILE_OP_TIMEOUT_MS = 5e3;
|
|
1622
|
+
FILE_OP_RETRY_TIMEOUT_MS = 3e4;
|
|
1623
|
+
FileOpTimeoutError = class extends Error {
|
|
1624
|
+
constructor(label, timeoutMs) {
|
|
1625
|
+
super(`${label} timed out after ${timeoutMs} ms`);
|
|
1626
|
+
this.name = "FileOpTimeoutError";
|
|
1627
|
+
}
|
|
1628
|
+
};
|
|
1629
|
+
UNREADABLE_SAMPLES = 5;
|
|
1630
|
+
BUILT_IN_IGNORE_DIRS = [
|
|
1631
|
+
// JS / web
|
|
1632
|
+
"node_modules",
|
|
1633
|
+
".next",
|
|
1634
|
+
".nuxt",
|
|
1635
|
+
".output",
|
|
1636
|
+
"dist",
|
|
1637
|
+
"build",
|
|
1638
|
+
"coverage",
|
|
1639
|
+
".turbo",
|
|
1640
|
+
".vercel",
|
|
1641
|
+
".svelte-kit",
|
|
1642
|
+
".angular",
|
|
1643
|
+
".astro",
|
|
1644
|
+
".parcel-cache",
|
|
1645
|
+
"bower_components",
|
|
1646
|
+
// Python
|
|
1647
|
+
"venv",
|
|
1648
|
+
".venv",
|
|
1649
|
+
"env",
|
|
1650
|
+
"__pycache__",
|
|
1651
|
+
".tox",
|
|
1652
|
+
".mypy_cache",
|
|
1653
|
+
".pytest_cache",
|
|
1654
|
+
".ruff_cache",
|
|
1655
|
+
// Native mobile - iOS / Android / React Native / Flutter (2026-07-11 Pods incident)
|
|
1656
|
+
"Pods",
|
|
1657
|
+
"DerivedData",
|
|
1658
|
+
".gradle",
|
|
1659
|
+
".expo",
|
|
1660
|
+
".dart_tool",
|
|
1661
|
+
// Infra / cloud
|
|
1662
|
+
".terraform",
|
|
1663
|
+
"cdk.out",
|
|
1664
|
+
".serverless",
|
|
1665
|
+
// Build systems, caches, VCS
|
|
1666
|
+
"target",
|
|
1667
|
+
"bin",
|
|
1668
|
+
"obj",
|
|
1669
|
+
".cache",
|
|
1670
|
+
".git",
|
|
1671
|
+
".svn",
|
|
1672
|
+
".hg",
|
|
1673
|
+
// The coding agent's captured shell environment, written as .sh files.
|
|
1674
|
+
// Harmless while only source extensions were collected; the moment shell
|
|
1675
|
+
// scripts became collectable these scanned as project code, which would put
|
|
1676
|
+
// an agent's own shell state into the user's searchable memory.
|
|
1677
|
+
//
|
|
1678
|
+
// Scoped to this one directory name deliberately. `.claude/` at large holds
|
|
1679
|
+
// real project content - `.claude/rules.md` is indexed on purpose
|
|
1680
|
+
// (tests/CodeScannerIgnorePatterns.test.ts), and only `.claude/worktrees/`
|
|
1681
|
+
// is suppressed, by the generic nested-git-boundary rule.
|
|
1682
|
+
"shell-snapshots"
|
|
1683
|
+
];
|
|
1684
|
+
BUILT_IN_IGNORE_FILE_PATTERNS = [
|
|
1685
|
+
// NOTE: the `.env` family and every other secret-bearing FILE KIND lives in
|
|
1686
|
+
// `isSecretFile` below, not here. This glob engine cannot express negation
|
|
1687
|
+
// (`.env.example` must stay collectable) and its unanchored substring
|
|
1688
|
+
// matching would make `.env.*` also swallow `.environment.ts`.
|
|
1689
|
+
//
|
|
1690
|
+
// `/tests/fixtures/*` used to live here. It is now a soft default in
|
|
1691
|
+
// indexClassifier.ts (test fixtures, R2), which a project can re-include.
|
|
1692
|
+
".DS_Store",
|
|
1693
|
+
"*.log",
|
|
1694
|
+
"*.lock",
|
|
1695
|
+
"package-lock.json",
|
|
1696
|
+
"yarn.lock",
|
|
1697
|
+
"*.min.js",
|
|
1698
|
+
"*.min.css",
|
|
1699
|
+
"*.bundle.js",
|
|
1700
|
+
"*.legacy.js",
|
|
1701
|
+
"*.map"
|
|
1702
|
+
];
|
|
1703
|
+
SECRET_FILE_PLACEHOLDER_BASENAMES = /* @__PURE__ */ new Set([".env.example"]);
|
|
1704
|
+
SECRET_FILE_BASENAME_PATTERNS = [
|
|
1705
|
+
/^\.env(\..+)?$/i,
|
|
1706
|
+
// .env, .env.local, .env.production.local
|
|
1707
|
+
/^id_(?:rsa|dsa|ecdsa|ed25519)/i,
|
|
1708
|
+
// ssh private keys (and .pub siblings)
|
|
1709
|
+
/^\.(?:npmrc|netrc|pgpass)$/i,
|
|
1710
|
+
// registry / ftp / postgres password files
|
|
1711
|
+
/^kubeconfig$/i,
|
|
1712
|
+
/\.(?:pem|key|p12|pfx|keystore|jks|kubeconfig)$/i,
|
|
1713
|
+
// Terraform variable files: `terraform.tfvars` is the conventional home for
|
|
1714
|
+
// provider credentials, and unlike the entries above nothing else was
|
|
1715
|
+
// keeping it out - it is excluded here rather than merely left off
|
|
1716
|
+
// `DEFAULT_INCLUDE_EXTENSIONS`, so a future widening of the allowlist cannot
|
|
1717
|
+
// quietly opt a project into embedding its own cloud keys.
|
|
1718
|
+
/\.tfvars(\.json)?$/i
|
|
1719
|
+
];
|
|
1720
|
+
IGNORE_FILE_NAMES = [".gitignore", ".mnemonikignore"];
|
|
1721
|
+
ATTRIBUTES_FILE_NAME = ".gitattributes";
|
|
1722
|
+
DEFAULT_INCLUDE_EXTENSIONS = [
|
|
1723
|
+
".ts",
|
|
1724
|
+
".tsx",
|
|
1725
|
+
".js",
|
|
1726
|
+
".jsx",
|
|
1727
|
+
".py",
|
|
1728
|
+
".rs",
|
|
1729
|
+
".go",
|
|
1730
|
+
".java",
|
|
1731
|
+
".c",
|
|
1732
|
+
".cpp",
|
|
1733
|
+
".h",
|
|
1734
|
+
".cs",
|
|
1735
|
+
".rb",
|
|
1736
|
+
".php",
|
|
1737
|
+
".swift",
|
|
1738
|
+
".kt",
|
|
1739
|
+
// Shell and Lua are the working languages of whole real projects -
|
|
1740
|
+
// deployment tooling, container entrypoints, imapfilter/nginx/redis
|
|
1741
|
+
// configuration. Omitting them meant such a project indexed ZERO code and
|
|
1742
|
+
// code_search could not answer anything about it, while reporting that as
|
|
1743
|
+
// "no indexed matches" rather than as missing coverage.
|
|
1744
|
+
".sh",
|
|
1745
|
+
".bash",
|
|
1746
|
+
".zsh",
|
|
1747
|
+
".lua",
|
|
1748
|
+
".md",
|
|
1749
|
+
// -- Variants of languages already on the list. `.mjs`/`.cjs`/`.mts`/`.cts`/
|
|
1750
|
+
// `.pyi` inherit their language string from a listed extension, so their
|
|
1751
|
+
// absence was oversight rather than policy. The C++ spellings are a choice,
|
|
1752
|
+
// not an inheritance: `.h` stays 'c' (C headers dominate, and reading a C++
|
|
1753
|
+
// header as C is the safer default), while the unambiguously-C++ spellings
|
|
1754
|
+
// resolve to 'cpp'.
|
|
1755
|
+
".mjs",
|
|
1756
|
+
".cjs",
|
|
1757
|
+
".mts",
|
|
1758
|
+
".cts",
|
|
1759
|
+
".pyi",
|
|
1760
|
+
".hpp",
|
|
1761
|
+
".hh",
|
|
1762
|
+
".hxx",
|
|
1763
|
+
".cc",
|
|
1764
|
+
".cxx",
|
|
1765
|
+
".kts",
|
|
1766
|
+
// -- Languages the scanner could not read at all.
|
|
1767
|
+
".dart",
|
|
1768
|
+
".m",
|
|
1769
|
+
".mm",
|
|
1770
|
+
".scala",
|
|
1771
|
+
".sc",
|
|
1772
|
+
".ex",
|
|
1773
|
+
".exs",
|
|
1774
|
+
".erl",
|
|
1775
|
+
".hrl",
|
|
1776
|
+
".hs",
|
|
1777
|
+
".jl",
|
|
1778
|
+
".ml",
|
|
1779
|
+
".mli",
|
|
1780
|
+
".clj",
|
|
1781
|
+
".cljs",
|
|
1782
|
+
".cljc",
|
|
1783
|
+
".groovy",
|
|
1784
|
+
".gradle",
|
|
1785
|
+
".ps1",
|
|
1786
|
+
".psm1",
|
|
1787
|
+
".pl",
|
|
1788
|
+
".pm",
|
|
1789
|
+
".r",
|
|
1790
|
+
".sol",
|
|
1791
|
+
".zig",
|
|
1792
|
+
".vue",
|
|
1793
|
+
".svelte",
|
|
1794
|
+
// -- Infrastructure and schema DSLs, where real logic lives and where "how
|
|
1795
|
+
// is this deployed?" and "what does this table hold?" go unanswered today.
|
|
1796
|
+
".tf",
|
|
1797
|
+
".sql",
|
|
1798
|
+
".proto",
|
|
1799
|
+
".graphql",
|
|
1800
|
+
".gql",
|
|
1801
|
+
".cmake",
|
|
1802
|
+
".nix",
|
|
1803
|
+
".bzl"
|
|
1804
|
+
];
|
|
1805
|
+
EXTENSION_LANGUAGES = {
|
|
1806
|
+
".ts": "typescript",
|
|
1807
|
+
".tsx": "typescript",
|
|
1808
|
+
".mts": "typescript",
|
|
1809
|
+
".cts": "typescript",
|
|
1810
|
+
".js": "javascript",
|
|
1811
|
+
".jsx": "javascript",
|
|
1812
|
+
".mjs": "javascript",
|
|
1813
|
+
".cjs": "javascript",
|
|
1814
|
+
".py": "python",
|
|
1815
|
+
".pyi": "python",
|
|
1816
|
+
".rs": "rust",
|
|
1817
|
+
".go": "go",
|
|
1818
|
+
".java": "java",
|
|
1819
|
+
".c": "c",
|
|
1820
|
+
".h": "c",
|
|
1821
|
+
".cpp": "cpp",
|
|
1822
|
+
".cc": "cpp",
|
|
1823
|
+
".cxx": "cpp",
|
|
1824
|
+
".hpp": "cpp",
|
|
1825
|
+
".hh": "cpp",
|
|
1826
|
+
".hxx": "cpp",
|
|
1827
|
+
".cs": "csharp",
|
|
1828
|
+
".rb": "ruby",
|
|
1829
|
+
".php": "php",
|
|
1830
|
+
".swift": "swift",
|
|
1831
|
+
".kt": "kotlin",
|
|
1832
|
+
".kts": "kotlin",
|
|
1833
|
+
".sh": "shell",
|
|
1834
|
+
".bash": "shell",
|
|
1835
|
+
".zsh": "shell",
|
|
1836
|
+
".lua": "lua",
|
|
1837
|
+
".md": "markdown",
|
|
1838
|
+
".dart": "dart",
|
|
1839
|
+
".m": "objc",
|
|
1840
|
+
".mm": "objc",
|
|
1841
|
+
".scala": "scala",
|
|
1842
|
+
".sc": "scala",
|
|
1843
|
+
".ex": "elixir",
|
|
1844
|
+
".exs": "elixir",
|
|
1845
|
+
".erl": "erlang",
|
|
1846
|
+
".hrl": "erlang",
|
|
1847
|
+
".hs": "haskell",
|
|
1848
|
+
".jl": "julia",
|
|
1849
|
+
".ml": "ocaml",
|
|
1850
|
+
".mli": "ocaml",
|
|
1851
|
+
".clj": "clojure",
|
|
1852
|
+
".cljs": "clojure",
|
|
1853
|
+
".cljc": "clojure",
|
|
1854
|
+
".groovy": "groovy",
|
|
1855
|
+
".gradle": "groovy",
|
|
1856
|
+
".ps1": "powershell",
|
|
1857
|
+
".psm1": "powershell",
|
|
1858
|
+
".pl": "perl",
|
|
1859
|
+
".pm": "perl",
|
|
1860
|
+
".r": "r",
|
|
1861
|
+
".sol": "solidity",
|
|
1862
|
+
".zig": "zig",
|
|
1863
|
+
".vue": "vue",
|
|
1864
|
+
".svelte": "svelte",
|
|
1865
|
+
".tf": "terraform",
|
|
1866
|
+
".sql": "sql",
|
|
1867
|
+
".proto": "protobuf",
|
|
1868
|
+
".graphql": "graphql",
|
|
1869
|
+
".gql": "graphql",
|
|
1870
|
+
".cmake": "cmake",
|
|
1871
|
+
".nix": "nix",
|
|
1872
|
+
".bzl": "starlark"
|
|
1873
|
+
};
|
|
1874
|
+
isMigrationSqlAuthorityPath = (posixRelPath) => /^migrations\/.*\.sql$/.test(posixRelPath);
|
|
1875
|
+
XCODE_AUTHORITY_PATH = /(?:^|\/)[^/]+\.xcodeproj\/project\.pbxproj$|\.xcconfig$/;
|
|
1876
|
+
isXcodeAuthorityPath = (path) => XCODE_AUTHORITY_PATH.test(path);
|
|
1877
|
+
CMAKE_LISTS_PATH = /(?:^|\/)CMakeLists\.txt$/;
|
|
1878
|
+
isCmakeListsPath = (path) => CMAKE_LISTS_PATH.test(path);
|
|
1879
|
+
AUTHORITY_ONLY_PATH_PREDICATES = [
|
|
1880
|
+
isMigrationSqlAuthorityPath,
|
|
1881
|
+
isXcodeAuthorityPath,
|
|
1882
|
+
isCmakeListsPath
|
|
1883
|
+
];
|
|
1884
|
+
AUTHORITY_FILE_MATCHERS = [
|
|
1885
|
+
(p) => p === "package.json",
|
|
1886
|
+
(p) => /^(packages|apps|services|tools)\/[^/]+\/package\.json$/.test(p),
|
|
1887
|
+
(p) => p === "tsconfig.json" || /^tsconfig\.[^/]+\.json$/.test(p),
|
|
1888
|
+
(p) => p === "pyproject.toml",
|
|
1889
|
+
(p) => p === "requirements.txt",
|
|
1890
|
+
(p) => p === "setup.py",
|
|
1891
|
+
(p) => p === "Cargo.toml",
|
|
1892
|
+
(p) => p === "Gemfile",
|
|
1893
|
+
(p) => p === "Makefile",
|
|
1894
|
+
(p) => p === ".env.example",
|
|
1895
|
+
(p) => /^\.github\/workflows\/[^/]+\.(yml|yaml)$/.test(p),
|
|
1896
|
+
// SQL migrations: the schema_columns authority extractor reads every .sql
|
|
1897
|
+
// under `migrations/` (listFiles('migrations/') -> LIKE 'migrations/%' then
|
|
1898
|
+
// .endsWith('.sql')). Without collecting these, that authority is empty and
|
|
1899
|
+
// every schema_table_enumeration claim falls to unverifiable. Shared with
|
|
1900
|
+
// `AUTHORITY_ONLY_PATH_PREDICATES` by reference, not by a copied regex, so
|
|
1901
|
+
// collection and the chunking exclusion cannot disagree.
|
|
1902
|
+
isMigrationSqlAuthorityPath,
|
|
1903
|
+
isXcodeAuthorityPath,
|
|
1904
|
+
isCmakeListsPath
|
|
1905
|
+
];
|
|
1906
|
+
FIXTURE_PATH_RE = /(^|\/)tests\/fixtures\//;
|
|
1907
|
+
BUILT_IN_IGNORE_DIR_SET = new Set(BUILT_IN_IGNORE_DIRS);
|
|
1908
|
+
lstatWithin = (path) => fileOp(() => lstat2(path), `lstat: ${path}`).catch(() => void 0);
|
|
1909
|
+
DEFAULT_IGNORE_PATTERNS = [
|
|
1910
|
+
...BUILT_IN_IGNORE_DIRS,
|
|
1911
|
+
...BUILT_IN_IGNORE_FILE_PATTERNS
|
|
1912
|
+
];
|
|
1913
|
+
FileSelector = class _FileSelector {
|
|
1914
|
+
selection;
|
|
1915
|
+
protectedPaths;
|
|
1916
|
+
/** Cumulative observations since this scanner started, including repeated walks. */
|
|
1917
|
+
skipReasons = { symlink_skipped: 0 };
|
|
1918
|
+
skipSymlink(stats) {
|
|
1919
|
+
if (!stats.isSymbolicLink())
|
|
1920
|
+
return false;
|
|
1921
|
+
this.skipReasons.symlink_skipped++;
|
|
1922
|
+
return true;
|
|
1923
|
+
}
|
|
1924
|
+
/**
|
|
1925
|
+
* `includeExtensions` folded to lowercase for matching. The gate used to
|
|
1926
|
+
* compare `extname()` verbatim while `detectLanguage` lowercased, so a
|
|
1927
|
+
* project spelling its files the canonical way - `.R` for R, `.SQL`/`.PS1`
|
|
1928
|
+
* on Windows, `.C`/`.H` in older C trees - indexed zero of them and nothing
|
|
1929
|
+
* said why.
|
|
1930
|
+
*/
|
|
1931
|
+
includeExtensionSet;
|
|
1932
|
+
constructor(options = {}) {
|
|
1933
|
+
this.selection = {
|
|
1934
|
+
ignorePatterns: [...DEFAULT_IGNORE_PATTERNS],
|
|
1935
|
+
includeExtensions: [...DEFAULT_INCLUDE_EXTENSIONS],
|
|
1936
|
+
protectedPaths: [],
|
|
1937
|
+
...options
|
|
1938
|
+
};
|
|
1939
|
+
this.protectedPaths = [
|
|
1940
|
+
.../* @__PURE__ */ new Set([...protectedLocalPaths(), ...options.protectedPaths ?? []])
|
|
1941
|
+
];
|
|
1942
|
+
this.includeExtensionSet = new Set(this.selection.includeExtensions.map((ext) => ext.toLowerCase()));
|
|
1943
|
+
}
|
|
1944
|
+
isProtected(path) {
|
|
1945
|
+
return isProtectedLocalPath(path, this.protectedPaths);
|
|
1946
|
+
}
|
|
1947
|
+
async isProtectedRead(path) {
|
|
1948
|
+
if (this.isProtected(path))
|
|
1949
|
+
return true;
|
|
1950
|
+
return isProtectedLocalPath(await realpath(path), this.protectedPaths);
|
|
1951
|
+
}
|
|
1952
|
+
/**
|
|
1953
|
+
* The chunkable-file gate, shared by every walker and by the explicit
|
|
1954
|
+
* file-list path so all three agree on what exists. `relPath` is the path
|
|
1955
|
+
* relative to the scan root (OS-native separators accepted).
|
|
1956
|
+
*/
|
|
1957
|
+
isChunkable(absOrRelPath, relPath) {
|
|
1958
|
+
if (!this.includeExtensionSet.has(extname(absOrRelPath).toLowerCase()) && !isCursorRulePath(relPath)) {
|
|
1959
|
+
return false;
|
|
1960
|
+
}
|
|
1961
|
+
if (isAuthorityOnlyPath(relPath)) {
|
|
1962
|
+
debug("Skipping chunking for authority-collected path", { relPath });
|
|
1963
|
+
return false;
|
|
1964
|
+
}
|
|
1965
|
+
return true;
|
|
1966
|
+
}
|
|
1967
|
+
/**
|
|
1968
|
+
* Read `.gitignore` + `.mnemonikignore` + `.gitattributes` in `absDir` and
|
|
1969
|
+
* compile them into one layer (the two ignore files are unioned -
|
|
1970
|
+
* `.mnemonikignore` is just an additional gitignore-syntax exclusion list).
|
|
1971
|
+
* Returns null when none of the three exists. FAIL-CLOSED: a non-ENOENT read
|
|
1972
|
+
* error (EISDIR, EACCES, timeout) latches `walk.complete = false` so callers
|
|
1973
|
+
* deriving *removals* from the walk treat it as untrustworthy rather than
|
|
1974
|
+
* silently ignoring nothing.
|
|
1975
|
+
*/
|
|
1976
|
+
async loadIgnoreLayer(absDir, baseRel, walk, rootPath) {
|
|
1977
|
+
if (this.isProtected(absDir))
|
|
1978
|
+
return null;
|
|
1979
|
+
const read = async (name) => {
|
|
1980
|
+
const filePath = join2(absDir, name);
|
|
1981
|
+
try {
|
|
1982
|
+
const content = await fileOp(() => this.readConfined(filePath, rootPath), `readFile: ${filePath}`);
|
|
1983
|
+
return content.split(/\r?\n/);
|
|
1984
|
+
} catch (err) {
|
|
1985
|
+
if (err.message === "symlink_skipped")
|
|
1986
|
+
return null;
|
|
1987
|
+
if (err?.code !== "ENOENT") {
|
|
1988
|
+
markUnreadable(walk, filePath, err);
|
|
1989
|
+
debug("Rule-file read failed (fail-closed)", { path: filePath, err });
|
|
1990
|
+
}
|
|
1991
|
+
return null;
|
|
1992
|
+
}
|
|
1993
|
+
};
|
|
1994
|
+
const patterns = [];
|
|
1995
|
+
for (const name of IGNORE_FILE_NAMES) {
|
|
1996
|
+
const lines = await read(name);
|
|
1997
|
+
if (lines)
|
|
1998
|
+
patterns.push(...lines);
|
|
1999
|
+
}
|
|
2000
|
+
const attributeLines = await read(ATTRIBUTES_FILE_NAME) ?? [];
|
|
2001
|
+
if (patterns.length > 0 || attributeLines.length > 0) {
|
|
2002
|
+
walk.rules?.set(baseRel, { ignore: patterns, attributes: attributeLines });
|
|
2003
|
+
}
|
|
2004
|
+
const attributes = attributeLines.length > 0 ? parseGitattributes(attributeLines.join("\n")) : [];
|
|
2005
|
+
if (patterns.length === 0 && attributes.length === 0)
|
|
2006
|
+
return null;
|
|
2007
|
+
return {
|
|
2008
|
+
baseRel,
|
|
2009
|
+
ig: patterns.length > 0 ? (0, import_ignore.default)().add(patterns) : null,
|
|
2010
|
+
hasNegation: patterns.some((line) => line.trimStart().startsWith("!")),
|
|
2011
|
+
attributes: attributes.length > 0 ? attributes : null
|
|
2012
|
+
};
|
|
2013
|
+
}
|
|
2014
|
+
/**
|
|
2015
|
+
* What the .gitignore / .mnemonikignore layers in `stack` say about
|
|
2016
|
+
* `entryRel` (relative to the scan root). Tested deepest-first: the first
|
|
2017
|
+
* layer that explicitly ignores OR re-includes (via a `!negation`) wins,
|
|
2018
|
+
* matching git's rule that a deeper `.gitignore` overrides a shallower one.
|
|
2019
|
+
* Directories are tested with a trailing slash so `foo/` dir-only rules
|
|
2020
|
+
* match the directory itself. `'unignored'` means a negation matched this
|
|
2021
|
+
* path itself; a re-included parent directory leaves its files `'unmatched'`.
|
|
2022
|
+
*/
|
|
2023
|
+
ignoreVerdict(entryRel, isDir, stack) {
|
|
2024
|
+
for (let i = stack.length - 1; i >= 0; i--) {
|
|
2025
|
+
const layer = stack[i];
|
|
2026
|
+
if (!layer?.ig)
|
|
2027
|
+
continue;
|
|
2028
|
+
const { baseRel, ig } = layer;
|
|
2029
|
+
let sub;
|
|
2030
|
+
if (baseRel === "") {
|
|
2031
|
+
sub = entryRel;
|
|
2032
|
+
} else if (entryRel === baseRel || !entryRel.startsWith(baseRel + "/")) {
|
|
2033
|
+
continue;
|
|
2034
|
+
} else {
|
|
2035
|
+
sub = entryRel.slice(baseRel.length + 1);
|
|
2036
|
+
}
|
|
2037
|
+
if (!sub)
|
|
2038
|
+
continue;
|
|
2039
|
+
const res = ig.test(isDir ? sub + "/" : sub);
|
|
2040
|
+
if (res.ignored)
|
|
2041
|
+
return "ignored";
|
|
2042
|
+
if (res.unignored)
|
|
2043
|
+
return "unignored";
|
|
2044
|
+
}
|
|
2045
|
+
return "unmatched";
|
|
2046
|
+
}
|
|
2047
|
+
ignoredByStack(entryRel, isDir, stack) {
|
|
2048
|
+
return this.ignoreVerdict(entryRel, isDir, stack) === "ignored";
|
|
2049
|
+
}
|
|
2050
|
+
static attributeLayers(stack) {
|
|
2051
|
+
return stack.flatMap((layer) => layer.attributes ? [{ baseRel: layer.baseRel, rules: layer.attributes }] : []);
|
|
2052
|
+
}
|
|
2053
|
+
/**
|
|
2054
|
+
* Skip this directory? User-ignored directories are skipped as before. A
|
|
2055
|
+
* directory a soft default excludes (vendor/, generated/, ...) is pruned too -
|
|
2056
|
+
* nothing under it can be indexed - UNLESS a rule above it could re-include
|
|
2057
|
+
* something inside it: a `!pattern` or a `-linguist-*` attribute. Then the
|
|
2058
|
+
* walk descends and judges each file, so the override works. Rule files
|
|
2059
|
+
* INSIDE a pruned directory are never read: a vendored library's own
|
|
2060
|
+
* .gitignore negations are not the project's intent.
|
|
2061
|
+
*/
|
|
2062
|
+
skipDirectory(relPath, stack) {
|
|
2063
|
+
const posixRel = relPath.split(sep).join("/");
|
|
2064
|
+
const verdict = this.ignoreVerdict(posixRel, true, stack);
|
|
2065
|
+
if (verdict === "ignored")
|
|
2066
|
+
return true;
|
|
2067
|
+
if (verdict === "unignored")
|
|
2068
|
+
return false;
|
|
2069
|
+
if (!softDefaultForDirectory(posixRel))
|
|
2070
|
+
return false;
|
|
2071
|
+
const canReinclude = stack.some((layer) => layer.hasNegation) || gitattributesCanReinclude(_FileSelector.attributeLayers(stack));
|
|
2072
|
+
return !canReinclude;
|
|
2073
|
+
}
|
|
2074
|
+
/**
|
|
2075
|
+
* The index decision for a file from its path and the rule stack: `null`
|
|
2076
|
+
* when a user ignore rule excludes it, otherwise the classifier's answer
|
|
2077
|
+
* (precedence in indexClassifier.ts). Content rules are the caller's next
|
|
2078
|
+
* step for an included path without an `override`.
|
|
2079
|
+
*/
|
|
2080
|
+
classifyEntry(relPath, stack) {
|
|
2081
|
+
const posixRel = relPath.split(sep).join("/");
|
|
2082
|
+
const verdict = this.ignoreVerdict(posixRel, false, stack);
|
|
2083
|
+
if (verdict === "ignored")
|
|
2084
|
+
return null;
|
|
2085
|
+
return classifyForIndex(posixRel, {
|
|
2086
|
+
ignoreFileVerdict: verdict,
|
|
2087
|
+
attributes: _FileSelector.attributeLayers(stack)
|
|
2088
|
+
});
|
|
2089
|
+
}
|
|
2090
|
+
/**
|
|
2091
|
+
* Reconstruct the root->parent ignore-layer stack for a single file, for the
|
|
2092
|
+
* incremental `scanFiles` path (which has a flat file list, not a recursive
|
|
2093
|
+
* walk). Layers are memoized per directory in `cache` across a batch.
|
|
2094
|
+
*/
|
|
2095
|
+
async buildIgnoreStackForFile(fileRel, rootPath, cache, walk) {
|
|
2096
|
+
const dirs = [""];
|
|
2097
|
+
let acc = "";
|
|
2098
|
+
for (const seg of fileRel.split("/").slice(0, -1)) {
|
|
2099
|
+
acc = acc === "" ? seg : `${acc}/${seg}`;
|
|
2100
|
+
dirs.push(acc);
|
|
2101
|
+
}
|
|
2102
|
+
const stack = [];
|
|
2103
|
+
for (const dir of dirs) {
|
|
2104
|
+
let layer = cache.get(dir);
|
|
2105
|
+
if (layer === void 0) {
|
|
2106
|
+
layer = await this.loadIgnoreLayer(dir === "" ? rootPath : join2(rootPath, dir), dir, walk, rootPath);
|
|
2107
|
+
cache.set(dir, layer);
|
|
2108
|
+
}
|
|
2109
|
+
if (layer)
|
|
2110
|
+
stack.push(layer);
|
|
2111
|
+
}
|
|
2112
|
+
return stack;
|
|
2113
|
+
}
|
|
2114
|
+
/**
|
|
2115
|
+
* A predicate for walkers outside this class (the daemon's file watcher):
|
|
2116
|
+
* true when the walk would descend into `absDir`, by the same rules
|
|
2117
|
+
* `listFiles` applies - user ignore files and soft-default directories,
|
|
2118
|
+
* with their overrides. Rule files are read once per directory for the life
|
|
2119
|
+
* of the returned predicate; take a fresh one after a rule file changes.
|
|
2120
|
+
* Directories outside `rootPath`, and the root itself, always pass.
|
|
2121
|
+
*/
|
|
2122
|
+
directoryFilter(rootPath) {
|
|
2123
|
+
const cache = /* @__PURE__ */ new Map();
|
|
2124
|
+
return async (absDir) => {
|
|
2125
|
+
try {
|
|
2126
|
+
const root = await this.canonicalRoot(rootPath);
|
|
2127
|
+
const rel = relative(root, absDir).split(sep).join("/");
|
|
2128
|
+
if (!rel || rel.startsWith(".."))
|
|
2129
|
+
return true;
|
|
2130
|
+
const stack = await this.buildIgnoreStackForFile(rel, root, cache, { complete: true });
|
|
2131
|
+
return !this.skipDirectory(rel, stack);
|
|
2132
|
+
} catch {
|
|
2133
|
+
return true;
|
|
2134
|
+
}
|
|
2135
|
+
};
|
|
2136
|
+
}
|
|
2137
|
+
/**
|
|
2138
|
+
* True when any directory strictly between `rootPath` and `filePath`
|
|
2139
|
+
* starts a subtree this project does not index (see isProjectBoundary). Paths that don't
|
|
2140
|
+
* resolve under `rootPath` are not judged here (return false) - the
|
|
2141
|
+
* server's path validation handles escapes.
|
|
2142
|
+
*/
|
|
2143
|
+
async insideNestedGitBoundary(filePath, rootPath) {
|
|
2144
|
+
const rel = relative(rootPath, filePath);
|
|
2145
|
+
if (!rel || rel.startsWith(".."))
|
|
2146
|
+
return false;
|
|
2147
|
+
const segments = rel.split(sep);
|
|
2148
|
+
let current = rootPath;
|
|
2149
|
+
for (let i = 0; i < segments.length - 1; i++) {
|
|
2150
|
+
const segment = segments[i];
|
|
2151
|
+
if (segment === void 0)
|
|
2152
|
+
continue;
|
|
2153
|
+
current = join2(current, segment);
|
|
2154
|
+
if (await this.isBoundary(current, rootPath))
|
|
2155
|
+
return true;
|
|
2156
|
+
}
|
|
2157
|
+
return false;
|
|
2158
|
+
}
|
|
2159
|
+
rootProjectIds = /* @__PURE__ */ new Map();
|
|
2160
|
+
/**
|
|
2161
|
+
* isProjectBoundary, with the scan root's own project as the enclosing one.
|
|
2162
|
+
* The root's file is read at most once a minute: a walk tests many folders,
|
|
2163
|
+
* and a project relinked since is seen on the next scan.
|
|
2164
|
+
*/
|
|
2165
|
+
async isBoundary(dirPath, rootPath) {
|
|
2166
|
+
let cached = this.rootProjectIds.get(rootPath);
|
|
2167
|
+
if (!cached || Date.now() - cached.at > 6e4) {
|
|
2168
|
+
cached = { at: Date.now(), projectId: rootProjectId(rootPath) };
|
|
2169
|
+
this.rootProjectIds.set(rootPath, cached);
|
|
2170
|
+
}
|
|
2171
|
+
return isProjectBoundary(dirPath, await cached.projectId);
|
|
2172
|
+
}
|
|
2173
|
+
canonicalRoots = /* @__PURE__ */ new Map();
|
|
2174
|
+
async canonicalRoot(root) {
|
|
2175
|
+
let canonical = this.canonicalRoots.get(root);
|
|
2176
|
+
if (!canonical) {
|
|
2177
|
+
canonical = await realpath(root);
|
|
2178
|
+
this.canonicalRoots.set(root, canonical);
|
|
2179
|
+
}
|
|
2180
|
+
return canonical;
|
|
2181
|
+
}
|
|
2182
|
+
static containsCanonical(candidate, root, paths = { sep, resolve }, caseInsensitive = process.platform === "win32") {
|
|
2183
|
+
candidate = paths.resolve(candidate);
|
|
2184
|
+
root = paths.resolve(root);
|
|
2185
|
+
if (caseInsensitive) {
|
|
2186
|
+
candidate = candidate.toLowerCase();
|
|
2187
|
+
root = root.toLowerCase();
|
|
2188
|
+
}
|
|
2189
|
+
return candidate === root || candidate.startsWith(root.endsWith(paths.sep) ? root : root + paths.sep);
|
|
2190
|
+
}
|
|
2191
|
+
async confineSymlink(fullPath, canonicalRoot) {
|
|
2192
|
+
let path = resolve(fullPath);
|
|
2193
|
+
if (!_FileSelector.containsCanonical(path, canonicalRoot))
|
|
2194
|
+
return false;
|
|
2195
|
+
for (; ; ) {
|
|
2196
|
+
if (this.skipSymlink(await lstat2(path)))
|
|
2197
|
+
throw new Error("symlink_skipped");
|
|
2198
|
+
if (_FileSelector.containsCanonical(canonicalRoot, path))
|
|
2199
|
+
break;
|
|
2200
|
+
path = dirname(path);
|
|
2201
|
+
}
|
|
2202
|
+
return _FileSelector.containsCanonical(await realpath(fullPath), canonicalRoot);
|
|
2203
|
+
}
|
|
2204
|
+
async readConfined(fullPath, canonicalRoot, enumerated) {
|
|
2205
|
+
const expected = enumerated ?? await lstat2(fullPath);
|
|
2206
|
+
if (this.skipSymlink(expected))
|
|
2207
|
+
throw new Error("symlink_skipped");
|
|
2208
|
+
if (!expected.isFile())
|
|
2209
|
+
throw new Error("scanner_not_regular");
|
|
2210
|
+
if (!await this.confineSymlink(fullPath, canonicalRoot))
|
|
2211
|
+
throw new Error("scanner_containment");
|
|
2212
|
+
const canonical = await realpath(fullPath);
|
|
2213
|
+
if (!_FileSelector.containsCanonical(canonical, canonicalRoot) || await this.isProtectedRead(canonical))
|
|
2214
|
+
throw new Error("scanner_containment");
|
|
2215
|
+
const flags = constants.O_RDONLY | (constants.O_NONBLOCK ?? 0) | (process.platform === "win32" ? 0 : constants.O_NOFOLLOW ?? 0);
|
|
2216
|
+
const handle = await open(fullPath, flags).catch((error) => {
|
|
2217
|
+
if (error.code === "ELOOP") {
|
|
2218
|
+
this.skipReasons.symlink_skipped++;
|
|
2219
|
+
throw new Error("symlink_skipped");
|
|
2220
|
+
}
|
|
2221
|
+
throw error;
|
|
2222
|
+
});
|
|
2223
|
+
try {
|
|
2224
|
+
const actual = await handle.stat();
|
|
2225
|
+
if (!actual.isFile())
|
|
2226
|
+
throw new Error("scanner_not_regular");
|
|
2227
|
+
if (actual.dev !== expected.dev || actual.ino !== expected.ino)
|
|
2228
|
+
throw new Error("scanner_identity_changed");
|
|
2229
|
+
if (process.platform === "win32" && this.skipSymlink(await lstat2(fullPath)))
|
|
2230
|
+
throw new Error("symlink_skipped");
|
|
2231
|
+
if (process.platform === "linux" && await realpath(`/proc/self/fd/${handle.fd}`) !== canonical)
|
|
2232
|
+
throw new Error("scanner_containment");
|
|
2233
|
+
return await handle.readFile("utf-8");
|
|
2234
|
+
} finally {
|
|
2235
|
+
await handle.close();
|
|
2236
|
+
}
|
|
2237
|
+
}
|
|
2238
|
+
/**
|
|
2239
|
+
* Enumerate scan-eligible relative file paths under `rootPath` without
|
|
2240
|
+
* reading or chunking content. Same walk + ignorePatterns + extension
|
|
2241
|
+
* filter as `scanDirectory`, but bounded to O(file count) directory ops
|
|
2242
|
+
* - cheap enough to call on every periodic reconcile tick.
|
|
2243
|
+
*
|
|
2244
|
+
* Returns relative paths normalized against `rootPath`, matching the
|
|
2245
|
+
* shape the server stores in `memories.metadata->>'filePath'`. Use this
|
|
2246
|
+
* for the scanner reconciliation channel (`POST /api/v1/scan/reconcile`).
|
|
2247
|
+
*
|
|
2248
|
+
* Defensive: skips any path whose `relative()` result contains a `..`
|
|
2249
|
+
* traversal segment (can happen when a symlink resolves under root but
|
|
2250
|
+
* the readdir entry path doesn't normalize cleanly). The server's
|
|
2251
|
+
* `safeScanPath` rejects such paths; filtering here keeps a single
|
|
2252
|
+
* malformed entry from failing the entire reconcile push.
|
|
2253
|
+
*/
|
|
2254
|
+
async listFiles(rootPath) {
|
|
2255
|
+
const { paths } = await this.listFilesWithStatus(rootPath);
|
|
2256
|
+
return paths;
|
|
2257
|
+
}
|
|
2258
|
+
/**
|
|
2259
|
+
* Completeness-aware variant of `listFiles`. `complete === false` means
|
|
2260
|
+
* an fs error truncated part of the walk - the returned paths may be
|
|
2261
|
+
* missing files that still exist on disk. The daemon's reconcile channel
|
|
2262
|
+
* MUST NOT derive removedFiles from an incomplete inventory (a truncated
|
|
2263
|
+
* walk otherwise reads as mass deletion and deprecates live memories).
|
|
2264
|
+
*/
|
|
2265
|
+
async listFilesWithStatus(rootPath) {
|
|
2266
|
+
rootPath = await this.canonicalRoot(rootPath);
|
|
2267
|
+
const paths = [];
|
|
2268
|
+
const overridden = /* @__PURE__ */ new Set();
|
|
2269
|
+
const stats = /* @__PURE__ */ new Map();
|
|
2270
|
+
const walk = { complete: true, rules: /* @__PURE__ */ new Map() };
|
|
2271
|
+
await this.traversePaths(rootPath, rootPath, paths, 0, walk, [], overridden, stats);
|
|
2272
|
+
if (walk.complete && walk.rules)
|
|
2273
|
+
this.ruleFilesByRoot.set(rootPath, walk.rules);
|
|
2274
|
+
return {
|
|
2275
|
+
paths: paths.filter((p) => !/(^|[/\\])\.\.([/\\]|$)/.test(p)),
|
|
2276
|
+
complete: walk.complete,
|
|
2277
|
+
overridden,
|
|
2278
|
+
unreadable: walk.unreadable ?? 0,
|
|
2279
|
+
unreadableSamples: walk.unreadableSamples ?? [],
|
|
2280
|
+
stats
|
|
2281
|
+
};
|
|
2282
|
+
}
|
|
2283
|
+
/**
|
|
2284
|
+
* The rule files the last complete `listFilesWithStatus` walk of `rootPath`
|
|
2285
|
+
* applied, by directory ('' = root). Null before the first complete walk.
|
|
2286
|
+
* The daemon uploads these lines so the server judges paths as it did.
|
|
2287
|
+
*/
|
|
2288
|
+
async indexRuleFiles(rootPath) {
|
|
2289
|
+
return this.ruleFilesByRoot.get(await this.canonicalRoot(rootPath)) ?? null;
|
|
2290
|
+
}
|
|
2291
|
+
ruleFilesByRoot = /* @__PURE__ */ new Map();
|
|
2292
|
+
async traversePaths(currentPath, rootPath, out, depth, walk, stack = [], overridden, statsOut) {
|
|
2293
|
+
if (this.isProtected(currentPath))
|
|
2294
|
+
return;
|
|
2295
|
+
let entries;
|
|
2296
|
+
try {
|
|
2297
|
+
if (!await this.confineSymlink(currentPath, rootPath))
|
|
2298
|
+
throw new Error("scanner_containment");
|
|
2299
|
+
if (depth === 0 && !await enterDirectory(walk, currentPath))
|
|
2300
|
+
return;
|
|
2301
|
+
entries = await fileOp(() => readdir(currentPath), `readdir: ${currentPath}`);
|
|
2302
|
+
} catch (error) {
|
|
2303
|
+
markUnreadable(walk, currentPath, error);
|
|
2304
|
+
debug("Error traversing directory (listFiles)", { path: currentPath, error });
|
|
2305
|
+
return;
|
|
2306
|
+
}
|
|
2307
|
+
const dirRel = currentPath === rootPath ? "" : relative(rootPath, currentPath).split(sep).join("/");
|
|
2308
|
+
const localLayer = await this.loadIgnoreLayer(currentPath, dirRel, walk, rootPath);
|
|
2309
|
+
const localStack = localLayer ? [...stack, localLayer] : stack;
|
|
2310
|
+
for (const entry of entries) {
|
|
2311
|
+
const fullPath = join2(currentPath, entry);
|
|
2312
|
+
const relativePath = relative(rootPath, fullPath);
|
|
2313
|
+
if (this.isProtected(fullPath))
|
|
2314
|
+
continue;
|
|
2315
|
+
if (this.shouldIgnore(relativePath))
|
|
2316
|
+
continue;
|
|
2317
|
+
try {
|
|
2318
|
+
const lstats = await fileOp(() => lstat2(fullPath), `lstat: ${fullPath}`);
|
|
2319
|
+
if (this.skipSymlink(lstats))
|
|
2320
|
+
continue;
|
|
2321
|
+
const stats = lstats;
|
|
2322
|
+
if (stats.isDirectory()) {
|
|
2323
|
+
if (this.skipDirectory(relativePath, localStack))
|
|
2324
|
+
continue;
|
|
2325
|
+
if (await this.isBoundary(fullPath, rootPath))
|
|
2326
|
+
continue;
|
|
2327
|
+
const leave = await enterDirectory(walk, fullPath);
|
|
2328
|
+
if (!leave)
|
|
2329
|
+
continue;
|
|
2330
|
+
try {
|
|
2331
|
+
await this.traversePaths(fullPath, rootPath, out, depth + 1, walk, localStack, overridden, statsOut);
|
|
2332
|
+
} finally {
|
|
2333
|
+
leave();
|
|
2334
|
+
}
|
|
2335
|
+
} else if (stats.isFile()) {
|
|
2336
|
+
if (!this.isChunkable(fullPath, relativePath))
|
|
2337
|
+
continue;
|
|
2338
|
+
const decision = this.classifyEntry(relativePath, localStack);
|
|
2339
|
+
if (!decision?.include)
|
|
2340
|
+
continue;
|
|
2341
|
+
out.push(relativePath);
|
|
2342
|
+
statsOut?.set(relativePath, statTuple(stats));
|
|
2343
|
+
if (decision.override)
|
|
2344
|
+
overridden?.add(relativePath);
|
|
2345
|
+
}
|
|
2346
|
+
} catch (error) {
|
|
2347
|
+
markUnreadable(walk, fullPath, error);
|
|
2348
|
+
debug("Error traversing directory (listFiles)", { path: fullPath, error });
|
|
2349
|
+
}
|
|
2350
|
+
}
|
|
2351
|
+
}
|
|
2352
|
+
/**
|
|
2353
|
+
* Check if path should be ignored
|
|
2354
|
+
* Fixed glob-to-regex conversion and substring matching.
|
|
2355
|
+
* - Escape regex special chars before replacing * with .*
|
|
2356
|
+
* - Replace ALL * occurrences (not just the first)
|
|
2357
|
+
* - For non-glob patterns, match on path segments to avoid false positives
|
|
2358
|
+
* (e.g., '.env' should not match '.environment.ts')
|
|
2359
|
+
*/
|
|
2360
|
+
shouldIgnore(path) {
|
|
2361
|
+
if (isSecretFile(path))
|
|
2362
|
+
return true;
|
|
2363
|
+
const segments = path.split("/");
|
|
2364
|
+
return this.selection.ignorePatterns.some((pattern) => {
|
|
2365
|
+
if (pattern.includes("*")) {
|
|
2366
|
+
const anchored = pattern.startsWith("/");
|
|
2367
|
+
const body = anchored ? pattern.slice(1) : pattern;
|
|
2368
|
+
const escaped = body.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
|
|
2369
|
+
const source = escaped.replace(/\*/g, ".*");
|
|
2370
|
+
const regex = new RegExp(anchored ? `(^|/)${source}` : source);
|
|
2371
|
+
return regex.test(path);
|
|
2372
|
+
}
|
|
2373
|
+
return segments.some((segment) => segment === pattern) || path.endsWith("/" + pattern);
|
|
2374
|
+
});
|
|
2375
|
+
}
|
|
2376
|
+
};
|
|
2377
|
+
}
|
|
2378
|
+
});
|
|
2379
|
+
|
|
2380
|
+
// packages/cli/src/scanner/estimate.ts
|
|
2381
|
+
import { lstat as lstat3 } from "node:fs/promises";
|
|
2382
|
+
import { join as join3 } from "node:path";
|
|
2383
|
+
var COUNT_TIME_BOUND_MS = 3e4;
|
|
2384
|
+
async function defaultLister() {
|
|
2385
|
+
const { FileSelector: FileSelector2 } = await Promise.resolve().then(() => (init_fileSelection(), fileSelection_exports));
|
|
2386
|
+
const selector = new FileSelector2();
|
|
2387
|
+
return (root) => selector.listFilesWithStatus(root);
|
|
2388
|
+
}
|
|
2389
|
+
async function countIndexableFiles(roots, options = {}) {
|
|
2390
|
+
const now = options.now ?? Date.now;
|
|
2391
|
+
const deadline = now() + (options.timeBoundMs ?? COUNT_TIME_BOUND_MS);
|
|
2392
|
+
const list = options.list ?? await defaultLister();
|
|
2393
|
+
let files = 0;
|
|
2394
|
+
let bytes = 0;
|
|
2395
|
+
for (const root of roots) {
|
|
2396
|
+
const remaining = deadline - now();
|
|
2397
|
+
if (remaining <= 0) return { files, bytes, partial: true };
|
|
2398
|
+
const listed = await Promise.race([
|
|
2399
|
+
list(root),
|
|
2400
|
+
new Promise((resolve2) => setTimeout(() => resolve2(null), remaining).unref())
|
|
2401
|
+
]);
|
|
2402
|
+
if (!listed) return { files, bytes, partial: true };
|
|
2403
|
+
for (let i = 0; i < listed.paths.length; i += 64) {
|
|
2404
|
+
if (now() > deadline) return { files, bytes, partial: true };
|
|
2405
|
+
const sizes = await Promise.all(
|
|
2406
|
+
listed.paths.slice(i, i + 64).map(
|
|
2407
|
+
(path) => lstat3(join3(root, path)).then(
|
|
2408
|
+
(stat) => stat.size,
|
|
2409
|
+
() => null
|
|
2410
|
+
)
|
|
2411
|
+
)
|
|
2412
|
+
);
|
|
2413
|
+
for (const size of sizes) {
|
|
2414
|
+
if (size === null) continue;
|
|
2415
|
+
files++;
|
|
2416
|
+
bytes += size;
|
|
2417
|
+
}
|
|
2418
|
+
}
|
|
2419
|
+
}
|
|
2420
|
+
return { files, bytes, partial: false };
|
|
2421
|
+
}
|
|
2422
|
+
var number = (value) => value.toLocaleString("en-US");
|
|
2423
|
+
function formatBytes(bytes) {
|
|
2424
|
+
if (bytes < 1024 * 1024) return `${Math.max(1, Math.round(bytes / 1024))} KB`;
|
|
2425
|
+
if (bytes < 1024 * 1024 * 1024) return `${number(Math.round(bytes / (1024 * 1024)))} MB`;
|
|
2426
|
+
return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`;
|
|
2427
|
+
}
|
|
2428
|
+
function unit(seconds) {
|
|
2429
|
+
if (seconds < 90 * 60) return { value: Math.max(1, Math.round(seconds / 60)), name: "minute" };
|
|
2430
|
+
if (seconds < 48 * 3600) return { value: Math.round(seconds / 3600), name: "hour" };
|
|
2431
|
+
return { value: Math.round(seconds / 86400), name: "day" };
|
|
2432
|
+
}
|
|
2433
|
+
function formatDurationRange(lowSeconds, highSeconds) {
|
|
2434
|
+
if (highSeconds < 60) return "under a minute";
|
|
2435
|
+
const low = unit(lowSeconds);
|
|
2436
|
+
const high = unit(highSeconds);
|
|
2437
|
+
const plural = (n, name) => `${n} ${name}${n === 1 ? "" : "s"}`;
|
|
2438
|
+
if (low.name !== high.name)
|
|
2439
|
+
return `about ${plural(low.value, low.name)} to ${plural(high.value, high.name)}`;
|
|
2440
|
+
if (low.value === high.value) return `about ${plural(high.value, high.name)}`;
|
|
2441
|
+
return `about ${low.value} to ${plural(high.value, high.name)}`;
|
|
2442
|
+
}
|
|
2443
|
+
function onboardingEstimateLines(count, estimate) {
|
|
2444
|
+
if (count.files === 0) return [];
|
|
2445
|
+
const files = `${count.partial ? "at least " : ""}${number(count.files)} file${count.files === 1 ? "" : "s"}`;
|
|
2446
|
+
const lines = [`Mnemonik will index ${files} (${formatBytes(count.bytes)}).`];
|
|
2447
|
+
if (estimate) {
|
|
2448
|
+
const busy = estimate.accountsAhead > 0 ? " at the current load" : "";
|
|
2449
|
+
lines.push(
|
|
2450
|
+
`Indexing will take ${formatDurationRange(estimate.lowSeconds, estimate.highSeconds)}${busy}. You can keep working while it runs; files become searchable as they are indexed.`
|
|
2451
|
+
);
|
|
2452
|
+
}
|
|
2453
|
+
return lines;
|
|
2454
|
+
}
|
|
2455
|
+
export {
|
|
2456
|
+
COUNT_TIME_BOUND_MS,
|
|
2457
|
+
countIndexableFiles,
|
|
2458
|
+
formatBytes,
|
|
2459
|
+
formatDurationRange,
|
|
2460
|
+
onboardingEstimateLines
|
|
2461
|
+
};
|