@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,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Screenshot Utility
|
|
3
|
+
*
|
|
4
|
+
* Captures screenshots of running web applications using Playwright.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { chromium } from 'playwright';
|
|
8
|
+
import fs from 'fs';
|
|
9
|
+
import path from 'path';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Takes a screenshot of a running Next.js application
|
|
13
|
+
*
|
|
14
|
+
* @param {Object} options - Screenshot options
|
|
15
|
+
* @param {string} options.url - URL to screenshot (default: http://localhost:3000)
|
|
16
|
+
* @param {number} [options.width=1920] - Viewport width
|
|
17
|
+
* @param {number} [options.height=1080] - Viewport height
|
|
18
|
+
* @param {boolean} [options.fullPage=false] - Capture full page or just viewport
|
|
19
|
+
* @returns {Promise<Object>} Screenshot result with base64 data
|
|
20
|
+
*/
|
|
21
|
+
export async function takeScreenshot(options = {}) {
|
|
22
|
+
const {
|
|
23
|
+
url = 'http://localhost:3000',
|
|
24
|
+
width = 1920,
|
|
25
|
+
height = 1080,
|
|
26
|
+
fullPage = false,
|
|
27
|
+
} = options;
|
|
28
|
+
|
|
29
|
+
let browser = null;
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
console.error(`📸 Taking screenshot of ${url}...`);
|
|
33
|
+
|
|
34
|
+
// Launch browser
|
|
35
|
+
browser = await chromium.launch({
|
|
36
|
+
headless: true,
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const context = await browser.newContext({
|
|
40
|
+
viewport: { width, height },
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const page = await context.newPage();
|
|
44
|
+
|
|
45
|
+
// Navigate to the URL
|
|
46
|
+
console.error(` ⏳ Loading page...`);
|
|
47
|
+
await page.goto(url, {
|
|
48
|
+
waitUntil: 'networkidle',
|
|
49
|
+
timeout: 30000,
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// Wait a bit for any animations/hydration
|
|
53
|
+
await page.waitForTimeout(2000);
|
|
54
|
+
|
|
55
|
+
console.error(` 📷 Capturing screenshot...`);
|
|
56
|
+
|
|
57
|
+
// Take screenshot
|
|
58
|
+
const screenshotBuffer = await page.screenshot({
|
|
59
|
+
type: 'png',
|
|
60
|
+
fullPage: fullPage,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
await browser.close();
|
|
64
|
+
browser = null;
|
|
65
|
+
|
|
66
|
+
// Convert to base64
|
|
67
|
+
const base64Data = screenshotBuffer.toString('base64');
|
|
68
|
+
|
|
69
|
+
console.error(` ✅ Screenshot captured successfully (${Math.round(base64Data.length / 1024)}KB)`);
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
success: true,
|
|
73
|
+
data: {
|
|
74
|
+
base64: base64Data,
|
|
75
|
+
mimeType: 'image/png',
|
|
76
|
+
width,
|
|
77
|
+
height,
|
|
78
|
+
url,
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
} catch (error) {
|
|
82
|
+
if (browser) {
|
|
83
|
+
await browser.close();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
console.error(` ❌ Failed to capture screenshot: ${error.message}`);
|
|
87
|
+
|
|
88
|
+
// Check if it's a connection error
|
|
89
|
+
if (error.message.includes('net::ERR_CONNECTION_REFUSED') ||
|
|
90
|
+
error.message.includes('Navigation timeout')) {
|
|
91
|
+
return {
|
|
92
|
+
success: false,
|
|
93
|
+
error: `Could not connect to ${url}. Please make sure your Next.js dev server is running (npm run dev).`,
|
|
94
|
+
hint: 'Run "npm run dev" in your project directory first, then try again.',
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
success: false,
|
|
100
|
+
error: error.message,
|
|
101
|
+
stack: error.stack,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Detects if a Next.js dev server is running
|
|
108
|
+
*
|
|
109
|
+
* @param {string} url - URL to check (default: http://localhost:3000)
|
|
110
|
+
* @returns {Promise<Object>} Result indicating if server is running
|
|
111
|
+
*/
|
|
112
|
+
export async function checkDevServerRunning(url = 'http://localhost:3000') {
|
|
113
|
+
let browser = null;
|
|
114
|
+
|
|
115
|
+
try {
|
|
116
|
+
browser = await chromium.launch({
|
|
117
|
+
headless: true,
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
const context = await browser.newContext();
|
|
121
|
+
const page = await context.newPage();
|
|
122
|
+
|
|
123
|
+
await page.goto(url, {
|
|
124
|
+
timeout: 5000,
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
await browser.close();
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
success: true,
|
|
131
|
+
running: true,
|
|
132
|
+
url,
|
|
133
|
+
};
|
|
134
|
+
} catch (error) {
|
|
135
|
+
if (browser) {
|
|
136
|
+
await browser.close();
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return {
|
|
140
|
+
success: true,
|
|
141
|
+
running: false,
|
|
142
|
+
url,
|
|
143
|
+
message: `Dev server not detected at ${url}`,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export default {
|
|
149
|
+
takeScreenshot,
|
|
150
|
+
checkDevServerRunning,
|
|
151
|
+
};
|
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* "use client" Directive Utility
|
|
3
|
+
*
|
|
4
|
+
* Ensures Next.js App Router files that need client-side behavior
|
|
5
|
+
* have the "use client" directive at the top of the file.
|
|
6
|
+
*
|
|
7
|
+
* This is required for files that use:
|
|
8
|
+
* - React hooks (useState, useEffect, useContext, etc.)
|
|
9
|
+
* - Browser APIs (window, document, localStorage, etc.)
|
|
10
|
+
* - Event handlers (onClick, onChange, onSubmit, etc.)
|
|
11
|
+
* - Velt UI components (VeltComments, VeltProvider, etc.)
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import fs from 'fs';
|
|
15
|
+
import path from 'path';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Patterns that indicate a file needs "use client"
|
|
19
|
+
*/
|
|
20
|
+
const CLIENT_INDICATORS = {
|
|
21
|
+
// React hooks
|
|
22
|
+
hooks: [
|
|
23
|
+
/\buse[A-Z]\w*\s*\(/, // useXxx() pattern
|
|
24
|
+
/\buseState\b/,
|
|
25
|
+
/\buseEffect\b/,
|
|
26
|
+
/\buseContext\b/,
|
|
27
|
+
/\buseRef\b/,
|
|
28
|
+
/\buseCallback\b/,
|
|
29
|
+
/\buseMemo\b/,
|
|
30
|
+
/\buseReducer\b/,
|
|
31
|
+
/\buseLayoutEffect\b/,
|
|
32
|
+
],
|
|
33
|
+
|
|
34
|
+
// Browser APIs
|
|
35
|
+
browserApis: [
|
|
36
|
+
/\bwindow\b/,
|
|
37
|
+
/\bdocument\b/,
|
|
38
|
+
/\blocalStorage\b/,
|
|
39
|
+
/\bsessionStorage\b/,
|
|
40
|
+
/\bnavigator\b/,
|
|
41
|
+
/\blocation\b/,
|
|
42
|
+
],
|
|
43
|
+
|
|
44
|
+
// Event handlers in JSX
|
|
45
|
+
eventHandlers: [
|
|
46
|
+
/\bonClick\s*=/,
|
|
47
|
+
/\bonChange\s*=/,
|
|
48
|
+
/\bonSubmit\s*=/,
|
|
49
|
+
/\bonKeyDown\s*=/,
|
|
50
|
+
/\bonKeyUp\s*=/,
|
|
51
|
+
/\bonKeyPress\s*=/,
|
|
52
|
+
/\bonFocus\s*=/,
|
|
53
|
+
/\bonBlur\s*=/,
|
|
54
|
+
/\bonInput\s*=/,
|
|
55
|
+
/\bonMouseEnter\s*=/,
|
|
56
|
+
/\bonMouseLeave\s*=/,
|
|
57
|
+
/\bonScroll\s*=/,
|
|
58
|
+
/\bonLoad\s*=/,
|
|
59
|
+
/\bonError\s*=/,
|
|
60
|
+
],
|
|
61
|
+
|
|
62
|
+
// Velt client components (these require client-side rendering)
|
|
63
|
+
veltComponents: [
|
|
64
|
+
/\bVeltComments\b/,
|
|
65
|
+
/\bVeltCommentsSidebar\b/,
|
|
66
|
+
/\bVeltProvider\b/,
|
|
67
|
+
/\bVeltPresence\b/,
|
|
68
|
+
/\bVeltCursor\b/,
|
|
69
|
+
/\bVeltCursors\b/,
|
|
70
|
+
/\bVeltNotificationsTool\b/,
|
|
71
|
+
/\bVeltSidebarButton\b/,
|
|
72
|
+
/\bVeltRecorder\b/,
|
|
73
|
+
/\bVeltHuddleTool\b/,
|
|
74
|
+
/\bVeltCommentTool\b/,
|
|
75
|
+
/\bVeltCommentBubble\b/,
|
|
76
|
+
/\bVeltWireframe\b/,
|
|
77
|
+
/\buseVeltClient\b/,
|
|
78
|
+
/\buseSetDocuments\b/,
|
|
79
|
+
],
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Checks if file content already has "use client" directive
|
|
84
|
+
*
|
|
85
|
+
* @param {string} content - File content
|
|
86
|
+
* @returns {boolean} True if has "use client"
|
|
87
|
+
*/
|
|
88
|
+
export function hasUseClientDirective(content) {
|
|
89
|
+
// "use client" must be at the very start (possibly after whitespace/comments)
|
|
90
|
+
const trimmed = content.trimStart();
|
|
91
|
+
return (
|
|
92
|
+
trimmed.startsWith('"use client"') ||
|
|
93
|
+
trimmed.startsWith("'use client'") ||
|
|
94
|
+
trimmed.startsWith('`use client`')
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Checks if file content needs "use client" directive
|
|
100
|
+
*
|
|
101
|
+
* @param {string} content - File content
|
|
102
|
+
* @returns {Object} { needsDirective: boolean, reasons: string[] }
|
|
103
|
+
*/
|
|
104
|
+
export function needsUseClientDirective(content) {
|
|
105
|
+
const reasons = [];
|
|
106
|
+
|
|
107
|
+
// Check hooks
|
|
108
|
+
for (const pattern of CLIENT_INDICATORS.hooks) {
|
|
109
|
+
if (pattern.test(content)) {
|
|
110
|
+
reasons.push(`React hook detected: ${pattern.source}`);
|
|
111
|
+
break; // One hook is enough
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Check browser APIs
|
|
116
|
+
for (const pattern of CLIENT_INDICATORS.browserApis) {
|
|
117
|
+
if (pattern.test(content)) {
|
|
118
|
+
reasons.push(`Browser API detected: ${pattern.source}`);
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Check event handlers
|
|
124
|
+
for (const pattern of CLIENT_INDICATORS.eventHandlers) {
|
|
125
|
+
if (pattern.test(content)) {
|
|
126
|
+
reasons.push(`Event handler detected: ${pattern.source}`);
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Check Velt components
|
|
132
|
+
for (const pattern of CLIENT_INDICATORS.veltComponents) {
|
|
133
|
+
if (pattern.test(content)) {
|
|
134
|
+
reasons.push(`Velt client component detected: ${pattern.source}`);
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return {
|
|
140
|
+
needsDirective: reasons.length > 0,
|
|
141
|
+
reasons,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Ensures file has "use client" directive if needed
|
|
147
|
+
*
|
|
148
|
+
* @param {string} filePath - Path to file (for logging)
|
|
149
|
+
* @param {string} content - File content
|
|
150
|
+
* @returns {Object} { modified: boolean, content: string, reason?: string }
|
|
151
|
+
*/
|
|
152
|
+
export function ensureUseClient(filePath, content) {
|
|
153
|
+
// Already has directive
|
|
154
|
+
if (hasUseClientDirective(content)) {
|
|
155
|
+
return {
|
|
156
|
+
modified: false,
|
|
157
|
+
content,
|
|
158
|
+
reason: 'Already has "use client" directive',
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Check if needs directive
|
|
163
|
+
const check = needsUseClientDirective(content);
|
|
164
|
+
|
|
165
|
+
if (!check.needsDirective) {
|
|
166
|
+
return {
|
|
167
|
+
modified: false,
|
|
168
|
+
content,
|
|
169
|
+
reason: 'Does not need "use client" (no client-only code detected)',
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Add directive at the very top
|
|
174
|
+
const newContent = `"use client";\n\n${content}`;
|
|
175
|
+
|
|
176
|
+
return {
|
|
177
|
+
modified: true,
|
|
178
|
+
content: newContent,
|
|
179
|
+
reason: `Added "use client" - ${check.reasons[0]}`,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Scans a directory for files that need "use client" and optionally fixes them
|
|
185
|
+
*
|
|
186
|
+
* @param {Object} params
|
|
187
|
+
* @param {string} params.projectPath - Project root path
|
|
188
|
+
* @param {string[]} [params.scanPaths] - Relative paths to scan (default: Velt component directories)
|
|
189
|
+
* @param {boolean} [params.fix=false] - Whether to actually fix files
|
|
190
|
+
* @returns {Object} Scan results
|
|
191
|
+
*/
|
|
192
|
+
export function scanAndFixUseClient({
|
|
193
|
+
projectPath,
|
|
194
|
+
scanPaths = null,
|
|
195
|
+
fix = false,
|
|
196
|
+
}) {
|
|
197
|
+
const results = {
|
|
198
|
+
scanned: [],
|
|
199
|
+
needsFix: [],
|
|
200
|
+
fixed: [],
|
|
201
|
+
alreadyCorrect: [],
|
|
202
|
+
errors: [],
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
// Default scan paths for Velt components
|
|
206
|
+
const defaultPaths = [
|
|
207
|
+
'components/velt',
|
|
208
|
+
'src/components/velt',
|
|
209
|
+
'app',
|
|
210
|
+
'src/app',
|
|
211
|
+
];
|
|
212
|
+
|
|
213
|
+
const pathsToScan = scanPaths || defaultPaths;
|
|
214
|
+
|
|
215
|
+
// Recursively find all .tsx and .jsx files
|
|
216
|
+
function findFiles(dir) {
|
|
217
|
+
const files = [];
|
|
218
|
+
|
|
219
|
+
if (!fs.existsSync(dir)) {
|
|
220
|
+
return files;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
224
|
+
|
|
225
|
+
for (const entry of entries) {
|
|
226
|
+
const fullPath = path.join(dir, entry.name);
|
|
227
|
+
|
|
228
|
+
if (entry.isDirectory()) {
|
|
229
|
+
// Skip node_modules and hidden directories
|
|
230
|
+
if (entry.name !== 'node_modules' && !entry.name.startsWith('.')) {
|
|
231
|
+
files.push(...findFiles(fullPath));
|
|
232
|
+
}
|
|
233
|
+
} else if (entry.isFile()) {
|
|
234
|
+
// Only process .tsx, .jsx, .ts, .js files
|
|
235
|
+
if (/\.(tsx|jsx|ts|js)$/.test(entry.name)) {
|
|
236
|
+
files.push(fullPath);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return files;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Scan each path
|
|
245
|
+
for (const scanPath of pathsToScan) {
|
|
246
|
+
const fullScanPath = path.join(projectPath, scanPath);
|
|
247
|
+
const files = findFiles(fullScanPath);
|
|
248
|
+
|
|
249
|
+
for (const filePath of files) {
|
|
250
|
+
const relativePath = path.relative(projectPath, filePath);
|
|
251
|
+
results.scanned.push(relativePath);
|
|
252
|
+
|
|
253
|
+
try {
|
|
254
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
255
|
+
const result = ensureUseClient(filePath, content);
|
|
256
|
+
|
|
257
|
+
if (result.modified) {
|
|
258
|
+
results.needsFix.push({
|
|
259
|
+
path: relativePath,
|
|
260
|
+
reason: result.reason,
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
if (fix) {
|
|
264
|
+
fs.writeFileSync(filePath, result.content, 'utf-8');
|
|
265
|
+
results.fixed.push({
|
|
266
|
+
path: relativePath,
|
|
267
|
+
reason: result.reason,
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
} else if (hasUseClientDirective(content)) {
|
|
271
|
+
results.alreadyCorrect.push(relativePath);
|
|
272
|
+
}
|
|
273
|
+
} catch (err) {
|
|
274
|
+
results.errors.push({
|
|
275
|
+
path: relativePath,
|
|
276
|
+
error: err.message,
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return results;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Validates "use client" directives in a Next.js project
|
|
287
|
+
*
|
|
288
|
+
* @param {string} projectPath - Project root path
|
|
289
|
+
* @returns {Object} Validation result with checks array
|
|
290
|
+
*/
|
|
291
|
+
export function validateUseClientDirectives(projectPath) {
|
|
292
|
+
const checks = [];
|
|
293
|
+
let passed = 0;
|
|
294
|
+
|
|
295
|
+
// Scan Velt component directories
|
|
296
|
+
const scanResult = scanAndFixUseClient({
|
|
297
|
+
projectPath,
|
|
298
|
+
fix: false,
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
// Check: All Velt component files have proper directives
|
|
302
|
+
const veltFiles = scanResult.scanned.filter(f =>
|
|
303
|
+
f.includes('velt') || f.includes('Velt')
|
|
304
|
+
);
|
|
305
|
+
|
|
306
|
+
const veltNeedsFix = scanResult.needsFix.filter(f =>
|
|
307
|
+
f.path.includes('velt') || f.path.includes('Velt')
|
|
308
|
+
);
|
|
309
|
+
|
|
310
|
+
if (veltFiles.length > 0) {
|
|
311
|
+
const allCorrect = veltNeedsFix.length === 0;
|
|
312
|
+
|
|
313
|
+
checks.push({
|
|
314
|
+
name: 'Velt components "use client"',
|
|
315
|
+
status: allCorrect ? 'pass' : 'warning',
|
|
316
|
+
message: allCorrect
|
|
317
|
+
? `All ${veltFiles.length} Velt component files have correct directives`
|
|
318
|
+
: `${veltNeedsFix.length} Velt component file(s) missing "use client": ${veltNeedsFix.map(f => f.path).join(', ')}`,
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
if (allCorrect) passed++;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Check: App Router files with client code have directives
|
|
325
|
+
const appFiles = scanResult.scanned.filter(f =>
|
|
326
|
+
f.startsWith('app/') || f.startsWith('src/app/')
|
|
327
|
+
);
|
|
328
|
+
|
|
329
|
+
const appNeedsFix = scanResult.needsFix.filter(f =>
|
|
330
|
+
f.path.startsWith('app/') || f.path.startsWith('src/app/')
|
|
331
|
+
);
|
|
332
|
+
|
|
333
|
+
if (appFiles.length > 0) {
|
|
334
|
+
const allCorrect = appNeedsFix.length === 0;
|
|
335
|
+
|
|
336
|
+
checks.push({
|
|
337
|
+
name: 'App Router "use client"',
|
|
338
|
+
status: allCorrect ? 'pass' : 'warning',
|
|
339
|
+
message: allCorrect
|
|
340
|
+
? `All App Router files have correct directives`
|
|
341
|
+
: `${appNeedsFix.length} App Router file(s) may need "use client": ${appNeedsFix.map(f => f.path).join(', ')}`,
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
if (allCorrect) passed++;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
return {
|
|
348
|
+
checks,
|
|
349
|
+
passed,
|
|
350
|
+
total: checks.length,
|
|
351
|
+
score: `${passed}/${checks.length}`,
|
|
352
|
+
details: {
|
|
353
|
+
scannedFiles: scanResult.scanned.length,
|
|
354
|
+
needsFix: scanResult.needsFix,
|
|
355
|
+
alreadyCorrect: scanResult.alreadyCorrect.length,
|
|
356
|
+
},
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
export default {
|
|
361
|
+
hasUseClientDirective,
|
|
362
|
+
needsUseClientDirective,
|
|
363
|
+
ensureUseClient,
|
|
364
|
+
scanAndFixUseClient,
|
|
365
|
+
validateUseClientDirectives,
|
|
366
|
+
};
|