lwc-convert 1.8.0 → 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.0") {
55
- return "1.8.0";
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({
@@ -8417,18 +8483,19 @@ ${resolved.contextualHelp}` : "";
8417
8483
  if (result.notes.length > 0) {
8418
8484
  await writeConversionNotes(outputDir, result.bundle.name, result.notes, options.dryRun);
8419
8485
  }
8420
- if (result.tests) {
8486
+ const shouldGenerateTests = options.generateTests !== false;
8487
+ if (result.tests && shouldGenerateTests) {
8421
8488
  const testDir = path8.join(outputDir, result.bundle.name, "__tests__");
8422
8489
  const testFilePath = path8.join(testDir, result.tests.filename);
8423
8490
  const { writeFile: writeFile2 } = await Promise.resolve().then(() => (init_file_io(), file_io_exports));
8424
8491
  await writeFile2(testFilePath, result.tests.content, options.dryRun);
8425
8492
  }
8426
- if (result.behaviorSpec) {
8493
+ if (result.behaviorSpec && shouldGenerateTests) {
8427
8494
  const specFilePath = path8.join(outputDir, result.bundle.name, `${result.bundle.name}-behavior-spec.md`);
8428
8495
  const { writeFile: writeFile2 } = await Promise.resolve().then(() => (init_file_io(), file_io_exports));
8429
8496
  await writeFile2(specFilePath, result.behaviorSpec, options.dryRun);
8430
8497
  }
8431
- if (result.testComparison) {
8498
+ if (result.testComparison && shouldGenerateTests) {
8432
8499
  const { writeFile: writeFile2 } = await Promise.resolve().then(() => (init_file_io(), file_io_exports));
8433
8500
  const testDir = path8.join(outputDir, result.bundle.name, "__tests__");
8434
8501
  const beforeTestPath = path8.join(testDir, `${result.bundle.name}.before.spec.js`);
@@ -8439,7 +8506,7 @@ ${resolved.contextualHelp}` : "";
8439
8506
  await writeFile2(comparisonReportPath, result.testComparison.comparisonReport, options.dryRun);
8440
8507
  }
8441
8508
  logger.divider();
8442
- if (result.testComparison) {
8509
+ if (result.testComparison && shouldGenerateTests) {
8443
8510
  const conversionRecord = await sessionStore.storeConversion(
8444
8511
  "aura",
8445
8512
  bundle.name,
@@ -8452,9 +8519,9 @@ ${resolved.contextualHelp}` : "";
8452
8519
  logger.debug(`Session stored: ${conversionRecord.id}`);
8453
8520
  }
8454
8521
  let totalFiles = writtenFiles.length + 1;
8455
- if (result.tests) totalFiles++;
8456
- if (result.behaviorSpec) totalFiles++;
8457
- if (result.testComparison) totalFiles += 3;
8522
+ if (result.tests && shouldGenerateTests) totalFiles++;
8523
+ if (result.behaviorSpec && shouldGenerateTests) totalFiles++;
8524
+ if (result.testComparison && shouldGenerateTests) totalFiles += 3;
8458
8525
  logger.successToast(
8459
8526
  "Conversion Complete",
8460
8527
  [
@@ -8464,9 +8531,11 @@ ${resolved.contextualHelp}` : "";
8464
8531
  path8.join(outputDir, result.bundle.name)
8465
8532
  );
8466
8533
  const sessionSummary = sessionStore.getSessionSummary();
8534
+ const jestTestsValue = !shouldGenerateTests ? "Skipped" : result.tests ? `${result.bundle.name}.test.js` : "None";
8467
8535
  logger.summaryBox("Conversion Summary", [
8468
8536
  { label: "Component", value: `${bundle.name} \u2192 ${result.bundle.name}`, type: "success" },
8469
8537
  { label: "Files created", value: `${totalFiles}`, type: "info" },
8538
+ { label: "Jest Tests", value: jestTestsValue, type: shouldGenerateTests && result.tests ? "success" : "warn" },
8470
8539
  { label: "Behaviors mapped", value: `${result.testComparison?.behaviorTests.length || 0}`, type: "info" },
8471
8540
  { label: "Warnings", value: `${result.warnings.length}`, type: result.warnings.length > 0 ? "warn" : "success" },
8472
8541
  { label: "Output", value: path8.join(outputDir, result.bundle.name), type: "info" }
@@ -9630,6 +9699,8 @@ var init_grader = __esm({
9630
9699
  Grader = class {
9631
9700
  auraGrader;
9632
9701
  vfGrader;
9702
+ /** Cache of grading results keyed by resolved file path */
9703
+ gradeCache = /* @__PURE__ */ new Map();
9633
9704
  constructor() {
9634
9705
  this.auraGrader = new AuraGrader();
9635
9706
  this.vfGrader = new VfGrader();
@@ -9678,7 +9749,8 @@ var init_grader = __esm({
9678
9749
  }
9679
9750
  }
9680
9751
  }
9681
- } catch {
9752
+ } catch (error) {
9753
+ logger.debug(`Error scanning ${dirPath}: ${error.message}`);
9682
9754
  }
9683
9755
  };
9684
9756
  await searchDir(basePath);
@@ -9694,29 +9766,48 @@ var init_grader = __esm({
9694
9766
  if (grade2) results.push(grade2);
9695
9767
  } else {
9696
9768
  const searchPaths = options.targetPath ? [options.targetPath] : await this.getStandardPaths(options.type);
9769
+ const allFiles = [];
9697
9770
  for (const searchPath of searchPaths) {
9698
9771
  const found = await this.scanDirectory(searchPath, options.type);
9699
- for (const file of found) {
9700
- try {
9701
- const grade2 = await this.gradeSingle(file, options.type === "both" ? this.detectType(file) : options.type);
9702
- if (grade2) results.push(grade2);
9703
- } catch (err) {
9704
- 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}`);
9705
9780
  }
9706
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");
9707
9791
  }
9708
9792
  }
9709
9793
  return results;
9710
9794
  }
9711
9795
  async gradeSingle(filePath, type) {
9796
+ const resolvedPath = path11.resolve(filePath);
9797
+ const cached = this.gradeCache.get(resolvedPath);
9798
+ if (cached) return cached;
9712
9799
  const resolvedType = type === "both" ? this.detectType(filePath) : type;
9800
+ let result = null;
9713
9801
  if (resolvedType === "aura") {
9714
9802
  const bundlePath = filePath.endsWith(".cmp") ? path11.dirname(filePath) : filePath;
9715
- return this.auraGrader.grade(bundlePath);
9803
+ result = await this.auraGrader.grade(bundlePath);
9716
9804
  } else if (resolvedType === "vf") {
9717
- return this.vfGrader.grade(filePath);
9805
+ result = await this.vfGrader.grade(filePath);
9718
9806
  }
9719
- return null;
9807
+ if (result) {
9808
+ this.gradeCache.set(resolvedPath, result);
9809
+ }
9810
+ return result;
9720
9811
  }
9721
9812
  detectType(filePath) {
9722
9813
  if (filePath.endsWith(".cmp") || filePath.includes("/aura/") || filePath.includes("\\aura\\")) return "aura";
@@ -10007,7 +10098,7 @@ var init_vf_controller_resolver = __esm({
10007
10098
 
10008
10099
  // src/utils/first-time.ts
10009
10100
  import fs14 from "fs-extra";
10010
- import path15 from "path";
10101
+ import path16 from "path";
10011
10102
  import os4 from "os";
10012
10103
  function isFirstTimeSync() {
10013
10104
  try {
@@ -10056,8 +10147,8 @@ var init_first_time = __esm({
10056
10147
  "src/utils/first-time.ts"() {
10057
10148
  "use strict";
10058
10149
  init_esm_shims();
10059
- CONFIG_DIR = path15.join(os4.homedir(), ".lwc-convert");
10060
- 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");
10061
10152
  }
10062
10153
  });
10063
10154
 
@@ -10067,7 +10158,7 @@ __export(interactive_exports, {
10067
10158
  runInteractiveTui: () => runInteractiveTui
10068
10159
  });
10069
10160
  import * as p from "@clack/prompts";
10070
- import * as path16 from "path";
10161
+ import * as path17 from "path";
10071
10162
  import fs15 from "fs-extra";
10072
10163
  function showBreadcrumbs(currentStep, conversionType) {
10073
10164
  const breadcrumb = STEPS.map((step, i) => {
@@ -10095,16 +10186,16 @@ async function findAuraComponents() {
10095
10186
  const components3 = [];
10096
10187
  const cwd = process.cwd();
10097
10188
  for (const searchPath of searchPaths) {
10098
- const fullPath = path16.join(cwd, searchPath);
10189
+ const fullPath = path17.join(cwd, searchPath);
10099
10190
  if (await fs15.pathExists(fullPath)) {
10100
10191
  try {
10101
10192
  const entries = await fs15.readdir(fullPath, { withFileTypes: true });
10102
10193
  for (const entry of entries) {
10103
10194
  if (entry.isDirectory()) {
10104
- const cmpFile = path16.join(fullPath, entry.name, `${entry.name}.cmp`);
10195
+ const cmpFile = path17.join(fullPath, entry.name, `${entry.name}.cmp`);
10105
10196
  if (await fs15.pathExists(cmpFile)) {
10106
10197
  components3.push({
10107
- value: path16.join(searchPath, entry.name),
10198
+ value: path17.join(searchPath, entry.name),
10108
10199
  label: `\u26A1 ${entry.name}`,
10109
10200
  hint: searchPath
10110
10201
  });
@@ -10126,14 +10217,14 @@ async function findVfPages() {
10126
10217
  const pages = [];
10127
10218
  const cwd = process.cwd();
10128
10219
  for (const searchPath of searchPaths) {
10129
- const fullPath = path16.join(cwd, searchPath);
10220
+ const fullPath = path17.join(cwd, searchPath);
10130
10221
  if (await fs15.pathExists(fullPath)) {
10131
10222
  try {
10132
10223
  const entries = await fs15.readdir(fullPath, { withFileTypes: true });
10133
10224
  for (const entry of entries) {
10134
10225
  if (entry.isFile() && entry.name.endsWith(".page")) {
10135
10226
  pages.push({
10136
- value: path16.join(searchPath, entry.name),
10227
+ value: path17.join(searchPath, entry.name),
10137
10228
  label: `\u{1F4C4} ${entry.name.replace(".page", "")}`,
10138
10229
  hint: searchPath
10139
10230
  });
@@ -10154,14 +10245,14 @@ async function findApexControllers() {
10154
10245
  const controllers = [];
10155
10246
  const cwd = process.cwd();
10156
10247
  for (const searchPath of searchPaths) {
10157
- const fullPath = path16.join(cwd, searchPath);
10248
+ const fullPath = path17.join(cwd, searchPath);
10158
10249
  if (await fs15.pathExists(fullPath)) {
10159
10250
  try {
10160
10251
  const entries = await fs15.readdir(fullPath, { withFileTypes: true });
10161
10252
  for (const entry of entries) {
10162
10253
  if (entry.isFile() && entry.name.endsWith(".cls")) {
10163
10254
  controllers.push({
10164
- value: path16.join(searchPath, entry.name),
10255
+ value: path17.join(searchPath, entry.name),
10165
10256
  label: `\u{1F4CB} ${entry.name.replace(".cls", "")}`
10166
10257
  });
10167
10258
  }
@@ -10415,7 +10506,7 @@ async function runInteractiveTui() {
10415
10506
  }
10416
10507
  while (currentStep === 2 && conversionType === "vf" && componentPath && action !== "grade") {
10417
10508
  showBreadcrumbs(currentStep, conversionType);
10418
- const resolvedPagePath = path16.resolve(componentPath);
10509
+ const resolvedPagePath = path17.resolve(componentPath);
10419
10510
  if (await fs15.pathExists(resolvedPagePath)) {
10420
10511
  const s = p.spinner();
10421
10512
  s.start("Analyzing page for controller references...");
@@ -10469,7 +10560,7 @@ async function runInteractiveTui() {
10469
10560
  if (addMore) {
10470
10561
  const allControllers = await findApexControllers();
10471
10562
  const availableControllers = allControllers.filter(
10472
- (c) => !controllerPaths.includes(path16.resolve(c.value))
10563
+ (c) => !controllerPaths.includes(path17.resolve(c.value))
10473
10564
  );
10474
10565
  if (availableControllers.length > 0) {
10475
10566
  const additional = await p.multiselect({
@@ -10478,7 +10569,7 @@ async function runInteractiveTui() {
10478
10569
  required: false
10479
10570
  });
10480
10571
  if (!isCancel2(additional)) {
10481
- controllerPaths.push(...additional.map((c) => path16.resolve(c)));
10572
+ controllerPaths.push(...additional.map((c) => path17.resolve(c)));
10482
10573
  }
10483
10574
  }
10484
10575
  }
@@ -10505,7 +10596,7 @@ async function runInteractiveTui() {
10505
10596
  continue wizardLoop;
10506
10597
  }
10507
10598
  controllerPath = selected;
10508
- controllerPaths = [path16.resolve(controllerPath)];
10599
+ controllerPaths = [path17.resolve(controllerPath)];
10509
10600
  }
10510
10601
  }
10511
10602
  }
@@ -10612,7 +10703,7 @@ async function runInteractiveTui() {
10612
10703
  if (controllerPaths.length > 0) {
10613
10704
  summaryLines.push(`${import_picocolors.default.dim("Controllers:")} ${controllerPaths.length} selected`);
10614
10705
  controllerPaths.forEach((cp) => {
10615
- summaryLines.push(` ${import_picocolors.default.dim("\u2022")} ${path16.basename(cp)}`);
10706
+ summaryLines.push(` ${import_picocolors.default.dim("\u2022")} ${path17.basename(cp)}`);
10616
10707
  });
10617
10708
  }
10618
10709
  summaryLines.push(`${import_picocolors.default.dim("Mode:")} ${conversionMode === "full" ? "\u26A1 Full" : "\u{1F4DD} Scaffolding"}`);
@@ -10682,7 +10773,7 @@ var init_types = __esm({
10682
10773
 
10683
10774
  // src/tui/store/persistence.ts
10684
10775
  import fs16 from "fs-extra";
10685
- import path17 from "path";
10776
+ import path18 from "path";
10686
10777
  import os5 from "os";
10687
10778
  async function savePreferences(prefs) {
10688
10779
  try {
@@ -10710,8 +10801,8 @@ var init_persistence = __esm({
10710
10801
  "use strict";
10711
10802
  init_esm_shims();
10712
10803
  init_types();
10713
- CONFIG_DIR2 = path17.join(os5.homedir(), ".lwc-convert");
10714
- 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");
10715
10806
  }
10716
10807
  });
10717
10808
 
@@ -10721,7 +10812,7 @@ __export(discovery_exports, {
10721
10812
  discoverComponents: () => discoverComponents
10722
10813
  });
10723
10814
  import fs17 from "fs-extra";
10724
- import path18 from "path";
10815
+ import path19 from "path";
10725
10816
  async function discoverComponents(projectPath) {
10726
10817
  const auraComponents = [];
10727
10818
  const vfComponents = [];
@@ -10734,7 +10825,7 @@ async function discoverComponents(projectPath) {
10734
10825
  "pages"
10735
10826
  ];
10736
10827
  for (const searchPath of searchPaths) {
10737
- const fullPath = path18.join(projectPath, searchPath);
10828
+ const fullPath = path19.join(projectPath, searchPath);
10738
10829
  if (await fs17.pathExists(fullPath)) {
10739
10830
  if (searchPath.includes("aura")) {
10740
10831
  const components3 = await discoverAuraComponents(fullPath);
@@ -10751,7 +10842,7 @@ async function discoverComponents(projectPath) {
10751
10842
  "components"
10752
10843
  ];
10753
10844
  for (const searchPath of vfComponentPaths) {
10754
- const fullPath = path18.join(projectPath, searchPath);
10845
+ const fullPath = path19.join(projectPath, searchPath);
10755
10846
  if (await fs17.pathExists(fullPath)) {
10756
10847
  const components3 = await discoverVfComponents(fullPath);
10757
10848
  vfComponents.push(...components3);
@@ -10775,7 +10866,7 @@ async function discoverAuraComponents(auraPath) {
10775
10866
  const entries = await fs17.readdir(auraPath, { withFileTypes: true });
10776
10867
  for (const entry of entries) {
10777
10868
  if (entry.isDirectory()) {
10778
- const componentPath = path18.join(auraPath, entry.name);
10869
+ const componentPath = path19.join(auraPath, entry.name);
10779
10870
  const files = await fs17.readdir(componentPath);
10780
10871
  const cmpFile = files.find((f) => f.endsWith(".cmp"));
10781
10872
  if (cmpFile) {
@@ -10804,10 +10895,10 @@ async function discoverVfPages(pagesPath) {
10804
10895
  for (const entry of entries) {
10805
10896
  if (entry.isFile() && entry.name.endsWith(".page")) {
10806
10897
  const name = entry.name.replace(".page", "");
10807
- const pagePath = path18.join(pagesPath, entry.name);
10898
+ const pagePath = path19.join(pagesPath, entry.name);
10808
10899
  const relatedFiles = [entry.name];
10809
10900
  const metaFile = `${entry.name}-meta.xml`;
10810
- if (await fs17.pathExists(path18.join(pagesPath, metaFile))) {
10901
+ if (await fs17.pathExists(path19.join(pagesPath, metaFile))) {
10811
10902
  relatedFiles.push(metaFile);
10812
10903
  }
10813
10904
  pages.push({
@@ -10833,7 +10924,7 @@ async function discoverVfComponents(componentsPath) {
10833
10924
  for (const entry of entries) {
10834
10925
  if (entry.isFile() && entry.name.endsWith(".component")) {
10835
10926
  const name = entry.name.replace(".component", "");
10836
- const componentPath = path18.join(componentsPath, entry.name);
10927
+ const componentPath = path19.join(componentsPath, entry.name);
10837
10928
  components3.push({
10838
10929
  id: `vf-component-${name}`,
10839
10930
  name,
@@ -11068,8 +11159,8 @@ var init_store = __esm({
11068
11159
  }
11069
11160
  },
11070
11161
  // Project actions
11071
- setProjectPath: (path19) => {
11072
- set({ projectPath: path19 });
11162
+ setProjectPath: (path20) => {
11163
+ set({ projectPath: path20 });
11073
11164
  },
11074
11165
  setComponents: (aura, vf) => {
11075
11166
  set({ auraComponents: aura, vfComponents: vf });
@@ -14095,6 +14186,10 @@ function ConversionWizard() {
14095
14186
  }
14096
14187
  } else if (wizard.currentStep === 1 && focusedField === 0) {
14097
14188
  handleNext();
14189
+ } else if (wizard.currentStep === 2 && focusedField === 1) {
14190
+ updateWizardState({ generatePreview: !wizard.generatePreview });
14191
+ } else if (wizard.currentStep === 2 && focusedField === 2) {
14192
+ updateWizardState({ generateTests: !wizard.generateTests });
14098
14193
  } else if (canGoNext()) {
14099
14194
  handleNext();
14100
14195
  }
@@ -14118,6 +14213,7 @@ function ConversionWizard() {
14118
14213
  output: wizard.outputDir,
14119
14214
  full: wizard.conversionMode === "full",
14120
14215
  preview: wizard.generatePreview,
14216
+ generateTests: wizard.generateTests,
14121
14217
  dryRun: false,
14122
14218
  verbose: false,
14123
14219
  open: false
@@ -14288,7 +14384,7 @@ function ConversionWizard() {
14288
14384
  setSelectedComponentIndex(0);
14289
14385
  setComponentListScrollOffset(0);
14290
14386
  },
14291
- onSourcePathChange: (path19) => updateWizardState({ sourcePath: path19 }),
14387
+ onSourcePathChange: (path20) => updateWizardState({ sourcePath: path20 }),
14292
14388
  onComponentSelect: (component) => {
14293
14389
  updateWizardState({ sourcePath: component.path });
14294
14390
  },
@@ -14462,7 +14558,9 @@ function OptionsStep({
14462
14558
  generatePreview,
14463
14559
  generateTests,
14464
14560
  focusedField,
14465
- onOutputDirChange
14561
+ onOutputDirChange,
14562
+ onPreviewChange,
14563
+ onTestsChange
14466
14564
  }) {
14467
14565
  const preferences = useStore((state) => state.preferences);
14468
14566
  const theme = getTheme(preferences.theme);
@@ -14478,7 +14576,32 @@ function OptionsStep({
14478
14576
  focus: focusedField === 0
14479
14577
  }
14480
14578
  ) }),
14481
- /* @__PURE__ */ jsx22(Box21, { flexDirection: "column", marginTop: 1, children: /* @__PURE__ */ jsx22(Text24, { color: theme.textMuted, children: "Additional options are configured in Settings" }) })
14579
+ /* @__PURE__ */ jsxs22(Box21, { flexDirection: "column", marginTop: 1, children: [
14580
+ /* @__PURE__ */ jsxs22(Text24, { color: theme.text, bold: true, children: [
14581
+ "Generation Options ",
14582
+ focusedField === 1 || focusedField === 2 ? /* @__PURE__ */ jsx22(Text24, { color: theme.accent, children: "(Enter to toggle)" }) : ""
14583
+ ] }),
14584
+ /* @__PURE__ */ jsxs22(Box21, { marginTop: 1, flexDirection: "column", children: [
14585
+ /* @__PURE__ */ jsx22(
14586
+ Checkbox,
14587
+ {
14588
+ label: "Generate UI Preview",
14589
+ checked: generatePreview,
14590
+ onChange: onPreviewChange,
14591
+ isFocused: focusedField === 1
14592
+ }
14593
+ ),
14594
+ /* @__PURE__ */ jsx22(
14595
+ Checkbox,
14596
+ {
14597
+ label: "Generate Jest Tests",
14598
+ checked: generateTests,
14599
+ onChange: onTestsChange,
14600
+ isFocused: focusedField === 2
14601
+ }
14602
+ )
14603
+ ] })
14604
+ ] })
14482
14605
  ] });
14483
14606
  }
14484
14607
  function ReviewStep({ wizard }) {
@@ -14751,7 +14874,7 @@ function ExportModal() {
14751
14874
  {
14752
14875
  label: "Output Path",
14753
14876
  value: options.outputPath,
14754
- onChange: (path19) => setOptions((o) => ({ ...o, outputPath: path19 })),
14877
+ onChange: (path20) => setOptions((o) => ({ ...o, outputPath: path20 })),
14755
14878
  focus: focusedField === 4
14756
14879
  }
14757
14880
  ) }),
@@ -15102,9 +15225,14 @@ async function fetchLatestVersion() {
15102
15225
  }
15103
15226
  }
15104
15227
  function parseVersion(version) {
15105
- 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/, ""));
15106
15233
  }
15107
15234
  function isNewerVersion(latest, current) {
15235
+ if (isPreRelease(latest)) return false;
15108
15236
  const latestParts = parseVersion(latest);
15109
15237
  const currentParts = parseVersion(current);
15110
15238
  for (let i = 0; i < Math.max(latestParts.length, currentParts.length); i++) {
@@ -15155,10 +15283,15 @@ async function checkForUpdates() {
15155
15283
  }
15156
15284
  function formatUpdateMessage(latestVersion, currentVersion) {
15157
15285
  const versionText = `${currentVersion} \u2192 ${latestVersion}`;
15158
- 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
15159
- \u2502 Update available: ${versionText.padEnd(25)} \u2502
15160
- \u2502 Run \x1B[36mnpm i -g ${CLI_NAME}\x1B[33m to update \u2502
15161
- \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`;
15162
15295
  }
15163
15296
 
15164
15297
  // src/cli/commands/grade.ts
@@ -15166,6 +15299,7 @@ init_esm_shims();
15166
15299
  init_logger();
15167
15300
  init_grader();
15168
15301
  init_path_resolver();
15302
+ import * as path12 from "path";
15169
15303
  import fs9 from "fs-extra";
15170
15304
 
15171
15305
  // src/cli/tui/grade-tui.ts
@@ -15464,7 +15598,7 @@ async function launchGradeTui(components3, summary) {
15464
15598
  process.stdin.setRawMode(true);
15465
15599
  hideCursor();
15466
15600
  render(state);
15467
- return new Promise((resolve8) => {
15601
+ return new Promise((resolve10) => {
15468
15602
  const handleKeypress = (str, key) => {
15469
15603
  if (!key) return;
15470
15604
  if (state.isSearching) {
@@ -15487,7 +15621,7 @@ async function launchGradeTui(components3, summary) {
15487
15621
  showCursor();
15488
15622
  clearScreen();
15489
15623
  process.stdin.setRawMode(false);
15490
- resolve8();
15624
+ resolve10();
15491
15625
  return;
15492
15626
  }
15493
15627
  if (state.viewMode === "detail") {
@@ -15639,6 +15773,7 @@ async function grade(target, options) {
15639
15773
  if (options.format === "json") {
15640
15774
  const output = { summary, components: filteredResults };
15641
15775
  if (options.output) {
15776
+ await fs9.ensureDir(path12.dirname(path12.resolve(options.output)));
15642
15777
  await fs9.writeJson(options.output, output, { spaces: 2 });
15643
15778
  logger.success(`Results written to ${options.output}`);
15644
15779
  } else {
@@ -15647,6 +15782,7 @@ async function grade(target, options) {
15647
15782
  } else if (options.format === "csv") {
15648
15783
  const csvContent = generateCsvReport(filteredResults, summary);
15649
15784
  if (options.output) {
15785
+ await fs9.ensureDir(path12.dirname(path12.resolve(options.output)));
15650
15786
  await fs9.writeFile(options.output, csvContent);
15651
15787
  logger.success(`CSV report written to ${options.output}`);
15652
15788
  } else {
@@ -15693,11 +15829,19 @@ function filterResults(results, filter) {
15693
15829
  if (filter.startsWith("score:")) {
15694
15830
  const condition = filter.substring(6);
15695
15831
  if (condition.startsWith("<")) {
15696
- 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
+ }
15697
15837
  return results.filter((r) => r.overallScore < val);
15698
15838
  }
15699
15839
  if (condition.startsWith(">")) {
15700
- 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
+ }
15701
15845
  return results.filter((r) => r.overallScore > val);
15702
15846
  }
15703
15847
  }
@@ -15793,7 +15937,7 @@ function generateCsvReport(results, summary) {
15793
15937
  init_esm_shims();
15794
15938
  init_logger();
15795
15939
  import fs12 from "fs-extra";
15796
- import * as path14 from "path";
15940
+ import * as path15 from "path";
15797
15941
 
15798
15942
  // src/dependency-graph/analyzer.ts
15799
15943
  init_esm_shims();
@@ -15803,7 +15947,7 @@ init_logger();
15803
15947
  init_esm_shims();
15804
15948
  init_logger();
15805
15949
  import fs10 from "fs-extra";
15806
- import * as path12 from "path";
15950
+ import * as path13 from "path";
15807
15951
  import * as htmlparser23 from "htmlparser2";
15808
15952
  import { DomHandler as DomHandler3 } from "domhandler";
15809
15953
  function extractMarkupDependencies(markup, includeBaseComponents) {
@@ -15981,7 +16125,7 @@ async function analyzeAuraComponent(bundlePath, includeBaseComponents = false) {
15981
16125
  logger.debug(`Not a directory: ${bundlePath}`);
15982
16126
  return null;
15983
16127
  }
15984
- const componentName = path12.basename(bundlePath);
16128
+ const componentName = path13.basename(bundlePath);
15985
16129
  const files = await fs10.readdir(bundlePath);
15986
16130
  const cmpFile = files.find((f) => f.endsWith(".cmp"));
15987
16131
  if (!cmpFile) {
@@ -15991,18 +16135,18 @@ async function analyzeAuraComponent(bundlePath, includeBaseComponents = false) {
15991
16135
  const context = {
15992
16136
  markup: ""
15993
16137
  };
15994
- context.markup = await fs10.readFile(path12.join(bundlePath, cmpFile), "utf-8");
16138
+ context.markup = await fs10.readFile(path13.join(bundlePath, cmpFile), "utf-8");
15995
16139
  const controllerFile = files.find((f) => f.endsWith("Controller.js"));
15996
16140
  if (controllerFile) {
15997
16141
  context.controllerJs = await fs10.readFile(
15998
- path12.join(bundlePath, controllerFile),
16142
+ path13.join(bundlePath, controllerFile),
15999
16143
  "utf-8"
16000
16144
  );
16001
16145
  }
16002
16146
  const helperFile = files.find((f) => f.endsWith("Helper.js"));
16003
16147
  if (helperFile) {
16004
16148
  context.helperJs = await fs10.readFile(
16005
- path12.join(bundlePath, helperFile),
16149
+ path13.join(bundlePath, helperFile),
16006
16150
  "utf-8"
16007
16151
  );
16008
16152
  }
@@ -16027,13 +16171,13 @@ async function analyzeAuraComponent(bundlePath, includeBaseComponents = false) {
16027
16171
  }
16028
16172
  }
16029
16173
  async function getPackageDirectories(rootPath) {
16030
- const sfdxProjectPath = path12.join(rootPath, "sfdx-project.json");
16174
+ const sfdxProjectPath = path13.join(rootPath, "sfdx-project.json");
16031
16175
  if (await fs10.pathExists(sfdxProjectPath)) {
16032
16176
  try {
16033
16177
  const projectConfig = await fs10.readJson(sfdxProjectPath);
16034
16178
  if (projectConfig.packageDirectories && Array.isArray(projectConfig.packageDirectories)) {
16035
16179
  return projectConfig.packageDirectories.map(
16036
- (pkg) => path12.join(rootPath, pkg.path)
16180
+ (pkg) => path13.join(rootPath, pkg.path)
16037
16181
  );
16038
16182
  }
16039
16183
  } catch (error) {
@@ -16050,7 +16194,7 @@ async function findAuraDirectories(basePath) {
16050
16194
  const entries = await fs10.readdir(dirPath, { withFileTypes: true });
16051
16195
  for (const entry of entries) {
16052
16196
  if (entry.isDirectory()) {
16053
- const fullPath = path12.join(dirPath, entry.name);
16197
+ const fullPath = path13.join(dirPath, entry.name);
16054
16198
  if (entry.name === "aura") {
16055
16199
  auraDirectories.push(fullPath);
16056
16200
  } else if (!entry.name.startsWith(".") && entry.name !== "node_modules") {
@@ -16076,9 +16220,9 @@ async function scanAuraComponents(rootPath, includeBaseComponents = false) {
16076
16220
  logger.debug(`Found aura directories from sfdx-project.json: ${searchPaths.join(", ")}`);
16077
16221
  }
16078
16222
  const fallbackPaths = [
16079
- path12.join(rootPath, "force-app/main/default/aura"),
16080
- path12.join(rootPath, "src/aura"),
16081
- path12.join(rootPath, "aura")
16223
+ path13.join(rootPath, "force-app/main/default/aura"),
16224
+ path13.join(rootPath, "src/aura"),
16225
+ path13.join(rootPath, "aura")
16082
16226
  ];
16083
16227
  for (const fallback of fallbackPaths) {
16084
16228
  if (!searchPaths.includes(fallback)) {
@@ -16107,7 +16251,7 @@ async function scanAuraComponents(rootPath, includeBaseComponents = false) {
16107
16251
  }
16108
16252
  } else {
16109
16253
  for (const item of files) {
16110
- const itemPath = path12.join(searchPath, item);
16254
+ const itemPath = path13.join(searchPath, item);
16111
16255
  const itemStat = await fs10.stat(itemPath);
16112
16256
  if (itemStat.isDirectory()) {
16113
16257
  const result = await analyzeAuraComponent(itemPath, includeBaseComponents);
@@ -16132,7 +16276,7 @@ async function scanAuraComponents(rootPath, includeBaseComponents = false) {
16132
16276
  init_esm_shims();
16133
16277
  init_logger();
16134
16278
  import fs11 from "fs-extra";
16135
- import * as path13 from "path";
16279
+ import * as path14 from "path";
16136
16280
  import * as htmlparser24 from "htmlparser2";
16137
16281
  import { DomHandler as DomHandler4 } from "domhandler";
16138
16282
  function extractVfDependencies(markup) {
@@ -16320,7 +16464,7 @@ async function analyzeVfPage(pagePath) {
16320
16464
  const files = await fs11.readdir(pagePath);
16321
16465
  const pageFile = files.find((f) => f.endsWith(".page"));
16322
16466
  if (pageFile) {
16323
- actualPath = path13.join(pagePath, pageFile);
16467
+ actualPath = path14.join(pagePath, pageFile);
16324
16468
  } else {
16325
16469
  return null;
16326
16470
  }
@@ -16332,7 +16476,7 @@ async function analyzeVfPage(pagePath) {
16332
16476
  return null;
16333
16477
  }
16334
16478
  const markup = await fs11.readFile(actualPath, "utf-8");
16335
- const pageName = path13.basename(actualPath, ".page");
16479
+ const pageName = path14.basename(actualPath, ".page");
16336
16480
  const dependencies = extractVfDependencies(markup);
16337
16481
  return {
16338
16482
  id: `vf:${pageName}`,
@@ -16356,7 +16500,7 @@ async function analyzeVfComponent(componentPath) {
16356
16500
  const files = await fs11.readdir(componentPath);
16357
16501
  const componentFile = files.find((f) => f.endsWith(".component"));
16358
16502
  if (componentFile) {
16359
- actualPath = path13.join(componentPath, componentFile);
16503
+ actualPath = path14.join(componentPath, componentFile);
16360
16504
  } else {
16361
16505
  return null;
16362
16506
  }
@@ -16368,7 +16512,7 @@ async function analyzeVfComponent(componentPath) {
16368
16512
  return null;
16369
16513
  }
16370
16514
  const markup = await fs11.readFile(actualPath, "utf-8");
16371
- const componentName = path13.basename(actualPath, ".component");
16515
+ const componentName = path14.basename(actualPath, ".component");
16372
16516
  const dependencies = extractVfDependencies(markup);
16373
16517
  return {
16374
16518
  id: `vf:${componentName}`,
@@ -16383,13 +16527,13 @@ async function analyzeVfComponent(componentPath) {
16383
16527
  }
16384
16528
  }
16385
16529
  async function getPackageDirectories2(rootPath) {
16386
- const sfdxProjectPath = path13.join(rootPath, "sfdx-project.json");
16530
+ const sfdxProjectPath = path14.join(rootPath, "sfdx-project.json");
16387
16531
  if (await fs11.pathExists(sfdxProjectPath)) {
16388
16532
  try {
16389
16533
  const projectConfig = await fs11.readJson(sfdxProjectPath);
16390
16534
  if (projectConfig.packageDirectories && Array.isArray(projectConfig.packageDirectories)) {
16391
16535
  return projectConfig.packageDirectories.map(
16392
- (pkg) => path13.join(rootPath, pkg.path)
16536
+ (pkg) => path14.join(rootPath, pkg.path)
16393
16537
  );
16394
16538
  }
16395
16539
  } catch (error) {
@@ -16407,7 +16551,7 @@ async function findVfDirectories(basePath) {
16407
16551
  const entries = await fs11.readdir(dirPath, { withFileTypes: true });
16408
16552
  for (const entry of entries) {
16409
16553
  if (entry.isDirectory()) {
16410
- const fullPath = path13.join(dirPath, entry.name);
16554
+ const fullPath = path14.join(dirPath, entry.name);
16411
16555
  if (entry.name === "pages") {
16412
16556
  pagesDirectories.push(fullPath);
16413
16557
  } else if (entry.name === "components") {
@@ -16438,9 +16582,9 @@ async function scanVfPages(rootPath) {
16438
16582
  logger.debug(`Found components directories from sfdx-project.json: ${componentSearchPaths.join(", ")}`);
16439
16583
  }
16440
16584
  const fallbackPagePaths = [
16441
- path13.join(rootPath, "force-app/main/default/pages"),
16442
- path13.join(rootPath, "src/pages"),
16443
- path13.join(rootPath, "pages")
16585
+ path14.join(rootPath, "force-app/main/default/pages"),
16586
+ path14.join(rootPath, "src/pages"),
16587
+ path14.join(rootPath, "pages")
16444
16588
  ];
16445
16589
  for (const fallback of fallbackPagePaths) {
16446
16590
  if (!pageSearchPaths.includes(fallback)) {
@@ -16448,9 +16592,9 @@ async function scanVfPages(rootPath) {
16448
16592
  }
16449
16593
  }
16450
16594
  const fallbackComponentPaths = [
16451
- path13.join(rootPath, "force-app/main/default/components"),
16452
- path13.join(rootPath, "src/components"),
16453
- path13.join(rootPath, "components")
16595
+ path14.join(rootPath, "force-app/main/default/components"),
16596
+ path14.join(rootPath, "src/components"),
16597
+ path14.join(rootPath, "components")
16454
16598
  ];
16455
16599
  for (const fallback of fallbackComponentPaths) {
16456
16600
  if (!componentSearchPaths.includes(fallback)) {
@@ -16487,7 +16631,7 @@ async function scanVfPages(rootPath) {
16487
16631
  const files = await fs11.readdir(searchPath);
16488
16632
  for (const file of files) {
16489
16633
  if (file.endsWith(".page")) {
16490
- const result = await analyzeVfPage(path13.join(searchPath, file));
16634
+ const result = await analyzeVfPage(path14.join(searchPath, file));
16491
16635
  if (result) {
16492
16636
  results.push(result);
16493
16637
  }
@@ -16508,7 +16652,7 @@ async function scanVfPages(rootPath) {
16508
16652
  const files = await fs11.readdir(searchPath);
16509
16653
  for (const file of files) {
16510
16654
  if (file.endsWith(".component")) {
16511
- const result = await analyzeVfComponent(path13.join(searchPath, file));
16655
+ const result = await analyzeVfComponent(path14.join(searchPath, file));
16512
16656
  if (result) {
16513
16657
  results.push(result);
16514
16658
  }
@@ -17515,7 +17659,7 @@ function formatSimpleMermaidOutput(graph, maxNodes = 30) {
17515
17659
  // src/cli/commands/deps.ts
17516
17660
  async function analyzeDeps(target, options) {
17517
17661
  logger.setVerbose(options.verbose);
17518
- const targetPath = target ? path14.resolve(target) : process.cwd();
17662
+ const targetPath = target ? path15.resolve(target) : process.cwd();
17519
17663
  logger.banner();
17520
17664
  logger.header("Dependency Graph Analysis");
17521
17665
  logger.info(`Analyzing: ${targetPath}`);
@@ -17565,14 +17709,6 @@ async function analyzeDeps(target, options) {
17565
17709
  console.log(output);
17566
17710
  }
17567
17711
  break;
17568
- case "html":
17569
- logger.warn("HTML format not yet implemented. Using console output.");
17570
- formatConsoleOutput(graph, conversionOrder, options.showOrphans);
17571
- break;
17572
- case "dot":
17573
- logger.warn("DOT format not yet implemented. Using console output.");
17574
- formatConsoleOutput(graph, conversionOrder, options.showOrphans);
17575
- break;
17576
17712
  case "console":
17577
17713
  default:
17578
17714
  formatConsoleOutput(graph, conversionOrder, options.showOrphans);
@@ -17591,8 +17727,8 @@ async function analyzeDeps(target, options) {
17591
17727
  }
17592
17728
  }
17593
17729
  async function writeOutput(filePath, content) {
17594
- const outputPath = path14.resolve(filePath);
17595
- const outputDir = path14.dirname(outputPath);
17730
+ const outputPath = path15.resolve(filePath);
17731
+ const outputDir = path15.dirname(outputPath);
17596
17732
  await fs12.ensureDir(outputDir);
17597
17733
  await fs12.writeFile(outputPath, content, "utf-8");
17598
17734
  }
@@ -17610,7 +17746,7 @@ program.name(CLI_NAME).description(CLI_DESCRIPTION).version(CLI_VERSION).hook("p
17610
17746
  } catch {
17611
17747
  }
17612
17748
  });
17613
- 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) => {
17614
17750
  logger.setVerbose(options.verbose);
17615
17751
  await convertAura(bundlePath, {
17616
17752
  output: options.output,
@@ -17618,10 +17754,11 @@ program.command("aura <path>").description("Convert an Aura component bundle to
17618
17754
  dryRun: options.dryRun,
17619
17755
  verbose: options.verbose,
17620
17756
  open: options.open,
17621
- preview: options.preview
17757
+ preview: options.preview,
17758
+ apiVersion: options.apiVersion
17622
17759
  });
17623
17760
  });
17624
- 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) => {
17625
17762
  logger.setVerbose(options.verbose);
17626
17763
  await convertVf(pagePath, {
17627
17764
  output: options.output,
@@ -17630,7 +17767,8 @@ program.command("vf <path>").description("Convert a Visualforce page to LWC").op
17630
17767
  verbose: options.verbose,
17631
17768
  controller: options.controller,
17632
17769
  open: options.open,
17633
- preview: options.preview
17770
+ preview: options.preview,
17771
+ apiVersion: options.apiVersion
17634
17772
  });
17635
17773
  });
17636
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) => {