@immense/vue-pom-generator 1.0.35 → 1.0.37

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/README.md CHANGED
@@ -147,13 +147,35 @@ export default defineConfig(() => {
147
147
 
148
148
  Notes:
149
149
 
150
- - `vuePomGenerator(...)` already wires `@vitejs/plugin-vue` internally for non-Nuxt apps.
151
- - Do not pass `vue()` into `createVuePomGeneratorPlugins(...)`; pass Vue options via `vueOptions`.
150
+ - `vuePomGenerator(...)` wires `@vitejs/plugin-vue` internally by default for standard Vue apps.
151
+ - Do not pass `vue()` into `createVuePomGeneratorPlugins(...)`; pass Vue plugin options via `vueOptions`.
152
+ - When the app should own `vue()` explicitly, set `vuePluginOwnership: "external"` and add `vue()` separately in your Vite config.
152
153
 
153
154
  - **Injection is enabled by plugin inclusion** (there is no longer an `injection.enabled` flag).
154
155
  - **Generation is enabled by default** and can be disabled via `generation: false`.
155
156
  - **Router-aware POM helpers are enabled** when `generation.router.entry` is provided (the generator will introspect your router).
156
157
 
158
+ ### External Vue plugin ownership
159
+
160
+ If your app should own the core Vue Vite plugin explicitly, add `vue()` yourself and let this package patch the resolved plugin:
161
+
162
+ ```ts
163
+ import vue from "@vitejs/plugin-vue";
164
+ import { defineConfig } from "vite";
165
+ import { defineVuePomGeneratorConfig, vuePomGenerator } from "@immense/vue-pom-generator";
166
+
167
+ const pomConfig = defineVuePomGeneratorConfig({
168
+ vuePluginOwnership: "external",
169
+ });
170
+
171
+ export default defineConfig({
172
+ plugins: [
173
+ vue(),
174
+ ...vuePomGenerator(pomConfig),
175
+ ],
176
+ });
177
+ ```
178
+
157
179
  ### `generation.router`
158
180
 
159
181
  Controls router introspection for `:to` analysis and navigation helper generation.
@@ -204,7 +226,9 @@ When you want CI/builds to fail on explicit test ids, pair `existingIdBehavior:
204
226
 
205
227
  ### ESLint cleanup rule: remove existing test-id attributes
206
228
 
207
- Use the `remove-existing-test-id-attributes` rule to strip explicit test-id attributes from `.vue` files before or while enforcing `existingIdBehavior: "error"`.
229
+ Use the `remove-existing-test-id-attributes` rule to strip explicit test-id usage from `.vue` files before or while enforcing `existingIdBehavior: "error"`.
230
+
231
+ The fixer handles both template attributes like `data-testid="save-button"` and object-literal keys such as `inputAttr: { 'data-testid': 'save-button' }` inside Vue SFC expressions/scripts.
208
232
 
209
233
  Add this to your ESLint flat-config file, typically `eslint.config.ts` (or `eslint.config.js` / `eslint.config.mjs` at the project root):
210
234
 
package/RELEASE_NOTES.md CHANGED
@@ -1,28 +1,44 @@
1
- ● # Release Notes: v1.0.35
1
+ ● # Release Notes: v1.0.37
2
2
 
3
3
  ## Highlights
4
- - Fixed wrapper collision fallbacks in transform logic
5
- - Added comprehensive test coverage for wrapper collision scenarios
6
- - Improved PR workflow with release-notes preview comments
4
+
5
+ - Added support for external Vue plugin ownership, enabling better plugin architecture
6
+ extensibility
7
+ - Stabilized generator plugin refactor with improved plugin handling and configuration
8
+ - Enhanced build plugin with expanded functionality (72+ lines added)
9
+ - Refactored test suite with improved coverage and organization
10
+ - Added automated PR release-notes preview comments to development workflow
7
11
 
8
12
  ## Changes
9
13
 
10
- ### Bug Fixes
11
- - Resolved issues with wrapper collision fallback behavior in transform module
14
+ ### Plugin System
15
+ - Added external Vue plugin ownership support for more flexible plugin architecture
16
+ - Refactored `create-vue-pom-generator-plugins.ts` with 134 lines of improvements
17
+ - Enhanced `build-plugin.ts` with additional capabilities and refinements
18
+ - Simplified `dev-plugin.ts` by removing ~34 lines of redundant code
19
+ - Updated plugin types with new interfaces and type definitions
20
+
21
+ ### Documentation
22
+ - Expanded README.md with 30+ lines of additional documentation
12
23
 
13
24
  ### Testing
14
- - Added 41 lines of new tests for wrapper collision scenarios in `transform.test.ts`
25
+ - Restructured `options.test.ts` with 163 lines of changes for better test coverage and clarity
15
26
 
16
- ### Developer Experience
17
- - Integrated automated release-notes preview comments on pull requests
27
+ ### Code Quality
28
+ - Improved class generation logic
29
+ - Refined Vue plugin integration points
18
30
 
19
31
  ## Breaking Changes
20
- None
32
+
33
+ None identified.
21
34
 
22
35
  ## Pull Requests Included
23
- - #1 Add PR release-notes preview comments (https://github.com/immense/vue-pom-generator/pull/1)
24
- by @dkattan
36
+
37
+ - [#1](https://github.com/immense/vue-pom-generator/pull/1) Add PR release-notes preview
38
+ comments (@dkattan)
25
39
 
26
40
  ## Testing
27
- New test cases added to validate wrapper collision fallback fixes. All tests passing.
41
+
42
+ Test suite refactored and expanded in `tests/options.test.ts` with improved organization and
43
+ coverage.
28
44
 
@@ -233,7 +233,6 @@ function generateExtraClickMethodContent(spec: PomExtraClickMethodSpec): string
233
233
  if (labelNeedsTemplate) {
234
234
  lines.push(` const label = ${labelExpr};`);
235
235
  }
236
-
237
236
  const rootArg = rootNeedsTemplate ? "rootTestId" : rootExpr;
238
237
  const labelArg = labelNeedsTemplate ? "label" : labelExpr;
239
238
  lines.push(` await this.clickWithinTestIdByLabel(${rootArg}, ${labelArg}, ${annotationArg}, ${waitArg});`);
@@ -1896,8 +1895,9 @@ function getWidgetInstancesForView(
1896
1895
  continue;
1897
1896
  }
1898
1897
 
1899
- if (!availableClassIdentifiers.has(className))
1898
+ if (!availableClassIdentifiers.has(className)) {
1900
1899
  continue;
1900
+ }
1901
1901
 
1902
1902
  // Prefer stripping the view prefix (e.g. PreferencesPage-) for cleaner member names.
1903
1903
  const viewPrefix = `${componentName}-`;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../class-generation/index.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,oCAAoC,EAAE,MAAM,sBAAsB,CAAC;AAE5E,OAAO,EAAE,sBAAsB,EAAoE,MAAM,UAAU,CAAC;AAQpH,OAAO,EAAE,oCAAoC,EAAE,CAAC;AA8ChD,UAAU,SAAS;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAwPD,MAAM,WAAW,oBAAoB;IACnC;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB;;;;;;;;;;;OAWG;IACH,gBAAgB,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG;QAAE,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAE1D;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;;;;;;OAOG;IACH,sBAAsB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAEhD;;;;;OAKG;IACH,oCAAoC,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC;IAEzD;;;;OAIG;IACH,oBAAoB,CAAC,EAAE,KAAK,CAAC;QAC3B,SAAS,EAAE,MAAM,CAAC;QAClB,YAAY,EAAE,MAAM,CAAC;QACrB,wBAAwB,EAAE,MAAM,EAAE,CAAC;QAEnC;;;WAGG;QACH,QAAQ,CAAC,EAAE,OAAO,GAAG,YAAY,GAAG,MAAM,CAAC;KAC5C,CAAC,CAAC;IAEH,yEAAyE;IACzE,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB,uDAAuD;IACvD,aAAa,CAAC,EAAE,KAAK,CAAC,IAAI,GAAG,QAAQ,CAAC,CAAC;IAEvC,6BAA6B;IAC7B,MAAM,CAAC,EAAE;QACP,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,CAAC;IAEF,6EAA6E;IAC7E,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAElC,2FAA2F;IAC3F,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB,mDAAmD;IACnD,UAAU,CAAC,EAAE,YAAY,GAAG,MAAM,CAAC;IAEnC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IAEpB,oBAAoB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;CAClD;AAwCD,wBAAsB,aAAa,CACjC,qBAAqB,EAAE,GAAG,CAAC,MAAM,EAAE,sBAAsB,CAAC,EAC1D,eAAe,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EACpC,iBAAiB,EAAE,MAAM,EACzB,OAAO,GAAE,oBAAyB,iBAkFnC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../class-generation/index.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,oCAAoC,EAAE,MAAM,sBAAsB,CAAC;AAE5E,OAAO,EAAE,sBAAsB,EAAoE,MAAM,UAAU,CAAC;AAQpH,OAAO,EAAE,oCAAoC,EAAE,CAAC;AA8ChD,UAAU,SAAS;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAuPD,MAAM,WAAW,oBAAoB;IACnC;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB;;;;;;;;;;;OAWG;IACH,gBAAgB,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG;QAAE,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAE1D;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;;;;;;OAOG;IACH,sBAAsB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAEhD;;;;;OAKG;IACH,oCAAoC,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC;IAEzD;;;;OAIG;IACH,oBAAoB,CAAC,EAAE,KAAK,CAAC;QAC3B,SAAS,EAAE,MAAM,CAAC;QAClB,YAAY,EAAE,MAAM,CAAC;QACrB,wBAAwB,EAAE,MAAM,EAAE,CAAC;QAEnC;;;WAGG;QACH,QAAQ,CAAC,EAAE,OAAO,GAAG,YAAY,GAAG,MAAM,CAAC;KAC5C,CAAC,CAAC;IAEH,yEAAyE;IACzE,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB,uDAAuD;IACvD,aAAa,CAAC,EAAE,KAAK,CAAC,IAAI,GAAG,QAAQ,CAAC,CAAC;IAEvC,6BAA6B;IAC7B,MAAM,CAAC,EAAE;QACP,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,CAAC;IAEF,6EAA6E;IAC7E,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAElC,2FAA2F;IAC3F,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB,mDAAmD;IACnD,UAAU,CAAC,EAAE,YAAY,GAAG,MAAM,CAAC;IAEnC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IAEpB,oBAAoB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;CAClD;AAwCD,wBAAsB,aAAa,CACjC,qBAAqB,EAAE,GAAG,CAAC,MAAM,EAAE,sBAAsB,CAAC,EAC1D,eAAe,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EACpC,iBAAiB,EAAE,MAAM,EACzB,OAAO,GAAE,oBAAyB,iBAkFnC"}
package/dist/index.cjs CHANGED
@@ -1005,6 +1005,18 @@ function nodeHandlerAttributeInfo(node) {
1005
1005
  }
1006
1006
  return null;
1007
1007
  };
1008
+ const getRootIdentifierFromMemberChain = (node2) => {
1009
+ if (!node2) {
1010
+ return null;
1011
+ }
1012
+ if (isIdentifierNode(node2)) {
1013
+ return node2.name;
1014
+ }
1015
+ if (isMemberExpressionNode(node2)) {
1016
+ return getRootIdentifierFromMemberChain(node2.object);
1017
+ }
1018
+ return null;
1019
+ };
1008
1020
  const getAssignmentTargetName = (lhs) => {
1009
1021
  if (!lhs) {
1010
1022
  return null;
@@ -1059,7 +1071,10 @@ function nodeHandlerAttributeInfo(node) {
1059
1071
  }
1060
1072
  if (isMemberExpressionNode(arg)) {
1061
1073
  const stableName = getLastIdentifierFromMemberChain(arg);
1062
- if (stableName) {
1074
+ const rootName = getRootIdentifierFromMemberChain(arg);
1075
+ const firstChar = (rootName ?? "").charAt(0);
1076
+ const isConstantLikeRoot = firstChar !== "" && firstChar === firstChar.toUpperCase() && firstChar !== firstChar.toLowerCase();
1077
+ if (stableName && isConstantLikeRoot) {
1063
1078
  return toPascalCase(stableName.slice(0, 24));
1064
1079
  }
1065
1080
  }
@@ -1077,12 +1092,15 @@ function nodeHandlerAttributeInfo(node) {
1077
1092
  const first = args.length > 0 ? args[0] : null;
1078
1093
  if (!isObjectExpressionNode(first)) {
1079
1094
  const parts2 = [];
1080
- for (const arg of args.slice(0, 2)) {
1095
+ for (const arg of args.slice(0, 4)) {
1081
1096
  const w = stableWordFromValue(arg ?? null);
1082
1097
  if (!w) {
1083
- return null;
1098
+ continue;
1084
1099
  }
1085
1100
  parts2.push(w);
1101
+ if (parts2.length >= 2) {
1102
+ break;
1103
+ }
1086
1104
  }
1087
1105
  if (parts2.length === 0) {
1088
1106
  return null;
@@ -4507,8 +4525,9 @@ function getWidgetInstancesForView(componentName, dataTestIdSet, availableClassI
4507
4525
  } else {
4508
4526
  continue;
4509
4527
  }
4510
- if (!availableClassIdentifiers.has(className))
4528
+ if (!availableClassIdentifiers.has(className)) {
4511
4529
  continue;
4530
+ }
4512
4531
  const viewPrefix = `${componentName}-`;
4513
4532
  const descriptorRaw = stem.startsWith(viewPrefix) ? stem.slice(viewPrefix.length) : stem;
4514
4533
  const descriptorPascal = toPascalCaseLocal(descriptorRaw);
@@ -4577,32 +4596,31 @@ function getConstructor(childrenComponent, componentHierarchyMap, attachmentsFor
4577
4596
  return `${content}
4578
4597
  `;
4579
4598
  }
4580
- function getGenerationMetrics(componentHierarchyMap) {
4581
- let selectorCount = 0;
4582
- let generatedMethodCount = 0;
4583
- for (const deps of componentHierarchyMap.values()) {
4584
- selectorCount += deps.dataTestIdSet?.size ?? 0;
4585
- generatedMethodCount += deps.generatedMethods?.size ?? 0;
4599
+ function summarizeHierarchyMap(componentHierarchyMap) {
4600
+ let interactiveComponentCount = 0;
4601
+ let dataTestIdCount = 0;
4602
+ for (const dependencies of componentHierarchyMap.values()) {
4603
+ const selectorCount = dependencies.dataTestIdSet?.size ?? 0;
4604
+ if (selectorCount > 0) {
4605
+ interactiveComponentCount += 1;
4606
+ dataTestIdCount += selectorCount;
4607
+ }
4586
4608
  }
4587
4609
  return {
4588
4610
  entryCount: componentHierarchyMap.size,
4589
- selectorCount,
4590
- generatedMethodCount
4611
+ interactiveComponentCount,
4612
+ dataTestIdCount
4591
4613
  };
4592
4614
  }
4593
- function isLessRich(current, previous) {
4594
- if (current.entryCount !== previous.entryCount) {
4595
- return current.entryCount < previous.entryCount;
4615
+ function isLessRich(candidate, previous) {
4616
+ if (candidate.dataTestIdCount !== previous.dataTestIdCount) {
4617
+ return candidate.dataTestIdCount < previous.dataTestIdCount;
4596
4618
  }
4597
- if (current.selectorCount !== previous.selectorCount) {
4598
- return current.selectorCount < previous.selectorCount;
4619
+ if (candidate.interactiveComponentCount !== previous.interactiveComponentCount) {
4620
+ return candidate.interactiveComponentCount < previous.interactiveComponentCount;
4599
4621
  }
4600
- return current.generatedMethodCount < previous.generatedMethodCount;
4601
- }
4602
- function getGenerationMetricsKey(projectRoot, outDir) {
4603
- return path.resolve(projectRoot, outDir ?? "./pom");
4622
+ return candidate.entryCount < previous.entryCount;
4604
4623
  }
4605
- const buildGenerationMetricsByOutputKey = /* @__PURE__ */ new Map();
4606
4624
  function createBuildProcessorPlugin(options) {
4607
4625
  const {
4608
4626
  componentHierarchyMap,
@@ -4627,6 +4645,11 @@ function createBuildProcessorPlugin(options) {
4627
4645
  routerModuleShims,
4628
4646
  loggerRef
4629
4647
  } = options;
4648
+ let lastGeneratedMetrics = {
4649
+ entryCount: 0,
4650
+ interactiveComponentCount: 0,
4651
+ dataTestIdCount: 0
4652
+ };
4630
4653
  return {
4631
4654
  name: "vue-pom-generator-build",
4632
4655
  // This plugin exists to generate code on build output; it is not needed during dev-server HMR.
@@ -4679,17 +4702,18 @@ function createBuildProcessorPlugin(options) {
4679
4702
  }
4680
4703
  this.addWatchFile(pointerPath);
4681
4704
  },
4682
- buildEnd() {
4683
- const metrics = getGenerationMetrics(componentHierarchyMap);
4684
- if (metrics.entryCount <= 0 || metrics.selectorCount <= 0) {
4705
+ async buildEnd(error) {
4706
+ if (error) {
4707
+ return;
4708
+ }
4709
+ const metrics = summarizeHierarchyMap(componentHierarchyMap);
4710
+ if (metrics.dataTestIdCount <= 0) {
4685
4711
  return;
4686
4712
  }
4687
- const generationMetricsKey = getGenerationMetricsKey(projectRootRef.current, outDir);
4688
- const previousMetrics = buildGenerationMetricsByOutputKey.get(generationMetricsKey);
4689
- if (previousMetrics && isLessRich(metrics, previousMetrics)) {
4713
+ if (isLessRich(metrics, lastGeneratedMetrics)) {
4690
4714
  return;
4691
4715
  }
4692
- generateFiles(componentHierarchyMap, vueFilesPathMap, normalizedBasePagePath, {
4716
+ await generateFiles(componentHierarchyMap, vueFilesPathMap, normalizedBasePagePath, {
4693
4717
  outDir,
4694
4718
  emitLanguages,
4695
4719
  csharp,
@@ -4706,8 +4730,8 @@ function createBuildProcessorPlugin(options) {
4706
4730
  viewsDir,
4707
4731
  scanDirs
4708
4732
  });
4709
- buildGenerationMetricsByOutputKey.set(generationMetricsKey, metrics);
4710
- loggerRef.current.info(`generated POMs (${metrics.entryCount} entries, ${metrics.selectorCount} selectors)`);
4733
+ lastGeneratedMetrics = metrics;
4734
+ loggerRef.current.info(`generated POMs (${metrics.entryCount} entries, ${metrics.interactiveComponentCount} interactive components, ${metrics.dataTestIdCount} selectors)`);
4711
4735
  },
4712
4736
  closeBundle() {
4713
4737
  loggerRef.current.info("build complete");
@@ -5796,7 +5820,6 @@ Fix: remove the explicit ${attrLabel}, or change existingIdBehavior to "overwrit
5796
5820
  }
5797
5821
  };
5798
5822
  }
5799
- const devStartupMetricsByOutputKey = /* @__PURE__ */ new Map();
5800
5823
  function createDevProcessorPlugin(options) {
5801
5824
  const {
5802
5825
  nativeWrappers,
@@ -5962,11 +5985,7 @@ function createDevProcessorPlugin(options) {
5962
5985
  nativeWrappers,
5963
5986
  excludedComponents,
5964
5987
  getViewsDirAbs(),
5965
- {
5966
- existingIdBehavior: "preserve",
5967
- testIdAttribute,
5968
- wrapperSearchRoots: getWrapperSearchRoots()
5969
- }
5988
+ { existingIdBehavior: "preserve", testIdAttribute, wrapperSearchRoots: getWrapperSearchRoots() }
5970
5989
  )
5971
5990
  ]
5972
5991
  });
@@ -5998,21 +6017,6 @@ function createDevProcessorPlugin(options) {
5998
6017
  logInfo(`initial compile: ${compiledCount}/${totalVueFiles} files in ${formatMs(t1 - t0)} (components=${snapshotHierarchy.size})`);
5999
6018
  };
6000
6019
  const generateAggregatedFromSnapshot = (reason) => {
6001
- const metrics = getGenerationMetrics(snapshotHierarchy);
6002
- if (metrics.entryCount <= 0 || metrics.selectorCount <= 0) {
6003
- logInfo(`generate(${reason}): skipped empty snapshot (components=${metrics.entryCount}, selectors=${metrics.selectorCount})`);
6004
- return;
6005
- }
6006
- const generationMetricsKey = getGenerationMetricsKey(projectRootRef.current, outDir);
6007
- if (reason === "startup") {
6008
- const previousMetrics = devStartupMetricsByOutputKey.get(generationMetricsKey);
6009
- if (previousMetrics && isLessRich(metrics, previousMetrics)) {
6010
- logInfo(
6011
- `generate(${reason}): skipped smaller snapshot (components=${metrics.entryCount}, selectors=${metrics.selectorCount})`
6012
- );
6013
- return;
6014
- }
6015
- }
6016
6020
  const t0 = node_perf_hooks.performance.now();
6017
6021
  generateFiles(snapshotHierarchy, snapshotVuePathMap, normalizedBasePagePath, {
6018
6022
  outDir,
@@ -6027,15 +6031,10 @@ function createDevProcessorPlugin(options) {
6027
6031
  testIdAttribute,
6028
6032
  vueRouterFluentChaining: routerAwarePoms,
6029
6033
  routerEntry: resolvedRouterEntry,
6030
- routerType,
6031
- viewsDir,
6032
- scanDirs
6034
+ routerType
6033
6035
  });
6034
- if (reason === "startup") {
6035
- devStartupMetricsByOutputKey.set(generationMetricsKey, metrics);
6036
- }
6037
6036
  const t1 = node_perf_hooks.performance.now();
6038
- logInfo(`generate(${reason}): components=${metrics.entryCount} selectors=${metrics.selectorCount} in ${formatMs(t1 - t0)}`);
6037
+ logInfo(`generate(${reason}): components=${snapshotHierarchy.size} in ${formatMs(t1 - t0)}`);
6039
6038
  };
6040
6039
  const initialBuildPromise = (async () => {
6041
6040
  const t0 = node_perf_hooks.performance.now();
@@ -6282,6 +6281,7 @@ function createSupportPlugins(options) {
6282
6281
  basePageClassPath,
6283
6282
  outDir,
6284
6283
  emitLanguages,
6284
+ csharp,
6285
6285
  generateFixtures,
6286
6286
  customPomAttachments,
6287
6287
  customPomDir,
@@ -6646,7 +6646,7 @@ function createVuePluginWithTestIds(options) {
6646
6646
  };
6647
6647
  }
6648
6648
  };
6649
- return { metadataCollectorPlugin, internalVuePlugin, nuxtVueBridgePlugin };
6649
+ return { metadataCollectorPlugin, internalVuePlugin, nuxtVueBridgePlugin, templateCompilerOptions };
6650
6650
  }
6651
6651
  function assertNonEmptyString(value, name) {
6652
6652
  if (!value || !value.trim()) {
@@ -6691,6 +6691,58 @@ function assertRouterModuleShims(value, name) {
6691
6691
  function resolveFromProjectRoot(projectRoot, maybePath) {
6692
6692
  return path.isAbsolute(maybePath) ? maybePath : path.resolve(projectRoot, maybePath);
6693
6693
  }
6694
+ const sharedGeneratorStateRegistry = /* @__PURE__ */ new Map();
6695
+ function toArray(value) {
6696
+ return Array.isArray(value) ? value : [];
6697
+ }
6698
+ function getSharedGeneratorState(key) {
6699
+ let state = sharedGeneratorStateRegistry.get(key);
6700
+ if (!state) {
6701
+ state = {
6702
+ componentTestIds: /* @__PURE__ */ new Map(),
6703
+ elementMetadata: /* @__PURE__ */ new Map(),
6704
+ semanticNameMap: /* @__PURE__ */ new Map(),
6705
+ componentHierarchyMap: /* @__PURE__ */ new Map(),
6706
+ vueFilesPathMap: /* @__PURE__ */ new Map()
6707
+ };
6708
+ sharedGeneratorStateRegistry.set(key, state);
6709
+ }
6710
+ return state;
6711
+ }
6712
+ function applyTemplateCompilerOptionsToResolvedVuePlugin(config, templateCompilerOptions, mode) {
6713
+ const viteVuePlugin = (config.plugins ?? []).find((plugin) => plugin.name === "vite:vue");
6714
+ if (!viteVuePlugin?.api) {
6715
+ if (mode === "external") {
6716
+ throw new Error(
6717
+ '[vue-pom-generator] vuePluginOwnership="external" requires the resolved Vite Vue plugin, but none was found. Add vue() to your Vite plugins before spreading createVuePomGeneratorPlugins(...).'
6718
+ );
6719
+ }
6720
+ throw new Error("[vue-pom-generator] Nuxt mode requires the resolved Vite Vue plugin, but none was found.");
6721
+ }
6722
+ const currentOptions = viteVuePlugin.api.options ?? {};
6723
+ const currentTemplate = currentOptions.template ?? {};
6724
+ const currentCompilerOptions = currentTemplate.compilerOptions ?? {};
6725
+ const mergedNodeTransforms = [
6726
+ ...toArray(currentCompilerOptions.nodeTransforms),
6727
+ ...toArray(templateCompilerOptions.nodeTransforms)
6728
+ ];
6729
+ const mergedExpressionPlugins = Array.from(/* @__PURE__ */ new Set([
6730
+ ...toArray(currentCompilerOptions.expressionPlugins),
6731
+ ...toArray(templateCompilerOptions.expressionPlugins)
6732
+ ]));
6733
+ viteVuePlugin.api.options = {
6734
+ ...currentOptions,
6735
+ template: {
6736
+ ...currentTemplate,
6737
+ compilerOptions: {
6738
+ ...currentCompilerOptions,
6739
+ ...templateCompilerOptions,
6740
+ ...mergedExpressionPlugins.length > 0 ? { expressionPlugins: mergedExpressionPlugins } : {},
6741
+ nodeTransforms: mergedNodeTransforms
6742
+ }
6743
+ }
6744
+ };
6745
+ }
6694
6746
  function assertNotVitePluginInstance(options) {
6695
6747
  const candidate = options;
6696
6748
  const pluginLikeKeys = [
@@ -6709,7 +6761,7 @@ function assertNotVitePluginInstance(options) {
6709
6761
  return;
6710
6762
  }
6711
6763
  throw new TypeError(
6712
- `[vue-pom-generator] Invalid options: received an object that looks like a Vite plugin (found key: "${pluginLikeKey}"). Do not pass vue() into createVuePomGeneratorPlugins(...). Pass Vue plugin options via { vueOptions: { ... } } instead.`
6764
+ `[vue-pom-generator] Invalid options: received an object that looks like a Vite plugin (found key: "${pluginLikeKey}"). Do not pass vue() into createVuePomGeneratorPlugins(...). Pass Vue plugin options via { vueOptions: { ... } } instead, or add vue() separately in Vite and use { vuePluginOwnership: "external" }.`
6713
6765
  );
6714
6766
  }
6715
6767
  function createVuePomGeneratorPlugins(options = {}) {
@@ -6733,6 +6785,12 @@ function createVuePomGeneratorPlugins(options = {}) {
6733
6785
  const routerEntry = generationOptions?.router?.entry;
6734
6786
  const routerType = generationOptions?.router?.type ?? "vue-router";
6735
6787
  const routerModuleShims = generationOptions?.router?.moduleShims;
6788
+ const isNuxt = routerType === "nuxt";
6789
+ if (isNuxt && options.vuePluginOwnership === "internal") {
6790
+ throw new Error('[vue-pom-generator] Nuxt projects must use the resolved app-owned vite:vue plugin. Omit vuePluginOwnership or set it to "external".');
6791
+ }
6792
+ const vuePluginOwnership = isNuxt ? "external" : options.vuePluginOwnership ?? "internal";
6793
+ const usesExternalVuePlugin = vuePluginOwnership === "external";
6736
6794
  const csharp = generationOptions?.csharp;
6737
6795
  const generateFixtures = generationOptions?.playwright?.fixtures;
6738
6796
  const customPoms = generationOptions?.playwright?.customPoms;
@@ -6741,6 +6799,17 @@ function createVuePomGeneratorPlugins(options = {}) {
6741
6799
  const resolvedCustomPomImportAliases = customPoms?.importAliases;
6742
6800
  const resolvedCustomPomImportCollisionBehavior = customPoms?.importNameCollisionBehavior ?? "error";
6743
6801
  const basePageClassPathOverride = generationOptions?.basePageClassPath;
6802
+ const sharedStateKey = JSON.stringify({
6803
+ cwd: process.cwd(),
6804
+ viewsDir,
6805
+ scanDirs,
6806
+ wrapperSearchRoots,
6807
+ outDir,
6808
+ testIdAttribute,
6809
+ routerType,
6810
+ vuePluginOwnership
6811
+ });
6812
+ const sharedState = getSharedGeneratorState(sharedStateKey);
6744
6813
  const projectRootRef = { current: process.cwd() };
6745
6814
  const loggerRef = {
6746
6815
  current: createLogger({ verbosity })
@@ -6761,18 +6830,17 @@ function createVuePomGeneratorPlugins(options = {}) {
6761
6830
  assertNonEmptyString(routerEntry, "[vue-pom-generator] generation.router.entry");
6762
6831
  }
6763
6832
  }
6833
+ if (usesExternalVuePlugin) {
6834
+ applyTemplateCompilerOptionsToResolvedVuePlugin(config, templateCompilerOptions, isNuxt ? "nuxt" : vuePluginOwnership);
6835
+ }
6764
6836
  loggerRef.current.info(`projectRoot=${projectRootRef.current}`);
6765
6837
  loggerRef.current.info(`Active plugins: ${config.plugins.map((p) => p.name).filter((n) => n.includes("vue-pom")).join(", ")}`);
6766
6838
  }
6767
6839
  };
6768
6840
  const getViewsDirAbs = () => resolveFromProjectRoot(projectRootRef.current, viewsDir);
6769
6841
  const getWrapperSearchRootsAbs = () => wrapperSearchRoots.map((root) => resolveFromProjectRoot(projectRootRef.current, root));
6770
- const componentTestIds = /* @__PURE__ */ new Map();
6771
- const elementMetadata = /* @__PURE__ */ new Map();
6772
- const semanticNameMap = /* @__PURE__ */ new Map();
6773
- const componentHierarchyMap = /* @__PURE__ */ new Map();
6774
- const vueFilesPathMap = /* @__PURE__ */ new Map();
6775
- const { metadataCollectorPlugin, internalVuePlugin, nuxtVueBridgePlugin } = createVuePluginWithTestIds({
6842
+ const { componentTestIds, elementMetadata, semanticNameMap, componentHierarchyMap, vueFilesPathMap } = sharedState;
6843
+ const { metadataCollectorPlugin, internalVuePlugin, templateCompilerOptions } = createVuePluginWithTestIds({
6776
6844
  vueOptions,
6777
6845
  existingIdBehavior,
6778
6846
  nameCollisionBehavior,
@@ -6816,14 +6884,15 @@ function createVuePomGeneratorPlugins(options = {}) {
6816
6884
  routerType,
6817
6885
  routerModuleShims
6818
6886
  });
6819
- const isNuxt = routerType === "nuxt";
6820
6887
  if (isNuxt) {
6821
6888
  loggerRef.current.info("Nuxt environment detected. Skipping internal @vitejs/plugin-vue to avoid conflicts.");
6889
+ } else if (usesExternalVuePlugin) {
6890
+ loggerRef.current.info('vuePluginOwnership="external" enabled. Patching the resolved vite:vue plugin instead of creating an internal one.');
6822
6891
  }
6823
6892
  const resultPlugins = [
6824
6893
  configPlugin,
6825
6894
  metadataCollectorPlugin,
6826
- ...isNuxt ? [nuxtVueBridgePlugin] : [internalVuePlugin],
6895
+ ...usesExternalVuePlugin ? [] : [internalVuePlugin],
6827
6896
  ...supportPlugins
6828
6897
  ];
6829
6898
  if (!generationEnabled) {
@@ -6831,7 +6900,7 @@ function createVuePomGeneratorPlugins(options = {}) {
6831
6900
  return [
6832
6901
  configPlugin,
6833
6902
  metadataCollectorPlugin,
6834
- ...isNuxt ? [nuxtVueBridgePlugin] : [internalVuePlugin],
6903
+ ...usesExternalVuePlugin ? [] : [internalVuePlugin],
6835
6904
  virtualModules
6836
6905
  ];
6837
6906
  }