scenv 0.3.3 → 0.4.0

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.cjs CHANGED
@@ -20,6 +20,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
+ LOG_LEVELS: () => LOG_LEVELS,
23
24
  configure: () => configure,
24
25
  discoverContextPaths: () => discoverContextPaths,
25
26
  getCallbacks: () => getCallbacks,
@@ -27,6 +28,7 @@ __export(index_exports, {
27
28
  loadConfig: () => loadConfig,
28
29
  parseScenvArgs: () => parseScenvArgs,
29
30
  resetConfig: () => resetConfig,
31
+ resetLogState: () => resetLogState,
30
32
  scenv: () => scenv
31
33
  });
32
34
  module.exports = __toCommonJS(index_exports);
@@ -34,6 +36,7 @@ module.exports = __toCommonJS(index_exports);
34
36
  // src/config.ts
35
37
  var import_node_fs = require("fs");
36
38
  var import_node_path = require("path");
39
+ var LOG_LEVELS = ["none", "trace", "debug", "info", "warn", "error"];
37
40
  var CONFIG_FILENAME = "scenv.config.json";
38
41
  var envKeyMap = {
39
42
  SCENV_CONTEXT: "contexts",
@@ -42,7 +45,8 @@ var envKeyMap = {
42
45
  SCENV_IGNORE_ENV: "ignoreEnv",
43
46
  SCENV_IGNORE_CONTEXT: "ignoreContext",
44
47
  SCENV_SAVE_PROMPT: "savePrompt",
45
- SCENV_SAVE_CONTEXT_TO: "saveContextTo"
48
+ SCENV_SAVE_CONTEXT_TO: "saveContextTo",
49
+ SCENV_LOG_LEVEL: "logLevel"
46
50
  };
47
51
  var programmaticConfig = {};
48
52
  var programmaticCallbacks = {};
@@ -77,6 +81,9 @@ function configFromEnv() {
77
81
  out[configKey] = v;
78
82
  } else if (configKey === "saveContextTo") {
79
83
  out.saveContextTo = val;
84
+ } else if (configKey === "logLevel") {
85
+ const v = val.toLowerCase();
86
+ if (LOG_LEVELS.includes(v)) out.logLevel = v;
80
87
  }
81
88
  }
82
89
  return out;
@@ -108,8 +115,16 @@ function loadConfigFile(configDir) {
108
115
  if (typeof parsed.saveContextTo === "string")
109
116
  out.saveContextTo = parsed.saveContextTo;
110
117
  if (typeof parsed.root === "string") out.root = parsed.root;
118
+ if (typeof parsed.logLevel === "string" && LOG_LEVELS.includes(parsed.logLevel))
119
+ out.logLevel = parsed.logLevel;
111
120
  return out;
112
- } catch {
121
+ } catch (err) {
122
+ const envLevel = process.env.SCENV_LOG_LEVEL?.toLowerCase();
123
+ if (envLevel && envLevel !== "none" && ["trace", "debug", "info", "warn", "error"].includes(envLevel)) {
124
+ console.error(
125
+ `[scenv] error: failed to parse ${path}: ${err instanceof Error ? err.message : String(err)}`
126
+ );
127
+ }
113
128
  return {};
114
129
  }
115
130
  }
@@ -160,6 +175,47 @@ function resetConfig() {
160
175
  programmaticCallbacks = {};
161
176
  }
162
177
 
178
+ // src/log.ts
179
+ var LEVEL_NUM = {
180
+ none: -1,
181
+ trace: 0,
182
+ debug: 1,
183
+ info: 2,
184
+ warn: 3,
185
+ error: 4
186
+ };
187
+ function getLevelNum() {
188
+ const config = loadConfig();
189
+ const level = config.logLevel ?? "none";
190
+ return LEVEL_NUM[level];
191
+ }
192
+ var configLoadedLogged = false;
193
+ function resetLogState() {
194
+ configLoadedLogged = false;
195
+ }
196
+ function logConfigLoaded(config) {
197
+ if (configLoadedLogged) return;
198
+ if (getLevelNum() < LEVEL_NUM.info) return;
199
+ configLoadedLogged = true;
200
+ log(
201
+ "info",
202
+ "config loaded",
203
+ "root=" + (config.root ?? "(cwd)"),
204
+ "contexts=" + JSON.stringify(config.contexts ?? [])
205
+ );
206
+ }
207
+ function log(level, msg, ...args) {
208
+ const configured = getLevelNum();
209
+ if (configured < 0) return;
210
+ if (LEVEL_NUM[level] < configured) return;
211
+ const prefix = `[scenv] ${level}:`;
212
+ if (args.length === 0) {
213
+ console.error(prefix, msg);
214
+ } else {
215
+ console.error(prefix, msg, ...args);
216
+ }
217
+ }
218
+
163
219
  // src/context.ts
164
220
  var import_node_fs2 = require("fs");
165
221
  var import_node_path2 = require("path");
@@ -191,24 +247,43 @@ function discoverContextPathsInternal(dir, found) {
191
247
  }
192
248
  function discoverContextPaths(dir, found = /* @__PURE__ */ new Map()) {
193
249
  discoverContextPathsInternal(dir, found);
250
+ log(
251
+ "debug",
252
+ "context discovery",
253
+ "dir=" + dir,
254
+ "found=" + JSON.stringify([...found.entries()].map(([n, p]) => ({ name: n, path: p })))
255
+ );
194
256
  return found;
195
257
  }
196
258
  function getContextValues() {
197
259
  const config = loadConfig();
260
+ logConfigLoaded(config);
198
261
  if (config.ignoreContext) return {};
199
262
  const root = config.root ?? process.cwd();
200
263
  const paths = discoverContextPaths(root);
201
264
  const out = {};
202
265
  for (const contextName of config.contexts ?? []) {
203
266
  const filePath = paths.get(contextName);
204
- if (!filePath) continue;
267
+ if (!filePath) {
268
+ log("warn", `context "${contextName}" not found (no *.context.json)`);
269
+ continue;
270
+ }
205
271
  try {
206
272
  const raw = (0, import_node_fs2.readFileSync)(filePath, "utf-8");
207
273
  const data = JSON.parse(raw);
274
+ const keys = [];
208
275
  for (const [k, v] of Object.entries(data)) {
209
- if (typeof v === "string") out[k] = v;
276
+ if (typeof v === "string") {
277
+ out[k] = v;
278
+ keys.push(k);
279
+ }
210
280
  }
211
- } catch {
281
+ log("debug", `context "${contextName}" loaded keys=${JSON.stringify(keys)}`);
282
+ } catch (err) {
283
+ log(
284
+ "warn",
285
+ `context "${contextName}" unreadable or invalid JSON: ${err instanceof Error ? err.message : String(err)}`
286
+ );
212
287
  }
213
288
  }
214
289
  return out;
@@ -230,6 +305,7 @@ function writeToContext(contextName, key, value) {
230
305
  } catch {
231
306
  }
232
307
  data[key] = value;
308
+ log("trace", "writeToContext", "path=" + path, "key=" + key);
233
309
  const dir = (0, import_node_path2.dirname)(path);
234
310
  (0, import_node_fs2.mkdirSync)(dir, { recursive: true });
235
311
  (0, import_node_fs2.writeFileSync)(path, JSON.stringify(data, null, 2) + "\n", "utf-8");
@@ -257,16 +333,29 @@ function scenv(name, options = {}) {
257
333
  const defaultValue = options.default;
258
334
  async function resolveRaw() {
259
335
  const config = loadConfig();
260
- if (config.set?.[key] !== void 0) return config.set[key];
336
+ log("trace", `resolveRaw: checking set for key=${key}`);
337
+ if (config.set?.[key] !== void 0) {
338
+ log("trace", `resolveRaw: set hit key=${key}`);
339
+ return { raw: config.set[key], source: "set" };
340
+ }
261
341
  if (!config.ignoreEnv) {
342
+ log("trace", `resolveRaw: checking env ${envKey}`);
262
343
  const envVal = process.env[envKey];
263
- if (envVal !== void 0 && envVal !== "") return envVal;
344
+ if (envVal !== void 0 && envVal !== "") {
345
+ log("trace", "resolveRaw: env hit");
346
+ return { raw: envVal, source: "env" };
347
+ }
264
348
  }
265
349
  if (!config.ignoreContext) {
350
+ log("trace", "resolveRaw: checking context");
266
351
  const ctx = getContextValues();
267
- if (ctx[key] !== void 0) return ctx[key];
352
+ if (ctx[key] !== void 0) {
353
+ log("trace", `resolveRaw: context hit key=${key}`);
354
+ return { raw: ctx[key], source: "context" };
355
+ }
268
356
  }
269
- return void 0;
357
+ log("trace", "resolveRaw: no value");
358
+ return { raw: void 0, source: void 0 };
270
359
  }
271
360
  function shouldPrompt(config, hadValue, hadEnv) {
272
361
  const mode = config.prompt ?? "fallback";
@@ -278,13 +367,20 @@ function scenv(name, options = {}) {
278
367
  }
279
368
  async function getResolvedValue(overrides) {
280
369
  const config = loadConfig();
281
- const raw = await resolveRaw();
370
+ logConfigLoaded(config);
371
+ const { raw, source } = await resolveRaw();
282
372
  const hadEnv = !config.ignoreEnv && process.env[envKey] !== void 0 && process.env[envKey] !== "";
283
373
  const hadValue = raw !== void 0;
374
+ const doPrompt = shouldPrompt(config, hadValue, hadEnv);
375
+ log(
376
+ "debug",
377
+ `prompt decision key=${key} prompt=${config.prompt ?? "fallback"} hadValue=${hadValue} hadEnv=${hadEnv} -> ${doPrompt ? "prompt" : "no prompt"}`
378
+ );
284
379
  const effectiveDefault = overrides?.default !== void 0 ? overrides.default : defaultValue;
285
380
  let wasPrompted = false;
286
381
  let value;
287
- if (shouldPrompt(config, hadValue, hadEnv)) {
382
+ let resolvedFrom;
383
+ if (doPrompt) {
288
384
  const callbacks = getCallbacks();
289
385
  const fn = overrides?.prompt ?? promptFn ?? callbacks.defaultPrompt;
290
386
  if (typeof fn !== "function") {
@@ -295,13 +391,17 @@ function scenv(name, options = {}) {
295
391
  const defaultForPrompt = raw !== void 0 ? raw : effectiveDefault;
296
392
  value = await Promise.resolve(fn(name, defaultForPrompt));
297
393
  wasPrompted = true;
394
+ resolvedFrom = "prompt";
298
395
  } else if (raw !== void 0) {
299
396
  value = raw;
397
+ resolvedFrom = source;
300
398
  } else if (effectiveDefault !== void 0) {
301
399
  value = effectiveDefault;
400
+ resolvedFrom = "default";
302
401
  } else {
303
402
  throw new Error(`Missing value for variable "${name}" (key: ${key})`);
304
403
  }
404
+ log("info", `variable "${name}" (key=${key}) resolved from ${resolvedFrom}`);
305
405
  return { value, raw, hadEnv, wasPrompted };
306
406
  }
307
407
  function validate(value) {
@@ -321,9 +421,9 @@ function scenv(name, options = {}) {
321
421
  const { value, wasPrompted } = await getResolvedValue(options2);
322
422
  const validated = validate(value);
323
423
  if (!validated.success) {
324
- throw new Error(
325
- `Validation failed for "${name}": ${validated.error ?? "unknown"}`
326
- );
424
+ const errMsg = `Validation failed for "${name}": ${validated.error ?? "unknown"}`;
425
+ log("error", errMsg);
426
+ throw new Error(errMsg);
327
427
  }
328
428
  const final = validated.data;
329
429
  if (wasPrompted) {
@@ -342,7 +442,10 @@ function scenv(name, options = {}) {
342
442
  final,
343
443
  config.contexts ?? []
344
444
  );
345
- if (ctxToSave) writeToContext(ctxToSave, key, String(final));
445
+ if (ctxToSave) {
446
+ writeToContext(ctxToSave, key, String(final));
447
+ log("info", `Saved key=${key} to context ${ctxToSave}`);
448
+ }
346
449
  }
347
450
  }
348
451
  return final;
@@ -359,9 +462,9 @@ function scenv(name, options = {}) {
359
462
  const toSave = value ?? (await getResolvedValue()).value;
360
463
  const validated = validate(toSave);
361
464
  if (!validated.success) {
362
- throw new Error(
363
- `Validation failed for "${name}": ${validated.error ?? "unknown"}`
364
- );
465
+ const errMsg = `Validation failed for "${name}": ${validated.error ?? "unknown"}`;
466
+ log("error", errMsg);
467
+ throw new Error(errMsg);
365
468
  }
366
469
  const config = loadConfig();
367
470
  let contextName = config.saveContextTo;
@@ -379,6 +482,7 @@ function scenv(name, options = {}) {
379
482
  }
380
483
  if (!contextName) contextName = config.contexts?.[0] ?? "default";
381
484
  writeToContext(contextName, key, String(validated.data));
485
+ log("info", `Saved key=${key} to context ${contextName}`);
382
486
  }
383
487
  return { get, safeGet, save };
384
488
  }
@@ -416,6 +520,16 @@ function parseScenvArgs(argv) {
416
520
  }
417
521
  } else if (arg === "--save-context-to" && argv[i + 1] !== void 0) {
418
522
  config.saveContextTo = argv[++i];
523
+ } else if ((arg === "--log-level" || arg === "--log") && argv[i + 1] !== void 0) {
524
+ const v = argv[++i].toLowerCase();
525
+ if (LOG_LEVELS.includes(v)) {
526
+ config.logLevel = v;
527
+ }
528
+ } else if (arg.startsWith("--log=")) {
529
+ const v = arg.slice(6).toLowerCase();
530
+ if (LOG_LEVELS.includes(v)) {
531
+ config.logLevel = v;
532
+ }
419
533
  } else if (arg.startsWith("--set=")) {
420
534
  const pair = arg.slice(6);
421
535
  const eq = pair.indexOf("=");
@@ -430,6 +544,7 @@ function parseScenvArgs(argv) {
430
544
  }
431
545
  // Annotate the CommonJS export names for ESM import in node:
432
546
  0 && (module.exports = {
547
+ LOG_LEVELS,
433
548
  configure,
434
549
  discoverContextPaths,
435
550
  getCallbacks,
@@ -437,5 +552,6 @@ function parseScenvArgs(argv) {
437
552
  loadConfig,
438
553
  parseScenvArgs,
439
554
  resetConfig,
555
+ resetLogState,
440
556
  scenv
441
557
  });
package/dist/index.d.cts CHANGED
@@ -1,5 +1,7 @@
1
1
  type PromptMode = "always" | "never" | "fallback" | "no-env";
2
2
  type SavePromptMode = "always" | "never" | "ask";
3
+ declare const LOG_LEVELS: readonly ["none", "trace", "debug", "info", "warn", "error"];
4
+ type LogLevel = (typeof LOG_LEVELS)[number];
3
5
  interface ScenvConfig {
4
6
  /** Replace all contexts with this list (CLI: --context a,b,c) */
5
7
  contexts?: string[];
@@ -19,6 +21,8 @@ interface ScenvConfig {
19
21
  saveContextTo?: "ask" | string;
20
22
  /** Root directory for config/context search (default: cwd) */
21
23
  root?: string;
24
+ /** Log level: none (default), trace, debug, info, warn, error */
25
+ logLevel?: LogLevel;
22
26
  }
23
27
  /** (name, defaultValue) => value; used when a variable has no prompt option. Overridable per variable. */
24
28
  type DefaultPromptFn = (name: string, defaultValue: unknown) => unknown | Promise<unknown>;
@@ -46,6 +50,9 @@ declare function configure(partial: Partial<ScenvConfig> & {
46
50
  */
47
51
  declare function resetConfig(): void;
48
52
 
53
+ /** Reset internal log state (e.g. config-loaded guard). Useful in tests after resetConfig(). */
54
+ declare function resetLogState(): void;
55
+
49
56
  /**
50
57
  * Recursively find all *.context.json files under dir. Returns map: contextName -> absolute path (first found wins).
51
58
  */
@@ -93,8 +100,8 @@ declare function scenv<T>(name: string, options?: ScenvVariableOptions<T>): Scen
93
100
  /**
94
101
  * Parse argv (e.g. process.argv.slice(2)) into ScenvConfig for configure().
95
102
  * Supports: --context a,b,c --add-context x,y --prompt fallback --ignore-env --ignore-context
96
- * --set key=value --save-prompt ask --save-context-to prod
103
+ * --set key=value --save-prompt ask --save-context-to prod --log-level trace
97
104
  */
98
105
  declare function parseScenvArgs(argv: string[]): Partial<ScenvConfig>;
99
106
 
100
- export { type DefaultPromptFn, type GetOptions, type PromptMode, type SavePromptMode, type ScenvCallbacks, type ScenvConfig, type ScenvVariable, configure, discoverContextPaths, getCallbacks, getContextValues, loadConfig, parseScenvArgs, resetConfig, scenv };
107
+ export { type DefaultPromptFn, type GetOptions, LOG_LEVELS, type LogLevel, type PromptMode, type SavePromptMode, type ScenvCallbacks, type ScenvConfig, type ScenvVariable, configure, discoverContextPaths, getCallbacks, getContextValues, loadConfig, parseScenvArgs, resetConfig, resetLogState, scenv };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  type PromptMode = "always" | "never" | "fallback" | "no-env";
2
2
  type SavePromptMode = "always" | "never" | "ask";
3
+ declare const LOG_LEVELS: readonly ["none", "trace", "debug", "info", "warn", "error"];
4
+ type LogLevel = (typeof LOG_LEVELS)[number];
3
5
  interface ScenvConfig {
4
6
  /** Replace all contexts with this list (CLI: --context a,b,c) */
5
7
  contexts?: string[];
@@ -19,6 +21,8 @@ interface ScenvConfig {
19
21
  saveContextTo?: "ask" | string;
20
22
  /** Root directory for config/context search (default: cwd) */
21
23
  root?: string;
24
+ /** Log level: none (default), trace, debug, info, warn, error */
25
+ logLevel?: LogLevel;
22
26
  }
23
27
  /** (name, defaultValue) => value; used when a variable has no prompt option. Overridable per variable. */
24
28
  type DefaultPromptFn = (name: string, defaultValue: unknown) => unknown | Promise<unknown>;
@@ -46,6 +50,9 @@ declare function configure(partial: Partial<ScenvConfig> & {
46
50
  */
47
51
  declare function resetConfig(): void;
48
52
 
53
+ /** Reset internal log state (e.g. config-loaded guard). Useful in tests after resetConfig(). */
54
+ declare function resetLogState(): void;
55
+
49
56
  /**
50
57
  * Recursively find all *.context.json files under dir. Returns map: contextName -> absolute path (first found wins).
51
58
  */
@@ -93,8 +100,8 @@ declare function scenv<T>(name: string, options?: ScenvVariableOptions<T>): Scen
93
100
  /**
94
101
  * Parse argv (e.g. process.argv.slice(2)) into ScenvConfig for configure().
95
102
  * Supports: --context a,b,c --add-context x,y --prompt fallback --ignore-env --ignore-context
96
- * --set key=value --save-prompt ask --save-context-to prod
103
+ * --set key=value --save-prompt ask --save-context-to prod --log-level trace
97
104
  */
98
105
  declare function parseScenvArgs(argv: string[]): Partial<ScenvConfig>;
99
106
 
100
- export { type DefaultPromptFn, type GetOptions, type PromptMode, type SavePromptMode, type ScenvCallbacks, type ScenvConfig, type ScenvVariable, configure, discoverContextPaths, getCallbacks, getContextValues, loadConfig, parseScenvArgs, resetConfig, scenv };
107
+ export { type DefaultPromptFn, type GetOptions, LOG_LEVELS, type LogLevel, type PromptMode, type SavePromptMode, type ScenvCallbacks, type ScenvConfig, type ScenvVariable, configure, discoverContextPaths, getCallbacks, getContextValues, loadConfig, parseScenvArgs, resetConfig, resetLogState, scenv };
package/dist/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  // src/config.ts
2
2
  import { readFileSync, existsSync } from "fs";
3
3
  import { dirname, join } from "path";
4
+ var LOG_LEVELS = ["none", "trace", "debug", "info", "warn", "error"];
4
5
  var CONFIG_FILENAME = "scenv.config.json";
5
6
  var envKeyMap = {
6
7
  SCENV_CONTEXT: "contexts",
@@ -9,7 +10,8 @@ var envKeyMap = {
9
10
  SCENV_IGNORE_ENV: "ignoreEnv",
10
11
  SCENV_IGNORE_CONTEXT: "ignoreContext",
11
12
  SCENV_SAVE_PROMPT: "savePrompt",
12
- SCENV_SAVE_CONTEXT_TO: "saveContextTo"
13
+ SCENV_SAVE_CONTEXT_TO: "saveContextTo",
14
+ SCENV_LOG_LEVEL: "logLevel"
13
15
  };
14
16
  var programmaticConfig = {};
15
17
  var programmaticCallbacks = {};
@@ -44,6 +46,9 @@ function configFromEnv() {
44
46
  out[configKey] = v;
45
47
  } else if (configKey === "saveContextTo") {
46
48
  out.saveContextTo = val;
49
+ } else if (configKey === "logLevel") {
50
+ const v = val.toLowerCase();
51
+ if (LOG_LEVELS.includes(v)) out.logLevel = v;
47
52
  }
48
53
  }
49
54
  return out;
@@ -75,8 +80,16 @@ function loadConfigFile(configDir) {
75
80
  if (typeof parsed.saveContextTo === "string")
76
81
  out.saveContextTo = parsed.saveContextTo;
77
82
  if (typeof parsed.root === "string") out.root = parsed.root;
83
+ if (typeof parsed.logLevel === "string" && LOG_LEVELS.includes(parsed.logLevel))
84
+ out.logLevel = parsed.logLevel;
78
85
  return out;
79
- } catch {
86
+ } catch (err) {
87
+ const envLevel = process.env.SCENV_LOG_LEVEL?.toLowerCase();
88
+ if (envLevel && envLevel !== "none" && ["trace", "debug", "info", "warn", "error"].includes(envLevel)) {
89
+ console.error(
90
+ `[scenv] error: failed to parse ${path}: ${err instanceof Error ? err.message : String(err)}`
91
+ );
92
+ }
80
93
  return {};
81
94
  }
82
95
  }
@@ -127,6 +140,47 @@ function resetConfig() {
127
140
  programmaticCallbacks = {};
128
141
  }
129
142
 
143
+ // src/log.ts
144
+ var LEVEL_NUM = {
145
+ none: -1,
146
+ trace: 0,
147
+ debug: 1,
148
+ info: 2,
149
+ warn: 3,
150
+ error: 4
151
+ };
152
+ function getLevelNum() {
153
+ const config = loadConfig();
154
+ const level = config.logLevel ?? "none";
155
+ return LEVEL_NUM[level];
156
+ }
157
+ var configLoadedLogged = false;
158
+ function resetLogState() {
159
+ configLoadedLogged = false;
160
+ }
161
+ function logConfigLoaded(config) {
162
+ if (configLoadedLogged) return;
163
+ if (getLevelNum() < LEVEL_NUM.info) return;
164
+ configLoadedLogged = true;
165
+ log(
166
+ "info",
167
+ "config loaded",
168
+ "root=" + (config.root ?? "(cwd)"),
169
+ "contexts=" + JSON.stringify(config.contexts ?? [])
170
+ );
171
+ }
172
+ function log(level, msg, ...args) {
173
+ const configured = getLevelNum();
174
+ if (configured < 0) return;
175
+ if (LEVEL_NUM[level] < configured) return;
176
+ const prefix = `[scenv] ${level}:`;
177
+ if (args.length === 0) {
178
+ console.error(prefix, msg);
179
+ } else {
180
+ console.error(prefix, msg, ...args);
181
+ }
182
+ }
183
+
130
184
  // src/context.ts
131
185
  import {
132
186
  readFileSync as readFileSync2,
@@ -164,24 +218,43 @@ function discoverContextPathsInternal(dir, found) {
164
218
  }
165
219
  function discoverContextPaths(dir, found = /* @__PURE__ */ new Map()) {
166
220
  discoverContextPathsInternal(dir, found);
221
+ log(
222
+ "debug",
223
+ "context discovery",
224
+ "dir=" + dir,
225
+ "found=" + JSON.stringify([...found.entries()].map(([n, p]) => ({ name: n, path: p })))
226
+ );
167
227
  return found;
168
228
  }
169
229
  function getContextValues() {
170
230
  const config = loadConfig();
231
+ logConfigLoaded(config);
171
232
  if (config.ignoreContext) return {};
172
233
  const root = config.root ?? process.cwd();
173
234
  const paths = discoverContextPaths(root);
174
235
  const out = {};
175
236
  for (const contextName of config.contexts ?? []) {
176
237
  const filePath = paths.get(contextName);
177
- if (!filePath) continue;
238
+ if (!filePath) {
239
+ log("warn", `context "${contextName}" not found (no *.context.json)`);
240
+ continue;
241
+ }
178
242
  try {
179
243
  const raw = readFileSync2(filePath, "utf-8");
180
244
  const data = JSON.parse(raw);
245
+ const keys = [];
181
246
  for (const [k, v] of Object.entries(data)) {
182
- if (typeof v === "string") out[k] = v;
247
+ if (typeof v === "string") {
248
+ out[k] = v;
249
+ keys.push(k);
250
+ }
183
251
  }
184
- } catch {
252
+ log("debug", `context "${contextName}" loaded keys=${JSON.stringify(keys)}`);
253
+ } catch (err) {
254
+ log(
255
+ "warn",
256
+ `context "${contextName}" unreadable or invalid JSON: ${err instanceof Error ? err.message : String(err)}`
257
+ );
185
258
  }
186
259
  }
187
260
  return out;
@@ -203,6 +276,7 @@ function writeToContext(contextName, key, value) {
203
276
  } catch {
204
277
  }
205
278
  data[key] = value;
279
+ log("trace", "writeToContext", "path=" + path, "key=" + key);
206
280
  const dir = dirname2(path);
207
281
  mkdirSync(dir, { recursive: true });
208
282
  writeFileSync(path, JSON.stringify(data, null, 2) + "\n", "utf-8");
@@ -230,16 +304,29 @@ function scenv(name, options = {}) {
230
304
  const defaultValue = options.default;
231
305
  async function resolveRaw() {
232
306
  const config = loadConfig();
233
- if (config.set?.[key] !== void 0) return config.set[key];
307
+ log("trace", `resolveRaw: checking set for key=${key}`);
308
+ if (config.set?.[key] !== void 0) {
309
+ log("trace", `resolveRaw: set hit key=${key}`);
310
+ return { raw: config.set[key], source: "set" };
311
+ }
234
312
  if (!config.ignoreEnv) {
313
+ log("trace", `resolveRaw: checking env ${envKey}`);
235
314
  const envVal = process.env[envKey];
236
- if (envVal !== void 0 && envVal !== "") return envVal;
315
+ if (envVal !== void 0 && envVal !== "") {
316
+ log("trace", "resolveRaw: env hit");
317
+ return { raw: envVal, source: "env" };
318
+ }
237
319
  }
238
320
  if (!config.ignoreContext) {
321
+ log("trace", "resolveRaw: checking context");
239
322
  const ctx = getContextValues();
240
- if (ctx[key] !== void 0) return ctx[key];
323
+ if (ctx[key] !== void 0) {
324
+ log("trace", `resolveRaw: context hit key=${key}`);
325
+ return { raw: ctx[key], source: "context" };
326
+ }
241
327
  }
242
- return void 0;
328
+ log("trace", "resolveRaw: no value");
329
+ return { raw: void 0, source: void 0 };
243
330
  }
244
331
  function shouldPrompt(config, hadValue, hadEnv) {
245
332
  const mode = config.prompt ?? "fallback";
@@ -251,13 +338,20 @@ function scenv(name, options = {}) {
251
338
  }
252
339
  async function getResolvedValue(overrides) {
253
340
  const config = loadConfig();
254
- const raw = await resolveRaw();
341
+ logConfigLoaded(config);
342
+ const { raw, source } = await resolveRaw();
255
343
  const hadEnv = !config.ignoreEnv && process.env[envKey] !== void 0 && process.env[envKey] !== "";
256
344
  const hadValue = raw !== void 0;
345
+ const doPrompt = shouldPrompt(config, hadValue, hadEnv);
346
+ log(
347
+ "debug",
348
+ `prompt decision key=${key} prompt=${config.prompt ?? "fallback"} hadValue=${hadValue} hadEnv=${hadEnv} -> ${doPrompt ? "prompt" : "no prompt"}`
349
+ );
257
350
  const effectiveDefault = overrides?.default !== void 0 ? overrides.default : defaultValue;
258
351
  let wasPrompted = false;
259
352
  let value;
260
- if (shouldPrompt(config, hadValue, hadEnv)) {
353
+ let resolvedFrom;
354
+ if (doPrompt) {
261
355
  const callbacks = getCallbacks();
262
356
  const fn = overrides?.prompt ?? promptFn ?? callbacks.defaultPrompt;
263
357
  if (typeof fn !== "function") {
@@ -268,13 +362,17 @@ function scenv(name, options = {}) {
268
362
  const defaultForPrompt = raw !== void 0 ? raw : effectiveDefault;
269
363
  value = await Promise.resolve(fn(name, defaultForPrompt));
270
364
  wasPrompted = true;
365
+ resolvedFrom = "prompt";
271
366
  } else if (raw !== void 0) {
272
367
  value = raw;
368
+ resolvedFrom = source;
273
369
  } else if (effectiveDefault !== void 0) {
274
370
  value = effectiveDefault;
371
+ resolvedFrom = "default";
275
372
  } else {
276
373
  throw new Error(`Missing value for variable "${name}" (key: ${key})`);
277
374
  }
375
+ log("info", `variable "${name}" (key=${key}) resolved from ${resolvedFrom}`);
278
376
  return { value, raw, hadEnv, wasPrompted };
279
377
  }
280
378
  function validate(value) {
@@ -294,9 +392,9 @@ function scenv(name, options = {}) {
294
392
  const { value, wasPrompted } = await getResolvedValue(options2);
295
393
  const validated = validate(value);
296
394
  if (!validated.success) {
297
- throw new Error(
298
- `Validation failed for "${name}": ${validated.error ?? "unknown"}`
299
- );
395
+ const errMsg = `Validation failed for "${name}": ${validated.error ?? "unknown"}`;
396
+ log("error", errMsg);
397
+ throw new Error(errMsg);
300
398
  }
301
399
  const final = validated.data;
302
400
  if (wasPrompted) {
@@ -315,7 +413,10 @@ function scenv(name, options = {}) {
315
413
  final,
316
414
  config.contexts ?? []
317
415
  );
318
- if (ctxToSave) writeToContext(ctxToSave, key, String(final));
416
+ if (ctxToSave) {
417
+ writeToContext(ctxToSave, key, String(final));
418
+ log("info", `Saved key=${key} to context ${ctxToSave}`);
419
+ }
319
420
  }
320
421
  }
321
422
  return final;
@@ -332,9 +433,9 @@ function scenv(name, options = {}) {
332
433
  const toSave = value ?? (await getResolvedValue()).value;
333
434
  const validated = validate(toSave);
334
435
  if (!validated.success) {
335
- throw new Error(
336
- `Validation failed for "${name}": ${validated.error ?? "unknown"}`
337
- );
436
+ const errMsg = `Validation failed for "${name}": ${validated.error ?? "unknown"}`;
437
+ log("error", errMsg);
438
+ throw new Error(errMsg);
338
439
  }
339
440
  const config = loadConfig();
340
441
  let contextName = config.saveContextTo;
@@ -352,6 +453,7 @@ function scenv(name, options = {}) {
352
453
  }
353
454
  if (!contextName) contextName = config.contexts?.[0] ?? "default";
354
455
  writeToContext(contextName, key, String(validated.data));
456
+ log("info", `Saved key=${key} to context ${contextName}`);
355
457
  }
356
458
  return { get, safeGet, save };
357
459
  }
@@ -389,6 +491,16 @@ function parseScenvArgs(argv) {
389
491
  }
390
492
  } else if (arg === "--save-context-to" && argv[i + 1] !== void 0) {
391
493
  config.saveContextTo = argv[++i];
494
+ } else if ((arg === "--log-level" || arg === "--log") && argv[i + 1] !== void 0) {
495
+ const v = argv[++i].toLowerCase();
496
+ if (LOG_LEVELS.includes(v)) {
497
+ config.logLevel = v;
498
+ }
499
+ } else if (arg.startsWith("--log=")) {
500
+ const v = arg.slice(6).toLowerCase();
501
+ if (LOG_LEVELS.includes(v)) {
502
+ config.logLevel = v;
503
+ }
392
504
  } else if (arg.startsWith("--set=")) {
393
505
  const pair = arg.slice(6);
394
506
  const eq = pair.indexOf("=");
@@ -402,6 +514,7 @@ function parseScenvArgs(argv) {
402
514
  return config;
403
515
  }
404
516
  export {
517
+ LOG_LEVELS,
405
518
  configure,
406
519
  discoverContextPaths,
407
520
  getCallbacks,
@@ -409,5 +522,6 @@ export {
409
522
  loadConfig,
410
523
  parseScenvArgs,
411
524
  resetConfig,
525
+ resetLogState,
412
526
  scenv
413
527
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scenv",
3
- "version": "0.3.3",
3
+ "version": "0.4.0",
4
4
  "description": "Environment and context variables with runtime-configurable resolution",
5
5
  "repository": { "type": "git", "url": "https://github.com/PKWadsy/scenv" },
6
6
  "publishConfig": { "access": "public", "provenance": true },