@eventuras/app-config 0.1.4 → 0.1.5

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/dist/cli.js CHANGED
File without changes
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/types.ts","../src/validator.ts","../src/loader.ts","../src/next.ts"],"sourcesContent":["import { z } from 'zod';\n\n/**\n * Supported environment variable types\n */\nexport type EnvVarType = 'string' | 'url' | 'int' | 'bool' | 'json';\n\n/**\n * Environment variable definition\n */\nexport interface EnvVarDefinition {\n /** Whether this variable is required */\n required: boolean;\n /** Whether this variable is exposed to the client (e.g., NEXT_PUBLIC_*) */\n client: boolean;\n /** Expected type of the variable */\n type: EnvVarType;\n /** Human-readable description */\n description: string;\n /** Default value if not provided */\n default?: string | number | boolean | object;\n /** Regular expression pattern for validation */\n pattern?: string;\n /** Allowed values */\n enum?: string[];\n}\n\n/**\n * Application configuration schema\n */\nexport interface AppConfig {\n /** JSON Schema reference */\n $schema?: string;\n /** Application package name */\n name: string;\n /** Configuration type */\n type: 'app';\n /** Application description */\n description?: string;\n /** Environment variable definitions */\n env: Record<string, EnvVarDefinition>;\n /** Build configuration */\n build?: {\n outDir?: string;\n target?: string;\n };\n /** Runtime configuration */\n runtime?: {\n port?: number;\n };\n}\n\n/**\n * Zod schema for environment variable definition\n */\nexport const envVarDefinitionSchema = z.object({\n required: z.boolean(),\n client: z.boolean(),\n type: z.enum(['string', 'url', 'int', 'bool', 'json']),\n description: z.string(),\n default: z.union([z.string(), z.number(), z.boolean(), z.object({})]).optional(),\n pattern: z.string().optional(),\n enum: z.array(z.string()).optional(),\n});\n\n/**\n * Zod schema for app configuration\n */\nexport const appConfigSchema = z.object({\n $schema: z.string().optional(),\n name: z.string(),\n type: z.literal('app'),\n description: z.string().optional(),\n env: z.record(z.string(), envVarDefinitionSchema),\n build: z\n .object({\n outDir: z.string().optional(),\n target: z.string().optional(),\n })\n .optional(),\n runtime: z\n .object({\n port: z.number().optional(),\n })\n .optional(),\n});\n","import { EnvVarDefinition, EnvVarType } from './types.js';\n\n/**\n * Validation error class\n */\nexport class EnvValidationError extends Error {\n constructor(\n message: string,\n public varName: string,\n public definition?: EnvVarDefinition\n ) {\n super(message);\n this.name = 'EnvValidationError';\n }\n}\n\n/**\n * Parse and validate an environment variable value based on its type\n */\nexport function parseEnvValue(\n varName: string,\n rawValue: string | undefined,\n definition: EnvVarDefinition\n): unknown {\n // Handle missing values\n if (rawValue === undefined || rawValue === '') {\n if (definition.required && definition.default === undefined) {\n throw new EnvValidationError(\n `Required environment variable \"${varName}\" is not set.\\n` +\n `Description: ${definition.description}`,\n varName,\n definition\n );\n }\n return definition.default;\n }\n\n // Validate enum\n if (definition.enum && !definition.enum.includes(rawValue)) {\n throw new EnvValidationError(\n `Environment variable \"${varName}\" must be one of: ${definition.enum.join(', ')}.\\n` +\n `Got: \"${rawValue}\"`,\n varName,\n definition\n );\n }\n\n // Validate pattern\n if (definition.pattern) {\n const regex = new RegExp(definition.pattern);\n if (!regex.test(rawValue)) {\n throw new EnvValidationError(\n `Environment variable \"${varName}\" does not match pattern: ${definition.pattern}.\\n` +\n `Got: \"${rawValue}\"`,\n varName,\n definition\n );\n }\n }\n\n // Type conversion and validation\n switch (definition.type) {\n case 'string':\n return rawValue;\n\n case 'url':\n try {\n new URL(rawValue);\n return rawValue;\n } catch {\n throw new EnvValidationError(\n `Environment variable \"${varName}\" must be a valid URL.\\n` + `Got: \"${rawValue}\"`,\n varName,\n definition\n );\n }\n\n case 'int': {\n const intValue = Number.parseInt(rawValue, 10);\n if (Number.isNaN(intValue)) {\n throw new EnvValidationError(\n `Environment variable \"${varName}\" must be a valid integer.\\n` + `Got: \"${rawValue}\"`,\n varName,\n definition\n );\n }\n return intValue;\n }\n\n case 'bool': {\n const lowerValue = rawValue.toLowerCase();\n if (lowerValue === 'true' || lowerValue === '1') return true;\n if (lowerValue === 'false' || lowerValue === '0') return false;\n throw new EnvValidationError(\n `Environment variable \"${varName}\" must be a boolean (true/false, 1/0).\\n` +\n `Got: \"${rawValue}\"`,\n varName,\n definition\n );\n }\n\n case 'json':\n try {\n return JSON.parse(rawValue);\n } catch {\n throw new EnvValidationError(\n `Environment variable \"${varName}\" must be valid JSON.\\n` + `Got: \"${rawValue}\"`,\n varName,\n definition\n );\n }\n\n default: {\n const _exhaustive: never = definition.type;\n throw new Error(`Unknown type: ${_exhaustive}`);\n }\n }\n}\n\n/**\n * Get the TypeScript type string for an environment variable type\n */\nexport function getTypeString(type: EnvVarType): string {\n switch (type) {\n case 'string':\n case 'url':\n return 'string';\n case 'int':\n return 'number';\n case 'bool':\n return 'boolean';\n case 'json':\n return 'unknown';\n default: {\n const _exhaustive: never = type;\n throw new Error(`Unknown type: ${_exhaustive}`);\n }\n }\n}\n","import { AppConfig, appConfigSchema } from './types.js';\nimport { parseEnvValue, EnvValidationError } from './validator.js';\n\n/**\n * Configuration loader class\n */\nexport class ConfigLoader {\n private readonly config: AppConfig;\n private envValues: Record<string, unknown> = {};\n\n /**\n * Direct access to environment variables as properties\n * @example config.env.AUTH0_CLIENT_ID\n */\n public readonly env: Record<string, unknown>;\n\n constructor(\n configObject: unknown,\n private processEnv: NodeJS.ProcessEnv = process.env\n ) {\n // Validate schema\n const result = appConfigSchema.safeParse(configObject);\n if (!result.success) {\n const issues = result.error.issues.map(i => ` - ${i.path.join('.')}: ${i.message}`).join('\\n');\n throw new Error(\n `Invalid app.config.json:\\n${issues}`\n );\n }\n\n this.config = result.data;\n\n // Skip validation during Next.js build/compilation phases\n // During these phases, we still want to read the env vars, but not fail the build if they're missing\n // NEXT_PHASE is set by Next.js during build\n const isNextBuild =\n this.processEnv.NEXT_PHASE === 'phase-production-build' ||\n this.processEnv.NEXT_PHASE === 'phase-development-server';\n\n // Always try to validate/read environment variables\n // During build, we'll be lenient about missing required vars\n this.validateEnvironment(!isNextBuild);\n\n // Create readonly proxy for env access\n this.env = new Proxy(this.envValues, {\n get: (target, prop: string) => {\n if (!(prop in target)) {\n throw new Error(\n `Environment variable \"${prop}\" is not defined in app.config.json.\\n` +\n `Available variables: ${Object.keys(target).join(', ')}`\n );\n }\n return target[prop];\n },\n set: () => {\n throw new Error('Cannot modify environment variables through config.env');\n },\n });\n }\n\n /**\n * Validate all environment variables according to config\n * @param strictRequired - Whether to throw errors for missing required vars (false during build)\n */\n private validateEnvironment(strictRequired = true): void {\n const errors: EnvValidationError[] = [];\n\n for (const [varName, definition] of Object.entries(this.config.env)) {\n try {\n const value = parseEnvValue(varName, this.processEnv[varName], definition);\n this.envValues[varName] = value;\n\n // Set default values back to process.env if they were missing\n if (this.processEnv[varName] === undefined && value !== undefined) {\n this.processEnv[varName] = String(value);\n }\n } catch (error) {\n if (error instanceof EnvValidationError) {\n // During build (strictRequired=false), skip required validation errors but still collect type errors\n if (!strictRequired && error.message.includes('Required environment variable')) {\n // Use default value or empty string if required var is missing during build\n this.envValues[varName] = definition.default ?? '';\n continue;\n }\n errors.push(error);\n } else {\n throw error;\n }\n }\n }\n\n // Throw all errors at once\n if (errors.length > 0) {\n const errorMessage = errors.map(e => `\\n❌ ${e.message}`).join('\\n');\n throw new Error(`Environment validation failed:${errorMessage}\\n`);\n }\n }\n\n /**\n * Get a typed environment variable value\n */\n get<T = unknown>(varName: string): T {\n if (!(varName in this.envValues)) {\n throw new Error(\n `Environment variable \"${varName}\" is not defined in app.config.json.\\n` +\n `Available variables: ${Object.keys(this.envValues).join(', ')}`\n );\n }\n return this.envValues[varName] as T;\n }\n\n /**\n * Get the raw app configuration\n */\n getConfig(): Readonly<AppConfig> {\n return this.config;\n }\n\n /**\n * Get all environment variables as a typed object\n */\n getAllEnv(): Readonly<Record<string, unknown>> {\n return { ...this.envValues };\n }\n\n /**\n * Check if a variable exists\n */\n has(varName: string): boolean {\n return varName in this.envValues;\n }\n}\n\n/**\n * Create a config loader from a config object\n */\nexport function createConfig(configObject: unknown): ConfigLoader {\n return new ConfigLoader(configObject);\n}\n\n/**\n * Validate app configuration and environment variables.\n * This is useful for running validation explicitly during app initialization.\n *\n * @param configObject - App configuration object (parsed from app.config.json)\n * @throws Error if configuration is invalid or required env vars are missing\n *\n * @example\n * ```typescript\n * // In your app initialization (e.g., layout.tsx or main.ts)\n * import { validate } from '@eventuras/app-config';\n * import appConfigJson from './app.config.json';\n *\n * validate(appConfigJson);\n * console.log('✓ Environment validated successfully');\n * ```\n */\nexport function validate(configObject: unknown): void {\n // Simply creating the config will trigger validation\n // If validation fails, an error will be thrown\n new ConfigLoader(configObject);\n}\n","import { AppConfig } from './types.js';\nimport { ConfigLoader } from './loader.js';\n\n/**\n * Create explicit getters for NEXT_PUBLIC_* environment variables.\n *\n * This is required for Next.js because it performs build-time replacement of\n * process.env.NEXT_PUBLIC_* variables. We must access process.env directly,\n * not through a runtime lookup.\n *\n * @param config - The app configuration\n * @returns An object with explicit getters for each NEXT_PUBLIC_* variable\n */\nexport function createPublicEnvGetters(config: AppConfig) {\n const getters: Record<string, unknown> = {};\n\n for (const [varName, definition] of Object.entries(config.env)) {\n if (definition.client && varName.startsWith('NEXT_PUBLIC_')) {\n Object.defineProperty(getters, varName, {\n get() {\n return process.env[varName];\n },\n enumerable: true,\n });\n }\n }\n\n return getters;\n}\n\nexport interface EnvironmentObject {\n validate: () => void;\n get: <T = string>(varName: string) => T;\n [key: string]: unknown;\n}\n\n/**\n * Create a complete Environment object with validation and explicit getters.\n *\n * This provides a drop-in replacement for the old Environment class pattern,\n * with automatic validation and proper Next.js NEXT_PUBLIC_* support.\n *\n * @param configPath - Path to app.config.json\n * @returns An object with validate(), get(), and explicit NEXT_PUBLIC_* getters\n */\nexport function createEnvironment(configPath: string): EnvironmentObject {\n let configInstance: ConfigLoader | null = null;\n\n function getConfigInstance(): ConfigLoader {\n if (!configInstance) {\n configInstance = new ConfigLoader(configPath);\n }\n return configInstance;\n }\n\n const publicGetters = createPublicEnvGetters(getConfigInstance().getConfig());\n\n return {\n /**\n * Validate environment (happens automatically on first access)\n */\n validate: () => {\n getConfigInstance();\n },\n\n /**\n * Get a server-side environment variable\n */\n get: <T = string>(varName: string): T => {\n return getConfigInstance().get(varName) as T;\n },\n\n // Spread in all the NEXT_PUBLIC_* getters\n ...publicGetters,\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/types.ts","../src/validator.ts","../src/loader.ts","../src/next.ts"],"sourcesContent":["import { z } from 'zod';\n\n/**\n * Supported environment variable types\n */\nexport type EnvVarType = 'string' | 'url' | 'int' | 'bool' | 'json';\n\n/**\n * Environment variable definition\n */\nexport interface EnvVarDefinition {\n /** Whether this variable is required */\n required: boolean;\n /** Whether this variable is exposed to the client (e.g., NEXT_PUBLIC_*) */\n client: boolean;\n /** Expected type of the variable */\n type: EnvVarType;\n /** Human-readable description */\n description: string;\n /** Default value if not provided */\n default?: string | number | boolean | object;\n /** Regular expression pattern for validation */\n pattern?: string;\n /** Allowed values */\n enum?: string[];\n}\n\n/**\n * Application configuration schema\n */\nexport interface AppConfig {\n /** JSON Schema reference */\n $schema?: string;\n /** Application package name */\n name: string;\n /** Configuration type */\n type: 'app';\n /** Application description */\n description?: string;\n /** Environment variable definitions */\n env: Record<string, EnvVarDefinition>;\n /** Build configuration */\n build?: {\n outDir?: string;\n target?: string;\n };\n /** Runtime configuration */\n runtime?: {\n port?: number;\n };\n}\n\n/**\n * Zod schema for environment variable definition\n */\nexport const envVarDefinitionSchema = z.object({\n required: z.boolean(),\n client: z.boolean(),\n type: z.enum(['string', 'url', 'int', 'bool', 'json']),\n description: z.string(),\n default: z.union([z.string(), z.number(), z.boolean(), z.object({})]).optional(),\n pattern: z.string().optional(),\n enum: z.array(z.string()).optional(),\n});\n\n/**\n * Zod schema for app configuration\n */\nexport const appConfigSchema = z.object({\n $schema: z.string().optional(),\n name: z.string(),\n type: z.literal('app'),\n description: z.string().optional(),\n env: z.record(z.string(), envVarDefinitionSchema),\n build: z\n .object({\n outDir: z.string().optional(),\n target: z.string().optional(),\n })\n .optional(),\n runtime: z\n .object({\n port: z.number().optional(),\n })\n .optional(),\n});\n","import { EnvVarDefinition, EnvVarType } from './types.js';\n\n/**\n * Validation error class\n */\nexport class EnvValidationError extends Error {\n constructor(\n message: string,\n public varName: string,\n public definition?: EnvVarDefinition\n ) {\n super(message);\n this.name = 'EnvValidationError';\n }\n}\n\n/**\n * Parse and validate an environment variable value based on its type\n */\nexport function parseEnvValue(\n varName: string,\n rawValue: string | undefined,\n definition: EnvVarDefinition\n): unknown {\n // Handle missing values\n if (rawValue === undefined || rawValue === '') {\n if (definition.required && definition.default === undefined) {\n throw new EnvValidationError(\n `Required environment variable \"${varName}\" is not set.\\n` +\n `Description: ${definition.description}`,\n varName,\n definition\n );\n }\n return definition.default;\n }\n\n // Validate enum\n if (definition.enum && !definition.enum.includes(rawValue)) {\n throw new EnvValidationError(\n `Environment variable \"${varName}\" must be one of: ${definition.enum.join(', ')}.\\n` +\n `Got: \"${rawValue}\"`,\n varName,\n definition\n );\n }\n\n // Validate pattern\n if (definition.pattern) {\n const regex = new RegExp(definition.pattern);\n if (!regex.test(rawValue)) {\n throw new EnvValidationError(\n `Environment variable \"${varName}\" does not match pattern: ${definition.pattern}.\\n` +\n `Got: \"${rawValue}\"`,\n varName,\n definition\n );\n }\n }\n\n // Type conversion and validation\n switch (definition.type) {\n case 'string':\n return rawValue;\n\n case 'url':\n try {\n new URL(rawValue);\n return rawValue;\n } catch {\n throw new EnvValidationError(\n `Environment variable \"${varName}\" must be a valid URL.\\n` + `Got: \"${rawValue}\"`,\n varName,\n definition\n );\n }\n\n case 'int': {\n const intValue = Number.parseInt(rawValue, 10);\n if (Number.isNaN(intValue)) {\n throw new EnvValidationError(\n `Environment variable \"${varName}\" must be a valid integer.\\n` + `Got: \"${rawValue}\"`,\n varName,\n definition\n );\n }\n return intValue;\n }\n\n case 'bool': {\n const lowerValue = rawValue.toLowerCase();\n if (lowerValue === 'true' || lowerValue === '1') return true;\n if (lowerValue === 'false' || lowerValue === '0') return false;\n throw new EnvValidationError(\n `Environment variable \"${varName}\" must be a boolean (true/false, 1/0).\\n` +\n `Got: \"${rawValue}\"`,\n varName,\n definition\n );\n }\n\n case 'json':\n try {\n return JSON.parse(rawValue);\n } catch {\n throw new EnvValidationError(\n `Environment variable \"${varName}\" must be valid JSON.\\n` + `Got: \"${rawValue}\"`,\n varName,\n definition\n );\n }\n\n default: {\n const _exhaustive: never = definition.type;\n throw new Error(`Unknown type: ${_exhaustive}`);\n }\n }\n}\n\n/**\n * Get the TypeScript type string for an environment variable type\n */\nexport function getTypeString(type: EnvVarType): string {\n switch (type) {\n case 'string':\n case 'url':\n return 'string';\n case 'int':\n return 'number';\n case 'bool':\n return 'boolean';\n case 'json':\n return 'unknown';\n default: {\n const _exhaustive: never = type;\n throw new Error(`Unknown type: ${_exhaustive}`);\n }\n }\n}\n","import { AppConfig, appConfigSchema } from './types.js';\nimport { parseEnvValue, EnvValidationError } from './validator.js';\n\n/**\n * Configuration loader class\n */\nexport class ConfigLoader {\n private readonly config: AppConfig;\n private envValues: Record<string, unknown> = {};\n\n /**\n * Direct access to environment variables as properties\n * @example config.env.AUTH0_CLIENT_ID\n */\n public readonly env: Record<string, unknown>;\n\n constructor(\n configObject: unknown,\n private processEnv: NodeJS.ProcessEnv = process.env\n ) {\n // Validate schema\n const result = appConfigSchema.safeParse(configObject);\n if (!result.success) {\n const issues = result.error.issues.map(i => ` - ${i.path.join('.')}: ${i.message}`).join('\\n');\n throw new Error(\n `Invalid app.config.json:\\n${issues}`\n );\n }\n\n this.config = result.data;\n\n // Skip validation during Next.js build/compilation phases\n // During these phases, we still want to read the env vars, but not fail the build if they're missing\n // NEXT_PHASE is set by Next.js during build\n const isNextBuild =\n this.processEnv.NEXT_PHASE === 'phase-production-build' ||\n this.processEnv.NEXT_PHASE === 'phase-development-server';\n\n // Always try to validate/read environment variables\n // During build, we'll be lenient about missing required vars\n this.validateEnvironment(!isNextBuild);\n\n // Create readonly proxy for env access\n this.env = new Proxy(this.envValues, {\n get: (target, prop: string) => {\n if (!(prop in target)) {\n throw new Error(\n `Environment variable \"${prop}\" is not defined in app.config.json.\\n` +\n `Available variables: ${Object.keys(target).join(', ')}`\n );\n }\n return target[prop];\n },\n set: () => {\n throw new Error('Cannot modify environment variables through config.env');\n },\n });\n }\n\n /**\n * Validate all environment variables according to config\n * @param strictRequired - Whether to throw errors for missing required vars (false during build)\n */\n private validateEnvironment(strictRequired = true): void {\n const errors: EnvValidationError[] = [];\n\n for (const [varName, definition] of Object.entries(this.config.env)) {\n try {\n const value = parseEnvValue(varName, this.processEnv[varName], definition);\n this.envValues[varName] = value;\n\n // Set default values back to process.env if they were missing\n if (this.processEnv[varName] === undefined && value !== undefined) {\n this.processEnv[varName] = String(value);\n }\n } catch (error) {\n if (error instanceof EnvValidationError) {\n // During build (strictRequired=false), skip required validation errors but still collect type errors\n if (!strictRequired && error.message.includes('Required environment variable')) {\n // Use default value or empty string if required var is missing during build\n this.envValues[varName] = definition.default ?? '';\n continue;\n }\n errors.push(error);\n } else {\n throw error;\n }\n }\n }\n\n // Throw all errors at once\n if (errors.length > 0) {\n const errorMessage = errors.map(e => `\\n❌ ${e.message}`).join('\\n');\n throw new Error(`Environment validation failed:${errorMessage}\\n`);\n }\n }\n\n /**\n * Get a typed environment variable value\n */\n get<T = unknown>(varName: string): T {\n if (!(varName in this.envValues)) {\n throw new Error(\n `Environment variable \"${varName}\" is not defined in app.config.json.\\n` +\n `Available variables: ${Object.keys(this.envValues).join(', ')}`\n );\n }\n return this.envValues[varName] as T;\n }\n\n /**\n * Get the raw app configuration\n */\n getConfig(): Readonly<AppConfig> {\n return this.config;\n }\n\n /**\n * Get all environment variables as a typed object\n */\n getAllEnv(): Readonly<Record<string, unknown>> {\n return { ...this.envValues };\n }\n\n /**\n * Check if a variable exists\n */\n has(varName: string): boolean {\n return varName in this.envValues;\n }\n}\n\n/**\n * Create a config loader from a config object\n */\nexport function createConfig(configObject: unknown): ConfigLoader {\n return new ConfigLoader(configObject);\n}\n\n/**\n * Validate app configuration and environment variables.\n * This is useful for running validation explicitly during app initialization.\n *\n * @param configObject - App configuration object (parsed from app.config.json)\n * @throws Error if configuration is invalid or required env vars are missing\n *\n * @example\n * ```typescript\n * // In your app initialization (e.g., layout.tsx or main.ts)\n * import { validate } from '@eventuras/app-config';\n * import appConfigJson from './app.config.json';\n *\n * validate(appConfigJson);\n * console.log('✓ Environment validated successfully');\n * ```\n */\nexport function validate(configObject: unknown): void {\n // Simply creating the config will trigger validation\n // If validation fails, an error will be thrown\n new ConfigLoader(configObject);\n}\n","import { AppConfig } from './types.js';\nimport { ConfigLoader } from './loader.js';\n\n/**\n * Create explicit getters for NEXT_PUBLIC_* environment variables.\n *\n * This is required for Next.js because it performs build-time replacement of\n * process.env.NEXT_PUBLIC_* variables. We must access process.env directly,\n * not through a runtime lookup.\n *\n * @param config - The app configuration\n * @returns An object with explicit getters for each NEXT_PUBLIC_* variable\n */\nexport function createPublicEnvGetters(config: AppConfig) {\n const getters: Record<string, unknown> = {};\n\n for (const [varName, definition] of Object.entries(config.env)) {\n if (definition.client && varName.startsWith('NEXT_PUBLIC_')) {\n Object.defineProperty(getters, varName, {\n get() {\n return process.env[varName];\n },\n enumerable: true,\n });\n }\n }\n\n return getters;\n}\n\nexport interface EnvironmentObject {\n validate: () => void;\n get: <T = string>(varName: string) => T;\n [key: string]: unknown;\n}\n\n/**\n * Create a complete Environment object with validation and explicit getters.\n *\n * This provides a drop-in replacement for the old Environment class pattern,\n * with automatic validation and proper Next.js NEXT_PUBLIC_* support.\n *\n * @param configPath - Path to app.config.json\n * @returns An object with validate(), get(), and explicit NEXT_PUBLIC_* getters\n */\nexport function createEnvironment(configPath: string): EnvironmentObject {\n let configInstance: ConfigLoader | null = null;\n\n function getConfigInstance(): ConfigLoader {\n if (!configInstance) {\n configInstance = new ConfigLoader(configPath);\n }\n return configInstance;\n }\n\n const publicGetters = createPublicEnvGetters(getConfigInstance().getConfig());\n\n return {\n /**\n * Validate environment (happens automatically on first access)\n */\n validate: () => {\n getConfigInstance();\n },\n\n /**\n * Get a server-side environment variable\n */\n get: <T = string>(varName: string): T => {\n return getConfigInstance().get(varName) as T;\n },\n\n // Spread in all the NEXT_PUBLIC_* getters\n ...publicGetters,\n };\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eventuras/app-config",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "Declarative environment configuration for Eventuras applications",
5
5
  "keywords": [
6
6
  "eventuras",
@@ -44,21 +44,21 @@
44
44
  "dist",
45
45
  "schema"
46
46
  ],
47
- "scripts": {
48
- "build": "vite build",
49
- "dev": "vite build --watch",
50
- "lint": "eslint src",
51
- "typecheck": "tsc --noEmit"
52
- },
53
47
  "dependencies": {
54
48
  "zod": "^4.3.6"
55
49
  },
56
50
  "devDependencies": {
57
- "@eventuras/eslint-config": "workspace:*",
58
- "@eventuras/typescript-config": "workspace:*",
59
- "@eventuras/vite-config": "workspace:*",
60
51
  "@types/node": "^24.12.0",
61
52
  "typescript": "^6.0.2",
62
- "vite": "^8.1.0"
53
+ "vite": "^8.1.0",
54
+ "@eventuras/eslint-config": "1.0.5",
55
+ "@eventuras/typescript-config": "1.1.0",
56
+ "@eventuras/vite-config": "0.4.0"
57
+ },
58
+ "scripts": {
59
+ "build": "vite build && tsc --emitDeclarationOnly",
60
+ "dev": "vite build --watch",
61
+ "lint": "eslint src",
62
+ "typecheck": "tsc --noEmit"
63
63
  }
64
- }
64
+ }