archicore 0.3.0 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/README.md +2267 -374
  2. package/dist/cli/commands/interactive.js +83 -23
  3. package/dist/cli/commands/projects.js +3 -3
  4. package/dist/cli/ui/prompt.d.ts +4 -0
  5. package/dist/cli/ui/prompt.js +22 -0
  6. package/dist/cli/utils/config.js +2 -2
  7. package/dist/cli/utils/upload-utils.js +65 -18
  8. package/dist/code-index/ast-parser.d.ts +4 -0
  9. package/dist/code-index/ast-parser.js +42 -0
  10. package/dist/code-index/index.d.ts +21 -1
  11. package/dist/code-index/index.js +45 -1
  12. package/dist/code-index/source-map-extractor.d.ts +71 -0
  13. package/dist/code-index/source-map-extractor.js +194 -0
  14. package/dist/gitlab/gitlab-service.d.ts +162 -0
  15. package/dist/gitlab/gitlab-service.js +652 -0
  16. package/dist/gitlab/index.d.ts +8 -0
  17. package/dist/gitlab/index.js +8 -0
  18. package/dist/server/config/passport.d.ts +14 -0
  19. package/dist/server/config/passport.js +86 -0
  20. package/dist/server/index.js +52 -10
  21. package/dist/server/middleware/api-auth.d.ts +2 -2
  22. package/dist/server/middleware/api-auth.js +21 -2
  23. package/dist/server/middleware/csrf.d.ts +23 -0
  24. package/dist/server/middleware/csrf.js +96 -0
  25. package/dist/server/routes/auth.d.ts +2 -2
  26. package/dist/server/routes/auth.js +204 -5
  27. package/dist/server/routes/device-auth.js +2 -2
  28. package/dist/server/routes/gitlab.d.ts +12 -0
  29. package/dist/server/routes/gitlab.js +528 -0
  30. package/dist/server/routes/oauth.d.ts +6 -0
  31. package/dist/server/routes/oauth.js +198 -0
  32. package/dist/server/services/audit-service.d.ts +1 -1
  33. package/dist/server/services/auth-service.d.ts +13 -1
  34. package/dist/server/services/auth-service.js +108 -7
  35. package/dist/server/services/email-service.d.ts +63 -0
  36. package/dist/server/services/email-service.js +586 -0
  37. package/dist/server/utils/disposable-email-domains.d.ts +14 -0
  38. package/dist/server/utils/disposable-email-domains.js +192 -0
  39. package/dist/types/api.d.ts +98 -0
  40. package/dist/types/gitlab.d.ts +245 -0
  41. package/dist/types/gitlab.js +11 -0
  42. package/package.json +12 -4
@@ -0,0 +1,528 @@
1
+ /**
2
+ * GitLab Integration Routes for ArchiCore
3
+ *
4
+ * Provides API endpoints for:
5
+ * - Managing GitLab instances (add, remove, list)
6
+ * - Listing and connecting repositories
7
+ * - Branch selection
8
+ * - Webhooks for auto-analysis
9
+ */
10
+ import { Router } from 'express';
11
+ import { mkdir } from 'fs/promises';
12
+ import { join } from 'path';
13
+ import AdmZip from 'adm-zip';
14
+ import { GitLabService } from '../../gitlab/gitlab-service.js';
15
+ import { ProjectService } from '../services/project-service.js';
16
+ import { AuthService } from '../services/auth-service.js';
17
+ import { authMiddleware } from './auth.js';
18
+ import { Logger } from '../../utils/logger.js';
19
+ export const gitlabRouter = Router();
20
+ // Services
21
+ const gitlabService = new GitLabService();
22
+ const projectService = new ProjectService();
23
+ const authService = AuthService.getInstance();
24
+ // ===== INSTANCE MANAGEMENT =====
25
+ /**
26
+ * POST /api/gitlab/instances
27
+ * Add a new GitLab instance connection
28
+ */
29
+ gitlabRouter.post('/instances', authMiddleware, async (req, res) => {
30
+ try {
31
+ if (!req.user) {
32
+ res.status(401).json({ error: 'Not authenticated' });
33
+ return;
34
+ }
35
+ const { instanceUrl, accessToken, name, rejectUnauthorizedSSL } = req.body;
36
+ if (!instanceUrl || !accessToken) {
37
+ res.status(400).json({ error: 'instanceUrl and accessToken are required' });
38
+ return;
39
+ }
40
+ const instance = await gitlabService.addInstance(req.user.id, instanceUrl, accessToken, { name, rejectUnauthorizedSSL });
41
+ res.json({
42
+ success: true,
43
+ instance: {
44
+ id: instance.id,
45
+ name: instance.name,
46
+ instanceUrl: instance.instanceUrl,
47
+ gitlabUsername: instance.gitlabUsername,
48
+ tokenScopes: instance.tokenScopes,
49
+ createdAt: instance.createdAt
50
+ }
51
+ });
52
+ }
53
+ catch (error) {
54
+ Logger.error('Add GitLab instance error:', error);
55
+ const message = error instanceof Error ? error.message : 'Failed to add GitLab instance';
56
+ res.status(500).json({ error: message });
57
+ }
58
+ });
59
+ /**
60
+ * GET /api/gitlab/instances
61
+ * List all GitLab instances for current user
62
+ */
63
+ gitlabRouter.get('/instances', authMiddleware, async (req, res) => {
64
+ try {
65
+ if (!req.user) {
66
+ res.status(401).json({ error: 'Not authenticated' });
67
+ return;
68
+ }
69
+ const instances = await gitlabService.getInstances(req.user.id);
70
+ res.json({
71
+ instances: instances.map(i => ({
72
+ id: i.id,
73
+ name: i.name,
74
+ instanceUrl: i.instanceUrl,
75
+ gitlabUsername: i.gitlabUsername,
76
+ tokenScopes: i.tokenScopes,
77
+ createdAt: i.createdAt,
78
+ lastUsedAt: i.lastUsedAt
79
+ }))
80
+ });
81
+ }
82
+ catch (error) {
83
+ Logger.error('List GitLab instances error:', error);
84
+ res.status(500).json({ error: 'Failed to list GitLab instances' });
85
+ }
86
+ });
87
+ /**
88
+ * DELETE /api/gitlab/instances/:id
89
+ * Remove a GitLab instance connection
90
+ */
91
+ gitlabRouter.delete('/instances/:id', authMiddleware, async (req, res) => {
92
+ try {
93
+ if (!req.user) {
94
+ res.status(401).json({ error: 'Not authenticated' });
95
+ return;
96
+ }
97
+ const success = await gitlabService.removeInstance(req.user.id, req.params.id);
98
+ if (!success) {
99
+ res.status(404).json({ error: 'GitLab instance not found' });
100
+ return;
101
+ }
102
+ res.json({ success: true });
103
+ }
104
+ catch (error) {
105
+ Logger.error('Remove GitLab instance error:', error);
106
+ res.status(500).json({ error: 'Failed to remove GitLab instance' });
107
+ }
108
+ });
109
+ // ===== PROJECTS/REPOSITORIES =====
110
+ /**
111
+ * GET /api/gitlab/instances/:instanceId/projects
112
+ * List available projects from a GitLab instance
113
+ */
114
+ gitlabRouter.get('/instances/:instanceId/projects', authMiddleware, async (req, res) => {
115
+ try {
116
+ if (!req.user) {
117
+ res.status(401).json({ error: 'Not authenticated' });
118
+ return;
119
+ }
120
+ const { search, owned, page, per_page } = req.query;
121
+ const projects = await gitlabService.listProjects(req.user.id, req.params.instanceId, {
122
+ search: search,
123
+ owned: owned === 'true',
124
+ page: page ? parseInt(page, 10) : undefined,
125
+ perPage: per_page ? parseInt(per_page, 10) : undefined
126
+ });
127
+ // Get connected repositories to mark them
128
+ const connectedRepos = await gitlabService.getConnectedRepositories(req.user.id);
129
+ const connectedProjectIds = new Set(connectedRepos.map(r => r.gitlabProjectId));
130
+ res.json({
131
+ projects: projects.map(p => ({
132
+ id: p.id,
133
+ name: p.name,
134
+ fullPath: p.path_with_namespace,
135
+ description: p.description,
136
+ visibility: p.visibility,
137
+ defaultBranch: p.default_branch,
138
+ webUrl: p.web_url,
139
+ stars: p.star_count,
140
+ forks: p.forks_count,
141
+ lastActivity: p.last_activity_at,
142
+ namespace: p.namespace.name,
143
+ archived: p.archived,
144
+ connected: connectedProjectIds.has(p.id),
145
+ projectId: connectedRepos.find(r => r.gitlabProjectId === p.id)?.projectId
146
+ }))
147
+ });
148
+ }
149
+ catch (error) {
150
+ Logger.error('List GitLab projects error:', error);
151
+ const message = error instanceof Error ? error.message : 'Failed to list projects';
152
+ res.status(500).json({ error: message });
153
+ }
154
+ });
155
+ /**
156
+ * GET /api/gitlab/instances/:instanceId/projects/:projectId/branches
157
+ * List branches for a GitLab project
158
+ */
159
+ gitlabRouter.get('/instances/:instanceId/projects/:projectId/branches', authMiddleware, async (req, res) => {
160
+ try {
161
+ if (!req.user) {
162
+ res.status(401).json({ error: 'Not authenticated' });
163
+ return;
164
+ }
165
+ const projectId = parseInt(req.params.projectId, 10) || req.params.projectId;
166
+ // Get project info for default branch
167
+ const project = await gitlabService.getProject(req.user.id, req.params.instanceId, projectId);
168
+ const branches = await gitlabService.listBranches(req.user.id, req.params.instanceId, projectId);
169
+ res.json({
170
+ branches: branches.map(b => ({
171
+ name: b.name,
172
+ protected: b.protected,
173
+ isDefault: b.name === project.default_branch,
174
+ lastCommit: {
175
+ id: b.commit.short_id,
176
+ title: b.commit.title,
177
+ date: b.commit.created_at
178
+ }
179
+ }))
180
+ });
181
+ }
182
+ catch (error) {
183
+ Logger.error('List branches error:', error);
184
+ const message = error instanceof Error ? error.message : 'Failed to list branches';
185
+ res.status(500).json({ error: message });
186
+ }
187
+ });
188
+ // ===== CONNECTED REPOSITORIES =====
189
+ /**
190
+ * GET /api/gitlab/repositories
191
+ * List all connected GitLab repositories
192
+ */
193
+ gitlabRouter.get('/repositories', authMiddleware, async (req, res) => {
194
+ try {
195
+ if (!req.user) {
196
+ res.status(401).json({ error: 'Not authenticated' });
197
+ return;
198
+ }
199
+ const repositories = await gitlabService.getConnectedRepositories(req.user.id);
200
+ res.json({
201
+ repositories: repositories.map(r => ({
202
+ id: r.id,
203
+ instanceId: r.instanceId,
204
+ fullPath: r.fullPath,
205
+ name: r.name,
206
+ owner: r.owner,
207
+ visibility: r.visibility,
208
+ projectId: r.projectId,
209
+ autoAnalyze: r.autoAnalyze,
210
+ analyzeMRs: r.analyzeMRs,
211
+ status: r.status,
212
+ lastAnalyzedAt: r.lastAnalyzedAt,
213
+ createdAt: r.createdAt
214
+ }))
215
+ });
216
+ }
217
+ catch (error) {
218
+ Logger.error('List connected repositories error:', error);
219
+ res.status(500).json({ error: 'Failed to list repositories' });
220
+ }
221
+ });
222
+ /**
223
+ * POST /api/gitlab/repositories/connect
224
+ * Connect a GitLab repository to ArchiCore
225
+ */
226
+ gitlabRouter.post('/repositories/connect', authMiddleware, async (req, res) => {
227
+ try {
228
+ if (!req.user) {
229
+ res.status(401).json({ error: 'Not authenticated' });
230
+ return;
231
+ }
232
+ const { instanceId, projectId, autoAnalyze, analyzeMRs, createProject, projectName, branch, forceReconnect } = req.body;
233
+ if (!instanceId || !projectId) {
234
+ res.status(400).json({ error: 'instanceId and projectId are required' });
235
+ return;
236
+ }
237
+ // Connect repository
238
+ const connectedRepo = await gitlabService.connectRepository(req.user.id, instanceId, projectId, { autoAnalyze, analyzeMRs, forceReconnect });
239
+ // Optionally create ArchiCore project
240
+ let archiProjectId;
241
+ if (createProject !== false) {
242
+ try {
243
+ // Download repository
244
+ const project = await gitlabService.getProject(req.user.id, instanceId, projectId);
245
+ const targetBranch = branch || project.default_branch || 'main';
246
+ Logger.progress(`Downloading GitLab repository: ${project.path_with_namespace} (branch: ${targetBranch})`);
247
+ const zipBuffer = await gitlabService.downloadRepository(req.user.id, instanceId, projectId, targetBranch);
248
+ Logger.info(`Downloaded ZIP: ${zipBuffer.length} bytes`);
249
+ // Create projects directory
250
+ const projectsDir = process.env.PROJECTS_DIR || join('.archicore', 'projects');
251
+ await mkdir(projectsDir, { recursive: true });
252
+ // Extract to project directory
253
+ const projectPath = join(projectsDir, project.name);
254
+ await mkdir(projectPath, { recursive: true });
255
+ const zip = new AdmZip(zipBuffer);
256
+ const zipEntries = zip.getEntries();
257
+ Logger.info(`ZIP contains ${zipEntries.length} entries`);
258
+ // Extract files
259
+ let extractedCount = 0;
260
+ for (const entry of zipEntries) {
261
+ if (entry.isDirectory) {
262
+ const dirPath = join(projectPath, entry.entryName);
263
+ await mkdir(dirPath, { recursive: true });
264
+ }
265
+ else {
266
+ const filePath = join(projectPath, entry.entryName);
267
+ const fileDir = join(projectPath, entry.entryName.split('/').slice(0, -1).join('/'));
268
+ await mkdir(fileDir, { recursive: true });
269
+ const content = entry.getData();
270
+ const { writeFile } = await import('fs/promises');
271
+ await writeFile(filePath, content);
272
+ extractedCount++;
273
+ }
274
+ }
275
+ Logger.info(`Extraction complete: ${extractedCount} files extracted`);
276
+ // GitLab creates a folder like "project-branch" inside, find it
277
+ const { readdir, stat } = await import('fs/promises');
278
+ const contents = await readdir(projectPath);
279
+ let actualPath = projectPath;
280
+ if (contents.length === 1) {
281
+ const innerPath = join(projectPath, contents[0]);
282
+ const stats = await stat(innerPath);
283
+ if (stats.isDirectory()) {
284
+ actualPath = innerPath;
285
+ Logger.info(`Using inner path: ${actualPath}`);
286
+ }
287
+ }
288
+ Logger.success(`Downloaded and extracted to: ${actualPath}`);
289
+ // Check project creation limit
290
+ const usageResult = await authService.checkAndUpdateUsage(req.user.id, 'project');
291
+ if (!usageResult.allowed) {
292
+ res.status(429).json({
293
+ error: 'Project limit reached',
294
+ message: `You have reached your daily project limit (${usageResult.limit})`,
295
+ usage: { used: usageResult.limit, limit: usageResult.limit, remaining: 0 }
296
+ });
297
+ return;
298
+ }
299
+ // Create ArchiCore project
300
+ const archiProject = await projectService.createProject(projectName || project.name, actualPath, req.user.id);
301
+ archiProjectId = archiProject.id;
302
+ // Update connected repo with project ID
303
+ await gitlabService.updateRepositoryProjectId(connectedRepo.id, archiProjectId);
304
+ await gitlabService.updateRepositoryStatus(connectedRepo.id, 'active');
305
+ Logger.info(`Linked ArchiCore project ${archiProjectId} to GitLab repository ${project.path_with_namespace}`);
306
+ }
307
+ catch (e) {
308
+ Logger.warn(`Failed to create ArchiCore project for ${connectedRepo.fullPath}: ${e}`);
309
+ await gitlabService.updateRepositoryStatus(connectedRepo.id, 'error', String(e));
310
+ }
311
+ }
312
+ res.json({
313
+ success: true,
314
+ repository: {
315
+ id: connectedRepo.id,
316
+ fullPath: connectedRepo.fullPath,
317
+ projectId: archiProjectId,
318
+ status: connectedRepo.status
319
+ }
320
+ });
321
+ }
322
+ catch (error) {
323
+ Logger.error('Connect GitLab repository error:', error);
324
+ const message = error instanceof Error ? error.message : 'Failed to connect repository';
325
+ res.status(500).json({ error: message });
326
+ }
327
+ });
328
+ /**
329
+ * DELETE /api/gitlab/repositories/:id
330
+ * Disconnect a GitLab repository
331
+ */
332
+ gitlabRouter.delete('/repositories/:id', authMiddleware, async (req, res) => {
333
+ try {
334
+ if (!req.user) {
335
+ res.status(401).json({ error: 'Not authenticated' });
336
+ return;
337
+ }
338
+ const success = await gitlabService.disconnectRepository(req.user.id, req.params.id);
339
+ if (!success) {
340
+ res.status(404).json({ error: 'Repository not found' });
341
+ return;
342
+ }
343
+ res.json({ success: true });
344
+ }
345
+ catch (error) {
346
+ Logger.error('Disconnect GitLab repository error:', error);
347
+ res.status(500).json({ error: 'Failed to disconnect repository' });
348
+ }
349
+ });
350
+ /**
351
+ * POST /api/gitlab/repositories/:id/analyze
352
+ * Trigger analysis for a connected repository
353
+ */
354
+ gitlabRouter.post('/repositories/:id/analyze', authMiddleware, async (req, res) => {
355
+ try {
356
+ if (!req.user) {
357
+ res.status(401).json({ error: 'Not authenticated' });
358
+ return;
359
+ }
360
+ const repo = await gitlabService.getConnectedRepository(req.params.id);
361
+ if (!repo) {
362
+ res.status(404).json({ error: 'Repository not found' });
363
+ return;
364
+ }
365
+ if (!repo.projectId) {
366
+ res.status(400).json({ error: 'Repository not linked to an ArchiCore project' });
367
+ return;
368
+ }
369
+ // Update status
370
+ await gitlabService.updateRepositoryStatus(repo.id, 'syncing');
371
+ try {
372
+ const result = await projectService.runFullAnalysis(repo.projectId);
373
+ await gitlabService.updateLastAnalyzed(repo.id);
374
+ await gitlabService.updateRepositoryStatus(repo.id, 'active');
375
+ res.json({ success: true, result });
376
+ }
377
+ catch (e) {
378
+ await gitlabService.updateRepositoryStatus(repo.id, 'error', String(e));
379
+ throw e;
380
+ }
381
+ }
382
+ catch (error) {
383
+ Logger.error('Analyze GitLab repository error:', error);
384
+ res.status(500).json({ error: 'Failed to analyze repository' });
385
+ }
386
+ });
387
+ // ===== WEBHOOKS =====
388
+ /**
389
+ * POST /api/gitlab/webhook
390
+ * Receive GitLab webhooks
391
+ */
392
+ gitlabRouter.post('/webhook', async (req, res) => {
393
+ try {
394
+ const event = req.headers['x-gitlab-event'];
395
+ const token = req.headers['x-gitlab-token'];
396
+ const payload = req.body;
397
+ if (!event || !payload) {
398
+ res.status(400).json({ error: 'Invalid webhook' });
399
+ return;
400
+ }
401
+ // Get repository from payload
402
+ const fullPath = payload.project?.path_with_namespace;
403
+ if (!fullPath) {
404
+ res.status(200).json({ message: 'No project in payload' });
405
+ return;
406
+ }
407
+ // Find connected repository
408
+ const repo = await gitlabService.findRepositoryByWebhook(fullPath);
409
+ if (!repo) {
410
+ res.status(200).json({ message: 'Repository not connected' });
411
+ return;
412
+ }
413
+ // Verify webhook token
414
+ if (repo.webhookSecret) {
415
+ const secret = gitlabService.getWebhookSecret(repo.webhookSecret);
416
+ if (!token || !gitlabService.verifyWebhookToken(token, secret)) {
417
+ Logger.warn(`Invalid webhook token for ${fullPath}`);
418
+ res.status(401).json({ error: 'Invalid token' });
419
+ return;
420
+ }
421
+ }
422
+ Logger.info(`GitLab webhook received: ${event} for ${fullPath}`);
423
+ // Handle different events
424
+ switch (payload.object_kind) {
425
+ case 'push':
426
+ await handlePushEvent(repo, payload);
427
+ break;
428
+ case 'merge_request':
429
+ await handleMergeRequestEvent(repo, payload);
430
+ break;
431
+ default:
432
+ Logger.debug(`Unhandled GitLab webhook event: ${payload.object_kind}`);
433
+ }
434
+ res.status(200).json({ received: true });
435
+ }
436
+ catch (error) {
437
+ Logger.error('GitLab webhook error:', error);
438
+ res.status(500).json({ error: 'Webhook processing failed' });
439
+ }
440
+ });
441
+ /**
442
+ * Handle push event
443
+ */
444
+ async function handlePushEvent(repo, payload) {
445
+ if (!repo || !repo.autoAnalyze || !repo.projectId)
446
+ return;
447
+ const branch = payload.ref?.replace('refs/heads/', '');
448
+ if (branch !== repo.defaultBranch) {
449
+ Logger.debug(`Push to non-default branch ${branch}, skipping analysis`);
450
+ return;
451
+ }
452
+ Logger.info(`Auto-analyzing ${repo.fullPath} after push`);
453
+ try {
454
+ await gitlabService.updateRepositoryStatus(repo.id, 'syncing');
455
+ await projectService.runFullAnalysis(repo.projectId);
456
+ await gitlabService.updateLastAnalyzed(repo.id);
457
+ await gitlabService.updateRepositoryStatus(repo.id, 'active');
458
+ }
459
+ catch (e) {
460
+ Logger.error(`Analysis failed for ${repo.fullPath}: ${e}`);
461
+ await gitlabService.updateRepositoryStatus(repo.id, 'error', String(e));
462
+ }
463
+ }
464
+ /**
465
+ * Handle merge request event
466
+ */
467
+ async function handleMergeRequestEvent(repo, payload) {
468
+ if (!repo || !repo.analyzeMRs || !repo.projectId)
469
+ return;
470
+ const action = payload.object_attributes?.action;
471
+ const mrIid = payload.object_attributes?.iid;
472
+ if (!mrIid || !['open', 'reopen', 'update'].includes(action || '')) {
473
+ return;
474
+ }
475
+ Logger.info(`Analyzing MR !${mrIid} for ${repo.fullPath}`);
476
+ try {
477
+ const instance = await gitlabService.getInstanceForRepository(repo);
478
+ if (!instance)
479
+ return;
480
+ // Get MR changes
481
+ const changes = await gitlabService.getMergeRequestChanges(instance.userId, instance.id, repo.gitlabProjectId, mrIid);
482
+ // Analyze impact
483
+ const impact = await projectService.analyzeImpact(repo.projectId, {
484
+ description: payload.object_attributes?.title || 'Merge Request',
485
+ files: changes.map(c => c.new_path),
486
+ symbols: [],
487
+ type: 'modify'
488
+ });
489
+ // Generate comment
490
+ const riskEmoji = {
491
+ low: '✅',
492
+ medium: '⚠️',
493
+ high: '🔶',
494
+ critical: '🚨'
495
+ };
496
+ const riskLevel = impact.risks.length > 0
497
+ ? impact.risks.reduce((max, r) => ['critical', 'high', 'medium', 'low'].indexOf(r.severity) <
498
+ ['critical', 'high', 'medium', 'low'].indexOf(max) ? r.severity : max, 'low')
499
+ : 'low';
500
+ const comment = `## ArchiCore Analysis ${riskEmoji[riskLevel]}
501
+
502
+ **Risk Level:** ${riskLevel.toUpperCase()}
503
+ **Affected Components:** ${impact.affectedNodes.length}
504
+ **Files Changed:** ${changes.length}
505
+
506
+ ### Impact Summary
507
+
508
+ ${impact.affectedNodes.slice(0, 5).map(n => `- **${n.name}** (${n.type}) - ${n.reason}`).join('\n') || 'No significant impact detected.'}
509
+
510
+ ${impact.risks.length > 0 ? `### Risks
511
+
512
+ ${impact.risks.slice(0, 3).map(r => `- ${riskEmoji[r.severity]} **${r.category}**: ${r.description}`).join('\n')}` : ''}
513
+
514
+ ${impact.recommendations.length > 0 ? `### Recommendations
515
+
516
+ ${impact.recommendations.slice(0, 3).map(r => `- ${r.description}`).join('\n')}` : ''}
517
+
518
+ ---
519
+ *Analyzed by [ArchiCore](https://archicore.io)*`;
520
+ // Post comment
521
+ await gitlabService.postMRComment(instance.userId, instance.id, repo.gitlabProjectId, mrIid, comment);
522
+ }
523
+ catch (e) {
524
+ Logger.error(`MR analysis failed for ${repo.fullPath}!${mrIid}: ${e}`);
525
+ }
526
+ }
527
+ export default gitlabRouter;
528
+ //# sourceMappingURL=gitlab.js.map
@@ -0,0 +1,6 @@
1
+ /**
2
+ * OAuth Authentication Routes
3
+ * Google and GitHub OAuth login flows
4
+ */
5
+ export declare const oauthRouter: import("express-serve-static-core").Router;
6
+ //# sourceMappingURL=oauth.d.ts.map