@keak/sdk 1.0.1
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 +131 -0
- package/dist/Conversion.d.ts +13 -0
- package/dist/Conversion.d.ts.map +1 -0
- package/dist/KeakToolbarShadow.d.ts +21 -0
- package/dist/KeakToolbarShadow.d.ts.map +1 -0
- package/dist/components/ui/card.d.ts +9 -0
- package/dist/components/ui/card.d.ts.map +1 -0
- package/dist/components/ui/html-preview.d.ts +9 -0
- package/dist/components/ui/html-preview.d.ts.map +1 -0
- package/dist/components/ui/simple-tabs.d.ts +26 -0
- package/dist/components/ui/simple-tabs.d.ts.map +1 -0
- package/dist/components/ui/spinner.d.ts +6 -0
- package/dist/components/ui/spinner.d.ts.map +1 -0
- package/dist/components/ui/tabs.d.ts +13 -0
- package/dist/components/ui/tabs.d.ts.map +1 -0
- package/dist/components/ui/textarea.d.ts +6 -0
- package/dist/components/ui/textarea.d.ts.map +1 -0
- package/dist/index.cjs.js +17407 -0
- package/dist/index.cjs.js.map +1 -0
- package/dist/index.d.ts +101 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17395 -0
- package/dist/index.js.map +1 -0
- package/dist/runtime/sourceInjector.d.ts +2 -0
- package/dist/runtime/sourceInjector.d.ts.map +1 -0
- package/dist/scripts/sourcePathInjection.d.ts +11 -0
- package/dist/scripts/sourcePathInjection.d.ts.map +1 -0
- package/dist/services/index.d.ts +7 -0
- package/dist/services/index.d.ts.map +1 -0
- package/dist/services/telemetry/index.d.ts +20 -0
- package/dist/services/telemetry/index.d.ts.map +1 -0
- package/dist/services/telemetry/telemetryService.d.ts +66 -0
- package/dist/services/telemetry/telemetryService.d.ts.map +1 -0
- package/dist/services/telemetry/types.d.ts +64 -0
- package/dist/services/telemetry/types.d.ts.map +1 -0
- package/dist/toolbar/AIPromptPanel.d.ts +9 -0
- package/dist/toolbar/AIPromptPanel.d.ts.map +1 -0
- package/dist/toolbar/ElementSelector.d.ts +8 -0
- package/dist/toolbar/ElementSelector.d.ts.map +1 -0
- package/dist/toolbar/ExperimentPanel.d.ts +9 -0
- package/dist/toolbar/ExperimentPanel.d.ts.map +1 -0
- package/dist/toolbar/KeakToolbar.d.ts +10 -0
- package/dist/toolbar/KeakToolbar.d.ts.map +1 -0
- package/dist/toolbar/MetricsPanel.d.ts +7 -0
- package/dist/toolbar/MetricsPanel.d.ts.map +1 -0
- package/dist/toolbar/PageScanPanel.d.ts +15 -0
- package/dist/toolbar/PageScanPanel.d.ts.map +1 -0
- package/dist/toolbar/components/PrimaryButton.d.ts +12 -0
- package/dist/toolbar/components/PrimaryButton.d.ts.map +1 -0
- package/dist/toolbar/components/WarningButton.d.ts +12 -0
- package/dist/toolbar/components/WarningButton.d.ts.map +1 -0
- package/dist/toolbar/components/icons/index.d.ts +13 -0
- package/dist/toolbar/components/icons/index.d.ts.map +1 -0
- package/dist/toolbar/components/ui/Badge.d.ts +10 -0
- package/dist/toolbar/components/ui/Badge.d.ts.map +1 -0
- package/dist/toolbar/components/ui/Button.d.ts +12 -0
- package/dist/toolbar/components/ui/Button.d.ts.map +1 -0
- package/dist/toolbar/components/ui/Progress.d.ts +5 -0
- package/dist/toolbar/components/ui/Progress.d.ts.map +1 -0
- package/dist/toolbar/components/ui/Tabs.d.ts +44 -0
- package/dist/toolbar/components/ui/Tabs.d.ts.map +1 -0
- package/dist/toolbar/components/ui/dropdown-menu.d.ts +28 -0
- package/dist/toolbar/components/ui/dropdown-menu.d.ts.map +1 -0
- package/dist/toolbar/lib/utils.d.ts +3 -0
- package/dist/toolbar/lib/utils.d.ts.map +1 -0
- package/dist/toolbar/utils/fiberSource.d.ts +64 -0
- package/dist/toolbar/utils/fiberSource.d.ts.map +1 -0
- package/dist/toolbar/utils/keakCodeClient.d.ts +104 -0
- package/dist/toolbar/utils/keakCodeClient.d.ts.map +1 -0
- package/dist/toolbar.css +1 -0
- package/dist/toolbar.js +1257 -0
- package/dist/toolbar.js.map +1 -0
- package/dist/utils/generateElementId.d.ts +44 -0
- package/dist/utils/generateElementId.d.ts.map +1 -0
- package/dist/utils/injectDataId.d.ts +33 -0
- package/dist/utils/injectDataId.d.ts.map +1 -0
- package/package.json +152 -0
- package/src/cli/ai-helper.js +206 -0
- package/src/cli/code-transformer.js +354 -0
- package/src/cli/conversion-detector.js +716 -0
- package/src/cli/framework-config.js +477 -0
- package/src/cli/install.js +618 -0
- package/src/cli/keak-setup.js +43 -0
- package/src/cli/revert-conversions.js +264 -0
- package/src/cli/safe-transformer.js +456 -0
- package/src/cli/simple-transformer.js +339 -0
- package/src/plugins/README.md +131 -0
- package/src/plugins/babel-source-inject.cjs +170 -0
- package/src/plugins/next.cjs +145 -0
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
|
|
6
|
+
class SimpleTransformer {
|
|
7
|
+
constructor() {
|
|
8
|
+
this.stats = {
|
|
9
|
+
filesProcessed: 0,
|
|
10
|
+
elementsWrapped: 0,
|
|
11
|
+
filesModified: 0
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Simple approach: Transform React files to add Conversion wrapper using AST-like approach
|
|
16
|
+
transformReactFile(content, filePath) {
|
|
17
|
+
// Skip if file already has Conversion imports
|
|
18
|
+
if (content.includes('import') && content.includes('Conversion') && content.includes('keak-sdk')) {
|
|
19
|
+
return content;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
let modified = content;
|
|
23
|
+
let elementsFound = 0;
|
|
24
|
+
|
|
25
|
+
// Simple patterns for common clickable elements
|
|
26
|
+
const patterns = [
|
|
27
|
+
// Basic button pattern (with multiline support)
|
|
28
|
+
{
|
|
29
|
+
regex: /(<button[^]*)([\s\S]*?)(<\/button)/g,
|
|
30
|
+
type: 'button',
|
|
31
|
+
replacement: (match, open, content, close) => {
|
|
32
|
+
const category = this.detectCategory(content + open);
|
|
33
|
+
const label = this.extractText(content).substring(0, 50);
|
|
34
|
+
return `${open}${content}${close}
|
|
35
|
+
`;
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
// Basic link pattern (with multiline support)
|
|
39
|
+
{
|
|
40
|
+
regex: /(<a[^]*)([\s\S]*?)(<\/a)/g,
|
|
41
|
+
type: 'link',
|
|
42
|
+
replacement: (match, open, content, close) => {
|
|
43
|
+
const category = this.detectCategory(content + open);
|
|
44
|
+
const label = this.extractText(content).substring(0, 50);
|
|
45
|
+
return `${open}${content}${close}
|
|
46
|
+
`;
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
// Input submit/button
|
|
50
|
+
{
|
|
51
|
+
regex: /(<input[^]*type=["'](submit|button)["'][^]*\/)/g,
|
|
52
|
+
type: 'button',
|
|
53
|
+
replacement: (match, input) => {
|
|
54
|
+
const category = this.detectCategory(input);
|
|
55
|
+
const valueMatch = input.match(/value=["']([^"']+)["']/);
|
|
56
|
+
const label = valueMatch ? valueMatch[1] : 'button';
|
|
57
|
+
return `${input}
|
|
58
|
+
`;
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
// Clickable divs/spans with onClick
|
|
62
|
+
{
|
|
63
|
+
regex: /(<(div|span)[^]*onClick[^]*)([\s\S]*?)(<\/\2)/g,
|
|
64
|
+
type: 'custom',
|
|
65
|
+
replacement: (match, open, elementType, content, close) => {
|
|
66
|
+
const category = this.detectCategory(content + open);
|
|
67
|
+
const label = this.extractText(content).substring(0, 50);
|
|
68
|
+
return `${open}${content}${close}
|
|
69
|
+
`;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
];
|
|
73
|
+
|
|
74
|
+
// Apply transformations
|
|
75
|
+
for (const pattern of patterns) {
|
|
76
|
+
// Track position for wrapping detection
|
|
77
|
+
let lastIndex = 0;
|
|
78
|
+
modified = modified.replace(pattern.regex, (match, ...args) => {
|
|
79
|
+
// Get position of this match
|
|
80
|
+
const currentIndex = modified.indexOf(match, lastIndex);
|
|
81
|
+
lastIndex = currentIndex + match.length;
|
|
82
|
+
|
|
83
|
+
// Skip if already wrapped
|
|
84
|
+
if (this.isAlreadyWrapped(match, modified, currentIndex)) {
|
|
85
|
+
return match;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
elementsFound++;
|
|
89
|
+
return pattern.replacement(match, ...args);
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (elementsFound 0) {
|
|
94
|
+
// Add import
|
|
95
|
+
modified = this.addImport(modified);
|
|
96
|
+
this.stats.elementsWrapped += elementsFound;
|
|
97
|
+
this.stats.filesModified++;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return modified;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Check if element is already wrapped in a Conversion component
|
|
104
|
+
isAlreadyWrapped(match, content, index) {
|
|
105
|
+
if (!index) return false;
|
|
106
|
+
|
|
107
|
+
// Look backwards for ');
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Add import statement
|
|
111
|
+
addImport(content) {
|
|
112
|
+
if (content.includes("import { Conversion } from 'keak-sdk'")) {
|
|
113
|
+
return content;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const lines = content.split('\n');
|
|
117
|
+
let insertIndex = 0;
|
|
118
|
+
|
|
119
|
+
// Find the best place to insert import
|
|
120
|
+
for (let i = 0; i < lines.length; i++) {
|
|
121
|
+
const line = lines[i].trim();
|
|
122
|
+
|
|
123
|
+
// Skip comments and directives
|
|
124
|
+
if (line.startsWith('//') || line.startsWith('/*') || line === '' ||
|
|
125
|
+
line.includes("'use client'") || line.includes('"use client"')) {
|
|
126
|
+
insertIndex = i + 1;
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// If we find an import, add after imports
|
|
131
|
+
if (line.startsWith('import ')) {
|
|
132
|
+
while (i < lines.length && (lines[i].trim().startsWith('import ') || lines[i].trim() === '')) {
|
|
133
|
+
i++;
|
|
134
|
+
}
|
|
135
|
+
insertIndex = i;
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// If we find other code, insert before it
|
|
140
|
+
if (line !== '') {
|
|
141
|
+
insertIndex = i;
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
lines.splice(insertIndex, 0, "");
|
|
147
|
+
return lines.join('\n');
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Detect category from content
|
|
151
|
+
detectCategory(text) {
|
|
152
|
+
const t = text.toLowerCase();
|
|
153
|
+
|
|
154
|
+
if (/buy|purchase|checkout|order|cart|pay/.test(t)) return 'purchase';
|
|
155
|
+
if (/sign.?up|register|join|create.?account/.test(t)) return 'signup';
|
|
156
|
+
if (/download|install|get.?app/.test(t)) return 'download';
|
|
157
|
+
if (/demo|trial|preview|try/.test(t)) return 'demo';
|
|
158
|
+
if (/contact|call|phone|support/.test(t)) return 'contact';
|
|
159
|
+
if (/learn.?more|read.?more|details/.test(t)) return 'engagement';
|
|
160
|
+
if (/subscribe|newsletter/.test(t)) return 'subscription';
|
|
161
|
+
|
|
162
|
+
return 'interaction';
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Extract text content
|
|
166
|
+
extractText(html) {
|
|
167
|
+
return html
|
|
168
|
+
.replace(/<[^]*/g, ' ')
|
|
169
|
+
.replace(/\s+/g, ' ')
|
|
170
|
+
.trim();
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Escape attribute values
|
|
174
|
+
escapeAttr(str) {
|
|
175
|
+
return str
|
|
176
|
+
.replace(/"/g, '"')
|
|
177
|
+
.replace(/'/g, ''')
|
|
178
|
+
.replace(/</g, '<')
|
|
179
|
+
.replace(//g, '>');
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Process a single file
|
|
183
|
+
processFile(filePath) {
|
|
184
|
+
try {
|
|
185
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
186
|
+
const transformed = this.transformReactFile(content, filePath);
|
|
187
|
+
|
|
188
|
+
if (transformed !== content) {
|
|
189
|
+
// Create backup
|
|
190
|
+
const backupPath = `${filePath}.keak-backup`;
|
|
191
|
+
fs.writeFileSync(backupPath, content);
|
|
192
|
+
|
|
193
|
+
// Write transformed content
|
|
194
|
+
fs.writeFileSync(filePath, transformed);
|
|
195
|
+
|
|
196
|
+
return { success: true, modified: true, backup: backupPath };
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return { success: true, modified: false };
|
|
200
|
+
} catch (error) {
|
|
201
|
+
return { success: false, error: error.message };
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Find React files
|
|
206
|
+
findReactFiles(dir) {
|
|
207
|
+
const files = [];
|
|
208
|
+
|
|
209
|
+
if (!fs.existsSync(dir)) return files;
|
|
210
|
+
|
|
211
|
+
const items = fs.readdirSync(dir);
|
|
212
|
+
|
|
213
|
+
for (const item of items) {
|
|
214
|
+
const fullPath = path.join(dir, item);
|
|
215
|
+
|
|
216
|
+
// Skip directories we don't want to process
|
|
217
|
+
if (['node_modules', '.git', 'dist', 'build', '.next', 'coverage'].includes(item)) {
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const stat = fs.statSync(fullPath);
|
|
222
|
+
|
|
223
|
+
if (stat.isDirectory()) {
|
|
224
|
+
files.push(...this.findReactFiles(fullPath));
|
|
225
|
+
} else if (this.isReactFile(item, fullPath)) {
|
|
226
|
+
files.push(fullPath);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return files;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Check if file is a React file
|
|
234
|
+
isReactFile(filename, fullPath) {
|
|
235
|
+
// Check extension
|
|
236
|
+
if (!/\.(jsx|tsx|js|ts)$/.test(filename)) return false;
|
|
237
|
+
|
|
238
|
+
try {
|
|
239
|
+
const content = fs.readFileSync(fullPath, 'utf-8');
|
|
240
|
+
|
|
241
|
+
// Must contain JSX or React imports
|
|
242
|
+
return (
|
|
243
|
+
content.includes('import') && (
|
|
244
|
+
content.includes('react') ||
|
|
245
|
+
content.includes('React') ||
|
|
246
|
+
/\s*<\w+/.test(content) // JSX elements
|
|
247
|
+
)
|
|
248
|
+
);
|
|
249
|
+
} catch (error) {
|
|
250
|
+
return false;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Process directory
|
|
255
|
+
processDirectory(rootDir) {
|
|
256
|
+
console.log(`🔍 Scanning for React files in ${rootDir}...`);
|
|
257
|
+
|
|
258
|
+
const reactFiles = this.findReactFiles(rootDir);
|
|
259
|
+
console.log(`📁 Found ${reactFiles.length} React files`);
|
|
260
|
+
|
|
261
|
+
const results = [];
|
|
262
|
+
|
|
263
|
+
for (const filePath of reactFiles) {
|
|
264
|
+
console.log(`⚙️ Processing ${path.relative(rootDir, filePath)}...`);
|
|
265
|
+
|
|
266
|
+
const result = this.processFile(filePath);
|
|
267
|
+
result.filePath = filePath;
|
|
268
|
+
results.push(result);
|
|
269
|
+
|
|
270
|
+
this.stats.filesProcessed++;
|
|
271
|
+
|
|
272
|
+
if (result.success && result.modified) {
|
|
273
|
+
console.log(`✅ Modified ${path.relative(rootDir, filePath)}`);
|
|
274
|
+
if (result.backup) {
|
|
275
|
+
console.log(`📋 Backup created: ${path.basename(result.backup)}`);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return results;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Generate report
|
|
284
|
+
generateReport() {
|
|
285
|
+
return {
|
|
286
|
+
summary: `
|
|
287
|
+
🎯 Conversion Tracking Integration Complete!
|
|
288
|
+
|
|
289
|
+
📊 Summary:
|
|
290
|
+
• Files processed: ${this.stats.filesProcessed}
|
|
291
|
+
• Files modified: ${this.stats.filesModified}
|
|
292
|
+
• Elements wrapped: ${this.stats.elementsWrapped}
|
|
293
|
+
|
|
294
|
+
✅ All clickable elements have been automatically wrapped with components.
|
|
295
|
+
🔄 Backup files created for all modified files (*.keak-backup)
|
|
296
|
+
📡 Conversion telemetry is now active for all interactions.
|
|
297
|
+
`,
|
|
298
|
+
stats: this.stats
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
export default SimpleTransformer;
|
|
304
|
+
|
|
305
|
+
// CLI execution
|
|
306
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
307
|
+
const transformer = new SimpleTransformer();
|
|
308
|
+
const targetDir = process.argv[2] || process.cwd();
|
|
309
|
+
|
|
310
|
+
console.log('🎯 Setting up automatic conversion tracking...\n');
|
|
311
|
+
|
|
312
|
+
const results = transformer.processDirectory(targetDir);
|
|
313
|
+
const report = transformer.generateReport();
|
|
314
|
+
|
|
315
|
+
console.log(report.summary);
|
|
316
|
+
|
|
317
|
+
// Show any errors
|
|
318
|
+
const errors = results.filter(r =!r.success);
|
|
319
|
+
if (errors.length 0) {
|
|
320
|
+
console.log('\n⚠️ Some files had issues:');
|
|
321
|
+
errors.forEach(error ={
|
|
322
|
+
console.log(`❌ ${path.relative(targetDir, error.filePath)}: ${error.error}`);
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if (report.stats.filesModified 0) {
|
|
327
|
+
console.log(`
|
|
328
|
+
🔧 Next steps:
|
|
329
|
+
1. Review the modified files
|
|
330
|
+
2. Test your application to ensure everything works
|
|
331
|
+
3. The components will automatically track all interactions
|
|
332
|
+
4. Check your Keak dashboard for conversion analytics
|
|
333
|
+
|
|
334
|
+
💡 To undo changes, restore from the .keak-backup files
|
|
335
|
+
`);
|
|
336
|
+
} else {
|
|
337
|
+
console.log('\n✨ No files needed modification. Your code is already optimized!');
|
|
338
|
+
}
|
|
339
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# Keak SDK Plugins
|
|
2
|
+
|
|
3
|
+
Build-time plugins for automatic source location injection.
|
|
4
|
+
|
|
5
|
+
## Next.js Plugin
|
|
6
|
+
|
|
7
|
+
Automatically injects `data-keak-src` attributes into JSX elements for precise element-to-code mapping in Keak IDE.
|
|
8
|
+
|
|
9
|
+
### Installation
|
|
10
|
+
|
|
11
|
+
The plugin is included with `keak-sdk` - no additional installation needed.
|
|
12
|
+
|
|
13
|
+
### Usage
|
|
14
|
+
|
|
15
|
+
**For CommonJS (next.config.js):**
|
|
16
|
+
|
|
17
|
+
```javascript
|
|
18
|
+
const { withKeak } = require('keak-sdk/plugins/next');
|
|
19
|
+
|
|
20
|
+
/** @type {import('next').NextConfig} */
|
|
21
|
+
const nextConfig = {
|
|
22
|
+
// Your existing Next.js config
|
|
23
|
+
reactStrictMode: true,
|
|
24
|
+
// ... other options
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
module.exports = withKeak(nextConfig);
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
**For ES Modules (next.config.mjs):**
|
|
31
|
+
|
|
32
|
+
```javascript
|
|
33
|
+
import { withKeak } from 'keak-sdk/plugins/next';
|
|
34
|
+
|
|
35
|
+
/** @type {import('next').NextConfig} */
|
|
36
|
+
const nextConfig = {
|
|
37
|
+
// Your existing Next.js config
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export default withKeak(nextConfig);
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### What It Does
|
|
44
|
+
|
|
45
|
+
The plugin:
|
|
46
|
+
1. Adds Babel loader to webpack config (dev mode only)
|
|
47
|
+
2. Injects source location attributes into JSX elements:
|
|
48
|
+
- `data-keak-src="app/page.tsx:45:10"` (file:line:column)
|
|
49
|
+
- `data-keak-component="Hero"` (component name)
|
|
50
|
+
- `data-keak-idx="0"` (element index within component)
|
|
51
|
+
|
|
52
|
+
### Verification
|
|
53
|
+
|
|
54
|
+
After setup, check your browser console:
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
✅ Elements should have data-keak-src attributes
|
|
58
|
+
✅ Selecting elements in Keak IDE will instantly open the correct file
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Babel Plugin (Direct Usage)
|
|
62
|
+
|
|
63
|
+
If you have a custom Babel setup:
|
|
64
|
+
|
|
65
|
+
```javascript
|
|
66
|
+
// babel.config.js or .babelrc
|
|
67
|
+
module.exports = {
|
|
68
|
+
plugins: [
|
|
69
|
+
[
|
|
70
|
+
'keak-sdk/plugins/babel-source-inject',
|
|
71
|
+
{
|
|
72
|
+
includeElementIndex: true // Optional
|
|
73
|
+
}
|
|
74
|
+
]
|
|
75
|
+
]
|
|
76
|
+
};
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Vite Plugin
|
|
80
|
+
|
|
81
|
+
For Vite projects:
|
|
82
|
+
|
|
83
|
+
```javascript
|
|
84
|
+
// vite.config.js
|
|
85
|
+
import { defineConfig } from 'vite';
|
|
86
|
+
import react from '@vitejs/plugin-react';
|
|
87
|
+
|
|
88
|
+
export default defineConfig({
|
|
89
|
+
plugins: [
|
|
90
|
+
react({
|
|
91
|
+
babel: {
|
|
92
|
+
plugins: [
|
|
93
|
+
['keak-sdk/plugins/babel-source-inject', { includeElementIndex: true }]
|
|
94
|
+
]
|
|
95
|
+
}
|
|
96
|
+
})
|
|
97
|
+
]
|
|
98
|
+
});
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Troubleshooting
|
|
102
|
+
|
|
103
|
+
### Plugin Not Working
|
|
104
|
+
|
|
105
|
+
1. **Check dev mode:** Plugin only runs in development
|
|
106
|
+
2. **Restart dev server:** Changes to config require restart
|
|
107
|
+
3. **Check browser console:** Look for errors in plugin initialization
|
|
108
|
+
4. **Verify file paths:** Source paths should be relative to project root
|
|
109
|
+
|
|
110
|
+
### Still No Source Locations
|
|
111
|
+
|
|
112
|
+
If the plugin doesn't work:
|
|
113
|
+
- Keak IDE will automatically fall back to text search
|
|
114
|
+
- No action required - element selection will still work
|
|
115
|
+
- Consider using `pnpm dev --no-turbopack` for better React debugging
|
|
116
|
+
|
|
117
|
+
### Build-Time Injection vs Runtime Injection
|
|
118
|
+
|
|
119
|
+
| Method | Setup | Accuracy | Speed | Notes |
|
|
120
|
+
|--------|-------|----------|-------|-------|
|
|
121
|
+
| Build-Time (Plugin) | Configure once | 100% | Instant | Recommended |
|
|
122
|
+
| Runtime (Fiber) | None | 95% | Fast | Works without Turbopack |
|
|
123
|
+
| Text Search | None | 70-90% | 1-2s | Automatic fallback |
|
|
124
|
+
|
|
125
|
+
## Support
|
|
126
|
+
|
|
127
|
+
For issues or questions:
|
|
128
|
+
- Check `IDE_SOURCE_MAPPING.md` in the SDK root
|
|
129
|
+
- Open issue on GitHub
|
|
130
|
+
- See https://docs.keak.com/ide-setup
|
|
131
|
+
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Babel plugin to inject source location metadata into JSX elements
|
|
3
|
+
* This enables precise DOM element to source code mapping
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
console.log('[Keak Babel Plugin] MODULE LOADED - This means Babel is requiring this file');
|
|
7
|
+
|
|
8
|
+
const path = require('path');
|
|
9
|
+
|
|
10
|
+
let processedCount = 0;
|
|
11
|
+
|
|
12
|
+
function keakSourceInjectorPlugin({ types: t }) {
|
|
13
|
+
console.log('[Keak Babel Plugin] Plugin function called - Plugin loaded and initialized');
|
|
14
|
+
return {
|
|
15
|
+
name: 'keak-source-injector',
|
|
16
|
+
visitor: {
|
|
17
|
+
Program: {
|
|
18
|
+
enter(path, state) {
|
|
19
|
+
// Check if this file imports from next/font
|
|
20
|
+
// If so, skip the entire file to avoid Babel/SWC conflict
|
|
21
|
+
state.skipFile = false;
|
|
22
|
+
|
|
23
|
+
path.traverse({
|
|
24
|
+
ImportDeclaration(importPath) {
|
|
25
|
+
const source = importPath.node.source.value;
|
|
26
|
+
if (source.includes('next/font') || source.includes('@next/font')) {
|
|
27
|
+
state.skipFile = true;
|
|
28
|
+
importPath.stop(); // Stop traversing
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
},
|
|
33
|
+
exit() {
|
|
34
|
+
if (processedCount > 0) {
|
|
35
|
+
console.log(`[Keak Babel Plugin] Processed ${processedCount} JSX elements`);
|
|
36
|
+
processedCount = 0;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
JSXOpeningElement(nodePath, state) {
|
|
41
|
+
// Skip entire file if it uses next/font (Babel/SWC conflict)
|
|
42
|
+
if (state.skipFile) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Skip if we're not in development or if disabled
|
|
47
|
+
if (process.env.NODE_ENV === 'production' && !state.opts.forceProduction) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Skip if element already has keak source attribute
|
|
52
|
+
const existingAttr = nodePath.node.attributes.find((attr) =>
|
|
53
|
+
t.isJSXAttribute(attr) &&
|
|
54
|
+
t.isJSXIdentifier(attr.name) &&
|
|
55
|
+
attr.name.name === 'data-keak-src'
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
if (existingAttr) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Get source location info
|
|
63
|
+
const loc = nodePath.node.loc;
|
|
64
|
+
if (!loc) return;
|
|
65
|
+
|
|
66
|
+
const filename = state.file.opts.filename;
|
|
67
|
+
// Skip if filename is not available or not a valid string
|
|
68
|
+
if (!filename || typeof filename !== 'string') {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Skip next/font loader files to avoid Babel/SWC conflict
|
|
73
|
+
// next/font requires SWC and cannot be processed by Babel
|
|
74
|
+
if (filename.includes('next/font') || filename.includes('@next/font')) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Create relative path from project root
|
|
79
|
+
const relativePath = path.relative(process.cwd(), filename);
|
|
80
|
+
|
|
81
|
+
// Create source location string: "file:line:column"
|
|
82
|
+
const sourceLocation = `${relativePath}:${loc.start.line}:${loc.start.column}`;
|
|
83
|
+
|
|
84
|
+
// Get element name for filtering
|
|
85
|
+
const elementName = nodePath.node.name;
|
|
86
|
+
let tagName = '';
|
|
87
|
+
|
|
88
|
+
if (t.isJSXIdentifier(elementName)) {
|
|
89
|
+
tagName = elementName.name;
|
|
90
|
+
} else if (t.isJSXMemberExpression(elementName)) {
|
|
91
|
+
// Handle cases like <motion.div>
|
|
92
|
+
tagName = elementName.property.name;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Only instrument elements we care about
|
|
96
|
+
const targetElements = new Set([
|
|
97
|
+
'div', 'span', 'p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
|
|
98
|
+
'button', 'a', 'input', 'textarea', 'select', 'form',
|
|
99
|
+
'img', 'svg', 'canvas', 'video', 'audio',
|
|
100
|
+
'section', 'article', 'header', 'footer', 'nav', 'main',
|
|
101
|
+
'ul', 'ol', 'li', 'table', 'tr', 'td', 'th'
|
|
102
|
+
]);
|
|
103
|
+
|
|
104
|
+
// Also instrument custom components (capitalized names)
|
|
105
|
+
const isCustomComponent = tagName && tagName[0] === tagName[0].toUpperCase();
|
|
106
|
+
const isTargetElement = targetElements.has(tagName.toLowerCase());
|
|
107
|
+
|
|
108
|
+
if (!isTargetElement && !isCustomComponent) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Create the data attribute
|
|
113
|
+
const sourceAttr = t.jsxAttribute(
|
|
114
|
+
t.jsxIdentifier('data-keak-src'),
|
|
115
|
+
t.stringLiteral(sourceLocation)
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
// Add component context if available
|
|
119
|
+
let componentName = '';
|
|
120
|
+
let currentPath = nodePath;
|
|
121
|
+
|
|
122
|
+
// Walk up to find the containing component
|
|
123
|
+
while (currentPath) {
|
|
124
|
+
if (currentPath.isFunctionDeclaration() && currentPath.node.id) {
|
|
125
|
+
componentName = currentPath.node.id.name;
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (currentPath.isVariableDeclarator() && t.isIdentifier(currentPath.node.id)) {
|
|
130
|
+
const init = currentPath.node.init;
|
|
131
|
+
if (t.isArrowFunctionExpression(init) || t.isFunctionExpression(init)) {
|
|
132
|
+
componentName = currentPath.node.id.name;
|
|
133
|
+
break;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
currentPath = currentPath.parentPath;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Add component context attribute if found
|
|
141
|
+
if (componentName) {
|
|
142
|
+
const componentAttr = t.jsxAttribute(
|
|
143
|
+
t.jsxIdentifier('data-keak-component'),
|
|
144
|
+
t.stringLiteral(componentName)
|
|
145
|
+
);
|
|
146
|
+
nodePath.node.attributes.push(componentAttr);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Add the source location attribute
|
|
150
|
+
nodePath.node.attributes.push(sourceAttr);
|
|
151
|
+
processedCount++;
|
|
152
|
+
|
|
153
|
+
// Optional: Add element index within component for disambiguation
|
|
154
|
+
if (state.opts.includeElementIndex) {
|
|
155
|
+
const elementIndex = state.elementIndex || 0;
|
|
156
|
+
state.elementIndex = elementIndex + 1;
|
|
157
|
+
|
|
158
|
+
const indexAttr = t.jsxAttribute(
|
|
159
|
+
t.jsxIdentifier('data-keak-idx'),
|
|
160
|
+
t.stringLiteral(String(elementIndex))
|
|
161
|
+
);
|
|
162
|
+
nodePath.node.attributes.push(indexAttr);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
module.exports = keakSourceInjectorPlugin;
|
|
170
|
+
|