@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.
Files changed (89) hide show
  1. package/README.md +131 -0
  2. package/dist/Conversion.d.ts +13 -0
  3. package/dist/Conversion.d.ts.map +1 -0
  4. package/dist/KeakToolbarShadow.d.ts +21 -0
  5. package/dist/KeakToolbarShadow.d.ts.map +1 -0
  6. package/dist/components/ui/card.d.ts +9 -0
  7. package/dist/components/ui/card.d.ts.map +1 -0
  8. package/dist/components/ui/html-preview.d.ts +9 -0
  9. package/dist/components/ui/html-preview.d.ts.map +1 -0
  10. package/dist/components/ui/simple-tabs.d.ts +26 -0
  11. package/dist/components/ui/simple-tabs.d.ts.map +1 -0
  12. package/dist/components/ui/spinner.d.ts +6 -0
  13. package/dist/components/ui/spinner.d.ts.map +1 -0
  14. package/dist/components/ui/tabs.d.ts +13 -0
  15. package/dist/components/ui/tabs.d.ts.map +1 -0
  16. package/dist/components/ui/textarea.d.ts +6 -0
  17. package/dist/components/ui/textarea.d.ts.map +1 -0
  18. package/dist/index.cjs.js +17407 -0
  19. package/dist/index.cjs.js.map +1 -0
  20. package/dist/index.d.ts +101 -0
  21. package/dist/index.d.ts.map +1 -0
  22. package/dist/index.js +17395 -0
  23. package/dist/index.js.map +1 -0
  24. package/dist/runtime/sourceInjector.d.ts +2 -0
  25. package/dist/runtime/sourceInjector.d.ts.map +1 -0
  26. package/dist/scripts/sourcePathInjection.d.ts +11 -0
  27. package/dist/scripts/sourcePathInjection.d.ts.map +1 -0
  28. package/dist/services/index.d.ts +7 -0
  29. package/dist/services/index.d.ts.map +1 -0
  30. package/dist/services/telemetry/index.d.ts +20 -0
  31. package/dist/services/telemetry/index.d.ts.map +1 -0
  32. package/dist/services/telemetry/telemetryService.d.ts +66 -0
  33. package/dist/services/telemetry/telemetryService.d.ts.map +1 -0
  34. package/dist/services/telemetry/types.d.ts +64 -0
  35. package/dist/services/telemetry/types.d.ts.map +1 -0
  36. package/dist/toolbar/AIPromptPanel.d.ts +9 -0
  37. package/dist/toolbar/AIPromptPanel.d.ts.map +1 -0
  38. package/dist/toolbar/ElementSelector.d.ts +8 -0
  39. package/dist/toolbar/ElementSelector.d.ts.map +1 -0
  40. package/dist/toolbar/ExperimentPanel.d.ts +9 -0
  41. package/dist/toolbar/ExperimentPanel.d.ts.map +1 -0
  42. package/dist/toolbar/KeakToolbar.d.ts +10 -0
  43. package/dist/toolbar/KeakToolbar.d.ts.map +1 -0
  44. package/dist/toolbar/MetricsPanel.d.ts +7 -0
  45. package/dist/toolbar/MetricsPanel.d.ts.map +1 -0
  46. package/dist/toolbar/PageScanPanel.d.ts +15 -0
  47. package/dist/toolbar/PageScanPanel.d.ts.map +1 -0
  48. package/dist/toolbar/components/PrimaryButton.d.ts +12 -0
  49. package/dist/toolbar/components/PrimaryButton.d.ts.map +1 -0
  50. package/dist/toolbar/components/WarningButton.d.ts +12 -0
  51. package/dist/toolbar/components/WarningButton.d.ts.map +1 -0
  52. package/dist/toolbar/components/icons/index.d.ts +13 -0
  53. package/dist/toolbar/components/icons/index.d.ts.map +1 -0
  54. package/dist/toolbar/components/ui/Badge.d.ts +10 -0
  55. package/dist/toolbar/components/ui/Badge.d.ts.map +1 -0
  56. package/dist/toolbar/components/ui/Button.d.ts +12 -0
  57. package/dist/toolbar/components/ui/Button.d.ts.map +1 -0
  58. package/dist/toolbar/components/ui/Progress.d.ts +5 -0
  59. package/dist/toolbar/components/ui/Progress.d.ts.map +1 -0
  60. package/dist/toolbar/components/ui/Tabs.d.ts +44 -0
  61. package/dist/toolbar/components/ui/Tabs.d.ts.map +1 -0
  62. package/dist/toolbar/components/ui/dropdown-menu.d.ts +28 -0
  63. package/dist/toolbar/components/ui/dropdown-menu.d.ts.map +1 -0
  64. package/dist/toolbar/lib/utils.d.ts +3 -0
  65. package/dist/toolbar/lib/utils.d.ts.map +1 -0
  66. package/dist/toolbar/utils/fiberSource.d.ts +64 -0
  67. package/dist/toolbar/utils/fiberSource.d.ts.map +1 -0
  68. package/dist/toolbar/utils/keakCodeClient.d.ts +104 -0
  69. package/dist/toolbar/utils/keakCodeClient.d.ts.map +1 -0
  70. package/dist/toolbar.css +1 -0
  71. package/dist/toolbar.js +1257 -0
  72. package/dist/toolbar.js.map +1 -0
  73. package/dist/utils/generateElementId.d.ts +44 -0
  74. package/dist/utils/generateElementId.d.ts.map +1 -0
  75. package/dist/utils/injectDataId.d.ts +33 -0
  76. package/dist/utils/injectDataId.d.ts.map +1 -0
  77. package/package.json +152 -0
  78. package/src/cli/ai-helper.js +206 -0
  79. package/src/cli/code-transformer.js +354 -0
  80. package/src/cli/conversion-detector.js +716 -0
  81. package/src/cli/framework-config.js +477 -0
  82. package/src/cli/install.js +618 -0
  83. package/src/cli/keak-setup.js +43 -0
  84. package/src/cli/revert-conversions.js +264 -0
  85. package/src/cli/safe-transformer.js +456 -0
  86. package/src/cli/simple-transformer.js +339 -0
  87. package/src/plugins/README.md +131 -0
  88. package/src/plugins/babel-source-inject.cjs +170 -0
  89. 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, '&quot;')
177
+ .replace(/'/g, '&#39;')
178
+ .replace(/</g, '&lt;')
179
+ .replace(//g, '&gt;');
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
+