lwc-convert 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 (117) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +719 -0
  3. package/dist/cli/commands/aura.d.ts +6 -0
  4. package/dist/cli/commands/aura.d.ts.map +1 -0
  5. package/dist/cli/commands/aura.js +225 -0
  6. package/dist/cli/commands/aura.js.map +1 -0
  7. package/dist/cli/commands/vf.d.ts +6 -0
  8. package/dist/cli/commands/vf.d.ts.map +1 -0
  9. package/dist/cli/commands/vf.js +218 -0
  10. package/dist/cli/commands/vf.js.map +1 -0
  11. package/dist/cli/interactive.d.ts +20 -0
  12. package/dist/cli/interactive.d.ts.map +1 -0
  13. package/dist/cli/interactive.js +577 -0
  14. package/dist/cli/interactive.js.map +1 -0
  15. package/dist/cli/options.d.ts +21 -0
  16. package/dist/cli/options.d.ts.map +1 -0
  17. package/dist/cli/options.js +24 -0
  18. package/dist/cli/options.js.map +1 -0
  19. package/dist/generators/full-conversion.d.ts +41 -0
  20. package/dist/generators/full-conversion.d.ts.map +1 -0
  21. package/dist/generators/full-conversion.js +538 -0
  22. package/dist/generators/full-conversion.js.map +1 -0
  23. package/dist/generators/scaffolding.d.ts +40 -0
  24. package/dist/generators/scaffolding.d.ts.map +1 -0
  25. package/dist/generators/scaffolding.js +716 -0
  26. package/dist/generators/scaffolding.js.map +1 -0
  27. package/dist/generators/test-comparison.d.ts +47 -0
  28. package/dist/generators/test-comparison.d.ts.map +1 -0
  29. package/dist/generators/test-comparison.js +855 -0
  30. package/dist/generators/test-comparison.js.map +1 -0
  31. package/dist/generators/test-generator.d.ts +27 -0
  32. package/dist/generators/test-generator.d.ts.map +1 -0
  33. package/dist/generators/test-generator.js +385 -0
  34. package/dist/generators/test-generator.js.map +1 -0
  35. package/dist/index.d.ts +6 -0
  36. package/dist/index.d.ts.map +1 -0
  37. package/dist/index.js +226 -0
  38. package/dist/index.js.map +1 -0
  39. package/dist/mappings/aura-to-lwc.json +321 -0
  40. package/dist/mappings/vf-to-lwc.json +354 -0
  41. package/dist/parsers/aura/controller-parser.d.ts +36 -0
  42. package/dist/parsers/aura/controller-parser.d.ts.map +1 -0
  43. package/dist/parsers/aura/controller-parser.js +269 -0
  44. package/dist/parsers/aura/controller-parser.js.map +1 -0
  45. package/dist/parsers/aura/helper-parser.d.ts +21 -0
  46. package/dist/parsers/aura/helper-parser.d.ts.map +1 -0
  47. package/dist/parsers/aura/helper-parser.js +173 -0
  48. package/dist/parsers/aura/helper-parser.js.map +1 -0
  49. package/dist/parsers/aura/markup-parser.d.ts +59 -0
  50. package/dist/parsers/aura/markup-parser.d.ts.map +1 -0
  51. package/dist/parsers/aura/markup-parser.js +279 -0
  52. package/dist/parsers/aura/markup-parser.js.map +1 -0
  53. package/dist/parsers/aura/style-parser.d.ts +37 -0
  54. package/dist/parsers/aura/style-parser.d.ts.map +1 -0
  55. package/dist/parsers/aura/style-parser.js +151 -0
  56. package/dist/parsers/aura/style-parser.js.map +1 -0
  57. package/dist/parsers/vf/apex-parser.d.ts +51 -0
  58. package/dist/parsers/vf/apex-parser.d.ts.map +1 -0
  59. package/dist/parsers/vf/apex-parser.js +251 -0
  60. package/dist/parsers/vf/apex-parser.js.map +1 -0
  61. package/dist/parsers/vf/page-parser.d.ts +61 -0
  62. package/dist/parsers/vf/page-parser.d.ts.map +1 -0
  63. package/dist/parsers/vf/page-parser.js +403 -0
  64. package/dist/parsers/vf/page-parser.js.map +1 -0
  65. package/dist/transformers/aura-to-lwc/controller.d.ts +36 -0
  66. package/dist/transformers/aura-to-lwc/controller.d.ts.map +1 -0
  67. package/dist/transformers/aura-to-lwc/controller.js +372 -0
  68. package/dist/transformers/aura-to-lwc/controller.js.map +1 -0
  69. package/dist/transformers/aura-to-lwc/events.d.ts +47 -0
  70. package/dist/transformers/aura-to-lwc/events.d.ts.map +1 -0
  71. package/dist/transformers/aura-to-lwc/events.js +262 -0
  72. package/dist/transformers/aura-to-lwc/events.js.map +1 -0
  73. package/dist/transformers/aura-to-lwc/markup.d.ts +51 -0
  74. package/dist/transformers/aura-to-lwc/markup.d.ts.map +1 -0
  75. package/dist/transformers/aura-to-lwc/markup.js +465 -0
  76. package/dist/transformers/aura-to-lwc/markup.js.map +1 -0
  77. package/dist/transformers/vf-to-lwc/components.d.ts +40 -0
  78. package/dist/transformers/vf-to-lwc/components.d.ts.map +1 -0
  79. package/dist/transformers/vf-to-lwc/components.js +374 -0
  80. package/dist/transformers/vf-to-lwc/components.js.map +1 -0
  81. package/dist/transformers/vf-to-lwc/data-binding.d.ts +53 -0
  82. package/dist/transformers/vf-to-lwc/data-binding.d.ts.map +1 -0
  83. package/dist/transformers/vf-to-lwc/data-binding.js +660 -0
  84. package/dist/transformers/vf-to-lwc/data-binding.js.map +1 -0
  85. package/dist/transformers/vf-to-lwc/markup.d.ts +44 -0
  86. package/dist/transformers/vf-to-lwc/markup.d.ts.map +1 -0
  87. package/dist/transformers/vf-to-lwc/markup.js +816 -0
  88. package/dist/transformers/vf-to-lwc/markup.js.map +1 -0
  89. package/dist/utils/confidence-scorer.d.ts +100 -0
  90. package/dist/utils/confidence-scorer.d.ts.map +1 -0
  91. package/dist/utils/confidence-scorer.js +358 -0
  92. package/dist/utils/confidence-scorer.js.map +1 -0
  93. package/dist/utils/file-io.d.ts +62 -0
  94. package/dist/utils/file-io.d.ts.map +1 -0
  95. package/dist/utils/file-io.js +248 -0
  96. package/dist/utils/file-io.js.map +1 -0
  97. package/dist/utils/logger.d.ts +34 -0
  98. package/dist/utils/logger.d.ts.map +1 -0
  99. package/dist/utils/logger.js +130 -0
  100. package/dist/utils/logger.js.map +1 -0
  101. package/dist/utils/open-folder.d.ts +9 -0
  102. package/dist/utils/open-folder.d.ts.map +1 -0
  103. package/dist/utils/open-folder.js +76 -0
  104. package/dist/utils/open-folder.js.map +1 -0
  105. package/dist/utils/path-resolver.d.ts +29 -0
  106. package/dist/utils/path-resolver.d.ts.map +1 -0
  107. package/dist/utils/path-resolver.js +240 -0
  108. package/dist/utils/path-resolver.js.map +1 -0
  109. package/dist/utils/session-store.d.ts +158 -0
  110. package/dist/utils/session-store.d.ts.map +1 -0
  111. package/dist/utils/session-store.js +518 -0
  112. package/dist/utils/session-store.js.map +1 -0
  113. package/dist/utils/vf-controller-resolver.d.ts +36 -0
  114. package/dist/utils/vf-controller-resolver.d.ts.map +1 -0
  115. package/dist/utils/vf-controller-resolver.js +162 -0
  116. package/dist/utils/vf-controller-resolver.js.map +1 -0
  117. package/package.json +81 -0
@@ -0,0 +1,716 @@
1
+ "use strict";
2
+ /**
3
+ * Generate LWC scaffolding with TODO comments from Aura or VF components
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.generateAuraScaffolding = generateAuraScaffolding;
7
+ exports.generateVfScaffolding = generateVfScaffolding;
8
+ const style_parser_1 = require("../parsers/aura/style-parser");
9
+ const data_binding_1 = require("../transformers/vf-to-lwc/data-binding");
10
+ const test_generator_1 = require("./test-generator");
11
+ const test_comparison_1 = require("./test-comparison");
12
+ const file_io_1 = require("../utils/file-io");
13
+ const logger_1 = require("../utils/logger");
14
+ /**
15
+ * Generate LWC meta XML file content
16
+ */
17
+ function generateMetaXml(_componentName, options) {
18
+ const { apiVersion = '59.0', isExposed = true, targets = ['lightning__RecordPage', 'lightning__AppPage', 'lightning__HomePage'], description, } = options || {};
19
+ let xml = `<?xml version="1.0" encoding="UTF-8"?>
20
+ <LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
21
+ <apiVersion>${apiVersion}</apiVersion>
22
+ <isExposed>${isExposed}</isExposed>`;
23
+ if (description) {
24
+ xml += `\n <description>${description}</description>`;
25
+ }
26
+ if (targets.length > 0) {
27
+ xml += `\n <targets>`;
28
+ for (const target of targets) {
29
+ xml += `\n <target>${target}</target>`;
30
+ }
31
+ xml += `\n </targets>`;
32
+ }
33
+ xml += `\n</LightningComponentBundle>\n`;
34
+ return xml;
35
+ }
36
+ /**
37
+ * Generate scaffolding JS from Aura parsed data
38
+ */
39
+ function generateAuraScaffoldingJs(markup, transformedMarkup, controller, helper) {
40
+ const warnings = [];
41
+ const className = (0, file_io_1.toPascalCase)(markup.componentName);
42
+ const imports = ['LightningElement'];
43
+ const wireImports = [];
44
+ const lmsImports = [];
45
+ const schemaImports = [];
46
+ const channelImports = [];
47
+ const apiProperties = [];
48
+ const reactiveProperties = [];
49
+ const wireDeclarations = [];
50
+ const methodStubs = [];
51
+ const apexImports = [];
52
+ // Track which attributes are used by force:recordData (these should be private)
53
+ const recordDataBindings = new Set();
54
+ for (const rds of transformedMarkup.recordDataServices) {
55
+ recordDataBindings.add(rds.recordIdBinding);
56
+ if (rds.targetFields)
57
+ recordDataBindings.add(rds.targetFields);
58
+ }
59
+ // Process attributes -> properties
60
+ for (const attr of markup.attributes) {
61
+ // Check if this attribute is used internally by force:recordData or LMS
62
+ const usedByRecordData = recordDataBindings.has(attr.name);
63
+ const isPublic = (!attr.access || attr.access === 'public' || attr.access === 'global') && !usedByRecordData;
64
+ let propCode = '';
65
+ if (attr.description) {
66
+ propCode += ` // ${attr.description}\n`;
67
+ }
68
+ if (isPublic) {
69
+ propCode += ` @api ${attr.name}`;
70
+ if (!imports.includes('api'))
71
+ imports.push('api');
72
+ }
73
+ else {
74
+ // Private property - don't use @api
75
+ propCode += ` ${attr.name}`;
76
+ }
77
+ // Only add default value if it exists and is not empty
78
+ if (attr.default !== undefined && attr.default !== '' && attr.default.trim() !== '') {
79
+ propCode += ` = ${attr.default}`;
80
+ }
81
+ propCode += ';';
82
+ if (isPublic) {
83
+ apiProperties.push(propCode);
84
+ }
85
+ else {
86
+ reactiveProperties.push(propCode);
87
+ }
88
+ }
89
+ // Generate LMS code from lightning:messageChannel
90
+ if (transformedMarkup.lmsChannels.length > 0) {
91
+ if (!imports.includes('wire'))
92
+ imports.push('wire');
93
+ for (const lms of transformedMarkup.lmsChannels) {
94
+ const channelVar = lms.channelName.replace(/__c$/i, '').toUpperCase() + '_CHANNEL';
95
+ channelImports.push(`import ${channelVar} from '@salesforce/messageChannel/${lms.channelName}';`);
96
+ // Add wire for message context (needed for both publish and subscribe)
97
+ wireDeclarations.push(` @wire(MessageContext)
98
+ messageContext;`);
99
+ // Check if this is publisher-only (no onMessage handler)
100
+ if (lms.isPublisherOnly) {
101
+ // Publisher-only pattern - just need publish(), no subscribe/unsubscribe
102
+ lmsImports.push('publish', 'MessageContext');
103
+ // Add publish method
104
+ methodStubs.push(` // LMS: Publish to ${lms.channelName}
105
+ // Call this method to publish a message, e.g., from an onclick handler
106
+ publishMessage(payload) {
107
+ const message = {
108
+ recordId: payload.recordId,
109
+ // TODO: Add other message fields as needed
110
+ };
111
+ publish(this.messageContext, ${channelVar}, message);
112
+ }`);
113
+ }
114
+ else {
115
+ // Subscriber pattern - needs full subscribe/unsubscribe lifecycle
116
+ lmsImports.push('publish', 'subscribe', 'unsubscribe', 'MessageContext');
117
+ // Add subscription property
118
+ reactiveProperties.push(` // LMS subscription reference
119
+ _subscription = null;`);
120
+ // Generate handleMessage with converted controller logic
121
+ let handleMessageBody = '// TODO: Process message payload';
122
+ if (lms.onMessageHandler && controller) {
123
+ const handlerFunc = controller.functions.find(f => f.name === lms.onMessageHandler);
124
+ if (handlerFunc) {
125
+ // Convert Aura controller code to LWC
126
+ handleMessageBody = convertAuraControllerToLwc(handlerFunc, markup);
127
+ }
128
+ }
129
+ // Add LMS lifecycle methods for subscriber
130
+ methodStubs.push(` // LMS: Subscribe to ${lms.channelName}
131
+ connectedCallback() {
132
+ this.subscribeToMessageChannel();
133
+ }
134
+
135
+ disconnectedCallback() {
136
+ this.unsubscribeFromMessageChannel();
137
+ }
138
+
139
+ subscribeToMessageChannel() {
140
+ if (!this._subscription) {
141
+ this._subscription = subscribe(
142
+ this.messageContext,
143
+ ${channelVar},
144
+ (message) => this.handleMessage(message)
145
+ );
146
+ }
147
+ }
148
+
149
+ unsubscribeFromMessageChannel() {
150
+ unsubscribe(this._subscription);
151
+ this._subscription = null;
152
+ }
153
+
154
+ handleMessage(message) {
155
+ ${handleMessageBody}
156
+ }`);
157
+ }
158
+ }
159
+ }
160
+ // Generate @wire(getRecord) from force:recordData
161
+ if (transformedMarkup.recordDataServices.length > 0) {
162
+ wireImports.push('getRecord', 'getFieldValue');
163
+ if (!imports.includes('wire'))
164
+ imports.push('wire');
165
+ for (const rds of transformedMarkup.recordDataServices) {
166
+ const targetProp = rds.targetFields || 'record';
167
+ // Generate field constants and imports (active, not commented)
168
+ const fieldConstants = [];
169
+ const fieldImportStatements = [];
170
+ for (const field of rds.fields) {
171
+ const fieldConst = field.toUpperCase().replace(/[^A-Z0-9_]/g, '_') + '_FIELD';
172
+ // Use Contact as default - can be changed based on context
173
+ fieldImportStatements.push(`import ${fieldConst} from '@salesforce/schema/Contact.${field}';`);
174
+ fieldConstants.push(fieldConst);
175
+ }
176
+ // Add the imports to schema imports
177
+ schemaImports.push(...fieldImportStatements);
178
+ // Generate wire adapter with proper field constants
179
+ const fieldsArray = fieldConstants.join(', ');
180
+ // Generate individual getters for each field (proper LWC pattern)
181
+ const fieldGetters = rds.fields.map((field, index) => {
182
+ const getterName = field.charAt(0).toLowerCase() + field.slice(1).replace(/__c$/i, '');
183
+ const fieldConst = fieldConstants[index];
184
+ return ` get ${getterName}() {
185
+ return this.${targetProp}?.data
186
+ ? getFieldValue(this.${targetProp}.data, ${fieldConst})
187
+ : undefined;
188
+ }`;
189
+ }).join('\n\n');
190
+ wireDeclarations.push(` // Converted from force:recordData (aura:id="${rds.auraId}")
191
+ @wire(getRecord, { recordId: '$${rds.recordIdBinding}', fields: [${fieldsArray}] })
192
+ ${targetProp};
193
+
194
+ // Error getter for wire errors
195
+ get ${targetProp}Error() {
196
+ return this.${targetProp}?.error;
197
+ }
198
+
199
+ ${fieldGetters}`);
200
+ }
201
+ }
202
+ // Process handlers for lifecycle and event methods
203
+ // Check for LMS subscriber lifecycle (subscriber pattern uses connected/disconnected)
204
+ const hasLmsSubscriberLifecycle = transformedMarkup.lmsChannels.some(lms => !lms.isPublisherOnly);
205
+ const initHandler = markup.handlers.find((h) => h.name === 'init');
206
+ const renderHandler = markup.handlers.find((h) => h.name === 'render' || h.name === 'afterRender');
207
+ const destroyHandler = markup.handlers.find((h) => h.name === 'destroy');
208
+ // Find controller functions referenced by handlers
209
+ const controllerFunctions = new Map();
210
+ if (controller) {
211
+ for (const func of controller.functions) {
212
+ controllerFunctions.set(func.name, func.body);
213
+ }
214
+ }
215
+ // Check if init handler makes Apex calls - if so, convert to @wire instead of connectedCallback
216
+ let initHandlerHasApexCalls = false;
217
+ let initApexMethods = [];
218
+ if (initHandler && controller) {
219
+ const funcName = initHandler.action.replace('{!c.', '').replace('}', '');
220
+ const initFunc = controller.functions.find(f => f.name === funcName);
221
+ if (initFunc && initFunc.serverCalls.length > 0) {
222
+ initHandlerHasApexCalls = true;
223
+ initApexMethods = initFunc.serverCalls
224
+ .filter(sc => sc.controllerMethod)
225
+ .map(sc => sc.controllerMethod);
226
+ }
227
+ }
228
+ // If init handler calls Apex, generate @wire adapter instead of connectedCallback
229
+ if (initHandlerHasApexCalls && initApexMethods.length > 0) {
230
+ for (const apexMethod of initApexMethods) {
231
+ // Generate wire adapter for the Apex call
232
+ const wireProp = apexMethod.charAt(0).toLowerCase() + apexMethod.slice(1) + 'Result';
233
+ const dataProperty = apexMethod.charAt(0).toLowerCase() + apexMethod.slice(1);
234
+ // Add the Apex wire import
235
+ if (markup.controller) {
236
+ apexImports.push(apexMethod);
237
+ }
238
+ wireDeclarations.push(` // Converted from init handler Apex call - data loads automatically
239
+ @wire(${apexMethod})
240
+ ${wireProp};
241
+
242
+ // Getter to safely access wire data
243
+ get ${dataProperty}() {
244
+ return this.${wireProp}?.data;
245
+ }
246
+
247
+ get ${wireProp}Error() {
248
+ return this.${wireProp}?.error;
249
+ }`);
250
+ }
251
+ warnings.push('Init handler Apex call converted to @wire - data loads automatically, no connectedCallback needed');
252
+ }
253
+ else if (initHandler && !hasLmsSubscriberLifecycle) {
254
+ // Regular init handler without Apex calls - use connectedCallback
255
+ const funcName = initHandler.action.replace('{!c.', '').replace('}', '');
256
+ methodStubs.push(` // TODO: Migrate logic from Aura init handler (${funcName})
257
+ connectedCallback() {
258
+ // Original init handler logic goes here
259
+ ${controllerFunctions.has(funcName) ? '// Original body available in controller' : ''}
260
+ }`);
261
+ }
262
+ if (renderHandler) {
263
+ methodStubs.push(` // TODO: Migrate logic from Aura render/afterRender handler
264
+ // WARNING: renderedCallback fires on every render - use flag for one-time logic
265
+ isRendered = false;
266
+
267
+ renderedCallback() {
268
+ if (this.isRendered) return;
269
+ this.isRendered = true;
270
+ // Original render handler logic goes here
271
+ }`);
272
+ warnings.push('Render handler converted - renderedCallback fires on every render, not just initial');
273
+ }
274
+ if (destroyHandler && !hasLmsSubscriberLifecycle) {
275
+ methodStubs.push(` // TODO: Migrate cleanup logic from Aura destroy handler
276
+ disconnectedCallback() {
277
+ // Clean up event listeners, intervals, subscriptions
278
+ }`);
279
+ }
280
+ // Process controller functions as method stubs
281
+ if (controller) {
282
+ // Get names of handlers already processed
283
+ const processedHandlers = new Set();
284
+ if (initHandler)
285
+ processedHandlers.add(initHandler.action.replace('{!c.', '').replace('}', ''));
286
+ if (renderHandler)
287
+ processedHandlers.add(renderHandler.action.replace('{!c.', '').replace('}', ''));
288
+ if (destroyHandler)
289
+ processedHandlers.add(destroyHandler.action.replace('{!c.', '').replace('}', ''));
290
+ for (const lms of transformedMarkup.lmsChannels) {
291
+ if (lms.onMessageHandler)
292
+ processedHandlers.add(lms.onMessageHandler);
293
+ }
294
+ for (const func of controller.functions) {
295
+ // Skip if already handled
296
+ if (processedHandlers.has(func.name)) {
297
+ continue;
298
+ }
299
+ let paramStr = '';
300
+ if (func.hasEvent) {
301
+ paramStr = 'event';
302
+ }
303
+ methodStubs.push(` // TODO: Implement - converted from controller.${func.name}
304
+ ${func.name}(${paramStr}) {
305
+ // Original function accessed: ${func.attributeAccess.map((a) => `v.${a.name}`).join(', ') || 'none'}
306
+ // Helper calls: ${func.helperCalls.join(', ') || 'none'}
307
+ // Server calls: ${func.serverCalls.length > 0 ? 'yes' : 'none'}
308
+ }`);
309
+ // Track server calls
310
+ for (const serverCall of func.serverCalls) {
311
+ if (serverCall.controllerMethod) {
312
+ apexImports.push(serverCall.controllerMethod);
313
+ }
314
+ }
315
+ }
316
+ }
317
+ // Process helper functions
318
+ if (helper) {
319
+ for (const func of helper.functions) {
320
+ methodStubs.push(` // TODO: Implement - merged from helper.${func.name}
321
+ ${func.name}() {
322
+ // This was a helper function, now part of the class
323
+ }`);
324
+ for (const serverCall of func.serverCalls) {
325
+ if (!apexImports.includes(serverCall)) {
326
+ apexImports.push(serverCall);
327
+ }
328
+ }
329
+ }
330
+ }
331
+ // Process aura:method declarations
332
+ for (const method of markup.methods) {
333
+ if (!methodStubs.some((m) => m.includes(`${method.name}(`))) {
334
+ if (!imports.includes('api'))
335
+ imports.push('api');
336
+ const params = method.attributes.map((a) => a.name).join(', ');
337
+ methodStubs.push(` // Public method - was aura:method
338
+ @api
339
+ ${method.name}(${params}) {
340
+ // TODO: Implement public method
341
+ }`);
342
+ }
343
+ }
344
+ // Build JS file
345
+ let js = `import { ${imports.join(', ')} } from 'lwc';\n`;
346
+ // Add LMS imports
347
+ if (lmsImports.length > 0) {
348
+ // Deduplicate LMS imports
349
+ const uniqueLmsImports = [...new Set(lmsImports)];
350
+ js += `import { ${uniqueLmsImports.join(', ')} } from 'lightning/messageService';\n`;
351
+ for (const channelImport of channelImports) {
352
+ js += `${channelImport}\n`;
353
+ }
354
+ }
355
+ // Add wire adapter imports
356
+ if (wireImports.length > 0) {
357
+ // Deduplicate wire imports
358
+ const uniqueWireImports = [...new Set(wireImports)];
359
+ js += `import { ${uniqueWireImports.join(', ')} } from 'lightning/uiRecordApi';\n`;
360
+ }
361
+ // Add schema imports (now active imports for detected fields)
362
+ if (schemaImports.length > 0) {
363
+ js += `\n// Field schema imports (verify object name - defaulting to Contact)\n`;
364
+ for (const schemaImport of schemaImports) {
365
+ js += `${schemaImport}\n`;
366
+ }
367
+ }
368
+ // Add Apex import TODOs
369
+ if (apexImports.length > 0 || markup.controller) {
370
+ js += `\n// TODO: Import Apex methods - verify class and method names\n`;
371
+ for (const method of apexImports) {
372
+ js += `// import ${method} from '@salesforce/apex/${markup.controller || 'ControllerName'}.${method}';\n`;
373
+ }
374
+ }
375
+ js += `\nexport default class ${className} extends LightningElement {\n`;
376
+ // Add properties
377
+ if (apiProperties.length > 0) {
378
+ js += ` // Public properties (from aura:attribute)\n`;
379
+ js += apiProperties.join('\n\n') + '\n\n';
380
+ }
381
+ if (reactiveProperties.length > 0) {
382
+ js += ` // Private properties\n`;
383
+ js += reactiveProperties.join('\n\n') + '\n\n';
384
+ }
385
+ // Add wire declarations
386
+ if (wireDeclarations.length > 0) {
387
+ js += wireDeclarations.join('\n\n') + '\n\n';
388
+ }
389
+ // Add methods
390
+ if (methodStubs.length > 0) {
391
+ js += methodStubs.join('\n\n') + '\n';
392
+ }
393
+ js += `}\n`;
394
+ return { js, warnings };
395
+ }
396
+ /**
397
+ * Convert Aura controller function to LWC method body
398
+ */
399
+ function convertAuraControllerToLwc(func, _markup) {
400
+ // Parse the original function body and convert patterns
401
+ let body = func.body || '';
402
+ // Convert message.getParam('recordId') -> message.recordId
403
+ body = body.replace(/(\w+)\.getParam\s*\(\s*['"](\w+)['"]\s*\)/g, '$1.$2');
404
+ // Convert component.set('v.propName', value) -> this.propName = value
405
+ body = body.replace(/component\.set\s*\(\s*['"]v\.(\w+)['"]\s*,\s*([^)]+)\)/g, 'this.$1 = $2');
406
+ // Convert component.get('v.propName') -> this.propName
407
+ body = body.replace(/component\.get\s*\(\s*['"]v\.(\w+)['"]\s*\)/g, 'this.$1');
408
+ // Convert component.find('auraId').reloadRecord() -> automatic refresh via reactive property
409
+ // Since we're using @wire with '$contactId', changing contactId will automatically refresh
410
+ if (body.includes('reloadRecord')) {
411
+ body = body.replace(/(\w+)\s*=\s*component\.find\s*\(\s*['"][^'"]+['"]\s*\);?\s*\n?\s*\1\.reloadRecord\s*\(\s*\);?/g, '// Wire will auto-refresh when contactId changes (reactive binding)');
412
+ // Simpler pattern
413
+ body = body.replace(/component\.find\s*\(\s*['"][^'"]+['"]\s*\)\.reloadRecord\s*\(\s*\);?/g, '// Wire will auto-refresh when contactId changes (reactive binding)');
414
+ }
415
+ // Convert component.find('auraId') to this.template.querySelector('[data-id="auraId"]')
416
+ body = body.replace(/component\.find\s*\(\s*['"](\w+)['"]\s*\)/g, "this.template.querySelector('[data-id=\"$1\"]')");
417
+ // If the body has var declarations, suggest let/const
418
+ body = body.replace(/\bvar\s+/g, 'let ');
419
+ // Clean up and format
420
+ const lines = body.split('\n').filter(line => line.trim());
421
+ if (lines.length === 0) {
422
+ return '// TODO: Convert controller logic';
423
+ }
424
+ // Indent properly
425
+ return lines.map(line => line.trim()).join('\n ');
426
+ }
427
+ /**
428
+ * Extract static resource name from VF expression
429
+ * e.g., "{!URLFOR($Resource.lmsvf, 'lmsvf.css')}" -> { resourceName: 'lmsvf', path: 'lmsvf.css' }
430
+ */
431
+ function parseStaticResourceExpression(expr) {
432
+ // Match URLFOR($Resource.name, 'path')
433
+ const urlforMatch = expr.match(/URLFOR\s*\(\s*\$Resource\.(\w+)\s*,\s*['"]([^'"]+)['"]\s*\)/i);
434
+ if (urlforMatch) {
435
+ return { resourceName: urlforMatch[1], path: urlforMatch[2] };
436
+ }
437
+ // Match simple $Resource.name
438
+ const simpleMatch = expr.match(/\$Resource\.(\w+)/);
439
+ if (simpleMatch) {
440
+ return { resourceName: simpleMatch[1] };
441
+ }
442
+ return null;
443
+ }
444
+ /**
445
+ * Generate scaffolding JS from VF parsed data
446
+ */
447
+ function generateVfScaffoldingJs(vfPage, apexController) {
448
+ const warnings = [];
449
+ const className = (0, file_io_1.toPascalCase)(vfPage.pageName);
450
+ const dataAccess = (0, data_binding_1.generateDataAccessLayer)(vfPage, apexController);
451
+ warnings.push(...dataAccess.warnings);
452
+ // Build imports
453
+ const baseImports = ['LightningElement'];
454
+ if (dataAccess.wireDeclarations.length > 0) {
455
+ baseImports.push('wire');
456
+ }
457
+ // Check if we need platformResourceLoader for styles/scripts
458
+ const hasStyles = vfPage.includedStyles.length > 0;
459
+ const hasScripts = vfPage.includedScripts.length > 0;
460
+ const needsResourceLoader = hasStyles || hasScripts;
461
+ // Parse static resource references
462
+ const styleResources = [];
463
+ const scriptResources = [];
464
+ for (const style of vfPage.includedStyles) {
465
+ const parsed = parseStaticResourceExpression(style);
466
+ if (parsed) {
467
+ styleResources.push(parsed);
468
+ }
469
+ }
470
+ for (const script of vfPage.includedScripts) {
471
+ const parsed = parseStaticResourceExpression(script);
472
+ if (parsed) {
473
+ scriptResources.push(parsed);
474
+ }
475
+ }
476
+ let js = `import { ${baseImports.join(', ')} } from 'lwc';\n`;
477
+ // Add platformResourceLoader import if needed
478
+ if (needsResourceLoader) {
479
+ const loaderImports = [];
480
+ if (hasStyles)
481
+ loaderImports.push('loadStyle');
482
+ if (hasScripts)
483
+ loaderImports.push('loadScript');
484
+ js += `import { ${loaderImports.join(', ')} } from 'lightning/platformResourceLoader';\n`;
485
+ }
486
+ // Add static resource imports
487
+ const uniqueResources = new Set();
488
+ for (const res of [...styleResources, ...scriptResources]) {
489
+ uniqueResources.add(res.resourceName);
490
+ }
491
+ for (const resourceName of uniqueResources) {
492
+ js += `import ${resourceName}Resource from '@salesforce/resourceUrl/${resourceName}';\n`;
493
+ }
494
+ // Add other imports from data access layer
495
+ for (const imp of dataAccess.imports) {
496
+ js += `${imp}\n`;
497
+ }
498
+ // Add ShowToastEvent import - always needed for handleError method
499
+ js += `import { ShowToastEvent } from 'lightning/platformShowToastEvent';\n`;
500
+ js += `\nexport default class ${className} extends LightningElement {\n`;
501
+ // Add flag for one-time style/script loading
502
+ if (needsResourceLoader) {
503
+ js += ` // Flag to prevent duplicate resource loading\n`;
504
+ js += ` _resourcesLoaded = false;\n\n`;
505
+ }
506
+ // Add properties
507
+ if (dataAccess.properties.length > 0) {
508
+ js += ` // Properties\n`;
509
+ for (const prop of dataAccess.properties) {
510
+ js += ` ${prop}\n\n`;
511
+ }
512
+ }
513
+ // Add wire declarations
514
+ if (dataAccess.wireDeclarations.length > 0) {
515
+ js += ` // Wire adapters\n`;
516
+ for (const wire of dataAccess.wireDeclarations) {
517
+ js += ` ${wire}\n\n`;
518
+ }
519
+ }
520
+ // Add loading state for async operations
521
+ if (vfPage.actionFunctions.length > 0 || vfPage.remoteActions.length > 0) {
522
+ js += ` // Loading state\n`;
523
+ js += ` isLoading = false;\n\n`;
524
+ }
525
+ // Add connectedCallback if page had action
526
+ if (vfPage.pageAttributes.action) {
527
+ js += ` // Page action - runs on component load\n`;
528
+ js += ` connectedCallback() {\n`;
529
+ js += ` // TODO: Implement page load action: ${vfPage.pageAttributes.action}\n`;
530
+ js += ` }\n\n`;
531
+ }
532
+ // Add renderedCallback for loading static resources
533
+ if (needsResourceLoader) {
534
+ js += ` // Load static resources (styles/scripts)\n`;
535
+ js += ` renderedCallback() {\n`;
536
+ js += ` if (this._resourcesLoaded) return;\n`;
537
+ js += ` this._resourcesLoaded = true;\n\n`;
538
+ // Generate loadStyle calls
539
+ if (styleResources.length > 0) {
540
+ js += ` // Load stylesheets\n`;
541
+ js += ` Promise.all([\n`;
542
+ for (const res of styleResources) {
543
+ const resourcePath = res.path
544
+ ? `${res.resourceName}Resource + '/${res.path}'`
545
+ : `${res.resourceName}Resource`;
546
+ js += ` loadStyle(this, ${resourcePath}),\n`;
547
+ }
548
+ js += ` ])\n`;
549
+ js += ` .then(() => {\n`;
550
+ js += ` // Styles loaded successfully\n`;
551
+ js += ` })\n`;
552
+ js += ` .catch(error => {\n`;
553
+ js += ` console.error('Error loading styles:', error);\n`;
554
+ js += ` });\n`;
555
+ }
556
+ // Generate loadScript calls
557
+ if (scriptResources.length > 0) {
558
+ js += `\n // Load scripts\n`;
559
+ js += ` Promise.all([\n`;
560
+ for (const res of scriptResources) {
561
+ const resourcePath = res.path
562
+ ? `${res.resourceName}Resource + '/${res.path}'`
563
+ : `${res.resourceName}Resource`;
564
+ js += ` loadScript(this, ${resourcePath}),\n`;
565
+ }
566
+ js += ` ])\n`;
567
+ js += ` .then(() => {\n`;
568
+ js += ` // Scripts loaded - initialize any external libraries here\n`;
569
+ js += ` })\n`;
570
+ js += ` .catch(error => {\n`;
571
+ js += ` console.error('Error loading scripts:', error);\n`;
572
+ js += ` });\n`;
573
+ }
574
+ js += ` }\n\n`;
575
+ }
576
+ // Add methods
577
+ if (dataAccess.methods.length > 0) {
578
+ js += ` // Methods\n`;
579
+ for (const method of dataAccess.methods) {
580
+ js += ` ${method}\n\n`;
581
+ }
582
+ }
583
+ // Add error handler
584
+ js += ` // Error handler\n`;
585
+ js += ` handleError(error) {\n`;
586
+ js += ` this.dispatchEvent(\n`;
587
+ js += ` new ShowToastEvent({\n`;
588
+ js += ` title: 'Error',\n`;
589
+ js += ` message: error?.body?.message || 'An error occurred',\n`;
590
+ js += ` variant: 'error'\n`;
591
+ js += ` })\n`;
592
+ js += ` );\n`;
593
+ js += ` }\n`;
594
+ js += `}\n`;
595
+ return { js, warnings };
596
+ }
597
+ /**
598
+ * Generate LWC scaffolding from Aura component
599
+ */
600
+ function generateAuraScaffolding(markup, transformedMarkup, controller, helper, style) {
601
+ const lwcName = (0, file_io_1.toLwcName)(markup.componentName);
602
+ const allWarnings = [...transformedMarkup.warnings];
603
+ const notes = [];
604
+ // Generate HTML with comments
605
+ let html = transformedMarkup.html;
606
+ // Generate JS scaffolding
607
+ const { js, warnings: jsWarnings } = generateAuraScaffoldingJs(markup, transformedMarkup, controller, helper);
608
+ allWarnings.push(...jsWarnings);
609
+ // Generate CSS
610
+ let css;
611
+ if (style) {
612
+ css = (0, style_parser_1.convertAuraStyleToLwc)(style);
613
+ if (style.usesTokens) {
614
+ notes.push('CSS uses design tokens - may need conversion to SLDS or CSS custom properties');
615
+ }
616
+ }
617
+ // Generate meta XML
618
+ const meta = generateMetaXml(lwcName, {
619
+ description: `Converted from Aura component: ${markup.componentName}`,
620
+ targets: markup.implements?.includes('flexipage:availableForAllPageTypes')
621
+ ? ['lightning__RecordPage', 'lightning__AppPage', 'lightning__HomePage']
622
+ : ['lightning__AppPage'],
623
+ });
624
+ // Generate notes
625
+ if (markup.controller) {
626
+ notes.push(`Original Apex controller: ${markup.controller}`);
627
+ }
628
+ if (markup.implements && markup.implements.length > 0) {
629
+ notes.push(`Original interfaces: ${markup.implements.join(', ')}`);
630
+ }
631
+ if (markup.registeredEvents.length > 0) {
632
+ notes.push(`Registered events to convert: ${markup.registeredEvents.map((e) => e.name).join(', ')}`);
633
+ }
634
+ if (transformedMarkup.usedDirectives.length > 0) {
635
+ notes.push(`LWC directives used: ${transformedMarkup.usedDirectives.join(', ')}`);
636
+ }
637
+ // Add warnings as notes
638
+ for (const warning of allWarnings) {
639
+ notes.push(`WARNING: ${warning}`);
640
+ }
641
+ // Generate Jest tests
642
+ const tests = (0, test_generator_1.generateAuraToLwcTests)(markup, transformedMarkup, controller);
643
+ const behaviorSpec = (0, test_generator_1.generateBehaviorSpecDocument)(markup.componentName, tests.behaviorSpecs);
644
+ // Generate before/after test comparison for behavior verification
645
+ const testComparison = (0, test_comparison_1.generateTestComparison)(markup, transformedMarkup, controller, helper);
646
+ logger_1.logger.debug(`Generated scaffolding for ${lwcName}`);
647
+ logger_1.logger.debug(`Generated ${tests.behaviorSpecs.length} behavior specs`);
648
+ logger_1.logger.debug(`Generated ${testComparison.behaviorTests.length} behavior tests for comparison`);
649
+ return {
650
+ bundle: {
651
+ name: lwcName,
652
+ html,
653
+ js,
654
+ css,
655
+ meta,
656
+ },
657
+ notes,
658
+ warnings: allWarnings,
659
+ tests,
660
+ behaviorSpec,
661
+ testComparison,
662
+ };
663
+ }
664
+ /**
665
+ * Generate LWC scaffolding from VF page
666
+ */
667
+ function generateVfScaffolding(vfPage, transformedMarkup, apexController) {
668
+ const lwcName = (0, file_io_1.toLwcName)(vfPage.pageName);
669
+ const allWarnings = [...transformedMarkup.warnings];
670
+ const notes = [];
671
+ // Generate JS scaffolding
672
+ const { js, warnings: jsWarnings } = generateVfScaffoldingJs(vfPage, apexController);
673
+ allWarnings.push(...jsWarnings);
674
+ // Generate meta XML
675
+ const targets = [];
676
+ if (vfPage.pageAttributes.standardController) {
677
+ targets.push('lightning__RecordPage');
678
+ }
679
+ targets.push('lightning__AppPage', 'lightning__HomePage');
680
+ const meta = generateMetaXml(lwcName, {
681
+ description: `Converted from Visualforce page: ${vfPage.pageName}`,
682
+ targets,
683
+ });
684
+ // Generate notes
685
+ if (vfPage.pageAttributes.controller) {
686
+ notes.push(`Original controller: ${vfPage.pageAttributes.controller}`);
687
+ }
688
+ if (vfPage.pageAttributes.standardController) {
689
+ notes.push(`Standard controller: ${vfPage.pageAttributes.standardController}`);
690
+ }
691
+ if (vfPage.pageAttributes.extensions && vfPage.pageAttributes.extensions.length > 0) {
692
+ notes.push(`Controller extensions: ${vfPage.pageAttributes.extensions.join(', ')}`);
693
+ }
694
+ if (transformedMarkup.formFields.length > 0) {
695
+ notes.push(`Form fields to implement: ${transformedMarkup.formFields.map((f) => f.name).join(', ')}`);
696
+ }
697
+ if (vfPage.remoteActions.length > 0) {
698
+ notes.push(`Remote actions to convert: ${vfPage.remoteActions.map((ra) => `${ra.controller}.${ra.method}`).join(', ')}`);
699
+ }
700
+ // Add warnings as notes
701
+ for (const warning of allWarnings) {
702
+ notes.push(`WARNING: ${warning}`);
703
+ }
704
+ logger_1.logger.debug(`Generated VF scaffolding for ${lwcName}`);
705
+ return {
706
+ bundle: {
707
+ name: lwcName,
708
+ html: transformedMarkup.html,
709
+ js,
710
+ meta,
711
+ },
712
+ notes,
713
+ warnings: allWarnings,
714
+ };
715
+ }
716
+ //# sourceMappingURL=scaffolding.js.map