scenv 0.7.0 → 0.8.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
@@ -48,12 +48,12 @@ function defaultPrompt(name, defaultValue) {
48
48
  const defaultStr = defaultValue !== void 0 && defaultValue !== null ? String(defaultValue) : "";
49
49
  const message = defaultStr ? `Enter ${name} [${defaultStr}]: ` : `Enter ${name}: `;
50
50
  const rl = (0, import_node_readline.createInterface)({ input: process.stdin, output: process.stdout });
51
- return new Promise((resolve, reject) => {
51
+ return new Promise((resolve2, reject) => {
52
52
  rl.question(message, (answer) => {
53
53
  rl.close();
54
54
  const trimmed = answer.trim();
55
55
  const value = trimmed !== "" ? trimmed : defaultStr;
56
- resolve(value);
56
+ resolve2(value);
57
57
  });
58
58
  rl.on("error", (err) => {
59
59
  rl.close();
@@ -72,7 +72,7 @@ var envKeyMap = {
72
72
  SCENV_IGNORE_ENV: "ignoreEnv",
73
73
  SCENV_IGNORE_CONTEXT: "ignoreContext",
74
74
  SCENV_SAVE_CONTEXT_TO: "saveContextTo",
75
- SCENV_CONTEXT_DIR: "contextDir",
75
+ SCENV_SAVE_MODE: "saveMode",
76
76
  SCENV_LOG_LEVEL: "logLevel"
77
77
  };
78
78
  var programmaticConfig = {};
@@ -108,8 +108,10 @@ function configFromEnv() {
108
108
  out[configKey] = v;
109
109
  } else if (configKey === "saveContextTo") {
110
110
  out.saveContextTo = val;
111
- } else if (configKey === "contextDir") {
112
- out.contextDir = val;
111
+ } else if (configKey === "saveMode") {
112
+ const v = val.toLowerCase();
113
+ if (v === "all" || v === "prompts-only")
114
+ out[configKey] = v;
113
115
  } else if (configKey === "logLevel") {
114
116
  const v = val.toLowerCase();
115
117
  if (LOG_LEVELS.includes(v)) out.logLevel = v;
@@ -149,8 +151,8 @@ function loadConfigFile(configDir) {
149
151
  out.set = parsed.set;
150
152
  if (typeof parsed.saveContextTo === "string")
151
153
  out.saveContextTo = parsed.saveContextTo;
152
- if (typeof parsed.contextDir === "string") out.contextDir = parsed.contextDir;
153
- else if (typeof parsed.contextsDir === "string") out.contextDir = parsed.contextsDir;
154
+ if (typeof parsed.saveMode === "string" && ["all", "prompts-only"].includes(parsed.saveMode))
155
+ out.saveMode = parsed.saveMode;
154
156
  if (typeof parsed.root === "string") out.root = parsed.root;
155
157
  if (typeof parsed.logLevel === "string" && LOG_LEVELS.includes(parsed.logLevel))
156
158
  out.logLevel = parsed.logLevel;
@@ -238,7 +240,7 @@ var CONFIG_LOG_KEYS = [
238
240
  "ignoreContext",
239
241
  "set",
240
242
  "saveContextTo",
241
- "contextDir",
243
+ "saveMode",
242
244
  "logLevel"
243
245
  ];
244
246
  function configForLog(config) {
@@ -259,7 +261,7 @@ function logConfigLoaded(config) {
259
261
  if (config.ignoreEnv === true) parts.push("ignoreEnv=true");
260
262
  if (config.ignoreContext === true) parts.push("ignoreContext=true");
261
263
  if (config.saveContextTo !== void 0) parts.push("saveContextTo=" + config.saveContextTo);
262
- if (config.contextDir !== void 0) parts.push("contextDir=" + config.contextDir);
264
+ if (config.saveMode !== void 0) parts.push("saveMode=" + config.saveMode);
263
265
  log("info", "config loaded", parts.join(" "));
264
266
  }
265
267
  if (levelNum >= LEVEL_NUM.debug) {
@@ -328,10 +330,23 @@ function discoverContextPaths(dir, found = /* @__PURE__ */ new Map()) {
328
330
  return found;
329
331
  }
330
332
  function getContext(contextName, root) {
331
- const config = loadConfig();
332
- const searchRoot = root ?? config.root ?? process.cwd();
333
- const paths = discoverContextPaths(searchRoot);
334
- const filePath = paths.get(contextName);
333
+ let filePath;
334
+ if (root !== void 0) {
335
+ const paths = discoverContextPaths(root);
336
+ filePath = paths.get(contextName);
337
+ } else {
338
+ const cwd = process.cwd();
339
+ const paths = discoverContextPaths(cwd);
340
+ filePath = paths.get(contextName);
341
+ if (!filePath) {
342
+ const config = loadConfig();
343
+ const projectRoot = config.root ?? cwd;
344
+ if (projectRoot !== cwd) {
345
+ const rootPaths = discoverContextPaths(projectRoot);
346
+ filePath = rootPaths.get(contextName);
347
+ }
348
+ }
349
+ }
335
350
  if (!filePath) {
336
351
  log("trace", `getContext: context "${contextName}" not found`);
337
352
  return {};
@@ -356,17 +371,9 @@ function getMergedContextValues() {
356
371
  const config = loadConfig();
357
372
  logConfigLoaded(config);
358
373
  if (config.ignoreContext) return {};
359
- const root = config.root ?? process.cwd();
360
- const paths = discoverContextPaths(root);
374
+ const searchRoot = process.cwd();
375
+ const paths = discoverContextPaths(searchRoot);
361
376
  const out = {};
362
- if (config.saveContextTo) {
363
- const savePath = resolveSaveContextPath(config.saveContextTo);
364
- const saveCtx = getContextAtPath(savePath);
365
- for (const [k, v] of Object.entries(saveCtx)) out[k] = v;
366
- if (Object.keys(saveCtx).length > 0) {
367
- log("debug", `saveContextTo "${config.saveContextTo}" loaded keys=${JSON.stringify(Object.keys(saveCtx))}`);
368
- }
369
- }
370
377
  for (const contextName of config.context ?? []) {
371
378
  const filePath = paths.get(contextName);
372
379
  if (!filePath) {
@@ -398,32 +405,11 @@ function getContextWritePath(contextName) {
398
405
  return contextName.endsWith(CONTEXT_SUFFIX) ? contextName : contextName + CONTEXT_SUFFIX;
399
406
  }
400
407
  const config = loadConfig();
401
- const root = config.root ?? process.cwd();
402
- const paths = discoverContextPaths(root);
408
+ const paths = discoverContextPaths(process.cwd());
403
409
  const existing = paths.get(contextName);
404
410
  if (existing) return existing;
405
- const saveDir = config.contextDir ? (0, import_node_path2.isAbsolute)(config.contextDir) ? config.contextDir : (0, import_node_path2.join)(root, config.contextDir) : root;
406
- return (0, import_node_path2.join)(saveDir, `${contextName}${CONTEXT_SUFFIX}`);
407
- }
408
- function resolveSaveContextPath(nameOrPath) {
409
- if ((0, import_node_path2.isAbsolute)(nameOrPath) || nameOrPath.includes(import_node_path2.sep)) {
410
- return nameOrPath.endsWith(CONTEXT_SUFFIX) ? nameOrPath : nameOrPath + CONTEXT_SUFFIX;
411
- }
412
- return getContextWritePath(nameOrPath);
413
- }
414
- function getContextAtPath(filePath) {
415
- if (!(0, import_node_fs2.existsSync)(filePath)) return {};
416
- try {
417
- const raw = (0, import_node_fs2.readFileSync)(filePath, "utf-8");
418
- const data = JSON.parse(raw);
419
- const out = {};
420
- for (const [k, v] of Object.entries(data)) {
421
- if (typeof v === "string") out[k] = v;
422
- }
423
- return out;
424
- } catch {
425
- return {};
426
- }
411
+ const projectRoot = config.root ?? process.cwd();
412
+ return (0, import_node_path2.join)(projectRoot, `${contextName}${CONTEXT_SUFFIX}`);
427
413
  }
428
414
  function writeToContext(contextName, key, value) {
429
415
  const path = getContextWritePath(contextName);
@@ -610,10 +596,11 @@ function scenv(name, options = {}) {
610
596
  throw new Error(errMsg);
611
597
  }
612
598
  const final = validated.data;
613
- if (wasPrompted) {
614
- const config = loadConfig();
615
- setInMemoryContext(key, String(final));
616
- if (config.saveContextTo) {
599
+ const config = loadConfig();
600
+ if (wasPrompted) setInMemoryContext(key, String(final));
601
+ if (config.saveContextTo) {
602
+ const saveMode = config.saveMode ?? "all";
603
+ if (saveMode === "all" || wasPrompted) {
617
604
  writeToContext(config.saveContextTo, key, String(final));
618
605
  log("info", `Saved key=${key} to saveContextTo ${config.saveContextTo}`);
619
606
  }
@@ -647,12 +634,15 @@ function scenv(name, options = {}) {
647
634
  }
648
635
 
649
636
  // src/cli-args.ts
637
+ var import_node_path3 = require("path");
650
638
  function parseScenvArgs(argv) {
651
639
  const config = {};
652
640
  let i = 0;
653
641
  while (i < argv.length) {
654
642
  const arg = argv[i];
655
- if (arg === "--context" && argv[i + 1] !== void 0) {
643
+ if (arg === "--root" && argv[i + 1] !== void 0) {
644
+ config.root = (0, import_node_path3.resolve)(argv[++i]);
645
+ } else if (arg === "--context" && argv[i + 1] !== void 0) {
656
646
  config.context = argv[++i].split(",").map((s) => s.trim()).filter(Boolean);
657
647
  } else if (arg === "--add-context" && argv[i + 1] !== void 0) {
658
648
  config.addContext = argv[++i].split(",").map((s) => s.trim()).filter(Boolean);
@@ -674,8 +664,11 @@ function parseScenvArgs(argv) {
674
664
  }
675
665
  } else if (arg === "--save-context-to" && argv[i + 1] !== void 0) {
676
666
  config.saveContextTo = argv[++i];
677
- } else if (arg === "--context-dir" && argv[i + 1] !== void 0) {
678
- config.contextDir = argv[++i];
667
+ } else if (arg === "--save-mode" && argv[i + 1] !== void 0) {
668
+ const v = argv[++i].toLowerCase();
669
+ if (v === "all" || v === "prompts-only") {
670
+ config.saveMode = v;
671
+ }
679
672
  } else if ((arg === "--log-level" || arg === "--log") && argv[i + 1] !== void 0) {
680
673
  const v = argv[++i].toLowerCase();
681
674
  if (LOG_LEVELS.includes(v)) {
package/dist/index.d.cts CHANGED
@@ -13,6 +13,12 @@ type PromptMode = "always" | "never" | "fallback" | "no-env";
13
13
  declare const LOG_LEVELS: readonly ["none", "trace", "debug", "info", "warn", "error"];
14
14
  /** Log level type. `"none"` disables logging; higher values are more verbose. */
15
15
  type LogLevel = (typeof LOG_LEVELS)[number];
16
+ /**
17
+ * When to write resolved values to saveContextTo during get().
18
+ * - `"all"` – Save every resolved variable (from set, env, context, or prompt). Useful for re-running with the same values.
19
+ * - `"prompts-only"` – Save only when the user was prompted. Default is `"all"`.
20
+ */
21
+ type SaveMode = "all" | "prompts-only";
16
22
  /**
17
23
  * Full scenv configuration. Built from file (scenv.config.json), environment (SCENV_*),
18
24
  * and programmatic config (configure()), with programmatic > env > file precedence.
@@ -33,9 +39,9 @@ interface ScenvConfig {
33
39
  set?: Record<string, string>;
34
40
  /** Optional path or context name (without .context.json) where to save resolved values. If set, all saves go here and this context is used before prompting. If unset, values are saved to an in-memory context only (same process). */
35
41
  saveContextTo?: string;
36
- /** Directory to save context files to when the context is not already discovered. Relative to root unless absolute. If unset, new context files are saved under root. */
37
- contextDir?: string;
38
- /** Root directory for config file search and context discovery. Default is cwd or the directory containing scenv.config.json. */
42
+ /** When to write to saveContextTo during get(): "all" (default) saves every resolved variable; "prompts-only" saves only when the user was prompted. */
43
+ saveMode?: SaveMode;
44
+ /** Project root: where to search for scenv.config.json and where new context files are saved. Defaults to the directory containing scenv.config.json (when found) or cwd. Context files are discovered from cwd. */
39
45
  root?: string;
40
46
  /** Logging level. Default is `"none"`. Messages go to stderr. */
41
47
  logLevel?: LogLevel;
@@ -120,17 +126,18 @@ declare function discoverContextPaths(dir: string, found?: Map<string, string>):
120
126
  /**
121
127
  * Loads key-value pairs from a single context file. Used when resolving @context:key references.
122
128
  * Does not depend on config.context or ignoreContext; the context file is read if it exists
123
- * under the config root.
129
+ * under the search directory.
124
130
  *
125
131
  * @param contextName - Name of the context (e.g. "prod", "dev") — file is contextName.context.json.
126
- * @param root - Optional root directory to search. If omitted, uses loadConfig().root.
132
+ * @param root - Optional directory to search. If omitted, searches from process.cwd() then from project root (config.root) if the context is not found under cwd.
127
133
  * @returns A flat record of key → string value from that context file. Empty if file not found or invalid.
128
134
  */
129
135
  declare function getContext(contextName: string, root?: string): Record<string, string>;
130
136
  /**
131
- * Loads and merges context values from the current config. If {@link ScenvConfig.saveContextTo}
132
- * is set, that context (file path or name) is loaded first; then {@link ScenvConfig.context}
133
- * order. Respects {@link ScenvConfig.ignoreContext}. Later contexts overwrite earlier for the same key.
137
+ * Loads and merges context values from the current config from {@link ScenvConfig.context} only.
138
+ * saveContextTo is not used for resolution; it is only a write target. To use the same context
139
+ * for reading, add it explicitly via context or addContext.
140
+ * Respects {@link ScenvConfig.ignoreContext}. Later contexts overwrite earlier for the same key.
134
141
  * Used during variable resolution (set > env > in-memory > merged context > default).
135
142
  *
136
143
  * @returns A flat record of key → string value. Empty if ignoreContext is true or no context loaded.
@@ -139,7 +146,7 @@ declare function getMergedContextValues(): Record<string, string>;
139
146
  /**
140
147
  * Returns the file path used for a context name or path when saving.
141
148
  * - If contextName is path-like (absolute or contains path separator), returns that path with .context.json appended if not already present.
142
- * - Otherwise, if that context was already discovered under config.root, returns its path; else uses config.contextDir (if set) or root.
149
+ * - Otherwise, if that context was already discovered under cwd, returns its path; else saves under project root (config.root or cwd).
143
150
  *
144
151
  * @param contextName - Context name (e.g. "dev", "prod") or file path without suffix (e.g. "/path/to/myfile" → myfile.context.json).
145
152
  * @returns Absolute path to the context JSON file.
@@ -263,6 +270,7 @@ declare function scenv<T>(name: string, options?: ScenvVariableOptions<T>): Scen
263
270
  * Typical use: `configure(parseScenvArgs(process.argv.slice(2)))`. Unrecognized flags are ignored.
264
271
  *
265
272
  * Supported flags:
273
+ * - `--root path` – Project root (where to find scenv.config.json and where new context files are saved). Relative paths are resolved from cwd.
266
274
  * - `--context a,b,c` – Set context list (replace).
267
275
  * - `--add-context x,y` – Add context names.
268
276
  * - `--prompt always|never|fallback|no-env` – Prompt mode.
@@ -270,7 +278,7 @@ declare function scenv<T>(name: string, options?: ScenvVariableOptions<T>): Scen
270
278
  * - `--ignore-context` – Set ignoreContext to true.
271
279
  * - `--set key=value` or `--set=key=value` – Add to set overrides (multiple allowed).
272
280
  * - `--save-context-to pathOrName` – saveContextTo (path or context name without .context.json).
273
- * - `--context-dir path` – contextDir (directory to save context files to by default).
281
+ * - `--save-mode all|prompts-only` – When to write to saveContextTo during get(); default is all.
274
282
  * - `--log-level level`, `--log level`, `--log=level` – logLevel.
275
283
  *
276
284
  * @param argv - Array of CLI arguments (e.g. process.argv.slice(2)).
@@ -278,4 +286,4 @@ declare function scenv<T>(name: string, options?: ScenvVariableOptions<T>): Scen
278
286
  */
279
287
  declare function parseScenvArgs(argv: string[]): Partial<ScenvConfig>;
280
288
 
281
- export { type DefaultPromptFn, type GetOptions, LOG_LEVELS, type LogLevel, type PromptMode, type ScenvCallbacks, type ScenvConfig, type ScenvVariable, configure, discoverContextPaths, getCallbacks, getContext, getContextWritePath, getInMemoryContext, getMergedContextValues, loadConfig, parseScenvArgs, resetConfig, resetInMemoryContext, resetLogState, scenv, setInMemoryContext };
289
+ export { type DefaultPromptFn, type GetOptions, LOG_LEVELS, type LogLevel, type PromptMode, type SaveMode, type ScenvCallbacks, type ScenvConfig, type ScenvVariable, configure, discoverContextPaths, getCallbacks, getContext, getContextWritePath, getInMemoryContext, getMergedContextValues, loadConfig, parseScenvArgs, resetConfig, resetInMemoryContext, resetLogState, scenv, setInMemoryContext };
package/dist/index.d.ts CHANGED
@@ -13,6 +13,12 @@ type PromptMode = "always" | "never" | "fallback" | "no-env";
13
13
  declare const LOG_LEVELS: readonly ["none", "trace", "debug", "info", "warn", "error"];
14
14
  /** Log level type. `"none"` disables logging; higher values are more verbose. */
15
15
  type LogLevel = (typeof LOG_LEVELS)[number];
16
+ /**
17
+ * When to write resolved values to saveContextTo during get().
18
+ * - `"all"` – Save every resolved variable (from set, env, context, or prompt). Useful for re-running with the same values.
19
+ * - `"prompts-only"` – Save only when the user was prompted. Default is `"all"`.
20
+ */
21
+ type SaveMode = "all" | "prompts-only";
16
22
  /**
17
23
  * Full scenv configuration. Built from file (scenv.config.json), environment (SCENV_*),
18
24
  * and programmatic config (configure()), with programmatic > env > file precedence.
@@ -33,9 +39,9 @@ interface ScenvConfig {
33
39
  set?: Record<string, string>;
34
40
  /** Optional path or context name (without .context.json) where to save resolved values. If set, all saves go here and this context is used before prompting. If unset, values are saved to an in-memory context only (same process). */
35
41
  saveContextTo?: string;
36
- /** Directory to save context files to when the context is not already discovered. Relative to root unless absolute. If unset, new context files are saved under root. */
37
- contextDir?: string;
38
- /** Root directory for config file search and context discovery. Default is cwd or the directory containing scenv.config.json. */
42
+ /** When to write to saveContextTo during get(): "all" (default) saves every resolved variable; "prompts-only" saves only when the user was prompted. */
43
+ saveMode?: SaveMode;
44
+ /** Project root: where to search for scenv.config.json and where new context files are saved. Defaults to the directory containing scenv.config.json (when found) or cwd. Context files are discovered from cwd. */
39
45
  root?: string;
40
46
  /** Logging level. Default is `"none"`. Messages go to stderr. */
41
47
  logLevel?: LogLevel;
@@ -120,17 +126,18 @@ declare function discoverContextPaths(dir: string, found?: Map<string, string>):
120
126
  /**
121
127
  * Loads key-value pairs from a single context file. Used when resolving @context:key references.
122
128
  * Does not depend on config.context or ignoreContext; the context file is read if it exists
123
- * under the config root.
129
+ * under the search directory.
124
130
  *
125
131
  * @param contextName - Name of the context (e.g. "prod", "dev") — file is contextName.context.json.
126
- * @param root - Optional root directory to search. If omitted, uses loadConfig().root.
132
+ * @param root - Optional directory to search. If omitted, searches from process.cwd() then from project root (config.root) if the context is not found under cwd.
127
133
  * @returns A flat record of key → string value from that context file. Empty if file not found or invalid.
128
134
  */
129
135
  declare function getContext(contextName: string, root?: string): Record<string, string>;
130
136
  /**
131
- * Loads and merges context values from the current config. If {@link ScenvConfig.saveContextTo}
132
- * is set, that context (file path or name) is loaded first; then {@link ScenvConfig.context}
133
- * order. Respects {@link ScenvConfig.ignoreContext}. Later contexts overwrite earlier for the same key.
137
+ * Loads and merges context values from the current config from {@link ScenvConfig.context} only.
138
+ * saveContextTo is not used for resolution; it is only a write target. To use the same context
139
+ * for reading, add it explicitly via context or addContext.
140
+ * Respects {@link ScenvConfig.ignoreContext}. Later contexts overwrite earlier for the same key.
134
141
  * Used during variable resolution (set > env > in-memory > merged context > default).
135
142
  *
136
143
  * @returns A flat record of key → string value. Empty if ignoreContext is true or no context loaded.
@@ -139,7 +146,7 @@ declare function getMergedContextValues(): Record<string, string>;
139
146
  /**
140
147
  * Returns the file path used for a context name or path when saving.
141
148
  * - If contextName is path-like (absolute or contains path separator), returns that path with .context.json appended if not already present.
142
- * - Otherwise, if that context was already discovered under config.root, returns its path; else uses config.contextDir (if set) or root.
149
+ * - Otherwise, if that context was already discovered under cwd, returns its path; else saves under project root (config.root or cwd).
143
150
  *
144
151
  * @param contextName - Context name (e.g. "dev", "prod") or file path without suffix (e.g. "/path/to/myfile" → myfile.context.json).
145
152
  * @returns Absolute path to the context JSON file.
@@ -263,6 +270,7 @@ declare function scenv<T>(name: string, options?: ScenvVariableOptions<T>): Scen
263
270
  * Typical use: `configure(parseScenvArgs(process.argv.slice(2)))`. Unrecognized flags are ignored.
264
271
  *
265
272
  * Supported flags:
273
+ * - `--root path` – Project root (where to find scenv.config.json and where new context files are saved). Relative paths are resolved from cwd.
266
274
  * - `--context a,b,c` – Set context list (replace).
267
275
  * - `--add-context x,y` – Add context names.
268
276
  * - `--prompt always|never|fallback|no-env` – Prompt mode.
@@ -270,7 +278,7 @@ declare function scenv<T>(name: string, options?: ScenvVariableOptions<T>): Scen
270
278
  * - `--ignore-context` – Set ignoreContext to true.
271
279
  * - `--set key=value` or `--set=key=value` – Add to set overrides (multiple allowed).
272
280
  * - `--save-context-to pathOrName` – saveContextTo (path or context name without .context.json).
273
- * - `--context-dir path` – contextDir (directory to save context files to by default).
281
+ * - `--save-mode all|prompts-only` – When to write to saveContextTo during get(); default is all.
274
282
  * - `--log-level level`, `--log level`, `--log=level` – logLevel.
275
283
  *
276
284
  * @param argv - Array of CLI arguments (e.g. process.argv.slice(2)).
@@ -278,4 +286,4 @@ declare function scenv<T>(name: string, options?: ScenvVariableOptions<T>): Scen
278
286
  */
279
287
  declare function parseScenvArgs(argv: string[]): Partial<ScenvConfig>;
280
288
 
281
- export { type DefaultPromptFn, type GetOptions, LOG_LEVELS, type LogLevel, type PromptMode, type ScenvCallbacks, type ScenvConfig, type ScenvVariable, configure, discoverContextPaths, getCallbacks, getContext, getContextWritePath, getInMemoryContext, getMergedContextValues, loadConfig, parseScenvArgs, resetConfig, resetInMemoryContext, resetLogState, scenv, setInMemoryContext };
289
+ export { type DefaultPromptFn, type GetOptions, LOG_LEVELS, type LogLevel, type PromptMode, type SaveMode, type ScenvCallbacks, type ScenvConfig, type ScenvVariable, configure, discoverContextPaths, getCallbacks, getContext, getContextWritePath, getInMemoryContext, getMergedContextValues, loadConfig, parseScenvArgs, resetConfig, resetInMemoryContext, resetLogState, scenv, setInMemoryContext };
package/dist/index.js CHANGED
@@ -8,12 +8,12 @@ function defaultPrompt(name, defaultValue) {
8
8
  const defaultStr = defaultValue !== void 0 && defaultValue !== null ? String(defaultValue) : "";
9
9
  const message = defaultStr ? `Enter ${name} [${defaultStr}]: ` : `Enter ${name}: `;
10
10
  const rl = createInterface({ input: process.stdin, output: process.stdout });
11
- return new Promise((resolve, reject) => {
11
+ return new Promise((resolve2, reject) => {
12
12
  rl.question(message, (answer) => {
13
13
  rl.close();
14
14
  const trimmed = answer.trim();
15
15
  const value = trimmed !== "" ? trimmed : defaultStr;
16
- resolve(value);
16
+ resolve2(value);
17
17
  });
18
18
  rl.on("error", (err) => {
19
19
  rl.close();
@@ -32,7 +32,7 @@ var envKeyMap = {
32
32
  SCENV_IGNORE_ENV: "ignoreEnv",
33
33
  SCENV_IGNORE_CONTEXT: "ignoreContext",
34
34
  SCENV_SAVE_CONTEXT_TO: "saveContextTo",
35
- SCENV_CONTEXT_DIR: "contextDir",
35
+ SCENV_SAVE_MODE: "saveMode",
36
36
  SCENV_LOG_LEVEL: "logLevel"
37
37
  };
38
38
  var programmaticConfig = {};
@@ -68,8 +68,10 @@ function configFromEnv() {
68
68
  out[configKey] = v;
69
69
  } else if (configKey === "saveContextTo") {
70
70
  out.saveContextTo = val;
71
- } else if (configKey === "contextDir") {
72
- out.contextDir = val;
71
+ } else if (configKey === "saveMode") {
72
+ const v = val.toLowerCase();
73
+ if (v === "all" || v === "prompts-only")
74
+ out[configKey] = v;
73
75
  } else if (configKey === "logLevel") {
74
76
  const v = val.toLowerCase();
75
77
  if (LOG_LEVELS.includes(v)) out.logLevel = v;
@@ -109,8 +111,8 @@ function loadConfigFile(configDir) {
109
111
  out.set = parsed.set;
110
112
  if (typeof parsed.saveContextTo === "string")
111
113
  out.saveContextTo = parsed.saveContextTo;
112
- if (typeof parsed.contextDir === "string") out.contextDir = parsed.contextDir;
113
- else if (typeof parsed.contextsDir === "string") out.contextDir = parsed.contextsDir;
114
+ if (typeof parsed.saveMode === "string" && ["all", "prompts-only"].includes(parsed.saveMode))
115
+ out.saveMode = parsed.saveMode;
114
116
  if (typeof parsed.root === "string") out.root = parsed.root;
115
117
  if (typeof parsed.logLevel === "string" && LOG_LEVELS.includes(parsed.logLevel))
116
118
  out.logLevel = parsed.logLevel;
@@ -198,7 +200,7 @@ var CONFIG_LOG_KEYS = [
198
200
  "ignoreContext",
199
201
  "set",
200
202
  "saveContextTo",
201
- "contextDir",
203
+ "saveMode",
202
204
  "logLevel"
203
205
  ];
204
206
  function configForLog(config) {
@@ -219,7 +221,7 @@ function logConfigLoaded(config) {
219
221
  if (config.ignoreEnv === true) parts.push("ignoreEnv=true");
220
222
  if (config.ignoreContext === true) parts.push("ignoreContext=true");
221
223
  if (config.saveContextTo !== void 0) parts.push("saveContextTo=" + config.saveContextTo);
222
- if (config.contextDir !== void 0) parts.push("contextDir=" + config.contextDir);
224
+ if (config.saveMode !== void 0) parts.push("saveMode=" + config.saveMode);
223
225
  log("info", "config loaded", parts.join(" "));
224
226
  }
225
227
  if (levelNum >= LEVEL_NUM.debug) {
@@ -295,10 +297,23 @@ function discoverContextPaths(dir, found = /* @__PURE__ */ new Map()) {
295
297
  return found;
296
298
  }
297
299
  function getContext(contextName, root) {
298
- const config = loadConfig();
299
- const searchRoot = root ?? config.root ?? process.cwd();
300
- const paths = discoverContextPaths(searchRoot);
301
- const filePath = paths.get(contextName);
300
+ let filePath;
301
+ if (root !== void 0) {
302
+ const paths = discoverContextPaths(root);
303
+ filePath = paths.get(contextName);
304
+ } else {
305
+ const cwd = process.cwd();
306
+ const paths = discoverContextPaths(cwd);
307
+ filePath = paths.get(contextName);
308
+ if (!filePath) {
309
+ const config = loadConfig();
310
+ const projectRoot = config.root ?? cwd;
311
+ if (projectRoot !== cwd) {
312
+ const rootPaths = discoverContextPaths(projectRoot);
313
+ filePath = rootPaths.get(contextName);
314
+ }
315
+ }
316
+ }
302
317
  if (!filePath) {
303
318
  log("trace", `getContext: context "${contextName}" not found`);
304
319
  return {};
@@ -323,17 +338,9 @@ function getMergedContextValues() {
323
338
  const config = loadConfig();
324
339
  logConfigLoaded(config);
325
340
  if (config.ignoreContext) return {};
326
- const root = config.root ?? process.cwd();
327
- const paths = discoverContextPaths(root);
341
+ const searchRoot = process.cwd();
342
+ const paths = discoverContextPaths(searchRoot);
328
343
  const out = {};
329
- if (config.saveContextTo) {
330
- const savePath = resolveSaveContextPath(config.saveContextTo);
331
- const saveCtx = getContextAtPath(savePath);
332
- for (const [k, v] of Object.entries(saveCtx)) out[k] = v;
333
- if (Object.keys(saveCtx).length > 0) {
334
- log("debug", `saveContextTo "${config.saveContextTo}" loaded keys=${JSON.stringify(Object.keys(saveCtx))}`);
335
- }
336
- }
337
344
  for (const contextName of config.context ?? []) {
338
345
  const filePath = paths.get(contextName);
339
346
  if (!filePath) {
@@ -365,32 +372,11 @@ function getContextWritePath(contextName) {
365
372
  return contextName.endsWith(CONTEXT_SUFFIX) ? contextName : contextName + CONTEXT_SUFFIX;
366
373
  }
367
374
  const config = loadConfig();
368
- const root = config.root ?? process.cwd();
369
- const paths = discoverContextPaths(root);
375
+ const paths = discoverContextPaths(process.cwd());
370
376
  const existing = paths.get(contextName);
371
377
  if (existing) return existing;
372
- const saveDir = config.contextDir ? isAbsolute(config.contextDir) ? config.contextDir : join2(root, config.contextDir) : root;
373
- return join2(saveDir, `${contextName}${CONTEXT_SUFFIX}`);
374
- }
375
- function resolveSaveContextPath(nameOrPath) {
376
- if (isAbsolute(nameOrPath) || nameOrPath.includes(sep)) {
377
- return nameOrPath.endsWith(CONTEXT_SUFFIX) ? nameOrPath : nameOrPath + CONTEXT_SUFFIX;
378
- }
379
- return getContextWritePath(nameOrPath);
380
- }
381
- function getContextAtPath(filePath) {
382
- if (!existsSync2(filePath)) return {};
383
- try {
384
- const raw = readFileSync2(filePath, "utf-8");
385
- const data = JSON.parse(raw);
386
- const out = {};
387
- for (const [k, v] of Object.entries(data)) {
388
- if (typeof v === "string") out[k] = v;
389
- }
390
- return out;
391
- } catch {
392
- return {};
393
- }
378
+ const projectRoot = config.root ?? process.cwd();
379
+ return join2(projectRoot, `${contextName}${CONTEXT_SUFFIX}`);
394
380
  }
395
381
  function writeToContext(contextName, key, value) {
396
382
  const path = getContextWritePath(contextName);
@@ -577,10 +563,11 @@ function scenv(name, options = {}) {
577
563
  throw new Error(errMsg);
578
564
  }
579
565
  const final = validated.data;
580
- if (wasPrompted) {
581
- const config = loadConfig();
582
- setInMemoryContext(key, String(final));
583
- if (config.saveContextTo) {
566
+ const config = loadConfig();
567
+ if (wasPrompted) setInMemoryContext(key, String(final));
568
+ if (config.saveContextTo) {
569
+ const saveMode = config.saveMode ?? "all";
570
+ if (saveMode === "all" || wasPrompted) {
584
571
  writeToContext(config.saveContextTo, key, String(final));
585
572
  log("info", `Saved key=${key} to saveContextTo ${config.saveContextTo}`);
586
573
  }
@@ -614,12 +601,15 @@ function scenv(name, options = {}) {
614
601
  }
615
602
 
616
603
  // src/cli-args.ts
604
+ import { resolve } from "path";
617
605
  function parseScenvArgs(argv) {
618
606
  const config = {};
619
607
  let i = 0;
620
608
  while (i < argv.length) {
621
609
  const arg = argv[i];
622
- if (arg === "--context" && argv[i + 1] !== void 0) {
610
+ if (arg === "--root" && argv[i + 1] !== void 0) {
611
+ config.root = resolve(argv[++i]);
612
+ } else if (arg === "--context" && argv[i + 1] !== void 0) {
623
613
  config.context = argv[++i].split(",").map((s) => s.trim()).filter(Boolean);
624
614
  } else if (arg === "--add-context" && argv[i + 1] !== void 0) {
625
615
  config.addContext = argv[++i].split(",").map((s) => s.trim()).filter(Boolean);
@@ -641,8 +631,11 @@ function parseScenvArgs(argv) {
641
631
  }
642
632
  } else if (arg === "--save-context-to" && argv[i + 1] !== void 0) {
643
633
  config.saveContextTo = argv[++i];
644
- } else if (arg === "--context-dir" && argv[i + 1] !== void 0) {
645
- config.contextDir = argv[++i];
634
+ } else if (arg === "--save-mode" && argv[i + 1] !== void 0) {
635
+ const v = argv[++i].toLowerCase();
636
+ if (v === "all" || v === "prompts-only") {
637
+ config.saveMode = v;
638
+ }
646
639
  } else if ((arg === "--log-level" || arg === "--log") && argv[i + 1] !== void 0) {
647
640
  const v = argv[++i].toLowerCase();
648
641
  if (LOG_LEVELS.includes(v)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scenv",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "Environment and context variables with runtime-configurable resolution",
5
5
  "repository": {
6
6
  "type": "git",