@immense/vue-pom-generator 1.0.51 → 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 (43) hide show
  1. package/README.md +61 -22
  2. package/RELEASE_NOTES.md +47 -14
  3. package/class-generation/base-page.ts +0 -1
  4. package/class-generation/index.ts +23 -60
  5. package/dist/class-generation/base-page.d.ts.map +1 -1
  6. package/dist/class-generation/index.d.ts +3 -2
  7. package/dist/class-generation/index.d.ts.map +1 -1
  8. package/dist/eslint.config.d.ts.map +1 -1
  9. package/dist/index.cjs +531 -167
  10. package/dist/index.cjs.map +1 -1
  11. package/dist/index.d.ts +3 -2
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.mjs +531 -167
  14. package/dist/index.mjs.map +1 -1
  15. package/dist/plugin/create-vue-pom-generator-plugins.d.ts +2 -2
  16. package/dist/plugin/create-vue-pom-generator-plugins.d.ts.map +1 -1
  17. package/dist/plugin/nuxt-discovery.d.ts +47 -0
  18. package/dist/plugin/nuxt-discovery.d.ts.map +1 -0
  19. package/dist/plugin/path-utils.d.ts +4 -5
  20. package/dist/plugin/path-utils.d.ts.map +1 -1
  21. package/dist/plugin/support/build-plugin.d.ts +6 -3
  22. package/dist/plugin/support/build-plugin.d.ts.map +1 -1
  23. package/dist/plugin/support/dev-plugin.d.ts +6 -3
  24. package/dist/plugin/support/dev-plugin.d.ts.map +1 -1
  25. package/dist/plugin/support-plugins.d.ts +5 -2
  26. package/dist/plugin/support-plugins.d.ts.map +1 -1
  27. package/dist/plugin/types.d.ts +33 -17
  28. package/dist/plugin/types.d.ts.map +1 -1
  29. package/dist/plugin/vue-plugin.d.ts +1 -1
  30. package/dist/plugin/vue-plugin.d.ts.map +1 -1
  31. package/dist/router-introspection.d.ts +4 -2
  32. package/dist/router-introspection.d.ts.map +1 -1
  33. package/dist/tests/nuxt-discovery.test.d.ts +2 -0
  34. package/dist/tests/nuxt-discovery.test.d.ts.map +1 -0
  35. package/dist/transform.d.ts +11 -0
  36. package/dist/transform.d.ts.map +1 -1
  37. package/dist/utils.d.ts +15 -0
  38. package/dist/utils.d.ts.map +1 -1
  39. package/package.json +2 -2
  40. package/dist/plugin/support/generation-metrics.d.ts +0 -10
  41. package/dist/plugin/support/generation-metrics.d.ts.map +0 -1
  42. package/dist/tests/generation-metrics.test.d.ts +0 -2
  43. 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,
@@ -728,7 +856,14 @@ function getTemplateSlotScope(node) {
728
856
  return null;
729
857
  }
730
858
  function isSimpleScopeIdentifier(value) {
731
- return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(value);
859
+ if (!value) {
860
+ return false;
861
+ }
862
+ try {
863
+ return isIdentifier(parseExpression(value, { plugins: ["typescript"] }));
864
+ } catch {
865
+ return false;
866
+ }
732
867
  }
733
868
  function buildSlotScopeFallbackKeyExpression(identifier) {
734
869
  return `${identifier}.key ?? ${identifier}.data?.id ?? ${identifier}.id ?? ${identifier}.value ?? ${identifier}`;
@@ -748,6 +883,29 @@ function tryGetBindingIdentifierName(node) {
748
883
  }
749
884
  return null;
750
885
  }
886
+ function splitNullishCoalescingExpression(expr) {
887
+ const ast = parseExpression(expr, { plugins: ["typescript"] });
888
+ const toSourceText = (node) => {
889
+ if (node.start == null || node.end == null) {
890
+ throw new Error("[vue-pom-generator] Unable to recover source for nullish-coalescing expression.");
891
+ }
892
+ return expr.slice(node.start, node.end).trim();
893
+ };
894
+ const parts = [];
895
+ const visit = (node) => {
896
+ if (isLogicalExpression(node) && node.operator === "??") {
897
+ visit(node.left);
898
+ visit(node.right);
899
+ return;
900
+ }
901
+ const candidate = toSourceText(node);
902
+ if (candidate) {
903
+ parts.push(candidate);
904
+ }
905
+ };
906
+ visit(ast);
907
+ return parts;
908
+ }
751
909
  function getSlotScopeObjectPropertyKeyName(node) {
752
910
  if (isIdentifier(node)) {
753
911
  return node.name;
@@ -2004,7 +2162,7 @@ function applyResolvedDataTestId(args) {
2004
2162
  if (!expr) {
2005
2163
  return [];
2006
2164
  }
2007
- return expr.split("??").map((part) => part.trim()).filter(Boolean);
2165
+ return splitNullishCoalescingExpression(expr);
2008
2166
  };
2009
2167
  let dataTestId = args.preferredGeneratedValue;
2010
2168
  let fromExisting = false;
@@ -2725,27 +2883,24 @@ function safeRealpath(value) {
2725
2883
  const resolvedParent = safeRealpath(parent);
2726
2884
  return resolvedParent === parent ? value : path.join(resolvedParent, path.basename(value));
2727
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
+ }
2728
2894
  function resolveComponentNameFromPath(options) {
2729
- const { projectRoot, viewsDirAbs, scanDirs, extraRoots = [] } = options;
2895
+ const { projectRoot, viewsDirAbs, sourceDirs, extraRoots = [] } = options;
2730
2896
  const cleanFilename = options.filename.includes("?") ? options.filename.substring(0, options.filename.indexOf("?")) : options.filename;
2731
2897
  const absFilename = path.isAbsolute(cleanFilename) ? cleanFilename : path.resolve(projectRoot, cleanFilename);
2732
2898
  const normalizedAbsFilename = path.normalize(safeRealpath(absFilename));
2733
2899
  const rootBases = [projectRoot, ...extraRoots.filter((r) => r !== projectRoot)];
2734
- const roots = [viewsDirAbs, ...rootBases.flatMap((base) => scanDirs.map((d) => path.resolve(base, d)))];
2735
- for (const base of rootBases) {
2736
- for (const dir of scanDirs) {
2737
- const absDir = path.resolve(base, dir);
2738
- try {
2739
- const pagesDir = path.join(absDir, "pages");
2740
- if (fs.existsSync(pagesDir))
2741
- roots.push(pagesDir);
2742
- const componentsDir = path.join(absDir, "components");
2743
- if (fs.existsSync(componentsDir))
2744
- roots.push(componentsDir);
2745
- } catch {
2746
- }
2747
- }
2748
- }
2900
+ const roots = [
2901
+ viewsDirAbs,
2902
+ ...sourceDirs.flatMap((dir) => path.isAbsolute(dir) ? [dir] : rootBases.map((base) => path.resolve(base, dir)))
2903
+ ];
2749
2904
  const potentialRoots = Array.from(new Set(roots.map((r) => path.normalize(safeRealpath(r))))).sort((a, b) => b.length - a.length);
2750
2905
  for (const root of potentialRoots) {
2751
2906
  if (normalizedAbsFilename.startsWith(root + path.sep) || normalizedAbsFilename === root) {
@@ -3109,7 +3264,7 @@ function resolveIntrospectedComponentName(componentInfo, componentNaming) {
3109
3264
  filename: componentInfo.filePath,
3110
3265
  projectRoot: componentNaming.projectRoot,
3111
3266
  viewsDirAbs: componentNaming.viewsDirAbs,
3112
- scanDirs: componentNaming.scanDirs,
3267
+ sourceDirs: componentNaming.sourceDirs,
3113
3268
  extraRoots: componentNaming.extraRoots
3114
3269
  });
3115
3270
  }
@@ -3221,69 +3376,97 @@ async function ensureDomShim() {
3221
3376
  if (!g.requestAnimationFrame)
3222
3377
  g.requestAnimationFrame = (cb) => setTimeout(() => cb(Date.now()), 16);
3223
3378
  }
3224
- async function introspectNuxtPages(projectRoot) {
3225
- const possiblePagesDirs = ["app/pages", "pages"];
3226
- let pagesDir = "";
3227
- for (const dir of possiblePagesDirs) {
3228
- const abs = path.resolve(projectRoot, dir);
3229
- if (fs.existsSync(abs) && fs.statSync(abs).isDirectory()) {
3230
- 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 === ".")
3231
3418
  break;
3232
- }
3419
+ segments.unshift(parsed.base);
3420
+ if (!parsed.dir || parsed.dir === "." || parsed.dir === current)
3421
+ break;
3422
+ current = parsed.dir;
3233
3423
  }
3234
- 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) {
3235
3436
  debugLog(`[router-introspection][nuxt] Could not find pages directory in ${projectRoot}`);
3236
3437
  return { routeNameMap: /* @__PURE__ */ new Map(), routePathMap: /* @__PURE__ */ new Map(), routeMetaEntries: [] };
3237
3438
  }
3439
+ const routePathMap = /* @__PURE__ */ new Map();
3238
3440
  const routeMetaEntries = [];
3239
- const walk = (dir, baseRoute) => {
3441
+ const walk = (pagesDir, dir) => {
3240
3442
  const files = fs.readdirSync(dir);
3241
3443
  for (const file of files) {
3242
3444
  const fullPath = path.join(dir, file);
3243
3445
  const stat = fs.statSync(fullPath);
3244
3446
  if (stat.isDirectory()) {
3245
- walk(fullPath, `${baseRoute}/${file}`);
3447
+ walk(pagesDir, fullPath);
3246
3448
  continue;
3247
3449
  }
3248
3450
  if (!file.endsWith(".vue"))
3249
3451
  continue;
3250
- const componentName = file.slice(0, -4);
3251
- if (componentName === "index" && baseRoute === "") {
3252
- routeMetaEntries.push({
3253
- componentName: "index",
3254
- pathTemplate: "/",
3255
- params: [],
3256
- query: []
3257
- });
3258
- continue;
3259
- }
3260
- let routePath = componentName === "index" ? baseRoute : `${baseRoute}/${componentName}`;
3261
- if (!routePath.startsWith("/"))
3262
- 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));
3263
3462
  const params = [];
3264
- let pathTemplate = "";
3265
- for (let i = 0; i < routePath.length; i++) {
3266
- const ch = routePath[i];
3267
- if (ch !== "[") {
3268
- pathTemplate += ch;
3269
- continue;
3270
- }
3271
- let name = "";
3272
- i++;
3273
- while (i < routePath.length) {
3274
- const c = routePath[i];
3275
- if (c === "]")
3276
- break;
3277
- name += c;
3278
- i++;
3279
- }
3280
- if (name) {
3281
- params.push({ name, optional: false });
3282
- pathTemplate += `:${name}`;
3283
- } else {
3284
- pathTemplate += "[]";
3285
- }
3286
- }
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);
3287
3470
  routeMetaEntries.push({
3288
3471
  componentName,
3289
3472
  pathTemplate,
@@ -3292,10 +3475,12 @@ async function introspectNuxtPages(projectRoot) {
3292
3475
  });
3293
3476
  }
3294
3477
  };
3295
- walk(pagesDir, "");
3478
+ for (const pageDir of pageDirs) {
3479
+ walk(pageDir, pageDir);
3480
+ }
3296
3481
  return {
3297
3482
  routeNameMap: /* @__PURE__ */ new Map(),
3298
- routePathMap: /* @__PURE__ */ new Map(),
3483
+ routePathMap,
3299
3484
  routeMetaEntries
3300
3485
  };
3301
3486
  }
@@ -3591,15 +3776,20 @@ function resolveVueSourcePath(targetClassName, vueFilesPathMap, projectRoot) {
3591
3776
  }
3592
3777
  async function getRouteMetaByComponent(projectRoot, routerEntry, routerType, options = {}) {
3593
3778
  const root = projectRoot ?? process.cwd();
3594
- const viewsDir = options.viewsDir ?? "src/views";
3595
- const viewsDirAbs = path.isAbsolute(viewsDir) ? viewsDir : path.resolve(root, viewsDir);
3596
- 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
+ ];
3597
3787
  const extraRoots = process.cwd() !== root ? [process.cwd()] : [];
3598
- 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), {
3599
3789
  componentNaming: {
3600
3790
  projectRoot: root,
3601
- viewsDirAbs,
3602
- scanDirs,
3791
+ viewsDirAbs: primaryPageDirAbs,
3792
+ sourceDirs,
3603
3793
  extraRoots
3604
3794
  }
3605
3795
  });
@@ -3799,15 +3989,17 @@ async function generateFiles(componentHierarchyMap, vueFilesPathMap, basePageCla
3799
3989
  vueRouterFluentChaining,
3800
3990
  routerEntry,
3801
3991
  routerType,
3802
- viewsDir,
3803
- scanDirs,
3992
+ pageDirs,
3993
+ componentDirs,
3994
+ layoutDirs,
3804
3995
  routeMetaByComponent: routeMetaByComponentOverride
3805
3996
  } = options;
3806
3997
  const emitLanguages = emitLanguagesOverride?.length ? emitLanguagesOverride : ["ts"];
3807
3998
  const outDir = outDirOverride ?? "./pom";
3808
3999
  const routeMetaByComponent = routeMetaByComponentOverride ?? (vueRouterFluentChaining ? await getRouteMetaByComponent(projectRoot, routerEntry, routerType, {
3809
- viewsDir,
3810
- scanDirs
4000
+ pageDirs,
4001
+ componentDirs,
4002
+ layoutDirs
3811
4003
  }) : void 0);
3812
4004
  const generatedFilePaths = [];
3813
4005
  const writeGeneratedFile = (file) => {
@@ -6596,8 +6788,11 @@ function createBuildProcessorPlugin(options) {
6596
6788
  const {
6597
6789
  componentHierarchyMap,
6598
6790
  vueFilesPathMap,
6599
- viewsDir,
6600
- scanDirs,
6791
+ getPageDirs,
6792
+ getComponentDirs,
6793
+ getLayoutDirs,
6794
+ getViewsDir,
6795
+ getSourceDirs,
6601
6796
  basePageClassPath,
6602
6797
  normalizedBasePagePath,
6603
6798
  outDir,
@@ -6618,7 +6813,7 @@ function createBuildProcessorPlugin(options) {
6618
6813
  excludedComponents,
6619
6814
  getWrapperSearchRoots,
6620
6815
  routerAwarePoms,
6621
- resolvedRouterEntry,
6816
+ getResolvedRouterEntry,
6622
6817
  routerType,
6623
6818
  routerModuleShims,
6624
6819
  loggerRef
@@ -6628,7 +6823,8 @@ function createBuildProcessorPlugin(options) {
6628
6823
  interactiveComponentCount: 0,
6629
6824
  dataTestIdCount: 0
6630
6825
  };
6631
- 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));
6632
6828
  const getScriptInfo = (source, filename) => {
6633
6829
  try {
6634
6830
  const { descriptor } = parse$1(source, { filename });
@@ -6668,7 +6864,7 @@ function createBuildProcessorPlugin(options) {
6668
6864
  return out;
6669
6865
  };
6670
6866
  let supplemented = 0;
6671
- for (const dir of scanDirs) {
6867
+ for (const dir of getSourceDirs()) {
6672
6868
  const absDir = path.resolve(projectRootRef.current, dir);
6673
6869
  if (!fs.existsSync(absDir))
6674
6870
  continue;
@@ -6678,7 +6874,7 @@ function createBuildProcessorPlugin(options) {
6678
6874
  filename: absolutePath,
6679
6875
  projectRoot: projectRootRef.current,
6680
6876
  viewsDirAbs: getViewsDirAbs(),
6681
- scanDirs,
6877
+ sourceDirs: getSourceDirs(),
6682
6878
  extraRoots: [process.cwd()]
6683
6879
  });
6684
6880
  if (componentHierarchyMap.has(componentName))
@@ -6753,16 +6949,17 @@ function createBuildProcessorPlugin(options) {
6753
6949
  }
6754
6950
  let result;
6755
6951
  if (routerType === "nuxt") {
6756
- result = await introspectNuxtPages(projectRootRef.current);
6952
+ result = await introspectNuxtPages(projectRootRef.current, { pageDirs: getPageDirsAbs() });
6757
6953
  } else {
6954
+ const resolvedRouterEntry = getResolvedRouterEntry();
6758
6955
  if (!resolvedRouterEntry)
6759
6956
  throw new Error("[vue-pom-generator] router.entry is required when router introspection is enabled.");
6760
6957
  result = await parseRouterFileFromCwd(resolvedRouterEntry, {
6761
6958
  moduleShims: routerModuleShims,
6762
6959
  componentNaming: {
6763
6960
  projectRoot: projectRootRef.current,
6764
- viewsDirAbs: path.isAbsolute(viewsDir) ? viewsDir : path.resolve(projectRootRef.current, viewsDir),
6765
- scanDirs
6961
+ viewsDirAbs: getViewsDirAbs(),
6962
+ sourceDirs: getSourceDirs()
6766
6963
  }
6767
6964
  });
6768
6965
  }
@@ -6822,10 +7019,11 @@ function createBuildProcessorPlugin(options) {
6822
7019
  customPomImportNameCollisionBehavior,
6823
7020
  testIdAttribute,
6824
7021
  vueRouterFluentChaining: routerAwarePoms,
6825
- routerEntry: resolvedRouterEntry,
7022
+ routerEntry: getResolvedRouterEntry(),
6826
7023
  routerType,
6827
- viewsDir,
6828
- scanDirs
7024
+ pageDirs: getPageDirs(),
7025
+ componentDirs: getComponentDirs(),
7026
+ layoutDirs: getLayoutDirs()
6829
7027
  });
6830
7028
  lastGeneratedMetrics = metrics;
6831
7029
  loggerRef.current.info(`generated POMs (${metrics.entryCount} entries, ${metrics.interactiveComponentCount} interactive components, ${metrics.dataTestIdCount} selectors)`);
@@ -6839,8 +7037,11 @@ function createDevProcessorPlugin(options) {
6839
7037
  const {
6840
7038
  nativeWrappers,
6841
7039
  excludedComponents,
6842
- viewsDir,
6843
- scanDirs,
7040
+ getPageDirs,
7041
+ getComponentDirs,
7042
+ getLayoutDirs,
7043
+ getViewsDir,
7044
+ getSourceDirs,
6844
7045
  getWrapperSearchRoots,
6845
7046
  projectRootRef,
6846
7047
  normalizedBasePagePath,
@@ -6859,12 +7060,29 @@ function createDevProcessorPlugin(options) {
6859
7060
  existingIdBehavior,
6860
7061
  testIdAttribute,
6861
7062
  routerAwarePoms,
6862
- resolvedRouterEntry,
7063
+ getResolvedRouterEntry,
6863
7064
  routerType,
6864
7065
  routerModuleShims,
6865
7066
  loggerRef
6866
7067
  } = options;
6867
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
+ };
6868
7086
  return {
6869
7087
  name: "vue-pom-generator-dev",
6870
7088
  apply: "serve",
@@ -6875,16 +7093,13 @@ function createDevProcessorPlugin(options) {
6875
7093
  return;
6876
7094
  if (!ctx.file.endsWith(".vue"))
6877
7095
  return;
6878
- const isContainedInScanDirs = scanDirs.some((dir) => {
6879
- const absDir = path.resolve(projectRootRef.current, dir);
6880
- return ctx.file.startsWith(absDir + path.sep);
6881
- });
6882
- if (!isContainedInScanDirs)
7096
+ if (!isContainedInScanDirs(ctx.file))
6883
7097
  return;
6884
7098
  scheduleVueFileRegen(ctx.file, "hmr");
6885
7099
  },
6886
7100
  async configureServer(server) {
6887
- const getViewsDirAbs = () => path.isAbsolute(viewsDir) ? viewsDir : path.resolve(projectRootRef.current, viewsDir);
7101
+ const getViewsDirAbs = () => resolveProjectPath(getViewsDir());
7102
+ const getPageDirsAbs = () => getPageDirs().map((dir) => resolveProjectPath(dir));
6888
7103
  const routerInitPromise = (async () => {
6889
7104
  if (!routerAwarePoms) {
6890
7105
  setRouteNameToComponentNameMap(/* @__PURE__ */ new Map());
@@ -6893,8 +7108,9 @@ function createDevProcessorPlugin(options) {
6893
7108
  }
6894
7109
  let result;
6895
7110
  if (routerType === "nuxt") {
6896
- result = await introspectNuxtPages(projectRootRef.current);
7111
+ result = await introspectNuxtPages(projectRootRef.current, { pageDirs: getPageDirsAbs() });
6897
7112
  } else {
7113
+ const resolvedRouterEntry = getResolvedRouterEntry();
6898
7114
  if (!resolvedRouterEntry)
6899
7115
  throw new Error("[vue-pom-generator] router.entry is required when router introspection is enabled.");
6900
7116
  result = await parseRouterFileFromCwd(resolvedRouterEntry, {
@@ -6902,7 +7118,7 @@ function createDevProcessorPlugin(options) {
6902
7118
  componentNaming: {
6903
7119
  projectRoot: projectRootRef.current,
6904
7120
  viewsDirAbs: getViewsDirAbs(),
6905
- scanDirs,
7121
+ sourceDirs: getSourceDirs(),
6906
7122
  extraRoots: [process.cwd()]
6907
7123
  }
6908
7124
  });
@@ -6974,6 +7190,19 @@ function createDevProcessorPlugin(options) {
6974
7190
  let snapshotHierarchy = /* @__PURE__ */ new Map();
6975
7191
  let snapshotVuePathMap = /* @__PURE__ */ new Map();
6976
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
+ };
6977
7206
  const getComponentNameForFile = (filePath) => {
6978
7207
  const normalized = path.resolve(filePath);
6979
7208
  const existing = filePathToComponentName.get(normalized);
@@ -6983,7 +7212,7 @@ function createDevProcessorPlugin(options) {
6983
7212
  filename: normalized,
6984
7213
  projectRoot: projectRootRef.current,
6985
7214
  viewsDirAbs: getViewsDirAbs(),
6986
- scanDirs,
7215
+ sourceDirs: getSourceDirs(),
6987
7216
  extraRoots: [process.cwd()]
6988
7217
  });
6989
7218
  filePathToComponentName.set(normalized, name);
@@ -6993,8 +7222,6 @@ function createDevProcessorPlugin(options) {
6993
7222
  const started = performance.now();
6994
7223
  const absolutePath = path.resolve(filePath);
6995
7224
  const componentName = getComponentNameForFile(absolutePath);
6996
- targetVuePathMap.set(componentName, absolutePath);
6997
- targetHierarchy.delete(componentName);
6998
7225
  let sfc = "";
6999
7226
  try {
7000
7227
  sfc = fs.readFileSync(absolutePath, "utf8");
@@ -7002,9 +7229,15 @@ function createDevProcessorPlugin(options) {
7002
7229
  return { componentName, ms: performance.now() - started, compiled: false };
7003
7230
  }
7004
7231
  const template = extractTemplateFromSfc(sfc, absolutePath);
7005
- if (!template.trim())
7232
+ if (!template.trim()) {
7233
+ targetVuePathMap.set(componentName, absolutePath);
7234
+ targetHierarchy.set(componentName, createEmptyComponentDependencies(absolutePath));
7006
7235
  return { componentName, ms: performance.now() - started, compiled: true };
7236
+ }
7007
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);
7008
7241
  compilerDom.compile(template, {
7009
7242
  filename: absolutePath,
7010
7243
  prefixIdentifiers: true,
@@ -7013,7 +7246,7 @@ function createDevProcessorPlugin(options) {
7013
7246
  nodeTransforms: [
7014
7247
  createTestIdTransform(
7015
7248
  componentName,
7016
- targetHierarchy,
7249
+ provisionalHierarchy,
7017
7250
  nativeWrappers,
7018
7251
  excludedComponents,
7019
7252
  getViewsDirAbs(),
@@ -7023,23 +7256,27 @@ function createDevProcessorPlugin(options) {
7023
7256
  missingSemanticNameBehavior,
7024
7257
  testIdAttribute,
7025
7258
  warn: (message) => loggerRef.current.warn(message),
7026
- vueFilesPathMap: targetVuePathMap,
7259
+ vueFilesPathMap: provisionalVuePathMap,
7027
7260
  wrapperSearchRoots: getWrapperSearchRoots()
7028
7261
  }
7029
7262
  )
7030
7263
  ]
7031
7264
  });
7265
+ targetVuePathMap.set(componentName, absolutePath);
7266
+ targetHierarchy.set(
7267
+ componentName,
7268
+ provisionalHierarchy.get(componentName) ?? createEmptyComponentDependencies(absolutePath)
7269
+ );
7032
7270
  return { componentName, ms: performance.now() - started, compiled: true };
7033
7271
  };
7034
- const fullRebuildSnapshotFromFilesystem = () => {
7272
+ const fullRebuildSnapshotFromFilesystem = (logLabel) => {
7035
7273
  const t0 = performance.now();
7036
7274
  const nextHierarchy = /* @__PURE__ */ new Map();
7037
7275
  const nextVuePathMap = /* @__PURE__ */ new Map();
7038
7276
  filePathToComponentName.clear();
7039
7277
  let totalVueFiles = 0;
7040
7278
  let compiledCount = 0;
7041
- for (const dir of scanDirs) {
7042
- const absDir = path.resolve(projectRootRef.current, dir);
7279
+ for (const absDir of getSourceDirRoots()) {
7043
7280
  if (!fs.existsSync(absDir))
7044
7281
  continue;
7045
7282
  const vueFiles = walkFilesRecursive(absDir);
@@ -7053,12 +7290,12 @@ function createDevProcessorPlugin(options) {
7053
7290
  snapshotHierarchy = nextHierarchy;
7054
7291
  snapshotVuePathMap = nextVuePathMap;
7055
7292
  const t1 = performance.now();
7056
- logInfo(`initial scan: found ${totalVueFiles} .vue files in ${scanDirs.join(", ")}`);
7057
- 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})`);
7058
7295
  };
7059
- const generateAggregatedFromSnapshot = (reason) => {
7296
+ const generateAggregatedFromSnapshot = async (logLabel) => {
7060
7297
  const t0 = performance.now();
7061
- generateFiles(snapshotHierarchy, snapshotVuePathMap, normalizedBasePagePath, {
7298
+ await generateFiles(snapshotHierarchy, snapshotVuePathMap, normalizedBasePagePath, {
7062
7299
  outDir,
7063
7300
  emitLanguages,
7064
7301
  typescriptOutputStructure,
@@ -7069,25 +7306,27 @@ function createDevProcessorPlugin(options) {
7069
7306
  customPomDir,
7070
7307
  customPomImportAliases,
7071
7308
  customPomImportNameCollisionBehavior,
7072
- viewsDir,
7073
- scanDirs,
7309
+ pageDirs: getPageDirs(),
7310
+ componentDirs: getComponentDirs(),
7311
+ layoutDirs: getLayoutDirs(),
7074
7312
  testIdAttribute,
7075
7313
  vueRouterFluentChaining: routerAwarePoms,
7076
- routerEntry: resolvedRouterEntry,
7314
+ routerEntry: getResolvedRouterEntry(),
7077
7315
  routerType
7078
7316
  });
7079
7317
  const t1 = performance.now();
7080
- logInfo(`generate(${reason}): components=${snapshotHierarchy.size} in ${formatMs(t1 - t0)}`);
7318
+ logInfo(`generate(${logLabel}): components=${snapshotHierarchy.size} in ${formatMs(t1 - t0)}`);
7081
7319
  };
7082
7320
  let timer = null;
7083
7321
  let maxWaitTimer = null;
7084
7322
  const pendingChangedVueFiles = /* @__PURE__ */ new Set();
7085
7323
  const pendingDeletedComponents = /* @__PURE__ */ new Set();
7324
+ let regenerationSequence = Promise.resolve();
7086
7325
  const initialBuildPromise = (async () => {
7087
7326
  const t0 = performance.now();
7088
7327
  await routerInitPromise;
7089
- fullRebuildSnapshotFromFilesystem();
7090
- generateAggregatedFromSnapshot("startup");
7328
+ fullRebuildSnapshotFromFilesystem("startup");
7329
+ await generateAggregatedFromSnapshot("startup");
7091
7330
  const t1 = performance.now();
7092
7331
  logInfo(`startup total: ${formatMs(t1 - t0)}`);
7093
7332
  })();
@@ -7115,7 +7354,7 @@ function createDevProcessorPlugin(options) {
7115
7354
  snapshotHierarchy = nextHierarchy;
7116
7355
  snapshotVuePathMap = nextVuePathMap;
7117
7356
  const t1 = performance.now();
7118
- generateAggregatedFromSnapshot(reason);
7357
+ await generateAggregatedFromSnapshot(reason);
7119
7358
  const t2 = performance.now();
7120
7359
  return {
7121
7360
  files,
@@ -7126,7 +7365,12 @@ function createDevProcessorPlugin(options) {
7126
7365
  totalMs: t2 - t0
7127
7366
  };
7128
7367
  };
7129
- 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"));
7130
7374
  const watchedPluginGlob = path.resolve(projectRootRef.current, "vite-plugins", "vue-pom-generator", "**", "*.ts");
7131
7375
  const runtimeDir = path.dirname(basePageClassPath);
7132
7376
  server.watcher.add([
@@ -7152,7 +7396,7 @@ function createDevProcessorPlugin(options) {
7152
7396
  timer = null;
7153
7397
  }
7154
7398
  maxWaitTimer = null;
7155
- void regenerateFromPending("max-wait").then(({ files, deletedCount, compileMs, preGenerateMs, generateMs, totalMs }) => {
7399
+ void enqueueRegeneration("max-wait").then(({ files, deletedCount, compileMs, preGenerateMs, generateMs, totalMs }) => {
7156
7400
  logInfo(
7157
7401
  `max-wait: files=${files.length} deleted=${deletedCount} compile=${formatMs(compileMs)} wall=${formatMs(preGenerateMs)} gen=${formatMs(generateMs)} total=${formatMs(totalMs)}`
7158
7402
  );
@@ -7176,7 +7420,7 @@ function createDevProcessorPlugin(options) {
7176
7420
  maxWaitTimer = null;
7177
7421
  }
7178
7422
  const reason = pendingChangedVueFiles.size || pendingDeletedComponents.size ? "batched" : "noop";
7179
- void regenerateFromPending(reason).then(({ files, deletedCount, compileMs, preGenerateMs, generateMs, totalMs }) => {
7423
+ void enqueueRegeneration(reason).then(({ files, deletedCount, compileMs, preGenerateMs, generateMs, totalMs }) => {
7180
7424
  if (files.length || deletedCount) {
7181
7425
  logInfo(
7182
7426
  `batched: files=${files.length} deleted=${deletedCount} compile=${formatMs(compileMs)} wall=${formatMs(preGenerateMs)} gen=${formatMs(generateMs)} total=${formatMs(totalMs)}`
@@ -7203,11 +7447,7 @@ function createDevProcessorPlugin(options) {
7203
7447
  return;
7204
7448
  if (!p.endsWith(".vue"))
7205
7449
  return;
7206
- const isContainedInScanDirs = scanDirs.some((dir) => {
7207
- const absDir = path.resolve(projectRootRef.current, dir);
7208
- return p.startsWith(absDir + path.sep);
7209
- });
7210
- if (!isContainedInScanDirs)
7450
+ if (!isContainedInScanDirs(p))
7211
7451
  return;
7212
7452
  void (async () => {
7213
7453
  await initialBuildPromise;
@@ -7220,11 +7460,7 @@ function createDevProcessorPlugin(options) {
7220
7460
  return;
7221
7461
  if (!p.endsWith(".vue"))
7222
7462
  return;
7223
- const isContainedInScanDirs = scanDirs.some((dir) => {
7224
- const absDir = path.resolve(projectRootRef.current, dir);
7225
- return p.startsWith(absDir + path.sep);
7226
- });
7227
- if (!isContainedInScanDirs)
7463
+ if (!isContainedInScanDirs(p))
7228
7464
  return;
7229
7465
  void (async () => {
7230
7466
  await initialBuildPromise;
@@ -7288,8 +7524,11 @@ function createSupportPlugins(options) {
7288
7524
  vueFilesPathMap,
7289
7525
  nativeWrappers,
7290
7526
  excludedComponents,
7291
- viewsDir,
7292
- scanDirs,
7527
+ getPageDirs,
7528
+ getComponentDirs,
7529
+ getLayoutDirs,
7530
+ getViewsDir,
7531
+ getSourceDirs,
7293
7532
  getWrapperSearchRoots,
7294
7533
  nameCollisionBehavior = "suffix",
7295
7534
  missingSemanticNameBehavior,
@@ -7321,7 +7560,6 @@ function createSupportPlugins(options) {
7321
7560
  throw new Error("[vue-pom-generator] router.entry is required when router introspection is enabled.");
7322
7561
  return path.isAbsolute(routerEntry) ? routerEntry : path.resolve(projectRootRef.current, routerEntry);
7323
7562
  };
7324
- const resolvedRouterEntry = resolveRouterEntry2();
7325
7563
  const getDefaultBasePageClassPath = () => {
7326
7564
  try {
7327
7565
  return fileURLToPath(new URL("../class-generation/base-page.ts", import.meta.url));
@@ -7334,8 +7572,11 @@ function createSupportPlugins(options) {
7334
7572
  const tsProcessor = createBuildProcessorPlugin({
7335
7573
  componentHierarchyMap,
7336
7574
  vueFilesPathMap,
7337
- viewsDir,
7338
- scanDirs,
7575
+ getPageDirs,
7576
+ getComponentDirs,
7577
+ getLayoutDirs,
7578
+ getViewsDir,
7579
+ getSourceDirs,
7339
7580
  basePageClassPath,
7340
7581
  normalizedBasePagePath,
7341
7582
  outDir,
@@ -7357,15 +7598,18 @@ function createSupportPlugins(options) {
7357
7598
  getWrapperSearchRoots,
7358
7599
  routerAwarePoms,
7359
7600
  routerType,
7360
- resolvedRouterEntry,
7601
+ getResolvedRouterEntry: resolveRouterEntry2,
7361
7602
  routerModuleShims,
7362
7603
  loggerRef
7363
7604
  });
7364
7605
  const devProcessor = createDevProcessorPlugin({
7365
7606
  nativeWrappers,
7366
7607
  excludedComponents,
7367
- viewsDir,
7368
- scanDirs,
7608
+ getPageDirs,
7609
+ getComponentDirs,
7610
+ getLayoutDirs,
7611
+ getViewsDir,
7612
+ getSourceDirs,
7369
7613
  getWrapperSearchRoots,
7370
7614
  projectRootRef,
7371
7615
  normalizedBasePagePath,
@@ -7385,7 +7629,7 @@ function createSupportPlugins(options) {
7385
7629
  testIdAttribute,
7386
7630
  routerAwarePoms,
7387
7631
  routerType,
7388
- resolvedRouterEntry,
7632
+ getResolvedRouterEntry: resolveRouterEntry2,
7389
7633
  routerModuleShims,
7390
7634
  loggerRef
7391
7635
  });
@@ -7543,7 +7787,7 @@ function createVuePluginWithTestIds(options) {
7543
7787
  getViewsDirAbs,
7544
7788
  testIdAttribute,
7545
7789
  loggerRef,
7546
- scanDirs = ["src"],
7790
+ getSourceDirs,
7547
7791
  getWrapperSearchRoots,
7548
7792
  getProjectRoot
7549
7793
  } = options;
@@ -7552,7 +7796,7 @@ function createVuePluginWithTestIds(options) {
7552
7796
  filename,
7553
7797
  projectRoot: getProjectRoot(),
7554
7798
  viewsDirAbs: getViewsDirAbs(),
7555
- scanDirs,
7799
+ sourceDirs: getSourceDirs(),
7556
7800
  extraRoots: [process.cwd()]
7557
7801
  });
7558
7802
  };
@@ -7568,7 +7812,7 @@ function createVuePluginWithTestIds(options) {
7568
7812
  if (absFilename.startsWith(viewsDirAbs + path.sep) || absFilename === viewsDirAbs)
7569
7813
  return true;
7570
7814
  const rootsToTry = [projectRoot, process.cwd()];
7571
- const matched = scanDirs.some((dir) => {
7815
+ const matched = getSourceDirs().some((dir) => {
7572
7816
  return rootsToTry.some((root) => {
7573
7817
  const absDir = path.resolve(root, dir);
7574
7818
  if (absFilename.startsWith(absDir + path.sep) || absFilename === absDir)
@@ -7743,6 +7987,25 @@ function createVuePluginWithTestIds(options) {
7743
7987
  };
7744
7988
  return { metadataCollectorPlugin, internalVuePlugin, nuxtVueBridgePlugin, templateCompilerOptions };
7745
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
+ ];
7746
8009
  function assertNonEmptyString(value, name) {
7747
8010
  if (!value || !value.trim()) {
7748
8011
  throw new Error(`${name} must be a non-empty string.`);
@@ -7790,6 +8053,57 @@ function resolveMissingSemanticNameBehavior(value) {
7790
8053
  }
7791
8054
  return value.missingSemanticNameBehavior ?? "ignore";
7792
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
+ }
7793
8107
  function assertRouterModuleShims(value, name) {
7794
8108
  if (!value)
7795
8109
  return;
@@ -7897,14 +8211,18 @@ function assertNotVitePluginInstance(options) {
7897
8211
  function createVuePomGeneratorPlugins(options = {}) {
7898
8212
  assertNotVitePluginInstance(options);
7899
8213
  const injection = options.injection ?? {};
8214
+ const isNuxt = detectNuxtProject(options, process.cwd());
7900
8215
  const generationSetting = options.generation;
7901
8216
  const generationOptions = generationSetting === false ? null : generationSetting ?? {};
7902
8217
  const generationEnabled = generationOptions !== null;
8218
+ const vueGenerationOptions = generationOptions;
7903
8219
  const verbosity = options.logging?.verbosity ?? "warn";
7904
8220
  const vueOptions = options.vueOptions;
7905
- const viewsDir = injection.viewsDir ?? "src/views";
7906
- const scanDirs = injection.scanDirs ?? ["src"];
7907
- 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 ?? [] : [] };
7908
8226
  const nativeWrappers = injection.nativeWrappers ?? {};
7909
8227
  const excludedComponents = injection.excludeComponents ?? [];
7910
8228
  const testIdAttribute = (injection.attribute ?? "data-testid").trim() || "data-testid";
@@ -7912,10 +8230,9 @@ function createVuePomGeneratorPlugins(options = {}) {
7912
8230
  const outDir = (generationOptions?.outDir ?? "tests/playwright/__generated__").trim();
7913
8231
  const emitLanguages = generationOptions?.emit && generationOptions.emit.length ? generationOptions.emit : ["ts"];
7914
8232
  const nameCollisionBehavior = generationOptions?.nameCollisionBehavior ?? "suffix";
7915
- const routerEntry = generationOptions?.router?.entry;
7916
- const routerType = generationOptions?.router?.type ?? "vue-router";
7917
- const routerModuleShims = generationOptions?.router?.moduleShims;
7918
- 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;
7919
8236
  if (isNuxt && options.vuePluginOwnership === "internal") {
7920
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".');
7921
8238
  }
@@ -7932,17 +8249,30 @@ function createVuePomGeneratorPlugins(options = {}) {
7932
8249
  const resolvedCustomPomImportAliases = customPoms?.importAliases;
7933
8250
  const resolvedCustomPomImportCollisionBehavior = customPoms?.importNameCollisionBehavior ?? "error";
7934
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;
7935
8262
  const sharedStateKey = JSON.stringify({
7936
8263
  cwd: process.cwd(),
7937
- viewsDir,
7938
- scanDirs,
7939
- 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(),
7940
8269
  outDir,
7941
8270
  testIdAttribute,
7942
8271
  routerType,
7943
8272
  vuePluginOwnership
7944
8273
  });
7945
8274
  const sharedState = getSharedGeneratorState(sharedStateKey);
8275
+ let templateCompilerOptionsForResolvedPlugin;
7946
8276
  const projectRootRef = { current: process.cwd() };
7947
8277
  const loggerRef = {
7948
8278
  current: createLogger({ verbosity })
@@ -7950,30 +8280,50 @@ function createVuePomGeneratorPlugins(options = {}) {
7950
8280
  const configPlugin = {
7951
8281
  name: "vue-pom-generator-config",
7952
8282
  enforce: "pre",
7953
- configResolved(config) {
8283
+ async configResolved(config) {
7954
8284
  projectRootRef.current = config.root;
7955
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
+ }
7956
8297
  assertNonEmptyString(testIdAttribute, "[vue-pom-generator] injection.attribute");
7957
- assertNonEmptyString(viewsDir, "[vue-pom-generator] injection.viewsDir");
7958
- 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");
7959
8302
  assertErrorBehavior(errorBehavior, "[vue-pom-generator] errorBehavior");
7960
8303
  if (generationEnabled) {
7961
8304
  assertNonEmptyString(outDir, "[vue-pom-generator] generation.outDir");
7962
8305
  assertOneOf(typescriptOutputStructure, ["aggregated", "split"], "[vue-pom-generator] generation.playwright.outputStructure");
7963
8306
  assertRouterModuleShims(routerModuleShims, "[vue-pom-generator] generation.router.moduleShims");
7964
- if (generationOptions?.router && routerType === "vue-router") {
8307
+ if (!isNuxt && vueGenerationOptions?.router && routerType === "vue-router") {
7965
8308
  assertNonEmptyString(routerEntry, "[vue-pom-generator] generation.router.entry");
7966
8309
  }
7967
8310
  }
7968
8311
  if (usesExternalVuePlugin) {
7969
- applyTemplateCompilerOptionsToResolvedVuePlugin(config, templateCompilerOptions, isNuxt ? "nuxt" : vuePluginOwnership);
8312
+ applyTemplateCompilerOptionsToResolvedVuePlugin(
8313
+ config,
8314
+ templateCompilerOptionsForResolvedPlugin,
8315
+ isNuxt ? "nuxt" : vuePluginOwnership
8316
+ );
7970
8317
  }
7971
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(", ")}`);
7972
8322
  loggerRef.current.info(`Active plugins: ${(config.plugins ?? []).map((p) => p.name).filter((n) => n.includes("vue-pom")).join(", ")}`);
7973
8323
  }
7974
8324
  };
7975
- const getViewsDirAbs = () => resolveFromProjectRoot(projectRootRef.current, viewsDir);
7976
- const getWrapperSearchRootsAbs = () => wrapperSearchRoots.map((root) => resolveFromProjectRoot(projectRootRef.current, root));
8325
+ const getViewsDirAbs = () => resolveFromProjectRoot(projectRootRef.current, getViewsDir());
8326
+ const getWrapperSearchRootsAbs = () => getWrapperSearchRoots().map((root) => resolveFromProjectRoot(projectRootRef.current, root));
7977
8327
  const { componentTestIds, elementMetadata, semanticNameMap, componentHierarchyMap, vueFilesPathMap } = sharedState;
7978
8328
  const { metadataCollectorPlugin, internalVuePlugin, templateCompilerOptions } = createVuePluginWithTestIds({
7979
8329
  vueOptions,
@@ -7988,10 +8338,11 @@ function createVuePomGeneratorPlugins(options = {}) {
7988
8338
  getViewsDirAbs,
7989
8339
  testIdAttribute,
7990
8340
  loggerRef,
7991
- scanDirs,
8341
+ getSourceDirs,
7992
8342
  getWrapperSearchRoots: getWrapperSearchRootsAbs,
7993
8343
  getProjectRoot: () => projectRootRef.current
7994
8344
  });
8345
+ templateCompilerOptionsForResolvedPlugin = templateCompilerOptions;
7995
8346
  const routerAwarePoms = typeof routerEntry === "string" && routerEntry.trim().length > 0 || routerType === "nuxt";
7996
8347
  const supportPlugins = createSupportPlugins({
7997
8348
  componentTestIds,
@@ -7999,8 +8350,11 @@ function createVuePomGeneratorPlugins(options = {}) {
7999
8350
  vueFilesPathMap,
8000
8351
  nativeWrappers,
8001
8352
  excludedComponents,
8002
- viewsDir,
8003
- scanDirs,
8353
+ getPageDirs,
8354
+ getComponentDirs,
8355
+ getLayoutDirs,
8356
+ getViewsDir,
8357
+ getSourceDirs,
8004
8358
  getWrapperSearchRoots: getWrapperSearchRootsAbs,
8005
8359
  nameCollisionBehavior,
8006
8360
  missingSemanticNameBehavior,
@@ -8045,12 +8399,22 @@ function createVuePomGeneratorPlugins(options = {}) {
8045
8399
  }
8046
8400
  return resultPlugins;
8047
8401
  }
8402
+ const nuxtConfigMarker = /* @__PURE__ */ Symbol.for("@immense/vue-pom-generator.nuxt");
8048
8403
  function defineVuePomGeneratorConfig(options) {
8049
8404
  return options;
8050
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
+ }
8051
8414
  export {
8052
8415
  createVuePomGeneratorPlugins,
8053
8416
  createVuePomGeneratorPlugins as default,
8417
+ defineNuxtPomGeneratorConfig,
8054
8418
  defineVuePomGeneratorConfig,
8055
8419
  createVuePomGeneratorPlugins as vuePomGenerator
8056
8420
  };