@happyvertical/smrt-config 0.53.3 → 0.53.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/index.js CHANGED
@@ -1,5 +1,262 @@
1
1
  import { cosmiconfig } from "cosmiconfig";
2
2
  import { createJiti } from "jiti";
3
+ //#region src/ai.ts
4
+ var AI_PROVIDER_KEY_ENV = Object.freeze({
5
+ openai: "OPENAI_API_KEY",
6
+ anthropic: "ANTHROPIC_API_KEY",
7
+ gemini: "GEMINI_API_KEY"
8
+ });
9
+ var AUTO_DETECT_ORDER = [
10
+ "openai",
11
+ "anthropic",
12
+ "gemini"
13
+ ];
14
+ var DEFAULT_AI_ENV_PREFIXES = Object.freeze(["SMRT_AI", "HAVE_AI"]);
15
+ function getDefaultAIKeyEnvName(provider) {
16
+ const normalized = nonEmpty(provider)?.toLowerCase();
17
+ return normalized && Object.hasOwn(AI_PROVIDER_KEY_ENV, normalized) ? AI_PROVIDER_KEY_ENV[normalized] : void 0;
18
+ }
19
+ var AIProviderNotConfiguredError = class extends Error {
20
+ code = "SMRT_AI_NOT_CONFIGURED";
21
+ constructor(message) {
22
+ super(message);
23
+ this.name = "AIProviderNotConfiguredError";
24
+ }
25
+ };
26
+ function nonEmpty(value) {
27
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
28
+ }
29
+ function redactBaseUrl(value) {
30
+ if (!value) return void 0;
31
+ try {
32
+ const url = new URL(value);
33
+ return `${url.protocol}//${url.host}`;
34
+ } catch {
35
+ return "[unparseable-url]";
36
+ }
37
+ }
38
+ function tryResolveAIProviderConfig(options = {}) {
39
+ const env = options.env ?? process.env;
40
+ const read = (name) => nonEmpty(env[name]);
41
+ const prefixes = options.prefixes ?? DEFAULT_AI_ENV_PREFIXES;
42
+ const explicit = options.explicit ?? {};
43
+ const block = options.config ?? {};
44
+ const fromEnv = (suffix) => prefixes.map((prefix) => ({
45
+ source: `env:${prefix}_${suffix}`,
46
+ value: read(`${prefix}_${suffix}`)
47
+ }));
48
+ const explicitCand = (value) => ({
49
+ source: "explicit",
50
+ value: nonEmpty(value)
51
+ });
52
+ const configCand = (value) => ({
53
+ source: "config",
54
+ value: nonEmpty(value)
55
+ });
56
+ const layered = (exp, cfg, envs) => options.envOverridesConfig ? [
57
+ ...exp,
58
+ ...envs,
59
+ ...cfg
60
+ ] : [
61
+ ...exp,
62
+ ...cfg,
63
+ ...envs
64
+ ];
65
+ const first = (candidates) => {
66
+ for (const c of candidates) if (c.value) return {
67
+ value: c.value,
68
+ source: c.source
69
+ };
70
+ };
71
+ const sources = {};
72
+ const result = {};
73
+ const set = (field, hit) => {
74
+ if (hit) {
75
+ result[field] = hit.value;
76
+ sources[field] = hit.source;
77
+ }
78
+ };
79
+ set("provider", first(layered([explicitCand(nonEmpty(explicit.provider) ?? nonEmpty(explicit.type))], [configCand(block.provider)], fromEnv("PROVIDER"))));
80
+ set("model", first(layered([explicitCand(nonEmpty(explicit.model) ?? nonEmpty(explicit.defaultModel))], [configCand(block.model)], fromEnv("MODEL"))));
81
+ const autoDetected = result.provider || options.autoDetect === false ? void 0 : AUTO_DETECT_ORDER.find((p) => read(AI_PROVIDER_KEY_ENV[p]));
82
+ if (autoDetected) {
83
+ result.provider = autoDetected;
84
+ sources.provider = "auto-detect";
85
+ }
86
+ const selected = result.provider?.toLowerCase();
87
+ const configKeyEnv = nonEmpty(block.apiKeyEnv);
88
+ const explicitGroup = {
89
+ source: "explicit",
90
+ provider: nonEmpty(explicit.provider) ?? nonEmpty(explicit.type),
91
+ apiKey: nonEmpty(explicit.apiKey),
92
+ baseUrl: nonEmpty(explicit.baseUrl)
93
+ };
94
+ const configGroup = {
95
+ source: "config",
96
+ provider: nonEmpty(block.provider),
97
+ apiKey: nonEmpty(block.apiKey) ?? (configKeyEnv ? read(configKeyEnv) : void 0),
98
+ baseUrl: nonEmpty(block.baseUrl)
99
+ };
100
+ const envGroups = prefixes.map((prefix) => ({
101
+ source: `env:${prefix}`,
102
+ provider: read(`${prefix}_PROVIDER`),
103
+ apiKey: read(`${prefix}_API_KEY`),
104
+ baseUrl: read(`${prefix}_BASE_URL`)
105
+ }));
106
+ const groups = options.envOverridesConfig ? [
107
+ explicitGroup,
108
+ ...envGroups,
109
+ configGroup
110
+ ] : [
111
+ explicitGroup,
112
+ configGroup,
113
+ ...envGroups
114
+ ];
115
+ const usable = (g) => !g.provider || g.provider.toLowerCase() === selected;
116
+ const sourceName = (g, suffix) => g.source.startsWith("env:") ? `${g.source}_${suffix}` : g.source;
117
+ for (const g of groups) {
118
+ if (!usable(g)) continue;
119
+ if (g.baseUrl && !result.baseUrl) {
120
+ result.baseUrl = g.baseUrl;
121
+ sources.baseUrl = sourceName(g, "BASE_URL");
122
+ }
123
+ if (g.apiKey && !result.apiKey) {
124
+ result.apiKey = g.apiKey;
125
+ sources.apiKey = sourceName(g, "API_KEY");
126
+ }
127
+ }
128
+ const providerKeyEnv = getDefaultAIKeyEnvName(result.provider);
129
+ if (!result.apiKey && providerKeyEnv && options.providerKeyEnvFallback !== false && read(providerKeyEnv)) {
130
+ result.apiKey = read(providerKeyEnv);
131
+ sources.apiKey = "provider-key-env";
132
+ }
133
+ if (!result.provider && (options.requireProvider ?? true)) return;
134
+ if (!result.provider && !result.apiKey) return;
135
+ const resolved = {
136
+ ...result,
137
+ sources,
138
+ toJSON: () => describeAIProviderConfig(resolved)
139
+ };
140
+ Object.defineProperty(resolved, "toJSON", { enumerable: false });
141
+ return resolved;
142
+ }
143
+ function resolveAIProviderConfig(options = {}) {
144
+ const resolved = tryResolveAIProviderConfig(options);
145
+ if (resolved) return resolved;
146
+ const envNames = (options.prefixes ?? DEFAULT_AI_ENV_PREFIXES).map((p) => `${p}_PROVIDER`).join(", ");
147
+ const keyNames = Object.values(AI_PROVIDER_KEY_ENV).join(", ");
148
+ throw new AIProviderNotConfiguredError(`No AI provider is configured. Set the \`ai\` block in smrt.config.ts (provider, model, apiKeyEnv), or set ${envNames}` + (options.autoDetect === false ? "." : `, or one of ${keyNames} to auto-detect the provider.`));
149
+ }
150
+ function describeAIProviderConfig(resolved) {
151
+ return {
152
+ provider: resolved.provider,
153
+ model: resolved.model,
154
+ baseUrl: redactBaseUrl(resolved.baseUrl),
155
+ apiKey: resolved.apiKey ? "[redacted]" : void 0,
156
+ sources: { ...resolved.sources }
157
+ };
158
+ }
159
+ function toAIClientOptions(resolved) {
160
+ const out = {};
161
+ if (resolved.provider) {
162
+ out.type = resolved.provider;
163
+ out.provider = resolved.provider;
164
+ }
165
+ if (resolved.apiKey) out.apiKey = resolved.apiKey;
166
+ if (resolved.baseUrl) out.baseUrl = resolved.baseUrl;
167
+ if (resolved.model) out.defaultModel = resolved.model;
168
+ return out;
169
+ }
170
+ var OWNED_AI_FIELDS = [
171
+ "apiKey",
172
+ "apiKeyEnv",
173
+ "baseUrl",
174
+ "model"
175
+ ];
176
+ function canonicalizeAIConfig(input) {
177
+ if (!input || typeof input !== "object") return {};
178
+ const out = {};
179
+ for (const [key, value] of Object.entries(input)) {
180
+ if (key === "__proto__" || key === "constructor" || key === "prototype") continue;
181
+ if (typeof value === "string" && value.trim().length === 0) continue;
182
+ out[key] = value;
183
+ }
184
+ const provider = nonEmpty(out.provider) ?? nonEmpty(out.type);
185
+ const model = nonEmpty(out.model) ?? nonEmpty(out.defaultModel);
186
+ delete out.type;
187
+ delete out.defaultModel;
188
+ if (provider) out.provider = provider;
189
+ else delete out.provider;
190
+ if (model) out.model = model;
191
+ else delete out.model;
192
+ return out;
193
+ }
194
+ function mergeAIConfigObjects(lower, higher) {
195
+ const low = canonicalizeAIConfig(lower);
196
+ const high = canonicalizeAIConfig(higher);
197
+ const lowName = nonEmpty(low.provider)?.toLowerCase();
198
+ const highName = nonEmpty(high.provider)?.toLowerCase();
199
+ if (lowName && highName && lowName !== highName) {
200
+ delete low.provider;
201
+ for (const f of OWNED_AI_FIELDS) delete low[f];
202
+ }
203
+ return {
204
+ ...low,
205
+ ...high
206
+ };
207
+ }
208
+ function withAIAliases(config) {
209
+ const out = { ...config };
210
+ const provider = nonEmpty(out.provider) ?? nonEmpty(out.type);
211
+ const model = nonEmpty(out.model) ?? nonEmpty(out.defaultModel);
212
+ for (const k of ["provider", "type"]) if (provider) out[k] = provider;
213
+ else delete out[k];
214
+ for (const k of ["model", "defaultModel"]) if (model) out[k] = model;
215
+ else delete out[k];
216
+ return out;
217
+ }
218
+ //#endregion
219
+ //#region src/database-environment.ts
220
+ var POSTGRES_URL = /^postgres(?:ql)?:\/\//i;
221
+ function environmentType(value) {
222
+ const normalized = value?.trim().toLowerCase();
223
+ if (!normalized) return null;
224
+ if (normalized === "sqlite") return "sqlite";
225
+ if (normalized === "postgres" || normalized === "postgresql") return "postgres";
226
+ throw new TypeError("DATABASE_TYPE must be sqlite or postgres.");
227
+ }
228
+ function resolveCliDatabase(declared, env) {
229
+ if (typeof declared?.url === "string" && declared.url !== "") return {
230
+ source: "config",
231
+ database: typeof declared.type === "string" ? {
232
+ type: declared.type,
233
+ url: declared.url
234
+ } : { url: declared.url }
235
+ };
236
+ const url = env.DATABASE_URL?.trim();
237
+ if (!url) return {
238
+ source: "none",
239
+ database: null
240
+ };
241
+ let type;
242
+ try {
243
+ type = declared?.type === "sqlite" || declared?.type === "postgres" ? declared.type : environmentType(env.DATABASE_TYPE) ?? (POSTGRES_URL.test(url) ? "postgres" : "sqlite");
244
+ } catch (error) {
245
+ return {
246
+ source: "invalid-environment",
247
+ database: null,
248
+ error: error.message
249
+ };
250
+ }
251
+ return {
252
+ source: "environment",
253
+ database: {
254
+ type,
255
+ url
256
+ }
257
+ };
258
+ }
259
+ //#endregion
3
260
  //#region src/loader.ts
4
261
  var MODULE_NAME = "smrt";
5
262
  var jitiInstance;
@@ -569,6 +826,7 @@ function deepMerge(target, source) {
569
826
  }
570
827
  function setConfig$1(config) {
571
828
  const incomingRuntime = Object.hasOwn(config, "runtime") ? config.runtime : void 0;
829
+ if (incomingRuntime !== void 0 && (incomingRuntime === null || typeof incomingRuntime !== "object" || Array.isArray(incomingRuntime))) throw new RuntimeProfileValidationError([...validateApplicationRuntimeConfigShape(incomingRuntime)]);
572
830
  if (incomingRuntime !== null && (typeof incomingRuntime === "object" || typeof incomingRuntime === "function") && Object.hasOwn(incomingRuntime, "profile")) {
573
831
  const profileIssues = validateApplicationRuntimeConfigShape(incomingRuntime).filter((issue) => issue.path === "runtime" || issue.path === "profile");
574
832
  if (profileIssues.length > 0) throw new RuntimeProfileValidationError([...profileIssues]);
@@ -581,7 +839,12 @@ function setConfig$1(config) {
581
839
  const resetRuntime = current.runtime;
582
840
  delete resetRuntime.providers;
583
841
  }
584
- globalThis.__smrtRuntimeConfig = deepMerge(current, config);
842
+ const merged = deepMerge(current, config);
843
+ const aiOf = (c) => isPlainObject(c) && isPlainObject(c.ai) ? c.ai : void 0;
844
+ const pkgAiOf = (c) => isPlainObject(c) && isPlainObject(c.packages) && isPlainObject(c.packages.ai) ? c.packages.ai : void 0;
845
+ if (aiOf(config)) merged.ai = mergeAIConfigObjects(aiOf(current) ?? {}, aiOf(config));
846
+ if (pkgAiOf(config)) merged.packages.ai = mergeAIConfigObjects(pkgAiOf(current) ?? {}, pkgAiOf(config));
847
+ globalThis.__smrtRuntimeConfig = merged;
585
848
  }
586
849
  function getRuntimeConfig() {
587
850
  return globalThis.__smrtRuntimeConfig || {};
@@ -714,7 +977,8 @@ function mergeExportedConfig(baseConfig, exportedConfig) {
714
977
  const result = { ...baseConfig };
715
978
  for (const [key, value] of Object.entries(exportedConfig)) {
716
979
  if (key === "__proto__" || key === "constructor" || key === "prototype") continue;
717
- if (value !== null && typeof value === "object" && !Array.isArray(value) && result[key] !== null && typeof result[key] === "object" && !Array.isArray(result[key])) result[key] = mergeExportedConfig(result[key], value);
980
+ if (key === "ai" && value !== null && typeof value === "object" && !Array.isArray(value) && result[key] !== null && typeof result[key] === "object" && !Array.isArray(result[key])) result[key] = mergeAIConfigObjects(result[key], value);
981
+ else if (value !== null && typeof value === "object" && !Array.isArray(value) && result[key] !== null && typeof result[key] === "object" && !Array.isArray(result[key])) result[key] = mergeExportedConfig(result[key], value);
718
982
  else result[key] = value;
719
983
  }
720
984
  return result;
@@ -737,7 +1001,16 @@ function getConfig() {
737
1001
  return getLoadedConfig();
738
1002
  }
739
1003
  function resolveConfiguredApplicationRuntime() {
740
- const loadedConfig = getLoadedConfig();
1004
+ return resolveRuntimeLayers(getLoadedConfig());
1005
+ }
1006
+ function resolveEffectiveApplicationRuntime(config) {
1007
+ if (!declaresRuntime(config ?? null) && !declaresRuntime(getRuntimeConfig())) return resolveApplicationRuntime({ profile: "local" });
1008
+ return resolveRuntimeLayers(config ?? null);
1009
+ }
1010
+ function declaresRuntime(layer) {
1011
+ return layer !== null && Object.hasOwn(layer, "runtime") && layer.runtime !== void 0;
1012
+ }
1013
+ function resolveRuntimeLayers(loadedConfig) {
741
1014
  const runtimeConfig = getRuntimeConfig();
742
1015
  const fileRuntime = loadedConfig && Object.hasOwn(loadedConfig, "runtime") ? loadedConfig.runtime : void 0;
743
1016
  const runtimeOverride = Object.hasOwn(runtimeConfig, "runtime") ? runtimeConfig.runtime : void 0;
@@ -764,6 +1037,10 @@ function getModuleConfig(moduleName, defaults) {
764
1037
  const runtimeModuleConfig = runtime.modules?.[moduleName] || {};
765
1038
  return mergeConfigs(mergeConfigs(mergeConfigs(defaults || {}, globalConfig, {}), moduleConfig, {}), runtimeModuleConfig, {});
766
1039
  }
1040
+ function resolveCliDatabaseConfig(env = process.env) {
1041
+ const declared = getPackageConfig("cli").database;
1042
+ return resolveCliDatabase(declared, env);
1043
+ }
767
1044
  function getPackageConfig(packageName, defaults) {
768
1045
  const fileConfig = getLoadedConfig() || {};
769
1046
  const runtime = getRuntimeConfig();
@@ -783,7 +1060,65 @@ function clearCache() {
783
1060
  function defineConfig(config) {
784
1061
  return config;
785
1062
  }
1063
+ function getAIConfigBlock() {
1064
+ const file = getLoadedConfig();
1065
+ const runtime = getRuntimeConfig();
1066
+ return mergeAIConfigLayers([
1067
+ file?.packages?.ai,
1068
+ file?.ai,
1069
+ runtime.packages?.ai,
1070
+ runtime.ai
1071
+ ]);
1072
+ }
1073
+ function mergeAIConfigLayers(layers) {
1074
+ const FIELDS = [
1075
+ "model",
1076
+ "baseUrl",
1077
+ "apiKeyEnv",
1078
+ "apiKey"
1079
+ ];
1080
+ let provider;
1081
+ const values = {};
1082
+ const owners = {};
1083
+ const text = (v) => typeof v === "string" && v.trim() ? v.trim() : void 0;
1084
+ for (const layer of layers) {
1085
+ if (!layer || typeof layer !== "object") continue;
1086
+ const rec = layer;
1087
+ const named = text(rec.provider) ?? text(rec.type);
1088
+ if (named && provider && named.toLowerCase() !== provider.toLowerCase()) for (const f of FIELDS) {
1089
+ const owner = owners[f];
1090
+ if (owner && owner.toLowerCase() !== named.toLowerCase()) {
1091
+ delete values[f];
1092
+ delete owners[f];
1093
+ }
1094
+ }
1095
+ if (named) provider = named;
1096
+ for (const f of FIELDS) {
1097
+ const v = f === "model" ? text(rec.model) ?? text(rec.defaultModel) : text(rec[f]);
1098
+ if (v) {
1099
+ values[f] = v;
1100
+ owners[f] = provider;
1101
+ }
1102
+ }
1103
+ }
1104
+ if (!provider && FIELDS.every((f) => !values[f])) return null;
1105
+ const merged = { ...values };
1106
+ if (provider) merged.provider = provider;
1107
+ return merged;
1108
+ }
1109
+ function resolveConfiguredAIProvider(options = {}) {
1110
+ return resolveAIProviderConfig({
1111
+ ...options,
1112
+ config: options.config === void 0 ? getAIConfigBlock() : options.config
1113
+ });
1114
+ }
1115
+ function tryResolveConfiguredAIProvider(options = {}) {
1116
+ return tryResolveAIProviderConfig({
1117
+ ...options,
1118
+ config: options.config === void 0 ? getAIConfigBlock() : options.config
1119
+ });
1120
+ }
786
1121
  //#endregion
787
- export { RuntimeProfileValidationError, clearCache, clearRuntimeConfig, defineConfig, exportConfig, getApplicationRuntimePreset, getConfig, getModuleConfig, getPackageConfig, getRuntimeConfig, getSiteConfig, loadConfig, mergeConfigs, mergeExportedConfig, parseExportedConfig, resolveApplicationRuntime, resolveConfiguredApplicationRuntime, sanitizeConfig, setConfig };
1122
+ export { AIProviderNotConfiguredError, AI_PROVIDER_KEY_ENV, DEFAULT_AI_ENV_PREFIXES, RuntimeProfileValidationError, canonicalizeAIConfig, clearCache, clearRuntimeConfig, defineConfig, describeAIProviderConfig, exportConfig, getAIConfigBlock, getApplicationRuntimePreset, getConfig, getDefaultAIKeyEnvName, getModuleConfig, getPackageConfig, getRuntimeConfig, getSiteConfig, loadConfig, mergeAIConfigLayers, mergeAIConfigObjects, mergeConfigs, mergeExportedConfig, parseExportedConfig, redactBaseUrl, resolveAIProviderConfig, resolveApplicationRuntime, resolveCliDatabaseConfig, resolveConfiguredAIProvider, resolveConfiguredApplicationRuntime, resolveEffectiveApplicationRuntime, sanitizeConfig, setConfig, toAIClientOptions, tryResolveAIProviderConfig, tryResolveConfiguredAIProvider, withAIAliases };
788
1123
 
789
1124
  //# sourceMappingURL=index.js.map