@immense/vue-pom-generator 1.0.52 → 1.0.54

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 (51) hide show
  1. package/README.md +71 -27
  2. package/RELEASE_NOTES.md +38 -27
  3. package/class-generation/base-page.ts +18 -6
  4. package/class-generation/callout.ts +229 -679
  5. package/class-generation/floating-ui-callout.ts +857 -0
  6. package/class-generation/index.ts +24 -15
  7. package/class-generation/pointer.ts +152 -109
  8. package/dist/class-generation/base-page.d.ts +11 -5
  9. package/dist/class-generation/base-page.d.ts.map +1 -1
  10. package/dist/class-generation/callout.d.ts +44 -1
  11. package/dist/class-generation/callout.d.ts.map +1 -1
  12. package/dist/class-generation/floating-ui-callout.d.ts +4 -0
  13. package/dist/class-generation/floating-ui-callout.d.ts.map +1 -0
  14. package/dist/class-generation/index.d.ts +3 -2
  15. package/dist/class-generation/index.d.ts.map +1 -1
  16. package/dist/class-generation/pointer.d.ts +24 -5
  17. package/dist/class-generation/pointer.d.ts.map +1 -1
  18. package/dist/index.cjs +783 -231
  19. package/dist/index.cjs.map +1 -1
  20. package/dist/index.d.ts +3 -2
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.mjs +783 -231
  23. package/dist/index.mjs.map +1 -1
  24. package/dist/plugin/create-vue-pom-generator-plugins.d.ts +2 -2
  25. package/dist/plugin/create-vue-pom-generator-plugins.d.ts.map +1 -1
  26. package/dist/plugin/nuxt-discovery.d.ts +47 -0
  27. package/dist/plugin/nuxt-discovery.d.ts.map +1 -0
  28. package/dist/plugin/path-utils.d.ts +4 -5
  29. package/dist/plugin/path-utils.d.ts.map +1 -1
  30. package/dist/plugin/support/build-plugin.d.ts +6 -3
  31. package/dist/plugin/support/build-plugin.d.ts.map +1 -1
  32. package/dist/plugin/support/dev-plugin.d.ts +6 -3
  33. package/dist/plugin/support/dev-plugin.d.ts.map +1 -1
  34. package/dist/plugin/support/virtual-modules.d.ts +2 -2
  35. package/dist/plugin/support/virtual-modules.d.ts.map +1 -1
  36. package/dist/plugin/support-plugins.d.ts +5 -2
  37. package/dist/plugin/support-plugins.d.ts.map +1 -1
  38. package/dist/plugin/types.d.ts +35 -19
  39. package/dist/plugin/types.d.ts.map +1 -1
  40. package/dist/plugin/vue-plugin.d.ts +1 -1
  41. package/dist/plugin/vue-plugin.d.ts.map +1 -1
  42. package/dist/router-introspection.d.ts +4 -2
  43. package/dist/router-introspection.d.ts.map +1 -1
  44. package/dist/tests/nuxt-discovery.test.d.ts +2 -0
  45. package/dist/tests/nuxt-discovery.test.d.ts.map +1 -0
  46. package/dist/vite.config.d.ts.map +1 -1
  47. package/package.json +9 -13
  48. package/dist/plugin/support/generation-metrics.d.ts +0 -10
  49. package/dist/plugin/support/generation-metrics.d.ts.map +0 -1
  50. package/dist/tests/generation-metrics.test.d.ts +0 -2
  51. package/dist/tests/generation-metrics.test.d.ts.map +0 -1
package/dist/index.mjs CHANGED
@@ -1,17 +1,16 @@
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";
8
9
  import { parseExpression, parse } from "@babel/parser";
9
10
  import { NodeTypes, stringifyExpression, ConstantTypes, createSimpleExpression, ElementTypes } from "@vue/compiler-core";
10
11
  import { Project, QuoteKind, NewLineKind, IndentationText, StructureKind, CodeBlockWriter, VariableDeclarationKind } from "ts-morph";
11
- import { JSDOM } from "jsdom";
12
12
  import { isArrayExpression, isStringLiteral, isTemplateLiteral, isAssignmentExpression, isIdentifier, isMemberExpression, isCallExpression, isExpressionStatement, isArrowFunctionExpression, isOptionalMemberExpression, isObjectExpression, isFile, isBlockStatement, isOptionalCallExpression, isLogicalExpression, isConditionalExpression, isSequenceExpression, isAssignmentPattern, isRestElement, isObjectPattern, isObjectProperty, isProgram, VISITOR_KEYS, isBooleanLiteral, isNumericLiteral, isNullLiteral } from "@babel/types";
13
13
  import { performance } from "node:perf_hooks";
14
- import virtualImport from "vite-plugin-virtual";
15
14
  import vue from "@vitejs/plugin-vue";
16
15
  const VUE_POM_GENERATOR_LOG_PREFIX = "[vue-pom-generator]";
17
16
  function normalize(message) {
@@ -63,6 +62,133 @@ function createLogger(options) {
63
62
  }
64
63
  };
65
64
  }
65
+ const requireFromModule = createRequire(import.meta.url);
66
+ function toUniqueResolvedPaths(paths) {
67
+ return Array.from(new Set(paths.map((value) => path.resolve(value))));
68
+ }
69
+ function resolveNuxtAlias(value, context) {
70
+ const aliases = [
71
+ ["~~", context.rootDir],
72
+ ["@@", context.rootDir],
73
+ ["~/", `${context.srcDir}${path.sep}`],
74
+ ["@/", `${context.srcDir}${path.sep}`],
75
+ ["~", context.srcDir],
76
+ ["@", context.srcDir]
77
+ ];
78
+ for (const [alias, replacement] of Object.entries(context.alias ?? {})) {
79
+ aliases.push([alias, replacement]);
80
+ }
81
+ aliases.sort((a, b) => b[0].length - a[0].length);
82
+ for (const [alias, replacement] of aliases) {
83
+ if (value === alias)
84
+ return replacement;
85
+ if (value.startsWith(`${alias}/`) || value.startsWith(`${alias}${path.sep}`)) {
86
+ const suffix = value.slice(alias.length + 1);
87
+ return path.resolve(replacement, suffix);
88
+ }
89
+ }
90
+ return value;
91
+ }
92
+ function resolveNuxtPath(value, baseDir, context) {
93
+ const resolvedAlias = resolveNuxtAlias(value, context);
94
+ return path.isAbsolute(resolvedAlias) ? path.resolve(resolvedAlias) : path.resolve(baseDir, resolvedAlias);
95
+ }
96
+ function normalizeNuxtComponentDirs(value, baseDir, context) {
97
+ if (Array.isArray(value)) {
98
+ return value.flatMap((entry) => normalizeNuxtComponentDirs(entry, baseDir, context));
99
+ }
100
+ if (value === false) {
101
+ return [];
102
+ }
103
+ if (value === true || value === void 0) {
104
+ return [
105
+ path.resolve(baseDir, "components/islands"),
106
+ path.resolve(baseDir, "components/global"),
107
+ path.resolve(baseDir, "components")
108
+ ];
109
+ }
110
+ if (typeof value === "string") {
111
+ return [resolveNuxtPath(value, baseDir, context)];
112
+ }
113
+ if (!value || typeof value !== "object") {
114
+ return [];
115
+ }
116
+ if (typeof value === "object" && value && "path" in value && typeof value.path === "string") {
117
+ return [resolveNuxtPath(value.path, baseDir, context)];
118
+ }
119
+ if (typeof value !== "object" || !value || !("dirs" in value)) {
120
+ return [];
121
+ }
122
+ return normalizeNuxtComponentDirs(value.dirs, baseDir, context);
123
+ }
124
+ function resolveNuxtProjectDiscovery(nuxtOptions, getLayerDirectories, cwd = process.cwd()) {
125
+ const rootDir = path.resolve(nuxtOptions.rootDir ?? cwd);
126
+ const srcDir = path.resolve(nuxtOptions.srcDir ?? rootDir);
127
+ const fallbackLayer = {
128
+ cwd: rootDir,
129
+ config: {
130
+ rootDir,
131
+ srcDir,
132
+ dir: nuxtOptions.dir,
133
+ components: nuxtOptions.components,
134
+ alias: nuxtOptions.alias
135
+ }
136
+ };
137
+ const layers = nuxtOptions._layers?.length ? nuxtOptions._layers : [fallbackLayer];
138
+ const normalizedNuxtOptions = {
139
+ ...nuxtOptions,
140
+ _layers: layers
141
+ };
142
+ const layerDirectories = getLayerDirectories({ options: normalizedNuxtOptions });
143
+ const pageDirs = layerDirectories.map((layer) => layer.appPages);
144
+ const layoutDirs = layerDirectories.map((layer) => layer.appLayouts);
145
+ const componentDirs = [];
146
+ for (const [index, layer] of layers.entries()) {
147
+ const layerDirectory = layerDirectories[index];
148
+ const layerRootDir = path.resolve(layerDirectory?.root ?? layer.config?.rootDir ?? layer.cwd ?? rootDir);
149
+ const layerSrcDir = path.resolve(layerDirectory?.app ?? layer.config?.srcDir ?? layer.cwd ?? srcDir);
150
+ const context = {
151
+ rootDir: layerRootDir,
152
+ srcDir: layerSrcDir,
153
+ alias: {
154
+ ...nuxtOptions.alias ?? {},
155
+ ...layer.config?.alias ?? {}
156
+ }
157
+ };
158
+ componentDirs.push(...normalizeNuxtComponentDirs(layer.config?.components, layerSrcDir, context));
159
+ }
160
+ const uniquePageDirs = toUniqueResolvedPaths(pageDirs);
161
+ const uniqueLayoutDirs = toUniqueResolvedPaths(layoutDirs);
162
+ const uniqueComponentDirs = toUniqueResolvedPaths(componentDirs);
163
+ return {
164
+ rootDir,
165
+ srcDir,
166
+ pageDirs: uniquePageDirs,
167
+ layoutDirs: uniqueLayoutDirs,
168
+ componentDirs: uniqueComponentDirs,
169
+ wrapperSearchRoots: []
170
+ };
171
+ }
172
+ async function loadNuxtProjectDiscovery(cwd = process.cwd()) {
173
+ let loadNuxtConfig;
174
+ let getLayerDirectories;
175
+ try {
176
+ const nuxtKitEntry = requireFromModule.resolve("@nuxt/kit");
177
+ ({ loadNuxtConfig, getLayerDirectories } = await import(pathToFileURL(nuxtKitEntry).href));
178
+ } catch (error) {
179
+ throw new TypeError(
180
+ `[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)}`
181
+ );
182
+ }
183
+ if (typeof loadNuxtConfig !== "function") {
184
+ throw new TypeError("[vue-pom-generator] Nuxt mode requires @nuxt/kit.loadNuxtConfig().");
185
+ }
186
+ if (typeof getLayerDirectories !== "function") {
187
+ throw new TypeError("[vue-pom-generator] Nuxt mode requires @nuxt/kit.getLayerDirectories().");
188
+ }
189
+ const nuxtOptions = await loadNuxtConfig({ cwd });
190
+ return resolveNuxtProjectDiscovery(nuxtOptions, getLayerDirectories, cwd);
191
+ }
66
192
  function createTypeScriptProject() {
67
193
  return new Project({
68
194
  useInMemoryFileSystem: true,
@@ -2755,27 +2881,24 @@ function safeRealpath(value) {
2755
2881
  const resolvedParent = safeRealpath(parent);
2756
2882
  return resolvedParent === parent ? value : path.join(resolvedParent, path.basename(value));
2757
2883
  }
2884
+ function isPathWithinDir(filePathAbs, dirPathAbs) {
2885
+ const fileAbs = path.resolve(filePathAbs);
2886
+ const dirAbs = path.resolve(dirPathAbs);
2887
+ const rel = path.relative(dirAbs, fileAbs);
2888
+ if (!rel)
2889
+ return true;
2890
+ return !rel.startsWith("..") && !path.isAbsolute(rel);
2891
+ }
2758
2892
  function resolveComponentNameFromPath(options) {
2759
- const { projectRoot, viewsDirAbs, scanDirs, extraRoots = [] } = options;
2893
+ const { projectRoot, viewsDirAbs, sourceDirs, extraRoots = [] } = options;
2760
2894
  const cleanFilename = options.filename.includes("?") ? options.filename.substring(0, options.filename.indexOf("?")) : options.filename;
2761
2895
  const absFilename = path.isAbsolute(cleanFilename) ? cleanFilename : path.resolve(projectRoot, cleanFilename);
2762
2896
  const normalizedAbsFilename = path.normalize(safeRealpath(absFilename));
2763
2897
  const rootBases = [projectRoot, ...extraRoots.filter((r) => r !== projectRoot)];
2764
- const roots = [viewsDirAbs, ...rootBases.flatMap((base) => scanDirs.map((d) => path.resolve(base, d)))];
2765
- for (const base of rootBases) {
2766
- for (const dir of scanDirs) {
2767
- const absDir = path.resolve(base, dir);
2768
- try {
2769
- const pagesDir = path.join(absDir, "pages");
2770
- if (fs.existsSync(pagesDir))
2771
- roots.push(pagesDir);
2772
- const componentsDir = path.join(absDir, "components");
2773
- if (fs.existsSync(componentsDir))
2774
- roots.push(componentsDir);
2775
- } catch {
2776
- }
2777
- }
2778
- }
2898
+ const roots = [
2899
+ viewsDirAbs,
2900
+ ...sourceDirs.flatMap((dir) => path.isAbsolute(dir) ? [dir] : rootBases.map((base) => path.resolve(base, dir)))
2901
+ ];
2779
2902
  const potentialRoots = Array.from(new Set(roots.map((r) => path.normalize(safeRealpath(r))))).sort((a, b) => b.length - a.length);
2780
2903
  for (const root of potentialRoots) {
2781
2904
  if (normalizedAbsFilename.startsWith(root + path.sep) || normalizedAbsFilename === root) {
@@ -3139,30 +3262,260 @@ function resolveIntrospectedComponentName(componentInfo, componentNaming) {
3139
3262
  filename: componentInfo.filePath,
3140
3263
  projectRoot: componentNaming.projectRoot,
3141
3264
  viewsDirAbs: componentNaming.viewsDirAbs,
3142
- scanDirs: componentNaming.scanDirs,
3265
+ sourceDirs: componentNaming.sourceDirs,
3143
3266
  extraRoots: componentNaming.extraRoots
3144
3267
  });
3145
3268
  }
3146
3269
  return componentInfo.componentName;
3147
3270
  }
3271
+ function hasChildren(parent) {
3272
+ return "children" in parent;
3273
+ }
3274
+ function appendNode(parent, child) {
3275
+ parent.childNodes.push(child);
3276
+ if (hasChildren(parent) && child.nodeType === 1)
3277
+ parent.children.push(child);
3278
+ return child;
3279
+ }
3280
+ function removeNode(parent, child) {
3281
+ const idx = parent.childNodes.indexOf(child);
3282
+ if (idx >= 0)
3283
+ parent.childNodes.splice(idx, 1);
3284
+ if (hasChildren(parent) && child.nodeType === 1) {
3285
+ const childIdx = parent.children.indexOf(child);
3286
+ if (childIdx >= 0)
3287
+ parent.children.splice(childIdx, 1);
3288
+ }
3289
+ return child;
3290
+ }
3291
+ function createMinimalDocument() {
3292
+ const doc = {
3293
+ nodeType: 9,
3294
+ // DOCUMENT_NODE
3295
+ documentElement: null,
3296
+ head: null,
3297
+ body: null,
3298
+ createElement(tag) {
3299
+ const element = {
3300
+ nodeType: 1,
3301
+ tagName: tag.toUpperCase(),
3302
+ childNodes: [],
3303
+ children: [],
3304
+ style: {},
3305
+ dataset: {},
3306
+ setAttribute() {
3307
+ },
3308
+ getAttribute() {
3309
+ return null;
3310
+ },
3311
+ removeAttribute() {
3312
+ },
3313
+ addEventListener() {
3314
+ },
3315
+ removeEventListener() {
3316
+ },
3317
+ appendChild(child) {
3318
+ return appendNode(this, child);
3319
+ },
3320
+ removeChild(child) {
3321
+ return removeNode(this, child);
3322
+ },
3323
+ insertBefore(child) {
3324
+ return appendNode(this, child);
3325
+ },
3326
+ querySelector() {
3327
+ return null;
3328
+ },
3329
+ querySelectorAll() {
3330
+ return [];
3331
+ },
3332
+ contains() {
3333
+ return false;
3334
+ },
3335
+ matches() {
3336
+ return false;
3337
+ },
3338
+ closest() {
3339
+ return null;
3340
+ },
3341
+ getBoundingClientRect() {
3342
+ return { top: 0, left: 0, right: 0, bottom: 0, width: 0, height: 0, x: 0, y: 0, toJSON() {
3343
+ return {};
3344
+ } };
3345
+ },
3346
+ cloneNode() {
3347
+ return this;
3348
+ }
3349
+ };
3350
+ return element;
3351
+ },
3352
+ createTextNode(text) {
3353
+ return { nodeType: 3, textContent: text };
3354
+ },
3355
+ createComment(text) {
3356
+ return { nodeType: 8, textContent: text };
3357
+ },
3358
+ createDocumentFragment() {
3359
+ return {
3360
+ nodeType: 11,
3361
+ childNodes: [],
3362
+ appendChild(child) {
3363
+ return appendNode(this, child);
3364
+ }
3365
+ };
3366
+ },
3367
+ getElementById() {
3368
+ return null;
3369
+ },
3370
+ querySelector() {
3371
+ return null;
3372
+ },
3373
+ querySelectorAll() {
3374
+ return [];
3375
+ },
3376
+ addEventListener() {
3377
+ },
3378
+ removeEventListener() {
3379
+ },
3380
+ createEvent() {
3381
+ return {
3382
+ initEvent() {
3383
+ }
3384
+ };
3385
+ },
3386
+ queryCommandSupported() {
3387
+ return false;
3388
+ }
3389
+ };
3390
+ const html = doc.createElement("html");
3391
+ const head = doc.createElement("head");
3392
+ const body = doc.createElement("body");
3393
+ const app = doc.createElement("div");
3394
+ app.id = "app";
3395
+ html.appendChild(head);
3396
+ html.appendChild(body);
3397
+ body.appendChild(app);
3398
+ doc.documentElement = html;
3399
+ doc.head = head;
3400
+ doc.body = body;
3401
+ return Object.assign({}, doc);
3402
+ }
3403
+ function createMinimalLocation() {
3404
+ const url = new URL("https://example.test/");
3405
+ return {
3406
+ get href() {
3407
+ return url.href;
3408
+ },
3409
+ set href(v) {
3410
+ try {
3411
+ Object.assign(url, new URL(v));
3412
+ } catch {
3413
+ }
3414
+ },
3415
+ get origin() {
3416
+ return url.origin;
3417
+ },
3418
+ get protocol() {
3419
+ return url.protocol;
3420
+ },
3421
+ get host() {
3422
+ return url.host;
3423
+ },
3424
+ get hostname() {
3425
+ return url.hostname;
3426
+ },
3427
+ get port() {
3428
+ return url.port;
3429
+ },
3430
+ get pathname() {
3431
+ return url.pathname;
3432
+ },
3433
+ set pathname(v) {
3434
+ url.pathname = v;
3435
+ },
3436
+ get search() {
3437
+ return url.search;
3438
+ },
3439
+ set search(v) {
3440
+ url.search = v;
3441
+ },
3442
+ get hash() {
3443
+ return url.hash;
3444
+ },
3445
+ set hash(v) {
3446
+ url.hash = v;
3447
+ },
3448
+ assign() {
3449
+ },
3450
+ reload() {
3451
+ },
3452
+ replace() {
3453
+ },
3454
+ toString() {
3455
+ return url.href;
3456
+ },
3457
+ ancestorOrigins: { length: 0, contains: () => false, item: () => null, [Symbol.iterator]: [][Symbol.iterator] }
3458
+ };
3459
+ }
3148
3460
  async function ensureDomShim() {
3149
- const domShimHtml = "<!doctype html><html><head></head><body><div id='app'></div></body></html>";
3150
- const domShimUrl = "https://example.test/";
3151
3461
  const g = globalThis;
3152
3462
  if (typeof document !== "undefined" && typeof window !== "undefined")
3153
3463
  return;
3154
- const dom = new JSDOM(domShimHtml, { url: domShimUrl });
3155
- g.window = dom.window;
3156
- g.document = dom.window.document;
3157
- g.location = dom.window.location;
3464
+ const minimalDoc = createMinimalDocument();
3465
+ const minimalLocation = createMinimalLocation();
3466
+ const win = {
3467
+ document: minimalDoc,
3468
+ location: minimalLocation,
3469
+ navigator: { userAgent: "node" },
3470
+ history: { pushState() {
3471
+ }, replaceState() {
3472
+ }, go() {
3473
+ }, back() {
3474
+ }, forward() {
3475
+ }, state: null, length: 0, scrollRestoration: "auto" },
3476
+ addEventListener() {
3477
+ },
3478
+ removeEventListener() {
3479
+ },
3480
+ dispatchEvent() {
3481
+ return true;
3482
+ },
3483
+ getComputedStyle() {
3484
+ return {};
3485
+ },
3486
+ scrollTo() {
3487
+ },
3488
+ scroll() {
3489
+ },
3490
+ scrollBy() {
3491
+ },
3492
+ matchMedia() {
3493
+ return { matches: false, media: "", onchange: null, addListener() {
3494
+ }, removeListener() {
3495
+ }, addEventListener() {
3496
+ }, removeEventListener() {
3497
+ }, dispatchEvent() {
3498
+ return true;
3499
+ } };
3500
+ },
3501
+ requestAnimationFrame: (cb) => setTimeout(() => cb(Date.now()), 16),
3502
+ cancelAnimationFrame: (id) => clearTimeout(id),
3503
+ setTimeout,
3504
+ clearTimeout,
3505
+ setInterval,
3506
+ clearInterval,
3507
+ queueMicrotask,
3508
+ performance: globalThis.performance
3509
+ };
3510
+ g.window = win;
3511
+ g.document = minimalDoc;
3512
+ g.location = minimalLocation;
3158
3513
  if (!g.self)
3159
- g.self = dom.window;
3514
+ g.self = win;
3160
3515
  if (!g.navigator)
3161
- g.navigator = dom.window.navigator;
3516
+ g.navigator = win.navigator;
3162
3517
  if (!g.history)
3163
- g.history = { pushState() {
3164
- }, replaceState() {
3165
- } };
3518
+ g.history = win.history;
3166
3519
  if (!g.MutationObserver) {
3167
3520
  g.MutationObserver = class {
3168
3521
  disconnect() {
@@ -3200,10 +3553,6 @@ async function ensureDomShim() {
3200
3553
  if (!g.requestIdleCallback) {
3201
3554
  g.requestIdleCallback = (cb) => setTimeout(() => cb({ didTimeout: false, timeRemaining: () => 0 }), 1);
3202
3555
  }
3203
- const doc = g.document;
3204
- if (doc && typeof doc.queryCommandSupported !== "function") {
3205
- doc.queryCommandSupported = () => false;
3206
- }
3207
3556
  if (!g.localStorage || !g.sessionStorage) {
3208
3557
  const storageFactory = () => {
3209
3558
  const store = /* @__PURE__ */ new Map();
@@ -3229,91 +3578,100 @@ async function ensureDomShim() {
3229
3578
  if (!g.sessionStorage)
3230
3579
  g.sessionStorage = storageFactory();
3231
3580
  }
3232
- const names = Object.getOwnPropertyNames(dom.window);
3233
- const shouldCopyGlobal = (name) => {
3234
- if (name === "Node" || name === "Element" || name === "Document" || name === "Event" || name === "EventTarget")
3235
- return true;
3236
- if (name.endsWith("Event"))
3237
- return true;
3238
- if (name.startsWith("HTML") && name.endsWith("Element"))
3239
- return true;
3240
- if (name.startsWith("SVG") && name.endsWith("Element"))
3241
- return true;
3242
- return false;
3243
- };
3244
- for (const name of names) {
3245
- if (!shouldCopyGlobal(name) || g[name])
3246
- continue;
3247
- const value = Reflect.get(dom.window, name);
3248
- if (value)
3249
- g[name] = value;
3250
- }
3251
3581
  if (!g.requestAnimationFrame)
3252
3582
  g.requestAnimationFrame = (cb) => setTimeout(() => cb(Date.now()), 16);
3253
3583
  }
3254
- async function introspectNuxtPages(projectRoot) {
3255
- const possiblePagesDirs = ["app/pages", "pages"];
3256
- let pagesDir = "";
3257
- for (const dir of possiblePagesDirs) {
3258
- const abs = path.resolve(projectRoot, dir);
3259
- if (fs.existsSync(abs) && fs.statSync(abs).isDirectory()) {
3260
- pagesDir = abs;
3584
+ function unwrapNuxtPageSegment(segment, prefix, suffix) {
3585
+ if (!segment.startsWith(prefix) || !segment.endsWith(suffix))
3586
+ return null;
3587
+ const value = segment.slice(prefix.length, segment.length - suffix.length);
3588
+ return value.length > 0 ? value : null;
3589
+ }
3590
+ function resolveNuxtPageSegment(segment) {
3591
+ if (segment === "index") {
3592
+ return { pathPart: "", params: [] };
3593
+ }
3594
+ const optionalParamName = unwrapNuxtPageSegment(segment, "[[", "]]");
3595
+ if (optionalParamName) {
3596
+ return {
3597
+ pathPart: `:${optionalParamName}?`,
3598
+ params: [{ name: optionalParamName, optional: true }]
3599
+ };
3600
+ }
3601
+ const catchAllParamName = unwrapNuxtPageSegment(segment, "[...", "]");
3602
+ if (catchAllParamName) {
3603
+ return {
3604
+ pathPart: `:${catchAllParamName}(.*)*`,
3605
+ params: [{ name: catchAllParamName, optional: false }]
3606
+ };
3607
+ }
3608
+ const requiredParamName = unwrapNuxtPageSegment(segment, "[", "]");
3609
+ if (requiredParamName) {
3610
+ return {
3611
+ pathPart: `:${requiredParamName}`,
3612
+ params: [{ name: requiredParamName, optional: false }]
3613
+ };
3614
+ }
3615
+ return { pathPart: segment, params: [] };
3616
+ }
3617
+ function toPathSegments(value) {
3618
+ const segments = [];
3619
+ let current = path.normalize(value);
3620
+ while (current && current !== "." && current !== path.sep) {
3621
+ const parsed = path.parse(current);
3622
+ if (!parsed.base || parsed.base === ".")
3261
3623
  break;
3262
- }
3624
+ segments.unshift(parsed.base);
3625
+ if (!parsed.dir || parsed.dir === "." || parsed.dir === current)
3626
+ break;
3627
+ current = parsed.dir;
3263
3628
  }
3264
- if (!pagesDir) {
3629
+ return segments;
3630
+ }
3631
+ async function introspectNuxtPages(projectRoot, options = {}) {
3632
+ const possiblePageDirs = options.pageDirs?.length ? options.pageDirs : ["app/pages", "pages"].map((dir) => path.resolve(projectRoot, dir));
3633
+ const pageDirs = possiblePageDirs.map((dir) => path.resolve(projectRoot, dir)).filter((dir) => {
3634
+ try {
3635
+ return fs.existsSync(dir) && fs.statSync(dir).isDirectory();
3636
+ } catch {
3637
+ return false;
3638
+ }
3639
+ });
3640
+ if (!pageDirs.length) {
3265
3641
  debugLog(`[router-introspection][nuxt] Could not find pages directory in ${projectRoot}`);
3266
3642
  return { routeNameMap: /* @__PURE__ */ new Map(), routePathMap: /* @__PURE__ */ new Map(), routeMetaEntries: [] };
3267
3643
  }
3644
+ const routePathMap = /* @__PURE__ */ new Map();
3268
3645
  const routeMetaEntries = [];
3269
- const walk = (dir, baseRoute) => {
3646
+ const walk = (pagesDir, dir) => {
3270
3647
  const files = fs.readdirSync(dir);
3271
3648
  for (const file of files) {
3272
3649
  const fullPath = path.join(dir, file);
3273
3650
  const stat = fs.statSync(fullPath);
3274
3651
  if (stat.isDirectory()) {
3275
- walk(fullPath, `${baseRoute}/${file}`);
3652
+ walk(pagesDir, fullPath);
3276
3653
  continue;
3277
3654
  }
3278
3655
  if (!file.endsWith(".vue"))
3279
3656
  continue;
3280
- const componentName = file.slice(0, -4);
3281
- if (componentName === "index" && baseRoute === "") {
3282
- routeMetaEntries.push({
3283
- componentName: "index",
3284
- pathTemplate: "/",
3285
- params: [],
3286
- query: []
3287
- });
3288
- continue;
3289
- }
3290
- let routePath = componentName === "index" ? baseRoute : `${baseRoute}/${componentName}`;
3291
- if (!routePath.startsWith("/"))
3292
- routePath = `/${routePath}`;
3657
+ const componentName = resolveComponentNameFromPath({
3658
+ filename: fullPath,
3659
+ projectRoot,
3660
+ viewsDirAbs: pagesDir,
3661
+ sourceDirs: [pagesDir],
3662
+ extraRoots: [process.cwd()]
3663
+ });
3664
+ const relativePath = path.relative(pagesDir, fullPath);
3665
+ const parsed = path.parse(relativePath);
3666
+ const routeSegments = toPathSegments(path.join(parsed.dir, parsed.name));
3293
3667
  const params = [];
3294
- let pathTemplate = "";
3295
- for (let i = 0; i < routePath.length; i++) {
3296
- const ch = routePath[i];
3297
- if (ch !== "[") {
3298
- pathTemplate += ch;
3299
- continue;
3300
- }
3301
- let name = "";
3302
- i++;
3303
- while (i < routePath.length) {
3304
- const c = routePath[i];
3305
- if (c === "]")
3306
- break;
3307
- name += c;
3308
- i++;
3309
- }
3310
- if (name) {
3311
- params.push({ name, optional: false });
3312
- pathTemplate += `:${name}`;
3313
- } else {
3314
- pathTemplate += "[]";
3315
- }
3316
- }
3668
+ const pathParts = routeSegments.flatMap((segment) => {
3669
+ const resolution = resolveNuxtPageSegment(segment);
3670
+ params.push(...resolution.params);
3671
+ return resolution.pathPart ? [resolution.pathPart] : [];
3672
+ });
3673
+ const pathTemplate = pathParts.length ? `/${pathParts.join("/")}` : "/";
3674
+ routePathMap.set(pathTemplate, componentName);
3317
3675
  routeMetaEntries.push({
3318
3676
  componentName,
3319
3677
  pathTemplate,
@@ -3322,10 +3680,12 @@ async function introspectNuxtPages(projectRoot) {
3322
3680
  });
3323
3681
  }
3324
3682
  };
3325
- walk(pagesDir, "");
3683
+ for (const pageDir of pageDirs) {
3684
+ walk(pageDir, pageDir);
3685
+ }
3326
3686
  return {
3327
3687
  routeNameMap: /* @__PURE__ */ new Map(),
3328
- routePathMap: /* @__PURE__ */ new Map(),
3688
+ routePathMap,
3329
3689
  routeMetaEntries
3330
3690
  };
3331
3691
  }
@@ -3621,15 +3981,20 @@ function resolveVueSourcePath(targetClassName, vueFilesPathMap, projectRoot) {
3621
3981
  }
3622
3982
  async function getRouteMetaByComponent(projectRoot, routerEntry, routerType, options = {}) {
3623
3983
  const root = projectRoot ?? process.cwd();
3624
- const viewsDir = options.viewsDir ?? "src/views";
3625
- const viewsDirAbs = path.isAbsolute(viewsDir) ? viewsDir : path.resolve(root, viewsDir);
3626
- const scanDirs = options.scanDirs?.length ? options.scanDirs : ["src"];
3984
+ const pageDirs = options.pageDirs?.length ? options.pageDirs : ["src/views"];
3985
+ const pageDirsAbs = pageDirs.map((dir) => path.isAbsolute(dir) ? dir : path.resolve(root, dir));
3986
+ const primaryPageDirAbs = pageDirsAbs[0] ?? path.resolve(root, "src/views");
3987
+ const sourceDirs = [
3988
+ ...pageDirs,
3989
+ ...options.componentDirs?.length ? options.componentDirs : ["src/components"],
3990
+ ...options.layoutDirs?.length ? options.layoutDirs : ["src/layouts"]
3991
+ ];
3627
3992
  const extraRoots = process.cwd() !== root ? [process.cwd()] : [];
3628
- const { routeMetaEntries } = routerType === "nuxt" ? await introspectNuxtPages(root) : await parseRouterFileFromCwd(resolveRouterEntry(root, routerEntry), {
3993
+ const { routeMetaEntries } = routerType === "nuxt" ? await introspectNuxtPages(root, { pageDirs: pageDirsAbs }) : await parseRouterFileFromCwd(resolveRouterEntry(root, routerEntry), {
3629
3994
  componentNaming: {
3630
3995
  projectRoot: root,
3631
- viewsDirAbs,
3632
- scanDirs,
3996
+ viewsDirAbs: primaryPageDirAbs,
3997
+ sourceDirs,
3633
3998
  extraRoots
3634
3999
  }
3635
4000
  });
@@ -3829,15 +4194,17 @@ async function generateFiles(componentHierarchyMap, vueFilesPathMap, basePageCla
3829
4194
  vueRouterFluentChaining,
3830
4195
  routerEntry,
3831
4196
  routerType,
3832
- viewsDir,
3833
- scanDirs,
4197
+ pageDirs,
4198
+ componentDirs,
4199
+ layoutDirs,
3834
4200
  routeMetaByComponent: routeMetaByComponentOverride
3835
4201
  } = options;
3836
4202
  const emitLanguages = emitLanguagesOverride?.length ? emitLanguagesOverride : ["ts"];
3837
4203
  const outDir = outDirOverride ?? "./pom";
3838
4204
  const routeMetaByComponent = routeMetaByComponentOverride ?? (vueRouterFluentChaining ? await getRouteMetaByComponent(projectRoot, routerEntry, routerType, {
3839
- viewsDir,
3840
- scanDirs
4205
+ pageDirs,
4206
+ componentDirs,
4207
+ layoutDirs
3841
4208
  }) : void 0);
3842
4209
  const generatedFilePaths = [];
3843
4210
  const writeGeneratedFile = (file) => {
@@ -4182,7 +4549,7 @@ function generateAggregatedCSharpFiles(componentHierarchyMap, outDir, options =
4182
4549
  " }",
4183
4550
  "",
4184
4551
  " // Minimal vue-select helper mirroring the TS BasePage.selectVSelectByTestId behavior.",
4185
- " // Note: annotationText is currently a no-op in C# output (we don't render a cursor overlay).",
4552
+ " // Note: annotationText is currently a no-op in C# output (we don't render a pointer overlay).",
4186
4553
  " protected async Task SelectVSelectByTestIdAsync(string testId, string value, int timeOut = 500)",
4187
4554
  " {",
4188
4555
  " var root = LocatorByTestId(testId);",
@@ -6142,7 +6509,7 @@ function createTestIdTransform(componentName, componentHierarchyMap, nativeWrapp
6142
6509
  const existingIdBehavior = options.existingIdBehavior ?? "preserve";
6143
6510
  const testIdAttribute = (options.testIdAttribute || "data-testid").trim() || "data-testid";
6144
6511
  const nameCollisionBehavior = options.nameCollisionBehavior ?? "suffix";
6145
- const missingSemanticNameBehavior = options.missingSemanticNameBehavior ?? "ignore";
6512
+ const missingSemanticNameBehavior = options.missingSemanticNameBehavior ?? "error";
6146
6513
  const warn = options.warn;
6147
6514
  const vueFilesPathMap = options.vueFilesPathMap;
6148
6515
  const wrapperSearchRoots = options.wrapperSearchRoots ?? [];
@@ -6626,8 +6993,11 @@ function createBuildProcessorPlugin(options) {
6626
6993
  const {
6627
6994
  componentHierarchyMap,
6628
6995
  vueFilesPathMap,
6629
- viewsDir,
6630
- scanDirs,
6996
+ getPageDirs,
6997
+ getComponentDirs,
6998
+ getLayoutDirs,
6999
+ getViewsDir,
7000
+ getSourceDirs,
6631
7001
  basePageClassPath,
6632
7002
  normalizedBasePagePath,
6633
7003
  outDir,
@@ -6642,13 +7012,13 @@ function createBuildProcessorPlugin(options) {
6642
7012
  customPomImportNameCollisionBehavior,
6643
7013
  testIdAttribute,
6644
7014
  nameCollisionBehavior,
6645
- missingSemanticNameBehavior,
7015
+ missingSemanticNameBehavior = "error",
6646
7016
  existingIdBehavior,
6647
7017
  nativeWrappers,
6648
7018
  excludedComponents,
6649
7019
  getWrapperSearchRoots,
6650
7020
  routerAwarePoms,
6651
- resolvedRouterEntry,
7021
+ getResolvedRouterEntry,
6652
7022
  routerType,
6653
7023
  routerModuleShims,
6654
7024
  loggerRef
@@ -6658,7 +7028,8 @@ function createBuildProcessorPlugin(options) {
6658
7028
  interactiveComponentCount: 0,
6659
7029
  dataTestIdCount: 0
6660
7030
  };
6661
- const getViewsDirAbs = () => path.isAbsolute(viewsDir) ? viewsDir : path.resolve(projectRootRef.current, viewsDir);
7031
+ const getViewsDirAbs = () => path.isAbsolute(getViewsDir()) ? getViewsDir() : path.resolve(projectRootRef.current, getViewsDir());
7032
+ const getPageDirsAbs = () => getPageDirs().map((dir) => path.isAbsolute(dir) ? dir : path.resolve(projectRootRef.current, dir));
6662
7033
  const getScriptInfo = (source, filename) => {
6663
7034
  try {
6664
7035
  const { descriptor } = parse$1(source, { filename });
@@ -6698,7 +7069,7 @@ function createBuildProcessorPlugin(options) {
6698
7069
  return out;
6699
7070
  };
6700
7071
  let supplemented = 0;
6701
- for (const dir of scanDirs) {
7072
+ for (const dir of getSourceDirs()) {
6702
7073
  const absDir = path.resolve(projectRootRef.current, dir);
6703
7074
  if (!fs.existsSync(absDir))
6704
7075
  continue;
@@ -6708,7 +7079,7 @@ function createBuildProcessorPlugin(options) {
6708
7079
  filename: absolutePath,
6709
7080
  projectRoot: projectRootRef.current,
6710
7081
  viewsDirAbs: getViewsDirAbs(),
6711
- scanDirs,
7082
+ sourceDirs: getSourceDirs(),
6712
7083
  extraRoots: [process.cwd()]
6713
7084
  });
6714
7085
  if (componentHierarchyMap.has(componentName))
@@ -6783,16 +7154,17 @@ function createBuildProcessorPlugin(options) {
6783
7154
  }
6784
7155
  let result;
6785
7156
  if (routerType === "nuxt") {
6786
- result = await introspectNuxtPages(projectRootRef.current);
7157
+ result = await introspectNuxtPages(projectRootRef.current, { pageDirs: getPageDirsAbs() });
6787
7158
  } else {
7159
+ const resolvedRouterEntry = getResolvedRouterEntry();
6788
7160
  if (!resolvedRouterEntry)
6789
7161
  throw new Error("[vue-pom-generator] router.entry is required when router introspection is enabled.");
6790
7162
  result = await parseRouterFileFromCwd(resolvedRouterEntry, {
6791
7163
  moduleShims: routerModuleShims,
6792
7164
  componentNaming: {
6793
7165
  projectRoot: projectRootRef.current,
6794
- viewsDirAbs: path.isAbsolute(viewsDir) ? viewsDir : path.resolve(projectRootRef.current, viewsDir),
6795
- scanDirs
7166
+ viewsDirAbs: getViewsDirAbs(),
7167
+ sourceDirs: getSourceDirs()
6796
7168
  }
6797
7169
  });
6798
7170
  }
@@ -6826,6 +7198,10 @@ function createBuildProcessorPlugin(options) {
6826
7198
  this.error(`callout.ts not found at ${calloutPath}. Ensure it is included in the build.`);
6827
7199
  }
6828
7200
  this.addWatchFile(calloutPath);
7201
+ const floatingUiCalloutPath = path.resolve(path.dirname(basePageClassPath), "floating-ui-callout.ts");
7202
+ if (fs.existsSync(floatingUiCalloutPath)) {
7203
+ this.addWatchFile(floatingUiCalloutPath);
7204
+ }
6829
7205
  },
6830
7206
  async buildEnd(error) {
6831
7207
  if (error) {
@@ -6852,10 +7228,11 @@ function createBuildProcessorPlugin(options) {
6852
7228
  customPomImportNameCollisionBehavior,
6853
7229
  testIdAttribute,
6854
7230
  vueRouterFluentChaining: routerAwarePoms,
6855
- routerEntry: resolvedRouterEntry,
7231
+ routerEntry: getResolvedRouterEntry(),
6856
7232
  routerType,
6857
- viewsDir,
6858
- scanDirs
7233
+ pageDirs: getPageDirs(),
7234
+ componentDirs: getComponentDirs(),
7235
+ layoutDirs: getLayoutDirs()
6859
7236
  });
6860
7237
  lastGeneratedMetrics = metrics;
6861
7238
  loggerRef.current.info(`generated POMs (${metrics.entryCount} entries, ${metrics.interactiveComponentCount} interactive components, ${metrics.dataTestIdCount} selectors)`);
@@ -6869,8 +7246,11 @@ function createDevProcessorPlugin(options) {
6869
7246
  const {
6870
7247
  nativeWrappers,
6871
7248
  excludedComponents,
6872
- viewsDir,
6873
- scanDirs,
7249
+ getPageDirs,
7250
+ getComponentDirs,
7251
+ getLayoutDirs,
7252
+ getViewsDir,
7253
+ getSourceDirs,
6874
7254
  getWrapperSearchRoots,
6875
7255
  projectRootRef,
6876
7256
  normalizedBasePagePath,
@@ -6885,16 +7265,33 @@ function createDevProcessorPlugin(options) {
6885
7265
  customPomImportAliases,
6886
7266
  customPomImportNameCollisionBehavior,
6887
7267
  nameCollisionBehavior = "suffix",
6888
- missingSemanticNameBehavior,
7268
+ missingSemanticNameBehavior = "error",
6889
7269
  existingIdBehavior,
6890
7270
  testIdAttribute,
6891
7271
  routerAwarePoms,
6892
- resolvedRouterEntry,
7272
+ getResolvedRouterEntry,
6893
7273
  routerType,
6894
7274
  routerModuleShims,
6895
7275
  loggerRef
6896
7276
  } = options;
6897
7277
  let scheduleVueFileRegen = null;
7278
+ const getProjectRootCandidates = () => Array.from(/* @__PURE__ */ new Set([
7279
+ path.resolve(projectRootRef.current),
7280
+ path.resolve(process.cwd())
7281
+ ]));
7282
+ const resolveProjectPath = (maybePath) => {
7283
+ if (path.isAbsolute(maybePath))
7284
+ return maybePath;
7285
+ const candidates = getProjectRootCandidates().map((root) => path.resolve(root, maybePath));
7286
+ return candidates.find((candidate) => fs.existsSync(candidate)) ?? candidates[0];
7287
+ };
7288
+ const getSourceDirRoots = () => Array.from(new Set(
7289
+ getProjectRootCandidates().flatMap((root) => getSourceDirs().map((dir) => path.resolve(root, dir)))
7290
+ ));
7291
+ const isContainedInScanDirs = (filePath) => {
7292
+ const absolutePath = path.resolve(filePath);
7293
+ return getSourceDirRoots().some((scanDirAbs) => isPathWithinDir(absolutePath, scanDirAbs));
7294
+ };
6898
7295
  return {
6899
7296
  name: "vue-pom-generator-dev",
6900
7297
  apply: "serve",
@@ -6905,16 +7302,13 @@ function createDevProcessorPlugin(options) {
6905
7302
  return;
6906
7303
  if (!ctx.file.endsWith(".vue"))
6907
7304
  return;
6908
- const isContainedInScanDirs = scanDirs.some((dir) => {
6909
- const absDir = path.resolve(projectRootRef.current, dir);
6910
- return ctx.file.startsWith(absDir + path.sep);
6911
- });
6912
- if (!isContainedInScanDirs)
7305
+ if (!isContainedInScanDirs(ctx.file))
6913
7306
  return;
6914
7307
  scheduleVueFileRegen(ctx.file, "hmr");
6915
7308
  },
6916
7309
  async configureServer(server) {
6917
- const getViewsDirAbs = () => path.isAbsolute(viewsDir) ? viewsDir : path.resolve(projectRootRef.current, viewsDir);
7310
+ const getViewsDirAbs = () => resolveProjectPath(getViewsDir());
7311
+ const getPageDirsAbs = () => getPageDirs().map((dir) => resolveProjectPath(dir));
6918
7312
  const routerInitPromise = (async () => {
6919
7313
  if (!routerAwarePoms) {
6920
7314
  setRouteNameToComponentNameMap(/* @__PURE__ */ new Map());
@@ -6923,8 +7317,9 @@ function createDevProcessorPlugin(options) {
6923
7317
  }
6924
7318
  let result;
6925
7319
  if (routerType === "nuxt") {
6926
- result = await introspectNuxtPages(projectRootRef.current);
7320
+ result = await introspectNuxtPages(projectRootRef.current, { pageDirs: getPageDirsAbs() });
6927
7321
  } else {
7322
+ const resolvedRouterEntry = getResolvedRouterEntry();
6928
7323
  if (!resolvedRouterEntry)
6929
7324
  throw new Error("[vue-pom-generator] router.entry is required when router introspection is enabled.");
6930
7325
  result = await parseRouterFileFromCwd(resolvedRouterEntry, {
@@ -6932,7 +7327,7 @@ function createDevProcessorPlugin(options) {
6932
7327
  componentNaming: {
6933
7328
  projectRoot: projectRootRef.current,
6934
7329
  viewsDirAbs: getViewsDirAbs(),
6935
- scanDirs,
7330
+ sourceDirs: getSourceDirs(),
6936
7331
  extraRoots: [process.cwd()]
6937
7332
  }
6938
7333
  });
@@ -7004,6 +7399,19 @@ function createDevProcessorPlugin(options) {
7004
7399
  let snapshotHierarchy = /* @__PURE__ */ new Map();
7005
7400
  let snapshotVuePathMap = /* @__PURE__ */ new Map();
7006
7401
  const filePathToComponentName = /* @__PURE__ */ new Map();
7402
+ const createEmptyComponentDependencies = (absolutePath) => {
7403
+ const viewsDirAbs = path.resolve(getViewsDirAbs());
7404
+ const relToViewsDir = path.relative(viewsDirAbs, absolutePath);
7405
+ const isView = !relToViewsDir.startsWith("..") && !path.isAbsolute(relToViewsDir);
7406
+ return {
7407
+ filePath: absolutePath,
7408
+ childrenComponentSet: /* @__PURE__ */ new Set(),
7409
+ usedComponentSet: /* @__PURE__ */ new Set(),
7410
+ dataTestIdSet: /* @__PURE__ */ new Set(),
7411
+ isView,
7412
+ methodsContent: ""
7413
+ };
7414
+ };
7007
7415
  const getComponentNameForFile = (filePath) => {
7008
7416
  const normalized = path.resolve(filePath);
7009
7417
  const existing = filePathToComponentName.get(normalized);
@@ -7013,7 +7421,7 @@ function createDevProcessorPlugin(options) {
7013
7421
  filename: normalized,
7014
7422
  projectRoot: projectRootRef.current,
7015
7423
  viewsDirAbs: getViewsDirAbs(),
7016
- scanDirs,
7424
+ sourceDirs: getSourceDirs(),
7017
7425
  extraRoots: [process.cwd()]
7018
7426
  });
7019
7427
  filePathToComponentName.set(normalized, name);
@@ -7023,8 +7431,6 @@ function createDevProcessorPlugin(options) {
7023
7431
  const started = performance.now();
7024
7432
  const absolutePath = path.resolve(filePath);
7025
7433
  const componentName = getComponentNameForFile(absolutePath);
7026
- targetVuePathMap.set(componentName, absolutePath);
7027
- targetHierarchy.delete(componentName);
7028
7434
  let sfc = "";
7029
7435
  try {
7030
7436
  sfc = fs.readFileSync(absolutePath, "utf8");
@@ -7032,9 +7438,15 @@ function createDevProcessorPlugin(options) {
7032
7438
  return { componentName, ms: performance.now() - started, compiled: false };
7033
7439
  }
7034
7440
  const template = extractTemplateFromSfc(sfc, absolutePath);
7035
- if (!template.trim())
7441
+ if (!template.trim()) {
7442
+ targetVuePathMap.set(componentName, absolutePath);
7443
+ targetHierarchy.set(componentName, createEmptyComponentDependencies(absolutePath));
7036
7444
  return { componentName, ms: performance.now() - started, compiled: true };
7445
+ }
7037
7446
  const { bindings: bindingMetadata, isScriptSetup } = getScriptInfo(sfc, absolutePath);
7447
+ const provisionalHierarchy = /* @__PURE__ */ new Map();
7448
+ const provisionalVuePathMap = new Map(targetVuePathMap);
7449
+ provisionalVuePathMap.set(componentName, absolutePath);
7038
7450
  compilerDom.compile(template, {
7039
7451
  filename: absolutePath,
7040
7452
  prefixIdentifiers: true,
@@ -7043,7 +7455,7 @@ function createDevProcessorPlugin(options) {
7043
7455
  nodeTransforms: [
7044
7456
  createTestIdTransform(
7045
7457
  componentName,
7046
- targetHierarchy,
7458
+ provisionalHierarchy,
7047
7459
  nativeWrappers,
7048
7460
  excludedComponents,
7049
7461
  getViewsDirAbs(),
@@ -7053,23 +7465,27 @@ function createDevProcessorPlugin(options) {
7053
7465
  missingSemanticNameBehavior,
7054
7466
  testIdAttribute,
7055
7467
  warn: (message) => loggerRef.current.warn(message),
7056
- vueFilesPathMap: targetVuePathMap,
7468
+ vueFilesPathMap: provisionalVuePathMap,
7057
7469
  wrapperSearchRoots: getWrapperSearchRoots()
7058
7470
  }
7059
7471
  )
7060
7472
  ]
7061
7473
  });
7474
+ targetVuePathMap.set(componentName, absolutePath);
7475
+ targetHierarchy.set(
7476
+ componentName,
7477
+ provisionalHierarchy.get(componentName) ?? createEmptyComponentDependencies(absolutePath)
7478
+ );
7062
7479
  return { componentName, ms: performance.now() - started, compiled: true };
7063
7480
  };
7064
- const fullRebuildSnapshotFromFilesystem = () => {
7481
+ const fullRebuildSnapshotFromFilesystem = (logLabel) => {
7065
7482
  const t0 = performance.now();
7066
7483
  const nextHierarchy = /* @__PURE__ */ new Map();
7067
7484
  const nextVuePathMap = /* @__PURE__ */ new Map();
7068
7485
  filePathToComponentName.clear();
7069
7486
  let totalVueFiles = 0;
7070
7487
  let compiledCount = 0;
7071
- for (const dir of scanDirs) {
7072
- const absDir = path.resolve(projectRootRef.current, dir);
7488
+ for (const absDir of getSourceDirRoots()) {
7073
7489
  if (!fs.existsSync(absDir))
7074
7490
  continue;
7075
7491
  const vueFiles = walkFilesRecursive(absDir);
@@ -7083,12 +7499,12 @@ function createDevProcessorPlugin(options) {
7083
7499
  snapshotHierarchy = nextHierarchy;
7084
7500
  snapshotVuePathMap = nextVuePathMap;
7085
7501
  const t1 = performance.now();
7086
- logInfo(`initial scan: found ${totalVueFiles} .vue files in ${scanDirs.join(", ")}`);
7087
- logInfo(`initial compile: ${compiledCount}/${totalVueFiles} files in ${formatMs(t1 - t0)} (components=${snapshotHierarchy.size})`);
7502
+ logInfo(`scan(${logLabel}): found ${totalVueFiles} .vue files in ${getSourceDirs().join(", ")}`);
7503
+ logInfo(`compile(${logLabel}): ${compiledCount}/${totalVueFiles} files in ${formatMs(t1 - t0)} (components=${snapshotHierarchy.size})`);
7088
7504
  };
7089
- const generateAggregatedFromSnapshot = (reason) => {
7505
+ const generateAggregatedFromSnapshot = async (logLabel) => {
7090
7506
  const t0 = performance.now();
7091
- generateFiles(snapshotHierarchy, snapshotVuePathMap, normalizedBasePagePath, {
7507
+ await generateFiles(snapshotHierarchy, snapshotVuePathMap, normalizedBasePagePath, {
7092
7508
  outDir,
7093
7509
  emitLanguages,
7094
7510
  typescriptOutputStructure,
@@ -7099,25 +7515,27 @@ function createDevProcessorPlugin(options) {
7099
7515
  customPomDir,
7100
7516
  customPomImportAliases,
7101
7517
  customPomImportNameCollisionBehavior,
7102
- viewsDir,
7103
- scanDirs,
7518
+ pageDirs: getPageDirs(),
7519
+ componentDirs: getComponentDirs(),
7520
+ layoutDirs: getLayoutDirs(),
7104
7521
  testIdAttribute,
7105
7522
  vueRouterFluentChaining: routerAwarePoms,
7106
- routerEntry: resolvedRouterEntry,
7523
+ routerEntry: getResolvedRouterEntry(),
7107
7524
  routerType
7108
7525
  });
7109
7526
  const t1 = performance.now();
7110
- logInfo(`generate(${reason}): components=${snapshotHierarchy.size} in ${formatMs(t1 - t0)}`);
7527
+ logInfo(`generate(${logLabel}): components=${snapshotHierarchy.size} in ${formatMs(t1 - t0)}`);
7111
7528
  };
7112
7529
  let timer = null;
7113
7530
  let maxWaitTimer = null;
7114
7531
  const pendingChangedVueFiles = /* @__PURE__ */ new Set();
7115
7532
  const pendingDeletedComponents = /* @__PURE__ */ new Set();
7533
+ let regenerationSequence = Promise.resolve();
7116
7534
  const initialBuildPromise = (async () => {
7117
7535
  const t0 = performance.now();
7118
7536
  await routerInitPromise;
7119
- fullRebuildSnapshotFromFilesystem();
7120
- generateAggregatedFromSnapshot("startup");
7537
+ fullRebuildSnapshotFromFilesystem("startup");
7538
+ await generateAggregatedFromSnapshot("startup");
7121
7539
  const t1 = performance.now();
7122
7540
  logInfo(`startup total: ${formatMs(t1 - t0)}`);
7123
7541
  })();
@@ -7145,7 +7563,7 @@ function createDevProcessorPlugin(options) {
7145
7563
  snapshotHierarchy = nextHierarchy;
7146
7564
  snapshotVuePathMap = nextVuePathMap;
7147
7565
  const t1 = performance.now();
7148
- generateAggregatedFromSnapshot(reason);
7566
+ await generateAggregatedFromSnapshot(reason);
7149
7567
  const t2 = performance.now();
7150
7568
  return {
7151
7569
  files,
@@ -7156,7 +7574,12 @@ function createDevProcessorPlugin(options) {
7156
7574
  totalMs: t2 - t0
7157
7575
  };
7158
7576
  };
7159
- const watchedVueGlobs = scanDirs.map((dir) => path.resolve(projectRootRef.current, dir, "**", "*.vue"));
7577
+ const enqueueRegeneration = (reason) => {
7578
+ const currentRun = regenerationSequence.catch(() => void 0).then(() => regenerateFromPending(reason));
7579
+ regenerationSequence = currentRun.then(() => void 0, () => void 0);
7580
+ return currentRun;
7581
+ };
7582
+ const watchedVueGlobs = getSourceDirRoots().map((scanDirAbs) => path.resolve(scanDirAbs, "**", "*.vue"));
7160
7583
  const watchedPluginGlob = path.resolve(projectRootRef.current, "vite-plugins", "vue-pom-generator", "**", "*.ts");
7161
7584
  const runtimeDir = path.dirname(basePageClassPath);
7162
7585
  server.watcher.add([
@@ -7164,7 +7587,8 @@ function createDevProcessorPlugin(options) {
7164
7587
  watchedPluginGlob,
7165
7588
  basePageClassPath,
7166
7589
  path.resolve(runtimeDir, "pointer.ts"),
7167
- path.resolve(runtimeDir, "callout.ts")
7590
+ path.resolve(runtimeDir, "callout.ts"),
7591
+ path.resolve(runtimeDir, "floating-ui-callout.ts")
7168
7592
  ]);
7169
7593
  scheduleVueFileRegenLocal = (filePath, source) => {
7170
7594
  pendingChangedVueFiles.add(filePath);
@@ -7182,7 +7606,7 @@ function createDevProcessorPlugin(options) {
7182
7606
  timer = null;
7183
7607
  }
7184
7608
  maxWaitTimer = null;
7185
- void regenerateFromPending("max-wait").then(({ files, deletedCount, compileMs, preGenerateMs, generateMs, totalMs }) => {
7609
+ void enqueueRegeneration("max-wait").then(({ files, deletedCount, compileMs, preGenerateMs, generateMs, totalMs }) => {
7186
7610
  logInfo(
7187
7611
  `max-wait: files=${files.length} deleted=${deletedCount} compile=${formatMs(compileMs)} wall=${formatMs(preGenerateMs)} gen=${formatMs(generateMs)} total=${formatMs(totalMs)}`
7188
7612
  );
@@ -7206,7 +7630,7 @@ function createDevProcessorPlugin(options) {
7206
7630
  maxWaitTimer = null;
7207
7631
  }
7208
7632
  const reason = pendingChangedVueFiles.size || pendingDeletedComponents.size ? "batched" : "noop";
7209
- void regenerateFromPending(reason).then(({ files, deletedCount, compileMs, preGenerateMs, generateMs, totalMs }) => {
7633
+ void enqueueRegeneration(reason).then(({ files, deletedCount, compileMs, preGenerateMs, generateMs, totalMs }) => {
7210
7634
  if (files.length || deletedCount) {
7211
7635
  logInfo(
7212
7636
  `batched: files=${files.length} deleted=${deletedCount} compile=${formatMs(compileMs)} wall=${formatMs(preGenerateMs)} gen=${formatMs(generateMs)} total=${formatMs(totalMs)}`
@@ -7233,11 +7657,7 @@ function createDevProcessorPlugin(options) {
7233
7657
  return;
7234
7658
  if (!p.endsWith(".vue"))
7235
7659
  return;
7236
- const isContainedInScanDirs = scanDirs.some((dir) => {
7237
- const absDir = path.resolve(projectRootRef.current, dir);
7238
- return p.startsWith(absDir + path.sep);
7239
- });
7240
- if (!isContainedInScanDirs)
7660
+ if (!isContainedInScanDirs(p))
7241
7661
  return;
7242
7662
  void (async () => {
7243
7663
  await initialBuildPromise;
@@ -7250,11 +7670,7 @@ function createDevProcessorPlugin(options) {
7250
7670
  return;
7251
7671
  if (!p.endsWith(".vue"))
7252
7672
  return;
7253
- const isContainedInScanDirs = scanDirs.some((dir) => {
7254
- const absDir = path.resolve(projectRootRef.current, dir);
7255
- return p.startsWith(absDir + path.sep);
7256
- });
7257
- if (!isContainedInScanDirs)
7673
+ if (!isContainedInScanDirs(p))
7258
7674
  return;
7259
7675
  void (async () => {
7260
7676
  await initialBuildPromise;
@@ -7304,12 +7720,20 @@ function generateTestIdsModule(componentTestIds) {
7304
7720
  });
7305
7721
  });
7306
7722
  }
7723
+ const VIRTUAL_ID = "virtual:testids";
7724
+ const RESOLVED_ID = `\0${VIRTUAL_ID}`;
7307
7725
  function createTestIdsVirtualModulesPlugin(componentTestIds) {
7308
- const maybeModule = virtualImport;
7309
- const virtual = maybeModule.default ?? virtualImport;
7310
- return virtual({
7311
- "virtual:testids": () => generateTestIdsModule(componentTestIds)
7312
- });
7726
+ return {
7727
+ name: "vue-pom-generator:virtual-testids",
7728
+ resolveId(id) {
7729
+ if (id === VIRTUAL_ID)
7730
+ return RESOLVED_ID;
7731
+ },
7732
+ load(id) {
7733
+ if (id === RESOLVED_ID)
7734
+ return generateTestIdsModule(componentTestIds);
7735
+ }
7736
+ };
7313
7737
  }
7314
7738
  function createSupportPlugins(options) {
7315
7739
  const {
@@ -7318,11 +7742,14 @@ function createSupportPlugins(options) {
7318
7742
  vueFilesPathMap,
7319
7743
  nativeWrappers,
7320
7744
  excludedComponents,
7321
- viewsDir,
7322
- scanDirs,
7745
+ getPageDirs,
7746
+ getComponentDirs,
7747
+ getLayoutDirs,
7748
+ getViewsDir,
7749
+ getSourceDirs,
7323
7750
  getWrapperSearchRoots,
7324
7751
  nameCollisionBehavior = "suffix",
7325
- missingSemanticNameBehavior,
7752
+ missingSemanticNameBehavior = "error",
7326
7753
  existingIdBehavior,
7327
7754
  outDir,
7328
7755
  emitLanguages,
@@ -7351,7 +7778,6 @@ function createSupportPlugins(options) {
7351
7778
  throw new Error("[vue-pom-generator] router.entry is required when router introspection is enabled.");
7352
7779
  return path.isAbsolute(routerEntry) ? routerEntry : path.resolve(projectRootRef.current, routerEntry);
7353
7780
  };
7354
- const resolvedRouterEntry = resolveRouterEntry2();
7355
7781
  const getDefaultBasePageClassPath = () => {
7356
7782
  try {
7357
7783
  return fileURLToPath(new URL("../class-generation/base-page.ts", import.meta.url));
@@ -7364,8 +7790,11 @@ function createSupportPlugins(options) {
7364
7790
  const tsProcessor = createBuildProcessorPlugin({
7365
7791
  componentHierarchyMap,
7366
7792
  vueFilesPathMap,
7367
- viewsDir,
7368
- scanDirs,
7793
+ getPageDirs,
7794
+ getComponentDirs,
7795
+ getLayoutDirs,
7796
+ getViewsDir,
7797
+ getSourceDirs,
7369
7798
  basePageClassPath,
7370
7799
  normalizedBasePagePath,
7371
7800
  outDir,
@@ -7387,15 +7816,18 @@ function createSupportPlugins(options) {
7387
7816
  getWrapperSearchRoots,
7388
7817
  routerAwarePoms,
7389
7818
  routerType,
7390
- resolvedRouterEntry,
7819
+ getResolvedRouterEntry: resolveRouterEntry2,
7391
7820
  routerModuleShims,
7392
7821
  loggerRef
7393
7822
  });
7394
7823
  const devProcessor = createDevProcessorPlugin({
7395
7824
  nativeWrappers,
7396
7825
  excludedComponents,
7397
- viewsDir,
7398
- scanDirs,
7826
+ getPageDirs,
7827
+ getComponentDirs,
7828
+ getLayoutDirs,
7829
+ getViewsDir,
7830
+ getSourceDirs,
7399
7831
  getWrapperSearchRoots,
7400
7832
  projectRootRef,
7401
7833
  normalizedBasePagePath,
@@ -7415,7 +7847,7 @@ function createSupportPlugins(options) {
7415
7847
  testIdAttribute,
7416
7848
  routerAwarePoms,
7417
7849
  routerType,
7418
- resolvedRouterEntry,
7850
+ getResolvedRouterEntry: resolveRouterEntry2,
7419
7851
  routerModuleShims,
7420
7852
  loggerRef
7421
7853
  });
@@ -7573,7 +8005,7 @@ function createVuePluginWithTestIds(options) {
7573
8005
  getViewsDirAbs,
7574
8006
  testIdAttribute,
7575
8007
  loggerRef,
7576
- scanDirs = ["src"],
8008
+ getSourceDirs,
7577
8009
  getWrapperSearchRoots,
7578
8010
  getProjectRoot
7579
8011
  } = options;
@@ -7582,7 +8014,7 @@ function createVuePluginWithTestIds(options) {
7582
8014
  filename,
7583
8015
  projectRoot: getProjectRoot(),
7584
8016
  viewsDirAbs: getViewsDirAbs(),
7585
- scanDirs,
8017
+ sourceDirs: getSourceDirs(),
7586
8018
  extraRoots: [process.cwd()]
7587
8019
  });
7588
8020
  };
@@ -7598,7 +8030,7 @@ function createVuePluginWithTestIds(options) {
7598
8030
  if (absFilename.startsWith(viewsDirAbs + path.sep) || absFilename === viewsDirAbs)
7599
8031
  return true;
7600
8032
  const rootsToTry = [projectRoot, process.cwd()];
7601
- const matched = scanDirs.some((dir) => {
8033
+ const matched = getSourceDirs().some((dir) => {
7602
8034
  return rootsToTry.some((root) => {
7603
8035
  const absDir = path.resolve(root, dir);
7604
8036
  if (absFilename.startsWith(absDir + path.sep) || absFilename === absDir)
@@ -7773,6 +8205,25 @@ function createVuePluginWithTestIds(options) {
7773
8205
  };
7774
8206
  return { metadataCollectorPlugin, internalVuePlugin, nuxtVueBridgePlugin, templateCompilerOptions };
7775
8207
  }
8208
+ const nuxtConfigMarker$1 = /* @__PURE__ */ Symbol.for("@immense/vue-pom-generator.nuxt");
8209
+ const nuxtConfigFileNames = [
8210
+ "nuxt.config.ts",
8211
+ "nuxt.config.js",
8212
+ "nuxt.config.mjs",
8213
+ "nuxt.config.cjs",
8214
+ "nuxt.config.mts",
8215
+ "nuxt.config.cts",
8216
+ ".nuxtrc"
8217
+ ];
8218
+ const nuxtSourceMarkers = [
8219
+ "app.vue",
8220
+ "app",
8221
+ "pages",
8222
+ "layouts",
8223
+ "components",
8224
+ "layers",
8225
+ ".nuxt"
8226
+ ];
7776
8227
  function assertNonEmptyString(value, name) {
7777
8228
  if (!value || !value.trim()) {
7778
8229
  throw new Error(`${name} must be a non-empty string.`);
@@ -7813,12 +8264,63 @@ function assertErrorBehavior(value, name) {
7813
8264
  }
7814
8265
  function resolveMissingSemanticNameBehavior(value) {
7815
8266
  if (!value) {
7816
- return "ignore";
8267
+ return "error";
7817
8268
  }
7818
8269
  if (value === "ignore" || value === "error") {
7819
8270
  return value;
7820
8271
  }
7821
- return value.missingSemanticNameBehavior ?? "ignore";
8272
+ return value.missingSemanticNameBehavior ?? "error";
8273
+ }
8274
+ function readPackageJson(projectRoot) {
8275
+ const packageJsonPath = path.join(projectRoot, "package.json");
8276
+ if (!fs.existsSync(packageJsonPath)) {
8277
+ return null;
8278
+ }
8279
+ try {
8280
+ return JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
8281
+ } catch {
8282
+ return null;
8283
+ }
8284
+ }
8285
+ function recordHasOwnStringKey(value, key) {
8286
+ return value !== null && value !== void 0 && !Array.isArray(value) && Object.prototype.hasOwnProperty.call(value, key) && typeof value[key] === "string";
8287
+ }
8288
+ function projectPackageLooksNuxt(projectRoot) {
8289
+ const packageJson = readPackageJson(projectRoot);
8290
+ if (!packageJson) {
8291
+ return false;
8292
+ }
8293
+ const dependencyGroups = [
8294
+ packageJson.dependencies,
8295
+ packageJson.devDependencies,
8296
+ packageJson.peerDependencies,
8297
+ packageJson.optionalDependencies
8298
+ ];
8299
+ if (dependencyGroups.some((group) => {
8300
+ const dependencyGroup = typeof group === "object" && group !== null ? group : void 0;
8301
+ return recordHasOwnStringKey(dependencyGroup, "nuxt") || recordHasOwnStringKey(dependencyGroup, "nuxt-nightly");
8302
+ })) {
8303
+ return true;
8304
+ }
8305
+ if (typeof packageJson.scripts !== "object" || packageJson.scripts === null || Array.isArray(packageJson.scripts)) {
8306
+ return false;
8307
+ }
8308
+ return Object.values(packageJson.scripts).some((script) => {
8309
+ if (typeof script !== "string") {
8310
+ return false;
8311
+ }
8312
+ const normalizedScript = script.trim();
8313
+ return normalizedScript === "nuxt" || normalizedScript.startsWith("nuxt ") || normalizedScript === "nuxi" || normalizedScript.startsWith("nuxi ");
8314
+ });
8315
+ }
8316
+ function detectNuxtProject(options, projectRoot) {
8317
+ if (options[nuxtConfigMarker$1] === true) {
8318
+ return true;
8319
+ }
8320
+ if (nuxtConfigFileNames.some((fileName) => fs.existsSync(path.join(projectRoot, fileName)))) {
8321
+ return true;
8322
+ }
8323
+ return projectPackageLooksNuxt(projectRoot) && nuxtSourceMarkers.some((entry) => fs.existsSync(path.join(projectRoot, entry)));
7822
8324
  }
7823
8325
  function assertRouterModuleShims(value, name) {
7824
8326
  if (!value)
@@ -7927,14 +8429,18 @@ function assertNotVitePluginInstance(options) {
7927
8429
  function createVuePomGeneratorPlugins(options = {}) {
7928
8430
  assertNotVitePluginInstance(options);
7929
8431
  const injection = options.injection ?? {};
8432
+ const isNuxt = detectNuxtProject(options, process.cwd());
7930
8433
  const generationSetting = options.generation;
7931
8434
  const generationOptions = generationSetting === false ? null : generationSetting ?? {};
7932
8435
  const generationEnabled = generationOptions !== null;
8436
+ const vueGenerationOptions = generationOptions;
7933
8437
  const verbosity = options.logging?.verbosity ?? "warn";
7934
8438
  const vueOptions = options.vueOptions;
7935
- const viewsDir = injection.viewsDir ?? "src/views";
7936
- const scanDirs = injection.scanDirs ?? ["src"];
7937
- const wrapperSearchRoots = injection.wrapperSearchRoots ?? [];
8439
+ const legacyVueOptions = options;
8440
+ const pageDirsRef = { current: !isNuxt ? [legacyVueOptions.injection?.viewsDir ?? "src/views"] : ["app/pages"] };
8441
+ const componentDirsRef = { current: !isNuxt ? legacyVueOptions.injection?.componentDirs ?? ["src/components"] : ["app/components"] };
8442
+ const layoutDirsRef = { current: !isNuxt ? legacyVueOptions.injection?.layoutDirs ?? ["src/layouts"] : ["app/layouts"] };
8443
+ const wrapperSearchRootsRef = { current: !isNuxt ? legacyVueOptions.injection?.wrapperSearchRoots ?? [] : [] };
7938
8444
  const nativeWrappers = injection.nativeWrappers ?? {};
7939
8445
  const excludedComponents = injection.excludeComponents ?? [];
7940
8446
  const testIdAttribute = (injection.attribute ?? "data-testid").trim() || "data-testid";
@@ -7942,10 +8448,9 @@ function createVuePomGeneratorPlugins(options = {}) {
7942
8448
  const outDir = (generationOptions?.outDir ?? "tests/playwright/__generated__").trim();
7943
8449
  const emitLanguages = generationOptions?.emit && generationOptions.emit.length ? generationOptions.emit : ["ts"];
7944
8450
  const nameCollisionBehavior = generationOptions?.nameCollisionBehavior ?? "suffix";
7945
- const routerEntry = generationOptions?.router?.entry;
7946
- const routerType = generationOptions?.router?.type ?? "vue-router";
7947
- const routerModuleShims = generationOptions?.router?.moduleShims;
7948
- const isNuxt = routerType === "nuxt";
8451
+ const routerEntry = !isNuxt ? vueGenerationOptions?.router?.entry : void 0;
8452
+ const routerType = isNuxt ? "nuxt" : vueGenerationOptions?.router?.type ?? "vue-router";
8453
+ const routerModuleShims = !isNuxt ? vueGenerationOptions?.router?.moduleShims : void 0;
7949
8454
  if (isNuxt && options.vuePluginOwnership === "internal") {
7950
8455
  throw new Error('[vue-pom-generator] Nuxt projects must use the resolved app-owned vite:vue plugin. Omit vuePluginOwnership or set it to "external".');
7951
8456
  }
@@ -7962,66 +8467,100 @@ function createVuePomGeneratorPlugins(options = {}) {
7962
8467
  const resolvedCustomPomImportAliases = customPoms?.importAliases;
7963
8468
  const resolvedCustomPomImportCollisionBehavior = customPoms?.importNameCollisionBehavior ?? "error";
7964
8469
  const basePageClassPathOverride = generationOptions?.basePageClassPath;
8470
+ const getPageDirs = () => pageDirsRef.current;
8471
+ const getViewsDir = () => getPageDirs()[0] ?? "src/views";
8472
+ const getComponentDirs = () => componentDirsRef.current;
8473
+ const getLayoutDirs = () => layoutDirsRef.current;
8474
+ const getSourceDirs = () => Array.from(/* @__PURE__ */ new Set([
8475
+ ...getPageDirs(),
8476
+ ...getComponentDirs(),
8477
+ ...getLayoutDirs()
8478
+ ]));
8479
+ const getWrapperSearchRoots = () => wrapperSearchRootsRef.current;
7965
8480
  const sharedStateKey = JSON.stringify({
7966
8481
  cwd: process.cwd(),
7967
- viewsDir,
7968
- scanDirs,
7969
- wrapperSearchRoots,
8482
+ mode: isNuxt ? "nuxt" : "vue",
8483
+ pageDirs: isNuxt ? null : getPageDirs(),
8484
+ componentDirs: isNuxt ? null : getComponentDirs(),
8485
+ layoutDirs: isNuxt ? null : getLayoutDirs(),
8486
+ wrapperSearchRoots: isNuxt ? null : getWrapperSearchRoots(),
7970
8487
  outDir,
7971
8488
  testIdAttribute,
7972
8489
  routerType,
7973
8490
  vuePluginOwnership
7974
8491
  });
7975
8492
  const sharedState = getSharedGeneratorState(sharedStateKey);
8493
+ let templateCompilerOptionsForResolvedPlugin;
7976
8494
  const projectRootRef = { current: process.cwd() };
7977
8495
  const loggerRef = {
7978
8496
  current: createLogger({ verbosity })
7979
8497
  };
7980
- const getViewsDirAbs = () => resolveFromProjectRoot(projectRootRef.current, viewsDir);
7981
- const getWrapperSearchRootsAbs = () => wrapperSearchRoots.map((root) => resolveFromProjectRoot(projectRootRef.current, root));
7982
- const { componentTestIds, elementMetadata, semanticNameMap, componentHierarchyMap, vueFilesPathMap } = sharedState;
7983
- const { metadataCollectorPlugin, internalVuePlugin, templateCompilerOptions } = createVuePluginWithTestIds({
7984
- vueOptions,
7985
- existingIdBehavior,
7986
- nameCollisionBehavior,
7987
- nativeWrappers,
7988
- elementMetadata,
7989
- semanticNameMap,
7990
- componentHierarchyMap,
7991
- vueFilesPathMap,
7992
- excludedComponents,
7993
- getViewsDirAbs,
7994
- testIdAttribute,
7995
- loggerRef,
7996
- scanDirs,
7997
- getWrapperSearchRoots: getWrapperSearchRootsAbs,
7998
- getProjectRoot: () => projectRootRef.current
7999
- });
8000
8498
  const configPlugin = {
8001
8499
  name: "vue-pom-generator-config",
8002
8500
  enforce: "pre",
8003
- configResolved(config) {
8501
+ async configResolved(config) {
8004
8502
  projectRootRef.current = config.root;
8005
8503
  loggerRef.current = createLogger({ verbosity, viteLogger: config.logger });
8504
+ if (vueGenerationOptions?.router?.type === "nuxt") {
8505
+ throw new Error('[vue-pom-generator] Remove generation.router.type="nuxt". Nuxt projects are auto-detected.');
8506
+ }
8507
+ if (isNuxt) {
8508
+ const nuxtDiscovery = await loadNuxtProjectDiscovery(process.cwd());
8509
+ projectRootRef.current = nuxtDiscovery.rootDir;
8510
+ pageDirsRef.current = nuxtDiscovery.pageDirs.length ? nuxtDiscovery.pageDirs : [path.resolve(nuxtDiscovery.srcDir, "pages")];
8511
+ componentDirsRef.current = nuxtDiscovery.componentDirs;
8512
+ layoutDirsRef.current = nuxtDiscovery.layoutDirs;
8513
+ wrapperSearchRootsRef.current = nuxtDiscovery.wrapperSearchRoots;
8514
+ }
8006
8515
  assertNonEmptyString(testIdAttribute, "[vue-pom-generator] injection.attribute");
8007
- assertNonEmptyString(viewsDir, "[vue-pom-generator] injection.viewsDir");
8008
- assertNonEmptyStringArray(wrapperSearchRoots, "[vue-pom-generator] injection.wrapperSearchRoots");
8516
+ assertNonEmptyString(getViewsDir(), "[vue-pom-generator] injection.viewsDir");
8517
+ assertNonEmptyStringArray(getComponentDirs(), "[vue-pom-generator] injection.componentDirs");
8518
+ assertNonEmptyStringArray(getLayoutDirs(), "[vue-pom-generator] injection.layoutDirs");
8519
+ assertNonEmptyStringArray(getWrapperSearchRoots(), "[vue-pom-generator] injection.wrapperSearchRoots");
8009
8520
  assertErrorBehavior(errorBehavior, "[vue-pom-generator] errorBehavior");
8010
8521
  if (generationEnabled) {
8011
8522
  assertNonEmptyString(outDir, "[vue-pom-generator] generation.outDir");
8012
8523
  assertOneOf(typescriptOutputStructure, ["aggregated", "split"], "[vue-pom-generator] generation.playwright.outputStructure");
8013
8524
  assertRouterModuleShims(routerModuleShims, "[vue-pom-generator] generation.router.moduleShims");
8014
- if (generationOptions?.router && routerType === "vue-router") {
8525
+ if (!isNuxt && vueGenerationOptions?.router && routerType === "vue-router") {
8015
8526
  assertNonEmptyString(routerEntry, "[vue-pom-generator] generation.router.entry");
8016
8527
  }
8017
8528
  }
8018
8529
  if (usesExternalVuePlugin) {
8019
- applyTemplateCompilerOptionsToResolvedVuePlugin(config, templateCompilerOptions, isNuxt ? "nuxt" : vuePluginOwnership);
8530
+ applyTemplateCompilerOptionsToResolvedVuePlugin(
8531
+ config,
8532
+ templateCompilerOptionsForResolvedPlugin,
8533
+ isNuxt ? "nuxt" : vuePluginOwnership
8534
+ );
8020
8535
  }
8021
8536
  loggerRef.current.info(`projectRoot=${projectRootRef.current}`);
8537
+ loggerRef.current.info(`viewsDir=${getViewsDir()}`);
8538
+ loggerRef.current.info(`componentDirs=${getComponentDirs().join(", ")}`);
8539
+ loggerRef.current.info(`layoutDirs=${getLayoutDirs().join(", ")}`);
8022
8540
  loggerRef.current.info(`Active plugins: ${(config.plugins ?? []).map((p) => p.name).filter((n) => n.includes("vue-pom")).join(", ")}`);
8023
8541
  }
8024
8542
  };
8543
+ const getViewsDirAbs = () => resolveFromProjectRoot(projectRootRef.current, getViewsDir());
8544
+ const getWrapperSearchRootsAbs = () => getWrapperSearchRoots().map((root) => resolveFromProjectRoot(projectRootRef.current, root));
8545
+ const { componentTestIds, elementMetadata, semanticNameMap, componentHierarchyMap, vueFilesPathMap } = sharedState;
8546
+ const { metadataCollectorPlugin, internalVuePlugin, templateCompilerOptions } = createVuePluginWithTestIds({
8547
+ vueOptions,
8548
+ existingIdBehavior,
8549
+ nameCollisionBehavior,
8550
+ nativeWrappers,
8551
+ elementMetadata,
8552
+ semanticNameMap,
8553
+ componentHierarchyMap,
8554
+ vueFilesPathMap,
8555
+ excludedComponents,
8556
+ getViewsDirAbs,
8557
+ testIdAttribute,
8558
+ loggerRef,
8559
+ getSourceDirs,
8560
+ getWrapperSearchRoots: getWrapperSearchRootsAbs,
8561
+ getProjectRoot: () => projectRootRef.current
8562
+ });
8563
+ templateCompilerOptionsForResolvedPlugin = templateCompilerOptions;
8025
8564
  const routerAwarePoms = typeof routerEntry === "string" && routerEntry.trim().length > 0 || routerType === "nuxt";
8026
8565
  const supportPlugins = createSupportPlugins({
8027
8566
  componentTestIds,
@@ -8029,8 +8568,11 @@ function createVuePomGeneratorPlugins(options = {}) {
8029
8568
  vueFilesPathMap,
8030
8569
  nativeWrappers,
8031
8570
  excludedComponents,
8032
- viewsDir,
8033
- scanDirs,
8571
+ getPageDirs,
8572
+ getComponentDirs,
8573
+ getLayoutDirs,
8574
+ getViewsDir,
8575
+ getSourceDirs,
8034
8576
  getWrapperSearchRoots: getWrapperSearchRootsAbs,
8035
8577
  nameCollisionBehavior,
8036
8578
  missingSemanticNameBehavior,
@@ -8075,12 +8617,22 @@ function createVuePomGeneratorPlugins(options = {}) {
8075
8617
  }
8076
8618
  return resultPlugins;
8077
8619
  }
8620
+ const nuxtConfigMarker = /* @__PURE__ */ Symbol.for("@immense/vue-pom-generator.nuxt");
8078
8621
  function defineVuePomGeneratorConfig(options) {
8079
8622
  return options;
8080
8623
  }
8624
+ function defineNuxtPomGeneratorConfig(options) {
8625
+ const markedOptions = { ...options };
8626
+ Object.defineProperty(markedOptions, nuxtConfigMarker, {
8627
+ value: true,
8628
+ enumerable: false
8629
+ });
8630
+ return markedOptions;
8631
+ }
8081
8632
  export {
8082
8633
  createVuePomGeneratorPlugins,
8083
8634
  createVuePomGeneratorPlugins as default,
8635
+ defineNuxtPomGeneratorConfig,
8084
8636
  defineVuePomGeneratorConfig,
8085
8637
  createVuePomGeneratorPlugins as vuePomGenerator
8086
8638
  };