archicore 0.2.7 → 0.2.9

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.
@@ -13,7 +13,11 @@ async function getProjectId() {
13
13
  async function fetchAnalysis(endpoint, projectId) {
14
14
  const config = await loadConfig();
15
15
  const id = projectId || await getProjectId();
16
- const response = await fetch(`${config.serverUrl}/api/projects/${id}/${endpoint}`);
16
+ const response = await fetch(`${config.serverUrl}/api/projects/${id}/${endpoint}`, {
17
+ headers: {
18
+ 'Authorization': `Bearer ${config.accessToken}`
19
+ }
20
+ });
17
21
  if (!response.ok) {
18
22
  const error = await response.json();
19
23
  throw new Error(error.error || `Failed to run ${endpoint}`);
@@ -376,6 +380,9 @@ export function registerAnalyzerCommands(program) {
376
380
  }
377
381
  const response = await fetch(`${config.serverUrl}/api/projects/${projectId}/full-analysis`, {
378
382
  method: 'POST',
383
+ headers: {
384
+ 'Authorization': `Bearer ${config.accessToken}`
385
+ }
379
386
  });
380
387
  if (!response.ok) {
381
388
  const error = await response.json();
@@ -59,9 +59,17 @@ export function registerProjectsCommand(program) {
59
59
  const spinner = createSpinner('Creating project...').start();
60
60
  try {
61
61
  const config = await loadConfig();
62
+ if (!config.accessToken) {
63
+ spinner.fail('Not logged in');
64
+ console.log(' Please run: archicore login');
65
+ process.exit(1);
66
+ }
62
67
  const response = await fetch(`${config.serverUrl}/api/projects`, {
63
68
  method: 'POST',
64
- headers: { 'Content-Type': 'application/json' },
69
+ headers: {
70
+ 'Content-Type': 'application/json',
71
+ 'Authorization': `Bearer ${config.accessToken}`
72
+ },
65
73
  body: JSON.stringify({
66
74
  name: options.name || path.split('/').pop() || path.split('\\').pop(),
67
75
  path: path,
@@ -112,8 +120,16 @@ export function registerProjectsCommand(program) {
112
120
  const spinner = createSpinner('Deleting project...').start();
113
121
  try {
114
122
  const config = await loadConfig();
123
+ if (!config.accessToken) {
124
+ spinner.fail('Not logged in');
125
+ console.log(' Please run: archicore login');
126
+ process.exit(1);
127
+ }
115
128
  const response = await fetch(`${config.serverUrl}/api/projects/${id}`, {
116
129
  method: 'DELETE',
130
+ headers: {
131
+ 'Authorization': `Bearer ${config.accessToken}`
132
+ }
117
133
  });
118
134
  if (!response.ok) {
119
135
  const error = await response.json();
@@ -225,8 +241,16 @@ export function registerProjectsCommand(program) {
225
241
  printError('Use "archicore projects select <id>" or specify project ID');
226
242
  process.exit(1);
227
243
  }
244
+ if (!config.accessToken) {
245
+ spinner.fail('Not logged in');
246
+ console.log(' Please run: archicore login');
247
+ process.exit(1);
248
+ }
228
249
  const response = await fetch(`${config.serverUrl}/api/projects/${projectId}/index`, {
229
250
  method: 'POST',
251
+ headers: {
252
+ 'Authorization': `Bearer ${config.accessToken}`
253
+ }
230
254
  });
231
255
  if (!response.ok) {
232
256
  const error = await response.json();
@@ -30,9 +30,13 @@ export function getSession() {
30
30
  }
31
31
  export async function selectProject(projectId) {
32
32
  const project = await fetchProject(projectId);
33
- if (project && sessionState) {
34
- sessionState.project = project;
33
+ if (project) {
34
+ // Always save to config, regardless of session state
35
35
  await setActiveProject(project.id, project.path);
36
+ // Update session state if initialized
37
+ if (sessionState) {
38
+ sessionState.project = project;
39
+ }
36
40
  }
37
41
  return project;
38
42
  }
@@ -43,20 +47,27 @@ export async function clearProject() {
43
47
  }
44
48
  async function fetchProject(projectId) {
45
49
  const config = await loadConfig();
50
+ if (!config.accessToken)
51
+ return null;
46
52
  try {
47
- const response = await fetch(`${config.serverUrl}/api/projects/${projectId}`);
53
+ const response = await fetch(`${config.serverUrl}/api/projects/${projectId}`, {
54
+ headers: {
55
+ 'Authorization': `Bearer ${config.accessToken}`
56
+ }
57
+ });
48
58
  if (!response.ok)
49
59
  return null;
50
60
  const data = await response.json();
61
+ const p = data.project || data; // Handle both { project: {...} } and direct object
51
62
  return {
52
- id: data.id,
53
- name: data.name,
54
- path: data.path,
55
- status: data.status || 'unknown',
56
- files: data.statistics?.codeIndex?.totalFiles,
57
- symbols: data.statistics?.codeIndex?.totalSymbols,
58
- indexed: data.status === 'indexed',
59
- lastIndexed: data.lastIndexed ? new Date(data.lastIndexed) : undefined,
63
+ id: p.id,
64
+ name: p.name,
65
+ path: p.path,
66
+ status: p.status || 'unknown',
67
+ files: p.statistics?.codeIndex?.totalFiles || p.stats?.filesCount,
68
+ symbols: p.statistics?.codeIndex?.totalSymbols || p.stats?.symbolsCount,
69
+ indexed: p.status === 'indexed' || p.status === 'ready',
70
+ lastIndexed: p.lastIndexed ? new Date(p.lastIndexed) : undefined,
60
71
  };
61
72
  }
62
73
  catch {
@@ -65,8 +76,14 @@ async function fetchProject(projectId) {
65
76
  }
66
77
  export async function fetchProjects() {
67
78
  const config = await loadConfig();
79
+ if (!config.accessToken)
80
+ return [];
68
81
  try {
69
- const response = await fetch(`${config.serverUrl}/api/projects`);
82
+ const response = await fetch(`${config.serverUrl}/api/projects`, {
83
+ headers: {
84
+ 'Authorization': `Bearer ${config.accessToken}`
85
+ }
86
+ });
70
87
  if (!response.ok)
71
88
  return [];
72
89
  const data = await response.json();
@@ -7,15 +7,17 @@
7
7
  * - Детальная обработка ошибок
8
8
  */
9
9
  import { loadConfig } from './config.js';
10
- // Лимиты для chunked upload
11
- const MAX_PAYLOAD_SIZE = 10 * 1024 * 1024; // 10MB per chunk
12
- const MAX_SYMBOLS_PER_CHUNK = 5000;
13
- const MAX_FILES_PER_CHUNK = 100;
14
- const UPLOAD_TIMEOUT = 120000; // 120 секунд на chunk (увеличено для больших проектов)
15
- const MAX_RETRIES = 3;
10
+ // Лимиты для chunked upload (оптимизировано для очень больших проектов)
11
+ const MAX_PAYLOAD_SIZE = 5 * 1024 * 1024; // 5MB per chunk (уменьшено для надёжности)
12
+ const MAX_SYMBOLS_PER_CHUNK = 2000; // Меньше символов на chunk
13
+ const MAX_FILES_PER_CHUNK = 50; // Меньше файлов на chunk
14
+ const UPLOAD_TIMEOUT = 180000; // 3 минуты на chunk
15
+ const MAX_RETRIES = 5; // Больше попыток
16
16
  // Лимиты для минимальной загрузки
17
17
  const MINIMAL_MAX_SYMBOLS = 10000;
18
18
  const MINIMAL_MAX_FILES = 500;
19
+ // Порог для "очень большого" проекта (пропускаем fileContents)
20
+ const VERY_LARGE_PROJECT_SYMBOLS = 30000;
19
21
  /**
20
22
  * Определение размера JSON в байтах
21
23
  */
@@ -178,10 +180,9 @@ async function fetchWithRetry(url, options, timeout = UPLOAD_TIMEOUT, maxRetries
178
180
  }
179
181
  catch (error) {
180
182
  lastError = error instanceof Error ? error : new Error(String(error));
181
- // Не повторяем для определённых ошибок
183
+ // Не повторяем для определённых ошибок (кроме AbortError - таймаут можно повторить)
182
184
  const errorStr = String(error);
183
- if (errorStr.includes('AbortError') ||
184
- errorStr.includes('ENOTFOUND') ||
185
+ if (errorStr.includes('ENOTFOUND') ||
185
186
  errorStr.includes('CERT')) {
186
187
  throw error;
187
188
  }
@@ -287,12 +288,18 @@ async function uploadSingleRequest(url, projectId, data, accessToken, onProgress
287
288
  */
288
289
  async function uploadChunked(baseUrl, projectId, data, accessToken, onProgress) {
289
290
  const config = await loadConfig();
291
+ // Для очень больших проектов пропускаем fileContents (экономим трафик и время)
292
+ const isVeryLargeProject = data.symbols.length > VERY_LARGE_PROJECT_SYMBOLS;
293
+ if (isVeryLargeProject) {
294
+ console.log(`[DEBUG] Very large project (${data.symbols.length} symbols), skipping fileContents upload`);
295
+ }
290
296
  // Разбиваем данные на chunks
291
297
  const symbolChunks = chunkArray(data.symbols, MAX_SYMBOLS_PER_CHUNK);
292
298
  const astChunks = chunkArray(data.asts, MAX_FILES_PER_CHUNK);
293
- const fileChunks = chunkArray(data.fileContents, MAX_FILES_PER_CHUNK);
299
+ const fileChunks = isVeryLargeProject ? [] : chunkArray(data.fileContents, MAX_FILES_PER_CHUNK);
294
300
  const totalChunks = symbolChunks.length + astChunks.length + fileChunks.length + 1; // +1 for graph
295
301
  let completedChunks = 0;
302
+ console.log(`[DEBUG] Chunked upload: ${symbolChunks.length} symbol chunks, ${astChunks.length} AST chunks, ${fileChunks.length} file chunks`);
296
303
  onProgress?.({
297
304
  phase: 'uploading',
298
305
  current: 0,
@@ -330,28 +337,32 @@ async function uploadChunked(baseUrl, projectId, data, accessToken, onProgress)
330
337
  }
331
338
  const initResult = await initResponse.json();
332
339
  const uploadId = initResult.uploadId;
333
- // 2. Загружаем ASTs по частям
334
- for (let i = 0; i < astChunks.length; i++) {
335
- await uploadChunk(config.serverUrl, projectId, uploadId, 'asts', i, astChunks[i], accessToken);
336
- completedChunks++;
337
- onProgress?.({
338
- phase: 'uploading',
339
- current: completedChunks,
340
- total: totalChunks,
341
- message: `Uploading ASTs (${i + 1}/${astChunks.length})...`,
342
- });
343
- }
344
- // 3. Загружаем символы по частям
345
- for (let i = 0; i < symbolChunks.length; i++) {
346
- await uploadChunk(config.serverUrl, projectId, uploadId, 'symbols', i, symbolChunks[i], accessToken);
347
- completedChunks++;
348
- onProgress?.({
349
- phase: 'uploading',
350
- current: completedChunks,
351
- total: totalChunks,
352
- message: `Uploading symbols (${i + 1}/${symbolChunks.length})...`,
353
- });
340
+ // Параллельная загрузка chunks (по 3 одновременно)
341
+ const PARALLEL_UPLOADS = 3;
342
+ // Helper для параллельной загрузки
343
+ async function uploadChunksParallel(chunks, chunkType, label) {
344
+ console.log(`[DEBUG] Starting parallel upload of ${chunks.length} ${chunkType} chunks`);
345
+ for (let batch = 0; batch < chunks.length; batch += PARALLEL_UPLOADS) {
346
+ const batchChunks = chunks.slice(batch, batch + PARALLEL_UPLOADS);
347
+ console.log(`[DEBUG] Uploading batch ${batch / PARALLEL_UPLOADS + 1} (${batchChunks.length} chunks)`);
348
+ const promises = batchChunks.map((chunk, idx) => uploadChunk(config.serverUrl, projectId, uploadId, chunkType, batch + idx, chunk, accessToken).then(() => {
349
+ console.log(`[DEBUG] Chunk ${chunkType}[${batch + idx}] uploaded`);
350
+ }));
351
+ await Promise.all(promises);
352
+ completedChunks += batchChunks.length;
353
+ onProgress?.({
354
+ phase: 'uploading',
355
+ current: completedChunks,
356
+ total: totalChunks,
357
+ message: `${label} (${Math.min(batch + PARALLEL_UPLOADS, chunks.length)}/${chunks.length})...`,
358
+ });
359
+ }
360
+ console.log(`[DEBUG] Finished uploading ${chunks.length} ${chunkType} chunks`);
354
361
  }
362
+ // 2. Загружаем ASTs параллельно
363
+ await uploadChunksParallel(astChunks, 'asts', 'Uploading ASTs');
364
+ // 3. Загружаем символы параллельно
365
+ await uploadChunksParallel(symbolChunks, 'symbols', 'Uploading symbols');
355
366
  // 4. Загружаем граф
356
367
  await uploadChunk(config.serverUrl, projectId, uploadId, 'graph', 0, data.graph, accessToken);
357
368
  completedChunks++;
@@ -361,16 +372,9 @@ async function uploadChunked(baseUrl, projectId, data, accessToken, onProgress)
361
372
  total: totalChunks,
362
373
  message: 'Uploading dependency graph...',
363
374
  });
364
- // 5. Загружаем содержимое файлов (опционально)
365
- for (let i = 0; i < fileChunks.length; i++) {
366
- await uploadChunk(config.serverUrl, projectId, uploadId, 'fileContents', i, fileChunks[i], accessToken);
367
- completedChunks++;
368
- onProgress?.({
369
- phase: 'uploading',
370
- current: completedChunks,
371
- total: totalChunks,
372
- message: `Uploading file contents (${i + 1}/${fileChunks.length})...`,
373
- });
375
+ // 5. Загружаем содержимое файлов параллельно (опционально)
376
+ if (fileChunks.length > 0) {
377
+ await uploadChunksParallel(fileChunks, 'fileContents', 'Uploading files');
374
378
  }
375
379
  // 6. Финализируем upload
376
380
  onProgress?.({
@@ -176,7 +176,175 @@ export class ASTParser {
176
176
  /^(\w+)\s*\(\)\s*\{/, // Function definitions
177
177
  /^function\s+(\w+)/ // Function keyword
178
178
  ],
179
- markdown: [] // Markdown doesn't have functions/classes
179
+ markdown: [], // Markdown doesn't have functions/classes
180
+ // Additional languages
181
+ kotlin: [
182
+ /^(?:public|private|internal|protected)?\s*(?:suspend\s+)?fun\s+(\w+)/,
183
+ /^(?:public|private|internal|protected)?\s*(?:data\s+|sealed\s+|open\s+)?class\s+(\w+)/,
184
+ /^(?:public|private|internal|protected)?\s*interface\s+(\w+)/,
185
+ /^(?:public|private|internal|protected)?\s*object\s+(\w+)/,
186
+ /^(?:public|private|internal|protected)?\s*enum\s+class\s+(\w+)/
187
+ ],
188
+ swift: [
189
+ /^(?:public|private|internal|fileprivate|open)?\s*func\s+(\w+)/,
190
+ /^(?:public|private|internal|fileprivate|open)?\s*class\s+(\w+)/,
191
+ /^(?:public|private|internal|fileprivate|open)?\s*struct\s+(\w+)/,
192
+ /^(?:public|private|internal|fileprivate|open)?\s*enum\s+(\w+)/,
193
+ /^(?:public|private|internal|fileprivate|open)?\s*protocol\s+(\w+)/,
194
+ /^extension\s+(\w+)/
195
+ ],
196
+ scala: [
197
+ /^(?:def|override\s+def)\s+(\w+)/,
198
+ /^(?:class|case\s+class|abstract\s+class)\s+(\w+)/,
199
+ /^(?:object|case\s+object)\s+(\w+)/,
200
+ /^trait\s+(\w+)/
201
+ ],
202
+ dart: [
203
+ /^(?:Future<[^>]+>|void|int|String|bool|dynamic|\w+)\s+(\w+)\s*\(/,
204
+ /^class\s+(\w+)/,
205
+ /^(?:abstract\s+)?class\s+(\w+)/,
206
+ /^mixin\s+(\w+)/,
207
+ /^extension\s+(\w+)/,
208
+ /^enum\s+(\w+)/
209
+ ],
210
+ lua: [
211
+ /^(?:local\s+)?function\s+(\w+)/,
212
+ /^(\w+)\s*=\s*function\s*\(/
213
+ ],
214
+ perl: [
215
+ /^sub\s+(\w+)/,
216
+ /^package\s+(\w+)/
217
+ ],
218
+ r: [
219
+ /^(\w+)\s*<-\s*function/,
220
+ /^(\w+)\s*=\s*function/
221
+ ],
222
+ julia: [
223
+ /^function\s+(\w+)/,
224
+ /^(?:mutable\s+)?struct\s+(\w+)/,
225
+ /^abstract\s+type\s+(\w+)/,
226
+ /^(\w+)\(.*\)\s*=/ // Short function syntax
227
+ ],
228
+ elixir: [
229
+ /^def\s+(\w+)/,
230
+ /^defp\s+(\w+)/,
231
+ /^defmodule\s+(\w+)/,
232
+ /^defmacro\s+(\w+)/
233
+ ],
234
+ clojure: [
235
+ /^\(defn\s+(\w+)/,
236
+ /^\(defn-\s+(\w+)/,
237
+ /^\(def\s+(\w+)/,
238
+ /^\(defmacro\s+(\w+)/
239
+ ],
240
+ haskell: [
241
+ /^(\w+)\s*::/, // Type signature
242
+ /^data\s+(\w+)/,
243
+ /^type\s+(\w+)/,
244
+ /^newtype\s+(\w+)/,
245
+ /^class\s+(\w+)/,
246
+ /^instance\s+(\w+)/
247
+ ],
248
+ erlang: [
249
+ /^(\w+)\s*\([^)]*\)\s*->/,
250
+ /^-module\((\w+)\)/,
251
+ /^-record\((\w+)/
252
+ ],
253
+ fsharp: [
254
+ /^let\s+(\w+)/,
255
+ /^type\s+(\w+)/,
256
+ /^module\s+(\w+)/,
257
+ /^member\s+(?:this|_)\.(\w+)/
258
+ ],
259
+ ocaml: [
260
+ /^let\s+(\w+)/,
261
+ /^type\s+(\w+)/,
262
+ /^module\s+(\w+)/,
263
+ /^class\s+(\w+)/
264
+ ],
265
+ zig: [
266
+ /^(?:pub\s+)?fn\s+(\w+)/,
267
+ /^(?:pub\s+)?const\s+(\w+)\s*=/,
268
+ /^(?:pub\s+)?var\s+(\w+)\s*=/
269
+ ],
270
+ nim: [
271
+ /^proc\s+(\w+)/,
272
+ /^func\s+(\w+)/,
273
+ /^method\s+(\w+)/,
274
+ /^template\s+(\w+)/,
275
+ /^macro\s+(\w+)/,
276
+ /^type\s+(\w+)/
277
+ ],
278
+ crystal: [
279
+ /^def\s+(\w+)/,
280
+ /^class\s+(\w+)/,
281
+ /^struct\s+(\w+)/,
282
+ /^module\s+(\w+)/,
283
+ /^macro\s+(\w+)/
284
+ ],
285
+ groovy: [
286
+ /^def\s+(\w+)/,
287
+ /^(?:public|private|protected)?\s*(?:static\s+)?(?:\w+\s+)?(\w+)\s*\(/,
288
+ /^class\s+(\w+)/,
289
+ /^interface\s+(\w+)/
290
+ ],
291
+ powershell: [
292
+ /^function\s+(\w+[\w-]*)/,
293
+ /^filter\s+(\w+[\w-]*)/,
294
+ /^class\s+(\w+)/
295
+ ],
296
+ dockerfile: [
297
+ /^FROM\s+(\S+)/i,
298
+ /^(?:RUN|CMD|ENTRYPOINT|COPY|ADD|ENV|ARG|EXPOSE|WORKDIR|LABEL)\s/i
299
+ ],
300
+ terraform: [
301
+ /^resource\s+"(\w+)"\s+"(\w+)"/,
302
+ /^data\s+"(\w+)"\s+"(\w+)"/,
303
+ /^module\s+"(\w+)"/,
304
+ /^variable\s+"(\w+)"/,
305
+ /^output\s+"(\w+)"/
306
+ ],
307
+ graphql: [
308
+ /^type\s+(\w+)/,
309
+ /^input\s+(\w+)/,
310
+ /^interface\s+(\w+)/,
311
+ /^enum\s+(\w+)/,
312
+ /^scalar\s+(\w+)/,
313
+ /^query\s+(\w+)/,
314
+ /^mutation\s+(\w+)/,
315
+ /^subscription\s+(\w+)/
316
+ ],
317
+ protobuf: [
318
+ /^message\s+(\w+)/,
319
+ /^service\s+(\w+)/,
320
+ /^enum\s+(\w+)/,
321
+ /^rpc\s+(\w+)/
322
+ ],
323
+ toml: [
324
+ /^\[(\w+)\]/, // Section
325
+ /^\[\[(\w+)\]\]/ // Array of tables
326
+ ],
327
+ ini: [
328
+ /^\[(\w+)\]/ // Section
329
+ ],
330
+ make: [
331
+ /^(\w[\w-]*)\s*:/, // Target
332
+ /^\.PHONY:\s*(\w+)/ // Phony target
333
+ ],
334
+ cmake: [
335
+ /^function\s*\(\s*(\w+)/i,
336
+ /^macro\s*\(\s*(\w+)/i,
337
+ /^(?:add_executable|add_library)\s*\(\s*(\w+)/i
338
+ ],
339
+ svelte: [
340
+ /^(?:export\s+)?(?:async\s+)?function\s+(\w+)/,
341
+ /^(?:export\s+)?const\s+(\w+)\s*=/,
342
+ /^(?:export\s+)?let\s+(\w+)\s*=/
343
+ ],
344
+ astro: [
345
+ /^(?:export\s+)?(?:async\s+)?function\s+(\w+)/,
346
+ /^(?:export\s+)?const\s+(\w+)\s*=/
347
+ ]
180
348
  };
181
349
  const langPatterns = patterns[language] || [];
182
350
  lines.forEach((line, index) => {
@@ -228,9 +228,10 @@ export class ArchiCoreServer {
228
228
  credentials: true,
229
229
  maxAge: 86400, // Cache preflight for 24 hours
230
230
  }));
231
- // JSON парсинг
232
- this.app.use(express.json({ limit: '50mb' }));
233
- this.app.use(express.urlencoded({ extended: true }));
231
+ // JSON парсинг - лимит должен покрывать максимальный тариф (admin = 1GB)
232
+ // Проверка по тарифу пользователя происходит в бизнес-логике после парсинга
233
+ this.app.use(express.json({ limit: '1gb' }));
234
+ this.app.use(express.urlencoded({ extended: true, limit: '1gb' }));
234
235
  // Request ID для трейсинга
235
236
  this.app.use((req, res, next) => {
236
237
  const requestId = req.headers['x-request-id'] ||