@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.mjs
CHANGED
|
@@ -2528,6 +2528,53 @@ Fix: make the element identifiable (e.g. add id/name/inner text or use a more sp
|
|
|
2528
2528
|
registerGeneratedMethodSignature(generatedName, signature);
|
|
2529
2529
|
}
|
|
2530
2530
|
}
|
|
2531
|
+
function safeRealpath(value) {
|
|
2532
|
+
try {
|
|
2533
|
+
if (fs.existsSync(value)) {
|
|
2534
|
+
return fs.realpathSync(value);
|
|
2535
|
+
}
|
|
2536
|
+
} catch {
|
|
2537
|
+
return value;
|
|
2538
|
+
}
|
|
2539
|
+
const parent = path.dirname(value);
|
|
2540
|
+
if (!parent || parent === value) {
|
|
2541
|
+
return value;
|
|
2542
|
+
}
|
|
2543
|
+
const resolvedParent = safeRealpath(parent);
|
|
2544
|
+
return resolvedParent === parent ? value : path.join(resolvedParent, path.basename(value));
|
|
2545
|
+
}
|
|
2546
|
+
function resolveComponentNameFromPath(options) {
|
|
2547
|
+
const { projectRoot, viewsDirAbs, scanDirs, extraRoots = [] } = options;
|
|
2548
|
+
const cleanFilename = options.filename.includes("?") ? options.filename.substring(0, options.filename.indexOf("?")) : options.filename;
|
|
2549
|
+
const absFilename = path.isAbsolute(cleanFilename) ? cleanFilename : path.resolve(projectRoot, cleanFilename);
|
|
2550
|
+
const normalizedAbsFilename = path.normalize(safeRealpath(absFilename));
|
|
2551
|
+
const rootBases = [projectRoot, ...extraRoots.filter((r) => r !== projectRoot)];
|
|
2552
|
+
const roots = [viewsDirAbs, ...rootBases.flatMap((base) => scanDirs.map((d) => path.resolve(base, d)))];
|
|
2553
|
+
for (const base of rootBases) {
|
|
2554
|
+
for (const dir of scanDirs) {
|
|
2555
|
+
const absDir = path.resolve(base, dir);
|
|
2556
|
+
try {
|
|
2557
|
+
const pagesDir = path.join(absDir, "pages");
|
|
2558
|
+
if (fs.existsSync(pagesDir))
|
|
2559
|
+
roots.push(pagesDir);
|
|
2560
|
+
const componentsDir = path.join(absDir, "components");
|
|
2561
|
+
if (fs.existsSync(componentsDir))
|
|
2562
|
+
roots.push(componentsDir);
|
|
2563
|
+
} catch {
|
|
2564
|
+
}
|
|
2565
|
+
}
|
|
2566
|
+
}
|
|
2567
|
+
const potentialRoots = Array.from(new Set(roots.map((r) => path.normalize(safeRealpath(r))))).sort((a, b) => b.length - a.length);
|
|
2568
|
+
for (const root of potentialRoots) {
|
|
2569
|
+
if (normalizedAbsFilename.startsWith(root + path.sep) || normalizedAbsFilename === root) {
|
|
2570
|
+
const rel = path.relative(root, normalizedAbsFilename);
|
|
2571
|
+
const parsed = path.parse(rel);
|
|
2572
|
+
const segments = path.join(parsed.dir, parsed.name);
|
|
2573
|
+
return toPascalCase(segments);
|
|
2574
|
+
}
|
|
2575
|
+
}
|
|
2576
|
+
return toPascalCase(path.parse(normalizedAbsFilename).name);
|
|
2577
|
+
}
|
|
2531
2578
|
let routerIntrospectionQueue = Promise.resolve();
|
|
2532
2579
|
async function runRouterIntrospectionExclusive(fn) {
|
|
2533
2580
|
const prev = routerIntrospectionQueue.catch(() => void 0);
|
|
@@ -2807,20 +2854,84 @@ function getRouteParamMeta(router, record, paramNames) {
|
|
|
2807
2854
|
}
|
|
2808
2855
|
});
|
|
2809
2856
|
}
|
|
2810
|
-
function
|
|
2811
|
-
const
|
|
2812
|
-
|
|
2857
|
+
function normalizeRouteComponentFilePath(filePath, options = {}) {
|
|
2858
|
+
const queryIndex = filePath.indexOf("?");
|
|
2859
|
+
const cleanPath = queryIndex === -1 ? filePath : filePath.slice(0, queryIndex);
|
|
2860
|
+
if (cleanPath.startsWith("/@fs/")) {
|
|
2861
|
+
return path.normalize(cleanPath.slice("/@fs/".length));
|
|
2862
|
+
}
|
|
2863
|
+
if (path.isAbsolute(cleanPath)) {
|
|
2864
|
+
if (fs.existsSync(cleanPath) || !options.rootDir)
|
|
2865
|
+
return path.normalize(cleanPath);
|
|
2866
|
+
return path.normalize(path.resolve(options.rootDir, `.${cleanPath}`));
|
|
2867
|
+
}
|
|
2868
|
+
if (!options.rootDir)
|
|
2813
2869
|
return null;
|
|
2870
|
+
return path.normalize(path.resolve(options.rootDir, cleanPath));
|
|
2871
|
+
}
|
|
2872
|
+
function getComponentInfoFromVueComponent(comp, options = {}) {
|
|
2873
|
+
if (!comp) {
|
|
2874
|
+
return {
|
|
2875
|
+
componentName: null,
|
|
2876
|
+
filePath: null
|
|
2877
|
+
};
|
|
2878
|
+
}
|
|
2879
|
+
let componentName = null;
|
|
2880
|
+
let filePath = null;
|
|
2814
2881
|
if (typeof comp.__file === "string" && comp.__file.length) {
|
|
2882
|
+
filePath = normalizeRouteComponentFilePath(comp.__file, { rootDir: options.rootDir });
|
|
2815
2883
|
const base = path.posix.basename(path.posix.normalize(comp.__file));
|
|
2816
2884
|
if (base.toLowerCase().endsWith(".vue"))
|
|
2817
|
-
|
|
2885
|
+
componentName = base.slice(0, -".vue".length);
|
|
2818
2886
|
}
|
|
2819
|
-
if (typeof comp.__name === "string" && comp.__name.length)
|
|
2820
|
-
|
|
2821
|
-
if (typeof comp.name === "string" && comp.name.length)
|
|
2822
|
-
|
|
2823
|
-
|
|
2887
|
+
if (!componentName && typeof comp.__name === "string" && comp.__name.length)
|
|
2888
|
+
componentName = comp.__name;
|
|
2889
|
+
if (!componentName && options.allowFunctionNameFallback !== false && typeof comp.name === "string" && comp.name.length) {
|
|
2890
|
+
componentName = comp.name;
|
|
2891
|
+
}
|
|
2892
|
+
return {
|
|
2893
|
+
componentName,
|
|
2894
|
+
filePath
|
|
2895
|
+
};
|
|
2896
|
+
}
|
|
2897
|
+
async function getComponentInfoFromRouteRecord(record, options = {}) {
|
|
2898
|
+
const comp = record.components?.default;
|
|
2899
|
+
if (!comp) {
|
|
2900
|
+
return {
|
|
2901
|
+
componentName: null,
|
|
2902
|
+
filePath: null
|
|
2903
|
+
};
|
|
2904
|
+
}
|
|
2905
|
+
if (typeof comp !== "function") {
|
|
2906
|
+
return getComponentInfoFromVueComponent(comp, options);
|
|
2907
|
+
}
|
|
2908
|
+
const directInfo = getComponentInfoFromVueComponent(comp, {
|
|
2909
|
+
allowFunctionNameFallback: false,
|
|
2910
|
+
rootDir: options.rootDir
|
|
2911
|
+
});
|
|
2912
|
+
if (directInfo.componentName || directInfo.filePath)
|
|
2913
|
+
return directInfo;
|
|
2914
|
+
try {
|
|
2915
|
+
const loaded = await comp();
|
|
2916
|
+
const resolved = loaded && typeof loaded === "object" && "default" in loaded ? loaded.default : loaded;
|
|
2917
|
+
const loadedInfo = getComponentInfoFromVueComponent(resolved, options);
|
|
2918
|
+
if (loadedInfo.componentName || loadedInfo.filePath)
|
|
2919
|
+
return loadedInfo;
|
|
2920
|
+
} catch {
|
|
2921
|
+
}
|
|
2922
|
+
return getComponentInfoFromVueComponent(comp, options);
|
|
2923
|
+
}
|
|
2924
|
+
function resolveIntrospectedComponentName(componentInfo, componentNaming) {
|
|
2925
|
+
if (componentInfo.filePath && componentNaming) {
|
|
2926
|
+
return resolveComponentNameFromPath({
|
|
2927
|
+
filename: componentInfo.filePath,
|
|
2928
|
+
projectRoot: componentNaming.projectRoot,
|
|
2929
|
+
viewsDirAbs: componentNaming.viewsDirAbs,
|
|
2930
|
+
scanDirs: componentNaming.scanDirs,
|
|
2931
|
+
extraRoots: componentNaming.extraRoots
|
|
2932
|
+
});
|
|
2933
|
+
}
|
|
2934
|
+
return componentInfo.componentName;
|
|
2824
2935
|
}
|
|
2825
2936
|
async function ensureDomShim() {
|
|
2826
2937
|
const domShimHtml = "<!doctype html><html><head></head><body><div id='app'></div></body></html>";
|
|
@@ -3066,7 +3177,8 @@ async function parseRouterFileFromCwd(routerEntryPath, options = {}) {
|
|
|
3066
3177
|
const routePathMap = /* @__PURE__ */ new Map();
|
|
3067
3178
|
const routeMetaEntries = [];
|
|
3068
3179
|
for (const r of router.getRoutes()) {
|
|
3069
|
-
const
|
|
3180
|
+
const componentInfo = await getComponentInfoFromRouteRecord(r, { rootDir: cwd });
|
|
3181
|
+
const componentName = resolveIntrospectedComponentName(componentInfo, options.componentNaming);
|
|
3070
3182
|
if (!componentName)
|
|
3071
3183
|
continue;
|
|
3072
3184
|
if (typeof r.path === "string" && r.path.length) {
|
|
@@ -3124,9 +3236,20 @@ function resolveRouterEntry(projectRoot, routerEntry) {
|
|
|
3124
3236
|
const root = projectRoot ?? process.cwd();
|
|
3125
3237
|
return path.isAbsolute(routerEntry) ? routerEntry : path.resolve(root, routerEntry);
|
|
3126
3238
|
}
|
|
3127
|
-
async function getRouteMetaByComponent(projectRoot, routerEntry, routerType) {
|
|
3239
|
+
async function getRouteMetaByComponent(projectRoot, routerEntry, routerType, options = {}) {
|
|
3128
3240
|
const root = projectRoot ?? process.cwd();
|
|
3129
|
-
const
|
|
3241
|
+
const viewsDir = options.viewsDir ?? "src/views";
|
|
3242
|
+
const viewsDirAbs = path.isAbsolute(viewsDir) ? viewsDir : path.resolve(root, viewsDir);
|
|
3243
|
+
const scanDirs = options.scanDirs?.length ? options.scanDirs : ["src"];
|
|
3244
|
+
const extraRoots = process.cwd() !== root ? [process.cwd()] : [];
|
|
3245
|
+
const { routeMetaEntries } = routerType === "nuxt" ? await introspectNuxtPages(root) : await parseRouterFileFromCwd(resolveRouterEntry(root, routerEntry), {
|
|
3246
|
+
componentNaming: {
|
|
3247
|
+
projectRoot: root,
|
|
3248
|
+
viewsDirAbs,
|
|
3249
|
+
scanDirs,
|
|
3250
|
+
extraRoots
|
|
3251
|
+
}
|
|
3252
|
+
});
|
|
3130
3253
|
const map = /* @__PURE__ */ new Map();
|
|
3131
3254
|
for (const entry of routeMetaEntries) {
|
|
3132
3255
|
const list = map.get(entry.componentName) ?? [];
|
|
@@ -3303,11 +3426,17 @@ async function generateFiles(componentHierarchyMap, vueFilesPathMap, basePageCla
|
|
|
3303
3426
|
csharp,
|
|
3304
3427
|
vueRouterFluentChaining,
|
|
3305
3428
|
routerEntry,
|
|
3306
|
-
routerType
|
|
3429
|
+
routerType,
|
|
3430
|
+
viewsDir,
|
|
3431
|
+
scanDirs,
|
|
3432
|
+
routeMetaByComponent: routeMetaByComponentOverride
|
|
3307
3433
|
} = options;
|
|
3308
3434
|
const emitLanguages = emitLanguagesOverride?.length ? emitLanguagesOverride : ["ts"];
|
|
3309
3435
|
const outDir = outDirOverride ?? "./pom";
|
|
3310
|
-
const routeMetaByComponent = vueRouterFluentChaining ? await getRouteMetaByComponent(projectRoot, routerEntry, routerType
|
|
3436
|
+
const routeMetaByComponent = routeMetaByComponentOverride ?? (vueRouterFluentChaining ? await getRouteMetaByComponent(projectRoot, routerEntry, routerType, {
|
|
3437
|
+
viewsDir,
|
|
3438
|
+
scanDirs
|
|
3439
|
+
}) : void 0);
|
|
3311
3440
|
const generatedFilePaths = [];
|
|
3312
3441
|
const writeGeneratedFile = (file) => {
|
|
3313
3442
|
const resolvedFilePath = path.resolve(file.filePath);
|
|
@@ -4407,10 +4536,38 @@ function getConstructor(childrenComponent, componentHierarchyMap, attachmentsFor
|
|
|
4407
4536
|
return `${content}
|
|
4408
4537
|
`;
|
|
4409
4538
|
}
|
|
4539
|
+
function getGenerationMetrics(componentHierarchyMap) {
|
|
4540
|
+
let selectorCount = 0;
|
|
4541
|
+
let generatedMethodCount = 0;
|
|
4542
|
+
for (const deps of componentHierarchyMap.values()) {
|
|
4543
|
+
selectorCount += deps.dataTestIdSet?.size ?? 0;
|
|
4544
|
+
generatedMethodCount += deps.generatedMethods?.size ?? 0;
|
|
4545
|
+
}
|
|
4546
|
+
return {
|
|
4547
|
+
entryCount: componentHierarchyMap.size,
|
|
4548
|
+
selectorCount,
|
|
4549
|
+
generatedMethodCount
|
|
4550
|
+
};
|
|
4551
|
+
}
|
|
4552
|
+
function isLessRich(current, previous) {
|
|
4553
|
+
if (current.entryCount !== previous.entryCount) {
|
|
4554
|
+
return current.entryCount < previous.entryCount;
|
|
4555
|
+
}
|
|
4556
|
+
if (current.selectorCount !== previous.selectorCount) {
|
|
4557
|
+
return current.selectorCount < previous.selectorCount;
|
|
4558
|
+
}
|
|
4559
|
+
return current.generatedMethodCount < previous.generatedMethodCount;
|
|
4560
|
+
}
|
|
4561
|
+
function getGenerationMetricsKey(projectRoot, outDir) {
|
|
4562
|
+
return path.resolve(projectRoot, outDir ?? "./pom");
|
|
4563
|
+
}
|
|
4564
|
+
const buildGenerationMetricsByOutputKey = /* @__PURE__ */ new Map();
|
|
4410
4565
|
function createBuildProcessorPlugin(options) {
|
|
4411
4566
|
const {
|
|
4412
4567
|
componentHierarchyMap,
|
|
4413
4568
|
vueFilesPathMap,
|
|
4569
|
+
viewsDir,
|
|
4570
|
+
scanDirs,
|
|
4414
4571
|
basePageClassPath,
|
|
4415
4572
|
normalizedBasePagePath,
|
|
4416
4573
|
outDir,
|
|
@@ -4429,7 +4586,6 @@ function createBuildProcessorPlugin(options) {
|
|
|
4429
4586
|
routerModuleShims,
|
|
4430
4587
|
loggerRef
|
|
4431
4588
|
} = options;
|
|
4432
|
-
let lastGeneratedEntryCount = 0;
|
|
4433
4589
|
return {
|
|
4434
4590
|
name: "vue-pom-generator-build",
|
|
4435
4591
|
// This plugin exists to generate code on build output; it is not needed during dev-server HMR.
|
|
@@ -4447,7 +4603,14 @@ function createBuildProcessorPlugin(options) {
|
|
|
4447
4603
|
} else {
|
|
4448
4604
|
if (!resolvedRouterEntry)
|
|
4449
4605
|
throw new Error("[vue-pom-generator] router.entry is required when router introspection is enabled.");
|
|
4450
|
-
result = await parseRouterFileFromCwd(resolvedRouterEntry, {
|
|
4606
|
+
result = await parseRouterFileFromCwd(resolvedRouterEntry, {
|
|
4607
|
+
moduleShims: routerModuleShims,
|
|
4608
|
+
componentNaming: {
|
|
4609
|
+
projectRoot: projectRootRef.current,
|
|
4610
|
+
viewsDirAbs: path.isAbsolute(viewsDir) ? viewsDir : path.resolve(projectRootRef.current, viewsDir),
|
|
4611
|
+
scanDirs
|
|
4612
|
+
}
|
|
4613
|
+
});
|
|
4451
4614
|
}
|
|
4452
4615
|
const { routeNameMap, routePathMap } = result;
|
|
4453
4616
|
setRouteNameToComponentNameMap(routeNameMap);
|
|
@@ -4476,11 +4639,13 @@ function createBuildProcessorPlugin(options) {
|
|
|
4476
4639
|
this.addWatchFile(pointerPath);
|
|
4477
4640
|
},
|
|
4478
4641
|
buildEnd() {
|
|
4479
|
-
const
|
|
4480
|
-
if (entryCount <= 0) {
|
|
4642
|
+
const metrics = getGenerationMetrics(componentHierarchyMap);
|
|
4643
|
+
if (metrics.entryCount <= 0 || metrics.selectorCount <= 0) {
|
|
4481
4644
|
return;
|
|
4482
4645
|
}
|
|
4483
|
-
|
|
4646
|
+
const generationMetricsKey = getGenerationMetricsKey(projectRootRef.current, outDir);
|
|
4647
|
+
const previousMetrics = buildGenerationMetricsByOutputKey.get(generationMetricsKey);
|
|
4648
|
+
if (previousMetrics && isLessRich(metrics, previousMetrics)) {
|
|
4484
4649
|
return;
|
|
4485
4650
|
}
|
|
4486
4651
|
generateFiles(componentHierarchyMap, vueFilesPathMap, normalizedBasePagePath, {
|
|
@@ -4496,10 +4661,12 @@ function createBuildProcessorPlugin(options) {
|
|
|
4496
4661
|
testIdAttribute,
|
|
4497
4662
|
vueRouterFluentChaining: routerAwarePoms,
|
|
4498
4663
|
routerEntry: resolvedRouterEntry,
|
|
4499
|
-
routerType
|
|
4664
|
+
routerType,
|
|
4665
|
+
viewsDir,
|
|
4666
|
+
scanDirs
|
|
4500
4667
|
});
|
|
4501
|
-
|
|
4502
|
-
loggerRef.current.info(`generated POMs (${entryCount} entries)`);
|
|
4668
|
+
buildGenerationMetricsByOutputKey.set(generationMetricsKey, metrics);
|
|
4669
|
+
loggerRef.current.info(`generated POMs (${metrics.entryCount} entries, ${metrics.selectorCount} selectors)`);
|
|
4503
4670
|
},
|
|
4504
4671
|
closeBundle() {
|
|
4505
4672
|
loggerRef.current.info("build complete");
|
|
@@ -4537,6 +4704,89 @@ function toKebabCaseTag(tag) {
|
|
|
4537
4704
|
}
|
|
4538
4705
|
return result;
|
|
4539
4706
|
}
|
|
4707
|
+
function getStaticAttributeContent(element, name) {
|
|
4708
|
+
const attr = element.props.find((prop) => {
|
|
4709
|
+
return prop.type === NodeTypes.ATTRIBUTE && prop.name === name;
|
|
4710
|
+
});
|
|
4711
|
+
return attr?.value?.content?.trim() || null;
|
|
4712
|
+
}
|
|
4713
|
+
function getNativeHtmlControlRole(element) {
|
|
4714
|
+
const tag = (element.tag || "").toLowerCase();
|
|
4715
|
+
const type = (getStaticAttributeContent(element, "type") || "").toLowerCase();
|
|
4716
|
+
if (tag === "textarea") {
|
|
4717
|
+
return "input";
|
|
4718
|
+
}
|
|
4719
|
+
if (tag === "select") {
|
|
4720
|
+
return "select";
|
|
4721
|
+
}
|
|
4722
|
+
if (tag !== "input") {
|
|
4723
|
+
return null;
|
|
4724
|
+
}
|
|
4725
|
+
if (type === "radio") {
|
|
4726
|
+
return "radio";
|
|
4727
|
+
}
|
|
4728
|
+
if (type === "checkbox") {
|
|
4729
|
+
return "checkbox";
|
|
4730
|
+
}
|
|
4731
|
+
return "input";
|
|
4732
|
+
}
|
|
4733
|
+
function normalizeControlLabelText(value) {
|
|
4734
|
+
const normalized = (value ?? "").replace(/\*/g, " ").replace(/\s+/g, " ").trim();
|
|
4735
|
+
return normalized || null;
|
|
4736
|
+
}
|
|
4737
|
+
function getLabelNodeText(labelNode) {
|
|
4738
|
+
for (const child of labelNode.children || []) {
|
|
4739
|
+
if (child.type === NodeTypes.TEXT) {
|
|
4740
|
+
const normalized2 = normalizeControlLabelText(child.content);
|
|
4741
|
+
if (normalized2) {
|
|
4742
|
+
return normalized2;
|
|
4743
|
+
}
|
|
4744
|
+
continue;
|
|
4745
|
+
}
|
|
4746
|
+
if (child.type !== NodeTypes.ELEMENT) {
|
|
4747
|
+
continue;
|
|
4748
|
+
}
|
|
4749
|
+
if (getNativeHtmlControlRole(child)) {
|
|
4750
|
+
continue;
|
|
4751
|
+
}
|
|
4752
|
+
const normalized = normalizeControlLabelText(getInnerText(child));
|
|
4753
|
+
if (normalized) {
|
|
4754
|
+
return normalized;
|
|
4755
|
+
}
|
|
4756
|
+
}
|
|
4757
|
+
return normalizeControlLabelText(getInnerText(labelNode));
|
|
4758
|
+
}
|
|
4759
|
+
function getAssociatedLabelText(element, hierarchyMap2) {
|
|
4760
|
+
let parent = hierarchyMap2.get(element) || null;
|
|
4761
|
+
while (parent) {
|
|
4762
|
+
if (parent.tag === "label") {
|
|
4763
|
+
return getLabelNodeText(parent);
|
|
4764
|
+
}
|
|
4765
|
+
parent = hierarchyMap2.get(parent) || null;
|
|
4766
|
+
}
|
|
4767
|
+
const id = getStaticAttributeContent(element, "id");
|
|
4768
|
+
if (!id) {
|
|
4769
|
+
return null;
|
|
4770
|
+
}
|
|
4771
|
+
const candidates = /* @__PURE__ */ new Set();
|
|
4772
|
+
for (const child of hierarchyMap2.keys()) {
|
|
4773
|
+
candidates.add(child);
|
|
4774
|
+
}
|
|
4775
|
+
for (const maybeParent of hierarchyMap2.values()) {
|
|
4776
|
+
if (maybeParent) {
|
|
4777
|
+
candidates.add(maybeParent);
|
|
4778
|
+
}
|
|
4779
|
+
}
|
|
4780
|
+
for (const candidate of candidates) {
|
|
4781
|
+
if (candidate.tag !== "label") {
|
|
4782
|
+
continue;
|
|
4783
|
+
}
|
|
4784
|
+
if (getStaticAttributeContent(candidate, "for") === id) {
|
|
4785
|
+
return getLabelNodeText(candidate);
|
|
4786
|
+
}
|
|
4787
|
+
}
|
|
4788
|
+
return null;
|
|
4789
|
+
}
|
|
4540
4790
|
function normalizeSearchRoots(wrapperSearchRoots) {
|
|
4541
4791
|
const normalized = /* @__PURE__ */ new Set();
|
|
4542
4792
|
for (const root of wrapperSearchRoots) {
|
|
@@ -5069,14 +5319,14 @@ function createTestIdTransform(componentName, componentHierarchyMap, nativeWrapp
|
|
|
5069
5319
|
const warn = options.warn;
|
|
5070
5320
|
const vueFilesPathMap = options.vueFilesPathMap;
|
|
5071
5321
|
const wrapperSearchRoots = options.wrapperSearchRoots ?? [];
|
|
5072
|
-
const
|
|
5322
|
+
const safeRealpath2 = (p) => {
|
|
5073
5323
|
try {
|
|
5074
5324
|
return fs.existsSync(p) ? fs.realpathSync(p) : p;
|
|
5075
5325
|
} catch {
|
|
5076
5326
|
return p;
|
|
5077
5327
|
}
|
|
5078
5328
|
};
|
|
5079
|
-
const normalizedViewsDirAbs = path.normalize(
|
|
5329
|
+
const normalizedViewsDirAbs = path.normalize(safeRealpath2(path.resolve(viewsDirAbs)));
|
|
5080
5330
|
const generatedMethodContentByComponent = /* @__PURE__ */ new Map();
|
|
5081
5331
|
const lastConditionalHintByParent = /* @__PURE__ */ new WeakMap();
|
|
5082
5332
|
const lastConditionalMergeGroupByParent = /* @__PURE__ */ new WeakMap();
|
|
@@ -5164,7 +5414,7 @@ function createTestIdTransform(componentName, componentHierarchyMap, nativeWrapp
|
|
|
5164
5414
|
const parentIsRoot = context?.parent?.type === NodeTypes.ROOT;
|
|
5165
5415
|
const parentElement = !parentIsRoot && context?.parent?.type === NodeTypes.ELEMENT ? context.parent : null;
|
|
5166
5416
|
hierarchyMap.set(element, parentElement);
|
|
5167
|
-
const normalizeFilePath = (filePath) => path.normalize(
|
|
5417
|
+
const normalizeFilePath = (filePath) => path.normalize(safeRealpath2(path.resolve(filePath)));
|
|
5168
5418
|
const normalizedFilePath = normalizeFilePath(context.filename);
|
|
5169
5419
|
const parentComponentName = componentName;
|
|
5170
5420
|
const dependencies = (() => {
|
|
@@ -5204,7 +5454,9 @@ function createTestIdTransform(componentName, componentHierarchyMap, nativeWrapp
|
|
|
5204
5454
|
}
|
|
5205
5455
|
}
|
|
5206
5456
|
const getBestAvailableKeyValue = () => {
|
|
5207
|
-
const
|
|
5457
|
+
const parentNode = context.parent && typeof context.parent === "object" ? context.parent : null;
|
|
5458
|
+
const isDirectVForChild = parentNode?.type === NodeTypes.FOR;
|
|
5459
|
+
const vForKey = (isDirectVForChild ? getKeyDirectiveValue(element, context) : null) || getContainedInVForDirectiveKeyValue(context, element, hierarchyMap);
|
|
5208
5460
|
if (vForKey) return vForKey;
|
|
5209
5461
|
return getContainedInSlotDataKeyValue(element, hierarchyMap);
|
|
5210
5462
|
};
|
|
@@ -5361,6 +5613,50 @@ Fix: remove the explicit ${attrLabel}, or change existingIdBehavior to "overwrit
|
|
|
5361
5613
|
});
|
|
5362
5614
|
return;
|
|
5363
5615
|
}
|
|
5616
|
+
const nativeHtmlRole = getNativeHtmlControlRole(element);
|
|
5617
|
+
if (nativeHtmlRole) {
|
|
5618
|
+
const rawIdentifier = getStaticAttributeContent(element, "id") || getStaticAttributeContent(element, "name");
|
|
5619
|
+
const labelText = getAssociatedLabelText(element, hierarchyMap);
|
|
5620
|
+
const { vModel, modelValue } = getModelBindingValues(element);
|
|
5621
|
+
const bindingHint = modelValue || vModel || null;
|
|
5622
|
+
const labelToken = labelText ? toPascalCase(labelText) : "";
|
|
5623
|
+
const bindingToken = bindingHint ? toPascalCase(bindingHint) : "";
|
|
5624
|
+
let identifierToken = null;
|
|
5625
|
+
let semanticNameHint2;
|
|
5626
|
+
if (nativeHtmlRole === "radio" || nativeHtmlRole === "checkbox") {
|
|
5627
|
+
if (rawIdentifier) {
|
|
5628
|
+
identifierToken = rawIdentifier;
|
|
5629
|
+
semanticNameHint2 = rawIdentifier;
|
|
5630
|
+
} else if (bindingToken && labelToken) {
|
|
5631
|
+
identifierToken = `${bindingToken}${labelToken}`;
|
|
5632
|
+
semanticNameHint2 = `${bindingHint || bindingToken} ${labelText || labelToken}`;
|
|
5633
|
+
} else if (labelToken) {
|
|
5634
|
+
identifierToken = labelToken;
|
|
5635
|
+
semanticNameHint2 = labelText || labelToken;
|
|
5636
|
+
} else if (bindingToken) {
|
|
5637
|
+
identifierToken = bindingToken;
|
|
5638
|
+
semanticNameHint2 = bindingHint || bindingToken;
|
|
5639
|
+
}
|
|
5640
|
+
} else if (rawIdentifier) {
|
|
5641
|
+
identifierToken = rawIdentifier;
|
|
5642
|
+
semanticNameHint2 = rawIdentifier;
|
|
5643
|
+
} else if (labelToken) {
|
|
5644
|
+
identifierToken = labelToken;
|
|
5645
|
+
semanticNameHint2 = labelText || labelToken;
|
|
5646
|
+
} else if (bindingToken) {
|
|
5647
|
+
identifierToken = bindingToken;
|
|
5648
|
+
semanticNameHint2 = bindingHint || bindingToken;
|
|
5649
|
+
}
|
|
5650
|
+
if (identifierToken) {
|
|
5651
|
+
const preferredGeneratedValue = bestKeyPlaceholder ? templateAttributeValue(`${componentName}-${bestKeyPlaceholder}-${identifierToken}-${nativeHtmlRole}`) : staticAttributeValue(`${componentName}-${identifierToken}-${nativeHtmlRole}`);
|
|
5652
|
+
applyResolvedDataTestIdForElement({
|
|
5653
|
+
preferredGeneratedValue,
|
|
5654
|
+
nativeRoleOverride: nativeHtmlRole,
|
|
5655
|
+
semanticNameHint: semanticNameHint2 || conditionalHint || void 0
|
|
5656
|
+
});
|
|
5657
|
+
return;
|
|
5658
|
+
}
|
|
5659
|
+
}
|
|
5364
5660
|
const innerText = getInnerText(element) || null;
|
|
5365
5661
|
const toDirective = nodeHasToDirective(element);
|
|
5366
5662
|
if (toDirective) {
|
|
@@ -5402,12 +5698,14 @@ Fix: remove the explicit ${attrLabel}, or change existingIdBehavior to "overwrit
|
|
|
5402
5698
|
});
|
|
5403
5699
|
const clickHint = trimLeadingSeparators(clickSuffix) || void 0;
|
|
5404
5700
|
const idOrName = getIdOrName(element) || void 0;
|
|
5405
|
-
const
|
|
5701
|
+
const semanticHintCandidates = [clickHint, idOrName, innerText, conditionalHint].map((value) => (value ?? "").trim()).filter(Boolean).filter((value, index, values) => values.indexOf(value) === index);
|
|
5702
|
+
const [semanticNameHint2, ...semanticNameHintAlternates] = semanticHintCandidates;
|
|
5406
5703
|
const pomMergeKey = clickHint ? `click:hint:${clickHint}` : void 0;
|
|
5407
5704
|
const testId = getClickDataTestId(clickSuffix);
|
|
5408
5705
|
applyResolvedDataTestIdForElement({
|
|
5409
5706
|
preferredGeneratedValue: testId,
|
|
5410
5707
|
semanticNameHint: semanticNameHint2,
|
|
5708
|
+
semanticNameHintAlternates,
|
|
5411
5709
|
pomMergeKey
|
|
5412
5710
|
});
|
|
5413
5711
|
{
|
|
@@ -5448,37 +5746,7 @@ Fix: remove the explicit ${attrLabel}, or change existingIdBehavior to "overwrit
|
|
|
5448
5746
|
}
|
|
5449
5747
|
};
|
|
5450
5748
|
}
|
|
5451
|
-
|
|
5452
|
-
const { projectRoot, viewsDirAbs, scanDirs, extraRoots = [] } = options;
|
|
5453
|
-
const cleanFilename = options.filename.includes("?") ? options.filename.substring(0, options.filename.indexOf("?")) : options.filename;
|
|
5454
|
-
const absFilename = path.isAbsolute(cleanFilename) ? cleanFilename : path.resolve(projectRoot, cleanFilename);
|
|
5455
|
-
const rootBases = [projectRoot, ...extraRoots.filter((r) => r !== projectRoot)];
|
|
5456
|
-
const roots = [viewsDirAbs, ...rootBases.flatMap((base) => scanDirs.map((d) => path.resolve(base, d)))];
|
|
5457
|
-
for (const base of rootBases) {
|
|
5458
|
-
for (const dir of scanDirs) {
|
|
5459
|
-
const absDir = path.resolve(base, dir);
|
|
5460
|
-
try {
|
|
5461
|
-
const pagesDir = path.join(absDir, "pages");
|
|
5462
|
-
if (fs.existsSync(pagesDir))
|
|
5463
|
-
roots.push(pagesDir);
|
|
5464
|
-
const componentsDir = path.join(absDir, "components");
|
|
5465
|
-
if (fs.existsSync(componentsDir))
|
|
5466
|
-
roots.push(componentsDir);
|
|
5467
|
-
} catch {
|
|
5468
|
-
}
|
|
5469
|
-
}
|
|
5470
|
-
}
|
|
5471
|
-
const potentialRoots = Array.from(new Set(roots.map((r) => path.normalize(r)))).sort((a, b) => b.length - a.length);
|
|
5472
|
-
for (const root of potentialRoots) {
|
|
5473
|
-
if (absFilename.startsWith(root + path.sep) || absFilename === root) {
|
|
5474
|
-
const rel = path.relative(root, absFilename);
|
|
5475
|
-
const parsed = path.parse(rel);
|
|
5476
|
-
const segments = path.join(parsed.dir, parsed.name);
|
|
5477
|
-
return toPascalCase(segments);
|
|
5478
|
-
}
|
|
5479
|
-
}
|
|
5480
|
-
return toPascalCase(path.parse(absFilename).name);
|
|
5481
|
-
}
|
|
5749
|
+
const devStartupMetricsByOutputKey = /* @__PURE__ */ new Map();
|
|
5482
5750
|
function createDevProcessorPlugin(options) {
|
|
5483
5751
|
const {
|
|
5484
5752
|
nativeWrappers,
|
|
@@ -5524,6 +5792,7 @@ function createDevProcessorPlugin(options) {
|
|
|
5524
5792
|
scheduleVueFileRegen(ctx.file, "hmr");
|
|
5525
5793
|
},
|
|
5526
5794
|
configureServer(server) {
|
|
5795
|
+
const getViewsDirAbs = () => path.isAbsolute(viewsDir) ? viewsDir : path.resolve(projectRootRef.current, viewsDir);
|
|
5527
5796
|
const routerInitPromise = (async () => {
|
|
5528
5797
|
if (!routerAwarePoms) {
|
|
5529
5798
|
setRouteNameToComponentNameMap(/* @__PURE__ */ new Map());
|
|
@@ -5536,7 +5805,15 @@ function createDevProcessorPlugin(options) {
|
|
|
5536
5805
|
} else {
|
|
5537
5806
|
if (!resolvedRouterEntry)
|
|
5538
5807
|
throw new Error("[vue-pom-generator] router.entry is required when router introspection is enabled.");
|
|
5539
|
-
result = await parseRouterFileFromCwd(resolvedRouterEntry, {
|
|
5808
|
+
result = await parseRouterFileFromCwd(resolvedRouterEntry, {
|
|
5809
|
+
moduleShims: routerModuleShims,
|
|
5810
|
+
componentNaming: {
|
|
5811
|
+
projectRoot: projectRootRef.current,
|
|
5812
|
+
viewsDirAbs: getViewsDirAbs(),
|
|
5813
|
+
scanDirs,
|
|
5814
|
+
extraRoots: [process.cwd()]
|
|
5815
|
+
}
|
|
5816
|
+
});
|
|
5540
5817
|
}
|
|
5541
5818
|
const { routeNameMap, routePathMap } = result;
|
|
5542
5819
|
setRouteNameToComponentNameMap(routeNameMap);
|
|
@@ -5559,7 +5836,6 @@ function createDevProcessorPlugin(options) {
|
|
|
5559
5836
|
const logDebug = (message) => loggerRef.current.debug(message);
|
|
5560
5837
|
let scheduleVueFileRegenLocal = null;
|
|
5561
5838
|
const formatMs = (ms) => `${ms.toFixed(1)}ms`;
|
|
5562
|
-
const getViewsDirAbs = () => path.isAbsolute(viewsDir) ? viewsDir : path.resolve(projectRootRef.current, viewsDir);
|
|
5563
5839
|
const extractTemplateFromSfc = (source, filename) => {
|
|
5564
5840
|
const { descriptor } = parse$1(source, {
|
|
5565
5841
|
filename: filename ?? "anonymous.vue"
|
|
@@ -5672,6 +5948,21 @@ function createDevProcessorPlugin(options) {
|
|
|
5672
5948
|
logInfo(`initial compile: ${compiledCount}/${totalVueFiles} files in ${formatMs(t1 - t0)} (components=${snapshotHierarchy.size})`);
|
|
5673
5949
|
};
|
|
5674
5950
|
const generateAggregatedFromSnapshot = (reason) => {
|
|
5951
|
+
const metrics = getGenerationMetrics(snapshotHierarchy);
|
|
5952
|
+
if (metrics.entryCount <= 0 || metrics.selectorCount <= 0) {
|
|
5953
|
+
logInfo(`generate(${reason}): skipped empty snapshot (components=${metrics.entryCount}, selectors=${metrics.selectorCount})`);
|
|
5954
|
+
return;
|
|
5955
|
+
}
|
|
5956
|
+
const generationMetricsKey = getGenerationMetricsKey(projectRootRef.current, outDir);
|
|
5957
|
+
if (reason === "startup") {
|
|
5958
|
+
const previousMetrics = devStartupMetricsByOutputKey.get(generationMetricsKey);
|
|
5959
|
+
if (previousMetrics && isLessRich(metrics, previousMetrics)) {
|
|
5960
|
+
logInfo(
|
|
5961
|
+
`generate(${reason}): skipped smaller snapshot (components=${metrics.entryCount}, selectors=${metrics.selectorCount})`
|
|
5962
|
+
);
|
|
5963
|
+
return;
|
|
5964
|
+
}
|
|
5965
|
+
}
|
|
5675
5966
|
const t0 = performance.now();
|
|
5676
5967
|
generateFiles(snapshotHierarchy, snapshotVuePathMap, normalizedBasePagePath, {
|
|
5677
5968
|
outDir,
|
|
@@ -5686,10 +5977,15 @@ function createDevProcessorPlugin(options) {
|
|
|
5686
5977
|
testIdAttribute,
|
|
5687
5978
|
vueRouterFluentChaining: routerAwarePoms,
|
|
5688
5979
|
routerEntry: resolvedRouterEntry,
|
|
5689
|
-
routerType
|
|
5980
|
+
routerType,
|
|
5981
|
+
viewsDir,
|
|
5982
|
+
scanDirs
|
|
5690
5983
|
});
|
|
5984
|
+
if (reason === "startup") {
|
|
5985
|
+
devStartupMetricsByOutputKey.set(generationMetricsKey, metrics);
|
|
5986
|
+
}
|
|
5691
5987
|
const t1 = performance.now();
|
|
5692
|
-
logInfo(`generate(${reason}): components=${
|
|
5988
|
+
logInfo(`generate(${reason}): components=${metrics.entryCount} selectors=${metrics.selectorCount} in ${formatMs(t1 - t0)}`);
|
|
5693
5989
|
};
|
|
5694
5990
|
const initialBuildPromise = (async () => {
|
|
5695
5991
|
const t0 = performance.now();
|
|
@@ -5905,6 +6201,8 @@ function createSupportPlugins(options) {
|
|
|
5905
6201
|
const tsProcessor = createBuildProcessorPlugin({
|
|
5906
6202
|
componentHierarchyMap,
|
|
5907
6203
|
vueFilesPathMap,
|
|
6204
|
+
viewsDir,
|
|
6205
|
+
scanDirs,
|
|
5908
6206
|
basePageClassPath,
|
|
5909
6207
|
normalizedBasePagePath,
|
|
5910
6208
|
outDir,
|
|
@@ -6210,20 +6508,21 @@ function createVuePluginWithTestIds(options) {
|
|
|
6210
6508
|
}
|
|
6211
6509
|
];
|
|
6212
6510
|
};
|
|
6511
|
+
const runtimeNodeTransform = (node, context) => {
|
|
6512
|
+
const filename = context.filename;
|
|
6513
|
+
if (!filename || !filename.endsWith(".vue") || !isFileInScope(filename)) {
|
|
6514
|
+
return;
|
|
6515
|
+
}
|
|
6516
|
+
const transforms = getNodeTransforms(filename);
|
|
6517
|
+
const ourTransform = transforms[transforms.length - 1];
|
|
6518
|
+
return ourTransform(node, context);
|
|
6519
|
+
};
|
|
6213
6520
|
const templateCompilerOptions = {
|
|
6214
6521
|
...userCompilerOptions,
|
|
6215
6522
|
prefixIdentifiers: true,
|
|
6216
6523
|
nodeTransforms: [
|
|
6217
6524
|
...userNodeTransforms,
|
|
6218
|
-
|
|
6219
|
-
const filename = context.filename;
|
|
6220
|
-
if (!filename || !filename.endsWith(".vue") || !isFileInScope(filename)) {
|
|
6221
|
-
return;
|
|
6222
|
-
}
|
|
6223
|
-
const transforms = getNodeTransforms(filename);
|
|
6224
|
-
const ourTransform = transforms[transforms.length - 1];
|
|
6225
|
-
return ourTransform(node, context);
|
|
6226
|
-
}
|
|
6525
|
+
runtimeNodeTransform
|
|
6227
6526
|
]
|
|
6228
6527
|
};
|
|
6229
6528
|
const metadataCollectorPlugin = {
|
|
@@ -6262,7 +6561,42 @@ function createVuePluginWithTestIds(options) {
|
|
|
6262
6561
|
...vueOptions,
|
|
6263
6562
|
template
|
|
6264
6563
|
});
|
|
6265
|
-
|
|
6564
|
+
const nuxtVueBridgePlugin = {
|
|
6565
|
+
name: "vue-pom-generator-nuxt-vue-bridge",
|
|
6566
|
+
apply: "serve",
|
|
6567
|
+
configResolved(config) {
|
|
6568
|
+
const viteVuePlugin = config.plugins.find((plugin) => {
|
|
6569
|
+
return typeof plugin === "object" && plugin !== null && "name" in plugin && plugin.name === "vite:vue" && "api" in plugin;
|
|
6570
|
+
});
|
|
6571
|
+
const api = viteVuePlugin?.api;
|
|
6572
|
+
if (!api) {
|
|
6573
|
+
loggerRef.current.warn("[vue-pom-generator] Nuxt bridge could not find vite:vue plugin to patch.");
|
|
6574
|
+
return;
|
|
6575
|
+
}
|
|
6576
|
+
const currentOptions = api.options ?? {};
|
|
6577
|
+
const currentTemplate = currentOptions.template ?? {};
|
|
6578
|
+
const currentCompilerOptions = currentTemplate.compilerOptions ?? {};
|
|
6579
|
+
const currentNodeTransforms = currentCompilerOptions.nodeTransforms ?? [];
|
|
6580
|
+
if (currentNodeTransforms.includes(runtimeNodeTransform)) {
|
|
6581
|
+
return;
|
|
6582
|
+
}
|
|
6583
|
+
api.options = {
|
|
6584
|
+
...currentOptions,
|
|
6585
|
+
template: {
|
|
6586
|
+
...currentTemplate,
|
|
6587
|
+
compilerOptions: {
|
|
6588
|
+
...currentCompilerOptions,
|
|
6589
|
+
prefixIdentifiers: true,
|
|
6590
|
+
nodeTransforms: [
|
|
6591
|
+
...currentNodeTransforms,
|
|
6592
|
+
runtimeNodeTransform
|
|
6593
|
+
]
|
|
6594
|
+
}
|
|
6595
|
+
}
|
|
6596
|
+
};
|
|
6597
|
+
}
|
|
6598
|
+
};
|
|
6599
|
+
return { metadataCollectorPlugin, internalVuePlugin, nuxtVueBridgePlugin };
|
|
6266
6600
|
}
|
|
6267
6601
|
function assertNonEmptyString(value, name) {
|
|
6268
6602
|
if (!value || !value.trim()) {
|
|
@@ -6388,7 +6722,7 @@ function createVuePomGeneratorPlugins(options = {}) {
|
|
|
6388
6722
|
const semanticNameMap = /* @__PURE__ */ new Map();
|
|
6389
6723
|
const componentHierarchyMap = /* @__PURE__ */ new Map();
|
|
6390
6724
|
const vueFilesPathMap = /* @__PURE__ */ new Map();
|
|
6391
|
-
const { metadataCollectorPlugin, internalVuePlugin } = createVuePluginWithTestIds({
|
|
6725
|
+
const { metadataCollectorPlugin, internalVuePlugin, nuxtVueBridgePlugin } = createVuePluginWithTestIds({
|
|
6392
6726
|
vueOptions,
|
|
6393
6727
|
existingIdBehavior,
|
|
6394
6728
|
nameCollisionBehavior,
|
|
@@ -6439,7 +6773,7 @@ function createVuePomGeneratorPlugins(options = {}) {
|
|
|
6439
6773
|
const resultPlugins = [
|
|
6440
6774
|
configPlugin,
|
|
6441
6775
|
metadataCollectorPlugin,
|
|
6442
|
-
...isNuxt ? [] : [internalVuePlugin],
|
|
6776
|
+
...isNuxt ? [nuxtVueBridgePlugin] : [internalVuePlugin],
|
|
6443
6777
|
...supportPlugins
|
|
6444
6778
|
];
|
|
6445
6779
|
if (!generationEnabled) {
|
|
@@ -6447,7 +6781,7 @@ function createVuePomGeneratorPlugins(options = {}) {
|
|
|
6447
6781
|
return [
|
|
6448
6782
|
configPlugin,
|
|
6449
6783
|
metadataCollectorPlugin,
|
|
6450
|
-
...isNuxt ? [] : [internalVuePlugin],
|
|
6784
|
+
...isNuxt ? [nuxtVueBridgePlugin] : [internalVuePlugin],
|
|
6451
6785
|
virtualModules
|
|
6452
6786
|
];
|
|
6453
6787
|
}
|