@devcoffee/nuxt-core 1.0.2 → 1.1.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.
Files changed (64) hide show
  1. package/CHANGELOG.md +82 -0
  2. package/GUIDELINE.md +351 -0
  3. package/README.md +405 -84
  4. package/dist/module.d.mts +295 -156
  5. package/dist/module.d.ts +300 -0
  6. package/dist/module.json +1 -1
  7. package/dist/module.mjs +191 -36
  8. package/dist/runtime/app/composables/useAuthContext.d.ts +26 -0
  9. package/dist/runtime/app/composables/useAuthContext.js +111 -0
  10. package/dist/runtime/app/composables/useLogger.d.ts +3 -3
  11. package/dist/runtime/app/composables/useSessionContext.d.ts +22 -0
  12. package/dist/runtime/app/composables/useSessionContext.js +5 -0
  13. package/dist/runtime/app/middleware/authts.d.ts +12 -0
  14. package/dist/runtime/app/middleware/authts.js +101 -0
  15. package/dist/runtime/app/pages/authorize.d.vue.ts +3 -0
  16. package/dist/runtime/app/pages/authorize.vue +32 -0
  17. package/dist/runtime/app/pages/authorize.vue.d.ts +3 -0
  18. package/dist/runtime/app/plugins/authts.d.ts +82 -0
  19. package/dist/runtime/app/plugins/authts.js +91 -0
  20. package/dist/runtime/app/plugins/formatters.d.ts +29 -0
  21. package/dist/runtime/app/plugins/formatters.js +101 -0
  22. package/dist/runtime/app/plugins/locale.d.ts +37 -0
  23. package/dist/runtime/app/plugins/locale.js +39 -0
  24. package/dist/runtime/app/plugins/logging.d.ts +24 -16
  25. package/dist/runtime/app/plugins/logging.js +0 -1
  26. package/dist/runtime/app/utils/hashing.d.ts +1 -0
  27. package/dist/runtime/app/utils/hashing.js +3 -0
  28. package/dist/runtime/server/adapters/http.d.ts +5 -0
  29. package/dist/runtime/server/adapters/http.js +15 -0
  30. package/dist/runtime/server/adapters/oidc.d.ts +58 -0
  31. package/dist/runtime/server/adapters/oidc.js +21 -0
  32. package/dist/runtime/server/adapters/storage.d.ts +39 -0
  33. package/dist/runtime/server/adapters/storage.js +14 -0
  34. package/dist/runtime/server/adapters/utils.d.ts +31 -0
  35. package/dist/runtime/server/adapters/utils.js +28 -0
  36. package/dist/runtime/server/composables/useServerLogger.d.ts +3 -2
  37. package/dist/runtime/server/composables/useServerLogger.js +4 -4
  38. package/dist/runtime/server/core/crypto.d.ts +70 -0
  39. package/dist/runtime/server/core/crypto.js +55 -0
  40. package/dist/runtime/server/core/helpers.d.ts +194 -0
  41. package/dist/runtime/server/core/helpers.js +350 -0
  42. package/dist/runtime/server/core/index.d.ts +1 -0
  43. package/dist/runtime/server/core/index.js +1 -0
  44. package/dist/runtime/server/core/mutex.d.ts +19 -0
  45. package/dist/runtime/server/core/mutex.js +39 -0
  46. package/dist/runtime/server/core/nuxtAuthtsHandler.d.ts +26 -0
  47. package/dist/runtime/server/core/nuxtAuthtsHandler.js +238 -0
  48. package/dist/runtime/server/core/nuxtForwardHandler.d.ts +18 -0
  49. package/dist/runtime/server/core/nuxtForwardHandler.js +60 -0
  50. package/dist/runtime/server/dev/route/session.d.ts +2 -0
  51. package/dist/runtime/server/dev/route/session.js +8 -0
  52. package/dist/runtime/server/plugins/authts.d.ts +11 -0
  53. package/dist/runtime/server/plugins/authts.js +55 -0
  54. package/dist/runtime/server/plugins/logging.js +7 -2
  55. package/dist/runtime/server/tsconfig.json +3 -3
  56. package/dist/runtime/types/global.env.d.ts +21 -7
  57. package/dist/runtime/types/nitro.d.ts +7 -2
  58. package/dist/runtime/types/nuxt.d.ts +28 -8
  59. package/dist/runtime/utils.d.ts +31 -0
  60. package/dist/runtime/utils.js +28 -0
  61. package/dist/types.d.mts +6 -4
  62. package/package.json +45 -17
  63. package/dist/runtime/plugin.d.ts +0 -2
  64. package/dist/runtime/plugin.js +0 -4
package/README.md CHANGED
@@ -1,84 +1,405 @@
1
- <!--
2
- Get your module up and running quickly.
3
-
4
- Find and replace all on all files (CMD+SHIFT+F):
5
- - Name: My Module
6
- - Package name: my-module
7
- - Description: My new Nuxt module
8
- -->
9
-
10
- # My Module
11
-
12
- [![npm version][npm-version-src]][npm-version-href]
13
- [![npm downloads][npm-downloads-src]][npm-downloads-href]
14
- [![License][license-src]][license-href]
15
- [![Nuxt][nuxt-src]][nuxt-href]
16
-
17
- My new Nuxt module for doing amazing things.
18
-
19
- - [✨ &nbsp;Release Notes](/CHANGELOG.md)
20
- <!-- - [🏀 Online playground](https://stackblitz.com/github/your-org/my-module?file=playground%2Fapp.vue) -->
21
- <!-- - [📖 &nbsp;Documentation](https://example.com) -->
22
-
23
- ## Features
24
-
25
- <!-- Highlight some of the features your module provide here -->
26
- - &nbsp;Foo
27
- - 🚠 &nbsp;Bar
28
- - 🌲 &nbsp;Baz
29
-
30
- ## Quick Setup
31
-
32
- Install the module to your Nuxt application with one command:
33
-
34
- ```bash
35
- npx nuxi module add my-module
36
- ```
37
-
38
- That's it! You can now use My Module in your Nuxt app ✨
39
-
40
-
41
- ## Contribution
42
-
43
- <details>
44
- <summary>Local development</summary>
45
-
46
- ```bash
47
- # Install dependencies
48
- npm install
49
-
50
- # Generate type stubs
51
- npm run dev:prepare
52
-
53
- # Develop with the playground
54
- npm run dev
55
-
56
- # Build the playground
57
- npm run dev:build
58
-
59
- # Run ESLint
60
- npm run lint
61
-
62
- # Run Vitest
63
- npm run test
64
- npm run test:watch
65
-
66
- # Release new version
67
- npm run release
68
- ```
69
-
70
- </details>
71
-
72
-
73
- <!-- Badges -->
74
- [npm-version-src]: https://img.shields.io/npm/v/my-module/latest.svg?style=flat&colorA=020420&colorB=00DC82
75
- [npm-version-href]: https://npmjs.com/package/my-module
76
-
77
- [npm-downloads-src]: https://img.shields.io/npm/dm/my-module.svg?style=flat&colorA=020420&colorB=00DC82
78
- [npm-downloads-href]: https://npm.chart.dev/my-module
79
-
80
- [license-src]: https://img.shields.io/npm/l/my-module.svg?style=flat&colorA=020420&colorB=00DC82
81
- [license-href]: https://npmjs.com/package/my-module
82
-
83
- [nuxt-src]: https://img.shields.io/badge/Nuxt-020420?logo=nuxt.js
84
- [nuxt-href]: https://nuxt.com
1
+ # @devcoffee/nuxt-core
2
+
3
+ [![npm version][npm-version-src]][npm-version-href]
4
+ [![npm downloads][npm-downloads-src]][npm-downloads-href]
5
+ [![License][license-src]][license-href]
6
+ [![Nuxt][nuxt-src]][nuxt-href]
7
+
8
+ Full OpenID Connect / OAuth 2.0 authorization code grant with PKCE for DevCoffee internal Nuxt 4 applications.
9
+ Provides server-side session management via Nitro, client-side auth state via Vue composables, and universal route protection middleware.
10
+
11
+ - [Release Notes](/CHANGELOG.md)
12
+ - [Contributor Guide](/GUIDELINE.md)
13
+
14
+ ## Features
15
+
16
+ - PKCE-enforced authorization code flow (S256, enabled by default)
17
+ - HMAC-SHA256 signed session cookies when `sessions.secret` is configured
18
+ - AES-256-GCM encrypted tokenSet storage with HKDF-derived key
19
+ - 256-bit session ID entropy via `crypto.randomBytes(32)`
20
+ - Open redirect protection on all post-auth redirects
21
+ - Per-route protection via `definePageMeta` — no page-level boilerplate
22
+ - Auto-imported composables: `useAuthContext`, `useSessionContext`, `useLogger`
23
+ - Server-side session validation on every Nitro request
24
+ - Token refresh mutex — no concurrent refresh races
25
+ - User info caching with configurable TTL
26
+ - Nuxt DevTools integration (session inspector)
27
+
28
+ ## Requirements
29
+
30
+ - **Nuxt:** `^4.0.0` — Nuxt 3 is not supported
31
+ - **Node.js:** LTS (18+)
32
+ - **OIDC provider:** Any provider with a `.well-known/openid-configuration` endpoint
33
+
34
+ ## Installation
35
+
36
+ ```bash
37
+ npm install @devcoffee/nuxt-core
38
+ ```
39
+
40
+ Register the module in `nuxt.config.ts`:
41
+
42
+ ```ts
43
+ export default defineNuxtConfig({
44
+ modules: ['@devcoffee/nuxt-core'],
45
+ })
46
+ ```
47
+
48
+ ## Quick Setup
49
+
50
+ **1. Install and register the module** (see [Installation](#installation) above).
51
+
52
+ **2. Add the minimum config to `nuxt.config.ts`:**
53
+
54
+ ```ts
55
+ export default defineNuxtConfig({
56
+ modules: ['@devcoffee/nuxt-core'],
57
+ nuxtCore: {
58
+ authts: {
59
+ openid: {
60
+ wellKnownUrl: process.env.OIDC_WELL_KNOWN_URL!,
61
+ clientId: process.env.OIDC_CLIENT_ID!,
62
+ clientSecret: process.env.OIDC_CLIENT_SECRET!,
63
+ redirectUri: '/authorize',
64
+ scopes: ['openid', 'profile', 'email'],
65
+ },
66
+ sessions: {
67
+ secret: process.env.SESSION_SECRET!,
68
+ },
69
+ },
70
+ },
71
+ })
72
+ ```
73
+
74
+ **3. Create the server handler** — **required**. Auth endpoints return 404 without this file.
75
+
76
+ Create `server/api/_auth/[...].ts`:
77
+
78
+ ```ts
79
+ export default NuxtAuthtsHandler({
80
+ userInfo: async (user, { openidUser }) => ({
81
+ ...user,
82
+ id: openidUser?.sub ?? user.id,
83
+ email: openidUser?.email ?? user.email,
84
+ firstName: openidUser?.given_name ?? user.firstName,
85
+ lastName: openidUser?.family_name ?? user.lastName,
86
+ }),
87
+ })
88
+ ```
89
+
90
+ > The OIDC callback page at `openid.redirectUri` (default `/authorize`) is auto-registered by the module. Do NOT create it manually.
91
+
92
+ **4. Set environment variables:**
93
+
94
+ ```bash
95
+ OIDC_WELL_KNOWN_URL=https://your-provider/.well-known/openid-configuration
96
+ OIDC_CLIENT_ID=your-client-id
97
+ OIDC_CLIENT_SECRET=your-client-secret
98
+ SESSION_SECRET=your-32-char-minimum-random-secret
99
+ ```
100
+
101
+ ## Configuration Reference
102
+
103
+ All options are nested under `nuxtCore` in `nuxt.config.ts`.
104
+
105
+ ### `authts.openid` options
106
+
107
+ | Option | Type | Default | Description |
108
+ | --- | --- | --- | --- |
109
+ | `wellKnownUrl` | `string` | `''` (required) | OIDC provider discovery URL (`/.well-known/openid-configuration` endpoint) |
110
+ | `clientId` | `string` | `''` (required) | OAuth 2.0 client ID registered with the OIDC provider |
111
+ | `clientSecret` | `string` | `''` (required) | OAuth 2.0 client secret |
112
+ | `redirectUri` | `string` | `'/authorize'` | OIDC callback path. Must match the redirect URI registered with your provider. The module auto-registers this page. |
113
+ | `scopes` | `string[]` | `[]` | OAuth scopes to request. Include `'openid'` at minimum. |
114
+ | `usePkce` | `boolean` | `true` | Enable PKCE (S256). Strongly recommended — disabling reduces security. |
115
+ | `codeChallengeMethod` | `string` | `'S256'` | PKCE code challenge method |
116
+ | `autoFetchUser` | `boolean` | `true` | Fetch user info from the OIDC userinfo endpoint on GET_SESSION |
117
+ | `autoFetchUserTtl` | `number` | `300` | Userinfo cache TTL in seconds. Prevents redundant OIDC provider calls. |
118
+ | `fetchUserOnLogin` | `boolean` | `true` | Fetch user info immediately after the token exchange |
119
+ | `tokenRefreshBufferMs` | `number` | `60000` | Refresh tokens this many ms before expiry |
120
+ | `cache.prefix` | `string` | `'oidc-server-meta'` | Nitro cache key prefix for OIDC server metadata |
121
+ | `cache.expires` | `number` | `86400` | OIDC metadata cache TTL in seconds (24 hours) |
122
+
123
+ ### `authts.sessions` options
124
+
125
+ | Option | Type | Default | Description |
126
+ | --- | --- | --- | --- |
127
+ | `secret` | `string` | `''` | Secret for HMAC-SHA256 cookie signing and AES-256-GCM tokenSet encryption. **Must be set in production** (see [Security](#security)). Empty string disables signing and encryption. |
128
+ | `expiresIn` | `number` | `518400` | Session lifetime in seconds (default 6 days) |
129
+ | `names.sessionId` | `string` | `'auths.ssid'` | Session ID cookie name |
130
+ | `names.state` | `string` | `'auths.state'` | OAuth state cookie name |
131
+ | `names.redirectUrl` | `string` | `'auths.redirect'` | Redirect URL cookie name |
132
+ | `names.pkce` | `string` | `'auths.pkce'` | PKCE verifier cookie name |
133
+ | `storage.name` | `string` | `'sessions'` | Nitro storage mount name |
134
+ | `storage.prefix` | `string` | `'session'` | Nitro storage key prefix |
135
+ | `cookieOpts.path` | `string` | `'/'` | Cookie path |
136
+ | `cookieOpts.sameSite` | `string` | `'lax'` | Cookie SameSite attribute |
137
+ | `cookieOpts.httpOnly` | `boolean` | `true` | Cookie HttpOnly flag |
138
+ | `cookieOpts.secure` | `boolean` | `true` in production | Cookie Secure flag |
139
+
140
+ ### `authts.auth` options
141
+
142
+ | Option | Type | Default | Description |
143
+ | --- | --- | --- | --- |
144
+ | `loginUri` | `string` | `'/login'` | Path middleware redirects unauthenticated users to |
145
+ | `defaultLoginRedirectUri` | `string` | `'/'` | Default post-login redirect when no intended destination is recorded |
146
+ | `defaultLogoutRedirectUri` | `string` | `'/login'` | Post-logout redirect |
147
+ | `ignoreRegexPatterns` | `RegExp[]` | `[]` | Routes matching these patterns are excluded from middleware in all environments |
148
+ | `ignoreRegexPatternsDev` | `RegExp[]` | `[]` | Routes excluded from middleware in development only |
149
+
150
+ ### `logging` options
151
+
152
+ | Option | Type | Default | Description |
153
+ | --- | --- | --- | --- |
154
+ | `logging.server.level` | `number` | `2` | Server log level (0=silent, 1=fatal, 2=error, 3=warn, 4=info/debug) |
155
+ | `logging.server.tag` | `string` | `'server'` | Server log tag |
156
+ | `logging.ssr.tag` | `string` | `'app-ssr'` | SSR log tag |
157
+ | `logging.client.tag` | `string` | `'app-client'` | Client log tag |
158
+
159
+ ## Composables
160
+
161
+ All composables are auto-imported. No import statement needed.
162
+
163
+ ### `useAuthContext(initiator?)`
164
+
165
+ Reactive authentication state and actions.
166
+
167
+ ```ts
168
+ const { login, logout, authorize, isAuthenticated, user, session, processing, sanitizeError } = useAuthContext()
169
+
170
+ // login — redirects to the OIDC provider's authorization endpoint
171
+ await login('/dashboard')
172
+
173
+ // logout — revokes tokens and redirects to defaultLogoutRedirectUri
174
+ await logout()
175
+
176
+ // authorize — exchanges the authorization code for tokens (called on the callback page)
177
+ await authorize(new URLSearchParams(window.location.search))
178
+
179
+ // isAuthenticated — ComputedRef<boolean>, true when the user has a valid session
180
+ console.log(isAuthenticated.value) // true | false
181
+
182
+ // user — ComputedRef<AuthorizedUser>, the current authenticated user
183
+ console.log(user.value.email)
184
+
185
+ // processing — Ref<boolean>, true while an auth request is in flight
186
+ console.log(processing.value) // true during login/logout/authorize
187
+
188
+ // sanitizeError — normalizes any error to H3Error
189
+ const h3err = sanitizeError(unknownError)
190
+ ```
191
+
192
+ ### `useSessionContext()`
193
+
194
+ Low-level session accessor. Prefer `useAuthContext` for most use cases.
195
+
196
+ ```ts
197
+ const { getValue, refetch } = useSessionContext()
198
+
199
+ // getValue — returns the current NuxtSessionContext
200
+ const session = getValue()
201
+
202
+ // refetch — triggers a session re-fetch from the server
203
+ await refetch()
204
+ ```
205
+
206
+ ### `useLogger(opts?)`
207
+
208
+ Create a tagged logger instance.
209
+
210
+ ```ts
211
+ const logger = useLogger({ tag: 'my-feature', level: 4 })
212
+
213
+ logger.debug('Debug message')
214
+ logger.warn('Warning message')
215
+ logger.error('Error message', error)
216
+ ```
217
+
218
+ Log levels: 0 silent, 1 fatal, 2 error (default), 3 warn, 4 info/debug
219
+
220
+ ## Server Handlers
221
+
222
+ Server handlers are auto-imported into Nitro routes. No import needed.
223
+
224
+ ### `NuxtAuthtsHandler(options?)`
225
+
226
+ Main auth handler. **Required** — create this file or auth endpoints return 404.
227
+
228
+ ```ts
229
+ // server/api/_auth/[...].ts
230
+ export default NuxtAuthtsHandler({
231
+ userInfo: async (user, { openidUser }) => ({
232
+ ...user,
233
+ id: openidUser?.sub ?? user.id,
234
+ email: openidUser?.email ?? user.email,
235
+ firstName: openidUser?.given_name ?? user.firstName,
236
+ lastName: openidUser?.family_name ?? user.lastName,
237
+ }),
238
+ })
239
+ ```
240
+
241
+ The `userInfo` callback maps OIDC claims to `AuthorizedUser`. It is called after the token exchange (when `fetchUserOnLogin` is true) and on every `GET_SESSION` request when `autoFetchUser` is true (results are cached for `autoFetchUserTtl` seconds).
242
+
243
+ ### `NuxtForwardRequestHandler(opts)`
244
+
245
+ Authenticated reverse proxy — forwards requests to a backend service with the session access token attached.
246
+
247
+ ```ts
248
+ // server/api/backend/[...].ts
249
+ export default NuxtForwardRequestHandler({
250
+ targetBaseUrl: process.env.BACKEND_API_URL,
251
+ proxyPrefix: '/api/backend',
252
+ })
253
+ ```
254
+
255
+ ## Route Protection
256
+
257
+ The module registers a global Nuxt middleware that runs on every navigation. Control access per page with `definePageMeta`.
258
+
259
+ ### Require authentication
260
+
261
+ ```vue
262
+ <!-- pages/dashboard.vue -->
263
+ <script setup lang="ts">
264
+ definePageMeta({
265
+ authts: { required: true },
266
+ })
267
+
268
+ const { user, isAuthenticated, logout } = useAuthContext()
269
+ </script>
270
+
271
+ <template>
272
+ <div>
273
+ <p>Welcome, {{ user.firstName }}</p>
274
+ <button @click="logout()">Sign out</button>
275
+ </div>
276
+ </template>
277
+ ```
278
+
279
+ Unauthenticated users are redirected to `auth.loginUri` (default `/login`). The intended URL is preserved as the post-login redirect.
280
+
281
+ ### Restrict to unauthenticated users (login page)
282
+
283
+ ```vue
284
+ <!-- pages/login.vue -->
285
+ <script setup lang="ts">
286
+ definePageMeta({
287
+ authts: { unauthenticatedOnly: true },
288
+ })
289
+
290
+ const { login } = useAuthContext()
291
+ </script>
292
+
293
+ <template>
294
+ <button @click="login('/dashboard')">Sign in</button>
295
+ </template>
296
+ ```
297
+
298
+ Authenticated users navigating to this page receive a 404 from middleware.
299
+
300
+ > **Warning:** Do not set both `required: true` and `unauthenticatedOnly: true` on the same page. This combination causes a middleware error.
301
+
302
+ > **Note:** `roles` is a reserved field in `AuthtsMiddlewareMeta` and is not currently enforced.
303
+
304
+ ## Nuxt Hooks
305
+
306
+ ```ts
307
+ // plugins/my-app.ts
308
+ export default defineNuxtPlugin((_nuxtApp) => {
309
+ // Fires after every successful authorization code exchange
310
+ _nuxtApp.hook('user:loggedIn', async () => {
311
+ console.log('User logged in — session is now populated')
312
+ })
313
+
314
+ // Fires after successful logout
315
+ _nuxtApp.hook('user:loggedOut', async () => {
316
+ console.log('User logged out — session has been cleared')
317
+ })
318
+
319
+ // Trigger a session re-fetch manually (e.g. after a server-side state change)
320
+ // _nuxtApp.callHook('session:fetch', 'my-initiator')
321
+
322
+ // Fires after every session fetch — receives the latest session data
323
+ _nuxtApp.hook('session:changed', async (session) => {
324
+ console.log('Session updated:', session.isAuthenticated)
325
+ })
326
+ })
327
+ ```
328
+
329
+ ## TypeScript
330
+
331
+ ```ts
332
+ import type {
333
+ AuthorizedUser,
334
+ AuthtsMiddlewareMeta,
335
+ NuxtSessionContext,
336
+ SessionContext,
337
+ } from '@devcoffee/nuxt-core'
338
+ ```
339
+
340
+ ### Custom session data
341
+
342
+ `SessionContext` and `NuxtSessionContext` accept a generic `TData` parameter for type-safe custom session data.
343
+
344
+ ```ts
345
+ import type { SessionContext } from '@devcoffee/nuxt-core'
346
+
347
+ type MyAppData = {
348
+ preferences: { theme: string }
349
+ roles: string[]
350
+ }
351
+
352
+ // In a server handler or Nitro plugin:
353
+ const session: SessionContext<MyAppData> = await getSession(sessionId, opts)
354
+
355
+ // Fully typed — no casting required
356
+ console.log(session.data.preferences.theme) // string
357
+ console.log(session.data.roles) // string[]
358
+ ```
359
+
360
+ ## Security
361
+
362
+ ### Production checklist
363
+
364
+ **1. Set `sessions.secret` — most important step.**
365
+
366
+ Without it, session ID cookies are unsigned and tokenSet storage is unencrypted. An attacker who gains read access to Nitro storage can extract access tokens.
367
+
368
+ Set a 32+ character random string:
369
+
370
+ ```bash
371
+ SESSION_SECRET=your-32-char-minimum-random-secret
372
+ ```
373
+
374
+ When `sessions.secret` is configured:
375
+ - **HMAC-SHA256** signs the session ID cookie — tampering is detected with constant-time comparison
376
+ - **AES-256-GCM** encrypts the tokenSet in Nitro storage — keys are derived via HKDF-SHA256 with domain separation
377
+
378
+ **2. PKCE is enabled by default** — `usePkce: true` with `S256`. Do not disable it.
379
+
380
+ **3. Open redirect protection** — `isSameOrigin()` validates all post-auth redirects. External URLs are rejected with HTTP 400.
381
+
382
+ **4. Session ID entropy** — `crypto.randomBytes(32)`, 256-bit. UUID v4 is not used.
383
+
384
+ **5. HttpOnly cookies** — `httpOnly: true` and `secure: true` in production by default.
385
+
386
+ ## Changelog
387
+
388
+ See [CHANGELOG.md](/CHANGELOG.md) for the full release history.
389
+
390
+ ## Contributing
391
+
392
+ See [GUIDELINE.md](/GUIDELINE.md) for contribution guidelines, local development setup, and release instructions.
393
+
394
+ <!-- Badges -->
395
+ [npm-version-src]: https://img.shields.io/npm/v/@devcoffee/nuxt-core/latest.svg?style=flat&colorA=020420&colorB=00DC82
396
+ [npm-version-href]: https://npmjs.com/package/@devcoffee/nuxt-core
397
+
398
+ [npm-downloads-src]: https://img.shields.io/npm/dm/@devcoffee/nuxt-core.svg?style=flat&colorA=020420&colorB=00DC82
399
+ [npm-downloads-href]: https://npm.chart.dev/@devcoffee/nuxt-core
400
+
401
+ [license-src]: https://img.shields.io/npm/l/@devcoffee/nuxt-core.svg?style=flat&colorA=020420&colorB=00DC82
402
+ [license-href]: https://npmjs.com/package/@devcoffee/nuxt-core
403
+
404
+ [nuxt-src]: https://img.shields.io/badge/Nuxt-020420?logo=nuxt.js
405
+ [nuxt-href]: https://nuxt.com