@salesforce/plugin-omnistudio-migration-tool 2.0.0-rc.60 → 2.0.0-rc.61

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 (65) hide show
  1. package/lib/commands/omnistudio/migration/assess.d.ts +1 -0
  2. package/lib/commands/omnistudio/migration/assess.js +27 -0
  3. package/lib/commands/omnistudio/migration/assess.js.map +1 -1
  4. package/lib/commands/omnistudio/migration/clean.js +4 -2
  5. package/lib/commands/omnistudio/migration/clean.js.map +1 -1
  6. package/lib/commands/omnistudio/migration/migrate.js +11 -0
  7. package/lib/commands/omnistudio/migration/migrate.js.map +1 -1
  8. package/lib/javascripts/reportGeneratorUtility.js +181 -0
  9. package/lib/migration/ApexNamespaceRegistry.d.ts +49 -0
  10. package/lib/migration/ApexNamespaceRegistry.js +130 -0
  11. package/lib/migration/ApexNamespaceRegistry.js.map +1 -0
  12. package/lib/migration/CustomCssRegistry.d.ts +117 -0
  13. package/lib/migration/CustomCssRegistry.js +234 -0
  14. package/lib/migration/CustomCssRegistry.js.map +1 -0
  15. package/lib/migration/base.js +1 -1
  16. package/lib/migration/base.js.map +1 -1
  17. package/lib/migration/customLabels.js +14 -3
  18. package/lib/migration/customLabels.js.map +1 -1
  19. package/lib/migration/flexcard.d.ts +98 -0
  20. package/lib/migration/flexcard.js +448 -15
  21. package/lib/migration/flexcard.js.map +1 -1
  22. package/lib/migration/interfaces.d.ts +1 -0
  23. package/lib/migration/interfaces.js.map +1 -1
  24. package/lib/migration/omniscript.d.ts +42 -1
  25. package/lib/migration/omniscript.js +249 -6
  26. package/lib/migration/omniscript.js.map +1 -1
  27. package/lib/migration/omniscriptInstance.d.ts +90 -0
  28. package/lib/migration/omniscriptInstance.js +755 -0
  29. package/lib/migration/omniscriptInstance.js.map +1 -0
  30. package/lib/styles/reportGenerator.css +25 -0
  31. package/lib/templates/assessmentReport.template +8 -0
  32. package/lib/templates/migrationReport.template +8 -0
  33. package/lib/utils/constants/documentRegistry.d.ts +6 -0
  34. package/lib/utils/constants/documentRegistry.js +6 -0
  35. package/lib/utils/constants/documentRegistry.js.map +1 -1
  36. package/lib/utils/constants/stringContants.d.ts +28 -5
  37. package/lib/utils/constants/stringContants.js +30 -5
  38. package/lib/utils/constants/stringContants.js.map +1 -1
  39. package/lib/utils/customLabels.d.ts +1 -0
  40. package/lib/utils/customLabels.js +3 -0
  41. package/lib/utils/customLabels.js.map +1 -1
  42. package/lib/utils/interfaces.d.ts +19 -0
  43. package/lib/utils/query/index.d.ts +1 -0
  44. package/lib/utils/query/index.js +17 -0
  45. package/lib/utils/query/index.js.map +1 -1
  46. package/lib/utils/resultsbuilder/FlexcardAssessmentReporter.js +4 -0
  47. package/lib/utils/resultsbuilder/FlexcardAssessmentReporter.js.map +1 -1
  48. package/lib/utils/resultsbuilder/SaveForLaterAssessmentReporter.d.ts +12 -0
  49. package/lib/utils/resultsbuilder/SaveForLaterAssessmentReporter.js +109 -0
  50. package/lib/utils/resultsbuilder/SaveForLaterAssessmentReporter.js.map +1 -0
  51. package/lib/utils/resultsbuilder/assessmentReporter.js +18 -2
  52. package/lib/utils/resultsbuilder/assessmentReporter.js.map +1 -1
  53. package/lib/utils/resultsbuilder/helpers/AssessmentReportHelper.d.ts +11 -1
  54. package/lib/utils/resultsbuilder/helpers/AssessmentReportHelper.js +64 -1
  55. package/lib/utils/resultsbuilder/helpers/AssessmentReportHelper.js.map +1 -1
  56. package/lib/utils/resultsbuilder/index.d.ts +4 -0
  57. package/lib/utils/resultsbuilder/index.js +61 -0
  58. package/lib/utils/resultsbuilder/index.js.map +1 -1
  59. package/lib/utils/stringUtils.d.ts +15 -0
  60. package/lib/utils/stringUtils.js +37 -0
  61. package/lib/utils/stringUtils.js.map +1 -1
  62. package/messages/assess.json +23 -2
  63. package/messages/migrate.json +29 -3
  64. package/oclif.manifest.json +1 -1
  65. package/package.json +5 -4
@@ -8,6 +8,7 @@ const VlocityCard_1 = __importDefault(require("../mappings/VlocityCard"));
8
8
  const utils_1 = require("../utils");
9
9
  const net_1 = require("../utils/net");
10
10
  const base_1 = require("./base");
11
+ const CustomCssRegistry_1 = require("./CustomCssRegistry");
11
12
  const interfaces_1 = require("./interfaces");
12
13
  const logger_1 = require("../utils/logger");
13
14
  const base_2 = require("./base");
@@ -16,11 +17,14 @@ const storageUtil_1 = require("../utils/storageUtil");
16
17
  const stringUtils_1 = require("../utils/stringUtils");
17
18
  const dataModelService_1 = require("../utils/dataModelService");
18
19
  const recordPrioritization_1 = require("../utils/recordPrioritization");
20
+ const ApexNamespaceRegistry_1 = require("./ApexNamespaceRegistry");
19
21
  class CardMigrationTool extends base_1.BaseMigrationTool {
20
22
  constructor(namespace, connection, logger, messages, ux, allVersions) {
21
23
  super(namespace, connection, logger, messages, ux);
24
+ this.apexNamespaceRegistry = ApexNamespaceRegistry_1.ApexNamespaceRegistry.getInstance();
22
25
  this.IS_STANDARD_DATA_MODEL = (0, dataModelService_1.isStandardDataModel)();
23
26
  this.allVersions = allVersions;
27
+ CustomCssRegistry_1.CustomCssRegistry.getInstance().init(connection, namespace, messages);
24
28
  }
25
29
  getName() {
26
30
  return 'Flexcards';
@@ -160,6 +164,7 @@ class CardMigrationTool extends base_1.BaseMigrationTool {
160
164
  dependenciesOS: [],
161
165
  dependenciesLWC: [],
162
166
  dependenciesApexRemoteAction: [],
167
+ dependenciesVlocityAction: [],
163
168
  infos: [],
164
169
  warnings: [],
165
170
  errors: [this.messages.getMessage('unexpectedError')],
@@ -187,6 +192,7 @@ class CardMigrationTool extends base_1.BaseMigrationTool {
187
192
  dependenciesFC: [],
188
193
  dependenciesLWC: [],
189
194
  dependenciesApexRemoteAction: [],
195
+ dependenciesVlocityAction: [],
190
196
  infos: [],
191
197
  warnings: [],
192
198
  errors: [],
@@ -237,6 +243,17 @@ class CardMigrationTool extends base_1.BaseMigrationTool {
237
243
  };
238
244
  flexCardAssessmentInfo.migrationStatus = assessmentStatus;
239
245
  this.updateDependencies(flexCard, flexCardAssessmentInfo);
246
+ // Scan custom CSS (both StaticResource-backed and inline) for managed-package
247
+ // namespace references. The helper updates `migrationStatus` itself via
248
+ // `getUpdatedAssessmentStatus`, so it never downgrades a stricter status.
249
+ await this.collectStylesheetNamespaceDependencies(flexCard, flexCardAssessmentInfo);
250
+ if (flexCardAssessmentInfo.errors.length > 0) {
251
+ flexCardAssessmentInfo.migrationStatus = 'Needs manual intervention';
252
+ }
253
+ else if (flexCardAssessmentInfo.warnings.length > 0 &&
254
+ flexCardAssessmentInfo.migrationStatus === 'Ready for migration') {
255
+ flexCardAssessmentInfo.migrationStatus = 'Warnings';
256
+ }
240
257
  // Deduplicate all dependency arrays to ensure no duplicates
241
258
  flexCardAssessmentInfo.dependenciesIP = [...new Set(flexCardAssessmentInfo.dependenciesIP)];
242
259
  flexCardAssessmentInfo.dependenciesDR = [...new Set(flexCardAssessmentInfo.dependenciesDR)];
@@ -246,8 +263,77 @@ class CardMigrationTool extends base_1.BaseMigrationTool {
246
263
  flexCardAssessmentInfo.dependenciesApexRemoteAction = [
247
264
  ...new Set(flexCardAssessmentInfo.dependenciesApexRemoteAction),
248
265
  ];
266
+ flexCardAssessmentInfo.dependenciesVlocityAction = [...new Set(flexCardAssessmentInfo.dependenciesVlocityAction)];
249
267
  return flexCardAssessmentInfo;
250
268
  }
269
+ /**
270
+ * Scan a FlexCard for two distinct kinds of namespace-bound custom CSS:
271
+ *
272
+ * 1. **StaticResource-backed stylesheet** — `Definition__c.customStyleSheet`
273
+ * stores the *Name* of a Static Resource. We look it up, fetch its body,
274
+ * and substring-check for the org's managed-package namespace. Same flow
275
+ * and same warning message as the OmniScript implementation; resource
276
+ * scans are cached by name in {@link CustomCssRegistry}, so a stylesheet
277
+ * shared between OmniScripts and FlexCards is only fetched once per run.
278
+ *
279
+ * 2. **Inline CSS** — `Styles__c.customStyles` stores raw CSS text. We
280
+ * substring-check the namespace directly against that string. No
281
+ * SOQL/REST traffic and no caching (the CSS is per-FlexCard and not
282
+ * shared across records).
283
+ *
284
+ * Both checks emit a warning into `flexCardAssessmentInfo.warnings` and bump
285
+ * `migrationStatus` to 'Needs manual intervention' (via
286
+ * `getUpdatedAssessmentStatus`, which never downgrades a stricter status).
287
+ */
288
+ async collectStylesheetNamespaceDependencies(flexCard, flexCardAssessmentInfo) {
289
+ const registry = CustomCssRegistry_1.CustomCssRegistry.getInstance();
290
+ if (!registry.isEnabled()) {
291
+ return;
292
+ }
293
+ // ---- 1. Definition__c.customStyleSheet (StaticResource name) ----
294
+ const definitionRaw = flexCard[this.getFieldKey('Definition__c')];
295
+ if (definitionRaw) {
296
+ let definition;
297
+ try {
298
+ definition = JSON.parse(definitionRaw);
299
+ }
300
+ catch (ex) {
301
+ logger_1.Logger.error(`Failed to parse Definition__c for stylesheet scan: ${flexCard['Name']}`);
302
+ definition = null;
303
+ }
304
+ const resourceName = ((definition === null || definition === void 0 ? void 0 : definition.customStyleSheet) || '').toString().trim();
305
+ if (resourceName) {
306
+ const verdict = await registry.scanResource(resourceName);
307
+ if (verdict === 'namespaceFound') {
308
+ const message = registry.buildFlexCardNamespaceWarning(resourceName);
309
+ if (message) {
310
+ flexCardAssessmentInfo.warnings.push(message);
311
+ flexCardAssessmentInfo.migrationStatus = (0, stringUtils_1.getUpdatedAssessmentStatus)(flexCardAssessmentInfo.migrationStatus, 'Needs manual intervention');
312
+ }
313
+ }
314
+ }
315
+ }
316
+ // ---- 2. Styles__c.customStyles (raw inline CSS) ----
317
+ const stylesRaw = flexCard[this.getFieldKey('Styles__c')];
318
+ if (stylesRaw) {
319
+ let styles;
320
+ try {
321
+ styles = JSON.parse(stylesRaw);
322
+ }
323
+ catch (ex) {
324
+ logger_1.Logger.error(`Failed to parse Styles__c for stylesheet scan: ${flexCard['Name']}`);
325
+ styles = null;
326
+ }
327
+ const inlineCss = styles === null || styles === void 0 ? void 0 : styles.customStyles;
328
+ if (registry.containsNamespaceInText(inlineCss)) {
329
+ const message = registry.buildInlineCssNamespaceWarning();
330
+ if (message) {
331
+ flexCardAssessmentInfo.warnings.push(message);
332
+ flexCardAssessmentInfo.migrationStatus = (0, stringUtils_1.getUpdatedAssessmentStatus)(flexCardAssessmentInfo.migrationStatus, 'Needs manual intervention');
333
+ }
334
+ }
335
+ }
336
+ }
251
337
  updateDependencies(flexCard, flexCardAssessmentInfo) {
252
338
  var _a, _b, _c, _d;
253
339
  const dataSourceConfig = JSON.parse(flexCard[this.getFieldKey('Datasource__c')] || '{}');
@@ -271,10 +357,19 @@ class CardMigrationTool extends base_1.BaseMigrationTool {
271
357
  else if (ds.type === stringContants_1.Constants.ApexRemoteComponentName) {
272
358
  const remoteClass = (_c = ds.value) === null || _c === void 0 ? void 0 : _c.remoteClass;
273
359
  const remoteMethod = (_d = ds.value) === null || _d === void 0 ? void 0 : _d.remoteMethod;
274
- if (remoteClass &&
275
- remoteMethod &&
276
- !flexCardAssessmentInfo.dependenciesApexRemoteAction.includes(`${remoteClass}.${remoteMethod}`)) {
277
- flexCardAssessmentInfo.dependenciesApexRemoteAction.push(`${remoteClass}.${remoteMethod}`);
360
+ if (remoteClass && remoteMethod) {
361
+ const status = this.apexNamespaceRegistry.resolveStatus(remoteClass);
362
+ const qualifiedClass = this.apexNamespaceRegistry.getQualifiedClassName(remoteClass);
363
+ const qualifiedEntry = `${qualifiedClass}.${remoteMethod}`;
364
+ if (!flexCardAssessmentInfo.dependenciesApexRemoteAction.includes(qualifiedEntry)) {
365
+ flexCardAssessmentInfo.dependenciesApexRemoteAction.push(qualifiedEntry);
366
+ }
367
+ if (status === ApexNamespaceRegistry_1.ApexResolveStatus.NAMESPACED) {
368
+ flexCardAssessmentInfo.warnings.push(this.messages.getMessage('apexRemoteDatasourceNamespaceWarning', [remoteClass, qualifiedClass]));
369
+ }
370
+ else if (status === ApexNamespaceRegistry_1.ApexResolveStatus.NOT_FOUND) {
371
+ flexCardAssessmentInfo.errors.push(this.messages.getMessage('apexClassNotFound', [remoteClass, flexCardAssessmentInfo.name]));
372
+ }
278
373
  }
279
374
  }
280
375
  }
@@ -330,6 +425,10 @@ class CardMigrationTool extends base_1.BaseMigrationTool {
330
425
  continue;
331
426
  }
332
427
  const stateAction = action.stateAction;
428
+ // Detect Vlocity Action (not supported in standard runtime FlexCards)
429
+ if (stateAction.type === stringContants_1.Constants.VlocityAction) {
430
+ this.addVlocityActionDependency(stateAction, flexCardAssessmentInfo);
431
+ }
333
432
  // 1-2. Handle message.value.bundle (DataRaptor) and message.value.ipMethod (Integration Procedure)
334
433
  this.processStateActionMessageForDependencies(stateAction, flexCardAssessmentInfo);
335
434
  // 3. Handle cardName (FlexCard - Flyout childCard)
@@ -411,6 +510,15 @@ class CardMigrationTool extends base_1.BaseMigrationTool {
411
510
  flexCardAssessmentInfo.dependenciesLWC.push(customLwcName);
412
511
  }
413
512
  }
513
+ // "action" is a reserved keyword in the core runtime; flag any Custom LWC
514
+ // that defines it as a property so the user removes/renames it pre-migration.
515
+ if (Object.prototype.hasOwnProperty.call(component.property, 'action')) {
516
+ const lwcLabel = component.property.customlwcname || component.elementLabel || stringContants_1.Constants.CustomLwc;
517
+ const reservedActionMessage = this.messages.getMessage('customLwcReservedActionKey', [lwcLabel]);
518
+ flexCardAssessmentInfo.errors.push(reservedActionMessage);
519
+ flexCardAssessmentInfo.warnings.push(reservedActionMessage);
520
+ flexCardAssessmentInfo.migrationStatus = 'Needs manual intervention';
521
+ }
414
522
  }
415
523
  }
416
524
  /**
@@ -573,12 +681,16 @@ class CardMigrationTool extends base_1.BaseMigrationTool {
573
681
  }
574
682
  return childs;
575
683
  }
576
- checkComponentForDependencies(component, flexCardAssessmentInfo) {
684
+ checkComponentForDependencies(component, flexCardAssessmentInfo, omniScriptNavigateURLWarningKeys = new Set()) {
577
685
  // Check if this component is an action element
578
686
  if (component.element === 'action' && component.property && component.property.actionList) {
579
687
  // Process each action in the actionList
580
688
  for (const action of component.property.actionList) {
581
689
  if (action.stateAction) {
690
+ // Detect Vlocity Action (not supported in standard runtime FlexCards)
691
+ if (action.stateAction.type === stringContants_1.Constants.VlocityAction) {
692
+ this.addVlocityActionDependency(action.stateAction, flexCardAssessmentInfo);
693
+ }
582
694
  // Handle message field (contains DataRaptor/IP references as JSON string)
583
695
  this.processStateActionMessageForDependencies(action.stateAction, flexCardAssessmentInfo);
584
696
  // Case 1: Direct OmniScript reference
@@ -619,6 +731,9 @@ class CardMigrationTool extends base_1.BaseMigrationTool {
619
731
  else if (this.hasCustomLwcFlyoutDependency(action.stateAction)) {
620
732
  this.addCfPrefixedFlexCardDependency(action.stateAction.flyoutLwc, flexCardAssessmentInfo);
621
733
  }
734
+ else if (this.isCustomWebPageAction(action.stateAction)) {
735
+ this.addOmniScriptNavigateURLAssessmentWarning(action.stateAction, flexCardAssessmentInfo, omniScriptNavigateURLWarningKeys);
736
+ }
622
737
  }
623
738
  }
624
739
  }
@@ -659,6 +774,9 @@ class CardMigrationTool extends base_1.BaseMigrationTool {
659
774
  if (this.hasCustomLwcFlyoutDependency(component.property.stateAction)) {
660
775
  this.addCfPrefixedFlexCardDependency(component.property.stateAction.flyoutLwc, flexCardAssessmentInfo);
661
776
  }
777
+ else if (this.isCustomWebPageAction(component.property.stateAction)) {
778
+ this.addOmniScriptNavigateURLAssessmentWarning(component.property.stateAction, flexCardAssessmentInfo, omniScriptNavigateURLWarningKeys);
779
+ }
662
780
  }
663
781
  // MISSING PATTERN FIXED: Handle omni-flyout elements (from tests)
664
782
  if (component.element === stringContants_1.Constants.OmniFlyout && component.property && component.property.flyoutOmniScript) {
@@ -675,11 +793,80 @@ class CardMigrationTool extends base_1.BaseMigrationTool {
675
793
  }
676
794
  // Check child components recursively
677
795
  if (component.children && Array.isArray(component.children)) {
678
- for (const child of component.children) {
679
- this.checkComponentForDependencies(child, flexCardAssessmentInfo);
796
+ for (let childIndex = 0; childIndex < component.children.length; childIndex++) {
797
+ const child = component.children[childIndex];
798
+ this.checkComponentForDependencies(child, flexCardAssessmentInfo, omniScriptNavigateURLWarningKeys);
680
799
  }
681
800
  }
682
801
  }
802
+ addOmniScriptNavigateURLAssessmentWarning(stateAction, flexCardAssessmentInfo, warningKeys) {
803
+ if (this.IS_STANDARD_DATA_MODEL) {
804
+ return;
805
+ }
806
+ const rewrite = this.getOmniScriptURLRewrite(stateAction);
807
+ if (!rewrite) {
808
+ return;
809
+ }
810
+ const { targetName, updatedURL, malformedTokens } = rewrite;
811
+ const summary = this.formatURLRewriteSummary(targetName, updatedURL);
812
+ if (warningKeys.has(summary)) {
813
+ return;
814
+ }
815
+ warningKeys.add(summary);
816
+ // Emit a single warning per URL: when fragment tokens are malformed, the
817
+ // dedicated malformed-token warning is the authoritative one (it already
818
+ // contains both the original URL and the "will get replaced with" line,
819
+ // PLUS the malformed-token list and the verify instruction). For clean
820
+ // URLs we fall back to the plain rewrite warning. Defense-in-depth: if
821
+ // the malformed formatter unexpectedly returns empty, fall back to the
822
+ // plain rewrite warning so the customer still sees something.
823
+ const hasMalformedTokens = Array.isArray(malformedTokens) && malformedTokens.length > 0;
824
+ let warningText = '';
825
+ if (hasMalformedTokens) {
826
+ warningText = this.formatMalformedFragmentTokenWarning(targetName, updatedURL, malformedTokens);
827
+ }
828
+ if (!warningText) {
829
+ warningText = this.messages.getMessage('webPageOmniScriptNavigationDetected', [
830
+ this.toReadableURL(targetName),
831
+ this.toReadableURL(updatedURL),
832
+ ]);
833
+ }
834
+ flexCardAssessmentInfo.warnings.push(warningText);
835
+ flexCardAssessmentInfo.migrationStatus = (0, stringUtils_1.getUpdatedAssessmentStatus)(flexCardAssessmentInfo.migrationStatus, 'Warnings');
836
+ }
837
+ /**
838
+ * Build the customer-facing warning shown when a Web Page action URL contains
839
+ * fragment tokens that decodeURIComponent couldn't parse. Centralised here so
840
+ * the assessment and migration paths emit byte-identical text.
841
+ *
842
+ * Returns an empty string when the helper is called with no malformed tokens
843
+ * (defense in depth: callers already guard, but a future caller that forgets
844
+ * shouldn't be able to produce a degenerate warning with an empty bracket).
845
+ * Non-string entries are coerced via String() so a misbehaving caller can't
846
+ * surface "undefined" / "[object Object]" to customers; duplicates are
847
+ * suppressed so a fragment like `#/Foo%/Bar/Foo%/Baz` doesn't render as
848
+ * `["Foo%", "Foo%"]`.
849
+ */
850
+ formatMalformedFragmentTokenWarning(targetName, updatedURL, malformedTokens) {
851
+ if (!Array.isArray(malformedTokens) || malformedTokens.length === 0) {
852
+ return '';
853
+ }
854
+ const seen = new Set();
855
+ const uniqueTokens = [];
856
+ for (const raw of malformedTokens) {
857
+ const t = typeof raw === 'string' ? raw : String(raw);
858
+ if (!seen.has(t)) {
859
+ seen.add(t);
860
+ uniqueTokens.push(t);
861
+ }
862
+ }
863
+ const tokenList = uniqueTokens.map((t) => `"${t}"`).join(', ');
864
+ return this.messages.getMessage('webPageOmniScriptUrlMalformedToken', [
865
+ tokenList,
866
+ this.toReadableURL(targetName),
867
+ this.toReadableURL(updatedURL),
868
+ ]);
869
+ }
683
870
  async getAllCards() {
684
871
  //DebugTimer.getInstance().lap('Query Vlocity Cards');
685
872
  const filters = new Map();
@@ -752,7 +939,12 @@ class CardMigrationTool extends base_1.BaseMigrationTool {
752
939
  }
753
940
  // Perform the transformation
754
941
  const invalidIpNames = new Map();
755
- const transformedCard = this.mapVlocityCardRecord(card, cardsUploadInfo, invalidIpNames); // This only has the card structure, card definition is not there
942
+ const urlUpdateSummaries = new Set();
943
+ // Map keyed by rewrite summary so the emission step below can subtract
944
+ // malformed URLs from the aggregate "Updated URLs:" locations message.
945
+ // Same key for two actions on the same card => one entry (deduped).
946
+ const malformedFragmentWarnings = new Map();
947
+ const transformedCard = this.mapVlocityCardRecord(card, cardsUploadInfo, invalidIpNames, urlUpdateSummaries, malformedFragmentWarnings); // This only has the card structure, card definition is not there
756
948
  // Verify duplicated names
757
949
  let transformedCardName;
758
950
  if (this.allVersions) {
@@ -822,6 +1014,25 @@ class CardMigrationTool extends base_1.BaseMigrationTool {
822
1014
  if (transformedCard['Name'] !== card['Name']) {
823
1015
  uploadResult.warnings.unshift(this.messages.getMessage('cardNameChangeMessage', [transformedCardName]));
824
1016
  }
1017
+ // The count summary describes ONLY URLs that were converted cleanly
1018
+ // to the standard URL format. Malformed URLs were rewritten with
1019
+ // their bad tokens kept as-is -- claiming they were "updated to
1020
+ // standard URL format" alongside their own malformed warning would
1021
+ // contradict that warning and confuse the customer. Each malformed
1022
+ // URL is surfaced via its own dedicated bullet below which already
1023
+ // names the original URL, the rewritten URL, and the verify
1024
+ // instruction.
1025
+ const cleanRewriteSummaries = Array.from(urlUpdateSummaries).filter((summary) => !malformedFragmentWarnings.has(summary));
1026
+ if (cleanRewriteSummaries.length > 0) {
1027
+ uploadResult.warnings.unshift(this.messages.getMessage('flexCardOmniScriptNavigateURLUpdated', [String(cleanRewriteSummaries.length)]));
1028
+ }
1029
+ // Surface malformed-fragment-token warnings (one per affected URL).
1030
+ // These are intentionally emitted *instead of* the count bullet
1031
+ // above so the customer sees a single, actionable bullet per
1032
+ // malformed URL: original URL, rewrite, verify instruction.
1033
+ for (const malformedWarning of malformedFragmentWarnings.values()) {
1034
+ uploadResult.warnings.push(malformedWarning);
1035
+ }
825
1036
  if (uploadResult.id && invalidIpNames.size > 0) {
826
1037
  const val = Array.from(invalidIpNames.entries())
827
1038
  .map((e) => e[0])
@@ -1002,7 +1213,7 @@ class CardMigrationTool extends base_1.BaseMigrationTool {
1002
1213
  card[this.getFieldKey('Definition__c')] = JSON.stringify(definition);
1003
1214
  }
1004
1215
  // Maps an indivitdual VlocityCard__c record to an OmniUiCard record.
1005
- mapVlocityCardRecord(cardRecord, cardsUploadInfo, invalidIpNames) {
1216
+ mapVlocityCardRecord(cardRecord, cardsUploadInfo, invalidIpNames, urlUpdateSummaries, malformedFragmentWarnings) {
1006
1217
  var _a, _b;
1007
1218
  // Transformed object
1008
1219
  let mappedObject = {};
@@ -1082,7 +1293,7 @@ class CardMigrationTool extends base_1.BaseMigrationTool {
1082
1293
  const isCardActive = cardRecord[this.getFieldKey('Active__c')];
1083
1294
  this.ensureCommunityTargets(mappedObject, isCardActive);
1084
1295
  // Update all dependencies comprehensively
1085
- this.updateAllDependenciesWithRegistry(mappedObject, invalidIpNames);
1296
+ this.updateAllDependenciesWithRegistry(mappedObject, invalidIpNames, urlUpdateSummaries, malformedFragmentWarnings);
1086
1297
  mappedObject['attributes'] = {
1087
1298
  type: CardMigrationTool.OMNIUICARD_NAME,
1088
1299
  referenceId: cardRecord['Id'],
@@ -1092,7 +1303,7 @@ class CardMigrationTool extends base_1.BaseMigrationTool {
1092
1303
  /**
1093
1304
  * Comprehensive dependency update using NameMappingRegistry - mirrors assessment logic
1094
1305
  */
1095
- updateAllDependenciesWithRegistry(mappedObject, invalidIpNames) {
1306
+ updateAllDependenciesWithRegistry(mappedObject, invalidIpNames, urlUpdateSummaries, malformedFragmentWarnings) {
1096
1307
  // Handle propertySet (Definition) - update all dependency references
1097
1308
  const propertySet = JSON.parse(mappedObject[VlocityCard_1.default.Definition__c] || '{}');
1098
1309
  if (propertySet) {
@@ -1127,7 +1338,7 @@ class CardMigrationTool extends base_1.BaseMigrationTool {
1127
1338
  for (const componentKey in state.components) {
1128
1339
  if (state.components.hasOwnProperty(componentKey)) {
1129
1340
  const component = state.components[componentKey];
1130
- this.updateComponentDependenciesWithRegistry(component);
1341
+ this.updateComponentDependenciesWithRegistry(component, urlUpdateSummaries, malformedFragmentWarnings);
1131
1342
  }
1132
1343
  }
1133
1344
  }
@@ -1183,7 +1394,7 @@ class CardMigrationTool extends base_1.BaseMigrationTool {
1183
1394
  * Update dataSource (DataRaptor, Integration Procedures, Apex Remote) using registry
1184
1395
  */
1185
1396
  updateDataSourceWithRegistry(dataSource, invalidIpNames, context) {
1186
- var _a, _b;
1397
+ var _a, _b, _c;
1187
1398
  const type = dataSource.type;
1188
1399
  if (type === stringContants_1.Constants.DataRaptorComponentName) {
1189
1400
  // Handle DataRaptor using registry
@@ -1214,6 +1425,11 @@ class CardMigrationTool extends base_1.BaseMigrationTool {
1214
1425
  }
1215
1426
  }
1216
1427
  }
1428
+ else if (type === stringContants_1.Constants.ApexRemoteComponentName) {
1429
+ if ((_c = dataSource.value) === null || _c === void 0 ? void 0 : _c.remoteClass) {
1430
+ dataSource.value.remoteClass = this.apexNamespaceRegistry.getQualifiedClassName(dataSource.value.remoteClass);
1431
+ }
1432
+ }
1217
1433
  }
1218
1434
  /**
1219
1435
  * Update OmniScript reference using registry
@@ -1247,7 +1463,7 @@ class CardMigrationTool extends base_1.BaseMigrationTool {
1247
1463
  /**
1248
1464
  * Update component dependencies comprehensively
1249
1465
  */
1250
- updateComponentDependenciesWithRegistry(component) {
1466
+ updateComponentDependenciesWithRegistry(component, urlUpdateSummaries, malformedFragmentWarnings) {
1251
1467
  // Handle action elements with actionList (like assessment)
1252
1468
  if (component.element === 'action' && component.property && component.property.actionList) {
1253
1469
  for (const action of component.property.actionList) {
@@ -1266,6 +1482,10 @@ class CardMigrationTool extends base_1.BaseMigrationTool {
1266
1482
  else if (this.hasFlyoutLwc(action.stateAction)) {
1267
1483
  this.updateFlyoutLwcValue(action.stateAction);
1268
1484
  }
1485
+ // Case A: Custom Web Page action referencing OmniScript Universal Page
1486
+ else if (this.isCustomWebPageAction(action.stateAction)) {
1487
+ this.applyOmniScriptURLRewrite(action.stateAction, urlUpdateSummaries, malformedFragmentWarnings);
1488
+ }
1269
1489
  }
1270
1490
  }
1271
1491
  }
@@ -1291,6 +1511,9 @@ class CardMigrationTool extends base_1.BaseMigrationTool {
1291
1511
  if (this.hasFlyoutLwc(component.property.stateAction)) {
1292
1512
  this.updateFlyoutLwcValue(component.property.stateAction);
1293
1513
  }
1514
+ if (this.isCustomWebPageAction(component.property.stateAction)) {
1515
+ this.applyOmniScriptURLRewrite(component.property.stateAction, urlUpdateSummaries, malformedFragmentWarnings);
1516
+ }
1294
1517
  }
1295
1518
  // Handle childCardPreview elements (from old fixChildren method)
1296
1519
  if (component.element === stringContants_1.Constants.ChildCardPreview && component.property) {
@@ -1337,9 +1560,201 @@ class CardMigrationTool extends base_1.BaseMigrationTool {
1337
1560
  // Check child components recursively
1338
1561
  if (component.children && Array.isArray(component.children)) {
1339
1562
  for (const child of component.children) {
1340
- this.updateComponentDependenciesWithRegistry(child);
1563
+ this.updateComponentDependenciesWithRegistry(child, urlUpdateSummaries, malformedFragmentWarnings);
1564
+ }
1565
+ }
1566
+ }
1567
+ applyOmniScriptURLRewrite(stateAction, urlUpdateSummaries, malformedFragmentWarnings) {
1568
+ if (this.IS_STANDARD_DATA_MODEL) {
1569
+ return;
1570
+ }
1571
+ const rewrite = this.getOmniScriptURLRewrite(stateAction);
1572
+ if (!rewrite) {
1573
+ return;
1574
+ }
1575
+ const { targetName, updatedURL, malformedTokens } = rewrite;
1576
+ stateAction[stringContants_1.Constants.WebPageTargetType].targetName = updatedURL;
1577
+ const summary = this.formatURLRewriteSummary(targetName, updatedURL);
1578
+ urlUpdateSummaries.add(summary);
1579
+ // Per-card collector is optional so internal/test callers can omit it
1580
+ // without forcing a signature update. The Map is keyed by the rewrite
1581
+ // summary so the emission step can later subtract malformed URLs from
1582
+ // the aggregate "Updated URLs: ..." locations message -- each malformed
1583
+ // entry already carries the original URL, the rewritten URL, and the
1584
+ // verify instruction inside its own warning, so listing it twice would
1585
+ // be redundant. Same key for two actions on the same card => one entry.
1586
+ // Defense-in-depth: skip if the formatter returns an empty string so we
1587
+ // never store a blank warning under a real summary key.
1588
+ if (malformedFragmentWarnings && Array.isArray(malformedTokens) && malformedTokens.length > 0) {
1589
+ const malformedWarning = this.formatMalformedFragmentTokenWarning(targetName, updatedURL, malformedTokens);
1590
+ if (malformedWarning) {
1591
+ malformedFragmentWarnings.set(summary, malformedWarning);
1592
+ }
1593
+ }
1594
+ }
1595
+ /** Build a human-readable "before -> after" string for warning messages only. */
1596
+ formatURLRewriteSummary(originalURL, updatedURL) {
1597
+ return `${this.toReadableURL(originalURL)} -> ${this.toReadableURL(updatedURL)}`;
1598
+ }
1599
+ /**
1600
+ * Decode a URL for DISPLAY purposes only (warning/log messages). The output
1601
+ * is lossy with respect to URL parsing semantics (e.g. an encoded `%26`
1602
+ * becomes a literal `&`) and must NOT be re-fed into a URL parser. Falls
1603
+ * back to the raw input on malformed percent sequences.
1604
+ */
1605
+ toReadableURL(url) {
1606
+ try {
1607
+ return decodeURIComponent(url);
1608
+ }
1609
+ catch {
1610
+ return url;
1611
+ }
1612
+ }
1613
+ getOmniScriptURLRewrite(stateAction) {
1614
+ var _a;
1615
+ const targetName = (_a = stateAction[stringContants_1.Constants.WebPageTargetType]) === null || _a === void 0 ? void 0 : _a.targetName;
1616
+ if (typeof targetName !== 'string') {
1617
+ return undefined;
1618
+ }
1619
+ // Fast reject: if the raw text doesn't even mention the universal-page
1620
+ // path or token, skip URL parsing entirely. This is a substring screen,
1621
+ // not a security check; the strict structural validation happens after
1622
+ // parsing in isValidOmniScriptNavigationURL.
1623
+ if (!targetName.includes(stringContants_1.Constants.OmniScriptUniversalPagePath) ||
1624
+ !targetName.includes(stringContants_1.Constants.OmniScriptUniversalPageToken)) {
1625
+ return undefined;
1626
+ }
1627
+ const parsedURL = this.tryParseURL(targetName);
1628
+ if (!parsedURL || !this.isValidOmniScriptNavigationURL(parsedURL)) {
1629
+ return undefined;
1630
+ }
1631
+ const updatedURL = this.convertToStandardOmniScriptURL(parsedURL);
1632
+ // Re-parse the fragment once more here purely to capture malformed-token
1633
+ // diagnostics for the caller. Cheap (single split + map over a short
1634
+ // string) and keeps `convertToStandardOmniScriptURL` focused on rewriting.
1635
+ const { malformedTokens } = this.parseSlashFragmentParams(parsedURL.hash);
1636
+ return { targetName, updatedURL, malformedTokens };
1637
+ }
1638
+ isCustomWebPageAction(stateAction) {
1639
+ return (stateAction === null || stateAction === void 0 ? void 0 : stateAction.type) === stringContants_1.Constants.CustomActionType && (stateAction === null || stateAction === void 0 ? void 0 : stateAction.targetType) === stringContants_1.Constants.WebPageTargetType;
1640
+ }
1641
+ /**
1642
+ * Parse a URL string without throwing. Returns the parsed URL on success or
1643
+ * undefined for:
1644
+ * - syntactically invalid input (e.g. `https://[bad`, raw spaces in host,
1645
+ * `http:///` with no host), or
1646
+ * - URLs whose scheme is not `http:` or `https:` (e.g. `javascript:`,
1647
+ * `data:`, `file:`, `mailto:`).
1648
+ *
1649
+ * See URL_PARSE_BASE for why a synthetic base is supplied.
1650
+ */
1651
+ tryParseURL(input) {
1652
+ let parsed;
1653
+ try {
1654
+ parsed = new URL(input, CardMigrationTool.URL_PARSE_BASE);
1655
+ }
1656
+ catch {
1657
+ return undefined;
1658
+ }
1659
+ if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
1660
+ return undefined;
1661
+ }
1662
+ return parsed;
1663
+ }
1664
+ /**
1665
+ * Verify a parsed URL has the OmniScript Navigate shape. We validate against
1666
+ * the parsed components (pathname / searchParams / hash) rather than doing
1667
+ * substring matches on the raw input so callers cannot smuggle the required
1668
+ * tokens in unrelated positions (e.g. inside another query value).
1669
+ *
1670
+ * The pathname must START WITH `/apex/` and contain the universal-page
1671
+ * token. Anchoring to the prefix rejects non-http(s) schemes such as
1672
+ * `javascript:alert(1)//apex/...` whose parsed pathname does not begin
1673
+ * with `/` (it begins with the opaque body of the scheme).
1674
+ *
1675
+ * Two legitimate shapes are accepted:
1676
+ * 1. Path + query: /apex/<ns>__OmniScriptUniversalPage?OmniScriptType=...&OmniScriptSubType=...&OmniScriptLang=...
1677
+ * 2. Path + fragment: /apex/<ns>__OmniScriptUniversalPage?...#/OmniScriptType/.../OmniScriptSubType/.../OmniScriptLang/...
1678
+ */
1679
+ isValidOmniScriptNavigationURL(parsedURL) {
1680
+ if (!parsedURL.pathname.startsWith(`${stringContants_1.Constants.OmniScriptUniversalPagePath}/`)) {
1681
+ return false;
1682
+ }
1683
+ if (!parsedURL.pathname.includes(stringContants_1.Constants.OmniScriptUniversalPageToken)) {
1684
+ return false;
1685
+ }
1686
+ const fragmentKeys = new Set(this.parseSlashFragmentParams(parsedURL.hash).pairs.map(([key]) => key));
1687
+ const hasParam = (key) => parsedURL.searchParams.has(key) || fragmentKeys.has(key);
1688
+ return (hasParam(stringContants_1.Constants.OmniScriptTypeParam) &&
1689
+ hasParam(stringContants_1.Constants.OmniScriptSubTypeParam) &&
1690
+ hasParam(stringContants_1.Constants.OmniScriptLangParam));
1691
+ }
1692
+ convertToStandardOmniScriptURL(parsedURL) {
1693
+ var _a;
1694
+ const paramRenames = {
1695
+ [stringContants_1.Constants.OmniScriptTypeParam]: stringContants_1.Constants.OmniScriptStandardTypeParam,
1696
+ [stringContants_1.Constants.OmniScriptSubTypeParam]: stringContants_1.Constants.OmniScriptStandardSubTypeParam,
1697
+ [stringContants_1.Constants.OmniScriptLangParam]: stringContants_1.Constants.OmniScriptStandardLanguageParam,
1698
+ [stringContants_1.Constants.OmniScriptLayoutParam]: stringContants_1.Constants.OmniScriptStandardThemeParam,
1699
+ };
1700
+ const paramsToClean = new Set([stringContants_1.Constants.OmniScriptTypeParam, stringContants_1.Constants.OmniScriptSubTypeParam]);
1701
+ // Merge query-string params with fragment params. Query-string entries win on conflict.
1702
+ const mergedEntries = [];
1703
+ const seenKeys = new Set();
1704
+ parsedURL.searchParams.forEach((value, key) => {
1705
+ mergedEntries.push([key, value]);
1706
+ seenKeys.add(key);
1707
+ });
1708
+ for (const [key, value] of this.parseSlashFragmentParams(parsedURL.hash).pairs) {
1709
+ if (!seenKeys.has(key)) {
1710
+ mergedEntries.push([key, value]);
1711
+ seenKeys.add(key);
1712
+ }
1713
+ }
1714
+ const newSearchParams = new URLSearchParams();
1715
+ for (const [key, value] of mergedEntries) {
1716
+ const newKey = (_a = paramRenames[key]) !== null && _a !== void 0 ? _a : key;
1717
+ const newValue = paramsToClean.has(key) ? this.cleanName(value) : value;
1718
+ newSearchParams.append(newKey, newValue);
1719
+ }
1720
+ return `${stringContants_1.Constants.OmniScriptStandardPagePath}?${newSearchParams.toString()}`;
1721
+ }
1722
+ /**
1723
+ * Parse a slash-separated OmniScript fragment of the form
1724
+ * #/Key1/Value1/Key2/Value2/...
1725
+ * into [key, value] pairs. Tolerates leading `#`, leading `/`, empty values
1726
+ * (consecutive slashes), and a trailing orphan token (which is dropped).
1727
+ *
1728
+ * Tokens with malformed percent sequences (e.g. `Foo%`, `Bar%E2`) are kept
1729
+ * as-is rather than throwing; decodeURIComponent raises URIError on invalid
1730
+ * escape sequences and we don't want one bad token to crash the whole
1731
+ * FlexCard's URL rewrite. The raw (still-encoded) tokens are also returned
1732
+ * via `malformedTokens` so callers can surface them to customers as a
1733
+ * "please verify" warning rather than silently shipping a best-effort guess.
1734
+ */
1735
+ parseSlashFragmentParams(hash) {
1736
+ if (!hash) {
1737
+ return { pairs: [], malformedTokens: [] };
1738
+ }
1739
+ const trimmed = hash.replace(/^#\/?/, '');
1740
+ if (!trimmed) {
1741
+ return { pairs: [], malformedTokens: [] };
1742
+ }
1743
+ const malformedTokens = [];
1744
+ const tokens = trimmed.split('/').map((t) => {
1745
+ try {
1746
+ return decodeURIComponent(t);
1747
+ }
1748
+ catch {
1749
+ malformedTokens.push(t);
1750
+ return t;
1341
1751
  }
1752
+ });
1753
+ const pairs = [];
1754
+ for (let i = 0; i + 1 < tokens.length; i += 2) {
1755
+ pairs.push([tokens[i], tokens[i + 1]]);
1342
1756
  }
1757
+ return { pairs, malformedTokens };
1343
1758
  }
1344
1759
  /**
1345
1760
  * Update omniType.Name using registry (handles Type/SubType/Language format)
@@ -1517,6 +1932,23 @@ class CardMigrationTool extends base_1.BaseMigrationTool {
1517
1932
  flexCardAssessmentInfo.migrationStatus = (0, stringUtils_1.getUpdatedAssessmentStatus)(flexCardAssessmentInfo.migrationStatus, 'Warnings');
1518
1933
  }
1519
1934
  }
1935
+ /**
1936
+ * Helper method to flag a Vlocity Action dependency during assessment.
1937
+ * Vlocity Actions are not supported in standard runtime FlexCards, so the
1938
+ * FlexCard cannot be auto-migrated and must be manually reimplemented using
1939
+ * the FlexCard Action (cardAction) feature.
1940
+ */
1941
+ addVlocityActionDependency(stateAction, flexCardAssessmentInfo) {
1942
+ const actionName = stateAction.name || stateAction.displayName || 'Unnamed Vlocity Action';
1943
+ if (!flexCardAssessmentInfo.dependenciesVlocityAction.includes(actionName)) {
1944
+ flexCardAssessmentInfo.dependenciesVlocityAction.push(actionName);
1945
+ }
1946
+ const warning = this.messages.getMessage('vlocityActionNotSupportedMessage', [actionName]);
1947
+ if (!flexCardAssessmentInfo.warnings.includes(warning)) {
1948
+ flexCardAssessmentInfo.warnings.push(warning);
1949
+ }
1950
+ flexCardAssessmentInfo.migrationStatus = (0, stringUtils_1.getUpdatedAssessmentStatus)(flexCardAssessmentInfo.migrationStatus, 'Needs manual intervention');
1951
+ }
1520
1952
  // ==================== End Assessment Helper Methods ====================
1521
1953
  getCardFields() {
1522
1954
  return this.IS_STANDARD_DATA_MODEL
@@ -1789,4 +2221,5 @@ exports.CardMigrationTool = CardMigrationTool;
1789
2221
  CardMigrationTool.VLOCITYCARD_NAME = 'VlocityCard__c';
1790
2222
  CardMigrationTool.OMNIUICARD_NAME = 'OmniUiCard';
1791
2223
  CardMigrationTool.VERSION_PROP = 'Version__c';
2224
+ CardMigrationTool.URL_PARSE_BASE = 'https://placeholder.local';
1792
2225
  //# sourceMappingURL=flexcard.js.map