@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,456 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
|
|
6
|
+
class SafeTransformer {
|
|
7
|
+
constructor() {
|
|
8
|
+
this.stats = {
|
|
9
|
+
filesProcessed: 0,
|
|
10
|
+
elementsWrapped: 0,
|
|
11
|
+
filesModified: 0
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Safe approach: Only transform simple, well-formed JSX elements
|
|
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
|
+
// Handle common React patterns for buttons and links
|
|
26
|
+
const safePatterns = [
|
|
27
|
+
// HTML button elements
|
|
28
|
+
{
|
|
29
|
+
regex: /(<button\s[^>]*>)([\s\S]*?)(<\/button>)/g,
|
|
30
|
+
type: 'button',
|
|
31
|
+
replacement: (match, open, content, close) => {
|
|
32
|
+
if (this.isWellFormedJSX(match)) {
|
|
33
|
+
const category = this.detectCategory(content + open);
|
|
34
|
+
const label = this.extractText(content).substring(0, 30);
|
|
35
|
+
return `<Conversion type="button" category="${category}" label="${this.escapeAttr(label)}">${open}${content}${close}</Conversion>`;
|
|
36
|
+
}
|
|
37
|
+
return match;
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
// React Button components (capitalized)
|
|
41
|
+
{
|
|
42
|
+
regex: /(<Button\s[^>]*>)([\s\S]*?)(<\/Button>)/g,
|
|
43
|
+
type: 'button',
|
|
44
|
+
replacement: (match, open, content, close) => {
|
|
45
|
+
if (this.isWellFormedJSX(match)) {
|
|
46
|
+
const category = this.detectCategory(content + open);
|
|
47
|
+
const label = this.extractText(content).substring(0, 30);
|
|
48
|
+
return `<Conversion type="button" category="${category}" label="${this.escapeAttr(label)}">${open}${content}${close}</Conversion>`;
|
|
49
|
+
}
|
|
50
|
+
return match;
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
// Self-closing Button components
|
|
54
|
+
{
|
|
55
|
+
regex: /(<Button\s[^>]*\/>)/g,
|
|
56
|
+
type: 'button',
|
|
57
|
+
replacement: (match, open) => {
|
|
58
|
+
if (this.isWellFormedJSX(match)) {
|
|
59
|
+
const category = this.detectCategory(open);
|
|
60
|
+
const label = this.extractTextFromProps(open);
|
|
61
|
+
return `<Conversion type="button" category="${category}" label="${this.escapeAttr(label)}">${open}</Conversion>`;
|
|
62
|
+
}
|
|
63
|
+
return match;
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
// HTML links with href
|
|
67
|
+
{
|
|
68
|
+
regex: /(<a\s+[^>]*href=["'][^"']*["'][^>]*>)([\s\S]*?)(<\/a>)/g,
|
|
69
|
+
type: 'link',
|
|
70
|
+
replacement: (match, open, content, close) => {
|
|
71
|
+
if (this.isWellFormedJSX(match)) {
|
|
72
|
+
const category = this.detectCategory(content + open);
|
|
73
|
+
const label = this.extractText(content).substring(0, 30);
|
|
74
|
+
return `<Conversion type="link" category="${category}" label="${this.escapeAttr(label)}">${open}${content}${close}</Conversion>`;
|
|
75
|
+
}
|
|
76
|
+
return match;
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
// Link components
|
|
80
|
+
{
|
|
81
|
+
regex: /(<Link\s[^>]*>)([\s\S]*?)(<\/Link>)/g,
|
|
82
|
+
type: 'link',
|
|
83
|
+
replacement: (match, open, content, close) => {
|
|
84
|
+
if (this.isWellFormedJSX(match)) {
|
|
85
|
+
const category = this.detectCategory(content + open);
|
|
86
|
+
const label = this.extractText(content).substring(0, 30);
|
|
87
|
+
return `<Conversion type="link" category="${category}" label="${this.escapeAttr(label)}">${open}${content}${close}</Conversion>`;
|
|
88
|
+
}
|
|
89
|
+
return match;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
];
|
|
93
|
+
|
|
94
|
+
// Only apply transformations if we can safely parse the file
|
|
95
|
+
if (this.isSafeToTransform(content)) {
|
|
96
|
+
for (const pattern of safePatterns) {
|
|
97
|
+
modified = modified.replace(pattern.regex, (...args) => {
|
|
98
|
+
const match = args[0];
|
|
99
|
+
|
|
100
|
+
// Additional safety checks
|
|
101
|
+
if (this.isAlreadyWrapped(match, modified) ||
|
|
102
|
+
this.hasComplexJSX(match) ||
|
|
103
|
+
this.isInJSXExpression(match, modified, args.index)) {
|
|
104
|
+
return match;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
elementsFound++;
|
|
108
|
+
return pattern.replacement(...args);
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (elementsFound > 0) {
|
|
114
|
+
// Add import
|
|
115
|
+
modified = this.addImport(modified);
|
|
116
|
+
this.stats.elementsWrapped += elementsFound;
|
|
117
|
+
this.stats.filesModified++;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return modified;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Check if content is safe to transform
|
|
124
|
+
isSafeToTransform(content) {
|
|
125
|
+
// Only reject files with patterns that would definitely break our transformation
|
|
126
|
+
const dangerousPatterns = [
|
|
127
|
+
/dangerouslySetInnerHTML/, // Dangerous HTML
|
|
128
|
+
/jsx`/, // Template literals with JSX
|
|
129
|
+
/React\.createElement\s*\(/, // React.createElement calls
|
|
130
|
+
/<script\s*>/i, // Script tags
|
|
131
|
+
/eval\s*\(/, // eval calls
|
|
132
|
+
];
|
|
133
|
+
|
|
134
|
+
return !dangerousPatterns.some(pattern => pattern.test(content));
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Check if JSX is well-formed
|
|
138
|
+
isWellFormedJSX(jsx) {
|
|
139
|
+
// For conversion wrapping, we just need basic safety checks
|
|
140
|
+
// The regex patterns already ensure we have complete elements
|
|
141
|
+
|
|
142
|
+
// Check for unmatched curly braces (which could break JSX)
|
|
143
|
+
const openBraces = (jsx.match(/\{/g) || []).length;
|
|
144
|
+
const closeBraces = (jsx.match(/\}/g) || []).length;
|
|
145
|
+
if (openBraces !== closeBraces) return false;
|
|
146
|
+
|
|
147
|
+
// Check for unmatched quotes that could break attributes
|
|
148
|
+
const singleQuotes = (jsx.match(/'/g) || []).length;
|
|
149
|
+
const doubleQuotes = (jsx.match(/"/g) || []).length;
|
|
150
|
+
if (singleQuotes % 2 !== 0 || doubleQuotes % 2 !== 0) return false;
|
|
151
|
+
|
|
152
|
+
// Don't wrap elements that already have conversion tracking
|
|
153
|
+
if (jsx.includes('data-keak-conversion') || jsx.includes('Conversion')) return false;
|
|
154
|
+
|
|
155
|
+
return true;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Check if element has complex JSX that might break
|
|
159
|
+
hasComplexJSX(jsx) {
|
|
160
|
+
return (
|
|
161
|
+
jsx.includes('...') || // Spread
|
|
162
|
+
jsx.includes('&&') || // Logical AND
|
|
163
|
+
jsx.includes('?') || // Ternary
|
|
164
|
+
jsx.includes('as ') || // TypeScript casting
|
|
165
|
+
jsx.includes('dangerouslySetInnerHTML') ||
|
|
166
|
+
jsx.match(/\{\s*\w+\.\w+/) // Complex property access
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Check if we're inside a JSX expression
|
|
171
|
+
isInJSXExpression(match, content, index) {
|
|
172
|
+
if (!index) return false;
|
|
173
|
+
|
|
174
|
+
const before = content.substring(Math.max(0, index - 100), index);
|
|
175
|
+
const after = content.substring(index + match.length, index + match.length + 100);
|
|
176
|
+
|
|
177
|
+
// Check if we're inside curly braces
|
|
178
|
+
const openBraces = (before.match(/\{/g) || []).length;
|
|
179
|
+
const closeBraces = (before.match(/\}/g) || []).length;
|
|
180
|
+
|
|
181
|
+
return openBraces > closeBraces;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Check if already wrapped
|
|
185
|
+
isAlreadyWrapped(match, content) {
|
|
186
|
+
return content.includes('<Conversion') || match.includes('data-keak-conversion');
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Add import statement safely
|
|
190
|
+
addImport(content) {
|
|
191
|
+
if (content.includes("import { Conversion } from 'keak-sdk'")) {
|
|
192
|
+
return content;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const lines = content.split('\n');
|
|
196
|
+
let insertIndex = 0;
|
|
197
|
+
let lastImportIndex = -1;
|
|
198
|
+
|
|
199
|
+
// Find the best place to insert import
|
|
200
|
+
for (let i = 0; i < lines.length; i++) {
|
|
201
|
+
const line = lines[i].trim();
|
|
202
|
+
|
|
203
|
+
// Skip initial comments and directives
|
|
204
|
+
if (line.startsWith('//') || line.startsWith('/*') || line === '' ||
|
|
205
|
+
line.includes("'use client'") || line.includes('"use client"') ||
|
|
206
|
+
line.includes("'use server'") || line.includes('"use server"')) {
|
|
207
|
+
insertIndex = i + 1;
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Track imports
|
|
212
|
+
if (line.startsWith('import ')) {
|
|
213
|
+
lastImportIndex = i;
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// If we found imports, insert after them
|
|
218
|
+
if (lastImportIndex >= 0 && !line.startsWith('import ')) {
|
|
219
|
+
insertIndex = lastImportIndex + 1;
|
|
220
|
+
break;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// If we find other code, insert before it
|
|
224
|
+
if (line !== '' && !line.startsWith('import ')) {
|
|
225
|
+
insertIndex = i;
|
|
226
|
+
break;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
lines.splice(insertIndex, 0, "import { Conversion } from 'keak-sdk';");
|
|
231
|
+
return lines.join('\n');
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Detect category from content
|
|
235
|
+
detectCategory(text) {
|
|
236
|
+
const t = text.toLowerCase();
|
|
237
|
+
|
|
238
|
+
if (/buy|purchase|checkout|order|cart|pay/.test(t)) return 'purchase';
|
|
239
|
+
if (/sign.?up|register|join|create.?account/.test(t)) return 'signup';
|
|
240
|
+
if (/download|install|get.?app/.test(t)) return 'download';
|
|
241
|
+
if (/demo|trial|preview|try/.test(t)) return 'demo';
|
|
242
|
+
if (/contact|call|phone|support/.test(t)) return 'contact';
|
|
243
|
+
if (/learn.?more|read.?more|details/.test(t)) return 'engagement';
|
|
244
|
+
if (/subscribe|newsletter/.test(t)) return 'subscription';
|
|
245
|
+
|
|
246
|
+
return 'interaction';
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Extract text content safely
|
|
250
|
+
extractText(html) {
|
|
251
|
+
return html
|
|
252
|
+
.replace(/<[^]*/g, ' ')
|
|
253
|
+
.replace(/\{[^}]*\}/g, ' ') // Remove JSX expressions
|
|
254
|
+
.replace(/\s+/g, ' ')
|
|
255
|
+
.trim();
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Extract text from props (for self-closing elements)
|
|
259
|
+
extractTextFromProps(jsx) {
|
|
260
|
+
// Try to extract text from common props like children, title, aria-label
|
|
261
|
+
const propsText = [];
|
|
262
|
+
|
|
263
|
+
// Look for title prop
|
|
264
|
+
const titleMatch = jsx.match(/title=["']([^"']*)["']/);
|
|
265
|
+
if (titleMatch) propsText.push(titleMatch[1]);
|
|
266
|
+
|
|
267
|
+
// Look for aria-label prop
|
|
268
|
+
const ariaLabelMatch = jsx.match(/aria-label=["']([^"']*)["']/);
|
|
269
|
+
if (ariaLabelMatch) propsText.push(ariaLabelMatch[1]);
|
|
270
|
+
|
|
271
|
+
// Look for alt prop
|
|
272
|
+
const altMatch = jsx.match(/alt=["']([^"']*)["']/);
|
|
273
|
+
if (altMatch) propsText.push(altMatch[1]);
|
|
274
|
+
|
|
275
|
+
// Look for children prop
|
|
276
|
+
const childrenMatch = jsx.match(/children=["']([^"']*)["']/);
|
|
277
|
+
if (childrenMatch) propsText.push(childrenMatch[1]);
|
|
278
|
+
|
|
279
|
+
return propsText.join(' ').substring(0, 30) || 'button';
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Escape attribute values safely
|
|
283
|
+
escapeAttr(str) {
|
|
284
|
+
return str
|
|
285
|
+
.replace(/"/g, '"')
|
|
286
|
+
.replace(/'/g, ''')
|
|
287
|
+
.replace(/</g, '<')
|
|
288
|
+
.replace(/>/g, '>')
|
|
289
|
+
.replace(/\{/g, '{')
|
|
290
|
+
.replace(/\}/g, '}');
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Process a single file
|
|
294
|
+
processFile(filePath) {
|
|
295
|
+
try {
|
|
296
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
297
|
+
const transformed = this.transformReactFile(content, filePath);
|
|
298
|
+
|
|
299
|
+
if (transformed !== content) {
|
|
300
|
+
// Create backup
|
|
301
|
+
const backupPath = `${filePath}.keak-backup`;
|
|
302
|
+
fs.writeFileSync(backupPath, content);
|
|
303
|
+
|
|
304
|
+
// Write transformed content
|
|
305
|
+
fs.writeFileSync(filePath, transformed);
|
|
306
|
+
|
|
307
|
+
return { success: true, modified: true, backup: backupPath };
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
return { success: true, modified: false };
|
|
311
|
+
} catch (error) {
|
|
312
|
+
return { success: false, error: error.message };
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Find React files
|
|
317
|
+
findReactFiles(dir) {
|
|
318
|
+
const files = [];
|
|
319
|
+
|
|
320
|
+
if (!fs.existsSync(dir)) return files;
|
|
321
|
+
|
|
322
|
+
const items = fs.readdirSync(dir);
|
|
323
|
+
|
|
324
|
+
for (const item of items) {
|
|
325
|
+
const fullPath = path.join(dir, item);
|
|
326
|
+
|
|
327
|
+
// Skip directories we don't want to process
|
|
328
|
+
if (['node_modules', '.git', 'dist', 'build', '.next', 'coverage'].includes(item)) {
|
|
329
|
+
continue;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
const stat = fs.statSync(fullPath);
|
|
333
|
+
|
|
334
|
+
if (stat.isDirectory()) {
|
|
335
|
+
files.push(...this.findReactFiles(fullPath));
|
|
336
|
+
} else if (this.isReactFile(item, fullPath)) {
|
|
337
|
+
files.push(fullPath);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
return files;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// Check if file is a React file
|
|
345
|
+
isReactFile(filename, fullPath) {
|
|
346
|
+
// Check extension
|
|
347
|
+
if (!/\.(jsx|tsx|js|ts)$/.test(filename)) return false;
|
|
348
|
+
|
|
349
|
+
// Skip test files and config files
|
|
350
|
+
if (/\.(test|spec|config|stories)\.(jsx?|tsx?)$/.test(filename)) return false;
|
|
351
|
+
|
|
352
|
+
try {
|
|
353
|
+
const content = fs.readFileSync(fullPath, 'utf-8');
|
|
354
|
+
|
|
355
|
+
// Must contain JSX or React imports and be safe to transform
|
|
356
|
+
return (
|
|
357
|
+
content.includes('import') && (
|
|
358
|
+
content.includes('react') ||
|
|
359
|
+
content.includes('React') ||
|
|
360
|
+
/\s*<\w+/.test(content) // JSX elements
|
|
361
|
+
) && this.isSafeToTransform(content)
|
|
362
|
+
);
|
|
363
|
+
} catch (error) {
|
|
364
|
+
return false;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// Process directory
|
|
369
|
+
processDirectory(rootDir) {
|
|
370
|
+
console.log(`š Scanning for React files in ${rootDir}...`);
|
|
371
|
+
|
|
372
|
+
const reactFiles = this.findReactFiles(rootDir);
|
|
373
|
+
console.log(`š Found ${reactFiles.length} React files (safe to transform)`);
|
|
374
|
+
|
|
375
|
+
const results = [];
|
|
376
|
+
|
|
377
|
+
for (const filePath of reactFiles) {
|
|
378
|
+
console.log(`āļø Processing ${path.relative(rootDir, filePath)}...`);
|
|
379
|
+
|
|
380
|
+
const result = this.processFile(filePath);
|
|
381
|
+
result.filePath = filePath;
|
|
382
|
+
results.push(result);
|
|
383
|
+
|
|
384
|
+
this.stats.filesProcessed++;
|
|
385
|
+
|
|
386
|
+
if (result.success && result.modified) {
|
|
387
|
+
console.log(`ā
Modified ${path.relative(rootDir, filePath)}`);
|
|
388
|
+
if (result.backup) {
|
|
389
|
+
console.log(`š Backup created: ${path.basename(result.backup)}`);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
return results;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// Generate report
|
|
398
|
+
generateReport() {
|
|
399
|
+
return {
|
|
400
|
+
summary: `
|
|
401
|
+
šÆ Safe Conversion Tracking Integration Complete!
|
|
402
|
+
|
|
403
|
+
š Summary:
|
|
404
|
+
⢠Files processed: ${this.stats.filesProcessed}
|
|
405
|
+
⢠Files modified: ${this.stats.filesModified}
|
|
406
|
+
⢠Elements wrapped: ${this.stats.elementsWrapped}
|
|
407
|
+
|
|
408
|
+
ā
Safe conversion tracking applied to compatible elements.
|
|
409
|
+
š Backup files created for all modified files (*.keak-backup)
|
|
410
|
+
š” Conversion telemetry is now active for tracked interactions.
|
|
411
|
+
|
|
412
|
+
ā ļø Note: Only simple, well-formed JSX elements were wrapped to avoid breaking your code.
|
|
413
|
+
`,
|
|
414
|
+
stats: this.stats
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
export default SafeTransformer;
|
|
420
|
+
|
|
421
|
+
// CLI execution
|
|
422
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
423
|
+
const transformer = new SafeTransformer();
|
|
424
|
+
const targetDir = process.argv[2] || process.cwd();
|
|
425
|
+
|
|
426
|
+
console.log('šÆ Setting up safe conversion tracking...\n');
|
|
427
|
+
|
|
428
|
+
const results = transformer.processDirectory(targetDir);
|
|
429
|
+
const report = transformer.generateReport();
|
|
430
|
+
|
|
431
|
+
console.log(report.summary);
|
|
432
|
+
|
|
433
|
+
// Show any errors
|
|
434
|
+
const errors = results.filter(r => !r.success);
|
|
435
|
+
if (errors.length > 0) {
|
|
436
|
+
console.log('\nā ļø Some files had issues:');
|
|
437
|
+
errors.forEach(error => {
|
|
438
|
+
console.log(`ā ${path.relative(targetDir, error.filePath)}: ${error.error}`);
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
if (report.stats.filesModified > 0) {
|
|
443
|
+
console.log(`
|
|
444
|
+
š§ Next steps:
|
|
445
|
+
1. Test your application to ensure everything works
|
|
446
|
+
2. The components will automatically track interactions
|
|
447
|
+
3. Check your Keak dashboard for conversion analytics
|
|
448
|
+
4. If issues occur, run: npx keak-setup revert-conversions
|
|
449
|
+
|
|
450
|
+
š” This safe mode only wraps simple elements to avoid breaking complex JSX
|
|
451
|
+
`);
|
|
452
|
+
} else {
|
|
453
|
+
console.log('\n⨠No compatible elements found for safe wrapping.');
|
|
454
|
+
console.log('š” Complex JSX patterns were skipped to prevent breaking your code.');
|
|
455
|
+
}
|
|
456
|
+
}
|