@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.
- package/README.md +373 -0
- package/bin/mcp-server.js +22 -0
- package/package.json +42 -0
- package/src/index.js +754 -0
- package/src/tools/orchestrator.js +299 -0
- package/src/tools/unified-installer.js +886 -0
- package/src/utils/cli.js +380 -0
- package/src/utils/comment-detector.js +305 -0
- package/src/utils/config.js +149 -0
- package/src/utils/framework-detection.js +262 -0
- package/src/utils/header-positioning.js +146 -0
- package/src/utils/host-app-discovery.js +1000 -0
- package/src/utils/integration.js +803 -0
- package/src/utils/plan-formatter.js +1698 -0
- package/src/utils/screenshot.js +151 -0
- package/src/utils/use-client.js +366 -0
- package/src/utils/validation.js +556 -0
- package/src/utils/velt-docs-fetcher.js +288 -0
- package/src/utils/velt-docs-urls.js +140 -0
- package/src/utils/velt-mcp-client.js +202 -0
- package/src/utils/velt-mcp.js +718 -0
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration Collection Utilities
|
|
3
|
+
*
|
|
4
|
+
* Handles collecting user configuration from .env file
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import fs from 'fs';
|
|
8
|
+
import path from 'path';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Reads environment variables from .env.local or .env file
|
|
12
|
+
*
|
|
13
|
+
* @param {string} projectPath - Project root path
|
|
14
|
+
* @returns {Object} Environment variables object
|
|
15
|
+
*/
|
|
16
|
+
function readEnvFile(projectPath) {
|
|
17
|
+
// Ensure we're using absolute path - never search outside the specified directory
|
|
18
|
+
const absolutePath = path.resolve(projectPath);
|
|
19
|
+
|
|
20
|
+
// Process .env first, then .env.local so local overrides take precedence
|
|
21
|
+
// (later values overwrite earlier ones in the envVars object)
|
|
22
|
+
const envFiles = [
|
|
23
|
+
path.join(absolutePath, '.env'),
|
|
24
|
+
path.join(absolutePath, '.env.local'),
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
const envVars = {};
|
|
28
|
+
|
|
29
|
+
console.error(` 📂 Reading .env files from: ${absolutePath}`);
|
|
30
|
+
|
|
31
|
+
for (const envFile of envFiles) {
|
|
32
|
+
if (fs.existsSync(envFile)) {
|
|
33
|
+
try {
|
|
34
|
+
console.error(` ✓ Found: ${path.basename(envFile)}`);
|
|
35
|
+
const content = fs.readFileSync(envFile, 'utf-8');
|
|
36
|
+
const lines = content.split('\n');
|
|
37
|
+
|
|
38
|
+
for (const line of lines) {
|
|
39
|
+
// Skip comments and empty lines
|
|
40
|
+
const trimmed = line.trim();
|
|
41
|
+
if (!trimmed || trimmed.startsWith('#')) {
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Parse KEY=VALUE format
|
|
46
|
+
const match = trimmed.match(/^([^=]+)=(.*)$/);
|
|
47
|
+
if (match) {
|
|
48
|
+
const key = match[1].trim();
|
|
49
|
+
let value = match[2].trim();
|
|
50
|
+
|
|
51
|
+
// Remove quotes if present
|
|
52
|
+
if ((value.startsWith('"') && value.endsWith('"')) ||
|
|
53
|
+
(value.startsWith("'") && value.endsWith("'"))) {
|
|
54
|
+
value = value.slice(1, -1);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
envVars[key] = value;
|
|
58
|
+
// Log found keys (but not values for security)
|
|
59
|
+
if (key.includes('VELT') || key.includes('API')) {
|
|
60
|
+
console.error(` ✓ Found ${key}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
} catch (error) {
|
|
65
|
+
// Continue to next file if one fails
|
|
66
|
+
console.error(` ⚠️ Could not read ${path.basename(envFile)}: ${error.message}`);
|
|
67
|
+
}
|
|
68
|
+
} else {
|
|
69
|
+
console.error(` ⚠️ Not found: ${path.basename(envFile)}`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return envVars;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Collects installation configuration from .env file or provided parameters
|
|
78
|
+
*
|
|
79
|
+
* @param {Object} params
|
|
80
|
+
* @param {string} params.projectPath - Project path
|
|
81
|
+
* @param {string} [params.apiKey] - API key provided directly (optional, will read from .env if not provided)
|
|
82
|
+
* @param {string} [params.authToken] - Auth token provided directly (optional, will read from .env if not provided)
|
|
83
|
+
* @returns {Promise<Object>} Configuration result
|
|
84
|
+
*/
|
|
85
|
+
export async function collectConfiguration({ projectPath, apiKey: providedApiKey, authToken: providedAuthToken }) {
|
|
86
|
+
try {
|
|
87
|
+
// Resolve absolute path to ensure we're reading from the correct directory
|
|
88
|
+
const absoluteProjectPath = path.resolve(projectPath);
|
|
89
|
+
|
|
90
|
+
// If API key provided directly, use it; otherwise read from .env file
|
|
91
|
+
let apiKey = providedApiKey;
|
|
92
|
+
let authToken = providedAuthToken;
|
|
93
|
+
|
|
94
|
+
if (!apiKey) {
|
|
95
|
+
// Read from .env file ONLY in the specified project directory
|
|
96
|
+
console.error(` 📖 API key not provided, reading from .env files...`);
|
|
97
|
+
const envVars = readEnvFile(absoluteProjectPath);
|
|
98
|
+
apiKey = envVars.VELT_API_KEY || envVars.NEXT_PUBLIC_VELT_API_KEY;
|
|
99
|
+
if (!authToken) {
|
|
100
|
+
authToken = envVars.VELT_AUTH_TOKEN;
|
|
101
|
+
}
|
|
102
|
+
} else {
|
|
103
|
+
console.error(` ✓ Using API key provided as parameter`);
|
|
104
|
+
if (!authToken) {
|
|
105
|
+
// Still try to read auth token from .env if not provided
|
|
106
|
+
const envVars = readEnvFile(absoluteProjectPath);
|
|
107
|
+
authToken = envVars.VELT_AUTH_TOKEN;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// API key is required - must be in .env file
|
|
112
|
+
if (!apiKey) {
|
|
113
|
+
const envLocalPath = path.join(absoluteProjectPath, '.env.local');
|
|
114
|
+
const envPath = path.join(absoluteProjectPath, '.env');
|
|
115
|
+
const envLocalExists = fs.existsSync(envLocalPath);
|
|
116
|
+
const envExists = fs.existsSync(envPath);
|
|
117
|
+
|
|
118
|
+
let errorMessage = `API key not found in ${absoluteProjectPath}\n\n`;
|
|
119
|
+
errorMessage += `Checked files:\n`;
|
|
120
|
+
errorMessage += ` - ${envLocalPath} ${envLocalExists ? '(exists but no VELT_API_KEY found)' : '(not found)'}\n`;
|
|
121
|
+
errorMessage += ` - ${envPath} ${envExists ? '(exists but no VELT_API_KEY found)' : '(not found)'}\n\n`;
|
|
122
|
+
errorMessage += `Please add VELT_API_KEY or NEXT_PUBLIC_VELT_API_KEY to ${envLocalExists ? '.env.local' : '.env'} file in ${absoluteProjectPath}\n\n`;
|
|
123
|
+
errorMessage += `Example:\nVELT_API_KEY=your_api_key_here\n\n`;
|
|
124
|
+
errorMessage += `Get your API key from: https://console.velt.dev`;
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
success: false,
|
|
128
|
+
error: errorMessage,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return {
|
|
133
|
+
success: true,
|
|
134
|
+
data: {
|
|
135
|
+
installDir: absoluteProjectPath,
|
|
136
|
+
apiKey: apiKey,
|
|
137
|
+
authToken: authToken || null,
|
|
138
|
+
source: 'env-file',
|
|
139
|
+
},
|
|
140
|
+
};
|
|
141
|
+
} catch (error) {
|
|
142
|
+
return {
|
|
143
|
+
success: false,
|
|
144
|
+
error: error.message,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Framework Detection Utility
|
|
3
|
+
*
|
|
4
|
+
* Detects whether the target project is Next.js, React (CRA/Vite), or unknown.
|
|
5
|
+
* This information is used for:
|
|
6
|
+
* - Determining if "use client" directives are needed (Next.js only)
|
|
7
|
+
* - Adjusting installation patterns based on framework
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import fs from 'fs';
|
|
11
|
+
import path from 'path';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Project type enum
|
|
15
|
+
*/
|
|
16
|
+
export const ProjectType = {
|
|
17
|
+
NEXTJS: 'nextjs',
|
|
18
|
+
REACT: 'react',
|
|
19
|
+
UNKNOWN: 'unknown',
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Detects the project type by analyzing package.json and file structure
|
|
24
|
+
*
|
|
25
|
+
* @param {string} projectPath - Path to the project directory
|
|
26
|
+
* @returns {Object} Detection result { projectType, signals, confidence }
|
|
27
|
+
*/
|
|
28
|
+
export function detectProjectType(projectPath) {
|
|
29
|
+
const signals = [];
|
|
30
|
+
let nextjsScore = 0;
|
|
31
|
+
let reactScore = 0;
|
|
32
|
+
|
|
33
|
+
// === Check package.json dependencies ===
|
|
34
|
+
const packageJsonPath = path.join(projectPath, 'package.json');
|
|
35
|
+
|
|
36
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
37
|
+
try {
|
|
38
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
39
|
+
const allDeps = {
|
|
40
|
+
...packageJson.dependencies,
|
|
41
|
+
...packageJson.devDependencies,
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
// Next.js indicators
|
|
45
|
+
if (allDeps.next) {
|
|
46
|
+
signals.push({ type: 'dependency', name: 'next', framework: 'nextjs' });
|
|
47
|
+
nextjsScore += 3; // Strong signal
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// React (non-Next) indicators
|
|
51
|
+
if (allDeps['react-scripts']) {
|
|
52
|
+
signals.push({ type: 'dependency', name: 'react-scripts', framework: 'react' });
|
|
53
|
+
reactScore += 3; // CRA
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (allDeps.vite && allDeps.react) {
|
|
57
|
+
signals.push({ type: 'dependency', name: 'vite+react', framework: 'react' });
|
|
58
|
+
reactScore += 3; // Vite React
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Generic React (both Next.js and React have this)
|
|
62
|
+
if (allDeps.react) {
|
|
63
|
+
signals.push({ type: 'dependency', name: 'react', framework: 'both' });
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
} catch (err) {
|
|
67
|
+
signals.push({ type: 'error', message: `Failed to parse package.json: ${err.message}` });
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// === Check file structure ===
|
|
72
|
+
|
|
73
|
+
// Next.js specific files/folders
|
|
74
|
+
const nextjsIndicators = [
|
|
75
|
+
{ path: 'next.config.js', weight: 3 },
|
|
76
|
+
{ path: 'next.config.ts', weight: 3 },
|
|
77
|
+
{ path: 'next.config.mjs', weight: 3 },
|
|
78
|
+
{ path: 'app', weight: 2, isDir: true }, // App Router
|
|
79
|
+
{ path: 'pages', weight: 2, isDir: true }, // Pages Router
|
|
80
|
+
{ path: 'app/layout.tsx', weight: 2 },
|
|
81
|
+
{ path: 'app/layout.js', weight: 2 },
|
|
82
|
+
{ path: 'app/page.tsx', weight: 1 },
|
|
83
|
+
{ path: 'app/page.js', weight: 1 },
|
|
84
|
+
{ path: 'pages/_app.tsx', weight: 2 },
|
|
85
|
+
{ path: 'pages/_app.js', weight: 2 },
|
|
86
|
+
{ path: 'src/app/layout.tsx', weight: 2 },
|
|
87
|
+
{ path: 'src/app/layout.js', weight: 2 },
|
|
88
|
+
];
|
|
89
|
+
|
|
90
|
+
for (const indicator of nextjsIndicators) {
|
|
91
|
+
const fullPath = path.join(projectPath, indicator.path);
|
|
92
|
+
const exists = indicator.isDir
|
|
93
|
+
? fs.existsSync(fullPath) && fs.statSync(fullPath).isDirectory()
|
|
94
|
+
: fs.existsSync(fullPath);
|
|
95
|
+
|
|
96
|
+
if (exists) {
|
|
97
|
+
signals.push({ type: 'file', path: indicator.path, framework: 'nextjs' });
|
|
98
|
+
nextjsScore += indicator.weight;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// React (non-Next) specific files
|
|
103
|
+
const reactIndicators = [
|
|
104
|
+
{ path: 'src/main.tsx', weight: 2 }, // Vite React
|
|
105
|
+
{ path: 'src/main.jsx', weight: 2 },
|
|
106
|
+
{ path: 'src/index.tsx', weight: 2 }, // CRA
|
|
107
|
+
{ path: 'src/index.jsx', weight: 2 },
|
|
108
|
+
{ path: 'vite.config.ts', weight: 2 },
|
|
109
|
+
{ path: 'vite.config.js', weight: 2 },
|
|
110
|
+
{ path: 'public/index.html', weight: 1 }, // CRA/Vite
|
|
111
|
+
{ path: 'index.html', weight: 1 }, // Vite root
|
|
112
|
+
];
|
|
113
|
+
|
|
114
|
+
for (const indicator of reactIndicators) {
|
|
115
|
+
const fullPath = path.join(projectPath, indicator.path);
|
|
116
|
+
if (fs.existsSync(fullPath)) {
|
|
117
|
+
signals.push({ type: 'file', path: indicator.path, framework: 'react' });
|
|
118
|
+
reactScore += indicator.weight;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// === Determine project type ===
|
|
123
|
+
let projectType;
|
|
124
|
+
let confidence;
|
|
125
|
+
|
|
126
|
+
if (nextjsScore > reactScore && nextjsScore >= 3) {
|
|
127
|
+
projectType = ProjectType.NEXTJS;
|
|
128
|
+
confidence = nextjsScore >= 6 ? 'high' : 'medium';
|
|
129
|
+
} else if (reactScore > nextjsScore && reactScore >= 3) {
|
|
130
|
+
projectType = ProjectType.REACT;
|
|
131
|
+
confidence = reactScore >= 6 ? 'high' : 'medium';
|
|
132
|
+
} else if (nextjsScore > 0 || reactScore > 0) {
|
|
133
|
+
// Some signals but not conclusive
|
|
134
|
+
projectType = nextjsScore >= reactScore ? ProjectType.NEXTJS : ProjectType.REACT;
|
|
135
|
+
confidence = 'low';
|
|
136
|
+
} else {
|
|
137
|
+
projectType = ProjectType.UNKNOWN;
|
|
138
|
+
confidence = 'none';
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return {
|
|
142
|
+
projectType,
|
|
143
|
+
signals,
|
|
144
|
+
confidence,
|
|
145
|
+
scores: {
|
|
146
|
+
nextjs: nextjsScore,
|
|
147
|
+
react: reactScore,
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Checks if project is Next.js
|
|
154
|
+
*
|
|
155
|
+
* @param {string} projectPath - Path to project
|
|
156
|
+
* @returns {boolean} True if Next.js project
|
|
157
|
+
*/
|
|
158
|
+
export function isNextJsProject(projectPath) {
|
|
159
|
+
const detection = detectProjectType(projectPath);
|
|
160
|
+
return detection.projectType === ProjectType.NEXTJS;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Detects which router type Next.js project uses
|
|
165
|
+
*
|
|
166
|
+
* @param {string} projectPath - Path to project
|
|
167
|
+
* @returns {Object} Router detection { routerType: 'app'|'pages'|'both'|'unknown', paths: {} }
|
|
168
|
+
*/
|
|
169
|
+
export function detectNextJsRouter(projectPath) {
|
|
170
|
+
const result = {
|
|
171
|
+
routerType: 'unknown',
|
|
172
|
+
paths: {
|
|
173
|
+
appDir: null,
|
|
174
|
+
pagesDir: null,
|
|
175
|
+
layoutFile: null,
|
|
176
|
+
pageFile: null,
|
|
177
|
+
},
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
// Check for app directory (App Router)
|
|
181
|
+
const appDirPaths = ['app', 'src/app'];
|
|
182
|
+
for (const dir of appDirPaths) {
|
|
183
|
+
const fullPath = path.join(projectPath, dir);
|
|
184
|
+
if (fs.existsSync(fullPath) && fs.statSync(fullPath).isDirectory()) {
|
|
185
|
+
result.paths.appDir = dir;
|
|
186
|
+
|
|
187
|
+
// Find layout file
|
|
188
|
+
for (const ext of ['tsx', 'jsx', 'ts', 'js']) {
|
|
189
|
+
const layoutPath = path.join(fullPath, `layout.${ext}`);
|
|
190
|
+
if (fs.existsSync(layoutPath)) {
|
|
191
|
+
result.paths.layoutFile = path.join(dir, `layout.${ext}`);
|
|
192
|
+
break;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Find page file
|
|
197
|
+
for (const ext of ['tsx', 'jsx', 'ts', 'js']) {
|
|
198
|
+
const pagePath = path.join(fullPath, `page.${ext}`);
|
|
199
|
+
if (fs.existsSync(pagePath)) {
|
|
200
|
+
result.paths.pageFile = path.join(dir, `page.${ext}`);
|
|
201
|
+
break;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
break;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Check for pages directory (Pages Router)
|
|
209
|
+
const pagesDirPaths = ['pages', 'src/pages'];
|
|
210
|
+
for (const dir of pagesDirPaths) {
|
|
211
|
+
const fullPath = path.join(projectPath, dir);
|
|
212
|
+
if (fs.existsSync(fullPath) && fs.statSync(fullPath).isDirectory()) {
|
|
213
|
+
result.paths.pagesDir = dir;
|
|
214
|
+
break;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Determine router type
|
|
219
|
+
if (result.paths.appDir && result.paths.pagesDir) {
|
|
220
|
+
result.routerType = 'both';
|
|
221
|
+
} else if (result.paths.appDir) {
|
|
222
|
+
result.routerType = 'app';
|
|
223
|
+
} else if (result.paths.pagesDir) {
|
|
224
|
+
result.routerType = 'pages';
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return result;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Gets framework-specific information for installation
|
|
232
|
+
*
|
|
233
|
+
* @param {string} projectPath - Path to project
|
|
234
|
+
* @returns {Object} Framework info for installation
|
|
235
|
+
*/
|
|
236
|
+
export function getFrameworkInfo(projectPath) {
|
|
237
|
+
const detection = detectProjectType(projectPath);
|
|
238
|
+
const info = {
|
|
239
|
+
projectType: detection.projectType,
|
|
240
|
+
confidence: detection.confidence,
|
|
241
|
+
needsUseClient: false,
|
|
242
|
+
routerType: null,
|
|
243
|
+
paths: {},
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
if (detection.projectType === ProjectType.NEXTJS) {
|
|
247
|
+
info.needsUseClient = true;
|
|
248
|
+
const routerInfo = detectNextJsRouter(projectPath);
|
|
249
|
+
info.routerType = routerInfo.routerType;
|
|
250
|
+
info.paths = routerInfo.paths;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return info;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
export default {
|
|
257
|
+
ProjectType,
|
|
258
|
+
detectProjectType,
|
|
259
|
+
isNextJsProject,
|
|
260
|
+
detectNextJsRouter,
|
|
261
|
+
getFrameworkInfo,
|
|
262
|
+
};
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Header Positioning Utility
|
|
3
|
+
*
|
|
4
|
+
* Handles VeltCommentsSidebar positioning based on user preference.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import fs from 'fs';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Position mapping
|
|
11
|
+
*/
|
|
12
|
+
const POSITION_STYLES = {
|
|
13
|
+
'top-left': {
|
|
14
|
+
position: 'fixed',
|
|
15
|
+
top: '20px',
|
|
16
|
+
left: '20px',
|
|
17
|
+
right: 'auto',
|
|
18
|
+
bottom: 'auto',
|
|
19
|
+
},
|
|
20
|
+
'top-right': {
|
|
21
|
+
position: 'fixed',
|
|
22
|
+
top: '20px',
|
|
23
|
+
right: '20px',
|
|
24
|
+
left: 'auto',
|
|
25
|
+
bottom: 'auto',
|
|
26
|
+
},
|
|
27
|
+
'bottom-left': {
|
|
28
|
+
position: 'fixed',
|
|
29
|
+
bottom: '20px',
|
|
30
|
+
left: '20px',
|
|
31
|
+
right: 'auto',
|
|
32
|
+
top: 'auto',
|
|
33
|
+
},
|
|
34
|
+
'bottom-right': {
|
|
35
|
+
position: 'fixed',
|
|
36
|
+
bottom: '20px',
|
|
37
|
+
right: '20px',
|
|
38
|
+
left: 'auto',
|
|
39
|
+
top: 'auto',
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Applies header positioning to VeltCommentsSidebar
|
|
45
|
+
*
|
|
46
|
+
* @param {string} filePath - Path to the file containing VeltCommentsSidebar
|
|
47
|
+
* @param {string} position - Position (top-left, top-right, bottom-left, bottom-right)
|
|
48
|
+
* @param {string[]} filesModified - Array to track modified files
|
|
49
|
+
* @param {Object[]} integrationPoints - Array to track integration points
|
|
50
|
+
* @returns {boolean} Success status
|
|
51
|
+
*/
|
|
52
|
+
export function applyHeaderPositioning(filePath, position, filesModified, integrationPoints) {
|
|
53
|
+
if (!fs.existsSync(filePath)) {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
let content = fs.readFileSync(filePath, 'utf-8');
|
|
59
|
+
|
|
60
|
+
// Check if VeltCommentsSidebar exists
|
|
61
|
+
if (!content.includes('VeltCommentsSidebar')) {
|
|
62
|
+
console.error(` ⚠️ VeltCommentsSidebar not found in ${filePath}`);
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const positionStyle = POSITION_STYLES[position] || POSITION_STYLES['top-right'];
|
|
67
|
+
|
|
68
|
+
// Build inline style string
|
|
69
|
+
const styleString = `style={{ position: '${positionStyle.position}', top: '${positionStyle.top}', right: '${positionStyle.right}', bottom: '${positionStyle.bottom}', left: '${positionStyle.left}', zIndex: 9999 }}`;
|
|
70
|
+
|
|
71
|
+
// Match all VeltCommentsSidebar tags: self-closing (with or without props) and opening tags.
|
|
72
|
+
// Captures existing attributes in group 1 and self-closing slash in group 2.
|
|
73
|
+
const tagPattern = /<VeltCommentsSidebar(\s[^>]*?)?\s*(\/?)>/g;
|
|
74
|
+
content = content.replace(tagPattern, (match, existingAttrs, selfClose) => {
|
|
75
|
+
// Strip any existing style prop from the captured attributes to avoid
|
|
76
|
+
// the duplicate-prop override problem (last prop wins in JSX).
|
|
77
|
+
let attrs = (existingAttrs || '').replace(/\s*style=\{\{[^}]*\}\}/g, '');
|
|
78
|
+
const closing = selfClose ? ' /' : '';
|
|
79
|
+
return `<VeltCommentsSidebar ${styleString}${attrs}${closing}>`;
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// Write back
|
|
83
|
+
fs.writeFileSync(filePath, content, 'utf-8');
|
|
84
|
+
|
|
85
|
+
const relativePath = filePath.split('/').slice(-3).join('/');
|
|
86
|
+
if (!filesModified.includes(relativePath)) {
|
|
87
|
+
filesModified.push(relativePath);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
integrationPoints.push({
|
|
91
|
+
file: relativePath,
|
|
92
|
+
type: 'header-positioning',
|
|
93
|
+
description: `Applied ${position} positioning to VeltCommentsSidebar`,
|
|
94
|
+
position,
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
console.error(` 📍 Applied ${position} positioning to VeltCommentsSidebar`);
|
|
98
|
+
return true;
|
|
99
|
+
} catch (error) {
|
|
100
|
+
console.error(` ❌ Error applying header positioning: ${error.message}`);
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Finds files containing VeltCommentsSidebar
|
|
107
|
+
*
|
|
108
|
+
* @param {string} projectPath - Project root path
|
|
109
|
+
* @returns {string[]} Array of file paths
|
|
110
|
+
*/
|
|
111
|
+
export function findVeltSidebarFiles(projectPath) {
|
|
112
|
+
const results = [];
|
|
113
|
+
|
|
114
|
+
function searchDirectory(dir) {
|
|
115
|
+
if (!fs.existsSync(dir)) return;
|
|
116
|
+
|
|
117
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
118
|
+
|
|
119
|
+
for (const entry of entries) {
|
|
120
|
+
const fullPath = `${dir}/${entry.name}`;
|
|
121
|
+
|
|
122
|
+
if (entry.isDirectory()) {
|
|
123
|
+
if (entry.name === 'node_modules' || entry.name === '.next') continue;
|
|
124
|
+
searchDirectory(fullPath);
|
|
125
|
+
} else if (entry.isFile() && /\.(tsx|jsx)$/.test(entry.name)) {
|
|
126
|
+
try {
|
|
127
|
+
const content = fs.readFileSync(fullPath, 'utf-8');
|
|
128
|
+
if (content.includes('VeltCommentsSidebar')) {
|
|
129
|
+
results.push(fullPath);
|
|
130
|
+
}
|
|
131
|
+
} catch (err) {
|
|
132
|
+
// Skip files we can't read
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
searchDirectory(projectPath);
|
|
139
|
+
return results;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export default {
|
|
143
|
+
applyHeaderPositioning,
|
|
144
|
+
findVeltSidebarFiles,
|
|
145
|
+
POSITION_STYLES,
|
|
146
|
+
};
|