@happyvertical/smrt-config
Configuration management with cosmiconfig, secret sanitization, and SSG-safe export. Loads smrt.config.{js,ts,json} with globalThis caching so every package sees one config instance.
Overview
@happyvertical/smrt-config is the configuration backbone for the SMRT framework.
It uses cosmiconfig to load smrt.config.{js,ts,json} from the project root, deep-merges runtime
overrides on top, and caches the result on globalThis so every package -- and every
module instance -- sees the same merged config.
How It Works
loadConfig()uses cosmiconfig to findsmrt.config.{js,ts,json}- Merge priority (highest → lowest): runtime overrides (
setConfig()) >packages/modulessection > globalsmrtsection > caller defaults - The merged result is cached on
globalThis.__smrtConfigCache-- everygetConfig()/getPackageConfig()/getModuleConfig()call returns the same object
Installation
npm install @happyvertical/smrt-configQuick Start
1. Create Configuration File
Create smrt.config.js (or .ts / .json) in your project
root:
// smrt.config.js
export default {
smrt: {
cacheDir: '.cache',
logLevel: 'info',
},
packages: {
ai: {
defaultProvider: 'anthropic',
defaultModel: 'claude-sonnet-4-20250514',
apiKeys: {
anthropic: process.env.ANTHROPIC_API_KEY,
},
},
},
modules: {
'town-scraper': {
cronSchedule: '0 0 * * *',
maxPages: 100,
},
},
};2. Load Configuration
import { loadConfig } from '@happyvertical/smrt-config';
// Call once at app startup -- result is cached on globalThis
await loadConfig();3. Use Configuration
import { getPackageConfig, getModuleConfig, setConfig } from '@happyvertical/smrt-config';
// Package-scoped (used by every @happyvertical/smrt-* package)
const aiConfig = getPackageConfig('ai', {
defaultProvider: 'openai',
defaultModel: 'gpt-4',
});
// Module-scoped (per-app modules)
const scraperConfig = getModuleConfig('town-scraper', {
cronSchedule: '0 0 * * *',
maxPages: 50,
});
// Runtime override -- highest priority
setConfig({
packages: {
ai: { defaultModel: 'gpt-4-turbo' },
},
});API Reference
| Function | Purpose |
|---|---|
loadConfig(options?) | Async load from file via cosmiconfig |
getConfig() | Get the full merged config |
getPackageConfig(name, defaults?) | Package-scoped config section (used by all @happyvertical/smrt-* packages) |
getModuleConfig(name, defaults?) | Module-scoped config section (per-app modules) |
setConfig(overrides) | Runtime overrides (highest priority) |
clearCache() | Reset cached config -- affects all modules |
defineConfig(config) | Type-safe config-file helper |
exportConfig(options?) | SSG-safe export (defaults to no secrets) |
sanitizeConfig(config) | Strip keys matching: apiKey, password, secret, token, credential, private, auth, key |
getPackageConfig<T>()
This is the canonical pattern that @happyvertical/smrt-* packages use to read their
own config section (you'll see it in smrt-prompts, smrt-languages, smrt-features, smrt-core, and
many more):
import { getPackageConfig } from '@happyvertical/smrt-config';
interface AIConfig {
defaultProvider: string;
defaultModel: string;
temperature: number;
}
const aiConfig = getPackageConfig<AIConfig>('ai', {
defaultProvider: 'openai',
defaultModel: 'gpt-4',
temperature: 0.7,
});defineConfig() for type-safe config files
// smrt.config.ts
import { defineConfig } from '@happyvertical/smrt-config';
export default defineConfig({
smrt: { logLevel: 'info' },
packages: {
ai: { defaultProvider: 'anthropic' },
},
});SSG-Safe Export
exportConfig() is designed for static site generation -- it strips secrets by default
so you can safely inline config into bundled output:
import { exportConfig, sanitizeConfig } from '@happyvertical/smrt-config';
// Default: secrets stripped
const safe = exportConfig();
// Opt-in to keep secrets (rare -- typically only for internal CLIs)
const full = exportConfig({ includeSecrets: true });
// Manual sanitization (strips keys matching:
// apiKey, password, secret, token, credential, private, auth, key)
const sanitized = sanitizeConfig(config);Testing
Use setConfig() to inject test-specific values and clearCache() in afterEach to reset state between tests:
import { beforeEach, afterEach, test, expect } from 'vitest';
import { setConfig, clearCache, getPackageConfig } from '@happyvertical/smrt-config';
beforeEach(() => {
setConfig({
packages: {
ai: { defaultProvider: 'mock', defaultModel: 'test-model' },
},
});
});
afterEach(() => {
clearCache(); // reset shared state
});
test('uses test configuration', () => {
const config = getPackageConfig('ai');
expect(config.defaultProvider).toBe('mock');
});Key Files
src/loader.ts-- cosmiconfig integration and file discoverysrc/merge.ts-- deep-merge logic and runtime config storesrc/export.ts-- sanitization and JSON/JS export formattingsrc/types.ts-- full config schema (~800 lines)
Gotchas
clearCache()is global: it affects every package and module reading fromglobalThis.__smrtConfigCache. Call it carefully outside of tests.- SSG export defaults to no secrets: you must explicitly pass
{ includeSecrets: true }to keep them. - Deep merge: later values override earlier ones at each key level (objects merge, scalars/arrays replace).
- Reference via env vars: keep secrets in
process.envand reference them fromsmrt.config.js. Don't hardcode secrets in the config file.
Used By
Every @happyvertical/smrt-* package that needs configuration reads its section
via getPackageConfig(). Recent consumers include:
- smrt-core -- AI provider, database defaults
- smrt-cli -- CLI entry point and DB config
- smrt-prompts, smrt-languages, smrt-features -- prompts/locales/feature flags