lwc-convert 1.8.1 → 1.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -51,8 +51,8 @@ var init_esm_shims = __esm({
51
51
  import { readFileSync } from "fs";
52
52
  import { join } from "path";
53
53
  function getPackageVersion() {
54
- if ("1.8.1") {
55
- return "1.8.1";
54
+ if ("1.8.2") {
55
+ return "1.8.2";
56
56
  }
57
57
  try {
58
58
  const possiblePaths = [
@@ -76,11 +76,12 @@ function getPackageVersion() {
76
76
  return "0.0.0";
77
77
  }
78
78
  }
79
- var DEFAULT_OUTPUT_DIR, CLI_VERSION, CLI_NAME, CLI_DESCRIPTION;
79
+ var DEFAULT_API_VERSION, DEFAULT_OUTPUT_DIR, CLI_VERSION, CLI_NAME, CLI_DESCRIPTION;
80
80
  var init_options = __esm({
81
81
  "src/cli/options.ts"() {
82
82
  "use strict";
83
83
  init_esm_shims();
84
+ DEFAULT_API_VERSION = "62.0";
84
85
  DEFAULT_OUTPUT_DIR = "./lwc-output";
85
86
  CLI_VERSION = getPackageVersion();
86
87
  CLI_NAME = "lwc-convert";
@@ -516,7 +517,16 @@ async function writeFile(filePath, content, dryRun = false) {
516
517
  logger.file("CREATE", filePath);
517
518
  }
518
519
  function toLwcName(name) {
519
- return name.replace(/([a-z0-9])([A-Z])/g, "$1-$2").replace(/([A-Z])([A-Z][a-z])/g, "$1-$2").toLowerCase();
520
+ let result = name.replace(/([a-z0-9])([A-Z])/g, "$1-$2").replace(/([A-Z])([A-Z][a-z])/g, "$1-$2").toLowerCase();
521
+ result = result.replace(/[^a-z0-9-]/g, "-");
522
+ result = result.replace(/-{2,}/g, "-");
523
+ result = result.replace(/^[^a-z]+/, "");
524
+ result = result.replace(/-+$/, "");
525
+ if (!result) {
526
+ result = "component";
527
+ logger.warn(`Component name "${name}" could not be converted to a valid LWC name. Using "${result}" as fallback.`);
528
+ }
529
+ return result;
520
530
  }
521
531
  function toPascalCase(name) {
522
532
  return name.split("-").map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
@@ -770,13 +780,13 @@ function extractFunctionBody(node, source) {
770
780
  }
771
781
  return "";
772
782
  }
773
- function parseAttributeAccess(path19) {
774
- const callee = path19.node.callee;
783
+ function parseAttributeAccess(path20) {
784
+ const callee = path20.node.callee;
775
785
  if (!t.isMemberExpression(callee)) return null;
776
786
  const property = callee.property;
777
787
  if (!t.isIdentifier(property)) return null;
778
788
  if (property.name !== "get" && property.name !== "set") return null;
779
- const args2 = path19.node.arguments;
789
+ const args2 = path20.node.arguments;
780
790
  if (args2.length === 0) return null;
781
791
  const firstArg = args2[0];
782
792
  if (!t.isStringLiteral(firstArg)) return null;
@@ -789,8 +799,8 @@ function parseAttributeAccess(path19) {
789
799
  operation
790
800
  };
791
801
  }
792
- function parseServerCall(path19, _source) {
793
- const callee = path19.node.callee;
802
+ function parseServerCall(path20, _source) {
803
+ const callee = path20.node.callee;
794
804
  if (t.isMemberExpression(callee) && t.isIdentifier(callee.object, { name: "$A" }) && t.isIdentifier(callee.property, { name: "enqueueAction" })) {
795
805
  return {
796
806
  actionName: "enqueueAction",
@@ -800,7 +810,7 @@ function parseServerCall(path19, _source) {
800
810
  if (t.isMemberExpression(callee)) {
801
811
  const property = callee.property;
802
812
  if (t.isIdentifier(property, { name: "get" })) {
803
- const args2 = path19.node.arguments;
813
+ const args2 = path20.node.arguments;
804
814
  if (args2.length > 0 && t.isStringLiteral(args2[0])) {
805
815
  const value = args2[0].value;
806
816
  if (value.startsWith("c.")) {
@@ -815,17 +825,17 @@ function parseServerCall(path19, _source) {
815
825
  }
816
826
  return null;
817
827
  }
818
- function parseHelperCall(path19) {
819
- const callee = path19.node.callee;
828
+ function parseHelperCall(path20) {
829
+ const callee = path20.node.callee;
820
830
  if (t.isMemberExpression(callee) && t.isIdentifier(callee.object, { name: "helper" }) && t.isIdentifier(callee.property)) {
821
831
  return callee.property.name;
822
832
  }
823
833
  return null;
824
834
  }
825
- function parseEventFire(path19) {
826
- const callee = path19.node.callee;
835
+ function parseEventFire(path20) {
836
+ const callee = path20.node.callee;
827
837
  if (t.isMemberExpression(callee) && t.isIdentifier(callee.object, { name: "$A" }) && t.isIdentifier(callee.property, { name: "get" })) {
828
- const args2 = path19.node.arguments;
838
+ const args2 = path20.node.arguments;
829
839
  if (args2.length > 0 && t.isStringLiteral(args2[0])) {
830
840
  const value = args2[0].value;
831
841
  if (value.startsWith("e.")) {
@@ -834,7 +844,7 @@ function parseEventFire(path19) {
834
844
  }
835
845
  }
836
846
  if (t.isMemberExpression(callee) && t.isIdentifier(callee.property, { name: "getEvent" })) {
837
- const args2 = path19.node.arguments;
847
+ const args2 = path20.node.arguments;
838
848
  if (args2.length > 0 && t.isStringLiteral(args2[0])) {
839
849
  return args2[0].value;
840
850
  }
@@ -865,11 +875,11 @@ function parseAuraController(source) {
865
875
  return result;
866
876
  }
867
877
  traverse(ast, {
868
- ObjectExpression(path19) {
869
- if (path19.parent.type === "VariableDeclarator" && t.isIdentifier(path19.parent.id, {
878
+ ObjectExpression(path20) {
879
+ if (path20.parent.type === "VariableDeclarator" && t.isIdentifier(path20.parent.id, {
870
880
  name: "__auraController"
871
881
  })) {
872
- for (const prop of path19.node.properties) {
882
+ for (const prop of path20.node.properties) {
873
883
  if (t.isObjectProperty(prop) && t.isIdentifier(prop.key) && t.isFunctionExpression(prop.value)) {
874
884
  const funcName = prop.key.name;
875
885
  const funcExpr = prop.value;
@@ -893,7 +903,7 @@ function parseAuraController(source) {
893
903
  (p2) => p2 === "event" || p2 === "evt"
894
904
  );
895
905
  funcDef.hasHelper = funcDef.params.some((p2) => p2 === "helper");
896
- const funcPath = path19.get("properties").find((propPath) => {
906
+ const funcPath = path20.get("properties").find((propPath) => {
897
907
  const propNode = propPath.node;
898
908
  return t.isObjectProperty(propNode) && t.isIdentifier(propNode.key, { name: funcName });
899
909
  });
@@ -977,11 +987,11 @@ function parseAuraHelper(source) {
977
987
  }
978
988
  const helperFunctionNames = /* @__PURE__ */ new Set();
979
989
  traverse2(ast, {
980
- ObjectExpression(path19) {
981
- if (path19.parent.type === "VariableDeclarator" && t2.isIdentifier(path19.parent.id, {
990
+ ObjectExpression(path20) {
991
+ if (path20.parent.type === "VariableDeclarator" && t2.isIdentifier(path20.parent.id, {
982
992
  name: "__auraHelper"
983
993
  })) {
984
- for (const prop of path19.node.properties) {
994
+ for (const prop of path20.node.properties) {
985
995
  if (t2.isObjectProperty(prop) && t2.isIdentifier(prop.key)) {
986
996
  helperFunctionNames.add(prop.key.name);
987
997
  }
@@ -990,11 +1000,11 @@ function parseAuraHelper(source) {
990
1000
  }
991
1001
  });
992
1002
  traverse2(ast, {
993
- ObjectExpression(path19) {
994
- if (path19.parent.type === "VariableDeclarator" && t2.isIdentifier(path19.parent.id, {
1003
+ ObjectExpression(path20) {
1004
+ if (path20.parent.type === "VariableDeclarator" && t2.isIdentifier(path20.parent.id, {
995
1005
  name: "__auraHelper"
996
1006
  })) {
997
- for (const prop of path19.node.properties) {
1007
+ for (const prop of path20.node.properties) {
998
1008
  if (t2.isObjectProperty(prop) && t2.isIdentifier(prop.key) && t2.isFunctionExpression(prop.value)) {
999
1009
  const funcName = prop.key.name;
1000
1010
  const funcExpr = prop.value;
@@ -1010,7 +1020,7 @@ function parseAuraHelper(source) {
1010
1020
  funcDef.hasComponent = funcDef.params.some(
1011
1021
  (p2) => p2 === "component" || p2 === "cmp"
1012
1022
  );
1013
- const funcPath = path19.get("properties").find((propPath) => {
1023
+ const funcPath = path20.get("properties").find((propPath) => {
1014
1024
  const propNode = propPath.node;
1015
1025
  return t2.isObjectProperty(propNode) && t2.isIdentifier(propNode.key, { name: funcName });
1016
1026
  });
@@ -4394,11 +4404,23 @@ function convertFunctionBody(body, _func, allHelperFunctions) {
4394
4404
  /(?:component|cmp)\.get\s*\(\s*["']v\.(\w+)["']\s*\)/g,
4395
4405
  "this.$1"
4396
4406
  );
4397
- converted = converted.replace(
4398
- /(?:component|cmp)\.set\s*\(\s*["']v\.(\w+)["']\s*,\s*/g,
4399
- "this.$1 = "
4400
- );
4401
- converted = converted.replace(/this\.(\w+)\s*=\s*([^;]+)\s*\)/g, "this.$1 = $2");
4407
+ const setPattern = /(?:component|cmp)\.set\s*\(\s*["']v\.(\w+)["']\s*,\s*/g;
4408
+ let setMatch;
4409
+ while ((setMatch = setPattern.exec(converted)) !== null) {
4410
+ const propName = setMatch[1];
4411
+ const valueStart = setMatch.index + setMatch[0].length;
4412
+ let depth = 1;
4413
+ let i = valueStart;
4414
+ while (i < converted.length && depth > 0) {
4415
+ if (converted[i] === "(") depth++;
4416
+ else if (converted[i] === ")") depth--;
4417
+ i++;
4418
+ }
4419
+ const value = converted.substring(valueStart, i - 1).trim();
4420
+ const replacement = `this.${propName} = ${value}`;
4421
+ converted = converted.substring(0, setMatch.index) + replacement + converted.substring(i);
4422
+ setPattern.lastIndex = setMatch.index + replacement.length;
4423
+ }
4402
4424
  if (allHelperFunctions) {
4403
4425
  for (const helperFunc of allHelperFunctions) {
4404
4426
  const helperRegex = new RegExp(
@@ -4410,10 +4432,21 @@ function convertFunctionBody(body, _func, allHelperFunctions) {
4410
4432
  }
4411
4433
  if (converted.includes("$A.enqueueAction")) {
4412
4434
  warnings.push("Server action ($A.enqueueAction) found - convert to imperative Apex call");
4413
- converted = converted.replace(
4414
- /\$A\.enqueueAction\s*\([^)]+\)/g,
4415
- "/* TODO: Convert to imperative Apex call */"
4416
- );
4435
+ const enqueuePattern = /\$A\.enqueueAction\s*\(/g;
4436
+ let enqueueMatch;
4437
+ while ((enqueueMatch = enqueuePattern.exec(converted)) !== null) {
4438
+ const callStart = enqueueMatch.index;
4439
+ let depth = 1;
4440
+ let j = callStart + enqueueMatch[0].length;
4441
+ while (j < converted.length && depth > 0) {
4442
+ if (converted[j] === "(") depth++;
4443
+ else if (converted[j] === ")") depth--;
4444
+ j++;
4445
+ }
4446
+ const replacement = "/* TODO: Convert to imperative Apex call */";
4447
+ converted = converted.substring(0, callStart) + replacement + converted.substring(j);
4448
+ enqueuePattern.lastIndex = callStart + replacement.length;
4449
+ }
4417
4450
  }
4418
4451
  if (converted.includes('.get("c.') || converted.includes(".get('c.")) {
4419
4452
  warnings.push("Server action reference found - import Apex method and call imperatively");
@@ -6095,7 +6128,7 @@ var init_confidence_scorer = __esm({
6095
6128
  // src/generators/full-conversion.ts
6096
6129
  function generateMetaXml2(_componentName, options) {
6097
6130
  const {
6098
- apiVersion = "62.0",
6131
+ apiVersion = DEFAULT_API_VERSION,
6099
6132
  isExposed = true,
6100
6133
  targets = ["lightning__RecordPage", "lightning__AppPage", "lightning__HomePage"],
6101
6134
  description,
@@ -6661,6 +6694,7 @@ var init_full_conversion = __esm({
6661
6694
  init_test_generator();
6662
6695
  init_file_io();
6663
6696
  init_logger();
6697
+ init_options();
6664
6698
  init_confidence_scorer();
6665
6699
  }
6666
6700
  });
@@ -6715,40 +6749,53 @@ var init_project_detector = __esm({
6715
6749
  }
6716
6750
  });
6717
6751
 
6752
+ // src/utils/constants.ts
6753
+ var AURA_SEARCH_PATHS, VF_PAGE_SEARCH_PATHS, VF_COMPONENT_SEARCH_PATHS, APEX_SEARCH_PATHS;
6754
+ var init_constants = __esm({
6755
+ "src/utils/constants.ts"() {
6756
+ "use strict";
6757
+ init_esm_shims();
6758
+ AURA_SEARCH_PATHS = [
6759
+ "force-app/main/default/aura",
6760
+ "src/aura",
6761
+ "aura",
6762
+ "force-app/main/aura"
6763
+ ];
6764
+ VF_PAGE_SEARCH_PATHS = [
6765
+ "force-app/main/default/pages",
6766
+ "src/pages",
6767
+ "pages",
6768
+ "force-app/main/pages"
6769
+ ];
6770
+ VF_COMPONENT_SEARCH_PATHS = [
6771
+ "force-app/main/default/components",
6772
+ "src/components",
6773
+ "components",
6774
+ "force-app/main/components"
6775
+ ];
6776
+ APEX_SEARCH_PATHS = [
6777
+ "force-app/main/default/classes",
6778
+ "src/classes",
6779
+ "classes",
6780
+ "force-app/main/classes"
6781
+ ];
6782
+ }
6783
+ });
6784
+
6718
6785
  // src/utils/fuzzy-suggest.ts
6719
6786
  import Fuse from "fuse.js";
6720
6787
  import * as path4 from "path";
6721
6788
  import fs3 from "fs-extra";
6722
6789
  async function suggestAuraComponents(input, maxResults = 3) {
6723
- const searchPaths = [
6724
- "force-app/main/default/aura",
6725
- "src/aura",
6726
- "aura",
6727
- "force-app/main/aura"
6728
- ];
6729
- const components3 = await scanDirectories(searchPaths, ".cmp");
6790
+ const components3 = await scanDirectories(AURA_SEARCH_PATHS, ".cmp");
6730
6791
  return fuzzyMatch(input, components3, maxResults);
6731
6792
  }
6732
6793
  async function suggestVfPages(input, maxResults = 3) {
6733
- const searchPaths = [
6734
- "force-app/main/default/pages",
6735
- "force-app/main/default/components",
6736
- "src/pages",
6737
- "src/components",
6738
- "pages",
6739
- "components"
6740
- ];
6741
- const pages = await scanDirectories(searchPaths, ".page", ".component");
6794
+ const pages = await scanDirectories([...VF_PAGE_SEARCH_PATHS, ...VF_COMPONENT_SEARCH_PATHS], ".page", ".component");
6742
6795
  return fuzzyMatch(input, pages, maxResults);
6743
6796
  }
6744
6797
  async function suggestApexControllers(input, maxResults = 3) {
6745
- const searchPaths = [
6746
- "force-app/main/default/classes",
6747
- "src/classes",
6748
- "classes",
6749
- "force-app/main/classes"
6750
- ];
6751
- const controllers = await scanDirectories(searchPaths, ".cls");
6798
+ const controllers = await scanDirectories(APEX_SEARCH_PATHS, ".cls");
6752
6799
  return fuzzyMatch(input, controllers, maxResults);
6753
6800
  }
6754
6801
  async function scanDirectories(searchPaths, ...extensions) {
@@ -6837,6 +6884,7 @@ var init_fuzzy_suggest = __esm({
6837
6884
  "use strict";
6838
6885
  init_esm_shims();
6839
6886
  init_project_detector();
6887
+ init_constants();
6840
6888
  FUSE_OPTIONS = {
6841
6889
  keys: ["name"],
6842
6890
  threshold: 0.4,
@@ -6994,7 +7042,8 @@ async function resolveApexPath(input) {
6994
7042
  contextualHelp
6995
7043
  };
6996
7044
  }
6997
- async function searchForComponent(baseDir, componentName, extension) {
7045
+ async function searchForComponent(baseDir, componentName, extension, currentDepth = 0, maxDepth = 5) {
7046
+ if (currentDepth >= maxDepth) return null;
6998
7047
  try {
6999
7048
  const entries = await fs4.readdir(baseDir, { withFileTypes: true });
7000
7049
  for (const entry of entries) {
@@ -7009,51 +7058,31 @@ async function searchForComponent(baseDir, componentName, extension) {
7009
7058
  const subResult = await searchForComponent(
7010
7059
  path5.join(baseDir, entry.name),
7011
7060
  componentName,
7012
- extension
7061
+ extension,
7062
+ currentDepth + 1,
7063
+ maxDepth
7013
7064
  );
7014
7065
  if (subResult) {
7015
7066
  return subResult;
7016
7067
  }
7017
7068
  }
7018
7069
  }
7019
- } catch {
7070
+ } catch (error) {
7071
+ logger.debug(`Error searching ${baseDir}: ${error.message}`);
7020
7072
  }
7021
7073
  return null;
7022
7074
  }
7023
7075
  function formatSearchLocations(locations, cwd) {
7024
7076
  return locations.map((loc) => ` - ${path5.relative(cwd, loc) || loc}`).join("\n");
7025
7077
  }
7026
- var AURA_SEARCH_PATHS, VF_PAGE_SEARCH_PATHS, VF_COMPONENT_SEARCH_PATHS, APEX_SEARCH_PATHS;
7027
7078
  var init_path_resolver = __esm({
7028
7079
  "src/utils/path-resolver.ts"() {
7029
7080
  "use strict";
7030
7081
  init_esm_shims();
7031
7082
  init_project_detector();
7083
+ init_logger();
7032
7084
  init_fuzzy_suggest();
7033
- AURA_SEARCH_PATHS = [
7034
- "force-app/main/default/aura",
7035
- "src/aura",
7036
- "aura",
7037
- "force-app/main/aura"
7038
- ];
7039
- VF_PAGE_SEARCH_PATHS = [
7040
- "force-app/main/default/pages",
7041
- "src/pages",
7042
- "pages",
7043
- "force-app/main/pages"
7044
- ];
7045
- VF_COMPONENT_SEARCH_PATHS = [
7046
- "force-app/main/default/components",
7047
- "src/components",
7048
- "components",
7049
- "force-app/main/components"
7050
- ];
7051
- APEX_SEARCH_PATHS = [
7052
- "force-app/main/default/classes",
7053
- "src/classes",
7054
- "classes",
7055
- "force-app/main/classes"
7056
- ];
7085
+ init_constants();
7057
7086
  }
7058
7087
  });
7059
7088
 
@@ -7061,7 +7090,7 @@ var init_path_resolver = __esm({
7061
7090
  import * as fs5 from "fs";
7062
7091
  import * as path6 from "path";
7063
7092
  import * as os from "os";
7064
- var SESSIONS_BASE_DIR, ACTIVE_SESSION_FILE, SESSION_EXPIRY_MS, SessionStore, sessionStore;
7093
+ var SESSIONS_BASE_DIR, ACTIVE_SESSION_FILE, SESSION_EXPIRY_MS, MAX_PATTERN_LIBRARY_SIZE, SessionStore, sessionStore;
7065
7094
  var init_session_store = __esm({
7066
7095
  "src/utils/session-store.ts"() {
7067
7096
  "use strict";
@@ -7070,12 +7099,15 @@ var init_session_store = __esm({
7070
7099
  SESSIONS_BASE_DIR = path6.join(os.tmpdir(), "lwc-convert-sessions");
7071
7100
  ACTIVE_SESSION_FILE = path6.join(SESSIONS_BASE_DIR, "active-session.json");
7072
7101
  SESSION_EXPIRY_MS = 4 * 60 * 60 * 1e3;
7102
+ MAX_PATTERN_LIBRARY_SIZE = 1e3;
7073
7103
  SessionStore = class {
7074
7104
  sessionId = "";
7075
7105
  sessionDir = "";
7076
7106
  conversions = [];
7107
+ conversionIndex = /* @__PURE__ */ new Map();
7077
7108
  patternLibrary = /* @__PURE__ */ new Map();
7078
7109
  initialized = false;
7110
+ initPromise = null;
7079
7111
  startedAt = /* @__PURE__ */ new Date();
7080
7112
  constructor() {
7081
7113
  }
@@ -7130,6 +7162,7 @@ var init_session_store = __esm({
7130
7162
  const record = JSON.parse(content);
7131
7163
  record.timestamp = new Date(record.timestamp);
7132
7164
  this.conversions.push(record);
7165
+ this.conversionIndex.set(record.id, record);
7133
7166
  for (const pattern of record.patterns) {
7134
7167
  this.updatePatternLibrary([pattern]);
7135
7168
  }
@@ -7183,17 +7216,22 @@ var init_session_store = __esm({
7183
7216
  */
7184
7217
  async init() {
7185
7218
  if (this.initialized) return;
7186
- try {
7187
- const loaded = await this.tryLoadExistingSession();
7188
- if (!loaded) {
7189
- await this.createNewSession();
7219
+ if (this.initPromise) return this.initPromise;
7220
+ this.initPromise = (async () => {
7221
+ try {
7222
+ const loaded = await this.tryLoadExistingSession();
7223
+ if (!loaded) {
7224
+ await this.createNewSession();
7225
+ }
7226
+ await this.updateActiveSessionFile();
7227
+ this.initialized = true;
7228
+ logger.debug(`Session store initialized: ${this.sessionDir}`);
7229
+ } catch (error) {
7230
+ logger.debug(`Failed to initialize session store: ${error.message}`);
7231
+ this.initPromise = null;
7190
7232
  }
7191
- await this.updateActiveSessionFile();
7192
- this.initialized = true;
7193
- logger.debug(`Session store initialized: ${this.sessionDir}`);
7194
- } catch (error) {
7195
- logger.debug(`Failed to initialize session store: ${error.message}`);
7196
- }
7233
+ })();
7234
+ return this.initPromise;
7197
7235
  }
7198
7236
  /**
7199
7237
  * Get the session directory path
@@ -7233,6 +7271,7 @@ var init_session_store = __esm({
7233
7271
  success: true
7234
7272
  };
7235
7273
  this.conversions.push(record);
7274
+ this.conversionIndex.set(record.id, record);
7236
7275
  this.updatePatternLibrary(record.patterns);
7237
7276
  try {
7238
7277
  const conversionFile = path6.join(this.sessionDir, "conversions", `${record.id}.json`);
@@ -7296,7 +7335,15 @@ var init_session_store = __esm({
7296
7335
  if (existing) {
7297
7336
  existing.frequency++;
7298
7337
  existing.successRate = (existing.successRate * (existing.frequency - 1) + pattern.successRate) / existing.frequency;
7338
+ this.patternLibrary.delete(key);
7339
+ this.patternLibrary.set(key, existing);
7299
7340
  } else {
7341
+ if (this.patternLibrary.size >= MAX_PATTERN_LIBRARY_SIZE) {
7342
+ const firstKey = this.patternLibrary.keys().next().value;
7343
+ if (firstKey !== void 0) {
7344
+ this.patternLibrary.delete(firstKey);
7345
+ }
7346
+ }
7300
7347
  this.patternLibrary.set(key, { ...pattern });
7301
7348
  }
7302
7349
  }
@@ -7333,13 +7380,13 @@ var init_session_store = __esm({
7333
7380
  * Get conversion by ID
7334
7381
  */
7335
7382
  getConversion(id) {
7336
- return this.conversions.find((c) => c.id === id);
7383
+ return this.conversionIndex.get(id);
7337
7384
  }
7338
7385
  /**
7339
7386
  * Update test results for a conversion
7340
7387
  */
7341
7388
  async updateTestResults(conversionId, results) {
7342
- const conversion = this.conversions.find((c) => c.id === conversionId);
7389
+ const conversion = this.conversionIndex.get(conversionId);
7343
7390
  if (!conversion) return;
7344
7391
  conversion.testResults = results;
7345
7392
  conversion.success = results.failed === 0;
@@ -7471,8 +7518,10 @@ All session data is stored in:
7471
7518
  await fs5.promises.unlink(ACTIVE_SESSION_FILE);
7472
7519
  }
7473
7520
  this.conversions = [];
7521
+ this.conversionIndex.clear();
7474
7522
  this.patternLibrary.clear();
7475
7523
  this.initialized = false;
7524
+ this.initPromise = null;
7476
7525
  logger.debug(`Cleaned up session: ${this.sessionId}`);
7477
7526
  } catch (error) {
7478
7527
  logger.debug(`Failed to cleanup session: ${error.message}`);
@@ -7513,6 +7562,13 @@ All session data is stored in:
7513
7562
  // src/utils/preview-generator.ts
7514
7563
  import fs6 from "fs-extra";
7515
7564
  import * as path7 from "path";
7565
+ import { execFile, spawn } from "child_process";
7566
+ function escapeHtml(str) {
7567
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
7568
+ }
7569
+ function escapeCssContent(css) {
7570
+ return css.replace(/<\/style/gi, "<\\/style");
7571
+ }
7516
7572
  function parseAttributes(tagContent) {
7517
7573
  const attrs = {};
7518
7574
  const attrRegex = /(\S+?)(?:=(?:"([^"]*)"|{([^}]*)})|(?=\s|>|$))/g;
@@ -7611,12 +7667,14 @@ function transformLwcToPreviewHtml(lwcHtml) {
7611
7667
  function generatePreviewHtml(bundle, componentCss) {
7612
7668
  const previewContent = transformLwcToPreviewHtml(bundle.html);
7613
7669
  const css = componentCss ?? bundle.css;
7670
+ const safeName = escapeHtml(bundle.name);
7671
+ const safeCss = css ? escapeCssContent(css) : "";
7614
7672
  return `<!DOCTYPE html>
7615
7673
  <html lang="en">
7616
7674
  <head>
7617
7675
  <meta charset="UTF-8">
7618
7676
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
7619
- <title>LWC Preview: ${bundle.name}</title>
7677
+ <title>LWC Preview: ${safeName}</title>
7620
7678
 
7621
7679
  <!-- Salesforce Lightning Design System -->
7622
7680
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/design-system/2.24.2/styles/salesforce-lightning-design-system.min.css">
@@ -7778,7 +7836,7 @@ function generatePreviewHtml(bundle, componentCss) {
7778
7836
  }
7779
7837
 
7780
7838
  /* Component-specific CSS from conversion */
7781
- ${css || "/* No component CSS */"}
7839
+ ${safeCss || "/* No component CSS */"}
7782
7840
 
7783
7841
  /* Legend */
7784
7842
  .preview-legend {
@@ -7836,7 +7894,7 @@ function generatePreviewHtml(bundle, componentCss) {
7836
7894
  <body>
7837
7895
  <div class="preview-container">
7838
7896
  <div class="preview-header">
7839
- <h1>${bundle.name}</h1>
7897
+ <h1>${safeName}</h1>
7840
7898
  <div class="meta">LWC Preview &bull; Generated by lwc-convert</div>
7841
7899
  </div>
7842
7900
 
@@ -7911,20 +7969,21 @@ async function writePreviewFile(outputDir, bundle, dryRun = false) {
7911
7969
  return previewPath;
7912
7970
  }
7913
7971
  async function openPreview(previewPath) {
7914
- const { exec: exec2 } = await import("child_process");
7915
- const { promisify } = await import("util");
7916
- const execAsync = promisify(exec2);
7917
7972
  const platform3 = process.platform;
7918
- let command;
7919
- if (platform3 === "win32") {
7920
- command = `start "" "${previewPath}"`;
7921
- } else if (platform3 === "darwin") {
7922
- command = `open "${previewPath}"`;
7923
- } else {
7924
- command = `xdg-open "${previewPath}"`;
7925
- }
7926
7973
  try {
7927
- await execAsync(command);
7974
+ if (platform3 === "win32") {
7975
+ execFile("explorer", [previewPath], () => {
7976
+ });
7977
+ } else if (platform3 === "darwin") {
7978
+ execFile("open", [previewPath], () => {
7979
+ });
7980
+ } else {
7981
+ const child = spawn("xdg-open", [previewPath], {
7982
+ detached: true,
7983
+ stdio: "ignore"
7984
+ });
7985
+ child.unref();
7986
+ }
7928
7987
  logger.success("Opened preview in default browser");
7929
7988
  } catch (error) {
7930
7989
  logger.warn(`Could not open preview automatically: ${error.message}`);
@@ -8288,31 +8347,38 @@ var open_folder_exports = {};
8288
8347
  __export(open_folder_exports, {
8289
8348
  openFolder: () => openFolder
8290
8349
  });
8291
- import { exec } from "child_process";
8350
+ import { execFile as execFile2, spawn as spawn2 } from "child_process";
8292
8351
  import * as os2 from "os";
8293
8352
  function openFolder(folderPath) {
8294
- return new Promise((resolve8, _reject) => {
8353
+ return new Promise((resolve10) => {
8295
8354
  const platform3 = os2.platform();
8296
- let command;
8297
- switch (platform3) {
8298
- case "win32":
8299
- command = `start "" "${folderPath}"`;
8300
- break;
8301
- case "darwin":
8302
- command = `open "${folderPath}"`;
8303
- break;
8304
- default:
8305
- command = `xdg-open "${folderPath}"`;
8306
- break;
8307
- }
8308
- exec(command, (error) => {
8309
- if (error) {
8310
- logger.warn(`Could not open folder: ${error.message}`);
8311
- resolve8();
8355
+ try {
8356
+ if (platform3 === "win32") {
8357
+ execFile2("explorer", [folderPath], (error) => {
8358
+ if (error) {
8359
+ logger.warn(`Could not open folder: ${error.message}`);
8360
+ }
8361
+ resolve10();
8362
+ });
8363
+ } else if (platform3 === "darwin") {
8364
+ execFile2("open", [folderPath], (error) => {
8365
+ if (error) {
8366
+ logger.warn(`Could not open folder: ${error.message}`);
8367
+ }
8368
+ resolve10();
8369
+ });
8312
8370
  } else {
8313
- resolve8();
8371
+ const child = spawn2("xdg-open", [folderPath], {
8372
+ detached: true,
8373
+ stdio: "ignore"
8374
+ });
8375
+ child.unref();
8376
+ resolve10();
8314
8377
  }
8315
- });
8378
+ } catch (error) {
8379
+ logger.warn(`Could not open folder: ${error.message}`);
8380
+ resolve10();
8381
+ }
8316
8382
  });
8317
8383
  }
8318
8384
  var init_open_folder = __esm({
@@ -9633,6 +9699,8 @@ var init_grader = __esm({
9633
9699
  Grader = class {
9634
9700
  auraGrader;
9635
9701
  vfGrader;
9702
+ /** Cache of grading results keyed by resolved file path */
9703
+ gradeCache = /* @__PURE__ */ new Map();
9636
9704
  constructor() {
9637
9705
  this.auraGrader = new AuraGrader();
9638
9706
  this.vfGrader = new VfGrader();
@@ -9681,7 +9749,8 @@ var init_grader = __esm({
9681
9749
  }
9682
9750
  }
9683
9751
  }
9684
- } catch {
9752
+ } catch (error) {
9753
+ logger.debug(`Error scanning ${dirPath}: ${error.message}`);
9685
9754
  }
9686
9755
  };
9687
9756
  await searchDir(basePath);
@@ -9697,29 +9766,48 @@ var init_grader = __esm({
9697
9766
  if (grade2) results.push(grade2);
9698
9767
  } else {
9699
9768
  const searchPaths = options.targetPath ? [options.targetPath] : await this.getStandardPaths(options.type);
9769
+ const allFiles = [];
9700
9770
  for (const searchPath of searchPaths) {
9701
9771
  const found = await this.scanDirectory(searchPath, options.type);
9702
- for (const file of found) {
9703
- try {
9704
- const grade2 = await this.gradeSingle(file, options.type === "both" ? this.detectType(file) : options.type);
9705
- if (grade2) results.push(grade2);
9706
- } catch (err) {
9707
- logger.error(`Failed to grade ${file}: ${err.message}`);
9772
+ allFiles.push(...found);
9773
+ }
9774
+ for (let idx = 0; idx < allFiles.length; idx++) {
9775
+ const file = allFiles[idx];
9776
+ if (allFiles.length > 5) {
9777
+ logger.debug(`Grading ${idx + 1}/${allFiles.length}: ${path11.basename(file)}`);
9778
+ if ((idx + 1) % 10 === 0 || idx === allFiles.length - 1) {
9779
+ process.stdout.write(`\r Grading components... ${idx + 1}/${allFiles.length}`);
9708
9780
  }
9709
9781
  }
9782
+ try {
9783
+ const grade2 = await this.gradeSingle(file, options.type === "both" ? this.detectType(file) : options.type);
9784
+ if (grade2) results.push(grade2);
9785
+ } catch (err) {
9786
+ logger.error(`Failed to grade ${file}: ${err.message}`);
9787
+ }
9788
+ }
9789
+ if (allFiles.length > 5) {
9790
+ process.stdout.write("\r" + " ".repeat(60) + "\r");
9710
9791
  }
9711
9792
  }
9712
9793
  return results;
9713
9794
  }
9714
9795
  async gradeSingle(filePath, type) {
9796
+ const resolvedPath = path11.resolve(filePath);
9797
+ const cached = this.gradeCache.get(resolvedPath);
9798
+ if (cached) return cached;
9715
9799
  const resolvedType = type === "both" ? this.detectType(filePath) : type;
9800
+ let result = null;
9716
9801
  if (resolvedType === "aura") {
9717
9802
  const bundlePath = filePath.endsWith(".cmp") ? path11.dirname(filePath) : filePath;
9718
- return this.auraGrader.grade(bundlePath);
9803
+ result = await this.auraGrader.grade(bundlePath);
9719
9804
  } else if (resolvedType === "vf") {
9720
- return this.vfGrader.grade(filePath);
9805
+ result = await this.vfGrader.grade(filePath);
9721
9806
  }
9722
- return null;
9807
+ if (result) {
9808
+ this.gradeCache.set(resolvedPath, result);
9809
+ }
9810
+ return result;
9723
9811
  }
9724
9812
  detectType(filePath) {
9725
9813
  if (filePath.endsWith(".cmp") || filePath.includes("/aura/") || filePath.includes("\\aura\\")) return "aura";
@@ -10010,7 +10098,7 @@ var init_vf_controller_resolver = __esm({
10010
10098
 
10011
10099
  // src/utils/first-time.ts
10012
10100
  import fs14 from "fs-extra";
10013
- import path15 from "path";
10101
+ import path16 from "path";
10014
10102
  import os4 from "os";
10015
10103
  function isFirstTimeSync() {
10016
10104
  try {
@@ -10059,8 +10147,8 @@ var init_first_time = __esm({
10059
10147
  "src/utils/first-time.ts"() {
10060
10148
  "use strict";
10061
10149
  init_esm_shims();
10062
- CONFIG_DIR = path15.join(os4.homedir(), ".lwc-convert");
10063
- FIRST_RUN_FILE = path15.join(CONFIG_DIR, ".first-run-complete");
10150
+ CONFIG_DIR = path16.join(os4.homedir(), ".lwc-convert");
10151
+ FIRST_RUN_FILE = path16.join(CONFIG_DIR, ".first-run-complete");
10064
10152
  }
10065
10153
  });
10066
10154
 
@@ -10070,7 +10158,7 @@ __export(interactive_exports, {
10070
10158
  runInteractiveTui: () => runInteractiveTui
10071
10159
  });
10072
10160
  import * as p from "@clack/prompts";
10073
- import * as path16 from "path";
10161
+ import * as path17 from "path";
10074
10162
  import fs15 from "fs-extra";
10075
10163
  function showBreadcrumbs(currentStep, conversionType) {
10076
10164
  const breadcrumb = STEPS.map((step, i) => {
@@ -10098,16 +10186,16 @@ async function findAuraComponents() {
10098
10186
  const components3 = [];
10099
10187
  const cwd = process.cwd();
10100
10188
  for (const searchPath of searchPaths) {
10101
- const fullPath = path16.join(cwd, searchPath);
10189
+ const fullPath = path17.join(cwd, searchPath);
10102
10190
  if (await fs15.pathExists(fullPath)) {
10103
10191
  try {
10104
10192
  const entries = await fs15.readdir(fullPath, { withFileTypes: true });
10105
10193
  for (const entry of entries) {
10106
10194
  if (entry.isDirectory()) {
10107
- const cmpFile = path16.join(fullPath, entry.name, `${entry.name}.cmp`);
10195
+ const cmpFile = path17.join(fullPath, entry.name, `${entry.name}.cmp`);
10108
10196
  if (await fs15.pathExists(cmpFile)) {
10109
10197
  components3.push({
10110
- value: path16.join(searchPath, entry.name),
10198
+ value: path17.join(searchPath, entry.name),
10111
10199
  label: `\u26A1 ${entry.name}`,
10112
10200
  hint: searchPath
10113
10201
  });
@@ -10129,14 +10217,14 @@ async function findVfPages() {
10129
10217
  const pages = [];
10130
10218
  const cwd = process.cwd();
10131
10219
  for (const searchPath of searchPaths) {
10132
- const fullPath = path16.join(cwd, searchPath);
10220
+ const fullPath = path17.join(cwd, searchPath);
10133
10221
  if (await fs15.pathExists(fullPath)) {
10134
10222
  try {
10135
10223
  const entries = await fs15.readdir(fullPath, { withFileTypes: true });
10136
10224
  for (const entry of entries) {
10137
10225
  if (entry.isFile() && entry.name.endsWith(".page")) {
10138
10226
  pages.push({
10139
- value: path16.join(searchPath, entry.name),
10227
+ value: path17.join(searchPath, entry.name),
10140
10228
  label: `\u{1F4C4} ${entry.name.replace(".page", "")}`,
10141
10229
  hint: searchPath
10142
10230
  });
@@ -10157,14 +10245,14 @@ async function findApexControllers() {
10157
10245
  const controllers = [];
10158
10246
  const cwd = process.cwd();
10159
10247
  for (const searchPath of searchPaths) {
10160
- const fullPath = path16.join(cwd, searchPath);
10248
+ const fullPath = path17.join(cwd, searchPath);
10161
10249
  if (await fs15.pathExists(fullPath)) {
10162
10250
  try {
10163
10251
  const entries = await fs15.readdir(fullPath, { withFileTypes: true });
10164
10252
  for (const entry of entries) {
10165
10253
  if (entry.isFile() && entry.name.endsWith(".cls")) {
10166
10254
  controllers.push({
10167
- value: path16.join(searchPath, entry.name),
10255
+ value: path17.join(searchPath, entry.name),
10168
10256
  label: `\u{1F4CB} ${entry.name.replace(".cls", "")}`
10169
10257
  });
10170
10258
  }
@@ -10418,7 +10506,7 @@ async function runInteractiveTui() {
10418
10506
  }
10419
10507
  while (currentStep === 2 && conversionType === "vf" && componentPath && action !== "grade") {
10420
10508
  showBreadcrumbs(currentStep, conversionType);
10421
- const resolvedPagePath = path16.resolve(componentPath);
10509
+ const resolvedPagePath = path17.resolve(componentPath);
10422
10510
  if (await fs15.pathExists(resolvedPagePath)) {
10423
10511
  const s = p.spinner();
10424
10512
  s.start("Analyzing page for controller references...");
@@ -10472,7 +10560,7 @@ async function runInteractiveTui() {
10472
10560
  if (addMore) {
10473
10561
  const allControllers = await findApexControllers();
10474
10562
  const availableControllers = allControllers.filter(
10475
- (c) => !controllerPaths.includes(path16.resolve(c.value))
10563
+ (c) => !controllerPaths.includes(path17.resolve(c.value))
10476
10564
  );
10477
10565
  if (availableControllers.length > 0) {
10478
10566
  const additional = await p.multiselect({
@@ -10481,7 +10569,7 @@ async function runInteractiveTui() {
10481
10569
  required: false
10482
10570
  });
10483
10571
  if (!isCancel2(additional)) {
10484
- controllerPaths.push(...additional.map((c) => path16.resolve(c)));
10572
+ controllerPaths.push(...additional.map((c) => path17.resolve(c)));
10485
10573
  }
10486
10574
  }
10487
10575
  }
@@ -10508,7 +10596,7 @@ async function runInteractiveTui() {
10508
10596
  continue wizardLoop;
10509
10597
  }
10510
10598
  controllerPath = selected;
10511
- controllerPaths = [path16.resolve(controllerPath)];
10599
+ controllerPaths = [path17.resolve(controllerPath)];
10512
10600
  }
10513
10601
  }
10514
10602
  }
@@ -10615,7 +10703,7 @@ async function runInteractiveTui() {
10615
10703
  if (controllerPaths.length > 0) {
10616
10704
  summaryLines.push(`${import_picocolors.default.dim("Controllers:")} ${controllerPaths.length} selected`);
10617
10705
  controllerPaths.forEach((cp) => {
10618
- summaryLines.push(` ${import_picocolors.default.dim("\u2022")} ${path16.basename(cp)}`);
10706
+ summaryLines.push(` ${import_picocolors.default.dim("\u2022")} ${path17.basename(cp)}`);
10619
10707
  });
10620
10708
  }
10621
10709
  summaryLines.push(`${import_picocolors.default.dim("Mode:")} ${conversionMode === "full" ? "\u26A1 Full" : "\u{1F4DD} Scaffolding"}`);
@@ -10685,7 +10773,7 @@ var init_types = __esm({
10685
10773
 
10686
10774
  // src/tui/store/persistence.ts
10687
10775
  import fs16 from "fs-extra";
10688
- import path17 from "path";
10776
+ import path18 from "path";
10689
10777
  import os5 from "os";
10690
10778
  async function savePreferences(prefs) {
10691
10779
  try {
@@ -10713,8 +10801,8 @@ var init_persistence = __esm({
10713
10801
  "use strict";
10714
10802
  init_esm_shims();
10715
10803
  init_types();
10716
- CONFIG_DIR2 = path17.join(os5.homedir(), ".lwc-convert");
10717
- PREFS_PATH = path17.join(CONFIG_DIR2, "preferences.json");
10804
+ CONFIG_DIR2 = path18.join(os5.homedir(), ".lwc-convert");
10805
+ PREFS_PATH = path18.join(CONFIG_DIR2, "preferences.json");
10718
10806
  }
10719
10807
  });
10720
10808
 
@@ -10724,7 +10812,7 @@ __export(discovery_exports, {
10724
10812
  discoverComponents: () => discoverComponents
10725
10813
  });
10726
10814
  import fs17 from "fs-extra";
10727
- import path18 from "path";
10815
+ import path19 from "path";
10728
10816
  async function discoverComponents(projectPath) {
10729
10817
  const auraComponents = [];
10730
10818
  const vfComponents = [];
@@ -10737,7 +10825,7 @@ async function discoverComponents(projectPath) {
10737
10825
  "pages"
10738
10826
  ];
10739
10827
  for (const searchPath of searchPaths) {
10740
- const fullPath = path18.join(projectPath, searchPath);
10828
+ const fullPath = path19.join(projectPath, searchPath);
10741
10829
  if (await fs17.pathExists(fullPath)) {
10742
10830
  if (searchPath.includes("aura")) {
10743
10831
  const components3 = await discoverAuraComponents(fullPath);
@@ -10754,7 +10842,7 @@ async function discoverComponents(projectPath) {
10754
10842
  "components"
10755
10843
  ];
10756
10844
  for (const searchPath of vfComponentPaths) {
10757
- const fullPath = path18.join(projectPath, searchPath);
10845
+ const fullPath = path19.join(projectPath, searchPath);
10758
10846
  if (await fs17.pathExists(fullPath)) {
10759
10847
  const components3 = await discoverVfComponents(fullPath);
10760
10848
  vfComponents.push(...components3);
@@ -10778,7 +10866,7 @@ async function discoverAuraComponents(auraPath) {
10778
10866
  const entries = await fs17.readdir(auraPath, { withFileTypes: true });
10779
10867
  for (const entry of entries) {
10780
10868
  if (entry.isDirectory()) {
10781
- const componentPath = path18.join(auraPath, entry.name);
10869
+ const componentPath = path19.join(auraPath, entry.name);
10782
10870
  const files = await fs17.readdir(componentPath);
10783
10871
  const cmpFile = files.find((f) => f.endsWith(".cmp"));
10784
10872
  if (cmpFile) {
@@ -10807,10 +10895,10 @@ async function discoverVfPages(pagesPath) {
10807
10895
  for (const entry of entries) {
10808
10896
  if (entry.isFile() && entry.name.endsWith(".page")) {
10809
10897
  const name = entry.name.replace(".page", "");
10810
- const pagePath = path18.join(pagesPath, entry.name);
10898
+ const pagePath = path19.join(pagesPath, entry.name);
10811
10899
  const relatedFiles = [entry.name];
10812
10900
  const metaFile = `${entry.name}-meta.xml`;
10813
- if (await fs17.pathExists(path18.join(pagesPath, metaFile))) {
10901
+ if (await fs17.pathExists(path19.join(pagesPath, metaFile))) {
10814
10902
  relatedFiles.push(metaFile);
10815
10903
  }
10816
10904
  pages.push({
@@ -10836,7 +10924,7 @@ async function discoverVfComponents(componentsPath) {
10836
10924
  for (const entry of entries) {
10837
10925
  if (entry.isFile() && entry.name.endsWith(".component")) {
10838
10926
  const name = entry.name.replace(".component", "");
10839
- const componentPath = path18.join(componentsPath, entry.name);
10927
+ const componentPath = path19.join(componentsPath, entry.name);
10840
10928
  components3.push({
10841
10929
  id: `vf-component-${name}`,
10842
10930
  name,
@@ -11071,8 +11159,8 @@ var init_store = __esm({
11071
11159
  }
11072
11160
  },
11073
11161
  // Project actions
11074
- setProjectPath: (path19) => {
11075
- set({ projectPath: path19 });
11162
+ setProjectPath: (path20) => {
11163
+ set({ projectPath: path20 });
11076
11164
  },
11077
11165
  setComponents: (aura, vf) => {
11078
11166
  set({ auraComponents: aura, vfComponents: vf });
@@ -14296,7 +14384,7 @@ function ConversionWizard() {
14296
14384
  setSelectedComponentIndex(0);
14297
14385
  setComponentListScrollOffset(0);
14298
14386
  },
14299
- onSourcePathChange: (path19) => updateWizardState({ sourcePath: path19 }),
14387
+ onSourcePathChange: (path20) => updateWizardState({ sourcePath: path20 }),
14300
14388
  onComponentSelect: (component) => {
14301
14389
  updateWizardState({ sourcePath: component.path });
14302
14390
  },
@@ -14786,7 +14874,7 @@ function ExportModal() {
14786
14874
  {
14787
14875
  label: "Output Path",
14788
14876
  value: options.outputPath,
14789
- onChange: (path19) => setOptions((o) => ({ ...o, outputPath: path19 })),
14877
+ onChange: (path20) => setOptions((o) => ({ ...o, outputPath: path20 })),
14790
14878
  focus: focusedField === 4
14791
14879
  }
14792
14880
  ) }),
@@ -15137,9 +15225,14 @@ async function fetchLatestVersion() {
15137
15225
  }
15138
15226
  }
15139
15227
  function parseVersion(version) {
15140
- return version.replace(/^v/, "").split(".").map((n) => parseInt(n, 10) || 0);
15228
+ const cleaned = version.replace(/^v/, "").replace(/[-+].*$/, "");
15229
+ return cleaned.split(".").map((n) => parseInt(n, 10) || 0);
15230
+ }
15231
+ function isPreRelease(version) {
15232
+ return /[-]/.test(version.replace(/^v/, ""));
15141
15233
  }
15142
15234
  function isNewerVersion(latest, current) {
15235
+ if (isPreRelease(latest)) return false;
15143
15236
  const latestParts = parseVersion(latest);
15144
15237
  const currentParts = parseVersion(current);
15145
15238
  for (let i = 0; i < Math.max(latestParts.length, currentParts.length); i++) {
@@ -15190,10 +15283,15 @@ async function checkForUpdates() {
15190
15283
  }
15191
15284
  function formatUpdateMessage(latestVersion, currentVersion) {
15192
15285
  const versionText = `${currentVersion} \u2192 ${latestVersion}`;
15193
- return `\x1B[33m \u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E
15194
- \u2502 Update available: ${versionText.padEnd(25)} \u2502
15195
- \u2502 Run \x1B[36mnpm i -g ${CLI_NAME}\x1B[33m to update \u2502
15196
- \u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F\x1B[0m`;
15286
+ const updateCmd = `npm i -g ${CLI_NAME}`;
15287
+ const line1Content = ` Update available: ${versionText} `;
15288
+ const line2Content = ` Run ${updateCmd} to update `;
15289
+ const innerWidth = Math.max(line1Content.length, line2Content.length);
15290
+ const border = "\u2500".repeat(innerWidth);
15291
+ return `\x1B[33m \u256D${border}\u256E
15292
+ \u2502${line1Content.padEnd(innerWidth)}\u2502
15293
+ \u2502 Run \x1B[36m${updateCmd}\x1B[33m to update${" ".repeat(innerWidth - line2Content.length)} \u2502
15294
+ \u2570${border}\u256F\x1B[0m`;
15197
15295
  }
15198
15296
 
15199
15297
  // src/cli/commands/grade.ts
@@ -15201,6 +15299,7 @@ init_esm_shims();
15201
15299
  init_logger();
15202
15300
  init_grader();
15203
15301
  init_path_resolver();
15302
+ import * as path12 from "path";
15204
15303
  import fs9 from "fs-extra";
15205
15304
 
15206
15305
  // src/cli/tui/grade-tui.ts
@@ -15499,7 +15598,7 @@ async function launchGradeTui(components3, summary) {
15499
15598
  process.stdin.setRawMode(true);
15500
15599
  hideCursor();
15501
15600
  render(state);
15502
- return new Promise((resolve8) => {
15601
+ return new Promise((resolve10) => {
15503
15602
  const handleKeypress = (str, key) => {
15504
15603
  if (!key) return;
15505
15604
  if (state.isSearching) {
@@ -15522,7 +15621,7 @@ async function launchGradeTui(components3, summary) {
15522
15621
  showCursor();
15523
15622
  clearScreen();
15524
15623
  process.stdin.setRawMode(false);
15525
- resolve8();
15624
+ resolve10();
15526
15625
  return;
15527
15626
  }
15528
15627
  if (state.viewMode === "detail") {
@@ -15674,6 +15773,7 @@ async function grade(target, options) {
15674
15773
  if (options.format === "json") {
15675
15774
  const output = { summary, components: filteredResults };
15676
15775
  if (options.output) {
15776
+ await fs9.ensureDir(path12.dirname(path12.resolve(options.output)));
15677
15777
  await fs9.writeJson(options.output, output, { spaces: 2 });
15678
15778
  logger.success(`Results written to ${options.output}`);
15679
15779
  } else {
@@ -15682,6 +15782,7 @@ async function grade(target, options) {
15682
15782
  } else if (options.format === "csv") {
15683
15783
  const csvContent = generateCsvReport(filteredResults, summary);
15684
15784
  if (options.output) {
15785
+ await fs9.ensureDir(path12.dirname(path12.resolve(options.output)));
15685
15786
  await fs9.writeFile(options.output, csvContent);
15686
15787
  logger.success(`CSV report written to ${options.output}`);
15687
15788
  } else {
@@ -15728,11 +15829,19 @@ function filterResults(results, filter) {
15728
15829
  if (filter.startsWith("score:")) {
15729
15830
  const condition = filter.substring(6);
15730
15831
  if (condition.startsWith("<")) {
15731
- const val = parseInt(condition.substring(1));
15832
+ const val = parseInt(condition.substring(1), 10);
15833
+ if (isNaN(val)) {
15834
+ logger.warn(`Invalid score filter value: "${condition.substring(1)}". Expected a number.`);
15835
+ return results;
15836
+ }
15732
15837
  return results.filter((r) => r.overallScore < val);
15733
15838
  }
15734
15839
  if (condition.startsWith(">")) {
15735
- const val = parseInt(condition.substring(1));
15840
+ const val = parseInt(condition.substring(1), 10);
15841
+ if (isNaN(val)) {
15842
+ logger.warn(`Invalid score filter value: "${condition.substring(1)}". Expected a number.`);
15843
+ return results;
15844
+ }
15736
15845
  return results.filter((r) => r.overallScore > val);
15737
15846
  }
15738
15847
  }
@@ -15828,7 +15937,7 @@ function generateCsvReport(results, summary) {
15828
15937
  init_esm_shims();
15829
15938
  init_logger();
15830
15939
  import fs12 from "fs-extra";
15831
- import * as path14 from "path";
15940
+ import * as path15 from "path";
15832
15941
 
15833
15942
  // src/dependency-graph/analyzer.ts
15834
15943
  init_esm_shims();
@@ -15838,7 +15947,7 @@ init_logger();
15838
15947
  init_esm_shims();
15839
15948
  init_logger();
15840
15949
  import fs10 from "fs-extra";
15841
- import * as path12 from "path";
15950
+ import * as path13 from "path";
15842
15951
  import * as htmlparser23 from "htmlparser2";
15843
15952
  import { DomHandler as DomHandler3 } from "domhandler";
15844
15953
  function extractMarkupDependencies(markup, includeBaseComponents) {
@@ -16016,7 +16125,7 @@ async function analyzeAuraComponent(bundlePath, includeBaseComponents = false) {
16016
16125
  logger.debug(`Not a directory: ${bundlePath}`);
16017
16126
  return null;
16018
16127
  }
16019
- const componentName = path12.basename(bundlePath);
16128
+ const componentName = path13.basename(bundlePath);
16020
16129
  const files = await fs10.readdir(bundlePath);
16021
16130
  const cmpFile = files.find((f) => f.endsWith(".cmp"));
16022
16131
  if (!cmpFile) {
@@ -16026,18 +16135,18 @@ async function analyzeAuraComponent(bundlePath, includeBaseComponents = false) {
16026
16135
  const context = {
16027
16136
  markup: ""
16028
16137
  };
16029
- context.markup = await fs10.readFile(path12.join(bundlePath, cmpFile), "utf-8");
16138
+ context.markup = await fs10.readFile(path13.join(bundlePath, cmpFile), "utf-8");
16030
16139
  const controllerFile = files.find((f) => f.endsWith("Controller.js"));
16031
16140
  if (controllerFile) {
16032
16141
  context.controllerJs = await fs10.readFile(
16033
- path12.join(bundlePath, controllerFile),
16142
+ path13.join(bundlePath, controllerFile),
16034
16143
  "utf-8"
16035
16144
  );
16036
16145
  }
16037
16146
  const helperFile = files.find((f) => f.endsWith("Helper.js"));
16038
16147
  if (helperFile) {
16039
16148
  context.helperJs = await fs10.readFile(
16040
- path12.join(bundlePath, helperFile),
16149
+ path13.join(bundlePath, helperFile),
16041
16150
  "utf-8"
16042
16151
  );
16043
16152
  }
@@ -16062,13 +16171,13 @@ async function analyzeAuraComponent(bundlePath, includeBaseComponents = false) {
16062
16171
  }
16063
16172
  }
16064
16173
  async function getPackageDirectories(rootPath) {
16065
- const sfdxProjectPath = path12.join(rootPath, "sfdx-project.json");
16174
+ const sfdxProjectPath = path13.join(rootPath, "sfdx-project.json");
16066
16175
  if (await fs10.pathExists(sfdxProjectPath)) {
16067
16176
  try {
16068
16177
  const projectConfig = await fs10.readJson(sfdxProjectPath);
16069
16178
  if (projectConfig.packageDirectories && Array.isArray(projectConfig.packageDirectories)) {
16070
16179
  return projectConfig.packageDirectories.map(
16071
- (pkg) => path12.join(rootPath, pkg.path)
16180
+ (pkg) => path13.join(rootPath, pkg.path)
16072
16181
  );
16073
16182
  }
16074
16183
  } catch (error) {
@@ -16085,7 +16194,7 @@ async function findAuraDirectories(basePath) {
16085
16194
  const entries = await fs10.readdir(dirPath, { withFileTypes: true });
16086
16195
  for (const entry of entries) {
16087
16196
  if (entry.isDirectory()) {
16088
- const fullPath = path12.join(dirPath, entry.name);
16197
+ const fullPath = path13.join(dirPath, entry.name);
16089
16198
  if (entry.name === "aura") {
16090
16199
  auraDirectories.push(fullPath);
16091
16200
  } else if (!entry.name.startsWith(".") && entry.name !== "node_modules") {
@@ -16111,9 +16220,9 @@ async function scanAuraComponents(rootPath, includeBaseComponents = false) {
16111
16220
  logger.debug(`Found aura directories from sfdx-project.json: ${searchPaths.join(", ")}`);
16112
16221
  }
16113
16222
  const fallbackPaths = [
16114
- path12.join(rootPath, "force-app/main/default/aura"),
16115
- path12.join(rootPath, "src/aura"),
16116
- path12.join(rootPath, "aura")
16223
+ path13.join(rootPath, "force-app/main/default/aura"),
16224
+ path13.join(rootPath, "src/aura"),
16225
+ path13.join(rootPath, "aura")
16117
16226
  ];
16118
16227
  for (const fallback of fallbackPaths) {
16119
16228
  if (!searchPaths.includes(fallback)) {
@@ -16142,7 +16251,7 @@ async function scanAuraComponents(rootPath, includeBaseComponents = false) {
16142
16251
  }
16143
16252
  } else {
16144
16253
  for (const item of files) {
16145
- const itemPath = path12.join(searchPath, item);
16254
+ const itemPath = path13.join(searchPath, item);
16146
16255
  const itemStat = await fs10.stat(itemPath);
16147
16256
  if (itemStat.isDirectory()) {
16148
16257
  const result = await analyzeAuraComponent(itemPath, includeBaseComponents);
@@ -16167,7 +16276,7 @@ async function scanAuraComponents(rootPath, includeBaseComponents = false) {
16167
16276
  init_esm_shims();
16168
16277
  init_logger();
16169
16278
  import fs11 from "fs-extra";
16170
- import * as path13 from "path";
16279
+ import * as path14 from "path";
16171
16280
  import * as htmlparser24 from "htmlparser2";
16172
16281
  import { DomHandler as DomHandler4 } from "domhandler";
16173
16282
  function extractVfDependencies(markup) {
@@ -16355,7 +16464,7 @@ async function analyzeVfPage(pagePath) {
16355
16464
  const files = await fs11.readdir(pagePath);
16356
16465
  const pageFile = files.find((f) => f.endsWith(".page"));
16357
16466
  if (pageFile) {
16358
- actualPath = path13.join(pagePath, pageFile);
16467
+ actualPath = path14.join(pagePath, pageFile);
16359
16468
  } else {
16360
16469
  return null;
16361
16470
  }
@@ -16367,7 +16476,7 @@ async function analyzeVfPage(pagePath) {
16367
16476
  return null;
16368
16477
  }
16369
16478
  const markup = await fs11.readFile(actualPath, "utf-8");
16370
- const pageName = path13.basename(actualPath, ".page");
16479
+ const pageName = path14.basename(actualPath, ".page");
16371
16480
  const dependencies = extractVfDependencies(markup);
16372
16481
  return {
16373
16482
  id: `vf:${pageName}`,
@@ -16391,7 +16500,7 @@ async function analyzeVfComponent(componentPath) {
16391
16500
  const files = await fs11.readdir(componentPath);
16392
16501
  const componentFile = files.find((f) => f.endsWith(".component"));
16393
16502
  if (componentFile) {
16394
- actualPath = path13.join(componentPath, componentFile);
16503
+ actualPath = path14.join(componentPath, componentFile);
16395
16504
  } else {
16396
16505
  return null;
16397
16506
  }
@@ -16403,7 +16512,7 @@ async function analyzeVfComponent(componentPath) {
16403
16512
  return null;
16404
16513
  }
16405
16514
  const markup = await fs11.readFile(actualPath, "utf-8");
16406
- const componentName = path13.basename(actualPath, ".component");
16515
+ const componentName = path14.basename(actualPath, ".component");
16407
16516
  const dependencies = extractVfDependencies(markup);
16408
16517
  return {
16409
16518
  id: `vf:${componentName}`,
@@ -16418,13 +16527,13 @@ async function analyzeVfComponent(componentPath) {
16418
16527
  }
16419
16528
  }
16420
16529
  async function getPackageDirectories2(rootPath) {
16421
- const sfdxProjectPath = path13.join(rootPath, "sfdx-project.json");
16530
+ const sfdxProjectPath = path14.join(rootPath, "sfdx-project.json");
16422
16531
  if (await fs11.pathExists(sfdxProjectPath)) {
16423
16532
  try {
16424
16533
  const projectConfig = await fs11.readJson(sfdxProjectPath);
16425
16534
  if (projectConfig.packageDirectories && Array.isArray(projectConfig.packageDirectories)) {
16426
16535
  return projectConfig.packageDirectories.map(
16427
- (pkg) => path13.join(rootPath, pkg.path)
16536
+ (pkg) => path14.join(rootPath, pkg.path)
16428
16537
  );
16429
16538
  }
16430
16539
  } catch (error) {
@@ -16442,7 +16551,7 @@ async function findVfDirectories(basePath) {
16442
16551
  const entries = await fs11.readdir(dirPath, { withFileTypes: true });
16443
16552
  for (const entry of entries) {
16444
16553
  if (entry.isDirectory()) {
16445
- const fullPath = path13.join(dirPath, entry.name);
16554
+ const fullPath = path14.join(dirPath, entry.name);
16446
16555
  if (entry.name === "pages") {
16447
16556
  pagesDirectories.push(fullPath);
16448
16557
  } else if (entry.name === "components") {
@@ -16473,9 +16582,9 @@ async function scanVfPages(rootPath) {
16473
16582
  logger.debug(`Found components directories from sfdx-project.json: ${componentSearchPaths.join(", ")}`);
16474
16583
  }
16475
16584
  const fallbackPagePaths = [
16476
- path13.join(rootPath, "force-app/main/default/pages"),
16477
- path13.join(rootPath, "src/pages"),
16478
- path13.join(rootPath, "pages")
16585
+ path14.join(rootPath, "force-app/main/default/pages"),
16586
+ path14.join(rootPath, "src/pages"),
16587
+ path14.join(rootPath, "pages")
16479
16588
  ];
16480
16589
  for (const fallback of fallbackPagePaths) {
16481
16590
  if (!pageSearchPaths.includes(fallback)) {
@@ -16483,9 +16592,9 @@ async function scanVfPages(rootPath) {
16483
16592
  }
16484
16593
  }
16485
16594
  const fallbackComponentPaths = [
16486
- path13.join(rootPath, "force-app/main/default/components"),
16487
- path13.join(rootPath, "src/components"),
16488
- path13.join(rootPath, "components")
16595
+ path14.join(rootPath, "force-app/main/default/components"),
16596
+ path14.join(rootPath, "src/components"),
16597
+ path14.join(rootPath, "components")
16489
16598
  ];
16490
16599
  for (const fallback of fallbackComponentPaths) {
16491
16600
  if (!componentSearchPaths.includes(fallback)) {
@@ -16522,7 +16631,7 @@ async function scanVfPages(rootPath) {
16522
16631
  const files = await fs11.readdir(searchPath);
16523
16632
  for (const file of files) {
16524
16633
  if (file.endsWith(".page")) {
16525
- const result = await analyzeVfPage(path13.join(searchPath, file));
16634
+ const result = await analyzeVfPage(path14.join(searchPath, file));
16526
16635
  if (result) {
16527
16636
  results.push(result);
16528
16637
  }
@@ -16543,7 +16652,7 @@ async function scanVfPages(rootPath) {
16543
16652
  const files = await fs11.readdir(searchPath);
16544
16653
  for (const file of files) {
16545
16654
  if (file.endsWith(".component")) {
16546
- const result = await analyzeVfComponent(path13.join(searchPath, file));
16655
+ const result = await analyzeVfComponent(path14.join(searchPath, file));
16547
16656
  if (result) {
16548
16657
  results.push(result);
16549
16658
  }
@@ -17550,7 +17659,7 @@ function formatSimpleMermaidOutput(graph, maxNodes = 30) {
17550
17659
  // src/cli/commands/deps.ts
17551
17660
  async function analyzeDeps(target, options) {
17552
17661
  logger.setVerbose(options.verbose);
17553
- const targetPath = target ? path14.resolve(target) : process.cwd();
17662
+ const targetPath = target ? path15.resolve(target) : process.cwd();
17554
17663
  logger.banner();
17555
17664
  logger.header("Dependency Graph Analysis");
17556
17665
  logger.info(`Analyzing: ${targetPath}`);
@@ -17600,14 +17709,6 @@ async function analyzeDeps(target, options) {
17600
17709
  console.log(output);
17601
17710
  }
17602
17711
  break;
17603
- case "html":
17604
- logger.warn("HTML format not yet implemented. Using console output.");
17605
- formatConsoleOutput(graph, conversionOrder, options.showOrphans);
17606
- break;
17607
- case "dot":
17608
- logger.warn("DOT format not yet implemented. Using console output.");
17609
- formatConsoleOutput(graph, conversionOrder, options.showOrphans);
17610
- break;
17611
17712
  case "console":
17612
17713
  default:
17613
17714
  formatConsoleOutput(graph, conversionOrder, options.showOrphans);
@@ -17626,8 +17727,8 @@ async function analyzeDeps(target, options) {
17626
17727
  }
17627
17728
  }
17628
17729
  async function writeOutput(filePath, content) {
17629
- const outputPath = path14.resolve(filePath);
17630
- const outputDir = path14.dirname(outputPath);
17730
+ const outputPath = path15.resolve(filePath);
17731
+ const outputDir = path15.dirname(outputPath);
17631
17732
  await fs12.ensureDir(outputDir);
17632
17733
  await fs12.writeFile(outputPath, content, "utf-8");
17633
17734
  }
@@ -17645,7 +17746,7 @@ program.name(CLI_NAME).description(CLI_DESCRIPTION).version(CLI_VERSION).hook("p
17645
17746
  } catch {
17646
17747
  }
17647
17748
  });
17648
- program.command("aura <path>").description("Convert an Aura component bundle to LWC").option("--full", "Run full automated conversion (default: scaffolding only)", false).option("-o, --output <dir>", "Output directory", DEFAULT_OUTPUT_DIR).option("--open", "Open output folder in file explorer after conversion", false).option("--preview", "Generate and open HTML preview of converted component", false).option("--dry-run", "Preview conversion without writing files", false).option("--verbose", "Show detailed conversion logs", false).action(async (bundlePath, options) => {
17749
+ program.command("aura <path>").description("Convert an Aura component bundle to LWC").option("--full", "Run full automated conversion (default: scaffolding only)", false).option("-o, --output <dir>", "Output directory", DEFAULT_OUTPUT_DIR).option("--api-version <version>", "Salesforce API version for meta.xml", DEFAULT_API_VERSION).option("--open", "Open output folder in file explorer after conversion", false).option("--preview", "Generate and open HTML preview of converted component", false).option("--dry-run", "Preview conversion without writing files", false).option("--verbose", "Show detailed conversion logs", false).action(async (bundlePath, options) => {
17649
17750
  logger.setVerbose(options.verbose);
17650
17751
  await convertAura(bundlePath, {
17651
17752
  output: options.output,
@@ -17653,10 +17754,11 @@ program.command("aura <path>").description("Convert an Aura component bundle to
17653
17754
  dryRun: options.dryRun,
17654
17755
  verbose: options.verbose,
17655
17756
  open: options.open,
17656
- preview: options.preview
17757
+ preview: options.preview,
17758
+ apiVersion: options.apiVersion
17657
17759
  });
17658
17760
  });
17659
- program.command("vf <path>").description("Convert a Visualforce page to LWC").option("--full", "Run full automated conversion (default: scaffolding only)", false).option("-o, --output <dir>", "Output directory", DEFAULT_OUTPUT_DIR).option("--controller <path>", "Include Apex controller file for analysis").option("--open", "Open output folder in file explorer after conversion", false).option("--preview", "Generate and open HTML preview of converted component", false).option("--dry-run", "Preview conversion without writing files", false).option("--verbose", "Show detailed conversion logs", false).action(async (pagePath, options) => {
17761
+ program.command("vf <path>").description("Convert a Visualforce page to LWC").option("--full", "Run full automated conversion (default: scaffolding only)", false).option("-o, --output <dir>", "Output directory", DEFAULT_OUTPUT_DIR).option("--api-version <version>", "Salesforce API version for meta.xml", DEFAULT_API_VERSION).option("--controller <path>", "Include Apex controller file for analysis").option("--open", "Open output folder in file explorer after conversion", false).option("--preview", "Generate and open HTML preview of converted component", false).option("--dry-run", "Preview conversion without writing files", false).option("--verbose", "Show detailed conversion logs", false).action(async (pagePath, options) => {
17660
17762
  logger.setVerbose(options.verbose);
17661
17763
  await convertVf(pagePath, {
17662
17764
  output: options.output,
@@ -17665,7 +17767,8 @@ program.command("vf <path>").description("Convert a Visualforce page to LWC").op
17665
17767
  verbose: options.verbose,
17666
17768
  controller: options.controller,
17667
17769
  open: options.open,
17668
- preview: options.preview
17770
+ preview: options.preview,
17771
+ apiVersion: options.apiVersion
17669
17772
  });
17670
17773
  });
17671
17774
  program.command("grade [target]").description("Assess conversion complexity of components").option("-t, --type <type>", "Component type (aura, vf, both)", "both").option("-o, --output <file>", "Output file for report").option("--format <format>", "Output format (json, csv, console)", "console").option("--detailed", "Show detailed breakdown", false).option("--sort-by <field>", "Sort by (score, complexity, name)").option("--filter <filter>", 'Filter results (e.g., "grade:D,F")').option("--dry-run", "Run without writing files", false).option("--verbose", "Show detailed logs", false).action(async (target, options) => {