├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .gitignore ├── .nuxtrc ├── .prettierrc ├── CHANGELOG.md ├── README.md ├── package.json ├── playground ├── app.config.ts ├── app.vue ├── base │ ├── nuxt.config.ts │ └── nuxt.schema.ts ├── nuxt.config.ts ├── nuxt.schema.ts └── package.json ├── pnpm-lock.yaml ├── src ├── module.ts └── runtime │ └── virtual-imports.ts └── tsconfig.json /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_size = 2 5 | indent_style = space 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "@nuxt/eslint-config" 4 | ], 5 | "rules": { 6 | "@typescript-eslint/no-unused-vars": [ 7 | "off" 8 | ] 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | node_modules 3 | 4 | # Logs 5 | *.log* 6 | 7 | # Temp directories 8 | .temp 9 | .tmp 10 | .cache 11 | 12 | # Yarn 13 | **/.yarn/cache 14 | **/.yarn/*state* 15 | 16 | # Generated dirs 17 | dist 18 | 19 | # Nuxt 20 | .nuxt 21 | .output 22 | .vercel_build_output 23 | .build-* 24 | .env 25 | .netlify 26 | 27 | # Env 28 | .env 29 | 30 | # Testing 31 | reports 32 | coverage 33 | *.lcov 34 | .nyc_output 35 | 36 | # VSCode 37 | .vscode/* 38 | !.vscode/settings.json 39 | !.vscode/tasks.json 40 | !.vscode/launch.json 41 | !.vscode/extensions.json 42 | !.vscode/*.code-snippets 43 | 44 | # Intellij idea 45 | *.iml 46 | .idea 47 | 48 | # OSX 49 | .DS_Store 50 | .AppleDouble 51 | .LSOverride 52 | .AppleDB 53 | .AppleDesktop 54 | Network Trash Folder 55 | Temporary Items 56 | .apdisk 57 | -------------------------------------------------------------------------------- /.nuxtrc: -------------------------------------------------------------------------------- 1 | imports.autoImport=false 2 | typescript.includeWorkspace=true 3 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true 4 | } 5 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | ## v0.4.7 6 | 7 | [compare changes](https://github.com/nuxt-experiments/nuxt-config-schema/compare/v0.4.5...v0.4.7) 8 | 9 | ### 🩹 Fixes 10 | 11 | - Remove `changelogen` from dependencies ([ef9a57a](https://github.com/nuxt-experiments/nuxt-config-schema/commit/ef9a57a)) 12 | 13 | ### 🏡 Chore 14 | 15 | - Update dependencies and lockfile ([9f69946](https://github.com/nuxt-experiments/nuxt-config-schema/commit/9f69946)) 16 | - **release:** V0.4.6 ([45575ed](https://github.com/nuxt-experiments/nuxt-config-schema/commit/45575ed)) 17 | - Update dependencies ([#7](https://github.com/nuxt-experiments/nuxt-config-schema/pull/7)) 18 | 19 | ### ❤️ Contributors 20 | 21 | - Daniel Roe ([@danielroe](http://github.com/danielroe)) 22 | - Pooya Parsa ([@pi0](http://github.com/pi0)) 23 | 24 | ## v0.4.6 25 | 26 | [compare changes](https://github.com/pi0/nuxt-config-schema/compare/v0.4.5...v0.4.6) 27 | 28 | 29 | ### 🩹 Fixes 30 | 31 | - Remove `changelogen` from dependencies ([ef9a57a](https://github.com/pi0/nuxt-config-schema/commit/ef9a57a)) 32 | 33 | ### 🏡 Chore 34 | 35 | - Update dependencies and lockfile ([9f69946](https://github.com/pi0/nuxt-config-schema/commit/9f69946)) 36 | 37 | ### ❤️ Contributors 38 | 39 | - Pooya Parsa ([@pi0](http://github.com/pi0)) 40 | 41 | ## v0.4.5 42 | 43 | 44 | ### 🩹 Fixes 45 | 46 | - Avoid circular type definition (#6) 47 | 48 | ### ❤️ Contributors 49 | 50 | - Daniel Roe 51 | 52 | ## v0.4.4 53 | 54 | 55 | ### 🏡 Chore 56 | 57 | - Add `@deprecated` for `defineNuxtConfigSchema` (88f6a7d) 58 | 59 | ### ❤️ Contributors 60 | 61 | - Pooya Parsa 62 | 63 | ## v0.4.3 64 | 65 | 66 | ### 🚀 Enhancements 67 | 68 | - Detect and auto disable for 3.1.0 (0a9f75b) 69 | 70 | ### 🩹 Fixes 71 | 72 | - Pin conflicting types (dd7ed39) 73 | 74 | ### ❤️ Contributors 75 | 76 | - Pooya Parsa 77 | 78 | ## v0.4.2 79 | 80 | 81 | ### 🩹 Fixes 82 | 83 | - Update untyped (3d91612) 84 | 85 | ### ❤️ Contributors 86 | 87 | - Pooya Parsa 88 | 89 | ## v0.4.1 90 | 91 | 92 | ### 🚀 Enhancements 93 | 94 | - `schema:extend` hook (6763c7b) 95 | 96 | ### 🩹 Fixes 97 | 98 | - Extend schema defenitions before being resolved (6331cf5) 99 | 100 | ### 📖 Documentation 101 | 102 | - Add proposal (117c2ff) 103 | 104 | ### 🏡 Chore 105 | 106 | - Use changelogen (5396943) 107 | - Update script (97ade77) 108 | 109 | ### ❤️ Contributors 110 | 111 | - Pooya Parsa 112 | - Sébastien Chopin 113 | 114 | ## [0.4.0](https://github.com/pi0/nuxt-config-schema/compare/v0.3.8...v0.4.0) (2023-01-03) 115 | 116 | 117 | ### ⚠ BREAKING CHANGES 118 | 119 | * avoid merging schema defaults to nuxt config 120 | 121 | * avoid merging schema defaults to nuxt config ([fa095d1](https://github.com/pi0/nuxt-config-schema/commit/fa095d14f034b668397bf62156fe5a2fe7d29c00)) 122 | 123 | ### [0.3.8](https://github.com/pi0/nuxt-config-schema/compare/v0.3.7...v0.3.8) (2023-01-03) 124 | 125 | ### [0.3.7](https://github.com/pi0/nuxt-config-schema/compare/v0.3.6...v0.3.7) (2022-12-21) 126 | 127 | 128 | ### Features 129 | 130 | * `schema:beforeWrite` and `schema:written` hooks ([a152530](https://github.com/pi0/nuxt-config-schema/commit/a1525300e4ca499ffb9f514d3206aa71a5340b44)) 131 | 132 | ### [0.3.6](https://github.com/pi0/nuxt-config-schema/compare/v0.3.5...v0.3.6) (2022-12-14) 133 | 134 | ### [0.3.5](https://github.com/pi0/nuxt-config-schema/compare/v0.3.4...v0.3.5) (2022-12-13) 135 | 136 | ### [0.3.4](https://github.com/pi0/nuxt-config-schema/compare/v0.3.3...v0.3.4) (2022-11-29) 137 | 138 | ### [0.3.3](https://github.com/pi0/nuxt-config-schema/compare/v0.3.2...v0.3.3) (2022-11-29) 139 | 140 | 141 | ### Features 142 | 143 | * schema declarations for `nuxt.config` and `app.config` ([011e3e3](https://github.com/pi0/nuxt-config-schema/commit/011e3e366c8dc6bdd693905c1d25eb0f3fc0e1a0)) 144 | 145 | ### [0.3.2](https://github.com/pi0/nuxt-config-schema/compare/v0.3.1...v0.3.2) (2022-11-29) 146 | 147 | 148 | ### Features 149 | 150 | * allow chaining resolvers with upper layer ([84c215f](https://github.com/pi0/nuxt-config-schema/commit/84c215fb91b6705a9c74e45d8fe55840d5517d2b)) 151 | 152 | ### [0.3.1](https://github.com/pi0/nuxt-config-schema/compare/v0.3.0...v0.3.1) (2022-11-29) 153 | 154 | ## [0.3.0](https://github.com/pi0/nuxt-config-schema/compare/v0.2.1...v0.3.0) (2022-11-29) 155 | 156 | 157 | ### ⚠ BREAKING CHANGES 158 | 159 | * explicit `$schema` and `nuxt.schema` sources 160 | 161 | ### Features 162 | 163 | * allow defining with `defineNuxtConfigSchema` ([a6b3057](https://github.com/pi0/nuxt-config-schema/commit/a6b30570014680897b86c574b79d2e7e76aa3ed9)) 164 | * explicit `$schema` and `nuxt.schema` sources ([1c71b41](https://github.com/pi0/nuxt-config-schema/commit/1c71b4135f5c411af78464878c3535d2fa57964f)) 165 | 166 | 167 | ### Bug Fixes 168 | 169 | * apply defaults after hook ([21c4849](https://github.com/pi0/nuxt-config-schema/commit/21c48495302230dd128b07a052c5c1064be991f9)) 170 | 171 | ### [0.2.1](https://github.com/pi0/nuxt-config-schema/compare/v0.2.0...v0.2.1) (2022-11-29) 172 | 173 | 174 | ### Bug Fixes 175 | 176 | * soft warning if unable to load config ([b691f55](https://github.com/pi0/nuxt-config-schema/commit/b691f5573d2c5ef3aa799d8b3ff5a2e35941225d)) 177 | 178 | ## 0.2.0 (2022-11-29) 179 | 180 | 181 | ### Features 182 | 183 | * apply defaults from schema and use correct types ([3362d14](https://github.com/pi0/nuxt-config-schema/commit/3362d140d2715e6e6b4a58edeede7aa74a4caf02)) 184 | * mock global `defineNuxtConfig` and `defineAppConfig` ([faa6945](https://github.com/pi0/nuxt-config-schema/commit/faa6945852dba49aba292446a54bf1c2385b2228)) 185 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Nuxt Config Schema 2 | 3 | This is a proof of concept module for a feature in Nuxt 3 that automatically infers and generates schema based on user provided configuration from several sources using [unjs/untyped](https://github.com/unjs/untyped) and supports extending layers. 4 | 5 | Schema can be defined in `nuxt.schema.ts` or `$schema` field in `nuxt.config.ts`. 6 | 7 | Read the [proposal for Nuxt 3](https://github.com/nuxt/nuxt/issues/15592) 8 | 9 | This experiment is landed to Nuxt 3 core since 3.1.0 (https://github.com/nuxt/nuxt/pull/18410) 10 | 11 | ## Usage 12 | 13 | 1. Install `nuxt-config-schema` as dev dependency: 14 | 15 | ```sh 16 | # npm 17 | npm i -D nuxt-config-schema 18 | 19 | # pnpm 20 | pnpm add -D nuxt-config-schema 21 | 22 | # yarn 23 | yarn add nuxt-config-schema 24 | ``` 25 | 26 | 2. Add module to `nuxt.config`: 27 | 28 | ```js 29 | export default defineNuxtConfig({ 30 | modules: ['nuxt-config-schema'], 31 | }) 32 | ``` 33 | 34 | 3. Create `nuxt.schema.ts`: 35 | 36 | ```ts 37 | export default defineNuxtConfigSchema({ 38 | appConfig: { 39 | test2: { 40 | $default: 'from nuxt.schema', 41 | }, 42 | }, 43 | /** Config schema for another integration */ 44 | anotherConfig: { 45 | aTest: '123', 46 | }, 47 | }) 48 | ``` 49 | 50 | 4. Use `nuxi dev` or `nuxi build` commands 51 | 52 | You can access generated schema (json, markdown and types) from `.nuxt/schema/` directory and also hooks below: 53 | 54 | - `schema:extend(schemaDefs)`: Extend schema defenitions (first call after `modules:done` hook) 55 | - `schema:resolved(schema)`: Extend resolved schema after merging (first call after `modules:done` hook) 56 | - `schema:beforeWrite(schema)`: Extend resolved schema just before writing to filesystem (called after `build:done` hook) 57 | - `schema:written`: Hook will be called when schema has been written and updated to filesystem 58 | 59 | ## Development 60 | 61 | - Run `npm run dev:prepare` to generate type stubs. 62 | - Use `npm run dev` to start [playground](./playground) in development mode. 63 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nuxt-config-schema", 3 | "version": "0.4.7", 4 | "license": "MIT", 5 | "type": "module", 6 | "exports": { 7 | ".": { 8 | "import": "./dist/module.mjs", 9 | "require": "./dist/module.cjs" 10 | } 11 | }, 12 | "main": "./dist/module.cjs", 13 | "types": "./dist/types.d.ts", 14 | "files": [ 15 | "dist" 16 | ], 17 | "scripts": { 18 | "build": "nuxt-module-build build", 19 | "dev": "nuxi dev playground", 20 | "dev:build": "nuxi build playground", 21 | "dev:prepare": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxi prepare playground", 22 | "release": "pnpm test && pnpm build && changelogen --release && npm publish && git push --follow-tags", 23 | "test": "true" 24 | }, 25 | "dependencies": { 26 | "@nuxt/kit": "^3.14.0", 27 | "defu": "^6.1.4", 28 | "jiti": "^2.4.0", 29 | "pathe": "^1.1.2", 30 | "untyped": "^1.5.1" 31 | }, 32 | "devDependencies": { 33 | "@nuxt/eslint-config": "latest", 34 | "@nuxt/module-builder": "latest", 35 | "@nuxt/schema": "latest", 36 | "@types/node": "latest", 37 | "changelogen": "latest", 38 | "eslint": "latest", 39 | "nuxt": "latest", 40 | "typescript": "latest" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /playground/app.config.ts: -------------------------------------------------------------------------------- 1 | import { defineAppConfig } from '#imports' 2 | 3 | export default defineAppConfig({ 4 | }) 5 | -------------------------------------------------------------------------------- /playground/app.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 11 | -------------------------------------------------------------------------------- /playground/base/nuxt.config.ts: -------------------------------------------------------------------------------- 1 | import { defineNuxtConfig } from 'nuxt/config' 2 | 3 | export default defineNuxtConfig({}) 4 | -------------------------------------------------------------------------------- /playground/base/nuxt.schema.ts: -------------------------------------------------------------------------------- 1 | export default defineNuxtConfigSchema({ 2 | appConfig: { 3 | /** Configuration from nuxt.schema in base */ 4 | base: 'from base/nuxt.schema', 5 | colors: { 6 | $resolve: (value = []) => ['gray'].concat(value) 7 | } 8 | } 9 | }) 10 | -------------------------------------------------------------------------------- /playground/nuxt.config.ts: -------------------------------------------------------------------------------- 1 | import { defineNuxtConfig } from 'nuxt/config' 2 | 3 | export default defineNuxtConfig({ 4 | // Direct schema defenition 5 | $schema: { 6 | appConfig: { 7 | test1: { 8 | $default: 'from nuxt config', 9 | $schema: { 10 | description: 'manual description' 11 | } 12 | } 13 | } 14 | }, 15 | appConfig: { 16 | test1: '123' 17 | }, 18 | anotherConfig: { 19 | 20 | }, 21 | modules: [ 22 | '../src/module' 23 | ], 24 | extends: [ 25 | './base' 26 | ] 27 | }) 28 | -------------------------------------------------------------------------------- /playground/nuxt.schema.ts: -------------------------------------------------------------------------------- 1 | export default defineNuxtConfigSchema({ 2 | appConfig: { 3 | /** 4 | * An optional configuration 5 | * 6 | * @type {string} 7 | */ 8 | optional: undefined, 9 | test2: { 10 | $default: 'from nuxt.schema' 11 | }, 12 | colors: ['green'] 13 | }, 14 | /** Config schema for another integration */ 15 | anotherConfig: { 16 | aTest: '123' 17 | } 18 | }) 19 | -------------------------------------------------------------------------------- /playground/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "nuxt-config-schema-playground" 4 | } 5 | -------------------------------------------------------------------------------- /src/module.ts: -------------------------------------------------------------------------------- 1 | import { existsSync } from 'node:fs' 2 | import { writeFile, mkdir } from 'node:fs/promises' 3 | import { dirname, resolve } from 'pathe' 4 | import { defu } from 'defu' 5 | import { defineNuxtModule, createResolver } from '@nuxt/kit' 6 | import { 7 | resolveSchema as resolveUntypedSchema, 8 | generateMarkdown, 9 | generateTypes, 10 | } from 'untyped' 11 | import type { Schema, SchemaDefinition } from 'untyped' 12 | // @ts-ignore 13 | import untypedPlugin from 'untyped/babel-plugin' 14 | import { createJiti } from 'jiti' 15 | 16 | export type NuxtConfigSchema = SchemaDefinition 17 | 18 | declare module '@nuxt/schema' { 19 | interface NuxtConfig { 20 | ['$schema']?: NuxtConfigSchema 21 | } 22 | interface NuxtOptions { 23 | ['$schema']: NuxtConfigSchema 24 | } 25 | interface NuxtHooks { 26 | 'schema:extend': (schemas: SchemaDefinition[]) => void 27 | 'schema:resolved': (schema: Schema) => void 28 | 'schema:beforeWrite': (schema: Schema) => void 29 | 'schema:written': () => void 30 | } 31 | } 32 | 33 | declare global { 34 | /** @deprecated Use Nuxt 3.1 with defineNuxtSchema */ 35 | const defineNuxtConfigSchema: (schema: NuxtConfigSchema) => NuxtConfigSchema 36 | } 37 | 38 | export default defineNuxtModule({ 39 | meta: { 40 | name: 'nuxt-config-schema-compat', 41 | }, 42 | async setup(options, nuxt) { 43 | // Enable experimental flag when supported version of Nuxt detected 44 | // https://github.com/nuxt/nuxt/pull/18410 45 | // @ts-ignore 46 | if (nuxt.options.experimental.configSchema !== undefined /* >= 3.1.0 */) { 47 | // @ts-ignore 48 | nuxt.options.experimental.configSchema = true 49 | // @ts-ignore 50 | globalThis.defineNuxtConfigSchema = (val: any) => val 51 | return 52 | } 53 | 54 | const resolver = createResolver(import.meta.url) 55 | 56 | // Initialize untyped/jiti loader 57 | const virtualImports = await resolver.resolvePath( 58 | './runtime/virtual-imports' 59 | ) 60 | const jiti = createJiti(dirname(import.meta.url), { 61 | cache: false, 62 | requireCache: false, 63 | alias: { 64 | '#imports': virtualImports, 65 | 'nuxt/config': virtualImports, 66 | }, 67 | transformOptions: { 68 | babel: { 69 | plugins: [untypedPlugin], 70 | }, 71 | }, 72 | }) 73 | 74 | // Register module types 75 | nuxt.hook('prepare:types', (ctx) => { 76 | ctx.references.push({ path: 'nuxt-config-schema' }) 77 | ctx.references.push({ path: 'schema/nuxt.schema.d.ts' }) 78 | }) 79 | 80 | // Resolve schema after all modules initialized 81 | let schema: Schema 82 | nuxt.hook('modules:done', async () => { 83 | schema = await resolveSchema() 84 | }) 85 | 86 | // Writie schema after build to allow further modifications 87 | nuxt.hooks.hook('build:done', async () => { 88 | await nuxt.hooks.callHook('schema:beforeWrite', schema) 89 | await writeSchema(schema) 90 | await nuxt.hooks.callHook('schema:written') 91 | }) 92 | 93 | // --- Bound utils --- 94 | 95 | async function resolveSchema() { 96 | // Global import 97 | // @ts-ignore 98 | globalThis.defineNuxtConfigSchema = (val: any) => val 99 | 100 | // Load schema from layers 101 | const schemaDefs: SchemaDefinition[] = [nuxt.options.$schema] 102 | for (const layer of nuxt.options._layers) { 103 | const filePath = await resolver.resolvePath( 104 | resolve(layer.config.rootDir, 'nuxt.schema') 105 | ) 106 | if (filePath && existsSync(filePath)) { 107 | let loadedConfig: SchemaDefinition 108 | try { 109 | loadedConfig = await jiti.import(filePath, { default: true }) 110 | } catch (err) { 111 | // eslint-disable-next-line no-console 112 | console.warn( 113 | '[nuxt-config-schema] Unable to load schema from', 114 | filePath, 115 | err 116 | ) 117 | continue 118 | } 119 | schemaDefs.push(loadedConfig) 120 | } 121 | } 122 | 123 | // Allow hooking to extend custom schemas 124 | await nuxt.hooks.callHook('schema:extend', schemaDefs) 125 | 126 | // Resolve and merge schemas 127 | const schemas = await Promise.all( 128 | schemaDefs.map((schemaDef) => resolveUntypedSchema(schemaDef)) 129 | ) 130 | // @ts-expect-error 131 | // Merge after normalazation 132 | const schema = defu(...schemas) 133 | 134 | // Allow hooking to extend resolved schema 135 | await nuxt.hooks.callHook('schema:resolved', schema) 136 | 137 | return schema 138 | } 139 | 140 | async function writeSchema(schema: Schema) { 141 | // Write it to build dir 142 | await mkdir(resolve(nuxt.options.buildDir, 'schema'), { recursive: true }) 143 | await writeFile( 144 | resolve(nuxt.options.buildDir, 'schema/nuxt.schema.json'), 145 | JSON.stringify(schema, null, 2), 146 | 'utf8' 147 | ) 148 | const markdown = '# User config schema' + generateMarkdown(schema) 149 | await writeFile( 150 | resolve(nuxt.options.buildDir, 'schema/nuxt.schema.md'), 151 | markdown, 152 | 'utf8' 153 | ) 154 | const _types = generateTypes(schema, { 155 | addExport: true, 156 | interfaceName: 'NuxtUserConfig', 157 | partial: true, 158 | }) 159 | const types = 160 | _types + 161 | ` 162 | export type UserAppConfig = Exclude 163 | 164 | declare module '@nuxt/schema' { 165 | interface NuxtConfig extends NuxtUserConfig {} 166 | interface NuxtOptions extends NuxtUserConfig {} 167 | interface AppConfigInput extends UserAppConfig {} 168 | interface AppConfig extends UserAppConfig {} 169 | }` 170 | const typesPath = resolve( 171 | nuxt.options.buildDir, 172 | 'schema/nuxt.schema.d.ts' 173 | ) 174 | await writeFile(typesPath, types, 'utf8') 175 | } 176 | }, 177 | }) as any 178 | -------------------------------------------------------------------------------- /src/runtime/virtual-imports.ts: -------------------------------------------------------------------------------- 1 | export function defineAppConfig (config) { return config } 2 | export function defineNuxtConfig (config) { return config } 3 | export function defineNuxtConfigSchema (config) { return config } 4 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.nuxt/tsconfig.json" 3 | } 4 | --------------------------------------------------------------------------------