@wpnuxt/core 2.0.0-alpha.7 → 2.0.0-alpha.9
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 +5 -0
- package/dist/module.d.ts +5 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +441 -23
- package/dist/runtime/composables/useWPContent.js +3 -24
- package/dist/runtime/types/stub.d.ts +37 -16
- package/dist/runtime/util/content.d.ts +0 -0
- package/dist/runtime/util/content.js +33 -0
- package/package.json +8 -7
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
package/dist/module.mjs
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import { defu } from 'defu';
|
|
2
|
-
import { promises, cpSync, existsSync, statSync, readFileSync, writeFileSync } from 'node:fs';
|
|
3
|
-
import {
|
|
4
|
-
import { join } from 'node:path';
|
|
2
|
+
import { promises, cpSync, existsSync, readdirSync, statSync, readFileSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { writeFile, rename, readFile, mkdir } from 'node:fs/promises';
|
|
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';
|
|
8
8
|
import { parse, GraphQLError } from 'graphql';
|
|
9
9
|
import { execSync } from 'node:child_process';
|
|
10
|
+
import { consola } from 'consola';
|
|
11
|
+
|
|
12
|
+
const version = "2.0.0-alpha.8";
|
|
10
13
|
|
|
11
14
|
function createModuleError(module, message) {
|
|
12
15
|
return new Error(formatErrorMessage(module, message));
|
|
@@ -15,6 +18,29 @@ function formatErrorMessage(module, message) {
|
|
|
15
18
|
return `[wpnuxt:${module}] ${message}`;
|
|
16
19
|
}
|
|
17
20
|
|
|
21
|
+
function validateWordPressUrl(url) {
|
|
22
|
+
if (!url?.trim()) {
|
|
23
|
+
return { valid: false, error: "URL cannot be empty" };
|
|
24
|
+
}
|
|
25
|
+
let normalizedUrl = url.trim();
|
|
26
|
+
if (!normalizedUrl.startsWith("http://") && !normalizedUrl.startsWith("https://")) {
|
|
27
|
+
normalizedUrl = `https://${normalizedUrl}`;
|
|
28
|
+
}
|
|
29
|
+
try {
|
|
30
|
+
const parsed = new URL(normalizedUrl);
|
|
31
|
+
if (!["http:", "https:"].includes(parsed.protocol)) {
|
|
32
|
+
return { valid: false, error: "URL must use http or https protocol" };
|
|
33
|
+
}
|
|
34
|
+
return { valid: true, normalizedUrl: normalizedUrl.replace(/\/+$/, "") };
|
|
35
|
+
} catch {
|
|
36
|
+
return { valid: false, error: "Invalid URL format" };
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
async function atomicWriteFile(path, content) {
|
|
40
|
+
const tempPath = `${path}.${Date.now()}.tmp`;
|
|
41
|
+
await writeFile(tempPath, content, "utf-8");
|
|
42
|
+
await rename(tempPath, path);
|
|
43
|
+
}
|
|
18
44
|
function randHashGenerator(length = 12) {
|
|
19
45
|
return Math.random().toString(36).substring(2, 2 + length).toUpperCase().padEnd(length, "0");
|
|
20
46
|
}
|
|
@@ -35,6 +61,13 @@ async function mergeQueries(nuxt, wpNuxtConfig, resolver) {
|
|
|
35
61
|
const defaultQueriesPath = resolver.resolve("./runtime/queries");
|
|
36
62
|
await promises.rm(queryOutputPath, { recursive: true, force: true });
|
|
37
63
|
cpSync(defaultQueriesPath, queryOutputPath, { recursive: true });
|
|
64
|
+
const conflicts = findConflicts(userQueryPath, queryOutputPath);
|
|
65
|
+
if (conflicts.length && wpNuxtConfig.queries.warnOnOverride) {
|
|
66
|
+
logger.warn("The following user query files will override default queries:");
|
|
67
|
+
for (const file of conflicts) {
|
|
68
|
+
logger.warn(` - ${file}`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
38
71
|
if (existsSync(userQueryPath)) {
|
|
39
72
|
logger.debug("Extending queries:", userQueryPath);
|
|
40
73
|
cpSync(userQueryPath, queryOutputPath, { recursive: true });
|
|
@@ -42,6 +75,29 @@ async function mergeQueries(nuxt, wpNuxtConfig, resolver) {
|
|
|
42
75
|
logger.debug("Merged queries folder:", queryOutputPath);
|
|
43
76
|
return queryOutputPath;
|
|
44
77
|
}
|
|
78
|
+
function findConflicts(userQueryPath, outputPath) {
|
|
79
|
+
const conflicts = [];
|
|
80
|
+
function walk(dir) {
|
|
81
|
+
const entries = readdirSync(dir);
|
|
82
|
+
for (const entry of entries) {
|
|
83
|
+
const fullPath = join(dir, entry);
|
|
84
|
+
const stat = statSync(fullPath);
|
|
85
|
+
if (stat.isDirectory()) {
|
|
86
|
+
walk(fullPath);
|
|
87
|
+
} else if (stat.isFile()) {
|
|
88
|
+
const rel = relative(userQueryPath, fullPath);
|
|
89
|
+
const target = join(outputPath, rel);
|
|
90
|
+
if (existsSync(target)) {
|
|
91
|
+
conflicts.push(rel);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (existsSync(userQueryPath)) {
|
|
97
|
+
walk(userQueryPath);
|
|
98
|
+
}
|
|
99
|
+
return conflicts;
|
|
100
|
+
}
|
|
45
101
|
|
|
46
102
|
const _parseDoc = async (doc) => {
|
|
47
103
|
if (!doc || typeof doc !== "string" || doc.trim().length === 0) {
|
|
@@ -143,8 +199,21 @@ async function prepareContext(ctx) {
|
|
|
143
199
|
const mutationFnName = (fn) => `useMutation${upperFirst(fn)}`;
|
|
144
200
|
const formatNodes = (nodes) => nodes?.map((n) => `'${n}'`).join(",") ?? "";
|
|
145
201
|
const getFragmentType = (q) => {
|
|
146
|
-
|
|
147
|
-
|
|
202
|
+
if (q.fragments?.length) {
|
|
203
|
+
const fragmentSuffix = q.nodes?.includes("nodes") ? "[]" : "";
|
|
204
|
+
return q.fragments.map((f) => `${f}Fragment${fragmentSuffix}`).join(" | ");
|
|
205
|
+
}
|
|
206
|
+
if (q.nodes?.length) {
|
|
207
|
+
let typePath = `${q.name}RootQuery`;
|
|
208
|
+
for (const node of q.nodes) {
|
|
209
|
+
typePath = `NonNullable<${typePath}>['${node}']`;
|
|
210
|
+
}
|
|
211
|
+
if (q.nodes.includes("nodes")) {
|
|
212
|
+
typePath = `${typePath}[number]`;
|
|
213
|
+
}
|
|
214
|
+
return typePath;
|
|
215
|
+
}
|
|
216
|
+
return `${q.name}RootQuery`;
|
|
148
217
|
};
|
|
149
218
|
const queryFnExp = (q, typed = false) => {
|
|
150
219
|
const functionName = fnName(q.name);
|
|
@@ -161,19 +230,34 @@ async function prepareContext(ctx) {
|
|
|
161
230
|
return ` export const ${functionName}: (variables: ${m.name}MutationVariables, options?: WPMutationOptions) => Promise<WPMutationResult<${m.name}Mutation>>`;
|
|
162
231
|
};
|
|
163
232
|
ctx.generateImports = () => {
|
|
233
|
+
const lines = [];
|
|
164
234
|
const imports = [];
|
|
235
|
+
if (queries.length > 0) {
|
|
236
|
+
imports.push("useWPContent");
|
|
237
|
+
}
|
|
238
|
+
if (mutations.length > 0) {
|
|
239
|
+
imports.push("useGraphqlMutation");
|
|
240
|
+
}
|
|
241
|
+
if (imports.length > 0) {
|
|
242
|
+
lines.push(`import { ${imports.join(", ")} } from '#imports'`);
|
|
243
|
+
lines.push("");
|
|
244
|
+
}
|
|
165
245
|
queries.forEach((f) => {
|
|
166
|
-
|
|
246
|
+
lines.push(queryFnExp(f, false));
|
|
167
247
|
});
|
|
168
248
|
mutations.forEach((m) => {
|
|
169
|
-
|
|
249
|
+
lines.push(mutationFnExp(m, false));
|
|
170
250
|
});
|
|
171
|
-
return
|
|
251
|
+
return lines.join("\n");
|
|
172
252
|
};
|
|
173
253
|
const typeSet = /* @__PURE__ */ new Set();
|
|
174
254
|
queries.forEach((o) => {
|
|
175
255
|
typeSet.add(`${o.name}QueryVariables`);
|
|
176
|
-
o.fragments?.
|
|
256
|
+
if (o.fragments?.length) {
|
|
257
|
+
o.fragments.forEach((f) => typeSet.add(`${f}Fragment`));
|
|
258
|
+
} else {
|
|
259
|
+
typeSet.add(`${o.name}RootQuery`);
|
|
260
|
+
}
|
|
177
261
|
});
|
|
178
262
|
mutations.forEach((m) => {
|
|
179
263
|
typeSet.add(`${m.name}MutationVariables`);
|
|
@@ -404,9 +488,289 @@ function patchWPGraphQLSchema(schemaPath) {
|
|
|
404
488
|
writeFileSync(schemaPath, schema);
|
|
405
489
|
}
|
|
406
490
|
|
|
491
|
+
async function runInstall(nuxt) {
|
|
492
|
+
const logger = useLogger("wpnuxt", {
|
|
493
|
+
level: process.env.WPNUXT_DEBUG === "true" ? 4 : 3
|
|
494
|
+
});
|
|
495
|
+
const results = [];
|
|
496
|
+
results.push(await setupEnvFiles(nuxt, logger));
|
|
497
|
+
const parallel = await Promise.all([
|
|
498
|
+
setupMcpConfig(nuxt, logger),
|
|
499
|
+
setupGitignore(nuxt, logger),
|
|
500
|
+
setupQueriesFolder(nuxt, logger)
|
|
501
|
+
]);
|
|
502
|
+
results.push(...parallel);
|
|
503
|
+
displayInstallSummary(results, logger);
|
|
504
|
+
}
|
|
505
|
+
function displayInstallSummary(results, logger) {
|
|
506
|
+
const successes = results.filter((r) => r.success && !r.skipped);
|
|
507
|
+
const skipped = results.filter((r) => r.skipped);
|
|
508
|
+
const failures = results.filter((r) => !r.success);
|
|
509
|
+
if (successes.length > 0) {
|
|
510
|
+
const lines = [
|
|
511
|
+
...successes.map((r) => `\u2713 ${r.message || r.name}`),
|
|
512
|
+
"",
|
|
513
|
+
"Next steps:",
|
|
514
|
+
" 1. Ensure WPGraphQL is installed on WordPress",
|
|
515
|
+
" 2. Run `pnpm dev` to start development",
|
|
516
|
+
"",
|
|
517
|
+
"Docs: https://wpnuxt.com"
|
|
518
|
+
];
|
|
519
|
+
consola.box({
|
|
520
|
+
title: "WPNuxt Setup Complete",
|
|
521
|
+
message: lines.join("\n")
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
if (skipped.length > 0) {
|
|
525
|
+
logger.debug(`Skipped (already configured): ${skipped.map((r) => r.name).join(", ")}`);
|
|
526
|
+
}
|
|
527
|
+
if (failures.length > 0) {
|
|
528
|
+
logger.debug(`Failed: ${failures.map((r) => r.name).join(", ")}`);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
const ENV_EXAMPLE_CONTENT = `# WPNuxt Configuration
|
|
532
|
+
# Required: Your WordPress site URL (must have WPGraphQL plugin installed)
|
|
533
|
+
WPNUXT_WORDPRESS_URL=https://your-wordpress-site.com
|
|
534
|
+
|
|
535
|
+
# Optional: Custom GraphQL endpoint (default: /graphql)
|
|
536
|
+
# WPNUXT_GRAPHQL_ENDPOINT=/graphql
|
|
537
|
+
|
|
538
|
+
# Optional: Enable debug mode for verbose logging
|
|
539
|
+
# WPNUXT_DEBUG=true
|
|
540
|
+
`;
|
|
541
|
+
function isInteractiveEnvironment() {
|
|
542
|
+
return !(process.env.CI === "true" || process.env.CI === "1" || process.env.VITEST === "true" || process.env.TEST === "true" || process.env.NODE_ENV === "test");
|
|
543
|
+
}
|
|
544
|
+
async function checkExistingEnvConfig(nuxt, envPath) {
|
|
545
|
+
const nuxtConfig = nuxt.options;
|
|
546
|
+
if (nuxtConfig.wpNuxt?.wordpressUrl) {
|
|
547
|
+
return { hasUrl: true, source: "nuxt.config.ts", envContent: "" };
|
|
548
|
+
}
|
|
549
|
+
if (process.env.WPNUXT_WORDPRESS_URL) {
|
|
550
|
+
return { hasUrl: true, source: "WPNUXT_WORDPRESS_URL env var", envContent: "" };
|
|
551
|
+
}
|
|
552
|
+
if (existsSync(envPath)) {
|
|
553
|
+
const envContent = await readFile(envPath, "utf-8");
|
|
554
|
+
if (/^WPNUXT_WORDPRESS_URL\s*=\s*.+/m.test(envContent)) {
|
|
555
|
+
return { hasUrl: true, source: ".env file", envContent };
|
|
556
|
+
}
|
|
557
|
+
return { hasUrl: false, envContent };
|
|
558
|
+
}
|
|
559
|
+
return { hasUrl: false, envContent: "" };
|
|
560
|
+
}
|
|
561
|
+
async function setupEnvFiles(nuxt, logger) {
|
|
562
|
+
const envPath = join(nuxt.options.rootDir, ".env");
|
|
563
|
+
const envExamplePath = join(nuxt.options.rootDir, ".env.example");
|
|
564
|
+
try {
|
|
565
|
+
const existingConfig = await checkExistingEnvConfig(nuxt, envPath);
|
|
566
|
+
let envContent = existingConfig.envContent;
|
|
567
|
+
let urlConfigured = false;
|
|
568
|
+
if (existingConfig.hasUrl) {
|
|
569
|
+
logger.debug(`WordPress URL already configured in ${existingConfig.source}`);
|
|
570
|
+
urlConfigured = true;
|
|
571
|
+
} else if (isInteractiveEnvironment()) {
|
|
572
|
+
consola.box({
|
|
573
|
+
title: "WPNuxt Setup",
|
|
574
|
+
message: "Configure your WordPress connection"
|
|
575
|
+
});
|
|
576
|
+
const wordpressUrl = await consola.prompt(
|
|
577
|
+
"What is your WordPress site URL?",
|
|
578
|
+
{
|
|
579
|
+
type: "text",
|
|
580
|
+
placeholder: "https://your-wordpress-site.com",
|
|
581
|
+
initial: ""
|
|
582
|
+
}
|
|
583
|
+
);
|
|
584
|
+
if (wordpressUrl && typeof wordpressUrl === "string" && wordpressUrl.trim()) {
|
|
585
|
+
const validation = validateWordPressUrl(wordpressUrl);
|
|
586
|
+
if (!validation.valid) {
|
|
587
|
+
logger.warn(`Invalid URL: ${validation.error}`);
|
|
588
|
+
logger.info("Skipped WordPress URL configuration. Add WPNUXT_WORDPRESS_URL to your .env file later.");
|
|
589
|
+
} else {
|
|
590
|
+
const envLine = `WPNUXT_WORDPRESS_URL=${validation.normalizedUrl}
|
|
591
|
+
`;
|
|
592
|
+
if (envContent) {
|
|
593
|
+
envContent = envContent.trimEnd() + "\n\n" + envLine;
|
|
594
|
+
} else {
|
|
595
|
+
envContent = envLine;
|
|
596
|
+
}
|
|
597
|
+
await atomicWriteFile(envPath, envContent);
|
|
598
|
+
logger.success(`WordPress URL saved to .env: ${validation.normalizedUrl}`);
|
|
599
|
+
urlConfigured = true;
|
|
600
|
+
}
|
|
601
|
+
} else {
|
|
602
|
+
logger.info("Skipped WordPress URL configuration. Add WPNUXT_WORDPRESS_URL to your .env file later.");
|
|
603
|
+
}
|
|
604
|
+
} else {
|
|
605
|
+
logger.debug("Non-interactive environment detected, skipping WordPress URL prompt");
|
|
606
|
+
}
|
|
607
|
+
let exampleContent = "";
|
|
608
|
+
let exampleUpdated = false;
|
|
609
|
+
if (existsSync(envExamplePath)) {
|
|
610
|
+
exampleContent = await readFile(envExamplePath, "utf-8");
|
|
611
|
+
if (exampleContent.includes("WPNUXT_WORDPRESS_URL")) {
|
|
612
|
+
logger.debug(".env.example already includes WPNuxt configuration");
|
|
613
|
+
} else {
|
|
614
|
+
exampleContent = exampleContent.trimEnd() + "\n\n" + ENV_EXAMPLE_CONTENT;
|
|
615
|
+
await atomicWriteFile(envExamplePath, exampleContent);
|
|
616
|
+
exampleUpdated = true;
|
|
617
|
+
}
|
|
618
|
+
} else {
|
|
619
|
+
exampleContent = ENV_EXAMPLE_CONTENT;
|
|
620
|
+
await atomicWriteFile(envExamplePath, exampleContent);
|
|
621
|
+
exampleUpdated = true;
|
|
622
|
+
}
|
|
623
|
+
if (urlConfigured && exampleUpdated) {
|
|
624
|
+
return {
|
|
625
|
+
name: "Environment files",
|
|
626
|
+
success: true,
|
|
627
|
+
message: "Configured .env with WordPress URL"
|
|
628
|
+
};
|
|
629
|
+
} else if (urlConfigured) {
|
|
630
|
+
return { name: "Environment files", success: true, skipped: true };
|
|
631
|
+
} else if (exampleUpdated) {
|
|
632
|
+
return {
|
|
633
|
+
name: "Environment files",
|
|
634
|
+
success: true,
|
|
635
|
+
message: "Created .env.example template"
|
|
636
|
+
};
|
|
637
|
+
} else {
|
|
638
|
+
return { name: "Environment files", success: true, skipped: true };
|
|
639
|
+
}
|
|
640
|
+
} catch (error) {
|
|
641
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
642
|
+
logger.warn(`Failed to setup environment files: ${message}`);
|
|
643
|
+
return { name: "Environment files", success: false, message };
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
const MCP_CONFIG = {
|
|
647
|
+
wpnuxt: {
|
|
648
|
+
type: "http",
|
|
649
|
+
url: "https://wpnuxt.com/mcp"
|
|
650
|
+
}
|
|
651
|
+
};
|
|
652
|
+
async function setupMcpConfig(nuxt, logger) {
|
|
653
|
+
const mcpPath = join(nuxt.options.rootDir, ".mcp.json");
|
|
654
|
+
try {
|
|
655
|
+
let config = { mcpServers: {} };
|
|
656
|
+
if (existsSync(mcpPath)) {
|
|
657
|
+
const content = await readFile(mcpPath, "utf-8");
|
|
658
|
+
config = JSON.parse(content);
|
|
659
|
+
config.mcpServers = config.mcpServers || {};
|
|
660
|
+
if (config.mcpServers.wpnuxt) {
|
|
661
|
+
logger.debug("MCP config already includes wpnuxt server");
|
|
662
|
+
return { name: "MCP config", success: true, skipped: true };
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
config.mcpServers = {
|
|
666
|
+
...config.mcpServers,
|
|
667
|
+
...MCP_CONFIG
|
|
668
|
+
};
|
|
669
|
+
await atomicWriteFile(mcpPath, JSON.stringify(config, null, 2) + "\n");
|
|
670
|
+
return {
|
|
671
|
+
name: "MCP config",
|
|
672
|
+
success: true,
|
|
673
|
+
message: "Created .mcp.json with WPNuxt MCP server"
|
|
674
|
+
};
|
|
675
|
+
} catch (error) {
|
|
676
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
677
|
+
logger.warn(`Failed to setup MCP configuration: ${message}`);
|
|
678
|
+
return { name: "MCP config", success: false, message };
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
async function setupGitignore(nuxt, logger) {
|
|
682
|
+
const gitignorePath = join(nuxt.options.rootDir, ".gitignore");
|
|
683
|
+
try {
|
|
684
|
+
let content = "";
|
|
685
|
+
if (existsSync(gitignorePath)) {
|
|
686
|
+
content = await readFile(gitignorePath, "utf-8");
|
|
687
|
+
if (content.includes(".queries")) {
|
|
688
|
+
logger.debug(".gitignore already includes .queries");
|
|
689
|
+
return { name: "Gitignore", success: true, skipped: true };
|
|
690
|
+
}
|
|
691
|
+
content = content.trimEnd() + "\n\n# WPNuxt generated files\n.queries/\n";
|
|
692
|
+
} else {
|
|
693
|
+
content = "# WPNuxt generated files\n.queries/\n";
|
|
694
|
+
}
|
|
695
|
+
await atomicWriteFile(gitignorePath, content);
|
|
696
|
+
return {
|
|
697
|
+
name: "Gitignore",
|
|
698
|
+
success: true,
|
|
699
|
+
message: "Added .queries/ to .gitignore"
|
|
700
|
+
};
|
|
701
|
+
} catch (error) {
|
|
702
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
703
|
+
logger.warn(`Failed to setup .gitignore: ${message}`);
|
|
704
|
+
return { name: "Gitignore", success: false, message };
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
const QUERIES_README = `# Custom GraphQL Queries
|
|
708
|
+
|
|
709
|
+
Place your custom \`.gql\` or \`.graphql\` files here to extend or override the default WPNuxt queries.
|
|
710
|
+
|
|
711
|
+
## How it works
|
|
712
|
+
|
|
713
|
+
1. Files here are merged with WPNuxt's default queries during build
|
|
714
|
+
2. If a file has the same name as a default query, yours will override it
|
|
715
|
+
3. New files will generate new composables automatically
|
|
716
|
+
|
|
717
|
+
## Example
|
|
718
|
+
|
|
719
|
+
Create a file \`CustomPosts.gql\`:
|
|
720
|
+
|
|
721
|
+
\`\`\`graphql
|
|
722
|
+
query CustomPosts($first: Int = 10) {
|
|
723
|
+
posts(first: $first) {
|
|
724
|
+
nodes {
|
|
725
|
+
id
|
|
726
|
+
title
|
|
727
|
+
date
|
|
728
|
+
# Add your custom fields here
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
\`\`\`
|
|
733
|
+
|
|
734
|
+
This generates \`useCustomPosts()\` and \`useAsyncCustomPosts()\` composables.
|
|
735
|
+
|
|
736
|
+
## Available Fragments
|
|
737
|
+
|
|
738
|
+
You can use these fragments from WPNuxt's defaults:
|
|
739
|
+
- \`...Post\` - Standard post fields
|
|
740
|
+
- \`...Page\` - Standard page fields
|
|
741
|
+
- \`...ContentNode\` - Common content fields
|
|
742
|
+
- \`...FeaturedImage\` - Featured image with sizes
|
|
743
|
+
|
|
744
|
+
## Documentation
|
|
745
|
+
|
|
746
|
+
See https://wpnuxt.com/guide/custom-queries for more details.
|
|
747
|
+
`;
|
|
748
|
+
async function setupQueriesFolder(nuxt, logger) {
|
|
749
|
+
const queriesPath = join(nuxt.options.rootDir, "extend", "queries");
|
|
750
|
+
const readmePath = join(queriesPath, "README.md");
|
|
751
|
+
try {
|
|
752
|
+
if (existsSync(readmePath)) {
|
|
753
|
+
logger.debug("extend/queries/ folder already exists");
|
|
754
|
+
return { name: "Queries folder", success: true, skipped: true };
|
|
755
|
+
}
|
|
756
|
+
await mkdir(queriesPath, { recursive: true });
|
|
757
|
+
await atomicWriteFile(readmePath, QUERIES_README);
|
|
758
|
+
return {
|
|
759
|
+
name: "Queries folder",
|
|
760
|
+
success: true,
|
|
761
|
+
message: "Created extend/queries/ for custom GraphQL queries"
|
|
762
|
+
};
|
|
763
|
+
} catch (error) {
|
|
764
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
765
|
+
logger.warn(`Failed to setup extend/queries/ folder: ${message}`);
|
|
766
|
+
return { name: "Queries folder", success: false, message };
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
|
|
407
770
|
const module$1 = defineNuxtModule({
|
|
408
771
|
meta: {
|
|
409
772
|
name: "@wpnuxt/core",
|
|
773
|
+
version,
|
|
410
774
|
configKey: "wpNuxt",
|
|
411
775
|
compatibility: {
|
|
412
776
|
nuxt: ">=3.0.0"
|
|
@@ -417,7 +781,8 @@ const module$1 = defineNuxtModule({
|
|
|
417
781
|
graphqlEndpoint: "/graphql",
|
|
418
782
|
queries: {
|
|
419
783
|
extendFolder: "extend/queries/",
|
|
420
|
-
mergedOutputFolder: ".queries/"
|
|
784
|
+
mergedOutputFolder: ".queries/",
|
|
785
|
+
warnOnOverride: true
|
|
421
786
|
},
|
|
422
787
|
downloadSchema: true,
|
|
423
788
|
debug: false,
|
|
@@ -437,6 +802,7 @@ const module$1 = defineNuxtModule({
|
|
|
437
802
|
nuxt.options.runtimeConfig.public.buildHash = randHashGenerator();
|
|
438
803
|
addPlugin(resolver.resolve("./runtime/plugins/graphqlConfig"));
|
|
439
804
|
addPlugin(resolver.resolve("./runtime/plugins/graphqlErrors"));
|
|
805
|
+
configureTrailingSlash(nuxt, logger);
|
|
440
806
|
const mergedQueriesFolder = await mergeQueries(nuxt, wpNuxtConfig, resolver);
|
|
441
807
|
await setupServerOptions(nuxt, resolver, logger);
|
|
442
808
|
await setupClientOptions(nuxt, resolver, logger);
|
|
@@ -477,8 +843,10 @@ const module$1 = defineNuxtModule({
|
|
|
477
843
|
});
|
|
478
844
|
if (wpNuxtConfig.cache?.enabled !== false) {
|
|
479
845
|
const maxAge = wpNuxtConfig.cache?.maxAge ?? 300;
|
|
480
|
-
|
|
481
|
-
|
|
846
|
+
const nitroOptions = nuxt.options;
|
|
847
|
+
nitroOptions.nitro = nitroOptions.nitro || {};
|
|
848
|
+
nitroOptions.nitro.routeRules = nitroOptions.nitro.routeRules || {};
|
|
849
|
+
nitroOptions.nitro.routeRules["/api/wpnuxt/**"] = {
|
|
482
850
|
cache: {
|
|
483
851
|
maxAge,
|
|
484
852
|
swr: wpNuxtConfig.cache?.swr !== false
|
|
@@ -510,10 +878,12 @@ const module$1 = defineNuxtModule({
|
|
|
510
878
|
nuxt.options.alias["#wpnuxt"] = resolver.resolve(nuxt.options.buildDir, "wpnuxt");
|
|
511
879
|
nuxt.options.alias["#wpnuxt/*"] = resolver.resolve(nuxt.options.buildDir, "wpnuxt", "*");
|
|
512
880
|
nuxt.options.alias["#wpnuxt/types"] = resolver.resolve("./types");
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
881
|
+
const nitroOpts = nuxt.options;
|
|
882
|
+
nitroOpts.nitro = nitroOpts.nitro || {};
|
|
883
|
+
nitroOpts.nitro.alias = nitroOpts.nitro.alias || {};
|
|
884
|
+
nitroOpts.nitro.alias["#wpnuxt/types"] = resolver.resolve("./types");
|
|
885
|
+
nitroOpts.nitro.externals = nitroOpts.nitro.externals || {};
|
|
886
|
+
nitroOpts.nitro.externals.inline = nitroOpts.nitro.externals.inline || [];
|
|
517
887
|
addTemplate({
|
|
518
888
|
write: true,
|
|
519
889
|
filename: "wpnuxt/index.mjs",
|
|
@@ -529,6 +899,9 @@ const module$1 = defineNuxtModule({
|
|
|
529
899
|
});
|
|
530
900
|
logger.trace("Finished generating composables");
|
|
531
901
|
logger.info(`WPNuxt module loaded in ${(/* @__PURE__ */ new Date()).getTime() - startTime}ms`);
|
|
902
|
+
},
|
|
903
|
+
async onInstall(nuxt) {
|
|
904
|
+
await runInstall(nuxt);
|
|
532
905
|
}
|
|
533
906
|
});
|
|
534
907
|
function loadConfig(options, nuxt) {
|
|
@@ -661,18 +1034,63 @@ async function setupClientOptions(nuxt, _resolver, logger) {
|
|
|
661
1034
|
await writeFile(targetPath, CLIENT_OPTIONS_TEMPLATE);
|
|
662
1035
|
logger.debug("Created graphqlMiddleware.clientOptions.ts with WPNuxt defaults (preview mode support)");
|
|
663
1036
|
}
|
|
1037
|
+
function configureTrailingSlash(nuxt, logger) {
|
|
1038
|
+
const handlerPath = join(nuxt.options.buildDir, "wpnuxt", "trailing-slash-handler.ts");
|
|
1039
|
+
const handlerCode = `import { defineEventHandler, sendRedirect, getRequestURL } from 'h3'
|
|
1040
|
+
|
|
1041
|
+
export default defineEventHandler((event) => {
|
|
1042
|
+
const url = getRequestURL(event)
|
|
1043
|
+
const path = url.pathname
|
|
1044
|
+
|
|
1045
|
+
// Skip if:
|
|
1046
|
+
// - Already has trailing slash
|
|
1047
|
+
// - Is root path
|
|
1048
|
+
// - Is an API route
|
|
1049
|
+
// - Has a file extension (likely a static file)
|
|
1050
|
+
// - Is a Nuxt internal route (_nuxt, __nuxt)
|
|
1051
|
+
if (
|
|
1052
|
+
path.endsWith('/') ||
|
|
1053
|
+
path === '' ||
|
|
1054
|
+
path.startsWith('/api/') ||
|
|
1055
|
+
path.startsWith('/_nuxt/') ||
|
|
1056
|
+
path.startsWith('/__nuxt') ||
|
|
1057
|
+
path.includes('.')
|
|
1058
|
+
) {
|
|
1059
|
+
return
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
// Redirect to trailing slash version
|
|
1063
|
+
return sendRedirect(event, path + '/' + url.search, 301)
|
|
1064
|
+
})
|
|
1065
|
+
`;
|
|
1066
|
+
nuxt.hook("build:before", async () => {
|
|
1067
|
+
await mkdir(dirname(handlerPath), { recursive: true });
|
|
1068
|
+
await writeFile(handlerPath, handlerCode);
|
|
1069
|
+
logger.debug("Created trailing slash handler at " + handlerPath);
|
|
1070
|
+
});
|
|
1071
|
+
nuxt.hook("nitro:config", (nitroConfig) => {
|
|
1072
|
+
nitroConfig.handlers = nitroConfig.handlers || [];
|
|
1073
|
+
nitroConfig.handlers.unshift({
|
|
1074
|
+
route: "/**",
|
|
1075
|
+
handler: handlerPath
|
|
1076
|
+
});
|
|
1077
|
+
});
|
|
1078
|
+
logger.debug("Configured trailing slash handling for WordPress URI compatibility");
|
|
1079
|
+
}
|
|
664
1080
|
function configureVercelSettings(nuxt, logger) {
|
|
665
|
-
const
|
|
1081
|
+
const opts = nuxt.options;
|
|
1082
|
+
opts.nitro = opts.nitro || {};
|
|
1083
|
+
const isVercel = process.env.VERCEL === "1" || opts.nitro.preset === "vercel";
|
|
666
1084
|
if (isVercel) {
|
|
667
1085
|
logger.debug("Vercel deployment detected, applying recommended settings");
|
|
668
|
-
|
|
669
|
-
if (
|
|
670
|
-
|
|
1086
|
+
opts.nitro.future = opts.nitro.future || {};
|
|
1087
|
+
if (opts.nitro.future.nativeSWR === void 0) {
|
|
1088
|
+
opts.nitro.future.nativeSWR = true;
|
|
671
1089
|
logger.debug("Enabled nitro.future.nativeSWR for Vercel ISR compatibility");
|
|
672
1090
|
}
|
|
673
|
-
|
|
674
|
-
if (!
|
|
675
|
-
|
|
1091
|
+
opts.routeRules = opts.routeRules || {};
|
|
1092
|
+
if (!opts.routeRules["/**"]) {
|
|
1093
|
+
opts.routeRules["/**"] = { ssr: true };
|
|
676
1094
|
logger.debug("Enabled SSR for all routes (routeRules['/**'] = { ssr: true })");
|
|
677
1095
|
}
|
|
678
1096
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
11
|
-
export
|
|
12
|
-
export
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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,
|
|
42
|
+
export type Query = Record<string, unknown>
|
|
22
43
|
|
|
23
|
-
// Stub for #build/graphql-operations
|
|
24
|
-
export type PostFragment =
|
|
25
|
-
export type PageFragment =
|
|
26
|
-
export type MenuItemFragment =
|
|
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.
|
|
3
|
+
"version": "2.0.0-alpha.9",
|
|
4
4
|
"description": "Nuxt module for WordPress integration via GraphQL (WPGraphQL)",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"nuxt",
|
|
@@ -43,8 +43,9 @@
|
|
|
43
43
|
"access": "public"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@nuxt/kit": "4.
|
|
46
|
+
"@nuxt/kit": "4.3.0",
|
|
47
47
|
"@radya/nuxt-dompurify": "^1.0.5",
|
|
48
|
+
"consola": "^3.4.0",
|
|
48
49
|
"defu": "^6.1.4",
|
|
49
50
|
"graphql": "^16.12.0",
|
|
50
51
|
"nuxt-graphql-middleware": "5.3.2",
|
|
@@ -53,12 +54,12 @@
|
|
|
53
54
|
"devDependencies": {
|
|
54
55
|
"@nuxt/devtools": "^3.1.1",
|
|
55
56
|
"@nuxt/module-builder": "^1.0.2",
|
|
56
|
-
"@nuxt/schema": "4.
|
|
57
|
+
"@nuxt/schema": "4.3.0",
|
|
57
58
|
"@nuxt/test-utils": "^3.23.0",
|
|
58
|
-
"@types/node": "^25.
|
|
59
|
-
"nuxt": "4.
|
|
60
|
-
"vitest": "^4.0.
|
|
61
|
-
"vue-tsc": "^3.2.
|
|
59
|
+
"@types/node": "^25.2.1",
|
|
60
|
+
"nuxt": "4.3.0",
|
|
61
|
+
"vitest": "^4.0.18",
|
|
62
|
+
"vue-tsc": "^3.2.3"
|
|
62
63
|
},
|
|
63
64
|
"peerDependencies": {
|
|
64
65
|
"nuxt": "^4.0.0"
|