@wpnuxt/core 2.0.0-alpha.8 → 2.0.0-beta.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/LICENSE +9 -0
- package/dist/module.d.mts +35 -0
- package/dist/module.d.ts +35 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +417 -18
- package/dist/runtime/components/WPContent.d.vue.ts +0 -0
- package/dist/runtime/components/WPContent.vue +61 -0
- package/dist/runtime/components/WPContent.vue.d.ts +0 -0
- package/dist/runtime/composables/useWPContent.js +30 -12
- package/dist/runtime/plugins/sanitizeHtml.d.ts +0 -0
- package/dist/runtime/plugins/sanitizeHtml.js +29 -0
- package/dist/runtime/queries/Pages.gql +2 -2
- package/dist/runtime/queries/Posts.gql +2 -2
- package/dist/runtime/types/stub.d.ts +6 -2
- package/dist/runtime/util/content.js +15 -7
- package/dist/runtime/util/links.d.ts +0 -0
- package/dist/runtime/util/links.js +40 -0
- package/package.json +8 -7
package/LICENSE
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024-present - WPNuxt Team
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
6
|
+
|
|
7
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/dist/module.d.mts
CHANGED
|
@@ -45,6 +45,41 @@ interface WPNuxtConfig {
|
|
|
45
45
|
* @default true
|
|
46
46
|
*/
|
|
47
47
|
downloadSchema: boolean;
|
|
48
|
+
/**
|
|
49
|
+
* Bearer token for authenticated schema introspection at build time.
|
|
50
|
+
*
|
|
51
|
+
* Required when your WordPress GraphQL endpoint has public introspection disabled.
|
|
52
|
+
* The token is sent as an `Authorization: Bearer <token>` header during:
|
|
53
|
+
* - Endpoint validation (introspection query)
|
|
54
|
+
* - Schema download (get-graphql-schema)
|
|
55
|
+
*
|
|
56
|
+
* Can also be set via `WPNUXT_SCHEMA_AUTH_TOKEN` environment variable.
|
|
57
|
+
*
|
|
58
|
+
* This token is only used at build time and is NOT included in client bundles.
|
|
59
|
+
*/
|
|
60
|
+
schemaAuthToken?: string;
|
|
61
|
+
/**
|
|
62
|
+
* Whether to replace internal WordPress links with client-side navigation (NuxtLink).
|
|
63
|
+
*
|
|
64
|
+
* When enabled, clicks on `<a>` tags pointing to the WordPress domain inside
|
|
65
|
+
* `<WPContent>` are intercepted and handled via `navigateTo()`.
|
|
66
|
+
*
|
|
67
|
+
* @default true
|
|
68
|
+
*/
|
|
69
|
+
replaceLinks?: boolean;
|
|
70
|
+
/**
|
|
71
|
+
* Whether to convert featured image `sourceUrl` values to relative paths.
|
|
72
|
+
*
|
|
73
|
+
* When enabled, a `relativePath` property is added to `featuredImage.node`
|
|
74
|
+
* by stripping the WordPress domain from `sourceUrl`.
|
|
75
|
+
*
|
|
76
|
+
* Set to `true` when using relative image paths with Nuxt Image or a proxy.
|
|
77
|
+
* Leave `false` (default) for SSG or external image providers (Vercel, Cloudflare)
|
|
78
|
+
* that need full URLs.
|
|
79
|
+
*
|
|
80
|
+
* @default false
|
|
81
|
+
*/
|
|
82
|
+
imageRelativePaths?: boolean;
|
|
48
83
|
/**
|
|
49
84
|
* Whether to enable debug mode
|
|
50
85
|
*
|
package/dist/module.d.ts
CHANGED
|
@@ -45,6 +45,41 @@ interface WPNuxtConfig {
|
|
|
45
45
|
* @default true
|
|
46
46
|
*/
|
|
47
47
|
downloadSchema: boolean;
|
|
48
|
+
/**
|
|
49
|
+
* Bearer token for authenticated schema introspection at build time.
|
|
50
|
+
*
|
|
51
|
+
* Required when your WordPress GraphQL endpoint has public introspection disabled.
|
|
52
|
+
* The token is sent as an `Authorization: Bearer <token>` header during:
|
|
53
|
+
* - Endpoint validation (introspection query)
|
|
54
|
+
* - Schema download (get-graphql-schema)
|
|
55
|
+
*
|
|
56
|
+
* Can also be set via `WPNUXT_SCHEMA_AUTH_TOKEN` environment variable.
|
|
57
|
+
*
|
|
58
|
+
* This token is only used at build time and is NOT included in client bundles.
|
|
59
|
+
*/
|
|
60
|
+
schemaAuthToken?: string;
|
|
61
|
+
/**
|
|
62
|
+
* Whether to replace internal WordPress links with client-side navigation (NuxtLink).
|
|
63
|
+
*
|
|
64
|
+
* When enabled, clicks on `<a>` tags pointing to the WordPress domain inside
|
|
65
|
+
* `<WPContent>` are intercepted and handled via `navigateTo()`.
|
|
66
|
+
*
|
|
67
|
+
* @default true
|
|
68
|
+
*/
|
|
69
|
+
replaceLinks?: boolean;
|
|
70
|
+
/**
|
|
71
|
+
* Whether to convert featured image `sourceUrl` values to relative paths.
|
|
72
|
+
*
|
|
73
|
+
* When enabled, a `relativePath` property is added to `featuredImage.node`
|
|
74
|
+
* by stripping the WordPress domain from `sourceUrl`.
|
|
75
|
+
*
|
|
76
|
+
* Set to `true` when using relative image paths with Nuxt Image or a proxy.
|
|
77
|
+
* Leave `false` (default) for SSG or external image providers (Vercel, Cloudflare)
|
|
78
|
+
* that need full URLs.
|
|
79
|
+
*
|
|
80
|
+
* @default false
|
|
81
|
+
*/
|
|
82
|
+
imageRelativePaths?: boolean;
|
|
48
83
|
/**
|
|
49
84
|
* Whether to enable debug mode
|
|
50
85
|
*
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
|
+
import { consola } from 'consola';
|
|
1
2
|
import { defu } from 'defu';
|
|
2
3
|
import { promises, cpSync, existsSync, readdirSync, statSync, readFileSync, writeFileSync } from 'node:fs';
|
|
3
|
-
import {
|
|
4
|
+
import { writeFile, rename, readFile, mkdir } from 'node:fs/promises';
|
|
4
5
|
import { join, relative, dirname } from 'node:path';
|
|
5
|
-
import { useLogger, createResolver, resolveFiles, defineNuxtModule, addPlugin, addImports, addComponentsDir, addTemplate, addTypeTemplate,
|
|
6
|
+
import { useLogger, createResolver, resolveFiles, defineNuxtModule, addPlugin, hasNuxtModule, addImports, addComponentsDir, addTemplate, addTypeTemplate, installModule } from '@nuxt/kit';
|
|
6
7
|
import { upperFirst } from 'scule';
|
|
7
8
|
import { ref } from 'vue';
|
|
8
9
|
import { parse, GraphQLError } from 'graphql';
|
|
9
10
|
import { execSync } from 'node:child_process';
|
|
10
11
|
|
|
12
|
+
const version = "2.0.0-beta.1";
|
|
13
|
+
|
|
11
14
|
function createModuleError(module, message) {
|
|
12
15
|
return new Error(formatErrorMessage(module, message));
|
|
13
16
|
}
|
|
@@ -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
|
}
|
|
@@ -175,7 +201,7 @@ async function prepareContext(ctx) {
|
|
|
175
201
|
const getFragmentType = (q) => {
|
|
176
202
|
if (q.fragments?.length) {
|
|
177
203
|
const fragmentSuffix = q.nodes?.includes("nodes") ? "[]" : "";
|
|
178
|
-
return q.fragments.map((f) =>
|
|
204
|
+
return q.fragments.map((f) => `WithImagePath<${f}Fragment>${fragmentSuffix}`).join(" | ");
|
|
179
205
|
}
|
|
180
206
|
if (q.nodes?.length) {
|
|
181
207
|
let typePath = `${q.name}RootQuery`;
|
|
@@ -278,6 +304,11 @@ async function prepareContext(ctx) {
|
|
|
278
304
|
"",
|
|
279
305
|
"type WPMutationResult<T> = GraphqlResponse<T>",
|
|
280
306
|
"",
|
|
307
|
+
"/** Adds relativePath to featuredImage.node when present (injected at runtime by transformData) */",
|
|
308
|
+
"type WithImagePath<T> = T extends { featuredImage?: unknown }",
|
|
309
|
+
" ? T & { featuredImage?: { node: { relativePath?: string } } }",
|
|
310
|
+
" : T",
|
|
311
|
+
"",
|
|
281
312
|
"declare module '#wpnuxt' {"
|
|
282
313
|
];
|
|
283
314
|
queries.forEach((f) => {
|
|
@@ -324,11 +355,15 @@ async function validateWordPressEndpoint(wordpressUrl, graphqlEndpoint = "/graph
|
|
|
324
355
|
try {
|
|
325
356
|
const controller = new AbortController();
|
|
326
357
|
const timeout = setTimeout(() => controller.abort(), 1e4);
|
|
358
|
+
const headers = {
|
|
359
|
+
"Content-Type": "application/json"
|
|
360
|
+
};
|
|
361
|
+
if (options.authToken) {
|
|
362
|
+
headers["Authorization"] = `Bearer ${options.authToken}`;
|
|
363
|
+
}
|
|
327
364
|
const response = await fetch(fullUrl, {
|
|
328
365
|
method: "POST",
|
|
329
|
-
headers
|
|
330
|
-
"Content-Type": "application/json"
|
|
331
|
-
},
|
|
366
|
+
headers,
|
|
332
367
|
body: JSON.stringify({
|
|
333
368
|
query: "{ __typename }"
|
|
334
369
|
}),
|
|
@@ -371,7 +406,8 @@ Make sure WPGraphQL plugin is installed and activated on your WordPress site.`
|
|
|
371
406
|
}
|
|
372
407
|
if (options.schemaPath && !existsSync(options.schemaPath)) {
|
|
373
408
|
try {
|
|
374
|
-
|
|
409
|
+
const authFlag = options.authToken ? ` -h "Authorization=Bearer ${options.authToken}"` : "";
|
|
410
|
+
execSync(`npx get-graphql-schema "${fullUrl}"${authFlag} > "${options.schemaPath}"`, {
|
|
375
411
|
stdio: "pipe",
|
|
376
412
|
timeout: 6e4
|
|
377
413
|
// 60 second timeout
|
|
@@ -462,9 +498,289 @@ function patchWPGraphQLSchema(schemaPath) {
|
|
|
462
498
|
writeFileSync(schemaPath, schema);
|
|
463
499
|
}
|
|
464
500
|
|
|
501
|
+
async function runInstall(nuxt) {
|
|
502
|
+
const logger = useLogger("wpnuxt", {
|
|
503
|
+
level: process.env.WPNUXT_DEBUG === "true" ? 4 : 3
|
|
504
|
+
});
|
|
505
|
+
const results = [];
|
|
506
|
+
results.push(await setupEnvFiles(nuxt, logger));
|
|
507
|
+
const parallel = await Promise.all([
|
|
508
|
+
setupMcpConfig(nuxt, logger),
|
|
509
|
+
setupGitignore(nuxt, logger),
|
|
510
|
+
setupQueriesFolder(nuxt, logger)
|
|
511
|
+
]);
|
|
512
|
+
results.push(...parallel);
|
|
513
|
+
displayInstallSummary(results, logger);
|
|
514
|
+
}
|
|
515
|
+
function displayInstallSummary(results, logger) {
|
|
516
|
+
const successes = results.filter((r) => r.success && !r.skipped);
|
|
517
|
+
const skipped = results.filter((r) => r.skipped);
|
|
518
|
+
const failures = results.filter((r) => !r.success);
|
|
519
|
+
if (successes.length > 0) {
|
|
520
|
+
const lines = [
|
|
521
|
+
...successes.map((r) => `\u2713 ${r.message || r.name}`),
|
|
522
|
+
"",
|
|
523
|
+
"Next steps:",
|
|
524
|
+
" 1. Ensure WPGraphQL is installed on WordPress",
|
|
525
|
+
" 2. Run `pnpm dev` to start development",
|
|
526
|
+
"",
|
|
527
|
+
"Docs: https://wpnuxt.com"
|
|
528
|
+
];
|
|
529
|
+
consola.box({
|
|
530
|
+
title: "WPNuxt Setup Complete",
|
|
531
|
+
message: lines.join("\n")
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
if (skipped.length > 0) {
|
|
535
|
+
logger.debug(`Skipped (already configured): ${skipped.map((r) => r.name).join(", ")}`);
|
|
536
|
+
}
|
|
537
|
+
if (failures.length > 0) {
|
|
538
|
+
logger.debug(`Failed: ${failures.map((r) => r.name).join(", ")}`);
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
const ENV_EXAMPLE_CONTENT = `# WPNuxt Configuration
|
|
542
|
+
# Required: Your WordPress site URL (must have WPGraphQL plugin installed)
|
|
543
|
+
WPNUXT_WORDPRESS_URL=https://your-wordpress-site.com
|
|
544
|
+
|
|
545
|
+
# Optional: Custom GraphQL endpoint (default: /graphql)
|
|
546
|
+
# WPNUXT_GRAPHQL_ENDPOINT=/graphql
|
|
547
|
+
|
|
548
|
+
# Optional: Enable debug mode for verbose logging
|
|
549
|
+
# WPNUXT_DEBUG=true
|
|
550
|
+
`;
|
|
551
|
+
function isInteractiveEnvironment() {
|
|
552
|
+
return !(process.env.CI === "true" || process.env.CI === "1" || process.env.VITEST === "true" || process.env.TEST === "true" || process.env.NODE_ENV === "test");
|
|
553
|
+
}
|
|
554
|
+
async function checkExistingEnvConfig(nuxt, envPath) {
|
|
555
|
+
const nuxtConfig = nuxt.options;
|
|
556
|
+
if (nuxtConfig.wpNuxt?.wordpressUrl) {
|
|
557
|
+
return { hasUrl: true, source: "nuxt.config.ts", envContent: "" };
|
|
558
|
+
}
|
|
559
|
+
if (process.env.WPNUXT_WORDPRESS_URL) {
|
|
560
|
+
return { hasUrl: true, source: "WPNUXT_WORDPRESS_URL env var", envContent: "" };
|
|
561
|
+
}
|
|
562
|
+
if (existsSync(envPath)) {
|
|
563
|
+
const envContent = await readFile(envPath, "utf-8");
|
|
564
|
+
if (/^WPNUXT_WORDPRESS_URL\s*=\s*.+/m.test(envContent)) {
|
|
565
|
+
return { hasUrl: true, source: ".env file", envContent };
|
|
566
|
+
}
|
|
567
|
+
return { hasUrl: false, envContent };
|
|
568
|
+
}
|
|
569
|
+
return { hasUrl: false, envContent: "" };
|
|
570
|
+
}
|
|
571
|
+
async function setupEnvFiles(nuxt, logger) {
|
|
572
|
+
const envPath = join(nuxt.options.rootDir, ".env");
|
|
573
|
+
const envExamplePath = join(nuxt.options.rootDir, ".env.example");
|
|
574
|
+
try {
|
|
575
|
+
const existingConfig = await checkExistingEnvConfig(nuxt, envPath);
|
|
576
|
+
let envContent = existingConfig.envContent;
|
|
577
|
+
let urlConfigured = false;
|
|
578
|
+
if (existingConfig.hasUrl) {
|
|
579
|
+
logger.debug(`WordPress URL already configured in ${existingConfig.source}`);
|
|
580
|
+
urlConfigured = true;
|
|
581
|
+
} else if (isInteractiveEnvironment()) {
|
|
582
|
+
consola.box({
|
|
583
|
+
title: "WPNuxt Setup",
|
|
584
|
+
message: "Configure your WordPress connection"
|
|
585
|
+
});
|
|
586
|
+
const wordpressUrl = await consola.prompt(
|
|
587
|
+
"What is your WordPress site URL?",
|
|
588
|
+
{
|
|
589
|
+
type: "text",
|
|
590
|
+
placeholder: "https://your-wordpress-site.com",
|
|
591
|
+
initial: ""
|
|
592
|
+
}
|
|
593
|
+
);
|
|
594
|
+
if (wordpressUrl && typeof wordpressUrl === "string" && wordpressUrl.trim()) {
|
|
595
|
+
const validation = validateWordPressUrl(wordpressUrl);
|
|
596
|
+
if (!validation.valid) {
|
|
597
|
+
logger.warn(`Invalid URL: ${validation.error}`);
|
|
598
|
+
logger.info("Skipped WordPress URL configuration. Add WPNUXT_WORDPRESS_URL to your .env file later.");
|
|
599
|
+
} else {
|
|
600
|
+
const envLine = `WPNUXT_WORDPRESS_URL=${validation.normalizedUrl}
|
|
601
|
+
`;
|
|
602
|
+
if (envContent) {
|
|
603
|
+
envContent = envContent.trimEnd() + "\n\n" + envLine;
|
|
604
|
+
} else {
|
|
605
|
+
envContent = envLine;
|
|
606
|
+
}
|
|
607
|
+
await atomicWriteFile(envPath, envContent);
|
|
608
|
+
logger.success(`WordPress URL saved to .env: ${validation.normalizedUrl}`);
|
|
609
|
+
urlConfigured = true;
|
|
610
|
+
}
|
|
611
|
+
} else {
|
|
612
|
+
logger.info("Skipped WordPress URL configuration. Add WPNUXT_WORDPRESS_URL to your .env file later.");
|
|
613
|
+
}
|
|
614
|
+
} else {
|
|
615
|
+
logger.debug("Non-interactive environment detected, skipping WordPress URL prompt");
|
|
616
|
+
}
|
|
617
|
+
let exampleContent = "";
|
|
618
|
+
let exampleUpdated = false;
|
|
619
|
+
if (existsSync(envExamplePath)) {
|
|
620
|
+
exampleContent = await readFile(envExamplePath, "utf-8");
|
|
621
|
+
if (exampleContent.includes("WPNUXT_WORDPRESS_URL")) {
|
|
622
|
+
logger.debug(".env.example already includes WPNuxt configuration");
|
|
623
|
+
} else {
|
|
624
|
+
exampleContent = exampleContent.trimEnd() + "\n\n" + ENV_EXAMPLE_CONTENT;
|
|
625
|
+
await atomicWriteFile(envExamplePath, exampleContent);
|
|
626
|
+
exampleUpdated = true;
|
|
627
|
+
}
|
|
628
|
+
} else {
|
|
629
|
+
exampleContent = ENV_EXAMPLE_CONTENT;
|
|
630
|
+
await atomicWriteFile(envExamplePath, exampleContent);
|
|
631
|
+
exampleUpdated = true;
|
|
632
|
+
}
|
|
633
|
+
if (urlConfigured && exampleUpdated) {
|
|
634
|
+
return {
|
|
635
|
+
name: "Environment files",
|
|
636
|
+
success: true,
|
|
637
|
+
message: "Configured .env with WordPress URL"
|
|
638
|
+
};
|
|
639
|
+
} else if (urlConfigured) {
|
|
640
|
+
return { name: "Environment files", success: true, skipped: true };
|
|
641
|
+
} else if (exampleUpdated) {
|
|
642
|
+
return {
|
|
643
|
+
name: "Environment files",
|
|
644
|
+
success: true,
|
|
645
|
+
message: "Created .env.example template"
|
|
646
|
+
};
|
|
647
|
+
} else {
|
|
648
|
+
return { name: "Environment files", success: true, skipped: true };
|
|
649
|
+
}
|
|
650
|
+
} catch (error) {
|
|
651
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
652
|
+
logger.warn(`Failed to setup environment files: ${message}`);
|
|
653
|
+
return { name: "Environment files", success: false, message };
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
const MCP_CONFIG = {
|
|
657
|
+
wpnuxt: {
|
|
658
|
+
type: "http",
|
|
659
|
+
url: "https://wpnuxt.com/mcp"
|
|
660
|
+
}
|
|
661
|
+
};
|
|
662
|
+
async function setupMcpConfig(nuxt, logger) {
|
|
663
|
+
const mcpPath = join(nuxt.options.rootDir, ".mcp.json");
|
|
664
|
+
try {
|
|
665
|
+
let config = { mcpServers: {} };
|
|
666
|
+
if (existsSync(mcpPath)) {
|
|
667
|
+
const content = await readFile(mcpPath, "utf-8");
|
|
668
|
+
config = JSON.parse(content);
|
|
669
|
+
config.mcpServers = config.mcpServers || {};
|
|
670
|
+
if (config.mcpServers.wpnuxt) {
|
|
671
|
+
logger.debug("MCP config already includes wpnuxt server");
|
|
672
|
+
return { name: "MCP config", success: true, skipped: true };
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
config.mcpServers = {
|
|
676
|
+
...config.mcpServers,
|
|
677
|
+
...MCP_CONFIG
|
|
678
|
+
};
|
|
679
|
+
await atomicWriteFile(mcpPath, JSON.stringify(config, null, 2) + "\n");
|
|
680
|
+
return {
|
|
681
|
+
name: "MCP config",
|
|
682
|
+
success: true,
|
|
683
|
+
message: "Created .mcp.json with WPNuxt MCP server"
|
|
684
|
+
};
|
|
685
|
+
} catch (error) {
|
|
686
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
687
|
+
logger.warn(`Failed to setup MCP configuration: ${message}`);
|
|
688
|
+
return { name: "MCP config", success: false, message };
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
async function setupGitignore(nuxt, logger) {
|
|
692
|
+
const gitignorePath = join(nuxt.options.rootDir, ".gitignore");
|
|
693
|
+
try {
|
|
694
|
+
let content = "";
|
|
695
|
+
if (existsSync(gitignorePath)) {
|
|
696
|
+
content = await readFile(gitignorePath, "utf-8");
|
|
697
|
+
if (content.includes(".queries")) {
|
|
698
|
+
logger.debug(".gitignore already includes .queries");
|
|
699
|
+
return { name: "Gitignore", success: true, skipped: true };
|
|
700
|
+
}
|
|
701
|
+
content = content.trimEnd() + "\n\n# WPNuxt generated files\n.queries/\n";
|
|
702
|
+
} else {
|
|
703
|
+
content = "# WPNuxt generated files\n.queries/\n";
|
|
704
|
+
}
|
|
705
|
+
await atomicWriteFile(gitignorePath, content);
|
|
706
|
+
return {
|
|
707
|
+
name: "Gitignore",
|
|
708
|
+
success: true,
|
|
709
|
+
message: "Added .queries/ to .gitignore"
|
|
710
|
+
};
|
|
711
|
+
} catch (error) {
|
|
712
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
713
|
+
logger.warn(`Failed to setup .gitignore: ${message}`);
|
|
714
|
+
return { name: "Gitignore", success: false, message };
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
const QUERIES_README = `# Custom GraphQL Queries
|
|
718
|
+
|
|
719
|
+
Place your custom \`.gql\` or \`.graphql\` files here to extend or override the default WPNuxt queries.
|
|
720
|
+
|
|
721
|
+
## How it works
|
|
722
|
+
|
|
723
|
+
1. Files here are merged with WPNuxt's default queries during build
|
|
724
|
+
2. If a file has the same name as a default query, yours will override it
|
|
725
|
+
3. New files will generate new composables automatically
|
|
726
|
+
|
|
727
|
+
## Example
|
|
728
|
+
|
|
729
|
+
Create a file \`CustomPosts.gql\`:
|
|
730
|
+
|
|
731
|
+
\`\`\`graphql
|
|
732
|
+
query CustomPosts($first: Int = 10) {
|
|
733
|
+
posts(first: $first) {
|
|
734
|
+
nodes {
|
|
735
|
+
id
|
|
736
|
+
title
|
|
737
|
+
date
|
|
738
|
+
# Add your custom fields here
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
\`\`\`
|
|
743
|
+
|
|
744
|
+
This generates \`useCustomPosts()\` and \`useAsyncCustomPosts()\` composables.
|
|
745
|
+
|
|
746
|
+
## Available Fragments
|
|
747
|
+
|
|
748
|
+
You can use these fragments from WPNuxt's defaults:
|
|
749
|
+
- \`...Post\` - Standard post fields
|
|
750
|
+
- \`...Page\` - Standard page fields
|
|
751
|
+
- \`...ContentNode\` - Common content fields
|
|
752
|
+
- \`...FeaturedImage\` - Featured image with sizes
|
|
753
|
+
|
|
754
|
+
## Documentation
|
|
755
|
+
|
|
756
|
+
See https://wpnuxt.com/guide/custom-queries for more details.
|
|
757
|
+
`;
|
|
758
|
+
async function setupQueriesFolder(nuxt, logger) {
|
|
759
|
+
const queriesPath = join(nuxt.options.rootDir, "extend", "queries");
|
|
760
|
+
const readmePath = join(queriesPath, "README.md");
|
|
761
|
+
try {
|
|
762
|
+
if (existsSync(readmePath)) {
|
|
763
|
+
logger.debug("extend/queries/ folder already exists");
|
|
764
|
+
return { name: "Queries folder", success: true, skipped: true };
|
|
765
|
+
}
|
|
766
|
+
await mkdir(queriesPath, { recursive: true });
|
|
767
|
+
await atomicWriteFile(readmePath, QUERIES_README);
|
|
768
|
+
return {
|
|
769
|
+
name: "Queries folder",
|
|
770
|
+
success: true,
|
|
771
|
+
message: "Created extend/queries/ for custom GraphQL queries"
|
|
772
|
+
};
|
|
773
|
+
} catch (error) {
|
|
774
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
775
|
+
logger.warn(`Failed to setup extend/queries/ folder: ${message}`);
|
|
776
|
+
return { name: "Queries folder", success: false, message };
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
|
|
465
780
|
const module$1 = defineNuxtModule({
|
|
466
781
|
meta: {
|
|
467
782
|
name: "@wpnuxt/core",
|
|
783
|
+
version,
|
|
468
784
|
configKey: "wpNuxt",
|
|
469
785
|
compatibility: {
|
|
470
786
|
nuxt: ">=3.0.0"
|
|
@@ -479,6 +795,8 @@ const module$1 = defineNuxtModule({
|
|
|
479
795
|
warnOnOverride: true
|
|
480
796
|
},
|
|
481
797
|
downloadSchema: true,
|
|
798
|
+
replaceLinks: true,
|
|
799
|
+
imageRelativePaths: false,
|
|
482
800
|
debug: false,
|
|
483
801
|
cache: {
|
|
484
802
|
enabled: true,
|
|
@@ -489,13 +807,19 @@ const module$1 = defineNuxtModule({
|
|
|
489
807
|
},
|
|
490
808
|
async setup(options, nuxt) {
|
|
491
809
|
const startTime = (/* @__PURE__ */ new Date()).getTime();
|
|
492
|
-
const wpNuxtConfig = loadConfig(options, nuxt);
|
|
810
|
+
const wpNuxtConfig = await loadConfig(options, nuxt);
|
|
811
|
+
if (!wpNuxtConfig) {
|
|
812
|
+
const logger2 = initLogger(false);
|
|
813
|
+
logger2.warn("WordPress URL not configured. Skipping WPNuxt setup. Set it in nuxt.config.ts or via WPNUXT_WORDPRESS_URL environment variable.");
|
|
814
|
+
return;
|
|
815
|
+
}
|
|
493
816
|
const logger = initLogger(wpNuxtConfig.debug);
|
|
494
817
|
logger.debug("Starting WPNuxt in debug mode");
|
|
495
818
|
const resolver = createResolver(import.meta.url);
|
|
496
819
|
nuxt.options.runtimeConfig.public.buildHash = randHashGenerator();
|
|
497
820
|
addPlugin(resolver.resolve("./runtime/plugins/graphqlConfig"));
|
|
498
821
|
addPlugin(resolver.resolve("./runtime/plugins/graphqlErrors"));
|
|
822
|
+
addPlugin(resolver.resolve("./runtime/plugins/sanitizeHtml"));
|
|
499
823
|
configureTrailingSlash(nuxt, logger);
|
|
500
824
|
const mergedQueriesFolder = await mergeQueries(nuxt, wpNuxtConfig, resolver);
|
|
501
825
|
await setupServerOptions(nuxt, resolver, logger);
|
|
@@ -508,7 +832,7 @@ const module$1 = defineNuxtModule({
|
|
|
508
832
|
await validateWordPressEndpoint(
|
|
509
833
|
wpNuxtConfig.wordpressUrl,
|
|
510
834
|
wpNuxtConfig.graphqlEndpoint,
|
|
511
|
-
{ schemaPath }
|
|
835
|
+
{ schemaPath, authToken: wpNuxtConfig.schemaAuthToken }
|
|
512
836
|
);
|
|
513
837
|
logger.debug("Schema downloaded successfully");
|
|
514
838
|
} else {
|
|
@@ -516,7 +840,8 @@ const module$1 = defineNuxtModule({
|
|
|
516
840
|
try {
|
|
517
841
|
await validateWordPressEndpoint(
|
|
518
842
|
wpNuxtConfig.wordpressUrl,
|
|
519
|
-
wpNuxtConfig.graphqlEndpoint
|
|
843
|
+
wpNuxtConfig.graphqlEndpoint,
|
|
844
|
+
{ authToken: wpNuxtConfig.schemaAuthToken }
|
|
520
845
|
);
|
|
521
846
|
logger.debug("WordPress endpoint validation passed");
|
|
522
847
|
} catch (error) {
|
|
@@ -548,11 +873,39 @@ const module$1 = defineNuxtModule({
|
|
|
548
873
|
};
|
|
549
874
|
logger.debug(`Server-side caching enabled for GraphQL requests (maxAge: ${maxAge}s, SWR: ${wpNuxtConfig.cache?.swr !== false})`);
|
|
550
875
|
}
|
|
876
|
+
{
|
|
877
|
+
const nitroOptions = nuxt.options;
|
|
878
|
+
nitroOptions.nitro = nitroOptions.nitro || {};
|
|
879
|
+
nitroOptions.nitro.routeRules = nitroOptions.nitro.routeRules || {};
|
|
880
|
+
nitroOptions.nitro.routeRules["/wp-content/uploads/**"] = {
|
|
881
|
+
proxy: `${wpNuxtConfig.wordpressUrl}/wp-content/uploads/**`
|
|
882
|
+
};
|
|
883
|
+
logger.debug(`Configured WordPress uploads proxy: /wp-content/uploads/** \u2192 ${wpNuxtConfig.wordpressUrl}/wp-content/uploads/**`);
|
|
884
|
+
}
|
|
885
|
+
if (hasNuxtModule("@nuxt/image")) {
|
|
886
|
+
const imageConfig = nuxt.options.image || {};
|
|
887
|
+
const provider = process.env.NUXT_IMAGE_PROVIDER || imageConfig.provider || "ipx";
|
|
888
|
+
if (provider === "ipx") {
|
|
889
|
+
const wpHost = new URL(wpNuxtConfig.wordpressUrl).host;
|
|
890
|
+
const domains = imageConfig.domains || [];
|
|
891
|
+
if (!domains.includes(wpHost)) {
|
|
892
|
+
domains.push(wpHost);
|
|
893
|
+
}
|
|
894
|
+
imageConfig.domains = domains;
|
|
895
|
+
const alias = imageConfig.alias || {};
|
|
896
|
+
alias["/wp-content"] = `${wpNuxtConfig.wordpressUrl}/wp-content`;
|
|
897
|
+
imageConfig.alias = alias;
|
|
898
|
+
nuxt.options.image = imageConfig;
|
|
899
|
+
logger.debug(`Configured IPX for WordPress: alias /wp-content \u2192 ${wpNuxtConfig.wordpressUrl}/wp-content, domain '${wpHost}' added`);
|
|
900
|
+
}
|
|
901
|
+
}
|
|
551
902
|
configureVercelSettings(nuxt, logger);
|
|
552
903
|
addImports([
|
|
553
904
|
{ name: "useWPContent", as: "useWPContent", from: resolver.resolve("./runtime/composables/useWPContent") },
|
|
554
905
|
{ name: "useAsyncWPContent", as: "useAsyncWPContent", from: resolver.resolve("./runtime/composables/useWPContent") },
|
|
555
906
|
{ name: "getRelativeImagePath", as: "getRelativeImagePath", from: resolver.resolve("./runtime/util/images") },
|
|
907
|
+
{ name: "isInternalLink", as: "isInternalLink", from: resolver.resolve("./runtime/util/links") },
|
|
908
|
+
{ name: "toRelativePath", as: "toRelativePath", from: resolver.resolve("./runtime/util/links") },
|
|
556
909
|
{ name: "usePrevNextPost", as: "usePrevNextPost", from: resolver.resolve("./runtime/composables/usePrevNextPost") }
|
|
557
910
|
// Note: useGraphqlMutation is auto-imported via nuxt-graphql-middleware with includeComposables: true
|
|
558
911
|
]);
|
|
@@ -572,6 +925,8 @@ const module$1 = defineNuxtModule({
|
|
|
572
925
|
nuxt.options.alias["#wpnuxt"] = resolver.resolve(nuxt.options.buildDir, "wpnuxt");
|
|
573
926
|
nuxt.options.alias["#wpnuxt/*"] = resolver.resolve(nuxt.options.buildDir, "wpnuxt", "*");
|
|
574
927
|
nuxt.options.alias["#wpnuxt/types"] = resolver.resolve("./types");
|
|
928
|
+
nuxt.options.alias["@wpnuxt/core/server-options"] = resolver.resolve("./server-options");
|
|
929
|
+
nuxt.options.alias["@wpnuxt/core/client-options"] = resolver.resolve("./client-options");
|
|
575
930
|
const nitroOpts = nuxt.options;
|
|
576
931
|
nitroOpts.nitro = nitroOpts.nitro || {};
|
|
577
932
|
nitroOpts.nitro.alias = nitroOpts.nitro.alias || {};
|
|
@@ -593,12 +948,16 @@ const module$1 = defineNuxtModule({
|
|
|
593
948
|
});
|
|
594
949
|
logger.trace("Finished generating composables");
|
|
595
950
|
logger.info(`WPNuxt module loaded in ${(/* @__PURE__ */ new Date()).getTime() - startTime}ms`);
|
|
951
|
+
},
|
|
952
|
+
async onInstall(nuxt) {
|
|
953
|
+
await runInstall(nuxt);
|
|
596
954
|
}
|
|
597
955
|
});
|
|
598
|
-
function loadConfig(options, nuxt) {
|
|
956
|
+
async function loadConfig(options, nuxt) {
|
|
599
957
|
const config = defu({
|
|
600
958
|
wordpressUrl: process.env.WPNUXT_WORDPRESS_URL,
|
|
601
959
|
graphqlEndpoint: process.env.WPNUXT_GRAPHQL_ENDPOINT,
|
|
960
|
+
schemaAuthToken: process.env.WPNUXT_SCHEMA_AUTH_TOKEN,
|
|
602
961
|
// Only override downloadSchema if env var is explicitly set
|
|
603
962
|
downloadSchema: process.env.WPNUXT_DOWNLOAD_SCHEMA !== void 0 ? process.env.WPNUXT_DOWNLOAD_SCHEMA === "true" : void 0,
|
|
604
963
|
debug: process.env.WPNUXT_DEBUG ? process.env.WPNUXT_DEBUG === "true" : void 0
|
|
@@ -606,22 +965,55 @@ function loadConfig(options, nuxt) {
|
|
|
606
965
|
if (config.downloadSchema === void 0) {
|
|
607
966
|
config.downloadSchema = true;
|
|
608
967
|
}
|
|
968
|
+
if (!config.wordpressUrl?.trim()) {
|
|
969
|
+
if (nuxt.options._prepare) {
|
|
970
|
+
return null;
|
|
971
|
+
}
|
|
972
|
+
if (nuxt.options.dev && process.stdout.isTTY) {
|
|
973
|
+
const wordpressUrl = await consola.prompt(
|
|
974
|
+
"Enter your WordPress site URL (must have WPGraphQL installed):",
|
|
975
|
+
{ type: "text", placeholder: "https://your-wordpress-site.com" }
|
|
976
|
+
);
|
|
977
|
+
if (wordpressUrl && typeof wordpressUrl === "string" && wordpressUrl.trim()) {
|
|
978
|
+
const validation = validateWordPressUrl(wordpressUrl);
|
|
979
|
+
if (validation.valid && validation.normalizedUrl) {
|
|
980
|
+
config.wordpressUrl = validation.normalizedUrl;
|
|
981
|
+
const envPath = join(nuxt.options.rootDir, ".env");
|
|
982
|
+
const envLine = `WPNUXT_WORDPRESS_URL=${validation.normalizedUrl}
|
|
983
|
+
`;
|
|
984
|
+
if (existsSync(envPath)) {
|
|
985
|
+
const existing = await readFile(envPath, "utf-8");
|
|
986
|
+
await atomicWriteFile(envPath, existing.trimEnd() + "\n" + envLine);
|
|
987
|
+
} else {
|
|
988
|
+
await atomicWriteFile(envPath, envLine);
|
|
989
|
+
}
|
|
990
|
+
consola.success(`WordPress URL saved to .env: ${validation.normalizedUrl}`);
|
|
991
|
+
} else {
|
|
992
|
+
throw createModuleError("core", `Invalid WordPress URL: ${validation.error}`);
|
|
993
|
+
}
|
|
994
|
+
} else {
|
|
995
|
+
throw createModuleError("core", "WordPress URL is required. Set it in nuxt.config.ts or via WPNUXT_WORDPRESS_URL environment variable.");
|
|
996
|
+
}
|
|
997
|
+
} else {
|
|
998
|
+
throw createModuleError("core", "WordPress URL is required. Set it in nuxt.config.ts or via WPNUXT_WORDPRESS_URL environment variable.");
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
if (config.wordpressUrl.endsWith("/")) {
|
|
1002
|
+
throw createModuleError("core", `WordPress URL should not have a trailing slash: ${config.wordpressUrl}`);
|
|
1003
|
+
}
|
|
609
1004
|
nuxt.options.runtimeConfig.public.wordpressUrl = config.wordpressUrl;
|
|
610
1005
|
nuxt.options.runtimeConfig.public.wpNuxt = {
|
|
611
1006
|
wordpressUrl: config.wordpressUrl,
|
|
612
1007
|
graphqlEndpoint: config.graphqlEndpoint,
|
|
1008
|
+
replaceLinks: config.replaceLinks ?? true,
|
|
1009
|
+
imageRelativePaths: config.imageRelativePaths ?? false,
|
|
1010
|
+
hasBlocks: hasNuxtModule("@wpnuxt/blocks"),
|
|
613
1011
|
cache: {
|
|
614
1012
|
enabled: config.cache?.enabled ?? true,
|
|
615
1013
|
maxAge: config.cache?.maxAge ?? 300,
|
|
616
1014
|
swr: config.cache?.swr ?? true
|
|
617
1015
|
}
|
|
618
1016
|
};
|
|
619
|
-
if (!config.wordpressUrl?.trim()) {
|
|
620
|
-
throw createModuleError("core", "WordPress URL is required. Set it in nuxt.config.ts or via WPNUXT_WORDPRESS_URL environment variable.");
|
|
621
|
-
}
|
|
622
|
-
if (config.wordpressUrl.endsWith("/")) {
|
|
623
|
-
throw createModuleError("core", `WordPress URL should not have a trailing slash: ${config.wordpressUrl}`);
|
|
624
|
-
}
|
|
625
1017
|
return config;
|
|
626
1018
|
}
|
|
627
1019
|
const SERVER_OPTIONS_TEMPLATE = `import { defineGraphqlServerOptions } from '@wpnuxt/core/server-options'
|
|
@@ -816,6 +1208,14 @@ async function registerModules(nuxt, resolver, wpNuxtConfig, mergedQueriesFolder
|
|
|
816
1208
|
scalars: {
|
|
817
1209
|
DateTime: "string",
|
|
818
1210
|
ID: "string"
|
|
1211
|
+
},
|
|
1212
|
+
// Pass auth headers for schema download when token is configured
|
|
1213
|
+
...wpNuxtConfig.schemaAuthToken && {
|
|
1214
|
+
urlSchemaOptions: {
|
|
1215
|
+
headers: {
|
|
1216
|
+
Authorization: `Bearer ${wpNuxtConfig.schemaAuthToken}`
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
819
1219
|
}
|
|
820
1220
|
},
|
|
821
1221
|
experimental: {
|
|
@@ -823,7 +1223,6 @@ async function registerModules(nuxt, resolver, wpNuxtConfig, mergedQueriesFolder
|
|
|
823
1223
|
improvedQueryParamEncoding: true
|
|
824
1224
|
}
|
|
825
1225
|
});
|
|
826
|
-
await registerModule("@radya/nuxt-dompurify", "dompurify", {});
|
|
827
1226
|
}
|
|
828
1227
|
|
|
829
1228
|
export { module$1 as default };
|
|
File without changes
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { isInternalLink, toRelativePath } from "../util/links";
|
|
3
|
+
import { ref, resolveComponent, onMounted, onBeforeUnmount, useRuntimeConfig, navigateTo } from "#imports";
|
|
4
|
+
const wpNuxtConfig = useRuntimeConfig().public.wpNuxt;
|
|
5
|
+
const hasBlockRenderer = !!wpNuxtConfig?.hasBlocks;
|
|
6
|
+
const blockRenderer = hasBlockRenderer ? resolveComponent("BlockRenderer") : null;
|
|
7
|
+
const props = defineProps({
|
|
8
|
+
node: { type: Object, required: false, default: void 0 },
|
|
9
|
+
replaceLinks: { type: Boolean, required: false, default: void 0 }
|
|
10
|
+
});
|
|
11
|
+
const containerRef = ref(null);
|
|
12
|
+
function shouldReplaceLinks() {
|
|
13
|
+
if (props.replaceLinks !== void 0) {
|
|
14
|
+
return props.replaceLinks;
|
|
15
|
+
}
|
|
16
|
+
const config = useRuntimeConfig();
|
|
17
|
+
return config.public.wpNuxt?.replaceLinks !== false;
|
|
18
|
+
}
|
|
19
|
+
function onClick(event) {
|
|
20
|
+
if (!shouldReplaceLinks()) return;
|
|
21
|
+
if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
|
|
22
|
+
const anchor = event.target?.closest?.("a");
|
|
23
|
+
if (!anchor) return;
|
|
24
|
+
const href = anchor.getAttribute("href");
|
|
25
|
+
if (!href) return;
|
|
26
|
+
if (anchor.getAttribute("target") === "_blank") return;
|
|
27
|
+
if (anchor.hasAttribute("download")) return;
|
|
28
|
+
if (anchor.getAttribute("rel")?.includes("external")) return;
|
|
29
|
+
const trimmed = href.trim();
|
|
30
|
+
if (/^(?:mailto|tel|javascript|ftp):/i.test(trimmed)) return;
|
|
31
|
+
const config = useRuntimeConfig();
|
|
32
|
+
const wordpressUrl = config.public.wpNuxt?.wordpressUrl;
|
|
33
|
+
if (isInternalLink(href, wordpressUrl)) {
|
|
34
|
+
event.preventDefault();
|
|
35
|
+
navigateTo(toRelativePath(href));
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
onMounted(() => {
|
|
39
|
+
containerRef.value?.addEventListener("click", onClick);
|
|
40
|
+
});
|
|
41
|
+
onBeforeUnmount(() => {
|
|
42
|
+
containerRef.value?.removeEventListener("click", onClick);
|
|
43
|
+
});
|
|
44
|
+
</script>
|
|
45
|
+
|
|
46
|
+
<template>
|
|
47
|
+
<div ref="containerRef">
|
|
48
|
+
<template v-if="node">
|
|
49
|
+
<component
|
|
50
|
+
:is="blockRenderer"
|
|
51
|
+
v-if="hasBlockRenderer && node.editorBlocks?.length"
|
|
52
|
+
:node="node"
|
|
53
|
+
/>
|
|
54
|
+
<div
|
|
55
|
+
v-else-if="node.content"
|
|
56
|
+
v-sanitize-html="node.content"
|
|
57
|
+
/>
|
|
58
|
+
</template>
|
|
59
|
+
<slot v-else />
|
|
60
|
+
</div>
|
|
61
|
+
</template>
|
|
File without changes
|
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
import { transformData, normalizeUriParam } from "../util/content.js";
|
|
2
|
-
import { computed, ref, watch as vueWatch, useAsyncGraphqlQuery } from "#imports";
|
|
2
|
+
import { computed, ref, watch as vueWatch, useAsyncGraphqlQuery, useRuntimeConfig } from "#imports";
|
|
3
|
+
const defaultGetCachedData = (key, app, ctx) => {
|
|
4
|
+
if (app.isHydrating) {
|
|
5
|
+
return app.payload.data[key];
|
|
6
|
+
}
|
|
7
|
+
if (ctx.cause === "refresh:manual" || ctx.cause === "refresh:hook") {
|
|
8
|
+
return void 0;
|
|
9
|
+
}
|
|
10
|
+
return app.static?.data?.[key] ?? app.payload.data[key] ?? app.$graphqlCache?.get(key);
|
|
11
|
+
};
|
|
12
|
+
const noCacheGetCachedData = () => void 0;
|
|
3
13
|
export const useWPContent = (queryName, nodes, fixImagePaths, params, options) => {
|
|
14
|
+
const runtimeWpNuxt = useRuntimeConfig().public.wpNuxt;
|
|
15
|
+
const imageRelativePaths = runtimeWpNuxt?.imageRelativePaths ?? fixImagePaths;
|
|
4
16
|
const {
|
|
5
17
|
clientCache,
|
|
6
18
|
getCachedData: userGetCachedData,
|
|
@@ -26,19 +38,11 @@ export const useWPContent = (queryName, nodes, fixImagePaths, params, options) =
|
|
|
26
38
|
}
|
|
27
39
|
}, timeoutMs);
|
|
28
40
|
}
|
|
29
|
-
const
|
|
30
|
-
if (app.isHydrating) {
|
|
31
|
-
return app.payload.data[key];
|
|
32
|
-
}
|
|
33
|
-
if (ctx.cause === "refresh:manual" || ctx.cause === "refresh:hook") {
|
|
34
|
-
return void 0;
|
|
35
|
-
}
|
|
36
|
-
return app.static?.data?.[key] ?? app.payload.data[key] ?? app.$graphqlCache?.get(key);
|
|
37
|
-
});
|
|
41
|
+
const getCachedDataFn = userGetCachedData ?? (clientCache === false ? noCacheGetCachedData : defaultGetCachedData);
|
|
38
42
|
const asyncDataOptions = {
|
|
39
43
|
...restOptions,
|
|
40
44
|
// Our getCachedData that properly checks static.data for SSG
|
|
41
|
-
getCachedData:
|
|
45
|
+
getCachedData: getCachedDataFn,
|
|
42
46
|
// Enable graphql caching so the LRU cache is populated for subsequent navigations
|
|
43
47
|
graphqlCaching: { client: clientCache !== false },
|
|
44
48
|
// Pass abort signal for timeout support
|
|
@@ -91,7 +95,21 @@ export const useWPContent = (queryName, nodes, fixImagePaths, params, options) =
|
|
|
91
95
|
try {
|
|
92
96
|
const queryResult = data.value && typeof data.value === "object" && data.value !== null && "data" in data.value ? data.value.data : void 0;
|
|
93
97
|
if (!queryResult) return void 0;
|
|
94
|
-
|
|
98
|
+
const result = transformData(queryResult, nodes, imageRelativePaths);
|
|
99
|
+
if (import.meta.dev && String(queryName) === "Menu" && !result) {
|
|
100
|
+
console.warn(
|
|
101
|
+
`[wpnuxt] Menu not found. This usually means no classic WordPress menu exists with the specified name.
|
|
102
|
+
|
|
103
|
+
If you're using a block theme (WordPress 6.0+), menus are managed differently:
|
|
104
|
+
1. Classic menus: Go to /wp-admin/nav-menus.php to create a menu
|
|
105
|
+
2. Make sure the menu name matches your query parameter (default: "main")
|
|
106
|
+
|
|
107
|
+
Example: useMenu({ name: 'main' }) requires a menu named "main" in WordPress.
|
|
108
|
+
|
|
109
|
+
See: https://wpnuxt.com/guide/menus`
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
return result;
|
|
95
113
|
} catch (err) {
|
|
96
114
|
if (import.meta.dev) {
|
|
97
115
|
console.warn(`[wpnuxt] Data transformation error for "${String(queryName)}":`, err);
|
|
File without changes
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { defineNuxtPlugin } from "#imports";
|
|
2
|
+
export default defineNuxtPlugin((nuxtApp) => {
|
|
3
|
+
let sanitize;
|
|
4
|
+
if (import.meta.server) {
|
|
5
|
+
sanitize = (html) => html;
|
|
6
|
+
} else {
|
|
7
|
+
let purify = null;
|
|
8
|
+
sanitize = (html) => {
|
|
9
|
+
if (purify) {
|
|
10
|
+
return purify.sanitize(html);
|
|
11
|
+
}
|
|
12
|
+
return html;
|
|
13
|
+
};
|
|
14
|
+
import("dompurify").then((DOMPurify) => {
|
|
15
|
+
purify = DOMPurify.default(window);
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
nuxtApp.vueApp.directive("sanitize-html", {
|
|
19
|
+
created(el, binding) {
|
|
20
|
+
el.innerHTML = sanitize(binding.value);
|
|
21
|
+
},
|
|
22
|
+
updated(el, binding) {
|
|
23
|
+
el.innerHTML = sanitize(binding.value);
|
|
24
|
+
},
|
|
25
|
+
getSSRProps(binding) {
|
|
26
|
+
return { innerHTML: sanitize(binding.value) };
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
});
|
|
@@ -4,12 +4,15 @@
|
|
|
4
4
|
* Actual types are generated at runtime in consuming applications.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import type { Ref, ComputedRef, WatchSource, WatchCallback, WatchOptions } from 'vue'
|
|
7
|
+
import type { Ref, ComputedRef, Component, WatchSource, WatchCallback, WatchOptions } from 'vue'
|
|
8
8
|
import type { NuxtApp } from 'nuxt/app'
|
|
9
9
|
|
|
10
|
-
// Stub for #imports - Vue reactivity
|
|
10
|
+
// Stub for #imports - Vue reactivity & lifecycle
|
|
11
11
|
export function computed<T>(getter: () => T): ComputedRef<T>
|
|
12
12
|
export function ref<T>(value: T): Ref<T>
|
|
13
|
+
export function resolveComponent(name: string): Component | string
|
|
14
|
+
export function onMounted(callback: () => void): void
|
|
15
|
+
export function onBeforeUnmount(callback: () => void): void
|
|
13
16
|
export function watch<T>(
|
|
14
17
|
source: WatchSource<T> | WatchSource<T>[],
|
|
15
18
|
callback: WatchCallback<T>,
|
|
@@ -21,6 +24,7 @@ export function defineNuxtPlugin(plugin: (nuxtApp: NuxtApp) => void | Promise<vo
|
|
|
21
24
|
export function useNuxtApp(): NuxtApp
|
|
22
25
|
export function useRuntimeConfig(): Record<string, unknown>
|
|
23
26
|
export function useRoute(): { path: string, params: Record<string, string>, query: Record<string, string> }
|
|
27
|
+
export function navigateTo(to: string): Promise<void>
|
|
24
28
|
|
|
25
29
|
// Stub for #imports - nuxt-graphql-middleware
|
|
26
30
|
export function useAsyncGraphqlQuery<T = unknown>(
|
|
@@ -8,15 +8,23 @@ export const findData = (data, nodes) => {
|
|
|
8
8
|
return void 0;
|
|
9
9
|
}, data);
|
|
10
10
|
};
|
|
11
|
+
function addRelativePath(item) {
|
|
12
|
+
if (!item || typeof item !== "object" || !("featuredImage" in item)) return;
|
|
13
|
+
const featuredImage = item.featuredImage;
|
|
14
|
+
if (featuredImage && typeof featuredImage === "object" && "node" in featuredImage) {
|
|
15
|
+
const node = featuredImage.node;
|
|
16
|
+
if (node && typeof node === "object" && "sourceUrl" in node && typeof node.sourceUrl === "string") {
|
|
17
|
+
node.relativePath = getRelativeImagePath(node.sourceUrl);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
11
21
|
export const transformData = (data, nodes, fixImagePaths) => {
|
|
12
22
|
const transformedData = findData(data, nodes);
|
|
13
|
-
if (fixImagePaths && transformedData
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
node.relativePath = getRelativeImagePath(node.sourceUrl);
|
|
19
|
-
}
|
|
23
|
+
if (fixImagePaths && transformedData) {
|
|
24
|
+
if (Array.isArray(transformedData)) {
|
|
25
|
+
transformedData.forEach(addRelativePath);
|
|
26
|
+
} else {
|
|
27
|
+
addRelativePath(transformedData);
|
|
20
28
|
}
|
|
21
29
|
}
|
|
22
30
|
return transformedData;
|
|
File without changes
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
function isInternalLink(url, wordpressUrl) {
|
|
2
|
+
if (!url || typeof url !== "string") {
|
|
3
|
+
return false;
|
|
4
|
+
}
|
|
5
|
+
const trimmed = url.trim();
|
|
6
|
+
if (trimmed.startsWith("/") && !trimmed.startsWith("//")) {
|
|
7
|
+
return true;
|
|
8
|
+
}
|
|
9
|
+
if (/^(?!https?:\/\/)/i.test(trimmed) && !trimmed.startsWith("//")) {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
if (!wordpressUrl || typeof wordpressUrl !== "string") {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
try {
|
|
16
|
+
const fullUrl = trimmed.startsWith("//") ? `https:${trimmed}` : trimmed;
|
|
17
|
+
const parsedUrl = new URL(fullUrl);
|
|
18
|
+
const parsedWp = new URL(wordpressUrl);
|
|
19
|
+
return parsedUrl.hostname === parsedWp.hostname;
|
|
20
|
+
} catch {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function toRelativePath(url) {
|
|
25
|
+
if (!url || typeof url !== "string") {
|
|
26
|
+
return "";
|
|
27
|
+
}
|
|
28
|
+
const trimmed = url.trim();
|
|
29
|
+
if (trimmed.startsWith("/") && !trimmed.startsWith("//")) {
|
|
30
|
+
return trimmed;
|
|
31
|
+
}
|
|
32
|
+
try {
|
|
33
|
+
const fullUrl = trimmed.startsWith("//") ? `https:${trimmed}` : trimmed;
|
|
34
|
+
const parsed = new URL(fullUrl);
|
|
35
|
+
return parsed.pathname + parsed.search + parsed.hash;
|
|
36
|
+
} catch {
|
|
37
|
+
return trimmed;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
export { isInternalLink, toRelativePath };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wpnuxt/core",
|
|
3
|
-
"version": "2.0.0-
|
|
3
|
+
"version": "2.0.0-beta.1",
|
|
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.3.
|
|
47
|
-
"
|
|
46
|
+
"@nuxt/kit": "4.3.1",
|
|
47
|
+
"dompurify": "^3.1.7",
|
|
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,10 +54,10 @@
|
|
|
53
54
|
"devDependencies": {
|
|
54
55
|
"@nuxt/devtools": "^3.1.1",
|
|
55
56
|
"@nuxt/module-builder": "^1.0.2",
|
|
56
|
-
"@nuxt/schema": "4.3.
|
|
57
|
-
"@nuxt/test-utils": "^
|
|
58
|
-
"@types/node": "^25.
|
|
59
|
-
"nuxt": "4.3.
|
|
57
|
+
"@nuxt/schema": "4.3.1",
|
|
58
|
+
"@nuxt/test-utils": "^4.0.0",
|
|
59
|
+
"@types/node": "^25.2.2",
|
|
60
|
+
"nuxt": "4.3.1",
|
|
60
61
|
"vitest": "^4.0.18",
|
|
61
62
|
"vue-tsc": "^3.2.3"
|
|
62
63
|
},
|