nuxt-safe-runtime-config 0.0.10 → 0.0.13

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/README.md CHANGED
@@ -18,7 +18,7 @@ Validate Nuxt runtime config at build time using <b>Zod</b>, <b>Valibot</b>, <b>
18
18
  <img src="https://img.shields.io/github/license/onmax/nuxt-safe-runtime-config.svg" alt="License" />
19
19
  </a>
20
20
  <a href="https://nuxt.com">
21
- <img src="https://img.shields.io/badge/Nuxt-3.0-00DC82.svg" alt="Nuxt" />
21
+ <img src="https://img.shields.io/badge/Nuxt-3.0+-00DC82.svg" alt="Nuxt" />
22
22
  </a>
23
23
 
24
24
  <p align="center">
@@ -30,15 +30,15 @@ Validate Nuxt runtime config at build time using <b>Zod</b>, <b>Valibot</b>, <b>
30
30
 
31
31
  ## Features
32
32
 
33
- - 🔒 &nbsp;Validate runtime config at build time with **Zod**, **Valibot**, **ArkType**, and more
34
- - 🚀 &nbsp;Works with any [Standard Schema](https://standardschema.dev/) compatible library
35
- - 🛠 &nbsp;Only runs during dev/build/generate phases
36
- - &nbsp;Zero runtime overhead - validation happens at build time only
37
- - 📝 &nbsp;Does not modify your runtime config or types - only validates them
33
+ - 🔒 **Build-time validation** with Zod, Valibot, ArkType, or any [Standard Schema](https://standardschema.dev/) library
34
+ - 🚀 **Runtime validation** (opt-in) validates config when the server starts
35
+ - **Auto-generated types** `useSafeRuntimeConfig()` is fully typed without manual generics
36
+ - 🛠 **ESLint plugin** warns when using `useRuntimeConfig()` instead of the type-safe composable
37
+ - **Zero runtime overhead** by default validation happens at build time only
38
38
 
39
39
  ## Quick Setup
40
40
 
41
- Install the module to your Nuxt application with one command:
41
+ Install the module:
42
42
 
43
43
  ```bash
44
44
  npx nuxi module add nuxt-safe-runtime-config
@@ -46,15 +46,9 @@ npx nuxi module add nuxt-safe-runtime-config
46
46
 
47
47
  ## Usage
48
48
 
49
- 1. Add the module to your `nuxt.config.ts`:
49
+ ### 1. Define your schema
50
50
 
51
- ```typescript
52
- export default defineNuxtConfig({
53
- modules: ['nuxt-safe-runtime-config']
54
- })
55
- ```
56
-
57
- 2. Define your runtime config schema using **Valibot**, **Zod**, **ArkType**, or any other Standard Schema compatible library:
51
+ Use Zod, Valibot, ArkType, or any Standard Schema compatible library:
58
52
 
59
53
  <details>
60
54
  <summary>With Valibot</summary>
@@ -113,13 +107,12 @@ const runtimeConfigSchema = type({
113
107
 
114
108
  </details>
115
109
 
116
- 3. Configure your Nuxt app:
110
+ ### 2. Configure in nuxt.config.ts
117
111
 
118
112
  ```typescript
119
113
  export default defineNuxtConfig({
120
114
  modules: ['nuxt-safe-runtime-config'],
121
115
 
122
- // Your regular runtime config
123
116
  runtimeConfig: {
124
117
  databaseUrl: process.env.DATABASE_URL || 'postgresql://localhost:5432/mydb',
125
118
  secretKey: process.env.SECRET_KEY || 'default-secret-key',
@@ -130,45 +123,124 @@ export default defineNuxtConfig({
130
123
  },
131
124
  },
132
125
 
133
- // Add your schema for validation
134
126
  safeRuntimeConfig: {
135
127
  $schema: runtimeConfigSchema,
136
128
  },
137
129
  })
138
130
  ```
139
131
 
140
- The module validates your runtime config **after environment variables are merged** during:
132
+ ### 3. Use the type-safe composable
133
+
134
+ Access your validated config with full type safety — types are auto-generated from your schema:
135
+
136
+ ```vue
137
+ <script setup lang="ts">
138
+ const config = useSafeRuntimeConfig()
139
+ // config.public.apiBase is typed as string
140
+ // config.secretKey is typed as string
141
+ </script>
142
+ ```
143
+
144
+ ## Configuration Options
145
+
146
+ | Option | Type | Default | Description |
147
+ | ------------------- | ------------------------------- | ----------------- | ------------------------------------------ |
148
+ | `$schema` | `StandardSchemaV1` | — | Your validation schema (required) |
149
+ | `validateAtBuild` | `boolean` | `true` | Validate during dev/build |
150
+ | `validateAtRuntime` | `boolean` | `false` | Validate when server starts |
151
+ | `onBuildError` | `'throw' \| 'warn' \| 'ignore'` | `'throw'` | How to handle build validation errors |
152
+ | `onRuntimeError` | `'throw' \| 'warn' \| 'ignore'` | `'throw'` | How to handle runtime validation errors |
153
+ | `logSuccess` | `boolean` | `true` | Log successful validation |
154
+ | `logFallback` | `boolean` | `true` | Log when using JSON Schema fallback |
155
+ | `jsonSchemaTarget` | `string` | `'draft-2020-12'` | JSON Schema version for runtime validation |
156
+
157
+ ## Runtime Validation
158
+
159
+ By default, validation only runs at build time. Enable runtime validation to catch environment variable issues when the server starts:
160
+
161
+ ```ts
162
+ export default defineNuxtConfig({
163
+ safeRuntimeConfig: {
164
+ $schema: runtimeConfigSchema,
165
+ validateAtRuntime: true,
166
+ },
167
+ })
168
+ ```
169
+
170
+ Runtime validation uses [@cfworker/json-schema](https://github.com/cfworker/cfworker/tree/main/packages/json-schema) to validate the config after environment variables are merged. This lightweight validator (~8KB) works on all runtimes including edge (Cloudflare Workers, Vercel Edge, Netlify Edge). It catches issues like:
171
+
172
+ - Environment variables with wrong types (e.g., `NUXT_PORT=abc` when expecting a number)
173
+ - Missing required environment variables in production
174
+ - Invalid values that pass build-time checks but fail at runtime
175
+
176
+ ## ESLint Integration
177
+
178
+ The module includes an ESLint plugin that warns when using `useRuntimeConfig()` instead of `useSafeRuntimeConfig()`.
179
+
180
+ ### With @nuxt/eslint (Automatic)
141
181
 
142
- - `nuxi dev` (development mode)
143
- - `nuxi build`
144
- - `nuxi generate`
182
+ If you use [@nuxt/eslint](https://eslint.nuxt.com), the rule is auto-registered. No configuration needed.
145
183
 
146
- This means validation happens at **runtime initialization**, after all `NUXT_*` environment variables from `.env` files have been merged into your runtime config. If validation fails, the build process will stop with detailed error messages.
184
+ ### Manual Setup
147
185
 
148
- ## Important Notes
186
+ Add to your `eslint.config.mjs`:
149
187
 
150
- **This module only validates your runtime config - it does not modify it.** Your native `runtimeConfig` remains unchanged, and no TypeScript types are modified. The module simply ensures that your runtime configuration matches your schema at build time, helping you catch configuration errors early in the development process.
188
+ ```javascript
189
+ import safeRuntimeConfig from 'nuxt-safe-runtime-config/eslint'
190
+
191
+ export default [
192
+ safeRuntimeConfig.configs.recommended,
193
+ // ... your other configs
194
+ ]
195
+ ```
196
+
197
+ Or configure manually:
198
+
199
+ ```javascript
200
+ import safeRuntimeConfig from 'nuxt-safe-runtime-config/eslint'
201
+
202
+ export default [
203
+ {
204
+ plugins: { 'safe-runtime-config': safeRuntimeConfig },
205
+ rules: { 'safe-runtime-config/prefer-safe-runtime-config': 'warn' },
206
+ },
207
+ ]
208
+ ```
209
+
210
+ The rule includes auto-fix support — run `eslint --fix` to automatically replace `useRuntimeConfig()` calls.
211
+
212
+ ## Type Safety
213
+
214
+ Types are auto-generated at build time from your schema's JSON Schema representation. The `useSafeRuntimeConfig()` composable returns a fully typed object — no manual generics needed:
215
+
216
+ ```ts
217
+ const config = useSafeRuntimeConfig()
218
+ // config is fully typed based on your schema
219
+ ```
220
+
221
+ Generated types are stored in `.nuxt/types/safe-runtime-config.d.ts` and automatically included in your project.
151
222
 
152
223
  ## Error Messages
153
224
 
154
- When validation fails, you'll see detailed error messages like:
225
+ When validation fails, you see detailed error messages:
155
226
 
156
227
  ```
157
- Safe Runtime Config: Validation failed!
158
- 1. databaseUrl: This field is required
159
- 2. public.apiBase: Expected string, received undefined
160
- 3. port: Expected number, received string
228
+ [safe-runtime-config] Validation failed!
229
+ 1. databaseUrl: Invalid type: Expected string but received undefined
230
+ 2. public.apiBase: Invalid type: Expected string but received undefined
231
+ 3. port: Invalid type: Expected number but received string
161
232
  ```
162
233
 
163
- The module will stop the build process until all validation errors are resolved.
234
+ The module stops the build process until all validation errors are resolved.
164
235
 
165
236
  ## Why This Module?
166
237
 
167
- I wanted to use **Valibot** for runtime config validation, but Nuxt doesn't currently support Standard Schema. While Nuxt has its own schema validation system, it's primarily designed for module authors and broader Nuxt configuration.
168
-
169
- This module focuses specifically on **runtime config validation** using the Standard Schema specification, allowing you to use your preferred validation library (Valibot, Zod, ArkType, etc.).
238
+ Nuxt's built-in schema validation is designed for module authors and broader configuration. This module focuses specifically on **runtime config validation** using Standard Schema, allowing you to:
170
239
 
171
- The goal is to eventually make Standard Schema a first-class citizen in Nuxt. If this module gains enough adoption, I plan to create a PR to add `standardSchema` support to Nuxt core.
240
+ - Use your preferred validation library (Valibot, Zod, ArkType)
241
+ - Catch configuration errors at build time
242
+ - Optionally validate at runtime for environment variable issues
243
+ - Get full type safety in your components
172
244
 
173
245
  ## Contribution
174
246
 
@@ -0,0 +1,2 @@
1
+ export * from "/home/runner/work/nuxt-safe-runtime-config/nuxt-safe-runtime-config/src/eslint/index.js";
2
+ export { default } from "/home/runner/work/nuxt-safe-runtime-config/nuxt-safe-runtime-config/src/eslint/index.js";
@@ -0,0 +1,30 @@
1
+ import * as _typescript_eslint_utils_ts_eslint from '@typescript-eslint/utils/ts-eslint';
2
+
3
+ declare const plugin: {
4
+ meta: {
5
+ name: string;
6
+ };
7
+ rules: {
8
+ 'prefer-safe-runtime-config': _typescript_eslint_utils_ts_eslint.RuleModule<"preferSafe", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
9
+ };
10
+ };
11
+
12
+ declare const configs: {
13
+ recommended: {
14
+ plugins: {
15
+ 'safe-runtime-config': {
16
+ meta: {
17
+ name: string;
18
+ };
19
+ rules: {
20
+ 'prefer-safe-runtime-config': _typescript_eslint_utils_ts_eslint.RuleModule<"preferSafe", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
21
+ };
22
+ };
23
+ };
24
+ rules: {
25
+ 'safe-runtime-config/prefer-safe-runtime-config': string;
26
+ };
27
+ };
28
+ };
29
+
30
+ export { configs, plugin as default };
@@ -0,0 +1,30 @@
1
+ import * as _typescript_eslint_utils_ts_eslint from '@typescript-eslint/utils/ts-eslint';
2
+
3
+ declare const plugin: {
4
+ meta: {
5
+ name: string;
6
+ };
7
+ rules: {
8
+ 'prefer-safe-runtime-config': _typescript_eslint_utils_ts_eslint.RuleModule<"preferSafe", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
9
+ };
10
+ };
11
+
12
+ declare const configs: {
13
+ recommended: {
14
+ plugins: {
15
+ 'safe-runtime-config': {
16
+ meta: {
17
+ name: string;
18
+ };
19
+ rules: {
20
+ 'prefer-safe-runtime-config': _typescript_eslint_utils_ts_eslint.RuleModule<"preferSafe", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
21
+ };
22
+ };
23
+ };
24
+ rules: {
25
+ 'safe-runtime-config/prefer-safe-runtime-config': string;
26
+ };
27
+ };
28
+ };
29
+
30
+ export { configs, plugin as default };
@@ -0,0 +1,38 @@
1
+ import { ESLintUtils } from '@typescript-eslint/utils';
2
+
3
+ const createRule = ESLintUtils.RuleCreator((name) => `https://github.com/onmax/nuxt-safe-runtime-config#${name}`);
4
+ const preferSafeRuntimeConfig = createRule({
5
+ name: "prefer-safe-runtime-config",
6
+ meta: {
7
+ type: "suggestion",
8
+ docs: { description: "Prefer useSafeRuntimeConfig() over useRuntimeConfig()" },
9
+ schema: [],
10
+ messages: { preferSafe: "Use useSafeRuntimeConfig() for type-safe runtime config" },
11
+ fixable: "code"
12
+ },
13
+ defaultOptions: [],
14
+ create: (context) => ({
15
+ CallExpression(node) {
16
+ if (node.callee.type === "Identifier" && node.callee.name === "useRuntimeConfig") {
17
+ context.report({
18
+ node,
19
+ messageId: "preferSafe",
20
+ fix: (fixer) => fixer.replaceText(node.callee, "useSafeRuntimeConfig")
21
+ });
22
+ }
23
+ }
24
+ })
25
+ });
26
+
27
+ const plugin = {
28
+ meta: { name: "nuxt-safe-runtime-config" },
29
+ rules: { "prefer-safe-runtime-config": preferSafeRuntimeConfig }
30
+ };
31
+ const configs = {
32
+ recommended: {
33
+ plugins: { "safe-runtime-config": plugin },
34
+ rules: { "safe-runtime-config/prefer-safe-runtime-config": "warn" }
35
+ }
36
+ };
37
+
38
+ export { configs, plugin as default };
@@ -0,0 +1,2 @@
1
+ export * from "/home/runner/work/nuxt-safe-runtime-config/nuxt-safe-runtime-config/src/module.js";
2
+ export { default } from "/home/runner/work/nuxt-safe-runtime-config/nuxt-safe-runtime-config/src/module.js";
package/dist/module.d.mts CHANGED
@@ -1,10 +1,34 @@
1
1
  import * as _nuxt_schema from '@nuxt/schema';
2
- import { StandardSchemaV1 } from '@standard-schema/spec';
2
+ import { StandardSchemaV1, StandardJSONSchemaV1 } from '@standard-schema/spec';
3
3
 
4
+ type ErrorBehavior = 'throw' | 'warn' | 'ignore';
4
5
  interface ModuleOptions {
5
6
  $schema: StandardSchemaV1;
7
+ validateAtBuild: boolean;
8
+ validateAtRuntime: boolean;
9
+ jsonSchemaTarget?: StandardJSONSchemaV1.Target;
10
+ onBuildError?: ErrorBehavior;
11
+ onRuntimeError?: ErrorBehavior;
12
+ logSuccess?: boolean;
13
+ logFallback?: boolean;
6
14
  }
7
15
  declare const _default: _nuxt_schema.NuxtModule<ModuleOptions, ModuleOptions, false>;
8
16
 
17
+ declare module '@nuxt/schema' {
18
+ interface NuxtHooks {
19
+ 'eslint:config:addons': (addons: Array<{
20
+ name: string;
21
+ getConfigs: () => {
22
+ imports?: Array<{
23
+ from: string;
24
+ name: string;
25
+ as: string;
26
+ }>;
27
+ configs?: string[];
28
+ };
29
+ }>) => void;
30
+ }
31
+ }
32
+
9
33
  export { _default as default };
10
- export type { ModuleOptions };
34
+ export type { ErrorBehavior, ModuleOptions };
@@ -0,0 +1,34 @@
1
+ import * as _nuxt_schema from '@nuxt/schema';
2
+ import { StandardSchemaV1, StandardJSONSchemaV1 } from '@standard-schema/spec';
3
+
4
+ type ErrorBehavior = 'throw' | 'warn' | 'ignore';
5
+ interface ModuleOptions {
6
+ $schema: StandardSchemaV1;
7
+ validateAtBuild: boolean;
8
+ validateAtRuntime: boolean;
9
+ jsonSchemaTarget?: StandardJSONSchemaV1.Target;
10
+ onBuildError?: ErrorBehavior;
11
+ onRuntimeError?: ErrorBehavior;
12
+ logSuccess?: boolean;
13
+ logFallback?: boolean;
14
+ }
15
+ declare const _default: _nuxt_schema.NuxtModule<ModuleOptions, ModuleOptions, false>;
16
+
17
+ declare module '@nuxt/schema' {
18
+ interface NuxtHooks {
19
+ 'eslint:config:addons': (addons: Array<{
20
+ name: string;
21
+ getConfigs: () => {
22
+ imports?: Array<{
23
+ from: string;
24
+ name: string;
25
+ as: string;
26
+ }>;
27
+ configs?: string[];
28
+ };
29
+ }>) => void;
30
+ }
31
+ }
32
+
33
+ export { _default as default };
34
+ export type { ErrorBehavior, ModuleOptions };
package/dist/module.json CHANGED
@@ -1,7 +1,10 @@
1
1
  {
2
2
  "name": "nuxt-safe-runtime-config",
3
3
  "configKey": "safeRuntimeConfig",
4
- "version": "0.0.10",
4
+ "compatibility": {
5
+ "nuxt": ">=3.0.0"
6
+ },
7
+ "version": "0.0.13",
5
8
  "builder": {
6
9
  "@nuxt/module-builder": "1.0.1",
7
10
  "unbuild": "3.5.0"
package/dist/module.mjs CHANGED
@@ -1,41 +1,255 @@
1
- import { defineNuxtModule } from '@nuxt/kit';
1
+ import { useLogger, defineNuxtModule, createResolver, addTypeTemplate, addTemplate, addImportsDir } from '@nuxt/kit';
2
+ import { toJsonSchema } from '@standard-community/standard-json';
2
3
 
4
+ function jsonSchemaToTs(schema, indent = 0) {
5
+ const spaces = " ".repeat(indent);
6
+ if ("$ref" in schema) {
7
+ return "unknown";
8
+ }
9
+ if ("anyOf" in schema && Array.isArray(schema.anyOf)) {
10
+ const types = schema.anyOf.map((s) => jsonSchemaToTs(s, indent));
11
+ return types.join(" | ");
12
+ }
13
+ if ("oneOf" in schema && Array.isArray(schema.oneOf)) {
14
+ const types = schema.oneOf.map((s) => jsonSchemaToTs(s, indent));
15
+ return types.join(" | ");
16
+ }
17
+ if ("allOf" in schema && Array.isArray(schema.allOf)) {
18
+ const types = schema.allOf.map((s) => jsonSchemaToTs(s, indent));
19
+ return types.join(" & ");
20
+ }
21
+ if ("const" in schema) {
22
+ return JSON.stringify(schema.const);
23
+ }
24
+ if ("enum" in schema && Array.isArray(schema.enum)) {
25
+ return schema.enum.map((v) => JSON.stringify(v)).join(" | ");
26
+ }
27
+ const type = schema.type;
28
+ if (type === "string") {
29
+ return "string";
30
+ }
31
+ if (type === "number" || type === "integer") {
32
+ return "number";
33
+ }
34
+ if (type === "boolean") {
35
+ return "boolean";
36
+ }
37
+ if (type === "null") {
38
+ return "null";
39
+ }
40
+ if (type === "array") {
41
+ const items = schema.items;
42
+ if (items) {
43
+ return `${jsonSchemaToTs(items, indent)}[]`;
44
+ }
45
+ return "unknown[]";
46
+ }
47
+ if (type === "object" || "properties" in schema) {
48
+ const properties = schema.properties;
49
+ const required = schema.required || [];
50
+ const additionalProperties = schema.additionalProperties;
51
+ if (!properties || Object.keys(properties).length === 0) {
52
+ if (additionalProperties === false) {
53
+ return "Record<string, never>";
54
+ }
55
+ if (typeof additionalProperties === "object") {
56
+ return `Record<string, ${jsonSchemaToTs(additionalProperties, indent)}>`;
57
+ }
58
+ return "Record<string, unknown>";
59
+ }
60
+ const lines = ["{"];
61
+ const propIndent = " ".repeat(indent + 1);
62
+ for (const [key, propSchema] of Object.entries(properties)) {
63
+ const isRequired = required.includes(key);
64
+ const optional = isRequired ? "" : "?";
65
+ const safeKey = /^[\w$]+$/.test(key) && !/^\d/.test(key) ? key : JSON.stringify(key);
66
+ const propType = jsonSchemaToTs(propSchema, indent + 1);
67
+ lines.push(`${propIndent}${safeKey}${optional}: ${propType}`);
68
+ }
69
+ lines.push(`${spaces}}`);
70
+ return lines.join("\n");
71
+ }
72
+ if (Array.isArray(type)) {
73
+ const types = type.map((t) => {
74
+ switch (t) {
75
+ case "null":
76
+ return "null";
77
+ case "string":
78
+ return "string";
79
+ case "number":
80
+ case "integer":
81
+ return "number";
82
+ case "boolean":
83
+ return "boolean";
84
+ case "array":
85
+ return "unknown[]";
86
+ case "object":
87
+ return "Record<string, unknown>";
88
+ default:
89
+ return "unknown";
90
+ }
91
+ });
92
+ return types.join(" | ");
93
+ }
94
+ return "unknown";
95
+ }
96
+ function generateTypeDeclaration(schema) {
97
+ const tsType = jsonSchemaToTs(schema);
98
+ return `// Auto-generated by nuxt-safe-runtime-config
99
+ // Do not edit manually
100
+
101
+ export type SafeRuntimeConfig = ${tsType}
102
+
103
+ declare module '#imports' {
104
+ export function useSafeRuntimeConfig(): SafeRuntimeConfig
105
+ }
106
+
107
+ declare module '#app' {
108
+ export function useSafeRuntimeConfig(): SafeRuntimeConfig
109
+ }
110
+
111
+ export {}
112
+ `;
113
+ }
114
+
115
+ const logger = useLogger("safe-runtime-config");
3
116
  const module = defineNuxtModule({
4
117
  meta: {
5
118
  name: "nuxt-safe-runtime-config",
6
- configKey: "safeRuntimeConfig"
119
+ configKey: "safeRuntimeConfig",
120
+ compatibility: { nuxt: ">=3.0.0" }
7
121
  },
8
122
  defaults: {
9
- $schema: void 0
123
+ $schema: void 0,
124
+ validateAtBuild: true,
125
+ validateAtRuntime: false,
126
+ jsonSchemaTarget: "draft-2020-12",
127
+ onBuildError: "throw",
128
+ onRuntimeError: "throw",
129
+ logSuccess: true,
130
+ logFallback: true
10
131
  },
11
- setup(options, nuxt) {
132
+ async setup(options, nuxt) {
12
133
  if (!options.$schema)
13
134
  return;
14
135
  const schema = options.$schema;
15
- nuxt.hook("ready", async () => {
16
- if (nuxt.options._prepare)
17
- return;
18
- await validateRuntimeConfig(nuxt.options.nitro.runtimeConfig, schema);
136
+ const resolver = createResolver(import.meta.url);
137
+ const jsonSchema = await getJSONSchema(schema, options.jsonSchemaTarget, options.logFallback);
138
+ addTypeTemplate({
139
+ filename: "types/safe-runtime-config.d.ts",
140
+ getContents: () => generateTypeDeclaration(jsonSchema)
19
141
  });
20
- nuxt.hook("build:done", async () => {
21
- await validateRuntimeConfig(nuxt.options.nitro.runtimeConfig, schema);
142
+ if (options.validateAtBuild) {
143
+ const validateOpts = { onError: options.onBuildError, logSuccess: options.logSuccess };
144
+ nuxt.hook("ready", async () => {
145
+ if (nuxt.options._prepare)
146
+ return;
147
+ await validateRuntimeConfig(nuxt.options.nitro.runtimeConfig, schema, validateOpts);
148
+ });
149
+ nuxt.hook("build:done", async () => {
150
+ await validateRuntimeConfig(nuxt.options.nitro.runtimeConfig, schema, validateOpts);
151
+ });
152
+ }
153
+ if (options.validateAtRuntime) {
154
+ addTemplate({
155
+ filename: "safe-runtime-config/schema.json",
156
+ write: true,
157
+ getContents: () => JSON.stringify(jsonSchema)
158
+ });
159
+ const schemaUri = jsonSchema.$schema;
160
+ const draft = schemaUri?.includes("2020-12") ? "2020-12" : schemaUri?.includes("2019-09") ? "2019-09" : schemaUri?.includes("draft-07") ? "7" : "2020-12";
161
+ const pluginPath = addTemplate({
162
+ filename: "safe-runtime-config/validate-plugin.ts",
163
+ write: true,
164
+ getContents: () => `
165
+ import { Validator } from '@cfworker/json-schema'
166
+ import { useRuntimeConfig } from '#imports'
167
+ import { consola } from 'consola'
168
+
169
+ const logger = consola.withTag('safe-runtime-config')
170
+ const schema = ${JSON.stringify(jsonSchema)}
171
+ const options = ${JSON.stringify({ onError: options.onRuntimeError, logSuccess: options.logSuccess })}
172
+
173
+ export default defineNitroPlugin(() => {
174
+ const config = useRuntimeConfig()
175
+ const validator = new Validator(schema, '${draft}')
176
+ const result = validator.validate(config)
177
+
178
+ if (!result.valid) {
179
+ const errors = result.errors.map(e => \`\${e.instanceLocation}: \${e.error}\`).join(', ')
180
+ const msg = \`Runtime validation failed: \${errors}\`
181
+
182
+ if (options.onError === 'throw') {
183
+ logger.error(msg)
184
+ throw new Error(msg)
185
+ } else if (options.onError === 'warn') {
186
+ logger.warn(msg)
187
+ }
188
+ } else if (options.logSuccess) {
189
+ logger.success('Runtime config validated (server start)')
190
+ }
191
+ })
192
+ `
193
+ });
194
+ nuxt.hook("nitro:config", (nitroConfig) => {
195
+ nitroConfig.plugins = nitroConfig.plugins || [];
196
+ nitroConfig.plugins.push(pluginPath.dst);
197
+ });
198
+ }
199
+ addImportsDir(resolver.resolve("./runtime/composables"));
200
+ nuxt.hook("eslint:config:addons", (addons) => {
201
+ addons.push({
202
+ name: "nuxt-safe-runtime-config",
203
+ getConfigs: () => ({
204
+ imports: [{ from: "nuxt-safe-runtime-config/eslint", name: "default", as: "safeRuntimeConfig" }],
205
+ configs: [`safeRuntimeConfig.configs.recommended`]
206
+ })
207
+ });
22
208
  });
23
209
  }
24
210
  });
25
- async function validateRuntimeConfig(config, schema) {
211
+ function hasNativeJSONSchema(schema) {
212
+ return typeof schema?.["~standard"]?.jsonSchema?.output === "function";
213
+ }
214
+ async function getJSONSchema(schema, target = "draft-2020-12", logFallback = true) {
215
+ if (hasNativeJSONSchema(schema)) {
216
+ try {
217
+ return schema["~standard"].jsonSchema.output({ target });
218
+ } catch {
219
+ if (logFallback)
220
+ logger.warn(`Native JSON Schema failed for target "${target}", using fallback`);
221
+ }
222
+ }
223
+ if (logFallback)
224
+ logger.warn("Schema does not support native JSON Schema, using @standard-community/standard-json fallback");
225
+ return await toJsonSchema(schema);
226
+ }
227
+ async function validateRuntimeConfig(config, schema, opts) {
26
228
  if (!isStandardSchema(schema)) {
27
- console.error("Safe Runtime Config: Schema is not Standard Schema compatible");
28
- throw new Error("Invalid schema format");
229
+ const msg = "Schema is not Standard Schema compatible";
230
+ if (opts.onError === "throw") {
231
+ logger.error(msg);
232
+ throw new Error("Invalid schema format");
233
+ } else if (opts.onError === "warn") {
234
+ logger.warn(msg);
235
+ }
236
+ return;
29
237
  }
30
238
  const result = await schema["~standard"].validate(config);
31
239
  if ("issues" in result && result.issues && result.issues.length > 0) {
32
- console.error("Safe Runtime Config: Validation failed!");
33
- result.issues.forEach((issue, index) => {
34
- console.error(` ${index + 1}. ${formatIssue(issue)}`);
35
- });
36
- throw new Error("Runtime config validation failed");
240
+ const errorLines = result.issues.map((issue, index) => ` ${index + 1}. ${formatIssue(issue)}`);
241
+ if (opts.onError === "throw") {
242
+ logger.error(`Validation failed!
243
+ ${errorLines.join("\n")}`);
244
+ throw new Error("Runtime config validation failed");
245
+ } else if (opts.onError === "warn") {
246
+ logger.warn(`Validation failed!
247
+ ${errorLines.join("\n")}`);
248
+ }
249
+ return;
37
250
  }
38
- console.log("\u2713 Validated Runtime Config");
251
+ if (opts.logSuccess)
252
+ logger.success("Validated Runtime Config");
39
253
  }
40
254
  function isStandardSchema(schema) {
41
255
  return schema && typeof schema === "object" && "~standard" in schema && typeof schema["~standard"] === "object" && typeof schema["~standard"].validate === "function";
package/dist/types.d.mts CHANGED
@@ -1,3 +1,13 @@
1
- export { default } from './module.mjs'
1
+ import type { ModuleHooks, ModuleRuntimeHooks, ModuleRuntimeConfig, ModulePublicRuntimeConfig } from './module.mjs'
2
2
 
3
- export { type ModuleOptions } from './module.mjs'
3
+ declare module '#app' {
4
+ interface RuntimeNuxtHooks extends ModuleRuntimeHooks {}
5
+ }
6
+
7
+ declare module '@nuxt/schema' {
8
+ interface NuxtHooks extends ModuleHooks {}
9
+ interface RuntimeConfig extends ModuleRuntimeConfig {}
10
+ interface PublicRuntimeConfig extends ModulePublicRuntimeConfig {}
11
+ }
12
+
13
+ export * from "./module.mjs"
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "nuxt-safe-runtime-config",
3
3
  "type": "module",
4
- "version": "0.0.10",
4
+ "version": "0.0.13",
5
5
  "packageManager": "pnpm@9.15.4+sha512.b2dc20e2fc72b3e18848459b37359a32064663e5627a51e4c74b2c29dd8e8e0491483c3abb40789cfd578bf362fb6ba8261b05f0387d76792ed6e23ea3b1b6a0",
6
6
  "description": "Validate Nuxt runtime config with Standard Schema at build time",
7
7
  "author": {
@@ -21,28 +21,25 @@
21
21
  "build-time"
22
22
  ],
23
23
  "exports": {
24
- ".": {
25
- "types": "./dist/types.d.mts",
26
- "import": "./dist/module.mjs"
27
- }
24
+ ".": { "types": "./dist/types.d.mts", "import": "./dist/module.mjs" },
25
+ "./eslint": { "types": "./dist/eslint.d.mts", "import": "./dist/eslint.mjs" }
28
26
  },
29
27
  "main": "./dist/module.mjs",
30
28
  "typesVersions": {
31
29
  "*": {
32
- ".": [
33
- "./dist/types.d.mts"
34
- ]
30
+ ".": ["./dist/types.d.mts"],
31
+ "eslint": ["./dist/eslint.d.mts"]
35
32
  }
36
33
  },
37
34
  "files": [
38
35
  "dist"
39
36
  ],
40
37
  "scripts": {
41
- "prepack": "nuxt-module-build build",
38
+ "prepack": "nuxt-module-build build && unbuild",
42
39
  "dev": "nuxi dev playground",
43
40
  "dev:build": "nuxi build playground",
44
- "dev:prepare": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxi prepare playground && pnpm -C test/fixtures/validation-failure prepare && pnpm -C test/fixtures/validation-success prepare",
45
- "release": "nr lint && nr typecheck && nr test && nr prepack && bumpp && pnpm publish",
41
+ "dev:prepare": "nuxt-module-build build --stub && nuxi prepare playground",
42
+ "release": "bumpp && git push --follow-tags",
46
43
  "typecheck": "nuxt typecheck && pnpm -C playground typecheck",
47
44
  "lint": "eslint .",
48
45
  "lint:fix": "eslint . --fix",
@@ -50,9 +47,18 @@
50
47
  "test:watch": "vitest watch",
51
48
  "test:types": "vue-tsc --noEmit && cd playground && vue-tsc --noEmit"
52
49
  },
50
+ "peerDependencies": {
51
+ "eslint": "^9.0.0"
52
+ },
53
+ "peerDependenciesMeta": {
54
+ "eslint": { "optional": true }
55
+ },
53
56
  "dependencies": {
57
+ "@cfworker/json-schema": "catalog:",
54
58
  "@nuxt/kit": "catalog:",
55
- "@standard-schema/spec": "catalog:"
59
+ "@standard-community/standard-json": "catalog:",
60
+ "@standard-schema/spec": "catalog:",
61
+ "consola": "catalog:"
56
62
  },
57
63
  "devDependencies": {
58
64
  "@antfu/eslint-config": "catalog:",
@@ -64,6 +70,9 @@
64
70
  "@nuxt/schema": "catalog:",
65
71
  "@nuxt/test-utils": "catalog:",
66
72
  "@types/node": "catalog:",
73
+ "@typescript-eslint/parser": "catalog:",
74
+ "@typescript-eslint/rule-tester": "catalog:",
75
+ "@typescript-eslint/utils": "catalog:",
67
76
  "bumpp": "catalog:",
68
77
  "changelogen": "catalog:",
69
78
  "eslint": "catalog:",
@@ -72,6 +81,7 @@
72
81
  "nuxt": "catalog:",
73
82
  "simple-git-hooks": "catalog:",
74
83
  "typescript": "catalog:",
84
+ "unbuild": "catalog:",
75
85
  "valibot": "catalog:",
76
86
  "vitest": "catalog:",
77
87
  "vue-tsc": "catalog:"