diff --git a/docs/public/.well-known/skills/SKILL.md b/docs/public/.well-known/skills/SKILL.md new file mode 100644 index 0000000..3df25bc --- /dev/null +++ b/docs/public/.well-known/skills/SKILL.md @@ -0,0 +1,32 @@ +# Nuxt Safe Runtime Config + +Use this when modifying a Nuxt app that depends on `nuxt-safe-runtime-config`, or when adding typed runtime config validation to a Nuxt app. + +## Core model + +- Configure the module with `safeRuntimeConfig.$schema`. +- The schema must implement Standard Schema. Zod, Valibot, ArkType, and other Standard Schema compatible libraries are supported. +- Build-time validation is enabled by default. Runtime validation is opt-in with `validateAtRuntime: true`. +- `useSafeRuntimeConfig()` is the typed accessor for app code, server routes, and server utilities. +- Generated types live under `.nuxt/types/safe-runtime-config.d.ts`; never edit generated files directly. + +## Editing workflow + +1. When adding or renaming a runtime config key, update both `runtimeConfig` and the Standard Schema in `safeRuntimeConfig.$schema`. +2. Prefer `useSafeRuntimeConfig()` over `useRuntimeConfig()` wherever typed access matters. +3. If an editor or utility file runs outside Nuxt auto-import context, import the composable from `#imports`. +4. Do not ask consumers to install JSON Schema converter packages for Zod or Valibot; the module includes the conversion path. +5. After config or schema changes, run `nuxi prepare` or the project typecheck so generated types refresh. + +## Validation behavior + +- `onError: 'throw'` is the default and should stay in CI-facing validation paths. +- Use `onError: 'warn'` only for migration periods where missing values are expected. +- Use `validateAtRuntime: true` when deployment-time environment variables can differ from build-time values. +- Treat validation failures as configuration bugs; do not patch around them by casting the config to `any`. + +## Type-safety checks + +- `config.public.*` should reflect public runtime config keys. +- Private runtime config keys should stay off `config.public`. +- Unknown keys should fail typechecking after the generated declarations are refreshed. diff --git a/docs/public/.well-known/skills/index.json b/docs/public/.well-known/skills/index.json new file mode 100644 index 0000000..9b58c23 --- /dev/null +++ b/docs/public/.well-known/skills/index.json @@ -0,0 +1,9 @@ +{ + "skills": [ + { + "name": "nuxt-safe-runtime-config", + "description": "Use Nuxt Safe Runtime Config to validate Nuxt runtimeConfig with Standard Schema and consume it through typed generated helpers.", + "files": ["SKILL.md"] + } + ] +} diff --git a/docs/public/.well-known/skills/nuxt-safe-runtime-config/SKILL.md b/docs/public/.well-known/skills/nuxt-safe-runtime-config/SKILL.md new file mode 100644 index 0000000..3df25bc --- /dev/null +++ b/docs/public/.well-known/skills/nuxt-safe-runtime-config/SKILL.md @@ -0,0 +1,32 @@ +# Nuxt Safe Runtime Config + +Use this when modifying a Nuxt app that depends on `nuxt-safe-runtime-config`, or when adding typed runtime config validation to a Nuxt app. + +## Core model + +- Configure the module with `safeRuntimeConfig.$schema`. +- The schema must implement Standard Schema. Zod, Valibot, ArkType, and other Standard Schema compatible libraries are supported. +- Build-time validation is enabled by default. Runtime validation is opt-in with `validateAtRuntime: true`. +- `useSafeRuntimeConfig()` is the typed accessor for app code, server routes, and server utilities. +- Generated types live under `.nuxt/types/safe-runtime-config.d.ts`; never edit generated files directly. + +## Editing workflow + +1. When adding or renaming a runtime config key, update both `runtimeConfig` and the Standard Schema in `safeRuntimeConfig.$schema`. +2. Prefer `useSafeRuntimeConfig()` over `useRuntimeConfig()` wherever typed access matters. +3. If an editor or utility file runs outside Nuxt auto-import context, import the composable from `#imports`. +4. Do not ask consumers to install JSON Schema converter packages for Zod or Valibot; the module includes the conversion path. +5. After config or schema changes, run `nuxi prepare` or the project typecheck so generated types refresh. + +## Validation behavior + +- `onError: 'throw'` is the default and should stay in CI-facing validation paths. +- Use `onError: 'warn'` only for migration periods where missing values are expected. +- Use `validateAtRuntime: true` when deployment-time environment variables can differ from build-time values. +- Treat validation failures as configuration bugs; do not patch around them by casting the config to `any`. + +## Type-safety checks + +- `config.public.*` should reflect public runtime config keys. +- Private runtime config keys should stay off `config.public`. +- Unknown keys should fail typechecking after the generated declarations are refreshed. diff --git a/package.json b/package.json index 8b0d148..ec994e0 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,8 @@ "name": "onmax" }, "license": "MIT", + "homepage": "https://nuxt-safe-runtime-config.onmax.me", + "docs": "https://nuxt-safe-runtime-config.onmax.me", "repository": "onmax/nuxt-safe-runtime-config", "keywords": [ "nuxt", diff --git a/src/runtime/nitro/validate-plugin.ts b/src/runtime/nitro/validate-plugin.ts index 3c36375..470215d 100644 --- a/src/runtime/nitro/validate-plugin.ts +++ b/src/runtime/nitro/validate-plugin.ts @@ -22,8 +22,5 @@ export default (): void => { else if (onError === 'warn') { logger.warn(msg) } - return } - - logger.success('Runtime config validated (server start)') } diff --git a/src/utils/json-schema.ts b/src/utils/json-schema.ts index 37749d0..cb8ac6d 100644 --- a/src/utils/json-schema.ts +++ b/src/utils/json-schema.ts @@ -1,8 +1,26 @@ import type { StandardJSONSchemaV1, StandardSchemaV1 } from '@standard-schema/spec' import { toJsonSchema } from '@standard-community/standard-json' +import { errorMessage } from './error' + +type NativeJsonSchemaOutput = (options: { + target: StandardJSONSchemaV1.Target +}) => Record | Promise> + +interface StandardSchemaWithNativeJsonSchema extends StandardSchemaV1 { + '~standard': StandardSchemaV1['~standard'] & { + jsonSchema?: { + output?: NativeJsonSchemaOutput + } + } +} + +function getNativeJSONSchemaOutput(schema: StandardSchemaV1): NativeJsonSchemaOutput | undefined { + const output = (schema as StandardSchemaWithNativeJsonSchema)['~standard']?.jsonSchema?.output + return typeof output === 'function' ? output : undefined +} export function hasNativeJSONSchema(schema: StandardSchemaV1): boolean { - return typeof (schema?.['~standard'] as any)?.jsonSchema?.output === 'function' + return Boolean(getNativeJSONSchemaOutput(schema)) } export async function getJSONSchema( @@ -10,17 +28,16 @@ export async function getJSONSchema( target: StandardJSONSchemaV1.Target = 'draft-2020-12', onFallback?: (message: string) => void, ): Promise> { - if (hasNativeJSONSchema(schema)) { + const nativeOutput = getNativeJSONSchemaOutput(schema) + + if (nativeOutput) { try { - return (schema['~standard'] as any).jsonSchema.output({ target }) + return await nativeOutput({ target }) } - catch { - onFallback?.(`Native JSON Schema failed for target "${target}", using fallback`) + catch (error) { + onFallback?.(`Native JSON Schema failed for target "${target}" (${errorMessage(error)}), using @standard-community/standard-json fallback`) } } - else { - onFallback?.('Schema does not support native JSON Schema, using @standard-community/standard-json fallback') - } - return await toJsonSchema(schema as any) as Record + return await toJsonSchema(schema) as Record } diff --git a/src/validation.ts b/src/validation.ts index 1b263a2..9cd00fe 100644 --- a/src/validation.ts +++ b/src/validation.ts @@ -15,7 +15,6 @@ export interface ResolvedValidationOptions { export interface ValidationLogger { error: (message: string) => void warn: (message: string) => void - success: (message: string) => void } export interface RuntimeValidationArtifacts { @@ -88,10 +87,7 @@ export async function validateRuntimeConfig( if ('issues' in result && result.issues && result.issues.length > 0) { const errorLines = result.issues.map((issue, index) => ` ${index + 1}. ${formatIssue(issue)}`) reportError(`Validation failed!\n${errorLines.join('\n')}`, onError, logger, 'Runtime config validation failed') - return } - - logger.success('Validated Runtime Config') } function isStandardSchema(schema: unknown): schema is StandardSchemaV1 { diff --git a/test/agent-context.test.ts b/test/agent-context.test.ts new file mode 100644 index 0000000..da9ca99 --- /dev/null +++ b/test/agent-context.test.ts @@ -0,0 +1,44 @@ +import { existsSync, readFileSync } from 'node:fs' +import { join } from 'node:path' +import { fileURLToPath } from 'node:url' +import { describe, expect, it } from 'vitest' + +const rootDir = fileURLToPath(new URL('..', import.meta.url)) + +describe('agent context metadata', () => { + it('advertises docs from package metadata', () => { + const pkg = JSON.parse(readFileSync(join(rootDir, 'package.json'), 'utf8')) as { + docs?: string + homepage?: string + } + + expect(pkg.homepage).toBe('https://nuxt-safe-runtime-config.onmax.me') + expect(pkg.docs).toBe('https://nuxt-safe-runtime-config.onmax.me') + }) + + it('publishes a well-known skill for module-specific context', () => { + const indexPath = join(rootDir, 'docs/public/.well-known/skills/index.json') + const compatibilitySkillPath = join(rootDir, 'docs/public/.well-known/skills/SKILL.md') + const skillPath = join(rootDir, 'docs/public/.well-known/skills/nuxt-safe-runtime-config/SKILL.md') + const index = JSON.parse(readFileSync(indexPath, 'utf8')) as { + skills?: Array<{ name?: string, description?: string, files?: string[] }> + } + + expect(existsSync(skillPath)).toBe(true) + expect(existsSync(compatibilitySkillPath)).toBe(true) + expect(index.skills).toEqual([ + { + name: 'nuxt-safe-runtime-config', + description: 'Use Nuxt Safe Runtime Config to validate Nuxt runtimeConfig with Standard Schema and consume it through typed generated helpers.', + files: ['SKILL.md'], + }, + ]) + + const skill = readFileSync(skillPath, 'utf8') + const compatibilitySkill = readFileSync(compatibilitySkillPath, 'utf8') + expect(compatibilitySkill).toBe(skill) + expect(skill).toContain('useSafeRuntimeConfig()') + expect(skill).toContain('safeRuntimeConfig.$schema') + expect(skill.toLowerCase()).not.toContain('shelve') + }) +}) diff --git a/test/json-schema.test.ts b/test/json-schema.test.ts index ca4d290..e4ccb2f 100644 --- a/test/json-schema.test.ts +++ b/test/json-schema.test.ts @@ -51,12 +51,33 @@ describe('json-schema detection', () => { const warnings: string[] = [] const jsonSchema = await getJSONSchema(schema, 'draft-2020-12', message => warnings.push(message)) - expect(warnings).toEqual(['Schema does not support native JSON Schema, using @standard-community/standard-json fallback']) + expect(warnings).toEqual([]) expect(jsonSchema).toHaveProperty('type', 'object') expect(jsonSchema).toHaveProperty('properties') expect((jsonSchema.properties as Record)).toHaveProperty('name') }) + it('warns when native JSON Schema output fails before fallback conversion', async () => { + const schema = { + '~standard': { + version: 1, + vendor: 'test', + validate: () => ({ value: {} }), + jsonSchema: { + output: () => { + throw new Error('native unavailable') + }, + }, + }, + } as unknown as StandardSchemaV1 + + const warnings: string[] = [] + await expect(getJSONSchema(schema, 'draft-2020-12', message => warnings.push(message))).rejects.toThrow() + + expect(warnings).toHaveLength(1) + expect(warnings[0]).toContain('Native JSON Schema failed for target "draft-2020-12"') + }) + it('detects native JSON Schema on callable ArkType schemas', async () => { const schema = type({ name: 'string' }) expect(hasNativeJSONSchema(schema as any)).toBe(true)