@wpnuxt/core 2.2.2 → 2.3.0
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 +36 -0
- package/dist/module.d.ts +36 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +320 -56
- 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.0";
|
|
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}"`, {
|
|
@@ -649,6 +873,40 @@ function patchWPGraphQLSchema(schemaPath) {
|
|
|
649
873
|
writeFileSync(schemaPath, patchSchemaText(schema));
|
|
650
874
|
}
|
|
651
875
|
|
|
876
|
+
function validateGeneratedPaths(referencedTypes, operationsDtsPath) {
|
|
877
|
+
if (!existsSync(operationsDtsPath)) {
|
|
878
|
+
return { skipped: true, dangling: [] };
|
|
879
|
+
}
|
|
880
|
+
const source = readFileSync(operationsDtsPath, "utf8");
|
|
881
|
+
const sourceFile = ts.createSourceFile(
|
|
882
|
+
operationsDtsPath,
|
|
883
|
+
source,
|
|
884
|
+
ts.ScriptTarget.Latest,
|
|
885
|
+
false
|
|
886
|
+
);
|
|
887
|
+
const declared = /* @__PURE__ */ new Set();
|
|
888
|
+
const visit = (node) => {
|
|
889
|
+
if (ts.isTypeAliasDeclaration(node) || ts.isInterfaceDeclaration(node) || ts.isClassDeclaration(node)) {
|
|
890
|
+
if (node.name) declared.add(node.name.text);
|
|
891
|
+
} else if (ts.isVariableStatement(node)) {
|
|
892
|
+
for (const d of node.declarationList.declarations) {
|
|
893
|
+
if (ts.isIdentifier(d.name)) declared.add(d.name.text);
|
|
894
|
+
}
|
|
895
|
+
} else if (ts.isModuleDeclaration(node) && node.body && ts.isModuleBlock(node.body)) {
|
|
896
|
+
for (const child of node.body.statements) visit(child);
|
|
897
|
+
}
|
|
898
|
+
};
|
|
899
|
+
for (const stmt of sourceFile.statements) visit(stmt);
|
|
900
|
+
const dangling = [];
|
|
901
|
+
const seen = /* @__PURE__ */ new Set();
|
|
902
|
+
for (const ref of referencedTypes) {
|
|
903
|
+
if (seen.has(ref)) continue;
|
|
904
|
+
seen.add(ref);
|
|
905
|
+
if (!declared.has(ref)) dangling.push(ref);
|
|
906
|
+
}
|
|
907
|
+
return { skipped: false, dangling };
|
|
908
|
+
}
|
|
909
|
+
|
|
652
910
|
async function runInstall(nuxt) {
|
|
653
911
|
const logger = useLogger("wpnuxt", {
|
|
654
912
|
level: process.env.WPNUXT_DEBUG === "true" ? 4 : 3
|
|
@@ -872,6 +1130,11 @@ const module$1 = defineNuxtModule({
|
|
|
872
1130
|
maxAge: 60 * 5,
|
|
873
1131
|
// 5 minutes
|
|
874
1132
|
swr: true
|
|
1133
|
+
},
|
|
1134
|
+
cpt: {
|
|
1135
|
+
enabled: true,
|
|
1136
|
+
exclude: [],
|
|
1137
|
+
include: []
|
|
875
1138
|
}
|
|
876
1139
|
},
|
|
877
1140
|
async setup(options, nuxt) {
|
|
@@ -890,7 +1153,6 @@ const module$1 = defineNuxtModule({
|
|
|
890
1153
|
addPlugin(resolver.resolve("./runtime/plugins/graphqlErrors"));
|
|
891
1154
|
addPlugin(resolver.resolve("./runtime/plugins/sanitizeHtml"));
|
|
892
1155
|
configureTrailingSlash(nuxt, logger);
|
|
893
|
-
const mergedQueriesFolder = await mergeQueries(nuxt, wpNuxtConfig, resolver);
|
|
894
1156
|
const packageRoot = resolver.resolve("..");
|
|
895
1157
|
nuxt.options._layers.push({
|
|
896
1158
|
cwd: packageRoot,
|
|
@@ -906,31 +1168,21 @@ const module$1 = defineNuxtModule({
|
|
|
906
1168
|
const schemaPath = join(nuxt.options.rootDir, "schema.graphql");
|
|
907
1169
|
const schemaExists = existsSync(schemaPath);
|
|
908
1170
|
if (wpNuxtConfig.downloadSchema) {
|
|
909
|
-
|
|
910
|
-
|
|
1171
|
+
logger.debug(`Downloading schema from: ${wpNuxtConfig.wordpressUrl}${wpNuxtConfig.graphqlEndpoint}`);
|
|
1172
|
+
try {
|
|
911
1173
|
await validateWordPressEndpoint(
|
|
912
1174
|
wpNuxtConfig.wordpressUrl,
|
|
913
1175
|
wpNuxtConfig.graphqlEndpoint,
|
|
914
1176
|
{ schemaPath, authToken: wpNuxtConfig.schemaAuthToken }
|
|
915
1177
|
);
|
|
916
1178
|
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
|
-
});
|
|
1179
|
+
} catch (error) {
|
|
1180
|
+
if (!schemaExists) throw error;
|
|
1181
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1182
|
+
logger.warn(`Schema refresh failed, using cached schema.graphql: ${message.split("\n")[0]}`);
|
|
932
1183
|
}
|
|
933
1184
|
}
|
|
1185
|
+
const mergedQueriesFolder = await mergeQueries(nuxt, wpNuxtConfig, resolver, schemaPath);
|
|
934
1186
|
await registerModules(nuxt, resolver, wpNuxtConfig, mergedQueriesFolder);
|
|
935
1187
|
nuxt.hook("devtools:customTabs", (tabs) => {
|
|
936
1188
|
const middlewareTab = tabs.find((tab) => tab.name === "nuxt-graphql-middleware");
|
|
@@ -1031,12 +1283,14 @@ const module$1 = defineNuxtModule({
|
|
|
1031
1283
|
nuxt.options.alias["#wpnuxt"] = resolver.resolve(nuxt.options.buildDir, "wpnuxt");
|
|
1032
1284
|
nuxt.options.alias["#wpnuxt/*"] = resolver.resolve(nuxt.options.buildDir, "wpnuxt", "*");
|
|
1033
1285
|
nuxt.options.alias["#wpnuxt/types"] = resolver.resolve("./types");
|
|
1286
|
+
nuxt.options.alias["#wpnuxt-internal"] = resolver.resolve("./runtime/internal/graphql-client");
|
|
1034
1287
|
nuxt.options.alias["@wpnuxt/core/server-options"] = resolver.resolve("./server-options");
|
|
1035
1288
|
nuxt.options.alias["@wpnuxt/core/client-options"] = resolver.resolve("./client-options");
|
|
1036
1289
|
const nitroOpts = nuxt.options;
|
|
1037
1290
|
nitroOpts.nitro = nitroOpts.nitro || {};
|
|
1038
1291
|
nitroOpts.nitro.alias = nitroOpts.nitro.alias || {};
|
|
1039
1292
|
nitroOpts.nitro.alias["#wpnuxt/types"] = resolver.resolve("./types");
|
|
1293
|
+
nitroOpts.nitro.alias["#wpnuxt-internal"] = resolver.resolve("./runtime/internal/graphql-client");
|
|
1040
1294
|
nitroOpts.nitro.externals = nitroOpts.nitro.externals || {};
|
|
1041
1295
|
nitroOpts.nitro.externals.inline = nitroOpts.nitro.externals.inline || [];
|
|
1042
1296
|
addTemplate({
|
|
@@ -1053,6 +1307,16 @@ const module$1 = defineNuxtModule({
|
|
|
1053
1307
|
autoimports.push(...ctx.fnImports || []);
|
|
1054
1308
|
});
|
|
1055
1309
|
logger.trace("Finished generating composables");
|
|
1310
|
+
nuxt.hook("build:before", () => {
|
|
1311
|
+
if (!ctx.referencedTypes?.length) return;
|
|
1312
|
+
const operationsDtsPath = join(nuxt.options.buildDir, "graphql-operations.d.ts");
|
|
1313
|
+
const result = validateGeneratedPaths(ctx.referencedTypes, operationsDtsPath);
|
|
1314
|
+
if (result.skipped || result.dangling.length === 0) return;
|
|
1315
|
+
logger.warn(
|
|
1316
|
+
`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.`
|
|
1317
|
+
);
|
|
1318
|
+
for (const t of result.dangling) logger.warn(` - ${t}`);
|
|
1319
|
+
});
|
|
1056
1320
|
logger.info(`WPNuxt module loaded in ${(/* @__PURE__ */ new Date()).getTime() - startTime}ms`);
|
|
1057
1321
|
},
|
|
1058
1322
|
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.0",
|
|
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",
|