@immense/vue-pom-generator 1.0.66 → 1.0.68

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/AGENTS.md +21 -0
  2. package/README.md +1 -0
  3. package/RELEASE_NOTES.md +16 -9
  4. package/class-generation/index.ts +16 -5
  5. package/dist/class-generation/index.d.ts.map +1 -1
  6. package/dist/compiler-metadata-utils.d.ts.map +1 -1
  7. package/dist/index.cjs +309 -97
  8. package/dist/index.cjs.map +1 -1
  9. package/dist/index.d.ts +1 -1
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.mjs +309 -97
  12. package/dist/index.mjs.map +1 -1
  13. package/dist/manifest-generator.d.ts +2 -0
  14. package/dist/manifest-generator.d.ts.map +1 -1
  15. package/dist/metadata-collector.d.ts +2 -0
  16. package/dist/metadata-collector.d.ts.map +1 -1
  17. package/dist/plugin/create-vue-pom-generator-plugins.d.ts.map +1 -1
  18. package/dist/plugin/internal/build-plugin.d.ts.map +1 -0
  19. package/dist/plugin/internal/dev-plugin.d.ts.map +1 -0
  20. package/dist/plugin/internal/virtual-modules.d.ts.map +1 -0
  21. package/dist/plugin/{support-plugins.d.ts → internal-plugins.d.ts} +14 -3
  22. package/dist/plugin/internal-plugins.d.ts.map +1 -0
  23. package/dist/plugin/path-utils.d.ts +12 -0
  24. package/dist/plugin/path-utils.d.ts.map +1 -1
  25. package/dist/plugin/runtime/annotator/client.d.ts +67 -0
  26. package/dist/plugin/runtime/annotator/client.d.ts.map +1 -0
  27. package/dist/plugin/runtime/annotator/format.d.ts +13 -0
  28. package/dist/plugin/runtime/annotator/format.d.ts.map +1 -0
  29. package/dist/plugin/runtime/annotator/plugin.d.ts +12 -0
  30. package/dist/plugin/runtime/annotator/plugin.d.ts.map +1 -0
  31. package/dist/plugin/runtime/annotator/styles.d.ts +3 -0
  32. package/dist/plugin/runtime/annotator/styles.d.ts.map +1 -0
  33. package/dist/plugin/runtime/annotator/vue-detector.d.ts +12 -0
  34. package/dist/plugin/runtime/annotator/vue-detector.d.ts.map +1 -0
  35. package/dist/plugin/types.d.ts +58 -3
  36. package/dist/plugin/types.d.ts.map +1 -1
  37. package/dist/plugin/vue-plugin.d.ts +4 -0
  38. package/dist/plugin/vue-plugin.d.ts.map +1 -1
  39. package/dist/tests/path-utils-scope.test.d.ts +2 -0
  40. package/dist/tests/path-utils-scope.test.d.ts.map +1 -0
  41. package/dist/transform.d.ts +4 -0
  42. package/dist/transform.d.ts.map +1 -1
  43. package/dist/utils.d.ts +19 -0
  44. package/dist/utils.d.ts.map +1 -1
  45. package/package.json +3 -1
  46. package/plugin/runtime/annotator/client.ts +1005 -0
  47. package/plugin/runtime/annotator/format.ts +76 -0
  48. package/plugin/runtime/annotator/plugin.ts +109 -0
  49. package/plugin/runtime/annotator/styles.ts +379 -0
  50. package/plugin/runtime/annotator/vue-detector.ts +216 -0
  51. package/dist/plugin/support/build-plugin.d.ts.map +0 -1
  52. package/dist/plugin/support/dev-plugin.d.ts.map +0 -1
  53. package/dist/plugin/support/virtual-modules.d.ts.map +0 -1
  54. package/dist/plugin/support-plugins.d.ts.map +0 -1
  55. /package/dist/plugin/{support → internal}/build-plugin.d.ts +0 -0
  56. /package/dist/plugin/{support → internal}/dev-plugin.d.ts +0 -0
  57. /package/dist/plugin/{support → internal}/virtual-modules.d.ts +0 -0
package/dist/index.mjs CHANGED
@@ -645,7 +645,7 @@ function createInlineParameter(name, options = {}) {
645
645
  initializer: options.initializer
646
646
  };
647
647
  }
648
- function removeByKeySegment$1(value) {
648
+ function removeByKeySegment(value) {
649
649
  const idx = value.lastIndexOf("ByKey");
650
650
  if (idx < 0) {
651
651
  return value;
@@ -849,13 +849,13 @@ function generateGetElementByDataTestId(componentName, methodName, nativeRole, s
849
849
  const roleSuffix = upperFirst$1(nativeRole || "Element");
850
850
  const baseName = upperFirst$1(methodName);
851
851
  const numericSuffix = baseName.startsWith(roleSuffix) ? baseName.slice(roleSuffix.length) : "";
852
- const hasRoleSuffix2 = baseName.endsWith(roleSuffix) || baseName.startsWith(roleSuffix) && isAllDigits(numericSuffix);
853
- const propertyName = hasRoleSuffix2 ? `${baseName}` : `${baseName}${roleSuffix}`;
852
+ const hasRoleSuffix = baseName.endsWith(roleSuffix) || baseName.startsWith(roleSuffix) && isAllDigits(numericSuffix);
853
+ const propertyName = hasRoleSuffix ? `${baseName}` : `${baseName}${roleSuffix}`;
854
854
  const selectorParams = orderPomPatternParameters(parameters, [selector]);
855
855
  const indexedVariable = getIndexedPomPatternVariable(selector);
856
856
  if (indexedVariable) {
857
857
  const keyType = getPomParameter(selectorParams, indexedVariable)?.typeExpression || "string";
858
- const keyedPropertyName = getterNameOverride ?? removeByKeySegment$1(propertyName);
858
+ const keyedPropertyName = getterNameOverride ?? removeByKeySegment(propertyName);
859
859
  return [
860
860
  createClassGetter({
861
861
  name: keyedPropertyName,
@@ -2894,7 +2894,7 @@ Fix: either (1) include ${bestKeyPreservePlaceholder} in your :${attrLabel} temp
2894
2894
  }
2895
2895
  return value.slice(0, idx) + value.slice(idx + "ByKey".length);
2896
2896
  };
2897
- const hasRoleSuffix2 = (baseName, roleSuffix) => {
2897
+ const hasRoleSuffix = (baseName, roleSuffix) => {
2898
2898
  if (baseName.endsWith(roleSuffix)) {
2899
2899
  return true;
2900
2900
  }
@@ -2904,13 +2904,13 @@ Fix: either (1) include ${bestKeyPreservePlaceholder} in your :${attrLabel} temp
2904
2904
  const getPrimaryGetterName = (primaryMethodName) => {
2905
2905
  const roleSuffix = upperFirst(normalizedRole || "Element");
2906
2906
  const baseName = upperFirst(primaryMethodName);
2907
- const propertyName = hasRoleSuffix2(baseName, roleSuffix) ? baseName : `${baseName}${roleSuffix}`;
2907
+ const propertyName = hasRoleSuffix(baseName, roleSuffix) ? baseName : `${baseName}${roleSuffix}`;
2908
2908
  return selectorIsParameterized ? removeByKeySegment2(propertyName) : propertyName;
2909
2909
  };
2910
2910
  const getPrimaryGetterNameCandidates = (primaryMethodName) => {
2911
2911
  const roleSuffix = upperFirst(normalizedRole || "Element");
2912
2912
  const baseName = upperFirst(primaryMethodName);
2913
- const propertyName = hasRoleSuffix2(baseName, roleSuffix) ? baseName : `${baseName}${roleSuffix}`;
2913
+ const propertyName = hasRoleSuffix(baseName, roleSuffix) ? baseName : `${baseName}${roleSuffix}`;
2914
2914
  if (!selectorIsParameterized) {
2915
2915
  return { primary: propertyName };
2916
2916
  }
@@ -3037,7 +3037,7 @@ Fix: either (1) include ${bestKeyPreservePlaceholder} in your :${attrLabel} temp
3037
3037
  if (conflicts && nameCollisionBehavior === "error") {
3038
3038
  const roleSuffix = upperFirst(normalizedRole || "Element");
3039
3039
  const baseNameUpper = upperFirst(baseWithSuffix);
3040
- if (!hasRoleSuffix2(baseNameUpper, roleSuffix)) {
3040
+ if (!hasRoleSuffix(baseNameUpper, roleSuffix)) {
3041
3041
  const baseWithRoleSuffix = `${baseWithSuffix}${roleSuffix}`;
3042
3042
  const candidateWithRoleSuffix = selectorIsParameterized ? `${baseWithRoleSuffix}ByKey` : baseWithRoleSuffix;
3043
3043
  const actionNameWithRoleSuffix = getPrimaryActionMethodName(candidateWithRoleSuffix);
@@ -3147,12 +3147,32 @@ Fix: make the element identifiable (e.g. add id/name/inner text or use a more sp
3147
3147
  alternateSelectors: void 0,
3148
3148
  mergeKey: args.pomMergeKey,
3149
3149
  parameters: normalizedParameters,
3150
- keyValuesOverride: args.keyValuesOverride ?? null
3150
+ keyValuesOverride: args.keyValuesOverride ?? null,
3151
+ generatedActionName: getPrimaryActionMethodName(methodName),
3152
+ generatedPropertyName: getterNameOverride ?? getPrimaryGetterName(methodName)
3151
3153
  // emitPrimary defaults to true; special cases (including merge) may set it to false below.
3152
3154
  };
3153
3155
  if (mergedIntoExisting && dataTestIdEntry.pom) {
3154
3156
  dataTestIdEntry.pom.emitPrimary = false;
3155
3157
  }
3158
+ if (addHtmlAttribute && args.annotatorMetadata && dataTestIdEntry.pom) {
3159
+ const filename = args.contextFilename?.trim();
3160
+ if (!filename) {
3161
+ throw new Error(`[vue-pom-generator] runtime.annotator.enabled requires contextFilename for ${args.componentName}.`);
3162
+ }
3163
+ const sourceLocation = args.element.loc?.start;
3164
+ if (!sourceLocation) {
3165
+ throw new Error(`[vue-pom-generator] runtime.annotator.enabled requires element source locations for ${args.componentName}.`);
3166
+ }
3167
+ const metadataAttributePrefix = args.annotatorMetadata.metadataAttributePrefix;
3168
+ upsertAttribute(args.element, args.annotatorMetadata.sourceAttribute, staticAttributeValue(`${filename}:${sourceLocation.line}:${sourceLocation.column}`));
3169
+ upsertAttribute(args.element, `${metadataAttributePrefix}-component`, staticAttributeValue(args.componentName));
3170
+ upsertAttribute(args.element, `${metadataAttributePrefix}-tag`, staticAttributeValue(args.element.tag));
3171
+ upsertAttribute(args.element, `${metadataAttributePrefix}-testid`, runtimeDataTestId);
3172
+ upsertAttribute(args.element, `${metadataAttributePrefix}-action`, staticAttributeValue(buildPomGeneratedActionName(dataTestIdEntry.pom)));
3173
+ upsertAttribute(args.element, `${metadataAttributePrefix}-property`, staticAttributeValue(buildPomGeneratedPropertyName(dataTestIdEntry.pom)));
3174
+ upsertAttribute(args.element, `${metadataAttributePrefix}-role`, staticAttributeValue(normalizedRole));
3175
+ }
3156
3176
  args.dependencies.childrenComponentSet.add(childComponentName);
3157
3177
  args.dependencies.usedComponentSet.add(childComponentName);
3158
3178
  args.dependencies.dataTestIdSet.add(dataTestIdEntry);
@@ -3351,7 +3371,7 @@ Fix: make the element identifiable (e.g. add id/name/inner text or use a more sp
3351
3371
  registerGeneratedMethodSignature(generatedName2, createPomMethodSignature([createPomParameterSpec("annotationText", `string = ""`)]));
3352
3372
  }
3353
3373
  }
3354
- return { selectorValue: dataTestId, runtimeValue: runtimeDataTestId, fromExisting };
3374
+ return { selectorValue: dataTestId, runtimeValue: runtimeDataTestId, fromExisting, entry: dataTestIdEntry };
3355
3375
  }
3356
3376
  const generatedName = ensureUniqueGeneratedName(`select${upperFirst(methodName || "Radio")}`);
3357
3377
  if (dataTestIdEntry.pom) {
@@ -3378,7 +3398,7 @@ Fix: make the element identifiable (e.g. add id/name/inner text or use a more sp
3378
3398
  createPomParameterSpec("annotationText", `string = ""`)
3379
3399
  ]));
3380
3400
  }
3381
- return { selectorValue: dataTestId, runtimeValue: runtimeDataTestId, fromExisting };
3401
+ return { selectorValue: dataTestId, runtimeValue: runtimeDataTestId, fromExisting, entry: dataTestIdEntry };
3382
3402
  }
3383
3403
  const staticKeyValues = args.keyValuesOverride ?? null;
3384
3404
  const needsKey = hasPomParameter(normalizedParameters, "key") && selectorIsParameterized;
@@ -3412,7 +3432,7 @@ Fix: make the element identifiable (e.g. add id/name/inner text or use a more sp
3412
3432
  ]));
3413
3433
  }
3414
3434
  }
3415
- return { selectorValue: dataTestId, runtimeValue: runtimeDataTestId, fromExisting };
3435
+ return { selectorValue: dataTestId, runtimeValue: runtimeDataTestId, fromExisting, entry: dataTestIdEntry };
3416
3436
  }
3417
3437
  if (dataTestIdEntry.pom) {
3418
3438
  if (dataTestIdEntry.pom.emitPrimary !== false) {
@@ -3426,7 +3446,66 @@ Fix: make the element identifiable (e.g. add id/name/inner text or use a more sp
3426
3446
  const generatedName = getGeneratedMethodName();
3427
3447
  registerGeneratedMethodSignature(generatedName, signature);
3428
3448
  }
3429
- return { selectorValue: dataTestId, runtimeValue: runtimeDataTestId, fromExisting };
3449
+ return { selectorValue: dataTestId, runtimeValue: runtimeDataTestId, fromExisting, entry: dataTestIdEntry };
3450
+ }
3451
+ function requireStructuredPomName(value, fieldName, pom) {
3452
+ const normalized = value?.trim();
3453
+ if (normalized) {
3454
+ return normalized;
3455
+ }
3456
+ throw new Error(`[vue-pom-generator] Missing ${fieldName} for POM spec ${pom.methodName}.`);
3457
+ }
3458
+ function buildPomGeneratedPropertyName(pom) {
3459
+ return requireStructuredPomName(pom.generatedPropertyName, "generatedPropertyName", pom);
3460
+ }
3461
+ function buildPomGeneratedActionName(pom) {
3462
+ return requireStructuredPomName(pom.generatedActionName, "generatedActionName", pom);
3463
+ }
3464
+ const STANDALONE_WRAPPER_FALLBACK_ROLES = /* @__PURE__ */ new Set([
3465
+ "button",
3466
+ "input",
3467
+ "select",
3468
+ "vselect",
3469
+ "checkbox",
3470
+ "toggle",
3471
+ "radio"
3472
+ ]);
3473
+ function stripTrailingAsciiDigits(value) {
3474
+ let end = value.length;
3475
+ while (end > 0 && isAsciiDigitCode(value.charCodeAt(end - 1))) {
3476
+ end -= 1;
3477
+ }
3478
+ return value.slice(0, end);
3479
+ }
3480
+ function matchesStandaloneWrapperFallbackMethodName(methodName, componentClassName) {
3481
+ let normalizedMethodName = methodName;
3482
+ if (normalizedMethodName.endsWith("ByKey")) {
3483
+ normalizedMethodName = normalizedMethodName.slice(0, -"ByKey".length);
3484
+ }
3485
+ normalizedMethodName = stripTrailingAsciiDigits(normalizedMethodName);
3486
+ return normalizedMethodName === componentClassName;
3487
+ }
3488
+ function shouldSuppressStandaloneWrapperFallbackSurface(componentName, dependencies) {
3489
+ if (dependencies.isView || (dependencies.pomExtraMethods?.length ?? 0) > 0) {
3490
+ return false;
3491
+ }
3492
+ const entries = Array.from(dependencies.dataTestIdSet ?? []);
3493
+ if (!entries.length) {
3494
+ return false;
3495
+ }
3496
+ const primaryEntries = entries.filter((entry) => {
3497
+ return !!entry.pom && entry.pom.emitPrimary !== false;
3498
+ });
3499
+ if (!primaryEntries.length || primaryEntries.length !== entries.length) {
3500
+ return false;
3501
+ }
3502
+ const componentClassName = toPascalCase(componentName.endsWith(".vue") ? componentName.slice(0, -4) : componentName);
3503
+ if (!componentClassName) {
3504
+ return false;
3505
+ }
3506
+ return primaryEntries.every(({ pom }) => {
3507
+ return STANDALONE_WRAPPER_FALLBACK_ROLES.has(pom.nativeRole) && matchesStandaloneWrapperFallbackMethodName(pom.methodName, componentClassName);
3508
+ });
3430
3509
  }
3431
3510
  function safeRealpath(value) {
3432
3511
  try {
@@ -3443,6 +3522,14 @@ function safeRealpath(value) {
3443
3522
  const resolvedParent = safeRealpath(parent);
3444
3523
  return resolvedParent === parent ? value : path.join(resolvedParent, path.basename(value));
3445
3524
  }
3525
+ function normalizeScopePath(value, pathImpl = path) {
3526
+ return pathImpl.normalize(value);
3527
+ }
3528
+ function isNormalizedPathWithinDir(filePathAbs, dirPathAbs, pathImpl = path) {
3529
+ const normalizedFileAbs = normalizeScopePath(filePathAbs, pathImpl);
3530
+ const normalizedDirAbs = normalizeScopePath(dirPathAbs, pathImpl);
3531
+ return normalizedFileAbs === normalizedDirAbs || normalizedFileAbs.startsWith(`${normalizedDirAbs}${pathImpl.sep}`);
3532
+ }
3446
3533
  function isPathWithinDir(filePathAbs, dirPathAbs) {
3447
3534
  const fileAbs = path.resolve(filePathAbs);
3448
3535
  const dirAbs = path.resolve(dirPathAbs);
@@ -3472,6 +3559,43 @@ function resolveComponentNameFromPath(options) {
3472
3559
  }
3473
3560
  return toPascalCase(path.parse(normalizedAbsFilename).name);
3474
3561
  }
3562
+ function isFileInConfiguredSourceScope(options) {
3563
+ const { filename, projectRoot, viewsDirAbs, sourceDirs, extraRoots = [], pathImpl = path } = options;
3564
+ if (!filename) {
3565
+ return false;
3566
+ }
3567
+ const cleanFilename = filename.includes("?") ? filename.substring(0, filename.indexOf("?")) : filename;
3568
+ const normalizedProjectRoot = normalizeScopePath(projectRoot, pathImpl);
3569
+ const normalizedAbsFilename = normalizeScopePath(
3570
+ pathImpl.isAbsolute(cleanFilename) ? cleanFilename : pathImpl.resolve(normalizedProjectRoot, cleanFilename),
3571
+ pathImpl
3572
+ );
3573
+ if (normalizedAbsFilename.includes(`${pathImpl.sep}node_modules${pathImpl.sep}`) || normalizedAbsFilename.includes("/node_modules/")) {
3574
+ return false;
3575
+ }
3576
+ const normalizedViewsDirAbs = normalizeScopePath(viewsDirAbs, pathImpl);
3577
+ if (isNormalizedPathWithinDir(normalizedAbsFilename, normalizedViewsDirAbs, pathImpl)) {
3578
+ return true;
3579
+ }
3580
+ const rootsToTry = Array.from(/* @__PURE__ */ new Set([
3581
+ normalizedProjectRoot,
3582
+ ...extraRoots.map((root) => normalizeScopePath(root, pathImpl))
3583
+ ]));
3584
+ return sourceDirs.some((dir) => {
3585
+ return rootsToTry.some((root) => {
3586
+ const normalizedAbsDir = normalizeScopePath(pathImpl.resolve(root, dir), pathImpl);
3587
+ if (isNormalizedPathWithinDir(normalizedAbsFilename, normalizedAbsDir, pathImpl)) {
3588
+ return true;
3589
+ }
3590
+ if (dir.startsWith("app/") && pathImpl.basename(root) === "app") {
3591
+ const relativeDir = dir.substring(4);
3592
+ const normalizedAbsDirAlt = normalizeScopePath(pathImpl.resolve(root, relativeDir), pathImpl);
3593
+ return isNormalizedPathWithinDir(normalizedAbsFilename, normalizedAbsDirAlt, pathImpl);
3594
+ }
3595
+ return false;
3596
+ });
3597
+ });
3598
+ }
3475
3599
  let routerIntrospectionQueue = Promise.resolve();
3476
3600
  async function runRouterIntrospectionExclusive(fn) {
3477
3601
  const prev = routerIntrospectionQueue.catch(() => void 0);
@@ -4726,6 +4850,11 @@ async function generateFiles(componentHierarchyMap, vueFilesPathMap, basePageCla
4726
4850
  componentDirs,
4727
4851
  layoutDirs
4728
4852
  }) : void 0);
4853
+ const emittableComponentHierarchyMap = new Map(
4854
+ Array.from(componentHierarchyMap.entries()).filter(([componentName, dependencies]) => {
4855
+ return !shouldSuppressStandaloneWrapperFallbackSurface(componentName, dependencies);
4856
+ })
4857
+ );
4729
4858
  const generatedFilePaths = [];
4730
4859
  const writeGeneratedFile = (file) => {
4731
4860
  const resolvedFilePath = path.resolve(file.filePath);
@@ -4733,7 +4862,7 @@ async function generateFiles(componentHierarchyMap, vueFilesPathMap, basePageCla
4733
4862
  generatedFilePaths.push(resolvedFilePath);
4734
4863
  };
4735
4864
  if (emitLanguages.includes("ts")) {
4736
- const files = typescriptOutputStructure === "split" ? await generateSplitTypeScriptFiles(componentHierarchyMap, vueFilesPathMap, basePageClassPath, outDir, {
4865
+ const files = typescriptOutputStructure === "split" ? await generateSplitTypeScriptFiles(emittableComponentHierarchyMap, vueFilesPathMap, basePageClassPath, outDir, {
4737
4866
  customPomAttachments,
4738
4867
  projectRoot,
4739
4868
  customPomDir,
@@ -4743,7 +4872,7 @@ async function generateFiles(componentHierarchyMap, vueFilesPathMap, basePageCla
4743
4872
  testIdAttribute,
4744
4873
  routeMetaByComponent,
4745
4874
  vueRouterFluentChaining
4746
- }) : await generateAggregatedFiles(componentHierarchyMap, vueFilesPathMap, basePageClassPath, outDir, {
4875
+ }) : await generateAggregatedFiles(emittableComponentHierarchyMap, vueFilesPathMap, basePageClassPath, outDir, {
4747
4876
  customPomAttachments,
4748
4877
  projectRoot,
4749
4878
  customPomDir,
@@ -4757,7 +4886,7 @@ async function generateFiles(componentHierarchyMap, vueFilesPathMap, basePageCla
4757
4886
  for (const file of files) {
4758
4887
  writeGeneratedFile(file);
4759
4888
  }
4760
- const fixtureRegistryFile = maybeGenerateFixtureRegistry(componentHierarchyMap, {
4889
+ const fixtureRegistryFile = maybeGenerateFixtureRegistry(emittableComponentHierarchyMap, {
4761
4890
  generateFixtures,
4762
4891
  pomOutDir: outDir,
4763
4892
  projectRoot,
@@ -4768,7 +4897,7 @@ async function generateFiles(componentHierarchyMap, vueFilesPathMap, basePageCla
4768
4897
  }
4769
4898
  }
4770
4899
  if (emitLanguages.includes("csharp")) {
4771
- const csFiles = generateAggregatedCSharpFiles(componentHierarchyMap, outDir, {
4900
+ const csFiles = generateAggregatedCSharpFiles(emittableComponentHierarchyMap, outDir, {
4772
4901
  testIdAttribute,
4773
4902
  csharp
4774
4903
  });
@@ -5505,7 +5634,12 @@ function prepareViewObjectModelClass(componentName, dependencies, componentHiera
5505
5634
  const rawComponentRefsForInstances = isView ? usedComponentSet?.size ? usedComponentSet : childrenComponentSet : childrenComponentSet;
5506
5635
  const componentRefsForInstances = /* @__PURE__ */ new Set();
5507
5636
  for (const ref of rawComponentRefsForInstances) {
5508
- componentRefsForInstances.add(resolveTrackedComponentRef(ref) ?? normalizeTrackedComponentRef(ref));
5637
+ const resolvedRef = resolveTrackedComponentRef(ref) ?? normalizeTrackedComponentRef(ref);
5638
+ const resolvedDependencies = componentHierarchyMap.get(resolvedRef);
5639
+ if (!resolvedDependencies?.dataTestIdSet.size) {
5640
+ continue;
5641
+ }
5642
+ componentRefsForInstances.add(resolvedRef);
5509
5643
  }
5510
5644
  const hasChildComponent = (needle) => {
5511
5645
  const normalizedNeedle = normalizeTrackedComponentRef(needle);
@@ -7064,6 +7198,7 @@ function createTestIdTransform(componentName, componentHierarchyMap, nativeWrapp
7064
7198
  const warn = options.warn;
7065
7199
  const vueFilesPathMap = options.vueFilesPathMap;
7066
7200
  const wrapperSearchRoots = options.wrapperSearchRoots ?? [];
7201
+ const annotatorMetadata = options.annotatorMetadata ?? null;
7067
7202
  const safeRealpath2 = (p) => {
7068
7203
  try {
7069
7204
  return fs.existsSync(p) ? fs.realpathSync(p) : p;
@@ -7327,7 +7462,8 @@ function createTestIdTransform(componentName, componentHierarchyMap, nativeWrapp
7327
7462
  testIdAttribute,
7328
7463
  existingIdBehavior,
7329
7464
  nameCollisionBehavior,
7330
- warn
7465
+ warn,
7466
+ annotatorMetadata
7331
7467
  });
7332
7468
  };
7333
7469
  const { nativeWrappersValue, optionDataTestIdPrefixValue, semanticNameHint } = getNativeWrapperTransformInfo(element, componentName, nativeWrappers);
@@ -8318,48 +8454,6 @@ function collectAccessibilityReviewWarnings(manifest) {
8318
8454
  }
8319
8455
  return warnings;
8320
8456
  }
8321
- function removeByKeySegment(value) {
8322
- const idx = value.indexOf("ByKey");
8323
- if (idx < 0) {
8324
- return value;
8325
- }
8326
- return value.slice(0, idx) + value.slice(idx + "ByKey".length);
8327
- }
8328
- function hasRoleSuffix(baseName, roleSuffix) {
8329
- if (baseName.endsWith(roleSuffix)) {
8330
- return true;
8331
- }
8332
- const re = new RegExp(`^${roleSuffix}\\d+$`);
8333
- return re.test(baseName);
8334
- }
8335
- function getGeneratedPropertyName(pom) {
8336
- if (pom.getterNameOverride) {
8337
- return pom.getterNameOverride;
8338
- }
8339
- const roleSuffix = upperFirst(pom.nativeRole || "Element");
8340
- const baseName = upperFirst(pom.methodName);
8341
- const propertyName = hasRoleSuffix(baseName, roleSuffix) ? baseName : `${baseName}${roleSuffix}`;
8342
- return pom.selector.patternKind === "parameterized" ? removeByKeySegment(propertyName) : propertyName;
8343
- }
8344
- function getGeneratedActionName(entry, pom) {
8345
- const methodNameUpper = upperFirst(pom.methodName);
8346
- const radioMethodNameUpper = upperFirst(pom.methodName || "Radio");
8347
- const isNavigation = !!entry.targetPageObjectModelClass;
8348
- if (isNavigation) {
8349
- return `goTo${methodNameUpper}`;
8350
- }
8351
- switch (pom.nativeRole) {
8352
- case "input":
8353
- return `type${methodNameUpper}`;
8354
- case "select":
8355
- case "vselect":
8356
- return `select${methodNameUpper}`;
8357
- case "radio":
8358
- return `select${radioMethodNameUpper}`;
8359
- default:
8360
- return `click${methodNameUpper}`;
8361
- }
8362
- }
8363
8457
  function matchesPrimarySelector(extraMethod, pom) {
8364
8458
  if (extraMethod.selector.kind !== "testId") {
8365
8459
  return false;
@@ -8370,7 +8464,7 @@ function getManifestEntry(componentName, entry, componentMetadata, extraMethods)
8370
8464
  const testId = entry.selectorValue.formatted;
8371
8465
  const metadata = componentMetadata?.get(testId);
8372
8466
  const pom = entry.pom;
8373
- const generatedActionName = pom ? getGeneratedActionName(entry, pom) : null;
8467
+ const generatedActionName = pom ? buildPomGeneratedActionName(pom) : null;
8374
8468
  const extraActionNames = pom ? extraMethods.filter((extraMethod) => matchesPrimarySelector(extraMethod, pom)).map((extraMethod) => extraMethod.name).sort((a, b) => a.localeCompare(b)) : [];
8375
8469
  const generatedActionNames = Array.from(/* @__PURE__ */ new Set([
8376
8470
  ...generatedActionName ? [generatedActionName] : [],
@@ -8388,13 +8482,15 @@ function getManifestEntry(componentName, entry, componentMetadata, extraMethods)
8388
8482
  }) : componentName,
8389
8483
  inferredRole: pom?.nativeRole ?? null,
8390
8484
  ...accessibility ? { accessibility } : {},
8391
- generatedPropertyName: pom ? getGeneratedPropertyName(pom) : null,
8485
+ generatedPropertyName: pom ? buildPomGeneratedPropertyName(pom) : null,
8392
8486
  generatedActionName,
8393
8487
  generatedActionNames,
8394
8488
  emitPrimary: pom?.emitPrimary !== false,
8395
8489
  ...entry.targetPageObjectModelClass ? { targetPageObjectModelClass: entry.targetPageObjectModelClass } : {},
8396
8490
  ...metadata?.tag ? { sourceTag: metadata.tag } : {},
8397
8491
  ...metadata ? { sourceTagType: metadata.tagType } : {},
8492
+ ...metadata?.sourceLine !== void 0 ? { sourceLine: metadata.sourceLine } : {},
8493
+ ...metadata?.sourceColumn !== void 0 ? { sourceColumn: metadata.sourceColumn } : {},
8398
8494
  ...metadata?.patchFlag !== void 0 ? { patchFlag: metadata.patchFlag } : {},
8399
8495
  ...metadata?.dynamicProps?.length ? { dynamicProps: metadata.dynamicProps } : {},
8400
8496
  ...metadata?.hasClickHandler !== void 0 ? { hasClickHandler: metadata.hasClickHandler } : {},
@@ -8404,7 +8500,7 @@ function getManifestEntry(componentName, entry, componentMetadata, extraMethods)
8404
8500
  };
8405
8501
  }
8406
8502
  function buildPomManifest(componentHierarchyMap, elementMetadata) {
8407
- const manifestEntries = Array.from(componentHierarchyMap.entries()).sort((a, b) => a[0].localeCompare(b[0])).map(([componentName, dependencies]) => {
8503
+ const manifestEntries = Array.from(componentHierarchyMap.entries()).filter(([componentName, dependencies]) => !shouldSuppressStandaloneWrapperFallbackSurface(componentName, dependencies)).sort((a, b) => a[0].localeCompare(b[0])).map(([componentName, dependencies]) => {
8408
8504
  const entries = Array.from(dependencies.dataTestIdSet).sort((a, b) => a.selectorValue.formatted.localeCompare(b.selectorValue.formatted)).map((entry) => getManifestEntry(componentName, entry, elementMetadata.get(componentName), dependencies.pomExtraMethods ?? []));
8409
8505
  if (!entries.length) {
8410
8506
  return null;
@@ -8420,9 +8516,15 @@ function buildPomManifest(componentHierarchyMap, elementMetadata) {
8420
8516
  }).filter((entry) => entry !== null);
8421
8517
  return Object.fromEntries(manifestEntries);
8422
8518
  }
8423
- function buildTestIdManifest(pomManifest) {
8519
+ function buildTestIdManifest(componentHierarchyMap) {
8424
8520
  return Object.fromEntries(
8425
- Object.entries(pomManifest).map(([componentName, component]) => [componentName, Array.from(new Set(component.testIds)).sort((a, b) => a.localeCompare(b))])
8521
+ Array.from(componentHierarchyMap.entries()).sort((a, b) => a[0].localeCompare(b[0])).map(([componentName, dependencies]) => {
8522
+ const testIds = Array.from(dependencies.dataTestIdSet).map((entry) => entry.selectorValue.formatted).filter(Boolean);
8523
+ if (!testIds.length) {
8524
+ return null;
8525
+ }
8526
+ return [componentName, Array.from(new Set(testIds)).sort((a, b) => a.localeCompare(b))];
8527
+ }).filter((entry) => entry !== null)
8426
8528
  );
8427
8529
  }
8428
8530
  function writeConstJson(value) {
@@ -8432,7 +8534,7 @@ function writeConstJson(value) {
8432
8534
  }
8433
8535
  function generateTestIdsModule(componentHierarchyMap, elementMetadata) {
8434
8536
  const pomManifest = buildPomManifest(componentHierarchyMap, elementMetadata);
8435
- const testIdManifest = buildTestIdManifest(pomManifest);
8537
+ const testIdManifest = buildTestIdManifest(componentHierarchyMap);
8436
8538
  return renderSourceFile("virtual-testids.ts", (sourceFile) => {
8437
8539
  sourceFile.addStatements("// Virtual module: test id manifest");
8438
8540
  sourceFile.addVariableStatement({
@@ -8518,7 +8620,89 @@ function createTestIdsVirtualModulesPlugin(componentHierarchyMap, elementMetadat
8518
8620
  }
8519
8621
  };
8520
8622
  }
8521
- function createSupportPlugins(options) {
8623
+ const ANNOTATOR_CLIENT_VIRTUAL_ID = "virtual:vue-pom-generator/annotator-client";
8624
+ const ANNOTATOR_CLIENT_RESOLVED_ID = `\0${ANNOTATOR_CLIENT_VIRTUAL_ID}`;
8625
+ const DEFAULT_ENTRY_SUFFIXES = [
8626
+ "/src/main.ts",
8627
+ "/src/main.js",
8628
+ "/src/main.mts",
8629
+ "/src/main.tsx",
8630
+ "/src/main.jsx"
8631
+ ];
8632
+ function toFsImportPath(filePath) {
8633
+ return `/@fs/${filePath.replace(/\\/g, "/")}`;
8634
+ }
8635
+ function resolveAnnotatorClientPath() {
8636
+ const candidates = [
8637
+ fileURLToPath(new URL("./client.ts", import.meta.url)),
8638
+ fileURLToPath(new URL("../plugin/runtime/annotator/client.ts", import.meta.url))
8639
+ ];
8640
+ const resolved = candidates.find((candidate) => fs.existsSync(candidate));
8641
+ if (!resolved) {
8642
+ throw new Error("[vue-pom-generator] Unable to locate annotator client source.");
8643
+ }
8644
+ return path.resolve(resolved);
8645
+ }
8646
+ function createAnnotatorUiPlugin(options) {
8647
+ const clientPath = resolveAnnotatorClientPath();
8648
+ const clientImportPath = toFsImportPath(clientPath);
8649
+ const clientOptions = JSON.stringify({
8650
+ sourceAttribute: options.sourceAttribute,
8651
+ metadataAttributePrefix: options.metadataAttributePrefix,
8652
+ outputDetail: options.outputDetail,
8653
+ copyToClipboard: options.copyToClipboard,
8654
+ showComponentTree: options.showComponentTree
8655
+ });
8656
+ return {
8657
+ name: "vue-pom-generator:annotator-ui",
8658
+ apply: "serve",
8659
+ resolveId(id) {
8660
+ if (id === ANNOTATOR_CLIENT_VIRTUAL_ID) {
8661
+ return ANNOTATOR_CLIENT_RESOLVED_ID;
8662
+ }
8663
+ },
8664
+ load(id) {
8665
+ if (id !== ANNOTATOR_CLIENT_RESOLVED_ID) {
8666
+ return null;
8667
+ }
8668
+ return [
8669
+ `import { mountAnnotatorClient } from ${JSON.stringify(clientImportPath)};`,
8670
+ `mountAnnotatorClient(${clientOptions});`,
8671
+ ""
8672
+ ].join("\n");
8673
+ },
8674
+ transform(code, id) {
8675
+ if (!options.enabled) {
8676
+ return null;
8677
+ }
8678
+ const normalizedId = id.replace(/\\/g, "/");
8679
+ if (!DEFAULT_ENTRY_SUFFIXES.some((suffix) => normalizedId.endsWith(suffix))) {
8680
+ return null;
8681
+ }
8682
+ if (code.includes(ANNOTATOR_CLIENT_VIRTUAL_ID)) {
8683
+ return null;
8684
+ }
8685
+ return {
8686
+ code: `import ${JSON.stringify(ANNOTATOR_CLIENT_VIRTUAL_ID)};
8687
+ ${code}`,
8688
+ map: null
8689
+ };
8690
+ },
8691
+ transformIndexHtml() {
8692
+ if (!options.enabled) {
8693
+ return void 0;
8694
+ }
8695
+ const tag = {
8696
+ tag: "script",
8697
+ attrs: { type: "module" },
8698
+ children: `import ${JSON.stringify(ANNOTATOR_CLIENT_VIRTUAL_ID)};`,
8699
+ injectTo: "body"
8700
+ };
8701
+ return [tag];
8702
+ }
8703
+ };
8704
+ }
8705
+ function createInternalPlugins(options) {
8522
8706
  const {
8523
8707
  componentHierarchyMap,
8524
8708
  elementMetadata,
@@ -8534,7 +8718,8 @@ function createSupportPlugins(options) {
8534
8718
  generation,
8535
8719
  projectRootRef,
8536
8720
  basePageClassPath: basePageClassPathOverride,
8537
- loggerRef
8721
+ loggerRef,
8722
+ annotatorRuntime
8538
8723
  } = options;
8539
8724
  const {
8540
8725
  outDir,
@@ -8608,7 +8793,13 @@ function createSupportPlugins(options) {
8608
8793
  loggerRef
8609
8794
  });
8610
8795
  const virtualModules = createTestIdsVirtualModulesPlugin(componentHierarchyMap, elementMetadata);
8611
- return [tsProcessor, devProcessor, virtualModules];
8796
+ const annotatorUiPlugin = createAnnotatorUiPlugin({
8797
+ ...annotatorRuntime.ui,
8798
+ enabled: annotatorRuntime.enabled && annotatorRuntime.ui.enabled,
8799
+ sourceAttribute: annotatorRuntime.sourceAttribute,
8800
+ metadataAttributePrefix: annotatorRuntime.metadataAttributePrefix
8801
+ });
8802
+ return [tsProcessor, devProcessor, virtualModules, annotatorUiPlugin];
8612
8803
  }
8613
8804
  function findDataTestIdProp(element, attributeName = "data-testid") {
8614
8805
  return element.props.find(
@@ -8728,7 +8919,9 @@ function tryCreateElementMetadata(args) {
8728
8919
  staticAriaLabel: findStaticAttributeValue(element, "aria-label"),
8729
8920
  staticRole: findStaticAttributeValue(element, "role"),
8730
8921
  staticTitle: findStaticAttributeValue(element, "title"),
8731
- staticTextContent: collectStaticTextContent(element.children)
8922
+ staticTextContent: collectStaticTextContent(element.children),
8923
+ sourceLine: element.loc?.start.line,
8924
+ sourceColumn: element.loc?.start.column
8732
8925
  };
8733
8926
  return metadata;
8734
8927
  }
@@ -8800,7 +8993,8 @@ function createVuePluginWithTestIds(options) {
8800
8993
  loggerRef,
8801
8994
  getSourceDirs,
8802
8995
  getWrapperSearchRoots,
8803
- getProjectRoot
8996
+ getProjectRoot,
8997
+ annotatorMetadata
8804
8998
  } = options;
8805
8999
  const lastAccessibilityWarningSignatureByComponent = /* @__PURE__ */ new Map();
8806
9000
  const getComponentNameFromPath = (filename) => {
@@ -8817,27 +9011,15 @@ function createVuePluginWithTestIds(options) {
8817
9011
  return false;
8818
9012
  const cleanPath = filename.includes("?") ? filename.substring(0, filename.indexOf("?")) : filename;
8819
9013
  const projectRoot = getProjectRoot();
8820
- const absFilename = path.isAbsolute(cleanPath) ? cleanPath : path.resolve(projectRoot, cleanPath);
8821
- if (absFilename.includes(`${path.sep}node_modules${path.sep}`) || absFilename.includes("/node_modules/"))
8822
- return false;
8823
- const viewsDirAbs = getViewsDirAbs();
8824
- if (absFilename.startsWith(viewsDirAbs + path.sep) || absFilename === viewsDirAbs)
8825
- return true;
8826
- const rootsToTry = [projectRoot, process.cwd()];
8827
- const matched = getSourceDirs().some((dir) => {
8828
- return rootsToTry.some((root) => {
8829
- const absDir = path.resolve(root, dir);
8830
- if (absFilename.startsWith(absDir + path.sep) || absFilename === absDir)
8831
- return true;
8832
- if (dir.startsWith("app/") && root.endsWith("/app")) {
8833
- const relativeDir = dir.substring(4);
8834
- const absDirAlt = path.resolve(root, relativeDir);
8835
- return absFilename.startsWith(absDirAlt + path.sep) || absFilename === absDirAlt;
8836
- }
8837
- return false;
8838
- });
9014
+ const matched = isFileInConfiguredSourceScope({
9015
+ filename,
9016
+ projectRoot,
9017
+ viewsDirAbs: getViewsDirAbs(),
9018
+ sourceDirs: getSourceDirs(),
9019
+ extraRoots: [process.cwd()]
8839
9020
  });
8840
9021
  if (cleanPath.endsWith(".vue") && !matched) {
9022
+ const absFilename = path.normalize(path.isAbsolute(cleanPath) ? cleanPath : path.resolve(projectRoot, cleanPath));
8841
9023
  loggerRef.current.debug(`[isFileInScope] REJECTED: ${absFilename} (Clean: ${cleanPath})`);
8842
9024
  }
8843
9025
  return matched;
@@ -8871,7 +9053,8 @@ function createVuePluginWithTestIds(options) {
8871
9053
  missingSemanticNameBehavior,
8872
9054
  warn: (message) => loggerRef.current.warn(message),
8873
9055
  vueFilesPathMap,
8874
- wrapperSearchRoots: getWrapperSearchRoots()
9056
+ wrapperSearchRoots: getWrapperSearchRoots(),
9057
+ annotatorMetadata
8875
9058
  }
8876
9059
  )
8877
9060
  );
@@ -8926,7 +9109,8 @@ function createVuePluginWithTestIds(options) {
8926
9109
  missingSemanticNameBehavior,
8927
9110
  warn: (message) => loggerRef.current.warn(message),
8928
9111
  vueFilesPathMap,
8929
- wrapperSearchRoots: getWrapperSearchRoots()
9112
+ wrapperSearchRoots: getWrapperSearchRoots(),
9113
+ annotatorMetadata
8930
9114
  }
8931
9115
  );
8932
9116
  perFileTransform.set(componentName, transform);
@@ -9071,6 +9255,12 @@ function assertOneOf(value, allowed, name) {
9071
9255
  }
9072
9256
  throw new TypeError(`${name} must be one of: ${allowed.join(", ")}.`);
9073
9257
  }
9258
+ function assertDataAttributeName(value, name) {
9259
+ assertNonEmptyString(value, name);
9260
+ if (!value.startsWith("data-")) {
9261
+ throw new TypeError(`${name} must start with "data-".`);
9262
+ }
9263
+ }
9074
9264
  function readPackageJson(projectRoot) {
9075
9265
  const packageJsonPath = path.join(projectRoot, "package.json");
9076
9266
  if (!fs.existsSync(packageJsonPath)) {
@@ -9234,6 +9424,19 @@ function createVuePomGeneratorPlugins(options = {}) {
9234
9424
  const generationEnabled = generationOptions !== null;
9235
9425
  const vueGenerationOptions = generationOptions;
9236
9426
  const verbosity = options.logging?.verbosity ?? "warn";
9427
+ const annotatorRuntimeOptions = options.runtime?.annotator;
9428
+ const annotatorUiRuntimeOptions = annotatorRuntimeOptions?.ui;
9429
+ const resolvedAnnotatorRuntimeOptions = {
9430
+ enabled: annotatorRuntimeOptions?.enabled === true,
9431
+ sourceAttribute: annotatorRuntimeOptions?.sourceAttribute ?? "data-v-inspector",
9432
+ metadataAttributePrefix: annotatorRuntimeOptions?.metadataAttributePrefix ?? "data-v-pom",
9433
+ ui: {
9434
+ enabled: annotatorUiRuntimeOptions?.enabled === true,
9435
+ outputDetail: annotatorUiRuntimeOptions?.outputDetail ?? "forensic",
9436
+ copyToClipboard: annotatorUiRuntimeOptions?.copyToClipboard ?? false,
9437
+ showComponentTree: annotatorUiRuntimeOptions?.showComponentTree ?? true
9438
+ }
9439
+ };
9237
9440
  const vueOptions = options.vueOptions;
9238
9441
  const resolvedInjectionOptionsRef = {
9239
9442
  current: resolveInjectionSupportOptions({
@@ -9337,6 +9540,10 @@ function createVuePomGeneratorPlugins(options = {}) {
9337
9540
  assertNonEmptyStringArray(getComponentDirs(), "[vue-pom-generator] injection.componentDirs");
9338
9541
  assertNonEmptyStringArray(getLayoutDirs(), "[vue-pom-generator] injection.layoutDirs");
9339
9542
  assertNonEmptyStringArray(getWrapperSearchRoots(), "[vue-pom-generator] injection.wrapperSearchRoots");
9543
+ if (resolvedAnnotatorRuntimeOptions.enabled) {
9544
+ assertDataAttributeName(resolvedAnnotatorRuntimeOptions.sourceAttribute, "[vue-pom-generator] runtime.annotator.sourceAttribute");
9545
+ assertDataAttributeName(resolvedAnnotatorRuntimeOptions.metadataAttributePrefix, "[vue-pom-generator] runtime.annotator.metadataAttributePrefix");
9546
+ }
9340
9547
  if (generationEnabled) {
9341
9548
  assertNonEmptyString(resolvedGenerationOptions.outDir, "[vue-pom-generator] generation.outDir");
9342
9549
  assertOneOf(resolvedGenerationOptions.typescriptOutputStructure, ["aggregated", "split"], "[vue-pom-generator] generation.playwright.outputStructure");
@@ -9379,10 +9586,14 @@ function createVuePomGeneratorPlugins(options = {}) {
9379
9586
  loggerRef,
9380
9587
  getSourceDirs,
9381
9588
  getWrapperSearchRoots: getWrapperSearchRootsAbs,
9382
- getProjectRoot: () => projectRootRef.current
9589
+ getProjectRoot: () => projectRootRef.current,
9590
+ annotatorMetadata: resolvedAnnotatorRuntimeOptions.enabled ? {
9591
+ sourceAttribute: resolvedAnnotatorRuntimeOptions.sourceAttribute,
9592
+ metadataAttributePrefix: resolvedAnnotatorRuntimeOptions.metadataAttributePrefix
9593
+ } : null
9383
9594
  });
9384
9595
  templateCompilerOptionsForResolvedPlugin = templateCompilerOptions;
9385
- const supportPlugins = createSupportPlugins({
9596
+ const internalPlugins = createInternalPlugins({
9386
9597
  componentHierarchyMap,
9387
9598
  elementMetadata,
9388
9599
  vueFilesPathMap,
@@ -9397,7 +9608,8 @@ function createVuePomGeneratorPlugins(options = {}) {
9397
9608
  generation: resolvedGenerationOptions,
9398
9609
  projectRootRef,
9399
9610
  basePageClassPath: basePageClassPathOverride,
9400
- loggerRef
9611
+ loggerRef,
9612
+ annotatorRuntime: resolvedAnnotatorRuntimeOptions
9401
9613
  });
9402
9614
  if (isNuxt) {
9403
9615
  loggerRef.current.info("Nuxt environment detected. Skipping internal @vitejs/plugin-vue to avoid conflicts.");
@@ -9408,7 +9620,7 @@ function createVuePomGeneratorPlugins(options = {}) {
9408
9620
  configPlugin,
9409
9621
  metadataCollectorPlugin,
9410
9622
  ...usesExternalVuePlugin ? [] : [internalVuePlugin],
9411
- ...supportPlugins
9623
+ ...internalPlugins
9412
9624
  ];
9413
9625
  if (!generationEnabled) {
9414
9626
  const virtualModules = createTestIdsVirtualModulesPlugin(componentHierarchyMap, elementMetadata);