@immense/vue-pom-generator 1.0.32 → 1.0.34
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/RELEASE_NOTES.md +20 -19
- package/class-generation/index.ts +29 -4
- package/dist/class-generation/index.d.ts +2 -0
- package/dist/class-generation/index.d.ts.map +1 -1
- package/dist/index.cjs +409 -75
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +409 -75
- package/dist/index.mjs.map +1 -1
- package/dist/plugin/path-utils.d.ts.map +1 -1
- package/dist/plugin/support/build-plugin.d.ts +2 -0
- package/dist/plugin/support/build-plugin.d.ts.map +1 -1
- package/dist/plugin/support/dev-plugin.d.ts.map +1 -1
- package/dist/plugin/support/generation-metrics.d.ts +10 -0
- package/dist/plugin/support/generation-metrics.d.ts.map +1 -0
- package/dist/plugin/support-plugins.d.ts.map +1 -1
- package/dist/plugin/vue-plugin.d.ts +1 -0
- package/dist/plugin/vue-plugin.d.ts.map +1 -1
- package/dist/router-introspection.d.ts +10 -0
- package/dist/router-introspection.d.ts.map +1 -1
- package/dist/tests/generation-metrics.test.d.ts +2 -0
- package/dist/tests/generation-metrics.test.d.ts.map +1 -0
- package/dist/transform.d.ts.map +1 -1
- package/dist/utils.d.ts +10 -0
- package/dist/utils.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -2569,6 +2569,53 @@ Fix: make the element identifiable (e.g. add id/name/inner text or use a more sp
|
|
|
2569
2569
|
registerGeneratedMethodSignature(generatedName, signature);
|
|
2570
2570
|
}
|
|
2571
2571
|
}
|
|
2572
|
+
function safeRealpath(value) {
|
|
2573
|
+
try {
|
|
2574
|
+
if (fs.existsSync(value)) {
|
|
2575
|
+
return fs.realpathSync(value);
|
|
2576
|
+
}
|
|
2577
|
+
} catch {
|
|
2578
|
+
return value;
|
|
2579
|
+
}
|
|
2580
|
+
const parent = path.dirname(value);
|
|
2581
|
+
if (!parent || parent === value) {
|
|
2582
|
+
return value;
|
|
2583
|
+
}
|
|
2584
|
+
const resolvedParent = safeRealpath(parent);
|
|
2585
|
+
return resolvedParent === parent ? value : path.join(resolvedParent, path.basename(value));
|
|
2586
|
+
}
|
|
2587
|
+
function resolveComponentNameFromPath(options) {
|
|
2588
|
+
const { projectRoot, viewsDirAbs, scanDirs, extraRoots = [] } = options;
|
|
2589
|
+
const cleanFilename = options.filename.includes("?") ? options.filename.substring(0, options.filename.indexOf("?")) : options.filename;
|
|
2590
|
+
const absFilename = path.isAbsolute(cleanFilename) ? cleanFilename : path.resolve(projectRoot, cleanFilename);
|
|
2591
|
+
const normalizedAbsFilename = path.normalize(safeRealpath(absFilename));
|
|
2592
|
+
const rootBases = [projectRoot, ...extraRoots.filter((r) => r !== projectRoot)];
|
|
2593
|
+
const roots = [viewsDirAbs, ...rootBases.flatMap((base) => scanDirs.map((d) => path.resolve(base, d)))];
|
|
2594
|
+
for (const base of rootBases) {
|
|
2595
|
+
for (const dir of scanDirs) {
|
|
2596
|
+
const absDir = path.resolve(base, dir);
|
|
2597
|
+
try {
|
|
2598
|
+
const pagesDir = path.join(absDir, "pages");
|
|
2599
|
+
if (fs.existsSync(pagesDir))
|
|
2600
|
+
roots.push(pagesDir);
|
|
2601
|
+
const componentsDir = path.join(absDir, "components");
|
|
2602
|
+
if (fs.existsSync(componentsDir))
|
|
2603
|
+
roots.push(componentsDir);
|
|
2604
|
+
} catch {
|
|
2605
|
+
}
|
|
2606
|
+
}
|
|
2607
|
+
}
|
|
2608
|
+
const potentialRoots = Array.from(new Set(roots.map((r) => path.normalize(safeRealpath(r))))).sort((a, b) => b.length - a.length);
|
|
2609
|
+
for (const root of potentialRoots) {
|
|
2610
|
+
if (normalizedAbsFilename.startsWith(root + path.sep) || normalizedAbsFilename === root) {
|
|
2611
|
+
const rel = path.relative(root, normalizedAbsFilename);
|
|
2612
|
+
const parsed = path.parse(rel);
|
|
2613
|
+
const segments = path.join(parsed.dir, parsed.name);
|
|
2614
|
+
return toPascalCase(segments);
|
|
2615
|
+
}
|
|
2616
|
+
}
|
|
2617
|
+
return toPascalCase(path.parse(normalizedAbsFilename).name);
|
|
2618
|
+
}
|
|
2572
2619
|
let routerIntrospectionQueue = Promise.resolve();
|
|
2573
2620
|
async function runRouterIntrospectionExclusive(fn) {
|
|
2574
2621
|
const prev = routerIntrospectionQueue.catch(() => void 0);
|
|
@@ -2848,20 +2895,84 @@ function getRouteParamMeta(router, record, paramNames) {
|
|
|
2848
2895
|
}
|
|
2849
2896
|
});
|
|
2850
2897
|
}
|
|
2851
|
-
function
|
|
2852
|
-
const
|
|
2853
|
-
|
|
2898
|
+
function normalizeRouteComponentFilePath(filePath, options = {}) {
|
|
2899
|
+
const queryIndex = filePath.indexOf("?");
|
|
2900
|
+
const cleanPath = queryIndex === -1 ? filePath : filePath.slice(0, queryIndex);
|
|
2901
|
+
if (cleanPath.startsWith("/@fs/")) {
|
|
2902
|
+
return path.normalize(cleanPath.slice("/@fs/".length));
|
|
2903
|
+
}
|
|
2904
|
+
if (path.isAbsolute(cleanPath)) {
|
|
2905
|
+
if (fs.existsSync(cleanPath) || !options.rootDir)
|
|
2906
|
+
return path.normalize(cleanPath);
|
|
2907
|
+
return path.normalize(path.resolve(options.rootDir, `.${cleanPath}`));
|
|
2908
|
+
}
|
|
2909
|
+
if (!options.rootDir)
|
|
2854
2910
|
return null;
|
|
2911
|
+
return path.normalize(path.resolve(options.rootDir, cleanPath));
|
|
2912
|
+
}
|
|
2913
|
+
function getComponentInfoFromVueComponent(comp, options = {}) {
|
|
2914
|
+
if (!comp) {
|
|
2915
|
+
return {
|
|
2916
|
+
componentName: null,
|
|
2917
|
+
filePath: null
|
|
2918
|
+
};
|
|
2919
|
+
}
|
|
2920
|
+
let componentName = null;
|
|
2921
|
+
let filePath = null;
|
|
2855
2922
|
if (typeof comp.__file === "string" && comp.__file.length) {
|
|
2923
|
+
filePath = normalizeRouteComponentFilePath(comp.__file, { rootDir: options.rootDir });
|
|
2856
2924
|
const base = path.posix.basename(path.posix.normalize(comp.__file));
|
|
2857
2925
|
if (base.toLowerCase().endsWith(".vue"))
|
|
2858
|
-
|
|
2926
|
+
componentName = base.slice(0, -".vue".length);
|
|
2859
2927
|
}
|
|
2860
|
-
if (typeof comp.__name === "string" && comp.__name.length)
|
|
2861
|
-
|
|
2862
|
-
if (typeof comp.name === "string" && comp.name.length)
|
|
2863
|
-
|
|
2864
|
-
|
|
2928
|
+
if (!componentName && typeof comp.__name === "string" && comp.__name.length)
|
|
2929
|
+
componentName = comp.__name;
|
|
2930
|
+
if (!componentName && options.allowFunctionNameFallback !== false && typeof comp.name === "string" && comp.name.length) {
|
|
2931
|
+
componentName = comp.name;
|
|
2932
|
+
}
|
|
2933
|
+
return {
|
|
2934
|
+
componentName,
|
|
2935
|
+
filePath
|
|
2936
|
+
};
|
|
2937
|
+
}
|
|
2938
|
+
async function getComponentInfoFromRouteRecord(record, options = {}) {
|
|
2939
|
+
const comp = record.components?.default;
|
|
2940
|
+
if (!comp) {
|
|
2941
|
+
return {
|
|
2942
|
+
componentName: null,
|
|
2943
|
+
filePath: null
|
|
2944
|
+
};
|
|
2945
|
+
}
|
|
2946
|
+
if (typeof comp !== "function") {
|
|
2947
|
+
return getComponentInfoFromVueComponent(comp, options);
|
|
2948
|
+
}
|
|
2949
|
+
const directInfo = getComponentInfoFromVueComponent(comp, {
|
|
2950
|
+
allowFunctionNameFallback: false,
|
|
2951
|
+
rootDir: options.rootDir
|
|
2952
|
+
});
|
|
2953
|
+
if (directInfo.componentName || directInfo.filePath)
|
|
2954
|
+
return directInfo;
|
|
2955
|
+
try {
|
|
2956
|
+
const loaded = await comp();
|
|
2957
|
+
const resolved = loaded && typeof loaded === "object" && "default" in loaded ? loaded.default : loaded;
|
|
2958
|
+
const loadedInfo = getComponentInfoFromVueComponent(resolved, options);
|
|
2959
|
+
if (loadedInfo.componentName || loadedInfo.filePath)
|
|
2960
|
+
return loadedInfo;
|
|
2961
|
+
} catch {
|
|
2962
|
+
}
|
|
2963
|
+
return getComponentInfoFromVueComponent(comp, options);
|
|
2964
|
+
}
|
|
2965
|
+
function resolveIntrospectedComponentName(componentInfo, componentNaming) {
|
|
2966
|
+
if (componentInfo.filePath && componentNaming) {
|
|
2967
|
+
return resolveComponentNameFromPath({
|
|
2968
|
+
filename: componentInfo.filePath,
|
|
2969
|
+
projectRoot: componentNaming.projectRoot,
|
|
2970
|
+
viewsDirAbs: componentNaming.viewsDirAbs,
|
|
2971
|
+
scanDirs: componentNaming.scanDirs,
|
|
2972
|
+
extraRoots: componentNaming.extraRoots
|
|
2973
|
+
});
|
|
2974
|
+
}
|
|
2975
|
+
return componentInfo.componentName;
|
|
2865
2976
|
}
|
|
2866
2977
|
async function ensureDomShim() {
|
|
2867
2978
|
const domShimHtml = "<!doctype html><html><head></head><body><div id='app'></div></body></html>";
|
|
@@ -3107,7 +3218,8 @@ async function parseRouterFileFromCwd(routerEntryPath, options = {}) {
|
|
|
3107
3218
|
const routePathMap = /* @__PURE__ */ new Map();
|
|
3108
3219
|
const routeMetaEntries = [];
|
|
3109
3220
|
for (const r of router.getRoutes()) {
|
|
3110
|
-
const
|
|
3221
|
+
const componentInfo = await getComponentInfoFromRouteRecord(r, { rootDir: cwd });
|
|
3222
|
+
const componentName = resolveIntrospectedComponentName(componentInfo, options.componentNaming);
|
|
3111
3223
|
if (!componentName)
|
|
3112
3224
|
continue;
|
|
3113
3225
|
if (typeof r.path === "string" && r.path.length) {
|
|
@@ -3165,9 +3277,20 @@ function resolveRouterEntry(projectRoot, routerEntry) {
|
|
|
3165
3277
|
const root = projectRoot ?? process.cwd();
|
|
3166
3278
|
return path.isAbsolute(routerEntry) ? routerEntry : path.resolve(root, routerEntry);
|
|
3167
3279
|
}
|
|
3168
|
-
async function getRouteMetaByComponent(projectRoot, routerEntry, routerType) {
|
|
3280
|
+
async function getRouteMetaByComponent(projectRoot, routerEntry, routerType, options = {}) {
|
|
3169
3281
|
const root = projectRoot ?? process.cwd();
|
|
3170
|
-
const
|
|
3282
|
+
const viewsDir = options.viewsDir ?? "src/views";
|
|
3283
|
+
const viewsDirAbs = path.isAbsolute(viewsDir) ? viewsDir : path.resolve(root, viewsDir);
|
|
3284
|
+
const scanDirs = options.scanDirs?.length ? options.scanDirs : ["src"];
|
|
3285
|
+
const extraRoots = process.cwd() !== root ? [process.cwd()] : [];
|
|
3286
|
+
const { routeMetaEntries } = routerType === "nuxt" ? await introspectNuxtPages(root) : await parseRouterFileFromCwd(resolveRouterEntry(root, routerEntry), {
|
|
3287
|
+
componentNaming: {
|
|
3288
|
+
projectRoot: root,
|
|
3289
|
+
viewsDirAbs,
|
|
3290
|
+
scanDirs,
|
|
3291
|
+
extraRoots
|
|
3292
|
+
}
|
|
3293
|
+
});
|
|
3171
3294
|
const map = /* @__PURE__ */ new Map();
|
|
3172
3295
|
for (const entry of routeMetaEntries) {
|
|
3173
3296
|
const list = map.get(entry.componentName) ?? [];
|
|
@@ -3344,11 +3467,17 @@ async function generateFiles(componentHierarchyMap, vueFilesPathMap, basePageCla
|
|
|
3344
3467
|
csharp,
|
|
3345
3468
|
vueRouterFluentChaining,
|
|
3346
3469
|
routerEntry,
|
|
3347
|
-
routerType
|
|
3470
|
+
routerType,
|
|
3471
|
+
viewsDir,
|
|
3472
|
+
scanDirs,
|
|
3473
|
+
routeMetaByComponent: routeMetaByComponentOverride
|
|
3348
3474
|
} = options;
|
|
3349
3475
|
const emitLanguages = emitLanguagesOverride?.length ? emitLanguagesOverride : ["ts"];
|
|
3350
3476
|
const outDir = outDirOverride ?? "./pom";
|
|
3351
|
-
const routeMetaByComponent = vueRouterFluentChaining ? await getRouteMetaByComponent(projectRoot, routerEntry, routerType
|
|
3477
|
+
const routeMetaByComponent = routeMetaByComponentOverride ?? (vueRouterFluentChaining ? await getRouteMetaByComponent(projectRoot, routerEntry, routerType, {
|
|
3478
|
+
viewsDir,
|
|
3479
|
+
scanDirs
|
|
3480
|
+
}) : void 0);
|
|
3352
3481
|
const generatedFilePaths = [];
|
|
3353
3482
|
const writeGeneratedFile = (file) => {
|
|
3354
3483
|
const resolvedFilePath = path.resolve(file.filePath);
|
|
@@ -4448,10 +4577,38 @@ function getConstructor(childrenComponent, componentHierarchyMap, attachmentsFor
|
|
|
4448
4577
|
return `${content}
|
|
4449
4578
|
`;
|
|
4450
4579
|
}
|
|
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;
|
|
4586
|
+
}
|
|
4587
|
+
return {
|
|
4588
|
+
entryCount: componentHierarchyMap.size,
|
|
4589
|
+
selectorCount,
|
|
4590
|
+
generatedMethodCount
|
|
4591
|
+
};
|
|
4592
|
+
}
|
|
4593
|
+
function isLessRich(current, previous) {
|
|
4594
|
+
if (current.entryCount !== previous.entryCount) {
|
|
4595
|
+
return current.entryCount < previous.entryCount;
|
|
4596
|
+
}
|
|
4597
|
+
if (current.selectorCount !== previous.selectorCount) {
|
|
4598
|
+
return current.selectorCount < previous.selectorCount;
|
|
4599
|
+
}
|
|
4600
|
+
return current.generatedMethodCount < previous.generatedMethodCount;
|
|
4601
|
+
}
|
|
4602
|
+
function getGenerationMetricsKey(projectRoot, outDir) {
|
|
4603
|
+
return path.resolve(projectRoot, outDir ?? "./pom");
|
|
4604
|
+
}
|
|
4605
|
+
const buildGenerationMetricsByOutputKey = /* @__PURE__ */ new Map();
|
|
4451
4606
|
function createBuildProcessorPlugin(options) {
|
|
4452
4607
|
const {
|
|
4453
4608
|
componentHierarchyMap,
|
|
4454
4609
|
vueFilesPathMap,
|
|
4610
|
+
viewsDir,
|
|
4611
|
+
scanDirs,
|
|
4455
4612
|
basePageClassPath,
|
|
4456
4613
|
normalizedBasePagePath,
|
|
4457
4614
|
outDir,
|
|
@@ -4470,7 +4627,6 @@ function createBuildProcessorPlugin(options) {
|
|
|
4470
4627
|
routerModuleShims,
|
|
4471
4628
|
loggerRef
|
|
4472
4629
|
} = options;
|
|
4473
|
-
let lastGeneratedEntryCount = 0;
|
|
4474
4630
|
return {
|
|
4475
4631
|
name: "vue-pom-generator-build",
|
|
4476
4632
|
// This plugin exists to generate code on build output; it is not needed during dev-server HMR.
|
|
@@ -4488,7 +4644,14 @@ function createBuildProcessorPlugin(options) {
|
|
|
4488
4644
|
} else {
|
|
4489
4645
|
if (!resolvedRouterEntry)
|
|
4490
4646
|
throw new Error("[vue-pom-generator] router.entry is required when router introspection is enabled.");
|
|
4491
|
-
result = await parseRouterFileFromCwd(resolvedRouterEntry, {
|
|
4647
|
+
result = await parseRouterFileFromCwd(resolvedRouterEntry, {
|
|
4648
|
+
moduleShims: routerModuleShims,
|
|
4649
|
+
componentNaming: {
|
|
4650
|
+
projectRoot: projectRootRef.current,
|
|
4651
|
+
viewsDirAbs: path.isAbsolute(viewsDir) ? viewsDir : path.resolve(projectRootRef.current, viewsDir),
|
|
4652
|
+
scanDirs
|
|
4653
|
+
}
|
|
4654
|
+
});
|
|
4492
4655
|
}
|
|
4493
4656
|
const { routeNameMap, routePathMap } = result;
|
|
4494
4657
|
setRouteNameToComponentNameMap(routeNameMap);
|
|
@@ -4517,11 +4680,13 @@ function createBuildProcessorPlugin(options) {
|
|
|
4517
4680
|
this.addWatchFile(pointerPath);
|
|
4518
4681
|
},
|
|
4519
4682
|
buildEnd() {
|
|
4520
|
-
const
|
|
4521
|
-
if (entryCount <= 0) {
|
|
4683
|
+
const metrics = getGenerationMetrics(componentHierarchyMap);
|
|
4684
|
+
if (metrics.entryCount <= 0 || metrics.selectorCount <= 0) {
|
|
4522
4685
|
return;
|
|
4523
4686
|
}
|
|
4524
|
-
|
|
4687
|
+
const generationMetricsKey = getGenerationMetricsKey(projectRootRef.current, outDir);
|
|
4688
|
+
const previousMetrics = buildGenerationMetricsByOutputKey.get(generationMetricsKey);
|
|
4689
|
+
if (previousMetrics && isLessRich(metrics, previousMetrics)) {
|
|
4525
4690
|
return;
|
|
4526
4691
|
}
|
|
4527
4692
|
generateFiles(componentHierarchyMap, vueFilesPathMap, normalizedBasePagePath, {
|
|
@@ -4537,10 +4702,12 @@ function createBuildProcessorPlugin(options) {
|
|
|
4537
4702
|
testIdAttribute,
|
|
4538
4703
|
vueRouterFluentChaining: routerAwarePoms,
|
|
4539
4704
|
routerEntry: resolvedRouterEntry,
|
|
4540
|
-
routerType
|
|
4705
|
+
routerType,
|
|
4706
|
+
viewsDir,
|
|
4707
|
+
scanDirs
|
|
4541
4708
|
});
|
|
4542
|
-
|
|
4543
|
-
loggerRef.current.info(`generated POMs (${entryCount} entries)`);
|
|
4709
|
+
buildGenerationMetricsByOutputKey.set(generationMetricsKey, metrics);
|
|
4710
|
+
loggerRef.current.info(`generated POMs (${metrics.entryCount} entries, ${metrics.selectorCount} selectors)`);
|
|
4544
4711
|
},
|
|
4545
4712
|
closeBundle() {
|
|
4546
4713
|
loggerRef.current.info("build complete");
|
|
@@ -4578,6 +4745,89 @@ function toKebabCaseTag(tag) {
|
|
|
4578
4745
|
}
|
|
4579
4746
|
return result;
|
|
4580
4747
|
}
|
|
4748
|
+
function getStaticAttributeContent(element, name) {
|
|
4749
|
+
const attr = element.props.find((prop) => {
|
|
4750
|
+
return prop.type === compilerCore.NodeTypes.ATTRIBUTE && prop.name === name;
|
|
4751
|
+
});
|
|
4752
|
+
return attr?.value?.content?.trim() || null;
|
|
4753
|
+
}
|
|
4754
|
+
function getNativeHtmlControlRole(element) {
|
|
4755
|
+
const tag = (element.tag || "").toLowerCase();
|
|
4756
|
+
const type = (getStaticAttributeContent(element, "type") || "").toLowerCase();
|
|
4757
|
+
if (tag === "textarea") {
|
|
4758
|
+
return "input";
|
|
4759
|
+
}
|
|
4760
|
+
if (tag === "select") {
|
|
4761
|
+
return "select";
|
|
4762
|
+
}
|
|
4763
|
+
if (tag !== "input") {
|
|
4764
|
+
return null;
|
|
4765
|
+
}
|
|
4766
|
+
if (type === "radio") {
|
|
4767
|
+
return "radio";
|
|
4768
|
+
}
|
|
4769
|
+
if (type === "checkbox") {
|
|
4770
|
+
return "checkbox";
|
|
4771
|
+
}
|
|
4772
|
+
return "input";
|
|
4773
|
+
}
|
|
4774
|
+
function normalizeControlLabelText(value) {
|
|
4775
|
+
const normalized = (value ?? "").replace(/\*/g, " ").replace(/\s+/g, " ").trim();
|
|
4776
|
+
return normalized || null;
|
|
4777
|
+
}
|
|
4778
|
+
function getLabelNodeText(labelNode) {
|
|
4779
|
+
for (const child of labelNode.children || []) {
|
|
4780
|
+
if (child.type === compilerCore.NodeTypes.TEXT) {
|
|
4781
|
+
const normalized2 = normalizeControlLabelText(child.content);
|
|
4782
|
+
if (normalized2) {
|
|
4783
|
+
return normalized2;
|
|
4784
|
+
}
|
|
4785
|
+
continue;
|
|
4786
|
+
}
|
|
4787
|
+
if (child.type !== compilerCore.NodeTypes.ELEMENT) {
|
|
4788
|
+
continue;
|
|
4789
|
+
}
|
|
4790
|
+
if (getNativeHtmlControlRole(child)) {
|
|
4791
|
+
continue;
|
|
4792
|
+
}
|
|
4793
|
+
const normalized = normalizeControlLabelText(getInnerText(child));
|
|
4794
|
+
if (normalized) {
|
|
4795
|
+
return normalized;
|
|
4796
|
+
}
|
|
4797
|
+
}
|
|
4798
|
+
return normalizeControlLabelText(getInnerText(labelNode));
|
|
4799
|
+
}
|
|
4800
|
+
function getAssociatedLabelText(element, hierarchyMap2) {
|
|
4801
|
+
let parent = hierarchyMap2.get(element) || null;
|
|
4802
|
+
while (parent) {
|
|
4803
|
+
if (parent.tag === "label") {
|
|
4804
|
+
return getLabelNodeText(parent);
|
|
4805
|
+
}
|
|
4806
|
+
parent = hierarchyMap2.get(parent) || null;
|
|
4807
|
+
}
|
|
4808
|
+
const id = getStaticAttributeContent(element, "id");
|
|
4809
|
+
if (!id) {
|
|
4810
|
+
return null;
|
|
4811
|
+
}
|
|
4812
|
+
const candidates = /* @__PURE__ */ new Set();
|
|
4813
|
+
for (const child of hierarchyMap2.keys()) {
|
|
4814
|
+
candidates.add(child);
|
|
4815
|
+
}
|
|
4816
|
+
for (const maybeParent of hierarchyMap2.values()) {
|
|
4817
|
+
if (maybeParent) {
|
|
4818
|
+
candidates.add(maybeParent);
|
|
4819
|
+
}
|
|
4820
|
+
}
|
|
4821
|
+
for (const candidate of candidates) {
|
|
4822
|
+
if (candidate.tag !== "label") {
|
|
4823
|
+
continue;
|
|
4824
|
+
}
|
|
4825
|
+
if (getStaticAttributeContent(candidate, "for") === id) {
|
|
4826
|
+
return getLabelNodeText(candidate);
|
|
4827
|
+
}
|
|
4828
|
+
}
|
|
4829
|
+
return null;
|
|
4830
|
+
}
|
|
4581
4831
|
function normalizeSearchRoots(wrapperSearchRoots) {
|
|
4582
4832
|
const normalized = /* @__PURE__ */ new Set();
|
|
4583
4833
|
for (const root of wrapperSearchRoots) {
|
|
@@ -5110,14 +5360,14 @@ function createTestIdTransform(componentName, componentHierarchyMap, nativeWrapp
|
|
|
5110
5360
|
const warn = options.warn;
|
|
5111
5361
|
const vueFilesPathMap = options.vueFilesPathMap;
|
|
5112
5362
|
const wrapperSearchRoots = options.wrapperSearchRoots ?? [];
|
|
5113
|
-
const
|
|
5363
|
+
const safeRealpath2 = (p) => {
|
|
5114
5364
|
try {
|
|
5115
5365
|
return fs.existsSync(p) ? fs.realpathSync(p) : p;
|
|
5116
5366
|
} catch {
|
|
5117
5367
|
return p;
|
|
5118
5368
|
}
|
|
5119
5369
|
};
|
|
5120
|
-
const normalizedViewsDirAbs = path.normalize(
|
|
5370
|
+
const normalizedViewsDirAbs = path.normalize(safeRealpath2(path.resolve(viewsDirAbs)));
|
|
5121
5371
|
const generatedMethodContentByComponent = /* @__PURE__ */ new Map();
|
|
5122
5372
|
const lastConditionalHintByParent = /* @__PURE__ */ new WeakMap();
|
|
5123
5373
|
const lastConditionalMergeGroupByParent = /* @__PURE__ */ new WeakMap();
|
|
@@ -5205,7 +5455,7 @@ function createTestIdTransform(componentName, componentHierarchyMap, nativeWrapp
|
|
|
5205
5455
|
const parentIsRoot = context?.parent?.type === compilerCore.NodeTypes.ROOT;
|
|
5206
5456
|
const parentElement = !parentIsRoot && context?.parent?.type === compilerCore.NodeTypes.ELEMENT ? context.parent : null;
|
|
5207
5457
|
hierarchyMap.set(element, parentElement);
|
|
5208
|
-
const normalizeFilePath = (filePath) => path.normalize(
|
|
5458
|
+
const normalizeFilePath = (filePath) => path.normalize(safeRealpath2(path.resolve(filePath)));
|
|
5209
5459
|
const normalizedFilePath = normalizeFilePath(context.filename);
|
|
5210
5460
|
const parentComponentName = componentName;
|
|
5211
5461
|
const dependencies = (() => {
|
|
@@ -5245,7 +5495,9 @@ function createTestIdTransform(componentName, componentHierarchyMap, nativeWrapp
|
|
|
5245
5495
|
}
|
|
5246
5496
|
}
|
|
5247
5497
|
const getBestAvailableKeyValue = () => {
|
|
5248
|
-
const
|
|
5498
|
+
const parentNode = context.parent && typeof context.parent === "object" ? context.parent : null;
|
|
5499
|
+
const isDirectVForChild = parentNode?.type === compilerCore.NodeTypes.FOR;
|
|
5500
|
+
const vForKey = (isDirectVForChild ? getKeyDirectiveValue(element, context) : null) || getContainedInVForDirectiveKeyValue(context, element, hierarchyMap);
|
|
5249
5501
|
if (vForKey) return vForKey;
|
|
5250
5502
|
return getContainedInSlotDataKeyValue(element, hierarchyMap);
|
|
5251
5503
|
};
|
|
@@ -5402,6 +5654,50 @@ Fix: remove the explicit ${attrLabel}, or change existingIdBehavior to "overwrit
|
|
|
5402
5654
|
});
|
|
5403
5655
|
return;
|
|
5404
5656
|
}
|
|
5657
|
+
const nativeHtmlRole = getNativeHtmlControlRole(element);
|
|
5658
|
+
if (nativeHtmlRole) {
|
|
5659
|
+
const rawIdentifier = getStaticAttributeContent(element, "id") || getStaticAttributeContent(element, "name");
|
|
5660
|
+
const labelText = getAssociatedLabelText(element, hierarchyMap);
|
|
5661
|
+
const { vModel, modelValue } = getModelBindingValues(element);
|
|
5662
|
+
const bindingHint = modelValue || vModel || null;
|
|
5663
|
+
const labelToken = labelText ? toPascalCase(labelText) : "";
|
|
5664
|
+
const bindingToken = bindingHint ? toPascalCase(bindingHint) : "";
|
|
5665
|
+
let identifierToken = null;
|
|
5666
|
+
let semanticNameHint2;
|
|
5667
|
+
if (nativeHtmlRole === "radio" || nativeHtmlRole === "checkbox") {
|
|
5668
|
+
if (rawIdentifier) {
|
|
5669
|
+
identifierToken = rawIdentifier;
|
|
5670
|
+
semanticNameHint2 = rawIdentifier;
|
|
5671
|
+
} else if (bindingToken && labelToken) {
|
|
5672
|
+
identifierToken = `${bindingToken}${labelToken}`;
|
|
5673
|
+
semanticNameHint2 = `${bindingHint || bindingToken} ${labelText || labelToken}`;
|
|
5674
|
+
} else if (labelToken) {
|
|
5675
|
+
identifierToken = labelToken;
|
|
5676
|
+
semanticNameHint2 = labelText || labelToken;
|
|
5677
|
+
} else if (bindingToken) {
|
|
5678
|
+
identifierToken = bindingToken;
|
|
5679
|
+
semanticNameHint2 = bindingHint || bindingToken;
|
|
5680
|
+
}
|
|
5681
|
+
} else if (rawIdentifier) {
|
|
5682
|
+
identifierToken = rawIdentifier;
|
|
5683
|
+
semanticNameHint2 = rawIdentifier;
|
|
5684
|
+
} else if (labelToken) {
|
|
5685
|
+
identifierToken = labelToken;
|
|
5686
|
+
semanticNameHint2 = labelText || labelToken;
|
|
5687
|
+
} else if (bindingToken) {
|
|
5688
|
+
identifierToken = bindingToken;
|
|
5689
|
+
semanticNameHint2 = bindingHint || bindingToken;
|
|
5690
|
+
}
|
|
5691
|
+
if (identifierToken) {
|
|
5692
|
+
const preferredGeneratedValue = bestKeyPlaceholder ? templateAttributeValue(`${componentName}-${bestKeyPlaceholder}-${identifierToken}-${nativeHtmlRole}`) : staticAttributeValue(`${componentName}-${identifierToken}-${nativeHtmlRole}`);
|
|
5693
|
+
applyResolvedDataTestIdForElement({
|
|
5694
|
+
preferredGeneratedValue,
|
|
5695
|
+
nativeRoleOverride: nativeHtmlRole,
|
|
5696
|
+
semanticNameHint: semanticNameHint2 || conditionalHint || void 0
|
|
5697
|
+
});
|
|
5698
|
+
return;
|
|
5699
|
+
}
|
|
5700
|
+
}
|
|
5405
5701
|
const innerText = getInnerText(element) || null;
|
|
5406
5702
|
const toDirective = nodeHasToDirective(element);
|
|
5407
5703
|
if (toDirective) {
|
|
@@ -5443,12 +5739,14 @@ Fix: remove the explicit ${attrLabel}, or change existingIdBehavior to "overwrit
|
|
|
5443
5739
|
});
|
|
5444
5740
|
const clickHint = trimLeadingSeparators(clickSuffix) || void 0;
|
|
5445
5741
|
const idOrName = getIdOrName(element) || void 0;
|
|
5446
|
-
const
|
|
5742
|
+
const semanticHintCandidates = [clickHint, idOrName, innerText, conditionalHint].map((value) => (value ?? "").trim()).filter(Boolean).filter((value, index, values) => values.indexOf(value) === index);
|
|
5743
|
+
const [semanticNameHint2, ...semanticNameHintAlternates] = semanticHintCandidates;
|
|
5447
5744
|
const pomMergeKey = clickHint ? `click:hint:${clickHint}` : void 0;
|
|
5448
5745
|
const testId = getClickDataTestId(clickSuffix);
|
|
5449
5746
|
applyResolvedDataTestIdForElement({
|
|
5450
5747
|
preferredGeneratedValue: testId,
|
|
5451
5748
|
semanticNameHint: semanticNameHint2,
|
|
5749
|
+
semanticNameHintAlternates,
|
|
5452
5750
|
pomMergeKey
|
|
5453
5751
|
});
|
|
5454
5752
|
{
|
|
@@ -5489,37 +5787,7 @@ Fix: remove the explicit ${attrLabel}, or change existingIdBehavior to "overwrit
|
|
|
5489
5787
|
}
|
|
5490
5788
|
};
|
|
5491
5789
|
}
|
|
5492
|
-
|
|
5493
|
-
const { projectRoot, viewsDirAbs, scanDirs, extraRoots = [] } = options;
|
|
5494
|
-
const cleanFilename = options.filename.includes("?") ? options.filename.substring(0, options.filename.indexOf("?")) : options.filename;
|
|
5495
|
-
const absFilename = path.isAbsolute(cleanFilename) ? cleanFilename : path.resolve(projectRoot, cleanFilename);
|
|
5496
|
-
const rootBases = [projectRoot, ...extraRoots.filter((r) => r !== projectRoot)];
|
|
5497
|
-
const roots = [viewsDirAbs, ...rootBases.flatMap((base) => scanDirs.map((d) => path.resolve(base, d)))];
|
|
5498
|
-
for (const base of rootBases) {
|
|
5499
|
-
for (const dir of scanDirs) {
|
|
5500
|
-
const absDir = path.resolve(base, dir);
|
|
5501
|
-
try {
|
|
5502
|
-
const pagesDir = path.join(absDir, "pages");
|
|
5503
|
-
if (fs.existsSync(pagesDir))
|
|
5504
|
-
roots.push(pagesDir);
|
|
5505
|
-
const componentsDir = path.join(absDir, "components");
|
|
5506
|
-
if (fs.existsSync(componentsDir))
|
|
5507
|
-
roots.push(componentsDir);
|
|
5508
|
-
} catch {
|
|
5509
|
-
}
|
|
5510
|
-
}
|
|
5511
|
-
}
|
|
5512
|
-
const potentialRoots = Array.from(new Set(roots.map((r) => path.normalize(r)))).sort((a, b) => b.length - a.length);
|
|
5513
|
-
for (const root of potentialRoots) {
|
|
5514
|
-
if (absFilename.startsWith(root + path.sep) || absFilename === root) {
|
|
5515
|
-
const rel = path.relative(root, absFilename);
|
|
5516
|
-
const parsed = path.parse(rel);
|
|
5517
|
-
const segments = path.join(parsed.dir, parsed.name);
|
|
5518
|
-
return toPascalCase(segments);
|
|
5519
|
-
}
|
|
5520
|
-
}
|
|
5521
|
-
return toPascalCase(path.parse(absFilename).name);
|
|
5522
|
-
}
|
|
5790
|
+
const devStartupMetricsByOutputKey = /* @__PURE__ */ new Map();
|
|
5523
5791
|
function createDevProcessorPlugin(options) {
|
|
5524
5792
|
const {
|
|
5525
5793
|
nativeWrappers,
|
|
@@ -5565,6 +5833,7 @@ function createDevProcessorPlugin(options) {
|
|
|
5565
5833
|
scheduleVueFileRegen(ctx.file, "hmr");
|
|
5566
5834
|
},
|
|
5567
5835
|
configureServer(server) {
|
|
5836
|
+
const getViewsDirAbs = () => path.isAbsolute(viewsDir) ? viewsDir : path.resolve(projectRootRef.current, viewsDir);
|
|
5568
5837
|
const routerInitPromise = (async () => {
|
|
5569
5838
|
if (!routerAwarePoms) {
|
|
5570
5839
|
setRouteNameToComponentNameMap(/* @__PURE__ */ new Map());
|
|
@@ -5577,7 +5846,15 @@ function createDevProcessorPlugin(options) {
|
|
|
5577
5846
|
} else {
|
|
5578
5847
|
if (!resolvedRouterEntry)
|
|
5579
5848
|
throw new Error("[vue-pom-generator] router.entry is required when router introspection is enabled.");
|
|
5580
|
-
result = await parseRouterFileFromCwd(resolvedRouterEntry, {
|
|
5849
|
+
result = await parseRouterFileFromCwd(resolvedRouterEntry, {
|
|
5850
|
+
moduleShims: routerModuleShims,
|
|
5851
|
+
componentNaming: {
|
|
5852
|
+
projectRoot: projectRootRef.current,
|
|
5853
|
+
viewsDirAbs: getViewsDirAbs(),
|
|
5854
|
+
scanDirs,
|
|
5855
|
+
extraRoots: [process.cwd()]
|
|
5856
|
+
}
|
|
5857
|
+
});
|
|
5581
5858
|
}
|
|
5582
5859
|
const { routeNameMap, routePathMap } = result;
|
|
5583
5860
|
setRouteNameToComponentNameMap(routeNameMap);
|
|
@@ -5600,7 +5877,6 @@ function createDevProcessorPlugin(options) {
|
|
|
5600
5877
|
const logDebug = (message) => loggerRef.current.debug(message);
|
|
5601
5878
|
let scheduleVueFileRegenLocal = null;
|
|
5602
5879
|
const formatMs = (ms) => `${ms.toFixed(1)}ms`;
|
|
5603
|
-
const getViewsDirAbs = () => path.isAbsolute(viewsDir) ? viewsDir : path.resolve(projectRootRef.current, viewsDir);
|
|
5604
5880
|
const extractTemplateFromSfc = (source, filename) => {
|
|
5605
5881
|
const { descriptor } = compilerSfc.parse(source, {
|
|
5606
5882
|
filename: filename ?? "anonymous.vue"
|
|
@@ -5713,6 +5989,21 @@ function createDevProcessorPlugin(options) {
|
|
|
5713
5989
|
logInfo(`initial compile: ${compiledCount}/${totalVueFiles} files in ${formatMs(t1 - t0)} (components=${snapshotHierarchy.size})`);
|
|
5714
5990
|
};
|
|
5715
5991
|
const generateAggregatedFromSnapshot = (reason) => {
|
|
5992
|
+
const metrics = getGenerationMetrics(snapshotHierarchy);
|
|
5993
|
+
if (metrics.entryCount <= 0 || metrics.selectorCount <= 0) {
|
|
5994
|
+
logInfo(`generate(${reason}): skipped empty snapshot (components=${metrics.entryCount}, selectors=${metrics.selectorCount})`);
|
|
5995
|
+
return;
|
|
5996
|
+
}
|
|
5997
|
+
const generationMetricsKey = getGenerationMetricsKey(projectRootRef.current, outDir);
|
|
5998
|
+
if (reason === "startup") {
|
|
5999
|
+
const previousMetrics = devStartupMetricsByOutputKey.get(generationMetricsKey);
|
|
6000
|
+
if (previousMetrics && isLessRich(metrics, previousMetrics)) {
|
|
6001
|
+
logInfo(
|
|
6002
|
+
`generate(${reason}): skipped smaller snapshot (components=${metrics.entryCount}, selectors=${metrics.selectorCount})`
|
|
6003
|
+
);
|
|
6004
|
+
return;
|
|
6005
|
+
}
|
|
6006
|
+
}
|
|
5716
6007
|
const t0 = node_perf_hooks.performance.now();
|
|
5717
6008
|
generateFiles(snapshotHierarchy, snapshotVuePathMap, normalizedBasePagePath, {
|
|
5718
6009
|
outDir,
|
|
@@ -5727,10 +6018,15 @@ function createDevProcessorPlugin(options) {
|
|
|
5727
6018
|
testIdAttribute,
|
|
5728
6019
|
vueRouterFluentChaining: routerAwarePoms,
|
|
5729
6020
|
routerEntry: resolvedRouterEntry,
|
|
5730
|
-
routerType
|
|
6021
|
+
routerType,
|
|
6022
|
+
viewsDir,
|
|
6023
|
+
scanDirs
|
|
5731
6024
|
});
|
|
6025
|
+
if (reason === "startup") {
|
|
6026
|
+
devStartupMetricsByOutputKey.set(generationMetricsKey, metrics);
|
|
6027
|
+
}
|
|
5732
6028
|
const t1 = node_perf_hooks.performance.now();
|
|
5733
|
-
logInfo(`generate(${reason}): components=${
|
|
6029
|
+
logInfo(`generate(${reason}): components=${metrics.entryCount} selectors=${metrics.selectorCount} in ${formatMs(t1 - t0)}`);
|
|
5734
6030
|
};
|
|
5735
6031
|
const initialBuildPromise = (async () => {
|
|
5736
6032
|
const t0 = node_perf_hooks.performance.now();
|
|
@@ -5946,6 +6242,8 @@ function createSupportPlugins(options) {
|
|
|
5946
6242
|
const tsProcessor = createBuildProcessorPlugin({
|
|
5947
6243
|
componentHierarchyMap,
|
|
5948
6244
|
vueFilesPathMap,
|
|
6245
|
+
viewsDir,
|
|
6246
|
+
scanDirs,
|
|
5949
6247
|
basePageClassPath,
|
|
5950
6248
|
normalizedBasePagePath,
|
|
5951
6249
|
outDir,
|
|
@@ -6251,20 +6549,21 @@ function createVuePluginWithTestIds(options) {
|
|
|
6251
6549
|
}
|
|
6252
6550
|
];
|
|
6253
6551
|
};
|
|
6552
|
+
const runtimeNodeTransform = (node, context) => {
|
|
6553
|
+
const filename = context.filename;
|
|
6554
|
+
if (!filename || !filename.endsWith(".vue") || !isFileInScope(filename)) {
|
|
6555
|
+
return;
|
|
6556
|
+
}
|
|
6557
|
+
const transforms = getNodeTransforms(filename);
|
|
6558
|
+
const ourTransform = transforms[transforms.length - 1];
|
|
6559
|
+
return ourTransform(node, context);
|
|
6560
|
+
};
|
|
6254
6561
|
const templateCompilerOptions = {
|
|
6255
6562
|
...userCompilerOptions,
|
|
6256
6563
|
prefixIdentifiers: true,
|
|
6257
6564
|
nodeTransforms: [
|
|
6258
6565
|
...userNodeTransforms,
|
|
6259
|
-
|
|
6260
|
-
const filename = context.filename;
|
|
6261
|
-
if (!filename || !filename.endsWith(".vue") || !isFileInScope(filename)) {
|
|
6262
|
-
return;
|
|
6263
|
-
}
|
|
6264
|
-
const transforms = getNodeTransforms(filename);
|
|
6265
|
-
const ourTransform = transforms[transforms.length - 1];
|
|
6266
|
-
return ourTransform(node, context);
|
|
6267
|
-
}
|
|
6566
|
+
runtimeNodeTransform
|
|
6268
6567
|
]
|
|
6269
6568
|
};
|
|
6270
6569
|
const metadataCollectorPlugin = {
|
|
@@ -6303,7 +6602,42 @@ function createVuePluginWithTestIds(options) {
|
|
|
6303
6602
|
...vueOptions,
|
|
6304
6603
|
template
|
|
6305
6604
|
});
|
|
6306
|
-
|
|
6605
|
+
const nuxtVueBridgePlugin = {
|
|
6606
|
+
name: "vue-pom-generator-nuxt-vue-bridge",
|
|
6607
|
+
apply: "serve",
|
|
6608
|
+
configResolved(config) {
|
|
6609
|
+
const viteVuePlugin = config.plugins.find((plugin) => {
|
|
6610
|
+
return typeof plugin === "object" && plugin !== null && "name" in plugin && plugin.name === "vite:vue" && "api" in plugin;
|
|
6611
|
+
});
|
|
6612
|
+
const api = viteVuePlugin?.api;
|
|
6613
|
+
if (!api) {
|
|
6614
|
+
loggerRef.current.warn("[vue-pom-generator] Nuxt bridge could not find vite:vue plugin to patch.");
|
|
6615
|
+
return;
|
|
6616
|
+
}
|
|
6617
|
+
const currentOptions = api.options ?? {};
|
|
6618
|
+
const currentTemplate = currentOptions.template ?? {};
|
|
6619
|
+
const currentCompilerOptions = currentTemplate.compilerOptions ?? {};
|
|
6620
|
+
const currentNodeTransforms = currentCompilerOptions.nodeTransforms ?? [];
|
|
6621
|
+
if (currentNodeTransforms.includes(runtimeNodeTransform)) {
|
|
6622
|
+
return;
|
|
6623
|
+
}
|
|
6624
|
+
api.options = {
|
|
6625
|
+
...currentOptions,
|
|
6626
|
+
template: {
|
|
6627
|
+
...currentTemplate,
|
|
6628
|
+
compilerOptions: {
|
|
6629
|
+
...currentCompilerOptions,
|
|
6630
|
+
prefixIdentifiers: true,
|
|
6631
|
+
nodeTransforms: [
|
|
6632
|
+
...currentNodeTransforms,
|
|
6633
|
+
runtimeNodeTransform
|
|
6634
|
+
]
|
|
6635
|
+
}
|
|
6636
|
+
}
|
|
6637
|
+
};
|
|
6638
|
+
}
|
|
6639
|
+
};
|
|
6640
|
+
return { metadataCollectorPlugin, internalVuePlugin, nuxtVueBridgePlugin };
|
|
6307
6641
|
}
|
|
6308
6642
|
function assertNonEmptyString(value, name) {
|
|
6309
6643
|
if (!value || !value.trim()) {
|
|
@@ -6429,7 +6763,7 @@ function createVuePomGeneratorPlugins(options = {}) {
|
|
|
6429
6763
|
const semanticNameMap = /* @__PURE__ */ new Map();
|
|
6430
6764
|
const componentHierarchyMap = /* @__PURE__ */ new Map();
|
|
6431
6765
|
const vueFilesPathMap = /* @__PURE__ */ new Map();
|
|
6432
|
-
const { metadataCollectorPlugin, internalVuePlugin } = createVuePluginWithTestIds({
|
|
6766
|
+
const { metadataCollectorPlugin, internalVuePlugin, nuxtVueBridgePlugin } = createVuePluginWithTestIds({
|
|
6433
6767
|
vueOptions,
|
|
6434
6768
|
existingIdBehavior,
|
|
6435
6769
|
nameCollisionBehavior,
|
|
@@ -6480,7 +6814,7 @@ function createVuePomGeneratorPlugins(options = {}) {
|
|
|
6480
6814
|
const resultPlugins = [
|
|
6481
6815
|
configPlugin,
|
|
6482
6816
|
metadataCollectorPlugin,
|
|
6483
|
-
...isNuxt ? [] : [internalVuePlugin],
|
|
6817
|
+
...isNuxt ? [nuxtVueBridgePlugin] : [internalVuePlugin],
|
|
6484
6818
|
...supportPlugins
|
|
6485
6819
|
];
|
|
6486
6820
|
if (!generationEnabled) {
|
|
@@ -6488,7 +6822,7 @@ function createVuePomGeneratorPlugins(options = {}) {
|
|
|
6488
6822
|
return [
|
|
6489
6823
|
configPlugin,
|
|
6490
6824
|
metadataCollectorPlugin,
|
|
6491
|
-
...isNuxt ? [] : [internalVuePlugin],
|
|
6825
|
+
...isNuxt ? [nuxtVueBridgePlugin] : [internalVuePlugin],
|
|
6492
6826
|
virtualModules
|
|
6493
6827
|
];
|
|
6494
6828
|
}
|