@wpnuxt/core 2.2.2 → 2.3.1
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/app/graphqlMiddleware.clientOptions.ts +1 -1
- package/dist/module.d.mts +36 -0
- package/dist/module.d.ts +36 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +339 -57
- package/dist/runtime/composables/useWPContent.js +3 -2
- package/dist/runtime/internal/graphql-client.d.ts +0 -0
- package/dist/runtime/internal/graphql-client.js +73 -0
- package/dist/runtime/types/stub.d.ts +5 -0
- package/dist/runtime/util/content.js +5 -1
- package/package.json +3 -2
package/dist/module.d.mts
CHANGED
|
@@ -127,6 +127,42 @@ interface WPNuxtConfig {
|
|
|
127
127
|
*/
|
|
128
128
|
revalidateSecret?: string;
|
|
129
129
|
};
|
|
130
|
+
/**
|
|
131
|
+
* Auto-generation of fragments + queries for Custom Post Types.
|
|
132
|
+
*
|
|
133
|
+
* WPNuxt parses the downloaded `schema.graphql` at build time, finds every
|
|
134
|
+
* object type implementing `ContentNode`, and emits a base fragment plus
|
|
135
|
+
* `Listing`, `ByUri`, and `BySlug` queries for each discovered CPT. Built-in
|
|
136
|
+
* types (Post, Page, MediaItem, Revision, Comment) are excluded because
|
|
137
|
+
* they already have default queries.
|
|
138
|
+
*
|
|
139
|
+
* Generated files land in `.queries/` and can be fully overridden by
|
|
140
|
+
* dropping a file with the same name in `extend/queries/fragments/`
|
|
141
|
+
* (for fragments) or `extend/queries/` (for queries).
|
|
142
|
+
*/
|
|
143
|
+
cpt?: {
|
|
144
|
+
/**
|
|
145
|
+
* Enable CPT auto-generation.
|
|
146
|
+
*
|
|
147
|
+
* @default true
|
|
148
|
+
*/
|
|
149
|
+
enabled?: boolean;
|
|
150
|
+
/**
|
|
151
|
+
* Type names to skip in addition to the built-in exclusions.
|
|
152
|
+
*
|
|
153
|
+
* @example ['DraftPost', 'InternalNote']
|
|
154
|
+
*/
|
|
155
|
+
exclude?: string[];
|
|
156
|
+
/**
|
|
157
|
+
* If set, only these type names will be auto-generated.
|
|
158
|
+
*
|
|
159
|
+
* Useful when you want fine-grained control over which CPTs get
|
|
160
|
+
* auto-generated output. Built-in exclusions still apply.
|
|
161
|
+
*
|
|
162
|
+
* @example ['Event', 'Artist']
|
|
163
|
+
*/
|
|
164
|
+
include?: string[];
|
|
165
|
+
};
|
|
130
166
|
}
|
|
131
167
|
|
|
132
168
|
declare const _default: _nuxt_schema.NuxtModule<WPNuxtConfig, WPNuxtConfig, false>;
|
package/dist/module.d.ts
CHANGED
|
@@ -127,6 +127,42 @@ interface WPNuxtConfig {
|
|
|
127
127
|
*/
|
|
128
128
|
revalidateSecret?: string;
|
|
129
129
|
};
|
|
130
|
+
/**
|
|
131
|
+
* Auto-generation of fragments + queries for Custom Post Types.
|
|
132
|
+
*
|
|
133
|
+
* WPNuxt parses the downloaded `schema.graphql` at build time, finds every
|
|
134
|
+
* object type implementing `ContentNode`, and emits a base fragment plus
|
|
135
|
+
* `Listing`, `ByUri`, and `BySlug` queries for each discovered CPT. Built-in
|
|
136
|
+
* types (Post, Page, MediaItem, Revision, Comment) are excluded because
|
|
137
|
+
* they already have default queries.
|
|
138
|
+
*
|
|
139
|
+
* Generated files land in `.queries/` and can be fully overridden by
|
|
140
|
+
* dropping a file with the same name in `extend/queries/fragments/`
|
|
141
|
+
* (for fragments) or `extend/queries/` (for queries).
|
|
142
|
+
*/
|
|
143
|
+
cpt?: {
|
|
144
|
+
/**
|
|
145
|
+
* Enable CPT auto-generation.
|
|
146
|
+
*
|
|
147
|
+
* @default true
|
|
148
|
+
*/
|
|
149
|
+
enabled?: boolean;
|
|
150
|
+
/**
|
|
151
|
+
* Type names to skip in addition to the built-in exclusions.
|
|
152
|
+
*
|
|
153
|
+
* @example ['DraftPost', 'InternalNote']
|
|
154
|
+
*/
|
|
155
|
+
exclude?: string[];
|
|
156
|
+
/**
|
|
157
|
+
* If set, only these type names will be auto-generated.
|
|
158
|
+
*
|
|
159
|
+
* Useful when you want fine-grained control over which CPTs get
|
|
160
|
+
* auto-generated output. Built-in exclusions still apply.
|
|
161
|
+
*
|
|
162
|
+
* @example ['Event', 'Artist']
|
|
163
|
+
*/
|
|
164
|
+
include?: string[];
|
|
165
|
+
};
|
|
130
166
|
}
|
|
131
167
|
|
|
132
168
|
declare const _default: _nuxt_schema.NuxtModule<WPNuxtConfig, WPNuxtConfig, false>;
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -1,14 +1,175 @@
|
|
|
1
1
|
import { defu } from 'defu';
|
|
2
|
-
import { promises, cpSync,
|
|
3
|
-
import { writeFile, rename, readFile
|
|
2
|
+
import { existsSync, readFileSync, promises, cpSync, readdirSync, statSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { mkdir, writeFile, rename, readFile } from 'node:fs/promises';
|
|
4
4
|
import { join, relative, dirname } from 'node:path';
|
|
5
5
|
import { useLogger, createResolver, resolveFiles, defineNuxtModule, addPlugin, addServerHandler, addImports, addComponentsDir, addTemplate, addTypeTemplate, hasNuxtModule, installModule } from '@nuxt/kit';
|
|
6
6
|
import { upperFirst } from 'scule';
|
|
7
7
|
import { parse, GraphQLError, visit, print } from 'graphql';
|
|
8
|
+
import ts from 'typescript';
|
|
8
9
|
import { execSync } from 'node:child_process';
|
|
9
10
|
import { consola } from 'consola';
|
|
10
11
|
|
|
11
|
-
const version = "2.
|
|
12
|
+
const version = "2.3.1";
|
|
13
|
+
|
|
14
|
+
const DEFAULT_CPT_EXCLUSIONS = [
|
|
15
|
+
"Post",
|
|
16
|
+
"Page",
|
|
17
|
+
"MediaItem",
|
|
18
|
+
"Revision",
|
|
19
|
+
"Comment",
|
|
20
|
+
"ActionMonitorAction"
|
|
21
|
+
];
|
|
22
|
+
function discoverCpts(schemaPath, options = {}) {
|
|
23
|
+
if (!existsSync(schemaPath)) return [];
|
|
24
|
+
let ast;
|
|
25
|
+
try {
|
|
26
|
+
ast = parse(readFileSync(schemaPath, "utf8"), { noLocation: true });
|
|
27
|
+
} catch {
|
|
28
|
+
return [];
|
|
29
|
+
}
|
|
30
|
+
const typesByName = /* @__PURE__ */ new Map();
|
|
31
|
+
const enumsByName = /* @__PURE__ */ new Map();
|
|
32
|
+
for (const def of ast.definitions) {
|
|
33
|
+
if (def.kind === "ObjectTypeDefinition") typesByName.set(def.name.value, def);
|
|
34
|
+
else if (def.kind === "EnumTypeDefinition") enumsByName.set(def.name.value, def);
|
|
35
|
+
}
|
|
36
|
+
const rootQuery = typesByName.get("RootQuery");
|
|
37
|
+
if (!rootQuery?.fields) return [];
|
|
38
|
+
const fieldsByType = /* @__PURE__ */ new Map();
|
|
39
|
+
for (const field of rootQuery.fields) {
|
|
40
|
+
const returnType = unwrapNamedType(field.type);
|
|
41
|
+
if (!returnType) continue;
|
|
42
|
+
const hasIdType = field.arguments?.some((a) => a.name.value === "idType");
|
|
43
|
+
if (typesByName.has(returnType) && hasIdType) {
|
|
44
|
+
const idTypeArg = field.arguments?.find((a) => a.name.value === "idType");
|
|
45
|
+
const idTypeEnum = idTypeArg ? unwrapNamedType(idTypeArg.type) : void 0;
|
|
46
|
+
const entry = fieldsByType.get(returnType) ?? {};
|
|
47
|
+
entry.single = field.name.value;
|
|
48
|
+
entry.idTypeEnum = idTypeEnum;
|
|
49
|
+
fieldsByType.set(returnType, entry);
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
const connMatch = /^RootQueryTo(\w+)Connection$/.exec(returnType);
|
|
53
|
+
const hasCursorArgs = field.arguments?.some((a) => a.name.value === "first") && field.arguments?.some((a) => a.name.value === "after");
|
|
54
|
+
if (connMatch && hasCursorArgs) {
|
|
55
|
+
const typeName = connMatch[1];
|
|
56
|
+
const entry = fieldsByType.get(typeName) ?? {};
|
|
57
|
+
entry.connection = field.name.value;
|
|
58
|
+
fieldsByType.set(typeName, entry);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
const excludeSet = /* @__PURE__ */ new Set([...DEFAULT_CPT_EXCLUSIONS, ...options.exclude ?? []]);
|
|
62
|
+
const includeSet = options.include?.length ? new Set(options.include) : void 0;
|
|
63
|
+
const result = [];
|
|
64
|
+
for (const [typeName, node] of typesByName) {
|
|
65
|
+
if (!typeImplements(node, "ContentNode")) continue;
|
|
66
|
+
if (excludeSet.has(typeName)) continue;
|
|
67
|
+
if (includeSet && !includeSet.has(typeName)) continue;
|
|
68
|
+
const queryFields = fieldsByType.get(typeName);
|
|
69
|
+
if (!queryFields?.single || !queryFields.connection) continue;
|
|
70
|
+
const supportedIdTypes = /* @__PURE__ */ new Set();
|
|
71
|
+
if (queryFields.idTypeEnum) {
|
|
72
|
+
const enumDef = enumsByName.get(queryFields.idTypeEnum);
|
|
73
|
+
enumDef?.values?.forEach((v) => supportedIdTypes.add(v.name.value));
|
|
74
|
+
}
|
|
75
|
+
result.push({
|
|
76
|
+
typeName,
|
|
77
|
+
singleField: queryFields.single,
|
|
78
|
+
connectionField: queryFields.connection,
|
|
79
|
+
idTypeEnum: queryFields.idTypeEnum,
|
|
80
|
+
supportedIdTypes,
|
|
81
|
+
interfaces: new Set((node.interfaces ?? []).map((i) => i.name.value)),
|
|
82
|
+
hasField: collectScalarFieldFlags(node, ["title", "slug", "uri", "date"])
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
return result.sort((a, b) => a.typeName.localeCompare(b.typeName));
|
|
86
|
+
}
|
|
87
|
+
function unwrapNamedType(type) {
|
|
88
|
+
let current = type;
|
|
89
|
+
while (current) {
|
|
90
|
+
if (current.kind === "NamedType") return current.name.value;
|
|
91
|
+
current = current.type;
|
|
92
|
+
}
|
|
93
|
+
return void 0;
|
|
94
|
+
}
|
|
95
|
+
function typeImplements(node, interfaceName) {
|
|
96
|
+
return node.interfaces?.some((i) => i.name.value === interfaceName) ?? false;
|
|
97
|
+
}
|
|
98
|
+
function collectScalarFieldFlags(node, fieldNames) {
|
|
99
|
+
const flags = {};
|
|
100
|
+
const declared = new Set((node.fields ?? []).map((f) => f.name.value));
|
|
101
|
+
for (const name of fieldNames) flags[name] = declared.has(name);
|
|
102
|
+
return flags;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async function writeCptArtifacts(cpt, outputPath) {
|
|
106
|
+
const fragmentsDir = join(outputPath, "fragments");
|
|
107
|
+
const fragmentPath = join(fragmentsDir, `${cpt.typeName}.fragment.gql`);
|
|
108
|
+
const queryPath = join(outputPath, `${upperFirst(cpt.connectionField)}.gql`);
|
|
109
|
+
const wroteFragment = !existsSync(fragmentPath);
|
|
110
|
+
const wroteQueries = !existsSync(queryPath);
|
|
111
|
+
if (wroteFragment) {
|
|
112
|
+
await mkdir(fragmentsDir, { recursive: true });
|
|
113
|
+
await writeFile(fragmentPath, buildCptFragment(cpt), "utf-8");
|
|
114
|
+
}
|
|
115
|
+
if (wroteQueries) {
|
|
116
|
+
await writeFile(queryPath, buildCptQueries(cpt), "utf-8");
|
|
117
|
+
}
|
|
118
|
+
return { cpt, wroteFragment, wroteQueries };
|
|
119
|
+
}
|
|
120
|
+
function buildCptFragment(cpt) {
|
|
121
|
+
const lines = [`fragment ${cpt.typeName} on ${cpt.typeName} {`];
|
|
122
|
+
lines.push(" ...ContentNode");
|
|
123
|
+
if (cpt.interfaces.has("NodeWithExcerpt")) lines.push(" ...NodeWithExcerpt");
|
|
124
|
+
if (cpt.interfaces.has("NodeWithContentEditor")) lines.push(" ...NodeWithContentEditor");
|
|
125
|
+
if (cpt.interfaces.has("NodeWithFeaturedImage")) lines.push(" ...NodeWithFeaturedImage");
|
|
126
|
+
if (cpt.hasField.title) lines.push(" title");
|
|
127
|
+
lines.push("}");
|
|
128
|
+
lines.push("");
|
|
129
|
+
return lines.join("\n");
|
|
130
|
+
}
|
|
131
|
+
function buildCptQueries(cpt) {
|
|
132
|
+
const { typeName, singleField, connectionField, supportedIdTypes } = cpt;
|
|
133
|
+
const listQueryName = upperFirst(connectionField);
|
|
134
|
+
const queries = [];
|
|
135
|
+
queries.push(
|
|
136
|
+
`query ${listQueryName}($first: Int = 20, $after: String) {`,
|
|
137
|
+
` ${connectionField}(first: $first, after: $after) {`,
|
|
138
|
+
` nodes {`,
|
|
139
|
+
` ...${typeName}`,
|
|
140
|
+
` }`,
|
|
141
|
+
` pageInfo {`,
|
|
142
|
+
` hasNextPage`,
|
|
143
|
+
` hasPreviousPage`,
|
|
144
|
+
` startCursor`,
|
|
145
|
+
` endCursor`,
|
|
146
|
+
` }`,
|
|
147
|
+
` }`,
|
|
148
|
+
`}`,
|
|
149
|
+
""
|
|
150
|
+
);
|
|
151
|
+
if (supportedIdTypes.has("URI")) {
|
|
152
|
+
queries.push(
|
|
153
|
+
`query ${typeName}ByUri($uri: ID!) {`,
|
|
154
|
+
` ${singleField}(id: $uri, idType: URI) {`,
|
|
155
|
+
` ...${typeName}`,
|
|
156
|
+
` }`,
|
|
157
|
+
`}`,
|
|
158
|
+
""
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
if (supportedIdTypes.has("SLUG")) {
|
|
162
|
+
queries.push(
|
|
163
|
+
`query ${typeName}BySlug($slug: ID!) {`,
|
|
164
|
+
` ${singleField}(id: $slug, idType: SLUG) {`,
|
|
165
|
+
` ...${typeName}`,
|
|
166
|
+
` }`,
|
|
167
|
+
`}`,
|
|
168
|
+
""
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
return queries.join("\n");
|
|
172
|
+
}
|
|
12
173
|
|
|
13
174
|
function createModuleError(module, message) {
|
|
14
175
|
return new Error(formatErrorMessage(module, message));
|
|
@@ -51,7 +212,7 @@ const initLogger = (debug) => {
|
|
|
51
212
|
function getLogger() {
|
|
52
213
|
return loggerInstance;
|
|
53
214
|
}
|
|
54
|
-
async function mergeQueries(nuxt, wpNuxtConfig, resolver) {
|
|
215
|
+
async function mergeQueries(nuxt, wpNuxtConfig, resolver, schemaPath) {
|
|
55
216
|
const logger = getLogger();
|
|
56
217
|
const baseDir = nuxt.options.srcDir || nuxt.options.rootDir;
|
|
57
218
|
const { resolve } = createResolver(baseDir);
|
|
@@ -60,6 +221,20 @@ async function mergeQueries(nuxt, wpNuxtConfig, resolver) {
|
|
|
60
221
|
const defaultQueriesPath = resolver.resolve("./runtime/queries");
|
|
61
222
|
await promises.rm(queryOutputPath, { recursive: true, force: true });
|
|
62
223
|
cpSync(defaultQueriesPath, queryOutputPath, { recursive: true });
|
|
224
|
+
const cptSpreads = [];
|
|
225
|
+
if (schemaPath && wpNuxtConfig.cpt?.enabled !== false) {
|
|
226
|
+
const cpts = discoverCpts(schemaPath, {
|
|
227
|
+
exclude: wpNuxtConfig.cpt?.exclude,
|
|
228
|
+
include: wpNuxtConfig.cpt?.include
|
|
229
|
+
});
|
|
230
|
+
for (const cpt of cpts) {
|
|
231
|
+
await writeCptArtifacts(cpt, queryOutputPath);
|
|
232
|
+
cptSpreads.push({ name: cpt.typeName, type: cpt.typeName });
|
|
233
|
+
}
|
|
234
|
+
if (cpts.length) {
|
|
235
|
+
logger.debug(`Auto-generated fragments + queries for CPTs: ${cpts.map((c) => c.typeName).join(", ")}`);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
63
238
|
const conflicts = findConflicts(userQueryPath, queryOutputPath);
|
|
64
239
|
if (conflicts.length && wpNuxtConfig.queries.warnOnOverride) {
|
|
65
240
|
logger.warn("The following user query files will override default queries:");
|
|
@@ -71,26 +246,29 @@ async function mergeQueries(nuxt, wpNuxtConfig, resolver) {
|
|
|
71
246
|
logger.debug("Extending queries:", userQueryPath);
|
|
72
247
|
copyGraphqlFiles(userQueryPath, queryOutputPath);
|
|
73
248
|
}
|
|
74
|
-
await addCustomFragmentsToNodeQuery(queryOutputPath, userQueryPath, logger);
|
|
249
|
+
await addCustomFragmentsToNodeQuery(queryOutputPath, userQueryPath, logger, cptSpreads);
|
|
75
250
|
logger.debug("Merged queries folder:", queryOutputPath);
|
|
76
251
|
return queryOutputPath;
|
|
77
252
|
}
|
|
78
253
|
const FRAGMENT_DEF_PATTERN = /fragment\s+(\w+)\s+on\s+(\w+)/;
|
|
79
|
-
async function addCustomFragmentsToNodeQuery(queryOutputPath, userQueryPath, logger) {
|
|
254
|
+
async function addCustomFragmentsToNodeQuery(queryOutputPath, userQueryPath, logger, preDiscovered = []) {
|
|
255
|
+
const byType = /* @__PURE__ */ new Map();
|
|
256
|
+
for (const f of preDiscovered) byType.set(f.type, f);
|
|
80
257
|
const userFragmentsDir = join(userQueryPath, "fragments");
|
|
81
|
-
if (
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
258
|
+
if (existsSync(userFragmentsDir)) {
|
|
259
|
+
for (const file of readdirSync(userFragmentsDir)) {
|
|
260
|
+
if (!file.endsWith(".gql") && !file.endsWith(".graphql")) continue;
|
|
261
|
+
const content = await promises.readFile(join(userFragmentsDir, file), "utf-8");
|
|
262
|
+
const match = content.match(FRAGMENT_DEF_PATTERN);
|
|
263
|
+
if (!match) continue;
|
|
264
|
+
const name = match[1];
|
|
265
|
+
const type = match[2];
|
|
266
|
+
if (name && type && name === type) {
|
|
267
|
+
byType.set(type, { name, type });
|
|
268
|
+
}
|
|
92
269
|
}
|
|
93
270
|
}
|
|
271
|
+
const customFragments = [...byType.values()];
|
|
94
272
|
if (customFragments.length === 0) return;
|
|
95
273
|
const nodeGqlPath = join(queryOutputPath, "Node.gql");
|
|
96
274
|
if (!existsSync(nodeGqlPath)) return;
|
|
@@ -204,6 +382,46 @@ function processSelections(selections, level, query, canExtract = true) {
|
|
|
204
382
|
}
|
|
205
383
|
const parseDoc = _parseDoc;
|
|
206
384
|
|
|
385
|
+
const { factory, SyntaxKind, NewLineKind, EmitHint, ScriptTarget } = ts;
|
|
386
|
+
function typeRef(name, typeArguments) {
|
|
387
|
+
return factory.createTypeReferenceNode(name, typeArguments);
|
|
388
|
+
}
|
|
389
|
+
function nonNullable(node) {
|
|
390
|
+
return typeRef("NonNullable", [node]);
|
|
391
|
+
}
|
|
392
|
+
function withImagePath(node) {
|
|
393
|
+
return typeRef("WithImagePath", [node]);
|
|
394
|
+
}
|
|
395
|
+
function indexedAccess(target, key) {
|
|
396
|
+
return factory.createIndexedAccessTypeNode(
|
|
397
|
+
target,
|
|
398
|
+
// `isSingleQuote` preserves the single-quote style the generator used
|
|
399
|
+
// before this module was AST-based — keeps generated .d.ts text-stable.
|
|
400
|
+
factory.createLiteralTypeNode(factory.createStringLiteral(key, true))
|
|
401
|
+
);
|
|
402
|
+
}
|
|
403
|
+
function numberIndexedAccess(target) {
|
|
404
|
+
return factory.createIndexedAccessTypeNode(
|
|
405
|
+
target,
|
|
406
|
+
factory.createKeywordTypeNode(SyntaxKind.NumberKeyword)
|
|
407
|
+
);
|
|
408
|
+
}
|
|
409
|
+
function arrayOf(node) {
|
|
410
|
+
return factory.createArrayTypeNode(node);
|
|
411
|
+
}
|
|
412
|
+
function unionOrSingle(nodes) {
|
|
413
|
+
if (nodes.length === 0) {
|
|
414
|
+
throw new Error("unionOrSingle: empty type list");
|
|
415
|
+
}
|
|
416
|
+
if (nodes.length === 1) return nodes[0];
|
|
417
|
+
return factory.createUnionTypeNode(nodes);
|
|
418
|
+
}
|
|
419
|
+
const printer = ts.createPrinter({ newLine: NewLineKind.LineFeed, removeComments: false });
|
|
420
|
+
const syntheticFile = ts.createSourceFile("__synthetic.ts", "", ScriptTarget.Latest);
|
|
421
|
+
function printType(node) {
|
|
422
|
+
return printer.printNode(EmitHint.Unspecified, node, syntheticFile);
|
|
423
|
+
}
|
|
424
|
+
|
|
207
425
|
const SCHEMA_PATTERN = /schema\.(?:gql|graphql)$/i;
|
|
208
426
|
const COMPLEXITY_THRESHOLDS = {
|
|
209
427
|
/** Maximum recommended extraction depth */
|
|
@@ -257,37 +475,40 @@ async function prepareContext(ctx) {
|
|
|
257
475
|
const fnName = (fn) => ctx.composablesPrefix + upperFirst(fn);
|
|
258
476
|
const mutationFnName = (fn) => `useMutation${upperFirst(fn)}`;
|
|
259
477
|
const formatNodes = (nodes) => nodes?.map((n) => `'${n}'`).join(",") ?? "";
|
|
260
|
-
const
|
|
478
|
+
const buildFragmentTypeNode = (q) => {
|
|
261
479
|
if (q.hasPageInfo) {
|
|
262
480
|
if (q.hasInlineFields || !q.fragments?.length) {
|
|
263
481
|
if (q.nodes?.length) {
|
|
264
|
-
let
|
|
265
|
-
for (const
|
|
266
|
-
|
|
482
|
+
let node = typeRef(`${q.name}RootQuery`);
|
|
483
|
+
for (const segment of q.nodes) {
|
|
484
|
+
node = indexedAccess(nonNullable(node), segment);
|
|
267
485
|
}
|
|
268
|
-
|
|
269
|
-
return typePath;
|
|
486
|
+
return numberIndexedAccess(indexedAccess(nonNullable(node), "nodes"));
|
|
270
487
|
}
|
|
271
|
-
return `${q.name}RootQuery
|
|
488
|
+
return typeRef(`${q.name}RootQuery`);
|
|
272
489
|
}
|
|
273
|
-
return q.fragments.map((f) =>
|
|
490
|
+
return unionOrSingle(q.fragments.map((f) => withImagePath(typeRef(`${f}Fragment`))));
|
|
274
491
|
}
|
|
275
492
|
if (q.hasInlineFields || !q.fragments?.length) {
|
|
276
493
|
if (q.nodes?.length) {
|
|
277
|
-
let
|
|
278
|
-
for (const
|
|
279
|
-
|
|
494
|
+
let node = typeRef(`${q.name}RootQuery`);
|
|
495
|
+
for (const segment of q.nodes) {
|
|
496
|
+
node = indexedAccess(nonNullable(node), segment);
|
|
280
497
|
}
|
|
281
498
|
if (q.nodes.includes("nodes")) {
|
|
282
|
-
|
|
499
|
+
node = numberIndexedAccess(node);
|
|
283
500
|
}
|
|
284
|
-
return
|
|
501
|
+
return node;
|
|
285
502
|
}
|
|
286
|
-
return `${q.name}RootQuery
|
|
503
|
+
return typeRef(`${q.name}RootQuery`);
|
|
504
|
+
}
|
|
505
|
+
const fragmentTypes = q.fragments.map((f) => withImagePath(typeRef(`${f}Fragment`)));
|
|
506
|
+
if (q.nodes?.includes("nodes")) {
|
|
507
|
+
return unionOrSingle(fragmentTypes.map(arrayOf));
|
|
287
508
|
}
|
|
288
|
-
|
|
289
|
-
return q.fragments.map((f) => `WithImagePath<${f}Fragment>${fragmentSuffix}`).join(" | ");
|
|
509
|
+
return unionOrSingle(fragmentTypes);
|
|
290
510
|
};
|
|
511
|
+
const getFragmentType = (q) => printType(buildFragmentTypeNode(q));
|
|
291
512
|
const queryFnExp = (q, typed = false) => {
|
|
292
513
|
const functionName = fnName(q.name);
|
|
293
514
|
if (q.hasPageInfo) {
|
|
@@ -304,7 +525,7 @@ async function prepareContext(ctx) {
|
|
|
304
525
|
const mutationFnExp = (m, typed = false) => {
|
|
305
526
|
const functionName = mutationFnName(m.name);
|
|
306
527
|
if (!typed) {
|
|
307
|
-
return `export const ${functionName} = (variables, options) =>
|
|
528
|
+
return `export const ${functionName} = (variables, options) => wpMutation('${m.name}', variables, options)`;
|
|
308
529
|
}
|
|
309
530
|
return ` export const ${functionName}: (variables: ${m.name}MutationVariables, options?: WPMutationOptions) => Promise<WPMutationResult<${m.name}Mutation>>`;
|
|
310
531
|
};
|
|
@@ -319,11 +540,13 @@ async function prepareContext(ctx) {
|
|
|
319
540
|
if (hasConnectionQueries) {
|
|
320
541
|
imports.push("useWPConnection");
|
|
321
542
|
}
|
|
322
|
-
if (mutations.length > 0) {
|
|
323
|
-
imports.push("useGraphqlMutation");
|
|
324
|
-
}
|
|
325
543
|
if (imports.length > 0) {
|
|
326
544
|
lines.push(`import { ${imports.join(", ")} } from '#imports'`);
|
|
545
|
+
}
|
|
546
|
+
if (mutations.length > 0) {
|
|
547
|
+
lines.push(`import { wpMutation } from '#wpnuxt-internal'`);
|
|
548
|
+
}
|
|
549
|
+
if (lines.length > 0) {
|
|
327
550
|
lines.push("");
|
|
328
551
|
}
|
|
329
552
|
queries.forEach((f) => {
|
|
@@ -347,6 +570,7 @@ async function prepareContext(ctx) {
|
|
|
347
570
|
typeSet.add(`${m.name}MutationVariables`);
|
|
348
571
|
typeSet.add(`${m.name}Mutation`);
|
|
349
572
|
});
|
|
573
|
+
ctx.referencedTypes = [...typeSet];
|
|
350
574
|
ctx.generateDeclarations = () => {
|
|
351
575
|
const declarations = [
|
|
352
576
|
`import type { ${[...typeSet].join(", ")} } from '#build/graphql-operations'`,
|
|
@@ -518,7 +742,7 @@ Make sure WPGraphQL plugin is installed and activated on your WordPress site.`
|
|
|
518
742
|
);
|
|
519
743
|
}
|
|
520
744
|
await checkWPGraphQLVersion(fullUrl, headers);
|
|
521
|
-
if (options.schemaPath
|
|
745
|
+
if (options.schemaPath) {
|
|
522
746
|
try {
|
|
523
747
|
const authFlag = options.authToken ? ` -h "Authorization=Bearer ${options.authToken}"` : "";
|
|
524
748
|
execSync(`npx get-graphql-schema "${fullUrl}"${authFlag} > "${options.schemaPath}"`, {
|
|
@@ -602,7 +826,25 @@ async function checkWPGraphQLVersion(fullUrl, headers) {
|
|
|
602
826
|
clearTimeout(timeout);
|
|
603
827
|
if (!response.ok) return;
|
|
604
828
|
const data = await response.json();
|
|
605
|
-
const
|
|
829
|
+
const errorMessage = data?.errors?.[0]?.message || "";
|
|
830
|
+
if (/introspection/i.test(errorMessage)) {
|
|
831
|
+
throw new Error(
|
|
832
|
+
`[wpnuxt:core] WPGraphQL Public Introspection is disabled on this endpoint.
|
|
833
|
+
|
|
834
|
+
URL: ${fullUrl}
|
|
835
|
+
|
|
836
|
+
WPNuxt needs introspection to validate the schema and generate types.
|
|
837
|
+
|
|
838
|
+
To fix:
|
|
839
|
+
- Enable "Enable Public Introspection" under WPGraphQL \u2192 Settings in the WordPress admin.
|
|
840
|
+
- Or provide authenticated credentials that are permitted to introspect the schema.
|
|
841
|
+
|
|
842
|
+
WPGraphQL error: ${errorMessage}`
|
|
843
|
+
);
|
|
844
|
+
}
|
|
845
|
+
const type = data?.data?.__type;
|
|
846
|
+
if (!type) return;
|
|
847
|
+
const fields = type.fields || [];
|
|
606
848
|
const hasFilePath = fields.some((f) => f.name === "filePath");
|
|
607
849
|
if (!hasFilePath) {
|
|
608
850
|
throw new Error(
|
|
@@ -649,6 +891,40 @@ function patchWPGraphQLSchema(schemaPath) {
|
|
|
649
891
|
writeFileSync(schemaPath, patchSchemaText(schema));
|
|
650
892
|
}
|
|
651
893
|
|
|
894
|
+
function validateGeneratedPaths(referencedTypes, operationsDtsPath) {
|
|
895
|
+
if (!existsSync(operationsDtsPath)) {
|
|
896
|
+
return { skipped: true, dangling: [] };
|
|
897
|
+
}
|
|
898
|
+
const source = readFileSync(operationsDtsPath, "utf8");
|
|
899
|
+
const sourceFile = ts.createSourceFile(
|
|
900
|
+
operationsDtsPath,
|
|
901
|
+
source,
|
|
902
|
+
ts.ScriptTarget.Latest,
|
|
903
|
+
false
|
|
904
|
+
);
|
|
905
|
+
const declared = /* @__PURE__ */ new Set();
|
|
906
|
+
const visit = (node) => {
|
|
907
|
+
if (ts.isTypeAliasDeclaration(node) || ts.isInterfaceDeclaration(node) || ts.isClassDeclaration(node)) {
|
|
908
|
+
if (node.name) declared.add(node.name.text);
|
|
909
|
+
} else if (ts.isVariableStatement(node)) {
|
|
910
|
+
for (const d of node.declarationList.declarations) {
|
|
911
|
+
if (ts.isIdentifier(d.name)) declared.add(d.name.text);
|
|
912
|
+
}
|
|
913
|
+
} else if (ts.isModuleDeclaration(node) && node.body && ts.isModuleBlock(node.body)) {
|
|
914
|
+
for (const child of node.body.statements) visit(child);
|
|
915
|
+
}
|
|
916
|
+
};
|
|
917
|
+
for (const stmt of sourceFile.statements) visit(stmt);
|
|
918
|
+
const dangling = [];
|
|
919
|
+
const seen = /* @__PURE__ */ new Set();
|
|
920
|
+
for (const ref of referencedTypes) {
|
|
921
|
+
if (seen.has(ref)) continue;
|
|
922
|
+
seen.add(ref);
|
|
923
|
+
if (!declared.has(ref)) dangling.push(ref);
|
|
924
|
+
}
|
|
925
|
+
return { skipped: false, dangling };
|
|
926
|
+
}
|
|
927
|
+
|
|
652
928
|
async function runInstall(nuxt) {
|
|
653
929
|
const logger = useLogger("wpnuxt", {
|
|
654
930
|
level: process.env.WPNUXT_DEBUG === "true" ? 4 : 3
|
|
@@ -872,6 +1148,11 @@ const module$1 = defineNuxtModule({
|
|
|
872
1148
|
maxAge: 60 * 5,
|
|
873
1149
|
// 5 minutes
|
|
874
1150
|
swr: true
|
|
1151
|
+
},
|
|
1152
|
+
cpt: {
|
|
1153
|
+
enabled: true,
|
|
1154
|
+
exclude: [],
|
|
1155
|
+
include: []
|
|
875
1156
|
}
|
|
876
1157
|
},
|
|
877
1158
|
async setup(options, nuxt) {
|
|
@@ -890,7 +1171,6 @@ const module$1 = defineNuxtModule({
|
|
|
890
1171
|
addPlugin(resolver.resolve("./runtime/plugins/graphqlErrors"));
|
|
891
1172
|
addPlugin(resolver.resolve("./runtime/plugins/sanitizeHtml"));
|
|
892
1173
|
configureTrailingSlash(nuxt, logger);
|
|
893
|
-
const mergedQueriesFolder = await mergeQueries(nuxt, wpNuxtConfig, resolver);
|
|
894
1174
|
const packageRoot = resolver.resolve("..");
|
|
895
1175
|
nuxt.options._layers.push({
|
|
896
1176
|
cwd: packageRoot,
|
|
@@ -906,31 +1186,21 @@ const module$1 = defineNuxtModule({
|
|
|
906
1186
|
const schemaPath = join(nuxt.options.rootDir, "schema.graphql");
|
|
907
1187
|
const schemaExists = existsSync(schemaPath);
|
|
908
1188
|
if (wpNuxtConfig.downloadSchema) {
|
|
909
|
-
|
|
910
|
-
|
|
1189
|
+
logger.debug(`Downloading schema from: ${wpNuxtConfig.wordpressUrl}${wpNuxtConfig.graphqlEndpoint}`);
|
|
1190
|
+
try {
|
|
911
1191
|
await validateWordPressEndpoint(
|
|
912
1192
|
wpNuxtConfig.wordpressUrl,
|
|
913
1193
|
wpNuxtConfig.graphqlEndpoint,
|
|
914
1194
|
{ schemaPath, authToken: wpNuxtConfig.schemaAuthToken }
|
|
915
1195
|
);
|
|
916
1196
|
logger.debug("Schema downloaded successfully");
|
|
917
|
-
}
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
wpNuxtConfig.wordpressUrl,
|
|
922
|
-
wpNuxtConfig.graphqlEndpoint,
|
|
923
|
-
{ authToken: wpNuxtConfig.schemaAuthToken }
|
|
924
|
-
);
|
|
925
|
-
logger.debug("WordPress endpoint validation passed");
|
|
926
|
-
} catch (error) {
|
|
927
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
928
|
-
logger.warn(`WordPress endpoint validation failed: ${message.split("\n")[0]}`);
|
|
929
|
-
logger.warn("App will continue with existing schema.graphql file");
|
|
930
|
-
}
|
|
931
|
-
});
|
|
1197
|
+
} catch (error) {
|
|
1198
|
+
if (!schemaExists) throw error;
|
|
1199
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1200
|
+
logger.warn(`Schema refresh failed, using cached schema.graphql: ${message.split("\n")[0]}`);
|
|
932
1201
|
}
|
|
933
1202
|
}
|
|
1203
|
+
const mergedQueriesFolder = await mergeQueries(nuxt, wpNuxtConfig, resolver, schemaPath);
|
|
934
1204
|
await registerModules(nuxt, resolver, wpNuxtConfig, mergedQueriesFolder);
|
|
935
1205
|
nuxt.hook("devtools:customTabs", (tabs) => {
|
|
936
1206
|
const middlewareTab = tabs.find((tab) => tab.name === "nuxt-graphql-middleware");
|
|
@@ -1031,12 +1301,14 @@ const module$1 = defineNuxtModule({
|
|
|
1031
1301
|
nuxt.options.alias["#wpnuxt"] = resolver.resolve(nuxt.options.buildDir, "wpnuxt");
|
|
1032
1302
|
nuxt.options.alias["#wpnuxt/*"] = resolver.resolve(nuxt.options.buildDir, "wpnuxt", "*");
|
|
1033
1303
|
nuxt.options.alias["#wpnuxt/types"] = resolver.resolve("./types");
|
|
1304
|
+
nuxt.options.alias["#wpnuxt-internal"] = resolver.resolve("./runtime/internal/graphql-client");
|
|
1034
1305
|
nuxt.options.alias["@wpnuxt/core/server-options"] = resolver.resolve("./server-options");
|
|
1035
1306
|
nuxt.options.alias["@wpnuxt/core/client-options"] = resolver.resolve("./client-options");
|
|
1036
1307
|
const nitroOpts = nuxt.options;
|
|
1037
1308
|
nitroOpts.nitro = nitroOpts.nitro || {};
|
|
1038
1309
|
nitroOpts.nitro.alias = nitroOpts.nitro.alias || {};
|
|
1039
1310
|
nitroOpts.nitro.alias["#wpnuxt/types"] = resolver.resolve("./types");
|
|
1311
|
+
nitroOpts.nitro.alias["#wpnuxt-internal"] = resolver.resolve("./runtime/internal/graphql-client");
|
|
1040
1312
|
nitroOpts.nitro.externals = nitroOpts.nitro.externals || {};
|
|
1041
1313
|
nitroOpts.nitro.externals.inline = nitroOpts.nitro.externals.inline || [];
|
|
1042
1314
|
addTemplate({
|
|
@@ -1053,6 +1325,16 @@ const module$1 = defineNuxtModule({
|
|
|
1053
1325
|
autoimports.push(...ctx.fnImports || []);
|
|
1054
1326
|
});
|
|
1055
1327
|
logger.trace("Finished generating composables");
|
|
1328
|
+
nuxt.hook("build:before", () => {
|
|
1329
|
+
if (!ctx.referencedTypes?.length) return;
|
|
1330
|
+
const operationsDtsPath = join(nuxt.options.buildDir, "graphql-operations.d.ts");
|
|
1331
|
+
const result = validateGeneratedPaths(ctx.referencedTypes, operationsDtsPath);
|
|
1332
|
+
if (result.skipped || result.dangling.length === 0) return;
|
|
1333
|
+
logger.warn(
|
|
1334
|
+
`WPNuxt generated composables reference ${result.dangling.length} type(s) not declared in graphql-operations.d.ts. This usually means your WordPress GraphQL schema has drifted from your queries; delete schema.graphql to force a fresh download, then re-run pnpm dev:prepare.`
|
|
1335
|
+
);
|
|
1336
|
+
for (const t of result.dangling) logger.warn(` - ${t}`);
|
|
1337
|
+
});
|
|
1056
1338
|
logger.info(`WPNuxt module loaded in ${(/* @__PURE__ */ new Date()).getTime() - startTime}ms`);
|
|
1057
1339
|
},
|
|
1058
1340
|
async onInstall(nuxt) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { transformData, normalizeUriParam } from "../util/content.js";
|
|
2
|
-
import { computed, ref, toValue, watch as vueWatch,
|
|
2
|
+
import { computed, ref, toValue, watch as vueWatch, useRuntimeConfig } from "#imports";
|
|
3
|
+
import { wpQuery } from "../internal/graphql-client.js";
|
|
3
4
|
const defaultGetCachedData = (key, app, ctx) => {
|
|
4
5
|
if (app.isHydrating) {
|
|
5
6
|
return app.payload.data[key];
|
|
@@ -58,7 +59,7 @@ export const useWPContent = (queryName, nodes, fixImagePaths, params, options) =
|
|
|
58
59
|
}
|
|
59
60
|
}
|
|
60
61
|
};
|
|
61
|
-
const asyncResult =
|
|
62
|
+
const asyncResult = wpQuery(
|
|
62
63
|
String(queryName),
|
|
63
64
|
resolvedParams,
|
|
64
65
|
asyncDataOptions
|
|
File without changes
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { useAsyncGraphqlQuery, useGraphqlMutation, useNuxtApp, watch, toValue } from "#imports";
|
|
2
|
+
export const wpQuery = ((...args) => {
|
|
3
|
+
const [name, variables] = args;
|
|
4
|
+
const nuxtApp = useNuxtApp();
|
|
5
|
+
const result = useAsyncGraphqlQuery(...args);
|
|
6
|
+
let wasPending = false;
|
|
7
|
+
let cycleStart = 0;
|
|
8
|
+
const pendingSource = result.pending;
|
|
9
|
+
const errorSource = result.error;
|
|
10
|
+
watch(
|
|
11
|
+
pendingSource,
|
|
12
|
+
(isPending) => {
|
|
13
|
+
if (isPending) {
|
|
14
|
+
wasPending = true;
|
|
15
|
+
cycleStart = performance.now();
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
if (!wasPending) return;
|
|
19
|
+
wasPending = false;
|
|
20
|
+
const errorVal = errorSource?.value instanceof Error ? errorSource.value : void 0;
|
|
21
|
+
emitHook(nuxtApp, {
|
|
22
|
+
queryName: String(name),
|
|
23
|
+
queryType: "query",
|
|
24
|
+
variables: normalizeVariables(variables),
|
|
25
|
+
durationMs: performance.now() - cycleStart,
|
|
26
|
+
status: errorVal ? "error" : "success",
|
|
27
|
+
...errorVal ? { error: errorVal } : {}
|
|
28
|
+
});
|
|
29
|
+
},
|
|
30
|
+
{ immediate: true }
|
|
31
|
+
);
|
|
32
|
+
return result;
|
|
33
|
+
});
|
|
34
|
+
export const wpMutation = ((...args) => {
|
|
35
|
+
const [name, variables] = args;
|
|
36
|
+
const nuxtApp = useNuxtApp();
|
|
37
|
+
const startTime = performance.now();
|
|
38
|
+
const promise = useGraphqlMutation(...args);
|
|
39
|
+
Promise.resolve(promise).then(
|
|
40
|
+
() => emitHook(nuxtApp, {
|
|
41
|
+
queryName: String(name),
|
|
42
|
+
queryType: "mutation",
|
|
43
|
+
variables: normalizeVariables(variables),
|
|
44
|
+
durationMs: performance.now() - startTime,
|
|
45
|
+
status: "success"
|
|
46
|
+
}),
|
|
47
|
+
(err) => emitHook(nuxtApp, {
|
|
48
|
+
queryName: String(name),
|
|
49
|
+
queryType: "mutation",
|
|
50
|
+
variables: normalizeVariables(variables),
|
|
51
|
+
durationMs: performance.now() - startTime,
|
|
52
|
+
status: "error",
|
|
53
|
+
error: err instanceof Error ? err : new Error(String(err))
|
|
54
|
+
})
|
|
55
|
+
);
|
|
56
|
+
return promise;
|
|
57
|
+
});
|
|
58
|
+
function normalizeVariables(variables) {
|
|
59
|
+
const value = toValue(variables);
|
|
60
|
+
if (value && typeof value === "object") return value;
|
|
61
|
+
return void 0;
|
|
62
|
+
}
|
|
63
|
+
function emitHook(nuxtApp, payload) {
|
|
64
|
+
if (typeof nuxtApp?.callHook !== "function") return;
|
|
65
|
+
const maybePromise = nuxtApp.callHook("wpnuxt:query", payload);
|
|
66
|
+
if (maybePromise && typeof maybePromise.then === "function") {
|
|
67
|
+
Promise.resolve(maybePromise).catch((err) => {
|
|
68
|
+
if (import.meta.dev) {
|
|
69
|
+
console.warn("[wpnuxt] wpnuxt:query hook handler threw", err);
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -42,6 +42,11 @@ export function useAsyncGraphqlQuery<T = unknown>(
|
|
|
42
42
|
status: Ref<string>
|
|
43
43
|
}
|
|
44
44
|
export function useGraphqlState(): Record<string, unknown>
|
|
45
|
+
export function useGraphqlMutation<T = unknown>(
|
|
46
|
+
name: string,
|
|
47
|
+
variables?: Record<string, unknown>,
|
|
48
|
+
options?: Record<string, unknown>
|
|
49
|
+
): Promise<{ data: T | null, errors?: Array<{ message: string }> | null }>
|
|
45
50
|
|
|
46
51
|
// Stub for #nuxt-graphql-middleware/operation-types
|
|
47
52
|
export type Query = Record<string, unknown>
|
|
@@ -23,7 +23,11 @@ export const transformData = (data, nodes, fixImagePaths) => {
|
|
|
23
23
|
if (fixImagePaths && transformedData) {
|
|
24
24
|
if (Array.isArray(transformedData)) {
|
|
25
25
|
transformedData.forEach(addRelativePath);
|
|
26
|
-
} else {
|
|
26
|
+
} else if (typeof transformedData === "object") {
|
|
27
|
+
const maybeNodes = transformedData.nodes;
|
|
28
|
+
if (Array.isArray(maybeNodes)) {
|
|
29
|
+
maybeNodes.forEach(addRelativePath);
|
|
30
|
+
}
|
|
27
31
|
addRelativePath(transformedData);
|
|
28
32
|
}
|
|
29
33
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wpnuxt/core",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.1",
|
|
4
4
|
"description": "Nuxt module for WordPress integration via GraphQL (WPGraphQL)",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"nuxt",
|
|
@@ -54,7 +54,8 @@
|
|
|
54
54
|
"dompurify": "^3.4.0",
|
|
55
55
|
"graphql": "^16.13.2",
|
|
56
56
|
"nuxt-graphql-middleware": "5.4.0",
|
|
57
|
-
"scule": "^1.3.0"
|
|
57
|
+
"scule": "^1.3.0",
|
|
58
|
+
"typescript": "^5.9.3"
|
|
58
59
|
},
|
|
59
60
|
"devDependencies": {
|
|
60
61
|
"@nuxt/devtools": "^3.2.3",
|