@immense/vue-pom-generator 1.0.3 → 1.0.8

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.cjs CHANGED
@@ -26,16 +26,15 @@ const path = require("node:path");
26
26
  const process = require("node:process");
27
27
  const node_url = require("node:url");
28
28
  const fs = require("node:fs");
29
+ const jsdom = require("jsdom");
29
30
  const compilerCore = require("@vue/compiler-core");
30
31
  const types = require("@babel/types");
31
32
  const parser = require("@babel/parser");
32
- const jsdom = require("jsdom");
33
33
  const node_perf_hooks = require("node:perf_hooks");
34
34
  const compilerDom = require("@vue/compiler-dom");
35
35
  const compilerSfc = require("@vue/compiler-sfc");
36
36
  const virtualImport = require("vite-plugin-virtual");
37
37
  const vue = require("@vitejs/plugin-vue");
38
- const shared = require("@vue/shared");
39
38
  var _documentCurrentScript = typeof document !== "undefined" ? document.currentScript : null;
40
39
  function _interopNamespaceDefault(e) {
41
40
  const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
@@ -54,6 +53,56 @@ function _interopNamespaceDefault(e) {
54
53
  return Object.freeze(n);
55
54
  }
56
55
  const compilerDom__namespace = /* @__PURE__ */ _interopNamespaceDefault(compilerDom);
56
+ const VUE_POM_GENERATOR_LOG_PREFIX = "[vue-pom-generator]";
57
+ function normalize(message) {
58
+ const trimmed = (message ?? "").trim();
59
+ if (!trimmed)
60
+ return VUE_POM_GENERATOR_LOG_PREFIX;
61
+ if (trimmed.startsWith(VUE_POM_GENERATOR_LOG_PREFIX))
62
+ return trimmed;
63
+ return `${VUE_POM_GENERATOR_LOG_PREFIX} ${trimmed}`;
64
+ }
65
+ function createLogger(options) {
66
+ const { verbosity, viteLogger } = options;
67
+ const sinkInfo = (msg) => {
68
+ if (viteLogger) {
69
+ viteLogger.info(normalize(msg));
70
+ return;
71
+ }
72
+ console.log(normalize(msg));
73
+ };
74
+ const sinkWarn = (msg) => {
75
+ if (viteLogger) {
76
+ viteLogger.warn(normalize(msg));
77
+ return;
78
+ }
79
+ console.warn(normalize(msg));
80
+ };
81
+ const sinkDebug = (msg) => {
82
+ if (viteLogger) {
83
+ viteLogger.info(normalize(msg));
84
+ return;
85
+ }
86
+ console.log(normalize(msg));
87
+ };
88
+ return {
89
+ info(message) {
90
+ if (verbosity === "silent" || verbosity === "warn")
91
+ return;
92
+ sinkInfo(message);
93
+ },
94
+ debug(message) {
95
+ if (verbosity !== "debug")
96
+ return;
97
+ sinkDebug(message);
98
+ },
99
+ warn(message) {
100
+ if (verbosity === "silent")
101
+ return;
102
+ sinkWarn(message);
103
+ }
104
+ };
105
+ }
57
106
  const INDENT = " ";
58
107
  const INDENT2 = `${INDENT}${INDENT}`;
59
108
  const INDENT3 = `${INDENT2}${INDENT}`;
@@ -188,10 +237,21 @@ ${INDENT}}
188
237
  `;
189
238
  return content;
190
239
  }
240
+ function isAllDigits(value) {
241
+ if (!value)
242
+ return false;
243
+ for (let i = 0; i < value.length; i++) {
244
+ const code = value.charCodeAt(i);
245
+ if (code < 48 || code > 57)
246
+ return false;
247
+ }
248
+ return true;
249
+ }
191
250
  function generateGetElementByDataTestId(methodName, nativeRole, formattedDataTestId, alternateFormattedDataTestIds, getterNameOverride, params) {
192
251
  const roleSuffix = upperFirst$1(nativeRole || "Element");
193
252
  const baseName = upperFirst$1(methodName);
194
- const hasRoleSuffix = baseName.endsWith(roleSuffix) || new RegExp(`^${roleSuffix}\\d+$`).test(baseName);
253
+ const numericSuffix = baseName.startsWith(roleSuffix) ? baseName.slice(roleSuffix.length) : "";
254
+ const hasRoleSuffix = baseName.endsWith(roleSuffix) || baseName.startsWith(roleSuffix) && isAllDigits(numericSuffix);
195
255
  const propertyName = hasRoleSuffix ? `${baseName}` : `${baseName}${roleSuffix}`;
196
256
  const needsKey = hasParam(params, "key") || formattedDataTestId.includes("${key}");
197
257
  if (needsKey) {
@@ -497,6 +557,23 @@ function tryGetClickDirective(node) {
497
557
  function nodeHasClickDirective(node) {
498
558
  return tryGetClickDirective(node) !== void 0;
499
559
  }
560
+ function getTemplateSlotScope(node) {
561
+ if (node.tag !== "template") {
562
+ return null;
563
+ }
564
+ const slotProp = node.props.find((prop) => {
565
+ return prop.type === compilerCore.NodeTypes.DIRECTIVE && prop.name === "slot";
566
+ });
567
+ if (slotProp?.exp) {
568
+ if (slotProp.exp.type === compilerCore.NodeTypes.SIMPLE_EXPRESSION) {
569
+ return slotProp.exp.content;
570
+ }
571
+ if (slotProp.exp.type === compilerCore.NodeTypes.COMPOUND_EXPRESSION) {
572
+ return compilerCore.stringifyExpression(slotProp.exp);
573
+ }
574
+ }
575
+ return null;
576
+ }
500
577
  function nodeHasToDirective(node) {
501
578
  const toDirective = findDirectiveByName(node, "bind", "to");
502
579
  if (toDirective?.exp) {
@@ -566,6 +643,37 @@ function getIdOrName(node) {
566
643
  }
567
644
  return identifier;
568
645
  }
646
+ function getContainedInSlotDataKeyValue(node, hierarchyMap2) {
647
+ let parent = getParent(hierarchyMap2, node);
648
+ while (parent) {
649
+ if (parent.type === compilerCore.NodeTypes.ELEMENT && parent.tag === "template") {
650
+ const scope = getTemplateSlotScope(parent);
651
+ if (scope) {
652
+ let key = scope.trim();
653
+ if (key.startsWith("{") && key.endsWith("}")) {
654
+ const inner = key.slice(1, -1).trim();
655
+ let cutIdx = -1;
656
+ const commaIdx = inner.indexOf(",");
657
+ const colonIdx = inner.indexOf(":");
658
+ if (commaIdx !== -1 && colonIdx !== -1) {
659
+ cutIdx = Math.min(commaIdx, colonIdx);
660
+ } else if (commaIdx !== -1) {
661
+ cutIdx = commaIdx;
662
+ } else if (colonIdx !== -1) {
663
+ cutIdx = colonIdx;
664
+ }
665
+ const first = (cutIdx === -1 ? inner : inner.slice(0, cutIdx)).trim();
666
+ if (first) {
667
+ key = first;
668
+ }
669
+ }
670
+ return key;
671
+ }
672
+ }
673
+ parent = getParent(hierarchyMap2, parent);
674
+ }
675
+ return null;
676
+ }
569
677
  function getContainedInVForDirectiveKeyValue(context, node, hierarchyMap2) {
570
678
  if (!context.scopes.vFor || context.scopes.vFor === 0) {
571
679
  return null;
@@ -655,6 +763,9 @@ function tryGetContainedInStaticVForSourceLiteralValues(context, _node, _hierarc
655
763
  function getParent(hierarchyMap2, node) {
656
764
  return hierarchyMap2.get(node) || null;
657
765
  }
766
+ function nodeHandlerAttributeValue(node) {
767
+ return nodeHandlerAttributeInfo(node)?.semanticNameHint ?? null;
768
+ }
658
769
  function nodeHandlerAttributeInfo(node) {
659
770
  const handlerDirective = findDirectiveByName(node, "bind", "handler");
660
771
  if (!handlerDirective?.exp) {
@@ -1013,7 +1124,7 @@ function generateToDirectiveDataTestId(componentName, node, toDirective, context
1013
1124
  }
1014
1125
  const source = compilerCore.stringifyExpression(toDirective.exp);
1015
1126
  const toAst = toDirective.exp.ast;
1016
- const interpolated = !(toAst == null || toAst === false || toAst) && types.isTemplateLiteral(toAst);
1127
+ const interpolated = toAst !== void 0 && toAst !== null && toAst !== false && types.isTemplateLiteral(toAst);
1017
1128
  return templateAttributeValue(`${componentName}-\${${source}${interpolated ? ".replaceAll(' ', '')" : "?.name?.replaceAll(' ', '') ?? ''"}}${formatTagName(node, nativeWrappers)}`);
1018
1129
  } else {
1019
1130
  const innerText = getInnerText(node);
@@ -1074,7 +1185,7 @@ function toDirectiveObjectFieldNameValue(node) {
1074
1185
  return null;
1075
1186
  }
1076
1187
  }
1077
- function getComposedClickHandlerContent(node, _context, _innerText, clickDirective, _options = {}) {
1188
+ function getComposedClickHandlerContent(node, _context, innerText, clickDirective, _options = {}) {
1078
1189
  const click = clickDirective ?? tryGetClickDirective(node);
1079
1190
  if (!click) {
1080
1191
  return "";
@@ -1093,8 +1204,9 @@ function getComposedClickHandlerContent(node, _context, _innerText, clickDirecti
1093
1204
  }
1094
1205
  }
1095
1206
  }
1096
- handlerName = normalizeHandlerName(handlerName);
1097
- const normalizedHandlerSegment = handlerName ? `-${toPascalCase(handlerName)}` : "";
1207
+ const hName = normalizeHandlerName(handlerName);
1208
+ const name = hName || innerText || "";
1209
+ const normalizedHandlerSegment = name ? `-${toPascalCase(name)}` : "";
1098
1210
  const result = normalizedHandlerSegment;
1099
1211
  return result.replace(/[^a-z-]/gi, "");
1100
1212
  }
@@ -1390,6 +1502,31 @@ function tryGetExistingElementDataTestId(node, attributeName = "data-testid") {
1390
1502
  if (!raw) {
1391
1503
  return null;
1392
1504
  }
1505
+ try {
1506
+ const ast2 = parser.parseExpression(raw, { plugins: ["typescript"] });
1507
+ if (ast2 && typeof ast2 === "object" && "type" in ast2 && ast2.type === "TemplateLiteral") {
1508
+ const tl = ast2;
1509
+ const cooked = (tl.quasis ?? []).map((q) => q.value?.cooked ?? "").join("");
1510
+ const expressionCount = (tl.expressions ?? []).length;
1511
+ const isStatic = expressionCount === 0;
1512
+ const unwrappedTemplate = raw.startsWith("`") && raw.endsWith("`") && raw.length >= 2 ? raw.slice(1, -1) : cooked;
1513
+ if (isStatic) {
1514
+ return { value: unwrappedTemplate, isDynamic: false, isStaticLiteral: true };
1515
+ }
1516
+ return {
1517
+ value: unwrappedTemplate,
1518
+ isDynamic: true,
1519
+ isStaticLiteral: false,
1520
+ template: unwrappedTemplate,
1521
+ templateExpressionCount: expressionCount
1522
+ };
1523
+ }
1524
+ if (ast2 && typeof ast2 === "object" && "type" in ast2 && ast2.type === "StringLiteral") {
1525
+ const sl = ast2;
1526
+ return { value: sl.value ?? "", isDynamic: false, isStaticLiteral: true };
1527
+ }
1528
+ } catch {
1529
+ }
1393
1530
  return { value: raw, isDynamic: true, isStaticLiteral: false, rawExpression: raw };
1394
1531
  }
1395
1532
  function isTemplatePlaceholder(part) {
@@ -1517,13 +1654,15 @@ Existing ${attrLabel}: ${JSON.stringify(existing.value)}
1517
1654
  Fix: reduce the template to a single key-based interpolation, or remove the explicit ${attrLabel} so it can be auto-generated.`
1518
1655
  );
1519
1656
  }
1520
- if (args.bestKeyPlaceholder && !existing.template.includes(args.bestKeyPlaceholder)) {
1657
+ const hasExact = args.bestKeyPlaceholder && existing.template.includes(args.bestKeyPlaceholder);
1658
+ const hasVarAccess = args.bestKeyVariable && existing.template.includes(args.bestKeyVariable);
1659
+ if (!hasExact && !hasVarAccess && args.bestKeyPlaceholder) {
1521
1660
  throw new Error(
1522
1661
  `[vue-pom-generator] Existing ${attrLabel} appears to be missing the key placeholder needed to keep it unique.
1523
1662
  Component: ${args.componentName}
1524
1663
  File: ${file}:${locationHint}
1525
1664
  Existing ${attrLabel}: ${JSON.stringify(existing.value)}
1526
- Required placeholder: ${JSON.stringify(args.bestKeyPlaceholder)}
1665
+ Required placeholder: ${JSON.stringify(args.bestKeyPlaceholder)}${args.bestKeyVariable ? ` or an access on "${args.bestKeyVariable}"` : ""}
1527
1666
 
1528
1667
  Fix: either (1) include ${args.bestKeyPlaceholder} in your :${attrLabel} template literal, or (2) remove the explicit ${attrLabel} so it can be auto-generated.`
1529
1668
  );
@@ -1548,7 +1687,7 @@ If you really need a computed id, do not set existingIdBehavior="preserve".`
1548
1687
  Component: ${args.componentName}
1549
1688
  File: ${file}:${locationHint}
1550
1689
  Existing ${attrLabel}: ${JSON.stringify(existing.value)}
1551
- Required placeholder: ${JSON.stringify(args.bestKeyPlaceholder)}
1690
+ Required placeholder: ${JSON.stringify(args.bestKeyPlaceholder)}${args.bestKeyVariable ? ` or an access on "${args.bestKeyVariable}"` : ""}
1552
1691
 
1553
1692
  Fix: either (1) include ${args.bestKeyPlaceholder} in your :${attrLabel} template literal, or (2) remove the explicit ${attrLabel} so it can be auto-generated.`
1554
1693
  );
@@ -1575,6 +1714,7 @@ Fix: either (1) include ${args.bestKeyPlaceholder} in your :${attrLabel} templat
1575
1714
  case "checkbox":
1576
1715
  case "toggle":
1577
1716
  case "radio":
1717
+ case "grid":
1578
1718
  return role;
1579
1719
  default:
1580
1720
  return void 0;
@@ -1669,10 +1809,10 @@ Fix: either (1) include ${args.bestKeyPlaceholder} in your :${attrLabel} templat
1669
1809
  args.dependencies.__pomPrimaryByGetterName ??= /* @__PURE__ */ new Map();
1670
1810
  const primaryByActionName = args.dependencies.__pomPrimaryByActionName;
1671
1811
  const hintCandidates = (() => {
1672
- const baseHints = [args.semanticNameHint];
1673
- if (nameCollisionBehavior === "error") {
1674
- baseHints.push(...args.semanticNameHintAlternates ?? []);
1675
- }
1812
+ const baseHints = [
1813
+ args.semanticNameHint,
1814
+ ...args.semanticNameHintAlternates ?? []
1815
+ ];
1676
1816
  const out = [];
1677
1817
  const seen = /* @__PURE__ */ new Set();
1678
1818
  for (const h of baseHints) {
@@ -2421,6 +2561,84 @@ async function ensureDomShim() {
2421
2561
  if (!g.requestAnimationFrame)
2422
2562
  g.requestAnimationFrame = (cb) => setTimeout(() => cb(Date.now()), 16);
2423
2563
  }
2564
+ async function introspectNuxtPages(projectRoot) {
2565
+ const possiblePagesDirs = ["app/pages", "pages"];
2566
+ let pagesDir = "";
2567
+ for (const dir of possiblePagesDirs) {
2568
+ const abs = path.resolve(projectRoot, dir);
2569
+ if (fs.existsSync(abs) && fs.statSync(abs).isDirectory()) {
2570
+ pagesDir = abs;
2571
+ break;
2572
+ }
2573
+ }
2574
+ if (!pagesDir) {
2575
+ debugLog(`[router-introspection][nuxt] Could not find pages directory in ${projectRoot}`);
2576
+ return { routeNameMap: /* @__PURE__ */ new Map(), routePathMap: /* @__PURE__ */ new Map(), routeMetaEntries: [] };
2577
+ }
2578
+ const routeMetaEntries = [];
2579
+ const walk = (dir, baseRoute) => {
2580
+ const files = fs.readdirSync(dir);
2581
+ for (const file of files) {
2582
+ const fullPath = path.join(dir, file);
2583
+ const stat = fs.statSync(fullPath);
2584
+ if (stat.isDirectory()) {
2585
+ walk(fullPath, `${baseRoute}/${file}`);
2586
+ continue;
2587
+ }
2588
+ if (!file.endsWith(".vue"))
2589
+ continue;
2590
+ const componentName = file.slice(0, -4);
2591
+ if (componentName === "index" && baseRoute === "") {
2592
+ routeMetaEntries.push({
2593
+ componentName: "index",
2594
+ pathTemplate: "/",
2595
+ params: [],
2596
+ query: []
2597
+ });
2598
+ continue;
2599
+ }
2600
+ let routePath = componentName === "index" ? baseRoute : `${baseRoute}/${componentName}`;
2601
+ if (!routePath.startsWith("/"))
2602
+ routePath = `/${routePath}`;
2603
+ const params = [];
2604
+ let pathTemplate = "";
2605
+ for (let i = 0; i < routePath.length; i++) {
2606
+ const ch = routePath[i];
2607
+ if (ch !== "[") {
2608
+ pathTemplate += ch;
2609
+ continue;
2610
+ }
2611
+ let name = "";
2612
+ i++;
2613
+ while (i < routePath.length) {
2614
+ const c = routePath[i];
2615
+ if (c === "]")
2616
+ break;
2617
+ name += c;
2618
+ i++;
2619
+ }
2620
+ if (name) {
2621
+ params.push({ name, optional: false });
2622
+ pathTemplate += `:${name}`;
2623
+ } else {
2624
+ pathTemplate += "[]";
2625
+ }
2626
+ }
2627
+ routeMetaEntries.push({
2628
+ componentName,
2629
+ pathTemplate,
2630
+ params,
2631
+ query: []
2632
+ });
2633
+ }
2634
+ };
2635
+ walk(pagesDir, "");
2636
+ return {
2637
+ routeNameMap: /* @__PURE__ */ new Map(),
2638
+ routePathMap: /* @__PURE__ */ new Map(),
2639
+ routeMetaEntries
2640
+ };
2641
+ }
2424
2642
  async function parseRouterFileFromCwd(routerEntryPath) {
2425
2643
  return await runRouterIntrospectionExclusive(async () => {
2426
2644
  const routerEntry = path.resolve(routerEntryPath);
@@ -2536,9 +2754,9 @@ function resolveRouterEntry(projectRoot, routerEntry) {
2536
2754
  const root = projectRoot ?? process.cwd();
2537
2755
  return path.isAbsolute(routerEntry) ? routerEntry : path.resolve(root, routerEntry);
2538
2756
  }
2539
- async function getRouteMetaByComponent(projectRoot, routerEntry) {
2540
- const resolvedRouterEntry = resolveRouterEntry(projectRoot, routerEntry);
2541
- const { routeMetaEntries } = await parseRouterFileFromCwd(resolvedRouterEntry);
2757
+ async function getRouteMetaByComponent(projectRoot, routerEntry, routerType) {
2758
+ const root = projectRoot ?? process.cwd();
2759
+ const { routeMetaEntries } = routerType === "nuxt" ? await introspectNuxtPages(root) : await parseRouterFileFromCwd(resolveRouterEntry(root, routerEntry));
2542
2760
  const map = /* @__PURE__ */ new Map();
2543
2761
  for (const entry of routeMetaEntries) {
2544
2762
  const list = map.get(entry.componentName) ?? [];
@@ -2691,12 +2909,14 @@ async function generateFiles(componentHierarchyMap, vueFilesPathMap, basePageCla
2691
2909
  customPomImportAliases,
2692
2910
  testIdAttribute,
2693
2911
  emitLanguages: emitLanguagesOverride,
2912
+ csharp,
2694
2913
  vueRouterFluentChaining,
2695
- routerEntry
2914
+ routerEntry,
2915
+ routerType
2696
2916
  } = options;
2697
2917
  const emitLanguages = emitLanguagesOverride?.length ? emitLanguagesOverride : ["ts"];
2698
2918
  const outDir = outDirOverride ?? "./pom";
2699
- const routeMetaByComponent = vueRouterFluentChaining ? await getRouteMetaByComponent(projectRoot, routerEntry) : void 0;
2919
+ const routeMetaByComponent = vueRouterFluentChaining ? await getRouteMetaByComponent(projectRoot, routerEntry, routerType) : void 0;
2700
2920
  if (emitLanguages.includes("ts")) {
2701
2921
  const files = await generateAggregatedFiles(componentHierarchyMap, vueFilesPathMap, basePageClassPath, outDir, {
2702
2922
  customPomAttachments,
@@ -2718,7 +2938,8 @@ async function generateFiles(componentHierarchyMap, vueFilesPathMap, basePageCla
2718
2938
  }
2719
2939
  if (emitLanguages.includes("csharp")) {
2720
2940
  const csFiles = generateAggregatedCSharpFiles(componentHierarchyMap, outDir, {
2721
- projectRoot
2941
+ testIdAttribute,
2942
+ csharp
2722
2943
  });
2723
2944
  for (const file of csFiles) {
2724
2945
  createFile(file.filePath, file.content);
@@ -2741,11 +2962,11 @@ function toCSharpParam(paramTypeExpr) {
2741
2962
  const right = eqIdx >= 0 ? trimmed.slice(eqIdx + 1).trim() : void 0;
2742
2963
  const typePart = left.includes("|") ? "string" : left;
2743
2964
  let type = "string";
2744
- if (/(^|\s)boolean(\s|$)/.test(typePart))
2965
+ if (/(?:^|\s)boolean(?:\s|$)/.test(typePart))
2745
2966
  type = "bool";
2746
- else if (/(^|\s)string(\s|$)/.test(typePart))
2967
+ else if (/(?:^|\s)string(?:\s|$)/.test(typePart))
2747
2968
  type = "string";
2748
- else if (/(^|\s)number(\s|$)/.test(typePart))
2969
+ else if (/(?:^|\s)number(?:\s|$)/.test(typePart))
2749
2970
  type = "int";
2750
2971
  else if (/\d+/.test(typePart) && typePart === "")
2751
2972
  type = "int";
@@ -2782,8 +3003,9 @@ function formatCSharpParams(params) {
2782
3003
  return { signature: signatureParts.join(", "), argNames };
2783
3004
  }
2784
3005
  function generateAggregatedCSharpFiles(componentHierarchyMap, outDir, options = {}) {
2785
- const projectRoot = options.projectRoot ?? process.cwd();
2786
- const outAbs = path.isAbsolute(outDir) ? outDir : path.resolve(projectRoot, outDir);
3006
+ const outAbs = ensureDir(outDir);
3007
+ const namespace = options.csharp?.namespace ?? "Playwright.Generated";
3008
+ const testIdAttribute = (options.testIdAttribute || "data-testid").trim() || "data-testid";
2787
3009
  const entries = Array.from(componentHierarchyMap.entries()).sort((a, b) => a[0].localeCompare(b[0]));
2788
3010
  const header = [
2789
3011
  "// <auto-generated>",
@@ -2797,13 +3019,13 @@ function generateAggregatedCSharpFiles(componentHierarchyMap, outDir, options =
2797
3019
  "using System.Threading.Tasks;",
2798
3020
  "using Microsoft.Playwright;",
2799
3021
  "",
2800
- "namespace ImmyBot.Playwright.Generated;",
3022
+ `namespace ${namespace};`,
2801
3023
  "",
2802
- "public abstract class BasePage",
3024
+ "public abstract partial class BasePage",
2803
3025
  "{",
2804
3026
  " protected BasePage(IPage page) => Page = page;",
2805
3027
  " protected IPage Page { get; }",
2806
- " protected ILocator LocatorByTestId(string testId) => Page.GetByTestId(testId);",
3028
+ ` protected ILocator LocatorByTestId(string testId) => Page.Locator($"[${testIdAttribute}=\\"{testId}\\"]");`,
2807
3029
  "",
2808
3030
  " // Minimal vue-select helper mirroring the TS BasePage.selectVSelectByTestId behavior.",
2809
3031
  " // Note: annotationText is currently a no-op in C# output (we don't render a cursor overlay).",
@@ -2827,10 +3049,11 @@ function generateAggregatedCSharpFiles(componentHierarchyMap, outDir, options =
2827
3049
  ].join("\n");
2828
3050
  const chunks = [header];
2829
3051
  for (const [componentName, deps] of entries) {
3052
+ const className = toPascalCaseLocal(componentName);
2830
3053
  chunks.push(
2831
- `public sealed class ${componentName} : BasePage
3054
+ `public partial class ${className} : BasePage
2832
3055
  {
2833
- public ${componentName}(IPage page) : base(page) { }
3056
+ public ${className}(IPage page) : base(page) { }
2834
3057
  `
2835
3058
  );
2836
3059
  const primaries = Array.from(deps.dataTestIdSet ?? []).map((e) => ({ pom: e.pom, target: e.targetPageObjectModelClass })).filter((x) => !!x.pom).sort((a, b) => a.pom.methodName.localeCompare(b.pom.methodName));
@@ -2969,7 +3192,7 @@ function generateAggregatedCSharpFiles(componentHierarchyMap, outDir, options =
2969
3192
  return [{ filePath: outputFile, content: chunks.join("\n") }];
2970
3193
  }
2971
3194
  function maybeGenerateFixtureRegistry(componentHierarchyMap, options) {
2972
- const { generateFixtures, pomOutDir, projectRoot } = options;
3195
+ const { generateFixtures, pomOutDir } = options;
2973
3196
  if (!generateFixtures)
2974
3197
  return;
2975
3198
  const defaultFixtureOutDirRel = pomOutDir;
@@ -2977,7 +3200,7 @@ function maybeGenerateFixtureRegistry(componentHierarchyMap, options) {
2977
3200
  const looksLikeFilePath = fixtureOutRel.endsWith(".ts") || fixtureOutRel.endsWith(".tsx") || fixtureOutRel.endsWith(".mts") || fixtureOutRel.endsWith(".cts");
2978
3201
  const fixtureOutDirRel = looksLikeFilePath ? path.dirname(fixtureOutRel) : fixtureOutRel;
2979
3202
  const fixtureFileName = looksLikeFilePath ? path.basename(fixtureOutRel) : "fixtures.g.ts";
2980
- const root = projectRoot ?? process.cwd();
3203
+ const root = options.projectRoot ?? process.cwd();
2981
3204
  const fixtureOutDirAbs = path.isAbsolute(fixtureOutDirRel) ? fixtureOutDirRel : path.resolve(root, fixtureOutDirRel);
2982
3205
  const pomDirAbs = path.isAbsolute(pomOutDir) ? pomOutDir : path.resolve(root, pomOutDir);
2983
3206
  const pomImport = toPosixRelativePath(fixtureOutDirAbs, pomDirAbs);
@@ -3218,7 +3441,7 @@ async function generateAggregatedFiles(componentHierarchyMap, vueFilesPathMap, b
3218
3441
  const views = entries.filter(([, d]) => d.isView);
3219
3442
  const components = entries.filter(([, d]) => !d.isView);
3220
3443
  const makeAggregatedContent = (header2, outputDir, items) => {
3221
- const imports = ['import type { Locator as PwLocator, Page as PwPage } from "@playwright/test";'];
3444
+ const imports = [];
3222
3445
  if (!basePageClassPath) {
3223
3446
  throw new Error("basePageClassPath is required for aggregated generation");
3224
3447
  }
@@ -3231,6 +3454,16 @@ async function generateAggregatedFiles(componentHierarchyMap, vueFilesPathMap, b
3231
3454
  " err?: string;",
3232
3455
  "}"
3233
3456
  ].join("\n");
3457
+ const inlinePlaywrightTypesModule = () => {
3458
+ const typesPath = node_url.fileURLToPath(new URL("./playwright-types.ts", typeof document === "undefined" ? require("url").pathToFileURL(__filename).href : _documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === "SCRIPT" && _documentCurrentScript.src || new URL("index.cjs", document.baseURI).href));
3459
+ let typesSource = "";
3460
+ try {
3461
+ typesSource = fs.readFileSync(typesPath, "utf8");
3462
+ } catch {
3463
+ throw new Error(`Failed to read playwright-types.ts at ${typesPath}`);
3464
+ }
3465
+ return typesSource.trim();
3466
+ };
3234
3467
  const inlinePointerModule = () => {
3235
3468
  const pointerPath = node_url.fileURLToPath(new URL("./Pointer.ts", typeof document === "undefined" ? require("url").pathToFileURL(__filename).href : _documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === "SCRIPT" && _documentCurrentScript.src || new URL("index.cjs", document.baseURI).href));
3236
3469
  let pointerSource = "";
@@ -3253,6 +3486,10 @@ async function generateAggregatedFiles(componentHierarchyMap, vueFilesPathMap, b
3253
3486
  /import\s+type\s*\{\s*Locator\s+as\s+PwLocator\s*,\s*Page\s+as\s+PwPage\s*\}\s*from\s*["']@playwright\/test["'];?\s*/,
3254
3487
  ""
3255
3488
  );
3489
+ pointerSource = pointerSource.replace(
3490
+ /import\s+type\s*\{\s*PwLocator\s*,\s*PwPage\s*\}\s*from\s*["']\.\/playwright-types["'];?\s*/,
3491
+ ""
3492
+ );
3256
3493
  return pointerSource.trim();
3257
3494
  };
3258
3495
  const inlineBasePageModule = () => {
@@ -3277,18 +3514,23 @@ async function generateAggregatedFiles(componentHierarchyMap, vueFilesPathMap, b
3277
3514
  ""
3278
3515
  );
3279
3516
  basePageSource = basePageSource.replace(
3280
- /import\s*\{\s*Pointer\s*\}\s*from\s*["']\.\/Pointer["'];?\s*/g,
3517
+ /import\s+type\s*\{\s*PwLocator\s*,\s*PwPage\s*\}\s*from\s*["']\.\/playwright-types["'];?\s*/,
3518
+ ""
3519
+ );
3520
+ basePageSource = basePageSource.replace(
3521
+ /import\s+(?:type\s*)?\{[\s\S]*?\}\s*from\s*["']\.\/Pointer["'];?\s*/g,
3281
3522
  ""
3282
3523
  );
3283
3524
  return basePageSource.trim();
3284
3525
  };
3526
+ const playwrightTypesInline = inlinePlaywrightTypesModule();
3285
3527
  const pointerInline = inlinePointerModule();
3286
3528
  const basePageInline = inlineBasePageModule();
3287
3529
  const addCustomPomImports = () => {
3288
3530
  const importAliases = {
3289
3531
  Toggle: "ToggleWidget",
3290
3532
  Checkbox: "CheckboxWidget",
3291
- ...options.customPomImportAliases ?? {}
3533
+ ...options.customPomImportAliases
3292
3534
  };
3293
3535
  const customDirRelOrAbs = options.customPomDir ?? "tests/playwright/pom/custom";
3294
3536
  const customDirAbs = path.isAbsolute(customDirRelOrAbs) ? customDirRelOrAbs : path.resolve(projectRoot, customDirRelOrAbs);
@@ -3471,6 +3713,8 @@ async function generateAggregatedFiles(componentHierarchyMap, vueFilesPathMap, b
3471
3713
  header2,
3472
3714
  ...imports,
3473
3715
  "",
3716
+ playwrightTypesInline,
3717
+ "",
3474
3718
  pointerInline,
3475
3719
  "",
3476
3720
  basePageInline,
@@ -3482,7 +3726,8 @@ async function generateAggregatedFiles(componentHierarchyMap, vueFilesPathMap, b
3482
3726
  };
3483
3727
  const base = ensureDir(outDir);
3484
3728
  const outputFile = path.join(base, "page-object-models.g.ts");
3485
- const header = `${eslintSuppressionHeader}/**
3729
+ const header = `/// <reference lib="es2015" />
3730
+ ${eslintSuppressionHeader}/**
3486
3731
  * Aggregated generated POMs
3487
3732
  ${AUTO_GENERATED_COMMENT}`;
3488
3733
  const content = makeAggregatedContent(header, path.dirname(outputFile), [...views, ...components]);
@@ -3629,6 +3874,7 @@ function createBuildProcessorPlugin(options) {
3629
3874
  normalizedBasePagePath,
3630
3875
  outDir,
3631
3876
  emitLanguages,
3877
+ csharp,
3632
3878
  generateFixtures,
3633
3879
  customPomAttachments,
3634
3880
  projectRootRef,
@@ -3637,6 +3883,7 @@ function createBuildProcessorPlugin(options) {
3637
3883
  testIdAttribute,
3638
3884
  routerAwarePoms,
3639
3885
  resolvedRouterEntry,
3886
+ routerType,
3640
3887
  loggerRef
3641
3888
  } = options;
3642
3889
  let lastGeneratedEntryCount = 0;
@@ -3651,9 +3898,15 @@ function createBuildProcessorPlugin(options) {
3651
3898
  setResolveToComponentNameFn(() => null);
3652
3899
  return;
3653
3900
  }
3654
- if (!resolvedRouterEntry)
3655
- throw new Error("[vue-pom-generator] router.entry is required when router introspection is enabled.");
3656
- const { routeNameMap, routePathMap } = await parseRouterFileFromCwd(resolvedRouterEntry);
3901
+ let result;
3902
+ if (routerType === "nuxt") {
3903
+ result = await introspectNuxtPages(projectRootRef.current);
3904
+ } else {
3905
+ if (!resolvedRouterEntry)
3906
+ throw new Error("[vue-pom-generator] router.entry is required when router introspection is enabled.");
3907
+ result = await parseRouterFileFromCwd(resolvedRouterEntry);
3908
+ }
3909
+ const { routeNameMap, routePathMap } = result;
3657
3910
  setRouteNameToComponentNameMap(routeNameMap);
3658
3911
  setResolveToComponentNameFn((to) => {
3659
3912
  if (typeof to === "string") {
@@ -3690,6 +3943,7 @@ function createBuildProcessorPlugin(options) {
3690
3943
  generateFiles(componentHierarchyMap, vueFilesPathMap, normalizedBasePagePath, {
3691
3944
  outDir,
3692
3945
  emitLanguages,
3946
+ csharp,
3693
3947
  generateFixtures,
3694
3948
  customPomAttachments,
3695
3949
  projectRoot: projectRootRef.current,
@@ -3697,7 +3951,8 @@ function createBuildProcessorPlugin(options) {
3697
3951
  customPomImportAliases,
3698
3952
  testIdAttribute,
3699
3953
  vueRouterFluentChaining: routerAwarePoms,
3700
- routerEntry: resolvedRouterEntry
3954
+ routerEntry: resolvedRouterEntry,
3955
+ routerType
3701
3956
  });
3702
3957
  lastGeneratedEntryCount = entryCount;
3703
3958
  loggerRef.current.info(`generated POMs (${entryCount} entries)`);
@@ -3826,7 +4081,7 @@ function tryExtractStableHintFromConditionalExpressionSource(source) {
3826
4081
  return null;
3827
4082
  }
3828
4083
  }
3829
- function tryInferNativeWrapperRoleFromSfc(tag) {
4084
+ function tryInferNativeWrapperRoleFromSfc(tag, vueFilesPathMap) {
3830
4085
  const first = tag.charCodeAt(0);
3831
4086
  const isUpper = first >= 65 && first <= 90;
3832
4087
  if (!isUpper)
@@ -3835,8 +4090,14 @@ function tryInferNativeWrapperRoleFromSfc(tag) {
3835
4090
  if (cached)
3836
4091
  return cached;
3837
4092
  const candidates = [
3838
- path.resolve(process.cwd(), "src/components", `${tag}.vue`)
4093
+ path.resolve(process.cwd(), "src/components", `${tag}.vue`),
4094
+ path.resolve(process.cwd(), "components", `${tag}.vue`),
4095
+ path.resolve(process.cwd(), "app/components", `${tag}.vue`)
3839
4096
  ];
4097
+ const registeredPath = vueFilesPathMap?.get(tag);
4098
+ if (registeredPath) {
4099
+ candidates.unshift(path.resolve(process.cwd(), registeredPath));
4100
+ }
3840
4101
  const filePath = candidates.find((p) => fs.existsSync(p));
3841
4102
  if (!filePath) {
3842
4103
  inferredNativeWrapperConfigByTag.set(tag, { role: "" });
@@ -3877,14 +4138,22 @@ function tryInferNativeWrapperRoleFromSfc(tag) {
3877
4138
  inferredNativeWrapperConfigByTag.set(tag, { role: "" });
3878
4139
  return null;
3879
4140
  }
3880
- if (rootTag === "input" || rootTag === "textarea") {
4141
+ if (rootTag === "input" || rootTag === "textarea" || rootTag === "uinput" || rootTag === "utextarea") {
3881
4142
  inferredNativeWrapperConfigByTag.set(tag, { role: "input" });
3882
4143
  return { role: "input" };
3883
4144
  }
3884
- if (rootTag === "select") {
4145
+ if (rootTag === "select" || rootTag === "uselect") {
3885
4146
  inferredNativeWrapperConfigByTag.set(tag, { role: "select" });
3886
4147
  return { role: "select" };
3887
4148
  }
4149
+ if (rootTag === "vselect") {
4150
+ inferredNativeWrapperConfigByTag.set(tag, { role: "vselect" });
4151
+ return { role: "vselect" };
4152
+ }
4153
+ if (rootTag === "button" || rootTag === "ubutton") {
4154
+ inferredNativeWrapperConfigByTag.set(tag, { role: "button" });
4155
+ return { role: "button" };
4156
+ }
3888
4157
  inferredNativeWrapperConfigByTag.set(tag, { role: "" });
3889
4158
  return null;
3890
4159
  }
@@ -4059,6 +4328,7 @@ function createTestIdTransform(componentName, componentHierarchyMap, nativeWrapp
4059
4328
  const testIdAttribute = (options.testIdAttribute || "data-testid").trim() || "data-testid";
4060
4329
  const nameCollisionBehavior = options.nameCollisionBehavior ?? "suffix";
4061
4330
  const warn = options.warn;
4331
+ const vueFilesPathMap = options.vueFilesPathMap;
4062
4332
  const safeRealpath = (p) => {
4063
4333
  try {
4064
4334
  return fs.existsSync(p) ? fs.realpathSync(p) : p;
@@ -4110,18 +4380,14 @@ function createTestIdTransform(componentName, componentHierarchyMap, nativeWrapp
4110
4380
  const parentElement = !parentIsRoot && context?.parent?.type === compilerCore.NodeTypes.ELEMENT ? context.parent : null;
4111
4381
  hierarchyMap.set(element, parentElement);
4112
4382
  const normalizeFilePath = (filePath) => path.normalize(safeRealpath(path.resolve(filePath)));
4113
- const getParentComponentName = () => {
4114
- const normalizedFilePath2 = normalizeFilePath(context.filename);
4115
- return path.basename(normalizedFilePath2, ".vue");
4116
- };
4117
- const parentComponentName = getParentComponentName();
4118
4383
  const normalizedFilePath = normalizeFilePath(context.filename);
4119
- const relToViewsDir = path.relative(normalizedViewsDirAbs, normalizedFilePath);
4120
- const isView = !relToViewsDir.startsWith("..") && !path.isAbsolute(relToViewsDir);
4121
- const ensureDependencies = (parentComponentName2) => {
4122
- let dependencies2 = componentHierarchyMap.get(parentComponentName2);
4123
- if (!dependencies2) {
4124
- dependencies2 = {
4384
+ const parentComponentName = componentName;
4385
+ const dependencies = (() => {
4386
+ let deps = componentHierarchyMap.get(componentName);
4387
+ if (!deps) {
4388
+ const relToViewsDir = path.relative(normalizedViewsDirAbs, normalizedFilePath);
4389
+ const isView = !relToViewsDir.startsWith("..") && !path.isAbsolute(relToViewsDir);
4390
+ deps = {
4125
4391
  filePath: context.filename,
4126
4392
  childrenComponentSet: /* @__PURE__ */ new Set(),
4127
4393
  usedComponentSet: /* @__PURE__ */ new Set(),
@@ -4129,11 +4395,10 @@ function createTestIdTransform(componentName, componentHierarchyMap, nativeWrapp
4129
4395
  isView,
4130
4396
  methodsContent: ""
4131
4397
  };
4132
- componentHierarchyMap.set(parentComponentName2, dependencies2);
4398
+ componentHierarchyMap.set(componentName, deps);
4133
4399
  }
4134
- return dependencies2;
4135
- };
4136
- const dependencies = ensureDependencies(parentComponentName);
4400
+ return deps;
4401
+ })();
4137
4402
  const isComponentLikeTag = (tag) => {
4138
4403
  if (!tag) return false;
4139
4404
  const first = tag.charCodeAt(0);
@@ -4144,13 +4409,24 @@ function createTestIdTransform(componentName, componentHierarchyMap, nativeWrapp
4144
4409
  dependencies.usedComponentSet.add(element.tag);
4145
4410
  }
4146
4411
  if (!nativeWrappers[element.tag]) {
4147
- const inferred = tryInferNativeWrapperRoleFromSfc(element.tag);
4412
+ const inferred = tryInferNativeWrapperRoleFromSfc(element.tag, vueFilesPathMap);
4148
4413
  if (inferred?.role) {
4149
4414
  nativeWrappers[element.tag] = { role: inferred.role };
4415
+ } else if (element.tag.endsWith("Button") || element.tag === "AylaButton") {
4416
+ nativeWrappers[element.tag] = { role: "button" };
4417
+ } else if (element.tag === "DxDataGrid") {
4418
+ nativeWrappers[element.tag] = { role: "grid" };
4150
4419
  }
4151
4420
  }
4152
- const getBestAvailableKeyValue = () => getKeyDirectiveValue(element, context) || getSelfClosingForDirectiveKeyAttrValue(element) || getContainedInVForDirectiveKeyValue(context, element, hierarchyMap);
4153
- const bestKeyPlaceholder = getBestAvailableKeyValue();
4421
+ const getBestAvailableKeyValue = () => {
4422
+ const vForKey = getKeyDirectiveValue(element, context) || getSelfClosingForDirectiveKeyAttrValue(element) || getContainedInVForDirectiveKeyValue(context, element, hierarchyMap);
4423
+ if (vForKey) return vForKey;
4424
+ return getContainedInSlotDataKeyValue(element, hierarchyMap);
4425
+ };
4426
+ const bestKeyInferred = getBestAvailableKeyValue();
4427
+ const isSlotKey = bestKeyInferred && !bestKeyInferred.startsWith("${");
4428
+ const bestKeyPlaceholder = isSlotKey ? `\${${bestKeyInferred}}` : bestKeyInferred;
4429
+ const bestKeyVariable = isSlotKey ? bestKeyInferred : null;
4154
4430
  const keyValuesOverride = tryGetContainedInStaticVForSourceLiteralValues(context);
4155
4431
  const parentKey = !parentIsRoot && context?.parent ? context.parent : null;
4156
4432
  const conditional = getConditionalDirectiveInfo(element);
@@ -4235,6 +4511,7 @@ function createTestIdTransform(componentName, componentHierarchyMap, nativeWrapp
4235
4511
  nativeRole,
4236
4512
  preferredGeneratedValue: args.preferredGeneratedValue,
4237
4513
  bestKeyPlaceholder,
4514
+ bestKeyVariable,
4238
4515
  keyValuesOverride,
4239
4516
  entryOverrides: args.entryOverrides,
4240
4517
  semanticNameHint: args.semanticNameHint,
@@ -4263,16 +4540,18 @@ function createTestIdTransform(componentName, componentHierarchyMap, nativeWrapp
4263
4540
  });
4264
4541
  return;
4265
4542
  }
4543
+ const innerText = getInnerText(element) || null;
4266
4544
  const toDirective = nodeHasToDirective(element);
4267
4545
  if (toDirective) {
4268
4546
  const dataTestId = generateToDirectiveDataTestId(componentName, element, toDirective, context, hierarchyMap, nativeWrappers);
4269
4547
  const target = tryResolveToDirectiveTargetComponentName(toDirective);
4270
4548
  const routeNameHint = toDirectiveObjectFieldNameValue(toDirective);
4271
- const semanticNameHint2 = routeNameHint || target || conditionalHint || void 0;
4549
+ const existing = tryGetExistingElementDataTestId(element, testIdAttribute);
4550
+ const semanticNameHint2 = routeNameHint || target || void 0;
4551
+ const alternates = (target ? [] : [innerText, existing?.value, conditionalHint]).filter(Boolean);
4272
4552
  const rawTo = (toDirective.exp?.loc?.source ?? "").trim();
4273
4553
  const pomMergeKey = routeNameHint ? `to:name:${routeNameHint}` : rawTo ? `to:expr:${rawTo}` : void 0;
4274
- const existing = tryGetExistingElementDataTestId(element, testIdAttribute);
4275
- const preferredGeneratedValue = dataTestId ?? (existing ? staticAttributeValue(existing.value) : null);
4554
+ const preferredGeneratedValue = dataTestId ?? (existing ? existing.isDynamic ? templateAttributeValue(existing.template) : staticAttributeValue(existing.value) : null);
4276
4555
  if (!preferredGeneratedValue) {
4277
4556
  return;
4278
4557
  }
@@ -4280,6 +4559,7 @@ function createTestIdTransform(componentName, componentHierarchyMap, nativeWrapp
4280
4559
  preferredGeneratedValue,
4281
4560
  entryOverrides: target ? { targetPageObjectModelClass: target } : {},
4282
4561
  semanticNameHint: semanticNameHint2,
4562
+ semanticNameHintAlternates: alternates,
4283
4563
  pomMergeKey
4284
4564
  });
4285
4565
  return;
@@ -4294,7 +4574,6 @@ function createTestIdTransform(componentName, componentHierarchyMap, nativeWrapp
4294
4574
  });
4295
4575
  return;
4296
4576
  }
4297
- const innerText = getInnerText(element) || null;
4298
4577
  const clickDirective = tryGetClickDirective(element);
4299
4578
  if (clickDirective) {
4300
4579
  const clickSuffix = getComposedClickHandlerContent(element, context, innerText, clickDirective, {
@@ -4302,7 +4581,7 @@ function createTestIdTransform(componentName, componentHierarchyMap, nativeWrapp
4302
4581
  });
4303
4582
  const clickHint = trimLeadingSeparators(clickSuffix) || void 0;
4304
4583
  const idOrName = getIdOrName(element) || void 0;
4305
- const semanticNameHint2 = clickHint || idOrName || conditionalHint || void 0;
4584
+ const semanticNameHint2 = clickHint || idOrName || innerText || conditionalHint || void 0;
4306
4585
  const pomMergeKey = clickHint ? `click:hint:${clickHint}` : void 0;
4307
4586
  const testId = getClickDataTestId(clickSuffix);
4308
4587
  applyResolvedDataTestIdForElement({
@@ -4316,15 +4595,16 @@ function createTestIdTransform(componentName, componentHierarchyMap, nativeWrapp
4316
4595
  return;
4317
4596
  }
4318
4597
  const existingElementDataTestId = tryGetExistingElementDataTestId(element, testIdAttribute);
4319
- if (existingElementDataTestId?.value) {
4598
+ if (existingElementDataTestId) {
4320
4599
  const inferredRole = getNativeRoleFromTagSuffix().toLowerCase();
4321
- const isRecognizedInteractiveRole = inferredRole === "button" || inferredRole === "input" || inferredRole === "select" || inferredRole === "vselect" || inferredRole === "checkbox" || inferredRole === "toggle" || inferredRole === "radio";
4600
+ const isRecognizedInteractiveRole = inferredRole === "button" || inferredRole === "input" || inferredRole === "select" || inferredRole === "vselect" || inferredRole === "checkbox" || inferredRole === "toggle" || inferredRole === "radio" || inferredRole === "grid" || isComponentLikeTag(element.tag);
4322
4601
  if (!isRecognizedInteractiveRole) {
4323
4602
  return;
4324
4603
  }
4325
- const identifierHint = getIdOrName(element) || conditionalHint || void 0;
4604
+ const identifierHint = getIdOrName(element) || nodeHandlerAttributeValue(element) || innerText || existingElementDataTestId.value || conditionalHint || void 0;
4605
+ const preferredGeneratedValue = existingElementDataTestId.isDynamic ? templateAttributeValue(existingElementDataTestId.template) : staticAttributeValue(existingElementDataTestId.value);
4326
4606
  applyResolvedDataTestIdForElement({
4327
- preferredGeneratedValue: staticAttributeValue(existingElementDataTestId.value),
4607
+ preferredGeneratedValue,
4328
4608
  semanticNameHint: identifierHint
4329
4609
  });
4330
4610
  return;
@@ -4352,11 +4632,13 @@ function createDevProcessorPlugin(options) {
4352
4632
  nativeWrappers,
4353
4633
  excludedComponents,
4354
4634
  viewsDir,
4635
+ scanDirs,
4355
4636
  projectRootRef,
4356
4637
  normalizedBasePagePath,
4357
4638
  basePageClassPath,
4358
4639
  outDir,
4359
4640
  emitLanguages,
4641
+ csharp,
4360
4642
  generateFixtures,
4361
4643
  customPomAttachments,
4362
4644
  customPomDir,
@@ -4364,6 +4646,7 @@ function createDevProcessorPlugin(options) {
4364
4646
  testIdAttribute,
4365
4647
  routerAwarePoms,
4366
4648
  resolvedRouterEntry,
4649
+ routerType,
4367
4650
  loggerRef
4368
4651
  } = options;
4369
4652
  let scheduleVueFileRegen = null;
@@ -4377,7 +4660,11 @@ function createDevProcessorPlugin(options) {
4377
4660
  return;
4378
4661
  if (!ctx.file.endsWith(".vue"))
4379
4662
  return;
4380
- if (!ctx.file.includes(`${path.sep}src${path.sep}`))
4663
+ const isContainedInScanDirs = scanDirs.some((dir) => {
4664
+ const absDir = path.resolve(projectRootRef.current, dir);
4665
+ return ctx.file.startsWith(absDir + path.sep);
4666
+ });
4667
+ if (!isContainedInScanDirs)
4381
4668
  return;
4382
4669
  scheduleVueFileRegen(ctx.file, "hmr");
4383
4670
  },
@@ -4388,9 +4675,15 @@ function createDevProcessorPlugin(options) {
4388
4675
  setResolveToComponentNameFn(() => null);
4389
4676
  return;
4390
4677
  }
4391
- if (!resolvedRouterEntry)
4392
- throw new Error("[vue-pom-generator] router.entry is required when router introspection is enabled.");
4393
- const { routeNameMap, routePathMap } = await parseRouterFileFromCwd(resolvedRouterEntry);
4678
+ let result;
4679
+ if (routerType === "nuxt") {
4680
+ result = await introspectNuxtPages(projectRootRef.current);
4681
+ } else {
4682
+ if (!resolvedRouterEntry)
4683
+ throw new Error("[vue-pom-generator] router.entry is required when router introspection is enabled.");
4684
+ result = await parseRouterFileFromCwd(resolvedRouterEntry);
4685
+ }
4686
+ const { routeNameMap, routePathMap } = result;
4394
4687
  setRouteNameToComponentNameMap(routeNameMap);
4395
4688
  setResolveToComponentNameFn((to) => {
4396
4689
  if (typeof to === "string") {
@@ -4491,29 +4784,34 @@ function createDevProcessorPlugin(options) {
4491
4784
  return { componentName, ms: node_perf_hooks.performance.now() - started, compiled: true };
4492
4785
  };
4493
4786
  const fullRebuildSnapshotFromFilesystem = () => {
4494
- const srcDir = path.resolve(projectRootRef.current, "src");
4495
- if (!fs.existsSync(srcDir))
4496
- return;
4497
4787
  const t0 = node_perf_hooks.performance.now();
4498
4788
  snapshotHierarchy.clear();
4499
4789
  snapshotVuePathMap.clear();
4500
4790
  filePathToComponentName.clear();
4501
- const vueFiles = walkFilesRecursive(srcDir);
4502
- logInfo(`initial scan: found ${vueFiles.length} .vue files under src/`);
4791
+ let totalVueFiles = 0;
4503
4792
  let compiledCount = 0;
4504
- for (const file of vueFiles) {
4505
- const res = compileVueFileIntoSnapshot(file);
4506
- if (res.compiled)
4507
- compiledCount++;
4793
+ for (const dir of scanDirs) {
4794
+ const absDir = path.resolve(projectRootRef.current, dir);
4795
+ if (!fs.existsSync(absDir))
4796
+ continue;
4797
+ const vueFiles = walkFilesRecursive(absDir);
4798
+ totalVueFiles += vueFiles.length;
4799
+ for (const file of vueFiles) {
4800
+ const res = compileVueFileIntoSnapshot(file);
4801
+ if (res.compiled)
4802
+ compiledCount++;
4803
+ }
4508
4804
  }
4509
4805
  const t1 = node_perf_hooks.performance.now();
4510
- logInfo(`initial compile: ${compiledCount}/${vueFiles.length} files in ${formatMs(t1 - t0)} (components=${snapshotHierarchy.size})`);
4806
+ logInfo(`initial scan: found ${totalVueFiles} .vue files in ${scanDirs.join(", ")}`);
4807
+ logInfo(`initial compile: ${compiledCount}/${totalVueFiles} files in ${formatMs(t1 - t0)} (components=${snapshotHierarchy.size})`);
4511
4808
  };
4512
4809
  const generateAggregatedFromSnapshot = (reason) => {
4513
4810
  const t0 = node_perf_hooks.performance.now();
4514
4811
  generateFiles(snapshotHierarchy, snapshotVuePathMap, normalizedBasePagePath, {
4515
4812
  outDir,
4516
4813
  emitLanguages,
4814
+ csharp,
4517
4815
  generateFixtures,
4518
4816
  customPomAttachments,
4519
4817
  projectRoot: projectRootRef.current,
@@ -4521,7 +4819,8 @@ function createDevProcessorPlugin(options) {
4521
4819
  customPomImportAliases,
4522
4820
  testIdAttribute,
4523
4821
  vueRouterFluentChaining: routerAwarePoms,
4524
- routerEntry: resolvedRouterEntry
4822
+ routerEntry: resolvedRouterEntry,
4823
+ routerType
4525
4824
  });
4526
4825
  const t1 = node_perf_hooks.performance.now();
4527
4826
  logInfo(`generate(${reason}): components=${snapshotHierarchy.size} in ${formatMs(t1 - t0)}`);
@@ -4534,9 +4833,9 @@ function createDevProcessorPlugin(options) {
4534
4833
  const t1 = node_perf_hooks.performance.now();
4535
4834
  logInfo(`startup total: ${formatMs(t1 - t0)}`);
4536
4835
  })();
4537
- const watchedVueGlob = path.resolve(projectRootRef.current, "src", "**", "*.vue");
4836
+ const watchedVueGlobs = scanDirs.map((dir) => path.resolve(projectRootRef.current, dir, "**", "*.vue"));
4538
4837
  const watchedPluginGlob = path.resolve(projectRootRef.current, "vite-plugins", "vue-pom-generator", "**", "*.ts");
4539
- server.watcher.add([watchedVueGlob, watchedPluginGlob, basePageClassPath]);
4838
+ server.watcher.add([...watchedVueGlobs, watchedPluginGlob, basePageClassPath]);
4540
4839
  let timer = null;
4541
4840
  let maxWaitTimer = null;
4542
4841
  const pendingChangedVueFiles = /* @__PURE__ */ new Set();
@@ -4635,7 +4934,13 @@ function createDevProcessorPlugin(options) {
4635
4934
  server.watcher.on("add", (p) => {
4636
4935
  if (typeof p !== "string")
4637
4936
  return;
4638
- if (!p.endsWith(".vue") || !p.includes(`${path.sep}src${path.sep}`))
4937
+ if (!p.endsWith(".vue"))
4938
+ return;
4939
+ const isContainedInScanDirs = scanDirs.some((dir) => {
4940
+ const absDir = path.resolve(projectRootRef.current, dir);
4941
+ return p.startsWith(absDir + path.sep);
4942
+ });
4943
+ if (!isContainedInScanDirs)
4639
4944
  return;
4640
4945
  void (async () => {
4641
4946
  await initialBuildPromise;
@@ -4646,7 +4951,13 @@ function createDevProcessorPlugin(options) {
4646
4951
  server.watcher.on("unlink", (p) => {
4647
4952
  if (typeof p !== "string")
4648
4953
  return;
4649
- if (!p.endsWith(".vue") || !p.includes(`${path.sep}src${path.sep}`))
4954
+ if (!p.endsWith(".vue"))
4955
+ return;
4956
+ const isContainedInScanDirs = scanDirs.some((dir) => {
4957
+ const absDir = path.resolve(projectRootRef.current, dir);
4958
+ return p.startsWith(absDir + path.sep);
4959
+ });
4960
+ if (!isContainedInScanDirs)
4650
4961
  return;
4651
4962
  void (async () => {
4652
4963
  await initialBuildPromise;
@@ -4687,10 +4998,13 @@ function createSupportPlugins(options) {
4687
4998
  nativeWrappers,
4688
4999
  excludedComponents,
4689
5000
  viewsDir,
5001
+ scanDirs,
4690
5002
  outDir,
4691
5003
  emitLanguages,
5004
+ csharp,
4692
5005
  routerAwarePoms,
4693
5006
  routerEntry,
5007
+ routerType,
4694
5008
  generateFixtures,
4695
5009
  customPomAttachments,
4696
5010
  projectRootRef,
@@ -4703,6 +5017,8 @@ function createSupportPlugins(options) {
4703
5017
  const resolveRouterEntry2 = () => {
4704
5018
  if (!routerAwarePoms)
4705
5019
  return void 0;
5020
+ if (routerType === "nuxt")
5021
+ return void 0;
4706
5022
  if (!routerEntry)
4707
5023
  throw new Error("[vue-pom-generator] router.entry is required when router introspection is enabled.");
4708
5024
  return path.isAbsolute(routerEntry) ? routerEntry : path.resolve(projectRootRef.current, routerEntry);
@@ -4724,6 +5040,7 @@ function createSupportPlugins(options) {
4724
5040
  normalizedBasePagePath,
4725
5041
  outDir,
4726
5042
  emitLanguages,
5043
+ csharp,
4727
5044
  generateFixtures,
4728
5045
  customPomAttachments,
4729
5046
  projectRootRef,
@@ -4731,6 +5048,7 @@ function createSupportPlugins(options) {
4731
5048
  customPomImportAliases,
4732
5049
  testIdAttribute,
4733
5050
  routerAwarePoms,
5051
+ routerType,
4734
5052
  resolvedRouterEntry,
4735
5053
  loggerRef
4736
5054
  });
@@ -4738,6 +5056,7 @@ function createSupportPlugins(options) {
4738
5056
  nativeWrappers,
4739
5057
  excludedComponents,
4740
5058
  viewsDir,
5059
+ scanDirs,
4741
5060
  projectRootRef,
4742
5061
  normalizedBasePagePath,
4743
5062
  basePageClassPath,
@@ -4749,6 +5068,7 @@ function createSupportPlugins(options) {
4749
5068
  customPomImportAliases,
4750
5069
  testIdAttribute,
4751
5070
  routerAwarePoms,
5071
+ routerType,
4752
5072
  resolvedRouterEntry,
4753
5073
  loggerRef
4754
5074
  });
@@ -4850,7 +5170,7 @@ function tryCreateElementMetadata(args) {
4850
5170
  };
4851
5171
  return metadata;
4852
5172
  }
4853
- function extractMetadataFromAST(ast, componentName, metadataMap, semanticNameMap, testIdAttribute) {
5173
+ function extractMetadataAfterTransform(ast, componentName, elementMetadata, semanticNameMap, testIdAttribute) {
4854
5174
  const componentMetadata = /* @__PURE__ */ new Map();
4855
5175
  function traverseNode(node) {
4856
5176
  if (node.type === compilerCore.NodeTypes.ELEMENT) {
@@ -4889,27 +5209,8 @@ function extractMetadataFromAST(ast, componentName, metadataMap, semanticNameMap
4889
5209
  }
4890
5210
  traverseNode(ast);
4891
5211
  if (componentMetadata.size > 0) {
4892
- metadataMap.set(componentName, componentMetadata);
4893
- }
4894
- }
4895
- function compileWithMetadataExtractionManual(source, options, componentName, metadataMap, semanticNameMap, testIdAttribute = "data-testid") {
4896
- const normalizedTestIdAttribute = (testIdAttribute ?? "data-testid").trim() || "data-testid";
4897
- const result = compilerDom.compile(
4898
- source,
4899
- shared.extend({}, options, {
4900
- nodeTransforms: [
4901
- ...options.nodeTransforms || [],
4902
- (node, _context) => {
4903
- if (node.type !== compilerCore.NodeTypes.ROOT)
4904
- return;
4905
- return () => {
4906
- extractMetadataFromAST(node, componentName, metadataMap, semanticNameMap, normalizedTestIdAttribute);
4907
- };
4908
- }
4909
- ]
4910
- })
4911
- );
4912
- return result;
5212
+ elementMetadata.set(componentName, componentMetadata);
5213
+ }
4913
5214
  }
4914
5215
  function createVuePluginWithTestIds(options) {
4915
5216
  const {
@@ -4924,27 +5225,91 @@ function createVuePluginWithTestIds(options) {
4924
5225
  excludedComponents,
4925
5226
  getViewsDirAbs,
4926
5227
  testIdAttribute,
4927
- loggerRef
5228
+ loggerRef,
5229
+ scanDirs = ["src"],
5230
+ getProjectRoot
4928
5231
  } = options;
5232
+ const getComponentNameFromPath = (filename) => {
5233
+ const cleanPath = filename.includes("?") ? filename.substring(0, filename.indexOf("?")) : filename;
5234
+ const projectRoot = getProjectRoot();
5235
+ const absFilename = path.isAbsolute(cleanPath) ? cleanPath : path.resolve(projectRoot, cleanPath);
5236
+ const viewsDirAbs = getViewsDirAbs();
5237
+ const roots = [viewsDirAbs, ...scanDirs.map((d) => path.resolve(projectRoot, d))];
5238
+ for (const dir of scanDirs) {
5239
+ const absDir = path.resolve(projectRoot, dir);
5240
+ try {
5241
+ const pagesDir = path.join(absDir, "pages");
5242
+ if (fs.existsSync(pagesDir)) {
5243
+ roots.push(pagesDir);
5244
+ }
5245
+ const componentsDir = path.join(absDir, "components");
5246
+ if (fs.existsSync(componentsDir)) {
5247
+ roots.push(componentsDir);
5248
+ }
5249
+ } catch {
5250
+ }
5251
+ }
5252
+ const potentialRoots = Array.from(new Set(roots.map((r) => path.normalize(r)))).sort((a, b) => b.length - a.length);
5253
+ let componentName = "";
5254
+ for (const root of potentialRoots) {
5255
+ if (absFilename.startsWith(root + path.sep) || absFilename === root) {
5256
+ const rel = path.relative(root, absFilename);
5257
+ const parsed = path.parse(rel);
5258
+ const segments = path.join(parsed.dir, parsed.name);
5259
+ componentName = toPascalCase(segments);
5260
+ break;
5261
+ }
5262
+ }
5263
+ if (!componentName) {
5264
+ const parsed = path.parse(absFilename);
5265
+ componentName = toPascalCase(parsed.name);
5266
+ }
5267
+ return componentName;
5268
+ };
5269
+ const isFileInScope = (filename) => {
5270
+ if (!filename)
5271
+ return false;
5272
+ const cleanPath = filename.includes("?") ? filename.substring(0, filename.indexOf("?")) : filename;
5273
+ const projectRoot = getProjectRoot();
5274
+ const absFilename = path.isAbsolute(cleanPath) ? cleanPath : path.resolve(projectRoot, cleanPath);
5275
+ if (absFilename.includes(`${path.sep}node_modules${path.sep}`) || absFilename.includes("/node_modules/"))
5276
+ return false;
5277
+ const viewsDirAbs = getViewsDirAbs();
5278
+ if (absFilename.startsWith(viewsDirAbs + path.sep) || absFilename === viewsDirAbs)
5279
+ return true;
5280
+ const rootsToTry = [projectRoot, process.cwd()];
5281
+ const matched = scanDirs.some((dir) => {
5282
+ return rootsToTry.some((root) => {
5283
+ const absDir = path.resolve(root, dir);
5284
+ if (absFilename.startsWith(absDir + path.sep) || absFilename === absDir)
5285
+ return true;
5286
+ if (dir.startsWith("app/") && root.endsWith("/app")) {
5287
+ const relativeDir = dir.substring(4);
5288
+ const absDirAlt = path.resolve(root, relativeDir);
5289
+ return absFilename.startsWith(absDirAlt + path.sep) || absFilename === absDirAlt;
5290
+ }
5291
+ return false;
5292
+ });
5293
+ });
5294
+ if (cleanPath.endsWith(".vue") && !matched) {
5295
+ loggerRef.current.debug(`[isFileInScope] REJECTED: ${absFilename} (Clean: ${cleanPath})`);
5296
+ }
5297
+ return matched;
5298
+ };
4929
5299
  const userTemplate = vueOptions?.template ?? {};
4930
5300
  const userCompilerOptions = userTemplate.compilerOptions ?? {};
4931
5301
  const userNodeTransforms = userCompilerOptions.nodeTransforms ?? [];
4932
5302
  const perFileTransform = /* @__PURE__ */ new Map();
4933
- const templateCompilerOptions = {
4934
- ...userCompilerOptions,
4935
- // Ensures compiler-core runs `transformExpression` (in non-browser builds),
4936
- // which parses directive expressions via @babel/parser and attaches `exp.ast`.
4937
- // This improves reliability for AST-based consumers (like our data-testid generator).
4938
- prefixIdentifiers: true,
4939
- nodeTransforms: [
5303
+ const getNodeTransforms = (filename, componentNameOverride) => {
5304
+ const cleanPath = filename.includes("?") ? filename.substring(0, filename.indexOf("?")) : filename;
5305
+ const viewsDirAbs = getViewsDirAbs();
5306
+ const componentName = componentNameOverride || getComponentNameFromPath(cleanPath);
5307
+ return [
4940
5308
  ...userNodeTransforms,
4941
5309
  (node, context) => {
4942
- if (!context.filename)
4943
- return;
4944
- const componentName = path.basename(context.filename, ".vue");
4945
5310
  if (node.type === compilerCore.NodeTypes.ROOT) {
4946
5311
  componentHierarchyMap.delete(componentName);
4947
- vueFilesPathMap.set(componentName, context.filename);
5312
+ vueFilesPathMap.set(componentName, filename);
4948
5313
  perFileTransform.set(
4949
5314
  componentName,
4950
5315
  createTestIdTransform(
@@ -4952,113 +5317,99 @@ function createVuePluginWithTestIds(options) {
4952
5317
  componentHierarchyMap,
4953
5318
  nativeWrappers,
4954
5319
  excludedComponents,
4955
- getViewsDirAbs(),
5320
+ viewsDirAbs,
4956
5321
  {
4957
5322
  existingIdBehavior,
4958
5323
  testIdAttribute,
4959
5324
  nameCollisionBehavior,
4960
- warn: (message) => loggerRef.current.warn(message)
5325
+ warn: (message) => loggerRef.current.warn(message),
5326
+ vueFilesPathMap
4961
5327
  }
4962
5328
  )
4963
5329
  );
5330
+ return () => {
5331
+ extractMetadataAfterTransform(
5332
+ node,
5333
+ componentName,
5334
+ elementMetadata,
5335
+ semanticNameMap,
5336
+ testIdAttribute
5337
+ );
5338
+ };
4964
5339
  }
4965
5340
  let transform = perFileTransform.get(componentName);
4966
5341
  if (!transform) {
4967
5342
  componentHierarchyMap.delete(componentName);
4968
- vueFilesPathMap.set(componentName, context.filename);
5343
+ vueFilesPathMap.set(componentName, filename);
4969
5344
  transform = createTestIdTransform(
4970
5345
  componentName,
4971
5346
  componentHierarchyMap,
4972
5347
  nativeWrappers,
4973
5348
  excludedComponents,
4974
- getViewsDirAbs(),
5349
+ viewsDirAbs,
4975
5350
  {
4976
5351
  existingIdBehavior,
4977
5352
  testIdAttribute,
4978
5353
  nameCollisionBehavior,
4979
- warn: (message) => loggerRef.current.warn(message)
5354
+ warn: (message) => loggerRef.current.warn(message),
5355
+ vueFilesPathMap
4980
5356
  }
4981
5357
  );
4982
5358
  perFileTransform.set(componentName, transform);
4983
5359
  }
4984
5360
  return transform(node, context);
4985
5361
  }
5362
+ ];
5363
+ };
5364
+ const templateCompilerOptions = {
5365
+ ...userCompilerOptions,
5366
+ prefixIdentifiers: true,
5367
+ nodeTransforms: [
5368
+ // This will be used if the user uses the returned vue plugin.
5369
+ // We'll populate it via the hook below for better compatibility.
4986
5370
  ]
4987
5371
  };
5372
+ const metadataCollectorPlugin = {
5373
+ name: "vue-pom-generator-metadata-collector",
5374
+ enforce: "pre",
5375
+ async transform(code, id) {
5376
+ const cleanPath = id.includes("?") ? id.substring(0, id.indexOf("?")) : id;
5377
+ if (!cleanPath.endsWith(".vue") || !isFileInScope(id)) {
5378
+ return null;
5379
+ }
5380
+ if (id !== cleanPath) {
5381
+ return null;
5382
+ }
5383
+ const componentName = getComponentNameFromPath(cleanPath);
5384
+ loggerRef.current.debug(`Collecting metadata for ${cleanPath} (component: ${componentName})`);
5385
+ try {
5386
+ const { parse } = await import("@vue/compiler-sfc");
5387
+ const compilerDom2 = await import("@vue/compiler-dom");
5388
+ const compile = compilerDom2.compile;
5389
+ const { descriptor } = parse(code, { filename: cleanPath });
5390
+ if (descriptor.template) {
5391
+ compile(descriptor.template.content, {
5392
+ ...userCompilerOptions,
5393
+ filename: cleanPath,
5394
+ nodeTransforms: getNodeTransforms(cleanPath, componentName)
5395
+ });
5396
+ loggerRef.current.debug(`Metadata collected for ${cleanPath}`);
5397
+ }
5398
+ } catch (e) {
5399
+ loggerRef.current.warn(`Metadata collection failed for ${cleanPath}: ${e}`);
5400
+ }
5401
+ return null;
5402
+ }
5403
+ };
4988
5404
  const template = {
4989
5405
  ...userTemplate,
4990
- compiler: {
4991
- // Preserve the full compiler-dom module behavior (directiveTransforms, nodeTransforms, etc.).
4992
- // We only override `compile` to run our metadata extraction after transforms.
4993
- ...compilerDom__namespace,
4994
- compile(source, compilerOptions) {
4995
- const componentName = compilerOptions.filename ? path.basename(compilerOptions.filename, ".vue") : "Unknown";
4996
- return compileWithMetadataExtractionManual(
4997
- source,
4998
- compilerOptions,
4999
- componentName,
5000
- elementMetadata,
5001
- semanticNameMap,
5002
- testIdAttribute
5003
- );
5004
- }
5005
- },
5006
5406
  compilerOptions: templateCompilerOptions
5007
5407
  };
5008
- return vue({
5408
+ const internalVuePlugin = vue({
5009
5409
  ...vueOptions,
5010
5410
  template
5011
5411
  });
5012
- }
5013
- const VUE_POM_GENERATOR_LOG_PREFIX = "[vue-pom-generator]";
5014
- function normalize(message) {
5015
- const trimmed = (message ?? "").trim();
5016
- if (!trimmed)
5017
- return VUE_POM_GENERATOR_LOG_PREFIX;
5018
- if (trimmed.startsWith(VUE_POM_GENERATOR_LOG_PREFIX))
5019
- return trimmed;
5020
- return `${VUE_POM_GENERATOR_LOG_PREFIX} ${trimmed}`;
5021
- }
5022
- function createLogger(options) {
5023
- const { verbosity, viteLogger } = options;
5024
- const sinkInfo = (msg) => {
5025
- if (viteLogger) {
5026
- viteLogger.info(normalize(msg));
5027
- return;
5028
- }
5029
- console.log(normalize(msg));
5030
- };
5031
- const sinkWarn = (msg) => {
5032
- if (viteLogger) {
5033
- viteLogger.warn(normalize(msg));
5034
- return;
5035
- }
5036
- console.warn(normalize(msg));
5037
- };
5038
- const sinkDebug = (msg) => {
5039
- if (viteLogger) {
5040
- viteLogger.info(normalize(msg));
5041
- return;
5042
- }
5043
- console.log(normalize(msg));
5044
- };
5045
- return {
5046
- info(message) {
5047
- if (verbosity === "silent")
5048
- return;
5049
- sinkInfo(message);
5050
- },
5051
- debug(message) {
5052
- if (verbosity !== "debug")
5053
- return;
5054
- sinkDebug(message);
5055
- },
5056
- warn(message) {
5057
- if (verbosity === "silent")
5058
- return;
5059
- sinkWarn(message);
5060
- }
5061
- };
5412
+ return { metadataCollectorPlugin, internalVuePlugin };
5062
5413
  }
5063
5414
  function assertNonEmptyString(value, name) {
5064
5415
  if (!value || !value.trim()) {
@@ -5073,9 +5424,10 @@ function createVuePomGeneratorPlugins(options = {}) {
5073
5424
  const generationSetting = options.generation;
5074
5425
  const generationOptions = generationSetting === false ? null : generationSetting ?? {};
5075
5426
  const generationEnabled = generationOptions !== null;
5076
- const verbosity = options.logging?.verbosity ?? "info";
5427
+ const verbosity = options.logging?.verbosity ?? "warn";
5077
5428
  const vueOptions = options.vueOptions;
5078
5429
  const viewsDir = injection.viewsDir ?? "src/views";
5430
+ const scanDirs = injection.scanDirs ?? ["src"];
5079
5431
  const nativeWrappers = injection.nativeWrappers ?? {};
5080
5432
  const excludedComponents = injection.excludeComponents ?? [];
5081
5433
  const testIdAttribute = (injection.attribute ?? "data-testid").trim() || "data-testid";
@@ -5084,6 +5436,8 @@ function createVuePomGeneratorPlugins(options = {}) {
5084
5436
  const emitLanguages = generationOptions?.emit && generationOptions.emit.length ? generationOptions.emit : ["ts"];
5085
5437
  const nameCollisionBehavior = generationOptions?.nameCollisionBehavior ?? "suffix";
5086
5438
  const routerEntry = generationOptions?.router?.entry;
5439
+ const routerType = generationOptions?.router?.type ?? "vue-router";
5440
+ const csharp = generationOptions?.csharp;
5087
5441
  const generateFixtures = generationOptions?.playwright?.fixtures;
5088
5442
  const customPoms = generationOptions?.playwright?.customPoms;
5089
5443
  const resolvedCustomPomAttachments = customPoms?.attachments ?? [];
@@ -5104,11 +5458,12 @@ function createVuePomGeneratorPlugins(options = {}) {
5104
5458
  assertNonEmptyString(viewsDir, "[vue-pom-generator] injection.viewsDir");
5105
5459
  if (generationEnabled) {
5106
5460
  assertNonEmptyString(outDir, "[vue-pom-generator] generation.outDir");
5107
- if (generationOptions?.router) {
5461
+ if (generationOptions?.router && routerType === "vue-router") {
5108
5462
  assertNonEmptyString(routerEntry, "[vue-pom-generator] generation.router.entry");
5109
5463
  }
5110
5464
  }
5111
- loggerRef.current.debug(`projectRoot=${projectRootRef.current}`);
5465
+ loggerRef.current.info(`projectRoot=${projectRootRef.current}`);
5466
+ loggerRef.current.info(`Active plugins: ${config.plugins.map((p) => p.name).filter((n) => n.includes("vue-pom")).join(", ")}`);
5112
5467
  }
5113
5468
  };
5114
5469
  const getViewsDirAbs = () => resolveFromProjectRoot(projectRootRef.current, viewsDir);
@@ -5117,7 +5472,7 @@ function createVuePomGeneratorPlugins(options = {}) {
5117
5472
  const semanticNameMap = /* @__PURE__ */ new Map();
5118
5473
  const componentHierarchyMap = /* @__PURE__ */ new Map();
5119
5474
  const vueFilesPathMap = /* @__PURE__ */ new Map();
5120
- const vuePlugin = createVuePluginWithTestIds({
5475
+ const { metadataCollectorPlugin, internalVuePlugin } = createVuePluginWithTestIds({
5121
5476
  vueOptions,
5122
5477
  existingIdBehavior,
5123
5478
  nameCollisionBehavior,
@@ -5129,13 +5484,11 @@ function createVuePomGeneratorPlugins(options = {}) {
5129
5484
  excludedComponents,
5130
5485
  getViewsDirAbs,
5131
5486
  testIdAttribute,
5132
- loggerRef
5487
+ loggerRef,
5488
+ scanDirs,
5489
+ getProjectRoot: () => projectRootRef.current
5133
5490
  });
5134
- if (!generationEnabled) {
5135
- const virtualModules = createTestIdsVirtualModulesPlugin(componentTestIds);
5136
- return [configPlugin, vuePlugin, virtualModules];
5137
- }
5138
- const routerAwarePoms = typeof routerEntry === "string" && routerEntry.trim().length > 0;
5491
+ const routerAwarePoms = typeof routerEntry === "string" && routerEntry.trim().length > 0 || routerType === "nuxt";
5139
5492
  const supportPlugins = createSupportPlugins({
5140
5493
  componentTestIds,
5141
5494
  componentHierarchyMap,
@@ -5143,8 +5496,10 @@ function createVuePomGeneratorPlugins(options = {}) {
5143
5496
  nativeWrappers,
5144
5497
  excludedComponents,
5145
5498
  viewsDir,
5499
+ scanDirs,
5146
5500
  outDir,
5147
5501
  emitLanguages,
5502
+ csharp,
5148
5503
  routerAwarePoms,
5149
5504
  routerEntry,
5150
5505
  generateFixtures,
@@ -5154,9 +5509,29 @@ function createVuePomGeneratorPlugins(options = {}) {
5154
5509
  customPomDir: resolvedCustomPomDir,
5155
5510
  customPomImportAliases: resolvedCustomPomImportAliases,
5156
5511
  testIdAttribute,
5157
- loggerRef
5512
+ loggerRef,
5513
+ routerType
5158
5514
  });
5159
- return [configPlugin, vuePlugin, ...supportPlugins];
5515
+ const isNuxt = routerType === "nuxt";
5516
+ if (isNuxt) {
5517
+ loggerRef.current.info("Nuxt environment detected. Skipping internal @vitejs/plugin-vue to avoid conflicts.");
5518
+ }
5519
+ const resultPlugins = [
5520
+ configPlugin,
5521
+ metadataCollectorPlugin,
5522
+ ...isNuxt ? [] : [internalVuePlugin],
5523
+ ...supportPlugins
5524
+ ];
5525
+ if (!generationEnabled) {
5526
+ const virtualModules = createTestIdsVirtualModulesPlugin(componentTestIds);
5527
+ return [
5528
+ configPlugin,
5529
+ metadataCollectorPlugin,
5530
+ ...isNuxt ? [] : [internalVuePlugin],
5531
+ virtualModules
5532
+ ];
5533
+ }
5534
+ return resultPlugins;
5160
5535
  }
5161
5536
  exports.createVuePomGeneratorPlugins = createVuePomGeneratorPlugins;
5162
5537
  exports.default = createVuePomGeneratorPlugins;