22 Commits

Author SHA1 Message Date
2d93d44a93 chore(release): v0.1.8
Some checks failed
Deployment / wordpress (push) Failing after 2s
Deployment / nuxt (push) Failing after 9s
2026-01-28 21:14:24 -05:00
ec64a42c2e chore: update pnpm-lock.yaml
Some checks failed
Deployment / wordpress (push) Failing after 2s
Deployment / nuxt (push) Has been cancelled
2026-01-28 21:14:12 -05:00
5f9c29c39a feat: Configure nuxt-svgo module 2026-01-28 21:13:49 -05:00
27f4f73148 fix: update nuxt-graphql and extractNodes typing (maybe => undefined instead of null) 2026-01-28 21:13:31 -05:00
21a7036ef5 feat: all of wrangler config in nuxt.config.ts 2026-01-28 21:12:43 -05:00
b1b1aa47c9 feat: graphql cache keyPrefix from package.json version 2026-01-28 21:11:48 -05:00
54fea5f64a minor: placeholder include for rankmath logic 2026-01-28 21:10:43 -05:00
a27e6af5db fix: term_order and default WPGraphQL settings 2026-01-28 21:10:26 -05:00
2c86905c91 fix: use public wpUrl runtime config for auth refresh token mutation 2026-01-28 21:09:37 -05:00
065b729a2f minor: better wp remote executor hooks 2026-01-28 21:08:58 -05:00
c82bf47e98 feat: useResponsive with device detection on SSR 2026-01-28 21:08:27 -05:00
51f594baa5 fix: headless_home_url needs to be in mu-plugins 2026-01-28 21:07:51 -05:00
3e56ba7eb3 minor: update schema.graphql 2026-01-28 08:34:44 -05:00
aaea0b062a chore(release): v0.1.7
Some checks failed
Deployment / wordpress (push) Failing after 1s
Deployment / nuxt (push) Failing after 8s
2026-01-27 20:25:04 -05:00
b886585be1 feat: Display theme version in admin footer
Some checks failed
Deployment / wordpress (push) Failing after 1s
Deployment / nuxt (push) Has been cancelled
2026-01-27 20:24:55 -05:00
c6dfbeb247 feat: Deploy to Cloudflare workers
Some checks failed
Deployment / wordpress (push) Failing after 1s
Deployment / nuxt (push) Failing after 7s
2026-01-27 19:59:34 -05:00
a1a8114f49 chore: update wp-graphql 2.7.0 2026-01-27 18:56:12 -05:00
63f8e443cf feat: Configure sitemap URL in robots.txt 2026-01-26 12:06:20 -05:00
5b8c50c758 chore(release): v0.1.6 2026-01-26 11:58:35 -05:00
c5ce607fae feat: Initial SEO integration 2026-01-26 11:57:35 -05:00
9cd99c36db chore: Update @lewebsimple/nuxt-graphql 2026-01-26 09:29:12 -05:00
108269e3fe fix: Bypass headless home URL for specific cases 2026-01-22 09:02:32 -05:00
26 changed files with 4777 additions and 1131 deletions

View File

@@ -0,0 +1,56 @@
name: Deployment
run-name: ${{ gitea.actor }} deploying ${{ gitea.repository.name }}
on: [push]
env:
NUXT_PROJECT_PATH: wp-content/themes/moonshine
PNPM_STORE_DIR: /cache/wp-scripts/pnpm
jobs:
wordpress:
runs-on: ubuntu-websimple
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Install WordPress scripts
env:
TEMPLATES_REPO_TOKEN: ${{ secrets.TEMPLATES_REPO_TOKEN }}
run: |
git clone https://$TEMPLATES_REPO_TOKEN@gitea.websimple.com/templates/wp-scripts.git /tmp/wp-scripts
- name: Run deployment script
env:
REMOTE_HOST: ${{ vars.REMOTE_HOST }}
REMOTE_PORT: ${{ vars.REMOTE_PORT }}
REMOTE_USER: ${{ vars.REMOTE_USER }}
REMOTE_PATH: ${{ vars.REMOTE_PATH }}
SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
run: /tmp/wp-scripts/wp-deploy.sh --skip-node
nuxt:
runs-on: ubuntu-websimple
defaults:
run:
working-directory: ${{ env.NUXT_PROJECT_PATH }}
env:
NUXT_SITE_ENV: ${{ vars.NUXT_SITE_ENV }}
NUXT_SITE_URL: ${{ vars.NUXT_SITE_URL }}
NUXT_WP_URL: ${{ vars.NUXT_WP_URL }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Install Node.js dependencies
run: pnpm install --frozen-lockfile --store-dir $PNPM_STORE_DIR
- name: Build Nuxt project
run: pnpm build
- name: Deploy to Cloudflare Workers
run: pnpm wrangler deploy
env:
CLOUDFLARE_ACCOUNT_ID: ${{ vars.CLOUDFLARE_ACCOUNT_ID }}
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}

View File

@@ -32,12 +32,14 @@
"lintfix": "vendor/bin/phpcbf" "lintfix": "vendor/bin/phpcbf"
}, },
"require": { "require": {
"axepress/wp-graphql-rank-math": "*",
"lewebsimple/advanced-custom-fields-pro": "*", "lewebsimple/advanced-custom-fields-pro": "*",
"lewebsimple/kaliroots": "*", "lewebsimple/kaliroots": "*",
"lewebsimple/wp-graphql-headless-login": "*", "lewebsimple/wp-graphql-headless-login": "*",
"wpackagist-plugin/acf-extended": "*", "wpackagist-plugin/acf-extended": "*",
"wpackagist-plugin/clean-image-filenames": "*", "wpackagist-plugin/clean-image-filenames": "*",
"wpackagist-plugin/disable-comments": "*", "wpackagist-plugin/disable-comments": "*",
"wpackagist-plugin/seo-by-rank-math": "*",
"wpackagist-plugin/wp-graphql": "*", "wpackagist-plugin/wp-graphql": "*",
"wpackagist-plugin/wpgraphql-acf": "*" "wpackagist-plugin/wpgraphql-acf": "*"
}, },

191
composer.lock generated
View File

@@ -4,8 +4,169 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "aec2b0e396a71ea02fe95432358ca91e", "content-hash": "9673ea7f3e3f21866ae50f70e1d6a16b",
"packages": [ "packages": [
{
"name": "axepress/wp-graphql-plugin-boilerplate",
"version": "0.1.1",
"source": {
"type": "git",
"url": "https://github.com/AxeWP/wp-graphql-plugin-boilerplate.git",
"reference": "09495b61346453baabdf4c71a38ada3cfc91c3a7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/AxeWP/wp-graphql-plugin-boilerplate/zipball/09495b61346453baabdf4c71a38ada3cfc91c3a7",
"reference": "09495b61346453baabdf4c71a38ada3cfc91c3a7",
"shasum": ""
},
"require": {
"php": ">=7.4"
},
"require-dev": {
"axepress/wp-graphql-cs": "^2.0.0",
"axepress/wp-graphql-stubs": "^2.3.0",
"phpcompatibility/php-compatibility": "dev-develop as 9.9.9",
"phpstan/extension-installer": "^1.1",
"phpstan/phpstan": "^2.0",
"phpstan/phpstan-deprecation-rules": "^2.0.1",
"szepeviktor/phpstan-wordpress": "^2.0",
"wp-cli/wp-cli-bundle": "^2.8.1"
},
"type": "library",
"autoload": {
"psr-4": {
"AxeWP\\GraphQL\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"GPL-3.0-or-later"
],
"authors": [
{
"name": "AxePress Development",
"homepage": "https://axepress.dev"
},
{
"name": "David Levine",
"role": "Developer"
}
],
"description": "Boilerplate for creating WPGraphQL extensions",
"support": {
"issues": "https://github.com/AxeWP/wp-graphql-plugin-boilerplate/issues",
"source": "https://github.com/AxeWP/wp-graphql-plugin-boilerplate/tree/0.1.1"
},
"funding": [
{
"url": "https://github.com/AxeWp",
"type": "github"
}
],
"time": "2025-06-07T02:03:50+00:00"
},
{
"name": "axepress/wp-graphql-rank-math",
"version": "0.3.4",
"source": {
"type": "git",
"url": "https://github.com/AxeWP/wp-graphql-rank-math.git",
"reference": "167bdd4a5350717ed34069c304e0ffc3fe02bc7d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/AxeWP/wp-graphql-rank-math/zipball/167bdd4a5350717ed34069c304e0ffc3fe02bc7d",
"reference": "167bdd4a5350717ed34069c304e0ffc3fe02bc7d",
"shasum": ""
},
"require": {
"axepress/wp-graphql-plugin-boilerplate": "^0.1.1",
"php": ">=7.4"
},
"require-dev": {
"axepress/wp-graphql-cs": "^2.0.0",
"axepress/wp-graphql-stubs": "^2.0.0",
"codeception/lib-innerbrowser": "^1.0",
"codeception/module-asserts": "^1.0",
"codeception/module-cli": "^1.0",
"codeception/module-db": "^1.0",
"codeception/module-filesystem": "^1.0",
"codeception/module-phpbrowser": "^1.0",
"codeception/module-rest": "^2.0",
"codeception/module-webdriver": "^1.0",
"codeception/phpunit-wrapper": "^9.0",
"codeception/util-universalframework": "^1.0",
"lucatume/wp-browser": "<3.5",
"php-coveralls/php-coveralls": "^2.5",
"phpcompatibility/php-compatibility": "dev-develop as 9.9.9",
"phpstan/extension-installer": "^1.1",
"phpstan/phpstan": "^2.1.5",
"phpunit/phpunit": "^9.5",
"szepeviktor/phpstan-wordpress": "^2.0.1",
"wp-cli/wp-cli-bundle": "^2.8.1",
"wp-graphql/wp-graphql-testcase": "~3.4.0"
},
"type": "wordpress-plugin",
"extra": {
"strauss": {
"packages": [
"axepress/wp-graphql-plugin-boilerplate"
],
"classmap_prefix": "WPGraphQL_RankMath_",
"constant_prefix": "WPGRAPHQL_SEO_",
"namespace_prefix": "WPGraphQL\\RankMath\\Vendor\\",
"target_directory": "vendor-prefixed",
"update_call_sites": false,
"exclude_from_prefix": {
"namespaces": [],
"file_patterns": []
},
"include_modified_date": false,
"delete_vendor_packages": true
}
},
"autoload": {
"files": [
"access-functions.php"
],
"psr-4": {
"WPGraphQL\\RankMath\\": "src/"
},
"classmap": [
"vendor-prefixed/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"GPL-3.0-or-later"
],
"authors": [
{
"name": "AxePress Development",
"email": "support@axepress.dev",
"homepage": "https://axepress.dev"
},
{
"name": "David Levine",
"role": "Developer"
}
],
"description": "Adds WPGraphQL support for RankMath SEO",
"support": {
"email": "support@axepress.dev",
"forum": "https://github.com/AxeWP/wp-graphql-rank-math/discussions",
"issues": "https://github.com/AxeWP/wp-graphql-rank-math/issues",
"source": "https://github.com/AxeWP/wp-graphql-rank-math/tree/0.3.4"
},
"funding": [
{
"url": "https://github.com/sponsors/AxeWP",
"type": "github"
}
],
"time": "2025-06-07T12:05:15+00:00"
},
{ {
"name": "composer/installers", "name": "composer/installers",
"version": "v2.3.0", "version": "v2.3.0",
@@ -258,16 +419,34 @@
"homepage": "https://wordpress.org/plugins/disable-comments/" "homepage": "https://wordpress.org/plugins/disable-comments/"
}, },
{ {
"name": "wpackagist-plugin/wp-graphql", "name": "wpackagist-plugin/seo-by-rank-math",
"version": "2.6.0", "version": "1.0.262",
"source": { "source": {
"type": "svn", "type": "svn",
"url": "https://plugins.svn.wordpress.org/wp-graphql/", "url": "https://plugins.svn.wordpress.org/seo-by-rank-math/",
"reference": "tags/2.6.0" "reference": "tags/1.0.262"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://downloads.wordpress.org/plugin/wp-graphql.2.6.0.zip" "url": "https://downloads.wordpress.org/plugin/seo-by-rank-math.1.0.262.zip"
},
"require": {
"composer/installers": "^1.0 || ^2.0"
},
"type": "wordpress-plugin",
"homepage": "https://wordpress.org/plugins/seo-by-rank-math/"
},
{
"name": "wpackagist-plugin/wp-graphql",
"version": "2.7.0",
"source": {
"type": "svn",
"url": "https://plugins.svn.wordpress.org/wp-graphql/",
"reference": "tags/2.7.0"
},
"dist": {
"type": "zip",
"url": "https://downloads.wordpress.org/plugin/wp-graphql.2.7.0.zip"
}, },
"require": { "require": {
"composer/installers": "^1.0 || ^2.0" "composer/installers": "^1.0 || ^2.0"

View File

@@ -0,0 +1,20 @@
<?php
// Customize home URL for headless WordPress
add_filter( 'home_url', 'headless_home_url', 10, 4 );
function headless_home_url( $url, $path, $orig_scheme, $blog_id ) {
// Exclude specific patterns from rewriting
$excluded_patterns = array(
'#/wp-json(/|$)#i', // WP REST API
'#\.(xsl|xml)$#i', // Sitemap and XSLT files
);
foreach ( $excluded_patterns as $pattern ) {
if ( preg_match( $pattern, $url ) ) {
return get_site_url( $blog_id, $path, $orig_scheme );
}
}
// Rewrite URL protocol to match original home scheme
$scheme = wp_parse_url( get_option( 'home' ) )['scheme'] ?? 'https';
return preg_replace( '#^https:#i', "$scheme:", $url );
}

View File

@@ -22,3 +22,6 @@ logs
.env .env
.env.* .env.*
!.env.example !.env.example
# Wrangler files
.wrangler

View File

@@ -1,5 +1,46 @@
# Changelog # Changelog
## v0.1.8
[compare changes](https://gitea.websimple.com/templates/wp-headless/compare/v0.1.7...v0.1.8)
### 🚀 Enhancements
- UseResponsive with device detection on SSR (c82bf47)
- Graphql cache keyPrefix from package.json version (b1b1aa4)
- All of wrangler config in nuxt.config.ts (21a7036)
- Configure nuxt-svgo module (5f9c29c)
### 🩹 Fixes
- Headless_home_url needs to be in mu-plugins (51f594b)
- Use public wpUrl runtime config for auth refresh token mutation (2c86905)
- Term_order and default WPGraphQL settings (a27e6af)
- Update nuxt-graphql and extractNodes typing (maybe => undefined instead of null) (27f4f73)
## v0.1.7
[compare changes](https://gitea.websimple.com/templates/wp-headless/compare/v0.1.6...v0.1.7)
### 🚀 Enhancements
- Configure sitemap URL in robots.txt (63f8e44)
- Deploy to Cloudflare workers (c6dfbeb)
- Display theme version in admin footer (b886585)
## v0.1.6
[compare changes](https://gitea.websimple.com/templates/wp-headless/compare/v0.1.5...v0.1.6)
### 🚀 Enhancements
- Site options page & field group (489ac82)
- Initial SEO integration (c5ce607)
### 🩹 Fixes
- Bypass headless home URL for specific cases (108269e)
## v0.1.5 ## v0.1.5
[compare changes](https://gitea.websimple.com/templates/wp-headless/compare/v0.1.4...v0.1.5) [compare changes](https://gitea.websimple.com/templates/wp-headless/compare/v0.1.4...v0.1.5)

View File

@@ -2,7 +2,7 @@
Thème WordPress en headless basé sur Nuxt. Thème WordPress en headless basé sur Nuxt.
## Varaibles d'environnement ## Variables d'environnement
| Nom | Description | Exemple | Requise | | Nom | Description | Exemple | Requise |
|-----|-------------|---------|---------| |-----|-------------|---------|---------|

View File

@@ -0,0 +1,20 @@
import type { NodeByUriQueryResult, NodeSeoFragment } from "#graphql/operations";
export function useNodeSeo(node: NodeByUriQueryResult["nodeByUri"]) {
// Check if node has SEO data
if (!node || !("seo" in node) || !node.seo) {
return;
}
const { seo } = node as NodeSeoFragment;
useSeoMeta({
title: seo?.title || undefined,
description: seo?.description || undefined,
robots: (seo?.robots || []).join(", "),
ogTitle: seo?.openGraph?.title || undefined,
ogDescription: seo?.openGraph?.description || undefined,
ogImage: seo?.openGraph?.image?.url || undefined,
ogUrl: seo?.canonicalUrl || undefined,
twitterCard: "summary_large_image",
});
}

View File

@@ -0,0 +1,9 @@
import { breakpointsTailwind, useBreakpoints } from "@vueuse/core";
export function useResponsive() {
const { isMobileOrTablet } = useDevice();
const breakpoints = useBreakpoints(breakpointsTailwind, { ssrWidth: isMobileOrTablet ? 375 : 1024 });
const isDesktop = breakpoints.greaterOrEqual("lg");
return { breakpoints, isDesktop };
}

View File

@@ -1,8 +1,27 @@
fragment NodeSeo on NodeWithRankMathSeo {
seo {
title
description
robots
canonicalUrl
openGraph {
title
description
image {
url
}
}
}
}
query NodeByUri($uri: String!) { query NodeByUri($uri: String!) {
nodeByUri(uri: $uri) { nodeByUri(uri: $uri) {
__typename __typename
... on Page { ... on Page {
... NodePage ... NodePage
} }
... on NodeWithRankMathSeo {
... NodeSeo
}
} }
} }

View File

@@ -1,19 +1,20 @@
<script setup lang="ts"> <script setup lang="ts">
// Resolve Node component from URI
const { path: uri } = useRoute(); const { path: uri } = useRoute();
const { data } = await useAsyncGraphQLQuery("NodeByUri", { uri }); const { data } = await useAsyncGraphQLQuery("NodeByUri", { uri });
if (!data.value?.nodeByUri) {
// Resolve and validate Node component
if (!data.value.nodeByUri) {
throw createError({ statusCode: 404, message: `La page demandée est introuvable: ${uri}`, fatal: true }); throw createError({ statusCode: 404, message: `La page demandée est introuvable: ${uri}`, fatal: true });
} }
const componentName = `Node${data.value.nodeByUri.__typename}`; const componentName = `Node${data.value.nodeByUri.__typename}`;
if (!useNuxtApp().vueApp.component(componentName)) { if (!useNuxtApp().vueApp.component(componentName)) {
throw createError({ statusCode: 404, message: `La page demandée ne peut pas être affichée correctement: ${componentName}`, fatal: true }); throw createError({ statusCode: 404, message: `La page demandée ne peut pas être affichée correctement: ${componentName}`, fatal: true });
} }
useNodeSeo(data.value.nodeByUri);
</script> </script>
<template> <template>
<div v-if="data.nodeByUri" id="page-node-from-uri"> <div v-if="data?.nodeByUri" id="page-node-from-uri">
<Component :is="componentName" v-bind="data.nodeByUri" /> <Component :is="componentName" v-bind="data.nodeByUri" />
</div> </div>
</template> </template>

View File

@@ -5,4 +5,9 @@ require_once __DIR__ . '/includes/core/theme-setup.php';
// Vendors // Vendors
require_once __DIR__ . '/includes/vendors/acf.php'; require_once __DIR__ . '/includes/vendors/acf.php';
require_once __DIR__ . '/includes/vendors/rankmath.php';
require_once __DIR__ . '/includes/vendors/tinymce.php'; require_once __DIR__ . '/includes/vendors/tinymce.php';
require_once __DIR__ . '/includes/vendors/wpgraphql.php';
// WPGraphQL
require_once __DIR__ . '/includes/wpgraphql/term-connection.php';

View File

@@ -16,3 +16,12 @@ function moonshine_after_setup_theme() {
// Register sidebars // Register sidebars
} }
// Display theme version in admin footer
add_filter( 'update_footer', 'moonshine_update_footer', 100 );
function moonshine_update_footer() {
$package_json = json_decode( file_get_contents( get_theme_file_path( 'package.json' ) ), true );
$name = $package_json['name'] ?? 'moonshine';
$version = $package_json['version'] ?? '(unknown)';
return sprintf( '%s v%s', esc_html( $name ), esc_html( $version ) );
}

View File

@@ -0,0 +1 @@
<?php

View File

@@ -0,0 +1,17 @@
<?php
// Default WPGraphQL settings
add_filter( 'graphql_get_setting_section_field_value', 'moonshine_wpgraphql_settings', 10, 5 );
function moonshine_wpgraphql_settings( $value, $default_value, $option_name, $section_fields, $section_name ) {
if ( $section_name === 'graphql_general_settings' ) {
switch ( $option_name ) {
case 'graphql_endpoint':
$value = 'graphql';
break;
case "public_introspection_enabled":
$value = "on";
break;
}
}
return $value;
}

View File

@@ -0,0 +1,13 @@
<?php
// Override TermConnection query args
add_filter( 'graphql_term_object_connection_query_args', 'moonshine_graphql_term_object_connection_query_args', 10, 3 );
function moonshine_graphql_term_object_connection_query_args( $query_args, $source, $args ) {
// Sort by 'order' meta value instead of legacy 'term_order' field
if ( 'term_order' === $args['where']['orderby'] ?? false ) {
$query_args['meta_key'] = 'order';
$query_args['orderby'] = 'meta_value_num';
}
return $query_args;
}

View File

@@ -1,3 +1,15 @@
import { version } from "./package.json";
const siteUrl = process.env.NUXT_SITE_URL;
if (!siteUrl) {
throw new Error(`NUXT_SITE_URL is not defined. Make sure to set it in your build environment variables.`);
}
const wpUrl = process.env.NUXT_WP_URL;
if (!wpUrl) {
throw new Error(`NUXT_WP_URL is not defined. Make sure to set it in your build environment variables.`);
}
// https://nuxt.com/docs/api/configuration/nuxt-config // https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({ export default defineNuxtConfig({
@@ -5,8 +17,10 @@ export default defineNuxtConfig({
"@lewebsimple/nuxt-graphql", "@lewebsimple/nuxt-graphql",
"@nuxt/eslint", "@nuxt/eslint",
"@nuxt/ui", "@nuxt/ui",
"@nuxtjs/device",
"@nuxtjs/seo", "@nuxtjs/seo",
"nuxt-auth-utils", "nuxt-auth-utils",
"nuxt-svgo",
], ],
components: { components: {
@@ -20,7 +34,7 @@ export default defineNuxtConfig({
css: ["~/assets/css/_main.css"], css: ["~/assets/css/_main.css"],
site: { site: {
url: "https://wp-headless.ledevsimple.ca", url: siteUrl,
name: "WP Headless", name: "WP Headless",
defaultLocale: "fr", defaultLocale: "fr",
}, },
@@ -29,8 +43,40 @@ export default defineNuxtConfig({
colorMode: false, colorMode: false,
}, },
runtimeConfig: {
wpUrl,
},
compatibilityDate: "2026-01-01", compatibilityDate: "2026-01-01",
nitro: {
preset: "cloudflare_module",
cloudflare: {
deployConfig: true,
nodeCompat: true,
wrangler: {
// Project name
name: "moonshine",
// Cloudflare Workers settings
compatibility_date: "2026-01-27",
main: "./.output/server/index.mjs",
observability: { enabled: true },
preview_urls: false,
// Environment variables
vars: {
NODE_ENV: "staging",
NUXT_SITE_URL: siteUrl,
NUXT_WP_URL: wpUrl,
},
// Bindings
assets: {
binding: "ASSETS",
directory: "./.output/public/",
},
},
},
},
eslint: { eslint: {
config: { config: {
stylistic: { stylistic: {
@@ -44,20 +90,29 @@ export default defineNuxtConfig({
}, },
graphql: { graphql: {
yoga: { client: {
context: ["~~/server/graphql/context"], cache: {
schemas: { keyVersion: version,
wp: { },
type: "remote", },
url: `${process.env.NUXT_WP_URL || "https://wp-headless.ledevsimple.ca"}/graphql`, server: {
hooks: ["~~/server/graphql/wp-hooks"], context: ["server/graphql/context"],
}, schema: {
wp: { type: "remote", endpoint: `${wpUrl}/graphql`, hooks: ["server/graphql/wp-hooks"] },
}, },
}, },
}, },
sitemap: { robots: {
zeroRuntime: true, sitemap: `${wpUrl}/sitemap_index.xml`,
},
sitemap: false,
svgo: {
autoImportPath: "~/assets/svg/",
componentPrefix: "Svg",
defaultImport: "component",
}, },
}); });

View File

@@ -1,7 +1,7 @@
{ {
"name": "@lewebsimple/moonshine", "name": "@lewebsimple/moonshine",
"description": "Headless WordPress theme based on Nuxt.", "description": "Headless WordPress theme based on Nuxt.",
"version": "0.1.5", "version": "0.1.8",
"type": "module", "type": "module",
"private": true, "private": true,
"scripts": { "scripts": {
@@ -9,37 +9,47 @@
"editor-style": "pnpx @tailwindcss/cli -i ./app/assets/css/_main.css -o ./editor-style.css --minify", "editor-style": "pnpx @tailwindcss/cli -i ./app/assets/css/_main.css -o ./editor-style.css --minify",
"dev": "nuxt dev", "dev": "nuxt dev",
"lint": "eslint --fix .", "lint": "eslint --fix .",
"postinstall": "pnpm --sequential /postinstall:.*/", "postinstall": "nuxt prepare",
"postinstall:nuxt": "nuxt prepare", "preview": "pnpm run build && wrangler dev --port 3000",
"preview": "nuxt preview",
"release": "pnpm lint && changelogen --noAuthors --release --push", "release": "pnpm lint && changelogen --noAuthors --release --push",
"typecheck": "nuxt typecheck" "typecheck": "nuxt typecheck"
}, },
"dependencies": { "dependencies": {
"@iconify-json/lucide": "^1.2.86", "@iconify-json/lucide": "^1.2.87",
"@lewebsimple/nuxt-graphql": "^0.5.11", "@lewebsimple/nuxt-graphql": "^0.6.6",
"@nuxt/ui": "4.3.0", "@nuxt/ui": "4.3.0",
"@nuxtjs/seo": "^3.3.0", "@nuxtjs/device": "4.0.0",
"@nuxtjs/seo": "^3.4.0",
"jwt-decode": "^4.0.0", "jwt-decode": "^4.0.0",
"nuxt": "^4.2.2", "nuxt": "^4.3.0",
"nuxt-auth-utils": "^0.5.27", "nuxt-auth-utils": "^0.5.28",
"nuxt-svgo": "^4.2.6",
"tailwindcss": "^4.1.18", "tailwindcss": "^4.1.18",
"vue": "^3.5.27", "vue": "^3.5.27",
"vue-router": "^4.6.4", "vue-router": "^4.6.4",
"zod": "^4.3.5" "zod": "^4.3.6"
}, },
"devDependencies": { "devDependencies": {
"@nuxt/eslint": "^1.12.1", "@nuxt/eslint": "^1.13.0",
"changelogen": "^0.6.2", "changelogen": "^0.6.2",
"eslint": "^9.39.2", "eslint": "^9.39.2",
"typescript": "^5.9.3", "typescript": "^5.9.3",
"vue-tsc": "^3.2.2" "vue-tsc": "^3.2.4",
"wrangler": "^4.61.0"
}, },
"pnpm": { "pnpm": {
"overrides": { "overrides": {
"@tiptap/core": "3.14.0", "@tiptap/core": "3.14.0",
"@tiptap/pm": "3.14.0" "@tiptap/pm": "3.14.0"
} },
"onlyBuiltDependencies": [
"@parcel/watcher",
"esbuild",
"sharp",
"unrs-resolver",
"vue-demi",
"workerd"
]
}, },
"changelog": { "changelog": {
"types": { "types": {

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +0,0 @@
onlyBuiltDependencies:
- '@parcel/watcher'
- esbuild
- unrs-resolver
- vue-demi

View File

@@ -1,7 +1,7 @@
export default defineEventHandler(async (event) => { export default defineEventHandler(async (event) => {
try { try {
const variables = await readBody<AuthLoginForm>(event); const variables = await readBody<AuthLoginForm>(event);
const { data } = await useServerGraphQLMutation(event, "AuthLogin", variables); const { data } = await useGraphQLOperation(event, "AuthLogin", variables);
if (!data?.login) { if (!data?.login) {
throw new Error("INVALID_LOGIN"); throw new Error("INVALID_LOGIN");
} }

File diff suppressed because it is too large Load Diff

View File

@@ -1,11 +1,10 @@
import { defu } from "defu";
export default defineRemoteExecutorHooks({ export default defineRemoteExecutorHooks({
onRequest(request) { onRequest(request) {
if (request.context.authToken) { // Attach the Authorization header if an authToken is present in the context
request.extensions ??= {}; if (request.context?.authToken) {
request.extensions.headers = { request.extensions = defu(request.extensions, { headers: { Authorization: `Bearer ${request.context.authToken}` } });
...request.extensions.headers,
Authorization: `Bearer ${request.context.authToken}`,
};
} }
}, },
}); });

View File

@@ -1,9 +1,9 @@
import type { H3Event } from "h3"; import type { H3Event } from "h3";
import { GraphQLClient } from "graphql-request";
import { jwtDecode } from "jwt-decode"; import { jwtDecode } from "jwt-decode";
import type { User } from "#auth-utils"; import type { User } from "#auth-utils";
import type { AuthUserFragment, AuthLoginMutationResult } from "#graphql/operations"; import type { AuthUserFragment, AuthLoginMutationResult } from "#graphql/operations";
import { AuthRefreshTokenDocument } from "#graphql/operations"; import { AuthRefreshTokenDocument } from "#graphql/operations";
import type { ResultOf } from "#graphql/registry";
// Handle login result and store user session // Handle login result and store user session
export async function handleLogin(event: H3Event, loginResult: AuthLoginMutationResult) { export async function handleLogin(event: H3Event, loginResult: AuthLoginMutationResult) {
@@ -42,9 +42,13 @@ function getAuthUser(user: AuthUserFragment): User {
// Refresh auth token by calling remote GraphQL endpoint directly // Refresh auth token by calling remote GraphQL endpoint directly
export async function refreshAuthToken(refreshToken: string): Promise<string | undefined> { export async function refreshAuthToken(refreshToken: string): Promise<string | undefined> {
const client = new GraphQLClient(`${process.env.NUXT_WP_URL || "https://wp-headless.ledevsimple.ca"}/graphql`); const { public: { wpUrl } } = useRuntimeConfig();
const data = await client.request(AuthRefreshTokenDocument, { refreshToken }); const endpoint = `${wpUrl}/graphql`;
return data.refreshToken?.authToken || undefined; const { data } = await executeGraphQLHTTP<ResultOf<"AuthRefreshToken">>({
query: AuthRefreshTokenDocument,
variables: { refreshToken },
}, { endpoint });
return data?.refreshToken?.authToken || undefined;
} }
// Get auth token from user session (refresh if needed) // Get auth token from user session (refresh if needed)

View File

@@ -1,4 +1,4 @@
// Helper: Extracts nodes from a GraphQL connection object, returning an empty array if nodes are absent. // Helper: Extracts nodes from a GraphQL connection object, returning an empty array if nodes are absent.
export function extractNodes<T>(connection: { nodes?: readonly T[] } | null | undefined): readonly T[] { export function extractNodes<T>(connection: { nodes?: T[] } | undefined): T[] {
return connection?.nodes ?? []; return connection?.nodes ?? [];
} }

View File

@@ -3,7 +3,6 @@ Theme Name: Moonshine
Author: Pascal Martineau <pascal@lewebsimple.ca> Author: Pascal Martineau <pascal@lewebsimple.ca>
Author URI: https://websimple.com/ Author URI: https://websimple.com/
Description: Headless WordPress theme based on Nuxt. Description: Headless WordPress theme based on Nuxt.
Version: 0.1.0
Text Domain: moonshine Text Domain: moonshine
Template: kaliroots Template: kaliroots
*/ */