lwc-convert 1.0.0 → 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.
@@ -123,25 +123,25 @@ function convertGlobalVariable(expression, lmsBinding) {
123
123
  const nameMatch = expr.match(/\$CurrentPage\.Name/i);
124
124
  if (paramMatch) {
125
125
  const paramName = paramMatch[1];
126
- code = `// Access URL parameter: ${paramName}
127
- @wire(CurrentPageReference)
128
- pageRef;
129
-
130
- get ${paramName}() {
131
- return this.pageRef?.state?.${paramName};
126
+ code = `// Access URL parameter: ${paramName}
127
+ @wire(CurrentPageReference)
128
+ pageRef;
129
+
130
+ get ${paramName}() {
131
+ return this.pageRef?.state?.${paramName};
132
132
  }`;
133
133
  }
134
134
  else if (nameMatch) {
135
- code = `// Access current page name
136
- @wire(CurrentPageReference)
137
- pageRef;
138
-
139
- get pageName() {
140
- return this.pageRef?.attributes?.name || this.pageRef?.state?.c__pageName || '';
135
+ code = `// Access current page name
136
+ @wire(CurrentPageReference)
137
+ pageRef;
138
+
139
+ get pageName() {
140
+ return this.pageRef?.attributes?.name || this.pageRef?.state?.c__pageName || '';
141
141
  }`;
142
142
  }
143
143
  else {
144
- code = `@wire(CurrentPageReference)
144
+ code = `@wire(CurrentPageReference)
145
145
  pageRef;`;
146
146
  }
147
147
  }
@@ -156,65 +156,65 @@ pageRef;`;
156
156
  // Generate handleMessage body based on LMS binding analysis
157
157
  let handleMessageBody;
158
158
  if (lmsBinding) {
159
- handleMessageBody = `// Auto-detected from VF: calls ${lmsBinding.actionFunctionName} with message.${lmsBinding.messageProperty}
160
- if (message.${lmsBinding.messageProperty}) {
161
- this.${lmsBinding.actionFunctionName}(message.${lmsBinding.messageProperty})
162
- .then(result => {
163
- // Update component state with result
164
- // Example: this.contactRecord = result;
165
- })
166
- .catch(error => {
167
- this.handleError(error);
168
- });
159
+ handleMessageBody = `// Auto-detected from VF: calls ${lmsBinding.actionFunctionName} with message.${lmsBinding.messageProperty}
160
+ if (message.${lmsBinding.messageProperty}) {
161
+ this.${lmsBinding.actionFunctionName}(message.${lmsBinding.messageProperty})
162
+ .then(result => {
163
+ // Update component state with result
164
+ // Example: this.contactRecord = result;
165
+ })
166
+ .catch(error => {
167
+ this.handleError(error);
168
+ });
169
169
  }`;
170
170
  warnings.push(`LMS handleMessage auto-wired to ${lmsBinding.actionFunctionName}(message.${lmsBinding.messageProperty})`);
171
171
  }
172
172
  else {
173
- handleMessageBody = `// TODO: Handle message payload
173
+ handleMessageBody = `// TODO: Handle message payload
174
174
  // Example: this.recordId = message.recordId;`;
175
175
  }
176
- code = `// Lightning Message Service: ${channelName}
177
- @wire(MessageContext)
178
- messageContext;
179
-
180
- // Subscription reference for cleanup
181
- _subscription = null;
182
-
183
- // Lifecycle: Subscribe to message channel
184
- connectedCallback() {
185
- this.subscribeToMessageChannel();
186
- }
187
-
188
- // Lifecycle: Cleanup subscription
189
- disconnectedCallback() {
190
- this.unsubscribeFromMessageChannel();
191
- }
192
-
193
- // Subscribe to messages
194
- subscribeToMessageChannel() {
195
- if (!this._subscription) {
196
- this._subscription = subscribe(
197
- this.messageContext,
198
- ${channelVar},
199
- (message) => this.handleMessage(message)
200
- );
201
- }
202
- }
203
-
204
- // Unsubscribe from messages
205
- unsubscribeFromMessageChannel() {
206
- unsubscribe(this._subscription);
207
- this._subscription = null;
208
- }
209
-
210
- // Handle incoming messages
211
- handleMessage(message) {
212
- ${handleMessageBody}
213
- }
214
-
215
- // Publish a message
216
- publishMessage(payload) {
217
- publish(this.messageContext, ${channelVar}, payload);
176
+ code = `// Lightning Message Service: ${channelName}
177
+ @wire(MessageContext)
178
+ messageContext;
179
+
180
+ // Subscription reference for cleanup
181
+ _subscription = null;
182
+
183
+ // Lifecycle: Subscribe to message channel
184
+ connectedCallback() {
185
+ this.subscribeToMessageChannel();
186
+ }
187
+
188
+ // Lifecycle: Cleanup subscription
189
+ disconnectedCallback() {
190
+ this.unsubscribeFromMessageChannel();
191
+ }
192
+
193
+ // Subscribe to messages
194
+ subscribeToMessageChannel() {
195
+ if (!this._subscription) {
196
+ this._subscription = subscribe(
197
+ this.messageContext,
198
+ ${channelVar},
199
+ (message) => this.handleMessage(message)
200
+ );
201
+ }
202
+ }
203
+
204
+ // Unsubscribe from messages
205
+ unsubscribeFromMessageChannel() {
206
+ unsubscribe(this._subscription);
207
+ this._subscription = null;
208
+ }
209
+
210
+ // Handle incoming messages
211
+ handleMessage(message) {
212
+ ${handleMessageBody}
213
+ }
214
+
215
+ // Publish a message
216
+ publishMessage(payload) {
217
+ publish(this.messageContext, ${channelVar}, payload);
218
218
  }`;
219
219
  warnings.push(`Lightning Message Service detected: ${channelName} - connectedCallback/disconnectedCallback auto-generated`);
220
220
  }
@@ -226,19 +226,19 @@ publishMessage(payload) {
226
226
  const field = fieldMatch[1];
227
227
  if (field === 'Id') {
228
228
  imports.push("import userId from '@salesforce/user/Id';");
229
- code = `// Current user ID
229
+ code = `// Current user ID
230
230
  userId = userId;`;
231
231
  }
232
232
  else {
233
233
  imports.push("import { getRecord } from 'lightning/uiRecordApi';");
234
234
  imports.push("import userId from '@salesforce/user/Id';");
235
235
  imports.push(`import USER_${field.toUpperCase()}_FIELD from '@salesforce/schema/User.${field}';`);
236
- code = `// Current user ${field}
237
- @wire(getRecord, { recordId: userId, fields: [USER_${field.toUpperCase()}_FIELD] })
238
- user;
239
-
240
- get user${field}() {
241
- return this.user?.data?.fields?.${field}?.value;
236
+ code = `// Current user ${field}
237
+ @wire(getRecord, { recordId: userId, fields: [USER_${field.toUpperCase()}_FIELD] })
238
+ user;
239
+
240
+ get user${field}() {
241
+ return this.user?.data?.fields?.${field}?.value;
242
242
  }`;
243
243
  pattern = 'wire';
244
244
  }
@@ -251,9 +251,9 @@ get user${field}() {
251
251
  if (labelMatch) {
252
252
  const [, namespace, labelName] = labelMatch;
253
253
  imports.push(`import ${labelName} from '@salesforce/label/${namespace}.${labelName}';`);
254
- code = `// Label: ${namespace}.${labelName}
255
- label = {
256
- ${labelName}
254
+ code = `// Label: ${namespace}.${labelName}
255
+ label = {
256
+ ${labelName}
257
257
  };`;
258
258
  }
259
259
  }
@@ -264,7 +264,7 @@ label = {
264
264
  if (resourceMatch) {
265
265
  const resourceName = resourceMatch[1];
266
266
  imports.push(`import ${resourceName} from '@salesforce/resourceUrl/${resourceName}';`);
267
- code = `// Static resource: ${resourceName}
267
+ code = `// Static resource: ${resourceName}
268
268
  ${resourceName}Url = ${resourceName};`;
269
269
  }
270
270
  }
@@ -273,10 +273,10 @@ ${resourceName}Url = ${resourceName};`;
273
273
  pattern = 'imperative';
274
274
  warnings.push('$Action expressions need NavigationMixin for navigation');
275
275
  imports.push("import { NavigationMixin } from 'lightning/navigation';");
276
- code = `// Navigation action - use NavigationMixin
277
- // Example: this[NavigationMixin.Navigate]({
278
- // type: 'standard__recordPage',
279
- // attributes: { recordId: id, actionName: 'view' }
276
+ code = `// Navigation action - use NavigationMixin
277
+ // Example: this[NavigationMixin.Navigate]({
278
+ // type: 'standard__recordPage',
279
+ // attributes: { recordId: id, actionName: 'view' }
280
280
  // });`;
281
281
  }
282
282
  // $ObjectType
@@ -286,7 +286,7 @@ ${resourceName}Url = ${resourceName};`;
286
286
  if (objectMatch) {
287
287
  const [, objectName, fieldName] = objectMatch;
288
288
  imports.push(`import ${fieldName.toUpperCase()}_FIELD from '@salesforce/schema/${objectName}.${fieldName}';`);
289
- code = `// Field reference: ${objectName}.${fieldName}
289
+ code = `// Field reference: ${objectName}.${fieldName}
290
290
  // Use in wire adapter or getFieldValue`;
291
291
  }
292
292
  }
@@ -311,28 +311,28 @@ function convertControllerProperty(property, controllerName) {
311
311
  let code = '';
312
312
  if (isWireCandidate) {
313
313
  // Use wire adapter
314
- code = `// Property: ${property.name} (from ${controllerName})
315
- // Option 1: Wire to Apex getter
316
- @wire(get${capitalize(property.name)})
317
- ${property.name};
318
-
319
- // Option 2: Simple reactive property
320
- ${property.name};
321
-
322
- // If using wire, add this to imports:
314
+ code = `// Property: ${property.name} (from ${controllerName})
315
+ // Option 1: Wire to Apex getter
316
+ @wire(get${capitalize(property.name)})
317
+ ${property.name};
318
+
319
+ // Option 2: Simple reactive property
320
+ ${property.name};
321
+
322
+ // If using wire, add this to imports:
323
323
  // import get${capitalize(property.name)} from '@salesforce/apex/${controllerName}.get${capitalize(property.name)}';`;
324
324
  }
325
325
  else {
326
326
  // Use imperative or reactive property
327
- code = `// Property: ${property.name}
327
+ code = `// Property: ${property.name}
328
328
  ${property.name}${property.initialValue ? ` = ${property.initialValue}` : ''};`;
329
329
  if (property.hasSetter) {
330
- code += `
331
-
332
- // This property had a setter in VF - update via method call
333
- async update${capitalize(property.name)}(value) {
334
- this.${property.name} = value;
335
- // TODO: If this needs to persist, call Apex method
330
+ code += `
331
+
332
+ // This property had a setter in VF - update via method call
333
+ async update${capitalize(property.name)}(value) {
334
+ this.${property.name} = value;
335
+ // TODO: If this needs to persist, call Apex method
336
336
  }`;
337
337
  }
338
338
  }
@@ -359,16 +359,16 @@ function convertControllerMethod(method, controllerName) {
359
359
  if (isWireCandidate) {
360
360
  // Generate wire-based code
361
361
  if (method.parameters.length === 0) {
362
- code = `// Wired Apex method: ${method.name}
363
- @wire(${method.name})
364
- ${method.name}Result;
365
-
366
- get ${method.name}Data() {
367
- return this.${method.name}Result?.data;
368
- }
369
-
370
- get ${method.name}Error() {
371
- return this.${method.name}Result?.error;
362
+ code = `// Wired Apex method: ${method.name}
363
+ @wire(${method.name})
364
+ ${method.name}Result;
365
+
366
+ get ${method.name}Data() {
367
+ return this.${method.name}Result?.data;
368
+ }
369
+
370
+ get ${method.name}Error() {
371
+ return this.${method.name}Result?.error;
372
372
  }`;
373
373
  }
374
374
  else {
@@ -376,15 +376,15 @@ get ${method.name}Error() {
376
376
  const params = method.parameters
377
377
  .map((p) => `${p.name}: this.${p.name}`)
378
378
  .join(', ');
379
- code = `// Wired Apex method: ${method.name}
380
- @wire(${method.name}, { ${params} })
379
+ code = `// Wired Apex method: ${method.name}
380
+ @wire(${method.name}, { ${params} })
381
381
  ${method.name}Result;`;
382
382
  }
383
383
  if (method.isCacheable) {
384
- code += `
385
-
386
- // Note: This method is cacheable. Use refreshApex() to refresh:
387
- // import { refreshApex } from '@salesforce/apex';
384
+ code += `
385
+
386
+ // Note: This method is cacheable. Use refreshApex() to refresh:
387
+ // import { refreshApex } from '@salesforce/apex';
388
388
  // await refreshApex(this.${method.name}Result);`;
389
389
  }
390
390
  }
@@ -394,15 +394,15 @@ ${method.name}Result;`;
394
394
  const paramObj = method.parameters.length > 0
395
395
  ? `{ ${method.parameters.map((p) => p.name).join(', ')} }`
396
396
  : '';
397
- code = `// Imperative Apex call: ${method.name}
398
- async call${capitalize(method.name)}(${params}) {
399
- try {
400
- const result = await ${method.name}(${paramObj});
401
- return result;
402
- } catch (error) {
403
- console.error('Error calling ${method.name}:', error);
404
- throw error;
405
- }
397
+ code = `// Imperative Apex call: ${method.name}
398
+ async call${capitalize(method.name)}(${params}) {
399
+ try {
400
+ const result = await ${method.name}(${paramObj});
401
+ return result;
402
+ } catch (error) {
403
+ console.error('Error calling ${method.name}:', error);
404
+ throw error;
405
+ }
406
406
  }`;
407
407
  }
408
408
  return {
@@ -460,20 +460,20 @@ function generateDataAccessLayer(vfPage, apexController) {
460
460
  for (const ro of remoteObjects) {
461
461
  const objectNameLower = ro.objectName.toLowerCase();
462
462
  // Generate wire adapter scaffolding
463
- wireDeclarations.push(`// Wire adapter for ${ro.objectName} (converted from apex:remoteObjectModel)
464
- // TODO: Create Apex controller method to replace Remote Objects
465
- //
466
- // Example Apex controller:
467
- // @AuraEnabled(cacheable=true)
468
- // public static List<${ro.objectName}> get${ro.objectName}s() {
469
- // return [SELECT ${ro.fields} FROM ${ro.objectName} LIMIT 10];
470
- // }
471
- //
472
- // @wire(get${ro.objectName}s)
473
- // ${objectNameLower}s;
474
- //
475
- // Or use lightning/uiRecordApi for standard objects:
476
- // import { getRecords } from 'lightning/uiRecordApi';
463
+ wireDeclarations.push(`// Wire adapter for ${ro.objectName} (converted from apex:remoteObjectModel)
464
+ // TODO: Create Apex controller method to replace Remote Objects
465
+ //
466
+ // Example Apex controller:
467
+ // @AuraEnabled(cacheable=true)
468
+ // public static List<${ro.objectName}> get${ro.objectName}s() {
469
+ // return [SELECT ${ro.fields} FROM ${ro.objectName} LIMIT 10];
470
+ // }
471
+ //
472
+ // @wire(get${ro.objectName}s)
473
+ // ${objectNameLower}s;
474
+ //
475
+ // Or use lightning/uiRecordApi for standard objects:
476
+ // import { getRecords } from 'lightning/uiRecordApi';
477
477
  `);
478
478
  imports.push(`// import get${ro.objectName}s from '@salesforce/apex/YourController.get${ro.objectName}s';`);
479
479
  warnings.push(`Remote Object "${ro.objectName}" detected - create Apex controller method to replace`);
@@ -534,21 +534,21 @@ function generateDataAccessLayer(vfPage, apexController) {
534
534
  : '';
535
535
  // Extract the method name from the action (e.g., "{!doSomething}" -> "doSomething")
536
536
  const methodName = extractMethodFromAction(af.action);
537
- methods.push(`// Converted from apex:actionFunction "${af.name}" (imperative - has parameters)
538
- // Parameters: ${paramNames.join(', ')}
539
- async ${af.name}(${paramDeclaration}) {
540
- this.isLoading = true;
541
- try {
542
- const result = await ${methodName || af.name}(${paramObject});
543
- ${af.rerender ? `// Originally rerendered: ${af.rerender} - update reactive properties to trigger re-render` : ''}
544
- ${af.oncomplete ? `// Original oncomplete callback: ${af.oncomplete}` : ''}
545
- return result;
546
- } catch (error) {
547
- console.error('Error calling ${af.name}:', error);
548
- throw error;
549
- } finally {
550
- this.isLoading = false;
551
- }
537
+ methods.push(`// Converted from apex:actionFunction "${af.name}" (imperative - has parameters)
538
+ // Parameters: ${paramNames.join(', ')}
539
+ async ${af.name}(${paramDeclaration}) {
540
+ this.isLoading = true;
541
+ try {
542
+ const result = await ${methodName || af.name}(${paramObject});
543
+ ${af.rerender ? `// Originally rerendered: ${af.rerender} - update reactive properties to trigger re-render` : ''}
544
+ ${af.oncomplete ? `// Original oncomplete callback: ${af.oncomplete}` : ''}
545
+ return result;
546
+ } catch (error) {
547
+ console.error('Error calling ${af.name}:', error);
548
+ throw error;
549
+ } finally {
550
+ this.isLoading = false;
551
+ }
552
552
  }`);
553
553
  // Add import for the Apex method if we can determine the controller
554
554
  if (methodName && apexController) {
@@ -561,19 +561,19 @@ async ${af.name}(${paramDeclaration}) {
561
561
  }
562
562
  else {
563
563
  // Simple action function without params can potentially use wire (but still default to imperative for consistency)
564
- methods.push(`// Converted from apex:actionFunction "${af.name}"
565
- async ${af.name}() {
566
- this.isLoading = true;
567
- try {
568
- // TODO: Implement - original action: ${af.action}
569
- ${af.rerender ? `// Originally rerendered: ${af.rerender}` : ''}
570
- ${af.oncomplete ? `// Had oncomplete: ${af.oncomplete}` : ''}
571
- } catch (error) {
572
- console.error('Error calling ${af.name}:', error);
573
- throw error;
574
- } finally {
575
- this.isLoading = false;
576
- }
564
+ methods.push(`// Converted from apex:actionFunction "${af.name}"
565
+ async ${af.name}() {
566
+ this.isLoading = true;
567
+ try {
568
+ // TODO: Implement - original action: ${af.action}
569
+ ${af.rerender ? `// Originally rerendered: ${af.rerender}` : ''}
570
+ ${af.oncomplete ? `// Had oncomplete: ${af.oncomplete}` : ''}
571
+ } catch (error) {
572
+ console.error('Error calling ${af.name}:', error);
573
+ throw error;
574
+ } finally {
575
+ this.isLoading = false;
576
+ }
577
577
  }`);
578
578
  }
579
579
  }
@@ -182,21 +182,21 @@ async function writeLwcBundle(outputDir, bundle, dryRun = false) {
182
182
  */
183
183
  async function writeConversionNotes(outputDir, componentName, notes, dryRun = false) {
184
184
  const notesPath = path.join(outputDir, componentName, `${componentName}-conversion-notes.md`);
185
- const content = `# Conversion Notes for ${componentName}
186
-
187
- This file documents items that require manual attention after automated conversion.
188
-
189
- ## Action Items
190
-
191
- ${notes.map((note, i) => `${i + 1}. ${note}`).join('\n')}
192
-
193
- ## Verification Checklist
194
-
195
- - [ ] Review all TODO comments in the generated code
196
- - [ ] Verify Apex method imports and wire adapters
197
- - [ ] Test event handling and data flow
198
- - [ ] Validate styling matches original component
199
- - [ ] Deploy to scratch org and test functionality
185
+ const content = `# Conversion Notes for ${componentName}
186
+
187
+ This file documents items that require manual attention after automated conversion.
188
+
189
+ ## Action Items
190
+
191
+ ${notes.map((note, i) => `${i + 1}. ${note}`).join('\n')}
192
+
193
+ ## Verification Checklist
194
+
195
+ - [ ] Review all TODO comments in the generated code
196
+ - [ ] Verify Apex method imports and wire adapters
197
+ - [ ] Test event handling and data flow
198
+ - [ ] Validate styling matches original component
199
+ - [ ] Deploy to scratch org and test functionality
200
200
  `;
201
201
  if (dryRun) {
202
202
  logger_1.logger.info(`[DRY RUN] Would write conversion notes: ${notesPath}`);
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Preview Generator - Creates standalone HTML previews of converted LWC components
3
+ *
4
+ * This allows users to evaluate the UI of their converted components in a browser
5
+ * without needing a full Salesforce deployment.
6
+ */
7
+ import { LwcBundle } from './file-io';
8
+ /**
9
+ * Generate a complete preview HTML document
10
+ */
11
+ export declare function generatePreviewHtml(bundle: LwcBundle, componentCss?: string): string;
12
+ /**
13
+ * Write the preview HTML file
14
+ */
15
+ export declare function writePreviewFile(outputDir: string, bundle: LwcBundle, dryRun?: boolean): Promise<string>;
16
+ /**
17
+ * Open the preview file in the default browser
18
+ */
19
+ export declare function openPreview(previewPath: string): Promise<void>;
20
+ //# sourceMappingURL=preview-generator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preview-generator.d.ts","sourceRoot":"","sources":["../../src/utils/preview-generator.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAqdtC;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,SAAS,EAAE,YAAY,CAAC,EAAE,MAAM,GAAG,MAAM,CAkSpF;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CACpC,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,SAAS,EACjB,MAAM,GAAE,OAAe,GACtB,OAAO,CAAC,MAAM,CAAC,CAcjB;AAED;;GAEG;AACH,wBAAsB,WAAW,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAuBpE"}