@immense/vue-pom-generator 1.0.29 → 1.0.31

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.mjs CHANGED
@@ -4,7 +4,7 @@ import { pathToFileURL, fileURLToPath } from "node:url";
4
4
  import fs from "node:fs";
5
5
  import { JSDOM } from "jsdom";
6
6
  import { NodeTypes, stringifyExpression, ConstantTypes, createSimpleExpression } from "@vue/compiler-core";
7
- import { isArrayExpression, isStringLiteral, isTemplateLiteral, isAssignmentExpression, isIdentifier, isMemberExpression, isCallExpression, isExpressionStatement, isOptionalMemberExpression, isObjectExpression, isFile, isArrowFunctionExpression, isBlockStatement, isOptionalCallExpression, isLogicalExpression, isConditionalExpression, isSequenceExpression, isObjectProperty, isProgram, VISITOR_KEYS } from "@babel/types";
7
+ import { isArrayExpression, isStringLiteral, isTemplateLiteral, isAssignmentExpression, isIdentifier, isMemberExpression, isCallExpression, isExpressionStatement, isArrowFunctionExpression, isOptionalMemberExpression, isObjectExpression, isFile, isBlockStatement, isOptionalCallExpression, isLogicalExpression, isConditionalExpression, isSequenceExpression, isAssignmentPattern, isRestElement, isObjectPattern, isObjectProperty, isProgram, VISITOR_KEYS, isBooleanLiteral, isNumericLiteral, isNullLiteral } from "@babel/types";
8
8
  import { parseExpression, parse } from "@babel/parser";
9
9
  import { performance } from "node:perf_hooks";
10
10
  import * as compilerDom from "@vue/compiler-dom";
@@ -565,6 +565,124 @@ function getTemplateSlotScope(node) {
565
565
  }
566
566
  return null;
567
567
  }
568
+ function isSimpleScopeIdentifier(value) {
569
+ return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(value);
570
+ }
571
+ function buildSlotScopeFallbackKeyExpression(identifier) {
572
+ return `${identifier}.key ?? ${identifier}.data?.id ?? ${identifier}.id ?? ${identifier}.value ?? ${identifier}`;
573
+ }
574
+ function tryGetBindingIdentifierName(node) {
575
+ if (!node) {
576
+ return null;
577
+ }
578
+ if (isIdentifier(node)) {
579
+ return node.name;
580
+ }
581
+ if (isAssignmentPattern(node)) {
582
+ return tryGetBindingIdentifierName(node.left);
583
+ }
584
+ if (isRestElement(node)) {
585
+ return tryGetBindingIdentifierName(node.argument);
586
+ }
587
+ return null;
588
+ }
589
+ function getSlotScopeObjectPropertyKeyName(node) {
590
+ if (isIdentifier(node)) {
591
+ return node.name;
592
+ }
593
+ if (isStringLiteral(node)) {
594
+ return node.value;
595
+ }
596
+ return null;
597
+ }
598
+ function tryGetSlotScopeKeyCandidate(node) {
599
+ if (!node) {
600
+ return null;
601
+ }
602
+ if (isIdentifier(node)) {
603
+ return {
604
+ priority: 2,
605
+ expression: buildSlotScopeFallbackKeyExpression(node.name)
606
+ };
607
+ }
608
+ if (isAssignmentPattern(node)) {
609
+ return tryGetSlotScopeKeyCandidate(node.left);
610
+ }
611
+ if (isRestElement(node)) {
612
+ return tryGetSlotScopeKeyCandidate(node.argument);
613
+ }
614
+ if (!isObjectPattern(node)) {
615
+ return null;
616
+ }
617
+ let best = null;
618
+ for (const property of node.properties) {
619
+ let candidate = null;
620
+ if (isRestElement(property)) {
621
+ candidate = tryGetSlotScopeKeyCandidate(property.argument);
622
+ if (candidate) {
623
+ candidate = { ...candidate, priority: Math.max(candidate.priority, 2) };
624
+ }
625
+ } else if (isObjectProperty(property)) {
626
+ const keyName = getSlotScopeObjectPropertyKeyName(property.key);
627
+ const bindingName = tryGetBindingIdentifierName(property.value) ?? tryGetBindingIdentifierName(property.key);
628
+ if (bindingName) {
629
+ if (keyName === "key" || bindingName === "key") {
630
+ candidate = {
631
+ priority: 0,
632
+ expression: bindingName
633
+ };
634
+ } else {
635
+ candidate = {
636
+ priority: keyName === "data" ? 1 : 2,
637
+ expression: buildSlotScopeFallbackKeyExpression(bindingName)
638
+ };
639
+ }
640
+ }
641
+ }
642
+ if (candidate && (!best || candidate.priority < best.priority)) {
643
+ best = candidate;
644
+ }
645
+ }
646
+ return best;
647
+ }
648
+ function tryGetTemplateSlotScopeKeyExpression(scope) {
649
+ const trimmed = scope.trim();
650
+ if (!trimmed) {
651
+ return null;
652
+ }
653
+ if (isSimpleScopeIdentifier(trimmed)) {
654
+ return buildSlotScopeFallbackKeyExpression(trimmed);
655
+ }
656
+ try {
657
+ const parsed = parse(`(${trimmed}) => {}`, {
658
+ sourceType: "module",
659
+ plugins: ["typescript"]
660
+ });
661
+ const statement = parsed.program.body[0];
662
+ if (statement && isExpressionStatement(statement) && isArrowFunctionExpression(statement.expression)) {
663
+ return tryGetSlotScopeKeyCandidate(statement.expression.params[0])?.expression ?? null;
664
+ }
665
+ } catch {
666
+ }
667
+ if (trimmed.startsWith("{") && trimmed.endsWith("}")) {
668
+ const inner = trimmed.slice(1, -1).trim();
669
+ let cutIdx = -1;
670
+ const commaIdx = inner.indexOf(",");
671
+ const colonIdx = inner.indexOf(":");
672
+ if (commaIdx !== -1 && colonIdx !== -1) {
673
+ cutIdx = Math.min(commaIdx, colonIdx);
674
+ } else if (commaIdx !== -1) {
675
+ cutIdx = commaIdx;
676
+ } else if (colonIdx !== -1) {
677
+ cutIdx = colonIdx;
678
+ }
679
+ const first = (cutIdx === -1 ? inner : inner.slice(0, cutIdx)).trim();
680
+ if (first && isSimpleScopeIdentifier(first)) {
681
+ return buildSlotScopeFallbackKeyExpression(first);
682
+ }
683
+ }
684
+ return trimmed;
685
+ }
568
686
  function nodeHasToDirective(node) {
569
687
  const toDirective = findDirectiveByName(node, "bind", "to");
570
688
  if (toDirective?.exp) {
@@ -640,25 +758,7 @@ function getContainedInSlotDataKeyValue(node, hierarchyMap2) {
640
758
  if (parent.type === NodeTypes.ELEMENT && parent.tag === "template") {
641
759
  const scope = getTemplateSlotScope(parent);
642
760
  if (scope) {
643
- let key = scope.trim();
644
- if (key.startsWith("{") && key.endsWith("}")) {
645
- const inner = key.slice(1, -1).trim();
646
- let cutIdx = -1;
647
- const commaIdx = inner.indexOf(",");
648
- const colonIdx = inner.indexOf(":");
649
- if (commaIdx !== -1 && colonIdx !== -1) {
650
- cutIdx = Math.min(commaIdx, colonIdx);
651
- } else if (commaIdx !== -1) {
652
- cutIdx = commaIdx;
653
- } else if (colonIdx !== -1) {
654
- cutIdx = colonIdx;
655
- }
656
- const first = (cutIdx === -1 ? inner : inner.slice(0, cutIdx)).trim();
657
- if (first) {
658
- key = first;
659
- }
660
- }
661
- return key;
761
+ return tryGetTemplateSlotScopeKeyExpression(scope);
662
762
  }
663
763
  }
664
764
  parent = getParent(hierarchyMap2, parent);
@@ -1309,11 +1409,11 @@ function getStableClickHandlerNameFromExpression(exp) {
1309
1409
  return extractNameFromCallee(callee);
1310
1410
  }
1311
1411
  if (isAssignmentExpression(exp)) {
1312
- const left = exp.left;
1313
- if (isIdentifier(left))
1314
- return left.name;
1315
- if (isMemberExpression(left) || isOptionalMemberExpression(left))
1316
- return extractMemberPropertyName(left);
1412
+ const left = getAssignmentTargetNameFromBabel(exp.left);
1413
+ if (left) {
1414
+ const right = getStableAssignmentValueSuffixFromBabel(exp.right);
1415
+ return `Set${toPascalCase(left)}${right}`;
1416
+ }
1317
1417
  return "";
1318
1418
  }
1319
1419
  if (isOptionalMemberExpression(exp)) {
@@ -1388,6 +1488,71 @@ function extractMemberPropertyName(member) {
1388
1488
  }
1389
1489
  return "";
1390
1490
  }
1491
+ function getLastIdentifierFromMemberChainBabel(node) {
1492
+ if (!node) {
1493
+ return "";
1494
+ }
1495
+ if (isIdentifier(node)) {
1496
+ return node.name;
1497
+ }
1498
+ if (isMemberExpression(node) || isOptionalMemberExpression(node)) {
1499
+ if (!node.computed) {
1500
+ if (isIdentifier(node.property)) {
1501
+ return node.property.name;
1502
+ }
1503
+ } else if (isStringLiteral(node.property)) {
1504
+ return node.property.value;
1505
+ }
1506
+ }
1507
+ return "";
1508
+ }
1509
+ function getAssignmentTargetNameFromBabel(node) {
1510
+ if (!node) {
1511
+ return "";
1512
+ }
1513
+ if (isIdentifier(node)) {
1514
+ return node.name;
1515
+ }
1516
+ if (isMemberExpression(node) || isOptionalMemberExpression(node)) {
1517
+ if (!node.computed && isIdentifier(node.property) && node.property.name === "value") {
1518
+ return getLastIdentifierFromMemberChainBabel(node.object);
1519
+ }
1520
+ return getLastIdentifierFromMemberChainBabel(node);
1521
+ }
1522
+ return "";
1523
+ }
1524
+ function getStableAssignmentValueSuffixFromBabel(node) {
1525
+ if (!node) {
1526
+ return "";
1527
+ }
1528
+ if (isBooleanLiteral(node)) {
1529
+ return node.value ? "True" : "False";
1530
+ }
1531
+ if (isNumericLiteral(node)) {
1532
+ return `Value${String(node.value)}`;
1533
+ }
1534
+ if (isNullLiteral(node)) {
1535
+ return "Null";
1536
+ }
1537
+ if (isStringLiteral(node)) {
1538
+ const cleaned = (node.value ?? "").trim();
1539
+ return cleaned ? toPascalCase(cleaned.slice(0, 24)) : "";
1540
+ }
1541
+ if (isTemplateLiteral(node) && node.expressions.length === 0) {
1542
+ const value = node.quasis.map((q) => q.value?.cooked ?? "").join("").trim();
1543
+ return value ? toPascalCase(value.slice(0, 24)) : "";
1544
+ }
1545
+ if (isMemberExpression(node) || isOptionalMemberExpression(node)) {
1546
+ const name = getLastIdentifierFromMemberChainBabel(node);
1547
+ return name ? toPascalCase(name.slice(0, 24)) : "";
1548
+ }
1549
+ if (isIdentifier(node)) {
1550
+ const firstChar = node.name.charAt(0);
1551
+ const isUpperAlpha = firstChar !== "" && firstChar === firstChar.toUpperCase() && firstChar !== firstChar.toLowerCase();
1552
+ return isUpperAlpha ? toPascalCase(node.name.slice(0, 24)) : "";
1553
+ }
1554
+ return "";
1555
+ }
1391
1556
  function isCompilerGeneratedReferenceRoot(name) {
1392
1557
  return name.startsWith("_") || name.startsWith("$");
1393
1558
  }
@@ -3199,6 +3364,10 @@ function escapeGitAttributesPattern(value) {
3199
3364
  }
3200
3365
  return output;
3201
3366
  }
3367
+ function pathUsesGeneratedHeuristic(filePath) {
3368
+ const normalized = path.normalize(filePath);
3369
+ return normalized.split(path.sep).includes("__generated__");
3370
+ }
3202
3371
  function buildManagedGitAttributesBlock(entries) {
3203
3372
  return [
3204
3373
  GENERATED_GITATTRIBUTES_BLOCK_START,
@@ -3246,6 +3415,9 @@ function buildGeneratedGitAttributesFiles(generatedFilePaths) {
3246
3415
  if (path.basename(resolvedFilePath) === ".gitattributes") {
3247
3416
  continue;
3248
3417
  }
3418
+ if (pathUsesGeneratedHeuristic(resolvedFilePath)) {
3419
+ continue;
3420
+ }
3249
3421
  const dir = path.dirname(resolvedFilePath);
3250
3422
  const entry = `${escapeGitAttributesPattern(path.basename(resolvedFilePath))} linguist-generated`;
3251
3423
  const entries = entriesByDir.get(dir) ?? /* @__PURE__ */ new Set();
@@ -4322,15 +4494,57 @@ function createBuildProcessorPlugin(options) {
4322
4494
  const TESTID_CLICK_EVENT_NAME = "__testid_event__";
4323
4495
  const TESTID_CLICK_EVENT_STRICT_FLAG = "__testid_click_event_strict__";
4324
4496
  const CLICK_EVENT_NAME = TESTID_CLICK_EVENT_NAME;
4325
- const inferredNativeWrapperConfigByTag = /* @__PURE__ */ new Map();
4326
- const inferredSfcPathByTag = /* @__PURE__ */ new Map();
4327
- let indexedVueSfcPathsByBasename = null;
4497
+ const inferredNativeWrapperConfigByLookup = /* @__PURE__ */ new Map();
4498
+ const inferredSfcPathByLookup = /* @__PURE__ */ new Map();
4499
+ const indexedVueSfcPathsByRoots = /* @__PURE__ */ new Map();
4328
4500
  function toKebabCaseTag(tag) {
4329
- return tag.replace(/([a-z0-9])([A-Z])/g, "$1-$2").replace(/[_.\s]+/g, "-").toLowerCase();
4501
+ let result = "";
4502
+ let previousWasSeparator = false;
4503
+ for (let i = 0; i < tag.length; i += 1) {
4504
+ const ch = tag[i];
4505
+ const code = ch.charCodeAt(0);
4506
+ if (ch === "_" || ch === "-" || ch === "." || ch === " " || ch === " " || ch === "\n" || ch === "\r") {
4507
+ if (result && !previousWasSeparator) {
4508
+ result += "-";
4509
+ }
4510
+ previousWasSeparator = true;
4511
+ continue;
4512
+ }
4513
+ const previous = i > 0 ? tag[i - 1] : "";
4514
+ const previousCode = previous ? previous.charCodeAt(0) : 0;
4515
+ const hasPrevious = i > 0;
4516
+ const shouldInsertSeparator = hasPrevious && isAsciiUppercaseLetterCode(code) && (isAsciiLetterCode(previousCode) || isAsciiDigitCode(previousCode)) && !previousWasSeparator;
4517
+ if (shouldInsertSeparator) {
4518
+ result += "-";
4519
+ }
4520
+ result += ch.toLowerCase();
4521
+ previousWasSeparator = false;
4522
+ }
4523
+ return result;
4330
4524
  }
4331
- function buildVueSfcPathIndex() {
4332
- if (indexedVueSfcPathsByBasename) {
4333
- return indexedVueSfcPathsByBasename;
4525
+ function normalizeSearchRoots(wrapperSearchRoots) {
4526
+ const normalized = /* @__PURE__ */ new Set();
4527
+ for (const root of wrapperSearchRoots) {
4528
+ const resolved = path.resolve(root);
4529
+ try {
4530
+ if (!fs.existsSync(resolved) || !fs.statSync(resolved).isDirectory()) {
4531
+ continue;
4532
+ }
4533
+ normalized.add(path.normalize(fs.realpathSync(resolved)));
4534
+ } catch {
4535
+ continue;
4536
+ }
4537
+ }
4538
+ return [...normalized];
4539
+ }
4540
+ function buildSearchRootsKey(searchRoots) {
4541
+ return searchRoots.join("\n");
4542
+ }
4543
+ function buildVueSfcPathIndex(searchRoots) {
4544
+ const indexKey = buildSearchRootsKey(searchRoots);
4545
+ const existingIndex = indexedVueSfcPathsByRoots.get(indexKey);
4546
+ if (existingIndex) {
4547
+ return existingIndex;
4334
4548
  }
4335
4549
  const index = /* @__PURE__ */ new Map();
4336
4550
  const ignoredDirNames = /* @__PURE__ */ new Set([
@@ -4348,22 +4562,6 @@ function buildVueSfcPathIndex() {
4348
4562
  "out",
4349
4563
  "tmp"
4350
4564
  ]);
4351
- const cwd = process.cwd();
4352
- const parent = path.resolve(cwd, "..");
4353
- const searchRoots = /* @__PURE__ */ new Set();
4354
- const enqueueIfDirectory = (dirPath) => {
4355
- try {
4356
- if (fs.existsSync(dirPath) && fs.statSync(dirPath).isDirectory()) {
4357
- searchRoots.add(dirPath);
4358
- }
4359
- } catch {
4360
- }
4361
- };
4362
- for (const root of [cwd, parent]) {
4363
- for (const rel of ["src", "components", "app", "shared", "packages", "libs"]) {
4364
- enqueueIfDirectory(path.resolve(root, rel));
4365
- }
4366
- }
4367
4565
  const stack = [...searchRoots];
4368
4566
  const seenDirs = /* @__PURE__ */ new Set();
4369
4567
  while (stack.length > 0) {
@@ -4396,52 +4594,58 @@ function buildVueSfcPathIndex() {
4396
4594
  index.set(entry.name, matches);
4397
4595
  }
4398
4596
  }
4399
- indexedVueSfcPathsByBasename = index;
4597
+ indexedVueSfcPathsByRoots.set(indexKey, index);
4400
4598
  return index;
4401
4599
  }
4402
- function tryResolveSfcPathForTag(tag, vueFilesPathMap) {
4403
- if (inferredSfcPathByTag.has(tag)) {
4404
- return inferredSfcPathByTag.get(tag) ?? null;
4405
- }
4600
+ function tryResolveSfcPathForTag(tag, vueFilesPathMap, wrapperSearchRoots = []) {
4406
4601
  const registeredPath = vueFilesPathMap?.get(tag);
4602
+ const normalizedSearchRoots = normalizeSearchRoots(wrapperSearchRoots);
4603
+ const lookupKey = `${tag}
4604
+ ${registeredPath ?? ""}
4605
+ ${buildSearchRootsKey(normalizedSearchRoots)}`;
4606
+ if (inferredSfcPathByLookup.has(lookupKey)) {
4607
+ return inferredSfcPathByLookup.get(lookupKey) ?? null;
4608
+ }
4407
4609
  const candidateNames = [`${tag}.vue`, `${toKebabCaseTag(tag)}.vue`];
4408
4610
  const directCandidates = [
4409
4611
  registeredPath ? path.resolve(process.cwd(), registeredPath) : null,
4410
- ...candidateNames.flatMap((fileName) => [
4411
- path.resolve(process.cwd(), "src/components", fileName),
4412
- path.resolve(process.cwd(), "components", fileName),
4413
- path.resolve(process.cwd(), "app/components", fileName),
4414
- path.resolve(process.cwd(), "shared/ui/src/components", fileName),
4415
- path.resolve(process.cwd(), "..", "shared/ui/src/components", fileName)
4416
- ])
4612
+ ...normalizedSearchRoots.flatMap((root) => candidateNames.map((fileName) => path.join(root, fileName)))
4417
4613
  ].filter((value) => !!value);
4418
4614
  const directMatch = directCandidates.find((candidatePath) => fs.existsSync(candidatePath));
4419
4615
  if (directMatch) {
4420
- inferredSfcPathByTag.set(tag, directMatch);
4616
+ inferredSfcPathByLookup.set(lookupKey, directMatch);
4421
4617
  return directMatch;
4422
4618
  }
4423
- const index = buildVueSfcPathIndex();
4619
+ if (normalizedSearchRoots.length === 0) {
4620
+ inferredSfcPathByLookup.set(lookupKey, null);
4621
+ return null;
4622
+ }
4623
+ const index = buildVueSfcPathIndex(normalizedSearchRoots);
4424
4624
  const scorePath = (candidatePath) => {
4425
- let score = candidatePath.length;
4426
- if (candidatePath.includes(`${path.sep}src${path.sep}components${path.sep}`)) {
4427
- score -= 50;
4428
- }
4429
- if (candidatePath.includes(`${path.sep}shared${path.sep}`)) {
4430
- score -= 10;
4431
- }
4432
- return score;
4625
+ const rootIndex = normalizedSearchRoots.findIndex((root) => {
4626
+ return candidatePath === root || candidatePath.startsWith(root + path.sep);
4627
+ });
4628
+ const effectiveRootIndex = rootIndex === -1 ? Number.MAX_SAFE_INTEGER : rootIndex;
4629
+ const relativeLength = rootIndex === -1 ? candidatePath.length : path.relative(normalizedSearchRoots[rootIndex], candidatePath).length;
4630
+ return [effectiveRootIndex, relativeLength, candidatePath];
4433
4631
  };
4632
+ let bestMatch = null;
4633
+ let bestScore = null;
4434
4634
  for (const fileName of candidateNames) {
4435
4635
  const matches = index.get(fileName);
4436
4636
  if (!matches?.length) {
4437
4637
  continue;
4438
4638
  }
4439
- const bestMatch = matches.slice().sort((a, b) => scorePath(a) - scorePath(b))[0] ?? null;
4440
- inferredSfcPathByTag.set(tag, bestMatch);
4441
- return bestMatch;
4639
+ for (const match of matches) {
4640
+ const score = scorePath(match);
4641
+ if (!bestScore || score[0] < bestScore[0] || score[0] === bestScore[0] && score[1] < bestScore[1] || score[0] === bestScore[0] && score[1] === bestScore[1] && score[2] < bestScore[2]) {
4642
+ bestScore = score;
4643
+ bestMatch = match;
4644
+ }
4645
+ }
4442
4646
  }
4443
- inferredSfcPathByTag.set(tag, null);
4444
- return null;
4647
+ inferredSfcPathByLookup.set(lookupKey, bestMatch);
4648
+ return bestMatch;
4445
4649
  }
4446
4650
  function trimLeadingSeparators(value) {
4447
4651
  if (!value) {
@@ -4556,7 +4760,7 @@ function tryExtractStableHintFromConditionalExpressionSource(source) {
4556
4760
  return null;
4557
4761
  }
4558
4762
  }
4559
- function tryInferNativeWrapperRoleFromSfc(tag, vueFilesPathMap, seenTags = /* @__PURE__ */ new Set()) {
4763
+ function tryInferNativeWrapperRoleFromSfc(tag, vueFilesPathMap, wrapperSearchRoots = [], seenTags = /* @__PURE__ */ new Set()) {
4560
4764
  const first = tag.charCodeAt(0);
4561
4765
  const isUpper = isAsciiUppercaseLetterCode(first);
4562
4766
  if (!isUpper)
@@ -4564,19 +4768,23 @@ function tryInferNativeWrapperRoleFromSfc(tag, vueFilesPathMap, seenTags = /* @_
4564
4768
  if (seenTags.has(tag)) {
4565
4769
  return null;
4566
4770
  }
4567
- const cached = inferredNativeWrapperConfigByTag.get(tag);
4771
+ const normalizedSearchRoots = normalizeSearchRoots(wrapperSearchRoots);
4772
+ const cacheKey = `${tag}
4773
+ ${vueFilesPathMap?.get(tag) ?? ""}
4774
+ ${buildSearchRootsKey(normalizedSearchRoots)}`;
4775
+ const cached = inferredNativeWrapperConfigByLookup.get(cacheKey);
4568
4776
  if (cached)
4569
4777
  return cached.role ? cached : null;
4570
- const filePath = tryResolveSfcPathForTag(tag, vueFilesPathMap);
4778
+ const filePath = tryResolveSfcPathForTag(tag, vueFilesPathMap, normalizedSearchRoots);
4571
4779
  if (!filePath) {
4572
- inferredNativeWrapperConfigByTag.set(tag, { role: "" });
4780
+ inferredNativeWrapperConfigByLookup.set(cacheKey, { role: "" });
4573
4781
  return null;
4574
4782
  }
4575
4783
  let source = "";
4576
4784
  try {
4577
4785
  source = fs.readFileSync(filePath, "utf8");
4578
4786
  } catch {
4579
- inferredNativeWrapperConfigByTag.set(tag, { role: "" });
4787
+ inferredNativeWrapperConfigByLookup.set(cacheKey, { role: "" });
4580
4788
  return null;
4581
4789
  }
4582
4790
  let template = "";
@@ -4584,11 +4792,11 @@ function tryInferNativeWrapperRoleFromSfc(tag, vueFilesPathMap, seenTags = /* @_
4584
4792
  const { descriptor } = parse$1(source, { filename: filePath });
4585
4793
  template = descriptor.template?.content ?? "";
4586
4794
  } catch {
4587
- inferredNativeWrapperConfigByTag.set(tag, { role: "" });
4795
+ inferredNativeWrapperConfigByLookup.set(cacheKey, { role: "" });
4588
4796
  return null;
4589
4797
  }
4590
4798
  if (!template.trim()) {
4591
- inferredNativeWrapperConfigByTag.set(tag, { role: "" });
4799
+ inferredNativeWrapperConfigByLookup.set(cacheKey, { role: "" });
4592
4800
  return null;
4593
4801
  }
4594
4802
  try {
@@ -4607,6 +4815,7 @@ function tryInferNativeWrapperRoleFromSfc(tag, vueFilesPathMap, seenTags = /* @_
4607
4815
  });
4608
4816
  return (typeAttr?.value?.content ?? "").toLowerCase();
4609
4817
  };
4818
+ let inferRoleFromNode;
4610
4819
  const inferRoleFromElement = (element) => {
4611
4820
  const elementTag = (element.tag || "").toLowerCase();
4612
4821
  const inputType = getStaticTypeAttribute(element);
@@ -4626,7 +4835,7 @@ function tryInferNativeWrapperRoleFromSfc(tag, vueFilesPathMap, seenTags = /* @_
4626
4835
  if (elementTag === "button" || elementTag === "ubutton")
4627
4836
  return { role: "button" };
4628
4837
  if (isComponentLikeTag(element.tag) && element.tag !== tag) {
4629
- const nested = tryInferNativeWrapperRoleFromSfc(element.tag, vueFilesPathMap, nextSeen);
4838
+ const nested = tryInferNativeWrapperRoleFromSfc(element.tag, vueFilesPathMap, normalizedSearchRoots, nextSeen);
4630
4839
  if (nested)
4631
4840
  return nested;
4632
4841
  }
@@ -4637,7 +4846,7 @@ function tryInferNativeWrapperRoleFromSfc(tag, vueFilesPathMap, seenTags = /* @_
4637
4846
  }
4638
4847
  return null;
4639
4848
  };
4640
- const inferRoleFromNode = (node) => {
4849
+ inferRoleFromNode = (node) => {
4641
4850
  if (!node || typeof node !== "object")
4642
4851
  return null;
4643
4852
  if (node.type === NodeTypes.ELEMENT) {
@@ -4662,14 +4871,14 @@ function tryInferNativeWrapperRoleFromSfc(tag, vueFilesPathMap, seenTags = /* @_
4662
4871
  };
4663
4872
  const inferred = inferRoleFromNode(ast);
4664
4873
  if (inferred) {
4665
- inferredNativeWrapperConfigByTag.set(tag, inferred);
4874
+ inferredNativeWrapperConfigByLookup.set(cacheKey, inferred);
4666
4875
  return inferred;
4667
4876
  }
4668
4877
  } catch {
4669
- inferredNativeWrapperConfigByTag.set(tag, { role: "" });
4878
+ inferredNativeWrapperConfigByLookup.set(cacheKey, { role: "" });
4670
4879
  return null;
4671
4880
  }
4672
- inferredNativeWrapperConfigByTag.set(tag, { role: "" });
4881
+ inferredNativeWrapperConfigByLookup.set(cacheKey, { role: "" });
4673
4882
  return null;
4674
4883
  }
4675
4884
  function tryWrapClickDirectiveForTestEvents(element, testIdAttribute) {
@@ -4844,6 +5053,7 @@ function createTestIdTransform(componentName, componentHierarchyMap, nativeWrapp
4844
5053
  const nameCollisionBehavior = options.nameCollisionBehavior ?? "suffix";
4845
5054
  const warn = options.warn;
4846
5055
  const vueFilesPathMap = options.vueFilesPathMap;
5056
+ const wrapperSearchRoots = options.wrapperSearchRoots ?? [];
4847
5057
  const safeRealpath = (p) => {
4848
5058
  try {
4849
5059
  return fs.existsSync(p) ? fs.realpathSync(p) : p;
@@ -4969,7 +5179,7 @@ function createTestIdTransform(componentName, componentHierarchyMap, nativeWrapp
4969
5179
  dependencies.usedComponentSet.add(element.tag);
4970
5180
  }
4971
5181
  if (!nativeWrappers[element.tag]) {
4972
- const inferred = tryInferNativeWrapperRoleFromSfc(element.tag, vueFilesPathMap);
5182
+ const inferred = tryInferNativeWrapperRoleFromSfc(element.tag, vueFilesPathMap, wrapperSearchRoots);
4973
5183
  if (inferred?.role) {
4974
5184
  nativeWrappers[element.tag] = { role: inferred.role, inferred: true };
4975
5185
  } else if (element.tag.endsWith("Button") || element.tag === "AylaButton") {
@@ -5260,6 +5470,7 @@ function createDevProcessorPlugin(options) {
5260
5470
  excludedComponents,
5261
5471
  viewsDir,
5262
5472
  scanDirs,
5473
+ getWrapperSearchRoots,
5263
5474
  projectRootRef,
5264
5475
  normalizedBasePagePath,
5265
5476
  basePageClassPath,
@@ -5410,7 +5621,11 @@ function createDevProcessorPlugin(options) {
5410
5621
  nativeWrappers,
5411
5622
  excludedComponents,
5412
5623
  getViewsDirAbs(),
5413
- { existingIdBehavior: "preserve", testIdAttribute }
5624
+ {
5625
+ existingIdBehavior: "preserve",
5626
+ testIdAttribute,
5627
+ wrapperSearchRoots: getWrapperSearchRoots()
5628
+ }
5414
5629
  )
5415
5630
  ]
5416
5631
  });
@@ -5635,6 +5850,7 @@ function createSupportPlugins(options) {
5635
5850
  excludedComponents,
5636
5851
  viewsDir,
5637
5852
  scanDirs,
5853
+ getWrapperSearchRoots,
5638
5854
  outDir,
5639
5855
  emitLanguages,
5640
5856
  csharp,
@@ -5697,6 +5913,7 @@ function createSupportPlugins(options) {
5697
5913
  excludedComponents,
5698
5914
  viewsDir,
5699
5915
  scanDirs,
5916
+ getWrapperSearchRoots,
5700
5917
  projectRootRef,
5701
5918
  normalizedBasePagePath,
5702
5919
  basePageClassPath,
@@ -5869,6 +6086,7 @@ function createVuePluginWithTestIds(options) {
5869
6086
  testIdAttribute,
5870
6087
  loggerRef,
5871
6088
  scanDirs = ["src"],
6089
+ getWrapperSearchRoots,
5872
6090
  getProjectRoot
5873
6091
  } = options;
5874
6092
  const getComponentNameFromPath = (filename) => {
@@ -5937,7 +6155,8 @@ function createVuePluginWithTestIds(options) {
5937
6155
  testIdAttribute,
5938
6156
  nameCollisionBehavior,
5939
6157
  warn: (message) => loggerRef.current.warn(message),
5940
- vueFilesPathMap
6158
+ vueFilesPathMap,
6159
+ wrapperSearchRoots: getWrapperSearchRoots()
5941
6160
  }
5942
6161
  )
5943
6162
  );
@@ -5966,7 +6185,8 @@ function createVuePluginWithTestIds(options) {
5966
6185
  testIdAttribute,
5967
6186
  nameCollisionBehavior,
5968
6187
  warn: (message) => loggerRef.current.warn(message),
5969
- vueFilesPathMap
6188
+ vueFilesPathMap,
6189
+ wrapperSearchRoots: getWrapperSearchRoots()
5970
6190
  }
5971
6191
  );
5972
6192
  perFileTransform.set(componentName, transform);
@@ -6034,6 +6254,13 @@ function assertNonEmptyString(value, name) {
6034
6254
  throw new Error(`${name} must be a non-empty string.`);
6035
6255
  }
6036
6256
  }
6257
+ function assertNonEmptyStringArray(value, name) {
6258
+ if (!value)
6259
+ return;
6260
+ for (const [index, entry] of value.entries()) {
6261
+ assertNonEmptyString(entry, `${name}[${index}]`);
6262
+ }
6263
+ }
6037
6264
  function assertRouterModuleShims(value, name) {
6038
6265
  if (!value)
6039
6266
  return;
@@ -6096,11 +6323,12 @@ function createVuePomGeneratorPlugins(options = {}) {
6096
6323
  const vueOptions = options.vueOptions;
6097
6324
  const viewsDir = injection.viewsDir ?? "src/views";
6098
6325
  const scanDirs = injection.scanDirs ?? ["src"];
6326
+ const wrapperSearchRoots = injection.wrapperSearchRoots ?? [];
6099
6327
  const nativeWrappers = injection.nativeWrappers ?? {};
6100
6328
  const excludedComponents = injection.excludeComponents ?? [];
6101
6329
  const testIdAttribute = (injection.attribute ?? "data-testid").trim() || "data-testid";
6102
6330
  const existingIdBehavior = injection.existingIdBehavior ?? "preserve";
6103
- const outDir = (generationOptions?.outDir ?? "tests/playwright/generated").trim();
6331
+ const outDir = (generationOptions?.outDir ?? "tests/playwright/__generated__").trim();
6104
6332
  const emitLanguages = generationOptions?.emit && generationOptions.emit.length ? generationOptions.emit : ["ts"];
6105
6333
  const nameCollisionBehavior = generationOptions?.nameCollisionBehavior ?? "suffix";
6106
6334
  const routerEntry = generationOptions?.router?.entry;
@@ -6126,6 +6354,7 @@ function createVuePomGeneratorPlugins(options = {}) {
6126
6354
  loggerRef.current = createLogger({ verbosity, viteLogger: config.logger });
6127
6355
  assertNonEmptyString(testIdAttribute, "[vue-pom-generator] injection.attribute");
6128
6356
  assertNonEmptyString(viewsDir, "[vue-pom-generator] injection.viewsDir");
6357
+ assertNonEmptyStringArray(wrapperSearchRoots, "[vue-pom-generator] injection.wrapperSearchRoots");
6129
6358
  if (generationEnabled) {
6130
6359
  assertNonEmptyString(outDir, "[vue-pom-generator] generation.outDir");
6131
6360
  assertRouterModuleShims(routerModuleShims, "[vue-pom-generator] generation.router.moduleShims");
@@ -6138,6 +6367,7 @@ function createVuePomGeneratorPlugins(options = {}) {
6138
6367
  }
6139
6368
  };
6140
6369
  const getViewsDirAbs = () => resolveFromProjectRoot(projectRootRef.current, viewsDir);
6370
+ const getWrapperSearchRootsAbs = () => wrapperSearchRoots.map((root) => resolveFromProjectRoot(projectRootRef.current, root));
6141
6371
  const componentTestIds = /* @__PURE__ */ new Map();
6142
6372
  const elementMetadata = /* @__PURE__ */ new Map();
6143
6373
  const semanticNameMap = /* @__PURE__ */ new Map();
@@ -6157,6 +6387,7 @@ function createVuePomGeneratorPlugins(options = {}) {
6157
6387
  testIdAttribute,
6158
6388
  loggerRef,
6159
6389
  scanDirs,
6390
+ getWrapperSearchRoots: getWrapperSearchRootsAbs,
6160
6391
  getProjectRoot: () => projectRootRef.current
6161
6392
  });
6162
6393
  const routerAwarePoms = typeof routerEntry === "string" && routerEntry.trim().length > 0 || routerType === "nuxt";
@@ -6168,6 +6399,7 @@ function createVuePomGeneratorPlugins(options = {}) {
6168
6399
  excludedComponents,
6169
6400
  viewsDir,
6170
6401
  scanDirs,
6402
+ getWrapperSearchRoots: getWrapperSearchRootsAbs,
6171
6403
  outDir,
6172
6404
  emitLanguages,
6173
6405
  csharp,