@immense/vue-pom-generator 1.0.52 → 1.0.53

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/README.md +59 -22
  2. package/RELEASE_NOTES.md +45 -25
  3. package/class-generation/index.ts +23 -14
  4. package/dist/class-generation/index.d.ts +3 -2
  5. package/dist/class-generation/index.d.ts.map +1 -1
  6. package/dist/index.cjs +516 -182
  7. package/dist/index.cjs.map +1 -1
  8. package/dist/index.d.ts +3 -2
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.mjs +516 -182
  11. package/dist/index.mjs.map +1 -1
  12. package/dist/plugin/create-vue-pom-generator-plugins.d.ts +2 -2
  13. package/dist/plugin/create-vue-pom-generator-plugins.d.ts.map +1 -1
  14. package/dist/plugin/nuxt-discovery.d.ts +47 -0
  15. package/dist/plugin/nuxt-discovery.d.ts.map +1 -0
  16. package/dist/plugin/path-utils.d.ts +4 -5
  17. package/dist/plugin/path-utils.d.ts.map +1 -1
  18. package/dist/plugin/support/build-plugin.d.ts +6 -3
  19. package/dist/plugin/support/build-plugin.d.ts.map +1 -1
  20. package/dist/plugin/support/dev-plugin.d.ts +6 -3
  21. package/dist/plugin/support/dev-plugin.d.ts.map +1 -1
  22. package/dist/plugin/support-plugins.d.ts +5 -2
  23. package/dist/plugin/support-plugins.d.ts.map +1 -1
  24. package/dist/plugin/types.d.ts +33 -17
  25. package/dist/plugin/types.d.ts.map +1 -1
  26. package/dist/plugin/vue-plugin.d.ts +1 -1
  27. package/dist/plugin/vue-plugin.d.ts.map +1 -1
  28. package/dist/router-introspection.d.ts +4 -2
  29. package/dist/router-introspection.d.ts.map +1 -1
  30. package/dist/tests/nuxt-discovery.test.d.ts +2 -0
  31. package/dist/tests/nuxt-discovery.test.d.ts.map +1 -0
  32. package/package.json +1 -1
  33. package/dist/plugin/support/generation-metrics.d.ts +0 -10
  34. package/dist/plugin/support/generation-metrics.d.ts.map +0 -1
  35. package/dist/tests/generation-metrics.test.d.ts +0 -2
  36. package/dist/tests/generation-metrics.test.d.ts.map +0 -1
package/dist/index.mjs CHANGED
@@ -1,7 +1,8 @@
1
+ import fs from "node:fs";
1
2
  import path from "node:path";
2
3
  import process from "node:process";
4
+ import { createRequire } from "node:module";
3
5
  import { pathToFileURL, fileURLToPath } from "node:url";
4
- import fs from "node:fs";
5
6
  import * as compilerDom from "@vue/compiler-dom";
6
7
  import { parse as parse$2, NodeTypes as NodeTypes$1 } from "@vue/compiler-dom";
7
8
  import { parse as parse$1, compileScript } from "@vue/compiler-sfc";
@@ -63,6 +64,133 @@ function createLogger(options) {
63
64
  }
64
65
  };
65
66
  }
67
+ const requireFromModule = createRequire(import.meta.url);
68
+ function toUniqueResolvedPaths(paths) {
69
+ return Array.from(new Set(paths.map((value) => path.resolve(value))));
70
+ }
71
+ function resolveNuxtAlias(value, context) {
72
+ const aliases = [
73
+ ["~~", context.rootDir],
74
+ ["@@", context.rootDir],
75
+ ["~/", `${context.srcDir}${path.sep}`],
76
+ ["@/", `${context.srcDir}${path.sep}`],
77
+ ["~", context.srcDir],
78
+ ["@", context.srcDir]
79
+ ];
80
+ for (const [alias, replacement] of Object.entries(context.alias ?? {})) {
81
+ aliases.push([alias, replacement]);
82
+ }
83
+ aliases.sort((a, b) => b[0].length - a[0].length);
84
+ for (const [alias, replacement] of aliases) {
85
+ if (value === alias)
86
+ return replacement;
87
+ if (value.startsWith(`${alias}/`) || value.startsWith(`${alias}${path.sep}`)) {
88
+ const suffix = value.slice(alias.length + 1);
89
+ return path.resolve(replacement, suffix);
90
+ }
91
+ }
92
+ return value;
93
+ }
94
+ function resolveNuxtPath(value, baseDir, context) {
95
+ const resolvedAlias = resolveNuxtAlias(value, context);
96
+ return path.isAbsolute(resolvedAlias) ? path.resolve(resolvedAlias) : path.resolve(baseDir, resolvedAlias);
97
+ }
98
+ function normalizeNuxtComponentDirs(value, baseDir, context) {
99
+ if (Array.isArray(value)) {
100
+ return value.flatMap((entry) => normalizeNuxtComponentDirs(entry, baseDir, context));
101
+ }
102
+ if (value === false) {
103
+ return [];
104
+ }
105
+ if (value === true || value === void 0) {
106
+ return [
107
+ path.resolve(baseDir, "components/islands"),
108
+ path.resolve(baseDir, "components/global"),
109
+ path.resolve(baseDir, "components")
110
+ ];
111
+ }
112
+ if (typeof value === "string") {
113
+ return [resolveNuxtPath(value, baseDir, context)];
114
+ }
115
+ if (!value || typeof value !== "object") {
116
+ return [];
117
+ }
118
+ if (typeof value === "object" && value && "path" in value && typeof value.path === "string") {
119
+ return [resolveNuxtPath(value.path, baseDir, context)];
120
+ }
121
+ if (typeof value !== "object" || !value || !("dirs" in value)) {
122
+ return [];
123
+ }
124
+ return normalizeNuxtComponentDirs(value.dirs, baseDir, context);
125
+ }
126
+ function resolveNuxtProjectDiscovery(nuxtOptions, getLayerDirectories, cwd = process.cwd()) {
127
+ const rootDir = path.resolve(nuxtOptions.rootDir ?? cwd);
128
+ const srcDir = path.resolve(nuxtOptions.srcDir ?? rootDir);
129
+ const fallbackLayer = {
130
+ cwd: rootDir,
131
+ config: {
132
+ rootDir,
133
+ srcDir,
134
+ dir: nuxtOptions.dir,
135
+ components: nuxtOptions.components,
136
+ alias: nuxtOptions.alias
137
+ }
138
+ };
139
+ const layers = nuxtOptions._layers?.length ? nuxtOptions._layers : [fallbackLayer];
140
+ const normalizedNuxtOptions = {
141
+ ...nuxtOptions,
142
+ _layers: layers
143
+ };
144
+ const layerDirectories = getLayerDirectories({ options: normalizedNuxtOptions });
145
+ const pageDirs = layerDirectories.map((layer) => layer.appPages);
146
+ const layoutDirs = layerDirectories.map((layer) => layer.appLayouts);
147
+ const componentDirs = [];
148
+ for (const [index, layer] of layers.entries()) {
149
+ const layerDirectory = layerDirectories[index];
150
+ const layerRootDir = path.resolve(layerDirectory?.root ?? layer.config?.rootDir ?? layer.cwd ?? rootDir);
151
+ const layerSrcDir = path.resolve(layerDirectory?.app ?? layer.config?.srcDir ?? layer.cwd ?? srcDir);
152
+ const context = {
153
+ rootDir: layerRootDir,
154
+ srcDir: layerSrcDir,
155
+ alias: {
156
+ ...nuxtOptions.alias ?? {},
157
+ ...layer.config?.alias ?? {}
158
+ }
159
+ };
160
+ componentDirs.push(...normalizeNuxtComponentDirs(layer.config?.components, layerSrcDir, context));
161
+ }
162
+ const uniquePageDirs = toUniqueResolvedPaths(pageDirs);
163
+ const uniqueLayoutDirs = toUniqueResolvedPaths(layoutDirs);
164
+ const uniqueComponentDirs = toUniqueResolvedPaths(componentDirs);
165
+ return {
166
+ rootDir,
167
+ srcDir,
168
+ pageDirs: uniquePageDirs,
169
+ layoutDirs: uniqueLayoutDirs,
170
+ componentDirs: uniqueComponentDirs,
171
+ wrapperSearchRoots: []
172
+ };
173
+ }
174
+ async function loadNuxtProjectDiscovery(cwd = process.cwd()) {
175
+ let loadNuxtConfig;
176
+ let getLayerDirectories;
177
+ try {
178
+ const nuxtKitEntry = requireFromModule.resolve("@nuxt/kit");
179
+ ({ loadNuxtConfig, getLayerDirectories } = await import(pathToFileURL(nuxtKitEntry).href));
180
+ } catch (error) {
181
+ throw new TypeError(
182
+ `[vue-pom-generator] Nuxt mode requires @nuxt/kit to be available so Nuxt directories can be resolved from nuxt.config. ${error instanceof Error ? error.message : String(error)}`
183
+ );
184
+ }
185
+ if (typeof loadNuxtConfig !== "function") {
186
+ throw new TypeError("[vue-pom-generator] Nuxt mode requires @nuxt/kit.loadNuxtConfig().");
187
+ }
188
+ if (typeof getLayerDirectories !== "function") {
189
+ throw new TypeError("[vue-pom-generator] Nuxt mode requires @nuxt/kit.getLayerDirectories().");
190
+ }
191
+ const nuxtOptions = await loadNuxtConfig({ cwd });
192
+ return resolveNuxtProjectDiscovery(nuxtOptions, getLayerDirectories, cwd);
193
+ }
66
194
  function createTypeScriptProject() {
67
195
  return new Project({
68
196
  useInMemoryFileSystem: true,
@@ -2755,27 +2883,24 @@ function safeRealpath(value) {
2755
2883
  const resolvedParent = safeRealpath(parent);
2756
2884
  return resolvedParent === parent ? value : path.join(resolvedParent, path.basename(value));
2757
2885
  }
2886
+ function isPathWithinDir(filePathAbs, dirPathAbs) {
2887
+ const fileAbs = path.resolve(filePathAbs);
2888
+ const dirAbs = path.resolve(dirPathAbs);
2889
+ const rel = path.relative(dirAbs, fileAbs);
2890
+ if (!rel)
2891
+ return true;
2892
+ return !rel.startsWith("..") && !path.isAbsolute(rel);
2893
+ }
2758
2894
  function resolveComponentNameFromPath(options) {
2759
- const { projectRoot, viewsDirAbs, scanDirs, extraRoots = [] } = options;
2895
+ const { projectRoot, viewsDirAbs, sourceDirs, extraRoots = [] } = options;
2760
2896
  const cleanFilename = options.filename.includes("?") ? options.filename.substring(0, options.filename.indexOf("?")) : options.filename;
2761
2897
  const absFilename = path.isAbsolute(cleanFilename) ? cleanFilename : path.resolve(projectRoot, cleanFilename);
2762
2898
  const normalizedAbsFilename = path.normalize(safeRealpath(absFilename));
2763
2899
  const rootBases = [projectRoot, ...extraRoots.filter((r) => r !== projectRoot)];
2764
- const roots = [viewsDirAbs, ...rootBases.flatMap((base) => scanDirs.map((d) => path.resolve(base, d)))];
2765
- for (const base of rootBases) {
2766
- for (const dir of scanDirs) {
2767
- const absDir = path.resolve(base, dir);
2768
- try {
2769
- const pagesDir = path.join(absDir, "pages");
2770
- if (fs.existsSync(pagesDir))
2771
- roots.push(pagesDir);
2772
- const componentsDir = path.join(absDir, "components");
2773
- if (fs.existsSync(componentsDir))
2774
- roots.push(componentsDir);
2775
- } catch {
2776
- }
2777
- }
2778
- }
2900
+ const roots = [
2901
+ viewsDirAbs,
2902
+ ...sourceDirs.flatMap((dir) => path.isAbsolute(dir) ? [dir] : rootBases.map((base) => path.resolve(base, dir)))
2903
+ ];
2779
2904
  const potentialRoots = Array.from(new Set(roots.map((r) => path.normalize(safeRealpath(r))))).sort((a, b) => b.length - a.length);
2780
2905
  for (const root of potentialRoots) {
2781
2906
  if (normalizedAbsFilename.startsWith(root + path.sep) || normalizedAbsFilename === root) {
@@ -3139,7 +3264,7 @@ function resolveIntrospectedComponentName(componentInfo, componentNaming) {
3139
3264
  filename: componentInfo.filePath,
3140
3265
  projectRoot: componentNaming.projectRoot,
3141
3266
  viewsDirAbs: componentNaming.viewsDirAbs,
3142
- scanDirs: componentNaming.scanDirs,
3267
+ sourceDirs: componentNaming.sourceDirs,
3143
3268
  extraRoots: componentNaming.extraRoots
3144
3269
  });
3145
3270
  }
@@ -3251,69 +3376,97 @@ async function ensureDomShim() {
3251
3376
  if (!g.requestAnimationFrame)
3252
3377
  g.requestAnimationFrame = (cb) => setTimeout(() => cb(Date.now()), 16);
3253
3378
  }
3254
- async function introspectNuxtPages(projectRoot) {
3255
- const possiblePagesDirs = ["app/pages", "pages"];
3256
- let pagesDir = "";
3257
- for (const dir of possiblePagesDirs) {
3258
- const abs = path.resolve(projectRoot, dir);
3259
- if (fs.existsSync(abs) && fs.statSync(abs).isDirectory()) {
3260
- pagesDir = abs;
3379
+ function unwrapNuxtPageSegment(segment, prefix, suffix) {
3380
+ if (!segment.startsWith(prefix) || !segment.endsWith(suffix))
3381
+ return null;
3382
+ const value = segment.slice(prefix.length, segment.length - suffix.length);
3383
+ return value.length > 0 ? value : null;
3384
+ }
3385
+ function resolveNuxtPageSegment(segment) {
3386
+ if (segment === "index") {
3387
+ return { pathPart: "", params: [] };
3388
+ }
3389
+ const optionalParamName = unwrapNuxtPageSegment(segment, "[[", "]]");
3390
+ if (optionalParamName) {
3391
+ return {
3392
+ pathPart: `:${optionalParamName}?`,
3393
+ params: [{ name: optionalParamName, optional: true }]
3394
+ };
3395
+ }
3396
+ const catchAllParamName = unwrapNuxtPageSegment(segment, "[...", "]");
3397
+ if (catchAllParamName) {
3398
+ return {
3399
+ pathPart: `:${catchAllParamName}(.*)*`,
3400
+ params: [{ name: catchAllParamName, optional: false }]
3401
+ };
3402
+ }
3403
+ const requiredParamName = unwrapNuxtPageSegment(segment, "[", "]");
3404
+ if (requiredParamName) {
3405
+ return {
3406
+ pathPart: `:${requiredParamName}`,
3407
+ params: [{ name: requiredParamName, optional: false }]
3408
+ };
3409
+ }
3410
+ return { pathPart: segment, params: [] };
3411
+ }
3412
+ function toPathSegments(value) {
3413
+ const segments = [];
3414
+ let current = path.normalize(value);
3415
+ while (current && current !== "." && current !== path.sep) {
3416
+ const parsed = path.parse(current);
3417
+ if (!parsed.base || parsed.base === ".")
3261
3418
  break;
3262
- }
3419
+ segments.unshift(parsed.base);
3420
+ if (!parsed.dir || parsed.dir === "." || parsed.dir === current)
3421
+ break;
3422
+ current = parsed.dir;
3263
3423
  }
3264
- if (!pagesDir) {
3424
+ return segments;
3425
+ }
3426
+ async function introspectNuxtPages(projectRoot, options = {}) {
3427
+ const possiblePageDirs = options.pageDirs?.length ? options.pageDirs : ["app/pages", "pages"].map((dir) => path.resolve(projectRoot, dir));
3428
+ const pageDirs = possiblePageDirs.map((dir) => path.resolve(projectRoot, dir)).filter((dir) => {
3429
+ try {
3430
+ return fs.existsSync(dir) && fs.statSync(dir).isDirectory();
3431
+ } catch {
3432
+ return false;
3433
+ }
3434
+ });
3435
+ if (!pageDirs.length) {
3265
3436
  debugLog(`[router-introspection][nuxt] Could not find pages directory in ${projectRoot}`);
3266
3437
  return { routeNameMap: /* @__PURE__ */ new Map(), routePathMap: /* @__PURE__ */ new Map(), routeMetaEntries: [] };
3267
3438
  }
3439
+ const routePathMap = /* @__PURE__ */ new Map();
3268
3440
  const routeMetaEntries = [];
3269
- const walk = (dir, baseRoute) => {
3441
+ const walk = (pagesDir, dir) => {
3270
3442
  const files = fs.readdirSync(dir);
3271
3443
  for (const file of files) {
3272
3444
  const fullPath = path.join(dir, file);
3273
3445
  const stat = fs.statSync(fullPath);
3274
3446
  if (stat.isDirectory()) {
3275
- walk(fullPath, `${baseRoute}/${file}`);
3447
+ walk(pagesDir, fullPath);
3276
3448
  continue;
3277
3449
  }
3278
3450
  if (!file.endsWith(".vue"))
3279
3451
  continue;
3280
- const componentName = file.slice(0, -4);
3281
- if (componentName === "index" && baseRoute === "") {
3282
- routeMetaEntries.push({
3283
- componentName: "index",
3284
- pathTemplate: "/",
3285
- params: [],
3286
- query: []
3287
- });
3288
- continue;
3289
- }
3290
- let routePath = componentName === "index" ? baseRoute : `${baseRoute}/${componentName}`;
3291
- if (!routePath.startsWith("/"))
3292
- routePath = `/${routePath}`;
3452
+ const componentName = resolveComponentNameFromPath({
3453
+ filename: fullPath,
3454
+ projectRoot,
3455
+ viewsDirAbs: pagesDir,
3456
+ sourceDirs: [pagesDir],
3457
+ extraRoots: [process.cwd()]
3458
+ });
3459
+ const relativePath = path.relative(pagesDir, fullPath);
3460
+ const parsed = path.parse(relativePath);
3461
+ const routeSegments = toPathSegments(path.join(parsed.dir, parsed.name));
3293
3462
  const params = [];
3294
- let pathTemplate = "";
3295
- for (let i = 0; i < routePath.length; i++) {
3296
- const ch = routePath[i];
3297
- if (ch !== "[") {
3298
- pathTemplate += ch;
3299
- continue;
3300
- }
3301
- let name = "";
3302
- i++;
3303
- while (i < routePath.length) {
3304
- const c = routePath[i];
3305
- if (c === "]")
3306
- break;
3307
- name += c;
3308
- i++;
3309
- }
3310
- if (name) {
3311
- params.push({ name, optional: false });
3312
- pathTemplate += `:${name}`;
3313
- } else {
3314
- pathTemplate += "[]";
3315
- }
3316
- }
3463
+ const pathParts = routeSegments.flatMap((segment) => {
3464
+ const resolution = resolveNuxtPageSegment(segment);
3465
+ params.push(...resolution.params);
3466
+ return resolution.pathPart ? [resolution.pathPart] : [];
3467
+ });
3468
+ const pathTemplate = pathParts.length ? `/${pathParts.join("/")}` : "/";
3469
+ routePathMap.set(pathTemplate, componentName);
3317
3470
  routeMetaEntries.push({
3318
3471
  componentName,
3319
3472
  pathTemplate,
@@ -3322,10 +3475,12 @@ async function introspectNuxtPages(projectRoot) {
3322
3475
  });
3323
3476
  }
3324
3477
  };
3325
- walk(pagesDir, "");
3478
+ for (const pageDir of pageDirs) {
3479
+ walk(pageDir, pageDir);
3480
+ }
3326
3481
  return {
3327
3482
  routeNameMap: /* @__PURE__ */ new Map(),
3328
- routePathMap: /* @__PURE__ */ new Map(),
3483
+ routePathMap,
3329
3484
  routeMetaEntries
3330
3485
  };
3331
3486
  }
@@ -3621,15 +3776,20 @@ function resolveVueSourcePath(targetClassName, vueFilesPathMap, projectRoot) {
3621
3776
  }
3622
3777
  async function getRouteMetaByComponent(projectRoot, routerEntry, routerType, options = {}) {
3623
3778
  const root = projectRoot ?? process.cwd();
3624
- const viewsDir = options.viewsDir ?? "src/views";
3625
- const viewsDirAbs = path.isAbsolute(viewsDir) ? viewsDir : path.resolve(root, viewsDir);
3626
- const scanDirs = options.scanDirs?.length ? options.scanDirs : ["src"];
3779
+ const pageDirs = options.pageDirs?.length ? options.pageDirs : ["src/views"];
3780
+ const pageDirsAbs = pageDirs.map((dir) => path.isAbsolute(dir) ? dir : path.resolve(root, dir));
3781
+ const primaryPageDirAbs = pageDirsAbs[0] ?? path.resolve(root, "src/views");
3782
+ const sourceDirs = [
3783
+ ...pageDirs,
3784
+ ...options.componentDirs?.length ? options.componentDirs : ["src/components"],
3785
+ ...options.layoutDirs?.length ? options.layoutDirs : ["src/layouts"]
3786
+ ];
3627
3787
  const extraRoots = process.cwd() !== root ? [process.cwd()] : [];
3628
- const { routeMetaEntries } = routerType === "nuxt" ? await introspectNuxtPages(root) : await parseRouterFileFromCwd(resolveRouterEntry(root, routerEntry), {
3788
+ const { routeMetaEntries } = routerType === "nuxt" ? await introspectNuxtPages(root, { pageDirs: pageDirsAbs }) : await parseRouterFileFromCwd(resolveRouterEntry(root, routerEntry), {
3629
3789
  componentNaming: {
3630
3790
  projectRoot: root,
3631
- viewsDirAbs,
3632
- scanDirs,
3791
+ viewsDirAbs: primaryPageDirAbs,
3792
+ sourceDirs,
3633
3793
  extraRoots
3634
3794
  }
3635
3795
  });
@@ -3829,15 +3989,17 @@ async function generateFiles(componentHierarchyMap, vueFilesPathMap, basePageCla
3829
3989
  vueRouterFluentChaining,
3830
3990
  routerEntry,
3831
3991
  routerType,
3832
- viewsDir,
3833
- scanDirs,
3992
+ pageDirs,
3993
+ componentDirs,
3994
+ layoutDirs,
3834
3995
  routeMetaByComponent: routeMetaByComponentOverride
3835
3996
  } = options;
3836
3997
  const emitLanguages = emitLanguagesOverride?.length ? emitLanguagesOverride : ["ts"];
3837
3998
  const outDir = outDirOverride ?? "./pom";
3838
3999
  const routeMetaByComponent = routeMetaByComponentOverride ?? (vueRouterFluentChaining ? await getRouteMetaByComponent(projectRoot, routerEntry, routerType, {
3839
- viewsDir,
3840
- scanDirs
4000
+ pageDirs,
4001
+ componentDirs,
4002
+ layoutDirs
3841
4003
  }) : void 0);
3842
4004
  const generatedFilePaths = [];
3843
4005
  const writeGeneratedFile = (file) => {
@@ -6626,8 +6788,11 @@ function createBuildProcessorPlugin(options) {
6626
6788
  const {
6627
6789
  componentHierarchyMap,
6628
6790
  vueFilesPathMap,
6629
- viewsDir,
6630
- scanDirs,
6791
+ getPageDirs,
6792
+ getComponentDirs,
6793
+ getLayoutDirs,
6794
+ getViewsDir,
6795
+ getSourceDirs,
6631
6796
  basePageClassPath,
6632
6797
  normalizedBasePagePath,
6633
6798
  outDir,
@@ -6648,7 +6813,7 @@ function createBuildProcessorPlugin(options) {
6648
6813
  excludedComponents,
6649
6814
  getWrapperSearchRoots,
6650
6815
  routerAwarePoms,
6651
- resolvedRouterEntry,
6816
+ getResolvedRouterEntry,
6652
6817
  routerType,
6653
6818
  routerModuleShims,
6654
6819
  loggerRef
@@ -6658,7 +6823,8 @@ function createBuildProcessorPlugin(options) {
6658
6823
  interactiveComponentCount: 0,
6659
6824
  dataTestIdCount: 0
6660
6825
  };
6661
- const getViewsDirAbs = () => path.isAbsolute(viewsDir) ? viewsDir : path.resolve(projectRootRef.current, viewsDir);
6826
+ const getViewsDirAbs = () => path.isAbsolute(getViewsDir()) ? getViewsDir() : path.resolve(projectRootRef.current, getViewsDir());
6827
+ const getPageDirsAbs = () => getPageDirs().map((dir) => path.isAbsolute(dir) ? dir : path.resolve(projectRootRef.current, dir));
6662
6828
  const getScriptInfo = (source, filename) => {
6663
6829
  try {
6664
6830
  const { descriptor } = parse$1(source, { filename });
@@ -6698,7 +6864,7 @@ function createBuildProcessorPlugin(options) {
6698
6864
  return out;
6699
6865
  };
6700
6866
  let supplemented = 0;
6701
- for (const dir of scanDirs) {
6867
+ for (const dir of getSourceDirs()) {
6702
6868
  const absDir = path.resolve(projectRootRef.current, dir);
6703
6869
  if (!fs.existsSync(absDir))
6704
6870
  continue;
@@ -6708,7 +6874,7 @@ function createBuildProcessorPlugin(options) {
6708
6874
  filename: absolutePath,
6709
6875
  projectRoot: projectRootRef.current,
6710
6876
  viewsDirAbs: getViewsDirAbs(),
6711
- scanDirs,
6877
+ sourceDirs: getSourceDirs(),
6712
6878
  extraRoots: [process.cwd()]
6713
6879
  });
6714
6880
  if (componentHierarchyMap.has(componentName))
@@ -6783,16 +6949,17 @@ function createBuildProcessorPlugin(options) {
6783
6949
  }
6784
6950
  let result;
6785
6951
  if (routerType === "nuxt") {
6786
- result = await introspectNuxtPages(projectRootRef.current);
6952
+ result = await introspectNuxtPages(projectRootRef.current, { pageDirs: getPageDirsAbs() });
6787
6953
  } else {
6954
+ const resolvedRouterEntry = getResolvedRouterEntry();
6788
6955
  if (!resolvedRouterEntry)
6789
6956
  throw new Error("[vue-pom-generator] router.entry is required when router introspection is enabled.");
6790
6957
  result = await parseRouterFileFromCwd(resolvedRouterEntry, {
6791
6958
  moduleShims: routerModuleShims,
6792
6959
  componentNaming: {
6793
6960
  projectRoot: projectRootRef.current,
6794
- viewsDirAbs: path.isAbsolute(viewsDir) ? viewsDir : path.resolve(projectRootRef.current, viewsDir),
6795
- scanDirs
6961
+ viewsDirAbs: getViewsDirAbs(),
6962
+ sourceDirs: getSourceDirs()
6796
6963
  }
6797
6964
  });
6798
6965
  }
@@ -6852,10 +7019,11 @@ function createBuildProcessorPlugin(options) {
6852
7019
  customPomImportNameCollisionBehavior,
6853
7020
  testIdAttribute,
6854
7021
  vueRouterFluentChaining: routerAwarePoms,
6855
- routerEntry: resolvedRouterEntry,
7022
+ routerEntry: getResolvedRouterEntry(),
6856
7023
  routerType,
6857
- viewsDir,
6858
- scanDirs
7024
+ pageDirs: getPageDirs(),
7025
+ componentDirs: getComponentDirs(),
7026
+ layoutDirs: getLayoutDirs()
6859
7027
  });
6860
7028
  lastGeneratedMetrics = metrics;
6861
7029
  loggerRef.current.info(`generated POMs (${metrics.entryCount} entries, ${metrics.interactiveComponentCount} interactive components, ${metrics.dataTestIdCount} selectors)`);
@@ -6869,8 +7037,11 @@ function createDevProcessorPlugin(options) {
6869
7037
  const {
6870
7038
  nativeWrappers,
6871
7039
  excludedComponents,
6872
- viewsDir,
6873
- scanDirs,
7040
+ getPageDirs,
7041
+ getComponentDirs,
7042
+ getLayoutDirs,
7043
+ getViewsDir,
7044
+ getSourceDirs,
6874
7045
  getWrapperSearchRoots,
6875
7046
  projectRootRef,
6876
7047
  normalizedBasePagePath,
@@ -6889,12 +7060,29 @@ function createDevProcessorPlugin(options) {
6889
7060
  existingIdBehavior,
6890
7061
  testIdAttribute,
6891
7062
  routerAwarePoms,
6892
- resolvedRouterEntry,
7063
+ getResolvedRouterEntry,
6893
7064
  routerType,
6894
7065
  routerModuleShims,
6895
7066
  loggerRef
6896
7067
  } = options;
6897
7068
  let scheduleVueFileRegen = null;
7069
+ const getProjectRootCandidates = () => Array.from(/* @__PURE__ */ new Set([
7070
+ path.resolve(projectRootRef.current),
7071
+ path.resolve(process.cwd())
7072
+ ]));
7073
+ const resolveProjectPath = (maybePath) => {
7074
+ if (path.isAbsolute(maybePath))
7075
+ return maybePath;
7076
+ const candidates = getProjectRootCandidates().map((root) => path.resolve(root, maybePath));
7077
+ return candidates.find((candidate) => fs.existsSync(candidate)) ?? candidates[0];
7078
+ };
7079
+ const getSourceDirRoots = () => Array.from(new Set(
7080
+ getProjectRootCandidates().flatMap((root) => getSourceDirs().map((dir) => path.resolve(root, dir)))
7081
+ ));
7082
+ const isContainedInScanDirs = (filePath) => {
7083
+ const absolutePath = path.resolve(filePath);
7084
+ return getSourceDirRoots().some((scanDirAbs) => isPathWithinDir(absolutePath, scanDirAbs));
7085
+ };
6898
7086
  return {
6899
7087
  name: "vue-pom-generator-dev",
6900
7088
  apply: "serve",
@@ -6905,16 +7093,13 @@ function createDevProcessorPlugin(options) {
6905
7093
  return;
6906
7094
  if (!ctx.file.endsWith(".vue"))
6907
7095
  return;
6908
- const isContainedInScanDirs = scanDirs.some((dir) => {
6909
- const absDir = path.resolve(projectRootRef.current, dir);
6910
- return ctx.file.startsWith(absDir + path.sep);
6911
- });
6912
- if (!isContainedInScanDirs)
7096
+ if (!isContainedInScanDirs(ctx.file))
6913
7097
  return;
6914
7098
  scheduleVueFileRegen(ctx.file, "hmr");
6915
7099
  },
6916
7100
  async configureServer(server) {
6917
- const getViewsDirAbs = () => path.isAbsolute(viewsDir) ? viewsDir : path.resolve(projectRootRef.current, viewsDir);
7101
+ const getViewsDirAbs = () => resolveProjectPath(getViewsDir());
7102
+ const getPageDirsAbs = () => getPageDirs().map((dir) => resolveProjectPath(dir));
6918
7103
  const routerInitPromise = (async () => {
6919
7104
  if (!routerAwarePoms) {
6920
7105
  setRouteNameToComponentNameMap(/* @__PURE__ */ new Map());
@@ -6923,8 +7108,9 @@ function createDevProcessorPlugin(options) {
6923
7108
  }
6924
7109
  let result;
6925
7110
  if (routerType === "nuxt") {
6926
- result = await introspectNuxtPages(projectRootRef.current);
7111
+ result = await introspectNuxtPages(projectRootRef.current, { pageDirs: getPageDirsAbs() });
6927
7112
  } else {
7113
+ const resolvedRouterEntry = getResolvedRouterEntry();
6928
7114
  if (!resolvedRouterEntry)
6929
7115
  throw new Error("[vue-pom-generator] router.entry is required when router introspection is enabled.");
6930
7116
  result = await parseRouterFileFromCwd(resolvedRouterEntry, {
@@ -6932,7 +7118,7 @@ function createDevProcessorPlugin(options) {
6932
7118
  componentNaming: {
6933
7119
  projectRoot: projectRootRef.current,
6934
7120
  viewsDirAbs: getViewsDirAbs(),
6935
- scanDirs,
7121
+ sourceDirs: getSourceDirs(),
6936
7122
  extraRoots: [process.cwd()]
6937
7123
  }
6938
7124
  });
@@ -7004,6 +7190,19 @@ function createDevProcessorPlugin(options) {
7004
7190
  let snapshotHierarchy = /* @__PURE__ */ new Map();
7005
7191
  let snapshotVuePathMap = /* @__PURE__ */ new Map();
7006
7192
  const filePathToComponentName = /* @__PURE__ */ new Map();
7193
+ const createEmptyComponentDependencies = (absolutePath) => {
7194
+ const viewsDirAbs = path.resolve(getViewsDirAbs());
7195
+ const relToViewsDir = path.relative(viewsDirAbs, absolutePath);
7196
+ const isView = !relToViewsDir.startsWith("..") && !path.isAbsolute(relToViewsDir);
7197
+ return {
7198
+ filePath: absolutePath,
7199
+ childrenComponentSet: /* @__PURE__ */ new Set(),
7200
+ usedComponentSet: /* @__PURE__ */ new Set(),
7201
+ dataTestIdSet: /* @__PURE__ */ new Set(),
7202
+ isView,
7203
+ methodsContent: ""
7204
+ };
7205
+ };
7007
7206
  const getComponentNameForFile = (filePath) => {
7008
7207
  const normalized = path.resolve(filePath);
7009
7208
  const existing = filePathToComponentName.get(normalized);
@@ -7013,7 +7212,7 @@ function createDevProcessorPlugin(options) {
7013
7212
  filename: normalized,
7014
7213
  projectRoot: projectRootRef.current,
7015
7214
  viewsDirAbs: getViewsDirAbs(),
7016
- scanDirs,
7215
+ sourceDirs: getSourceDirs(),
7017
7216
  extraRoots: [process.cwd()]
7018
7217
  });
7019
7218
  filePathToComponentName.set(normalized, name);
@@ -7023,8 +7222,6 @@ function createDevProcessorPlugin(options) {
7023
7222
  const started = performance.now();
7024
7223
  const absolutePath = path.resolve(filePath);
7025
7224
  const componentName = getComponentNameForFile(absolutePath);
7026
- targetVuePathMap.set(componentName, absolutePath);
7027
- targetHierarchy.delete(componentName);
7028
7225
  let sfc = "";
7029
7226
  try {
7030
7227
  sfc = fs.readFileSync(absolutePath, "utf8");
@@ -7032,9 +7229,15 @@ function createDevProcessorPlugin(options) {
7032
7229
  return { componentName, ms: performance.now() - started, compiled: false };
7033
7230
  }
7034
7231
  const template = extractTemplateFromSfc(sfc, absolutePath);
7035
- if (!template.trim())
7232
+ if (!template.trim()) {
7233
+ targetVuePathMap.set(componentName, absolutePath);
7234
+ targetHierarchy.set(componentName, createEmptyComponentDependencies(absolutePath));
7036
7235
  return { componentName, ms: performance.now() - started, compiled: true };
7236
+ }
7037
7237
  const { bindings: bindingMetadata, isScriptSetup } = getScriptInfo(sfc, absolutePath);
7238
+ const provisionalHierarchy = /* @__PURE__ */ new Map();
7239
+ const provisionalVuePathMap = new Map(targetVuePathMap);
7240
+ provisionalVuePathMap.set(componentName, absolutePath);
7038
7241
  compilerDom.compile(template, {
7039
7242
  filename: absolutePath,
7040
7243
  prefixIdentifiers: true,
@@ -7043,7 +7246,7 @@ function createDevProcessorPlugin(options) {
7043
7246
  nodeTransforms: [
7044
7247
  createTestIdTransform(
7045
7248
  componentName,
7046
- targetHierarchy,
7249
+ provisionalHierarchy,
7047
7250
  nativeWrappers,
7048
7251
  excludedComponents,
7049
7252
  getViewsDirAbs(),
@@ -7053,23 +7256,27 @@ function createDevProcessorPlugin(options) {
7053
7256
  missingSemanticNameBehavior,
7054
7257
  testIdAttribute,
7055
7258
  warn: (message) => loggerRef.current.warn(message),
7056
- vueFilesPathMap: targetVuePathMap,
7259
+ vueFilesPathMap: provisionalVuePathMap,
7057
7260
  wrapperSearchRoots: getWrapperSearchRoots()
7058
7261
  }
7059
7262
  )
7060
7263
  ]
7061
7264
  });
7265
+ targetVuePathMap.set(componentName, absolutePath);
7266
+ targetHierarchy.set(
7267
+ componentName,
7268
+ provisionalHierarchy.get(componentName) ?? createEmptyComponentDependencies(absolutePath)
7269
+ );
7062
7270
  return { componentName, ms: performance.now() - started, compiled: true };
7063
7271
  };
7064
- const fullRebuildSnapshotFromFilesystem = () => {
7272
+ const fullRebuildSnapshotFromFilesystem = (logLabel) => {
7065
7273
  const t0 = performance.now();
7066
7274
  const nextHierarchy = /* @__PURE__ */ new Map();
7067
7275
  const nextVuePathMap = /* @__PURE__ */ new Map();
7068
7276
  filePathToComponentName.clear();
7069
7277
  let totalVueFiles = 0;
7070
7278
  let compiledCount = 0;
7071
- for (const dir of scanDirs) {
7072
- const absDir = path.resolve(projectRootRef.current, dir);
7279
+ for (const absDir of getSourceDirRoots()) {
7073
7280
  if (!fs.existsSync(absDir))
7074
7281
  continue;
7075
7282
  const vueFiles = walkFilesRecursive(absDir);
@@ -7083,12 +7290,12 @@ function createDevProcessorPlugin(options) {
7083
7290
  snapshotHierarchy = nextHierarchy;
7084
7291
  snapshotVuePathMap = nextVuePathMap;
7085
7292
  const t1 = performance.now();
7086
- logInfo(`initial scan: found ${totalVueFiles} .vue files in ${scanDirs.join(", ")}`);
7087
- logInfo(`initial compile: ${compiledCount}/${totalVueFiles} files in ${formatMs(t1 - t0)} (components=${snapshotHierarchy.size})`);
7293
+ logInfo(`scan(${logLabel}): found ${totalVueFiles} .vue files in ${getSourceDirs().join(", ")}`);
7294
+ logInfo(`compile(${logLabel}): ${compiledCount}/${totalVueFiles} files in ${formatMs(t1 - t0)} (components=${snapshotHierarchy.size})`);
7088
7295
  };
7089
- const generateAggregatedFromSnapshot = (reason) => {
7296
+ const generateAggregatedFromSnapshot = async (logLabel) => {
7090
7297
  const t0 = performance.now();
7091
- generateFiles(snapshotHierarchy, snapshotVuePathMap, normalizedBasePagePath, {
7298
+ await generateFiles(snapshotHierarchy, snapshotVuePathMap, normalizedBasePagePath, {
7092
7299
  outDir,
7093
7300
  emitLanguages,
7094
7301
  typescriptOutputStructure,
@@ -7099,25 +7306,27 @@ function createDevProcessorPlugin(options) {
7099
7306
  customPomDir,
7100
7307
  customPomImportAliases,
7101
7308
  customPomImportNameCollisionBehavior,
7102
- viewsDir,
7103
- scanDirs,
7309
+ pageDirs: getPageDirs(),
7310
+ componentDirs: getComponentDirs(),
7311
+ layoutDirs: getLayoutDirs(),
7104
7312
  testIdAttribute,
7105
7313
  vueRouterFluentChaining: routerAwarePoms,
7106
- routerEntry: resolvedRouterEntry,
7314
+ routerEntry: getResolvedRouterEntry(),
7107
7315
  routerType
7108
7316
  });
7109
7317
  const t1 = performance.now();
7110
- logInfo(`generate(${reason}): components=${snapshotHierarchy.size} in ${formatMs(t1 - t0)}`);
7318
+ logInfo(`generate(${logLabel}): components=${snapshotHierarchy.size} in ${formatMs(t1 - t0)}`);
7111
7319
  };
7112
7320
  let timer = null;
7113
7321
  let maxWaitTimer = null;
7114
7322
  const pendingChangedVueFiles = /* @__PURE__ */ new Set();
7115
7323
  const pendingDeletedComponents = /* @__PURE__ */ new Set();
7324
+ let regenerationSequence = Promise.resolve();
7116
7325
  const initialBuildPromise = (async () => {
7117
7326
  const t0 = performance.now();
7118
7327
  await routerInitPromise;
7119
- fullRebuildSnapshotFromFilesystem();
7120
- generateAggregatedFromSnapshot("startup");
7328
+ fullRebuildSnapshotFromFilesystem("startup");
7329
+ await generateAggregatedFromSnapshot("startup");
7121
7330
  const t1 = performance.now();
7122
7331
  logInfo(`startup total: ${formatMs(t1 - t0)}`);
7123
7332
  })();
@@ -7145,7 +7354,7 @@ function createDevProcessorPlugin(options) {
7145
7354
  snapshotHierarchy = nextHierarchy;
7146
7355
  snapshotVuePathMap = nextVuePathMap;
7147
7356
  const t1 = performance.now();
7148
- generateAggregatedFromSnapshot(reason);
7357
+ await generateAggregatedFromSnapshot(reason);
7149
7358
  const t2 = performance.now();
7150
7359
  return {
7151
7360
  files,
@@ -7156,7 +7365,12 @@ function createDevProcessorPlugin(options) {
7156
7365
  totalMs: t2 - t0
7157
7366
  };
7158
7367
  };
7159
- const watchedVueGlobs = scanDirs.map((dir) => path.resolve(projectRootRef.current, dir, "**", "*.vue"));
7368
+ const enqueueRegeneration = (reason) => {
7369
+ const currentRun = regenerationSequence.catch(() => void 0).then(() => regenerateFromPending(reason));
7370
+ regenerationSequence = currentRun.then(() => void 0, () => void 0);
7371
+ return currentRun;
7372
+ };
7373
+ const watchedVueGlobs = getSourceDirRoots().map((scanDirAbs) => path.resolve(scanDirAbs, "**", "*.vue"));
7160
7374
  const watchedPluginGlob = path.resolve(projectRootRef.current, "vite-plugins", "vue-pom-generator", "**", "*.ts");
7161
7375
  const runtimeDir = path.dirname(basePageClassPath);
7162
7376
  server.watcher.add([
@@ -7182,7 +7396,7 @@ function createDevProcessorPlugin(options) {
7182
7396
  timer = null;
7183
7397
  }
7184
7398
  maxWaitTimer = null;
7185
- void regenerateFromPending("max-wait").then(({ files, deletedCount, compileMs, preGenerateMs, generateMs, totalMs }) => {
7399
+ void enqueueRegeneration("max-wait").then(({ files, deletedCount, compileMs, preGenerateMs, generateMs, totalMs }) => {
7186
7400
  logInfo(
7187
7401
  `max-wait: files=${files.length} deleted=${deletedCount} compile=${formatMs(compileMs)} wall=${formatMs(preGenerateMs)} gen=${formatMs(generateMs)} total=${formatMs(totalMs)}`
7188
7402
  );
@@ -7206,7 +7420,7 @@ function createDevProcessorPlugin(options) {
7206
7420
  maxWaitTimer = null;
7207
7421
  }
7208
7422
  const reason = pendingChangedVueFiles.size || pendingDeletedComponents.size ? "batched" : "noop";
7209
- void regenerateFromPending(reason).then(({ files, deletedCount, compileMs, preGenerateMs, generateMs, totalMs }) => {
7423
+ void enqueueRegeneration(reason).then(({ files, deletedCount, compileMs, preGenerateMs, generateMs, totalMs }) => {
7210
7424
  if (files.length || deletedCount) {
7211
7425
  logInfo(
7212
7426
  `batched: files=${files.length} deleted=${deletedCount} compile=${formatMs(compileMs)} wall=${formatMs(preGenerateMs)} gen=${formatMs(generateMs)} total=${formatMs(totalMs)}`
@@ -7233,11 +7447,7 @@ function createDevProcessorPlugin(options) {
7233
7447
  return;
7234
7448
  if (!p.endsWith(".vue"))
7235
7449
  return;
7236
- const isContainedInScanDirs = scanDirs.some((dir) => {
7237
- const absDir = path.resolve(projectRootRef.current, dir);
7238
- return p.startsWith(absDir + path.sep);
7239
- });
7240
- if (!isContainedInScanDirs)
7450
+ if (!isContainedInScanDirs(p))
7241
7451
  return;
7242
7452
  void (async () => {
7243
7453
  await initialBuildPromise;
@@ -7250,11 +7460,7 @@ function createDevProcessorPlugin(options) {
7250
7460
  return;
7251
7461
  if (!p.endsWith(".vue"))
7252
7462
  return;
7253
- const isContainedInScanDirs = scanDirs.some((dir) => {
7254
- const absDir = path.resolve(projectRootRef.current, dir);
7255
- return p.startsWith(absDir + path.sep);
7256
- });
7257
- if (!isContainedInScanDirs)
7463
+ if (!isContainedInScanDirs(p))
7258
7464
  return;
7259
7465
  void (async () => {
7260
7466
  await initialBuildPromise;
@@ -7318,8 +7524,11 @@ function createSupportPlugins(options) {
7318
7524
  vueFilesPathMap,
7319
7525
  nativeWrappers,
7320
7526
  excludedComponents,
7321
- viewsDir,
7322
- scanDirs,
7527
+ getPageDirs,
7528
+ getComponentDirs,
7529
+ getLayoutDirs,
7530
+ getViewsDir,
7531
+ getSourceDirs,
7323
7532
  getWrapperSearchRoots,
7324
7533
  nameCollisionBehavior = "suffix",
7325
7534
  missingSemanticNameBehavior,
@@ -7351,7 +7560,6 @@ function createSupportPlugins(options) {
7351
7560
  throw new Error("[vue-pom-generator] router.entry is required when router introspection is enabled.");
7352
7561
  return path.isAbsolute(routerEntry) ? routerEntry : path.resolve(projectRootRef.current, routerEntry);
7353
7562
  };
7354
- const resolvedRouterEntry = resolveRouterEntry2();
7355
7563
  const getDefaultBasePageClassPath = () => {
7356
7564
  try {
7357
7565
  return fileURLToPath(new URL("../class-generation/base-page.ts", import.meta.url));
@@ -7364,8 +7572,11 @@ function createSupportPlugins(options) {
7364
7572
  const tsProcessor = createBuildProcessorPlugin({
7365
7573
  componentHierarchyMap,
7366
7574
  vueFilesPathMap,
7367
- viewsDir,
7368
- scanDirs,
7575
+ getPageDirs,
7576
+ getComponentDirs,
7577
+ getLayoutDirs,
7578
+ getViewsDir,
7579
+ getSourceDirs,
7369
7580
  basePageClassPath,
7370
7581
  normalizedBasePagePath,
7371
7582
  outDir,
@@ -7387,15 +7598,18 @@ function createSupportPlugins(options) {
7387
7598
  getWrapperSearchRoots,
7388
7599
  routerAwarePoms,
7389
7600
  routerType,
7390
- resolvedRouterEntry,
7601
+ getResolvedRouterEntry: resolveRouterEntry2,
7391
7602
  routerModuleShims,
7392
7603
  loggerRef
7393
7604
  });
7394
7605
  const devProcessor = createDevProcessorPlugin({
7395
7606
  nativeWrappers,
7396
7607
  excludedComponents,
7397
- viewsDir,
7398
- scanDirs,
7608
+ getPageDirs,
7609
+ getComponentDirs,
7610
+ getLayoutDirs,
7611
+ getViewsDir,
7612
+ getSourceDirs,
7399
7613
  getWrapperSearchRoots,
7400
7614
  projectRootRef,
7401
7615
  normalizedBasePagePath,
@@ -7415,7 +7629,7 @@ function createSupportPlugins(options) {
7415
7629
  testIdAttribute,
7416
7630
  routerAwarePoms,
7417
7631
  routerType,
7418
- resolvedRouterEntry,
7632
+ getResolvedRouterEntry: resolveRouterEntry2,
7419
7633
  routerModuleShims,
7420
7634
  loggerRef
7421
7635
  });
@@ -7573,7 +7787,7 @@ function createVuePluginWithTestIds(options) {
7573
7787
  getViewsDirAbs,
7574
7788
  testIdAttribute,
7575
7789
  loggerRef,
7576
- scanDirs = ["src"],
7790
+ getSourceDirs,
7577
7791
  getWrapperSearchRoots,
7578
7792
  getProjectRoot
7579
7793
  } = options;
@@ -7582,7 +7796,7 @@ function createVuePluginWithTestIds(options) {
7582
7796
  filename,
7583
7797
  projectRoot: getProjectRoot(),
7584
7798
  viewsDirAbs: getViewsDirAbs(),
7585
- scanDirs,
7799
+ sourceDirs: getSourceDirs(),
7586
7800
  extraRoots: [process.cwd()]
7587
7801
  });
7588
7802
  };
@@ -7598,7 +7812,7 @@ function createVuePluginWithTestIds(options) {
7598
7812
  if (absFilename.startsWith(viewsDirAbs + path.sep) || absFilename === viewsDirAbs)
7599
7813
  return true;
7600
7814
  const rootsToTry = [projectRoot, process.cwd()];
7601
- const matched = scanDirs.some((dir) => {
7815
+ const matched = getSourceDirs().some((dir) => {
7602
7816
  return rootsToTry.some((root) => {
7603
7817
  const absDir = path.resolve(root, dir);
7604
7818
  if (absFilename.startsWith(absDir + path.sep) || absFilename === absDir)
@@ -7773,6 +7987,25 @@ function createVuePluginWithTestIds(options) {
7773
7987
  };
7774
7988
  return { metadataCollectorPlugin, internalVuePlugin, nuxtVueBridgePlugin, templateCompilerOptions };
7775
7989
  }
7990
+ const nuxtConfigMarker$1 = /* @__PURE__ */ Symbol.for("@immense/vue-pom-generator.nuxt");
7991
+ const nuxtConfigFileNames = [
7992
+ "nuxt.config.ts",
7993
+ "nuxt.config.js",
7994
+ "nuxt.config.mjs",
7995
+ "nuxt.config.cjs",
7996
+ "nuxt.config.mts",
7997
+ "nuxt.config.cts",
7998
+ ".nuxtrc"
7999
+ ];
8000
+ const nuxtSourceMarkers = [
8001
+ "app.vue",
8002
+ "app",
8003
+ "pages",
8004
+ "layouts",
8005
+ "components",
8006
+ "layers",
8007
+ ".nuxt"
8008
+ ];
7776
8009
  function assertNonEmptyString(value, name) {
7777
8010
  if (!value || !value.trim()) {
7778
8011
  throw new Error(`${name} must be a non-empty string.`);
@@ -7820,6 +8053,57 @@ function resolveMissingSemanticNameBehavior(value) {
7820
8053
  }
7821
8054
  return value.missingSemanticNameBehavior ?? "ignore";
7822
8055
  }
8056
+ function readPackageJson(projectRoot) {
8057
+ const packageJsonPath = path.join(projectRoot, "package.json");
8058
+ if (!fs.existsSync(packageJsonPath)) {
8059
+ return null;
8060
+ }
8061
+ try {
8062
+ return JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
8063
+ } catch {
8064
+ return null;
8065
+ }
8066
+ }
8067
+ function recordHasOwnStringKey(value, key) {
8068
+ return value !== null && value !== void 0 && !Array.isArray(value) && Object.prototype.hasOwnProperty.call(value, key) && typeof value[key] === "string";
8069
+ }
8070
+ function projectPackageLooksNuxt(projectRoot) {
8071
+ const packageJson = readPackageJson(projectRoot);
8072
+ if (!packageJson) {
8073
+ return false;
8074
+ }
8075
+ const dependencyGroups = [
8076
+ packageJson.dependencies,
8077
+ packageJson.devDependencies,
8078
+ packageJson.peerDependencies,
8079
+ packageJson.optionalDependencies
8080
+ ];
8081
+ if (dependencyGroups.some((group) => {
8082
+ const dependencyGroup = typeof group === "object" && group !== null ? group : void 0;
8083
+ return recordHasOwnStringKey(dependencyGroup, "nuxt") || recordHasOwnStringKey(dependencyGroup, "nuxt-nightly");
8084
+ })) {
8085
+ return true;
8086
+ }
8087
+ if (typeof packageJson.scripts !== "object" || packageJson.scripts === null || Array.isArray(packageJson.scripts)) {
8088
+ return false;
8089
+ }
8090
+ return Object.values(packageJson.scripts).some((script) => {
8091
+ if (typeof script !== "string") {
8092
+ return false;
8093
+ }
8094
+ const normalizedScript = script.trim();
8095
+ return normalizedScript === "nuxt" || normalizedScript.startsWith("nuxt ") || normalizedScript === "nuxi" || normalizedScript.startsWith("nuxi ");
8096
+ });
8097
+ }
8098
+ function detectNuxtProject(options, projectRoot) {
8099
+ if (options[nuxtConfigMarker$1] === true) {
8100
+ return true;
8101
+ }
8102
+ if (nuxtConfigFileNames.some((fileName) => fs.existsSync(path.join(projectRoot, fileName)))) {
8103
+ return true;
8104
+ }
8105
+ return projectPackageLooksNuxt(projectRoot) && nuxtSourceMarkers.some((entry) => fs.existsSync(path.join(projectRoot, entry)));
8106
+ }
7823
8107
  function assertRouterModuleShims(value, name) {
7824
8108
  if (!value)
7825
8109
  return;
@@ -7927,14 +8211,18 @@ function assertNotVitePluginInstance(options) {
7927
8211
  function createVuePomGeneratorPlugins(options = {}) {
7928
8212
  assertNotVitePluginInstance(options);
7929
8213
  const injection = options.injection ?? {};
8214
+ const isNuxt = detectNuxtProject(options, process.cwd());
7930
8215
  const generationSetting = options.generation;
7931
8216
  const generationOptions = generationSetting === false ? null : generationSetting ?? {};
7932
8217
  const generationEnabled = generationOptions !== null;
8218
+ const vueGenerationOptions = generationOptions;
7933
8219
  const verbosity = options.logging?.verbosity ?? "warn";
7934
8220
  const vueOptions = options.vueOptions;
7935
- const viewsDir = injection.viewsDir ?? "src/views";
7936
- const scanDirs = injection.scanDirs ?? ["src"];
7937
- const wrapperSearchRoots = injection.wrapperSearchRoots ?? [];
8221
+ const legacyVueOptions = options;
8222
+ const pageDirsRef = { current: !isNuxt ? [legacyVueOptions.injection?.viewsDir ?? "src/views"] : ["app/pages"] };
8223
+ const componentDirsRef = { current: !isNuxt ? legacyVueOptions.injection?.componentDirs ?? ["src/components"] : ["app/components"] };
8224
+ const layoutDirsRef = { current: !isNuxt ? legacyVueOptions.injection?.layoutDirs ?? ["src/layouts"] : ["app/layouts"] };
8225
+ const wrapperSearchRootsRef = { current: !isNuxt ? legacyVueOptions.injection?.wrapperSearchRoots ?? [] : [] };
7938
8226
  const nativeWrappers = injection.nativeWrappers ?? {};
7939
8227
  const excludedComponents = injection.excludeComponents ?? [];
7940
8228
  const testIdAttribute = (injection.attribute ?? "data-testid").trim() || "data-testid";
@@ -7942,10 +8230,9 @@ function createVuePomGeneratorPlugins(options = {}) {
7942
8230
  const outDir = (generationOptions?.outDir ?? "tests/playwright/__generated__").trim();
7943
8231
  const emitLanguages = generationOptions?.emit && generationOptions.emit.length ? generationOptions.emit : ["ts"];
7944
8232
  const nameCollisionBehavior = generationOptions?.nameCollisionBehavior ?? "suffix";
7945
- const routerEntry = generationOptions?.router?.entry;
7946
- const routerType = generationOptions?.router?.type ?? "vue-router";
7947
- const routerModuleShims = generationOptions?.router?.moduleShims;
7948
- const isNuxt = routerType === "nuxt";
8233
+ const routerEntry = !isNuxt ? vueGenerationOptions?.router?.entry : void 0;
8234
+ const routerType = isNuxt ? "nuxt" : vueGenerationOptions?.router?.type ?? "vue-router";
8235
+ const routerModuleShims = !isNuxt ? vueGenerationOptions?.router?.moduleShims : void 0;
7949
8236
  if (isNuxt && options.vuePluginOwnership === "internal") {
7950
8237
  throw new Error('[vue-pom-generator] Nuxt projects must use the resolved app-owned vite:vue plugin. Omit vuePluginOwnership or set it to "external".');
7951
8238
  }
@@ -7962,66 +8249,100 @@ function createVuePomGeneratorPlugins(options = {}) {
7962
8249
  const resolvedCustomPomImportAliases = customPoms?.importAliases;
7963
8250
  const resolvedCustomPomImportCollisionBehavior = customPoms?.importNameCollisionBehavior ?? "error";
7964
8251
  const basePageClassPathOverride = generationOptions?.basePageClassPath;
8252
+ const getPageDirs = () => pageDirsRef.current;
8253
+ const getViewsDir = () => getPageDirs()[0] ?? "src/views";
8254
+ const getComponentDirs = () => componentDirsRef.current;
8255
+ const getLayoutDirs = () => layoutDirsRef.current;
8256
+ const getSourceDirs = () => Array.from(/* @__PURE__ */ new Set([
8257
+ ...getPageDirs(),
8258
+ ...getComponentDirs(),
8259
+ ...getLayoutDirs()
8260
+ ]));
8261
+ const getWrapperSearchRoots = () => wrapperSearchRootsRef.current;
7965
8262
  const sharedStateKey = JSON.stringify({
7966
8263
  cwd: process.cwd(),
7967
- viewsDir,
7968
- scanDirs,
7969
- wrapperSearchRoots,
8264
+ mode: isNuxt ? "nuxt" : "vue",
8265
+ pageDirs: isNuxt ? null : getPageDirs(),
8266
+ componentDirs: isNuxt ? null : getComponentDirs(),
8267
+ layoutDirs: isNuxt ? null : getLayoutDirs(),
8268
+ wrapperSearchRoots: isNuxt ? null : getWrapperSearchRoots(),
7970
8269
  outDir,
7971
8270
  testIdAttribute,
7972
8271
  routerType,
7973
8272
  vuePluginOwnership
7974
8273
  });
7975
8274
  const sharedState = getSharedGeneratorState(sharedStateKey);
8275
+ let templateCompilerOptionsForResolvedPlugin;
7976
8276
  const projectRootRef = { current: process.cwd() };
7977
8277
  const loggerRef = {
7978
8278
  current: createLogger({ verbosity })
7979
8279
  };
7980
- const getViewsDirAbs = () => resolveFromProjectRoot(projectRootRef.current, viewsDir);
7981
- const getWrapperSearchRootsAbs = () => wrapperSearchRoots.map((root) => resolveFromProjectRoot(projectRootRef.current, root));
7982
- const { componentTestIds, elementMetadata, semanticNameMap, componentHierarchyMap, vueFilesPathMap } = sharedState;
7983
- const { metadataCollectorPlugin, internalVuePlugin, templateCompilerOptions } = createVuePluginWithTestIds({
7984
- vueOptions,
7985
- existingIdBehavior,
7986
- nameCollisionBehavior,
7987
- nativeWrappers,
7988
- elementMetadata,
7989
- semanticNameMap,
7990
- componentHierarchyMap,
7991
- vueFilesPathMap,
7992
- excludedComponents,
7993
- getViewsDirAbs,
7994
- testIdAttribute,
7995
- loggerRef,
7996
- scanDirs,
7997
- getWrapperSearchRoots: getWrapperSearchRootsAbs,
7998
- getProjectRoot: () => projectRootRef.current
7999
- });
8000
8280
  const configPlugin = {
8001
8281
  name: "vue-pom-generator-config",
8002
8282
  enforce: "pre",
8003
- configResolved(config) {
8283
+ async configResolved(config) {
8004
8284
  projectRootRef.current = config.root;
8005
8285
  loggerRef.current = createLogger({ verbosity, viteLogger: config.logger });
8286
+ if (vueGenerationOptions?.router?.type === "nuxt") {
8287
+ throw new Error('[vue-pom-generator] Remove generation.router.type="nuxt". Nuxt projects are auto-detected.');
8288
+ }
8289
+ if (isNuxt) {
8290
+ const nuxtDiscovery = await loadNuxtProjectDiscovery(process.cwd());
8291
+ projectRootRef.current = nuxtDiscovery.rootDir;
8292
+ pageDirsRef.current = nuxtDiscovery.pageDirs.length ? nuxtDiscovery.pageDirs : [path.resolve(nuxtDiscovery.srcDir, "pages")];
8293
+ componentDirsRef.current = nuxtDiscovery.componentDirs;
8294
+ layoutDirsRef.current = nuxtDiscovery.layoutDirs;
8295
+ wrapperSearchRootsRef.current = nuxtDiscovery.wrapperSearchRoots;
8296
+ }
8006
8297
  assertNonEmptyString(testIdAttribute, "[vue-pom-generator] injection.attribute");
8007
- assertNonEmptyString(viewsDir, "[vue-pom-generator] injection.viewsDir");
8008
- assertNonEmptyStringArray(wrapperSearchRoots, "[vue-pom-generator] injection.wrapperSearchRoots");
8298
+ assertNonEmptyString(getViewsDir(), "[vue-pom-generator] injection.viewsDir");
8299
+ assertNonEmptyStringArray(getComponentDirs(), "[vue-pom-generator] injection.componentDirs");
8300
+ assertNonEmptyStringArray(getLayoutDirs(), "[vue-pom-generator] injection.layoutDirs");
8301
+ assertNonEmptyStringArray(getWrapperSearchRoots(), "[vue-pom-generator] injection.wrapperSearchRoots");
8009
8302
  assertErrorBehavior(errorBehavior, "[vue-pom-generator] errorBehavior");
8010
8303
  if (generationEnabled) {
8011
8304
  assertNonEmptyString(outDir, "[vue-pom-generator] generation.outDir");
8012
8305
  assertOneOf(typescriptOutputStructure, ["aggregated", "split"], "[vue-pom-generator] generation.playwright.outputStructure");
8013
8306
  assertRouterModuleShims(routerModuleShims, "[vue-pom-generator] generation.router.moduleShims");
8014
- if (generationOptions?.router && routerType === "vue-router") {
8307
+ if (!isNuxt && vueGenerationOptions?.router && routerType === "vue-router") {
8015
8308
  assertNonEmptyString(routerEntry, "[vue-pom-generator] generation.router.entry");
8016
8309
  }
8017
8310
  }
8018
8311
  if (usesExternalVuePlugin) {
8019
- applyTemplateCompilerOptionsToResolvedVuePlugin(config, templateCompilerOptions, isNuxt ? "nuxt" : vuePluginOwnership);
8312
+ applyTemplateCompilerOptionsToResolvedVuePlugin(
8313
+ config,
8314
+ templateCompilerOptionsForResolvedPlugin,
8315
+ isNuxt ? "nuxt" : vuePluginOwnership
8316
+ );
8020
8317
  }
8021
8318
  loggerRef.current.info(`projectRoot=${projectRootRef.current}`);
8319
+ loggerRef.current.info(`viewsDir=${getViewsDir()}`);
8320
+ loggerRef.current.info(`componentDirs=${getComponentDirs().join(", ")}`);
8321
+ loggerRef.current.info(`layoutDirs=${getLayoutDirs().join(", ")}`);
8022
8322
  loggerRef.current.info(`Active plugins: ${(config.plugins ?? []).map((p) => p.name).filter((n) => n.includes("vue-pom")).join(", ")}`);
8023
8323
  }
8024
8324
  };
8325
+ const getViewsDirAbs = () => resolveFromProjectRoot(projectRootRef.current, getViewsDir());
8326
+ const getWrapperSearchRootsAbs = () => getWrapperSearchRoots().map((root) => resolveFromProjectRoot(projectRootRef.current, root));
8327
+ const { componentTestIds, elementMetadata, semanticNameMap, componentHierarchyMap, vueFilesPathMap } = sharedState;
8328
+ const { metadataCollectorPlugin, internalVuePlugin, templateCompilerOptions } = createVuePluginWithTestIds({
8329
+ vueOptions,
8330
+ existingIdBehavior,
8331
+ nameCollisionBehavior,
8332
+ nativeWrappers,
8333
+ elementMetadata,
8334
+ semanticNameMap,
8335
+ componentHierarchyMap,
8336
+ vueFilesPathMap,
8337
+ excludedComponents,
8338
+ getViewsDirAbs,
8339
+ testIdAttribute,
8340
+ loggerRef,
8341
+ getSourceDirs,
8342
+ getWrapperSearchRoots: getWrapperSearchRootsAbs,
8343
+ getProjectRoot: () => projectRootRef.current
8344
+ });
8345
+ templateCompilerOptionsForResolvedPlugin = templateCompilerOptions;
8025
8346
  const routerAwarePoms = typeof routerEntry === "string" && routerEntry.trim().length > 0 || routerType === "nuxt";
8026
8347
  const supportPlugins = createSupportPlugins({
8027
8348
  componentTestIds,
@@ -8029,8 +8350,11 @@ function createVuePomGeneratorPlugins(options = {}) {
8029
8350
  vueFilesPathMap,
8030
8351
  nativeWrappers,
8031
8352
  excludedComponents,
8032
- viewsDir,
8033
- scanDirs,
8353
+ getPageDirs,
8354
+ getComponentDirs,
8355
+ getLayoutDirs,
8356
+ getViewsDir,
8357
+ getSourceDirs,
8034
8358
  getWrapperSearchRoots: getWrapperSearchRootsAbs,
8035
8359
  nameCollisionBehavior,
8036
8360
  missingSemanticNameBehavior,
@@ -8075,12 +8399,22 @@ function createVuePomGeneratorPlugins(options = {}) {
8075
8399
  }
8076
8400
  return resultPlugins;
8077
8401
  }
8402
+ const nuxtConfigMarker = /* @__PURE__ */ Symbol.for("@immense/vue-pom-generator.nuxt");
8078
8403
  function defineVuePomGeneratorConfig(options) {
8079
8404
  return options;
8080
8405
  }
8406
+ function defineNuxtPomGeneratorConfig(options) {
8407
+ const markedOptions = { ...options };
8408
+ Object.defineProperty(markedOptions, nuxtConfigMarker, {
8409
+ value: true,
8410
+ enumerable: false
8411
+ });
8412
+ return markedOptions;
8413
+ }
8081
8414
  export {
8082
8415
  createVuePomGeneratorPlugins,
8083
8416
  createVuePomGeneratorPlugins as default,
8417
+ defineNuxtPomGeneratorConfig,
8084
8418
  defineVuePomGeneratorConfig,
8085
8419
  createVuePomGeneratorPlugins as vuePomGenerator
8086
8420
  };