@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.cjs CHANGED
@@ -686,7 +686,7 @@ function createInlineParameter(name, options = {}) {
686
686
  initializer: options.initializer
687
687
  };
688
688
  }
689
- function removeByKeySegment$1(value) {
689
+ function removeByKeySegment(value) {
690
690
  const idx = value.lastIndexOf("ByKey");
691
691
  if (idx < 0) {
692
692
  return value;
@@ -890,13 +890,13 @@ function generateGetElementByDataTestId(componentName, methodName, nativeRole, s
890
890
  const roleSuffix = upperFirst$1(nativeRole || "Element");
891
891
  const baseName = upperFirst$1(methodName);
892
892
  const numericSuffix = baseName.startsWith(roleSuffix) ? baseName.slice(roleSuffix.length) : "";
893
- const hasRoleSuffix2 = baseName.endsWith(roleSuffix) || baseName.startsWith(roleSuffix) && isAllDigits(numericSuffix);
894
- const propertyName = hasRoleSuffix2 ? `${baseName}` : `${baseName}${roleSuffix}`;
893
+ const hasRoleSuffix = baseName.endsWith(roleSuffix) || baseName.startsWith(roleSuffix) && isAllDigits(numericSuffix);
894
+ const propertyName = hasRoleSuffix ? `${baseName}` : `${baseName}${roleSuffix}`;
895
895
  const selectorParams = orderPomPatternParameters(parameters, [selector]);
896
896
  const indexedVariable = getIndexedPomPatternVariable(selector);
897
897
  if (indexedVariable) {
898
898
  const keyType = getPomParameter(selectorParams, indexedVariable)?.typeExpression || "string";
899
- const keyedPropertyName = getterNameOverride ?? removeByKeySegment$1(propertyName);
899
+ const keyedPropertyName = getterNameOverride ?? removeByKeySegment(propertyName);
900
900
  return [
901
901
  createClassGetter({
902
902
  name: keyedPropertyName,
@@ -2935,7 +2935,7 @@ Fix: either (1) include ${bestKeyPreservePlaceholder} in your :${attrLabel} temp
2935
2935
  }
2936
2936
  return value.slice(0, idx) + value.slice(idx + "ByKey".length);
2937
2937
  };
2938
- const hasRoleSuffix2 = (baseName, roleSuffix) => {
2938
+ const hasRoleSuffix = (baseName, roleSuffix) => {
2939
2939
  if (baseName.endsWith(roleSuffix)) {
2940
2940
  return true;
2941
2941
  }
@@ -2945,13 +2945,13 @@ Fix: either (1) include ${bestKeyPreservePlaceholder} in your :${attrLabel} temp
2945
2945
  const getPrimaryGetterName = (primaryMethodName) => {
2946
2946
  const roleSuffix = upperFirst(normalizedRole || "Element");
2947
2947
  const baseName = upperFirst(primaryMethodName);
2948
- const propertyName = hasRoleSuffix2(baseName, roleSuffix) ? baseName : `${baseName}${roleSuffix}`;
2948
+ const propertyName = hasRoleSuffix(baseName, roleSuffix) ? baseName : `${baseName}${roleSuffix}`;
2949
2949
  return selectorIsParameterized ? removeByKeySegment2(propertyName) : propertyName;
2950
2950
  };
2951
2951
  const getPrimaryGetterNameCandidates = (primaryMethodName) => {
2952
2952
  const roleSuffix = upperFirst(normalizedRole || "Element");
2953
2953
  const baseName = upperFirst(primaryMethodName);
2954
- const propertyName = hasRoleSuffix2(baseName, roleSuffix) ? baseName : `${baseName}${roleSuffix}`;
2954
+ const propertyName = hasRoleSuffix(baseName, roleSuffix) ? baseName : `${baseName}${roleSuffix}`;
2955
2955
  if (!selectorIsParameterized) {
2956
2956
  return { primary: propertyName };
2957
2957
  }
@@ -3078,7 +3078,7 @@ Fix: either (1) include ${bestKeyPreservePlaceholder} in your :${attrLabel} temp
3078
3078
  if (conflicts && nameCollisionBehavior === "error") {
3079
3079
  const roleSuffix = upperFirst(normalizedRole || "Element");
3080
3080
  const baseNameUpper = upperFirst(baseWithSuffix);
3081
- if (!hasRoleSuffix2(baseNameUpper, roleSuffix)) {
3081
+ if (!hasRoleSuffix(baseNameUpper, roleSuffix)) {
3082
3082
  const baseWithRoleSuffix = `${baseWithSuffix}${roleSuffix}`;
3083
3083
  const candidateWithRoleSuffix = selectorIsParameterized ? `${baseWithRoleSuffix}ByKey` : baseWithRoleSuffix;
3084
3084
  const actionNameWithRoleSuffix = getPrimaryActionMethodName(candidateWithRoleSuffix);
@@ -3188,12 +3188,32 @@ Fix: make the element identifiable (e.g. add id/name/inner text or use a more sp
3188
3188
  alternateSelectors: void 0,
3189
3189
  mergeKey: args.pomMergeKey,
3190
3190
  parameters: normalizedParameters,
3191
- keyValuesOverride: args.keyValuesOverride ?? null
3191
+ keyValuesOverride: args.keyValuesOverride ?? null,
3192
+ generatedActionName: getPrimaryActionMethodName(methodName),
3193
+ generatedPropertyName: getterNameOverride ?? getPrimaryGetterName(methodName)
3192
3194
  // emitPrimary defaults to true; special cases (including merge) may set it to false below.
3193
3195
  };
3194
3196
  if (mergedIntoExisting && dataTestIdEntry.pom) {
3195
3197
  dataTestIdEntry.pom.emitPrimary = false;
3196
3198
  }
3199
+ if (addHtmlAttribute && args.annotatorMetadata && dataTestIdEntry.pom) {
3200
+ const filename = args.contextFilename?.trim();
3201
+ if (!filename) {
3202
+ throw new Error(`[vue-pom-generator] runtime.annotator.enabled requires contextFilename for ${args.componentName}.`);
3203
+ }
3204
+ const sourceLocation = args.element.loc?.start;
3205
+ if (!sourceLocation) {
3206
+ throw new Error(`[vue-pom-generator] runtime.annotator.enabled requires element source locations for ${args.componentName}.`);
3207
+ }
3208
+ const metadataAttributePrefix = args.annotatorMetadata.metadataAttributePrefix;
3209
+ upsertAttribute(args.element, args.annotatorMetadata.sourceAttribute, staticAttributeValue(`${filename}:${sourceLocation.line}:${sourceLocation.column}`));
3210
+ upsertAttribute(args.element, `${metadataAttributePrefix}-component`, staticAttributeValue(args.componentName));
3211
+ upsertAttribute(args.element, `${metadataAttributePrefix}-tag`, staticAttributeValue(args.element.tag));
3212
+ upsertAttribute(args.element, `${metadataAttributePrefix}-testid`, runtimeDataTestId);
3213
+ upsertAttribute(args.element, `${metadataAttributePrefix}-action`, staticAttributeValue(buildPomGeneratedActionName(dataTestIdEntry.pom)));
3214
+ upsertAttribute(args.element, `${metadataAttributePrefix}-property`, staticAttributeValue(buildPomGeneratedPropertyName(dataTestIdEntry.pom)));
3215
+ upsertAttribute(args.element, `${metadataAttributePrefix}-role`, staticAttributeValue(normalizedRole));
3216
+ }
3197
3217
  args.dependencies.childrenComponentSet.add(childComponentName);
3198
3218
  args.dependencies.usedComponentSet.add(childComponentName);
3199
3219
  args.dependencies.dataTestIdSet.add(dataTestIdEntry);
@@ -3392,7 +3412,7 @@ Fix: make the element identifiable (e.g. add id/name/inner text or use a more sp
3392
3412
  registerGeneratedMethodSignature(generatedName2, createPomMethodSignature([createPomParameterSpec("annotationText", `string = ""`)]));
3393
3413
  }
3394
3414
  }
3395
- return { selectorValue: dataTestId, runtimeValue: runtimeDataTestId, fromExisting };
3415
+ return { selectorValue: dataTestId, runtimeValue: runtimeDataTestId, fromExisting, entry: dataTestIdEntry };
3396
3416
  }
3397
3417
  const generatedName = ensureUniqueGeneratedName(`select${upperFirst(methodName || "Radio")}`);
3398
3418
  if (dataTestIdEntry.pom) {
@@ -3419,7 +3439,7 @@ Fix: make the element identifiable (e.g. add id/name/inner text or use a more sp
3419
3439
  createPomParameterSpec("annotationText", `string = ""`)
3420
3440
  ]));
3421
3441
  }
3422
- return { selectorValue: dataTestId, runtimeValue: runtimeDataTestId, fromExisting };
3442
+ return { selectorValue: dataTestId, runtimeValue: runtimeDataTestId, fromExisting, entry: dataTestIdEntry };
3423
3443
  }
3424
3444
  const staticKeyValues = args.keyValuesOverride ?? null;
3425
3445
  const needsKey = hasPomParameter(normalizedParameters, "key") && selectorIsParameterized;
@@ -3453,7 +3473,7 @@ Fix: make the element identifiable (e.g. add id/name/inner text or use a more sp
3453
3473
  ]));
3454
3474
  }
3455
3475
  }
3456
- return { selectorValue: dataTestId, runtimeValue: runtimeDataTestId, fromExisting };
3476
+ return { selectorValue: dataTestId, runtimeValue: runtimeDataTestId, fromExisting, entry: dataTestIdEntry };
3457
3477
  }
3458
3478
  if (dataTestIdEntry.pom) {
3459
3479
  if (dataTestIdEntry.pom.emitPrimary !== false) {
@@ -3467,7 +3487,66 @@ Fix: make the element identifiable (e.g. add id/name/inner text or use a more sp
3467
3487
  const generatedName = getGeneratedMethodName();
3468
3488
  registerGeneratedMethodSignature(generatedName, signature);
3469
3489
  }
3470
- return { selectorValue: dataTestId, runtimeValue: runtimeDataTestId, fromExisting };
3490
+ return { selectorValue: dataTestId, runtimeValue: runtimeDataTestId, fromExisting, entry: dataTestIdEntry };
3491
+ }
3492
+ function requireStructuredPomName(value, fieldName, pom) {
3493
+ const normalized = value?.trim();
3494
+ if (normalized) {
3495
+ return normalized;
3496
+ }
3497
+ throw new Error(`[vue-pom-generator] Missing ${fieldName} for POM spec ${pom.methodName}.`);
3498
+ }
3499
+ function buildPomGeneratedPropertyName(pom) {
3500
+ return requireStructuredPomName(pom.generatedPropertyName, "generatedPropertyName", pom);
3501
+ }
3502
+ function buildPomGeneratedActionName(pom) {
3503
+ return requireStructuredPomName(pom.generatedActionName, "generatedActionName", pom);
3504
+ }
3505
+ const STANDALONE_WRAPPER_FALLBACK_ROLES = /* @__PURE__ */ new Set([
3506
+ "button",
3507
+ "input",
3508
+ "select",
3509
+ "vselect",
3510
+ "checkbox",
3511
+ "toggle",
3512
+ "radio"
3513
+ ]);
3514
+ function stripTrailingAsciiDigits(value) {
3515
+ let end = value.length;
3516
+ while (end > 0 && isAsciiDigitCode(value.charCodeAt(end - 1))) {
3517
+ end -= 1;
3518
+ }
3519
+ return value.slice(0, end);
3520
+ }
3521
+ function matchesStandaloneWrapperFallbackMethodName(methodName, componentClassName) {
3522
+ let normalizedMethodName = methodName;
3523
+ if (normalizedMethodName.endsWith("ByKey")) {
3524
+ normalizedMethodName = normalizedMethodName.slice(0, -"ByKey".length);
3525
+ }
3526
+ normalizedMethodName = stripTrailingAsciiDigits(normalizedMethodName);
3527
+ return normalizedMethodName === componentClassName;
3528
+ }
3529
+ function shouldSuppressStandaloneWrapperFallbackSurface(componentName, dependencies) {
3530
+ if (dependencies.isView || (dependencies.pomExtraMethods?.length ?? 0) > 0) {
3531
+ return false;
3532
+ }
3533
+ const entries = Array.from(dependencies.dataTestIdSet ?? []);
3534
+ if (!entries.length) {
3535
+ return false;
3536
+ }
3537
+ const primaryEntries = entries.filter((entry) => {
3538
+ return !!entry.pom && entry.pom.emitPrimary !== false;
3539
+ });
3540
+ if (!primaryEntries.length || primaryEntries.length !== entries.length) {
3541
+ return false;
3542
+ }
3543
+ const componentClassName = toPascalCase(componentName.endsWith(".vue") ? componentName.slice(0, -4) : componentName);
3544
+ if (!componentClassName) {
3545
+ return false;
3546
+ }
3547
+ return primaryEntries.every(({ pom }) => {
3548
+ return STANDALONE_WRAPPER_FALLBACK_ROLES.has(pom.nativeRole) && matchesStandaloneWrapperFallbackMethodName(pom.methodName, componentClassName);
3549
+ });
3471
3550
  }
3472
3551
  function safeRealpath(value) {
3473
3552
  try {
@@ -3484,6 +3563,14 @@ function safeRealpath(value) {
3484
3563
  const resolvedParent = safeRealpath(parent);
3485
3564
  return resolvedParent === parent ? value : path.join(resolvedParent, path.basename(value));
3486
3565
  }
3566
+ function normalizeScopePath(value, pathImpl = path) {
3567
+ return pathImpl.normalize(value);
3568
+ }
3569
+ function isNormalizedPathWithinDir(filePathAbs, dirPathAbs, pathImpl = path) {
3570
+ const normalizedFileAbs = normalizeScopePath(filePathAbs, pathImpl);
3571
+ const normalizedDirAbs = normalizeScopePath(dirPathAbs, pathImpl);
3572
+ return normalizedFileAbs === normalizedDirAbs || normalizedFileAbs.startsWith(`${normalizedDirAbs}${pathImpl.sep}`);
3573
+ }
3487
3574
  function isPathWithinDir(filePathAbs, dirPathAbs) {
3488
3575
  const fileAbs = path.resolve(filePathAbs);
3489
3576
  const dirAbs = path.resolve(dirPathAbs);
@@ -3513,6 +3600,43 @@ function resolveComponentNameFromPath(options) {
3513
3600
  }
3514
3601
  return toPascalCase(path.parse(normalizedAbsFilename).name);
3515
3602
  }
3603
+ function isFileInConfiguredSourceScope(options) {
3604
+ const { filename, projectRoot, viewsDirAbs, sourceDirs, extraRoots = [], pathImpl = path } = options;
3605
+ if (!filename) {
3606
+ return false;
3607
+ }
3608
+ const cleanFilename = filename.includes("?") ? filename.substring(0, filename.indexOf("?")) : filename;
3609
+ const normalizedProjectRoot = normalizeScopePath(projectRoot, pathImpl);
3610
+ const normalizedAbsFilename = normalizeScopePath(
3611
+ pathImpl.isAbsolute(cleanFilename) ? cleanFilename : pathImpl.resolve(normalizedProjectRoot, cleanFilename),
3612
+ pathImpl
3613
+ );
3614
+ if (normalizedAbsFilename.includes(`${pathImpl.sep}node_modules${pathImpl.sep}`) || normalizedAbsFilename.includes("/node_modules/")) {
3615
+ return false;
3616
+ }
3617
+ const normalizedViewsDirAbs = normalizeScopePath(viewsDirAbs, pathImpl);
3618
+ if (isNormalizedPathWithinDir(normalizedAbsFilename, normalizedViewsDirAbs, pathImpl)) {
3619
+ return true;
3620
+ }
3621
+ const rootsToTry = Array.from(/* @__PURE__ */ new Set([
3622
+ normalizedProjectRoot,
3623
+ ...extraRoots.map((root) => normalizeScopePath(root, pathImpl))
3624
+ ]));
3625
+ return sourceDirs.some((dir) => {
3626
+ return rootsToTry.some((root) => {
3627
+ const normalizedAbsDir = normalizeScopePath(pathImpl.resolve(root, dir), pathImpl);
3628
+ if (isNormalizedPathWithinDir(normalizedAbsFilename, normalizedAbsDir, pathImpl)) {
3629
+ return true;
3630
+ }
3631
+ if (dir.startsWith("app/") && pathImpl.basename(root) === "app") {
3632
+ const relativeDir = dir.substring(4);
3633
+ const normalizedAbsDirAlt = normalizeScopePath(pathImpl.resolve(root, relativeDir), pathImpl);
3634
+ return isNormalizedPathWithinDir(normalizedAbsFilename, normalizedAbsDirAlt, pathImpl);
3635
+ }
3636
+ return false;
3637
+ });
3638
+ });
3639
+ }
3516
3640
  let routerIntrospectionQueue = Promise.resolve();
3517
3641
  async function runRouterIntrospectionExclusive(fn) {
3518
3642
  const prev = routerIntrospectionQueue.catch(() => void 0);
@@ -4767,6 +4891,11 @@ async function generateFiles(componentHierarchyMap, vueFilesPathMap, basePageCla
4767
4891
  componentDirs,
4768
4892
  layoutDirs
4769
4893
  }) : void 0);
4894
+ const emittableComponentHierarchyMap = new Map(
4895
+ Array.from(componentHierarchyMap.entries()).filter(([componentName, dependencies]) => {
4896
+ return !shouldSuppressStandaloneWrapperFallbackSurface(componentName, dependencies);
4897
+ })
4898
+ );
4770
4899
  const generatedFilePaths = [];
4771
4900
  const writeGeneratedFile = (file) => {
4772
4901
  const resolvedFilePath = path.resolve(file.filePath);
@@ -4774,7 +4903,7 @@ async function generateFiles(componentHierarchyMap, vueFilesPathMap, basePageCla
4774
4903
  generatedFilePaths.push(resolvedFilePath);
4775
4904
  };
4776
4905
  if (emitLanguages.includes("ts")) {
4777
- const files = typescriptOutputStructure === "split" ? await generateSplitTypeScriptFiles(componentHierarchyMap, vueFilesPathMap, basePageClassPath, outDir, {
4906
+ const files = typescriptOutputStructure === "split" ? await generateSplitTypeScriptFiles(emittableComponentHierarchyMap, vueFilesPathMap, basePageClassPath, outDir, {
4778
4907
  customPomAttachments,
4779
4908
  projectRoot,
4780
4909
  customPomDir,
@@ -4784,7 +4913,7 @@ async function generateFiles(componentHierarchyMap, vueFilesPathMap, basePageCla
4784
4913
  testIdAttribute,
4785
4914
  routeMetaByComponent,
4786
4915
  vueRouterFluentChaining
4787
- }) : await generateAggregatedFiles(componentHierarchyMap, vueFilesPathMap, basePageClassPath, outDir, {
4916
+ }) : await generateAggregatedFiles(emittableComponentHierarchyMap, vueFilesPathMap, basePageClassPath, outDir, {
4788
4917
  customPomAttachments,
4789
4918
  projectRoot,
4790
4919
  customPomDir,
@@ -4798,7 +4927,7 @@ async function generateFiles(componentHierarchyMap, vueFilesPathMap, basePageCla
4798
4927
  for (const file of files) {
4799
4928
  writeGeneratedFile(file);
4800
4929
  }
4801
- const fixtureRegistryFile = maybeGenerateFixtureRegistry(componentHierarchyMap, {
4930
+ const fixtureRegistryFile = maybeGenerateFixtureRegistry(emittableComponentHierarchyMap, {
4802
4931
  generateFixtures,
4803
4932
  pomOutDir: outDir,
4804
4933
  projectRoot,
@@ -4809,7 +4938,7 @@ async function generateFiles(componentHierarchyMap, vueFilesPathMap, basePageCla
4809
4938
  }
4810
4939
  }
4811
4940
  if (emitLanguages.includes("csharp")) {
4812
- const csFiles = generateAggregatedCSharpFiles(componentHierarchyMap, outDir, {
4941
+ const csFiles = generateAggregatedCSharpFiles(emittableComponentHierarchyMap, outDir, {
4813
4942
  testIdAttribute,
4814
4943
  csharp
4815
4944
  });
@@ -5546,7 +5675,12 @@ function prepareViewObjectModelClass(componentName, dependencies, componentHiera
5546
5675
  const rawComponentRefsForInstances = isView ? usedComponentSet?.size ? usedComponentSet : childrenComponentSet : childrenComponentSet;
5547
5676
  const componentRefsForInstances = /* @__PURE__ */ new Set();
5548
5677
  for (const ref of rawComponentRefsForInstances) {
5549
- componentRefsForInstances.add(resolveTrackedComponentRef(ref) ?? normalizeTrackedComponentRef(ref));
5678
+ const resolvedRef = resolveTrackedComponentRef(ref) ?? normalizeTrackedComponentRef(ref);
5679
+ const resolvedDependencies = componentHierarchyMap.get(resolvedRef);
5680
+ if (!resolvedDependencies?.dataTestIdSet.size) {
5681
+ continue;
5682
+ }
5683
+ componentRefsForInstances.add(resolvedRef);
5550
5684
  }
5551
5685
  const hasChildComponent = (needle) => {
5552
5686
  const normalizedNeedle = normalizeTrackedComponentRef(needle);
@@ -7105,6 +7239,7 @@ function createTestIdTransform(componentName, componentHierarchyMap, nativeWrapp
7105
7239
  const warn = options.warn;
7106
7240
  const vueFilesPathMap = options.vueFilesPathMap;
7107
7241
  const wrapperSearchRoots = options.wrapperSearchRoots ?? [];
7242
+ const annotatorMetadata = options.annotatorMetadata ?? null;
7108
7243
  const safeRealpath2 = (p) => {
7109
7244
  try {
7110
7245
  return fs.existsSync(p) ? fs.realpathSync(p) : p;
@@ -7368,7 +7503,8 @@ function createTestIdTransform(componentName, componentHierarchyMap, nativeWrapp
7368
7503
  testIdAttribute,
7369
7504
  existingIdBehavior,
7370
7505
  nameCollisionBehavior,
7371
- warn
7506
+ warn,
7507
+ annotatorMetadata
7372
7508
  });
7373
7509
  };
7374
7510
  const { nativeWrappersValue, optionDataTestIdPrefixValue, semanticNameHint } = getNativeWrapperTransformInfo(element, componentName, nativeWrappers);
@@ -8359,48 +8495,6 @@ function collectAccessibilityReviewWarnings(manifest) {
8359
8495
  }
8360
8496
  return warnings;
8361
8497
  }
8362
- function removeByKeySegment(value) {
8363
- const idx = value.indexOf("ByKey");
8364
- if (idx < 0) {
8365
- return value;
8366
- }
8367
- return value.slice(0, idx) + value.slice(idx + "ByKey".length);
8368
- }
8369
- function hasRoleSuffix(baseName, roleSuffix) {
8370
- if (baseName.endsWith(roleSuffix)) {
8371
- return true;
8372
- }
8373
- const re = new RegExp(`^${roleSuffix}\\d+$`);
8374
- return re.test(baseName);
8375
- }
8376
- function getGeneratedPropertyName(pom) {
8377
- if (pom.getterNameOverride) {
8378
- return pom.getterNameOverride;
8379
- }
8380
- const roleSuffix = upperFirst(pom.nativeRole || "Element");
8381
- const baseName = upperFirst(pom.methodName);
8382
- const propertyName = hasRoleSuffix(baseName, roleSuffix) ? baseName : `${baseName}${roleSuffix}`;
8383
- return pom.selector.patternKind === "parameterized" ? removeByKeySegment(propertyName) : propertyName;
8384
- }
8385
- function getGeneratedActionName(entry, pom) {
8386
- const methodNameUpper = upperFirst(pom.methodName);
8387
- const radioMethodNameUpper = upperFirst(pom.methodName || "Radio");
8388
- const isNavigation = !!entry.targetPageObjectModelClass;
8389
- if (isNavigation) {
8390
- return `goTo${methodNameUpper}`;
8391
- }
8392
- switch (pom.nativeRole) {
8393
- case "input":
8394
- return `type${methodNameUpper}`;
8395
- case "select":
8396
- case "vselect":
8397
- return `select${methodNameUpper}`;
8398
- case "radio":
8399
- return `select${radioMethodNameUpper}`;
8400
- default:
8401
- return `click${methodNameUpper}`;
8402
- }
8403
- }
8404
8498
  function matchesPrimarySelector(extraMethod, pom) {
8405
8499
  if (extraMethod.selector.kind !== "testId") {
8406
8500
  return false;
@@ -8411,7 +8505,7 @@ function getManifestEntry(componentName, entry, componentMetadata, extraMethods)
8411
8505
  const testId = entry.selectorValue.formatted;
8412
8506
  const metadata = componentMetadata?.get(testId);
8413
8507
  const pom = entry.pom;
8414
- const generatedActionName = pom ? getGeneratedActionName(entry, pom) : null;
8508
+ const generatedActionName = pom ? buildPomGeneratedActionName(pom) : null;
8415
8509
  const extraActionNames = pom ? extraMethods.filter((extraMethod) => matchesPrimarySelector(extraMethod, pom)).map((extraMethod) => extraMethod.name).sort((a, b) => a.localeCompare(b)) : [];
8416
8510
  const generatedActionNames = Array.from(/* @__PURE__ */ new Set([
8417
8511
  ...generatedActionName ? [generatedActionName] : [],
@@ -8429,13 +8523,15 @@ function getManifestEntry(componentName, entry, componentMetadata, extraMethods)
8429
8523
  }) : componentName,
8430
8524
  inferredRole: pom?.nativeRole ?? null,
8431
8525
  ...accessibility ? { accessibility } : {},
8432
- generatedPropertyName: pom ? getGeneratedPropertyName(pom) : null,
8526
+ generatedPropertyName: pom ? buildPomGeneratedPropertyName(pom) : null,
8433
8527
  generatedActionName,
8434
8528
  generatedActionNames,
8435
8529
  emitPrimary: pom?.emitPrimary !== false,
8436
8530
  ...entry.targetPageObjectModelClass ? { targetPageObjectModelClass: entry.targetPageObjectModelClass } : {},
8437
8531
  ...metadata?.tag ? { sourceTag: metadata.tag } : {},
8438
8532
  ...metadata ? { sourceTagType: metadata.tagType } : {},
8533
+ ...metadata?.sourceLine !== void 0 ? { sourceLine: metadata.sourceLine } : {},
8534
+ ...metadata?.sourceColumn !== void 0 ? { sourceColumn: metadata.sourceColumn } : {},
8439
8535
  ...metadata?.patchFlag !== void 0 ? { patchFlag: metadata.patchFlag } : {},
8440
8536
  ...metadata?.dynamicProps?.length ? { dynamicProps: metadata.dynamicProps } : {},
8441
8537
  ...metadata?.hasClickHandler !== void 0 ? { hasClickHandler: metadata.hasClickHandler } : {},
@@ -8445,7 +8541,7 @@ function getManifestEntry(componentName, entry, componentMetadata, extraMethods)
8445
8541
  };
8446
8542
  }
8447
8543
  function buildPomManifest(componentHierarchyMap, elementMetadata) {
8448
- const manifestEntries = Array.from(componentHierarchyMap.entries()).sort((a, b) => a[0].localeCompare(b[0])).map(([componentName, dependencies]) => {
8544
+ const manifestEntries = Array.from(componentHierarchyMap.entries()).filter(([componentName, dependencies]) => !shouldSuppressStandaloneWrapperFallbackSurface(componentName, dependencies)).sort((a, b) => a[0].localeCompare(b[0])).map(([componentName, dependencies]) => {
8449
8545
  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 ?? []));
8450
8546
  if (!entries.length) {
8451
8547
  return null;
@@ -8461,9 +8557,15 @@ function buildPomManifest(componentHierarchyMap, elementMetadata) {
8461
8557
  }).filter((entry) => entry !== null);
8462
8558
  return Object.fromEntries(manifestEntries);
8463
8559
  }
8464
- function buildTestIdManifest(pomManifest) {
8560
+ function buildTestIdManifest(componentHierarchyMap) {
8465
8561
  return Object.fromEntries(
8466
- Object.entries(pomManifest).map(([componentName, component]) => [componentName, Array.from(new Set(component.testIds)).sort((a, b) => a.localeCompare(b))])
8562
+ Array.from(componentHierarchyMap.entries()).sort((a, b) => a[0].localeCompare(b[0])).map(([componentName, dependencies]) => {
8563
+ const testIds = Array.from(dependencies.dataTestIdSet).map((entry) => entry.selectorValue.formatted).filter(Boolean);
8564
+ if (!testIds.length) {
8565
+ return null;
8566
+ }
8567
+ return [componentName, Array.from(new Set(testIds)).sort((a, b) => a.localeCompare(b))];
8568
+ }).filter((entry) => entry !== null)
8467
8569
  );
8468
8570
  }
8469
8571
  function writeConstJson(value) {
@@ -8473,7 +8575,7 @@ function writeConstJson(value) {
8473
8575
  }
8474
8576
  function generateTestIdsModule(componentHierarchyMap, elementMetadata) {
8475
8577
  const pomManifest = buildPomManifest(componentHierarchyMap, elementMetadata);
8476
- const testIdManifest = buildTestIdManifest(pomManifest);
8578
+ const testIdManifest = buildTestIdManifest(componentHierarchyMap);
8477
8579
  return renderSourceFile("virtual-testids.ts", (sourceFile) => {
8478
8580
  sourceFile.addStatements("// Virtual module: test id manifest");
8479
8581
  sourceFile.addVariableStatement({
@@ -8559,7 +8661,89 @@ function createTestIdsVirtualModulesPlugin(componentHierarchyMap, elementMetadat
8559
8661
  }
8560
8662
  };
8561
8663
  }
8562
- function createSupportPlugins(options) {
8664
+ const ANNOTATOR_CLIENT_VIRTUAL_ID = "virtual:vue-pom-generator/annotator-client";
8665
+ const ANNOTATOR_CLIENT_RESOLVED_ID = `\0${ANNOTATOR_CLIENT_VIRTUAL_ID}`;
8666
+ const DEFAULT_ENTRY_SUFFIXES = [
8667
+ "/src/main.ts",
8668
+ "/src/main.js",
8669
+ "/src/main.mts",
8670
+ "/src/main.tsx",
8671
+ "/src/main.jsx"
8672
+ ];
8673
+ function toFsImportPath(filePath) {
8674
+ return `/@fs/${filePath.replace(/\\/g, "/")}`;
8675
+ }
8676
+ function resolveAnnotatorClientPath() {
8677
+ const candidates = [
8678
+ node_url.fileURLToPath(new URL("./client.ts", typeof document === "undefined" ? require("url").pathToFileURL(__filename).href : _documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === "SCRIPT" && _documentCurrentScript.src || new URL("index.cjs", document.baseURI).href)),
8679
+ node_url.fileURLToPath(new URL("../plugin/runtime/annotator/client.ts", typeof document === "undefined" ? require("url").pathToFileURL(__filename).href : _documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === "SCRIPT" && _documentCurrentScript.src || new URL("index.cjs", document.baseURI).href))
8680
+ ];
8681
+ const resolved = candidates.find((candidate) => fs.existsSync(candidate));
8682
+ if (!resolved) {
8683
+ throw new Error("[vue-pom-generator] Unable to locate annotator client source.");
8684
+ }
8685
+ return path.resolve(resolved);
8686
+ }
8687
+ function createAnnotatorUiPlugin(options) {
8688
+ const clientPath = resolveAnnotatorClientPath();
8689
+ const clientImportPath = toFsImportPath(clientPath);
8690
+ const clientOptions = JSON.stringify({
8691
+ sourceAttribute: options.sourceAttribute,
8692
+ metadataAttributePrefix: options.metadataAttributePrefix,
8693
+ outputDetail: options.outputDetail,
8694
+ copyToClipboard: options.copyToClipboard,
8695
+ showComponentTree: options.showComponentTree
8696
+ });
8697
+ return {
8698
+ name: "vue-pom-generator:annotator-ui",
8699
+ apply: "serve",
8700
+ resolveId(id) {
8701
+ if (id === ANNOTATOR_CLIENT_VIRTUAL_ID) {
8702
+ return ANNOTATOR_CLIENT_RESOLVED_ID;
8703
+ }
8704
+ },
8705
+ load(id) {
8706
+ if (id !== ANNOTATOR_CLIENT_RESOLVED_ID) {
8707
+ return null;
8708
+ }
8709
+ return [
8710
+ `import { mountAnnotatorClient } from ${JSON.stringify(clientImportPath)};`,
8711
+ `mountAnnotatorClient(${clientOptions});`,
8712
+ ""
8713
+ ].join("\n");
8714
+ },
8715
+ transform(code, id) {
8716
+ if (!options.enabled) {
8717
+ return null;
8718
+ }
8719
+ const normalizedId = id.replace(/\\/g, "/");
8720
+ if (!DEFAULT_ENTRY_SUFFIXES.some((suffix) => normalizedId.endsWith(suffix))) {
8721
+ return null;
8722
+ }
8723
+ if (code.includes(ANNOTATOR_CLIENT_VIRTUAL_ID)) {
8724
+ return null;
8725
+ }
8726
+ return {
8727
+ code: `import ${JSON.stringify(ANNOTATOR_CLIENT_VIRTUAL_ID)};
8728
+ ${code}`,
8729
+ map: null
8730
+ };
8731
+ },
8732
+ transformIndexHtml() {
8733
+ if (!options.enabled) {
8734
+ return void 0;
8735
+ }
8736
+ const tag = {
8737
+ tag: "script",
8738
+ attrs: { type: "module" },
8739
+ children: `import ${JSON.stringify(ANNOTATOR_CLIENT_VIRTUAL_ID)};`,
8740
+ injectTo: "body"
8741
+ };
8742
+ return [tag];
8743
+ }
8744
+ };
8745
+ }
8746
+ function createInternalPlugins(options) {
8563
8747
  const {
8564
8748
  componentHierarchyMap,
8565
8749
  elementMetadata,
@@ -8575,7 +8759,8 @@ function createSupportPlugins(options) {
8575
8759
  generation,
8576
8760
  projectRootRef,
8577
8761
  basePageClassPath: basePageClassPathOverride,
8578
- loggerRef
8762
+ loggerRef,
8763
+ annotatorRuntime
8579
8764
  } = options;
8580
8765
  const {
8581
8766
  outDir,
@@ -8649,7 +8834,13 @@ function createSupportPlugins(options) {
8649
8834
  loggerRef
8650
8835
  });
8651
8836
  const virtualModules = createTestIdsVirtualModulesPlugin(componentHierarchyMap, elementMetadata);
8652
- return [tsProcessor, devProcessor, virtualModules];
8837
+ const annotatorUiPlugin = createAnnotatorUiPlugin({
8838
+ ...annotatorRuntime.ui,
8839
+ enabled: annotatorRuntime.enabled && annotatorRuntime.ui.enabled,
8840
+ sourceAttribute: annotatorRuntime.sourceAttribute,
8841
+ metadataAttributePrefix: annotatorRuntime.metadataAttributePrefix
8842
+ });
8843
+ return [tsProcessor, devProcessor, virtualModules, annotatorUiPlugin];
8653
8844
  }
8654
8845
  function findDataTestIdProp(element, attributeName = "data-testid") {
8655
8846
  return element.props.find(
@@ -8769,7 +8960,9 @@ function tryCreateElementMetadata(args) {
8769
8960
  staticAriaLabel: findStaticAttributeValue(element, "aria-label"),
8770
8961
  staticRole: findStaticAttributeValue(element, "role"),
8771
8962
  staticTitle: findStaticAttributeValue(element, "title"),
8772
- staticTextContent: collectStaticTextContent(element.children)
8963
+ staticTextContent: collectStaticTextContent(element.children),
8964
+ sourceLine: element.loc?.start.line,
8965
+ sourceColumn: element.loc?.start.column
8773
8966
  };
8774
8967
  return metadata;
8775
8968
  }
@@ -8841,7 +9034,8 @@ function createVuePluginWithTestIds(options) {
8841
9034
  loggerRef,
8842
9035
  getSourceDirs,
8843
9036
  getWrapperSearchRoots,
8844
- getProjectRoot
9037
+ getProjectRoot,
9038
+ annotatorMetadata
8845
9039
  } = options;
8846
9040
  const lastAccessibilityWarningSignatureByComponent = /* @__PURE__ */ new Map();
8847
9041
  const getComponentNameFromPath = (filename) => {
@@ -8858,27 +9052,15 @@ function createVuePluginWithTestIds(options) {
8858
9052
  return false;
8859
9053
  const cleanPath = filename.includes("?") ? filename.substring(0, filename.indexOf("?")) : filename;
8860
9054
  const projectRoot = getProjectRoot();
8861
- const absFilename = path.isAbsolute(cleanPath) ? cleanPath : path.resolve(projectRoot, cleanPath);
8862
- if (absFilename.includes(`${path.sep}node_modules${path.sep}`) || absFilename.includes("/node_modules/"))
8863
- return false;
8864
- const viewsDirAbs = getViewsDirAbs();
8865
- if (absFilename.startsWith(viewsDirAbs + path.sep) || absFilename === viewsDirAbs)
8866
- return true;
8867
- const rootsToTry = [projectRoot, process.cwd()];
8868
- const matched = getSourceDirs().some((dir) => {
8869
- return rootsToTry.some((root) => {
8870
- const absDir = path.resolve(root, dir);
8871
- if (absFilename.startsWith(absDir + path.sep) || absFilename === absDir)
8872
- return true;
8873
- if (dir.startsWith("app/") && root.endsWith("/app")) {
8874
- const relativeDir = dir.substring(4);
8875
- const absDirAlt = path.resolve(root, relativeDir);
8876
- return absFilename.startsWith(absDirAlt + path.sep) || absFilename === absDirAlt;
8877
- }
8878
- return false;
8879
- });
9055
+ const matched = isFileInConfiguredSourceScope({
9056
+ filename,
9057
+ projectRoot,
9058
+ viewsDirAbs: getViewsDirAbs(),
9059
+ sourceDirs: getSourceDirs(),
9060
+ extraRoots: [process.cwd()]
8880
9061
  });
8881
9062
  if (cleanPath.endsWith(".vue") && !matched) {
9063
+ const absFilename = path.normalize(path.isAbsolute(cleanPath) ? cleanPath : path.resolve(projectRoot, cleanPath));
8882
9064
  loggerRef.current.debug(`[isFileInScope] REJECTED: ${absFilename} (Clean: ${cleanPath})`);
8883
9065
  }
8884
9066
  return matched;
@@ -8912,7 +9094,8 @@ function createVuePluginWithTestIds(options) {
8912
9094
  missingSemanticNameBehavior,
8913
9095
  warn: (message) => loggerRef.current.warn(message),
8914
9096
  vueFilesPathMap,
8915
- wrapperSearchRoots: getWrapperSearchRoots()
9097
+ wrapperSearchRoots: getWrapperSearchRoots(),
9098
+ annotatorMetadata
8916
9099
  }
8917
9100
  )
8918
9101
  );
@@ -8967,7 +9150,8 @@ function createVuePluginWithTestIds(options) {
8967
9150
  missingSemanticNameBehavior,
8968
9151
  warn: (message) => loggerRef.current.warn(message),
8969
9152
  vueFilesPathMap,
8970
- wrapperSearchRoots: getWrapperSearchRoots()
9153
+ wrapperSearchRoots: getWrapperSearchRoots(),
9154
+ annotatorMetadata
8971
9155
  }
8972
9156
  );
8973
9157
  perFileTransform.set(componentName, transform);
@@ -9112,6 +9296,12 @@ function assertOneOf(value, allowed, name) {
9112
9296
  }
9113
9297
  throw new TypeError(`${name} must be one of: ${allowed.join(", ")}.`);
9114
9298
  }
9299
+ function assertDataAttributeName(value, name) {
9300
+ assertNonEmptyString(value, name);
9301
+ if (!value.startsWith("data-")) {
9302
+ throw new TypeError(`${name} must start with "data-".`);
9303
+ }
9304
+ }
9115
9305
  function readPackageJson(projectRoot) {
9116
9306
  const packageJsonPath = path.join(projectRoot, "package.json");
9117
9307
  if (!fs.existsSync(packageJsonPath)) {
@@ -9275,6 +9465,19 @@ function createVuePomGeneratorPlugins(options = {}) {
9275
9465
  const generationEnabled = generationOptions !== null;
9276
9466
  const vueGenerationOptions = generationOptions;
9277
9467
  const verbosity = options.logging?.verbosity ?? "warn";
9468
+ const annotatorRuntimeOptions = options.runtime?.annotator;
9469
+ const annotatorUiRuntimeOptions = annotatorRuntimeOptions?.ui;
9470
+ const resolvedAnnotatorRuntimeOptions = {
9471
+ enabled: annotatorRuntimeOptions?.enabled === true,
9472
+ sourceAttribute: annotatorRuntimeOptions?.sourceAttribute ?? "data-v-inspector",
9473
+ metadataAttributePrefix: annotatorRuntimeOptions?.metadataAttributePrefix ?? "data-v-pom",
9474
+ ui: {
9475
+ enabled: annotatorUiRuntimeOptions?.enabled === true,
9476
+ outputDetail: annotatorUiRuntimeOptions?.outputDetail ?? "forensic",
9477
+ copyToClipboard: annotatorUiRuntimeOptions?.copyToClipboard ?? false,
9478
+ showComponentTree: annotatorUiRuntimeOptions?.showComponentTree ?? true
9479
+ }
9480
+ };
9278
9481
  const vueOptions = options.vueOptions;
9279
9482
  const resolvedInjectionOptionsRef = {
9280
9483
  current: resolveInjectionSupportOptions({
@@ -9378,6 +9581,10 @@ function createVuePomGeneratorPlugins(options = {}) {
9378
9581
  assertNonEmptyStringArray(getComponentDirs(), "[vue-pom-generator] injection.componentDirs");
9379
9582
  assertNonEmptyStringArray(getLayoutDirs(), "[vue-pom-generator] injection.layoutDirs");
9380
9583
  assertNonEmptyStringArray(getWrapperSearchRoots(), "[vue-pom-generator] injection.wrapperSearchRoots");
9584
+ if (resolvedAnnotatorRuntimeOptions.enabled) {
9585
+ assertDataAttributeName(resolvedAnnotatorRuntimeOptions.sourceAttribute, "[vue-pom-generator] runtime.annotator.sourceAttribute");
9586
+ assertDataAttributeName(resolvedAnnotatorRuntimeOptions.metadataAttributePrefix, "[vue-pom-generator] runtime.annotator.metadataAttributePrefix");
9587
+ }
9381
9588
  if (generationEnabled) {
9382
9589
  assertNonEmptyString(resolvedGenerationOptions.outDir, "[vue-pom-generator] generation.outDir");
9383
9590
  assertOneOf(resolvedGenerationOptions.typescriptOutputStructure, ["aggregated", "split"], "[vue-pom-generator] generation.playwright.outputStructure");
@@ -9420,10 +9627,14 @@ function createVuePomGeneratorPlugins(options = {}) {
9420
9627
  loggerRef,
9421
9628
  getSourceDirs,
9422
9629
  getWrapperSearchRoots: getWrapperSearchRootsAbs,
9423
- getProjectRoot: () => projectRootRef.current
9630
+ getProjectRoot: () => projectRootRef.current,
9631
+ annotatorMetadata: resolvedAnnotatorRuntimeOptions.enabled ? {
9632
+ sourceAttribute: resolvedAnnotatorRuntimeOptions.sourceAttribute,
9633
+ metadataAttributePrefix: resolvedAnnotatorRuntimeOptions.metadataAttributePrefix
9634
+ } : null
9424
9635
  });
9425
9636
  templateCompilerOptionsForResolvedPlugin = templateCompilerOptions;
9426
- const supportPlugins = createSupportPlugins({
9637
+ const internalPlugins = createInternalPlugins({
9427
9638
  componentHierarchyMap,
9428
9639
  elementMetadata,
9429
9640
  vueFilesPathMap,
@@ -9438,7 +9649,8 @@ function createVuePomGeneratorPlugins(options = {}) {
9438
9649
  generation: resolvedGenerationOptions,
9439
9650
  projectRootRef,
9440
9651
  basePageClassPath: basePageClassPathOverride,
9441
- loggerRef
9652
+ loggerRef,
9653
+ annotatorRuntime: resolvedAnnotatorRuntimeOptions
9442
9654
  });
9443
9655
  if (isNuxt) {
9444
9656
  loggerRef.current.info("Nuxt environment detected. Skipping internal @vitejs/plugin-vue to avoid conflicts.");
@@ -9449,7 +9661,7 @@ function createVuePomGeneratorPlugins(options = {}) {
9449
9661
  configPlugin,
9450
9662
  metadataCollectorPlugin,
9451
9663
  ...usesExternalVuePlugin ? [] : [internalVuePlugin],
9452
- ...supportPlugins
9664
+ ...internalPlugins
9453
9665
  ];
9454
9666
  if (!generationEnabled) {
9455
9667
  const virtualModules = createTestIdsVirtualModulesPlugin(componentHierarchyMap, elementMetadata);