@wpnuxt/auth 2.0.0-alpha.1 → 2.0.0-alpha.10

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.
@@ -0,0 +1,7 @@
1
+ import * as _nuxt_schema from '@nuxt/schema';
2
+ import { WPNuxtAuthConfig } from '../dist/runtime/types/index.js';
3
+ export { WPNuxtAuthConfig } from '../dist/runtime/types/index.js';
4
+
5
+ declare const _default: _nuxt_schema.NuxtModule<WPNuxtAuthConfig, WPNuxtAuthConfig, false>;
6
+
7
+ export { _default as default };
package/dist/module.json CHANGED
@@ -1,7 +1,10 @@
1
1
  {
2
2
  "name": "@wpnuxt/auth",
3
3
  "configKey": "wpNuxtAuth",
4
- "version": "2.0.0-alpha.0",
4
+ "compatibility": {
5
+ "nuxt": ">=3.0.0"
6
+ },
7
+ "version": "2.0.0-alpha.10",
5
8
  "builder": {
6
9
  "@nuxt/module-builder": "1.0.2",
7
10
  "unbuild": "3.6.1"
package/dist/module.mjs CHANGED
@@ -1,9 +1,106 @@
1
- import { defineNuxtModule, createResolver, addPlugin, addImports } from '@nuxt/kit';
1
+ import { existsSync, readFileSync, cpSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { defineNuxtModule, createResolver, addPlugin, addImports, addServerHandler, useLogger } from '@nuxt/kit';
2
4
 
5
+ function detectAuthCapabilities(schemaPath) {
6
+ if (!existsSync(schemaPath)) {
7
+ return null;
8
+ }
9
+ let schemaContent;
10
+ try {
11
+ schemaContent = readFileSync(schemaPath, "utf-8");
12
+ } catch {
13
+ return null;
14
+ }
15
+ if (!schemaContent || schemaContent.length < 100) {
16
+ return null;
17
+ }
18
+ const hasHeadlessLogin = /loginClients\s*[:[(]/.test(schemaContent);
19
+ const hasPasswordAuth = /\blogin\s*\(/.test(schemaContent) && /LoginInput/.test(schemaContent);
20
+ const hasRefreshToken = /refreshToken\s*\(/.test(schemaContent);
21
+ const detectedProviders = [];
22
+ const enumMatch = schemaContent.match(/enum\s+LoginProviderEnum\s*\{([^}]+)\}/);
23
+ if (enumMatch?.[1]) {
24
+ const enumBody = enumMatch[1];
25
+ const providerMatches = enumBody.match(/\b[A-Z][A-Z0-9_]+\b/g);
26
+ if (providerMatches) {
27
+ detectedProviders.push(...providerMatches);
28
+ }
29
+ }
30
+ return {
31
+ hasHeadlessLogin,
32
+ hasPasswordAuth,
33
+ hasRefreshToken,
34
+ detectedProviders
35
+ };
36
+ }
37
+ function validateAuthSchema(schemaPath, options = {}) {
38
+ const capabilities = detectAuthCapabilities(schemaPath);
39
+ if (capabilities === null) {
40
+ throw new Error(
41
+ `[wpnuxt:auth] Cannot validate GraphQL schema - file not found or unreadable.
42
+
43
+ Schema path: ${schemaPath}
44
+
45
+ Make sure @wpnuxt/core is loaded before @wpnuxt/auth in your nuxt.config.ts modules,
46
+ and that 'downloadSchema' is enabled (the default).
47
+
48
+ To fix this, run: pnpm dev:prepare`
49
+ );
50
+ }
51
+ if (!capabilities.hasHeadlessLogin) {
52
+ throw new Error(
53
+ `[wpnuxt:auth] Headless Login for WPGraphQL plugin not detected.
54
+
55
+ The @wpnuxt/auth module requires this WordPress plugin for authentication.
56
+ Your WordPress GraphQL schema is missing the required 'loginClients' query.
57
+
58
+ To fix this:
59
+ 1. Install the plugin: https://github.com/AxeWP/wp-graphql-headless-login
60
+ 2. Configure at least one authentication provider in WordPress admin
61
+ 3. Run 'pnpm dev:prepare' to refresh the GraphQL schema
62
+
63
+ To disable @wpnuxt/auth, remove it from your nuxt.config.ts modules.`
64
+ );
65
+ }
66
+ if (options.requirePassword && !capabilities.hasPasswordAuth) {
67
+ throw new Error(
68
+ `[wpnuxt:auth] Password authentication not available in GraphQL schema.
69
+
70
+ The 'login' mutation is missing from your WordPress GraphQL schema.
71
+ Make sure Headless Login for WPGraphQL is properly configured with PASSWORD provider enabled.`
72
+ );
73
+ }
74
+ if (options.requireHeadlessLogin && capabilities.detectedProviders.length === 0) {
75
+ throw new Error(
76
+ `[wpnuxt:auth] No OAuth providers detected in GraphQL schema.
77
+
78
+ Headless Login is installed but no providers are configured.
79
+ Configure OAuth providers (Google, GitHub, etc.) in WordPress admin under:
80
+ GraphQL > Headless Login > Client Settings`
81
+ );
82
+ }
83
+ }
84
+
85
+ const DEFAULT_OAUTH_CONFIG = {
86
+ enabled: false,
87
+ clientId: "",
88
+ clientSecret: "",
89
+ authorizationEndpoint: "/wp-json/moserver/authorize",
90
+ tokenEndpoint: "/wp-json/moserver/token",
91
+ userInfoEndpoint: "/wp-json/moserver/resource",
92
+ scopes: ["openid", "profile", "email"]
93
+ };
94
+ const DEFAULT_HEADLESS_LOGIN_CONFIG = {
95
+ enabled: false
96
+ };
3
97
  const module$1 = defineNuxtModule({
4
98
  meta: {
5
99
  name: "@wpnuxt/auth",
6
- configKey: "wpNuxtAuth"
100
+ configKey: "wpNuxtAuth",
101
+ compatibility: {
102
+ nuxt: ">=3.0.0"
103
+ }
7
104
  },
8
105
  defaults: {
9
106
  enabled: true,
@@ -13,13 +110,50 @@ const module$1 = defineNuxtModule({
13
110
  refreshTokenMaxAge: 604800,
14
111
  redirectOnLogin: "/",
15
112
  redirectOnLogout: "/",
16
- loginPage: "/login"
113
+ loginPage: "/login",
114
+ providers: {
115
+ password: { enabled: true },
116
+ oauth: DEFAULT_OAUTH_CONFIG,
117
+ headlessLogin: DEFAULT_HEADLESS_LOGIN_CONFIG
118
+ }
17
119
  },
18
120
  async setup(options, nuxt) {
19
121
  if (!options.enabled) {
20
122
  return;
21
123
  }
22
124
  const resolver = createResolver(import.meta.url);
125
+ const baseDir = nuxt.options.srcDir || nuxt.options.rootDir;
126
+ const { resolve } = createResolver(baseDir);
127
+ const wpNuxtConfig = nuxt.options.wpNuxt;
128
+ const mergedQueriesPath = resolve(wpNuxtConfig?.queries?.mergedOutputFolder || ".queries/");
129
+ const userQueryPath = resolve(wpNuxtConfig?.queries?.extendFolder || "extend/queries/");
130
+ const authQueriesPath = resolver.resolve("./runtime/queries");
131
+ if (existsSync(authQueriesPath)) {
132
+ cpSync(authQueriesPath, mergedQueriesPath, { recursive: true });
133
+ }
134
+ if (existsSync(userQueryPath)) {
135
+ cpSync(userQueryPath, mergedQueriesPath, { recursive: true });
136
+ }
137
+ const oauthConfig = {
138
+ ...DEFAULT_OAUTH_CONFIG,
139
+ ...options.providers?.oauth
140
+ };
141
+ const headlessLoginConfig = {
142
+ ...DEFAULT_HEADLESS_LOGIN_CONFIG,
143
+ ...options.providers?.headlessLogin
144
+ };
145
+ const passwordEnabled = options.providers?.password?.enabled ?? true;
146
+ const oauthEnabled = oauthConfig.enabled && !!oauthConfig.clientId;
147
+ const headlessLoginEnabled = headlessLoginConfig.enabled ?? false;
148
+ const nuxtWithFlag = nuxt;
149
+ if ((passwordEnabled || headlessLoginEnabled) && !nuxtWithFlag._wpnuxtAuthValidated) {
150
+ nuxtWithFlag._wpnuxtAuthValidated = true;
151
+ const schemaPath = join(nuxt.options.rootDir, "schema.graphql");
152
+ validateAuthSchema(schemaPath, {
153
+ requirePassword: passwordEnabled,
154
+ requireHeadlessLogin: headlessLoginEnabled
155
+ });
156
+ }
23
157
  nuxt.options.runtimeConfig.public.wpNuxtAuth = {
24
158
  cookieName: options.cookieName,
25
159
  refreshCookieName: options.refreshCookieName,
@@ -27,19 +161,73 @@ const module$1 = defineNuxtModule({
27
161
  refreshTokenMaxAge: options.refreshTokenMaxAge,
28
162
  redirectOnLogin: options.redirectOnLogin,
29
163
  redirectOnLogout: options.redirectOnLogout,
30
- loginPage: options.loginPage
164
+ loginPage: options.loginPage,
165
+ providers: {
166
+ password: { enabled: passwordEnabled },
167
+ oauth: {
168
+ enabled: oauthEnabled,
169
+ clientId: oauthConfig.clientId,
170
+ authorizationEndpoint: oauthConfig.authorizationEndpoint,
171
+ scopes: oauthConfig.scopes
172
+ },
173
+ headlessLogin: {
174
+ enabled: headlessLoginEnabled
175
+ }
176
+ }
31
177
  };
178
+ if (oauthEnabled) {
179
+ nuxt.options.runtimeConfig.wpNuxtAuthOAuth = {
180
+ clientId: oauthConfig.clientId,
181
+ clientSecret: oauthConfig.clientSecret,
182
+ tokenEndpoint: oauthConfig.tokenEndpoint,
183
+ userInfoEndpoint: oauthConfig.userInfoEndpoint
184
+ };
185
+ }
32
186
  addPlugin(resolver.resolve("./runtime/plugins/auth"));
33
187
  addImports([
34
188
  { name: "useWPAuth", from: resolver.resolve("./runtime/composables/useWPAuth") },
35
189
  { name: "useWPUser", from: resolver.resolve("./runtime/composables/useWPUser") }
36
190
  ]);
191
+ addServerHandler({
192
+ route: "/api/_wpnuxt-auth/logout",
193
+ method: "post",
194
+ handler: resolver.resolve("./runtime/server/api/auth/logout.post")
195
+ });
196
+ if (oauthEnabled) {
197
+ addServerHandler({
198
+ route: "/api/_wpnuxt-auth/oauth/authorize",
199
+ method: "get",
200
+ handler: resolver.resolve("./runtime/server/api/auth/oauth/authorize.get")
201
+ });
202
+ addServerHandler({
203
+ route: "/api/_wpnuxt-auth/oauth/callback",
204
+ method: "get",
205
+ handler: resolver.resolve("./runtime/server/api/auth/oauth/callback.get")
206
+ });
207
+ }
208
+ if (headlessLoginEnabled) {
209
+ addServerHandler({
210
+ route: "/api/_wpnuxt-auth/provider/:provider/authorize",
211
+ method: "get",
212
+ handler: resolver.resolve("./runtime/server/api/auth/provider/[provider]/authorize.get")
213
+ });
214
+ addServerHandler({
215
+ route: "/api/_wpnuxt-auth/provider/:provider/callback",
216
+ method: "get",
217
+ handler: resolver.resolve("./runtime/server/api/auth/provider/[provider]/callback.get")
218
+ });
219
+ }
37
220
  nuxt.hook("prepare:types", ({ references }) => {
38
221
  references.push({
39
222
  path: resolver.resolve("./runtime/types/index.ts")
40
223
  });
41
224
  });
42
- console.log("[WPNuxt Auth] Module loaded");
225
+ const logger = useLogger("wpnuxt:auth");
226
+ const providers = [];
227
+ if (passwordEnabled) providers.push("password");
228
+ if (oauthEnabled) providers.push("oauth");
229
+ if (headlessLoginEnabled) providers.push("headlessLogin");
230
+ logger.info(`Module loaded (providers: ${providers.join(", ") || "none"})`);
43
231
  }
44
232
  });
45
233
 
@@ -1,4 +1,5 @@
1
1
  import { computed, useState, useCookie, useRuntimeConfig, navigateTo, useGraphqlMutation } from "#imports";
2
+ import { logger } from "../utils/logger.js";
2
3
  export function useWPAuth() {
3
4
  const config = useRuntimeConfig().public.wpNuxtAuth;
4
5
  const authState = useState("wpnuxt-auth", () => ({
@@ -12,11 +13,16 @@ export function useWPAuth() {
12
13
  secure: process.env.NODE_ENV === "production",
13
14
  sameSite: "lax"
14
15
  });
15
- const refreshToken = useCookie(config.refreshCookieName, {
16
+ const refreshTokenCookie = useCookie(config.refreshCookieName, {
16
17
  maxAge: config.refreshTokenMaxAge,
17
18
  secure: process.env.NODE_ENV === "production",
18
19
  sameSite: "lax"
19
20
  });
21
+ const userDataCookie = useCookie("wpnuxt-user", {
22
+ maxAge: config.tokenMaxAge,
23
+ secure: process.env.NODE_ENV === "production",
24
+ sameSite: "lax"
25
+ });
20
26
  async function login(credentials) {
21
27
  authState.value.isLoading = true;
22
28
  authState.value.error = null;
@@ -31,18 +37,20 @@ export function useWPAuth() {
31
37
  authState.value.isLoading = false;
32
38
  return { success: false, error: errorMessage };
33
39
  }
34
- if (data?.login) {
35
- authToken.value = data.login.authToken;
36
- refreshToken.value = data.login.refreshToken;
37
- authState.value.user = data.login.user;
40
+ const loginData = data;
41
+ if (loginData?.login) {
42
+ authToken.value = loginData.login.authToken;
43
+ refreshTokenCookie.value = loginData.login.refreshToken;
44
+ userDataCookie.value = JSON.stringify(loginData.login.user);
45
+ authState.value.user = loginData.login.user;
38
46
  authState.value.isAuthenticated = true;
39
47
  authState.value.isLoading = false;
40
48
  return {
41
49
  success: true,
42
- user: data.login.user,
50
+ user: loginData.login.user,
43
51
  tokens: {
44
- authToken: data.login.authToken,
45
- refreshToken: data.login.refreshToken
52
+ authToken: loginData.login.authToken,
53
+ refreshToken: loginData.login.refreshToken
46
54
  }
47
55
  };
48
56
  }
@@ -56,8 +64,11 @@ export function useWPAuth() {
56
64
  }
57
65
  }
58
66
  async function logout() {
67
+ await $fetch("/api/_wpnuxt-auth/logout", { method: "POST" }).catch(() => {
68
+ });
59
69
  authToken.value = null;
60
- refreshToken.value = null;
70
+ refreshTokenCookie.value = null;
71
+ userDataCookie.value = null;
61
72
  authState.value.user = null;
62
73
  authState.value.isAuthenticated = false;
63
74
  authState.value.error = null;
@@ -66,27 +77,129 @@ export function useWPAuth() {
66
77
  }
67
78
  }
68
79
  async function refresh() {
69
- if (!refreshToken.value) {
70
- return false;
80
+ if (!refreshTokenCookie.value) {
81
+ return { success: false, error: "No refresh token available" };
71
82
  }
72
83
  try {
73
- const { data, errors } = await useGraphqlMutation("RefreshAuthToken", {
74
- refreshToken: refreshToken.value
84
+ const { data, errors } = await useGraphqlMutation("RefreshToken", {
85
+ refreshToken: refreshTokenCookie.value
75
86
  });
76
- if (errors?.length || !data?.refreshJwtAuthToken) {
77
- await logout();
78
- return false;
87
+ if (errors?.length) {
88
+ const errorMessage = errors[0]?.message || "Token refresh failed";
89
+ authToken.value = null;
90
+ refreshTokenCookie.value = null;
91
+ userDataCookie.value = null;
92
+ authState.value.isAuthenticated = false;
93
+ authState.value.user = null;
94
+ authState.value.error = errorMessage;
95
+ return { success: false, error: errorMessage };
79
96
  }
80
- authToken.value = data.refreshJwtAuthToken.authToken;
81
- return true;
82
- } catch {
83
- await logout();
84
- return false;
97
+ const refreshData = data;
98
+ if (!refreshData?.refreshToken?.success) {
99
+ const errorMessage = "Token refresh was not successful";
100
+ authToken.value = null;
101
+ refreshTokenCookie.value = null;
102
+ userDataCookie.value = null;
103
+ authState.value.isAuthenticated = false;
104
+ authState.value.user = null;
105
+ authState.value.error = errorMessage;
106
+ return { success: false, error: errorMessage };
107
+ }
108
+ authToken.value = refreshData.refreshToken.authToken;
109
+ authState.value.error = null;
110
+ return { success: true };
111
+ } catch (error) {
112
+ const errorMessage = error instanceof Error ? error.message : "Token refresh failed unexpectedly";
113
+ authToken.value = null;
114
+ refreshTokenCookie.value = null;
115
+ userDataCookie.value = null;
116
+ authState.value.isAuthenticated = false;
117
+ authState.value.user = null;
118
+ authState.value.error = errorMessage;
119
+ return { success: false, error: errorMessage };
85
120
  }
86
121
  }
87
122
  function getToken() {
88
123
  return authToken.value || null;
89
124
  }
125
+ function getProviders() {
126
+ const providers = config.providers;
127
+ return {
128
+ password: providers?.password?.enabled ?? true,
129
+ oauth: providers?.oauth?.enabled ?? false,
130
+ headlessLogin: providers?.headlessLogin?.enabled ?? false
131
+ };
132
+ }
133
+ async function loginWithOAuth() {
134
+ const providers = getProviders();
135
+ if (!providers.oauth) {
136
+ logger.warn("OAuth is not enabled");
137
+ return;
138
+ }
139
+ await navigateTo("/api/_wpnuxt-auth/oauth/authorize", { external: true });
140
+ }
141
+ function hasPasswordAuth() {
142
+ return getProviders().password;
143
+ }
144
+ function hasOAuthAuth() {
145
+ return getProviders().oauth;
146
+ }
147
+ function hasHeadlessLoginAuth() {
148
+ return getProviders().headlessLogin;
149
+ }
150
+ async function fetchHeadlessLoginProviders() {
151
+ if (!hasHeadlessLoginAuth()) {
152
+ return [];
153
+ }
154
+ try {
155
+ const runtimeConfig = useRuntimeConfig();
156
+ const wordpressUrl = runtimeConfig.public.wordpressUrl;
157
+ const graphqlEndpoint = runtimeConfig.public.wpNuxt?.graphqlEndpoint || "/graphql";
158
+ if (!wordpressUrl) {
159
+ logger.warn("WordPress URL not configured");
160
+ return [];
161
+ }
162
+ const response = await $fetch(`${wordpressUrl}${graphqlEndpoint}`, {
163
+ method: "POST",
164
+ headers: { "Content-Type": "application/json" },
165
+ body: {
166
+ query: `
167
+ query LoginClients {
168
+ loginClients {
169
+ name
170
+ provider
171
+ authorizationUrl
172
+ isEnabled
173
+ }
174
+ }
175
+ `
176
+ }
177
+ });
178
+ if (response.errors?.length) {
179
+ logger.warn("Failed to fetch login clients:", response.errors[0]?.message);
180
+ return [];
181
+ }
182
+ const clients = response.data?.loginClients || [];
183
+ return clients.filter(
184
+ (client) => client.isEnabled && client.provider !== "PASSWORD" && client.provider !== "SITETOKEN" && client.authorizationUrl
185
+ ).map((client) => ({
186
+ name: client.name,
187
+ provider: client.provider,
188
+ authorizationUrl: client.authorizationUrl,
189
+ isEnabled: client.isEnabled
190
+ }));
191
+ } catch (error) {
192
+ logger.warn("Failed to fetch login providers:", error);
193
+ return [];
194
+ }
195
+ }
196
+ async function loginWithProvider(provider) {
197
+ if (!hasHeadlessLoginAuth()) {
198
+ logger.warn("Headless Login is not enabled");
199
+ return;
200
+ }
201
+ await navigateTo(`/api/_wpnuxt-auth/provider/${provider.toLowerCase()}/authorize`, { external: true });
202
+ }
90
203
  return {
91
204
  // State
92
205
  state: authState,
@@ -96,8 +209,15 @@ export function useWPAuth() {
96
209
  error: computed(() => authState.value.error),
97
210
  // Methods
98
211
  login,
212
+ loginWithOAuth,
213
+ loginWithProvider,
99
214
  logout,
100
215
  refresh,
101
- getToken
216
+ getToken,
217
+ getProviders,
218
+ hasPasswordAuth,
219
+ hasOAuthAuth,
220
+ hasHeadlessLoginAuth,
221
+ fetchHeadlessLoginProviders
102
222
  };
103
223
  }
@@ -8,12 +8,13 @@ export function useWPUser() {
8
8
  errorState.value = null;
9
9
  try {
10
10
  const { data, errors } = await useGraphqlQuery("Viewer");
11
+ const viewerData = data;
11
12
  if (errors?.length) {
12
13
  errorState.value = errors[0]?.message || "Failed to fetch user";
13
14
  loadingState.value = false;
14
15
  return null;
15
16
  }
16
- userState.value = data?.viewer || null;
17
+ userState.value = viewerData?.viewer || null;
17
18
  loadingState.value = false;
18
19
  return userState.value;
19
20
  } catch (error) {
@@ -11,8 +11,18 @@ export default defineNuxtPlugin({
11
11
  error: null
12
12
  }));
13
13
  const authToken = useCookie(config.cookieName);
14
+ const headlessLoginUserCookie = useCookie("wpnuxt-user");
15
+ const oauthUserCookie = useCookie("wpnuxt-oauth-user");
14
16
  if (authToken.value) {
15
17
  authState.value.isAuthenticated = true;
18
+ const userCookieValue = headlessLoginUserCookie.value || oauthUserCookie.value;
19
+ if (userCookieValue) {
20
+ try {
21
+ const userData = typeof userCookieValue === "string" ? JSON.parse(userCookieValue) : userCookieValue;
22
+ authState.value.user = userData;
23
+ } catch {
24
+ }
25
+ }
16
26
  }
17
27
  authState.value.isLoading = false;
18
28
  }
@@ -1,8 +1,17 @@
1
- # Login mutation for WPGraphQL JWT Authentication
1
+ # Login with username and password
2
+ # Requires: Headless Login for WPGraphQL plugin
2
3
  mutation Login($username: String!, $password: String!) {
3
- login(input: { username: $username, password: $password }) {
4
+ login(input: {
5
+ provider: PASSWORD
6
+ credentials: {
7
+ username: $username
8
+ password: $password
9
+ }
10
+ }) {
4
11
  authToken
12
+ authTokenExpiration
5
13
  refreshToken
14
+ refreshTokenExpiration
6
15
  user {
7
16
  id
8
17
  databaseId
@@ -23,9 +32,106 @@ mutation Login($username: String!, $password: String!) {
23
32
  }
24
33
  }
25
34
 
26
- # Refresh auth token mutation
27
- mutation RefreshAuthToken($refreshToken: String!) {
28
- refreshJwtAuthToken(input: { jwtRefreshToken: $refreshToken }) {
35
+ # Refresh authentication token
36
+ mutation RefreshToken($refreshToken: String!) {
37
+ refreshToken(input: { refreshToken: $refreshToken }) {
29
38
  authToken
39
+ authTokenExpiration
40
+ success
41
+ }
42
+ }
43
+
44
+ # Register a new user (requires user registration to be enabled in WordPress)
45
+ mutation RegisterUser($username: String!, $email: String!, $password: String!) {
46
+ registerUser(input: { username: $username, email: $email, password: $password }) {
47
+ user {
48
+ id
49
+ databaseId
50
+ username
51
+ email
52
+ name
53
+ }
54
+ }
55
+ }
56
+
57
+ # Update the current user's profile
58
+ mutation UpdateUser($id: ID!, $firstName: String, $lastName: String, $nickname: String, $description: String) {
59
+ updateUser(input: { id: $id, firstName: $firstName, lastName: $lastName, nickname: $nickname, description: $description }) {
60
+ user {
61
+ id
62
+ databaseId
63
+ username
64
+ email
65
+ firstName
66
+ lastName
67
+ nickname
68
+ name
69
+ description
70
+ }
71
+ }
72
+ }
73
+
74
+ # Send password reset email
75
+ mutation SendPasswordResetEmail($username: String!) {
76
+ sendPasswordResetEmail(input: { username: $username }) {
77
+ success
78
+ }
79
+ }
80
+
81
+ # Reset password with key from email
82
+ mutation ResetUserPassword($key: String!, $login: String!, $password: String!) {
83
+ resetUserPassword(input: { key: $key, login: $login, password: $password }) {
84
+ user {
85
+ id
86
+ databaseId
87
+ username
88
+ email
89
+ }
90
+ }
91
+ }
92
+
93
+ # Get available login clients/providers from Headless Login for WPGraphQL
94
+ query LoginClients {
95
+ loginClients {
96
+ name
97
+ provider
98
+ authorizationUrl
99
+ isEnabled
100
+ }
101
+ }
102
+
103
+ # Login with an OAuth provider (Google, GitHub, etc.) using authorization code
104
+ # Requires: Headless Login for WPGraphQL plugin with provider configured
105
+ mutation LoginWithProvider($provider: LoginProviderEnum!, $code: String!, $state: String) {
106
+ login(input: {
107
+ provider: $provider
108
+ oauthResponse: {
109
+ code: $code
110
+ state: $state
111
+ }
112
+ }) {
113
+ authToken
114
+ authTokenExpiration
115
+ refreshToken
116
+ refreshTokenExpiration
117
+ user {
118
+ id
119
+ databaseId
120
+ name
121
+ email
122
+ firstName
123
+ lastName
124
+ username
125
+ nickname
126
+ description
127
+ avatar {
128
+ url
129
+ }
130
+ roles {
131
+ nodes {
132
+ name
133
+ }
134
+ }
135
+ }
30
136
  }
31
137
  }
@@ -0,0 +1,26 @@
1
+ query Viewer {
2
+ viewer {
3
+ id
4
+ databaseId
5
+ userId
6
+ username
7
+ email
8
+ firstName
9
+ lastName
10
+ name
11
+ nickname
12
+ description
13
+ locale
14
+ url
15
+ uri
16
+ avatar {
17
+ url
18
+ }
19
+ roles {
20
+ nodes {
21
+ name
22
+ }
23
+ }
24
+ capabilities
25
+ }
26
+ }
@@ -0,0 +1,11 @@
1
+ fragment GeneralSettings on GeneralSettings {
2
+ title
3
+ description
4
+ url
5
+ dateFormat
6
+ language
7
+ startOfWeek
8
+ timezone
9
+ timeFormat
10
+ email
11
+ }
File without changes
@@ -0,0 +1,18 @@
1
+ import { defineEventHandler, deleteCookie } from "h3";
2
+ import { useRuntimeConfig } from "#imports";
3
+ export default defineEventHandler((event) => {
4
+ const config = useRuntimeConfig().public.wpNuxtAuth;
5
+ deleteCookie(event, config.cookieName, {
6
+ path: "/"
7
+ });
8
+ deleteCookie(event, config.refreshCookieName, {
9
+ path: "/"
10
+ });
11
+ deleteCookie(event, "wpnuxt-oauth-user", {
12
+ path: "/"
13
+ });
14
+ deleteCookie(event, "wpnuxt-oauth-state", {
15
+ path: "/"
16
+ });
17
+ return { success: true };
18
+ });
@@ -0,0 +1,42 @@
1
+ import { defineEventHandler, setCookie, getRequestURL, setResponseStatus, setResponseHeader, createError } from "h3";
2
+ import { useRuntimeConfig } from "#imports";
3
+ export default defineEventHandler(async (event) => {
4
+ const config = useRuntimeConfig();
5
+ const publicConfig = config.public.wpNuxtAuth;
6
+ const wordpressUrl = config.public.wordpressUrl;
7
+ if (!wordpressUrl) {
8
+ throw createError({
9
+ statusCode: 500,
10
+ message: "[wpnuxt:auth] WordPress URL not configured"
11
+ });
12
+ }
13
+ if (!publicConfig.providers.oauth.enabled) {
14
+ throw createError({
15
+ statusCode: 403,
16
+ message: "[wpnuxt:auth] OAuth authentication is not enabled"
17
+ });
18
+ }
19
+ const state = crypto.randomUUID();
20
+ setCookie(event, "wpnuxt-oauth-state", state, {
21
+ httpOnly: true,
22
+ secure: process.env.NODE_ENV === "production",
23
+ sameSite: "lax",
24
+ maxAge: 600,
25
+ // 10 minutes
26
+ path: "/"
27
+ });
28
+ const requestUrl = getRequestURL(event);
29
+ const callbackUrl = `${requestUrl.origin}/api/_wpnuxt-auth/oauth/callback`;
30
+ const authUrl = new URL(
31
+ publicConfig.providers.oauth.authorizationEndpoint,
32
+ wordpressUrl
33
+ );
34
+ authUrl.searchParams.set("response_type", "code");
35
+ authUrl.searchParams.set("client_id", publicConfig.providers.oauth.clientId);
36
+ authUrl.searchParams.set("redirect_uri", callbackUrl);
37
+ authUrl.searchParams.set("state", state);
38
+ authUrl.searchParams.set("scope", publicConfig.providers.oauth.scopes.join(" "));
39
+ setResponseStatus(event, 302);
40
+ setResponseHeader(event, "Location", authUrl.toString());
41
+ return "";
42
+ });
@@ -0,0 +1,106 @@
1
+ import { defineEventHandler, getQuery, getCookie, setCookie, deleteCookie, sendRedirect, createError, getRequestURL } from "h3";
2
+ import { useRuntimeConfig } from "#imports";
3
+ import { logger } from "../../../utils/logger.js";
4
+ export default defineEventHandler(async (event) => {
5
+ const query = getQuery(event);
6
+ const config = useRuntimeConfig();
7
+ const publicConfig = config.public.wpNuxtAuth;
8
+ const privateConfig = config.wpNuxtAuthOAuth;
9
+ const wordpressUrl = config.public.wordpressUrl;
10
+ if (!wordpressUrl) {
11
+ throw createError({ statusCode: 500, message: "WordPress URL not configured" });
12
+ }
13
+ if (!privateConfig) {
14
+ throw createError({ statusCode: 500, message: "OAuth not configured" });
15
+ }
16
+ if (query.error) {
17
+ const errorDesc = query.error_description || query.error;
18
+ throw createError({ statusCode: 400, message: `OAuth error: ${errorDesc}` });
19
+ }
20
+ const code = query.code;
21
+ if (!code) {
22
+ throw createError({ statusCode: 400, message: "Missing authorization code" });
23
+ }
24
+ const state = query.state;
25
+ const storedState = getCookie(event, "wpnuxt-oauth-state");
26
+ if (!state || state !== storedState) {
27
+ throw createError({ statusCode: 400, message: "Invalid state parameter" });
28
+ }
29
+ deleteCookie(event, "wpnuxt-oauth-state");
30
+ const requestUrl = getRequestURL(event);
31
+ const callbackUrl = `${requestUrl.origin}/api/_wpnuxt-auth/oauth/callback`;
32
+ const tokenUrl = new URL(privateConfig.tokenEndpoint, wordpressUrl);
33
+ const tokenResponse = await $fetch(tokenUrl.toString(), {
34
+ method: "POST",
35
+ headers: {
36
+ "Content-Type": "application/x-www-form-urlencoded"
37
+ },
38
+ body: new URLSearchParams({
39
+ grant_type: "authorization_code",
40
+ code,
41
+ redirect_uri: callbackUrl,
42
+ client_id: privateConfig.clientId,
43
+ client_secret: privateConfig.clientSecret
44
+ }).toString()
45
+ }).catch((error) => {
46
+ logger.error("Token exchange failed:", error);
47
+ throw createError({ statusCode: 500, message: "Failed to exchange authorization code" });
48
+ });
49
+ if (!tokenResponse.access_token) {
50
+ throw createError({ statusCode: 500, message: "No access token received" });
51
+ }
52
+ const userInfoUrl = new URL(privateConfig.userInfoEndpoint, wordpressUrl);
53
+ const userInfo = await $fetch(userInfoUrl.toString(), {
54
+ headers: {
55
+ Authorization: `Bearer ${tokenResponse.access_token}`
56
+ }
57
+ }).catch((error) => {
58
+ logger.error("User info fetch failed:", error);
59
+ return null;
60
+ });
61
+ const isProduction = process.env.NODE_ENV === "production";
62
+ setCookie(event, publicConfig.cookieName, tokenResponse.access_token, {
63
+ httpOnly: true,
64
+ secure: isProduction,
65
+ sameSite: "lax",
66
+ maxAge: tokenResponse.expires_in || publicConfig.tokenMaxAge,
67
+ path: "/"
68
+ });
69
+ if (tokenResponse.refresh_token) {
70
+ setCookie(event, publicConfig.refreshCookieName, tokenResponse.refresh_token, {
71
+ httpOnly: true,
72
+ secure: isProduction,
73
+ sameSite: "lax",
74
+ maxAge: publicConfig.refreshTokenMaxAge,
75
+ path: "/"
76
+ });
77
+ }
78
+ if (userInfo) {
79
+ const userId = userInfo.sub || userInfo.id || userInfo.ID;
80
+ const firstName = userInfo.given_name || userInfo.first_name || "";
81
+ const lastName = userInfo.family_name || userInfo.last_name || "";
82
+ const displayName = userInfo.name || userInfo.display_name || `${firstName} ${lastName}`.trim();
83
+ const username = userInfo.preferred_username || userInfo.username || userInfo.user_login || userInfo.nickname;
84
+ const avatarUrl = userInfo.picture || userInfo.avatar_url;
85
+ const userData = {
86
+ id: userId?.toString() || "",
87
+ databaseId: typeof userId === "number" ? userId : Number.parseInt(userId?.toString() || "0", 10),
88
+ email: userInfo.email,
89
+ name: displayName,
90
+ firstName,
91
+ lastName,
92
+ username,
93
+ nickname: userInfo.nickname,
94
+ avatar: avatarUrl ? { url: avatarUrl } : void 0
95
+ };
96
+ setCookie(event, "wpnuxt-oauth-user", JSON.stringify(userData), {
97
+ httpOnly: false,
98
+ // Client needs to read this
99
+ secure: isProduction,
100
+ sameSite: "lax",
101
+ maxAge: tokenResponse.expires_in || publicConfig.tokenMaxAge,
102
+ path: "/"
103
+ });
104
+ }
105
+ return sendRedirect(event, publicConfig.redirectOnLogin);
106
+ });
@@ -0,0 +1,82 @@
1
+ import { defineEventHandler, getRouterParam, setCookie, sendRedirect, createError, getRequestURL } from "h3";
2
+ import { useRuntimeConfig } from "#imports";
3
+ import { logger } from "../../../../utils/logger.js";
4
+ export default defineEventHandler(async (event) => {
5
+ const provider = getRouterParam(event, "provider");
6
+ if (!provider) {
7
+ throw createError({ statusCode: 400, message: "Missing provider parameter" });
8
+ }
9
+ const config = useRuntimeConfig();
10
+ const wordpressUrl = config.public.wordpressUrl;
11
+ const wpNuxtConfig = config.public.wpNuxt;
12
+ const graphqlEndpoint = wpNuxtConfig?.graphqlEndpoint || "/graphql";
13
+ if (!wordpressUrl) {
14
+ throw createError({ statusCode: 500, message: "WordPress URL not configured" });
15
+ }
16
+ const graphqlUrl = `${wordpressUrl}${graphqlEndpoint}`;
17
+ const response = await $fetch(graphqlUrl, {
18
+ method: "POST",
19
+ headers: {
20
+ "Content-Type": "application/json"
21
+ },
22
+ body: {
23
+ query: `
24
+ query LoginClients {
25
+ loginClients {
26
+ name
27
+ provider
28
+ authorizationUrl
29
+ isEnabled
30
+ }
31
+ }
32
+ `
33
+ }
34
+ }).catch((error) => {
35
+ logger.error("Failed to fetch login clients:", error);
36
+ throw createError({ statusCode: 500, message: "Failed to fetch login providers from WordPress" });
37
+ });
38
+ if (response.errors?.length) {
39
+ logger.error("GraphQL errors:", response.errors);
40
+ throw createError({ statusCode: 500, message: response.errors[0]?.message || "GraphQL error" });
41
+ }
42
+ const loginClients = response.data?.loginClients || [];
43
+ const providerUpperCase = provider.toUpperCase();
44
+ const loginClient = loginClients.find(
45
+ (client) => client.provider === providerUpperCase && client.isEnabled
46
+ );
47
+ if (!loginClient) {
48
+ throw createError({
49
+ statusCode: 404,
50
+ message: `Provider '${provider}' not found or not enabled. Available providers: ${loginClients.filter((c) => c.isEnabled).map((c) => c.name).join(", ")}`
51
+ });
52
+ }
53
+ if (!loginClient.authorizationUrl) {
54
+ throw createError({
55
+ statusCode: 500,
56
+ message: `Provider '${provider}' does not have an authorization URL configured`
57
+ });
58
+ }
59
+ const state = crypto.randomUUID();
60
+ const isProduction = process.env.NODE_ENV === "production";
61
+ setCookie(event, "wpnuxt-headless-login-state", state, {
62
+ httpOnly: true,
63
+ secure: isProduction,
64
+ sameSite: "lax",
65
+ maxAge: 600,
66
+ // 10 minutes
67
+ path: "/"
68
+ });
69
+ setCookie(event, "wpnuxt-headless-login-provider", providerUpperCase, {
70
+ httpOnly: true,
71
+ secure: isProduction,
72
+ sameSite: "lax",
73
+ maxAge: 600,
74
+ path: "/"
75
+ });
76
+ const authUrl = new URL(loginClient.authorizationUrl);
77
+ authUrl.searchParams.set("state", state);
78
+ const requestUrl = getRequestURL(event);
79
+ const callbackUrl = `${requestUrl.origin}/api/_wpnuxt-auth/provider/${provider}/callback`;
80
+ authUrl.searchParams.set("redirect_uri", callbackUrl);
81
+ return sendRedirect(event, authUrl.toString());
82
+ });
@@ -0,0 +1,136 @@
1
+ import { defineEventHandler, getQuery, getRouterParam, getCookie, setCookie, deleteCookie, sendRedirect, createError } from "h3";
2
+ import { useRuntimeConfig } from "#imports";
3
+ import { logger } from "../../../../utils/logger.js";
4
+ export default defineEventHandler(async (event) => {
5
+ const query = getQuery(event);
6
+ const routeProvider = getRouterParam(event, "provider");
7
+ const config = useRuntimeConfig();
8
+ const publicConfig = config.public.wpNuxtAuth;
9
+ const wordpressUrl = config.public.wordpressUrl;
10
+ const wpNuxtConfig = config.public.wpNuxt;
11
+ const graphqlEndpoint = wpNuxtConfig?.graphqlEndpoint || "/graphql";
12
+ if (!wordpressUrl) {
13
+ throw createError({ statusCode: 500, message: "WordPress URL not configured" });
14
+ }
15
+ if (query.error) {
16
+ const errorDesc = query.error_description || query.error;
17
+ throw createError({ statusCode: 400, message: `OAuth error: ${errorDesc}` });
18
+ }
19
+ const code = query.code;
20
+ if (!code) {
21
+ throw createError({ statusCode: 400, message: "Missing authorization code" });
22
+ }
23
+ const state = query.state;
24
+ const storedState = getCookie(event, "wpnuxt-headless-login-state");
25
+ if (!state || state !== storedState) {
26
+ throw createError({ statusCode: 400, message: "Invalid state parameter" });
27
+ }
28
+ const storedProvider = getCookie(event, "wpnuxt-headless-login-provider");
29
+ const provider = storedProvider || routeProvider?.toUpperCase();
30
+ if (!provider) {
31
+ throw createError({ statusCode: 400, message: "Missing provider information" });
32
+ }
33
+ deleteCookie(event, "wpnuxt-headless-login-state");
34
+ deleteCookie(event, "wpnuxt-headless-login-provider");
35
+ const graphqlUrl = `${wordpressUrl}${graphqlEndpoint}`;
36
+ const response = await $fetch(graphqlUrl, {
37
+ method: "POST",
38
+ headers: {
39
+ "Content-Type": "application/json"
40
+ },
41
+ body: {
42
+ query: `
43
+ mutation LoginWithProvider($provider: LoginProviderEnum!, $code: String!, $state: String) {
44
+ login(input: {
45
+ provider: $provider
46
+ oauthResponse: {
47
+ code: $code
48
+ state: $state
49
+ }
50
+ }) {
51
+ authToken
52
+ authTokenExpiration
53
+ refreshToken
54
+ refreshTokenExpiration
55
+ user {
56
+ id
57
+ databaseId
58
+ name
59
+ email
60
+ firstName
61
+ lastName
62
+ username
63
+ nickname
64
+ description
65
+ avatar {
66
+ url
67
+ }
68
+ roles {
69
+ nodes {
70
+ name
71
+ }
72
+ }
73
+ }
74
+ }
75
+ }
76
+ `,
77
+ variables: {
78
+ provider,
79
+ code,
80
+ state
81
+ }
82
+ }
83
+ }).catch((error) => {
84
+ logger.error("Login mutation failed:", error);
85
+ throw createError({ statusCode: 500, message: "Failed to authenticate with WordPress" });
86
+ });
87
+ if (response.errors?.length) {
88
+ logger.error("GraphQL errors:", response.errors);
89
+ throw createError({ statusCode: 400, message: response.errors[0]?.message || "Authentication failed" });
90
+ }
91
+ const loginData = response.data?.login;
92
+ if (!loginData?.authToken) {
93
+ throw createError({ statusCode: 500, message: "No auth token received from WordPress" });
94
+ }
95
+ const isProduction = process.env.NODE_ENV === "production";
96
+ setCookie(event, publicConfig.cookieName, loginData.authToken, {
97
+ httpOnly: true,
98
+ secure: isProduction,
99
+ sameSite: "lax",
100
+ maxAge: publicConfig.tokenMaxAge,
101
+ path: "/"
102
+ });
103
+ if (loginData.refreshToken) {
104
+ setCookie(event, publicConfig.refreshCookieName, loginData.refreshToken, {
105
+ httpOnly: true,
106
+ secure: isProduction,
107
+ sameSite: "lax",
108
+ maxAge: publicConfig.refreshTokenMaxAge,
109
+ path: "/"
110
+ });
111
+ }
112
+ if (loginData.user) {
113
+ const userData = {
114
+ id: loginData.user.id,
115
+ databaseId: loginData.user.databaseId,
116
+ email: loginData.user.email,
117
+ name: loginData.user.name,
118
+ firstName: loginData.user.firstName,
119
+ lastName: loginData.user.lastName,
120
+ username: loginData.user.username,
121
+ nickname: loginData.user.nickname,
122
+ description: loginData.user.description,
123
+ avatar: loginData.user.avatar,
124
+ roles: loginData.user.roles
125
+ };
126
+ setCookie(event, "wpnuxt-user", JSON.stringify(userData), {
127
+ httpOnly: false,
128
+ // Client needs to read this for hydration
129
+ secure: isProduction,
130
+ sameSite: "lax",
131
+ maxAge: publicConfig.tokenMaxAge,
132
+ path: "/"
133
+ });
134
+ }
135
+ return sendRedirect(event, publicConfig.redirectOnLogin);
136
+ });
File without changes
@@ -0,0 +1,2 @@
1
+ import { consola } from "consola";
2
+ export const logger = consola.withTag("wpnuxt:auth");
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Stub for nuxt/schema types
3
+ * Used during module development when Nuxt types aren't available
4
+ */
5
+
6
+ export interface PublicRuntimeConfig {
7
+ [key: string]: unknown
8
+ }
9
+
10
+ export interface RuntimeConfig {
11
+ public: PublicRuntimeConfig
12
+ [key: string]: unknown
13
+ }
14
+
15
+ export interface NuxtConfig {
16
+ runtimeConfig?: RuntimeConfig
17
+ [key: string]: unknown
18
+ }
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Type stubs for Nuxt-generated imports.
3
+ * These are used during module development when .nuxt folder doesn't exist.
4
+ * Actual types are generated at runtime in consuming applications.
5
+ */
6
+
7
+ import type { ComputedRef, Ref } from 'vue'
8
+ import type { NuxtApp } from 'nuxt/app'
9
+
10
+ // Navigation options
11
+ interface NavigateToOptions {
12
+ replace?: boolean
13
+ redirectCode?: number
14
+ external?: boolean
15
+ }
16
+
17
+ // Cookie options
18
+ interface CookieOptions {
19
+ maxAge?: number
20
+ expires?: Date
21
+ path?: string
22
+ domain?: string
23
+ secure?: boolean
24
+ sameSite?: 'strict' | 'lax' | 'none'
25
+ }
26
+
27
+ // OAuth public config (without secrets)
28
+ interface OAuthPublicConfig {
29
+ enabled: boolean
30
+ clientId: string
31
+ authorizationEndpoint: string
32
+ scopes: string[]
33
+ }
34
+
35
+ // Headless Login public config
36
+ interface HeadlessLoginPublicConfig {
37
+ enabled: boolean
38
+ }
39
+
40
+ // Public providers config (without secrets)
41
+ interface AuthProvidersPublic {
42
+ password: { enabled: boolean }
43
+ oauth: OAuthPublicConfig
44
+ headlessLogin: HeadlessLoginPublicConfig
45
+ }
46
+
47
+ // WPNuxt Auth public config interface
48
+ interface WPNuxtAuthPublicConfig {
49
+ cookieName: string
50
+ refreshCookieName: string
51
+ tokenMaxAge: number
52
+ refreshTokenMaxAge: number
53
+ redirectOnLogin: string
54
+ redirectOnLogout: string
55
+ loginPage: string
56
+ providers: AuthProvidersPublic
57
+ }
58
+
59
+ // Runtime config interface
60
+ interface RuntimeConfig {
61
+ public: {
62
+ wpNuxtAuth: WPNuxtAuthPublicConfig
63
+ wordpressUrl?: string
64
+ [key: string]: unknown
65
+ }
66
+ wpNuxtAuth?: WPNuxtAuthPublicConfig
67
+ [key: string]: unknown
68
+ }
69
+
70
+ // Nuxt plugin with name
71
+ interface NuxtPluginConfig {
72
+ name?: string
73
+ enforce?: 'pre' | 'post'
74
+ setup?: (nuxtApp: NuxtApp) => void | Promise<void>
75
+ }
76
+
77
+ // Stub for #imports - Vue reactivity
78
+ export function computed<T>(getter: () => T): ComputedRef<T>
79
+
80
+ // Stub for #imports - Nuxt
81
+ export function defineNuxtPlugin(plugin: NuxtPluginConfig | ((nuxtApp: NuxtApp) => void | Promise<void>)): unknown
82
+ export function navigateTo(to: string | { path: string }, options?: NavigateToOptions): Promise<void>
83
+ export function useCookie<T = string>(name: string, options?: CookieOptions): Ref<T | null>
84
+ export function useRuntimeConfig(): RuntimeConfig
85
+ export function useState<T = unknown>(key: string, init?: () => T): Ref<T>
86
+
87
+ // Stub for #imports - nuxt-graphql-middleware
88
+ export function useGraphqlMutation<T = unknown>(
89
+ name: string,
90
+ variables?: Record<string, unknown>,
91
+ options?: Record<string, unknown>
92
+ ): Promise<{ data: T | null, error: Error | null }>
93
+
94
+ export function useGraphqlQuery<T = unknown>(
95
+ name: string,
96
+ params?: Record<string, unknown>,
97
+ options?: Record<string, unknown>
98
+ ): { data: Ref<T | null>, pending: Ref<boolean>, error: Ref<Error | null> }
File without changes
@@ -0,0 +1,2 @@
1
+ import { consola } from "consola";
2
+ export const logger = consola.withTag("wpnuxt:auth");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wpnuxt/auth",
3
- "version": "2.0.0-alpha.1",
3
+ "version": "2.0.0-alpha.10",
4
4
  "description": "Authentication module for WPNuxt using WPGraphQL JWT",
5
5
  "keywords": [
6
6
  "nuxt",
@@ -34,22 +34,26 @@
34
34
  "access": "public"
35
35
  },
36
36
  "dependencies": {
37
- "@nuxt/kit": "4.2.2"
37
+ "@nuxt/kit": "4.3.0"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@nuxt/module-builder": "^1.0.2",
41
- "@nuxt/schema": "4.2.2",
42
- "@types/node": "^25.0.3",
43
- "nuxt": "4.2.2",
44
- "vue-tsc": "^3.2.1"
41
+ "@nuxt/schema": "4.3.0",
42
+ "@nuxt/test-utils": "^3.23.0",
43
+ "@types/node": "^25.2.1",
44
+ "nuxt": "4.3.0",
45
+ "vitest": "^4.0.18",
46
+ "vue-tsc": "^3.2.3"
45
47
  },
46
48
  "peerDependencies": {
47
49
  "nuxt": "^4.0.0",
48
- "@wpnuxt/core": "2.0.0-alpha.1"
50
+ "@wpnuxt/core": "2.0.0-alpha.10"
49
51
  },
50
52
  "scripts": {
51
53
  "build": "nuxt-module-build build",
52
- "dev:prepare": "nuxt-module-build build --stub && nuxt-module-build prepare",
54
+ "dev:prepare": "nuxt-module-build build --stub",
55
+ "test": "vitest run",
56
+ "test:watch": "vitest watch",
53
57
  "typecheck": "vue-tsc --noEmit",
54
58
  "clean": "rm -rf dist .nuxt node_modules"
55
59
  }