@uptrademedia/site-kit 1.0.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.
Files changed (120) hide show
  1. package/README.md +305 -0
  2. package/dist/analytics/index.js +88 -0
  3. package/dist/analytics/index.js.map +1 -0
  4. package/dist/analytics/index.mjs +70 -0
  5. package/dist/analytics/index.mjs.map +1 -0
  6. package/dist/api-N35S3EES.js +57 -0
  7. package/dist/api-N35S3EES.js.map +1 -0
  8. package/dist/api-SYBTK7Z7.mjs +4 -0
  9. package/dist/api-SYBTK7Z7.mjs.map +1 -0
  10. package/dist/blog/index.js +200 -0
  11. package/dist/blog/index.js.map +1 -0
  12. package/dist/blog/index.mjs +194 -0
  13. package/dist/blog/index.mjs.map +1 -0
  14. package/dist/chunk-3MUOUXHV.js +3721 -0
  15. package/dist/chunk-3MUOUXHV.js.map +1 -0
  16. package/dist/chunk-4HVYXYQL 2.mjs +255 -0
  17. package/dist/chunk-4HVYXYQL.mjs +255 -0
  18. package/dist/chunk-4HVYXYQL.mjs.map +1 -0
  19. package/dist/chunk-7H6I3ECV.mjs +120 -0
  20. package/dist/chunk-7H6I3ECV.mjs.map +1 -0
  21. package/dist/chunk-COI6GOX2.mjs +3679 -0
  22. package/dist/chunk-COI6GOX2.mjs.map +1 -0
  23. package/dist/chunk-EQCVQC35.js +35 -0
  24. package/dist/chunk-EQCVQC35.js 2.map +1 -0
  25. package/dist/chunk-EQCVQC35.js.map +1 -0
  26. package/dist/chunk-FEBYQGY4 2.mjs +251 -0
  27. package/dist/chunk-FEBYQGY4.mjs +251 -0
  28. package/dist/chunk-FEBYQGY4.mjs.map +1 -0
  29. package/dist/chunk-FKVJOT2F.mjs +796 -0
  30. package/dist/chunk-FKVJOT2F.mjs.map +1 -0
  31. package/dist/chunk-GQ6ZOU2N.mjs +134 -0
  32. package/dist/chunk-GQ6ZOU2N.mjs.map +1 -0
  33. package/dist/chunk-HCFPU7TU.js +137 -0
  34. package/dist/chunk-HCFPU7TU.js.map +1 -0
  35. package/dist/chunk-NYKRE2FL 2.mjs +31 -0
  36. package/dist/chunk-NYKRE2FL.mjs +31 -0
  37. package/dist/chunk-NYKRE2FL.mjs 2.map +1 -0
  38. package/dist/chunk-NYKRE2FL.mjs.map +1 -0
  39. package/dist/chunk-QP5NCO2E.js +133 -0
  40. package/dist/chunk-QP5NCO2E.js.map +1 -0
  41. package/dist/chunk-RV7H3I6J.js +255 -0
  42. package/dist/chunk-RV7H3I6J.js 2.map +1 -0
  43. package/dist/chunk-RV7H3I6J.js.map +1 -0
  44. package/dist/chunk-SBVEYCSV.js +140 -0
  45. package/dist/chunk-SBVEYCSV.js.map +1 -0
  46. package/dist/chunk-TUKGA3UK.js +257 -0
  47. package/dist/chunk-TUKGA3UK.js 2.map +1 -0
  48. package/dist/chunk-TUKGA3UK.js.map +1 -0
  49. package/dist/chunk-V3F5J6CV.js +801 -0
  50. package/dist/chunk-V3F5J6CV.js.map +1 -0
  51. package/dist/chunk-WPSRS352.mjs +135 -0
  52. package/dist/chunk-WPSRS352.mjs.map +1 -0
  53. package/dist/commerce/index.js +157 -0
  54. package/dist/commerce/index.js.map +1 -0
  55. package/dist/commerce/index.mjs +4 -0
  56. package/dist/commerce/index.mjs.map +1 -0
  57. package/dist/commerce/server.js +186 -0
  58. package/dist/commerce/server.js.map +1 -0
  59. package/dist/commerce/server.mjs +176 -0
  60. package/dist/commerce/server.mjs.map +1 -0
  61. package/dist/engage/index.js +50 -0
  62. package/dist/engage/index.js.map +1 -0
  63. package/dist/engage/index.mjs +44 -0
  64. package/dist/engage/index.mjs.map +1 -0
  65. package/dist/forms/index.js +1053 -0
  66. package/dist/forms/index.js.map +1 -0
  67. package/dist/forms/index.mjs +1035 -0
  68. package/dist/forms/index.mjs.map +1 -0
  69. package/dist/generators-7Y5ABRYV 2.mjs +161 -0
  70. package/dist/generators-7Y5ABRYV.mjs +161 -0
  71. package/dist/generators-7Y5ABRYV.mjs 2.map +1 -0
  72. package/dist/generators-7Y5ABRYV.mjs.map +1 -0
  73. package/dist/generators-GWIYCA5M.js +171 -0
  74. package/dist/generators-GWIYCA5M.js 2.map +1 -0
  75. package/dist/generators-GWIYCA5M.js.map +1 -0
  76. package/dist/index 2.mjs +74 -0
  77. package/dist/index.js +326 -0
  78. package/dist/index.js 2.map +1 -0
  79. package/dist/index.js.map +1 -0
  80. package/dist/index.mjs +222 -0
  81. package/dist/index.mjs.map +1 -0
  82. package/dist/migrator-V6KS75EA 2.mjs +265 -0
  83. package/dist/migrator-V6KS75EA.mjs +265 -0
  84. package/dist/migrator-V6KS75EA.mjs 2.map +1 -0
  85. package/dist/migrator-V6KS75EA.mjs.map +1 -0
  86. package/dist/migrator-XKM7YQCY.js +272 -0
  87. package/dist/migrator-XKM7YQCY.js 2.map +1 -0
  88. package/dist/migrator-XKM7YQCY.js.map +1 -0
  89. package/dist/scanner-MF7P3CDE 2.mjs +14386 -0
  90. package/dist/scanner-MF7P3CDE.mjs +14386 -0
  91. package/dist/scanner-MF7P3CDE.mjs 2.map +1 -0
  92. package/dist/scanner-MF7P3CDE.mjs.map +1 -0
  93. package/dist/scanner-NT6YG4TD 2.js +14397 -0
  94. package/dist/scanner-NT6YG4TD.js +14397 -0
  95. package/dist/scanner-NT6YG4TD.js 2.map +1 -0
  96. package/dist/scanner-NT6YG4TD.js.map +1 -0
  97. package/dist/seo/index.js +447 -0
  98. package/dist/seo/index.js.map +1 -0
  99. package/dist/seo/index.mjs +411 -0
  100. package/dist/seo/index.mjs.map +1 -0
  101. package/dist/seo/server.js +66 -0
  102. package/dist/seo/server.js.map +1 -0
  103. package/dist/seo/server.mjs +5 -0
  104. package/dist/seo/server.mjs.map +1 -0
  105. package/dist/setup/index.js +1050 -0
  106. package/dist/setup/index.js.map +1 -0
  107. package/dist/setup/index.mjs +1046 -0
  108. package/dist/setup/index.mjs.map +1 -0
  109. package/dist/sitemap/index.js +212 -0
  110. package/dist/sitemap/index.js.map +1 -0
  111. package/dist/sitemap/index.mjs +206 -0
  112. package/dist/sitemap/index.mjs.map +1 -0
  113. package/dist/web-vitals-BH55V7EJ.js +252 -0
  114. package/dist/web-vitals-BH55V7EJ.js 2.map +1 -0
  115. package/dist/web-vitals-BH55V7EJ.js.map +1 -0
  116. package/dist/web-vitals-RJYPWAR3 2.mjs +241 -0
  117. package/dist/web-vitals-RJYPWAR3.mjs +241 -0
  118. package/dist/web-vitals-RJYPWAR3.mjs 2.map +1 -0
  119. package/dist/web-vitals-RJYPWAR3.mjs.map +1 -0
  120. package/package.json +118 -0
@@ -0,0 +1,265 @@
1
+ import './chunk-NYKRE2FL.mjs';
2
+ import fs from 'fs/promises';
3
+ import path from 'path';
4
+
5
+ async function migrateFiles(scanResults, options) {
6
+ const results = [];
7
+ for (const form of scanResults.forms) {
8
+ if (form.suggestedAction === "manual") {
9
+ results.push({
10
+ filePath: form.filePath,
11
+ success: false,
12
+ changes: [],
13
+ error: "Form too complex for auto-migration"
14
+ });
15
+ continue;
16
+ }
17
+ try {
18
+ const result = await migrateForm(form, options);
19
+ results.push(result);
20
+ } catch (error) {
21
+ results.push({
22
+ filePath: form.filePath,
23
+ success: false,
24
+ changes: [],
25
+ error: error.message
26
+ });
27
+ }
28
+ }
29
+ for (const widget of scanResults.widgets) {
30
+ try {
31
+ const result = await migrateWidget(widget.filePath, widget, options);
32
+ results.push(result);
33
+ } catch (error) {
34
+ results.push({
35
+ filePath: widget.filePath,
36
+ success: false,
37
+ changes: [],
38
+ error: error.message
39
+ });
40
+ }
41
+ }
42
+ return results;
43
+ }
44
+ async function migrateForm(form, options) {
45
+ const changes = [];
46
+ const fullPath = path.resolve(process.cwd(), form.filePath);
47
+ const formSlug = generateSlug(form.componentName);
48
+ const formId = await createFormInUptrade(form, formSlug, options);
49
+ changes.push(`Created managed form: ${formSlug}`);
50
+ if (options.dryRun) {
51
+ changes.push("[DRY RUN] Would update file");
52
+ return { filePath: form.filePath, success: true, changes, formId };
53
+ }
54
+ await fs.readFile(fullPath, "utf-8");
55
+ const newCode = generateMigratedFormCode(form, formSlug);
56
+ await fs.writeFile(fullPath, newCode, "utf-8");
57
+ changes.push("Updated component to use useForm hook");
58
+ return {
59
+ filePath: form.filePath,
60
+ success: true,
61
+ changes,
62
+ formId
63
+ };
64
+ }
65
+ function generateMigratedFormCode(form, formSlug) {
66
+ const componentName = form.componentName || "MigratedForm";
67
+ return `/**
68
+ * ${componentName}
69
+ *
70
+ * Migrated to @uptrade/site-kit
71
+ * Managed form: ${formSlug}
72
+ */
73
+
74
+ 'use client'
75
+
76
+ import { useForm } from '@uptrade/site-kit/forms'
77
+
78
+ export function ${componentName}({ className }: { className?: string }) {
79
+ const {
80
+ form,
81
+ fields,
82
+ values,
83
+ errors,
84
+ setFieldValue,
85
+ submit,
86
+ isSubmitting,
87
+ isComplete
88
+ } = useForm('${formSlug}')
89
+
90
+ if (isComplete) {
91
+ return (
92
+ <div className={className}>
93
+ <p className="text-green-600">{form?.successMessage || 'Thanks for your submission!'}</p>
94
+ </div>
95
+ )
96
+ }
97
+
98
+ return (
99
+ <form
100
+ onSubmit={(e) => { e.preventDefault(); submit() }}
101
+ className={className}
102
+ >
103
+ {fields.map(field => (
104
+ <div key={field.slug} className="mb-4">
105
+ <label className="block text-sm font-medium mb-1">
106
+ {field.label}
107
+ {field.isRequired && <span className="text-red-500 ml-1">*</span>}
108
+ </label>
109
+
110
+ {field.fieldType === 'textarea' ? (
111
+ <textarea
112
+ name={field.slug}
113
+ placeholder={field.placeholder}
114
+ value={String(values[field.slug] || '')}
115
+ onChange={(e) => setFieldValue(field.slug, e.target.value)}
116
+ className="w-full p-2 border rounded focus:ring-2 focus:ring-blue-500 focus:border-transparent"
117
+ rows={4}
118
+ />
119
+ ) : field.fieldType === 'select' && field.options ? (
120
+ <select
121
+ name={field.slug}
122
+ value={String(values[field.slug] || '')}
123
+ onChange={(e) => setFieldValue(field.slug, e.target.value)}
124
+ className="w-full p-2 border rounded focus:ring-2 focus:ring-blue-500 focus:border-transparent"
125
+ >
126
+ <option value="">Select...</option>
127
+ {field.options.map(opt => (
128
+ <option key={opt.value} value={opt.value}>{opt.label}</option>
129
+ ))}
130
+ </select>
131
+ ) : (
132
+ <input
133
+ type={field.fieldType}
134
+ name={field.slug}
135
+ placeholder={field.placeholder}
136
+ value={String(values[field.slug] || '')}
137
+ onChange={(e) => setFieldValue(field.slug, e.target.value)}
138
+ className="w-full p-2 border rounded focus:ring-2 focus:ring-blue-500 focus:border-transparent"
139
+ />
140
+ )}
141
+
142
+ {errors[field.slug] && (
143
+ <p className="mt-1 text-sm text-red-500">{errors[field.slug]}</p>
144
+ )}
145
+
146
+ {field.helpText && (
147
+ <p className="mt-1 text-xs text-gray-500">{field.helpText}</p>
148
+ )}
149
+ </div>
150
+ ))}
151
+
152
+ <button
153
+ type="submit"
154
+ disabled={isSubmitting}
155
+ className="w-full py-2 px-4 bg-blue-600 text-white rounded hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
156
+ >
157
+ {isSubmitting ? 'Submitting...' : (form?.submitButtonText || 'Submit')}
158
+ </button>
159
+ </form>
160
+ )
161
+ }
162
+
163
+ export default ${componentName}
164
+ `;
165
+ }
166
+ async function createFormInUptrade(form, slug, options) {
167
+ const response = await fetch("https://api.uptrademedia.com/forms", {
168
+ method: "POST",
169
+ headers: {
170
+ "Content-Type": "application/json",
171
+ "Authorization": `Bearer ${options.apiKey}`
172
+ },
173
+ body: JSON.stringify({
174
+ projectId: options.projectId,
175
+ slug,
176
+ name: formatName(form.componentName),
177
+ formType: detectFormType(form),
178
+ successMessage: "Thanks for your submission!",
179
+ submitButtonText: "Submit",
180
+ fields: form.fields.map((f, i) => ({
181
+ slug: f.name,
182
+ label: formatLabel(f.name),
183
+ fieldType: mapFieldType(f.type),
184
+ placeholder: f.placeholder,
185
+ isRequired: f.required,
186
+ sortOrder: i,
187
+ width: "full"
188
+ }))
189
+ })
190
+ });
191
+ if (!response.ok) {
192
+ const error = await response.json();
193
+ throw new Error(error.message || "Failed to create form");
194
+ }
195
+ const data = await response.json();
196
+ return data.id;
197
+ }
198
+ async function migrateWidget(filePath, widget, options) {
199
+ const changes = [];
200
+ if (options.dryRun) {
201
+ changes.push(`[DRY RUN] Would remove ${widget.widgetType} script`);
202
+ changes.push("[DRY RUN] Would enable Engage in SiteKitProvider");
203
+ return { filePath, success: true, changes };
204
+ }
205
+ const fullPath = path.resolve(process.cwd(), filePath);
206
+ let content = await fs.readFile(fullPath, "utf-8");
207
+ switch (widget.widgetType) {
208
+ case "intercom":
209
+ content = content.replace(/<Script[^>]*intercom[^>]*\/?>(?:<\/Script>)?/gi, "{/* Intercom replaced with Uptrade Engage */}");
210
+ content = content.replace(/window\.Intercom\s*=\s*[^;]+;/g, "");
211
+ break;
212
+ case "crisp":
213
+ content = content.replace(/<Script[^>]*crisp[^>]*\/?>(?:<\/Script>)?/gi, "{/* Crisp replaced with Uptrade Engage */}");
214
+ break;
215
+ case "drift":
216
+ content = content.replace(/<Script[^>]*drift[^>]*\/?>(?:<\/Script>)?/gi, "{/* Drift replaced with Uptrade Engage */}");
217
+ break;
218
+ }
219
+ await fs.writeFile(fullPath, content, "utf-8");
220
+ changes.push(`Removed ${widget.widgetType} script`);
221
+ changes.push("Enable Engage in SiteKitProvider to add chat widget");
222
+ return { filePath, success: true, changes };
223
+ }
224
+ function generateSlug(name) {
225
+ return name.replace(/([A-Z])/g, "-$1").toLowerCase().replace(/^-/, "").replace(/form$/i, "").replace(/-+/g, "-").replace(/-$/, "") || "form";
226
+ }
227
+ function formatName(name) {
228
+ return name.replace(/([A-Z])/g, " $1").replace(/^./, (str) => str.toUpperCase()).trim();
229
+ }
230
+ function formatLabel(name) {
231
+ return name.replace(/([A-Z])/g, " $1").replace(/_/g, " ").replace(/^./, (str) => str.toUpperCase()).trim();
232
+ }
233
+ function mapFieldType(type) {
234
+ const mapping = {
235
+ "text": "text",
236
+ "email": "email",
237
+ "tel": "phone",
238
+ "phone": "phone",
239
+ "number": "number",
240
+ "textarea": "textarea",
241
+ "select": "select",
242
+ "checkbox": "checkbox",
243
+ "radio": "radio",
244
+ "date": "date",
245
+ "file": "file",
246
+ "url": "url",
247
+ "password": "text"
248
+ // Don't use password for managed forms
249
+ };
250
+ return mapping[type] || "text";
251
+ }
252
+ function detectFormType(form) {
253
+ const name = form.componentName.toLowerCase();
254
+ const fields = form.fields.map((f) => f.name.toLowerCase()).join(" ");
255
+ if (name.includes("contact") || fields.includes("message")) return "contact";
256
+ if (name.includes("newsletter") || name.includes("subscribe")) return "newsletter";
257
+ if (name.includes("quote") || name.includes("estimate")) return "prospect";
258
+ if (name.includes("support") || name.includes("help")) return "support";
259
+ if (name.includes("feedback")) return "feedback";
260
+ return "contact";
261
+ }
262
+
263
+ export { migrateFiles };
264
+ //# sourceMappingURL=migrator-V6KS75EA.mjs.map
265
+ //# sourceMappingURL=migrator-V6KS75EA.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cli/migrator/index.ts"],"names":[],"mappings":"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","file":"migrator-V6KS75EA.mjs","sourcesContent":["/**\n * Code Migrator - Transforms existing forms to Site-Kit managed forms\n */\n\nimport fs from 'fs/promises'\nimport path from 'path'\nimport { parse } from '@babel/parser'\nimport generate from '@babel/generator'\nimport traverse from '@babel/traverse'\nimport * as t from '@babel/types'\nimport type { ScanResults, DetectedForm, DetectedField } from '../scanner/index.js'\n\n// ============================================\n// Types\n// ============================================\n\nexport interface MigrationResult {\n filePath: string\n success: boolean\n changes: string[]\n error?: string\n formId?: string\n}\n\nexport interface MigrationOptions {\n projectId: string\n apiKey: string\n dryRun?: boolean\n}\n\n// ============================================\n// Main Migrator\n// ============================================\n\nexport async function migrateFiles(\n scanResults: ScanResults,\n options: MigrationOptions\n): Promise<MigrationResult[]> {\n const results: MigrationResult[] = []\n\n // Migrate forms\n for (const form of scanResults.forms) {\n if (form.suggestedAction === 'manual') {\n results.push({\n filePath: form.filePath,\n success: false,\n changes: [],\n error: 'Form too complex for auto-migration',\n })\n continue\n }\n\n try {\n const result = await migrateForm(form, options)\n results.push(result)\n } catch (error: any) {\n results.push({\n filePath: form.filePath,\n success: false,\n changes: [],\n error: error.message,\n })\n }\n }\n\n // Migrate widgets (simpler - just remove and add provider flag)\n for (const widget of scanResults.widgets) {\n try {\n const result = await migrateWidget(widget.filePath, widget, options)\n results.push(result)\n } catch (error: any) {\n results.push({\n filePath: widget.filePath,\n success: false,\n changes: [],\n error: error.message,\n })\n }\n }\n\n return results\n}\n\n// ============================================\n// Form Migration\n// ============================================\n\nasync function migrateForm(\n form: DetectedForm,\n options: MigrationOptions\n): Promise<MigrationResult> {\n const changes: string[] = []\n const fullPath = path.resolve(process.cwd(), form.filePath)\n\n // Step 1: Create the form in Uptrade\n const formSlug = generateSlug(form.componentName)\n const formId = await createFormInUptrade(form, formSlug, options)\n changes.push(`Created managed form: ${formSlug}`)\n\n if (options.dryRun) {\n changes.push('[DRY RUN] Would update file')\n return { filePath: form.filePath, success: true, changes, formId }\n }\n\n // Step 2: Read the file\n const content = await fs.readFile(fullPath, 'utf-8')\n \n // Step 3: Generate new code\n const newCode = generateMigratedFormCode(form, formSlug)\n\n // Step 4: Write the file\n await fs.writeFile(fullPath, newCode, 'utf-8')\n changes.push('Updated component to use useForm hook')\n\n return {\n filePath: form.filePath,\n success: true,\n changes,\n formId,\n }\n}\n\nfunction generateMigratedFormCode(form: DetectedForm, formSlug: string): string {\n // Generate a clean, migrated component\n const componentName = form.componentName || 'MigratedForm'\n\n return `/**\n * ${componentName}\n * \n * Migrated to @uptrade/site-kit\n * Managed form: ${formSlug}\n */\n\n'use client'\n\nimport { useForm } from '@uptrade/site-kit/forms'\n\nexport function ${componentName}({ className }: { className?: string }) {\n const { \n form,\n fields, \n values, \n errors, \n setFieldValue, \n submit, \n isSubmitting,\n isComplete \n } = useForm('${formSlug}')\n\n if (isComplete) {\n return (\n <div className={className}>\n <p className=\"text-green-600\">{form?.successMessage || 'Thanks for your submission!'}</p>\n </div>\n )\n }\n\n return (\n <form \n onSubmit={(e) => { e.preventDefault(); submit() }} \n className={className}\n >\n {fields.map(field => (\n <div key={field.slug} className=\"mb-4\">\n <label className=\"block text-sm font-medium mb-1\">\n {field.label}\n {field.isRequired && <span className=\"text-red-500 ml-1\">*</span>}\n </label>\n \n {field.fieldType === 'textarea' ? (\n <textarea\n name={field.slug}\n placeholder={field.placeholder}\n value={String(values[field.slug] || '')}\n onChange={(e) => setFieldValue(field.slug, e.target.value)}\n className=\"w-full p-2 border rounded focus:ring-2 focus:ring-blue-500 focus:border-transparent\"\n rows={4}\n />\n ) : field.fieldType === 'select' && field.options ? (\n <select\n name={field.slug}\n value={String(values[field.slug] || '')}\n onChange={(e) => setFieldValue(field.slug, e.target.value)}\n className=\"w-full p-2 border rounded focus:ring-2 focus:ring-blue-500 focus:border-transparent\"\n >\n <option value=\"\">Select...</option>\n {field.options.map(opt => (\n <option key={opt.value} value={opt.value}>{opt.label}</option>\n ))}\n </select>\n ) : (\n <input\n type={field.fieldType}\n name={field.slug}\n placeholder={field.placeholder}\n value={String(values[field.slug] || '')}\n onChange={(e) => setFieldValue(field.slug, e.target.value)}\n className=\"w-full p-2 border rounded focus:ring-2 focus:ring-blue-500 focus:border-transparent\"\n />\n )}\n \n {errors[field.slug] && (\n <p className=\"mt-1 text-sm text-red-500\">{errors[field.slug]}</p>\n )}\n \n {field.helpText && (\n <p className=\"mt-1 text-xs text-gray-500\">{field.helpText}</p>\n )}\n </div>\n ))}\n \n <button \n type=\"submit\" \n disabled={isSubmitting}\n className=\"w-full py-2 px-4 bg-blue-600 text-white rounded hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors\"\n >\n {isSubmitting ? 'Submitting...' : (form?.submitButtonText || 'Submit')}\n </button>\n </form>\n )\n}\n\nexport default ${componentName}\n`\n}\n\nasync function createFormInUptrade(\n form: DetectedForm,\n slug: string,\n options: MigrationOptions\n): Promise<string> {\n // Call Portal API to create the form\n const response = await fetch('https://api.uptrademedia.com/forms', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${options.apiKey}`,\n },\n body: JSON.stringify({\n projectId: options.projectId,\n slug,\n name: formatName(form.componentName),\n formType: detectFormType(form),\n successMessage: 'Thanks for your submission!',\n submitButtonText: 'Submit',\n fields: form.fields.map((f, i) => ({\n slug: f.name,\n label: formatLabel(f.name),\n fieldType: mapFieldType(f.type),\n placeholder: f.placeholder,\n isRequired: f.required,\n sortOrder: i,\n width: 'full',\n })),\n }),\n })\n\n if (!response.ok) {\n const error = await response.json()\n throw new Error(error.message || 'Failed to create form')\n }\n\n const data = await response.json()\n return data.id\n}\n\n// ============================================\n// Widget Migration\n// ============================================\n\nasync function migrateWidget(\n filePath: string,\n widget: any,\n options: MigrationOptions\n): Promise<MigrationResult> {\n const changes: string[] = []\n\n if (options.dryRun) {\n changes.push(`[DRY RUN] Would remove ${widget.widgetType} script`)\n changes.push('[DRY RUN] Would enable Engage in SiteKitProvider')\n return { filePath, success: true, changes }\n }\n\n const fullPath = path.resolve(process.cwd(), filePath)\n let content = await fs.readFile(fullPath, 'utf-8')\n\n // Remove the widget script based on type\n switch (widget.widgetType) {\n case 'intercom':\n content = content.replace(/<Script[^>]*intercom[^>]*\\/?>(?:<\\/Script>)?/gi, '{/* Intercom replaced with Uptrade Engage */}')\n content = content.replace(/window\\.Intercom\\s*=\\s*[^;]+;/g, '')\n break\n case 'crisp':\n content = content.replace(/<Script[^>]*crisp[^>]*\\/?>(?:<\\/Script>)?/gi, '{/* Crisp replaced with Uptrade Engage */}')\n break\n case 'drift':\n content = content.replace(/<Script[^>]*drift[^>]*\\/?>(?:<\\/Script>)?/gi, '{/* Drift replaced with Uptrade Engage */}')\n break\n }\n\n await fs.writeFile(fullPath, content, 'utf-8')\n changes.push(`Removed ${widget.widgetType} script`)\n changes.push('Enable Engage in SiteKitProvider to add chat widget')\n\n return { filePath, success: true, changes }\n}\n\n// ============================================\n// Helpers\n// ============================================\n\nfunction generateSlug(name: string): string {\n return name\n .replace(/([A-Z])/g, '-$1')\n .toLowerCase()\n .replace(/^-/, '')\n .replace(/form$/i, '')\n .replace(/-+/g, '-')\n .replace(/-$/, '') || 'form'\n}\n\nfunction formatName(name: string): string {\n return name\n .replace(/([A-Z])/g, ' $1')\n .replace(/^./, str => str.toUpperCase())\n .trim()\n}\n\nfunction formatLabel(name: string): string {\n return name\n .replace(/([A-Z])/g, ' $1')\n .replace(/_/g, ' ')\n .replace(/^./, str => str.toUpperCase())\n .trim()\n}\n\nfunction mapFieldType(type: string): string {\n const mapping: Record<string, string> = {\n 'text': 'text',\n 'email': 'email',\n 'tel': 'phone',\n 'phone': 'phone',\n 'number': 'number',\n 'textarea': 'textarea',\n 'select': 'select',\n 'checkbox': 'checkbox',\n 'radio': 'radio',\n 'date': 'date',\n 'file': 'file',\n 'url': 'url',\n 'password': 'text', // Don't use password for managed forms\n }\n return mapping[type] || 'text'\n}\n\nfunction detectFormType(form: DetectedForm): string {\n const name = form.componentName.toLowerCase()\n const fields = form.fields.map(f => f.name.toLowerCase()).join(' ')\n\n if (name.includes('contact') || fields.includes('message')) return 'contact'\n if (name.includes('newsletter') || name.includes('subscribe')) return 'newsletter'\n if (name.includes('quote') || name.includes('estimate')) return 'prospect'\n if (name.includes('support') || name.includes('help')) return 'support'\n if (name.includes('feedback')) return 'feedback'\n\n return 'contact'\n}\n"]}
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cli/migrator/index.ts"],"names":[],"mappings":"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","file":"migrator-V6KS75EA.mjs","sourcesContent":["/**\n * Code Migrator - Transforms existing forms to Site-Kit managed forms\n */\n\nimport fs from 'fs/promises'\nimport path from 'path'\nimport { parse } from '@babel/parser'\nimport generate from '@babel/generator'\nimport traverse from '@babel/traverse'\nimport * as t from '@babel/types'\nimport type { ScanResults, DetectedForm, DetectedField } from '../scanner/index.js'\n\n// ============================================\n// Types\n// ============================================\n\nexport interface MigrationResult {\n filePath: string\n success: boolean\n changes: string[]\n error?: string\n formId?: string\n}\n\nexport interface MigrationOptions {\n projectId: string\n apiKey: string\n dryRun?: boolean\n}\n\n// ============================================\n// Main Migrator\n// ============================================\n\nexport async function migrateFiles(\n scanResults: ScanResults,\n options: MigrationOptions\n): Promise<MigrationResult[]> {\n const results: MigrationResult[] = []\n\n // Migrate forms\n for (const form of scanResults.forms) {\n if (form.suggestedAction === 'manual') {\n results.push({\n filePath: form.filePath,\n success: false,\n changes: [],\n error: 'Form too complex for auto-migration',\n })\n continue\n }\n\n try {\n const result = await migrateForm(form, options)\n results.push(result)\n } catch (error: any) {\n results.push({\n filePath: form.filePath,\n success: false,\n changes: [],\n error: error.message,\n })\n }\n }\n\n // Migrate widgets (simpler - just remove and add provider flag)\n for (const widget of scanResults.widgets) {\n try {\n const result = await migrateWidget(widget.filePath, widget, options)\n results.push(result)\n } catch (error: any) {\n results.push({\n filePath: widget.filePath,\n success: false,\n changes: [],\n error: error.message,\n })\n }\n }\n\n return results\n}\n\n// ============================================\n// Form Migration\n// ============================================\n\nasync function migrateForm(\n form: DetectedForm,\n options: MigrationOptions\n): Promise<MigrationResult> {\n const changes: string[] = []\n const fullPath = path.resolve(process.cwd(), form.filePath)\n\n // Step 1: Create the form in Uptrade\n const formSlug = generateSlug(form.componentName)\n const formId = await createFormInUptrade(form, formSlug, options)\n changes.push(`Created managed form: ${formSlug}`)\n\n if (options.dryRun) {\n changes.push('[DRY RUN] Would update file')\n return { filePath: form.filePath, success: true, changes, formId }\n }\n\n // Step 2: Read the file\n const content = await fs.readFile(fullPath, 'utf-8')\n \n // Step 3: Generate new code\n const newCode = generateMigratedFormCode(form, formSlug)\n\n // Step 4: Write the file\n await fs.writeFile(fullPath, newCode, 'utf-8')\n changes.push('Updated component to use useForm hook')\n\n return {\n filePath: form.filePath,\n success: true,\n changes,\n formId,\n }\n}\n\nfunction generateMigratedFormCode(form: DetectedForm, formSlug: string): string {\n // Generate a clean, migrated component\n const componentName = form.componentName || 'MigratedForm'\n\n return `/**\n * ${componentName}\n * \n * Migrated to @uptrade/site-kit\n * Managed form: ${formSlug}\n */\n\n'use client'\n\nimport { useForm } from '@uptrade/site-kit/forms'\n\nexport function ${componentName}({ className }: { className?: string }) {\n const { \n form,\n fields, \n values, \n errors, \n setFieldValue, \n submit, \n isSubmitting,\n isComplete \n } = useForm('${formSlug}')\n\n if (isComplete) {\n return (\n <div className={className}>\n <p className=\"text-green-600\">{form?.successMessage || 'Thanks for your submission!'}</p>\n </div>\n )\n }\n\n return (\n <form \n onSubmit={(e) => { e.preventDefault(); submit() }} \n className={className}\n >\n {fields.map(field => (\n <div key={field.slug} className=\"mb-4\">\n <label className=\"block text-sm font-medium mb-1\">\n {field.label}\n {field.isRequired && <span className=\"text-red-500 ml-1\">*</span>}\n </label>\n \n {field.fieldType === 'textarea' ? (\n <textarea\n name={field.slug}\n placeholder={field.placeholder}\n value={String(values[field.slug] || '')}\n onChange={(e) => setFieldValue(field.slug, e.target.value)}\n className=\"w-full p-2 border rounded focus:ring-2 focus:ring-blue-500 focus:border-transparent\"\n rows={4}\n />\n ) : field.fieldType === 'select' && field.options ? (\n <select\n name={field.slug}\n value={String(values[field.slug] || '')}\n onChange={(e) => setFieldValue(field.slug, e.target.value)}\n className=\"w-full p-2 border rounded focus:ring-2 focus:ring-blue-500 focus:border-transparent\"\n >\n <option value=\"\">Select...</option>\n {field.options.map(opt => (\n <option key={opt.value} value={opt.value}>{opt.label}</option>\n ))}\n </select>\n ) : (\n <input\n type={field.fieldType}\n name={field.slug}\n placeholder={field.placeholder}\n value={String(values[field.slug] || '')}\n onChange={(e) => setFieldValue(field.slug, e.target.value)}\n className=\"w-full p-2 border rounded focus:ring-2 focus:ring-blue-500 focus:border-transparent\"\n />\n )}\n \n {errors[field.slug] && (\n <p className=\"mt-1 text-sm text-red-500\">{errors[field.slug]}</p>\n )}\n \n {field.helpText && (\n <p className=\"mt-1 text-xs text-gray-500\">{field.helpText}</p>\n )}\n </div>\n ))}\n \n <button \n type=\"submit\" \n disabled={isSubmitting}\n className=\"w-full py-2 px-4 bg-blue-600 text-white rounded hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors\"\n >\n {isSubmitting ? 'Submitting...' : (form?.submitButtonText || 'Submit')}\n </button>\n </form>\n )\n}\n\nexport default ${componentName}\n`\n}\n\nasync function createFormInUptrade(\n form: DetectedForm,\n slug: string,\n options: MigrationOptions\n): Promise<string> {\n // Call Portal API to create the form\n const response = await fetch('https://api.uptrademedia.com/forms', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${options.apiKey}`,\n },\n body: JSON.stringify({\n projectId: options.projectId,\n slug,\n name: formatName(form.componentName),\n formType: detectFormType(form),\n successMessage: 'Thanks for your submission!',\n submitButtonText: 'Submit',\n fields: form.fields.map((f, i) => ({\n slug: f.name,\n label: formatLabel(f.name),\n fieldType: mapFieldType(f.type),\n placeholder: f.placeholder,\n isRequired: f.required,\n sortOrder: i,\n width: 'full',\n })),\n }),\n })\n\n if (!response.ok) {\n const error = await response.json()\n throw new Error(error.message || 'Failed to create form')\n }\n\n const data = await response.json()\n return data.id\n}\n\n// ============================================\n// Widget Migration\n// ============================================\n\nasync function migrateWidget(\n filePath: string,\n widget: any,\n options: MigrationOptions\n): Promise<MigrationResult> {\n const changes: string[] = []\n\n if (options.dryRun) {\n changes.push(`[DRY RUN] Would remove ${widget.widgetType} script`)\n changes.push('[DRY RUN] Would enable Engage in SiteKitProvider')\n return { filePath, success: true, changes }\n }\n\n const fullPath = path.resolve(process.cwd(), filePath)\n let content = await fs.readFile(fullPath, 'utf-8')\n\n // Remove the widget script based on type\n switch (widget.widgetType) {\n case 'intercom':\n content = content.replace(/<Script[^>]*intercom[^>]*\\/?>(?:<\\/Script>)?/gi, '{/* Intercom replaced with Uptrade Engage */}')\n content = content.replace(/window\\.Intercom\\s*=\\s*[^;]+;/g, '')\n break\n case 'crisp':\n content = content.replace(/<Script[^>]*crisp[^>]*\\/?>(?:<\\/Script>)?/gi, '{/* Crisp replaced with Uptrade Engage */}')\n break\n case 'drift':\n content = content.replace(/<Script[^>]*drift[^>]*\\/?>(?:<\\/Script>)?/gi, '{/* Drift replaced with Uptrade Engage */}')\n break\n }\n\n await fs.writeFile(fullPath, content, 'utf-8')\n changes.push(`Removed ${widget.widgetType} script`)\n changes.push('Enable Engage in SiteKitProvider to add chat widget')\n\n return { filePath, success: true, changes }\n}\n\n// ============================================\n// Helpers\n// ============================================\n\nfunction generateSlug(name: string): string {\n return name\n .replace(/([A-Z])/g, '-$1')\n .toLowerCase()\n .replace(/^-/, '')\n .replace(/form$/i, '')\n .replace(/-+/g, '-')\n .replace(/-$/, '') || 'form'\n}\n\nfunction formatName(name: string): string {\n return name\n .replace(/([A-Z])/g, ' $1')\n .replace(/^./, str => str.toUpperCase())\n .trim()\n}\n\nfunction formatLabel(name: string): string {\n return name\n .replace(/([A-Z])/g, ' $1')\n .replace(/_/g, ' ')\n .replace(/^./, str => str.toUpperCase())\n .trim()\n}\n\nfunction mapFieldType(type: string): string {\n const mapping: Record<string, string> = {\n 'text': 'text',\n 'email': 'email',\n 'tel': 'phone',\n 'phone': 'phone',\n 'number': 'number',\n 'textarea': 'textarea',\n 'select': 'select',\n 'checkbox': 'checkbox',\n 'radio': 'radio',\n 'date': 'date',\n 'file': 'file',\n 'url': 'url',\n 'password': 'text', // Don't use password for managed forms\n }\n return mapping[type] || 'text'\n}\n\nfunction detectFormType(form: DetectedForm): string {\n const name = form.componentName.toLowerCase()\n const fields = form.fields.map(f => f.name.toLowerCase()).join(' ')\n\n if (name.includes('contact') || fields.includes('message')) return 'contact'\n if (name.includes('newsletter') || name.includes('subscribe')) return 'newsletter'\n if (name.includes('quote') || name.includes('estimate')) return 'prospect'\n if (name.includes('support') || name.includes('help')) return 'support'\n if (name.includes('feedback')) return 'feedback'\n\n return 'contact'\n}\n"]}
@@ -0,0 +1,272 @@
1
+ 'use strict';
2
+
3
+ require('./chunk-EQCVQC35.js');
4
+ var fs = require('fs/promises');
5
+ var path = require('path');
6
+
7
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
8
+
9
+ var fs__default = /*#__PURE__*/_interopDefault(fs);
10
+ var path__default = /*#__PURE__*/_interopDefault(path);
11
+
12
+ async function migrateFiles(scanResults, options) {
13
+ const results = [];
14
+ for (const form of scanResults.forms) {
15
+ if (form.suggestedAction === "manual") {
16
+ results.push({
17
+ filePath: form.filePath,
18
+ success: false,
19
+ changes: [],
20
+ error: "Form too complex for auto-migration"
21
+ });
22
+ continue;
23
+ }
24
+ try {
25
+ const result = await migrateForm(form, options);
26
+ results.push(result);
27
+ } catch (error) {
28
+ results.push({
29
+ filePath: form.filePath,
30
+ success: false,
31
+ changes: [],
32
+ error: error.message
33
+ });
34
+ }
35
+ }
36
+ for (const widget of scanResults.widgets) {
37
+ try {
38
+ const result = await migrateWidget(widget.filePath, widget, options);
39
+ results.push(result);
40
+ } catch (error) {
41
+ results.push({
42
+ filePath: widget.filePath,
43
+ success: false,
44
+ changes: [],
45
+ error: error.message
46
+ });
47
+ }
48
+ }
49
+ return results;
50
+ }
51
+ async function migrateForm(form, options) {
52
+ const changes = [];
53
+ const fullPath = path__default.default.resolve(process.cwd(), form.filePath);
54
+ const formSlug = generateSlug(form.componentName);
55
+ const formId = await createFormInUptrade(form, formSlug, options);
56
+ changes.push(`Created managed form: ${formSlug}`);
57
+ if (options.dryRun) {
58
+ changes.push("[DRY RUN] Would update file");
59
+ return { filePath: form.filePath, success: true, changes, formId };
60
+ }
61
+ await fs__default.default.readFile(fullPath, "utf-8");
62
+ const newCode = generateMigratedFormCode(form, formSlug);
63
+ await fs__default.default.writeFile(fullPath, newCode, "utf-8");
64
+ changes.push("Updated component to use useForm hook");
65
+ return {
66
+ filePath: form.filePath,
67
+ success: true,
68
+ changes,
69
+ formId
70
+ };
71
+ }
72
+ function generateMigratedFormCode(form, formSlug) {
73
+ const componentName = form.componentName || "MigratedForm";
74
+ return `/**
75
+ * ${componentName}
76
+ *
77
+ * Migrated to @uptrade/site-kit
78
+ * Managed form: ${formSlug}
79
+ */
80
+
81
+ 'use client'
82
+
83
+ import { useForm } from '@uptrade/site-kit/forms'
84
+
85
+ export function ${componentName}({ className }: { className?: string }) {
86
+ const {
87
+ form,
88
+ fields,
89
+ values,
90
+ errors,
91
+ setFieldValue,
92
+ submit,
93
+ isSubmitting,
94
+ isComplete
95
+ } = useForm('${formSlug}')
96
+
97
+ if (isComplete) {
98
+ return (
99
+ <div className={className}>
100
+ <p className="text-green-600">{form?.successMessage || 'Thanks for your submission!'}</p>
101
+ </div>
102
+ )
103
+ }
104
+
105
+ return (
106
+ <form
107
+ onSubmit={(e) => { e.preventDefault(); submit() }}
108
+ className={className}
109
+ >
110
+ {fields.map(field => (
111
+ <div key={field.slug} className="mb-4">
112
+ <label className="block text-sm font-medium mb-1">
113
+ {field.label}
114
+ {field.isRequired && <span className="text-red-500 ml-1">*</span>}
115
+ </label>
116
+
117
+ {field.fieldType === 'textarea' ? (
118
+ <textarea
119
+ name={field.slug}
120
+ placeholder={field.placeholder}
121
+ value={String(values[field.slug] || '')}
122
+ onChange={(e) => setFieldValue(field.slug, e.target.value)}
123
+ className="w-full p-2 border rounded focus:ring-2 focus:ring-blue-500 focus:border-transparent"
124
+ rows={4}
125
+ />
126
+ ) : field.fieldType === 'select' && field.options ? (
127
+ <select
128
+ name={field.slug}
129
+ value={String(values[field.slug] || '')}
130
+ onChange={(e) => setFieldValue(field.slug, e.target.value)}
131
+ className="w-full p-2 border rounded focus:ring-2 focus:ring-blue-500 focus:border-transparent"
132
+ >
133
+ <option value="">Select...</option>
134
+ {field.options.map(opt => (
135
+ <option key={opt.value} value={opt.value}>{opt.label}</option>
136
+ ))}
137
+ </select>
138
+ ) : (
139
+ <input
140
+ type={field.fieldType}
141
+ name={field.slug}
142
+ placeholder={field.placeholder}
143
+ value={String(values[field.slug] || '')}
144
+ onChange={(e) => setFieldValue(field.slug, e.target.value)}
145
+ className="w-full p-2 border rounded focus:ring-2 focus:ring-blue-500 focus:border-transparent"
146
+ />
147
+ )}
148
+
149
+ {errors[field.slug] && (
150
+ <p className="mt-1 text-sm text-red-500">{errors[field.slug]}</p>
151
+ )}
152
+
153
+ {field.helpText && (
154
+ <p className="mt-1 text-xs text-gray-500">{field.helpText}</p>
155
+ )}
156
+ </div>
157
+ ))}
158
+
159
+ <button
160
+ type="submit"
161
+ disabled={isSubmitting}
162
+ className="w-full py-2 px-4 bg-blue-600 text-white rounded hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
163
+ >
164
+ {isSubmitting ? 'Submitting...' : (form?.submitButtonText || 'Submit')}
165
+ </button>
166
+ </form>
167
+ )
168
+ }
169
+
170
+ export default ${componentName}
171
+ `;
172
+ }
173
+ async function createFormInUptrade(form, slug, options) {
174
+ const response = await fetch("https://api.uptrademedia.com/forms", {
175
+ method: "POST",
176
+ headers: {
177
+ "Content-Type": "application/json",
178
+ "Authorization": `Bearer ${options.apiKey}`
179
+ },
180
+ body: JSON.stringify({
181
+ projectId: options.projectId,
182
+ slug,
183
+ name: formatName(form.componentName),
184
+ formType: detectFormType(form),
185
+ successMessage: "Thanks for your submission!",
186
+ submitButtonText: "Submit",
187
+ fields: form.fields.map((f, i) => ({
188
+ slug: f.name,
189
+ label: formatLabel(f.name),
190
+ fieldType: mapFieldType(f.type),
191
+ placeholder: f.placeholder,
192
+ isRequired: f.required,
193
+ sortOrder: i,
194
+ width: "full"
195
+ }))
196
+ })
197
+ });
198
+ if (!response.ok) {
199
+ const error = await response.json();
200
+ throw new Error(error.message || "Failed to create form");
201
+ }
202
+ const data = await response.json();
203
+ return data.id;
204
+ }
205
+ async function migrateWidget(filePath, widget, options) {
206
+ const changes = [];
207
+ if (options.dryRun) {
208
+ changes.push(`[DRY RUN] Would remove ${widget.widgetType} script`);
209
+ changes.push("[DRY RUN] Would enable Engage in SiteKitProvider");
210
+ return { filePath, success: true, changes };
211
+ }
212
+ const fullPath = path__default.default.resolve(process.cwd(), filePath);
213
+ let content = await fs__default.default.readFile(fullPath, "utf-8");
214
+ switch (widget.widgetType) {
215
+ case "intercom":
216
+ content = content.replace(/<Script[^>]*intercom[^>]*\/?>(?:<\/Script>)?/gi, "{/* Intercom replaced with Uptrade Engage */}");
217
+ content = content.replace(/window\.Intercom\s*=\s*[^;]+;/g, "");
218
+ break;
219
+ case "crisp":
220
+ content = content.replace(/<Script[^>]*crisp[^>]*\/?>(?:<\/Script>)?/gi, "{/* Crisp replaced with Uptrade Engage */}");
221
+ break;
222
+ case "drift":
223
+ content = content.replace(/<Script[^>]*drift[^>]*\/?>(?:<\/Script>)?/gi, "{/* Drift replaced with Uptrade Engage */}");
224
+ break;
225
+ }
226
+ await fs__default.default.writeFile(fullPath, content, "utf-8");
227
+ changes.push(`Removed ${widget.widgetType} script`);
228
+ changes.push("Enable Engage in SiteKitProvider to add chat widget");
229
+ return { filePath, success: true, changes };
230
+ }
231
+ function generateSlug(name) {
232
+ return name.replace(/([A-Z])/g, "-$1").toLowerCase().replace(/^-/, "").replace(/form$/i, "").replace(/-+/g, "-").replace(/-$/, "") || "form";
233
+ }
234
+ function formatName(name) {
235
+ return name.replace(/([A-Z])/g, " $1").replace(/^./, (str) => str.toUpperCase()).trim();
236
+ }
237
+ function formatLabel(name) {
238
+ return name.replace(/([A-Z])/g, " $1").replace(/_/g, " ").replace(/^./, (str) => str.toUpperCase()).trim();
239
+ }
240
+ function mapFieldType(type) {
241
+ const mapping = {
242
+ "text": "text",
243
+ "email": "email",
244
+ "tel": "phone",
245
+ "phone": "phone",
246
+ "number": "number",
247
+ "textarea": "textarea",
248
+ "select": "select",
249
+ "checkbox": "checkbox",
250
+ "radio": "radio",
251
+ "date": "date",
252
+ "file": "file",
253
+ "url": "url",
254
+ "password": "text"
255
+ // Don't use password for managed forms
256
+ };
257
+ return mapping[type] || "text";
258
+ }
259
+ function detectFormType(form) {
260
+ const name = form.componentName.toLowerCase();
261
+ const fields = form.fields.map((f) => f.name.toLowerCase()).join(" ");
262
+ if (name.includes("contact") || fields.includes("message")) return "contact";
263
+ if (name.includes("newsletter") || name.includes("subscribe")) return "newsletter";
264
+ if (name.includes("quote") || name.includes("estimate")) return "prospect";
265
+ if (name.includes("support") || name.includes("help")) return "support";
266
+ if (name.includes("feedback")) return "feedback";
267
+ return "contact";
268
+ }
269
+
270
+ exports.migrateFiles = migrateFiles;
271
+ //# sourceMappingURL=migrator-XKM7YQCY.js.map
272
+ //# sourceMappingURL=migrator-XKM7YQCY.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cli/migrator/index.ts"],"names":["path","fs"],"mappings":"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","file":"migrator-XKM7YQCY.js","sourcesContent":["/**\n * Code Migrator - Transforms existing forms to Site-Kit managed forms\n */\n\nimport fs from 'fs/promises'\nimport path from 'path'\nimport { parse } from '@babel/parser'\nimport generate from '@babel/generator'\nimport traverse from '@babel/traverse'\nimport * as t from '@babel/types'\nimport type { ScanResults, DetectedForm, DetectedField } from '../scanner/index.js'\n\n// ============================================\n// Types\n// ============================================\n\nexport interface MigrationResult {\n filePath: string\n success: boolean\n changes: string[]\n error?: string\n formId?: string\n}\n\nexport interface MigrationOptions {\n projectId: string\n apiKey: string\n dryRun?: boolean\n}\n\n// ============================================\n// Main Migrator\n// ============================================\n\nexport async function migrateFiles(\n scanResults: ScanResults,\n options: MigrationOptions\n): Promise<MigrationResult[]> {\n const results: MigrationResult[] = []\n\n // Migrate forms\n for (const form of scanResults.forms) {\n if (form.suggestedAction === 'manual') {\n results.push({\n filePath: form.filePath,\n success: false,\n changes: [],\n error: 'Form too complex for auto-migration',\n })\n continue\n }\n\n try {\n const result = await migrateForm(form, options)\n results.push(result)\n } catch (error: any) {\n results.push({\n filePath: form.filePath,\n success: false,\n changes: [],\n error: error.message,\n })\n }\n }\n\n // Migrate widgets (simpler - just remove and add provider flag)\n for (const widget of scanResults.widgets) {\n try {\n const result = await migrateWidget(widget.filePath, widget, options)\n results.push(result)\n } catch (error: any) {\n results.push({\n filePath: widget.filePath,\n success: false,\n changes: [],\n error: error.message,\n })\n }\n }\n\n return results\n}\n\n// ============================================\n// Form Migration\n// ============================================\n\nasync function migrateForm(\n form: DetectedForm,\n options: MigrationOptions\n): Promise<MigrationResult> {\n const changes: string[] = []\n const fullPath = path.resolve(process.cwd(), form.filePath)\n\n // Step 1: Create the form in Uptrade\n const formSlug = generateSlug(form.componentName)\n const formId = await createFormInUptrade(form, formSlug, options)\n changes.push(`Created managed form: ${formSlug}`)\n\n if (options.dryRun) {\n changes.push('[DRY RUN] Would update file')\n return { filePath: form.filePath, success: true, changes, formId }\n }\n\n // Step 2: Read the file\n const content = await fs.readFile(fullPath, 'utf-8')\n \n // Step 3: Generate new code\n const newCode = generateMigratedFormCode(form, formSlug)\n\n // Step 4: Write the file\n await fs.writeFile(fullPath, newCode, 'utf-8')\n changes.push('Updated component to use useForm hook')\n\n return {\n filePath: form.filePath,\n success: true,\n changes,\n formId,\n }\n}\n\nfunction generateMigratedFormCode(form: DetectedForm, formSlug: string): string {\n // Generate a clean, migrated component\n const componentName = form.componentName || 'MigratedForm'\n\n return `/**\n * ${componentName}\n * \n * Migrated to @uptrade/site-kit\n * Managed form: ${formSlug}\n */\n\n'use client'\n\nimport { useForm } from '@uptrade/site-kit/forms'\n\nexport function ${componentName}({ className }: { className?: string }) {\n const { \n form,\n fields, \n values, \n errors, \n setFieldValue, \n submit, \n isSubmitting,\n isComplete \n } = useForm('${formSlug}')\n\n if (isComplete) {\n return (\n <div className={className}>\n <p className=\"text-green-600\">{form?.successMessage || 'Thanks for your submission!'}</p>\n </div>\n )\n }\n\n return (\n <form \n onSubmit={(e) => { e.preventDefault(); submit() }} \n className={className}\n >\n {fields.map(field => (\n <div key={field.slug} className=\"mb-4\">\n <label className=\"block text-sm font-medium mb-1\">\n {field.label}\n {field.isRequired && <span className=\"text-red-500 ml-1\">*</span>}\n </label>\n \n {field.fieldType === 'textarea' ? (\n <textarea\n name={field.slug}\n placeholder={field.placeholder}\n value={String(values[field.slug] || '')}\n onChange={(e) => setFieldValue(field.slug, e.target.value)}\n className=\"w-full p-2 border rounded focus:ring-2 focus:ring-blue-500 focus:border-transparent\"\n rows={4}\n />\n ) : field.fieldType === 'select' && field.options ? (\n <select\n name={field.slug}\n value={String(values[field.slug] || '')}\n onChange={(e) => setFieldValue(field.slug, e.target.value)}\n className=\"w-full p-2 border rounded focus:ring-2 focus:ring-blue-500 focus:border-transparent\"\n >\n <option value=\"\">Select...</option>\n {field.options.map(opt => (\n <option key={opt.value} value={opt.value}>{opt.label}</option>\n ))}\n </select>\n ) : (\n <input\n type={field.fieldType}\n name={field.slug}\n placeholder={field.placeholder}\n value={String(values[field.slug] || '')}\n onChange={(e) => setFieldValue(field.slug, e.target.value)}\n className=\"w-full p-2 border rounded focus:ring-2 focus:ring-blue-500 focus:border-transparent\"\n />\n )}\n \n {errors[field.slug] && (\n <p className=\"mt-1 text-sm text-red-500\">{errors[field.slug]}</p>\n )}\n \n {field.helpText && (\n <p className=\"mt-1 text-xs text-gray-500\">{field.helpText}</p>\n )}\n </div>\n ))}\n \n <button \n type=\"submit\" \n disabled={isSubmitting}\n className=\"w-full py-2 px-4 bg-blue-600 text-white rounded hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors\"\n >\n {isSubmitting ? 'Submitting...' : (form?.submitButtonText || 'Submit')}\n </button>\n </form>\n )\n}\n\nexport default ${componentName}\n`\n}\n\nasync function createFormInUptrade(\n form: DetectedForm,\n slug: string,\n options: MigrationOptions\n): Promise<string> {\n // Call Portal API to create the form\n const response = await fetch('https://api.uptrademedia.com/forms', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${options.apiKey}`,\n },\n body: JSON.stringify({\n projectId: options.projectId,\n slug,\n name: formatName(form.componentName),\n formType: detectFormType(form),\n successMessage: 'Thanks for your submission!',\n submitButtonText: 'Submit',\n fields: form.fields.map((f, i) => ({\n slug: f.name,\n label: formatLabel(f.name),\n fieldType: mapFieldType(f.type),\n placeholder: f.placeholder,\n isRequired: f.required,\n sortOrder: i,\n width: 'full',\n })),\n }),\n })\n\n if (!response.ok) {\n const error = await response.json()\n throw new Error(error.message || 'Failed to create form')\n }\n\n const data = await response.json()\n return data.id\n}\n\n// ============================================\n// Widget Migration\n// ============================================\n\nasync function migrateWidget(\n filePath: string,\n widget: any,\n options: MigrationOptions\n): Promise<MigrationResult> {\n const changes: string[] = []\n\n if (options.dryRun) {\n changes.push(`[DRY RUN] Would remove ${widget.widgetType} script`)\n changes.push('[DRY RUN] Would enable Engage in SiteKitProvider')\n return { filePath, success: true, changes }\n }\n\n const fullPath = path.resolve(process.cwd(), filePath)\n let content = await fs.readFile(fullPath, 'utf-8')\n\n // Remove the widget script based on type\n switch (widget.widgetType) {\n case 'intercom':\n content = content.replace(/<Script[^>]*intercom[^>]*\\/?>(?:<\\/Script>)?/gi, '{/* Intercom replaced with Uptrade Engage */}')\n content = content.replace(/window\\.Intercom\\s*=\\s*[^;]+;/g, '')\n break\n case 'crisp':\n content = content.replace(/<Script[^>]*crisp[^>]*\\/?>(?:<\\/Script>)?/gi, '{/* Crisp replaced with Uptrade Engage */}')\n break\n case 'drift':\n content = content.replace(/<Script[^>]*drift[^>]*\\/?>(?:<\\/Script>)?/gi, '{/* Drift replaced with Uptrade Engage */}')\n break\n }\n\n await fs.writeFile(fullPath, content, 'utf-8')\n changes.push(`Removed ${widget.widgetType} script`)\n changes.push('Enable Engage in SiteKitProvider to add chat widget')\n\n return { filePath, success: true, changes }\n}\n\n// ============================================\n// Helpers\n// ============================================\n\nfunction generateSlug(name: string): string {\n return name\n .replace(/([A-Z])/g, '-$1')\n .toLowerCase()\n .replace(/^-/, '')\n .replace(/form$/i, '')\n .replace(/-+/g, '-')\n .replace(/-$/, '') || 'form'\n}\n\nfunction formatName(name: string): string {\n return name\n .replace(/([A-Z])/g, ' $1')\n .replace(/^./, str => str.toUpperCase())\n .trim()\n}\n\nfunction formatLabel(name: string): string {\n return name\n .replace(/([A-Z])/g, ' $1')\n .replace(/_/g, ' ')\n .replace(/^./, str => str.toUpperCase())\n .trim()\n}\n\nfunction mapFieldType(type: string): string {\n const mapping: Record<string, string> = {\n 'text': 'text',\n 'email': 'email',\n 'tel': 'phone',\n 'phone': 'phone',\n 'number': 'number',\n 'textarea': 'textarea',\n 'select': 'select',\n 'checkbox': 'checkbox',\n 'radio': 'radio',\n 'date': 'date',\n 'file': 'file',\n 'url': 'url',\n 'password': 'text', // Don't use password for managed forms\n }\n return mapping[type] || 'text'\n}\n\nfunction detectFormType(form: DetectedForm): string {\n const name = form.componentName.toLowerCase()\n const fields = form.fields.map(f => f.name.toLowerCase()).join(' ')\n\n if (name.includes('contact') || fields.includes('message')) return 'contact'\n if (name.includes('newsletter') || name.includes('subscribe')) return 'newsletter'\n if (name.includes('quote') || name.includes('estimate')) return 'prospect'\n if (name.includes('support') || name.includes('help')) return 'support'\n if (name.includes('feedback')) return 'feedback'\n\n return 'contact'\n}\n"]}