@happyvertical/smrt-config 0.34.8 → 0.34.9
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/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../src/loader.ts","../src/merge.ts","../src/export.ts","../src/index.ts"],"sourcesContent":["import { cosmiconfig } from 'cosmiconfig';\nimport type { LoadConfigOptions, SmrtConfig } from './types.js';\n\nconst MODULE_NAME = 'smrt';\n\n/**\n * Extend globalThis to include loader cache properties.\n * Using globalThis ensures all module instances share the same loader state,\n * which is critical in monorepos where the same package can be loaded\n * from different paths (e.g., pnpm store vs workspace symlink).\n *\n * @see https://github.com/happyvertical/smrt/issues/543\n */\ndeclare global {\n // eslint-disable-next-line no-var\n var __smrtLoaderCachedConfig: SmrtConfig | null | undefined;\n // eslint-disable-next-line no-var\n var __smrtLoaderExplorer: ReturnType<typeof cosmiconfig> | null | undefined;\n}\n\n/** Retrieve the cached config from the globalThis singleton store. */\nfunction getCachedConfig(): SmrtConfig | null {\n return globalThis.__smrtLoaderCachedConfig ?? null;\n}\n\n/** Write the config (or null to invalidate) into the globalThis singleton store. */\nfunction setCachedConfig(config: SmrtConfig | null): void {\n globalThis.__smrtLoaderCachedConfig = config;\n}\n\n/** Retrieve the cosmiconfig explorer instance from the globalThis singleton store. */\nfunction getExplorer(): ReturnType<typeof cosmiconfig> | null {\n return globalThis.__smrtLoaderExplorer ?? null;\n}\n\n/** Write the cosmiconfig explorer instance into the globalThis singleton store. */\nfunction setExplorer(exp: ReturnType<typeof cosmiconfig> | null): void {\n globalThis.__smrtLoaderExplorer = exp;\n}\n\n/**\n * Load and parse configuration from the project root using cosmiconfig.\n *\n * Searches for `smrt.config.{js,mjs,cjs,json}` starting from `cwd`, walking\n * up the directory tree unless `searchParents` is `false`. The result is\n * cached in `globalThis.__smrtLoaderCachedConfig` so that all modules sharing\n * the same runtime (including pnpm workspace symlinks) see the same config.\n *\n * Returns an empty object (`{}`) when no config file is found or when the\n * file fails to parse — callers should always treat every field as optional.\n *\n * @param options - Search and caching options.\n * @returns The parsed {@link SmrtConfig}, or `{}` if no file is found.\n *\n * @example\n * ```ts\n * import { loadConfig } from '@happyvertical/smrt-config';\n *\n * const config = await loadConfig();\n * console.log(config.smrt?.logLevel); // 'debug'\n * ```\n *\n * @example Load a specific file (useful in tests):\n * ```ts\n * const config = await loadConfig({ configPath: './fixtures/smrt.config.js', cache: false });\n * ```\n *\n * @see {@link LoadConfigOptions}\n * @see {@link clearConfigCache}\n */\nexport async function loadConfig(\n options: LoadConfigOptions = {},\n): Promise<SmrtConfig> {\n const { configPath, searchParents = true, cache = true } = options;\n\n // Return cached config if available\n const cached = getCachedConfig();\n if (cache && cached) {\n return cached;\n }\n\n // Initialize or reuse cosmiconfig explorer\n let explorer = getExplorer();\n if (!explorer || !cache) {\n explorer = cosmiconfig(MODULE_NAME, {\n searchPlaces: [\n `${MODULE_NAME}.config.js`,\n `${MODULE_NAME}.config.mjs`,\n `${MODULE_NAME}.config.cjs`,\n `${MODULE_NAME}.config.json`,\n ],\n stopDir: searchParents ? undefined : process.cwd(),\n cache: cache, // Respect cache option\n });\n setExplorer(explorer);\n }\n\n let result: Awaited<ReturnType<typeof explorer.load>> = null;\n\n // Load from specific path or search\n try {\n if (configPath) {\n result = await explorer.load(configPath);\n } else {\n result = await explorer.search();\n }\n } catch (_error) {\n // Return empty config on error\n return {};\n }\n\n const config: SmrtConfig = result?.config || {};\n\n // Cache the config\n if (cache) {\n setCachedConfig(config);\n }\n\n return config;\n}\n\n/**\n * Clear the internal loader cache.\n *\n * Resets `globalThis.__smrtLoaderCachedConfig` and invalidates the cosmiconfig\n * explorer so that the next `loadConfig()` call performs a fresh file search.\n *\n * This is a low-level helper. Consumer code should call {@link clearCache}\n * from `index.ts` instead, which also clears the runtime-override store.\n *\n * @see {@link clearCache}\n */\nexport function clearConfigCache(): void {\n setCachedConfig(null);\n\n // Clear cosmiconfig's cache\n const explorer = getExplorer();\n if (explorer) {\n explorer.clearCaches();\n setExplorer(null);\n }\n}\n","import type { SmrtConfig } from './types.js';\n\ndeclare global {\n // eslint-disable-next-line no-var\n var __smrtRuntimeConfig: Partial<SmrtConfig> | undefined;\n}\n\n// Runtime config overrides\nglobalThis.__smrtRuntimeConfig ??= {};\n\n/**\n * Deep-merge two plain objects, with `source` values taking precedence over\n * `target` values at each key level.\n *\n * Rules:\n * - Both values are non-array objects → recurse.\n * - `source` value is `null` or `undefined` → keep the `target` value.\n * - Otherwise → `source` value replaces `target` value (including `false`, `0`, `''`).\n *\n * @param target - Base object (lower priority).\n * @param source - Override object (higher priority).\n * @returns A new merged object.\n */\nfunction deepMerge<T extends Record<string, any>>(\n target: T,\n source: Partial<T>,\n): T {\n // Typed as Record<string, unknown> (not the generic T) so the own-key writes\n // below are well-typed — indexing a generic T for *write* is a TS2862 error.\n const result: Record<string, unknown> = { ...target };\n const src = source as Record<string, unknown>;\n\n for (const key of Object.keys(src)) {\n // Guard against prototype pollution when merging untrusted/DB-exported\n // input: never write to __proto__/constructor/prototype keys.\n if (key === '__proto__' || key === 'constructor' || key === 'prototype') {\n continue;\n }\n const sourceValue = src[key];\n const targetValue = result[key];\n\n if (\n sourceValue &&\n typeof sourceValue === 'object' &&\n !Array.isArray(sourceValue) &&\n targetValue &&\n typeof targetValue === 'object' &&\n !Array.isArray(targetValue)\n ) {\n result[key] = deepMerge(\n targetValue as Record<string, any>,\n sourceValue as Record<string, any>,\n );\n } else if (sourceValue !== undefined && sourceValue !== null) {\n result[key] = sourceValue;\n }\n }\n\n return result as T;\n}\n\n/**\n * Deep-merge `config` into the in-memory runtime override store.\n *\n * Runtime overrides take the highest priority in the merge order:\n * runtime > file config > defaults. Subsequent calls accumulate — they do not\n * replace previous overrides. Call {@link clearRuntimeConfig} to reset.\n *\n * This is the low-level setter; consumer code should call {@link setConfig}\n * from `index.ts` which delegates here.\n *\n * @param config - Partial config to merge into the runtime store.\n *\n * @see {@link clearRuntimeConfig}\n * @see {@link getRuntimeConfig}\n */\nexport function setConfig(config: Partial<SmrtConfig>): void {\n globalThis.__smrtRuntimeConfig = deepMerge(\n globalThis.__smrtRuntimeConfig || {},\n config,\n );\n}\n\n/**\n * Return the current runtime configuration override store.\n *\n * Used internally by {@link getModuleConfig} and {@link getPackageConfig} to\n * layer runtime overrides on top of file-based config. Prefer those helpers\n * for reading config; this is a low-level accessor.\n *\n * @returns The accumulated runtime config partial.\n */\nexport function getRuntimeConfig(): Partial<SmrtConfig> {\n return globalThis.__smrtRuntimeConfig || {};\n}\n\n/**\n * Reset the runtime configuration override store to an empty object.\n *\n * Called by {@link clearCache} in `index.ts` as part of a full cache reset.\n * Useful in tests to ensure a clean state between cases.\n *\n * @see {@link setConfig}\n */\nexport function clearRuntimeConfig(): void {\n globalThis.__smrtRuntimeConfig = {};\n}\n\n/**\n * Merge three config layers in ascending priority order.\n *\n * Priority (highest to lowest):\n * 1. `runtime` — runtime overrides set via {@link setConfig}\n * 2. `fileConfig` — values loaded from `smrt.config.js`\n * 3. `defaults` — caller-supplied fallback values\n *\n * Uses {@link deepMerge} internally, so `null` / `undefined` source values\n * never overwrite existing target values.\n *\n * @param defaults - Lowest-priority base values (caller defaults).\n * @param fileConfig - Mid-priority values from the loaded config file.\n * @param runtime - Highest-priority runtime override values.\n * @returns A new object with all three layers merged.\n *\n * @example\n * ```ts\n * const merged = mergeConfigs(\n * { timeout: 5000, retries: 3 },\n * { retries: 5 },\n * { timeout: 1000 },\n * );\n * // => { timeout: 1000, retries: 5 }\n * ```\n */\nexport function mergeConfigs<T extends Record<string, unknown>>(\n defaults: T,\n fileConfig: Partial<T>,\n runtime: Partial<T>,\n): T {\n let result = { ...defaults };\n result = deepMerge(result, fileConfig);\n result = deepMerge(result, runtime);\n return result;\n}\n","/**\n * Config export utilities for static site generation\n *\n * These utilities enable exporting database-backed agent configurations\n * to static files for SSG sites.\n *\n * @module\n */\n\n/**\n * Patterns that identify sensitive configuration keys. Any key matching one of\n * these patterns is stripped during {@link sanitizeConfig}.\n *\n * Redaction policy\n * ----------------\n * These exports feed static-site-generation (SSG) artifacts that are typically\n * served publicly, so the cost of leaking a secret is far higher than the cost\n * of over-redacting a benign key. We therefore **bias toward over-redaction**:\n * the patterns are mostly case-insensitive *substring* matches (no word\n * boundaries) so they catch every common casing/separator convention —\n * `apiKey`, `api_key`, `API-KEY`, `apikey`, etc. A few are deliberately scoped\n * to avoid benign collisions: `\\bcert\\b` (not `concert`), and `oauth` /\n * `authorization` rather than a bare `auth` (which would hit `author`/`authors`).\n *\n * The previous implementation used `\\b` word boundaries (e.g. `/\\bauth\\b/i`,\n * `/\\bkey\\b$/i`). In JS regex `\\b` sits between a word char and a non-word char,\n * and `_`/letters/digits are all word chars, so those boundaries never fire\n * inside `camelCase` or `snake_case` identifiers — leaking keys like `api_key`,\n * `accessKey`, `authorization`, `auth_token`, `connectionString`, and `dbUrl`.\n * See issue #1357.\n *\n * To keep over-redaction targeted (rather than redacting on a bare `key`/`url`\n * substring, which would strip benign `apiEndpoint`-style nesting that legit\n * config relies on, e.g. `db: { url }`), `key`/`url` only trigger when paired\n * with a secret-adjacent qualifier (`apiKey`, `accessKey`, `dbUrl`, …).\n */\nconst SECRET_PATTERNS = [\n // Direct secret-bearing terms (substring, any casing/separator).\n /secret/i,\n /password/i,\n /passwd/i,\n // Short credential aliases (#1381, codex audit cross-finding): `pass` / `pwd`\n // keys that `password`/`passwd` don't cover. Segment- or camelCase-anchored\n // (a capital after a letter/digit) so `smtpPass`/`databasePwd`/`db_pass`/`pwd`\n // are stripped while `compass`/`bypass`/`passive` are not.\n /(^|[_-])pass([_-]|$)/i,\n /(^|[_-])pwd([_-]|$)/i,\n /[a-z0-9]Pass([_-]|$)/,\n /[a-z0-9]Pwd([_-]|$)/,\n /^(pass|pwd)$/i,\n /token/i,\n /credential/i,\n /private/i,\n // NB: not a bare /auth/i — that also matches benign `author`/`authors`\n // metadata (common in site/content/agent config). The segment-anchored form\n // strips a key literally named `auth` (and `auth_token`, `x_auth`,\n // `oauth_token`) without hitting `author`; `oauth`/`authorization` cover the\n // no-separator camelCase cases. (authToken/authSecret also hit /token//secret/.)\n /oauth/i,\n /authorization/i,\n /(^|[_-])auth([_-]|$)/i,\n /cookie/i,\n /salt/i,\n /passphrase/i,\n /\\bcert\\b/i, // cert/certificate, but not \"uncertain\"/\"concert\" style false hits\n /certificate/i,\n // Qualified \"key\" — only when paired with a secret-adjacent prefix, so we\n // strip apiKey/accessKey/signingKey/encryptionKey/privateKey but keep benign\n // keys such as `keywords`, `monkey`, or `keyboardShortcuts`.\n /api[_-]?key/i,\n /access[_-]?key/i,\n /secret[_-]?key/i,\n /signing[_-]?key/i,\n /encryption[_-]?key/i,\n /private[_-]?key/i,\n /public[_-]?key/i,\n // camelCase `<vendor>Key` keys — `consumerKey`, `masterKey`, `sslKey`,\n // `hmacKey`, `sharedKey`, `rootKey`, `clientKey`, `serverKey`, `deployKey`,\n // `sshKey`, etc. The hardcoded prefixes above and the segment-anchored\n // standalone-`key` pattern below only fire on a `_`/`-`/start boundary, so a\n // camelCase secret key with an unlisted vendor prefix slips through (a raw\n // hex/base64 secret then exports verbatim into the public SSG artifact).\n // Mirrors the `[a-z0-9]Pass`/`[a-z0-9]Pwd` idiom: a capital `K` after a\n // letter/digit, followed by a segment/`Id`/end boundary, so `consumerKey` and\n // `masterKeyId` are stripped while benign `keywords`/`monkey`/\n // `keyboardShortcuts` (lowercase `key`) are kept.\n /[a-z0-9]Key([_-]|Id|$)/,\n /(^|[_-])key([_-]|id$|$)/i, // standalone `key`, `key_id`, `keyId`, `aws…KeyId`\n // Connection / database URLs that commonly embed credentials.\n /connection[_-]?string/i,\n /conn[_-]?str/i,\n /(^|[_-])db[_-]?url/i, // dbUrl / db_url / DB_URL\n /database[_-]?url/i,\n /datasource[_-]?url/i,\n];\n\n/** Return `true` when `key` matches any of the {@link SECRET_PATTERNS}. */\nfunction isSecretKey(key: string): boolean {\n return SECRET_PATTERNS.some((pattern) => pattern.test(key));\n}\n\n// Credentials embedded in the *value* of an otherwise-benign key — most\n// importantly DB connection strings like `postgres://user:pass@host:5432/db`\n// stored under keys such as `url` / `database.url` that the key-based\n// {@link SECRET_PATTERNS} deliberately don't match (#1381). Key-based redaction\n// alone leaks these into the published SSG artifact. Mask the userinfo\n// (`scheme://user:pass@host` → `scheme://***@host`) while preserving the\n// non-secret host so the value stays diagnosable. Not anchored/global so it\n// also catches a credential URL embedded mid-string (e.g. `dsn: postgres://u:p@h`)\n// or multiple URLs in one value (review #1549).\nconst CREDENTIAL_URL_RE = /([a-z][a-z0-9+.-]*:\\/\\/)[^/?#@\\s]+@/gi;\n\n/**\n * High-confidence secret *token* shapes that can appear in the value of an\n * otherwise-benign key (#1381). Key-based {@link SECRET_PATTERNS} only catch a\n * secret when the *key* is named like one — a provider token pasted into a\n * `note`, `header`, `instructions`, or `defaultModel`-adjacent field leaks\n * verbatim into the published SSG artifact. These patterns match the\n * distinctive prefixes/structures of real credentials so we can mask the token\n * while leaving surrounding prose intact. They are intentionally specific\n * (prefix- or structure-anchored) to avoid mangling benign config values.\n *\n * Coverage:\n * - OpenAI / Anthropic style `sk-...` and `sk-proj-...` (and `sk-ant-...`)\n * - GitHub PATs / app tokens: `ghp_ gho_ ghu_ ghs_ ghr_` + fine-grained `github_pat_`\n * - Slack tokens: `xox[abprs]-...`\n * - Google API keys: `AIza...`\n * - AWS access key IDs: `AKIA`/`ASIA` + 16 uppercase alnum\n * - Stripe live/test keys: `sk_live_` / `sk_test_` / `rk_live_` / `rk_test_`\n * - `Bearer <token>` Authorization header values\n * - PEM private-key blocks\n */\nconst VALUE_SECRET_PATTERNS: Array<{ re: RegExp; replace: string }> = [\n // PEM private key block (multi-line) — collapse the whole block.\n {\n re: /-----BEGIN (?:[A-Z ]+ )?PRIVATE KEY-----[\\s\\S]*?-----END (?:[A-Z ]+ )?PRIVATE KEY-----/g,\n replace: '***REDACTED PRIVATE KEY***',\n },\n // OpenAI / Anthropic secret keys: sk-, sk-proj-, sk-ant-...\n { re: /\\bsk-(?:proj-|ant-)?[A-Za-z0-9_-]{16,}/g, replace: '***' },\n // Stripe-style prefixed keys.\n { re: /\\b[sr]k_(?:live|test)_[A-Za-z0-9]{16,}/g, replace: '***' },\n // GitHub tokens.\n { re: /\\bgh[pousr]_[A-Za-z0-9]{20,}/g, replace: '***' },\n { re: /\\bgithub_pat_[A-Za-z0-9_]{22,}/g, replace: '***' },\n // Slack tokens.\n { re: /\\bxox[abprs]-[A-Za-z0-9-]{10,}/g, replace: '***' },\n // Google API keys.\n { re: /\\bAIza[A-Za-z0-9_-]{35}/g, replace: '***' },\n // AWS access key IDs.\n { re: /\\b(?:AKIA|ASIA)[A-Z0-9]{16}\\b/g, replace: '***' },\n // Bearer / JWT authorization values (header strings pasted into config).\n // Case-insensitive: HTTP auth schemes are case-insensitive and clients often\n // emit lowercase `bearer ` (review #1549).\n {\n re: /\\bBearer\\s+[A-Za-z0-9._~+/=-]{16,}/gi,\n replace: 'Bearer ***',\n },\n];\n\n/**\n * Redact credentials embedded in a string value: URL userinfo plus any\n * high-confidence secret-token shape (see {@link VALUE_SECRET_PATTERNS}).\n */\nfunction redactValueSecrets(value: string): string {\n let out = value.replace(CREDENTIAL_URL_RE, '$1***@');\n for (const { re, replace } of VALUE_SECRET_PATTERNS) {\n out = out.replace(re, replace);\n }\n return out;\n}\n\n/**\n * Deep-clone a config object, removing every key that looks like a secret.\n *\n * Keys are tested (case-insensitively, across camelCase / snake_case / kebab /\n * UPPER variants) against {@link SECRET_PATTERNS}, which cover secrets such as\n * `apiKey`/`api_key`, `password`, `secret`, `token`, `credential`, `private`,\n * `auth`/`authorization`/`oauth`, `accessKey`, `signingKey`, `encryptionKey`,\n * `connectionString`, and `dbUrl`. The policy biases toward over-redaction\n * because these exports are typically published as static, publicly served\n * assets — see {@link SECRET_PATTERNS} for the full rationale.\n *\n * Nested objects are recursively sanitized; arrays are mapped element-by-element.\n * `number` / `boolean` primitives pass through unchanged; `string` values are\n * additionally run through value-level redaction that masks credentials embedded\n * in a URL (`scheme://user:pass@host` → `scheme://***@host`) as well as\n * high-confidence secret-token shapes pasted into otherwise-benign keys\n * (OpenAI/Anthropic `sk-...`, AWS `AKIA...` access key IDs, GitHub/Slack/Google\n * tokens, `Bearer ...` headers, and PEM private-key blocks) — catching secrets\n * stored under a benign key the key-based patterns don't match.\n *\n * Own `__proto__` / `constructor` / `prototype` keys are dropped to prevent the\n * cloned result's prototype from being reassigned via the `__proto__` setter.\n *\n * This function is called automatically by {@link exportConfig} unless\n * `includeSecrets: true` is passed.\n *\n * @param config - Value to sanitize. Accepts any JSON-serializable structure.\n * @returns A new value with all secret keys omitted. `null` / `undefined` are\n * returned as-is.\n *\n * @example\n * ```ts\n * const sanitized = sanitizeConfig({\n * apiEndpoint: 'https://api.example.com',\n * apiKey: 'sk-secret-123',\n * nested: { password: 'hunter2', name: 'test' },\n * });\n * // => { apiEndpoint: 'https://api.example.com', nested: { name: 'test' } }\n * ```\n *\n * @see {@link exportConfig}\n */\nexport function sanitizeConfig(config: unknown): unknown {\n if (config === null || config === undefined) {\n return config;\n }\n\n if (Array.isArray(config)) {\n return config.map((item) => sanitizeConfig(item));\n }\n\n if (typeof config === 'object') {\n const result: Record<string, unknown> = {};\n\n for (const [key, value] of Object.entries(config)) {\n // Guard against prototype pollution: a `__proto__` own-key (e.g. from a\n // DB-backed config round-tripped through JSON.parse) would, via the\n // `result[key] = ...` setter, silently reassign the prototype of the\n // sanitized object we hand back to callers. `constructor`/`prototype`\n // are harmless as plain keys but dropped for parity with the merge\n // guards (deepMerge / mergeExportedConfig). See #1381.\n if (key === '__proto__' || key === 'constructor' || key === 'prototype') {\n continue;\n }\n\n // Skip keys that match secret patterns\n if (isSecretKey(key)) {\n continue;\n }\n\n // Recursively sanitize nested objects\n result[key] = sanitizeConfig(value);\n }\n\n return result;\n }\n\n // String primitives get value-level secret redaction (credentials embedded in\n // URLs under benign keys, #1381); other primitives pass through unchanged.\n if (typeof config === 'string') {\n return redactValueSecrets(config);\n }\n return config;\n}\n\n/**\n * Options accepted by {@link exportConfig}.\n */\nexport interface ExportConfigOptions {\n /**\n * Include secrets in the export (use with caution!)\n * @default false\n */\n includeSecrets?: boolean;\n\n /**\n * Output format\n * - 'json': Plain JSON format\n * - 'js': ES module format (export default)\n * @default 'json'\n */\n format?: 'json' | 'js';\n\n /**\n * Number of spaces for indentation\n * @default 2\n */\n indent?: number;\n}\n\n/**\n * Serialize a configuration object to a formatted string for SSG file output.\n *\n * By default, {@link sanitizeConfig} is applied before serialization to strip\n * any keys that match secret patterns (API keys, passwords, tokens, etc.).\n * Pass `includeSecrets: true` only in secured server-side contexts.\n *\n * @param config - Configuration object to export. Any JSON-serializable value.\n * @param options - Format, indentation, and secret-inclusion options.\n * @returns A JSON string, or an ES module string (`export default {...};\\n`)\n * when `format: 'js'`.\n *\n * @example\n * ```ts\n * // Write a sanitized JSON export file for SSG\n * const json = exportConfig(agentConfig);\n * await fs.writeFile('./public/config.json', json);\n * ```\n *\n * @example\n * ```ts\n * // ES module format (importable via `import config from './config.js'`)\n * const js = exportConfig(agentConfig, { format: 'js' });\n * ```\n *\n * @see {@link sanitizeConfig}\n * @see {@link parseExportedConfig}\n * @see {@link ExportConfigOptions}\n */\nexport function exportConfig(\n config: unknown,\n options: ExportConfigOptions = {},\n): string {\n const { includeSecrets = false, format = 'json', indent = 2 } = options;\n\n // Sanitize unless secrets are explicitly requested\n const exportData = includeSecrets ? config : sanitizeConfig(config);\n\n const jsonString = JSON.stringify(exportData, null, indent);\n\n if (format === 'js') {\n return `export default ${jsonString};\\n`;\n }\n\n return jsonString;\n}\n\n/**\n * Parse a config string previously produced by {@link exportConfig} back into\n * a plain JavaScript object.\n *\n * Handles both output formats:\n * - **JSON** — standard `JSON.parse`.\n * - **JS module** — strips the `export default` prefix and trailing semicolon\n * before parsing as JSON.\n *\n * Throws a `SyntaxError` if the content is not valid JSON after stripping.\n *\n * @param content - Exported config string (`'json'` or `'js'` format).\n * @returns Parsed configuration value.\n *\n * @example\n * ```ts\n * const raw = await fs.readFile('./smrt.exported.json', 'utf8');\n * const config = parseExportedConfig(raw);\n * ```\n *\n * @see {@link exportConfig}\n * @see {@link mergeExportedConfig}\n */\nexport function parseExportedConfig(content: string): unknown {\n // Handle JS module format (strip export default and trailing semicolon)\n const trimmed = content.trim();\n if (trimmed.startsWith('export default')) {\n const jsonPart = trimmed\n .replace(/^export default\\s*/, '')\n .replace(/;\\s*$/, '');\n return JSON.parse(jsonPart);\n }\n\n // Plain JSON\n return JSON.parse(content);\n}\n\n/**\n * Shallow-merge an exported (DB-backed) config into a base (file-backed) config.\n *\n * Object values are deep-merged recursively. Primitive values and arrays from\n * `exportedConfig` replace their counterparts in `baseConfig`. This is a\n * one-level-smarter spread that lets file-based env overrides win when placed\n * after the spread.\n *\n * Intended for the SSG pattern where an agent exports its runtime config to a\n * static JSON file and `smrt.config.js` imports it as a base:\n *\n * @example\n * ```js\n * // smrt.config.js\n * import exported from './smrt.exported.json' with { type: 'json' };\n *\n * export default defineConfig({\n * modules: {\n * praeco: mergeExportedConfig(exported, {\n * apiEndpoint: process.env.API_URL, // env override wins\n * }),\n * },\n * });\n * ```\n *\n * @param baseConfig - Lower-priority base (typically from the DB export file).\n * @param exportedConfig - Higher-priority overrides (typically env / file-based).\n * @returns A new merged object of type `T`.\n *\n * @see {@link exportConfig}\n * @see {@link parseExportedConfig}\n */\nexport function mergeExportedConfig<T extends Record<string, unknown>>(\n baseConfig: T,\n exportedConfig: Partial<T>,\n): T {\n const result = { ...baseConfig };\n\n for (const [key, value] of Object.entries(exportedConfig)) {\n // Guard against prototype pollution when merging untrusted/DB-exported\n // input: never write to __proto__/constructor/prototype keys.\n if (key === '__proto__' || key === 'constructor' || key === 'prototype') {\n continue;\n }\n if (\n value !== null &&\n typeof value === 'object' &&\n !Array.isArray(value) &&\n result[key] !== null &&\n typeof result[key] === 'object' &&\n !Array.isArray(result[key])\n ) {\n // Deep merge objects\n (result as Record<string, unknown>)[key] = mergeExportedConfig(\n result[key] as Record<string, unknown>,\n value as Record<string, unknown>,\n );\n } else {\n // Override primitive values and arrays\n (result as Record<string, unknown>)[key] = value;\n }\n }\n\n return result;\n}\n","import { loadConfig as _loadConfig, clearConfigCache } from './loader.js';\nimport {\n setConfig as _setConfig,\n clearRuntimeConfig,\n getRuntimeConfig,\n mergeConfigs,\n} from './merge.js';\nimport type { LoadConfigOptions, SmrtConfig } from './types.js';\n\n// Re-export config export utilities\nexport {\n type ExportConfigOptions,\n exportConfig,\n mergeExportedConfig,\n parseExportedConfig,\n sanitizeConfig,\n} from './export.js';\nexport { clearRuntimeConfig, getRuntimeConfig, mergeConfigs } from './merge.js';\n\n// Re-export types\nexport type {\n // CLI and migrations configuration types\n CliConfig,\n DatabaseConfig,\n // Export configuration types\n ExportConfig,\n ExportFileConfig,\n ExportFilterValue,\n LoadConfigOptions,\n MigrationsConfig,\n MigrationsPostgresConfig,\n SchemaContractConfig,\n // Site configuration types\n SiteConfig,\n SiteLocation,\n SiteNavigation,\n SiteNavigationLink,\n SitePublisher,\n SiteTheme,\n SmrtConfig,\n SmrtGlobalConfig,\n} from './types.js';\n\n/**\n * Extend globalThis to include our config cache properties.\n * Using globalThis ensures all module instances share the same config,\n * which is critical in monorepos where the same package can be loaded\n * from different paths (e.g., pnpm store vs workspace symlink).\n *\n * This fixes issue #543: external packages not receiving user config\n * when smrt-cli loads them.\n *\n * @see https://github.com/happyvertical/smrt/issues/543\n */\ndeclare global {\n // eslint-disable-next-line no-var\n var __smrtConfigCache: SmrtConfig | null | undefined;\n // eslint-disable-next-line no-var\n var __smrtRuntimeConfig: Partial<SmrtConfig> | undefined;\n}\n\n// Use globalThis for cross-module config sharing\n// This ensures loadConfig() in smrt-cli affects all packages that use smrt-config\nglobalThis.__smrtConfigCache ??= null;\n\n/**\n * Get the cached config from globalThis\n */\nfunction getLoadedConfig(): SmrtConfig | null {\n return globalThis.__smrtConfigCache ?? null;\n}\n\n/**\n * Set the cached config in globalThis\n */\nfunction setLoadedConfig(config: SmrtConfig | null): void {\n globalThis.__smrtConfigCache = config;\n}\n\n/**\n * Load and parse configuration from the project root.\n *\n * Searches for `smrt.config.{js,mjs,cjs,json}` starting from the current\n * working directory and walking up the tree (unless `searchParents: false`).\n * The result is stored in `globalThis.__smrtConfigCache` so every module in\n * the process — including pnpm workspace packages loaded from different paths\n * — sees the same instance.\n *\n * Must be called before {@link getConfig}, {@link getModuleConfig}, or\n * {@link getPackageConfig} return meaningful values (they fall back to an\n * empty config if called before loading).\n *\n * @param options - Optional search path, parent traversal, and cache control.\n * @returns The parsed {@link SmrtConfig}. Returns `{}` if no file is found.\n *\n * @example\n * ```ts\n * // Typical app startup\n * import { loadConfig } from '@happyvertical/smrt-config';\n * await loadConfig();\n * ```\n *\n * @example\n * ```ts\n * // Load a specific file (tests / scripts)\n * const config = await loadConfig({ configPath: './fixtures/smrt.config.js', cache: false });\n * ```\n *\n * @see {@link getConfig}\n * @see {@link clearCache}\n * @see {@link LoadConfigOptions}\n */\nexport async function loadConfig(\n options?: LoadConfigOptions,\n): Promise<SmrtConfig> {\n const config = await _loadConfig(options);\n // Always update config cache (even if caching is disabled)\n setLoadedConfig(config);\n return config;\n}\n\n/**\n * Return the full merged configuration synchronously.\n *\n * Reads from `globalThis.__smrtConfigCache` without triggering a file search.\n * Returns `null` when {@link loadConfig} has not been called yet (or after\n * {@link clearCache} resets the cache).\n *\n * @returns The cached {@link SmrtConfig}, or `null` if not yet loaded.\n *\n * @example\n * ```ts\n * const config = getConfig();\n * if (config?.smrt?.logLevel === 'debug') {\n * console.log('Debug logging enabled');\n * }\n * ```\n *\n * @see {@link loadConfig}\n * @see {@link getSiteConfig}\n * @see {@link getModuleConfig}\n */\nexport function getConfig(): SmrtConfig | null {\n return getLoadedConfig();\n}\n\n/**\n * Return the `site` section of the loaded configuration.\n *\n * Equivalent to `getConfig()?.site ?? null`. Returns `null` both when no\n * config has been loaded and when the loaded config has no `site` key.\n *\n * @returns The {@link SiteConfig} object, or `null` if not defined.\n *\n * @example\n * ```ts\n * const site = getSiteConfig();\n * const title = site?.name ?? 'Untitled Site';\n * ```\n *\n * @see {@link SiteConfig}\n * @see {@link getConfig}\n */\nexport function getSiteConfig(): import('./types.js').SiteConfig | null {\n const config = getLoadedConfig() || {};\n return config.site || null;\n}\n\n/**\n * Return the resolved configuration for a named module.\n *\n * Merges four layers in ascending priority order:\n * 1. `defaults` (caller-supplied fallbacks)\n * 2. Global `smrt` section of the loaded config\n * 3. `modules[moduleName]` section of the loaded config\n * 4. Runtime `modules[moduleName]` overrides set via {@link setConfig}\n *\n * Works synchronously — call {@link loadConfig} first to populate the cache.\n * If no config has been loaded, only `defaults` are returned.\n *\n * @param moduleName - Key in `config.modules` (e.g., `'praeco'`).\n * @param defaults - Optional fallback values used when a key is absent from all\n * higher-priority layers.\n * @returns The fully merged module config typed as `T`.\n *\n * @example\n * ```ts\n * const config = getModuleConfig('praeco', {\n * rssLimit: 50,\n * fetchTimeout: 10_000,\n * });\n * // config.rssLimit may be overridden by smrt.config.js or setConfig()\n * ```\n *\n * @see {@link getPackageConfig}\n * @see {@link setConfig}\n * @see {@link SmrtConfig.modules}\n */\nexport function getModuleConfig<T extends Record<string, unknown>>(\n moduleName: string,\n defaults?: T,\n): T {\n // Ensure config is loaded (will use empty config if not loaded)\n const fileConfig = getLoadedConfig() || {};\n const runtime = getRuntimeConfig();\n\n // Get global smrt config\n const globalConfig = (fileConfig.smrt || {}) as Partial<T>;\n\n // Get module-specific config\n const moduleConfig = (fileConfig.modules?.[moduleName] || {}) as Partial<T>;\n\n // Get runtime module config\n const runtimeModuleConfig = (runtime.modules?.[moduleName] ||\n {}) as Partial<T>;\n\n // Merge: defaults < global < module < runtime\n const defaultsWithGlobal = mergeConfigs(\n defaults || ({} as T),\n globalConfig,\n {},\n );\n const withModuleConfig = mergeConfigs(defaultsWithGlobal, moduleConfig, {});\n const final = mergeConfigs(withModuleConfig, runtimeModuleConfig, {});\n\n return final;\n}\n\n/**\n * Return the resolved configuration for a named package.\n *\n * Identical to {@link getModuleConfig} but reads from `config.packages` instead\n * of `config.modules`. Use this inside `@happyvertical/smrt-*` packages that\n * want to expose their own config section without conflicting with user-defined\n * module names.\n *\n * Merges four layers in ascending priority order:\n * 1. `defaults` (caller-supplied fallbacks)\n * 2. Global `smrt` section of the loaded config\n * 3. `packages[packageName]` section of the loaded config\n * 4. Runtime `packages[packageName]` overrides set via {@link setConfig}\n *\n * @param packageName - Key in `config.packages` (e.g., `'ai'`, `'spider'`).\n * @param defaults - Optional fallback values.\n * @returns The fully merged package config typed as `T`.\n *\n * @example\n * ```ts\n * // Inside @happyvertical/smrt-ai package\n * const config = getPackageConfig('ai', {\n * defaultModel: 'gpt-4o-mini',\n * maxTokens: 2048,\n * });\n * ```\n *\n * @see {@link getModuleConfig}\n * @see {@link setConfig}\n * @see {@link SmrtConfig.packages}\n */\nexport function getPackageConfig<T extends Record<string, unknown>>(\n packageName: string,\n defaults?: T,\n): T {\n // Ensure config is loaded (will use empty config if not loaded)\n const fileConfig = getLoadedConfig() || {};\n const runtime = getRuntimeConfig();\n\n // Get global smrt config\n const globalConfig = (fileConfig.smrt || {}) as Partial<T>;\n\n // Get package-specific config\n const packageConfig = (fileConfig.packages?.[packageName] ||\n {}) as Partial<T>;\n\n // Get runtime package config\n const runtimePackageConfig = (runtime.packages?.[packageName] ||\n {}) as Partial<T>;\n\n // Merge: defaults < global < package < runtime\n const defaultsWithGlobal = mergeConfigs(\n defaults || ({} as T),\n globalConfig,\n {},\n );\n const withPackageConfig = mergeConfigs(defaultsWithGlobal, packageConfig, {});\n const final = mergeConfigs(withPackageConfig, runtimePackageConfig, {});\n\n return final;\n}\n\n/**\n * Apply runtime configuration overrides.\n *\n * Deep-merges `config` into an in-memory store that takes the highest priority\n * in every subsequent {@link getModuleConfig} / {@link getPackageConfig} call.\n * Successive calls accumulate — they do not replace earlier overrides.\n *\n * Common uses:\n * - Inject test doubles or environment-specific values without a config file.\n * - Apply feature flags from a remote source after startup.\n *\n * @param config - Partial {@link SmrtConfig} to merge into the runtime store.\n * `null` values are ignored (they do not clear existing keys).\n *\n * @example\n * ```ts\n * setConfig({ modules: { ai: { defaultModel: 'gpt-4o' } } });\n * ```\n *\n * @see {@link clearCache}\n * @see {@link getModuleConfig}\n */\nexport function setConfig(config: Partial<SmrtConfig>): void {\n _setConfig(config);\n}\n\n/**\n * Reset all cached configuration state.\n *\n * Clears three independent caches:\n * 1. `globalThis.__smrtConfigCache` — the merged config loaded by {@link loadConfig}.\n * 2. The internal loader cache (`globalThis.__smrtLoaderCachedConfig`) and the\n * cosmiconfig explorer instance.\n * 3. The runtime override store populated by {@link setConfig}.\n *\n * After calling this, {@link getConfig} returns `null` and a fresh\n * {@link loadConfig} call will re-read from disk.\n *\n * WARNING: This is a global reset — it affects every module sharing the same\n * runtime, not just the caller. Use with care outside of test teardown.\n *\n * @example\n * ```ts\n * afterEach(() => {\n * clearCache(); // reset between tests\n * });\n * ```\n *\n * @see {@link loadConfig}\n * @see {@link setConfig}\n */\nexport function clearCache(): void {\n setLoadedConfig(null);\n clearConfigCache(); // Clear loader.ts cache\n clearRuntimeConfig(); // Clear runtime config\n}\n\n/**\n * Type-safe helper for authoring `smrt.config.js` files.\n *\n * This is an identity function — it returns its argument unchanged. Its sole\n * purpose is to provide TypeScript type-checking and IDE auto-completion when\n * writing the config object literal.\n *\n * @param config - The config object to validate against {@link SmrtConfig}.\n * @returns The same object, unchanged.\n *\n * @example\n * ```js\n * // smrt.config.js\n * import { defineConfig } from '@happyvertical/smrt-config';\n *\n * export default defineConfig({\n * smrt: { logLevel: 'info' },\n * site: { name: 'My Site', ... },\n * });\n * ```\n *\n * @see {@link SmrtConfig}\n * @see {@link loadConfig}\n */\nexport function defineConfig(config: SmrtConfig): SmrtConfig {\n return config;\n}\n"],"names":["loadConfig","setConfig","_loadConfig","_setConfig"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/loader.ts","../src/merge.ts","../src/export.ts","../src/index.ts"],"sourcesContent":["import { cosmiconfig } from 'cosmiconfig';\nimport type { LoadConfigOptions, SmrtConfig } from './types.js';\n\nconst MODULE_NAME = 'smrt';\n\n/**\n * Extend globalThis to include loader cache properties.\n * Using globalThis ensures all module instances share the same loader state,\n * which is critical in monorepos where the same package can be loaded\n * from different paths (e.g., pnpm store vs workspace symlink).\n *\n * @see https://github.com/happyvertical/smrt/issues/543\n */\ndeclare global {\n // eslint-disable-next-line no-var\n var __smrtLoaderCachedConfig: SmrtConfig | null | undefined;\n // eslint-disable-next-line no-var\n var __smrtLoaderExplorer: ReturnType<typeof cosmiconfig> | null | undefined;\n}\n\n/** Retrieve the cached config from the globalThis singleton store. */\nfunction getCachedConfig(): SmrtConfig | null {\n return globalThis.__smrtLoaderCachedConfig ?? null;\n}\n\n/** Write the config (or null to invalidate) into the globalThis singleton store. */\nfunction setCachedConfig(config: SmrtConfig | null): void {\n globalThis.__smrtLoaderCachedConfig = config;\n}\n\n/** Retrieve the cosmiconfig explorer instance from the globalThis singleton store. */\nfunction getExplorer(): ReturnType<typeof cosmiconfig> | null {\n return globalThis.__smrtLoaderExplorer ?? null;\n}\n\n/** Write the cosmiconfig explorer instance into the globalThis singleton store. */\nfunction setExplorer(exp: ReturnType<typeof cosmiconfig> | null): void {\n globalThis.__smrtLoaderExplorer = exp;\n}\n\n/**\n * Load and parse configuration from the project root using cosmiconfig.\n *\n * Searches for `smrt.config.{js,mjs,cjs,json}` starting from `cwd`, walking\n * up the directory tree unless `searchParents` is `false`. The result is\n * cached in `globalThis.__smrtLoaderCachedConfig` so that all modules sharing\n * the same runtime (including pnpm workspace symlinks) see the same config.\n *\n * Returns an empty object (`{}`) when no config file is found or when the\n * file fails to parse — callers should always treat every field as optional.\n *\n * @param options - Search and caching options.\n * @returns The parsed {@link SmrtConfig}, or `{}` if no file is found.\n *\n * @example\n * ```ts\n * import { loadConfig } from '@happyvertical/smrt-config';\n *\n * const config = await loadConfig();\n * console.log(config.smrt?.logLevel); // 'debug'\n * ```\n *\n * @example Load a specific file (useful in tests):\n * ```ts\n * const config = await loadConfig({ configPath: './fixtures/smrt.config.js', cache: false });\n * ```\n *\n * @see {@link LoadConfigOptions}\n * @see {@link clearConfigCache}\n */\nexport async function loadConfig(\n options: LoadConfigOptions = {},\n): Promise<SmrtConfig> {\n const { configPath, searchParents = true, cache = true } = options;\n\n // Return cached config if available\n const cached = getCachedConfig();\n if (cache && cached) {\n return cached;\n }\n\n // Initialize or reuse cosmiconfig explorer\n let explorer = getExplorer();\n if (!explorer || !cache) {\n explorer = cosmiconfig(MODULE_NAME, {\n searchPlaces: [\n `${MODULE_NAME}.config.js`,\n `${MODULE_NAME}.config.mjs`,\n `${MODULE_NAME}.config.cjs`,\n `${MODULE_NAME}.config.json`,\n ],\n stopDir: searchParents ? undefined : process.cwd(),\n cache: cache, // Respect cache option\n });\n setExplorer(explorer);\n }\n\n let result: Awaited<ReturnType<typeof explorer.load>> = null;\n\n // Load from specific path or search\n try {\n if (configPath) {\n result = await explorer.load(configPath);\n } else {\n result = await explorer.search();\n }\n } catch (_error) {\n // Return empty config on error\n return {};\n }\n\n const config: SmrtConfig = result?.config || {};\n\n // Cache the config\n if (cache) {\n setCachedConfig(config);\n }\n\n return config;\n}\n\n/**\n * Clear the internal loader cache.\n *\n * Resets `globalThis.__smrtLoaderCachedConfig` and invalidates the cosmiconfig\n * explorer so that the next `loadConfig()` call performs a fresh file search.\n *\n * This is a low-level helper. Consumer code should call {@link clearCache}\n * from `index.ts` instead, which also clears the runtime-override store.\n *\n * @see {@link clearCache}\n */\nexport function clearConfigCache(): void {\n setCachedConfig(null);\n\n // Clear cosmiconfig's cache\n const explorer = getExplorer();\n if (explorer) {\n explorer.clearCaches();\n setExplorer(null);\n }\n}\n","import type { SmrtConfig } from './types.js';\n\ndeclare global {\n // eslint-disable-next-line no-var\n var __smrtRuntimeConfig: Partial<SmrtConfig> | undefined;\n}\n\n// Runtime config overrides\nglobalThis.__smrtRuntimeConfig ??= {};\n\n/**\n * Deep-merge two plain objects, with `source` values taking precedence over\n * `target` values at each key level.\n *\n * Rules:\n * - Both values are non-array objects → recurse.\n * - `source` value is `null` or `undefined` → keep the `target` value.\n * - Otherwise → `source` value replaces `target` value (including `false`, `0`, `''`).\n *\n * @param target - Base object (lower priority).\n * @param source - Override object (higher priority).\n * @returns A new merged object.\n */\nfunction deepMerge<T extends Record<string, unknown>>(\n target: T,\n source: Partial<T>,\n): T {\n // Typed as Record<string, unknown> (not the generic T) so the own-key writes\n // below are well-typed — indexing a generic T for *write* is a TS2862 error.\n const result: Record<string, unknown> = { ...target };\n const src = source as Record<string, unknown>;\n\n for (const key of Object.keys(src)) {\n // Guard against prototype pollution when merging untrusted/DB-exported\n // input: never write to __proto__/constructor/prototype keys.\n if (key === '__proto__' || key === 'constructor' || key === 'prototype') {\n continue;\n }\n const sourceValue = src[key];\n const targetValue = result[key];\n\n if (\n sourceValue &&\n typeof sourceValue === 'object' &&\n !Array.isArray(sourceValue) &&\n targetValue &&\n typeof targetValue === 'object' &&\n !Array.isArray(targetValue)\n ) {\n result[key] = deepMerge(\n targetValue as Record<string, unknown>,\n sourceValue as Record<string, unknown>,\n );\n } else if (sourceValue !== undefined && sourceValue !== null) {\n result[key] = sourceValue;\n }\n }\n\n return result as T;\n}\n\n/**\n * Deep-merge `config` into the in-memory runtime override store.\n *\n * Runtime overrides take the highest priority in the merge order:\n * runtime > file config > defaults. Subsequent calls accumulate — they do not\n * replace previous overrides. Call {@link clearRuntimeConfig} to reset.\n *\n * This is the low-level setter; consumer code should call {@link setConfig}\n * from `index.ts` which delegates here.\n *\n * @param config - Partial config to merge into the runtime store.\n *\n * @see {@link clearRuntimeConfig}\n * @see {@link getRuntimeConfig}\n */\nexport function setConfig(config: Partial<SmrtConfig>): void {\n globalThis.__smrtRuntimeConfig = deepMerge(\n globalThis.__smrtRuntimeConfig || {},\n config,\n );\n}\n\n/**\n * Return the current runtime configuration override store.\n *\n * Used internally by {@link getModuleConfig} and {@link getPackageConfig} to\n * layer runtime overrides on top of file-based config. Prefer those helpers\n * for reading config; this is a low-level accessor.\n *\n * @returns The accumulated runtime config partial.\n */\nexport function getRuntimeConfig(): Partial<SmrtConfig> {\n return globalThis.__smrtRuntimeConfig || {};\n}\n\n/**\n * Reset the runtime configuration override store to an empty object.\n *\n * Called by {@link clearCache} in `index.ts` as part of a full cache reset.\n * Useful in tests to ensure a clean state between cases.\n *\n * @see {@link setConfig}\n */\nexport function clearRuntimeConfig(): void {\n globalThis.__smrtRuntimeConfig = {};\n}\n\n/**\n * Merge three config layers in ascending priority order.\n *\n * Priority (highest to lowest):\n * 1. `runtime` — runtime overrides set via {@link setConfig}\n * 2. `fileConfig` — values loaded from `smrt.config.js`\n * 3. `defaults` — caller-supplied fallback values\n *\n * Uses {@link deepMerge} internally, so `null` / `undefined` source values\n * never overwrite existing target values.\n *\n * @param defaults - Lowest-priority base values (caller defaults).\n * @param fileConfig - Mid-priority values from the loaded config file.\n * @param runtime - Highest-priority runtime override values.\n * @returns A new object with all three layers merged.\n *\n * @example\n * ```ts\n * const merged = mergeConfigs(\n * { timeout: 5000, retries: 3 },\n * { retries: 5 },\n * { timeout: 1000 },\n * );\n * // => { timeout: 1000, retries: 5 }\n * ```\n */\nexport function mergeConfigs<T extends Record<string, unknown>>(\n defaults: T,\n fileConfig: Partial<T>,\n runtime: Partial<T>,\n): T {\n let result = { ...defaults };\n result = deepMerge(result, fileConfig);\n result = deepMerge(result, runtime);\n return result;\n}\n","/**\n * Config export utilities for static site generation\n *\n * These utilities enable exporting database-backed agent configurations\n * to static files for SSG sites.\n *\n * @module\n */\n\n/**\n * Patterns that identify sensitive configuration keys. Any key matching one of\n * these patterns is stripped during {@link sanitizeConfig}.\n *\n * Redaction policy\n * ----------------\n * These exports feed static-site-generation (SSG) artifacts that are typically\n * served publicly, so the cost of leaking a secret is far higher than the cost\n * of over-redacting a benign key. We therefore **bias toward over-redaction**:\n * the patterns are mostly case-insensitive *substring* matches (no word\n * boundaries) so they catch every common casing/separator convention —\n * `apiKey`, `api_key`, `API-KEY`, `apikey`, etc. A few are deliberately scoped\n * to avoid benign collisions: `\\bcert\\b` (not `concert`), and `oauth` /\n * `authorization` rather than a bare `auth` (which would hit `author`/`authors`).\n *\n * The previous implementation used `\\b` word boundaries (e.g. `/\\bauth\\b/i`,\n * `/\\bkey\\b$/i`). In JS regex `\\b` sits between a word char and a non-word char,\n * and `_`/letters/digits are all word chars, so those boundaries never fire\n * inside `camelCase` or `snake_case` identifiers — leaking keys like `api_key`,\n * `accessKey`, `authorization`, `auth_token`, `connectionString`, and `dbUrl`.\n * See issue #1357.\n *\n * To keep over-redaction targeted (rather than redacting on a bare `key`/`url`\n * substring, which would strip benign `apiEndpoint`-style nesting that legit\n * config relies on, e.g. `db: { url }`), `key`/`url` only trigger when paired\n * with a secret-adjacent qualifier (`apiKey`, `accessKey`, `dbUrl`, …).\n */\nconst SECRET_PATTERNS = [\n // Direct secret-bearing terms (substring, any casing/separator).\n /secret/i,\n /password/i,\n /passwd/i,\n // Short credential aliases (#1381, codex audit cross-finding): `pass` / `pwd`\n // keys that `password`/`passwd` don't cover. Segment- or camelCase-anchored\n // (a capital after a letter/digit) so `smtpPass`/`databasePwd`/`db_pass`/`pwd`\n // are stripped while `compass`/`bypass`/`passive` are not.\n /(^|[_-])pass([_-]|$)/i,\n /(^|[_-])pwd([_-]|$)/i,\n /[a-z0-9]Pass([_-]|$)/,\n /[a-z0-9]Pwd([_-]|$)/,\n /^(pass|pwd)$/i,\n /token/i,\n /credential/i,\n /private/i,\n // NB: not a bare /auth/i — that also matches benign `author`/`authors`\n // metadata (common in site/content/agent config). The segment-anchored form\n // strips a key literally named `auth` (and `auth_token`, `x_auth`,\n // `oauth_token`) without hitting `author`; `oauth`/`authorization` cover the\n // no-separator camelCase cases. (authToken/authSecret also hit /token//secret/.)\n /oauth/i,\n /authorization/i,\n /(^|[_-])auth([_-]|$)/i,\n /cookie/i,\n /salt/i,\n /passphrase/i,\n /\\bcert\\b/i, // cert/certificate, but not \"uncertain\"/\"concert\" style false hits\n /certificate/i,\n // Qualified \"key\" — only when paired with a secret-adjacent prefix, so we\n // strip apiKey/accessKey/signingKey/encryptionKey/privateKey but keep benign\n // keys such as `keywords`, `monkey`, or `keyboardShortcuts`.\n /api[_-]?key/i,\n /access[_-]?key/i,\n /secret[_-]?key/i,\n /signing[_-]?key/i,\n /encryption[_-]?key/i,\n /private[_-]?key/i,\n /public[_-]?key/i,\n // camelCase `<vendor>Key` keys — `consumerKey`, `masterKey`, `sslKey`,\n // `hmacKey`, `sharedKey`, `rootKey`, `clientKey`, `serverKey`, `deployKey`,\n // `sshKey`, etc. The hardcoded prefixes above and the segment-anchored\n // standalone-`key` pattern below only fire on a `_`/`-`/start boundary, so a\n // camelCase secret key with an unlisted vendor prefix slips through (a raw\n // hex/base64 secret then exports verbatim into the public SSG artifact).\n // Mirrors the `[a-z0-9]Pass`/`[a-z0-9]Pwd` idiom: a capital `K` after a\n // letter/digit, followed by a segment/`Id`/end boundary, so `consumerKey` and\n // `masterKeyId` are stripped while benign `keywords`/`monkey`/\n // `keyboardShortcuts` (lowercase `key`) are kept.\n /[a-z0-9]Key([_-]|Id|$)/,\n /(^|[_-])key([_-]|id$|$)/i, // standalone `key`, `key_id`, `keyId`, `aws…KeyId`\n // Connection / database URLs that commonly embed credentials.\n /connection[_-]?string/i,\n /conn[_-]?str/i,\n /(^|[_-])db[_-]?url/i, // dbUrl / db_url / DB_URL\n /database[_-]?url/i,\n /datasource[_-]?url/i,\n];\n\n/** Return `true` when `key` matches any of the {@link SECRET_PATTERNS}. */\nfunction isSecretKey(key: string): boolean {\n return SECRET_PATTERNS.some((pattern) => pattern.test(key));\n}\n\n// Credentials embedded in the *value* of an otherwise-benign key — most\n// importantly DB connection strings like `postgres://user:pass@host:5432/db`\n// stored under keys such as `url` / `database.url` that the key-based\n// {@link SECRET_PATTERNS} deliberately don't match (#1381). Key-based redaction\n// alone leaks these into the published SSG artifact. Mask the userinfo\n// (`scheme://user:pass@host` → `scheme://***@host`) while preserving the\n// non-secret host so the value stays diagnosable. Not anchored/global so it\n// also catches a credential URL embedded mid-string (e.g. `dsn: postgres://u:p@h`)\n// or multiple URLs in one value (review #1549).\nconst CREDENTIAL_URL_RE = /([a-z][a-z0-9+.-]*:\\/\\/)[^/?#@\\s]+@/gi;\n\n/**\n * High-confidence secret *token* shapes that can appear in the value of an\n * otherwise-benign key (#1381). Key-based {@link SECRET_PATTERNS} only catch a\n * secret when the *key* is named like one — a provider token pasted into a\n * `note`, `header`, `instructions`, or `defaultModel`-adjacent field leaks\n * verbatim into the published SSG artifact. These patterns match the\n * distinctive prefixes/structures of real credentials so we can mask the token\n * while leaving surrounding prose intact. They are intentionally specific\n * (prefix- or structure-anchored) to avoid mangling benign config values.\n *\n * Coverage:\n * - OpenAI / Anthropic style `sk-...` and `sk-proj-...` (and `sk-ant-...`)\n * - GitHub PATs / app tokens: `ghp_ gho_ ghu_ ghs_ ghr_` + fine-grained `github_pat_`\n * - Slack tokens: `xox[abprs]-...`\n * - Google API keys: `AIza...`\n * - AWS access key IDs: `AKIA`/`ASIA` + 16 uppercase alnum\n * - Stripe live/test keys: `sk_live_` / `sk_test_` / `rk_live_` / `rk_test_`\n * - `Bearer <token>` Authorization header values\n * - PEM private-key blocks\n */\nconst VALUE_SECRET_PATTERNS: Array<{ re: RegExp; replace: string }> = [\n // PEM private key block (multi-line) — collapse the whole block.\n {\n re: /-----BEGIN (?:[A-Z ]+ )?PRIVATE KEY-----[\\s\\S]*?-----END (?:[A-Z ]+ )?PRIVATE KEY-----/g,\n replace: '***REDACTED PRIVATE KEY***',\n },\n // OpenAI / Anthropic secret keys: sk-, sk-proj-, sk-ant-...\n { re: /\\bsk-(?:proj-|ant-)?[A-Za-z0-9_-]{16,}/g, replace: '***' },\n // Stripe-style prefixed keys.\n { re: /\\b[sr]k_(?:live|test)_[A-Za-z0-9]{16,}/g, replace: '***' },\n // GitHub tokens.\n { re: /\\bgh[pousr]_[A-Za-z0-9]{20,}/g, replace: '***' },\n { re: /\\bgithub_pat_[A-Za-z0-9_]{22,}/g, replace: '***' },\n // Slack tokens.\n { re: /\\bxox[abprs]-[A-Za-z0-9-]{10,}/g, replace: '***' },\n // Google API keys.\n { re: /\\bAIza[A-Za-z0-9_-]{35}/g, replace: '***' },\n // AWS access key IDs.\n { re: /\\b(?:AKIA|ASIA)[A-Z0-9]{16}\\b/g, replace: '***' },\n // Bearer / JWT authorization values (header strings pasted into config).\n // Case-insensitive: HTTP auth schemes are case-insensitive and clients often\n // emit lowercase `bearer ` (review #1549).\n {\n re: /\\bBearer\\s+[A-Za-z0-9._~+/=-]{16,}/gi,\n replace: 'Bearer ***',\n },\n];\n\n/**\n * Redact credentials embedded in a string value: URL userinfo plus any\n * high-confidence secret-token shape (see {@link VALUE_SECRET_PATTERNS}).\n */\nfunction redactValueSecrets(value: string): string {\n let out = value.replace(CREDENTIAL_URL_RE, '$1***@');\n for (const { re, replace } of VALUE_SECRET_PATTERNS) {\n out = out.replace(re, replace);\n }\n return out;\n}\n\n/**\n * Deep-clone a config object, removing every key that looks like a secret.\n *\n * Keys are tested (case-insensitively, across camelCase / snake_case / kebab /\n * UPPER variants) against {@link SECRET_PATTERNS}, which cover secrets such as\n * `apiKey`/`api_key`, `password`, `secret`, `token`, `credential`, `private`,\n * `auth`/`authorization`/`oauth`, `accessKey`, `signingKey`, `encryptionKey`,\n * `connectionString`, and `dbUrl`. The policy biases toward over-redaction\n * because these exports are typically published as static, publicly served\n * assets — see {@link SECRET_PATTERNS} for the full rationale.\n *\n * Nested objects are recursively sanitized; arrays are mapped element-by-element.\n * `number` / `boolean` primitives pass through unchanged; `string` values are\n * additionally run through value-level redaction that masks credentials embedded\n * in a URL (`scheme://user:pass@host` → `scheme://***@host`) as well as\n * high-confidence secret-token shapes pasted into otherwise-benign keys\n * (OpenAI/Anthropic `sk-...`, AWS `AKIA...` access key IDs, GitHub/Slack/Google\n * tokens, `Bearer ...` headers, and PEM private-key blocks) — catching secrets\n * stored under a benign key the key-based patterns don't match.\n *\n * Own `__proto__` / `constructor` / `prototype` keys are dropped to prevent the\n * cloned result's prototype from being reassigned via the `__proto__` setter.\n *\n * This function is called automatically by {@link exportConfig} unless\n * `includeSecrets: true` is passed.\n *\n * @param config - Value to sanitize. Accepts any JSON-serializable structure.\n * @returns A new value with all secret keys omitted. `null` / `undefined` are\n * returned as-is.\n *\n * @example\n * ```ts\n * const sanitized = sanitizeConfig({\n * apiEndpoint: 'https://api.example.com',\n * apiKey: 'sk-secret-123',\n * nested: { password: 'hunter2', name: 'test' },\n * });\n * // => { apiEndpoint: 'https://api.example.com', nested: { name: 'test' } }\n * ```\n *\n * @see {@link exportConfig}\n */\nexport function sanitizeConfig(config: unknown): unknown {\n if (config === null || config === undefined) {\n return config;\n }\n\n if (Array.isArray(config)) {\n return config.map((item) => sanitizeConfig(item));\n }\n\n if (typeof config === 'object') {\n const result: Record<string, unknown> = {};\n\n for (const [key, value] of Object.entries(config)) {\n // Guard against prototype pollution: a `__proto__` own-key (e.g. from a\n // DB-backed config round-tripped through JSON.parse) would, via the\n // `result[key] = ...` setter, silently reassign the prototype of the\n // sanitized object we hand back to callers. `constructor`/`prototype`\n // are harmless as plain keys but dropped for parity with the merge\n // guards (deepMerge / mergeExportedConfig). See #1381.\n if (key === '__proto__' || key === 'constructor' || key === 'prototype') {\n continue;\n }\n\n // Skip keys that match secret patterns\n if (isSecretKey(key)) {\n continue;\n }\n\n // Recursively sanitize nested objects\n result[key] = sanitizeConfig(value);\n }\n\n return result;\n }\n\n // String primitives get value-level secret redaction (credentials embedded in\n // URLs under benign keys, #1381); other primitives pass through unchanged.\n if (typeof config === 'string') {\n return redactValueSecrets(config);\n }\n return config;\n}\n\n/**\n * Options accepted by {@link exportConfig}.\n */\nexport interface ExportConfigOptions {\n /**\n * Include secrets in the export (use with caution!)\n * @default false\n */\n includeSecrets?: boolean;\n\n /**\n * Output format\n * - 'json': Plain JSON format\n * - 'js': ES module format (export default)\n * @default 'json'\n */\n format?: 'json' | 'js';\n\n /**\n * Number of spaces for indentation\n * @default 2\n */\n indent?: number;\n}\n\n/**\n * Serialize a configuration object to a formatted string for SSG file output.\n *\n * By default, {@link sanitizeConfig} is applied before serialization to strip\n * any keys that match secret patterns (API keys, passwords, tokens, etc.).\n * Pass `includeSecrets: true` only in secured server-side contexts.\n *\n * @param config - Configuration object to export. Any JSON-serializable value.\n * @param options - Format, indentation, and secret-inclusion options.\n * @returns A JSON string, or an ES module string (`export default {...};\\n`)\n * when `format: 'js'`.\n *\n * @example\n * ```ts\n * // Write a sanitized JSON export file for SSG\n * const json = exportConfig(agentConfig);\n * await fs.writeFile('./public/config.json', json);\n * ```\n *\n * @example\n * ```ts\n * // ES module format (importable via `import config from './config.js'`)\n * const js = exportConfig(agentConfig, { format: 'js' });\n * ```\n *\n * @see {@link sanitizeConfig}\n * @see {@link parseExportedConfig}\n * @see {@link ExportConfigOptions}\n */\nexport function exportConfig(\n config: unknown,\n options: ExportConfigOptions = {},\n): string {\n const { includeSecrets = false, format = 'json', indent = 2 } = options;\n\n // Sanitize unless secrets are explicitly requested\n const exportData = includeSecrets ? config : sanitizeConfig(config);\n\n const jsonString = JSON.stringify(exportData, null, indent);\n\n if (format === 'js') {\n return `export default ${jsonString};\\n`;\n }\n\n return jsonString;\n}\n\n/**\n * Parse a config string previously produced by {@link exportConfig} back into\n * a plain JavaScript object.\n *\n * Handles both output formats:\n * - **JSON** — standard `JSON.parse`.\n * - **JS module** — strips the `export default` prefix and trailing semicolon\n * before parsing as JSON.\n *\n * Throws a `SyntaxError` if the content is not valid JSON after stripping.\n *\n * @param content - Exported config string (`'json'` or `'js'` format).\n * @returns Parsed configuration value.\n *\n * @example\n * ```ts\n * const raw = await fs.readFile('./smrt.exported.json', 'utf8');\n * const config = parseExportedConfig(raw);\n * ```\n *\n * @see {@link exportConfig}\n * @see {@link mergeExportedConfig}\n */\nexport function parseExportedConfig(content: string): unknown {\n // Handle JS module format (strip export default and trailing semicolon)\n const trimmed = content.trim();\n if (trimmed.startsWith('export default')) {\n const jsonPart = trimmed\n .replace(/^export default\\s*/, '')\n .replace(/;\\s*$/, '');\n return JSON.parse(jsonPart);\n }\n\n // Plain JSON\n return JSON.parse(content);\n}\n\n/**\n * Shallow-merge an exported (DB-backed) config into a base (file-backed) config.\n *\n * Object values are deep-merged recursively. Primitive values and arrays from\n * `exportedConfig` replace their counterparts in `baseConfig`. This is a\n * one-level-smarter spread that lets file-based env overrides win when placed\n * after the spread.\n *\n * Intended for the SSG pattern where an agent exports its runtime config to a\n * static JSON file and `smrt.config.js` imports it as a base:\n *\n * @example\n * ```js\n * // smrt.config.js\n * import exported from './smrt.exported.json' with { type: 'json' };\n *\n * export default defineConfig({\n * modules: {\n * praeco: mergeExportedConfig(exported, {\n * apiEndpoint: process.env.API_URL, // env override wins\n * }),\n * },\n * });\n * ```\n *\n * @param baseConfig - Lower-priority base (typically from the DB export file).\n * @param exportedConfig - Higher-priority overrides (typically env / file-based).\n * @returns A new merged object of type `T`.\n *\n * @see {@link exportConfig}\n * @see {@link parseExportedConfig}\n */\nexport function mergeExportedConfig<T extends Record<string, unknown>>(\n baseConfig: T,\n exportedConfig: Partial<T>,\n): T {\n const result = { ...baseConfig };\n\n for (const [key, value] of Object.entries(exportedConfig)) {\n // Guard against prototype pollution when merging untrusted/DB-exported\n // input: never write to __proto__/constructor/prototype keys.\n if (key === '__proto__' || key === 'constructor' || key === 'prototype') {\n continue;\n }\n if (\n value !== null &&\n typeof value === 'object' &&\n !Array.isArray(value) &&\n result[key] !== null &&\n typeof result[key] === 'object' &&\n !Array.isArray(result[key])\n ) {\n // Deep merge objects\n (result as Record<string, unknown>)[key] = mergeExportedConfig(\n result[key] as Record<string, unknown>,\n value as Record<string, unknown>,\n );\n } else {\n // Override primitive values and arrays\n (result as Record<string, unknown>)[key] = value;\n }\n }\n\n return result;\n}\n","import { loadConfig as _loadConfig, clearConfigCache } from './loader.js';\nimport {\n setConfig as _setConfig,\n clearRuntimeConfig,\n getRuntimeConfig,\n mergeConfigs,\n} from './merge.js';\nimport type { LoadConfigOptions, SmrtConfig } from './types.js';\n\n// Re-export config export utilities\nexport {\n type ExportConfigOptions,\n exportConfig,\n mergeExportedConfig,\n parseExportedConfig,\n sanitizeConfig,\n} from './export.js';\nexport { clearRuntimeConfig, getRuntimeConfig, mergeConfigs } from './merge.js';\n\n// Re-export types\nexport type {\n // CLI and migrations configuration types\n CliConfig,\n DatabaseConfig,\n // Export configuration types\n ExportConfig,\n ExportFileConfig,\n ExportFilterValue,\n LoadConfigOptions,\n MigrationsConfig,\n MigrationsPostgresConfig,\n SchemaContractConfig,\n // Site configuration types\n SiteConfig,\n SiteLocation,\n SiteNavigation,\n SiteNavigationLink,\n SitePublisher,\n SiteTheme,\n SmrtConfig,\n SmrtGlobalConfig,\n} from './types.js';\n\n/**\n * Extend globalThis to include our config cache properties.\n * Using globalThis ensures all module instances share the same config,\n * which is critical in monorepos where the same package can be loaded\n * from different paths (e.g., pnpm store vs workspace symlink).\n *\n * This fixes issue #543: external packages not receiving user config\n * when smrt-cli loads them.\n *\n * @see https://github.com/happyvertical/smrt/issues/543\n */\ndeclare global {\n // eslint-disable-next-line no-var\n var __smrtConfigCache: SmrtConfig | null | undefined;\n // eslint-disable-next-line no-var\n var __smrtRuntimeConfig: Partial<SmrtConfig> | undefined;\n}\n\n// Use globalThis for cross-module config sharing\n// This ensures loadConfig() in smrt-cli affects all packages that use smrt-config\nglobalThis.__smrtConfigCache ??= null;\n\n/**\n * Get the cached config from globalThis\n */\nfunction getLoadedConfig(): SmrtConfig | null {\n return globalThis.__smrtConfigCache ?? null;\n}\n\n/**\n * Set the cached config in globalThis\n */\nfunction setLoadedConfig(config: SmrtConfig | null): void {\n globalThis.__smrtConfigCache = config;\n}\n\n/**\n * Load and parse configuration from the project root.\n *\n * Searches for `smrt.config.{js,mjs,cjs,json}` starting from the current\n * working directory and walking up the tree (unless `searchParents: false`).\n * The result is stored in `globalThis.__smrtConfigCache` so every module in\n * the process — including pnpm workspace packages loaded from different paths\n * — sees the same instance.\n *\n * Must be called before {@link getConfig}, {@link getModuleConfig}, or\n * {@link getPackageConfig} return meaningful values (they fall back to an\n * empty config if called before loading).\n *\n * @param options - Optional search path, parent traversal, and cache control.\n * @returns The parsed {@link SmrtConfig}. Returns `{}` if no file is found.\n *\n * @example\n * ```ts\n * // Typical app startup\n * import { loadConfig } from '@happyvertical/smrt-config';\n * await loadConfig();\n * ```\n *\n * @example\n * ```ts\n * // Load a specific file (tests / scripts)\n * const config = await loadConfig({ configPath: './fixtures/smrt.config.js', cache: false });\n * ```\n *\n * @see {@link getConfig}\n * @see {@link clearCache}\n * @see {@link LoadConfigOptions}\n */\nexport async function loadConfig(\n options?: LoadConfigOptions,\n): Promise<SmrtConfig> {\n const config = await _loadConfig(options);\n // Always update config cache (even if caching is disabled)\n setLoadedConfig(config);\n return config;\n}\n\n/**\n * Return the full merged configuration synchronously.\n *\n * Reads from `globalThis.__smrtConfigCache` without triggering a file search.\n * Returns `null` when {@link loadConfig} has not been called yet (or after\n * {@link clearCache} resets the cache).\n *\n * @returns The cached {@link SmrtConfig}, or `null` if not yet loaded.\n *\n * @example\n * ```ts\n * const config = getConfig();\n * if (config?.smrt?.logLevel === 'debug') {\n * console.log('Debug logging enabled');\n * }\n * ```\n *\n * @see {@link loadConfig}\n * @see {@link getSiteConfig}\n * @see {@link getModuleConfig}\n */\nexport function getConfig(): SmrtConfig | null {\n return getLoadedConfig();\n}\n\n/**\n * Return the `site` section of the loaded configuration.\n *\n * Equivalent to `getConfig()?.site ?? null`. Returns `null` both when no\n * config has been loaded and when the loaded config has no `site` key.\n *\n * @returns The {@link SiteConfig} object, or `null` if not defined.\n *\n * @example\n * ```ts\n * const site = getSiteConfig();\n * const title = site?.name ?? 'Untitled Site';\n * ```\n *\n * @see {@link SiteConfig}\n * @see {@link getConfig}\n */\nexport function getSiteConfig(): import('./types.js').SiteConfig | null {\n const config = getLoadedConfig() || {};\n return config.site || null;\n}\n\n/**\n * Return the resolved configuration for a named module.\n *\n * Merges four layers in ascending priority order:\n * 1. `defaults` (caller-supplied fallbacks)\n * 2. Global `smrt` section of the loaded config\n * 3. `modules[moduleName]` section of the loaded config\n * 4. Runtime `modules[moduleName]` overrides set via {@link setConfig}\n *\n * Works synchronously — call {@link loadConfig} first to populate the cache.\n * If no config has been loaded, only `defaults` are returned.\n *\n * @param moduleName - Key in `config.modules` (e.g., `'praeco'`).\n * @param defaults - Optional fallback values used when a key is absent from all\n * higher-priority layers.\n * @returns The fully merged module config typed as `T`.\n *\n * @example\n * ```ts\n * const config = getModuleConfig('praeco', {\n * rssLimit: 50,\n * fetchTimeout: 10_000,\n * });\n * // config.rssLimit may be overridden by smrt.config.js or setConfig()\n * ```\n *\n * @see {@link getPackageConfig}\n * @see {@link setConfig}\n * @see {@link SmrtConfig.modules}\n */\nexport function getModuleConfig<T extends Record<string, unknown>>(\n moduleName: string,\n defaults?: T,\n): T {\n // Ensure config is loaded (will use empty config if not loaded)\n const fileConfig = getLoadedConfig() || {};\n const runtime = getRuntimeConfig();\n\n // Get global smrt config\n const globalConfig = (fileConfig.smrt || {}) as Partial<T>;\n\n // Get module-specific config\n const moduleConfig = (fileConfig.modules?.[moduleName] || {}) as Partial<T>;\n\n // Get runtime module config\n const runtimeModuleConfig = (runtime.modules?.[moduleName] ||\n {}) as Partial<T>;\n\n // Merge: defaults < global < module < runtime\n const defaultsWithGlobal = mergeConfigs(\n defaults || ({} as T),\n globalConfig,\n {},\n );\n const withModuleConfig = mergeConfigs(defaultsWithGlobal, moduleConfig, {});\n const final = mergeConfigs(withModuleConfig, runtimeModuleConfig, {});\n\n return final;\n}\n\n/**\n * Return the resolved configuration for a named package.\n *\n * Identical to {@link getModuleConfig} but reads from `config.packages` instead\n * of `config.modules`. Use this inside `@happyvertical/smrt-*` packages that\n * want to expose their own config section without conflicting with user-defined\n * module names.\n *\n * Merges four layers in ascending priority order:\n * 1. `defaults` (caller-supplied fallbacks)\n * 2. Global `smrt` section of the loaded config\n * 3. `packages[packageName]` section of the loaded config\n * 4. Runtime `packages[packageName]` overrides set via {@link setConfig}\n *\n * @param packageName - Key in `config.packages` (e.g., `'ai'`, `'spider'`).\n * @param defaults - Optional fallback values.\n * @returns The fully merged package config typed as `T`.\n *\n * @example\n * ```ts\n * // Inside @happyvertical/smrt-ai package\n * const config = getPackageConfig('ai', {\n * defaultModel: 'gpt-4o-mini',\n * maxTokens: 2048,\n * });\n * ```\n *\n * @see {@link getModuleConfig}\n * @see {@link setConfig}\n * @see {@link SmrtConfig.packages}\n */\nexport function getPackageConfig<T extends Record<string, unknown>>(\n packageName: string,\n defaults?: T,\n): T {\n // Ensure config is loaded (will use empty config if not loaded)\n const fileConfig = getLoadedConfig() || {};\n const runtime = getRuntimeConfig();\n\n // Get global smrt config\n const globalConfig = (fileConfig.smrt || {}) as Partial<T>;\n\n // Get package-specific config\n const packageConfig = (fileConfig.packages?.[packageName] ||\n {}) as Partial<T>;\n\n // Get runtime package config\n const runtimePackageConfig = (runtime.packages?.[packageName] ||\n {}) as Partial<T>;\n\n // Merge: defaults < global < package < runtime\n const defaultsWithGlobal = mergeConfigs(\n defaults || ({} as T),\n globalConfig,\n {},\n );\n const withPackageConfig = mergeConfigs(defaultsWithGlobal, packageConfig, {});\n const final = mergeConfigs(withPackageConfig, runtimePackageConfig, {});\n\n return final;\n}\n\n/**\n * Apply runtime configuration overrides.\n *\n * Deep-merges `config` into an in-memory store that takes the highest priority\n * in every subsequent {@link getModuleConfig} / {@link getPackageConfig} call.\n * Successive calls accumulate — they do not replace earlier overrides.\n *\n * Common uses:\n * - Inject test doubles or environment-specific values without a config file.\n * - Apply feature flags from a remote source after startup.\n *\n * @param config - Partial {@link SmrtConfig} to merge into the runtime store.\n * `null` values are ignored (they do not clear existing keys).\n *\n * @example\n * ```ts\n * setConfig({ modules: { ai: { defaultModel: 'gpt-4o' } } });\n * ```\n *\n * @see {@link clearCache}\n * @see {@link getModuleConfig}\n */\nexport function setConfig(config: Partial<SmrtConfig>): void {\n _setConfig(config);\n}\n\n/**\n * Reset all cached configuration state.\n *\n * Clears three independent caches:\n * 1. `globalThis.__smrtConfigCache` — the merged config loaded by {@link loadConfig}.\n * 2. The internal loader cache (`globalThis.__smrtLoaderCachedConfig`) and the\n * cosmiconfig explorer instance.\n * 3. The runtime override store populated by {@link setConfig}.\n *\n * After calling this, {@link getConfig} returns `null` and a fresh\n * {@link loadConfig} call will re-read from disk.\n *\n * WARNING: This is a global reset — it affects every module sharing the same\n * runtime, not just the caller. Use with care outside of test teardown.\n *\n * @example\n * ```ts\n * afterEach(() => {\n * clearCache(); // reset between tests\n * });\n * ```\n *\n * @see {@link loadConfig}\n * @see {@link setConfig}\n */\nexport function clearCache(): void {\n setLoadedConfig(null);\n clearConfigCache(); // Clear loader.ts cache\n clearRuntimeConfig(); // Clear runtime config\n}\n\n/**\n * Type-safe helper for authoring `smrt.config.js` files.\n *\n * This is an identity function — it returns its argument unchanged. Its sole\n * purpose is to provide TypeScript type-checking and IDE auto-completion when\n * writing the config object literal.\n *\n * @param config - The config object to validate against {@link SmrtConfig}.\n * @returns The same object, unchanged.\n *\n * @example\n * ```js\n * // smrt.config.js\n * import { defineConfig } from '@happyvertical/smrt-config';\n *\n * export default defineConfig({\n * smrt: { logLevel: 'info' },\n * site: { name: 'My Site', ... },\n * });\n * ```\n *\n * @see {@link SmrtConfig}\n * @see {@link loadConfig}\n */\nexport function defineConfig(config: SmrtConfig): SmrtConfig {\n return config;\n}\n"],"names":["loadConfig","setConfig","_loadConfig","_setConfig"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@happyvertical/smrt-config",
|
|
3
|
-
"version": "0.34.
|
|
3
|
+
"version": "0.34.9",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Centralized configuration management for SMRT modules",
|
|
6
6
|
"author": "HappyVertical",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
],
|
|
21
21
|
"dependencies": {
|
|
22
22
|
"cosmiconfig": "^9.0.0",
|
|
23
|
-
"@happyvertical/smrt-types": "0.34.
|
|
23
|
+
"@happyvertical/smrt-types": "0.34.9"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
26
|
"@types/node": "25.0.9",
|