@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.cjs CHANGED
@@ -22,10 +22,11 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
22
22
  mod
23
23
  ));
24
24
  Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: "Module" } });
25
+ const fs = require("node:fs");
25
26
  const path = require("node:path");
26
27
  const process = require("node:process");
28
+ const node_module = require("node:module");
27
29
  const node_url = require("node:url");
28
- const fs = require("node:fs");
29
30
  const compilerDom = require("@vue/compiler-dom");
30
31
  const compilerSfc = require("@vue/compiler-sfc");
31
32
  const parser = require("@babel/parser");
@@ -104,6 +105,133 @@ function createLogger(options) {
104
105
  }
105
106
  };
106
107
  }
108
+ const requireFromModule = node_module.createRequire(typeof document === "undefined" ? require("url").pathToFileURL(__filename).href : _documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === "SCRIPT" && _documentCurrentScript.src || new URL("index.cjs", document.baseURI).href);
109
+ function toUniqueResolvedPaths(paths) {
110
+ return Array.from(new Set(paths.map((value) => path.resolve(value))));
111
+ }
112
+ function resolveNuxtAlias(value, context) {
113
+ const aliases = [
114
+ ["~~", context.rootDir],
115
+ ["@@", context.rootDir],
116
+ ["~/", `${context.srcDir}${path.sep}`],
117
+ ["@/", `${context.srcDir}${path.sep}`],
118
+ ["~", context.srcDir],
119
+ ["@", context.srcDir]
120
+ ];
121
+ for (const [alias, replacement] of Object.entries(context.alias ?? {})) {
122
+ aliases.push([alias, replacement]);
123
+ }
124
+ aliases.sort((a, b) => b[0].length - a[0].length);
125
+ for (const [alias, replacement] of aliases) {
126
+ if (value === alias)
127
+ return replacement;
128
+ if (value.startsWith(`${alias}/`) || value.startsWith(`${alias}${path.sep}`)) {
129
+ const suffix = value.slice(alias.length + 1);
130
+ return path.resolve(replacement, suffix);
131
+ }
132
+ }
133
+ return value;
134
+ }
135
+ function resolveNuxtPath(value, baseDir, context) {
136
+ const resolvedAlias = resolveNuxtAlias(value, context);
137
+ return path.isAbsolute(resolvedAlias) ? path.resolve(resolvedAlias) : path.resolve(baseDir, resolvedAlias);
138
+ }
139
+ function normalizeNuxtComponentDirs(value, baseDir, context) {
140
+ if (Array.isArray(value)) {
141
+ return value.flatMap((entry) => normalizeNuxtComponentDirs(entry, baseDir, context));
142
+ }
143
+ if (value === false) {
144
+ return [];
145
+ }
146
+ if (value === true || value === void 0) {
147
+ return [
148
+ path.resolve(baseDir, "components/islands"),
149
+ path.resolve(baseDir, "components/global"),
150
+ path.resolve(baseDir, "components")
151
+ ];
152
+ }
153
+ if (typeof value === "string") {
154
+ return [resolveNuxtPath(value, baseDir, context)];
155
+ }
156
+ if (!value || typeof value !== "object") {
157
+ return [];
158
+ }
159
+ if (typeof value === "object" && value && "path" in value && typeof value.path === "string") {
160
+ return [resolveNuxtPath(value.path, baseDir, context)];
161
+ }
162
+ if (typeof value !== "object" || !value || !("dirs" in value)) {
163
+ return [];
164
+ }
165
+ return normalizeNuxtComponentDirs(value.dirs, baseDir, context);
166
+ }
167
+ function resolveNuxtProjectDiscovery(nuxtOptions, getLayerDirectories, cwd = process.cwd()) {
168
+ const rootDir = path.resolve(nuxtOptions.rootDir ?? cwd);
169
+ const srcDir = path.resolve(nuxtOptions.srcDir ?? rootDir);
170
+ const fallbackLayer = {
171
+ cwd: rootDir,
172
+ config: {
173
+ rootDir,
174
+ srcDir,
175
+ dir: nuxtOptions.dir,
176
+ components: nuxtOptions.components,
177
+ alias: nuxtOptions.alias
178
+ }
179
+ };
180
+ const layers = nuxtOptions._layers?.length ? nuxtOptions._layers : [fallbackLayer];
181
+ const normalizedNuxtOptions = {
182
+ ...nuxtOptions,
183
+ _layers: layers
184
+ };
185
+ const layerDirectories = getLayerDirectories({ options: normalizedNuxtOptions });
186
+ const pageDirs = layerDirectories.map((layer) => layer.appPages);
187
+ const layoutDirs = layerDirectories.map((layer) => layer.appLayouts);
188
+ const componentDirs = [];
189
+ for (const [index, layer] of layers.entries()) {
190
+ const layerDirectory = layerDirectories[index];
191
+ const layerRootDir = path.resolve(layerDirectory?.root ?? layer.config?.rootDir ?? layer.cwd ?? rootDir);
192
+ const layerSrcDir = path.resolve(layerDirectory?.app ?? layer.config?.srcDir ?? layer.cwd ?? srcDir);
193
+ const context = {
194
+ rootDir: layerRootDir,
195
+ srcDir: layerSrcDir,
196
+ alias: {
197
+ ...nuxtOptions.alias ?? {},
198
+ ...layer.config?.alias ?? {}
199
+ }
200
+ };
201
+ componentDirs.push(...normalizeNuxtComponentDirs(layer.config?.components, layerSrcDir, context));
202
+ }
203
+ const uniquePageDirs = toUniqueResolvedPaths(pageDirs);
204
+ const uniqueLayoutDirs = toUniqueResolvedPaths(layoutDirs);
205
+ const uniqueComponentDirs = toUniqueResolvedPaths(componentDirs);
206
+ return {
207
+ rootDir,
208
+ srcDir,
209
+ pageDirs: uniquePageDirs,
210
+ layoutDirs: uniqueLayoutDirs,
211
+ componentDirs: uniqueComponentDirs,
212
+ wrapperSearchRoots: []
213
+ };
214
+ }
215
+ async function loadNuxtProjectDiscovery(cwd = process.cwd()) {
216
+ let loadNuxtConfig;
217
+ let getLayerDirectories;
218
+ try {
219
+ const nuxtKitEntry = requireFromModule.resolve("@nuxt/kit");
220
+ ({ loadNuxtConfig, getLayerDirectories } = await import(node_url.pathToFileURL(nuxtKitEntry).href));
221
+ } catch (error) {
222
+ throw new TypeError(
223
+ `[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)}`
224
+ );
225
+ }
226
+ if (typeof loadNuxtConfig !== "function") {
227
+ throw new TypeError("[vue-pom-generator] Nuxt mode requires @nuxt/kit.loadNuxtConfig().");
228
+ }
229
+ if (typeof getLayerDirectories !== "function") {
230
+ throw new TypeError("[vue-pom-generator] Nuxt mode requires @nuxt/kit.getLayerDirectories().");
231
+ }
232
+ const nuxtOptions = await loadNuxtConfig({ cwd });
233
+ return resolveNuxtProjectDiscovery(nuxtOptions, getLayerDirectories, cwd);
234
+ }
107
235
  function createTypeScriptProject() {
108
236
  return new tsMorph.Project({
109
237
  useInMemoryFileSystem: true,
@@ -2796,27 +2924,24 @@ function safeRealpath(value) {
2796
2924
  const resolvedParent = safeRealpath(parent);
2797
2925
  return resolvedParent === parent ? value : path.join(resolvedParent, path.basename(value));
2798
2926
  }
2927
+ function isPathWithinDir(filePathAbs, dirPathAbs) {
2928
+ const fileAbs = path.resolve(filePathAbs);
2929
+ const dirAbs = path.resolve(dirPathAbs);
2930
+ const rel = path.relative(dirAbs, fileAbs);
2931
+ if (!rel)
2932
+ return true;
2933
+ return !rel.startsWith("..") && !path.isAbsolute(rel);
2934
+ }
2799
2935
  function resolveComponentNameFromPath(options) {
2800
- const { projectRoot, viewsDirAbs, scanDirs, extraRoots = [] } = options;
2936
+ const { projectRoot, viewsDirAbs, sourceDirs, extraRoots = [] } = options;
2801
2937
  const cleanFilename = options.filename.includes("?") ? options.filename.substring(0, options.filename.indexOf("?")) : options.filename;
2802
2938
  const absFilename = path.isAbsolute(cleanFilename) ? cleanFilename : path.resolve(projectRoot, cleanFilename);
2803
2939
  const normalizedAbsFilename = path.normalize(safeRealpath(absFilename));
2804
2940
  const rootBases = [projectRoot, ...extraRoots.filter((r) => r !== projectRoot)];
2805
- const roots = [viewsDirAbs, ...rootBases.flatMap((base) => scanDirs.map((d) => path.resolve(base, d)))];
2806
- for (const base of rootBases) {
2807
- for (const dir of scanDirs) {
2808
- const absDir = path.resolve(base, dir);
2809
- try {
2810
- const pagesDir = path.join(absDir, "pages");
2811
- if (fs.existsSync(pagesDir))
2812
- roots.push(pagesDir);
2813
- const componentsDir = path.join(absDir, "components");
2814
- if (fs.existsSync(componentsDir))
2815
- roots.push(componentsDir);
2816
- } catch {
2817
- }
2818
- }
2819
- }
2941
+ const roots = [
2942
+ viewsDirAbs,
2943
+ ...sourceDirs.flatMap((dir) => path.isAbsolute(dir) ? [dir] : rootBases.map((base) => path.resolve(base, dir)))
2944
+ ];
2820
2945
  const potentialRoots = Array.from(new Set(roots.map((r) => path.normalize(safeRealpath(r))))).sort((a, b) => b.length - a.length);
2821
2946
  for (const root of potentialRoots) {
2822
2947
  if (normalizedAbsFilename.startsWith(root + path.sep) || normalizedAbsFilename === root) {
@@ -3180,7 +3305,7 @@ function resolveIntrospectedComponentName(componentInfo, componentNaming) {
3180
3305
  filename: componentInfo.filePath,
3181
3306
  projectRoot: componentNaming.projectRoot,
3182
3307
  viewsDirAbs: componentNaming.viewsDirAbs,
3183
- scanDirs: componentNaming.scanDirs,
3308
+ sourceDirs: componentNaming.sourceDirs,
3184
3309
  extraRoots: componentNaming.extraRoots
3185
3310
  });
3186
3311
  }
@@ -3292,69 +3417,97 @@ async function ensureDomShim() {
3292
3417
  if (!g.requestAnimationFrame)
3293
3418
  g.requestAnimationFrame = (cb) => setTimeout(() => cb(Date.now()), 16);
3294
3419
  }
3295
- async function introspectNuxtPages(projectRoot) {
3296
- const possiblePagesDirs = ["app/pages", "pages"];
3297
- let pagesDir = "";
3298
- for (const dir of possiblePagesDirs) {
3299
- const abs = path.resolve(projectRoot, dir);
3300
- if (fs.existsSync(abs) && fs.statSync(abs).isDirectory()) {
3301
- pagesDir = abs;
3420
+ function unwrapNuxtPageSegment(segment, prefix, suffix) {
3421
+ if (!segment.startsWith(prefix) || !segment.endsWith(suffix))
3422
+ return null;
3423
+ const value = segment.slice(prefix.length, segment.length - suffix.length);
3424
+ return value.length > 0 ? value : null;
3425
+ }
3426
+ function resolveNuxtPageSegment(segment) {
3427
+ if (segment === "index") {
3428
+ return { pathPart: "", params: [] };
3429
+ }
3430
+ const optionalParamName = unwrapNuxtPageSegment(segment, "[[", "]]");
3431
+ if (optionalParamName) {
3432
+ return {
3433
+ pathPart: `:${optionalParamName}?`,
3434
+ params: [{ name: optionalParamName, optional: true }]
3435
+ };
3436
+ }
3437
+ const catchAllParamName = unwrapNuxtPageSegment(segment, "[...", "]");
3438
+ if (catchAllParamName) {
3439
+ return {
3440
+ pathPart: `:${catchAllParamName}(.*)*`,
3441
+ params: [{ name: catchAllParamName, optional: false }]
3442
+ };
3443
+ }
3444
+ const requiredParamName = unwrapNuxtPageSegment(segment, "[", "]");
3445
+ if (requiredParamName) {
3446
+ return {
3447
+ pathPart: `:${requiredParamName}`,
3448
+ params: [{ name: requiredParamName, optional: false }]
3449
+ };
3450
+ }
3451
+ return { pathPart: segment, params: [] };
3452
+ }
3453
+ function toPathSegments(value) {
3454
+ const segments = [];
3455
+ let current = path.normalize(value);
3456
+ while (current && current !== "." && current !== path.sep) {
3457
+ const parsed = path.parse(current);
3458
+ if (!parsed.base || parsed.base === ".")
3302
3459
  break;
3303
- }
3460
+ segments.unshift(parsed.base);
3461
+ if (!parsed.dir || parsed.dir === "." || parsed.dir === current)
3462
+ break;
3463
+ current = parsed.dir;
3304
3464
  }
3305
- if (!pagesDir) {
3465
+ return segments;
3466
+ }
3467
+ async function introspectNuxtPages(projectRoot, options = {}) {
3468
+ const possiblePageDirs = options.pageDirs?.length ? options.pageDirs : ["app/pages", "pages"].map((dir) => path.resolve(projectRoot, dir));
3469
+ const pageDirs = possiblePageDirs.map((dir) => path.resolve(projectRoot, dir)).filter((dir) => {
3470
+ try {
3471
+ return fs.existsSync(dir) && fs.statSync(dir).isDirectory();
3472
+ } catch {
3473
+ return false;
3474
+ }
3475
+ });
3476
+ if (!pageDirs.length) {
3306
3477
  debugLog(`[router-introspection][nuxt] Could not find pages directory in ${projectRoot}`);
3307
3478
  return { routeNameMap: /* @__PURE__ */ new Map(), routePathMap: /* @__PURE__ */ new Map(), routeMetaEntries: [] };
3308
3479
  }
3480
+ const routePathMap = /* @__PURE__ */ new Map();
3309
3481
  const routeMetaEntries = [];
3310
- const walk = (dir, baseRoute) => {
3482
+ const walk = (pagesDir, dir) => {
3311
3483
  const files = fs.readdirSync(dir);
3312
3484
  for (const file of files) {
3313
3485
  const fullPath = path.join(dir, file);
3314
3486
  const stat = fs.statSync(fullPath);
3315
3487
  if (stat.isDirectory()) {
3316
- walk(fullPath, `${baseRoute}/${file}`);
3488
+ walk(pagesDir, fullPath);
3317
3489
  continue;
3318
3490
  }
3319
3491
  if (!file.endsWith(".vue"))
3320
3492
  continue;
3321
- const componentName = file.slice(0, -4);
3322
- if (componentName === "index" && baseRoute === "") {
3323
- routeMetaEntries.push({
3324
- componentName: "index",
3325
- pathTemplate: "/",
3326
- params: [],
3327
- query: []
3328
- });
3329
- continue;
3330
- }
3331
- let routePath = componentName === "index" ? baseRoute : `${baseRoute}/${componentName}`;
3332
- if (!routePath.startsWith("/"))
3333
- routePath = `/${routePath}`;
3493
+ const componentName = resolveComponentNameFromPath({
3494
+ filename: fullPath,
3495
+ projectRoot,
3496
+ viewsDirAbs: pagesDir,
3497
+ sourceDirs: [pagesDir],
3498
+ extraRoots: [process.cwd()]
3499
+ });
3500
+ const relativePath = path.relative(pagesDir, fullPath);
3501
+ const parsed = path.parse(relativePath);
3502
+ const routeSegments = toPathSegments(path.join(parsed.dir, parsed.name));
3334
3503
  const params = [];
3335
- let pathTemplate = "";
3336
- for (let i = 0; i < routePath.length; i++) {
3337
- const ch = routePath[i];
3338
- if (ch !== "[") {
3339
- pathTemplate += ch;
3340
- continue;
3341
- }
3342
- let name = "";
3343
- i++;
3344
- while (i < routePath.length) {
3345
- const c = routePath[i];
3346
- if (c === "]")
3347
- break;
3348
- name += c;
3349
- i++;
3350
- }
3351
- if (name) {
3352
- params.push({ name, optional: false });
3353
- pathTemplate += `:${name}`;
3354
- } else {
3355
- pathTemplate += "[]";
3356
- }
3357
- }
3504
+ const pathParts = routeSegments.flatMap((segment) => {
3505
+ const resolution = resolveNuxtPageSegment(segment);
3506
+ params.push(...resolution.params);
3507
+ return resolution.pathPart ? [resolution.pathPart] : [];
3508
+ });
3509
+ const pathTemplate = pathParts.length ? `/${pathParts.join("/")}` : "/";
3510
+ routePathMap.set(pathTemplate, componentName);
3358
3511
  routeMetaEntries.push({
3359
3512
  componentName,
3360
3513
  pathTemplate,
@@ -3363,10 +3516,12 @@ async function introspectNuxtPages(projectRoot) {
3363
3516
  });
3364
3517
  }
3365
3518
  };
3366
- walk(pagesDir, "");
3519
+ for (const pageDir of pageDirs) {
3520
+ walk(pageDir, pageDir);
3521
+ }
3367
3522
  return {
3368
3523
  routeNameMap: /* @__PURE__ */ new Map(),
3369
- routePathMap: /* @__PURE__ */ new Map(),
3524
+ routePathMap,
3370
3525
  routeMetaEntries
3371
3526
  };
3372
3527
  }
@@ -3662,15 +3817,20 @@ function resolveVueSourcePath(targetClassName, vueFilesPathMap, projectRoot) {
3662
3817
  }
3663
3818
  async function getRouteMetaByComponent(projectRoot, routerEntry, routerType, options = {}) {
3664
3819
  const root = projectRoot ?? process.cwd();
3665
- const viewsDir = options.viewsDir ?? "src/views";
3666
- const viewsDirAbs = path.isAbsolute(viewsDir) ? viewsDir : path.resolve(root, viewsDir);
3667
- const scanDirs = options.scanDirs?.length ? options.scanDirs : ["src"];
3820
+ const pageDirs = options.pageDirs?.length ? options.pageDirs : ["src/views"];
3821
+ const pageDirsAbs = pageDirs.map((dir) => path.isAbsolute(dir) ? dir : path.resolve(root, dir));
3822
+ const primaryPageDirAbs = pageDirsAbs[0] ?? path.resolve(root, "src/views");
3823
+ const sourceDirs = [
3824
+ ...pageDirs,
3825
+ ...options.componentDirs?.length ? options.componentDirs : ["src/components"],
3826
+ ...options.layoutDirs?.length ? options.layoutDirs : ["src/layouts"]
3827
+ ];
3668
3828
  const extraRoots = process.cwd() !== root ? [process.cwd()] : [];
3669
- const { routeMetaEntries } = routerType === "nuxt" ? await introspectNuxtPages(root) : await parseRouterFileFromCwd(resolveRouterEntry(root, routerEntry), {
3829
+ const { routeMetaEntries } = routerType === "nuxt" ? await introspectNuxtPages(root, { pageDirs: pageDirsAbs }) : await parseRouterFileFromCwd(resolveRouterEntry(root, routerEntry), {
3670
3830
  componentNaming: {
3671
3831
  projectRoot: root,
3672
- viewsDirAbs,
3673
- scanDirs,
3832
+ viewsDirAbs: primaryPageDirAbs,
3833
+ sourceDirs,
3674
3834
  extraRoots
3675
3835
  }
3676
3836
  });
@@ -3870,15 +4030,17 @@ async function generateFiles(componentHierarchyMap, vueFilesPathMap, basePageCla
3870
4030
  vueRouterFluentChaining,
3871
4031
  routerEntry,
3872
4032
  routerType,
3873
- viewsDir,
3874
- scanDirs,
4033
+ pageDirs,
4034
+ componentDirs,
4035
+ layoutDirs,
3875
4036
  routeMetaByComponent: routeMetaByComponentOverride
3876
4037
  } = options;
3877
4038
  const emitLanguages = emitLanguagesOverride?.length ? emitLanguagesOverride : ["ts"];
3878
4039
  const outDir = outDirOverride ?? "./pom";
3879
4040
  const routeMetaByComponent = routeMetaByComponentOverride ?? (vueRouterFluentChaining ? await getRouteMetaByComponent(projectRoot, routerEntry, routerType, {
3880
- viewsDir,
3881
- scanDirs
4041
+ pageDirs,
4042
+ componentDirs,
4043
+ layoutDirs
3882
4044
  }) : void 0);
3883
4045
  const generatedFilePaths = [];
3884
4046
  const writeGeneratedFile = (file) => {
@@ -6667,8 +6829,11 @@ function createBuildProcessorPlugin(options) {
6667
6829
  const {
6668
6830
  componentHierarchyMap,
6669
6831
  vueFilesPathMap,
6670
- viewsDir,
6671
- scanDirs,
6832
+ getPageDirs,
6833
+ getComponentDirs,
6834
+ getLayoutDirs,
6835
+ getViewsDir,
6836
+ getSourceDirs,
6672
6837
  basePageClassPath,
6673
6838
  normalizedBasePagePath,
6674
6839
  outDir,
@@ -6689,7 +6854,7 @@ function createBuildProcessorPlugin(options) {
6689
6854
  excludedComponents,
6690
6855
  getWrapperSearchRoots,
6691
6856
  routerAwarePoms,
6692
- resolvedRouterEntry,
6857
+ getResolvedRouterEntry,
6693
6858
  routerType,
6694
6859
  routerModuleShims,
6695
6860
  loggerRef
@@ -6699,7 +6864,8 @@ function createBuildProcessorPlugin(options) {
6699
6864
  interactiveComponentCount: 0,
6700
6865
  dataTestIdCount: 0
6701
6866
  };
6702
- const getViewsDirAbs = () => path.isAbsolute(viewsDir) ? viewsDir : path.resolve(projectRootRef.current, viewsDir);
6867
+ const getViewsDirAbs = () => path.isAbsolute(getViewsDir()) ? getViewsDir() : path.resolve(projectRootRef.current, getViewsDir());
6868
+ const getPageDirsAbs = () => getPageDirs().map((dir) => path.isAbsolute(dir) ? dir : path.resolve(projectRootRef.current, dir));
6703
6869
  const getScriptInfo = (source, filename) => {
6704
6870
  try {
6705
6871
  const { descriptor } = compilerSfc.parse(source, { filename });
@@ -6739,7 +6905,7 @@ function createBuildProcessorPlugin(options) {
6739
6905
  return out;
6740
6906
  };
6741
6907
  let supplemented = 0;
6742
- for (const dir of scanDirs) {
6908
+ for (const dir of getSourceDirs()) {
6743
6909
  const absDir = path.resolve(projectRootRef.current, dir);
6744
6910
  if (!fs.existsSync(absDir))
6745
6911
  continue;
@@ -6749,7 +6915,7 @@ function createBuildProcessorPlugin(options) {
6749
6915
  filename: absolutePath,
6750
6916
  projectRoot: projectRootRef.current,
6751
6917
  viewsDirAbs: getViewsDirAbs(),
6752
- scanDirs,
6918
+ sourceDirs: getSourceDirs(),
6753
6919
  extraRoots: [process.cwd()]
6754
6920
  });
6755
6921
  if (componentHierarchyMap.has(componentName))
@@ -6824,16 +6990,17 @@ function createBuildProcessorPlugin(options) {
6824
6990
  }
6825
6991
  let result;
6826
6992
  if (routerType === "nuxt") {
6827
- result = await introspectNuxtPages(projectRootRef.current);
6993
+ result = await introspectNuxtPages(projectRootRef.current, { pageDirs: getPageDirsAbs() });
6828
6994
  } else {
6995
+ const resolvedRouterEntry = getResolvedRouterEntry();
6829
6996
  if (!resolvedRouterEntry)
6830
6997
  throw new Error("[vue-pom-generator] router.entry is required when router introspection is enabled.");
6831
6998
  result = await parseRouterFileFromCwd(resolvedRouterEntry, {
6832
6999
  moduleShims: routerModuleShims,
6833
7000
  componentNaming: {
6834
7001
  projectRoot: projectRootRef.current,
6835
- viewsDirAbs: path.isAbsolute(viewsDir) ? viewsDir : path.resolve(projectRootRef.current, viewsDir),
6836
- scanDirs
7002
+ viewsDirAbs: getViewsDirAbs(),
7003
+ sourceDirs: getSourceDirs()
6837
7004
  }
6838
7005
  });
6839
7006
  }
@@ -6893,10 +7060,11 @@ function createBuildProcessorPlugin(options) {
6893
7060
  customPomImportNameCollisionBehavior,
6894
7061
  testIdAttribute,
6895
7062
  vueRouterFluentChaining: routerAwarePoms,
6896
- routerEntry: resolvedRouterEntry,
7063
+ routerEntry: getResolvedRouterEntry(),
6897
7064
  routerType,
6898
- viewsDir,
6899
- scanDirs
7065
+ pageDirs: getPageDirs(),
7066
+ componentDirs: getComponentDirs(),
7067
+ layoutDirs: getLayoutDirs()
6900
7068
  });
6901
7069
  lastGeneratedMetrics = metrics;
6902
7070
  loggerRef.current.info(`generated POMs (${metrics.entryCount} entries, ${metrics.interactiveComponentCount} interactive components, ${metrics.dataTestIdCount} selectors)`);
@@ -6910,8 +7078,11 @@ function createDevProcessorPlugin(options) {
6910
7078
  const {
6911
7079
  nativeWrappers,
6912
7080
  excludedComponents,
6913
- viewsDir,
6914
- scanDirs,
7081
+ getPageDirs,
7082
+ getComponentDirs,
7083
+ getLayoutDirs,
7084
+ getViewsDir,
7085
+ getSourceDirs,
6915
7086
  getWrapperSearchRoots,
6916
7087
  projectRootRef,
6917
7088
  normalizedBasePagePath,
@@ -6930,12 +7101,29 @@ function createDevProcessorPlugin(options) {
6930
7101
  existingIdBehavior,
6931
7102
  testIdAttribute,
6932
7103
  routerAwarePoms,
6933
- resolvedRouterEntry,
7104
+ getResolvedRouterEntry,
6934
7105
  routerType,
6935
7106
  routerModuleShims,
6936
7107
  loggerRef
6937
7108
  } = options;
6938
7109
  let scheduleVueFileRegen = null;
7110
+ const getProjectRootCandidates = () => Array.from(/* @__PURE__ */ new Set([
7111
+ path.resolve(projectRootRef.current),
7112
+ path.resolve(process.cwd())
7113
+ ]));
7114
+ const resolveProjectPath = (maybePath) => {
7115
+ if (path.isAbsolute(maybePath))
7116
+ return maybePath;
7117
+ const candidates = getProjectRootCandidates().map((root) => path.resolve(root, maybePath));
7118
+ return candidates.find((candidate) => fs.existsSync(candidate)) ?? candidates[0];
7119
+ };
7120
+ const getSourceDirRoots = () => Array.from(new Set(
7121
+ getProjectRootCandidates().flatMap((root) => getSourceDirs().map((dir) => path.resolve(root, dir)))
7122
+ ));
7123
+ const isContainedInScanDirs = (filePath) => {
7124
+ const absolutePath = path.resolve(filePath);
7125
+ return getSourceDirRoots().some((scanDirAbs) => isPathWithinDir(absolutePath, scanDirAbs));
7126
+ };
6939
7127
  return {
6940
7128
  name: "vue-pom-generator-dev",
6941
7129
  apply: "serve",
@@ -6946,16 +7134,13 @@ function createDevProcessorPlugin(options) {
6946
7134
  return;
6947
7135
  if (!ctx.file.endsWith(".vue"))
6948
7136
  return;
6949
- const isContainedInScanDirs = scanDirs.some((dir) => {
6950
- const absDir = path.resolve(projectRootRef.current, dir);
6951
- return ctx.file.startsWith(absDir + path.sep);
6952
- });
6953
- if (!isContainedInScanDirs)
7137
+ if (!isContainedInScanDirs(ctx.file))
6954
7138
  return;
6955
7139
  scheduleVueFileRegen(ctx.file, "hmr");
6956
7140
  },
6957
7141
  async configureServer(server) {
6958
- const getViewsDirAbs = () => path.isAbsolute(viewsDir) ? viewsDir : path.resolve(projectRootRef.current, viewsDir);
7142
+ const getViewsDirAbs = () => resolveProjectPath(getViewsDir());
7143
+ const getPageDirsAbs = () => getPageDirs().map((dir) => resolveProjectPath(dir));
6959
7144
  const routerInitPromise = (async () => {
6960
7145
  if (!routerAwarePoms) {
6961
7146
  setRouteNameToComponentNameMap(/* @__PURE__ */ new Map());
@@ -6964,8 +7149,9 @@ function createDevProcessorPlugin(options) {
6964
7149
  }
6965
7150
  let result;
6966
7151
  if (routerType === "nuxt") {
6967
- result = await introspectNuxtPages(projectRootRef.current);
7152
+ result = await introspectNuxtPages(projectRootRef.current, { pageDirs: getPageDirsAbs() });
6968
7153
  } else {
7154
+ const resolvedRouterEntry = getResolvedRouterEntry();
6969
7155
  if (!resolvedRouterEntry)
6970
7156
  throw new Error("[vue-pom-generator] router.entry is required when router introspection is enabled.");
6971
7157
  result = await parseRouterFileFromCwd(resolvedRouterEntry, {
@@ -6973,7 +7159,7 @@ function createDevProcessorPlugin(options) {
6973
7159
  componentNaming: {
6974
7160
  projectRoot: projectRootRef.current,
6975
7161
  viewsDirAbs: getViewsDirAbs(),
6976
- scanDirs,
7162
+ sourceDirs: getSourceDirs(),
6977
7163
  extraRoots: [process.cwd()]
6978
7164
  }
6979
7165
  });
@@ -7045,6 +7231,19 @@ function createDevProcessorPlugin(options) {
7045
7231
  let snapshotHierarchy = /* @__PURE__ */ new Map();
7046
7232
  let snapshotVuePathMap = /* @__PURE__ */ new Map();
7047
7233
  const filePathToComponentName = /* @__PURE__ */ new Map();
7234
+ const createEmptyComponentDependencies = (absolutePath) => {
7235
+ const viewsDirAbs = path.resolve(getViewsDirAbs());
7236
+ const relToViewsDir = path.relative(viewsDirAbs, absolutePath);
7237
+ const isView = !relToViewsDir.startsWith("..") && !path.isAbsolute(relToViewsDir);
7238
+ return {
7239
+ filePath: absolutePath,
7240
+ childrenComponentSet: /* @__PURE__ */ new Set(),
7241
+ usedComponentSet: /* @__PURE__ */ new Set(),
7242
+ dataTestIdSet: /* @__PURE__ */ new Set(),
7243
+ isView,
7244
+ methodsContent: ""
7245
+ };
7246
+ };
7048
7247
  const getComponentNameForFile = (filePath) => {
7049
7248
  const normalized = path.resolve(filePath);
7050
7249
  const existing = filePathToComponentName.get(normalized);
@@ -7054,7 +7253,7 @@ function createDevProcessorPlugin(options) {
7054
7253
  filename: normalized,
7055
7254
  projectRoot: projectRootRef.current,
7056
7255
  viewsDirAbs: getViewsDirAbs(),
7057
- scanDirs,
7256
+ sourceDirs: getSourceDirs(),
7058
7257
  extraRoots: [process.cwd()]
7059
7258
  });
7060
7259
  filePathToComponentName.set(normalized, name);
@@ -7064,8 +7263,6 @@ function createDevProcessorPlugin(options) {
7064
7263
  const started = node_perf_hooks.performance.now();
7065
7264
  const absolutePath = path.resolve(filePath);
7066
7265
  const componentName = getComponentNameForFile(absolutePath);
7067
- targetVuePathMap.set(componentName, absolutePath);
7068
- targetHierarchy.delete(componentName);
7069
7266
  let sfc = "";
7070
7267
  try {
7071
7268
  sfc = fs.readFileSync(absolutePath, "utf8");
@@ -7073,9 +7270,15 @@ function createDevProcessorPlugin(options) {
7073
7270
  return { componentName, ms: node_perf_hooks.performance.now() - started, compiled: false };
7074
7271
  }
7075
7272
  const template = extractTemplateFromSfc(sfc, absolutePath);
7076
- if (!template.trim())
7273
+ if (!template.trim()) {
7274
+ targetVuePathMap.set(componentName, absolutePath);
7275
+ targetHierarchy.set(componentName, createEmptyComponentDependencies(absolutePath));
7077
7276
  return { componentName, ms: node_perf_hooks.performance.now() - started, compiled: true };
7277
+ }
7078
7278
  const { bindings: bindingMetadata, isScriptSetup } = getScriptInfo(sfc, absolutePath);
7279
+ const provisionalHierarchy = /* @__PURE__ */ new Map();
7280
+ const provisionalVuePathMap = new Map(targetVuePathMap);
7281
+ provisionalVuePathMap.set(componentName, absolutePath);
7079
7282
  compilerDom__namespace.compile(template, {
7080
7283
  filename: absolutePath,
7081
7284
  prefixIdentifiers: true,
@@ -7084,7 +7287,7 @@ function createDevProcessorPlugin(options) {
7084
7287
  nodeTransforms: [
7085
7288
  createTestIdTransform(
7086
7289
  componentName,
7087
- targetHierarchy,
7290
+ provisionalHierarchy,
7088
7291
  nativeWrappers,
7089
7292
  excludedComponents,
7090
7293
  getViewsDirAbs(),
@@ -7094,23 +7297,27 @@ function createDevProcessorPlugin(options) {
7094
7297
  missingSemanticNameBehavior,
7095
7298
  testIdAttribute,
7096
7299
  warn: (message) => loggerRef.current.warn(message),
7097
- vueFilesPathMap: targetVuePathMap,
7300
+ vueFilesPathMap: provisionalVuePathMap,
7098
7301
  wrapperSearchRoots: getWrapperSearchRoots()
7099
7302
  }
7100
7303
  )
7101
7304
  ]
7102
7305
  });
7306
+ targetVuePathMap.set(componentName, absolutePath);
7307
+ targetHierarchy.set(
7308
+ componentName,
7309
+ provisionalHierarchy.get(componentName) ?? createEmptyComponentDependencies(absolutePath)
7310
+ );
7103
7311
  return { componentName, ms: node_perf_hooks.performance.now() - started, compiled: true };
7104
7312
  };
7105
- const fullRebuildSnapshotFromFilesystem = () => {
7313
+ const fullRebuildSnapshotFromFilesystem = (logLabel) => {
7106
7314
  const t0 = node_perf_hooks.performance.now();
7107
7315
  const nextHierarchy = /* @__PURE__ */ new Map();
7108
7316
  const nextVuePathMap = /* @__PURE__ */ new Map();
7109
7317
  filePathToComponentName.clear();
7110
7318
  let totalVueFiles = 0;
7111
7319
  let compiledCount = 0;
7112
- for (const dir of scanDirs) {
7113
- const absDir = path.resolve(projectRootRef.current, dir);
7320
+ for (const absDir of getSourceDirRoots()) {
7114
7321
  if (!fs.existsSync(absDir))
7115
7322
  continue;
7116
7323
  const vueFiles = walkFilesRecursive(absDir);
@@ -7124,12 +7331,12 @@ function createDevProcessorPlugin(options) {
7124
7331
  snapshotHierarchy = nextHierarchy;
7125
7332
  snapshotVuePathMap = nextVuePathMap;
7126
7333
  const t1 = node_perf_hooks.performance.now();
7127
- logInfo(`initial scan: found ${totalVueFiles} .vue files in ${scanDirs.join(", ")}`);
7128
- logInfo(`initial compile: ${compiledCount}/${totalVueFiles} files in ${formatMs(t1 - t0)} (components=${snapshotHierarchy.size})`);
7334
+ logInfo(`scan(${logLabel}): found ${totalVueFiles} .vue files in ${getSourceDirs().join(", ")}`);
7335
+ logInfo(`compile(${logLabel}): ${compiledCount}/${totalVueFiles} files in ${formatMs(t1 - t0)} (components=${snapshotHierarchy.size})`);
7129
7336
  };
7130
- const generateAggregatedFromSnapshot = (reason) => {
7337
+ const generateAggregatedFromSnapshot = async (logLabel) => {
7131
7338
  const t0 = node_perf_hooks.performance.now();
7132
- generateFiles(snapshotHierarchy, snapshotVuePathMap, normalizedBasePagePath, {
7339
+ await generateFiles(snapshotHierarchy, snapshotVuePathMap, normalizedBasePagePath, {
7133
7340
  outDir,
7134
7341
  emitLanguages,
7135
7342
  typescriptOutputStructure,
@@ -7140,25 +7347,27 @@ function createDevProcessorPlugin(options) {
7140
7347
  customPomDir,
7141
7348
  customPomImportAliases,
7142
7349
  customPomImportNameCollisionBehavior,
7143
- viewsDir,
7144
- scanDirs,
7350
+ pageDirs: getPageDirs(),
7351
+ componentDirs: getComponentDirs(),
7352
+ layoutDirs: getLayoutDirs(),
7145
7353
  testIdAttribute,
7146
7354
  vueRouterFluentChaining: routerAwarePoms,
7147
- routerEntry: resolvedRouterEntry,
7355
+ routerEntry: getResolvedRouterEntry(),
7148
7356
  routerType
7149
7357
  });
7150
7358
  const t1 = node_perf_hooks.performance.now();
7151
- logInfo(`generate(${reason}): components=${snapshotHierarchy.size} in ${formatMs(t1 - t0)}`);
7359
+ logInfo(`generate(${logLabel}): components=${snapshotHierarchy.size} in ${formatMs(t1 - t0)}`);
7152
7360
  };
7153
7361
  let timer = null;
7154
7362
  let maxWaitTimer = null;
7155
7363
  const pendingChangedVueFiles = /* @__PURE__ */ new Set();
7156
7364
  const pendingDeletedComponents = /* @__PURE__ */ new Set();
7365
+ let regenerationSequence = Promise.resolve();
7157
7366
  const initialBuildPromise = (async () => {
7158
7367
  const t0 = node_perf_hooks.performance.now();
7159
7368
  await routerInitPromise;
7160
- fullRebuildSnapshotFromFilesystem();
7161
- generateAggregatedFromSnapshot("startup");
7369
+ fullRebuildSnapshotFromFilesystem("startup");
7370
+ await generateAggregatedFromSnapshot("startup");
7162
7371
  const t1 = node_perf_hooks.performance.now();
7163
7372
  logInfo(`startup total: ${formatMs(t1 - t0)}`);
7164
7373
  })();
@@ -7186,7 +7395,7 @@ function createDevProcessorPlugin(options) {
7186
7395
  snapshotHierarchy = nextHierarchy;
7187
7396
  snapshotVuePathMap = nextVuePathMap;
7188
7397
  const t1 = node_perf_hooks.performance.now();
7189
- generateAggregatedFromSnapshot(reason);
7398
+ await generateAggregatedFromSnapshot(reason);
7190
7399
  const t2 = node_perf_hooks.performance.now();
7191
7400
  return {
7192
7401
  files,
@@ -7197,7 +7406,12 @@ function createDevProcessorPlugin(options) {
7197
7406
  totalMs: t2 - t0
7198
7407
  };
7199
7408
  };
7200
- const watchedVueGlobs = scanDirs.map((dir) => path.resolve(projectRootRef.current, dir, "**", "*.vue"));
7409
+ const enqueueRegeneration = (reason) => {
7410
+ const currentRun = regenerationSequence.catch(() => void 0).then(() => regenerateFromPending(reason));
7411
+ regenerationSequence = currentRun.then(() => void 0, () => void 0);
7412
+ return currentRun;
7413
+ };
7414
+ const watchedVueGlobs = getSourceDirRoots().map((scanDirAbs) => path.resolve(scanDirAbs, "**", "*.vue"));
7201
7415
  const watchedPluginGlob = path.resolve(projectRootRef.current, "vite-plugins", "vue-pom-generator", "**", "*.ts");
7202
7416
  const runtimeDir = path.dirname(basePageClassPath);
7203
7417
  server.watcher.add([
@@ -7223,7 +7437,7 @@ function createDevProcessorPlugin(options) {
7223
7437
  timer = null;
7224
7438
  }
7225
7439
  maxWaitTimer = null;
7226
- void regenerateFromPending("max-wait").then(({ files, deletedCount, compileMs, preGenerateMs, generateMs, totalMs }) => {
7440
+ void enqueueRegeneration("max-wait").then(({ files, deletedCount, compileMs, preGenerateMs, generateMs, totalMs }) => {
7227
7441
  logInfo(
7228
7442
  `max-wait: files=${files.length} deleted=${deletedCount} compile=${formatMs(compileMs)} wall=${formatMs(preGenerateMs)} gen=${formatMs(generateMs)} total=${formatMs(totalMs)}`
7229
7443
  );
@@ -7247,7 +7461,7 @@ function createDevProcessorPlugin(options) {
7247
7461
  maxWaitTimer = null;
7248
7462
  }
7249
7463
  const reason = pendingChangedVueFiles.size || pendingDeletedComponents.size ? "batched" : "noop";
7250
- void regenerateFromPending(reason).then(({ files, deletedCount, compileMs, preGenerateMs, generateMs, totalMs }) => {
7464
+ void enqueueRegeneration(reason).then(({ files, deletedCount, compileMs, preGenerateMs, generateMs, totalMs }) => {
7251
7465
  if (files.length || deletedCount) {
7252
7466
  logInfo(
7253
7467
  `batched: files=${files.length} deleted=${deletedCount} compile=${formatMs(compileMs)} wall=${formatMs(preGenerateMs)} gen=${formatMs(generateMs)} total=${formatMs(totalMs)}`
@@ -7274,11 +7488,7 @@ function createDevProcessorPlugin(options) {
7274
7488
  return;
7275
7489
  if (!p.endsWith(".vue"))
7276
7490
  return;
7277
- const isContainedInScanDirs = scanDirs.some((dir) => {
7278
- const absDir = path.resolve(projectRootRef.current, dir);
7279
- return p.startsWith(absDir + path.sep);
7280
- });
7281
- if (!isContainedInScanDirs)
7491
+ if (!isContainedInScanDirs(p))
7282
7492
  return;
7283
7493
  void (async () => {
7284
7494
  await initialBuildPromise;
@@ -7291,11 +7501,7 @@ function createDevProcessorPlugin(options) {
7291
7501
  return;
7292
7502
  if (!p.endsWith(".vue"))
7293
7503
  return;
7294
- const isContainedInScanDirs = scanDirs.some((dir) => {
7295
- const absDir = path.resolve(projectRootRef.current, dir);
7296
- return p.startsWith(absDir + path.sep);
7297
- });
7298
- if (!isContainedInScanDirs)
7504
+ if (!isContainedInScanDirs(p))
7299
7505
  return;
7300
7506
  void (async () => {
7301
7507
  await initialBuildPromise;
@@ -7359,8 +7565,11 @@ function createSupportPlugins(options) {
7359
7565
  vueFilesPathMap,
7360
7566
  nativeWrappers,
7361
7567
  excludedComponents,
7362
- viewsDir,
7363
- scanDirs,
7568
+ getPageDirs,
7569
+ getComponentDirs,
7570
+ getLayoutDirs,
7571
+ getViewsDir,
7572
+ getSourceDirs,
7364
7573
  getWrapperSearchRoots,
7365
7574
  nameCollisionBehavior = "suffix",
7366
7575
  missingSemanticNameBehavior,
@@ -7392,7 +7601,6 @@ function createSupportPlugins(options) {
7392
7601
  throw new Error("[vue-pom-generator] router.entry is required when router introspection is enabled.");
7393
7602
  return path.isAbsolute(routerEntry) ? routerEntry : path.resolve(projectRootRef.current, routerEntry);
7394
7603
  };
7395
- const resolvedRouterEntry = resolveRouterEntry2();
7396
7604
  const getDefaultBasePageClassPath = () => {
7397
7605
  try {
7398
7606
  return node_url.fileURLToPath(new URL("../class-generation/base-page.ts", typeof document === "undefined" ? require("url").pathToFileURL(__filename).href : _documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === "SCRIPT" && _documentCurrentScript.src || new URL("index.cjs", document.baseURI).href));
@@ -7405,8 +7613,11 @@ function createSupportPlugins(options) {
7405
7613
  const tsProcessor = createBuildProcessorPlugin({
7406
7614
  componentHierarchyMap,
7407
7615
  vueFilesPathMap,
7408
- viewsDir,
7409
- scanDirs,
7616
+ getPageDirs,
7617
+ getComponentDirs,
7618
+ getLayoutDirs,
7619
+ getViewsDir,
7620
+ getSourceDirs,
7410
7621
  basePageClassPath,
7411
7622
  normalizedBasePagePath,
7412
7623
  outDir,
@@ -7428,15 +7639,18 @@ function createSupportPlugins(options) {
7428
7639
  getWrapperSearchRoots,
7429
7640
  routerAwarePoms,
7430
7641
  routerType,
7431
- resolvedRouterEntry,
7642
+ getResolvedRouterEntry: resolveRouterEntry2,
7432
7643
  routerModuleShims,
7433
7644
  loggerRef
7434
7645
  });
7435
7646
  const devProcessor = createDevProcessorPlugin({
7436
7647
  nativeWrappers,
7437
7648
  excludedComponents,
7438
- viewsDir,
7439
- scanDirs,
7649
+ getPageDirs,
7650
+ getComponentDirs,
7651
+ getLayoutDirs,
7652
+ getViewsDir,
7653
+ getSourceDirs,
7440
7654
  getWrapperSearchRoots,
7441
7655
  projectRootRef,
7442
7656
  normalizedBasePagePath,
@@ -7456,7 +7670,7 @@ function createSupportPlugins(options) {
7456
7670
  testIdAttribute,
7457
7671
  routerAwarePoms,
7458
7672
  routerType,
7459
- resolvedRouterEntry,
7673
+ getResolvedRouterEntry: resolveRouterEntry2,
7460
7674
  routerModuleShims,
7461
7675
  loggerRef
7462
7676
  });
@@ -7614,7 +7828,7 @@ function createVuePluginWithTestIds(options) {
7614
7828
  getViewsDirAbs,
7615
7829
  testIdAttribute,
7616
7830
  loggerRef,
7617
- scanDirs = ["src"],
7831
+ getSourceDirs,
7618
7832
  getWrapperSearchRoots,
7619
7833
  getProjectRoot
7620
7834
  } = options;
@@ -7623,7 +7837,7 @@ function createVuePluginWithTestIds(options) {
7623
7837
  filename,
7624
7838
  projectRoot: getProjectRoot(),
7625
7839
  viewsDirAbs: getViewsDirAbs(),
7626
- scanDirs,
7840
+ sourceDirs: getSourceDirs(),
7627
7841
  extraRoots: [process.cwd()]
7628
7842
  });
7629
7843
  };
@@ -7639,7 +7853,7 @@ function createVuePluginWithTestIds(options) {
7639
7853
  if (absFilename.startsWith(viewsDirAbs + path.sep) || absFilename === viewsDirAbs)
7640
7854
  return true;
7641
7855
  const rootsToTry = [projectRoot, process.cwd()];
7642
- const matched = scanDirs.some((dir) => {
7856
+ const matched = getSourceDirs().some((dir) => {
7643
7857
  return rootsToTry.some((root) => {
7644
7858
  const absDir = path.resolve(root, dir);
7645
7859
  if (absFilename.startsWith(absDir + path.sep) || absFilename === absDir)
@@ -7814,6 +8028,25 @@ function createVuePluginWithTestIds(options) {
7814
8028
  };
7815
8029
  return { metadataCollectorPlugin, internalVuePlugin, nuxtVueBridgePlugin, templateCompilerOptions };
7816
8030
  }
8031
+ const nuxtConfigMarker$1 = /* @__PURE__ */ Symbol.for("@immense/vue-pom-generator.nuxt");
8032
+ const nuxtConfigFileNames = [
8033
+ "nuxt.config.ts",
8034
+ "nuxt.config.js",
8035
+ "nuxt.config.mjs",
8036
+ "nuxt.config.cjs",
8037
+ "nuxt.config.mts",
8038
+ "nuxt.config.cts",
8039
+ ".nuxtrc"
8040
+ ];
8041
+ const nuxtSourceMarkers = [
8042
+ "app.vue",
8043
+ "app",
8044
+ "pages",
8045
+ "layouts",
8046
+ "components",
8047
+ "layers",
8048
+ ".nuxt"
8049
+ ];
7817
8050
  function assertNonEmptyString(value, name) {
7818
8051
  if (!value || !value.trim()) {
7819
8052
  throw new Error(`${name} must be a non-empty string.`);
@@ -7861,6 +8094,57 @@ function resolveMissingSemanticNameBehavior(value) {
7861
8094
  }
7862
8095
  return value.missingSemanticNameBehavior ?? "ignore";
7863
8096
  }
8097
+ function readPackageJson(projectRoot) {
8098
+ const packageJsonPath = path.join(projectRoot, "package.json");
8099
+ if (!fs.existsSync(packageJsonPath)) {
8100
+ return null;
8101
+ }
8102
+ try {
8103
+ return JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
8104
+ } catch {
8105
+ return null;
8106
+ }
8107
+ }
8108
+ function recordHasOwnStringKey(value, key) {
8109
+ return value !== null && value !== void 0 && !Array.isArray(value) && Object.prototype.hasOwnProperty.call(value, key) && typeof value[key] === "string";
8110
+ }
8111
+ function projectPackageLooksNuxt(projectRoot) {
8112
+ const packageJson = readPackageJson(projectRoot);
8113
+ if (!packageJson) {
8114
+ return false;
8115
+ }
8116
+ const dependencyGroups = [
8117
+ packageJson.dependencies,
8118
+ packageJson.devDependencies,
8119
+ packageJson.peerDependencies,
8120
+ packageJson.optionalDependencies
8121
+ ];
8122
+ if (dependencyGroups.some((group) => {
8123
+ const dependencyGroup = typeof group === "object" && group !== null ? group : void 0;
8124
+ return recordHasOwnStringKey(dependencyGroup, "nuxt") || recordHasOwnStringKey(dependencyGroup, "nuxt-nightly");
8125
+ })) {
8126
+ return true;
8127
+ }
8128
+ if (typeof packageJson.scripts !== "object" || packageJson.scripts === null || Array.isArray(packageJson.scripts)) {
8129
+ return false;
8130
+ }
8131
+ return Object.values(packageJson.scripts).some((script) => {
8132
+ if (typeof script !== "string") {
8133
+ return false;
8134
+ }
8135
+ const normalizedScript = script.trim();
8136
+ return normalizedScript === "nuxt" || normalizedScript.startsWith("nuxt ") || normalizedScript === "nuxi" || normalizedScript.startsWith("nuxi ");
8137
+ });
8138
+ }
8139
+ function detectNuxtProject(options, projectRoot) {
8140
+ if (options[nuxtConfigMarker$1] === true) {
8141
+ return true;
8142
+ }
8143
+ if (nuxtConfigFileNames.some((fileName) => fs.existsSync(path.join(projectRoot, fileName)))) {
8144
+ return true;
8145
+ }
8146
+ return projectPackageLooksNuxt(projectRoot) && nuxtSourceMarkers.some((entry) => fs.existsSync(path.join(projectRoot, entry)));
8147
+ }
7864
8148
  function assertRouterModuleShims(value, name) {
7865
8149
  if (!value)
7866
8150
  return;
@@ -7968,14 +8252,18 @@ function assertNotVitePluginInstance(options) {
7968
8252
  function createVuePomGeneratorPlugins(options = {}) {
7969
8253
  assertNotVitePluginInstance(options);
7970
8254
  const injection = options.injection ?? {};
8255
+ const isNuxt = detectNuxtProject(options, process.cwd());
7971
8256
  const generationSetting = options.generation;
7972
8257
  const generationOptions = generationSetting === false ? null : generationSetting ?? {};
7973
8258
  const generationEnabled = generationOptions !== null;
8259
+ const vueGenerationOptions = generationOptions;
7974
8260
  const verbosity = options.logging?.verbosity ?? "warn";
7975
8261
  const vueOptions = options.vueOptions;
7976
- const viewsDir = injection.viewsDir ?? "src/views";
7977
- const scanDirs = injection.scanDirs ?? ["src"];
7978
- const wrapperSearchRoots = injection.wrapperSearchRoots ?? [];
8262
+ const legacyVueOptions = options;
8263
+ const pageDirsRef = { current: !isNuxt ? [legacyVueOptions.injection?.viewsDir ?? "src/views"] : ["app/pages"] };
8264
+ const componentDirsRef = { current: !isNuxt ? legacyVueOptions.injection?.componentDirs ?? ["src/components"] : ["app/components"] };
8265
+ const layoutDirsRef = { current: !isNuxt ? legacyVueOptions.injection?.layoutDirs ?? ["src/layouts"] : ["app/layouts"] };
8266
+ const wrapperSearchRootsRef = { current: !isNuxt ? legacyVueOptions.injection?.wrapperSearchRoots ?? [] : [] };
7979
8267
  const nativeWrappers = injection.nativeWrappers ?? {};
7980
8268
  const excludedComponents = injection.excludeComponents ?? [];
7981
8269
  const testIdAttribute = (injection.attribute ?? "data-testid").trim() || "data-testid";
@@ -7983,10 +8271,9 @@ function createVuePomGeneratorPlugins(options = {}) {
7983
8271
  const outDir = (generationOptions?.outDir ?? "tests/playwright/__generated__").trim();
7984
8272
  const emitLanguages = generationOptions?.emit && generationOptions.emit.length ? generationOptions.emit : ["ts"];
7985
8273
  const nameCollisionBehavior = generationOptions?.nameCollisionBehavior ?? "suffix";
7986
- const routerEntry = generationOptions?.router?.entry;
7987
- const routerType = generationOptions?.router?.type ?? "vue-router";
7988
- const routerModuleShims = generationOptions?.router?.moduleShims;
7989
- const isNuxt = routerType === "nuxt";
8274
+ const routerEntry = !isNuxt ? vueGenerationOptions?.router?.entry : void 0;
8275
+ const routerType = isNuxt ? "nuxt" : vueGenerationOptions?.router?.type ?? "vue-router";
8276
+ const routerModuleShims = !isNuxt ? vueGenerationOptions?.router?.moduleShims : void 0;
7990
8277
  if (isNuxt && options.vuePluginOwnership === "internal") {
7991
8278
  throw new Error('[vue-pom-generator] Nuxt projects must use the resolved app-owned vite:vue plugin. Omit vuePluginOwnership or set it to "external".');
7992
8279
  }
@@ -8003,66 +8290,100 @@ function createVuePomGeneratorPlugins(options = {}) {
8003
8290
  const resolvedCustomPomImportAliases = customPoms?.importAliases;
8004
8291
  const resolvedCustomPomImportCollisionBehavior = customPoms?.importNameCollisionBehavior ?? "error";
8005
8292
  const basePageClassPathOverride = generationOptions?.basePageClassPath;
8293
+ const getPageDirs = () => pageDirsRef.current;
8294
+ const getViewsDir = () => getPageDirs()[0] ?? "src/views";
8295
+ const getComponentDirs = () => componentDirsRef.current;
8296
+ const getLayoutDirs = () => layoutDirsRef.current;
8297
+ const getSourceDirs = () => Array.from(/* @__PURE__ */ new Set([
8298
+ ...getPageDirs(),
8299
+ ...getComponentDirs(),
8300
+ ...getLayoutDirs()
8301
+ ]));
8302
+ const getWrapperSearchRoots = () => wrapperSearchRootsRef.current;
8006
8303
  const sharedStateKey = JSON.stringify({
8007
8304
  cwd: process.cwd(),
8008
- viewsDir,
8009
- scanDirs,
8010
- wrapperSearchRoots,
8305
+ mode: isNuxt ? "nuxt" : "vue",
8306
+ pageDirs: isNuxt ? null : getPageDirs(),
8307
+ componentDirs: isNuxt ? null : getComponentDirs(),
8308
+ layoutDirs: isNuxt ? null : getLayoutDirs(),
8309
+ wrapperSearchRoots: isNuxt ? null : getWrapperSearchRoots(),
8011
8310
  outDir,
8012
8311
  testIdAttribute,
8013
8312
  routerType,
8014
8313
  vuePluginOwnership
8015
8314
  });
8016
8315
  const sharedState = getSharedGeneratorState(sharedStateKey);
8316
+ let templateCompilerOptionsForResolvedPlugin;
8017
8317
  const projectRootRef = { current: process.cwd() };
8018
8318
  const loggerRef = {
8019
8319
  current: createLogger({ verbosity })
8020
8320
  };
8021
- const getViewsDirAbs = () => resolveFromProjectRoot(projectRootRef.current, viewsDir);
8022
- const getWrapperSearchRootsAbs = () => wrapperSearchRoots.map((root) => resolveFromProjectRoot(projectRootRef.current, root));
8023
- const { componentTestIds, elementMetadata, semanticNameMap, componentHierarchyMap, vueFilesPathMap } = sharedState;
8024
- const { metadataCollectorPlugin, internalVuePlugin, templateCompilerOptions } = createVuePluginWithTestIds({
8025
- vueOptions,
8026
- existingIdBehavior,
8027
- nameCollisionBehavior,
8028
- nativeWrappers,
8029
- elementMetadata,
8030
- semanticNameMap,
8031
- componentHierarchyMap,
8032
- vueFilesPathMap,
8033
- excludedComponents,
8034
- getViewsDirAbs,
8035
- testIdAttribute,
8036
- loggerRef,
8037
- scanDirs,
8038
- getWrapperSearchRoots: getWrapperSearchRootsAbs,
8039
- getProjectRoot: () => projectRootRef.current
8040
- });
8041
8321
  const configPlugin = {
8042
8322
  name: "vue-pom-generator-config",
8043
8323
  enforce: "pre",
8044
- configResolved(config) {
8324
+ async configResolved(config) {
8045
8325
  projectRootRef.current = config.root;
8046
8326
  loggerRef.current = createLogger({ verbosity, viteLogger: config.logger });
8327
+ if (vueGenerationOptions?.router?.type === "nuxt") {
8328
+ throw new Error('[vue-pom-generator] Remove generation.router.type="nuxt". Nuxt projects are auto-detected.');
8329
+ }
8330
+ if (isNuxt) {
8331
+ const nuxtDiscovery = await loadNuxtProjectDiscovery(process.cwd());
8332
+ projectRootRef.current = nuxtDiscovery.rootDir;
8333
+ pageDirsRef.current = nuxtDiscovery.pageDirs.length ? nuxtDiscovery.pageDirs : [path.resolve(nuxtDiscovery.srcDir, "pages")];
8334
+ componentDirsRef.current = nuxtDiscovery.componentDirs;
8335
+ layoutDirsRef.current = nuxtDiscovery.layoutDirs;
8336
+ wrapperSearchRootsRef.current = nuxtDiscovery.wrapperSearchRoots;
8337
+ }
8047
8338
  assertNonEmptyString(testIdAttribute, "[vue-pom-generator] injection.attribute");
8048
- assertNonEmptyString(viewsDir, "[vue-pom-generator] injection.viewsDir");
8049
- assertNonEmptyStringArray(wrapperSearchRoots, "[vue-pom-generator] injection.wrapperSearchRoots");
8339
+ assertNonEmptyString(getViewsDir(), "[vue-pom-generator] injection.viewsDir");
8340
+ assertNonEmptyStringArray(getComponentDirs(), "[vue-pom-generator] injection.componentDirs");
8341
+ assertNonEmptyStringArray(getLayoutDirs(), "[vue-pom-generator] injection.layoutDirs");
8342
+ assertNonEmptyStringArray(getWrapperSearchRoots(), "[vue-pom-generator] injection.wrapperSearchRoots");
8050
8343
  assertErrorBehavior(errorBehavior, "[vue-pom-generator] errorBehavior");
8051
8344
  if (generationEnabled) {
8052
8345
  assertNonEmptyString(outDir, "[vue-pom-generator] generation.outDir");
8053
8346
  assertOneOf(typescriptOutputStructure, ["aggregated", "split"], "[vue-pom-generator] generation.playwright.outputStructure");
8054
8347
  assertRouterModuleShims(routerModuleShims, "[vue-pom-generator] generation.router.moduleShims");
8055
- if (generationOptions?.router && routerType === "vue-router") {
8348
+ if (!isNuxt && vueGenerationOptions?.router && routerType === "vue-router") {
8056
8349
  assertNonEmptyString(routerEntry, "[vue-pom-generator] generation.router.entry");
8057
8350
  }
8058
8351
  }
8059
8352
  if (usesExternalVuePlugin) {
8060
- applyTemplateCompilerOptionsToResolvedVuePlugin(config, templateCompilerOptions, isNuxt ? "nuxt" : vuePluginOwnership);
8353
+ applyTemplateCompilerOptionsToResolvedVuePlugin(
8354
+ config,
8355
+ templateCompilerOptionsForResolvedPlugin,
8356
+ isNuxt ? "nuxt" : vuePluginOwnership
8357
+ );
8061
8358
  }
8062
8359
  loggerRef.current.info(`projectRoot=${projectRootRef.current}`);
8360
+ loggerRef.current.info(`viewsDir=${getViewsDir()}`);
8361
+ loggerRef.current.info(`componentDirs=${getComponentDirs().join(", ")}`);
8362
+ loggerRef.current.info(`layoutDirs=${getLayoutDirs().join(", ")}`);
8063
8363
  loggerRef.current.info(`Active plugins: ${(config.plugins ?? []).map((p) => p.name).filter((n) => n.includes("vue-pom")).join(", ")}`);
8064
8364
  }
8065
8365
  };
8366
+ const getViewsDirAbs = () => resolveFromProjectRoot(projectRootRef.current, getViewsDir());
8367
+ const getWrapperSearchRootsAbs = () => getWrapperSearchRoots().map((root) => resolveFromProjectRoot(projectRootRef.current, root));
8368
+ const { componentTestIds, elementMetadata, semanticNameMap, componentHierarchyMap, vueFilesPathMap } = sharedState;
8369
+ const { metadataCollectorPlugin, internalVuePlugin, templateCompilerOptions } = createVuePluginWithTestIds({
8370
+ vueOptions,
8371
+ existingIdBehavior,
8372
+ nameCollisionBehavior,
8373
+ nativeWrappers,
8374
+ elementMetadata,
8375
+ semanticNameMap,
8376
+ componentHierarchyMap,
8377
+ vueFilesPathMap,
8378
+ excludedComponents,
8379
+ getViewsDirAbs,
8380
+ testIdAttribute,
8381
+ loggerRef,
8382
+ getSourceDirs,
8383
+ getWrapperSearchRoots: getWrapperSearchRootsAbs,
8384
+ getProjectRoot: () => projectRootRef.current
8385
+ });
8386
+ templateCompilerOptionsForResolvedPlugin = templateCompilerOptions;
8066
8387
  const routerAwarePoms = typeof routerEntry === "string" && routerEntry.trim().length > 0 || routerType === "nuxt";
8067
8388
  const supportPlugins = createSupportPlugins({
8068
8389
  componentTestIds,
@@ -8070,8 +8391,11 @@ function createVuePomGeneratorPlugins(options = {}) {
8070
8391
  vueFilesPathMap,
8071
8392
  nativeWrappers,
8072
8393
  excludedComponents,
8073
- viewsDir,
8074
- scanDirs,
8394
+ getPageDirs,
8395
+ getComponentDirs,
8396
+ getLayoutDirs,
8397
+ getViewsDir,
8398
+ getSourceDirs,
8075
8399
  getWrapperSearchRoots: getWrapperSearchRootsAbs,
8076
8400
  nameCollisionBehavior,
8077
8401
  missingSemanticNameBehavior,
@@ -8116,11 +8440,21 @@ function createVuePomGeneratorPlugins(options = {}) {
8116
8440
  }
8117
8441
  return resultPlugins;
8118
8442
  }
8443
+ const nuxtConfigMarker = /* @__PURE__ */ Symbol.for("@immense/vue-pom-generator.nuxt");
8119
8444
  function defineVuePomGeneratorConfig(options) {
8120
8445
  return options;
8121
8446
  }
8447
+ function defineNuxtPomGeneratorConfig(options) {
8448
+ const markedOptions = { ...options };
8449
+ Object.defineProperty(markedOptions, nuxtConfigMarker, {
8450
+ value: true,
8451
+ enumerable: false
8452
+ });
8453
+ return markedOptions;
8454
+ }
8122
8455
  exports.createVuePomGeneratorPlugins = createVuePomGeneratorPlugins;
8123
8456
  exports.default = createVuePomGeneratorPlugins;
8457
+ exports.defineNuxtPomGeneratorConfig = defineNuxtPomGeneratorConfig;
8124
8458
  exports.defineVuePomGeneratorConfig = defineVuePomGeneratorConfig;
8125
8459
  exports.vuePomGenerator = createVuePomGeneratorPlugins;
8126
8460
  //# sourceMappingURL=index.cjs.map