indexer-cli 0.2.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 +156 -0
- package/bin/indexer-cli.js +97 -0
- package/dist/_temp_test.d.ts +1 -0
- package/dist/_temp_test.js +4 -0
- package/dist/_temp_test.js.map +1 -0
- package/dist/chunking/adaptive.d.ts +15 -0
- package/dist/chunking/adaptive.js +43 -0
- package/dist/chunking/adaptive.js.map +1 -0
- package/dist/chunking/function.d.ts +6 -0
- package/dist/chunking/function.js +96 -0
- package/dist/chunking/function.js.map +1 -0
- package/dist/chunking/index.d.ts +5 -0
- package/dist/chunking/index.js +22 -0
- package/dist/chunking/index.js.map +1 -0
- package/dist/chunking/module.d.ts +6 -0
- package/dist/chunking/module.js +33 -0
- package/dist/chunking/module.js.map +1 -0
- package/dist/chunking/single.d.ts +4 -0
- package/dist/chunking/single.js +19 -0
- package/dist/chunking/single.js.map +1 -0
- package/dist/chunking/types.d.ts +17 -0
- package/dist/chunking/types.js +3 -0
- package/dist/chunking/types.js.map +1 -0
- package/dist/cli/commands/architecture.d.ts +2 -0
- package/dist/cli/commands/architecture.js +162 -0
- package/dist/cli/commands/architecture.js.map +1 -0
- package/dist/cli/commands/context.d.ts +2 -0
- package/dist/cli/commands/context.js +241 -0
- package/dist/cli/commands/context.js.map +1 -0
- package/dist/cli/commands/deps.d.ts +2 -0
- package/dist/cli/commands/deps.js +129 -0
- package/dist/cli/commands/deps.js.map +1 -0
- package/dist/cli/commands/enrich.d.ts +2 -0
- package/dist/cli/commands/ensure-indexed.d.ts +4 -0
- package/dist/cli/commands/ensure-indexed.js +168 -0
- package/dist/cli/commands/ensure-indexed.js.map +1 -0
- package/dist/cli/commands/explain.d.ts +2 -0
- package/dist/cli/commands/explain.js +165 -0
- package/dist/cli/commands/explain.js.map +1 -0
- package/dist/cli/commands/index.d.ts +2 -0
- package/dist/cli/commands/index.js +271 -0
- package/dist/cli/commands/index.js.map +1 -0
- package/dist/cli/commands/init.d.ts +2 -0
- package/dist/cli/commands/init.js +132 -0
- package/dist/cli/commands/init.js.map +1 -0
- package/dist/cli/commands/search.d.ts +2 -0
- package/dist/cli/commands/search.js +206 -0
- package/dist/cli/commands/search.js.map +1 -0
- package/dist/cli/commands/setup.d.ts +2 -0
- package/dist/cli/commands/setup.js +425 -0
- package/dist/cli/commands/setup.js.map +1 -0
- package/dist/cli/commands/skill-template.d.ts +6 -0
- package/dist/cli/commands/skill-template.js +72 -0
- package/dist/cli/commands/skill-template.js.map +1 -0
- package/dist/cli/commands/structure.d.ts +2 -0
- package/dist/cli/commands/structure.js +243 -0
- package/dist/cli/commands/structure.js.map +1 -0
- package/dist/cli/commands/uninstall.d.ts +2 -0
- package/dist/cli/commands/uninstall.js +138 -0
- package/dist/cli/commands/uninstall.js.map +1 -0
- package/dist/cli/entry.d.ts +1 -0
- package/dist/cli/entry.js +55 -0
- package/dist/cli/entry.js.map +1 -0
- package/dist/cli/help-text.d.ts +2 -0
- package/dist/cli/help-text.js +9 -0
- package/dist/cli/help-text.js.map +1 -0
- package/dist/cli/version.d.ts +1 -0
- package/dist/cli/version.js +9 -0
- package/dist/cli/version.js.map +1 -0
- package/dist/core/config.d.ts +21 -0
- package/dist/core/config.js +77 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/logger.d.ts +19 -0
- package/dist/core/logger.js +116 -0
- package/dist/core/logger.js.map +1 -0
- package/dist/core/types.d.ts +194 -0
- package/dist/core/types.js +5 -0
- package/dist/core/types.js.map +1 -0
- package/dist/core/update-check.d.ts +1 -0
- package/dist/core/update-check.js +61 -0
- package/dist/core/update-check.js.map +1 -0
- package/dist/embedding/ollama.d.ts +29 -0
- package/dist/embedding/ollama.js +264 -0
- package/dist/embedding/ollama.js.map +1 -0
- package/dist/engine/architecture.d.ts +55 -0
- package/dist/engine/architecture.js +359 -0
- package/dist/engine/architecture.js.map +1 -0
- package/dist/engine/dependency-resolver.d.ts +4 -0
- package/dist/engine/dependency-resolver.js +69 -0
- package/dist/engine/dependency-resolver.js.map +1 -0
- package/dist/engine/git.d.ts +11 -0
- package/dist/engine/git.js +246 -0
- package/dist/engine/git.js.map +1 -0
- package/dist/engine/indexer.d.ts +86 -0
- package/dist/engine/indexer.js +933 -0
- package/dist/engine/indexer.js.map +1 -0
- package/dist/engine/scanner.d.ts +1 -0
- package/dist/engine/scanner.js +42 -0
- package/dist/engine/scanner.js.map +1 -0
- package/dist/engine/searcher.d.ts +26 -0
- package/dist/engine/searcher.js +70 -0
- package/dist/engine/searcher.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/languages/csharp.d.ts +25 -0
- package/dist/languages/csharp.js +311 -0
- package/dist/languages/csharp.js.map +1 -0
- package/dist/languages/gdscript.d.ts +25 -0
- package/dist/languages/gdscript.js +382 -0
- package/dist/languages/gdscript.js.map +1 -0
- package/dist/languages/plugin.d.ts +73 -0
- package/dist/languages/plugin.js +35 -0
- package/dist/languages/plugin.js.map +1 -0
- package/dist/languages/python.d.ts +24 -0
- package/dist/languages/python.js +292 -0
- package/dist/languages/python.js.map +1 -0
- package/dist/languages/ruby.d.ts +25 -0
- package/dist/languages/ruby.js +328 -0
- package/dist/languages/ruby.js.map +1 -0
- package/dist/languages/typescript.d.ts +21 -0
- package/dist/languages/typescript.js +439 -0
- package/dist/languages/typescript.js.map +1 -0
- package/dist/storage/sqlite.d.ts +51 -0
- package/dist/storage/sqlite.js +726 -0
- package/dist/storage/sqlite.js.map +1 -0
- package/dist/storage/vectors.d.ts +39 -0
- package/dist/storage/vectors.js +450 -0
- package/dist/storage/vectors.js.map +1 -0
- package/dist/utils/gitignore.d.ts +4 -0
- package/dist/utils/gitignore.js +85 -0
- package/dist/utils/gitignore.js.map +1 -0
- package/dist/utils/hash.d.ts +1 -0
- package/dist/utils/hash.js +12 -0
- package/dist/utils/hash.js.map +1 -0
- package/dist/utils/token-estimator.d.ts +3 -0
- package/dist/utils/token-estimator.js +13 -0
- package/dist/utils/token-estimator.js.map +1 -0
- package/package.json +54 -0
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.OllamaEmbeddingProvider = void 0;
|
|
4
|
+
const node_child_process_1 = require("node:child_process");
|
|
5
|
+
const logger_js_1 = require("../core/logger.js");
|
|
6
|
+
const logger = new logger_js_1.SystemLogger("embeddings-ollama");
|
|
7
|
+
class OllamaEmbeddingProvider {
|
|
8
|
+
id = "ollama";
|
|
9
|
+
dimension = 768;
|
|
10
|
+
baseUrl;
|
|
11
|
+
model;
|
|
12
|
+
batchSize;
|
|
13
|
+
concurrency;
|
|
14
|
+
numCtx;
|
|
15
|
+
reconnectInFlight = null;
|
|
16
|
+
requestTimeoutMs = 15_000;
|
|
17
|
+
constructor(baseUrl, model = "jina-8k", batchSize = 1, concurrency = 1, numCtx = 512) {
|
|
18
|
+
this.baseUrl = baseUrl.replace(/\/$/, "");
|
|
19
|
+
this.model = model;
|
|
20
|
+
this.batchSize = batchSize;
|
|
21
|
+
this.concurrency = concurrency;
|
|
22
|
+
this.numCtx = numCtx;
|
|
23
|
+
}
|
|
24
|
+
async initialize() {
|
|
25
|
+
await this.ensureOllamaAvailable("initialize");
|
|
26
|
+
}
|
|
27
|
+
async close() { }
|
|
28
|
+
getDimension() {
|
|
29
|
+
return this.dimension;
|
|
30
|
+
}
|
|
31
|
+
async embed(texts) {
|
|
32
|
+
const startTime = Date.now();
|
|
33
|
+
const batches = this.createBatches(texts, this.batchSize);
|
|
34
|
+
const results = new Array(texts.length);
|
|
35
|
+
const batchesWithIndices = batches.map((batch, index) => ({
|
|
36
|
+
batch,
|
|
37
|
+
startIdx: index * this.batchSize,
|
|
38
|
+
}));
|
|
39
|
+
const queue = [...batchesWithIndices];
|
|
40
|
+
const workers = Array(Math.min(this.concurrency, batches.length))
|
|
41
|
+
.fill(null)
|
|
42
|
+
.map(async () => {
|
|
43
|
+
while (queue.length > 0) {
|
|
44
|
+
const current = queue.shift();
|
|
45
|
+
if (!current)
|
|
46
|
+
break;
|
|
47
|
+
const { batch, startIdx } = current;
|
|
48
|
+
const embeddings = await this.embedBatch(batch);
|
|
49
|
+
embeddings.forEach((embedding, i) => {
|
|
50
|
+
results[startIdx + i] = embedding;
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
await Promise.all(workers);
|
|
55
|
+
void startTime;
|
|
56
|
+
return results;
|
|
57
|
+
}
|
|
58
|
+
createBatches(items, batchSize) {
|
|
59
|
+
const batches = [];
|
|
60
|
+
for (let i = 0; i < items.length; i += batchSize) {
|
|
61
|
+
batches.push(items.slice(i, i + batchSize));
|
|
62
|
+
}
|
|
63
|
+
return batches;
|
|
64
|
+
}
|
|
65
|
+
async embedBatch(texts) {
|
|
66
|
+
try {
|
|
67
|
+
return await this.performEmbedRequest(texts);
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
if (this.isConnectionError(error)) {
|
|
71
|
+
logger.warn("Ollama connection failed. Waiting for service readiness...");
|
|
72
|
+
await this.ensureOllamaAvailable("embedBatch");
|
|
73
|
+
return await this.performEmbedRequest(texts);
|
|
74
|
+
}
|
|
75
|
+
if (this.isNotFoundError(error)) {
|
|
76
|
+
logger.info(`Model ${this.model} not found. Pulling...`);
|
|
77
|
+
await this.pullModel();
|
|
78
|
+
return await this.performEmbedRequest(texts);
|
|
79
|
+
}
|
|
80
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
81
|
+
throw new Error(`Ollama embedding failed: ${message}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
isConnectionError(error) {
|
|
85
|
+
if (!error || typeof error !== "object") {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
// Native fetch wraps network errors in TypeError with a cause
|
|
89
|
+
const cause = error.cause;
|
|
90
|
+
const code = error.code ?? cause?.code;
|
|
91
|
+
const messageValue = error.message;
|
|
92
|
+
const message = typeof messageValue === "string" ? messageValue : "";
|
|
93
|
+
if (code &&
|
|
94
|
+
[
|
|
95
|
+
"ECONNREFUSED",
|
|
96
|
+
"ETIMEDOUT",
|
|
97
|
+
"ECONNRESET",
|
|
98
|
+
"EHOSTUNREACH",
|
|
99
|
+
"ECONNABORTED",
|
|
100
|
+
].includes(code)) {
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
return (message.includes("ECONNREFUSED") ||
|
|
104
|
+
message.includes("ETIMEDOUT") ||
|
|
105
|
+
message.includes("ECONNRESET") ||
|
|
106
|
+
message.includes("EHOSTUNREACH") ||
|
|
107
|
+
message.toLowerCase().includes("timeout"));
|
|
108
|
+
}
|
|
109
|
+
isNotFoundError(error) {
|
|
110
|
+
if (error instanceof Response) {
|
|
111
|
+
return error.status === 404;
|
|
112
|
+
}
|
|
113
|
+
if (!error || typeof error !== "object") {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
const status = error.status;
|
|
117
|
+
return status === 404;
|
|
118
|
+
}
|
|
119
|
+
async performEmbedRequest(texts) {
|
|
120
|
+
try {
|
|
121
|
+
const response = await this.fetchWithTimeout(`${this.baseUrl}/api/embed`, {
|
|
122
|
+
method: "POST",
|
|
123
|
+
headers: { "Content-Type": "application/json" },
|
|
124
|
+
body: JSON.stringify({
|
|
125
|
+
model: this.model,
|
|
126
|
+
input: texts,
|
|
127
|
+
options: {
|
|
128
|
+
num_ctx: this.numCtx,
|
|
129
|
+
},
|
|
130
|
+
}),
|
|
131
|
+
});
|
|
132
|
+
if (response.status === 404) {
|
|
133
|
+
return await this.fallbackSequentialEmbed(texts);
|
|
134
|
+
}
|
|
135
|
+
if (!response.ok) {
|
|
136
|
+
throw new Error(`Ollama responded with status ${response.status}`);
|
|
137
|
+
}
|
|
138
|
+
const data = await response.json();
|
|
139
|
+
if (data?.embeddings) {
|
|
140
|
+
return data.embeddings;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
if (this.isNotFoundError(error)) {
|
|
145
|
+
return await this.fallbackSequentialEmbed(texts);
|
|
146
|
+
}
|
|
147
|
+
throw error;
|
|
148
|
+
}
|
|
149
|
+
throw new Error("Invalid response from Ollama");
|
|
150
|
+
}
|
|
151
|
+
async fallbackSequentialEmbed(texts) {
|
|
152
|
+
const embeddings = [];
|
|
153
|
+
for (const text of texts) {
|
|
154
|
+
const response = await this.fetchWithTimeout(`${this.baseUrl}/api/embeddings`, {
|
|
155
|
+
method: "POST",
|
|
156
|
+
headers: { "Content-Type": "application/json" },
|
|
157
|
+
body: JSON.stringify({
|
|
158
|
+
model: this.model,
|
|
159
|
+
prompt: text,
|
|
160
|
+
options: {
|
|
161
|
+
num_ctx: this.numCtx,
|
|
162
|
+
},
|
|
163
|
+
}),
|
|
164
|
+
});
|
|
165
|
+
if (!response.ok) {
|
|
166
|
+
throw new Error(`Ollama responded with status ${response.status} (fallback)`);
|
|
167
|
+
}
|
|
168
|
+
const data = await response.json();
|
|
169
|
+
if (data?.embedding) {
|
|
170
|
+
embeddings.push(data.embedding);
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
throw new Error("Invalid response from Ollama (fallback)");
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return embeddings;
|
|
177
|
+
}
|
|
178
|
+
async pullModel() {
|
|
179
|
+
try {
|
|
180
|
+
await this.fetchWithTimeout(`${this.baseUrl}/api/pull`, {
|
|
181
|
+
method: "POST",
|
|
182
|
+
headers: { "Content-Type": "application/json" },
|
|
183
|
+
body: JSON.stringify({
|
|
184
|
+
name: this.model,
|
|
185
|
+
stream: false,
|
|
186
|
+
}),
|
|
187
|
+
});
|
|
188
|
+
logger.info(`Model ${this.model} pulled successfully.`);
|
|
189
|
+
}
|
|
190
|
+
catch (error) {
|
|
191
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
192
|
+
throw new Error(`Failed to pull model ${this.model}: ${message}`);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
async checkOllamaRunning() {
|
|
196
|
+
try {
|
|
197
|
+
const controller = new AbortController();
|
|
198
|
+
const timeoutId = setTimeout(() => controller.abort(), 2000);
|
|
199
|
+
try {
|
|
200
|
+
const response = await fetch(`${this.baseUrl}/api/version`, {
|
|
201
|
+
signal: controller.signal,
|
|
202
|
+
});
|
|
203
|
+
return response.status === 200;
|
|
204
|
+
}
|
|
205
|
+
finally {
|
|
206
|
+
clearTimeout(timeoutId);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
catch {
|
|
210
|
+
return false;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
async waitForOllama(maxAttempts = 60, delayMs = 500) {
|
|
214
|
+
for (let i = 0; i < maxAttempts; i++) {
|
|
215
|
+
if (await this.checkOllamaRunning()) {
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
219
|
+
}
|
|
220
|
+
throw new Error(`Ollama did not become ready at ${this.baseUrl} within ${maxAttempts * delayMs}ms`);
|
|
221
|
+
}
|
|
222
|
+
startOllama() {
|
|
223
|
+
try {
|
|
224
|
+
(0, node_child_process_1.execSync)("ollama serve > /dev/null 2>&1 &", {
|
|
225
|
+
stdio: "pipe",
|
|
226
|
+
timeout: 3000,
|
|
227
|
+
});
|
|
228
|
+
logger.info("Started Ollama daemon in background.");
|
|
229
|
+
}
|
|
230
|
+
catch {
|
|
231
|
+
// already running or not installed — waitForOllama handles both
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
async ensureOllamaAvailable(context) {
|
|
235
|
+
if (await this.checkOllamaRunning()) {
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
if (!this.reconnectInFlight) {
|
|
239
|
+
this.reconnectInFlight = (async () => {
|
|
240
|
+
logger.warn(`Ollama is unavailable during ${context}. Starting and waiting for readiness...`);
|
|
241
|
+
this.startOllama();
|
|
242
|
+
await this.waitForOllama();
|
|
243
|
+
})();
|
|
244
|
+
}
|
|
245
|
+
try {
|
|
246
|
+
await this.reconnectInFlight;
|
|
247
|
+
}
|
|
248
|
+
finally {
|
|
249
|
+
this.reconnectInFlight = null;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
async fetchWithTimeout(url, init, timeoutMs = this.requestTimeoutMs) {
|
|
253
|
+
const controller = new AbortController();
|
|
254
|
+
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
255
|
+
try {
|
|
256
|
+
return await fetch(url, { ...init, signal: controller.signal });
|
|
257
|
+
}
|
|
258
|
+
finally {
|
|
259
|
+
clearTimeout(timeoutId);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
exports.OllamaEmbeddingProvider = OllamaEmbeddingProvider;
|
|
264
|
+
//# sourceMappingURL=ollama.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ollama.js","sourceRoot":"","sources":["../../src/embedding/ollama.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { MetadataStore, ProjectId, SnapshotId } from "../core/types.js";
|
|
2
|
+
import type { LanguagePlugin } from "../languages/plugin.js";
|
|
3
|
+
export interface DirectoryNode {
|
|
4
|
+
name: string;
|
|
5
|
+
path: string;
|
|
6
|
+
type: "directory";
|
|
7
|
+
children: (DirectoryNode | FileNode)[];
|
|
8
|
+
}
|
|
9
|
+
export interface FileNode {
|
|
10
|
+
name: string;
|
|
11
|
+
path: string;
|
|
12
|
+
type: "file";
|
|
13
|
+
size: number;
|
|
14
|
+
language: string;
|
|
15
|
+
}
|
|
16
|
+
export interface ArchitectureDependencyMap {
|
|
17
|
+
internal: Record<string, string[]>;
|
|
18
|
+
external: Record<string, string[]>;
|
|
19
|
+
builtin: Record<string, string[]>;
|
|
20
|
+
unresolved: Record<string, string[]>;
|
|
21
|
+
}
|
|
22
|
+
export interface ArchitectureFileSummary {
|
|
23
|
+
path: string;
|
|
24
|
+
language: string;
|
|
25
|
+
}
|
|
26
|
+
export interface ArchitectureSnapshot {
|
|
27
|
+
structure: DirectoryNode;
|
|
28
|
+
entrypoints: string[];
|
|
29
|
+
dependencies: Record<string, number>;
|
|
30
|
+
dependency_map: ArchitectureDependencyMap;
|
|
31
|
+
file_stats: Record<string, number>;
|
|
32
|
+
files?: ArchitectureFileSummary[];
|
|
33
|
+
module_files?: Record<string, string[]>;
|
|
34
|
+
}
|
|
35
|
+
export declare function matchesPathPatterns(filePath: string, patterns: string[]): boolean;
|
|
36
|
+
export declare function filterArchitectureSnapshot(snapshot: ArchitectureSnapshot, excludePathPatterns: string[]): ArchitectureSnapshot;
|
|
37
|
+
export declare class ArchitectureGenerator {
|
|
38
|
+
private metadataStore;
|
|
39
|
+
private languagePlugins;
|
|
40
|
+
constructor(metadataStore: MetadataStore, languagePlugins?: LanguagePlugin[]);
|
|
41
|
+
generate(projectId: ProjectId, snapshotId: SnapshotId): Promise<void>;
|
|
42
|
+
private buildModuleFiles;
|
|
43
|
+
private buildDirectoryTree;
|
|
44
|
+
private calculateFileStats;
|
|
45
|
+
private findEntrypoints;
|
|
46
|
+
private dedupePreserveOrder;
|
|
47
|
+
private buildDependencies;
|
|
48
|
+
private addDependencyToBuckets;
|
|
49
|
+
private summarizeDependencies;
|
|
50
|
+
private mapToSortedRecord;
|
|
51
|
+
private normalizePath;
|
|
52
|
+
private canonicalizePath;
|
|
53
|
+
private getModuleKey;
|
|
54
|
+
private isTestPath;
|
|
55
|
+
}
|
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.ArchitectureGenerator = void 0;
|
|
7
|
+
exports.matchesPathPatterns = matchesPathPatterns;
|
|
8
|
+
exports.filterArchitectureSnapshot = filterArchitectureSnapshot;
|
|
9
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
10
|
+
function normalizePath(value) {
|
|
11
|
+
return value.replace(/\\/g, "/");
|
|
12
|
+
}
|
|
13
|
+
function escapeRegExp(value) {
|
|
14
|
+
return value.replace(/[|\\{}()[\]^$+?.]/g, "\\$&");
|
|
15
|
+
}
|
|
16
|
+
function pathPatternToRegExp(pattern) {
|
|
17
|
+
const normalized = normalizePath(pattern.trim()).replace(/^\.\//, "");
|
|
18
|
+
const doubleWildcardToken = "__INDEXER_DOUBLE_WILDCARD__";
|
|
19
|
+
const regexSource = escapeRegExp(normalized)
|
|
20
|
+
.replace(/\*\*/g, doubleWildcardToken)
|
|
21
|
+
.replace(/\*/g, "[^/]*")
|
|
22
|
+
.replace(/\?/g, "[^/]")
|
|
23
|
+
.replace(new RegExp(doubleWildcardToken, "g"), ".*");
|
|
24
|
+
return new RegExp(`^${regexSource}$`);
|
|
25
|
+
}
|
|
26
|
+
function matchesPathPatterns(filePath, patterns) {
|
|
27
|
+
const normalizedPath = normalizePath(filePath);
|
|
28
|
+
for (const pattern of patterns) {
|
|
29
|
+
const trimmedPattern = pattern.trim();
|
|
30
|
+
if (!trimmedPattern) {
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
const regex = pathPatternToRegExp(trimmedPattern);
|
|
34
|
+
if (regex.test(normalizedPath)) {
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
function calculateFileStatsFromSummary(files) {
|
|
41
|
+
const stats = {};
|
|
42
|
+
for (const file of files) {
|
|
43
|
+
const language = file.language || "unknown";
|
|
44
|
+
stats[language] = (stats[language] || 0) + 1;
|
|
45
|
+
}
|
|
46
|
+
return stats;
|
|
47
|
+
}
|
|
48
|
+
function summarizeDependencies(detail) {
|
|
49
|
+
const summary = new Map();
|
|
50
|
+
const bump = (name) => summary.set(name, (summary.get(name) || 0) + 1);
|
|
51
|
+
for (const bucket of Object.values(detail)) {
|
|
52
|
+
for (const deps of Object.values(bucket)) {
|
|
53
|
+
for (const dep of new Set(deps)) {
|
|
54
|
+
bump(dep);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return Object.fromEntries(Array.from(summary.entries()).sort((a, b) => a[0].localeCompare(b[0])));
|
|
59
|
+
}
|
|
60
|
+
function filterStructure(node, includedFilePaths) {
|
|
61
|
+
const children = [];
|
|
62
|
+
for (const child of node.children) {
|
|
63
|
+
if (child.type === "file") {
|
|
64
|
+
if (includedFilePaths.has(normalizePath(child.path))) {
|
|
65
|
+
children.push(child);
|
|
66
|
+
}
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
const filteredChild = filterStructure(child, includedFilePaths);
|
|
70
|
+
if (filteredChild) {
|
|
71
|
+
children.push(filteredChild);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (node.path !== "." && children.length === 0) {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
...node,
|
|
79
|
+
children,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
function filterArchitectureSnapshot(snapshot, excludePathPatterns) {
|
|
83
|
+
if (excludePathPatterns.length === 0) {
|
|
84
|
+
return snapshot;
|
|
85
|
+
}
|
|
86
|
+
const files = snapshot.files ?? [];
|
|
87
|
+
const includedFiles = files.filter((file) => !matchesPathPatterns(file.path, excludePathPatterns));
|
|
88
|
+
const includedFilePaths = new Set(includedFiles.map((file) => normalizePath(file.path)));
|
|
89
|
+
const filteredModuleFiles = Object.fromEntries(Object.entries(snapshot.module_files ?? {})
|
|
90
|
+
.map(([moduleKey, filePaths]) => [
|
|
91
|
+
moduleKey,
|
|
92
|
+
filePaths.filter((filePath) => includedFilePaths.has(normalizePath(filePath))),
|
|
93
|
+
])
|
|
94
|
+
.filter(([, filePaths]) => filePaths.length > 0));
|
|
95
|
+
const includedModules = new Set(Object.keys(filteredModuleFiles));
|
|
96
|
+
const dependencyMap = {
|
|
97
|
+
internal: Object.fromEntries(Object.entries(snapshot.dependency_map.internal ?? {})
|
|
98
|
+
.filter(([fromModule, toModules]) => includedModules.has(fromModule) &&
|
|
99
|
+
toModules.some((toModule) => includedModules.has(toModule)))
|
|
100
|
+
.map(([fromModule, toModules]) => [
|
|
101
|
+
fromModule,
|
|
102
|
+
toModules.filter((toModule) => includedModules.has(toModule)),
|
|
103
|
+
])),
|
|
104
|
+
external: Object.fromEntries(Object.entries(snapshot.dependency_map.external ?? {}).filter(([fromModule]) => includedModules.has(fromModule))),
|
|
105
|
+
builtin: Object.fromEntries(Object.entries(snapshot.dependency_map.builtin ?? {}).filter(([fromModule]) => includedModules.has(fromModule))),
|
|
106
|
+
unresolved: Object.fromEntries(Object.entries(snapshot.dependency_map.unresolved ?? {}).filter(([fromModule]) => includedModules.has(fromModule))),
|
|
107
|
+
};
|
|
108
|
+
return {
|
|
109
|
+
...snapshot,
|
|
110
|
+
structure: filterStructure(snapshot.structure, includedFilePaths) ??
|
|
111
|
+
snapshot.structure,
|
|
112
|
+
entrypoints: (snapshot.entrypoints ?? []).filter((entrypoint) => includedFilePaths.has(normalizePath(entrypoint))),
|
|
113
|
+
dependencies: summarizeDependencies(dependencyMap),
|
|
114
|
+
dependency_map: dependencyMap,
|
|
115
|
+
file_stats: calculateFileStatsFromSummary(includedFiles),
|
|
116
|
+
files: includedFiles,
|
|
117
|
+
module_files: filteredModuleFiles,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
class ArchitectureGenerator {
|
|
121
|
+
metadataStore;
|
|
122
|
+
languagePlugins;
|
|
123
|
+
constructor(metadataStore, languagePlugins = []) {
|
|
124
|
+
this.metadataStore = metadataStore;
|
|
125
|
+
this.languagePlugins = languagePlugins;
|
|
126
|
+
}
|
|
127
|
+
async generate(projectId, snapshotId) {
|
|
128
|
+
const files = await this.metadataStore.listFiles(projectId, snapshotId);
|
|
129
|
+
const dependencies = await this.buildDependencies(projectId, snapshotId, files);
|
|
130
|
+
const structure = this.buildDirectoryTree(files);
|
|
131
|
+
const file_stats = this.calculateFileStats(files);
|
|
132
|
+
const entrypoints = this.findEntrypoints(files);
|
|
133
|
+
const filesSummary = files.map((file) => ({
|
|
134
|
+
path: file.path,
|
|
135
|
+
language: file.languageId || "unknown",
|
|
136
|
+
}));
|
|
137
|
+
const module_files = this.buildModuleFiles(files);
|
|
138
|
+
await this.metadataStore.upsertArtifact(projectId, {
|
|
139
|
+
snapshotId,
|
|
140
|
+
projectId,
|
|
141
|
+
artifactType: "architecture_snapshot",
|
|
142
|
+
scope: "project",
|
|
143
|
+
dataJson: JSON.stringify({
|
|
144
|
+
structure,
|
|
145
|
+
entrypoints,
|
|
146
|
+
dependencies: dependencies.summary,
|
|
147
|
+
dependency_map: dependencies.detail,
|
|
148
|
+
file_stats,
|
|
149
|
+
files: filesSummary,
|
|
150
|
+
module_files,
|
|
151
|
+
}),
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
buildModuleFiles(files) {
|
|
155
|
+
const moduleFiles = new Map();
|
|
156
|
+
for (const file of files) {
|
|
157
|
+
const moduleKey = this.getModuleKey(file.path);
|
|
158
|
+
if (!moduleFiles.has(moduleKey)) {
|
|
159
|
+
moduleFiles.set(moduleKey, new Set());
|
|
160
|
+
}
|
|
161
|
+
moduleFiles.get(moduleKey)?.add(file.path);
|
|
162
|
+
}
|
|
163
|
+
return Object.fromEntries(Array.from(moduleFiles.entries())
|
|
164
|
+
.sort((a, b) => a[0].localeCompare(b[0]))
|
|
165
|
+
.map(([moduleKey, filePaths]) => [
|
|
166
|
+
moduleKey,
|
|
167
|
+
Array.from(filePaths).sort(),
|
|
168
|
+
]));
|
|
169
|
+
}
|
|
170
|
+
buildDirectoryTree(files) {
|
|
171
|
+
const root = {
|
|
172
|
+
name: "root",
|
|
173
|
+
path: ".",
|
|
174
|
+
type: "directory",
|
|
175
|
+
children: [],
|
|
176
|
+
};
|
|
177
|
+
const map = new Map([[".", root]]);
|
|
178
|
+
for (const file of files) {
|
|
179
|
+
const parts = file.path.split("/");
|
|
180
|
+
let currentPath = ".";
|
|
181
|
+
for (let index = 0; index < parts.length - 1; index += 1) {
|
|
182
|
+
const part = parts[index];
|
|
183
|
+
const parentPath = currentPath;
|
|
184
|
+
currentPath = currentPath === "." ? part : `${currentPath}/${part}`;
|
|
185
|
+
if (!map.has(currentPath)) {
|
|
186
|
+
const node = {
|
|
187
|
+
name: part,
|
|
188
|
+
path: currentPath,
|
|
189
|
+
type: "directory",
|
|
190
|
+
children: [],
|
|
191
|
+
};
|
|
192
|
+
map.set(currentPath, node);
|
|
193
|
+
map.get(parentPath)?.children.push(node);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
const fileName = parts[parts.length - 1];
|
|
197
|
+
const parentPath = parts.length > 1 ? parts.slice(0, -1).join("/") : ".";
|
|
198
|
+
map.get(parentPath)?.children.push({
|
|
199
|
+
name: fileName,
|
|
200
|
+
path: file.path,
|
|
201
|
+
type: "file",
|
|
202
|
+
size: file.size,
|
|
203
|
+
language: file.languageId,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
return root;
|
|
207
|
+
}
|
|
208
|
+
calculateFileStats(files) {
|
|
209
|
+
const stats = {};
|
|
210
|
+
for (const file of files) {
|
|
211
|
+
const language = file.languageId || "unknown";
|
|
212
|
+
stats[language] = (stats[language] || 0) + 1;
|
|
213
|
+
}
|
|
214
|
+
return stats;
|
|
215
|
+
}
|
|
216
|
+
findEntrypoints(files) {
|
|
217
|
+
const entrypoints = [];
|
|
218
|
+
for (const plugin of this.languagePlugins) {
|
|
219
|
+
if (!plugin.getEntrypoints) {
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
const extensions = new Set(plugin.fileExtensions.map((ext) => ext.toLowerCase()));
|
|
223
|
+
const pluginFiles = files
|
|
224
|
+
.filter((file) => !this.isTestPath(file.path))
|
|
225
|
+
.filter((file) => extensions.has(node_path_1.default.extname(file.path).toLowerCase()))
|
|
226
|
+
.map((file) => file.path);
|
|
227
|
+
if (pluginFiles.length === 0) {
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
entrypoints.push(...plugin.getEntrypoints(pluginFiles));
|
|
231
|
+
}
|
|
232
|
+
return this.dedupePreserveOrder(entrypoints);
|
|
233
|
+
}
|
|
234
|
+
dedupePreserveOrder(paths) {
|
|
235
|
+
const seen = new Set();
|
|
236
|
+
const result = [];
|
|
237
|
+
for (const value of paths) {
|
|
238
|
+
if (seen.has(value)) {
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
241
|
+
seen.add(value);
|
|
242
|
+
result.push(value);
|
|
243
|
+
}
|
|
244
|
+
return result;
|
|
245
|
+
}
|
|
246
|
+
async buildDependencies(projectId, snapshotId, files) {
|
|
247
|
+
const dependencies = await this.metadataStore.listDependencies(projectId, snapshotId);
|
|
248
|
+
const fileSet = new Set(files.map((file) => this.normalizePath(file.path)));
|
|
249
|
+
const buckets = {
|
|
250
|
+
internal: new Map(),
|
|
251
|
+
external: new Map(),
|
|
252
|
+
builtin: new Map(),
|
|
253
|
+
unresolved: new Map(),
|
|
254
|
+
};
|
|
255
|
+
const add = (bucket, from, to) => {
|
|
256
|
+
if (!to)
|
|
257
|
+
return;
|
|
258
|
+
if (!buckets[bucket].has(from)) {
|
|
259
|
+
buckets[bucket].set(from, new Set());
|
|
260
|
+
}
|
|
261
|
+
buckets[bucket].get(from)?.add(to);
|
|
262
|
+
};
|
|
263
|
+
for (const dependency of dependencies) {
|
|
264
|
+
this.addDependencyToBuckets(dependency, fileSet, add);
|
|
265
|
+
}
|
|
266
|
+
const detail = {
|
|
267
|
+
internal: this.mapToSortedRecord(buckets.internal),
|
|
268
|
+
external: this.mapToSortedRecord(buckets.external),
|
|
269
|
+
builtin: this.mapToSortedRecord(buckets.builtin),
|
|
270
|
+
unresolved: this.mapToSortedRecord(buckets.unresolved),
|
|
271
|
+
};
|
|
272
|
+
return {
|
|
273
|
+
summary: this.summarizeDependencies(detail),
|
|
274
|
+
detail,
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
addDependencyToBuckets(dependency, fileSet, add) {
|
|
278
|
+
const fromPath = this.normalizePath(dependency.fromPath);
|
|
279
|
+
const fromModule = this.getModuleKey(fromPath);
|
|
280
|
+
const dependencyType = dependency.dependencyType ?? "unresolved";
|
|
281
|
+
if (dependencyType === "internal" && dependency.toPath) {
|
|
282
|
+
const toPath = this.normalizePath(dependency.toPath);
|
|
283
|
+
const toModule = this.getModuleKey(this.canonicalizePath(toPath, fileSet));
|
|
284
|
+
if (toModule && toModule !== fromModule) {
|
|
285
|
+
add("internal", fromModule, toModule);
|
|
286
|
+
}
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
const target = dependency.toSpecifier || dependency.toPath;
|
|
290
|
+
if (!target) {
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
if (dependencyType === "external") {
|
|
294
|
+
add("external", fromModule, target);
|
|
295
|
+
}
|
|
296
|
+
else if (dependencyType === "builtin") {
|
|
297
|
+
add("builtin", fromModule, target);
|
|
298
|
+
}
|
|
299
|
+
else {
|
|
300
|
+
add("unresolved", fromModule, target);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
summarizeDependencies(detail) {
|
|
304
|
+
return summarizeDependencies(detail);
|
|
305
|
+
}
|
|
306
|
+
mapToSortedRecord(map) {
|
|
307
|
+
const entries = Array.from(map.entries()).sort((a, b) => a[0].localeCompare(b[0]));
|
|
308
|
+
return Object.fromEntries(entries.map(([key, values]) => [key, Array.from(values).sort()]));
|
|
309
|
+
}
|
|
310
|
+
normalizePath(value) {
|
|
311
|
+
return normalizePath(value);
|
|
312
|
+
}
|
|
313
|
+
canonicalizePath(value, fileSet) {
|
|
314
|
+
const normalized = this.normalizePath(value);
|
|
315
|
+
if (!normalized.includes("/dist/"))
|
|
316
|
+
return normalized;
|
|
317
|
+
if (!normalized.endsWith(".js") && !normalized.endsWith(".jsx"))
|
|
318
|
+
return normalized;
|
|
319
|
+
const tsCandidate = normalized
|
|
320
|
+
.replace("/dist/", "/src/")
|
|
321
|
+
.replace(/\.jsx?$/, ".ts");
|
|
322
|
+
if (fileSet.has(tsCandidate))
|
|
323
|
+
return tsCandidate;
|
|
324
|
+
const tsxCandidate = tsCandidate.replace(/\.ts$/, ".tsx");
|
|
325
|
+
if (fileSet.has(tsxCandidate))
|
|
326
|
+
return tsxCandidate;
|
|
327
|
+
return normalized;
|
|
328
|
+
}
|
|
329
|
+
getModuleKey(filePath) {
|
|
330
|
+
const parts = this.normalizePath(filePath).split("/");
|
|
331
|
+
if (parts.length === 1)
|
|
332
|
+
return "root";
|
|
333
|
+
const rootGroup = parts[0];
|
|
334
|
+
if (["packages", "services", "apps", "libs", "modules"].includes(rootGroup) &&
|
|
335
|
+
parts[1]) {
|
|
336
|
+
return `${rootGroup}/${parts[1]}`;
|
|
337
|
+
}
|
|
338
|
+
if (parts.length >= 2) {
|
|
339
|
+
return `${parts[0]}/${parts[1]}`;
|
|
340
|
+
}
|
|
341
|
+
return rootGroup;
|
|
342
|
+
}
|
|
343
|
+
isTestPath(filePath) {
|
|
344
|
+
const normalized = this.normalizePath(filePath).toLowerCase();
|
|
345
|
+
if (normalized.includes("/__tests__/"))
|
|
346
|
+
return true;
|
|
347
|
+
if (normalized.includes("/__test__/"))
|
|
348
|
+
return true;
|
|
349
|
+
if (normalized.includes("/tests/"))
|
|
350
|
+
return true;
|
|
351
|
+
if (normalized.includes("/test/"))
|
|
352
|
+
return true;
|
|
353
|
+
if (normalized.includes("/fixtures/"))
|
|
354
|
+
return true;
|
|
355
|
+
return /(\.|\/)(spec|test)\.[^/]+$/.test(normalized);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
exports.ArchitectureGenerator = ArchitectureGenerator;
|
|
359
|
+
//# sourceMappingURL=architecture.js.map
|