kly 0.0.1
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/README.md +150 -0
- package/README.zh-CN.md +224 -0
- package/dist/cli.d.mts +1 -0
- package/dist/cli.mjs +699 -0
- package/dist/cli.mjs.map +1 -0
- package/dist/enrich-BHaZ2daX.mjs +3000 -0
- package/dist/enrich-BHaZ2daX.mjs.map +1 -0
- package/dist/index.d.mts +307 -0
- package/dist/index.mjs +2 -0
- package/package.json +82 -0
|
@@ -0,0 +1,3000 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { parse, stringify } from "yaml";
|
|
6
|
+
import Database from "better-sqlite3";
|
|
7
|
+
import { globby } from "globby";
|
|
8
|
+
import { execSync } from "node:child_process";
|
|
9
|
+
import Parser from "tree-sitter";
|
|
10
|
+
import Swift from "tree-sitter-swift";
|
|
11
|
+
import TypeScript from "tree-sitter-typescript";
|
|
12
|
+
import { complete, getModel } from "@mariozechner/pi-ai";
|
|
13
|
+
import pLimit from "p-limit";
|
|
14
|
+
import { renderMermaidASCII, renderMermaidSVG } from "beautiful-mermaid";
|
|
15
|
+
//#region \0rolldown/runtime.js
|
|
16
|
+
var __create = Object.create;
|
|
17
|
+
var __defProp = Object.defineProperty;
|
|
18
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
19
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
20
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
21
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
22
|
+
var __commonJSMin = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
23
|
+
var __copyProps = (to, from, except, desc) => {
|
|
24
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
25
|
+
key = keys[i];
|
|
26
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
27
|
+
get: ((k) => from[k]).bind(null, key),
|
|
28
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
return to;
|
|
32
|
+
};
|
|
33
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
34
|
+
value: mod,
|
|
35
|
+
enumerable: true
|
|
36
|
+
}) : target, mod));
|
|
37
|
+
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
38
|
+
//#endregion
|
|
39
|
+
//#region src/config.ts
|
|
40
|
+
const KLY_DIR = ".kly";
|
|
41
|
+
const CONFIG_FILE = "config.yaml";
|
|
42
|
+
const DB_DIR = "db";
|
|
43
|
+
const STATE_FILE = "state.yaml";
|
|
44
|
+
const DEFAULT_CONFIG = {
|
|
45
|
+
llm: {
|
|
46
|
+
provider: "openrouter",
|
|
47
|
+
model: "anthropic/claude-haiku-4.5",
|
|
48
|
+
apiKey: ""
|
|
49
|
+
},
|
|
50
|
+
include: [
|
|
51
|
+
"**/*.ts",
|
|
52
|
+
"**/*.tsx",
|
|
53
|
+
"**/*.js",
|
|
54
|
+
"**/*.jsx",
|
|
55
|
+
"**/*.swift"
|
|
56
|
+
],
|
|
57
|
+
exclude: [
|
|
58
|
+
"**/node_modules/**",
|
|
59
|
+
"**/dist/**",
|
|
60
|
+
"**/build/**",
|
|
61
|
+
"**/.git/**",
|
|
62
|
+
"**/.kly/**",
|
|
63
|
+
"**/vendor/**",
|
|
64
|
+
"**/*.d.ts",
|
|
65
|
+
"**/*.test.*",
|
|
66
|
+
"**/*.spec.*",
|
|
67
|
+
"**/__tests__/**"
|
|
68
|
+
]
|
|
69
|
+
};
|
|
70
|
+
function getKlyDir(root) {
|
|
71
|
+
return path.join(root, KLY_DIR);
|
|
72
|
+
}
|
|
73
|
+
function getConfigPath(root) {
|
|
74
|
+
return path.join(getKlyDir(root), CONFIG_FILE);
|
|
75
|
+
}
|
|
76
|
+
function getDbDir(root) {
|
|
77
|
+
return path.join(getKlyDir(root), DB_DIR);
|
|
78
|
+
}
|
|
79
|
+
function getDbPath(root, dbName) {
|
|
80
|
+
return path.join(getDbDir(root), `${dbName}.db`);
|
|
81
|
+
}
|
|
82
|
+
function getStatePath(root) {
|
|
83
|
+
return path.join(getKlyDir(root), STATE_FILE);
|
|
84
|
+
}
|
|
85
|
+
function isInitialized(root) {
|
|
86
|
+
return fs.existsSync(getKlyDir(root));
|
|
87
|
+
}
|
|
88
|
+
function initKlyDir(root, config) {
|
|
89
|
+
const klyDir = getKlyDir(root);
|
|
90
|
+
if (!fs.existsSync(klyDir)) fs.mkdirSync(klyDir, { recursive: true });
|
|
91
|
+
const dbDir = getDbDir(root);
|
|
92
|
+
if (!fs.existsSync(dbDir)) fs.mkdirSync(dbDir, { recursive: true });
|
|
93
|
+
const configToWrite = config || DEFAULT_CONFIG;
|
|
94
|
+
fs.writeFileSync(getConfigPath(root), stringify(configToWrite), "utf-8");
|
|
95
|
+
}
|
|
96
|
+
function loadConfig(root) {
|
|
97
|
+
const configPath = getConfigPath(root);
|
|
98
|
+
if (!fs.existsSync(configPath)) return DEFAULT_CONFIG;
|
|
99
|
+
const parsed = parse(fs.readFileSync(configPath, "utf-8"));
|
|
100
|
+
return {
|
|
101
|
+
...DEFAULT_CONFIG,
|
|
102
|
+
...parsed,
|
|
103
|
+
llm: {
|
|
104
|
+
...DEFAULT_CONFIG.llm,
|
|
105
|
+
...parsed.llm
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
function hashConfig(config) {
|
|
110
|
+
const significant = JSON.stringify({
|
|
111
|
+
include: config.include,
|
|
112
|
+
exclude: config.exclude
|
|
113
|
+
});
|
|
114
|
+
return createHash("sha256").update(significant).digest("hex");
|
|
115
|
+
}
|
|
116
|
+
//#endregion
|
|
117
|
+
//#region src/database.ts
|
|
118
|
+
var IndexDatabase = class {
|
|
119
|
+
db;
|
|
120
|
+
constructor(dbPath) {
|
|
121
|
+
this.db = new Database(dbPath);
|
|
122
|
+
this.db.pragma("journal_mode = WAL");
|
|
123
|
+
this.db.pragma("foreign_keys = ON");
|
|
124
|
+
this.init();
|
|
125
|
+
}
|
|
126
|
+
init() {
|
|
127
|
+
this.db.exec(`
|
|
128
|
+
CREATE TABLE IF NOT EXISTS files (
|
|
129
|
+
path TEXT PRIMARY KEY,
|
|
130
|
+
name TEXT NOT NULL,
|
|
131
|
+
description TEXT NOT NULL DEFAULT '',
|
|
132
|
+
language TEXT NOT NULL,
|
|
133
|
+
imports TEXT NOT NULL DEFAULT '[]',
|
|
134
|
+
exports TEXT NOT NULL DEFAULT '[]',
|
|
135
|
+
symbols TEXT NOT NULL DEFAULT '[]',
|
|
136
|
+
summary TEXT NOT NULL DEFAULT '',
|
|
137
|
+
hash TEXT NOT NULL,
|
|
138
|
+
indexed_at INTEGER NOT NULL
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
CREATE TABLE IF NOT EXISTS metadata (
|
|
142
|
+
key TEXT PRIMARY KEY,
|
|
143
|
+
value TEXT NOT NULL
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
CREATE TABLE IF NOT EXISTS dependencies (
|
|
147
|
+
from_path TEXT NOT NULL,
|
|
148
|
+
to_path TEXT NOT NULL,
|
|
149
|
+
PRIMARY KEY (from_path, to_path)
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
CREATE INDEX IF NOT EXISTS idx_deps_to ON dependencies(to_path);
|
|
153
|
+
`);
|
|
154
|
+
try {
|
|
155
|
+
this.db.exec(`
|
|
156
|
+
CREATE VIRTUAL TABLE files_fts USING fts5(
|
|
157
|
+
path, name, description, summary, symbols_text,
|
|
158
|
+
content=files,
|
|
159
|
+
content_rowid=rowid
|
|
160
|
+
);
|
|
161
|
+
`);
|
|
162
|
+
} catch {}
|
|
163
|
+
this.db.exec(`
|
|
164
|
+
CREATE TRIGGER IF NOT EXISTS files_ai AFTER INSERT ON files BEGIN
|
|
165
|
+
INSERT INTO files_fts(rowid, path, name, description, summary, symbols_text)
|
|
166
|
+
VALUES (new.rowid, new.path, new.name, new.description, new.summary, '');
|
|
167
|
+
END;
|
|
168
|
+
|
|
169
|
+
CREATE TRIGGER IF NOT EXISTS files_ad AFTER DELETE ON files BEGIN
|
|
170
|
+
INSERT INTO files_fts(files_fts, rowid, path, name, description, summary, symbols_text)
|
|
171
|
+
VALUES ('delete', old.rowid, old.path, old.name, old.description, old.summary, '');
|
|
172
|
+
END;
|
|
173
|
+
|
|
174
|
+
CREATE TRIGGER IF NOT EXISTS files_au AFTER UPDATE ON files BEGIN
|
|
175
|
+
INSERT INTO files_fts(files_fts, rowid, path, name, description, summary, symbols_text)
|
|
176
|
+
VALUES ('delete', old.rowid, old.path, old.name, old.description, old.summary, '');
|
|
177
|
+
INSERT INTO files_fts(rowid, path, name, description, summary, symbols_text)
|
|
178
|
+
VALUES (new.rowid, new.path, new.name, new.description, new.summary, '');
|
|
179
|
+
END;
|
|
180
|
+
`);
|
|
181
|
+
}
|
|
182
|
+
getFile(filePath) {
|
|
183
|
+
const row = this.db.prepare("SELECT * FROM files WHERE path = ?").get(filePath);
|
|
184
|
+
return row ? rowToFileIndex(row) : void 0;
|
|
185
|
+
}
|
|
186
|
+
upsertFile(fileIndex) {
|
|
187
|
+
this.db.prepare(`
|
|
188
|
+
INSERT INTO files (path, name, description, language, imports, exports, symbols, summary, hash, indexed_at)
|
|
189
|
+
VALUES (@path, @name, @description, @language, @imports, @exports, @symbols, @summary, @hash, @indexed_at)
|
|
190
|
+
ON CONFLICT(path) DO UPDATE SET
|
|
191
|
+
name = @name,
|
|
192
|
+
description = @description,
|
|
193
|
+
language = @language,
|
|
194
|
+
imports = @imports,
|
|
195
|
+
exports = @exports,
|
|
196
|
+
symbols = @symbols,
|
|
197
|
+
summary = @summary,
|
|
198
|
+
hash = @hash,
|
|
199
|
+
indexed_at = @indexed_at
|
|
200
|
+
`).run(fileIndexToRow(fileIndex));
|
|
201
|
+
this.updateFtsSymbolsText(fileIndex);
|
|
202
|
+
}
|
|
203
|
+
upsertFiles(fileIndexes) {
|
|
204
|
+
const stmt = this.db.prepare(`
|
|
205
|
+
INSERT INTO files (path, name, description, language, imports, exports, symbols, summary, hash, indexed_at)
|
|
206
|
+
VALUES (@path, @name, @description, @language, @imports, @exports, @symbols, @summary, @hash, @indexed_at)
|
|
207
|
+
ON CONFLICT(path) DO UPDATE SET
|
|
208
|
+
name = @name,
|
|
209
|
+
description = @description,
|
|
210
|
+
language = @language,
|
|
211
|
+
imports = @imports,
|
|
212
|
+
exports = @exports,
|
|
213
|
+
symbols = @symbols,
|
|
214
|
+
summary = @summary,
|
|
215
|
+
hash = @hash,
|
|
216
|
+
indexed_at = @indexed_at
|
|
217
|
+
`);
|
|
218
|
+
this.db.transaction((files) => {
|
|
219
|
+
for (const file of files) {
|
|
220
|
+
stmt.run(fileIndexToRow(file));
|
|
221
|
+
this.updateFtsSymbolsText(file);
|
|
222
|
+
}
|
|
223
|
+
})(fileIndexes);
|
|
224
|
+
}
|
|
225
|
+
removeFile(filePath) {
|
|
226
|
+
this.db.prepare("DELETE FROM files WHERE path = ?").run(filePath);
|
|
227
|
+
}
|
|
228
|
+
removeFiles(paths) {
|
|
229
|
+
const stmt = this.db.prepare("DELETE FROM files WHERE path = ?");
|
|
230
|
+
this.db.transaction((pathList) => {
|
|
231
|
+
for (const p of pathList) stmt.run(p);
|
|
232
|
+
})(paths);
|
|
233
|
+
}
|
|
234
|
+
searchFiles(query, limit = 20) {
|
|
235
|
+
const ftsQuery = query.split(/\s+/).filter(Boolean).map((term) => `"${term.replace(/"/g, "\"\"")}"`).join(" OR ");
|
|
236
|
+
if (!ftsQuery) return [];
|
|
237
|
+
return this.db.prepare(`
|
|
238
|
+
SELECT f.*, bm25(files_fts) as rank
|
|
239
|
+
FROM files_fts fts
|
|
240
|
+
JOIN files f ON f.rowid = fts.rowid
|
|
241
|
+
WHERE files_fts MATCH ?
|
|
242
|
+
ORDER BY rank
|
|
243
|
+
LIMIT ?
|
|
244
|
+
`).all(ftsQuery, limit).map((row) => ({
|
|
245
|
+
file: rowToFileIndex(row),
|
|
246
|
+
score: -row.rank
|
|
247
|
+
}));
|
|
248
|
+
}
|
|
249
|
+
getAllFiles() {
|
|
250
|
+
return this.db.prepare("SELECT * FROM files ORDER BY path").all().map(rowToFileIndex);
|
|
251
|
+
}
|
|
252
|
+
getFileCount() {
|
|
253
|
+
return this.db.prepare("SELECT COUNT(*) as count FROM files").get().count;
|
|
254
|
+
}
|
|
255
|
+
getLanguageStats() {
|
|
256
|
+
const rows = this.db.prepare("SELECT language, COUNT(*) as count FROM files GROUP BY language").all();
|
|
257
|
+
const stats = {};
|
|
258
|
+
for (const row of rows) stats[row.language] = row.count;
|
|
259
|
+
return stats;
|
|
260
|
+
}
|
|
261
|
+
getMetadata(key) {
|
|
262
|
+
return this.db.prepare("SELECT value FROM metadata WHERE key = ?").get(key)?.value;
|
|
263
|
+
}
|
|
264
|
+
setMetadata(key, value) {
|
|
265
|
+
this.db.prepare(`INSERT INTO metadata (key, value) VALUES (?, ?)
|
|
266
|
+
ON CONFLICT(key) DO UPDATE SET value = ?`).run(key, value, value);
|
|
267
|
+
}
|
|
268
|
+
upsertDependencies(fromPath, toPaths) {
|
|
269
|
+
this.db.prepare("DELETE FROM dependencies WHERE from_path = ?").run(fromPath);
|
|
270
|
+
const stmt = this.db.prepare("INSERT OR IGNORE INTO dependencies (from_path, to_path) VALUES (?, ?)");
|
|
271
|
+
for (const to of toPaths) stmt.run(fromPath, to);
|
|
272
|
+
}
|
|
273
|
+
upsertBatchDependencies(entries) {
|
|
274
|
+
const del = this.db.prepare("DELETE FROM dependencies WHERE from_path = ?");
|
|
275
|
+
const ins = this.db.prepare("INSERT OR IGNORE INTO dependencies (from_path, to_path) VALUES (?, ?)");
|
|
276
|
+
this.db.transaction((items) => {
|
|
277
|
+
for (const { fromPath, toPaths } of items) {
|
|
278
|
+
del.run(fromPath);
|
|
279
|
+
for (const to of toPaths) ins.run(fromPath, to);
|
|
280
|
+
}
|
|
281
|
+
})(entries);
|
|
282
|
+
}
|
|
283
|
+
removeDependencies(fromPath) {
|
|
284
|
+
this.db.prepare("DELETE FROM dependencies WHERE from_path = ?").run(fromPath);
|
|
285
|
+
}
|
|
286
|
+
removeDependenciesBatch(fromPaths) {
|
|
287
|
+
const stmt = this.db.prepare("DELETE FROM dependencies WHERE from_path = ?");
|
|
288
|
+
this.db.transaction((paths) => {
|
|
289
|
+
for (const p of paths) stmt.run(p);
|
|
290
|
+
})(fromPaths);
|
|
291
|
+
}
|
|
292
|
+
getDependencies(filePath) {
|
|
293
|
+
return this.db.prepare("SELECT to_path FROM dependencies WHERE from_path = ? ORDER BY to_path").all(filePath).map((r) => r.to_path);
|
|
294
|
+
}
|
|
295
|
+
getDependents(filePath) {
|
|
296
|
+
return this.db.prepare("SELECT from_path FROM dependencies WHERE to_path = ? ORDER BY from_path").all(filePath).map((r) => r.from_path);
|
|
297
|
+
}
|
|
298
|
+
removeAllDependencies() {
|
|
299
|
+
this.db.exec("DELETE FROM dependencies");
|
|
300
|
+
}
|
|
301
|
+
close() {
|
|
302
|
+
this.db.close();
|
|
303
|
+
}
|
|
304
|
+
updateFtsSymbolsText(fileIndex) {
|
|
305
|
+
const symbolsText = fileIndex.symbols.map((s) => `${s.name} ${s.description}`).join(" ");
|
|
306
|
+
const row = this.db.prepare("SELECT rowid FROM files WHERE path = ?").get(fileIndex.path);
|
|
307
|
+
this.db.prepare(`INSERT INTO files_fts(files_fts, rowid, path, name, description, summary, symbols_text)
|
|
308
|
+
VALUES ('delete', ?, ?, ?, ?, ?, '')`).run(row.rowid, fileIndex.path, fileIndex.name, fileIndex.description, fileIndex.summary);
|
|
309
|
+
this.db.prepare(`INSERT INTO files_fts(rowid, path, name, description, summary, symbols_text)
|
|
310
|
+
VALUES (?, ?, ?, ?, ?, ?)`).run(row.rowid, fileIndex.path, fileIndex.name, fileIndex.description, fileIndex.summary, symbolsText);
|
|
311
|
+
}
|
|
312
|
+
};
|
|
313
|
+
function rowToFileIndex(row) {
|
|
314
|
+
return {
|
|
315
|
+
path: row.path,
|
|
316
|
+
name: row.name,
|
|
317
|
+
description: row.description,
|
|
318
|
+
language: row.language,
|
|
319
|
+
imports: JSON.parse(row.imports),
|
|
320
|
+
exports: JSON.parse(row.exports),
|
|
321
|
+
symbols: JSON.parse(row.symbols),
|
|
322
|
+
summary: row.summary,
|
|
323
|
+
hash: row.hash,
|
|
324
|
+
indexedAt: row.indexed_at
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
function fileIndexToRow(fileIndex) {
|
|
328
|
+
return {
|
|
329
|
+
path: fileIndex.path,
|
|
330
|
+
name: fileIndex.name,
|
|
331
|
+
description: fileIndex.description,
|
|
332
|
+
language: fileIndex.language,
|
|
333
|
+
imports: JSON.stringify(fileIndex.imports),
|
|
334
|
+
exports: JSON.stringify(fileIndex.exports),
|
|
335
|
+
symbols: JSON.stringify(fileIndex.symbols),
|
|
336
|
+
summary: fileIndex.summary,
|
|
337
|
+
hash: fileIndex.hash,
|
|
338
|
+
indexed_at: fileIndex.indexedAt
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
//#endregion
|
|
342
|
+
//#region src/scanner.ts
|
|
343
|
+
async function scanFiles(root, config) {
|
|
344
|
+
return (await globby(config.include, {
|
|
345
|
+
cwd: root,
|
|
346
|
+
ignore: config.exclude,
|
|
347
|
+
gitignore: true,
|
|
348
|
+
absolute: false
|
|
349
|
+
})).sort();
|
|
350
|
+
}
|
|
351
|
+
//#endregion
|
|
352
|
+
//#region src/hasher.ts
|
|
353
|
+
function hashFile(root, filePath) {
|
|
354
|
+
const fullPath = path.join(root, filePath);
|
|
355
|
+
const content = fs.readFileSync(fullPath, "utf-8");
|
|
356
|
+
return createHash("sha256").update(content).digest("hex");
|
|
357
|
+
}
|
|
358
|
+
function hasChanged(oldHash, newHash) {
|
|
359
|
+
return oldHash !== newHash;
|
|
360
|
+
}
|
|
361
|
+
//#endregion
|
|
362
|
+
//#region src/git.ts
|
|
363
|
+
function exec(root, cmd) {
|
|
364
|
+
return execSync(cmd, {
|
|
365
|
+
cwd: root,
|
|
366
|
+
encoding: "utf-8",
|
|
367
|
+
stdio: [
|
|
368
|
+
"pipe",
|
|
369
|
+
"pipe",
|
|
370
|
+
"pipe"
|
|
371
|
+
]
|
|
372
|
+
}).trim();
|
|
373
|
+
}
|
|
374
|
+
function isGitRepo(root) {
|
|
375
|
+
try {
|
|
376
|
+
exec(root, "git rev-parse --is-inside-work-tree");
|
|
377
|
+
return true;
|
|
378
|
+
} catch {
|
|
379
|
+
return false;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
function getCurrentBranch(root) {
|
|
383
|
+
try {
|
|
384
|
+
return exec(root, "git symbolic-ref --short HEAD");
|
|
385
|
+
} catch {
|
|
386
|
+
return null;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
function getCurrentCommit(root) {
|
|
390
|
+
return exec(root, "git rev-parse HEAD");
|
|
391
|
+
}
|
|
392
|
+
function getChangedFiles(root, from, to) {
|
|
393
|
+
const output = exec(root, `git diff --name-status ${from} ${to || "HEAD"}`);
|
|
394
|
+
const diff = {
|
|
395
|
+
added: [],
|
|
396
|
+
modified: [],
|
|
397
|
+
deleted: [],
|
|
398
|
+
renamed: []
|
|
399
|
+
};
|
|
400
|
+
if (!output) return diff;
|
|
401
|
+
for (const line of output.split("\n")) {
|
|
402
|
+
const parts = line.split(" ");
|
|
403
|
+
const status = parts[0];
|
|
404
|
+
const filePath = parts[1];
|
|
405
|
+
switch (status[0]) {
|
|
406
|
+
case "A":
|
|
407
|
+
diff.added.push(filePath);
|
|
408
|
+
break;
|
|
409
|
+
case "M":
|
|
410
|
+
diff.modified.push(filePath);
|
|
411
|
+
break;
|
|
412
|
+
case "D":
|
|
413
|
+
diff.deleted.push(filePath);
|
|
414
|
+
break;
|
|
415
|
+
case "R":
|
|
416
|
+
diff.renamed.push({
|
|
417
|
+
from: filePath,
|
|
418
|
+
to: parts[2]
|
|
419
|
+
});
|
|
420
|
+
break;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
return diff;
|
|
424
|
+
}
|
|
425
|
+
function isAncestor(root, ancestor, descendant) {
|
|
426
|
+
try {
|
|
427
|
+
execSync(`git merge-base --is-ancestor ${ancestor} ${descendant}`, {
|
|
428
|
+
cwd: root,
|
|
429
|
+
stdio: [
|
|
430
|
+
"pipe",
|
|
431
|
+
"pipe",
|
|
432
|
+
"pipe"
|
|
433
|
+
]
|
|
434
|
+
});
|
|
435
|
+
return true;
|
|
436
|
+
} catch {
|
|
437
|
+
return false;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
function getMergeBase(root, a, b) {
|
|
441
|
+
try {
|
|
442
|
+
return exec(root, `git merge-base ${a} ${b}`);
|
|
443
|
+
} catch {
|
|
444
|
+
return null;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
function getFileHistory(root, filePath, limit = 5) {
|
|
448
|
+
let output;
|
|
449
|
+
try {
|
|
450
|
+
output = exec(root, `git log --follow -n ${limit} --format="%H|%an|%ae|%at|%s" -- "${filePath}"`);
|
|
451
|
+
} catch {
|
|
452
|
+
return [];
|
|
453
|
+
}
|
|
454
|
+
if (!output) return [];
|
|
455
|
+
return output.split("\n").filter(Boolean).map((line) => {
|
|
456
|
+
const [hash, author, email, date, ...msgParts] = line.split("|");
|
|
457
|
+
return {
|
|
458
|
+
hash,
|
|
459
|
+
author,
|
|
460
|
+
email,
|
|
461
|
+
date: parseInt(date, 10),
|
|
462
|
+
message: msgParts.join("|")
|
|
463
|
+
};
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
function branchToDbName(branch, commitHash) {
|
|
467
|
+
if (branch === null) return `_detached--${commitHash ? commitHash.slice(0, 8) : "unknown"}`;
|
|
468
|
+
return branch.replace(/\//g, "--");
|
|
469
|
+
}
|
|
470
|
+
//#endregion
|
|
471
|
+
//#region src/store.ts
|
|
472
|
+
const STATE_VERSION = 2;
|
|
473
|
+
function openDatabase(root, dbName) {
|
|
474
|
+
const name = dbName || resolveDbName(root);
|
|
475
|
+
const dbDir = getDbDir(root);
|
|
476
|
+
if (!fs.existsSync(dbDir)) fs.mkdirSync(dbDir, { recursive: true });
|
|
477
|
+
return new IndexDatabase(getDbPath(root, name));
|
|
478
|
+
}
|
|
479
|
+
function resolveDbName(root) {
|
|
480
|
+
if (!isGitRepo(root)) return "default";
|
|
481
|
+
const branch = getCurrentBranch(root);
|
|
482
|
+
return branchToDbName(branch, branch === null ? getCurrentCommit(root) : void 0);
|
|
483
|
+
}
|
|
484
|
+
function copyDatabase(root, fromName, toName) {
|
|
485
|
+
const fromPath = getDbPath(root, fromName);
|
|
486
|
+
const toPath = getDbPath(root, toName);
|
|
487
|
+
if (fs.existsSync(fromPath)) fs.copyFileSync(fromPath, toPath);
|
|
488
|
+
}
|
|
489
|
+
function loadState(root) {
|
|
490
|
+
const statePath = getStatePath(root);
|
|
491
|
+
if (!fs.existsSync(statePath)) return {
|
|
492
|
+
version: STATE_VERSION,
|
|
493
|
+
configHash: "",
|
|
494
|
+
branches: {}
|
|
495
|
+
};
|
|
496
|
+
return parse(fs.readFileSync(statePath, "utf-8"));
|
|
497
|
+
}
|
|
498
|
+
function saveState(root, state) {
|
|
499
|
+
const statePath = getStatePath(root);
|
|
500
|
+
fs.writeFileSync(statePath, stringify(state), "utf-8");
|
|
501
|
+
}
|
|
502
|
+
function getBranchState(state, dbName) {
|
|
503
|
+
return state.branches[dbName];
|
|
504
|
+
}
|
|
505
|
+
function setBranchState(state, dbName, branchState) {
|
|
506
|
+
state.branches[dbName] = branchState;
|
|
507
|
+
}
|
|
508
|
+
function getFileFromDb(root, filePath) {
|
|
509
|
+
const db = openDatabase(root);
|
|
510
|
+
try {
|
|
511
|
+
return db.getFile(filePath);
|
|
512
|
+
} finally {
|
|
513
|
+
db.close();
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
function getAllFilesFromDb(root) {
|
|
517
|
+
const db = openDatabase(root);
|
|
518
|
+
try {
|
|
519
|
+
return db.getAllFiles();
|
|
520
|
+
} finally {
|
|
521
|
+
db.close();
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
function listBranchDbs(root) {
|
|
525
|
+
const dbDir = getDbDir(root);
|
|
526
|
+
if (!fs.existsSync(dbDir)) return [];
|
|
527
|
+
return fs.readdirSync(dbDir).filter((f) => f.endsWith(".db")).map((f) => f.replace(/\.db$/, ""));
|
|
528
|
+
}
|
|
529
|
+
function removeBranchDb(root, dbName) {
|
|
530
|
+
const dbPath = getDbPath(root, dbName);
|
|
531
|
+
if (fs.existsSync(dbPath)) fs.unlinkSync(dbPath);
|
|
532
|
+
for (const ext of ["-wal", "-shm"]) {
|
|
533
|
+
const auxPath = dbPath + ext;
|
|
534
|
+
if (fs.existsSync(auxPath)) fs.unlinkSync(auxPath);
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
//#endregion
|
|
538
|
+
//#region node_modules/picomatch/lib/constants.js
|
|
539
|
+
var require_constants = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
540
|
+
const path$3 = __require("path");
|
|
541
|
+
const WIN_SLASH = "\\\\/";
|
|
542
|
+
const WIN_NO_SLASH = `[^${WIN_SLASH}]`;
|
|
543
|
+
/**
|
|
544
|
+
* Posix glob regex
|
|
545
|
+
*/
|
|
546
|
+
const DOT_LITERAL = "\\.";
|
|
547
|
+
const PLUS_LITERAL = "\\+";
|
|
548
|
+
const QMARK_LITERAL = "\\?";
|
|
549
|
+
const SLASH_LITERAL = "\\/";
|
|
550
|
+
const ONE_CHAR = "(?=.)";
|
|
551
|
+
const QMARK = "[^/]";
|
|
552
|
+
const END_ANCHOR = `(?:${SLASH_LITERAL}|$)`;
|
|
553
|
+
const START_ANCHOR = `(?:^|${SLASH_LITERAL})`;
|
|
554
|
+
const DOTS_SLASH = `${DOT_LITERAL}{1,2}${END_ANCHOR}`;
|
|
555
|
+
const POSIX_CHARS = {
|
|
556
|
+
DOT_LITERAL,
|
|
557
|
+
PLUS_LITERAL,
|
|
558
|
+
QMARK_LITERAL,
|
|
559
|
+
SLASH_LITERAL,
|
|
560
|
+
ONE_CHAR,
|
|
561
|
+
QMARK,
|
|
562
|
+
END_ANCHOR,
|
|
563
|
+
DOTS_SLASH,
|
|
564
|
+
NO_DOT: `(?!${DOT_LITERAL})`,
|
|
565
|
+
NO_DOTS: `(?!${START_ANCHOR}${DOTS_SLASH})`,
|
|
566
|
+
NO_DOT_SLASH: `(?!${DOT_LITERAL}{0,1}${END_ANCHOR})`,
|
|
567
|
+
NO_DOTS_SLASH: `(?!${DOTS_SLASH})`,
|
|
568
|
+
QMARK_NO_DOT: `[^.${SLASH_LITERAL}]`,
|
|
569
|
+
STAR: `${QMARK}*?`,
|
|
570
|
+
START_ANCHOR
|
|
571
|
+
};
|
|
572
|
+
/**
|
|
573
|
+
* Windows glob regex
|
|
574
|
+
*/
|
|
575
|
+
const WINDOWS_CHARS = {
|
|
576
|
+
...POSIX_CHARS,
|
|
577
|
+
SLASH_LITERAL: `[${WIN_SLASH}]`,
|
|
578
|
+
QMARK: WIN_NO_SLASH,
|
|
579
|
+
STAR: `${WIN_NO_SLASH}*?`,
|
|
580
|
+
DOTS_SLASH: `${DOT_LITERAL}{1,2}(?:[${WIN_SLASH}]|$)`,
|
|
581
|
+
NO_DOT: `(?!${DOT_LITERAL})`,
|
|
582
|
+
NO_DOTS: `(?!(?:^|[${WIN_SLASH}])${DOT_LITERAL}{1,2}(?:[${WIN_SLASH}]|$))`,
|
|
583
|
+
NO_DOT_SLASH: `(?!${DOT_LITERAL}{0,1}(?:[${WIN_SLASH}]|$))`,
|
|
584
|
+
NO_DOTS_SLASH: `(?!${DOT_LITERAL}{1,2}(?:[${WIN_SLASH}]|$))`,
|
|
585
|
+
QMARK_NO_DOT: `[^.${WIN_SLASH}]`,
|
|
586
|
+
START_ANCHOR: `(?:^|[${WIN_SLASH}])`,
|
|
587
|
+
END_ANCHOR: `(?:[${WIN_SLASH}]|$)`
|
|
588
|
+
};
|
|
589
|
+
module.exports = {
|
|
590
|
+
MAX_LENGTH: 1024 * 64,
|
|
591
|
+
POSIX_REGEX_SOURCE: {
|
|
592
|
+
alnum: "a-zA-Z0-9",
|
|
593
|
+
alpha: "a-zA-Z",
|
|
594
|
+
ascii: "\\x00-\\x7F",
|
|
595
|
+
blank: " \\t",
|
|
596
|
+
cntrl: "\\x00-\\x1F\\x7F",
|
|
597
|
+
digit: "0-9",
|
|
598
|
+
graph: "\\x21-\\x7E",
|
|
599
|
+
lower: "a-z",
|
|
600
|
+
print: "\\x20-\\x7E ",
|
|
601
|
+
punct: "\\-!\"#$%&'()\\*+,./:;<=>?@[\\]^_`{|}~",
|
|
602
|
+
space: " \\t\\r\\n\\v\\f",
|
|
603
|
+
upper: "A-Z",
|
|
604
|
+
word: "A-Za-z0-9_",
|
|
605
|
+
xdigit: "A-Fa-f0-9"
|
|
606
|
+
},
|
|
607
|
+
REGEX_BACKSLASH: /\\(?![*+?^${}(|)[\]])/g,
|
|
608
|
+
REGEX_NON_SPECIAL_CHARS: /^[^@![\].,$*+?^{}()|\\/]+/,
|
|
609
|
+
REGEX_SPECIAL_CHARS: /[-*+?.^${}(|)[\]]/,
|
|
610
|
+
REGEX_SPECIAL_CHARS_BACKREF: /(\\?)((\W)(\3*))/g,
|
|
611
|
+
REGEX_SPECIAL_CHARS_GLOBAL: /([-*+?.^${}(|)[\]])/g,
|
|
612
|
+
REGEX_REMOVE_BACKSLASH: /(?:\[.*?[^\\]\]|\\(?=.))/g,
|
|
613
|
+
REPLACEMENTS: {
|
|
614
|
+
"***": "*",
|
|
615
|
+
"**/**": "**",
|
|
616
|
+
"**/**/**": "**"
|
|
617
|
+
},
|
|
618
|
+
CHAR_0: 48,
|
|
619
|
+
CHAR_9: 57,
|
|
620
|
+
CHAR_UPPERCASE_A: 65,
|
|
621
|
+
CHAR_LOWERCASE_A: 97,
|
|
622
|
+
CHAR_UPPERCASE_Z: 90,
|
|
623
|
+
CHAR_LOWERCASE_Z: 122,
|
|
624
|
+
CHAR_LEFT_PARENTHESES: 40,
|
|
625
|
+
CHAR_RIGHT_PARENTHESES: 41,
|
|
626
|
+
CHAR_ASTERISK: 42,
|
|
627
|
+
CHAR_AMPERSAND: 38,
|
|
628
|
+
CHAR_AT: 64,
|
|
629
|
+
CHAR_BACKWARD_SLASH: 92,
|
|
630
|
+
CHAR_CARRIAGE_RETURN: 13,
|
|
631
|
+
CHAR_CIRCUMFLEX_ACCENT: 94,
|
|
632
|
+
CHAR_COLON: 58,
|
|
633
|
+
CHAR_COMMA: 44,
|
|
634
|
+
CHAR_DOT: 46,
|
|
635
|
+
CHAR_DOUBLE_QUOTE: 34,
|
|
636
|
+
CHAR_EQUAL: 61,
|
|
637
|
+
CHAR_EXCLAMATION_MARK: 33,
|
|
638
|
+
CHAR_FORM_FEED: 12,
|
|
639
|
+
CHAR_FORWARD_SLASH: 47,
|
|
640
|
+
CHAR_GRAVE_ACCENT: 96,
|
|
641
|
+
CHAR_HASH: 35,
|
|
642
|
+
CHAR_HYPHEN_MINUS: 45,
|
|
643
|
+
CHAR_LEFT_ANGLE_BRACKET: 60,
|
|
644
|
+
CHAR_LEFT_CURLY_BRACE: 123,
|
|
645
|
+
CHAR_LEFT_SQUARE_BRACKET: 91,
|
|
646
|
+
CHAR_LINE_FEED: 10,
|
|
647
|
+
CHAR_NO_BREAK_SPACE: 160,
|
|
648
|
+
CHAR_PERCENT: 37,
|
|
649
|
+
CHAR_PLUS: 43,
|
|
650
|
+
CHAR_QUESTION_MARK: 63,
|
|
651
|
+
CHAR_RIGHT_ANGLE_BRACKET: 62,
|
|
652
|
+
CHAR_RIGHT_CURLY_BRACE: 125,
|
|
653
|
+
CHAR_RIGHT_SQUARE_BRACKET: 93,
|
|
654
|
+
CHAR_SEMICOLON: 59,
|
|
655
|
+
CHAR_SINGLE_QUOTE: 39,
|
|
656
|
+
CHAR_SPACE: 32,
|
|
657
|
+
CHAR_TAB: 9,
|
|
658
|
+
CHAR_UNDERSCORE: 95,
|
|
659
|
+
CHAR_VERTICAL_LINE: 124,
|
|
660
|
+
CHAR_ZERO_WIDTH_NOBREAK_SPACE: 65279,
|
|
661
|
+
SEP: path$3.sep,
|
|
662
|
+
extglobChars(chars) {
|
|
663
|
+
return {
|
|
664
|
+
"!": {
|
|
665
|
+
type: "negate",
|
|
666
|
+
open: "(?:(?!(?:",
|
|
667
|
+
close: `))${chars.STAR})`
|
|
668
|
+
},
|
|
669
|
+
"?": {
|
|
670
|
+
type: "qmark",
|
|
671
|
+
open: "(?:",
|
|
672
|
+
close: ")?"
|
|
673
|
+
},
|
|
674
|
+
"+": {
|
|
675
|
+
type: "plus",
|
|
676
|
+
open: "(?:",
|
|
677
|
+
close: ")+"
|
|
678
|
+
},
|
|
679
|
+
"*": {
|
|
680
|
+
type: "star",
|
|
681
|
+
open: "(?:",
|
|
682
|
+
close: ")*"
|
|
683
|
+
},
|
|
684
|
+
"@": {
|
|
685
|
+
type: "at",
|
|
686
|
+
open: "(?:",
|
|
687
|
+
close: ")"
|
|
688
|
+
}
|
|
689
|
+
};
|
|
690
|
+
},
|
|
691
|
+
globChars(win32) {
|
|
692
|
+
return win32 === true ? WINDOWS_CHARS : POSIX_CHARS;
|
|
693
|
+
}
|
|
694
|
+
};
|
|
695
|
+
}));
|
|
696
|
+
//#endregion
|
|
697
|
+
//#region node_modules/picomatch/lib/utils.js
|
|
698
|
+
var require_utils = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
699
|
+
const path$2 = __require("path");
|
|
700
|
+
const win32 = process.platform === "win32";
|
|
701
|
+
const { REGEX_BACKSLASH, REGEX_REMOVE_BACKSLASH, REGEX_SPECIAL_CHARS, REGEX_SPECIAL_CHARS_GLOBAL } = require_constants();
|
|
702
|
+
exports.isObject = (val) => val !== null && typeof val === "object" && !Array.isArray(val);
|
|
703
|
+
exports.hasRegexChars = (str) => REGEX_SPECIAL_CHARS.test(str);
|
|
704
|
+
exports.isRegexChar = (str) => str.length === 1 && exports.hasRegexChars(str);
|
|
705
|
+
exports.escapeRegex = (str) => str.replace(REGEX_SPECIAL_CHARS_GLOBAL, "\\$1");
|
|
706
|
+
exports.toPosixSlashes = (str) => str.replace(REGEX_BACKSLASH, "/");
|
|
707
|
+
exports.removeBackslashes = (str) => {
|
|
708
|
+
return str.replace(REGEX_REMOVE_BACKSLASH, (match) => {
|
|
709
|
+
return match === "\\" ? "" : match;
|
|
710
|
+
});
|
|
711
|
+
};
|
|
712
|
+
exports.supportsLookbehinds = () => {
|
|
713
|
+
const segs = process.version.slice(1).split(".").map(Number);
|
|
714
|
+
if (segs.length === 3 && segs[0] >= 9 || segs[0] === 8 && segs[1] >= 10) return true;
|
|
715
|
+
return false;
|
|
716
|
+
};
|
|
717
|
+
exports.isWindows = (options) => {
|
|
718
|
+
if (options && typeof options.windows === "boolean") return options.windows;
|
|
719
|
+
return win32 === true || path$2.sep === "\\";
|
|
720
|
+
};
|
|
721
|
+
exports.escapeLast = (input, char, lastIdx) => {
|
|
722
|
+
const idx = input.lastIndexOf(char, lastIdx);
|
|
723
|
+
if (idx === -1) return input;
|
|
724
|
+
if (input[idx - 1] === "\\") return exports.escapeLast(input, char, idx - 1);
|
|
725
|
+
return `${input.slice(0, idx)}\\${input.slice(idx)}`;
|
|
726
|
+
};
|
|
727
|
+
exports.removePrefix = (input, state = {}) => {
|
|
728
|
+
let output = input;
|
|
729
|
+
if (output.startsWith("./")) {
|
|
730
|
+
output = output.slice(2);
|
|
731
|
+
state.prefix = "./";
|
|
732
|
+
}
|
|
733
|
+
return output;
|
|
734
|
+
};
|
|
735
|
+
exports.wrapOutput = (input, state = {}, options = {}) => {
|
|
736
|
+
let output = `${options.contains ? "" : "^"}(?:${input})${options.contains ? "" : "$"}`;
|
|
737
|
+
if (state.negated === true) output = `(?:^(?!${output}).*$)`;
|
|
738
|
+
return output;
|
|
739
|
+
};
|
|
740
|
+
}));
|
|
741
|
+
//#endregion
|
|
742
|
+
//#region node_modules/picomatch/lib/scan.js
|
|
743
|
+
var require_scan = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
744
|
+
const utils = require_utils();
|
|
745
|
+
const { CHAR_ASTERISK, CHAR_AT, CHAR_BACKWARD_SLASH, CHAR_COMMA, CHAR_DOT, CHAR_EXCLAMATION_MARK, CHAR_FORWARD_SLASH, CHAR_LEFT_CURLY_BRACE, CHAR_LEFT_PARENTHESES, CHAR_LEFT_SQUARE_BRACKET, CHAR_PLUS, CHAR_QUESTION_MARK, CHAR_RIGHT_CURLY_BRACE, CHAR_RIGHT_PARENTHESES, CHAR_RIGHT_SQUARE_BRACKET } = require_constants();
|
|
746
|
+
const isPathSeparator = (code) => {
|
|
747
|
+
return code === CHAR_FORWARD_SLASH || code === CHAR_BACKWARD_SLASH;
|
|
748
|
+
};
|
|
749
|
+
const depth = (token) => {
|
|
750
|
+
if (token.isPrefix !== true) token.depth = token.isGlobstar ? Infinity : 1;
|
|
751
|
+
};
|
|
752
|
+
/**
|
|
753
|
+
* Quickly scans a glob pattern and returns an object with a handful of
|
|
754
|
+
* useful properties, like `isGlob`, `path` (the leading non-glob, if it exists),
|
|
755
|
+
* `glob` (the actual pattern), `negated` (true if the path starts with `!` but not
|
|
756
|
+
* with `!(`) and `negatedExtglob` (true if the path starts with `!(`).
|
|
757
|
+
*
|
|
758
|
+
* ```js
|
|
759
|
+
* const pm = require('picomatch');
|
|
760
|
+
* console.log(pm.scan('foo/bar/*.js'));
|
|
761
|
+
* { isGlob: true, input: 'foo/bar/*.js', base: 'foo/bar', glob: '*.js' }
|
|
762
|
+
* ```
|
|
763
|
+
* @param {String} `str`
|
|
764
|
+
* @param {Object} `options`
|
|
765
|
+
* @return {Object} Returns an object with tokens and regex source string.
|
|
766
|
+
* @api public
|
|
767
|
+
*/
|
|
768
|
+
const scan = (input, options) => {
|
|
769
|
+
const opts = options || {};
|
|
770
|
+
const length = input.length - 1;
|
|
771
|
+
const scanToEnd = opts.parts === true || opts.scanToEnd === true;
|
|
772
|
+
const slashes = [];
|
|
773
|
+
const tokens = [];
|
|
774
|
+
const parts = [];
|
|
775
|
+
let str = input;
|
|
776
|
+
let index = -1;
|
|
777
|
+
let start = 0;
|
|
778
|
+
let lastIndex = 0;
|
|
779
|
+
let isBrace = false;
|
|
780
|
+
let isBracket = false;
|
|
781
|
+
let isGlob = false;
|
|
782
|
+
let isExtglob = false;
|
|
783
|
+
let isGlobstar = false;
|
|
784
|
+
let braceEscaped = false;
|
|
785
|
+
let backslashes = false;
|
|
786
|
+
let negated = false;
|
|
787
|
+
let negatedExtglob = false;
|
|
788
|
+
let finished = false;
|
|
789
|
+
let braces = 0;
|
|
790
|
+
let prev;
|
|
791
|
+
let code;
|
|
792
|
+
let token = {
|
|
793
|
+
value: "",
|
|
794
|
+
depth: 0,
|
|
795
|
+
isGlob: false
|
|
796
|
+
};
|
|
797
|
+
const eos = () => index >= length;
|
|
798
|
+
const peek = () => str.charCodeAt(index + 1);
|
|
799
|
+
const advance = () => {
|
|
800
|
+
prev = code;
|
|
801
|
+
return str.charCodeAt(++index);
|
|
802
|
+
};
|
|
803
|
+
while (index < length) {
|
|
804
|
+
code = advance();
|
|
805
|
+
let next;
|
|
806
|
+
if (code === CHAR_BACKWARD_SLASH) {
|
|
807
|
+
backslashes = token.backslashes = true;
|
|
808
|
+
code = advance();
|
|
809
|
+
if (code === CHAR_LEFT_CURLY_BRACE) braceEscaped = true;
|
|
810
|
+
continue;
|
|
811
|
+
}
|
|
812
|
+
if (braceEscaped === true || code === CHAR_LEFT_CURLY_BRACE) {
|
|
813
|
+
braces++;
|
|
814
|
+
while (eos() !== true && (code = advance())) {
|
|
815
|
+
if (code === CHAR_BACKWARD_SLASH) {
|
|
816
|
+
backslashes = token.backslashes = true;
|
|
817
|
+
advance();
|
|
818
|
+
continue;
|
|
819
|
+
}
|
|
820
|
+
if (code === CHAR_LEFT_CURLY_BRACE) {
|
|
821
|
+
braces++;
|
|
822
|
+
continue;
|
|
823
|
+
}
|
|
824
|
+
if (braceEscaped !== true && code === CHAR_DOT && (code = advance()) === CHAR_DOT) {
|
|
825
|
+
isBrace = token.isBrace = true;
|
|
826
|
+
isGlob = token.isGlob = true;
|
|
827
|
+
finished = true;
|
|
828
|
+
if (scanToEnd === true) continue;
|
|
829
|
+
break;
|
|
830
|
+
}
|
|
831
|
+
if (braceEscaped !== true && code === CHAR_COMMA) {
|
|
832
|
+
isBrace = token.isBrace = true;
|
|
833
|
+
isGlob = token.isGlob = true;
|
|
834
|
+
finished = true;
|
|
835
|
+
if (scanToEnd === true) continue;
|
|
836
|
+
break;
|
|
837
|
+
}
|
|
838
|
+
if (code === CHAR_RIGHT_CURLY_BRACE) {
|
|
839
|
+
braces--;
|
|
840
|
+
if (braces === 0) {
|
|
841
|
+
braceEscaped = false;
|
|
842
|
+
isBrace = token.isBrace = true;
|
|
843
|
+
finished = true;
|
|
844
|
+
break;
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
if (scanToEnd === true) continue;
|
|
849
|
+
break;
|
|
850
|
+
}
|
|
851
|
+
if (code === CHAR_FORWARD_SLASH) {
|
|
852
|
+
slashes.push(index);
|
|
853
|
+
tokens.push(token);
|
|
854
|
+
token = {
|
|
855
|
+
value: "",
|
|
856
|
+
depth: 0,
|
|
857
|
+
isGlob: false
|
|
858
|
+
};
|
|
859
|
+
if (finished === true) continue;
|
|
860
|
+
if (prev === CHAR_DOT && index === start + 1) {
|
|
861
|
+
start += 2;
|
|
862
|
+
continue;
|
|
863
|
+
}
|
|
864
|
+
lastIndex = index + 1;
|
|
865
|
+
continue;
|
|
866
|
+
}
|
|
867
|
+
if (opts.noext !== true) {
|
|
868
|
+
if ((code === CHAR_PLUS || code === CHAR_AT || code === CHAR_ASTERISK || code === CHAR_QUESTION_MARK || code === CHAR_EXCLAMATION_MARK) === true && peek() === CHAR_LEFT_PARENTHESES) {
|
|
869
|
+
isGlob = token.isGlob = true;
|
|
870
|
+
isExtglob = token.isExtglob = true;
|
|
871
|
+
finished = true;
|
|
872
|
+
if (code === CHAR_EXCLAMATION_MARK && index === start) negatedExtglob = true;
|
|
873
|
+
if (scanToEnd === true) {
|
|
874
|
+
while (eos() !== true && (code = advance())) {
|
|
875
|
+
if (code === CHAR_BACKWARD_SLASH) {
|
|
876
|
+
backslashes = token.backslashes = true;
|
|
877
|
+
code = advance();
|
|
878
|
+
continue;
|
|
879
|
+
}
|
|
880
|
+
if (code === CHAR_RIGHT_PARENTHESES) {
|
|
881
|
+
isGlob = token.isGlob = true;
|
|
882
|
+
finished = true;
|
|
883
|
+
break;
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
continue;
|
|
887
|
+
}
|
|
888
|
+
break;
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
if (code === CHAR_ASTERISK) {
|
|
892
|
+
if (prev === CHAR_ASTERISK) isGlobstar = token.isGlobstar = true;
|
|
893
|
+
isGlob = token.isGlob = true;
|
|
894
|
+
finished = true;
|
|
895
|
+
if (scanToEnd === true) continue;
|
|
896
|
+
break;
|
|
897
|
+
}
|
|
898
|
+
if (code === CHAR_QUESTION_MARK) {
|
|
899
|
+
isGlob = token.isGlob = true;
|
|
900
|
+
finished = true;
|
|
901
|
+
if (scanToEnd === true) continue;
|
|
902
|
+
break;
|
|
903
|
+
}
|
|
904
|
+
if (code === CHAR_LEFT_SQUARE_BRACKET) {
|
|
905
|
+
while (eos() !== true && (next = advance())) {
|
|
906
|
+
if (next === CHAR_BACKWARD_SLASH) {
|
|
907
|
+
backslashes = token.backslashes = true;
|
|
908
|
+
advance();
|
|
909
|
+
continue;
|
|
910
|
+
}
|
|
911
|
+
if (next === CHAR_RIGHT_SQUARE_BRACKET) {
|
|
912
|
+
isBracket = token.isBracket = true;
|
|
913
|
+
isGlob = token.isGlob = true;
|
|
914
|
+
finished = true;
|
|
915
|
+
break;
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
if (scanToEnd === true) continue;
|
|
919
|
+
break;
|
|
920
|
+
}
|
|
921
|
+
if (opts.nonegate !== true && code === CHAR_EXCLAMATION_MARK && index === start) {
|
|
922
|
+
negated = token.negated = true;
|
|
923
|
+
start++;
|
|
924
|
+
continue;
|
|
925
|
+
}
|
|
926
|
+
if (opts.noparen !== true && code === CHAR_LEFT_PARENTHESES) {
|
|
927
|
+
isGlob = token.isGlob = true;
|
|
928
|
+
if (scanToEnd === true) {
|
|
929
|
+
while (eos() !== true && (code = advance())) {
|
|
930
|
+
if (code === CHAR_LEFT_PARENTHESES) {
|
|
931
|
+
backslashes = token.backslashes = true;
|
|
932
|
+
code = advance();
|
|
933
|
+
continue;
|
|
934
|
+
}
|
|
935
|
+
if (code === CHAR_RIGHT_PARENTHESES) {
|
|
936
|
+
finished = true;
|
|
937
|
+
break;
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
continue;
|
|
941
|
+
}
|
|
942
|
+
break;
|
|
943
|
+
}
|
|
944
|
+
if (isGlob === true) {
|
|
945
|
+
finished = true;
|
|
946
|
+
if (scanToEnd === true) continue;
|
|
947
|
+
break;
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
if (opts.noext === true) {
|
|
951
|
+
isExtglob = false;
|
|
952
|
+
isGlob = false;
|
|
953
|
+
}
|
|
954
|
+
let base = str;
|
|
955
|
+
let prefix = "";
|
|
956
|
+
let glob = "";
|
|
957
|
+
if (start > 0) {
|
|
958
|
+
prefix = str.slice(0, start);
|
|
959
|
+
str = str.slice(start);
|
|
960
|
+
lastIndex -= start;
|
|
961
|
+
}
|
|
962
|
+
if (base && isGlob === true && lastIndex > 0) {
|
|
963
|
+
base = str.slice(0, lastIndex);
|
|
964
|
+
glob = str.slice(lastIndex);
|
|
965
|
+
} else if (isGlob === true) {
|
|
966
|
+
base = "";
|
|
967
|
+
glob = str;
|
|
968
|
+
} else base = str;
|
|
969
|
+
if (base && base !== "" && base !== "/" && base !== str) {
|
|
970
|
+
if (isPathSeparator(base.charCodeAt(base.length - 1))) base = base.slice(0, -1);
|
|
971
|
+
}
|
|
972
|
+
if (opts.unescape === true) {
|
|
973
|
+
if (glob) glob = utils.removeBackslashes(glob);
|
|
974
|
+
if (base && backslashes === true) base = utils.removeBackslashes(base);
|
|
975
|
+
}
|
|
976
|
+
const state = {
|
|
977
|
+
prefix,
|
|
978
|
+
input,
|
|
979
|
+
start,
|
|
980
|
+
base,
|
|
981
|
+
glob,
|
|
982
|
+
isBrace,
|
|
983
|
+
isBracket,
|
|
984
|
+
isGlob,
|
|
985
|
+
isExtglob,
|
|
986
|
+
isGlobstar,
|
|
987
|
+
negated,
|
|
988
|
+
negatedExtglob
|
|
989
|
+
};
|
|
990
|
+
if (opts.tokens === true) {
|
|
991
|
+
state.maxDepth = 0;
|
|
992
|
+
if (!isPathSeparator(code)) tokens.push(token);
|
|
993
|
+
state.tokens = tokens;
|
|
994
|
+
}
|
|
995
|
+
if (opts.parts === true || opts.tokens === true) {
|
|
996
|
+
let prevIndex;
|
|
997
|
+
for (let idx = 0; idx < slashes.length; idx++) {
|
|
998
|
+
const n = prevIndex ? prevIndex + 1 : start;
|
|
999
|
+
const i = slashes[idx];
|
|
1000
|
+
const value = input.slice(n, i);
|
|
1001
|
+
if (opts.tokens) {
|
|
1002
|
+
if (idx === 0 && start !== 0) {
|
|
1003
|
+
tokens[idx].isPrefix = true;
|
|
1004
|
+
tokens[idx].value = prefix;
|
|
1005
|
+
} else tokens[idx].value = value;
|
|
1006
|
+
depth(tokens[idx]);
|
|
1007
|
+
state.maxDepth += tokens[idx].depth;
|
|
1008
|
+
}
|
|
1009
|
+
if (idx !== 0 || value !== "") parts.push(value);
|
|
1010
|
+
prevIndex = i;
|
|
1011
|
+
}
|
|
1012
|
+
if (prevIndex && prevIndex + 1 < input.length) {
|
|
1013
|
+
const value = input.slice(prevIndex + 1);
|
|
1014
|
+
parts.push(value);
|
|
1015
|
+
if (opts.tokens) {
|
|
1016
|
+
tokens[tokens.length - 1].value = value;
|
|
1017
|
+
depth(tokens[tokens.length - 1]);
|
|
1018
|
+
state.maxDepth += tokens[tokens.length - 1].depth;
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
state.slashes = slashes;
|
|
1022
|
+
state.parts = parts;
|
|
1023
|
+
}
|
|
1024
|
+
return state;
|
|
1025
|
+
};
|
|
1026
|
+
module.exports = scan;
|
|
1027
|
+
}));
|
|
1028
|
+
//#endregion
|
|
1029
|
+
//#region node_modules/picomatch/lib/parse.js
|
|
1030
|
+
var require_parse = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
1031
|
+
const constants = require_constants();
|
|
1032
|
+
const utils = require_utils();
|
|
1033
|
+
/**
|
|
1034
|
+
* Constants
|
|
1035
|
+
*/
|
|
1036
|
+
const { MAX_LENGTH, POSIX_REGEX_SOURCE, REGEX_NON_SPECIAL_CHARS, REGEX_SPECIAL_CHARS_BACKREF, REPLACEMENTS } = constants;
|
|
1037
|
+
/**
|
|
1038
|
+
* Helpers
|
|
1039
|
+
*/
|
|
1040
|
+
const expandRange = (args, options) => {
|
|
1041
|
+
if (typeof options.expandRange === "function") return options.expandRange(...args, options);
|
|
1042
|
+
args.sort();
|
|
1043
|
+
const value = `[${args.join("-")}]`;
|
|
1044
|
+
try {
|
|
1045
|
+
new RegExp(value);
|
|
1046
|
+
} catch (ex) {
|
|
1047
|
+
return args.map((v) => utils.escapeRegex(v)).join("..");
|
|
1048
|
+
}
|
|
1049
|
+
return value;
|
|
1050
|
+
};
|
|
1051
|
+
/**
|
|
1052
|
+
* Create the message for a syntax error
|
|
1053
|
+
*/
|
|
1054
|
+
const syntaxError = (type, char) => {
|
|
1055
|
+
return `Missing ${type}: "${char}" - use "\\\\${char}" to match literal characters`;
|
|
1056
|
+
};
|
|
1057
|
+
/**
|
|
1058
|
+
* Parse the given input string.
|
|
1059
|
+
* @param {String} input
|
|
1060
|
+
* @param {Object} options
|
|
1061
|
+
* @return {Object}
|
|
1062
|
+
*/
|
|
1063
|
+
const parse = (input, options) => {
|
|
1064
|
+
if (typeof input !== "string") throw new TypeError("Expected a string");
|
|
1065
|
+
input = REPLACEMENTS[input] || input;
|
|
1066
|
+
const opts = { ...options };
|
|
1067
|
+
const max = typeof opts.maxLength === "number" ? Math.min(MAX_LENGTH, opts.maxLength) : MAX_LENGTH;
|
|
1068
|
+
let len = input.length;
|
|
1069
|
+
if (len > max) throw new SyntaxError(`Input length: ${len}, exceeds maximum allowed length: ${max}`);
|
|
1070
|
+
const bos = {
|
|
1071
|
+
type: "bos",
|
|
1072
|
+
value: "",
|
|
1073
|
+
output: opts.prepend || ""
|
|
1074
|
+
};
|
|
1075
|
+
const tokens = [bos];
|
|
1076
|
+
const capture = opts.capture ? "" : "?:";
|
|
1077
|
+
const win32 = utils.isWindows(options);
|
|
1078
|
+
const PLATFORM_CHARS = constants.globChars(win32);
|
|
1079
|
+
const EXTGLOB_CHARS = constants.extglobChars(PLATFORM_CHARS);
|
|
1080
|
+
const { DOT_LITERAL, PLUS_LITERAL, SLASH_LITERAL, ONE_CHAR, DOTS_SLASH, NO_DOT, NO_DOT_SLASH, NO_DOTS_SLASH, QMARK, QMARK_NO_DOT, STAR, START_ANCHOR } = PLATFORM_CHARS;
|
|
1081
|
+
const globstar = (opts) => {
|
|
1082
|
+
return `(${capture}(?:(?!${START_ANCHOR}${opts.dot ? DOTS_SLASH : DOT_LITERAL}).)*?)`;
|
|
1083
|
+
};
|
|
1084
|
+
const nodot = opts.dot ? "" : NO_DOT;
|
|
1085
|
+
const qmarkNoDot = opts.dot ? QMARK : QMARK_NO_DOT;
|
|
1086
|
+
let star = opts.bash === true ? globstar(opts) : STAR;
|
|
1087
|
+
if (opts.capture) star = `(${star})`;
|
|
1088
|
+
if (typeof opts.noext === "boolean") opts.noextglob = opts.noext;
|
|
1089
|
+
const state = {
|
|
1090
|
+
input,
|
|
1091
|
+
index: -1,
|
|
1092
|
+
start: 0,
|
|
1093
|
+
dot: opts.dot === true,
|
|
1094
|
+
consumed: "",
|
|
1095
|
+
output: "",
|
|
1096
|
+
prefix: "",
|
|
1097
|
+
backtrack: false,
|
|
1098
|
+
negated: false,
|
|
1099
|
+
brackets: 0,
|
|
1100
|
+
braces: 0,
|
|
1101
|
+
parens: 0,
|
|
1102
|
+
quotes: 0,
|
|
1103
|
+
globstar: false,
|
|
1104
|
+
tokens
|
|
1105
|
+
};
|
|
1106
|
+
input = utils.removePrefix(input, state);
|
|
1107
|
+
len = input.length;
|
|
1108
|
+
const extglobs = [];
|
|
1109
|
+
const braces = [];
|
|
1110
|
+
const stack = [];
|
|
1111
|
+
let prev = bos;
|
|
1112
|
+
let value;
|
|
1113
|
+
/**
|
|
1114
|
+
* Tokenizing helpers
|
|
1115
|
+
*/
|
|
1116
|
+
const eos = () => state.index === len - 1;
|
|
1117
|
+
const peek = state.peek = (n = 1) => input[state.index + n];
|
|
1118
|
+
const advance = state.advance = () => input[++state.index] || "";
|
|
1119
|
+
const remaining = () => input.slice(state.index + 1);
|
|
1120
|
+
const consume = (value = "", num = 0) => {
|
|
1121
|
+
state.consumed += value;
|
|
1122
|
+
state.index += num;
|
|
1123
|
+
};
|
|
1124
|
+
const append = (token) => {
|
|
1125
|
+
state.output += token.output != null ? token.output : token.value;
|
|
1126
|
+
consume(token.value);
|
|
1127
|
+
};
|
|
1128
|
+
const negate = () => {
|
|
1129
|
+
let count = 1;
|
|
1130
|
+
while (peek() === "!" && (peek(2) !== "(" || peek(3) === "?")) {
|
|
1131
|
+
advance();
|
|
1132
|
+
state.start++;
|
|
1133
|
+
count++;
|
|
1134
|
+
}
|
|
1135
|
+
if (count % 2 === 0) return false;
|
|
1136
|
+
state.negated = true;
|
|
1137
|
+
state.start++;
|
|
1138
|
+
return true;
|
|
1139
|
+
};
|
|
1140
|
+
const increment = (type) => {
|
|
1141
|
+
state[type]++;
|
|
1142
|
+
stack.push(type);
|
|
1143
|
+
};
|
|
1144
|
+
const decrement = (type) => {
|
|
1145
|
+
state[type]--;
|
|
1146
|
+
stack.pop();
|
|
1147
|
+
};
|
|
1148
|
+
/**
|
|
1149
|
+
* Push tokens onto the tokens array. This helper speeds up
|
|
1150
|
+
* tokenizing by 1) helping us avoid backtracking as much as possible,
|
|
1151
|
+
* and 2) helping us avoid creating extra tokens when consecutive
|
|
1152
|
+
* characters are plain text. This improves performance and simplifies
|
|
1153
|
+
* lookbehinds.
|
|
1154
|
+
*/
|
|
1155
|
+
const push = (tok) => {
|
|
1156
|
+
if (prev.type === "globstar") {
|
|
1157
|
+
const isBrace = state.braces > 0 && (tok.type === "comma" || tok.type === "brace");
|
|
1158
|
+
const isExtglob = tok.extglob === true || extglobs.length && (tok.type === "pipe" || tok.type === "paren");
|
|
1159
|
+
if (tok.type !== "slash" && tok.type !== "paren" && !isBrace && !isExtglob) {
|
|
1160
|
+
state.output = state.output.slice(0, -prev.output.length);
|
|
1161
|
+
prev.type = "star";
|
|
1162
|
+
prev.value = "*";
|
|
1163
|
+
prev.output = star;
|
|
1164
|
+
state.output += prev.output;
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
if (extglobs.length && tok.type !== "paren") extglobs[extglobs.length - 1].inner += tok.value;
|
|
1168
|
+
if (tok.value || tok.output) append(tok);
|
|
1169
|
+
if (prev && prev.type === "text" && tok.type === "text") {
|
|
1170
|
+
prev.value += tok.value;
|
|
1171
|
+
prev.output = (prev.output || "") + tok.value;
|
|
1172
|
+
return;
|
|
1173
|
+
}
|
|
1174
|
+
tok.prev = prev;
|
|
1175
|
+
tokens.push(tok);
|
|
1176
|
+
prev = tok;
|
|
1177
|
+
};
|
|
1178
|
+
const extglobOpen = (type, value) => {
|
|
1179
|
+
const token = {
|
|
1180
|
+
...EXTGLOB_CHARS[value],
|
|
1181
|
+
conditions: 1,
|
|
1182
|
+
inner: ""
|
|
1183
|
+
};
|
|
1184
|
+
token.prev = prev;
|
|
1185
|
+
token.parens = state.parens;
|
|
1186
|
+
token.output = state.output;
|
|
1187
|
+
const output = (opts.capture ? "(" : "") + token.open;
|
|
1188
|
+
increment("parens");
|
|
1189
|
+
push({
|
|
1190
|
+
type,
|
|
1191
|
+
value,
|
|
1192
|
+
output: state.output ? "" : ONE_CHAR
|
|
1193
|
+
});
|
|
1194
|
+
push({
|
|
1195
|
+
type: "paren",
|
|
1196
|
+
extglob: true,
|
|
1197
|
+
value: advance(),
|
|
1198
|
+
output
|
|
1199
|
+
});
|
|
1200
|
+
extglobs.push(token);
|
|
1201
|
+
};
|
|
1202
|
+
const extglobClose = (token) => {
|
|
1203
|
+
let output = token.close + (opts.capture ? ")" : "");
|
|
1204
|
+
let rest;
|
|
1205
|
+
if (token.type === "negate") {
|
|
1206
|
+
let extglobStar = star;
|
|
1207
|
+
if (token.inner && token.inner.length > 1 && token.inner.includes("/")) extglobStar = globstar(opts);
|
|
1208
|
+
if (extglobStar !== star || eos() || /^\)+$/.test(remaining())) output = token.close = `)$))${extglobStar}`;
|
|
1209
|
+
if (token.inner.includes("*") && (rest = remaining()) && /^\.[^\\/.]+$/.test(rest)) output = token.close = `)${parse(rest, {
|
|
1210
|
+
...options,
|
|
1211
|
+
fastpaths: false
|
|
1212
|
+
}).output})${extglobStar})`;
|
|
1213
|
+
if (token.prev.type === "bos") state.negatedExtglob = true;
|
|
1214
|
+
}
|
|
1215
|
+
push({
|
|
1216
|
+
type: "paren",
|
|
1217
|
+
extglob: true,
|
|
1218
|
+
value,
|
|
1219
|
+
output
|
|
1220
|
+
});
|
|
1221
|
+
decrement("parens");
|
|
1222
|
+
};
|
|
1223
|
+
/**
|
|
1224
|
+
* Fast paths
|
|
1225
|
+
*/
|
|
1226
|
+
if (opts.fastpaths !== false && !/(^[*!]|[/()[\]{}"])/.test(input)) {
|
|
1227
|
+
let backslashes = false;
|
|
1228
|
+
let output = input.replace(REGEX_SPECIAL_CHARS_BACKREF, (m, esc, chars, first, rest, index) => {
|
|
1229
|
+
if (first === "\\") {
|
|
1230
|
+
backslashes = true;
|
|
1231
|
+
return m;
|
|
1232
|
+
}
|
|
1233
|
+
if (first === "?") {
|
|
1234
|
+
if (esc) return esc + first + (rest ? QMARK.repeat(rest.length) : "");
|
|
1235
|
+
if (index === 0) return qmarkNoDot + (rest ? QMARK.repeat(rest.length) : "");
|
|
1236
|
+
return QMARK.repeat(chars.length);
|
|
1237
|
+
}
|
|
1238
|
+
if (first === ".") return DOT_LITERAL.repeat(chars.length);
|
|
1239
|
+
if (first === "*") {
|
|
1240
|
+
if (esc) return esc + first + (rest ? star : "");
|
|
1241
|
+
return star;
|
|
1242
|
+
}
|
|
1243
|
+
return esc ? m : `\\${m}`;
|
|
1244
|
+
});
|
|
1245
|
+
if (backslashes === true) if (opts.unescape === true) output = output.replace(/\\/g, "");
|
|
1246
|
+
else output = output.replace(/\\+/g, (m) => {
|
|
1247
|
+
return m.length % 2 === 0 ? "\\\\" : m ? "\\" : "";
|
|
1248
|
+
});
|
|
1249
|
+
if (output === input && opts.contains === true) {
|
|
1250
|
+
state.output = input;
|
|
1251
|
+
return state;
|
|
1252
|
+
}
|
|
1253
|
+
state.output = utils.wrapOutput(output, state, options);
|
|
1254
|
+
return state;
|
|
1255
|
+
}
|
|
1256
|
+
/**
|
|
1257
|
+
* Tokenize input until we reach end-of-string
|
|
1258
|
+
*/
|
|
1259
|
+
while (!eos()) {
|
|
1260
|
+
value = advance();
|
|
1261
|
+
if (value === "\0") continue;
|
|
1262
|
+
/**
|
|
1263
|
+
* Escaped characters
|
|
1264
|
+
*/
|
|
1265
|
+
if (value === "\\") {
|
|
1266
|
+
const next = peek();
|
|
1267
|
+
if (next === "/" && opts.bash !== true) continue;
|
|
1268
|
+
if (next === "." || next === ";") continue;
|
|
1269
|
+
if (!next) {
|
|
1270
|
+
value += "\\";
|
|
1271
|
+
push({
|
|
1272
|
+
type: "text",
|
|
1273
|
+
value
|
|
1274
|
+
});
|
|
1275
|
+
continue;
|
|
1276
|
+
}
|
|
1277
|
+
const match = /^\\+/.exec(remaining());
|
|
1278
|
+
let slashes = 0;
|
|
1279
|
+
if (match && match[0].length > 2) {
|
|
1280
|
+
slashes = match[0].length;
|
|
1281
|
+
state.index += slashes;
|
|
1282
|
+
if (slashes % 2 !== 0) value += "\\";
|
|
1283
|
+
}
|
|
1284
|
+
if (opts.unescape === true) value = advance();
|
|
1285
|
+
else value += advance();
|
|
1286
|
+
if (state.brackets === 0) {
|
|
1287
|
+
push({
|
|
1288
|
+
type: "text",
|
|
1289
|
+
value
|
|
1290
|
+
});
|
|
1291
|
+
continue;
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
/**
|
|
1295
|
+
* If we're inside a regex character class, continue
|
|
1296
|
+
* until we reach the closing bracket.
|
|
1297
|
+
*/
|
|
1298
|
+
if (state.brackets > 0 && (value !== "]" || prev.value === "[" || prev.value === "[^")) {
|
|
1299
|
+
if (opts.posix !== false && value === ":") {
|
|
1300
|
+
const inner = prev.value.slice(1);
|
|
1301
|
+
if (inner.includes("[")) {
|
|
1302
|
+
prev.posix = true;
|
|
1303
|
+
if (inner.includes(":")) {
|
|
1304
|
+
const idx = prev.value.lastIndexOf("[");
|
|
1305
|
+
const pre = prev.value.slice(0, idx);
|
|
1306
|
+
const posix = POSIX_REGEX_SOURCE[prev.value.slice(idx + 2)];
|
|
1307
|
+
if (posix) {
|
|
1308
|
+
prev.value = pre + posix;
|
|
1309
|
+
state.backtrack = true;
|
|
1310
|
+
advance();
|
|
1311
|
+
if (!bos.output && tokens.indexOf(prev) === 1) bos.output = ONE_CHAR;
|
|
1312
|
+
continue;
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
if (value === "[" && peek() !== ":" || value === "-" && peek() === "]") value = `\\${value}`;
|
|
1318
|
+
if (value === "]" && (prev.value === "[" || prev.value === "[^")) value = `\\${value}`;
|
|
1319
|
+
if (opts.posix === true && value === "!" && prev.value === "[") value = "^";
|
|
1320
|
+
prev.value += value;
|
|
1321
|
+
append({ value });
|
|
1322
|
+
continue;
|
|
1323
|
+
}
|
|
1324
|
+
/**
|
|
1325
|
+
* If we're inside a quoted string, continue
|
|
1326
|
+
* until we reach the closing double quote.
|
|
1327
|
+
*/
|
|
1328
|
+
if (state.quotes === 1 && value !== "\"") {
|
|
1329
|
+
value = utils.escapeRegex(value);
|
|
1330
|
+
prev.value += value;
|
|
1331
|
+
append({ value });
|
|
1332
|
+
continue;
|
|
1333
|
+
}
|
|
1334
|
+
/**
|
|
1335
|
+
* Double quotes
|
|
1336
|
+
*/
|
|
1337
|
+
if (value === "\"") {
|
|
1338
|
+
state.quotes = state.quotes === 1 ? 0 : 1;
|
|
1339
|
+
if (opts.keepQuotes === true) push({
|
|
1340
|
+
type: "text",
|
|
1341
|
+
value
|
|
1342
|
+
});
|
|
1343
|
+
continue;
|
|
1344
|
+
}
|
|
1345
|
+
/**
|
|
1346
|
+
* Parentheses
|
|
1347
|
+
*/
|
|
1348
|
+
if (value === "(") {
|
|
1349
|
+
increment("parens");
|
|
1350
|
+
push({
|
|
1351
|
+
type: "paren",
|
|
1352
|
+
value
|
|
1353
|
+
});
|
|
1354
|
+
continue;
|
|
1355
|
+
}
|
|
1356
|
+
if (value === ")") {
|
|
1357
|
+
if (state.parens === 0 && opts.strictBrackets === true) throw new SyntaxError(syntaxError("opening", "("));
|
|
1358
|
+
const extglob = extglobs[extglobs.length - 1];
|
|
1359
|
+
if (extglob && state.parens === extglob.parens + 1) {
|
|
1360
|
+
extglobClose(extglobs.pop());
|
|
1361
|
+
continue;
|
|
1362
|
+
}
|
|
1363
|
+
push({
|
|
1364
|
+
type: "paren",
|
|
1365
|
+
value,
|
|
1366
|
+
output: state.parens ? ")" : "\\)"
|
|
1367
|
+
});
|
|
1368
|
+
decrement("parens");
|
|
1369
|
+
continue;
|
|
1370
|
+
}
|
|
1371
|
+
/**
|
|
1372
|
+
* Square brackets
|
|
1373
|
+
*/
|
|
1374
|
+
if (value === "[") {
|
|
1375
|
+
if (opts.nobracket === true || !remaining().includes("]")) {
|
|
1376
|
+
if (opts.nobracket !== true && opts.strictBrackets === true) throw new SyntaxError(syntaxError("closing", "]"));
|
|
1377
|
+
value = `\\${value}`;
|
|
1378
|
+
} else increment("brackets");
|
|
1379
|
+
push({
|
|
1380
|
+
type: "bracket",
|
|
1381
|
+
value
|
|
1382
|
+
});
|
|
1383
|
+
continue;
|
|
1384
|
+
}
|
|
1385
|
+
if (value === "]") {
|
|
1386
|
+
if (opts.nobracket === true || prev && prev.type === "bracket" && prev.value.length === 1) {
|
|
1387
|
+
push({
|
|
1388
|
+
type: "text",
|
|
1389
|
+
value,
|
|
1390
|
+
output: `\\${value}`
|
|
1391
|
+
});
|
|
1392
|
+
continue;
|
|
1393
|
+
}
|
|
1394
|
+
if (state.brackets === 0) {
|
|
1395
|
+
if (opts.strictBrackets === true) throw new SyntaxError(syntaxError("opening", "["));
|
|
1396
|
+
push({
|
|
1397
|
+
type: "text",
|
|
1398
|
+
value,
|
|
1399
|
+
output: `\\${value}`
|
|
1400
|
+
});
|
|
1401
|
+
continue;
|
|
1402
|
+
}
|
|
1403
|
+
decrement("brackets");
|
|
1404
|
+
const prevValue = prev.value.slice(1);
|
|
1405
|
+
if (prev.posix !== true && prevValue[0] === "^" && !prevValue.includes("/")) value = `/${value}`;
|
|
1406
|
+
prev.value += value;
|
|
1407
|
+
append({ value });
|
|
1408
|
+
if (opts.literalBrackets === false || utils.hasRegexChars(prevValue)) continue;
|
|
1409
|
+
const escaped = utils.escapeRegex(prev.value);
|
|
1410
|
+
state.output = state.output.slice(0, -prev.value.length);
|
|
1411
|
+
if (opts.literalBrackets === true) {
|
|
1412
|
+
state.output += escaped;
|
|
1413
|
+
prev.value = escaped;
|
|
1414
|
+
continue;
|
|
1415
|
+
}
|
|
1416
|
+
prev.value = `(${capture}${escaped}|${prev.value})`;
|
|
1417
|
+
state.output += prev.value;
|
|
1418
|
+
continue;
|
|
1419
|
+
}
|
|
1420
|
+
/**
|
|
1421
|
+
* Braces
|
|
1422
|
+
*/
|
|
1423
|
+
if (value === "{" && opts.nobrace !== true) {
|
|
1424
|
+
increment("braces");
|
|
1425
|
+
const open = {
|
|
1426
|
+
type: "brace",
|
|
1427
|
+
value,
|
|
1428
|
+
output: "(",
|
|
1429
|
+
outputIndex: state.output.length,
|
|
1430
|
+
tokensIndex: state.tokens.length
|
|
1431
|
+
};
|
|
1432
|
+
braces.push(open);
|
|
1433
|
+
push(open);
|
|
1434
|
+
continue;
|
|
1435
|
+
}
|
|
1436
|
+
if (value === "}") {
|
|
1437
|
+
const brace = braces[braces.length - 1];
|
|
1438
|
+
if (opts.nobrace === true || !brace) {
|
|
1439
|
+
push({
|
|
1440
|
+
type: "text",
|
|
1441
|
+
value,
|
|
1442
|
+
output: value
|
|
1443
|
+
});
|
|
1444
|
+
continue;
|
|
1445
|
+
}
|
|
1446
|
+
let output = ")";
|
|
1447
|
+
if (brace.dots === true) {
|
|
1448
|
+
const arr = tokens.slice();
|
|
1449
|
+
const range = [];
|
|
1450
|
+
for (let i = arr.length - 1; i >= 0; i--) {
|
|
1451
|
+
tokens.pop();
|
|
1452
|
+
if (arr[i].type === "brace") break;
|
|
1453
|
+
if (arr[i].type !== "dots") range.unshift(arr[i].value);
|
|
1454
|
+
}
|
|
1455
|
+
output = expandRange(range, opts);
|
|
1456
|
+
state.backtrack = true;
|
|
1457
|
+
}
|
|
1458
|
+
if (brace.comma !== true && brace.dots !== true) {
|
|
1459
|
+
const out = state.output.slice(0, brace.outputIndex);
|
|
1460
|
+
const toks = state.tokens.slice(brace.tokensIndex);
|
|
1461
|
+
brace.value = brace.output = "\\{";
|
|
1462
|
+
value = output = "\\}";
|
|
1463
|
+
state.output = out;
|
|
1464
|
+
for (const t of toks) state.output += t.output || t.value;
|
|
1465
|
+
}
|
|
1466
|
+
push({
|
|
1467
|
+
type: "brace",
|
|
1468
|
+
value,
|
|
1469
|
+
output
|
|
1470
|
+
});
|
|
1471
|
+
decrement("braces");
|
|
1472
|
+
braces.pop();
|
|
1473
|
+
continue;
|
|
1474
|
+
}
|
|
1475
|
+
/**
|
|
1476
|
+
* Pipes
|
|
1477
|
+
*/
|
|
1478
|
+
if (value === "|") {
|
|
1479
|
+
if (extglobs.length > 0) extglobs[extglobs.length - 1].conditions++;
|
|
1480
|
+
push({
|
|
1481
|
+
type: "text",
|
|
1482
|
+
value
|
|
1483
|
+
});
|
|
1484
|
+
continue;
|
|
1485
|
+
}
|
|
1486
|
+
/**
|
|
1487
|
+
* Commas
|
|
1488
|
+
*/
|
|
1489
|
+
if (value === ",") {
|
|
1490
|
+
let output = value;
|
|
1491
|
+
const brace = braces[braces.length - 1];
|
|
1492
|
+
if (brace && stack[stack.length - 1] === "braces") {
|
|
1493
|
+
brace.comma = true;
|
|
1494
|
+
output = "|";
|
|
1495
|
+
}
|
|
1496
|
+
push({
|
|
1497
|
+
type: "comma",
|
|
1498
|
+
value,
|
|
1499
|
+
output
|
|
1500
|
+
});
|
|
1501
|
+
continue;
|
|
1502
|
+
}
|
|
1503
|
+
/**
|
|
1504
|
+
* Slashes
|
|
1505
|
+
*/
|
|
1506
|
+
if (value === "/") {
|
|
1507
|
+
if (prev.type === "dot" && state.index === state.start + 1) {
|
|
1508
|
+
state.start = state.index + 1;
|
|
1509
|
+
state.consumed = "";
|
|
1510
|
+
state.output = "";
|
|
1511
|
+
tokens.pop();
|
|
1512
|
+
prev = bos;
|
|
1513
|
+
continue;
|
|
1514
|
+
}
|
|
1515
|
+
push({
|
|
1516
|
+
type: "slash",
|
|
1517
|
+
value,
|
|
1518
|
+
output: SLASH_LITERAL
|
|
1519
|
+
});
|
|
1520
|
+
continue;
|
|
1521
|
+
}
|
|
1522
|
+
/**
|
|
1523
|
+
* Dots
|
|
1524
|
+
*/
|
|
1525
|
+
if (value === ".") {
|
|
1526
|
+
if (state.braces > 0 && prev.type === "dot") {
|
|
1527
|
+
if (prev.value === ".") prev.output = DOT_LITERAL;
|
|
1528
|
+
const brace = braces[braces.length - 1];
|
|
1529
|
+
prev.type = "dots";
|
|
1530
|
+
prev.output += value;
|
|
1531
|
+
prev.value += value;
|
|
1532
|
+
brace.dots = true;
|
|
1533
|
+
continue;
|
|
1534
|
+
}
|
|
1535
|
+
if (state.braces + state.parens === 0 && prev.type !== "bos" && prev.type !== "slash") {
|
|
1536
|
+
push({
|
|
1537
|
+
type: "text",
|
|
1538
|
+
value,
|
|
1539
|
+
output: DOT_LITERAL
|
|
1540
|
+
});
|
|
1541
|
+
continue;
|
|
1542
|
+
}
|
|
1543
|
+
push({
|
|
1544
|
+
type: "dot",
|
|
1545
|
+
value,
|
|
1546
|
+
output: DOT_LITERAL
|
|
1547
|
+
});
|
|
1548
|
+
continue;
|
|
1549
|
+
}
|
|
1550
|
+
/**
|
|
1551
|
+
* Question marks
|
|
1552
|
+
*/
|
|
1553
|
+
if (value === "?") {
|
|
1554
|
+
if (!(prev && prev.value === "(") && opts.noextglob !== true && peek() === "(" && peek(2) !== "?") {
|
|
1555
|
+
extglobOpen("qmark", value);
|
|
1556
|
+
continue;
|
|
1557
|
+
}
|
|
1558
|
+
if (prev && prev.type === "paren") {
|
|
1559
|
+
const next = peek();
|
|
1560
|
+
let output = value;
|
|
1561
|
+
if (next === "<" && !utils.supportsLookbehinds()) throw new Error("Node.js v10 or higher is required for regex lookbehinds");
|
|
1562
|
+
if (prev.value === "(" && !/[!=<:]/.test(next) || next === "<" && !/<([!=]|\w+>)/.test(remaining())) output = `\\${value}`;
|
|
1563
|
+
push({
|
|
1564
|
+
type: "text",
|
|
1565
|
+
value,
|
|
1566
|
+
output
|
|
1567
|
+
});
|
|
1568
|
+
continue;
|
|
1569
|
+
}
|
|
1570
|
+
if (opts.dot !== true && (prev.type === "slash" || prev.type === "bos")) {
|
|
1571
|
+
push({
|
|
1572
|
+
type: "qmark",
|
|
1573
|
+
value,
|
|
1574
|
+
output: QMARK_NO_DOT
|
|
1575
|
+
});
|
|
1576
|
+
continue;
|
|
1577
|
+
}
|
|
1578
|
+
push({
|
|
1579
|
+
type: "qmark",
|
|
1580
|
+
value,
|
|
1581
|
+
output: QMARK
|
|
1582
|
+
});
|
|
1583
|
+
continue;
|
|
1584
|
+
}
|
|
1585
|
+
/**
|
|
1586
|
+
* Exclamation
|
|
1587
|
+
*/
|
|
1588
|
+
if (value === "!") {
|
|
1589
|
+
if (opts.noextglob !== true && peek() === "(") {
|
|
1590
|
+
if (peek(2) !== "?" || !/[!=<:]/.test(peek(3))) {
|
|
1591
|
+
extglobOpen("negate", value);
|
|
1592
|
+
continue;
|
|
1593
|
+
}
|
|
1594
|
+
}
|
|
1595
|
+
if (opts.nonegate !== true && state.index === 0) {
|
|
1596
|
+
negate();
|
|
1597
|
+
continue;
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1600
|
+
/**
|
|
1601
|
+
* Plus
|
|
1602
|
+
*/
|
|
1603
|
+
if (value === "+") {
|
|
1604
|
+
if (opts.noextglob !== true && peek() === "(" && peek(2) !== "?") {
|
|
1605
|
+
extglobOpen("plus", value);
|
|
1606
|
+
continue;
|
|
1607
|
+
}
|
|
1608
|
+
if (prev && prev.value === "(" || opts.regex === false) {
|
|
1609
|
+
push({
|
|
1610
|
+
type: "plus",
|
|
1611
|
+
value,
|
|
1612
|
+
output: PLUS_LITERAL
|
|
1613
|
+
});
|
|
1614
|
+
continue;
|
|
1615
|
+
}
|
|
1616
|
+
if (prev && (prev.type === "bracket" || prev.type === "paren" || prev.type === "brace") || state.parens > 0) {
|
|
1617
|
+
push({
|
|
1618
|
+
type: "plus",
|
|
1619
|
+
value
|
|
1620
|
+
});
|
|
1621
|
+
continue;
|
|
1622
|
+
}
|
|
1623
|
+
push({
|
|
1624
|
+
type: "plus",
|
|
1625
|
+
value: PLUS_LITERAL
|
|
1626
|
+
});
|
|
1627
|
+
continue;
|
|
1628
|
+
}
|
|
1629
|
+
/**
|
|
1630
|
+
* Plain text
|
|
1631
|
+
*/
|
|
1632
|
+
if (value === "@") {
|
|
1633
|
+
if (opts.noextglob !== true && peek() === "(" && peek(2) !== "?") {
|
|
1634
|
+
push({
|
|
1635
|
+
type: "at",
|
|
1636
|
+
extglob: true,
|
|
1637
|
+
value,
|
|
1638
|
+
output: ""
|
|
1639
|
+
});
|
|
1640
|
+
continue;
|
|
1641
|
+
}
|
|
1642
|
+
push({
|
|
1643
|
+
type: "text",
|
|
1644
|
+
value
|
|
1645
|
+
});
|
|
1646
|
+
continue;
|
|
1647
|
+
}
|
|
1648
|
+
/**
|
|
1649
|
+
* Plain text
|
|
1650
|
+
*/
|
|
1651
|
+
if (value !== "*") {
|
|
1652
|
+
if (value === "$" || value === "^") value = `\\${value}`;
|
|
1653
|
+
const match = REGEX_NON_SPECIAL_CHARS.exec(remaining());
|
|
1654
|
+
if (match) {
|
|
1655
|
+
value += match[0];
|
|
1656
|
+
state.index += match[0].length;
|
|
1657
|
+
}
|
|
1658
|
+
push({
|
|
1659
|
+
type: "text",
|
|
1660
|
+
value
|
|
1661
|
+
});
|
|
1662
|
+
continue;
|
|
1663
|
+
}
|
|
1664
|
+
/**
|
|
1665
|
+
* Stars
|
|
1666
|
+
*/
|
|
1667
|
+
if (prev && (prev.type === "globstar" || prev.star === true)) {
|
|
1668
|
+
prev.type = "star";
|
|
1669
|
+
prev.star = true;
|
|
1670
|
+
prev.value += value;
|
|
1671
|
+
prev.output = star;
|
|
1672
|
+
state.backtrack = true;
|
|
1673
|
+
state.globstar = true;
|
|
1674
|
+
consume(value);
|
|
1675
|
+
continue;
|
|
1676
|
+
}
|
|
1677
|
+
let rest = remaining();
|
|
1678
|
+
if (opts.noextglob !== true && /^\([^?]/.test(rest)) {
|
|
1679
|
+
extglobOpen("star", value);
|
|
1680
|
+
continue;
|
|
1681
|
+
}
|
|
1682
|
+
if (prev.type === "star") {
|
|
1683
|
+
if (opts.noglobstar === true) {
|
|
1684
|
+
consume(value);
|
|
1685
|
+
continue;
|
|
1686
|
+
}
|
|
1687
|
+
const prior = prev.prev;
|
|
1688
|
+
const before = prior.prev;
|
|
1689
|
+
const isStart = prior.type === "slash" || prior.type === "bos";
|
|
1690
|
+
const afterStar = before && (before.type === "star" || before.type === "globstar");
|
|
1691
|
+
if (opts.bash === true && (!isStart || rest[0] && rest[0] !== "/")) {
|
|
1692
|
+
push({
|
|
1693
|
+
type: "star",
|
|
1694
|
+
value,
|
|
1695
|
+
output: ""
|
|
1696
|
+
});
|
|
1697
|
+
continue;
|
|
1698
|
+
}
|
|
1699
|
+
const isBrace = state.braces > 0 && (prior.type === "comma" || prior.type === "brace");
|
|
1700
|
+
const isExtglob = extglobs.length && (prior.type === "pipe" || prior.type === "paren");
|
|
1701
|
+
if (!isStart && prior.type !== "paren" && !isBrace && !isExtglob) {
|
|
1702
|
+
push({
|
|
1703
|
+
type: "star",
|
|
1704
|
+
value,
|
|
1705
|
+
output: ""
|
|
1706
|
+
});
|
|
1707
|
+
continue;
|
|
1708
|
+
}
|
|
1709
|
+
while (rest.slice(0, 3) === "/**") {
|
|
1710
|
+
const after = input[state.index + 4];
|
|
1711
|
+
if (after && after !== "/") break;
|
|
1712
|
+
rest = rest.slice(3);
|
|
1713
|
+
consume("/**", 3);
|
|
1714
|
+
}
|
|
1715
|
+
if (prior.type === "bos" && eos()) {
|
|
1716
|
+
prev.type = "globstar";
|
|
1717
|
+
prev.value += value;
|
|
1718
|
+
prev.output = globstar(opts);
|
|
1719
|
+
state.output = prev.output;
|
|
1720
|
+
state.globstar = true;
|
|
1721
|
+
consume(value);
|
|
1722
|
+
continue;
|
|
1723
|
+
}
|
|
1724
|
+
if (prior.type === "slash" && prior.prev.type !== "bos" && !afterStar && eos()) {
|
|
1725
|
+
state.output = state.output.slice(0, -(prior.output + prev.output).length);
|
|
1726
|
+
prior.output = `(?:${prior.output}`;
|
|
1727
|
+
prev.type = "globstar";
|
|
1728
|
+
prev.output = globstar(opts) + (opts.strictSlashes ? ")" : "|$)");
|
|
1729
|
+
prev.value += value;
|
|
1730
|
+
state.globstar = true;
|
|
1731
|
+
state.output += prior.output + prev.output;
|
|
1732
|
+
consume(value);
|
|
1733
|
+
continue;
|
|
1734
|
+
}
|
|
1735
|
+
if (prior.type === "slash" && prior.prev.type !== "bos" && rest[0] === "/") {
|
|
1736
|
+
const end = rest[1] !== void 0 ? "|$" : "";
|
|
1737
|
+
state.output = state.output.slice(0, -(prior.output + prev.output).length);
|
|
1738
|
+
prior.output = `(?:${prior.output}`;
|
|
1739
|
+
prev.type = "globstar";
|
|
1740
|
+
prev.output = `${globstar(opts)}${SLASH_LITERAL}|${SLASH_LITERAL}${end})`;
|
|
1741
|
+
prev.value += value;
|
|
1742
|
+
state.output += prior.output + prev.output;
|
|
1743
|
+
state.globstar = true;
|
|
1744
|
+
consume(value + advance());
|
|
1745
|
+
push({
|
|
1746
|
+
type: "slash",
|
|
1747
|
+
value: "/",
|
|
1748
|
+
output: ""
|
|
1749
|
+
});
|
|
1750
|
+
continue;
|
|
1751
|
+
}
|
|
1752
|
+
if (prior.type === "bos" && rest[0] === "/") {
|
|
1753
|
+
prev.type = "globstar";
|
|
1754
|
+
prev.value += value;
|
|
1755
|
+
prev.output = `(?:^|${SLASH_LITERAL}|${globstar(opts)}${SLASH_LITERAL})`;
|
|
1756
|
+
state.output = prev.output;
|
|
1757
|
+
state.globstar = true;
|
|
1758
|
+
consume(value + advance());
|
|
1759
|
+
push({
|
|
1760
|
+
type: "slash",
|
|
1761
|
+
value: "/",
|
|
1762
|
+
output: ""
|
|
1763
|
+
});
|
|
1764
|
+
continue;
|
|
1765
|
+
}
|
|
1766
|
+
state.output = state.output.slice(0, -prev.output.length);
|
|
1767
|
+
prev.type = "globstar";
|
|
1768
|
+
prev.output = globstar(opts);
|
|
1769
|
+
prev.value += value;
|
|
1770
|
+
state.output += prev.output;
|
|
1771
|
+
state.globstar = true;
|
|
1772
|
+
consume(value);
|
|
1773
|
+
continue;
|
|
1774
|
+
}
|
|
1775
|
+
const token = {
|
|
1776
|
+
type: "star",
|
|
1777
|
+
value,
|
|
1778
|
+
output: star
|
|
1779
|
+
};
|
|
1780
|
+
if (opts.bash === true) {
|
|
1781
|
+
token.output = ".*?";
|
|
1782
|
+
if (prev.type === "bos" || prev.type === "slash") token.output = nodot + token.output;
|
|
1783
|
+
push(token);
|
|
1784
|
+
continue;
|
|
1785
|
+
}
|
|
1786
|
+
if (prev && (prev.type === "bracket" || prev.type === "paren") && opts.regex === true) {
|
|
1787
|
+
token.output = value;
|
|
1788
|
+
push(token);
|
|
1789
|
+
continue;
|
|
1790
|
+
}
|
|
1791
|
+
if (state.index === state.start || prev.type === "slash" || prev.type === "dot") {
|
|
1792
|
+
if (prev.type === "dot") {
|
|
1793
|
+
state.output += NO_DOT_SLASH;
|
|
1794
|
+
prev.output += NO_DOT_SLASH;
|
|
1795
|
+
} else if (opts.dot === true) {
|
|
1796
|
+
state.output += NO_DOTS_SLASH;
|
|
1797
|
+
prev.output += NO_DOTS_SLASH;
|
|
1798
|
+
} else {
|
|
1799
|
+
state.output += nodot;
|
|
1800
|
+
prev.output += nodot;
|
|
1801
|
+
}
|
|
1802
|
+
if (peek() !== "*") {
|
|
1803
|
+
state.output += ONE_CHAR;
|
|
1804
|
+
prev.output += ONE_CHAR;
|
|
1805
|
+
}
|
|
1806
|
+
}
|
|
1807
|
+
push(token);
|
|
1808
|
+
}
|
|
1809
|
+
while (state.brackets > 0) {
|
|
1810
|
+
if (opts.strictBrackets === true) throw new SyntaxError(syntaxError("closing", "]"));
|
|
1811
|
+
state.output = utils.escapeLast(state.output, "[");
|
|
1812
|
+
decrement("brackets");
|
|
1813
|
+
}
|
|
1814
|
+
while (state.parens > 0) {
|
|
1815
|
+
if (opts.strictBrackets === true) throw new SyntaxError(syntaxError("closing", ")"));
|
|
1816
|
+
state.output = utils.escapeLast(state.output, "(");
|
|
1817
|
+
decrement("parens");
|
|
1818
|
+
}
|
|
1819
|
+
while (state.braces > 0) {
|
|
1820
|
+
if (opts.strictBrackets === true) throw new SyntaxError(syntaxError("closing", "}"));
|
|
1821
|
+
state.output = utils.escapeLast(state.output, "{");
|
|
1822
|
+
decrement("braces");
|
|
1823
|
+
}
|
|
1824
|
+
if (opts.strictSlashes !== true && (prev.type === "star" || prev.type === "bracket")) push({
|
|
1825
|
+
type: "maybe_slash",
|
|
1826
|
+
value: "",
|
|
1827
|
+
output: `${SLASH_LITERAL}?`
|
|
1828
|
+
});
|
|
1829
|
+
if (state.backtrack === true) {
|
|
1830
|
+
state.output = "";
|
|
1831
|
+
for (const token of state.tokens) {
|
|
1832
|
+
state.output += token.output != null ? token.output : token.value;
|
|
1833
|
+
if (token.suffix) state.output += token.suffix;
|
|
1834
|
+
}
|
|
1835
|
+
}
|
|
1836
|
+
return state;
|
|
1837
|
+
};
|
|
1838
|
+
/**
|
|
1839
|
+
* Fast paths for creating regular expressions for common glob patterns.
|
|
1840
|
+
* This can significantly speed up processing and has very little downside
|
|
1841
|
+
* impact when none of the fast paths match.
|
|
1842
|
+
*/
|
|
1843
|
+
parse.fastpaths = (input, options) => {
|
|
1844
|
+
const opts = { ...options };
|
|
1845
|
+
const max = typeof opts.maxLength === "number" ? Math.min(MAX_LENGTH, opts.maxLength) : MAX_LENGTH;
|
|
1846
|
+
const len = input.length;
|
|
1847
|
+
if (len > max) throw new SyntaxError(`Input length: ${len}, exceeds maximum allowed length: ${max}`);
|
|
1848
|
+
input = REPLACEMENTS[input] || input;
|
|
1849
|
+
const win32 = utils.isWindows(options);
|
|
1850
|
+
const { DOT_LITERAL, SLASH_LITERAL, ONE_CHAR, DOTS_SLASH, NO_DOT, NO_DOTS, NO_DOTS_SLASH, STAR, START_ANCHOR } = constants.globChars(win32);
|
|
1851
|
+
const nodot = opts.dot ? NO_DOTS : NO_DOT;
|
|
1852
|
+
const slashDot = opts.dot ? NO_DOTS_SLASH : NO_DOT;
|
|
1853
|
+
const capture = opts.capture ? "" : "?:";
|
|
1854
|
+
const state = {
|
|
1855
|
+
negated: false,
|
|
1856
|
+
prefix: ""
|
|
1857
|
+
};
|
|
1858
|
+
let star = opts.bash === true ? ".*?" : STAR;
|
|
1859
|
+
if (opts.capture) star = `(${star})`;
|
|
1860
|
+
const globstar = (opts) => {
|
|
1861
|
+
if (opts.noglobstar === true) return star;
|
|
1862
|
+
return `(${capture}(?:(?!${START_ANCHOR}${opts.dot ? DOTS_SLASH : DOT_LITERAL}).)*?)`;
|
|
1863
|
+
};
|
|
1864
|
+
const create = (str) => {
|
|
1865
|
+
switch (str) {
|
|
1866
|
+
case "*": return `${nodot}${ONE_CHAR}${star}`;
|
|
1867
|
+
case ".*": return `${DOT_LITERAL}${ONE_CHAR}${star}`;
|
|
1868
|
+
case "*.*": return `${nodot}${star}${DOT_LITERAL}${ONE_CHAR}${star}`;
|
|
1869
|
+
case "*/*": return `${nodot}${star}${SLASH_LITERAL}${ONE_CHAR}${slashDot}${star}`;
|
|
1870
|
+
case "**": return nodot + globstar(opts);
|
|
1871
|
+
case "**/*": return `(?:${nodot}${globstar(opts)}${SLASH_LITERAL})?${slashDot}${ONE_CHAR}${star}`;
|
|
1872
|
+
case "**/*.*": return `(?:${nodot}${globstar(opts)}${SLASH_LITERAL})?${slashDot}${star}${DOT_LITERAL}${ONE_CHAR}${star}`;
|
|
1873
|
+
case "**/.*": return `(?:${nodot}${globstar(opts)}${SLASH_LITERAL})?${DOT_LITERAL}${ONE_CHAR}${star}`;
|
|
1874
|
+
default: {
|
|
1875
|
+
const match = /^(.*?)\.(\w+)$/.exec(str);
|
|
1876
|
+
if (!match) return;
|
|
1877
|
+
const source = create(match[1]);
|
|
1878
|
+
if (!source) return;
|
|
1879
|
+
return source + DOT_LITERAL + match[2];
|
|
1880
|
+
}
|
|
1881
|
+
}
|
|
1882
|
+
};
|
|
1883
|
+
let source = create(utils.removePrefix(input, state));
|
|
1884
|
+
if (source && opts.strictSlashes !== true) source += `${SLASH_LITERAL}?`;
|
|
1885
|
+
return source;
|
|
1886
|
+
};
|
|
1887
|
+
module.exports = parse;
|
|
1888
|
+
}));
|
|
1889
|
+
//#endregion
|
|
1890
|
+
//#region node_modules/picomatch/lib/picomatch.js
|
|
1891
|
+
var require_picomatch$1 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
1892
|
+
const path$1 = __require("path");
|
|
1893
|
+
const scan = require_scan();
|
|
1894
|
+
const parse = require_parse();
|
|
1895
|
+
const utils = require_utils();
|
|
1896
|
+
const constants = require_constants();
|
|
1897
|
+
const isObject = (val) => val && typeof val === "object" && !Array.isArray(val);
|
|
1898
|
+
/**
|
|
1899
|
+
* Creates a matcher function from one or more glob patterns. The
|
|
1900
|
+
* returned function takes a string to match as its first argument,
|
|
1901
|
+
* and returns true if the string is a match. The returned matcher
|
|
1902
|
+
* function also takes a boolean as the second argument that, when true,
|
|
1903
|
+
* returns an object with additional information.
|
|
1904
|
+
*
|
|
1905
|
+
* ```js
|
|
1906
|
+
* const picomatch = require('picomatch');
|
|
1907
|
+
* // picomatch(glob[, options]);
|
|
1908
|
+
*
|
|
1909
|
+
* const isMatch = picomatch('*.!(*a)');
|
|
1910
|
+
* console.log(isMatch('a.a')); //=> false
|
|
1911
|
+
* console.log(isMatch('a.b')); //=> true
|
|
1912
|
+
* ```
|
|
1913
|
+
* @name picomatch
|
|
1914
|
+
* @param {String|Array} `globs` One or more glob patterns.
|
|
1915
|
+
* @param {Object=} `options`
|
|
1916
|
+
* @return {Function=} Returns a matcher function.
|
|
1917
|
+
* @api public
|
|
1918
|
+
*/
|
|
1919
|
+
const picomatch = (glob, options, returnState = false) => {
|
|
1920
|
+
if (Array.isArray(glob)) {
|
|
1921
|
+
const fns = glob.map((input) => picomatch(input, options, returnState));
|
|
1922
|
+
const arrayMatcher = (str) => {
|
|
1923
|
+
for (const isMatch of fns) {
|
|
1924
|
+
const state = isMatch(str);
|
|
1925
|
+
if (state) return state;
|
|
1926
|
+
}
|
|
1927
|
+
return false;
|
|
1928
|
+
};
|
|
1929
|
+
return arrayMatcher;
|
|
1930
|
+
}
|
|
1931
|
+
const isState = isObject(glob) && glob.tokens && glob.input;
|
|
1932
|
+
if (glob === "" || typeof glob !== "string" && !isState) throw new TypeError("Expected pattern to be a non-empty string");
|
|
1933
|
+
const opts = options || {};
|
|
1934
|
+
const posix = utils.isWindows(options);
|
|
1935
|
+
const regex = isState ? picomatch.compileRe(glob, options) : picomatch.makeRe(glob, options, false, true);
|
|
1936
|
+
const state = regex.state;
|
|
1937
|
+
delete regex.state;
|
|
1938
|
+
let isIgnored = () => false;
|
|
1939
|
+
if (opts.ignore) {
|
|
1940
|
+
const ignoreOpts = {
|
|
1941
|
+
...options,
|
|
1942
|
+
ignore: null,
|
|
1943
|
+
onMatch: null,
|
|
1944
|
+
onResult: null
|
|
1945
|
+
};
|
|
1946
|
+
isIgnored = picomatch(opts.ignore, ignoreOpts, returnState);
|
|
1947
|
+
}
|
|
1948
|
+
const matcher = (input, returnObject = false) => {
|
|
1949
|
+
const { isMatch, match, output } = picomatch.test(input, regex, options, {
|
|
1950
|
+
glob,
|
|
1951
|
+
posix
|
|
1952
|
+
});
|
|
1953
|
+
const result = {
|
|
1954
|
+
glob,
|
|
1955
|
+
state,
|
|
1956
|
+
regex,
|
|
1957
|
+
posix,
|
|
1958
|
+
input,
|
|
1959
|
+
output,
|
|
1960
|
+
match,
|
|
1961
|
+
isMatch
|
|
1962
|
+
};
|
|
1963
|
+
if (typeof opts.onResult === "function") opts.onResult(result);
|
|
1964
|
+
if (isMatch === false) {
|
|
1965
|
+
result.isMatch = false;
|
|
1966
|
+
return returnObject ? result : false;
|
|
1967
|
+
}
|
|
1968
|
+
if (isIgnored(input)) {
|
|
1969
|
+
if (typeof opts.onIgnore === "function") opts.onIgnore(result);
|
|
1970
|
+
result.isMatch = false;
|
|
1971
|
+
return returnObject ? result : false;
|
|
1972
|
+
}
|
|
1973
|
+
if (typeof opts.onMatch === "function") opts.onMatch(result);
|
|
1974
|
+
return returnObject ? result : true;
|
|
1975
|
+
};
|
|
1976
|
+
if (returnState) matcher.state = state;
|
|
1977
|
+
return matcher;
|
|
1978
|
+
};
|
|
1979
|
+
/**
|
|
1980
|
+
* Test `input` with the given `regex`. This is used by the main
|
|
1981
|
+
* `picomatch()` function to test the input string.
|
|
1982
|
+
*
|
|
1983
|
+
* ```js
|
|
1984
|
+
* const picomatch = require('picomatch');
|
|
1985
|
+
* // picomatch.test(input, regex[, options]);
|
|
1986
|
+
*
|
|
1987
|
+
* console.log(picomatch.test('foo/bar', /^(?:([^/]*?)\/([^/]*?))$/));
|
|
1988
|
+
* // { isMatch: true, match: [ 'foo/', 'foo', 'bar' ], output: 'foo/bar' }
|
|
1989
|
+
* ```
|
|
1990
|
+
* @param {String} `input` String to test.
|
|
1991
|
+
* @param {RegExp} `regex`
|
|
1992
|
+
* @return {Object} Returns an object with matching info.
|
|
1993
|
+
* @api public
|
|
1994
|
+
*/
|
|
1995
|
+
picomatch.test = (input, regex, options, { glob, posix } = {}) => {
|
|
1996
|
+
if (typeof input !== "string") throw new TypeError("Expected input to be a string");
|
|
1997
|
+
if (input === "") return {
|
|
1998
|
+
isMatch: false,
|
|
1999
|
+
output: ""
|
|
2000
|
+
};
|
|
2001
|
+
const opts = options || {};
|
|
2002
|
+
const format = opts.format || (posix ? utils.toPosixSlashes : null);
|
|
2003
|
+
let match = input === glob;
|
|
2004
|
+
let output = match && format ? format(input) : input;
|
|
2005
|
+
if (match === false) {
|
|
2006
|
+
output = format ? format(input) : input;
|
|
2007
|
+
match = output === glob;
|
|
2008
|
+
}
|
|
2009
|
+
if (match === false || opts.capture === true) if (opts.matchBase === true || opts.basename === true) match = picomatch.matchBase(input, regex, options, posix);
|
|
2010
|
+
else match = regex.exec(output);
|
|
2011
|
+
return {
|
|
2012
|
+
isMatch: Boolean(match),
|
|
2013
|
+
match,
|
|
2014
|
+
output
|
|
2015
|
+
};
|
|
2016
|
+
};
|
|
2017
|
+
/**
|
|
2018
|
+
* Match the basename of a filepath.
|
|
2019
|
+
*
|
|
2020
|
+
* ```js
|
|
2021
|
+
* const picomatch = require('picomatch');
|
|
2022
|
+
* // picomatch.matchBase(input, glob[, options]);
|
|
2023
|
+
* console.log(picomatch.matchBase('foo/bar.js', '*.js'); // true
|
|
2024
|
+
* ```
|
|
2025
|
+
* @param {String} `input` String to test.
|
|
2026
|
+
* @param {RegExp|String} `glob` Glob pattern or regex created by [.makeRe](#makeRe).
|
|
2027
|
+
* @return {Boolean}
|
|
2028
|
+
* @api public
|
|
2029
|
+
*/
|
|
2030
|
+
picomatch.matchBase = (input, glob, options, posix = utils.isWindows(options)) => {
|
|
2031
|
+
return (glob instanceof RegExp ? glob : picomatch.makeRe(glob, options)).test(path$1.basename(input));
|
|
2032
|
+
};
|
|
2033
|
+
/**
|
|
2034
|
+
* Returns true if **any** of the given glob `patterns` match the specified `string`.
|
|
2035
|
+
*
|
|
2036
|
+
* ```js
|
|
2037
|
+
* const picomatch = require('picomatch');
|
|
2038
|
+
* // picomatch.isMatch(string, patterns[, options]);
|
|
2039
|
+
*
|
|
2040
|
+
* console.log(picomatch.isMatch('a.a', ['b.*', '*.a'])); //=> true
|
|
2041
|
+
* console.log(picomatch.isMatch('a.a', 'b.*')); //=> false
|
|
2042
|
+
* ```
|
|
2043
|
+
* @param {String|Array} str The string to test.
|
|
2044
|
+
* @param {String|Array} patterns One or more glob patterns to use for matching.
|
|
2045
|
+
* @param {Object} [options] See available [options](#options).
|
|
2046
|
+
* @return {Boolean} Returns true if any patterns match `str`
|
|
2047
|
+
* @api public
|
|
2048
|
+
*/
|
|
2049
|
+
picomatch.isMatch = (str, patterns, options) => picomatch(patterns, options)(str);
|
|
2050
|
+
/**
|
|
2051
|
+
* Parse a glob pattern to create the source string for a regular
|
|
2052
|
+
* expression.
|
|
2053
|
+
*
|
|
2054
|
+
* ```js
|
|
2055
|
+
* const picomatch = require('picomatch');
|
|
2056
|
+
* const result = picomatch.parse(pattern[, options]);
|
|
2057
|
+
* ```
|
|
2058
|
+
* @param {String} `pattern`
|
|
2059
|
+
* @param {Object} `options`
|
|
2060
|
+
* @return {Object} Returns an object with useful properties and output to be used as a regex source string.
|
|
2061
|
+
* @api public
|
|
2062
|
+
*/
|
|
2063
|
+
picomatch.parse = (pattern, options) => {
|
|
2064
|
+
if (Array.isArray(pattern)) return pattern.map((p) => picomatch.parse(p, options));
|
|
2065
|
+
return parse(pattern, {
|
|
2066
|
+
...options,
|
|
2067
|
+
fastpaths: false
|
|
2068
|
+
});
|
|
2069
|
+
};
|
|
2070
|
+
/**
|
|
2071
|
+
* Scan a glob pattern to separate the pattern into segments.
|
|
2072
|
+
*
|
|
2073
|
+
* ```js
|
|
2074
|
+
* const picomatch = require('picomatch');
|
|
2075
|
+
* // picomatch.scan(input[, options]);
|
|
2076
|
+
*
|
|
2077
|
+
* const result = picomatch.scan('!./foo/*.js');
|
|
2078
|
+
* console.log(result);
|
|
2079
|
+
* { prefix: '!./',
|
|
2080
|
+
* input: '!./foo/*.js',
|
|
2081
|
+
* start: 3,
|
|
2082
|
+
* base: 'foo',
|
|
2083
|
+
* glob: '*.js',
|
|
2084
|
+
* isBrace: false,
|
|
2085
|
+
* isBracket: false,
|
|
2086
|
+
* isGlob: true,
|
|
2087
|
+
* isExtglob: false,
|
|
2088
|
+
* isGlobstar: false,
|
|
2089
|
+
* negated: true }
|
|
2090
|
+
* ```
|
|
2091
|
+
* @param {String} `input` Glob pattern to scan.
|
|
2092
|
+
* @param {Object} `options`
|
|
2093
|
+
* @return {Object} Returns an object with
|
|
2094
|
+
* @api public
|
|
2095
|
+
*/
|
|
2096
|
+
picomatch.scan = (input, options) => scan(input, options);
|
|
2097
|
+
/**
|
|
2098
|
+
* Compile a regular expression from the `state` object returned by the
|
|
2099
|
+
* [parse()](#parse) method.
|
|
2100
|
+
*
|
|
2101
|
+
* @param {Object} `state`
|
|
2102
|
+
* @param {Object} `options`
|
|
2103
|
+
* @param {Boolean} `returnOutput` Intended for implementors, this argument allows you to return the raw output from the parser.
|
|
2104
|
+
* @param {Boolean} `returnState` Adds the state to a `state` property on the returned regex. Useful for implementors and debugging.
|
|
2105
|
+
* @return {RegExp}
|
|
2106
|
+
* @api public
|
|
2107
|
+
*/
|
|
2108
|
+
picomatch.compileRe = (state, options, returnOutput = false, returnState = false) => {
|
|
2109
|
+
if (returnOutput === true) return state.output;
|
|
2110
|
+
const opts = options || {};
|
|
2111
|
+
const prepend = opts.contains ? "" : "^";
|
|
2112
|
+
const append = opts.contains ? "" : "$";
|
|
2113
|
+
let source = `${prepend}(?:${state.output})${append}`;
|
|
2114
|
+
if (state && state.negated === true) source = `^(?!${source}).*$`;
|
|
2115
|
+
const regex = picomatch.toRegex(source, options);
|
|
2116
|
+
if (returnState === true) regex.state = state;
|
|
2117
|
+
return regex;
|
|
2118
|
+
};
|
|
2119
|
+
/**
|
|
2120
|
+
* Create a regular expression from a parsed glob pattern.
|
|
2121
|
+
*
|
|
2122
|
+
* ```js
|
|
2123
|
+
* const picomatch = require('picomatch');
|
|
2124
|
+
* const state = picomatch.parse('*.js');
|
|
2125
|
+
* // picomatch.compileRe(state[, options]);
|
|
2126
|
+
*
|
|
2127
|
+
* console.log(picomatch.compileRe(state));
|
|
2128
|
+
* //=> /^(?:(?!\.)(?=.)[^/]*?\.js)$/
|
|
2129
|
+
* ```
|
|
2130
|
+
* @param {String} `state` The object returned from the `.parse` method.
|
|
2131
|
+
* @param {Object} `options`
|
|
2132
|
+
* @param {Boolean} `returnOutput` Implementors may use this argument to return the compiled output, instead of a regular expression. This is not exposed on the options to prevent end-users from mutating the result.
|
|
2133
|
+
* @param {Boolean} `returnState` Implementors may use this argument to return the state from the parsed glob with the returned regular expression.
|
|
2134
|
+
* @return {RegExp} Returns a regex created from the given pattern.
|
|
2135
|
+
* @api public
|
|
2136
|
+
*/
|
|
2137
|
+
picomatch.makeRe = (input, options = {}, returnOutput = false, returnState = false) => {
|
|
2138
|
+
if (!input || typeof input !== "string") throw new TypeError("Expected a non-empty string");
|
|
2139
|
+
let parsed = {
|
|
2140
|
+
negated: false,
|
|
2141
|
+
fastpaths: true
|
|
2142
|
+
};
|
|
2143
|
+
if (options.fastpaths !== false && (input[0] === "." || input[0] === "*")) parsed.output = parse.fastpaths(input, options);
|
|
2144
|
+
if (!parsed.output) parsed = parse(input, options);
|
|
2145
|
+
return picomatch.compileRe(parsed, options, returnOutput, returnState);
|
|
2146
|
+
};
|
|
2147
|
+
/**
|
|
2148
|
+
* Create a regular expression from the given regex source string.
|
|
2149
|
+
*
|
|
2150
|
+
* ```js
|
|
2151
|
+
* const picomatch = require('picomatch');
|
|
2152
|
+
* // picomatch.toRegex(source[, options]);
|
|
2153
|
+
*
|
|
2154
|
+
* const { output } = picomatch.parse('*.js');
|
|
2155
|
+
* console.log(picomatch.toRegex(output));
|
|
2156
|
+
* //=> /^(?:(?!\.)(?=.)[^/]*?\.js)$/
|
|
2157
|
+
* ```
|
|
2158
|
+
* @param {String} `source` Regular expression source string.
|
|
2159
|
+
* @param {Object} `options`
|
|
2160
|
+
* @return {RegExp}
|
|
2161
|
+
* @api public
|
|
2162
|
+
*/
|
|
2163
|
+
picomatch.toRegex = (source, options) => {
|
|
2164
|
+
try {
|
|
2165
|
+
const opts = options || {};
|
|
2166
|
+
return new RegExp(source, opts.flags || (opts.nocase ? "i" : ""));
|
|
2167
|
+
} catch (err) {
|
|
2168
|
+
if (options && options.debug === true) throw err;
|
|
2169
|
+
return /$^/;
|
|
2170
|
+
}
|
|
2171
|
+
};
|
|
2172
|
+
/**
|
|
2173
|
+
* Picomatch constants.
|
|
2174
|
+
* @return {Object}
|
|
2175
|
+
*/
|
|
2176
|
+
picomatch.constants = constants;
|
|
2177
|
+
/**
|
|
2178
|
+
* Expose "picomatch"
|
|
2179
|
+
*/
|
|
2180
|
+
module.exports = picomatch;
|
|
2181
|
+
}));
|
|
2182
|
+
//#endregion
|
|
2183
|
+
//#region src/diff-filter.ts
|
|
2184
|
+
var import_picomatch = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
2185
|
+
module.exports = require_picomatch$1();
|
|
2186
|
+
})))(), 1);
|
|
2187
|
+
function filterGitDiff(diff, config) {
|
|
2188
|
+
const isIncluded = (0, import_picomatch.default)(config.include);
|
|
2189
|
+
const isExcluded = (0, import_picomatch.default)(config.exclude);
|
|
2190
|
+
const matches = (filePath) => isIncluded(filePath) && !isExcluded(filePath);
|
|
2191
|
+
const toIndex = [];
|
|
2192
|
+
const toDelete = [];
|
|
2193
|
+
const renamed = [];
|
|
2194
|
+
for (const filePath of [...diff.added, ...diff.modified]) if (matches(filePath)) toIndex.push(filePath);
|
|
2195
|
+
for (const filePath of diff.deleted) if (matches(filePath)) toDelete.push(filePath);
|
|
2196
|
+
for (const r of diff.renamed) {
|
|
2197
|
+
const fromMatches = matches(r.from);
|
|
2198
|
+
const toMatches = matches(r.to);
|
|
2199
|
+
if (toMatches) renamed.push(r);
|
|
2200
|
+
if (fromMatches && !toMatches) toDelete.push(r.from);
|
|
2201
|
+
}
|
|
2202
|
+
return {
|
|
2203
|
+
toIndex,
|
|
2204
|
+
toDelete,
|
|
2205
|
+
renamed
|
|
2206
|
+
};
|
|
2207
|
+
}
|
|
2208
|
+
//#endregion
|
|
2209
|
+
//#region src/parser/base.ts
|
|
2210
|
+
var BaseParser = class {
|
|
2211
|
+
supports(filePath) {
|
|
2212
|
+
return this.extensions.some((ext) => filePath.endsWith(ext));
|
|
2213
|
+
}
|
|
2214
|
+
};
|
|
2215
|
+
//#endregion
|
|
2216
|
+
//#region src/parser/swift.ts
|
|
2217
|
+
const swiftParser = new Parser();
|
|
2218
|
+
swiftParser.setLanguage(Swift);
|
|
2219
|
+
const SYMBOL_NODE_TYPES$1 = {
|
|
2220
|
+
class_declaration: "class",
|
|
2221
|
+
protocol_declaration: "protocol",
|
|
2222
|
+
function_declaration: "function"
|
|
2223
|
+
};
|
|
2224
|
+
const KEYWORD_TO_KIND = {
|
|
2225
|
+
struct: "struct",
|
|
2226
|
+
enum: "enum",
|
|
2227
|
+
class: "class"
|
|
2228
|
+
};
|
|
2229
|
+
function extractName$1(node) {
|
|
2230
|
+
/* v8 ignore next */
|
|
2231
|
+
return node.childForFieldName("name")?.text ?? null;
|
|
2232
|
+
}
|
|
2233
|
+
var SwiftParser = class extends BaseParser {
|
|
2234
|
+
extensions = [".swift"];
|
|
2235
|
+
parse(content, _filePath) {
|
|
2236
|
+
const rootNode = swiftParser.parse(content).rootNode;
|
|
2237
|
+
const imports = [];
|
|
2238
|
+
const exports = [];
|
|
2239
|
+
const symbols = [];
|
|
2240
|
+
function walk(node) {
|
|
2241
|
+
if (node.type === "import_declaration") {
|
|
2242
|
+
const children = node.children.filter((c) => c.type !== "import");
|
|
2243
|
+
/* v8 ignore next */
|
|
2244
|
+
if (children.length > 0) imports.push(children.map((c) => c.text).join("."));
|
|
2245
|
+
}
|
|
2246
|
+
let kind = SYMBOL_NODE_TYPES$1[node.type];
|
|
2247
|
+
if (node.type === "class_declaration") {
|
|
2248
|
+
const keyword = node.children.find((c) => c.type in KEYWORD_TO_KIND);
|
|
2249
|
+
/* v8 ignore next */
|
|
2250
|
+
kind = keyword ? KEYWORD_TO_KIND[keyword.type] : "class";
|
|
2251
|
+
}
|
|
2252
|
+
if (kind) {
|
|
2253
|
+
const name = extractName$1(node);
|
|
2254
|
+
/* v8 ignore next */
|
|
2255
|
+
if (name) {
|
|
2256
|
+
symbols.push({
|
|
2257
|
+
name,
|
|
2258
|
+
kind,
|
|
2259
|
+
description: ""
|
|
2260
|
+
});
|
|
2261
|
+
exports.push(name);
|
|
2262
|
+
}
|
|
2263
|
+
}
|
|
2264
|
+
for (const child of node.children) walk(child);
|
|
2265
|
+
}
|
|
2266
|
+
walk(rootNode);
|
|
2267
|
+
return {
|
|
2268
|
+
imports,
|
|
2269
|
+
exports,
|
|
2270
|
+
symbols
|
|
2271
|
+
};
|
|
2272
|
+
}
|
|
2273
|
+
};
|
|
2274
|
+
//#endregion
|
|
2275
|
+
//#region src/parser/typescript.ts
|
|
2276
|
+
const tsParser = new Parser();
|
|
2277
|
+
tsParser.setLanguage(TypeScript.typescript);
|
|
2278
|
+
const tsxParser = new Parser();
|
|
2279
|
+
tsxParser.setLanguage(TypeScript.tsx);
|
|
2280
|
+
const IMPORT_TYPES = new Set(["import_statement"]);
|
|
2281
|
+
const EXPORT_NODE_TYPES = new Set(["export_statement"]);
|
|
2282
|
+
const SYMBOL_NODE_TYPES = {
|
|
2283
|
+
class_declaration: "class",
|
|
2284
|
+
function_declaration: "function",
|
|
2285
|
+
method_definition: "method",
|
|
2286
|
+
interface_declaration: "interface",
|
|
2287
|
+
type_alias_declaration: "type",
|
|
2288
|
+
enum_declaration: "enum",
|
|
2289
|
+
lexical_declaration: "variable",
|
|
2290
|
+
variable_declaration: "variable"
|
|
2291
|
+
};
|
|
2292
|
+
function extractName(node) {
|
|
2293
|
+
const nameNode = node.childForFieldName("name");
|
|
2294
|
+
if (nameNode) return nameNode.text;
|
|
2295
|
+
for (const child of node.children) if (child.type === "variable_declarator") {
|
|
2296
|
+
const n = child.childForFieldName("name");
|
|
2297
|
+
/* v8 ignore next */
|
|
2298
|
+
if (n) return n.text;
|
|
2299
|
+
}
|
|
2300
|
+
/* v8 ignore next */
|
|
2301
|
+
return null;
|
|
2302
|
+
}
|
|
2303
|
+
function extractImports(rootNode) {
|
|
2304
|
+
const imports = [];
|
|
2305
|
+
for (const child of rootNode.children) if (IMPORT_TYPES.has(child.type)) {
|
|
2306
|
+
const source = child.childForFieldName("source");
|
|
2307
|
+
/* v8 ignore next */
|
|
2308
|
+
if (source) imports.push(source.text.replace(/['"]/g, ""));
|
|
2309
|
+
}
|
|
2310
|
+
return imports;
|
|
2311
|
+
}
|
|
2312
|
+
function extractExports(rootNode) {
|
|
2313
|
+
const exports = [];
|
|
2314
|
+
for (const child of rootNode.children) if (EXPORT_NODE_TYPES.has(child.type)) {
|
|
2315
|
+
const exportClause = child.children.find((c) => c.type === "export_clause");
|
|
2316
|
+
if (exportClause) {
|
|
2317
|
+
for (const specifier of exportClause.children) if (specifier.type === "export_specifier") {
|
|
2318
|
+
const name = specifier.childForFieldName("name");
|
|
2319
|
+
/* v8 ignore next */
|
|
2320
|
+
if (name) exports.push(name.text);
|
|
2321
|
+
}
|
|
2322
|
+
continue;
|
|
2323
|
+
}
|
|
2324
|
+
const declaration = child.childForFieldName("declaration");
|
|
2325
|
+
if (declaration) {
|
|
2326
|
+
const name = extractName(declaration);
|
|
2327
|
+
/* v8 ignore next */
|
|
2328
|
+
if (name) exports.push(name);
|
|
2329
|
+
}
|
|
2330
|
+
}
|
|
2331
|
+
return exports;
|
|
2332
|
+
}
|
|
2333
|
+
function extractSymbols(rootNode) {
|
|
2334
|
+
const symbols = [];
|
|
2335
|
+
function walk(node) {
|
|
2336
|
+
if (EXPORT_NODE_TYPES.has(node.type)) {
|
|
2337
|
+
const declaration = node.childForFieldName("declaration");
|
|
2338
|
+
if (declaration) processSymbolNode(declaration);
|
|
2339
|
+
return;
|
|
2340
|
+
}
|
|
2341
|
+
processSymbolNode(node);
|
|
2342
|
+
for (const child of node.children) if (child.type === "class_body") for (const member of child.children) processSymbolNode(member);
|
|
2343
|
+
}
|
|
2344
|
+
function processSymbolNode(node) {
|
|
2345
|
+
const kind = SYMBOL_NODE_TYPES[node.type];
|
|
2346
|
+
if (!kind) return;
|
|
2347
|
+
const name = extractName(node);
|
|
2348
|
+
/* v8 ignore next */
|
|
2349
|
+
if (!name) return;
|
|
2350
|
+
symbols.push({
|
|
2351
|
+
name,
|
|
2352
|
+
kind,
|
|
2353
|
+
description: ""
|
|
2354
|
+
});
|
|
2355
|
+
}
|
|
2356
|
+
for (const child of rootNode.children) walk(child);
|
|
2357
|
+
return symbols;
|
|
2358
|
+
}
|
|
2359
|
+
var TypeScriptParser = class extends BaseParser {
|
|
2360
|
+
extensions = [
|
|
2361
|
+
".ts",
|
|
2362
|
+
".tsx",
|
|
2363
|
+
".js",
|
|
2364
|
+
".jsx"
|
|
2365
|
+
];
|
|
2366
|
+
parse(content, filePath) {
|
|
2367
|
+
const rootNode = (filePath.endsWith(".tsx") || filePath.endsWith(".jsx") ? tsxParser : tsParser).parse(content).rootNode;
|
|
2368
|
+
return {
|
|
2369
|
+
imports: extractImports(rootNode),
|
|
2370
|
+
exports: extractExports(rootNode),
|
|
2371
|
+
symbols: extractSymbols(rootNode)
|
|
2372
|
+
};
|
|
2373
|
+
}
|
|
2374
|
+
};
|
|
2375
|
+
//#endregion
|
|
2376
|
+
//#region src/parser/index.ts
|
|
2377
|
+
const EXTENSION_TO_LANGUAGE = {
|
|
2378
|
+
".ts": "typescript",
|
|
2379
|
+
".tsx": "typescript",
|
|
2380
|
+
".js": "javascript",
|
|
2381
|
+
".jsx": "javascript",
|
|
2382
|
+
".swift": "swift"
|
|
2383
|
+
};
|
|
2384
|
+
var ParserManager = class {
|
|
2385
|
+
parsers;
|
|
2386
|
+
constructor() {
|
|
2387
|
+
this.parsers = [new TypeScriptParser(), new SwiftParser()];
|
|
2388
|
+
}
|
|
2389
|
+
getParser(filePath) {
|
|
2390
|
+
return this.parsers.find((p) => p.supports(filePath));
|
|
2391
|
+
}
|
|
2392
|
+
parse(content, filePath) {
|
|
2393
|
+
const parser = this.getParser(filePath);
|
|
2394
|
+
if (!parser) return null;
|
|
2395
|
+
return parser.parse(content, filePath);
|
|
2396
|
+
}
|
|
2397
|
+
getLanguage(filePath) {
|
|
2398
|
+
return EXTENSION_TO_LANGUAGE[filePath.slice(filePath.lastIndexOf("."))];
|
|
2399
|
+
}
|
|
2400
|
+
};
|
|
2401
|
+
//#endregion
|
|
2402
|
+
//#region src/llm/batcher.ts
|
|
2403
|
+
var Batcher = class {
|
|
2404
|
+
limit;
|
|
2405
|
+
constructor(concurrency = 5) {
|
|
2406
|
+
this.limit = pLimit(concurrency);
|
|
2407
|
+
}
|
|
2408
|
+
async run(tasks) {
|
|
2409
|
+
return Promise.all(tasks.map((task) => this.limit(() => task.execute())));
|
|
2410
|
+
}
|
|
2411
|
+
};
|
|
2412
|
+
//#endregion
|
|
2413
|
+
//#region src/llm/prompts.ts
|
|
2414
|
+
const INDEXING_SYSTEM_PROMPT = `You are a code indexing assistant. Your job is to analyze source code files and generate structured metadata.
|
|
2415
|
+
|
|
2416
|
+
For each file, you must return a JSON object with exactly these fields:
|
|
2417
|
+
- "name": A short, descriptive name for the file (e.g., "User Authentication Service")
|
|
2418
|
+
- "description": A one-line description of what this file does
|
|
2419
|
+
- "summary": A 2-3 sentence summary of the file's purpose and key functionality
|
|
2420
|
+
- "symbols": An array of symbol descriptions, each with "name" and "description"
|
|
2421
|
+
|
|
2422
|
+
Rules:
|
|
2423
|
+
- Be concise and precise
|
|
2424
|
+
- Focus on what the code DOES, not how it's structured
|
|
2425
|
+
- Symbol descriptions should explain the purpose, not restate the signature
|
|
2426
|
+
- Return ONLY valid JSON, no markdown or explanation`;
|
|
2427
|
+
const RERANK_SYSTEM_PROMPT = `You are a search result reranking assistant. Given a user query and a list of candidate files, reorder them by relevance.
|
|
2428
|
+
|
|
2429
|
+
Return ONLY a JSON array of file paths, ordered from most relevant to least relevant.
|
|
2430
|
+
Do not include any explanation, markdown, or extra text — just the JSON array.`;
|
|
2431
|
+
function buildRerankPrompt(query, candidates) {
|
|
2432
|
+
return `User query: "${query}"
|
|
2433
|
+
|
|
2434
|
+
Candidate files:
|
|
2435
|
+
${candidates.map((c, i) => `${i + 1}. ${c.path}\n Name: ${c.name}\n Description: ${c.description}\n Summary: ${c.summary}`).join("\n\n")}
|
|
2436
|
+
|
|
2437
|
+
Reorder these files by relevance to the query. Return a JSON array of file paths, most relevant first.`;
|
|
2438
|
+
}
|
|
2439
|
+
function buildIndexingPrompt(filePath, content, symbols) {
|
|
2440
|
+
return `Analyze this source file and generate indexing metadata.
|
|
2441
|
+
|
|
2442
|
+
File: ${filePath}
|
|
2443
|
+
Detected symbols:
|
|
2444
|
+
${symbols.map((s) => `- ${s.kind}: ${s.name}`).join("\n")}
|
|
2445
|
+
|
|
2446
|
+
Source code:
|
|
2447
|
+
\`\`\`
|
|
2448
|
+
${content}
|
|
2449
|
+
\`\`\`
|
|
2450
|
+
|
|
2451
|
+
Return a JSON object with: name, description, summary, and symbols (array with name + description for each detected symbol).`;
|
|
2452
|
+
}
|
|
2453
|
+
//#endregion
|
|
2454
|
+
//#region src/llm/index.ts
|
|
2455
|
+
/**
|
|
2456
|
+
* Resolve a model by provider and model ID from user config.
|
|
2457
|
+
* pi-ai's getModel is generic over compile-time known providers/models,
|
|
2458
|
+
* but user config is dynamic. This wrapper bridges the gap.
|
|
2459
|
+
*/
|
|
2460
|
+
function resolveModel(provider, modelId) {
|
|
2461
|
+
const model = getModel(provider, modelId);
|
|
2462
|
+
if (!model) throw new Error(`Unknown model "${modelId}" for provider "${provider}". Check your .kly/config.yaml.`);
|
|
2463
|
+
return model;
|
|
2464
|
+
}
|
|
2465
|
+
var LLMService = class {
|
|
2466
|
+
model;
|
|
2467
|
+
batcher;
|
|
2468
|
+
constructor(config) {
|
|
2469
|
+
const envKey = this.getEnvKeyName(config.llm.provider);
|
|
2470
|
+
if (config.llm.apiKey && !process.env[envKey]) process.env[envKey] = config.llm.apiKey;
|
|
2471
|
+
this.model = resolveModel(config.llm.provider, config.llm.model);
|
|
2472
|
+
this.batcher = new Batcher(5);
|
|
2473
|
+
}
|
|
2474
|
+
getEnvKeyName(provider) {
|
|
2475
|
+
return {
|
|
2476
|
+
openrouter: "OPENROUTER_API_KEY",
|
|
2477
|
+
anthropic: "ANTHROPIC_API_KEY",
|
|
2478
|
+
openai: "OPENAI_API_KEY",
|
|
2479
|
+
google: "GOOGLE_API_KEY",
|
|
2480
|
+
mistral: "MISTRAL_API_KEY",
|
|
2481
|
+
groq: "GROQ_API_KEY",
|
|
2482
|
+
xai: "XAI_API_KEY"
|
|
2483
|
+
}[provider] || `${provider.toUpperCase()}_API_KEY`;
|
|
2484
|
+
}
|
|
2485
|
+
async indexFile(filePath, content, symbols) {
|
|
2486
|
+
const prompt = buildIndexingPrompt(filePath, content, symbols.map((s) => ({
|
|
2487
|
+
name: s.name,
|
|
2488
|
+
kind: s.kind
|
|
2489
|
+
})));
|
|
2490
|
+
const text = (await complete(this.model, {
|
|
2491
|
+
systemPrompt: INDEXING_SYSTEM_PROMPT,
|
|
2492
|
+
messages: [{
|
|
2493
|
+
role: "user",
|
|
2494
|
+
content: prompt,
|
|
2495
|
+
timestamp: Date.now()
|
|
2496
|
+
}]
|
|
2497
|
+
})).content.filter((block) => block.type === "text").map((block) => block.text).join("");
|
|
2498
|
+
return this.parseResponse(text);
|
|
2499
|
+
}
|
|
2500
|
+
async indexFiles(files) {
|
|
2501
|
+
const results = /* @__PURE__ */ new Map();
|
|
2502
|
+
const tasks = files.map((file) => ({ execute: async () => {
|
|
2503
|
+
const result = await this.indexFile(file.path, file.content, file.symbols);
|
|
2504
|
+
results.set(file.path, result);
|
|
2505
|
+
return result;
|
|
2506
|
+
} }));
|
|
2507
|
+
await this.batcher.run(tasks);
|
|
2508
|
+
return results;
|
|
2509
|
+
}
|
|
2510
|
+
parseResponse(text) {
|
|
2511
|
+
const jsonMatch = text.match(/```(?:json)?\s*([\s\S]*?)```/);
|
|
2512
|
+
const jsonStr = jsonMatch ? jsonMatch[1].trim() : text.trim();
|
|
2513
|
+
const parsed = JSON.parse(jsonStr);
|
|
2514
|
+
return {
|
|
2515
|
+
name: parsed.name || "",
|
|
2516
|
+
description: parsed.description || "",
|
|
2517
|
+
summary: parsed.summary || "",
|
|
2518
|
+
symbols: Array.isArray(parsed.symbols) ? parsed.symbols.map((s) => ({
|
|
2519
|
+
name: s.name || "",
|
|
2520
|
+
description: s.description || ""
|
|
2521
|
+
})) : []
|
|
2522
|
+
};
|
|
2523
|
+
}
|
|
2524
|
+
};
|
|
2525
|
+
//#endregion
|
|
2526
|
+
//#region src/graph.ts
|
|
2527
|
+
const RESOLVE_EXTENSIONS = [
|
|
2528
|
+
".ts",
|
|
2529
|
+
".tsx",
|
|
2530
|
+
".js",
|
|
2531
|
+
".jsx",
|
|
2532
|
+
"/index.ts",
|
|
2533
|
+
"/index.tsx",
|
|
2534
|
+
"/index.js",
|
|
2535
|
+
"/index.jsx"
|
|
2536
|
+
];
|
|
2537
|
+
function isRelativeImport(importPath) {
|
|
2538
|
+
return importPath.startsWith(".") || importPath.startsWith("/");
|
|
2539
|
+
}
|
|
2540
|
+
/**
|
|
2541
|
+
* Resolve a relative import path to a file path that exists in the index.
|
|
2542
|
+
*/
|
|
2543
|
+
function resolveImport(fromFile, importPath, indexedPaths) {
|
|
2544
|
+
if (!isRelativeImport(importPath)) return void 0;
|
|
2545
|
+
const dir = path.dirname(fromFile);
|
|
2546
|
+
const resolved = path.normalize(path.join(dir, importPath));
|
|
2547
|
+
if (indexedPaths.has(resolved)) return resolved;
|
|
2548
|
+
for (const ext of RESOLVE_EXTENSIONS) {
|
|
2549
|
+
const candidate = resolved + ext;
|
|
2550
|
+
if (indexedPaths.has(candidate)) return candidate;
|
|
2551
|
+
}
|
|
2552
|
+
}
|
|
2553
|
+
/**
|
|
2554
|
+
* Build a dependency graph from indexed files.
|
|
2555
|
+
*/
|
|
2556
|
+
function buildDependencyGraph(db, options = {}) {
|
|
2557
|
+
const { focus, depth = 2 } = options;
|
|
2558
|
+
const allFiles = db.getAllFiles();
|
|
2559
|
+
const indexedPaths = new Set(allFiles.map((f) => f.path));
|
|
2560
|
+
const fileMap = /* @__PURE__ */ new Map();
|
|
2561
|
+
for (const file of allFiles) fileMap.set(file.path, file);
|
|
2562
|
+
const adjacency = /* @__PURE__ */ new Map();
|
|
2563
|
+
for (const file of allFiles) {
|
|
2564
|
+
const deps = [];
|
|
2565
|
+
for (const imp of file.imports) {
|
|
2566
|
+
const resolved = resolveImport(file.path, imp, indexedPaths);
|
|
2567
|
+
if (resolved) deps.push(resolved);
|
|
2568
|
+
}
|
|
2569
|
+
adjacency.set(file.path, deps);
|
|
2570
|
+
}
|
|
2571
|
+
let includedPaths;
|
|
2572
|
+
if (focus) {
|
|
2573
|
+
includedPaths = /* @__PURE__ */ new Set();
|
|
2574
|
+
const queue = [{
|
|
2575
|
+
path: focus,
|
|
2576
|
+
currentDepth: 0
|
|
2577
|
+
}];
|
|
2578
|
+
includedPaths.add(focus);
|
|
2579
|
+
while (queue.length > 0) {
|
|
2580
|
+
const { path: current, currentDepth } = queue.shift();
|
|
2581
|
+
if (currentDepth >= depth) continue;
|
|
2582
|
+
const deps = adjacency.get(current) || [];
|
|
2583
|
+
for (const dep of deps) if (!includedPaths.has(dep)) {
|
|
2584
|
+
includedPaths.add(dep);
|
|
2585
|
+
queue.push({
|
|
2586
|
+
path: dep,
|
|
2587
|
+
currentDepth: currentDepth + 1
|
|
2588
|
+
});
|
|
2589
|
+
}
|
|
2590
|
+
for (const [source, targets] of adjacency) if (targets.includes(current) && !includedPaths.has(source)) {
|
|
2591
|
+
includedPaths.add(source);
|
|
2592
|
+
queue.push({
|
|
2593
|
+
path: source,
|
|
2594
|
+
currentDepth: currentDepth + 1
|
|
2595
|
+
});
|
|
2596
|
+
}
|
|
2597
|
+
}
|
|
2598
|
+
} else includedPaths = indexedPaths;
|
|
2599
|
+
const nodes = /* @__PURE__ */ new Map();
|
|
2600
|
+
const edges = [];
|
|
2601
|
+
for (const filePath of includedPaths) {
|
|
2602
|
+
const file = fileMap.get(filePath);
|
|
2603
|
+
if (!file) continue;
|
|
2604
|
+
nodes.set(filePath, {
|
|
2605
|
+
path: file.path,
|
|
2606
|
+
name: file.name,
|
|
2607
|
+
language: file.language
|
|
2608
|
+
});
|
|
2609
|
+
}
|
|
2610
|
+
for (const filePath of includedPaths) {
|
|
2611
|
+
const deps = adjacency.get(filePath) || [];
|
|
2612
|
+
for (const dep of deps) if (includedPaths.has(dep)) edges.push({
|
|
2613
|
+
from: filePath,
|
|
2614
|
+
to: dep
|
|
2615
|
+
});
|
|
2616
|
+
}
|
|
2617
|
+
return {
|
|
2618
|
+
nodes,
|
|
2619
|
+
edges
|
|
2620
|
+
};
|
|
2621
|
+
}
|
|
2622
|
+
/**
|
|
2623
|
+
* Generate Mermaid syntax from a dependency graph.
|
|
2624
|
+
*/
|
|
2625
|
+
function generateMermaid(graph) {
|
|
2626
|
+
const lines = ["graph LR"];
|
|
2627
|
+
const nodeIds = /* @__PURE__ */ new Map();
|
|
2628
|
+
let idCounter = 0;
|
|
2629
|
+
for (const filePath of graph.nodes.keys()) nodeIds.set(filePath, `N${idCounter++}`);
|
|
2630
|
+
for (const [filePath, node] of graph.nodes) {
|
|
2631
|
+
const id = nodeIds.get(filePath);
|
|
2632
|
+
const label = node.path.replace(/"/g, "'");
|
|
2633
|
+
lines.push(` ${id}["${label}"]`);
|
|
2634
|
+
}
|
|
2635
|
+
for (const edge of graph.edges) {
|
|
2636
|
+
const fromId = nodeIds.get(edge.from);
|
|
2637
|
+
const toId = nodeIds.get(edge.to);
|
|
2638
|
+
if (fromId && toId) lines.push(` ${fromId} --> ${toId}`);
|
|
2639
|
+
}
|
|
2640
|
+
return lines.join("\n");
|
|
2641
|
+
}
|
|
2642
|
+
/**
|
|
2643
|
+
* Render a Mermaid diagram string to ASCII/Unicode art.
|
|
2644
|
+
*/
|
|
2645
|
+
function renderGraphAscii(mermaid) {
|
|
2646
|
+
return renderMermaidASCII(mermaid, { colorMode: "none" });
|
|
2647
|
+
}
|
|
2648
|
+
/**
|
|
2649
|
+
* Render a Mermaid diagram string to SVG.
|
|
2650
|
+
*/
|
|
2651
|
+
function renderGraphSvg(mermaid) {
|
|
2652
|
+
return renderMermaidSVG(mermaid, { transparent: true });
|
|
2653
|
+
}
|
|
2654
|
+
//#endregion
|
|
2655
|
+
//#region src/indexer.ts
|
|
2656
|
+
function emptyBuildResult(root) {
|
|
2657
|
+
const gitRepo = isGitRepo(root);
|
|
2658
|
+
return {
|
|
2659
|
+
totalFiles: 0,
|
|
2660
|
+
newFiles: 0,
|
|
2661
|
+
updatedFiles: 0,
|
|
2662
|
+
deletedFiles: 0,
|
|
2663
|
+
unchangedFiles: 0,
|
|
2664
|
+
branch: gitRepo ? getCurrentBranch(root) || "detached" : "default",
|
|
2665
|
+
commit: gitRepo ? getCurrentCommit(root) : "",
|
|
2666
|
+
durationMs: 0
|
|
2667
|
+
};
|
|
2668
|
+
}
|
|
2669
|
+
async function buildIndex(root, options = {}) {
|
|
2670
|
+
const start = Date.now();
|
|
2671
|
+
const config = loadConfig(root);
|
|
2672
|
+
const parserManager = new ParserManager();
|
|
2673
|
+
const llmService = new LLMService(config);
|
|
2674
|
+
const gitRepo = isGitRepo(root);
|
|
2675
|
+
let result;
|
|
2676
|
+
if (gitRepo && !options.full) result = await buildGitAware(root, config, parserManager, llmService, options);
|
|
2677
|
+
else result = await buildClassic(root, config, parserManager, llmService, options);
|
|
2678
|
+
result.durationMs = Date.now() - start;
|
|
2679
|
+
return result;
|
|
2680
|
+
}
|
|
2681
|
+
async function buildGitAware(root, config, parserManager, llmService, options) {
|
|
2682
|
+
const branch = getCurrentBranch(root);
|
|
2683
|
+
const commit = getCurrentCommit(root);
|
|
2684
|
+
const dbName = branchToDbName(branch, commit);
|
|
2685
|
+
const state = loadState(root);
|
|
2686
|
+
const currentConfigHash = hashConfig(config);
|
|
2687
|
+
let result;
|
|
2688
|
+
if (state.configHash && state.configHash !== currentConfigHash) {
|
|
2689
|
+
result = await buildClassic(root, config, parserManager, llmService, options, dbName);
|
|
2690
|
+
state.configHash = currentConfigHash;
|
|
2691
|
+
setBranchState(state, dbName, {
|
|
2692
|
+
lastCommit: commit,
|
|
2693
|
+
lastBuilt: Date.now()
|
|
2694
|
+
});
|
|
2695
|
+
saveState(root, state);
|
|
2696
|
+
return result;
|
|
2697
|
+
}
|
|
2698
|
+
const branchState = getBranchState(state, dbName);
|
|
2699
|
+
if (branchState?.lastCommit) if (isAncestor(root, branchState.lastCommit, commit)) result = await buildFromGitDiff(root, config, parserManager, llmService, options, dbName, branchState.lastCommit, commit);
|
|
2700
|
+
else result = await buildClassic(root, config, parserManager, llmService, options, dbName);
|
|
2701
|
+
else {
|
|
2702
|
+
const forkedFromDb = tryForkFromParent(root, dbName, state);
|
|
2703
|
+
if (forkedFromDb) {
|
|
2704
|
+
setBranchState(state, dbName, {
|
|
2705
|
+
lastCommit: "",
|
|
2706
|
+
lastBuilt: 0,
|
|
2707
|
+
forkedFrom: forkedFromDb
|
|
2708
|
+
});
|
|
2709
|
+
const mergeBase = getMergeBase(root, getBranchState(state, forkedFromDb).lastCommit, commit);
|
|
2710
|
+
if (mergeBase) result = await buildFromGitDiff(root, config, parserManager, llmService, options, dbName, mergeBase, commit);
|
|
2711
|
+
else result = await buildClassic(root, config, parserManager, llmService, options, dbName);
|
|
2712
|
+
} else result = await buildClassic(root, config, parserManager, llmService, options, dbName);
|
|
2713
|
+
}
|
|
2714
|
+
state.configHash = currentConfigHash;
|
|
2715
|
+
const newBranchState = {
|
|
2716
|
+
lastCommit: commit,
|
|
2717
|
+
lastBuilt: Date.now()
|
|
2718
|
+
};
|
|
2719
|
+
const currentBranchState = getBranchState(state, dbName);
|
|
2720
|
+
if (currentBranchState?.forkedFrom) newBranchState.forkedFrom = currentBranchState.forkedFrom;
|
|
2721
|
+
setBranchState(state, dbName, newBranchState);
|
|
2722
|
+
saveState(root, state);
|
|
2723
|
+
return result;
|
|
2724
|
+
}
|
|
2725
|
+
function tryForkFromParent(root, dbName, state) {
|
|
2726
|
+
for (const candidate of ["main", "master"]) if (state.branches[candidate] && candidate !== dbName) {
|
|
2727
|
+
copyDatabase(root, candidate, dbName);
|
|
2728
|
+
return candidate;
|
|
2729
|
+
}
|
|
2730
|
+
return null;
|
|
2731
|
+
}
|
|
2732
|
+
async function buildFromGitDiff(root, config, parserManager, llmService, options, dbName, fromCommit, toCommit) {
|
|
2733
|
+
const filtered = filterGitDiff(getChangedFiles(root, fromCommit, toCommit), config);
|
|
2734
|
+
const toIndex = [...filtered.toIndex];
|
|
2735
|
+
const toDelete = [...filtered.toDelete];
|
|
2736
|
+
for (const r of filtered.renamed) {
|
|
2737
|
+
toIndex.push(r.to);
|
|
2738
|
+
toDelete.push(r.from);
|
|
2739
|
+
}
|
|
2740
|
+
const db = openDatabase(root, dbName);
|
|
2741
|
+
const result = emptyBuildResult(root);
|
|
2742
|
+
try {
|
|
2743
|
+
if (toDelete.length > 0) {
|
|
2744
|
+
db.removeFiles(toDelete);
|
|
2745
|
+
db.removeDependenciesBatch(toDelete);
|
|
2746
|
+
result.deletedFiles = toDelete.length;
|
|
2747
|
+
}
|
|
2748
|
+
if (toIndex.length === 0) {
|
|
2749
|
+
options.onProgress?.({
|
|
2750
|
+
total: 0,
|
|
2751
|
+
completed: 0,
|
|
2752
|
+
current: "",
|
|
2753
|
+
skipped: 0
|
|
2754
|
+
});
|
|
2755
|
+
result.totalFiles = db.getFileCount();
|
|
2756
|
+
result.unchangedFiles = result.totalFiles;
|
|
2757
|
+
return result;
|
|
2758
|
+
}
|
|
2759
|
+
const indexResult = await indexFiles(root, toIndex, db, parserManager, llmService, options);
|
|
2760
|
+
result.newFiles = indexResult.newFiles;
|
|
2761
|
+
result.updatedFiles = indexResult.updatedFiles;
|
|
2762
|
+
result.totalFiles = db.getFileCount();
|
|
2763
|
+
result.unchangedFiles = result.totalFiles - result.newFiles - result.updatedFiles;
|
|
2764
|
+
return result;
|
|
2765
|
+
} finally {
|
|
2766
|
+
db.close();
|
|
2767
|
+
}
|
|
2768
|
+
}
|
|
2769
|
+
async function buildClassic(root, config, parserManager, llmService, options, dbName) {
|
|
2770
|
+
const db = openDatabase(root, dbName || (isGitRepo(root) ? branchToDbName(getCurrentBranch(root), getCurrentCommit(root)) : "default"));
|
|
2771
|
+
const result = emptyBuildResult(root);
|
|
2772
|
+
try {
|
|
2773
|
+
const files = await scanFiles(root, config);
|
|
2774
|
+
const progress = {
|
|
2775
|
+
total: files.length,
|
|
2776
|
+
completed: 0,
|
|
2777
|
+
current: "",
|
|
2778
|
+
skipped: 0
|
|
2779
|
+
};
|
|
2780
|
+
const toIndex = [];
|
|
2781
|
+
const existingPaths = /* @__PURE__ */ new Set();
|
|
2782
|
+
for (const filePath of files) {
|
|
2783
|
+
const hash = hashFile(root, filePath);
|
|
2784
|
+
const existing = db.getFile(filePath);
|
|
2785
|
+
if (existing) existingPaths.add(filePath);
|
|
2786
|
+
if (existing && !hasChanged(existing.hash, hash) && !options.full) {
|
|
2787
|
+
progress.skipped++;
|
|
2788
|
+
progress.completed++;
|
|
2789
|
+
result.unchangedFiles++;
|
|
2790
|
+
options.onProgress?.(progress);
|
|
2791
|
+
continue;
|
|
2792
|
+
}
|
|
2793
|
+
toIndex.push(filePath);
|
|
2794
|
+
}
|
|
2795
|
+
if (toIndex.length > 0) {
|
|
2796
|
+
await indexFiles(root, toIndex, db, parserManager, llmService, options, progress);
|
|
2797
|
+
for (const filePath of toIndex) if (existingPaths.has(filePath)) result.updatedFiles++;
|
|
2798
|
+
else result.newFiles++;
|
|
2799
|
+
}
|
|
2800
|
+
const fileSet = new Set(files);
|
|
2801
|
+
const toRemove = db.getAllFiles().filter((f) => !fileSet.has(f.path)).map((f) => f.path);
|
|
2802
|
+
if (toRemove.length > 0) {
|
|
2803
|
+
db.removeFiles(toRemove);
|
|
2804
|
+
db.removeDependenciesBatch(toRemove);
|
|
2805
|
+
result.deletedFiles = toRemove.length;
|
|
2806
|
+
}
|
|
2807
|
+
result.totalFiles = db.getFileCount();
|
|
2808
|
+
return result;
|
|
2809
|
+
} finally {
|
|
2810
|
+
db.close();
|
|
2811
|
+
}
|
|
2812
|
+
}
|
|
2813
|
+
async function indexFiles(root, filePaths, db, parserManager, llmService, options, progress) {
|
|
2814
|
+
const prog = progress || {
|
|
2815
|
+
total: filePaths.length,
|
|
2816
|
+
completed: 0,
|
|
2817
|
+
current: "",
|
|
2818
|
+
skipped: 0
|
|
2819
|
+
};
|
|
2820
|
+
if (!progress) prog.total = filePaths.length;
|
|
2821
|
+
const parsedFiles = filePaths.map((filePath) => {
|
|
2822
|
+
const fullPath = path.join(root, filePath);
|
|
2823
|
+
const content = fs.readFileSync(fullPath, "utf-8");
|
|
2824
|
+
const hash = hashFile(root, filePath);
|
|
2825
|
+
const language = parserManager.getLanguage(filePath);
|
|
2826
|
+
const parseResult = parserManager.parse(content, filePath);
|
|
2827
|
+
return {
|
|
2828
|
+
path: filePath,
|
|
2829
|
+
content,
|
|
2830
|
+
hash,
|
|
2831
|
+
language: language || "typescript",
|
|
2832
|
+
imports: parseResult?.imports || [],
|
|
2833
|
+
exports: parseResult?.exports || [],
|
|
2834
|
+
symbols: parseResult?.symbols || []
|
|
2835
|
+
};
|
|
2836
|
+
});
|
|
2837
|
+
const llmResults = await llmService.indexFiles(parsedFiles.map((f) => ({
|
|
2838
|
+
path: f.path,
|
|
2839
|
+
content: f.content,
|
|
2840
|
+
symbols: f.symbols
|
|
2841
|
+
})));
|
|
2842
|
+
const fileIndexes = parsedFiles.map((file) => {
|
|
2843
|
+
const llmResult = llmResults.get(file.path);
|
|
2844
|
+
const fileIndex = {
|
|
2845
|
+
path: file.path,
|
|
2846
|
+
name: llmResult?.name || path.basename(file.path),
|
|
2847
|
+
description: llmResult?.description || "",
|
|
2848
|
+
language: file.language,
|
|
2849
|
+
imports: file.imports,
|
|
2850
|
+
exports: file.exports,
|
|
2851
|
+
symbols: file.symbols.map((s) => {
|
|
2852
|
+
const llmSymbol = llmResult?.symbols.find((ls) => ls.name === s.name);
|
|
2853
|
+
return {
|
|
2854
|
+
...s,
|
|
2855
|
+
description: llmSymbol?.description || s.description
|
|
2856
|
+
};
|
|
2857
|
+
}),
|
|
2858
|
+
summary: llmResult?.summary || "",
|
|
2859
|
+
hash: file.hash,
|
|
2860
|
+
indexedAt: Date.now()
|
|
2861
|
+
};
|
|
2862
|
+
prog.completed++;
|
|
2863
|
+
prog.current = file.path;
|
|
2864
|
+
options.onProgress?.(prog);
|
|
2865
|
+
return fileIndex;
|
|
2866
|
+
});
|
|
2867
|
+
db.upsertFiles(fileIndexes);
|
|
2868
|
+
const allFiles = db.getAllFiles();
|
|
2869
|
+
const indexedPaths = new Set(allFiles.map((f) => f.path));
|
|
2870
|
+
const depEntries = [];
|
|
2871
|
+
for (const file of parsedFiles) {
|
|
2872
|
+
const resolved = [];
|
|
2873
|
+
for (const imp of file.imports) {
|
|
2874
|
+
const target = resolveImport(file.path, imp, indexedPaths);
|
|
2875
|
+
if (target) resolved.push(target);
|
|
2876
|
+
}
|
|
2877
|
+
depEntries.push({
|
|
2878
|
+
fromPath: file.path,
|
|
2879
|
+
toPaths: resolved
|
|
2880
|
+
});
|
|
2881
|
+
}
|
|
2882
|
+
if (depEntries.length > 0) db.upsertBatchDependencies(depEntries);
|
|
2883
|
+
return {
|
|
2884
|
+
indexed: fileIndexes.length,
|
|
2885
|
+
newFiles: 0,
|
|
2886
|
+
updatedFiles: 0
|
|
2887
|
+
};
|
|
2888
|
+
}
|
|
2889
|
+
//#endregion
|
|
2890
|
+
//#region src/llm/reranker.ts
|
|
2891
|
+
/**
|
|
2892
|
+
* Rerank search results using LLM.
|
|
2893
|
+
* Takes FTS5 candidates and returns them reordered by semantic relevance.
|
|
2894
|
+
*/
|
|
2895
|
+
async function rerankResults(model, query, candidates, topK = 10) {
|
|
2896
|
+
if (candidates.length === 0) return [];
|
|
2897
|
+
if (candidates.length <= 1) return candidates.slice(0, topK);
|
|
2898
|
+
const rankedPaths = parseRerankResponse((await complete(model, {
|
|
2899
|
+
systemPrompt: RERANK_SYSTEM_PROMPT,
|
|
2900
|
+
messages: [{
|
|
2901
|
+
role: "user",
|
|
2902
|
+
content: buildRerankPrompt(query, candidates.map((c) => ({
|
|
2903
|
+
path: c.file.path,
|
|
2904
|
+
name: c.file.name,
|
|
2905
|
+
description: c.file.description,
|
|
2906
|
+
summary: c.file.summary
|
|
2907
|
+
}))),
|
|
2908
|
+
timestamp: Date.now()
|
|
2909
|
+
}]
|
|
2910
|
+
})).content.filter((block) => block.type === "text").map((block) => block.text).join(""));
|
|
2911
|
+
const resultMap = /* @__PURE__ */ new Map();
|
|
2912
|
+
for (const candidate of candidates) resultMap.set(candidate.file.path, candidate);
|
|
2913
|
+
const reranked = [];
|
|
2914
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2915
|
+
for (const filePath of rankedPaths) {
|
|
2916
|
+
const result = resultMap.get(filePath);
|
|
2917
|
+
if (result && !seen.has(filePath)) {
|
|
2918
|
+
reranked.push(result);
|
|
2919
|
+
seen.add(filePath);
|
|
2920
|
+
}
|
|
2921
|
+
}
|
|
2922
|
+
for (const candidate of candidates) if (!seen.has(candidate.file.path)) {
|
|
2923
|
+
reranked.push(candidate);
|
|
2924
|
+
seen.add(candidate.file.path);
|
|
2925
|
+
}
|
|
2926
|
+
return reranked.slice(0, topK);
|
|
2927
|
+
}
|
|
2928
|
+
/**
|
|
2929
|
+
* Parse the LLM rerank response into an array of file paths.
|
|
2930
|
+
*/
|
|
2931
|
+
function parseRerankResponse(text) {
|
|
2932
|
+
const jsonMatch = text.match(/```(?:json)?\s*([\s\S]*?)```/);
|
|
2933
|
+
const jsonStr = jsonMatch ? jsonMatch[1].trim() : text.trim();
|
|
2934
|
+
try {
|
|
2935
|
+
const parsed = JSON.parse(jsonStr);
|
|
2936
|
+
if (Array.isArray(parsed) && parsed.every((item) => typeof item === "string")) return parsed;
|
|
2937
|
+
} catch {}
|
|
2938
|
+
return jsonStr.split("\n").map((line) => line.replace(/^[\d.)\-\s"]+/, "").replace(/[",\s]+$/, "").trim()).filter((line) => line.length > 0 && (line.includes("/") || line.includes(".")));
|
|
2939
|
+
}
|
|
2940
|
+
//#endregion
|
|
2941
|
+
//#region src/query.ts
|
|
2942
|
+
function searchFiles(db, query, limit = 20) {
|
|
2943
|
+
return db.searchFiles(query, limit);
|
|
2944
|
+
}
|
|
2945
|
+
async function searchFilesWithRerank(db, model, query, topK = 10) {
|
|
2946
|
+
const candidates = db.searchFiles(query, 50);
|
|
2947
|
+
if (candidates.length === 0) return [];
|
|
2948
|
+
return rerankResults(model, query, candidates, topK);
|
|
2949
|
+
}
|
|
2950
|
+
function filterByLanguage(db, language) {
|
|
2951
|
+
return db.getAllFiles().filter((f) => f.language === language);
|
|
2952
|
+
}
|
|
2953
|
+
function filterByPath(db, pathPattern) {
|
|
2954
|
+
return db.getAllFiles().filter((f) => f.path.includes(pathPattern));
|
|
2955
|
+
}
|
|
2956
|
+
//#endregion
|
|
2957
|
+
//#region src/enrich.ts
|
|
2958
|
+
/**
|
|
2959
|
+
* Enrich error stack frames with file descriptions, dependencies, and git history.
|
|
2960
|
+
*
|
|
2961
|
+
* For each frame, looks up the file in the kly index and augments it with:
|
|
2962
|
+
* - File description, summary, symbols, language (from index)
|
|
2963
|
+
* - importedBy / importsFrom (from dependencies table)
|
|
2964
|
+
* - Recent git commits (from git log)
|
|
2965
|
+
*
|
|
2966
|
+
* Frames whose files are not in the index are included with empty/default values.
|
|
2967
|
+
*/
|
|
2968
|
+
function enrichErrorStack(db, root, frames) {
|
|
2969
|
+
const allImportedBy = /* @__PURE__ */ new Set();
|
|
2970
|
+
const enrichedFrames = [];
|
|
2971
|
+
for (const frame of frames) {
|
|
2972
|
+
const fileIndex = db.getFile(frame.file);
|
|
2973
|
+
const importedBy = db.getDependents(frame.file);
|
|
2974
|
+
const importsFrom = db.getDependencies(frame.file);
|
|
2975
|
+
const commits = getFileHistory(root, frame.file, 5);
|
|
2976
|
+
for (const dep of importedBy) allImportedBy.add(dep);
|
|
2977
|
+
enrichedFrames.push({
|
|
2978
|
+
file: frame.file,
|
|
2979
|
+
line: frame.line,
|
|
2980
|
+
column: frame.column,
|
|
2981
|
+
function: frame.function,
|
|
2982
|
+
fileDescription: fileIndex?.description ?? "",
|
|
2983
|
+
fileSummary: fileIndex?.summary ?? "",
|
|
2984
|
+
symbols: fileIndex?.symbols ?? [],
|
|
2985
|
+
language: fileIndex?.language ?? "typescript",
|
|
2986
|
+
importedBy,
|
|
2987
|
+
importsFrom,
|
|
2988
|
+
lastModified: commits[0] ?? null,
|
|
2989
|
+
recentCommits: commits
|
|
2990
|
+
});
|
|
2991
|
+
}
|
|
2992
|
+
return {
|
|
2993
|
+
frames: enrichedFrames,
|
|
2994
|
+
affectedFiles: allImportedBy.size
|
|
2995
|
+
};
|
|
2996
|
+
}
|
|
2997
|
+
//#endregion
|
|
2998
|
+
export { getCurrentBranch as A, getConfigPath as B, openDatabase as C, setBranchState as D, saveState as E, isGitRepo as F, hashConfig as G, getDbPath as H, hasChanged as I, loadConfig as J, initKlyDir as K, hashFile as L, getFileHistory as M, getMergeBase as N, branchToDbName as O, isAncestor as P, scanFiles as R, loadState as S, resolveDbName as T, getKlyDir as U, getDbDir as V, getStatePath as W, copyDatabase as _, searchFilesWithRerank as a, getFileFromDb as b, buildDependencyGraph as c, renderGraphAscii as d, renderGraphSvg as f, filterGitDiff as g, ParserManager as h, searchFiles as i, getCurrentCommit as j, getChangedFiles as k, generateMermaid as l, LLMService as m, filterByLanguage as n, rerankResults as o, resolveImport as p, isInitialized as q, filterByPath as r, buildIndex as s, enrichErrorStack as t, isRelativeImport as u, getAllFilesFromDb as v, removeBranchDb as w, listBranchDbs as x, getBranchState as y, IndexDatabase as z };
|
|
2999
|
+
|
|
3000
|
+
//# sourceMappingURL=enrich-BHaZ2daX.mjs.map
|