@wpnuxt/core 2.0.0-alpha.7 → 2.0.0-alpha.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/module.d.mts CHANGED
@@ -30,6 +30,11 @@ interface WPNuxtConfig {
30
30
  * @default '.queries/'
31
31
  */
32
32
  mergedOutputFolder: string;
33
+ /**
34
+ * Whether to warn when a user query file overrides a default query file
35
+ * @default true
36
+ */
37
+ warnOnOverride?: boolean;
33
38
  };
34
39
  /**
35
40
  * Whether to download the schema from the WordPress site and save it to disk
package/dist/module.d.ts CHANGED
@@ -30,6 +30,11 @@ interface WPNuxtConfig {
30
30
  * @default '.queries/'
31
31
  */
32
32
  mergedOutputFolder: string;
33
+ /**
34
+ * Whether to warn when a user query file overrides a default query file
35
+ * @default true
36
+ */
37
+ warnOnOverride?: boolean;
33
38
  };
34
39
  /**
35
40
  * Whether to download the schema from the WordPress site and save it to disk
package/dist/module.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "compatibility": {
5
5
  "nuxt": ">=3.0.0"
6
6
  },
7
- "version": "2.0.0-alpha.6",
7
+ "version": "2.0.0-alpha.7",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "1.0.2",
10
10
  "unbuild": "3.6.1"
package/dist/module.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import { defu } from 'defu';
2
- import { promises, cpSync, existsSync, statSync, readFileSync, writeFileSync } from 'node:fs';
2
+ import { promises, cpSync, existsSync, readdirSync, statSync, readFileSync, writeFileSync } from 'node:fs';
3
3
  import { mkdir, writeFile } from 'node:fs/promises';
4
- import { join } from 'node:path';
4
+ import { join, relative, dirname } from 'node:path';
5
5
  import { useLogger, createResolver, resolveFiles, defineNuxtModule, addPlugin, addImports, addComponentsDir, addTemplate, addTypeTemplate, hasNuxtModule, installModule } from '@nuxt/kit';
6
6
  import { upperFirst } from 'scule';
7
7
  import { ref } from 'vue';
@@ -35,6 +35,13 @@ async function mergeQueries(nuxt, wpNuxtConfig, resolver) {
35
35
  const defaultQueriesPath = resolver.resolve("./runtime/queries");
36
36
  await promises.rm(queryOutputPath, { recursive: true, force: true });
37
37
  cpSync(defaultQueriesPath, queryOutputPath, { recursive: true });
38
+ const conflicts = findConflicts(userQueryPath, queryOutputPath);
39
+ if (conflicts.length && wpNuxtConfig.queries.warnOnOverride) {
40
+ logger.warn("The following user query files will override default queries:");
41
+ for (const file of conflicts) {
42
+ logger.warn(` - ${file}`);
43
+ }
44
+ }
38
45
  if (existsSync(userQueryPath)) {
39
46
  logger.debug("Extending queries:", userQueryPath);
40
47
  cpSync(userQueryPath, queryOutputPath, { recursive: true });
@@ -42,6 +49,29 @@ async function mergeQueries(nuxt, wpNuxtConfig, resolver) {
42
49
  logger.debug("Merged queries folder:", queryOutputPath);
43
50
  return queryOutputPath;
44
51
  }
52
+ function findConflicts(userQueryPath, outputPath) {
53
+ const conflicts = [];
54
+ function walk(dir) {
55
+ const entries = readdirSync(dir);
56
+ for (const entry of entries) {
57
+ const fullPath = join(dir, entry);
58
+ const stat = statSync(fullPath);
59
+ if (stat.isDirectory()) {
60
+ walk(fullPath);
61
+ } else if (stat.isFile()) {
62
+ const rel = relative(userQueryPath, fullPath);
63
+ const target = join(outputPath, rel);
64
+ if (existsSync(target)) {
65
+ conflicts.push(rel);
66
+ }
67
+ }
68
+ }
69
+ }
70
+ if (existsSync(userQueryPath)) {
71
+ walk(userQueryPath);
72
+ }
73
+ return conflicts;
74
+ }
45
75
 
46
76
  const _parseDoc = async (doc) => {
47
77
  if (!doc || typeof doc !== "string" || doc.trim().length === 0) {
@@ -143,8 +173,21 @@ async function prepareContext(ctx) {
143
173
  const mutationFnName = (fn) => `useMutation${upperFirst(fn)}`;
144
174
  const formatNodes = (nodes) => nodes?.map((n) => `'${n}'`).join(",") ?? "";
145
175
  const getFragmentType = (q) => {
146
- const fragmentSuffix = q.fragments?.length && q.nodes?.includes("nodes") ? "[]" : "";
147
- return q.fragments?.length ? q.fragments.map((f) => `${f}Fragment${fragmentSuffix}`).join(" | ") : "any";
176
+ if (q.fragments?.length) {
177
+ const fragmentSuffix = q.nodes?.includes("nodes") ? "[]" : "";
178
+ return q.fragments.map((f) => `${f}Fragment${fragmentSuffix}`).join(" | ");
179
+ }
180
+ if (q.nodes?.length) {
181
+ let typePath = `${q.name}RootQuery`;
182
+ for (const node of q.nodes) {
183
+ typePath = `NonNullable<${typePath}>['${node}']`;
184
+ }
185
+ if (q.nodes.includes("nodes")) {
186
+ typePath = `${typePath}[number]`;
187
+ }
188
+ return typePath;
189
+ }
190
+ return `${q.name}RootQuery`;
148
191
  };
149
192
  const queryFnExp = (q, typed = false) => {
150
193
  const functionName = fnName(q.name);
@@ -161,19 +204,34 @@ async function prepareContext(ctx) {
161
204
  return ` export const ${functionName}: (variables: ${m.name}MutationVariables, options?: WPMutationOptions) => Promise<WPMutationResult<${m.name}Mutation>>`;
162
205
  };
163
206
  ctx.generateImports = () => {
207
+ const lines = [];
164
208
  const imports = [];
209
+ if (queries.length > 0) {
210
+ imports.push("useWPContent");
211
+ }
212
+ if (mutations.length > 0) {
213
+ imports.push("useGraphqlMutation");
214
+ }
215
+ if (imports.length > 0) {
216
+ lines.push(`import { ${imports.join(", ")} } from '#imports'`);
217
+ lines.push("");
218
+ }
165
219
  queries.forEach((f) => {
166
- imports.push(queryFnExp(f, false));
220
+ lines.push(queryFnExp(f, false));
167
221
  });
168
222
  mutations.forEach((m) => {
169
- imports.push(mutationFnExp(m, false));
223
+ lines.push(mutationFnExp(m, false));
170
224
  });
171
- return imports.join("\n");
225
+ return lines.join("\n");
172
226
  };
173
227
  const typeSet = /* @__PURE__ */ new Set();
174
228
  queries.forEach((o) => {
175
229
  typeSet.add(`${o.name}QueryVariables`);
176
- o.fragments?.forEach((f) => typeSet.add(`${f}Fragment`));
230
+ if (o.fragments?.length) {
231
+ o.fragments.forEach((f) => typeSet.add(`${f}Fragment`));
232
+ } else {
233
+ typeSet.add(`${o.name}RootQuery`);
234
+ }
177
235
  });
178
236
  mutations.forEach((m) => {
179
237
  typeSet.add(`${m.name}MutationVariables`);
@@ -417,7 +475,8 @@ const module$1 = defineNuxtModule({
417
475
  graphqlEndpoint: "/graphql",
418
476
  queries: {
419
477
  extendFolder: "extend/queries/",
420
- mergedOutputFolder: ".queries/"
478
+ mergedOutputFolder: ".queries/",
479
+ warnOnOverride: true
421
480
  },
422
481
  downloadSchema: true,
423
482
  debug: false,
@@ -437,6 +496,7 @@ const module$1 = defineNuxtModule({
437
496
  nuxt.options.runtimeConfig.public.buildHash = randHashGenerator();
438
497
  addPlugin(resolver.resolve("./runtime/plugins/graphqlConfig"));
439
498
  addPlugin(resolver.resolve("./runtime/plugins/graphqlErrors"));
499
+ configureTrailingSlash(nuxt, logger);
440
500
  const mergedQueriesFolder = await mergeQueries(nuxt, wpNuxtConfig, resolver);
441
501
  await setupServerOptions(nuxt, resolver, logger);
442
502
  await setupClientOptions(nuxt, resolver, logger);
@@ -477,8 +537,10 @@ const module$1 = defineNuxtModule({
477
537
  });
478
538
  if (wpNuxtConfig.cache?.enabled !== false) {
479
539
  const maxAge = wpNuxtConfig.cache?.maxAge ?? 300;
480
- nuxt.options.nitro.routeRules = nuxt.options.nitro.routeRules || {};
481
- nuxt.options.nitro.routeRules["/api/wpnuxt/**"] = {
540
+ const nitroOptions = nuxt.options;
541
+ nitroOptions.nitro = nitroOptions.nitro || {};
542
+ nitroOptions.nitro.routeRules = nitroOptions.nitro.routeRules || {};
543
+ nitroOptions.nitro.routeRules["/api/wpnuxt/**"] = {
482
544
  cache: {
483
545
  maxAge,
484
546
  swr: wpNuxtConfig.cache?.swr !== false
@@ -510,10 +572,12 @@ const module$1 = defineNuxtModule({
510
572
  nuxt.options.alias["#wpnuxt"] = resolver.resolve(nuxt.options.buildDir, "wpnuxt");
511
573
  nuxt.options.alias["#wpnuxt/*"] = resolver.resolve(nuxt.options.buildDir, "wpnuxt", "*");
512
574
  nuxt.options.alias["#wpnuxt/types"] = resolver.resolve("./types");
513
- nuxt.options.nitro.alias = nuxt.options.nitro.alias || {};
514
- nuxt.options.nitro.alias["#wpnuxt/types"] = resolver.resolve("./types");
515
- nuxt.options.nitro.externals = nuxt.options.nitro.externals || {};
516
- nuxt.options.nitro.externals.inline = nuxt.options.nitro.externals.inline || [];
575
+ const nitroOpts = nuxt.options;
576
+ nitroOpts.nitro = nitroOpts.nitro || {};
577
+ nitroOpts.nitro.alias = nitroOpts.nitro.alias || {};
578
+ nitroOpts.nitro.alias["#wpnuxt/types"] = resolver.resolve("./types");
579
+ nitroOpts.nitro.externals = nitroOpts.nitro.externals || {};
580
+ nitroOpts.nitro.externals.inline = nitroOpts.nitro.externals.inline || [];
517
581
  addTemplate({
518
582
  write: true,
519
583
  filename: "wpnuxt/index.mjs",
@@ -661,18 +725,63 @@ async function setupClientOptions(nuxt, _resolver, logger) {
661
725
  await writeFile(targetPath, CLIENT_OPTIONS_TEMPLATE);
662
726
  logger.debug("Created graphqlMiddleware.clientOptions.ts with WPNuxt defaults (preview mode support)");
663
727
  }
728
+ function configureTrailingSlash(nuxt, logger) {
729
+ const handlerPath = join(nuxt.options.buildDir, "wpnuxt", "trailing-slash-handler.ts");
730
+ const handlerCode = `import { defineEventHandler, sendRedirect, getRequestURL } from 'h3'
731
+
732
+ export default defineEventHandler((event) => {
733
+ const url = getRequestURL(event)
734
+ const path = url.pathname
735
+
736
+ // Skip if:
737
+ // - Already has trailing slash
738
+ // - Is root path
739
+ // - Is an API route
740
+ // - Has a file extension (likely a static file)
741
+ // - Is a Nuxt internal route (_nuxt, __nuxt)
742
+ if (
743
+ path.endsWith('/') ||
744
+ path === '' ||
745
+ path.startsWith('/api/') ||
746
+ path.startsWith('/_nuxt/') ||
747
+ path.startsWith('/__nuxt') ||
748
+ path.includes('.')
749
+ ) {
750
+ return
751
+ }
752
+
753
+ // Redirect to trailing slash version
754
+ return sendRedirect(event, path + '/' + url.search, 301)
755
+ })
756
+ `;
757
+ nuxt.hook("build:before", async () => {
758
+ await mkdir(dirname(handlerPath), { recursive: true });
759
+ await writeFile(handlerPath, handlerCode);
760
+ logger.debug("Created trailing slash handler at " + handlerPath);
761
+ });
762
+ nuxt.hook("nitro:config", (nitroConfig) => {
763
+ nitroConfig.handlers = nitroConfig.handlers || [];
764
+ nitroConfig.handlers.unshift({
765
+ route: "/**",
766
+ handler: handlerPath
767
+ });
768
+ });
769
+ logger.debug("Configured trailing slash handling for WordPress URI compatibility");
770
+ }
664
771
  function configureVercelSettings(nuxt, logger) {
665
- const isVercel = process.env.VERCEL === "1" || nuxt.options.nitro.preset === "vercel";
772
+ const opts = nuxt.options;
773
+ opts.nitro = opts.nitro || {};
774
+ const isVercel = process.env.VERCEL === "1" || opts.nitro.preset === "vercel";
666
775
  if (isVercel) {
667
776
  logger.debug("Vercel deployment detected, applying recommended settings");
668
- nuxt.options.nitro.future = nuxt.options.nitro.future || {};
669
- if (nuxt.options.nitro.future.nativeSWR === void 0) {
670
- nuxt.options.nitro.future.nativeSWR = true;
777
+ opts.nitro.future = opts.nitro.future || {};
778
+ if (opts.nitro.future.nativeSWR === void 0) {
779
+ opts.nitro.future.nativeSWR = true;
671
780
  logger.debug("Enabled nitro.future.nativeSWR for Vercel ISR compatibility");
672
781
  }
673
- nuxt.options.routeRules = nuxt.options.routeRules || {};
674
- if (!nuxt.options.routeRules["/**"]) {
675
- nuxt.options.routeRules["/**"] = { ssr: true };
782
+ opts.routeRules = opts.routeRules || {};
783
+ if (!opts.routeRules["/**"]) {
784
+ opts.routeRules["/**"] = { ssr: true };
676
785
  logger.debug("Enabled SSR for all routes (routeRules['/**'] = { ssr: true })");
677
786
  }
678
787
  }
@@ -1,4 +1,4 @@
1
- import { getRelativeImagePath } from "../util/images.js";
1
+ import { transformData, normalizeUriParam } from "../util/content.js";
2
2
  import { computed, ref, watch as vueWatch, useAsyncGraphqlQuery } from "#imports";
3
3
  export const useWPContent = (queryName, nodes, fixImagePaths, params, options) => {
4
4
  const {
@@ -9,6 +9,7 @@ export const useWPContent = (queryName, nodes, fixImagePaths, params, options) =
9
9
  timeout: timeoutOption,
10
10
  ...restOptions
11
11
  } = options ?? {};
12
+ const normalizedParams = normalizeUriParam(params);
12
13
  const maxRetries = retryOption === false ? 0 : retryOption ?? 0;
13
14
  const baseRetryDelay = retryDelayOption ?? 1e3;
14
15
  const retryCount = ref(0);
@@ -50,7 +51,7 @@ export const useWPContent = (queryName, nodes, fixImagePaths, params, options) =
50
51
  };
51
52
  const { data, pending, refresh, execute, clear, error, status } = useAsyncGraphqlQuery(
52
53
  String(queryName),
53
- params ?? {},
54
+ normalizedParams ?? {},
54
55
  asyncDataOptions
55
56
  );
56
57
  const transformError = ref(null);
@@ -115,25 +116,3 @@ export const useWPContent = (queryName, nodes, fixImagePaths, params, options) =
115
116
  isRetrying
116
117
  };
117
118
  };
118
- const transformData = (data, nodes, fixImagePaths) => {
119
- const transformedData = findData(data, nodes);
120
- if (fixImagePaths && transformedData && typeof transformedData === "object" && "featuredImage" in transformedData) {
121
- const featuredImage = transformedData.featuredImage;
122
- if (featuredImage && typeof featuredImage === "object" && "node" in featuredImage) {
123
- const node = featuredImage.node;
124
- if (node && typeof node === "object" && "sourceUrl" in node && typeof node.sourceUrl === "string") {
125
- node.relativePath = getRelativeImagePath(node.sourceUrl);
126
- }
127
- }
128
- }
129
- return transformedData;
130
- };
131
- const findData = (data, nodes) => {
132
- if (nodes.length === 0) return data;
133
- return nodes.reduce((acc, node) => {
134
- if (acc && typeof acc === "object" && node in acc) {
135
- return acc[node];
136
- }
137
- return void 0;
138
- }, data);
139
- };
@@ -4,23 +4,44 @@
4
4
  * Actual types are generated at runtime in consuming applications.
5
5
  */
6
6
 
7
- /* eslint-disable @typescript-eslint/no-explicit-any */
7
+ import type { Ref, ComputedRef, WatchSource, WatchCallback, WatchOptions } from 'vue'
8
+ import type { NuxtApp } from 'nuxt/app'
8
9
 
9
- // Stub for #imports
10
- export const computed: any
11
- export const ref: any
12
- export const watch: any
13
- export const defineNuxtPlugin: any
14
- export const useAsyncGraphqlQuery: any
15
- export const useGraphqlState: any
16
- export const useNuxtApp: any
17
- export const useRuntimeConfig: any
18
- export const useRoute: any
10
+ // Stub for #imports - Vue reactivity
11
+ export function computed<T>(getter: () => T): ComputedRef<T>
12
+ export function ref<T>(value: T): Ref<T>
13
+ export function watch<T>(
14
+ source: WatchSource<T> | WatchSource<T>[],
15
+ callback: WatchCallback<T>,
16
+ options?: WatchOptions
17
+ ): () => void
18
+
19
+ // Stub for #imports - Nuxt
20
+ export function defineNuxtPlugin(plugin: (nuxtApp: NuxtApp) => void | Promise<void>): unknown
21
+ export function useNuxtApp(): NuxtApp
22
+ export function useRuntimeConfig(): Record<string, unknown>
23
+ export function useRoute(): { path: string, params: Record<string, string>, query: Record<string, string> }
24
+
25
+ // Stub for #imports - nuxt-graphql-middleware
26
+ export function useAsyncGraphqlQuery<T = unknown>(
27
+ name: string,
28
+ params?: Record<string, unknown>,
29
+ options?: Record<string, unknown>
30
+ ): {
31
+ data: Ref<T | null>
32
+ pending: Ref<boolean>
33
+ refresh: () => Promise<void>
34
+ execute: () => Promise<void>
35
+ clear: () => void
36
+ error: Ref<Error | null>
37
+ status: Ref<string>
38
+ }
39
+ export function useGraphqlState(): Record<string, unknown>
19
40
 
20
41
  // Stub for #nuxt-graphql-middleware/operation-types
21
- export type Query = Record<string, any>
42
+ export type Query = Record<string, unknown>
22
43
 
23
- // Stub for #build/graphql-operations
24
- export type PostFragment = any
25
- export type PageFragment = any
26
- export type MenuItemFragment = any
44
+ // Stub for #build/graphql-operations - these are generated types
45
+ export type PostFragment = Record<string, unknown>
46
+ export type PageFragment = Record<string, unknown>
47
+ export type MenuItemFragment = Record<string, unknown>
File without changes
@@ -0,0 +1,33 @@
1
+ import { getRelativeImagePath } from "./images.js";
2
+ export const findData = (data, nodes) => {
3
+ if (nodes.length === 0) return data;
4
+ return nodes.reduce((acc, node) => {
5
+ if (acc && typeof acc === "object" && node in acc) {
6
+ return acc[node];
7
+ }
8
+ return void 0;
9
+ }, data);
10
+ };
11
+ export const transformData = (data, nodes, fixImagePaths) => {
12
+ const transformedData = findData(data, nodes);
13
+ if (fixImagePaths && transformedData && typeof transformedData === "object" && "featuredImage" in transformedData) {
14
+ const featuredImage = transformedData.featuredImage;
15
+ if (featuredImage && typeof featuredImage === "object" && "node" in featuredImage) {
16
+ const node = featuredImage.node;
17
+ if (node && typeof node === "object" && "sourceUrl" in node && typeof node.sourceUrl === "string") {
18
+ node.relativePath = getRelativeImagePath(node.sourceUrl);
19
+ }
20
+ }
21
+ }
22
+ return transformedData;
23
+ };
24
+ export const normalizeUriParam = (params) => {
25
+ if (!params || typeof params !== "object") return params;
26
+ const paramsObj = params;
27
+ if ("uri" in paramsObj && typeof paramsObj.uri === "string") {
28
+ const uri = paramsObj.uri;
29
+ const normalizedUri = uri.endsWith("/") ? uri : `${uri}/`;
30
+ return { ...paramsObj, uri: normalizedUri };
31
+ }
32
+ return params;
33
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wpnuxt/core",
3
- "version": "2.0.0-alpha.7",
3
+ "version": "2.0.0-alpha.8",
4
4
  "description": "Nuxt module for WordPress integration via GraphQL (WPGraphQL)",
5
5
  "keywords": [
6
6
  "nuxt",
@@ -43,7 +43,7 @@
43
43
  "access": "public"
44
44
  },
45
45
  "dependencies": {
46
- "@nuxt/kit": "4.2.2",
46
+ "@nuxt/kit": "4.3.0",
47
47
  "@radya/nuxt-dompurify": "^1.0.5",
48
48
  "defu": "^6.1.4",
49
49
  "graphql": "^16.12.0",
@@ -53,12 +53,12 @@
53
53
  "devDependencies": {
54
54
  "@nuxt/devtools": "^3.1.1",
55
55
  "@nuxt/module-builder": "^1.0.2",
56
- "@nuxt/schema": "4.2.2",
56
+ "@nuxt/schema": "4.3.0",
57
57
  "@nuxt/test-utils": "^3.23.0",
58
- "@types/node": "^25.0.9",
59
- "nuxt": "4.2.2",
60
- "vitest": "^4.0.17",
61
- "vue-tsc": "^3.2.2"
58
+ "@types/node": "^25.0.10",
59
+ "nuxt": "4.3.0",
60
+ "vitest": "^4.0.18",
61
+ "vue-tsc": "^3.2.3"
62
62
  },
63
63
  "peerDependencies": {
64
64
  "nuxt": "^4.0.0"