@velt-js/mcp-installer 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,803 @@
1
+ /**
2
+ * Velt Integration Script
3
+ *
4
+ * Post-CLI integration and validation tool for Velt Next.js projects.
5
+ * Assumes @velt-js/add-velt CLI has already been run.
6
+ *
7
+ * @module integration
8
+ */
9
+
10
+ import fs from 'fs';
11
+ import path from 'path';
12
+ import { applyHeaderPositioning, findVeltSidebarFiles } from './header-positioning.js';
13
+
14
+ /**
15
+ * @typedef {Object} IntegrationConfig
16
+ * @property {string} apiKey - Required: Velt API key
17
+ * @property {string} [authToken] - Optional: Velt auth token for JWT
18
+ * @property {boolean} [enableReactFlowCursor] - Optional: Enable ReactFlow cursor integration
19
+ */
20
+
21
+ /**
22
+ * @typedef {Object} IntegrationPatterns
23
+ * @property {boolean} [hasReactFlow] - Automatically wire VeltCursor
24
+ * @property {boolean} [hasTiptap] - Add TODO comment for Tiptap integration
25
+ * @property {boolean} [hasCodeMirror] - Add TODO comment for CodeMirror integration
26
+ * @property {boolean} [hasAgGrid] - Add TODO comment for AG-Grid integration
27
+ * @property {boolean} [hasTanStack] - Add TODO comment for TanStack table integration
28
+ */
29
+
30
+ /**
31
+ * @typedef {Object} ComponentAdded
32
+ * @property {string} file - File path
33
+ * @property {string} name - Component name
34
+ * @property {string} [description] - Description of component
35
+ */
36
+
37
+ /**
38
+ * @typedef {Object} IntegrationPoint
39
+ * @property {string} file - File path
40
+ * @property {string} type - Type of integration point
41
+ * @property {string} description - Description of what was done
42
+ * @property {string} [placeholder] - The placeholder that was replaced
43
+ */
44
+
45
+ /**
46
+ * @typedef {Object} ValidationIssue
47
+ * @property {string} [file] - File path where issue occurred
48
+ * @property {string} type - Type of validation issue
49
+ * @property {string} message - Description of the issue
50
+ */
51
+
52
+ /**
53
+ * @typedef {Object} IntegrationResult
54
+ * @property {boolean} success - True if no validation issues
55
+ * @property {Object} data - Integration data
56
+ * @property {string[]} data.filesModified - Paths of files that were modified
57
+ * @property {ComponentAdded[]} data.componentsAdded - Components that were added
58
+ * @property {IntegrationPoint[]} data.integrationPoints - Integration points
59
+ * @property {ValidationIssue[]} data.validationIssues - Validation issues found
60
+ */
61
+
62
+ /**
63
+ * @typedef {Object} AnalyzeOptions
64
+ * @property {string} projectPath - Project root path
65
+ * @property {IntegrationConfig} config - Integration configuration
66
+ * @property {IntegrationPatterns} [patterns] - Library detection patterns
67
+ */
68
+
69
+ // ============================================================================
70
+ // Helper Functions
71
+ // ============================================================================
72
+
73
+ /**
74
+ * Detects project structure and returns path helper functions
75
+ *
76
+ * @param {string} projectPath - Project root path
77
+ * @returns {Object} Project structure information
78
+ */
79
+ function detectProjectStructure(projectPath) {
80
+ const srcAppPath = path.join(projectPath, 'src', 'app');
81
+ const hasSrcApp = fs.existsSync(srcAppPath);
82
+
83
+ const appRoot = hasSrcApp
84
+ ? path.join(projectPath, 'src', 'app')
85
+ : path.join(projectPath, 'app');
86
+
87
+ const componentsRoot = hasSrcApp
88
+ ? path.join(projectPath, 'src', 'components')
89
+ : path.join(projectPath, 'components');
90
+
91
+ return {
92
+ hasSrcApp,
93
+ appRoot,
94
+ componentsRoot,
95
+ appPath: (...segments) => path.join(appRoot, ...segments),
96
+ componentsPath: (...segments) => path.join(componentsRoot, ...segments),
97
+ };
98
+ }
99
+
100
+ /**
101
+ * Safely reads a file
102
+ *
103
+ * @param {string} filePath - File path
104
+ * @returns {string} File content
105
+ * @throws {Error} If file cannot be read
106
+ */
107
+ function readFile(filePath) {
108
+ try {
109
+ return fs.readFileSync(filePath, 'utf-8');
110
+ } catch (error) {
111
+ throw new Error(`Failed to read file ${filePath}: ${error.message}`);
112
+ }
113
+ }
114
+
115
+ /**
116
+ * Writes file only if content has changed
117
+ *
118
+ * @param {string} filePath - File path
119
+ * @param {string} newContent - New content
120
+ * @param {string[]} filesModified - Array to track modified files
121
+ */
122
+ function writeFileIfChanged(filePath, newContent, filesModified) {
123
+ try {
124
+ const currentContent = fs.existsSync(filePath) ? fs.readFileSync(filePath, 'utf-8') : '';
125
+
126
+ if (currentContent !== newContent) {
127
+ fs.writeFileSync(filePath, newContent, 'utf-8');
128
+
129
+ // Track as relative path
130
+ const relativePath = filePath.split('/src/').pop() || filePath.split('/app/').pop() || filePath;
131
+ if (!filesModified.includes(relativePath)) {
132
+ filesModified.push(relativePath);
133
+ }
134
+ }
135
+ } catch (error) {
136
+ throw new Error(`Failed to write file ${filePath}: ${error.message}`);
137
+ }
138
+ }
139
+
140
+ /**
141
+ * Checks if CLI output exists
142
+ *
143
+ * @param {Object} structure - Project structure from detectProjectStructure
144
+ * @param {ValidationIssue[]} validationIssues - Array to collect validation issues
145
+ * @returns {boolean} True if core CLI files exist
146
+ */
147
+ function ensureCliOutputExists(structure, validationIssues) {
148
+ const { appPath, componentsPath } = structure;
149
+
150
+ const requiredFiles = [
151
+ { path: appPath('page.tsx'), name: 'app/page.tsx' },
152
+ { path: appPath('layout.tsx'), name: 'app/layout.tsx' },
153
+ { path: appPath('userAuth', 'AppProviders.tsx'), name: 'app/userAuth/AppProviders.tsx' },
154
+ { path: appPath('userAuth', 'SignIn.tsx'), name: 'app/userAuth/SignIn.tsx' },
155
+ { path: appPath('document', 'page.tsx'), name: 'app/document/page.tsx' },
156
+ { path: appPath('api', 'velt', 'token', 'route.ts'), name: 'app/api/velt/token/route.ts' },
157
+ { path: componentsPath('velt', 'VeltCollaboration.tsx'), name: 'components/velt/VeltCollaboration.tsx' },
158
+ { path: componentsPath('velt', 'ui-customization', 'styles.css'), name: 'components/velt/ui-customization/styles.css' },
159
+ ];
160
+
161
+ let missingCount = 0;
162
+
163
+ for (const file of requiredFiles) {
164
+ if (!fs.existsSync(file.path)) {
165
+ validationIssues.push({
166
+ file: file.name,
167
+ type: 'missing-file',
168
+ message: `Expected file generated by @velt-js/add-velt CLI is missing: ${file.name}`,
169
+ });
170
+ missingCount++;
171
+ }
172
+ }
173
+
174
+ // If most files are missing, CLI likely wasn't run
175
+ if (missingCount >= requiredFiles.length / 2) {
176
+ validationIssues.push({
177
+ type: 'cli-not-run',
178
+ message: 'CLI output not found. Please run `npx @velt-js/add-velt` first before running integration.',
179
+ });
180
+ return false;
181
+ }
182
+
183
+ return true;
184
+ }
185
+
186
+ /**
187
+ * Replaces API key placeholder in page.tsx
188
+ *
189
+ * @param {Object} structure - Project structure
190
+ * @param {string} apiKey - API key to insert
191
+ * @param {string[]} filesModified - Array to track modified files
192
+ * @param {IntegrationPoint[]} integrationPoints - Array to track integration points
193
+ * @param {ValidationIssue[]} validationIssues - Array to track validation issues
194
+ */
195
+ function replaceApiKeyInPage(structure, apiKey, filesModified, integrationPoints, validationIssues) {
196
+ const { appPath } = structure;
197
+ const pagePath = appPath('page.tsx');
198
+
199
+ if (!fs.existsSync(pagePath)) {
200
+ validationIssues.push({
201
+ file: 'app/page.tsx',
202
+ type: 'missing-file',
203
+ message: 'Cannot replace API key: app/page.tsx does not exist',
204
+ });
205
+ return;
206
+ }
207
+
208
+ try {
209
+ let content = readFile(pagePath);
210
+ const originalContent = content;
211
+
212
+ // Replace API key placeholder
213
+ const placeholder = '"YOUR_VELT_API_KEY"';
214
+ if (content.includes(placeholder)) {
215
+ content = content.replace(new RegExp(placeholder, 'g'), `"${apiKey}"`);
216
+
217
+ writeFileIfChanged(pagePath, content, filesModified);
218
+
219
+ integrationPoints.push({
220
+ file: 'app/page.tsx',
221
+ type: 'apiKeyReplacement',
222
+ description: 'Replaced YOUR_VELT_API_KEY placeholder with provided Velt API key.',
223
+ placeholder: 'YOUR_VELT_API_KEY',
224
+ });
225
+ }
226
+
227
+ // Validate that placeholder is gone
228
+ if (content.includes(placeholder)) {
229
+ validationIssues.push({
230
+ file: 'app/page.tsx',
231
+ type: 'placeholder-remaining',
232
+ message: 'YOUR_VELT_API_KEY placeholder still exists after replacement attempt',
233
+ });
234
+ }
235
+ } catch (error) {
236
+ validationIssues.push({
237
+ file: 'app/page.tsx',
238
+ type: 'error',
239
+ message: `Error replacing API key: ${error.message}`,
240
+ });
241
+ }
242
+ }
243
+
244
+ /**
245
+ * Replaces auth token placeholder in JWT route
246
+ *
247
+ * @param {Object} structure - Project structure
248
+ * @param {string} apiKey - API key to insert
249
+ * @param {string} authToken - Auth token to insert
250
+ * @param {string[]} filesModified - Array to track modified files
251
+ * @param {IntegrationPoint[]} integrationPoints - Array to track integration points
252
+ * @param {ValidationIssue[]} validationIssues - Array to track validation issues
253
+ */
254
+ function replaceAuthTokenInRoute(structure, apiKey, authToken, filesModified, integrationPoints, validationIssues) {
255
+ const { appPath } = structure;
256
+ const routePath = appPath('api', 'velt', 'token', 'route.ts');
257
+
258
+ if (!fs.existsSync(routePath)) {
259
+ validationIssues.push({
260
+ file: 'app/api/velt/token/route.ts',
261
+ type: 'missing-file',
262
+ message: 'Cannot replace auth token: app/api/velt/token/route.ts does not exist',
263
+ });
264
+ return;
265
+ }
266
+
267
+ try {
268
+ let content = readFile(routePath);
269
+ let modified = false;
270
+
271
+ // Replace API key if present
272
+ const apiKeyPlaceholder = '"YOUR_VELT_API_KEY"';
273
+ if (content.includes(apiKeyPlaceholder)) {
274
+ content = content.replace(new RegExp(apiKeyPlaceholder, 'g'), `"${apiKey}"`);
275
+ modified = true;
276
+ }
277
+
278
+ // Replace auth token if provided
279
+ const authTokenPlaceholder = '"YOUR_VELT_AUTH_TOKEN"';
280
+ if (authToken && content.includes(authTokenPlaceholder)) {
281
+ content = content.replace(new RegExp(authTokenPlaceholder, 'g'), `"${authToken}"`);
282
+ modified = true;
283
+ }
284
+
285
+ if (modified) {
286
+ writeFileIfChanged(routePath, content, filesModified);
287
+
288
+ integrationPoints.push({
289
+ file: 'app/api/velt/token/route.ts',
290
+ type: 'authTokenReplacement',
291
+ description: 'Replaced YOUR_VELT_API_KEY and YOUR_VELT_AUTH_TOKEN placeholders in Velt token route.',
292
+ });
293
+ }
294
+
295
+ // Validate that placeholders are gone if they should be
296
+ if (content.includes(apiKeyPlaceholder)) {
297
+ validationIssues.push({
298
+ file: 'app/api/velt/token/route.ts',
299
+ type: 'placeholder-remaining',
300
+ message: 'YOUR_VELT_API_KEY placeholder still exists in token route',
301
+ });
302
+ }
303
+
304
+ if (authToken && content.includes(authTokenPlaceholder)) {
305
+ validationIssues.push({
306
+ file: 'app/api/velt/token/route.ts',
307
+ type: 'placeholder-remaining',
308
+ message: 'YOUR_VELT_AUTH_TOKEN placeholder still exists in token route',
309
+ });
310
+ }
311
+ } catch (error) {
312
+ validationIssues.push({
313
+ file: 'app/api/velt/token/route.ts',
314
+ type: 'error',
315
+ message: `Error replacing auth token: ${error.message}`,
316
+ });
317
+ }
318
+ }
319
+
320
+ /**
321
+ * Wires ReactFlow cursor integration
322
+ *
323
+ * @param {Object} structure - Project structure
324
+ * @param {string[]} filesModified - Array to track modified files
325
+ * @param {ComponentAdded[]} componentsAdded - Array to track components added
326
+ * @param {IntegrationPoint[]} integrationPoints - Array to track integration points
327
+ * @param {ValidationIssue[]} validationIssues - Array to track validation issues
328
+ */
329
+ function wireReactFlowCursor(structure, filesModified, componentsAdded, integrationPoints, validationIssues) {
330
+ const { componentsPath } = structure;
331
+ const collaborationPath = componentsPath('velt', 'VeltCollaboration.tsx');
332
+
333
+ if (!fs.existsSync(collaborationPath)) {
334
+ validationIssues.push({
335
+ file: 'components/velt/VeltCollaboration.tsx',
336
+ type: 'missing-file',
337
+ message: 'Cannot wire ReactFlow cursor: VeltCollaboration.tsx does not exist',
338
+ });
339
+ return;
340
+ }
341
+
342
+ try {
343
+ let content = readFile(collaborationPath);
344
+
345
+ // Check if VeltCursor already exists
346
+ if (content.includes('VeltCursor')) {
347
+ console.error(' ✓ VeltCursor already present');
348
+ return; // Already wired
349
+ }
350
+
351
+ console.error(' ✓ Adding VeltCursor component (ReactFlow integration)');
352
+
353
+ // Add import if not present
354
+ if (!content.includes("import { VeltCursor }")) {
355
+ const importStatement = 'import { VeltCursor } from "@veltdev/react";';
356
+
357
+ // Find last import and add after it
358
+ const importRegex = /^import\s+.*?from\s+['"].*?['"];?\s*$/gm;
359
+ const imports = content.match(importRegex) || [];
360
+
361
+ if (imports.length > 0) {
362
+ const lastImport = imports[imports.length - 1];
363
+ const lastImportIndex = content.lastIndexOf(lastImport);
364
+ const afterLastImport = lastImportIndex + lastImport.length;
365
+ content = content.slice(0, afterLastImport) + '\n' + importStatement + content.slice(afterLastImport);
366
+ } else {
367
+ // Add at top of file
368
+ content = importStatement + '\n' + content;
369
+ }
370
+ }
371
+
372
+ // Find a good place to add VeltCursor - look for a return statement with a div/main
373
+ // Simple approach: add before the last closing tag in the return statement
374
+ const returnMatch = content.match(/return\s*\(([\s\S]*?)\);?\s*}[\s]*$/m);
375
+
376
+ if (returnMatch) {
377
+ const returnContent = returnMatch[1];
378
+ const lastClosingTag = returnContent.lastIndexOf('</');
379
+
380
+ if (lastClosingTag > 0) {
381
+ const beforeClosing = returnContent.slice(0, lastClosingTag);
382
+ const afterClosing = returnContent.slice(lastClosingTag);
383
+ const cursorComponent = ' <VeltCursor />\n';
384
+ const newReturnContent = beforeClosing + cursorComponent + afterClosing;
385
+ content = content.replace(returnMatch[0], `return (\n${newReturnContent}\n );\n}`);
386
+ }
387
+ }
388
+
389
+ writeFileIfChanged(collaborationPath, content, filesModified);
390
+
391
+ componentsAdded.push({
392
+ file: 'components/velt/VeltCollaboration.tsx',
393
+ name: 'VeltCursor',
394
+ description: 'Automatically wired ReactFlow cursor into VeltCollaboration.',
395
+ });
396
+
397
+ integrationPoints.push({
398
+ file: 'components/velt/VeltCollaboration.tsx',
399
+ type: 'reactflowCursor',
400
+ description: 'Injected Velt cursor component into ReactFlow collaboration canvas because patterns.hasReactFlow is true.',
401
+ });
402
+ } catch (error) {
403
+ validationIssues.push({
404
+ file: 'components/velt/VeltCollaboration.tsx',
405
+ type: 'error',
406
+ message: `Error wiring ReactFlow cursor: ${error.message}`,
407
+ });
408
+ }
409
+ }
410
+
411
+ /**
412
+ * Adds TODO comments for library integrations
413
+ *
414
+ * @param {Object} structure - Project structure
415
+ * @param {IntegrationPatterns} patterns - Library patterns
416
+ * @param {string[]} filesModified - Array to track modified files
417
+ * @param {IntegrationPoint[]} integrationPoints - Array to track integration points
418
+ * @param {ValidationIssue[]} validationIssues - Array to track validation issues
419
+ */
420
+ function addLibraryTodoComments(structure, patterns, filesModified, integrationPoints, validationIssues) {
421
+ const { componentsPath } = structure;
422
+ const collaborationPath = componentsPath('velt', 'VeltCollaboration.tsx');
423
+
424
+ if (!fs.existsSync(collaborationPath)) {
425
+ return; // Already reported in ensureCliOutputExists
426
+ }
427
+
428
+ try {
429
+ let content = readFile(collaborationPath);
430
+ let modified = false;
431
+
432
+ const todos = [];
433
+
434
+ if (patterns.hasTiptap && !content.includes('[Velt] TODO: Tiptap')) {
435
+ todos.push({
436
+ library: 'Tiptap',
437
+ comment: '// [Velt] TODO: Wire Tiptap comments/collaboration here. See docs at https://docs.velt.dev/text-editor/tiptap/setup',
438
+ pattern: 'hasTiptap',
439
+ });
440
+ }
441
+
442
+ if (patterns.hasCodeMirror && !content.includes('[Velt] TODO: CodeMirror')) {
443
+ todos.push({
444
+ library: 'CodeMirror',
445
+ comment: '// [Velt] TODO: Wire CodeMirror CRDT integration here. See docs at https://docs.velt.dev/async-collaboration/setup/codemirror',
446
+ pattern: 'hasCodeMirror',
447
+ });
448
+ }
449
+
450
+ if (patterns.hasAgGrid && !content.includes('[Velt] TODO: AG-Grid')) {
451
+ todos.push({
452
+ library: 'AG-Grid',
453
+ comment: '// [Velt] TODO: Wire AG-Grid table comments here. Add data-velt-target-comment-element-id to cells. See docs at https://docs.velt.dev/async-collaboration/comments/customize-behavior/target-element',
454
+ pattern: 'hasAgGrid',
455
+ });
456
+ }
457
+
458
+ if (patterns.hasTanStack && !content.includes('[Velt] TODO: TanStack')) {
459
+ todos.push({
460
+ library: 'TanStack',
461
+ comment: '// [Velt] TODO: Wire TanStack table comments here. Add data-velt-target-comment-element-id to cells. See docs at https://docs.velt.dev/async-collaboration/comments/customize-behavior/target-element',
462
+ pattern: 'hasTanStack',
463
+ });
464
+ }
465
+
466
+ if (todos.length > 0) {
467
+ // Find a good place to add TODOs - after imports, before the component export
468
+ const exportMatch = content.match(/export\s+(default\s+)?function/);
469
+
470
+ if (exportMatch) {
471
+ const exportIndex = exportMatch.index;
472
+ const todosText = '\n' + todos.map(t => t.comment).join('\n') + '\n';
473
+ content = content.slice(0, exportIndex) + todosText + content.slice(exportIndex);
474
+ modified = true;
475
+
476
+ // Track each TODO
477
+ for (const todo of todos) {
478
+ integrationPoints.push({
479
+ file: 'components/velt/VeltCollaboration.tsx',
480
+ type: 'todoComment',
481
+ description: `Added TODO comment for ${todo.library} integration (patterns.${todo.pattern} detected).`,
482
+ });
483
+ }
484
+ }
485
+ }
486
+
487
+ if (modified) {
488
+ writeFileIfChanged(collaborationPath, content, filesModified);
489
+ }
490
+ } catch (error) {
491
+ validationIssues.push({
492
+ file: 'components/velt/VeltCollaboration.tsx',
493
+ type: 'error',
494
+ message: `Error adding library TODO comments: ${error.message}`,
495
+ });
496
+ }
497
+ }
498
+
499
+ /**
500
+ * Runs validation checks
501
+ *
502
+ * @param {Object} structure - Project structure
503
+ * @param {IntegrationConfig} config - Integration config
504
+ * @param {ValidationIssue[]} validationIssues - Array to collect validation issues
505
+ */
506
+ function runValidationChecks(structure, config, validationIssues) {
507
+ const { appPath, componentsPath } = structure;
508
+
509
+ // Validate app/page.tsx
510
+ const pagePath = appPath('page.tsx');
511
+ if (fs.existsSync(pagePath)) {
512
+ try {
513
+ const pageContent = readFile(pagePath);
514
+
515
+ // Check for "use client" directive
516
+ if (!pageContent.includes('"use client"') && !pageContent.includes("'use client'")) {
517
+ validationIssues.push({
518
+ file: 'app/page.tsx',
519
+ type: 'validation-failed',
520
+ message: 'Missing "use client" directive at top of file',
521
+ });
522
+ }
523
+
524
+ // Check for VeltProvider
525
+ if (!pageContent.includes('VeltProvider')) {
526
+ validationIssues.push({
527
+ file: 'app/page.tsx',
528
+ type: 'validation-failed',
529
+ message: 'VeltProvider not found in page.tsx. Expected CLI to generate this.',
530
+ });
531
+ }
532
+
533
+ // Check that API key placeholder is gone
534
+ if (pageContent.includes('"YOUR_VELT_API_KEY"')) {
535
+ validationIssues.push({
536
+ file: 'app/page.tsx',
537
+ type: 'validation-failed',
538
+ message: 'YOUR_VELT_API_KEY placeholder still exists. API key replacement may have failed.',
539
+ });
540
+ }
541
+ } catch (error) {
542
+ validationIssues.push({
543
+ file: 'app/page.tsx',
544
+ type: 'error',
545
+ message: `Error validating page.tsx: ${error.message}`,
546
+ });
547
+ }
548
+ }
549
+
550
+ // Validate app/layout.tsx
551
+ const layoutPath = appPath('layout.tsx');
552
+ if (fs.existsSync(layoutPath)) {
553
+ try {
554
+ const layoutContent = readFile(layoutPath);
555
+
556
+ // Check for AppProviders
557
+ if (!layoutContent.includes('AppProviders')) {
558
+ validationIssues.push({
559
+ file: 'app/layout.tsx',
560
+ type: 'validation-failed',
561
+ message: 'AppProviders not found in layout.tsx. Expected CLI to generate this.',
562
+ });
563
+ }
564
+
565
+ // Check that AppProviders wraps {children}
566
+ if (layoutContent.includes('AppProviders') && !layoutContent.includes('{children}')) {
567
+ validationIssues.push({
568
+ file: 'app/layout.tsx',
569
+ type: 'validation-failed',
570
+ message: 'AppProviders exists but {children} not found. Layout structure may be incorrect.',
571
+ });
572
+ }
573
+ } catch (error) {
574
+ validationIssues.push({
575
+ file: 'app/layout.tsx',
576
+ type: 'error',
577
+ message: `Error validating layout.tsx: ${error.message}`,
578
+ });
579
+ }
580
+ }
581
+
582
+ // Validate JWT route if auth token was provided
583
+ const tokenRoutePath = appPath('api', 'velt', 'token', 'route.ts');
584
+ if (config.authToken && fs.existsSync(tokenRoutePath)) {
585
+ try {
586
+ const routeContent = readFile(tokenRoutePath);
587
+
588
+ // Check that auth token placeholder is gone
589
+ if (routeContent.includes('"YOUR_VELT_AUTH_TOKEN"')) {
590
+ validationIssues.push({
591
+ file: 'app/api/velt/token/route.ts',
592
+ type: 'validation-failed',
593
+ message: 'YOUR_VELT_AUTH_TOKEN placeholder still exists. Auth token replacement may have failed.',
594
+ });
595
+ }
596
+ } catch (error) {
597
+ validationIssues.push({
598
+ file: 'app/api/velt/token/route.ts',
599
+ type: 'error',
600
+ message: `Error validating token route: ${error.message}`,
601
+ });
602
+ }
603
+ }
604
+
605
+ // Validate core CLI-generated files exist (already done in ensureCliOutputExists, but double-check critical ones)
606
+ const criticalFiles = [
607
+ { path: appPath('userAuth', 'AppProviders.tsx'), name: 'app/userAuth/AppProviders.tsx' },
608
+ { path: appPath('document', 'page.tsx'), name: 'app/document/page.tsx' },
609
+ { path: componentsPath('velt', 'VeltCollaboration.tsx'), name: 'components/velt/VeltCollaboration.tsx' },
610
+ ];
611
+
612
+ for (const file of criticalFiles) {
613
+ if (!fs.existsSync(file.path)) {
614
+ // Only add if not already added
615
+ const alreadyReported = validationIssues.some(
616
+ issue => issue.file === file.name && issue.type === 'missing-file'
617
+ );
618
+
619
+ if (!alreadyReported) {
620
+ validationIssues.push({
621
+ file: file.name,
622
+ type: 'validation-failed',
623
+ message: `Critical CLI-generated file is missing: ${file.name}`,
624
+ });
625
+ }
626
+ }
627
+ }
628
+ }
629
+
630
+ // ============================================================================
631
+ // Main Integration Function
632
+ // ============================================================================
633
+
634
+ /**
635
+ * Analyzes project and integrates Velt components
636
+ *
637
+ * @param {AnalyzeOptions} options - Integration options
638
+ * @returns {Promise<IntegrationResult>} Integration result
639
+ */
640
+ export async function analyzeAndIntegrate(options) {
641
+ const { projectPath, config, patterns = {} } = options;
642
+
643
+ // Initialize result
644
+ const filesModified = [];
645
+ const componentsAdded = [];
646
+ const integrationPoints = [];
647
+ const validationIssues = [];
648
+
649
+ try {
650
+ // Step 1: Verify this is a Next.js project
651
+ const packageJsonPath = path.join(projectPath, 'package.json');
652
+ if (!fs.existsSync(packageJsonPath)) {
653
+ validationIssues.push({
654
+ type: 'not-nextjs',
655
+ message: 'package.json not found. This does not appear to be a valid Next.js project.',
656
+ });
657
+
658
+ return {
659
+ success: false,
660
+ data: { filesModified, componentsAdded, integrationPoints, validationIssues },
661
+ };
662
+ }
663
+
664
+ try {
665
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
666
+ const hasNext = packageJson.dependencies?.next || packageJson.devDependencies?.next;
667
+
668
+ if (!hasNext) {
669
+ validationIssues.push({
670
+ type: 'not-nextjs',
671
+ message: 'Next.js dependency not found in package.json. This does not appear to be a Next.js project.',
672
+ });
673
+
674
+ return {
675
+ success: false,
676
+ data: { filesModified, componentsAdded, integrationPoints, validationIssues },
677
+ };
678
+ }
679
+ } catch (error) {
680
+ validationIssues.push({
681
+ type: 'error',
682
+ message: `Error reading package.json: ${error.message}`,
683
+ });
684
+
685
+ return {
686
+ success: false,
687
+ data: { filesModified, componentsAdded, integrationPoints, validationIssues },
688
+ };
689
+ }
690
+
691
+ // Step 2: Detect project structure
692
+ const structure = detectProjectStructure(projectPath);
693
+
694
+ // Step 3: Ensure CLI output exists
695
+ const cliOutputExists = ensureCliOutputExists(structure, validationIssues);
696
+
697
+ if (!cliOutputExists) {
698
+ // CLI likely not run - return early
699
+ return {
700
+ success: false,
701
+ data: { filesModified, componentsAdded, integrationPoints, validationIssues },
702
+ };
703
+ }
704
+
705
+ // Step 4: Replace API key in page.tsx
706
+ console.error(' 🔑 Replacing API key placeholders...');
707
+ replaceApiKeyInPage(structure, config.apiKey, filesModified, integrationPoints, validationIssues);
708
+
709
+ // Step 5: Replace auth token in JWT route if provided
710
+ if (config.authToken) {
711
+ console.error(' 🔐 Replacing auth token placeholders...');
712
+ replaceAuthTokenInRoute(structure, config.apiKey, config.authToken, filesModified, integrationPoints, validationIssues);
713
+ }
714
+
715
+ // Step 6: Wire library-specific integrations
716
+
717
+ // ReactFlow cursor (automatic wiring)
718
+ if (patterns.hasReactFlow) {
719
+ console.error(' 🔌 Wiring ReactFlow cursor integration (from detected library)...');
720
+ wireReactFlowCursor(structure, filesModified, componentsAdded, integrationPoints, validationIssues);
721
+ }
722
+
723
+ // Other libraries (TODO comments)
724
+ const librariesToWire = [];
725
+ if (patterns.hasTiptap) librariesToWire.push('Tiptap');
726
+ if (patterns.hasCodeMirror) librariesToWire.push('CodeMirror');
727
+ if (patterns.hasAgGrid) librariesToWire.push('AG-Grid');
728
+ if (patterns.hasTanStack) librariesToWire.push('TanStack');
729
+
730
+ if (librariesToWire.length > 0) {
731
+ console.error(` 📝 Adding TODO comments for: ${librariesToWire.join(', ')}`);
732
+ }
733
+ addLibraryTodoComments(structure, patterns, filesModified, integrationPoints, validationIssues);
734
+
735
+ // Step 7: Apply header positioning if specified
736
+ if (config.headerPosition) {
737
+ console.error(` 📍 Applying header positioning: ${config.headerPosition}...`);
738
+ const sidebarFiles = findVeltSidebarFiles(projectPath);
739
+
740
+ for (const filePath of sidebarFiles) {
741
+ applyHeaderPositioning(filePath, config.headerPosition, filesModified, integrationPoints);
742
+ }
743
+ }
744
+
745
+ // Step 8: Handle comment-type specific integration
746
+ if (config.commentType && config.targetPlacement) {
747
+ console.error(` 💬 Applying ${config.commentType} comment integration...`);
748
+
749
+ if (config.commentType === 'popover' && config.targetPlacement.file) {
750
+ // Add popover-specific integration
751
+ integrationPoints.push({
752
+ file: config.targetPlacement.file,
753
+ type: 'popover-target',
754
+ description: `Recommended file for popover comments: ${config.targetPlacement.file}`,
755
+ implementationGuide: config.targetPlacement.implementationGuide,
756
+ });
757
+ } else if (config.commentType === 'freestyle' && config.targetPlacement.file) {
758
+ // Add freestyle-specific integration
759
+ integrationPoints.push({
760
+ file: config.targetPlacement.file,
761
+ type: 'freestyle-target',
762
+ description: `Recommended file for freestyle comments: ${config.targetPlacement.file}`,
763
+ implementationGuide: config.targetPlacement.implementationGuide,
764
+ });
765
+ }
766
+ }
767
+
768
+ // Step 9: Run validation checks
769
+ runValidationChecks(structure, config, validationIssues);
770
+
771
+ // Step 10: Return result
772
+ const success = validationIssues.length === 0;
773
+
774
+ return {
775
+ success,
776
+ data: {
777
+ filesModified,
778
+ componentsAdded,
779
+ integrationPoints,
780
+ validationIssues,
781
+ },
782
+ };
783
+ } catch (error) {
784
+ // Unexpected error
785
+ validationIssues.push({
786
+ type: 'error',
787
+ message: `Unexpected error during integration: ${error.message}`,
788
+ });
789
+
790
+ return {
791
+ success: false,
792
+ data: {
793
+ filesModified,
794
+ componentsAdded,
795
+ integrationPoints,
796
+ validationIssues,
797
+ },
798
+ };
799
+ }
800
+ }
801
+
802
+ // Export as both named and default for compatibility
803
+ export default { analyzeAndIntegrate };